import { encode, decode } from '@borderless/base64'; import { registerPlugin } from '@capacitor/core'; import { AbstractIterator, AbstractLevel } from 'abstract-level'; import type { AbstractDatabaseOptions, AbstractIteratorOptions, NodeCallback, } from 'abstract-level'; import type { MobileLevelPluginT } from './definitions'; const MobileLevelPlugin = registerPlugin<MobileLevelPluginT>('MobileLevel', {}); export * from './definitions'; class DummyIterator extends AbstractIterator< MobileLevel, Uint8Array, Uint8Array > { private readonly options: AbstractIteratorOptions<Uint8Array, Uint8Array>; readonly location: string; constructor( db: MobileLevel, location: string, options: AbstractIteratorOptions<Uint8Array, Uint8Array>, ) { super(db, options); this.options = options; this.location = location; } // Note: if called by _all() then size can be Infinity. This is an internal // detail; by design AbstractIterator.nextv() does not support Infinity. _nextv() { return; } _next() { return; } _all(options: unknown, callback: NodeCallback<[Uint8Array, Uint8Array][]>) { if (options) throw new Error('options not supported, feel free to submit a PR'); const { gt, gte, lt, lte, ...restOptions } = this.options; const encodedOptions = { gt: gt ? encode(gt) : undefined, gte: gte ? encode(gte) : undefined, lt: lt ? encode(lt) : undefined, lte: lte ? encode(lte) : undefined, ...restOptions, }; MobileLevelPlugin.Iterator(encodedOptions).then(({ results }) => { const decodedResults = results.map( ([key, value]): [Uint8Array, Uint8Array] => [ decode(key), decode(value), ], ); callback(null, decodedResults); }); } _seek() { return; } } export class MobileLevel extends AbstractLevel< Uint8Array, Uint8Array, Uint8Array > { public readonly name: string; constructor( name: string, options: AbstractDatabaseOptions<Uint8Array, Uint8Array> | undefined, ) { super( { encodings: { view: true }, // TODO: It technically supports snapshots, I think? snapshots: false, }, options, ); this.name = name; } async _open(): Promise<void> { await MobileLevelPlugin.Open({ path: this.name, }); } async _get(key: Uint8Array): Promise<Uint8Array | null> { const encodedKey = encode(key); const { value } = await MobileLevelPlugin.Get({ key: encodedKey }); if (value === null) return null; return decode(value); } async _put(key: Uint8Array, value: Uint8Array): Promise<void> { const encodedKey = encode(key); const encodedValue = encode(value); await MobileLevelPlugin.Put({ key: encodedKey, value: encodedValue }); } async _delete(key: Uint8Array): Promise<void> { const encodedKey = encode(key); await MobileLevelPlugin.Delete({ key: encodedKey }); } async _close(): Promise<void> { await MobileLevelPlugin.Close(); } async _batch( operations: { type: 'put' | 'delete'; key: Uint8Array; value?: Uint8Array; }[], ): Promise<void> { const encodedOperations = operations.map(({ type, key, value }) => ({ type, key: encode(key), value: value ? encode(value) : undefined, })); await MobileLevelPlugin.Batch({ operations: encodedOperations }); } _iterator(options: { gt?: Uint8Array; gte?: Uint8Array; lt?: Uint8Array; lte?: Uint8Array; limit?: number; reverse?: boolean; }): DummyIterator { return new DummyIterator(this, this.name, options); } async _clear(): Promise<void> { await MobileLevelPlugin.Clear(); } }