// Type definitions for abstract-leveldown 5.0 // Project: https://github.com/Level/abstract-leveldown // Definitions by: Meirion Hughes // Daniel Byrne // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.3 export interface AbstractOptions { // wtf is this? readonly [k: string]: any; } export type ErrorCallback = (err: Error | undefined) => void; export type ErrorValueCallback = (err: Error | undefined, value: V) => void; export type ErrorKeyValueCallback = (err: Error | undefined, key: K, value: V) => void; export interface AbstractOpenOptions extends AbstractOptions { createIfMissing?: boolean; errorIfExists?: boolean; } export interface AbstractGetOptions extends AbstractOptions { asBuffer?: boolean; } export interface LevelDocument { _id?: any; _documentType?: string; } export interface LevelDocumentConstructor { new(): T; } export interface AbstractLevelDOWN /* extends AbstractOptions */ { open(cb?: ErrorCallback): Promise; open(options: AbstractOpenOptions, cb?: ErrorCallback): Promise; close(cb?: ErrorCallback): void; get(key: K, cb?: ErrorValueCallback): Promise; get(key: K, options: AbstractGetOptions, cb?: ErrorValueCallback): Promise; put(key: K, value: V, cb?: ErrorCallback): Promise; put(key: K, value: V, options: AbstractOptions, cb?: ErrorCallback): Promise; del(key: K, cb?: ErrorCallback): Promise; del(key: K, options: AbstractOptions, cb?: ErrorCallback): Promise; batch(): AbstractChainedBatch; batch(array: ReadonlyArray>, cb?: ErrorCallback): AbstractChainedBatch; batch( array: ReadonlyArray>, options: AbstractOptions, cb?: ErrorCallback, ): AbstractChainedBatch; iterator(options?: AbstractIteratorOptions): AbstractIterator; [Symbol.asyncIterator](): AsyncIterator<{ key: K, value: V }>; nextId(): number; tryGet(documentConstructor: LevelDocumentConstructor, _id: any, options?: AbstractGetOptions): Promise; getAll(documentConstructor: LevelDocumentConstructor, options?: AbstractGetOptions): AsyncIterable; upsert(value: T, options?: AbstractOptions): Promise; remove(value: T): Promise; removeId(documentConstructor: LevelDocumentConstructor, _id: any): Promise; removeAll(documentConstructor: LevelDocumentConstructor): Promise; getCount(documentConstructor: LevelDocumentConstructor, options?: AbstractGetOptions): Promise; } export interface AbstractLevelDOWNConstructor { // tslint:disable-next-line no-unnecessary-generics new (location: string): AbstractLevelDOWN; // tslint:disable-next-line no-unnecessary-generics (location: string): AbstractLevelDOWN; } export interface AbstractIteratorOptions extends AbstractOptions { gt?: K; gte?: K; lt?: K; lte?: K; reverse?: boolean; limit?: number; keys?: boolean; values?: boolean; keyAsBuffer?: boolean; valueAsBuffer?: boolean; } export type AbstractBatch = PutBatch | DelBatch; export interface PutBatch { readonly type: 'put'; readonly key: K; readonly value: V; } export interface DelBatch { readonly type: 'del'; readonly key: K; } export interface AbstractChainedBatch extends AbstractOptions { put: (key: K, value: V) => this; del: (key: K) => this; clear: () => this; write(cb?: ErrorCallback): any; write(options: any, cb?: ErrorCallback): any; } export interface AbstractChainedBatchConstructor { // tslint:disable-next-line no-unnecessary-generics new (db: any): AbstractChainedBatch; // tslint:disable-next-line no-unnecessary-generics (db: any): AbstractChainedBatch; } export interface AbstractIterator extends AbstractOptions { db: AbstractLevelDOWN; next(cb?: ErrorKeyValueCallback): this; end(cb?: ErrorCallback): void; } export interface AbstractIteratorConstructor { // tslint:disable-next-line no-unnecessary-generics new (db: any): AbstractIterator; // tslint:disable-next-line no-unnecessary-generics (db: any): AbstractIterator; } export interface Level extends AbstractLevelDOWN { readonly location: string; readonly prefix: string; readonly version: string | number; destroy(location: string, cb?: (err: Error | undefined) => void): void; destroy(location: string, prefix: string, cb?: (err: Error | undefined) => void): void; } interface LevelOptions { readonly prefix?: string; readonly version?: string | number; } interface LevelConstructor { new(location: string, options?: LevelOptions): Level; (location: string, options?: LevelOptions): Level; } declare const Level: LevelConstructor; const level = require('level') as LevelConstructor; function createLevelDocument(documentConstructor: any, json: any) { const doc = new documentConstructor(); Object.assign(doc, JSON.parse(json)); return doc; } const wrapped = (location: string, options?: LevelOptions) => { const ret = level(location, options); ret.tryGet = async (documentConstructor: any, _id: any, options?: AbstractGetOptions): Promise => { try { const _documentType = documentConstructor.name; const key = `${_documentType}/${_id}`; const json = await ret.get(key, options); return createLevelDocument(documentConstructor, json); } catch (e) { } } const iterable = { async*[Symbol.asyncIterator]() { const iterator = ret.iterator(); try { while (true) { const { key, value } = await new Promise<{ key: any, value: any }>((resolve, reject) => { iterator.next((err, key, value) => { if (err) { reject(err); } else { resolve({ key, value }); } }) }); if (key == null && value == null) break; yield { key, value, } } } finally { await new Promise(resolve => iterator.end(resolve)); } } }; ret[Symbol.asyncIterator] = iterable[Symbol.asyncIterator] as any; ret.getAll = (documentConstructor: any, options?: AbstractGetOptions): AsyncIterable => { const _documentType = documentConstructor.name; const prefix = `${_documentType}/`; return { async*[Symbol.asyncIterator]() { for await (const entry of ret) { if (entry.key.startsWith(prefix)) { const doc = createLevelDocument(documentConstructor, entry.value); if (doc._documentType === _documentType) { yield doc; } } } } } } ret.getCount = async (documentConstructor: any, options?: AbstractGetOptions): Promise => { let count = 0; for await (const doc of ret.getAll(documentConstructor)) { count++; } return count; } let curId: number; const oldOpen = ret.open.bind(ret); (ret as any).open = async (...args: any) => { try { curId = parseInt(await ret.get('_id')); } catch (e) { } if (!curId) curId = 0; return oldOpen(...args); } ret.nextId = () => { if (typeof curId !== 'number') throw new Error('curId is not a number'); return ++curId; } const saveId = async () => { return ret.put("_id", curId); } ret.upsert = async (value: LevelDocument, options?: AbstractOptions): Promise => { const _documentType = value.constructor.name; if (!value._id) value._id = ret.nextId(); await saveId(); value._documentType = _documentType; const key = `${_documentType}/${value._id}`; await ret.put(key, JSON.stringify(value), options); return value; }; ret.remove = async (value: LevelDocument) => { const _documentType = value.constructor.name; let { _id } = value; const key = `${_documentType}/${_id}`; await ret.del(key); } ret.removeId = async (documentConstructor: LevelDocumentConstructor, _id: any) => { const _documentType = documentConstructor.name; const key = `${_documentType}/${_id}`; await ret.del(key); } ret.removeAll = async (documentConstructor: LevelDocumentConstructor) => { const _documentType = documentConstructor.name; const prefix = `${_documentType}/`; for await (const entry of ret) { if (entry.key.startsWith(prefix)) { const doc = createLevelDocument(documentConstructor, entry.value); if (doc._documentType === _documentType) { await ret.del(entry.key); } } } } return ret; }; export default wrapped as LevelConstructor;