Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ddo crud #99

Merged
merged 20 commits into from
Nov 15, 2023
15 changes: 14 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
an7e marked this conversation as resolved.
Show resolved Hide resolved
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --detectOpenHandles",
"test": "jest --runInBand",
"build": "npm run clean && npm run build:tsc",
"build:swagger": "tsoa spec",
"build:tsc": "tsc --sourceMap",
Expand Down
6 changes: 2 additions & 4 deletions src/@types/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import { OceanIndexer } from '../components/Indexer/index'
import type { PeerId } from '@libp2p/interface/peer-id'
import { Stream } from 'stream'
import { TypesenseConfigOptions } from './Typesense'

Check warning on line 6 in src/@types/OceanNode.ts

View workflow job for this annotation

GitHub Actions / lint

'TypesenseConfigOptions' is defined but never used
import { Blockchain } from '../utils/blockchain'

export interface OceanNodeDBConfig {
host: string
user: string
pwd: string
dbname: string
url: string
}

export interface OceanNodeKeys {
Expand Down
6 changes: 2 additions & 4 deletions src/@types/Typesense.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Logger } from 'winston'

export interface TypesenseAbstractLogger {
error?: any
warn?: any
Expand All @@ -10,7 +8,7 @@ export interface TypesenseAbstractLogger {

export interface TypesenseNode {
host: string
port: number
port: string
protocol: string
}

Expand All @@ -21,7 +19,7 @@ export interface TypesenseConfigOptions {
retryIntervalSeconds?: number
connectionTimeoutSeconds?: number
logLevel?: string
logger?: Logger
logger?: TypesenseAbstractLogger
}

export type TypesenseFieldType =
Expand Down
223 changes: 222 additions & 1 deletion src/components/database/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,226 @@
import { OceanNodeDBConfig } from '../../@types/OceanNode'
import { convertTypesenseConfig, Typesense, TypesenseError } from './typesense.js'
import { Schema, schemas } from './schemas.js'

export class DdoDatabase {
private provider: Typesense

constructor(
private config: OceanNodeDBConfig,
private schemas: Schema[]
) {
return (async (): Promise<DdoDatabase> => {
this.provider = new Typesense(convertTypesenseConfig(this.config.url))
for (const ddoSchema of this.schemas) {
try {
await this.provider.collections(ddoSchema.name).retrieve()
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
await this.provider.collections().create(ddoSchema)
}
}
}
return this
})() as unknown as DdoDatabase
}

async create(ddo: Record<string, any>) {
try {
return await this.provider.collections(this.schemas[0].name).documents().create(ddo)
} catch (error) {
return null
}
}

async retrieve(id: string) {
try {
return await this.provider
.collections(this.schemas[0].name)
.documents()
.retrieve(id)
} catch (error) {
return null
}
}

async update(id: string, fields: Record<string, any>) {
try {
return await this.provider
.collections(this.schemas[0].name)
.documents()
.update(id, fields)
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
return await this.provider
.collections(this.schemas[0].name)
.documents()
.create({ id, ...fields })
}
return null
}
}

async delete(id: string) {
try {
return await this.provider.collections(this.schemas[0].name).documents().delete(id)
} catch (error) {
return null
}
}
}

export class NonceDatabase {
private provider: Typesense

constructor(
private config: OceanNodeDBConfig,
private schema: Schema
) {
return (async (): Promise<NonceDatabase> => {
this.provider = new Typesense(convertTypesenseConfig(this.config.url))
try {
await this.provider.collections(this.schema.name).retrieve()
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
await this.provider.collections().create(this.schema)
}
}
return this
})() as unknown as NonceDatabase
}

async create(address: string, nonce: number) {
try {
return await this.provider
.collections(this.schema.name)
.documents()
.create({ id: address, nonce })
} catch (error) {
return null
}
}

async retrieve(address: string) {
try {
return await this.provider
.collections(this.schema.name)
.documents()
.retrieve(address)
} catch (error) {
return null
}
}

async update(address: string, nonce: number) {
try {
return await this.provider
.collections(this.schema.name)
.documents()
.update(address, { nonce })
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
return await this.provider
.collections(this.schema.name)
.documents()
.create({ id: address, nonce })
}
return null
}
}

async delete(address: string) {
try {
return await this.provider.collections(this.schema.name).documents().delete(address)
} catch (error) {
return null
}
}
}

