import { AutoReconnectConnection } from '../../connection'; import type { BlobRecord, CrawlResult, DocClock, DocIndexedClock, DocRecord, ListedBlobRecord, } from '../../storage'; import { type SpaceType, universalId } from '../../utils/universal-id'; export interface SqliteNativeDBOptions { readonly flavour: string; readonly type: SpaceType; readonly id: string; } export interface NativeIndexField { field: string; values: string[]; } export interface NativeIndexQuery { kind: 'match' | 'exists' | 'all' | 'boolean' | 'boost'; field?: string; value?: string; occur?: 'must' | 'should' | 'must_not'; clauses?: NativeIndexQuery[]; boost?: number; } export interface NativeIndexSearchOptions { limit: number; offset: number; fields: string[]; highlights: string[]; } export interface NativeIndexHit { id: string; score: number; fields: NativeIndexField[]; highlights: { field: string; values: { valueIndex: number; spans: { start: number; end: number }[] }[]; }[]; } export interface NativeIndexSearchResult { total: number; hits: NativeIndexHit[]; } export interface NativeDBApis { connect: (id: string) => Promise; disconnect: (id: string) => Promise; pushUpdate: (id: string, docId: string, update: Uint8Array) => Promise; getDocSnapshot: (id: string, docId: string) => Promise; setDocSnapshot: (id: string, snapshot: DocRecord) => Promise; getDocUpdates: (id: string, docId: string) => Promise; markUpdatesMerged: ( id: string, docId: string, updates: Date[] ) => Promise; deleteDoc: (id: string, docId: string) => Promise; getDocClocks: (id: string, after?: Date | null) => Promise; getDocClock: (id: string, docId: string) => Promise; getDocIndexedClock: ( id: string, docId: string ) => Promise; setDocIndexedClock: ( id: string, docId: string, indexedClock: Date, indexerVersion: number ) => Promise; setDocIndexedClocks: (id: string, clocks: DocIndexedClock[]) => Promise; clearDocIndexedClock: (id: string, docId: string) => Promise; getBlob: (id: string, key: string) => Promise; setBlob: (id: string, blob: BlobRecord) => Promise; deleteBlob: (id: string, key: string, permanently: boolean) => Promise; releaseBlobs: (id: string) => Promise; listBlobs: (id: string) => Promise; getPeerRemoteClocks: (id: string, peer: string) => Promise; getPeerRemoteClock: ( id: string, peer: string, docId: string ) => Promise; setPeerRemoteClock: ( id: string, peer: string, docId: string, clock: Date ) => Promise; getPeerPulledRemoteClocks: (id: string, peer: string) => Promise; getPeerPulledRemoteClock: ( id: string, peer: string, docId: string ) => Promise; setPeerPulledRemoteClock: ( id: string, peer: string, docId: string, clock: Date ) => Promise; getPeerPushedClocks: (id: string, peer: string) => Promise; getPeerPushedClock: ( id: string, peer: string, docId: string ) => Promise; setPeerPushedClock: ( id: string, peer: string, docId: string, clock: Date ) => Promise; clearClocks: (id: string) => Promise; setBlobUploadedAt: ( id: string, peer: string, blobId: string, uploadedAt: Date | null ) => Promise; getBlobUploadedAt: ( id: string, peer: string, blobId: string ) => Promise; crawlDocData: (id: string, docId: string) => Promise; indexUpsert: ( id: string, table: string, document: { id: string; fields: NativeIndexField[] } ) => Promise; indexDelete: (id: string, table: string, docId: string) => Promise; indexSearch: ( id: string, table: string, query: NativeIndexQuery, options: NativeIndexSearchOptions ) => Promise; indexAggregate: ( id: string, table: string, query: NativeIndexQuery, field: string, limit: number, offset: number, hits?: NativeIndexSearchOptions ) => Promise<{ total: number; buckets: { key: string; count: number; score: number; hits: NativeIndexHit[]; }[]; }>; indexDeleteByQuery: ( id: string, table: string, query: NativeIndexQuery ) => Promise; indexFlush: (id: string) => Promise; indexVersion: () => Promise; } type NativeDBApisWrapper = NativeDBApis extends infer APIs ? { [K in keyof APIs]: APIs[K] extends (...args: any[]) => any ? Parameters extends [string, ...infer Rest] ? (...args: Rest) => ReturnType : (...args: Parameters) => ReturnType : never; } : never; let apis: NativeDBApis | null = null; export function bindNativeDBApis(a: NativeDBApis) { apis = a; } export class NativeDBConnection extends AutoReconnectConnection { readonly apis: NativeDBApisWrapper; readonly flavour = this.options.flavour; readonly type = this.options.type; readonly id = this.options.id; constructor(private readonly options: SqliteNativeDBOptions) { super(); if (!apis) { throw new Error('Not in native context.'); } this.apis = this.warpApis(apis); } override get shareId(): string { return `sqlite:${this.flavour}:${this.type}:${this.id}`; } warpApis(originalApis: NativeDBApis): NativeDBApisWrapper { const id = universalId({ peer: this.flavour, type: this.type, id: this.id, }); return new Proxy( {}, { get: (_target, key: keyof NativeDBApisWrapper) => { const v = originalApis[key]; return async (...args: any[]) => { return v.call( originalApis, id, // @ts-expect-error I don't know why it complains ts(2556) ...args ); }; }, } ) as unknown as NativeDBApisWrapper; } override async doConnect() { await this.apis.connect(); } override doDisconnect() { this.apis.disconnect().catch(err => { console.error('NativeDBConnection close failed', err); }); } }