Skip to content
This repository has been archived by the owner on Jul 1, 2023. It is now read-only.

Commit

Permalink
feat: add query function (#80)
Browse files Browse the repository at this point in the history
* feat: add query function
  • Loading branch information
imotai committed Jun 16, 2023
1 parent d99b550 commit f866a9a
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 122 deletions.
1 change: 0 additions & 1 deletion src/client/base.ts
Expand Up @@ -22,6 +22,5 @@ export interface DocumentData {
export interface DocumentEntry<T> {
id: string
owner: string
tx: string
doc: T
}
3 changes: 3 additions & 0 deletions src/client/client_v2.ts
Expand Up @@ -29,6 +29,7 @@ import type { DB3Account } from '../account/types'
import type { Client } from './types'
import { Index } from '../proto/db3_database_v2'
import { StorageProviderV2 } from '../provider/storage_provider_v2'
import { IndexerProvider } from '../provider/indexer_provider'
import { fromHEX } from '../crypto/crypto_utils'
import { BSON } from 'db3-bson'

Expand All @@ -54,8 +55,10 @@ export function createClient(
account: DB3Account
) {
const provider = new StorageProviderV2(rollup_node_url, account)
const indexer = new IndexerProvider(index_node_url)
return {
provider,
indexer,
account,
nonce: 0,
} as Client
Expand Down
2 changes: 2 additions & 0 deletions src/client/types.ts
Expand Up @@ -18,9 +18,11 @@
import type { DB3Account } from '../account/types'

import { StorageProviderV2 } from '../provider/storage_provider_v2'
import { IndexerProvider } from '../provider/indexer_provider'

export type Client = {
provider: StorageProviderV2
indexer: IndexerProvider
account: DB3Account
nonce: number
}
23 changes: 1 addition & 22 deletions src/index.ts
Expand Up @@ -14,34 +14,13 @@
* limitations under the License.
*/

//export { Account } from './proto/db3_account'
//export { Index, Database } from './proto/db3_database'
//export { EventMessage, EventType } from './proto/db3_event'
//export { DB3BrowserWallet } from './wallet/db3_browser_wallet'
//export { MetamaskWallet } from './wallet/metamask'
//export { DB3Client } from './client/client'
//export { initializeDB3, listMyDatabases, listDatabases } from './store/app'
////export { DB3Store } from './store/database'
//export { collection, CollectionReference } from './store/collection'
//export {
// addDoc,
// getDocs,
// deleteDoc,
// updateDoc,
// DocumentReference,
//} from './store/document'
//export { limit, where, query } from './store/query'
//export { DB3Network } from './store/network'

//===================v2======================

export {
createFromPrivateKey,
createRandomAccount,
signTypedData,
} from './account/db3_account'

export { addDoc, updateDoc, deleteDoc } from './store/document_v2'
export { addDoc, updateDoc, deleteDoc, queryDoc } from './store/document_v2'

export {
createClient,
Expand Down
49 changes: 49 additions & 0 deletions src/provider/indexer_provider.ts
@@ -0,0 +1,49 @@
//
// indexer_provider.ts
// Copyright (C) 2023 db3.network Author imotai <codego.me@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
GrpcWebFetchTransport,
GrpcWebOptions,
} from '@protobuf-ts/grpcweb-transport'
import { IndexerNodeClient } from '../proto/db3_indexer.client'
import { RunQueryRequest } from '../proto/db3_indexer'
import { Query } from '../proto/db3_database_v2'

export class IndexerProvider {
readonly client: IndexerNodeClient
constructor(url: string) {
const goptions: GrpcWebOptions = {
baseUrl: url,
// simple example for how to add auth headers to each request
// see `RpcInterceptor` for documentation
interceptors: [],
// you can set global request headers here
meta: {},
}
const transport = new GrpcWebFetchTransport(goptions)
this.client = new IndexerNodeClient(transport)
}

async runQuery(db: string, colName: string, query: Query) {
const request: RunQueryRequest = {
db,
colName,
query,
}
const { response } = await this.client.runQuery(request)
return response
}
}
22 changes: 0 additions & 22 deletions src/store/database.ts
Expand Up @@ -31,31 +31,9 @@ export class DB3Store {
}

async getCollections(name: string) {
if (this._database && this._collections && this._collections[name]) {
return this._collections[name]
}
const database = await this.client.getDatabase(this.address)
if (database === undefined) {
return undefined
}
this._database = database
this._collections = Object.fromEntries(
database!.collections.map((e) => [e.name, e])
)
if (this._collections && this._collections[name]) {
return this._collections[name]
}
return undefined
}

async getDatabase() {
if (!this._database) {
const database = await this.client.getDatabase(this.address)
this._database = database
this._collections = Object.fromEntries(
database!.collections.map((e) => [e.name, e])
)
}
return this._database
}
}
58 changes: 57 additions & 1 deletion src/store/document_v2.ts
Expand Up @@ -26,7 +26,62 @@ import { BSON } from 'db3-bson'

import { fromHEX } from '../crypto/crypto_utils'

import type { Collection, DocumentData } from './types'
import type { Collection, QueryResult } from './types'
import type { Query, QueryParameter } from '../proto/db3_database_v2'
import type { DocumentData, DocumentEntry } from '../client/base'

async function runQueryInternal<T>(col: Collection, query: Query) {
const response = await col.db.client.indexer.runQuery(
col.db.addr,
col.name,
query
)
const entries = response.documents.map((doc) => {
return {
doc: JSON.parse(doc.doc) as T,
id: doc.id,
owner: doc.owner,
} as DocumentEntry<T>
})
return {
docs: entries,
collection: col,
} as QueryResult<T>
}

/**
*
* Query document with a query language
*
* ```ts
* const queryStr = '/* | limit 1'
* const resultSet = await queryDoc<Profile>(collection, queryStr)
* ```
* @param col - the instance of collection
* @param queryStr - a document query string
* @param parameters - an optional query parameters
* @returns the {@link Queryresult}
*
**/
export async function queryDoc<T = DocumentData>(
col: Collection,
queryStr: string,
parameters?: QueryParameter[]
) {
if (!parameters) {
const query: Query = {
queryStr,
parameters: [],
}
return runQueryInternal(col, query)
} else {
const query: Query = {
queryStr,
parameters,
}
return runQueryInternal<T>(col, query)
}
}

export async function deleteDoc(col: Collection, ids: string[]) {
const documentMutation: DocumentMutation = {
Expand Down Expand Up @@ -61,6 +116,7 @@ export async function deleteDoc(col: Collection, ids: string[]) {
throw new Error('fail to create collection')
}
}

export async function updateDoc(
col: Collection,
id: string,
Expand Down
6 changes: 4 additions & 2 deletions src/store/types.ts
Expand Up @@ -16,6 +16,7 @@
//

import { Client } from '../client/types'
import { DocumentEntry, DocumentData } from '../client/base'
import {
DatabaseMessage as InternalDatabase,
Index,
Expand Down Expand Up @@ -51,6 +52,7 @@ export type Collection = {
internal: InternalCollection | undefined
}

export interface DocumentData {
[field: string]: any
export type QueryResult<T = DocumentData> = {
docs: Array<DocumentEntry<T>>
collection: Collection
}
60 changes: 1 addition & 59 deletions tests/client.test.ts
Expand Up @@ -36,65 +36,7 @@ describe('test db3.js client module', () => {
wallet
)
const [dbId, txId] = await client.createDatabase()
await new Promise((r) => setTimeout(r, 2000))
const db = await client.getDatabase(dbId)
expect(dbId).toEqual(`0x${toHEX(db!.address)}`)
const dbs = await client.listDatabases(wallet.getAddress())
console.log(`dbs length ${dbs.length}`)
expect(dbs.length == 1).toEqual(true)
})

test('create database smoke test', async () => {
const mnemonic =
'result crisp session latin must fruit genuine question prevent start coconut brave speak student dismiss'
const wallet = DB3BrowserWallet.createNew(mnemonic, 'DB3_SECP256K1')
const client = new DB3Client(
'http://127.0.0.1:26659',
'http://127.0.0.1:26639',
wallet
)
const [dbId, txId] = await client.createDatabase()
await new Promise((r) => setTimeout(r, 2000))
try {
const db = await client.getDatabase(dbId)
expect(dbId).toEqual(`0x${toHEX(db!.address)}`)
const dbs = await client.listDatabases(wallet.getAddress())
expect(dbs.length > 0).toEqual(true)
const indexList: Index[] = [
{
name: 'idx1',
id: 1,
fields: [
{
fieldPath: 'name',
valueMode: {
oneofKind: 'order',
order: Index_IndexField_Order.ASCENDING,
},
},
],
},
]
await client.createCollection(dbId, 'books', indexList)
await new Promise((r) => setTimeout(r, 2000))
const collections = await client.listCollection(dbId)
expect(collections.length).toEqual(1)
await client.createDocument(dbId, 'books', {
name: 'book1',
author: 'db3 developers',
})
await new Promise((r) => setTimeout(r, 2000))
const query: StructuredQuery = {
collectionName: 'books',
}
const books = await client.runQuery(dbId, query)
expect(books.length).toBe(1)
expect(books[0].doc['name']).toBe('book1')
const bookId = books[0].id
const result = await client.deleteDocument(dbId, 'books', [bookId])
expect(result).toBeDefined()
} catch (e) {
console.log(e)
}
})

})

0 comments on commit f866a9a

Please sign in to comment.