export class IndexerDatabase {
private provider: Typesense

constructor(
private config: OceanNodeDBConfig,
private schema: Schema
) {
return (async (): Promise<IndexerDatabase> => {
this.provider = new Typesense(convertTypesenseConfig(this.config.url))
try {
await this.provider.collections(this.schema.name).retrieve()
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
await this.provider.collections().create(this.schema)
}
}
return this
})() as unknown as IndexerDatabase
}

async create(id: string, fields: Record<string, any>) {
try {
return await this.provider
.collections(this.schema.name)
.documents()
.create({ id, ...fields })
} catch (error) {
return null
}
}

async retrieve(id: string) {
try {
return await this.provider.collections(this.schema.name).documents().retrieve(id)
} catch (error) {
return null
}
}

async update(id: string, fields: Record<string, any>) {
try {
return await this.provider
.collections(this.schema.name)
.documents()
.update(id, fields)
} catch (error) {
if (error instanceof TypesenseError && error.httpStatus === 404) {
return await this.provider
.collections(this.schema.name)
.documents()
.create({ id, ...fields })
}
return null
}
}

async delete(id: string) {
try {
return await this.provider.collections(this.schema.name).documents().delete(id)
} catch (error) {
return null
}
}
}

export class Database {
constructor(config: OceanNodeDBConfig) {}
ddo: DdoDatabase
nonce: NonceDatabase
indexer: IndexerDatabase

constructor(private config: OceanNodeDBConfig) {
return (async (): Promise<Database> => {
this.ddo = await new DdoDatabase(config, schemas.ddoSchemas)
this.nonce = await new NonceDatabase(config, schemas.nonceSchemas)
this.indexer = await new IndexerDatabase(config, schemas.indexerSchemas)
return this
})() as unknown as Database
}
}

// Example
//
// db.nonce.create('0x123', 1234567) return -> { id:'0x123', nonce:1234567 } or null or throw error
// db.nonce.update('0x123', 1234568) return -> { id:'0x123', nonce:1234568 } or null or throw error
// db.nonce.retrieve('0x123') return -> 1234568 or throw error
//
// db.indexer.create('Network_A', { last_indexed_block: 1234567 }) return -> { id:'Network_A', last_indexed_block:1234567 } or null or throw error
27 changes: 27 additions & 0 deletions src/components/database/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TypesenseCollectionCreateSchema } from '../../@types'

export type Schema = TypesenseCollectionCreateSchema
export type Schemas = {
ddoSchemas: Schema[]
nonceSchemas: Schema
indexerSchemas: Schema
}
export const schemas: Schemas = {
ddoSchemas: [
{
name: 'ddo_v0.1',
enable_nested_fields: true,
fields: [{ name: '.*', type: 'auto' }]
}
],
nonceSchemas: {
name: 'nonce',
enable_nested_fields: true,
fields: [{ name: 'nonce', type: 'int64' }]
},
indexerSchemas: {
name: 'indexer',
enable_nested_fields: true,
fields: [{ name: '.*', type: 'auto' }]
}
}
29 changes: 26 additions & 3 deletions src/components/database/typesense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,31 @@ import {
TypesenseDocumentSchema,
TypesenseSearchResponse
} from '../../@types'
import { TypesenseApi } from './typesenseApi'
import { TypesenseConfig } from './typesenseConfig'
import { TypesenseApi } from './typesenseApi.js'
import { TypesenseConfig } from './typesenseConfig.js'

export const convertTypesenseConfig = (url: string) => {
const urlObject = new URL(url)
const { protocol } = urlObject
const host = urlObject.hostname
const { port } = urlObject
const apiKey = urlObject.searchParams.get('apiKey')
const config: TypesenseConfigOptions = {
apiKey,
nodes: [{ host, port, protocol }]
}
return config
}

export class TypesenseError extends Error {
httpStatus?: number

constructor(message?: string) {
super(message)
this.name = new.target.name
Object.setPrototypeOf(this, new.target.prototype)
}
}

/**
* TypesenseDocuments class implements CRUD methods
Expand Down Expand Up @@ -120,7 +143,7 @@ export class TypesenseCollections {
* It initiates classes that provides access to methods of collections
* or an individual collection
*/
export default class Typesense {
export class Typesense {
config: TypesenseConfig
api: TypesenseApi
collectionsRecords: Record<string, TypesenseCollection> = {}
Expand Down
Loading
Loading