UNPKG

harperdb

Version:

HarperDB is a distributed database, caching service, streaming broker, and application development platform focused on performance and ease of use.

705 lines (704 loc) 31 kB
/** * This module provides the main table implementation of the Resource API, providing full access to HarperDB * tables through the interface defined by the Resource class. This module is responsible for handling these * table-level interactions, loading records, updating records, querying, and more. */ import { Database } from 'lmdb'; import type { Query, ResourceInterface, SubscriptionRequest, Id, Context, Sort, SubSelect } from './ResourceInterface'; import { Resource } from './Resource'; type Attribute = { name: string; type: string; assignCreatedTime?: boolean; assignUpdatedTime?: boolean; expiresAt?: boolean; isPrimaryKey?: boolean; }; type Entry = { key: any; value: any; version: number; localTime: number; expiresAt: number; deref?: () => any; }; export declare const INVALIDATED = 1; export declare const EVICTED = 8; export interface Table { primaryStore: Database; auditStore: Database; indices: {}; databasePath: string; tableName: string; databaseName: string; attributes: any[]; primaryKey: string; splitSegments?: boolean; replicate?: boolean; subscriptions: Map<any, Function[]>; expirationMS: number; indexingOperations?: Promise<void>; sources: (new () => ResourceInterface)[]; Transaction: ReturnType<typeof makeTable>; } type ResidencyDefinition = number | string[] | void; /** * This returns a Table class for the given table settings (determined from the metadata table) * Instances of the returned class are Resource instances, intended to provide a consistent view or transaction of the table * @param options */ export declare function makeTable(options: any): { new (identifier: Id, source: any): { #record: any; #changes: any; #version: number; #entry: Entry; #saveMode: boolean; #loadedFromSource: boolean; /** * This is a request to explicitly ensure that the record is loaded from source, rather than only using the local record. * This will load from source if the current record is expired, missing, or invalidated. * @returns */ ensureLoaded(): any; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. * @param query - If included, specifies a query to perform on the record */ get(query?: Query | string): Promise<object | void> | object | void; /** * Determine if the user is allowed to get/read data from the current resource * @param user The current, authenticated user * @param query The parsed query from the search part of the URL */ allowRead(user: any, query: any): any; /** * Determine if the user is allowed to update data from the current resource * @param user The current, authenticated user * @param updated_data * @param full_update */ allowUpdate(user: any, updated_data: any): boolean; /** * Determine if the user is allowed to create new data in the current resource * @param user The current, authenticated user * @param new_data */ allowCreate(user: any, new_data: {}): boolean; /** * Determine if the user is allowed to delete from the current resource * @param user The current, authenticated user */ allowDelete(user: any): boolean; /** * Start updating a record. The returned resource will record changes which are written * once the corresponding transaction is committed. These changes can (eventually) include CRDT type operations. * @param updates This can be a record to update the current resource with. * @param full_update The provided data in updates is the full intended record; any properties in the existing record that are not in the updates, should be removed */ update(updates?: any, full_update?: boolean): /*elided*/ any; addTo(property: any, value: any): void; subtractFrom(property: any, value: any): void; getMetadata(): Entry; getRecord(): any; getChanges(): any; _setChanges(changes: any): void; setRecord(record: any): void; invalidate(): void; _writeInvalidate(partial_record?: any, options?: any): void; _writeRelocate(options: any): void; /** * This is intended to acquire a lock on a record from the whole cluster. */ lock(): void; /** * Store the provided record data into the current resource. This is not written * until the corresponding transaction is committed. This will either immediately fail (synchronously) or always * succeed. That doesn't necessarily mean it will "win", another concurrent put could come "after" (monotonically, * even if not chronologically) this one. * @param record * @param options */ put(record: any): void; patch(record_update: any): void; _writeUpdate(record_update: any, full_update: boolean, options?: any): void; delete(request?: Query | string): Promise<boolean>; _writeDelete(options?: any): boolean; search(request: Query): AsyncIterable<any>; subscribe(request: SubscriptionRequest): Promise<{ listener: (key: any) => any; subscriptions: []; startTime?: number; end(): void; toJSON(): { name: string; }; resolveNext: Function; queue: any[]; hasDataListeners: boolean; drainCloseListener: boolean; currentDrainResolver: Function; push(message: any): void; send(message: any): void; getNextMessage(): any; waitForDrain(): Promise<boolean>; on(event_name: any, listener: any): /*elided*/ any; [Symbol.asyncIterator](): { queue: import("./IterableEventQueue").IterableEventQueue; push(message: any): void; next(): Promise<unknown> | { value: any; }; return(value: any): { value: any; done: boolean; }; throw(error: any): { done: boolean; }; }; [EventEmitter.captureRejectionSymbol]?<K>(error: Error, event: string | symbol, ...args: any[]): void; addListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; once<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; off<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeAllListeners(eventName?: string | symbol): /*elided*/ any; setMaxListeners(n: number): /*elided*/ any; getMaxListeners(): number; listeners<K>(eventName: string | symbol): Function[]; rawListeners<K>(eventName: string | symbol): Function[]; emit<K>(eventName: string | symbol, ...args: any[]): boolean; listenerCount<K>(eventName: string | symbol, listener?: Function): number; prependListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; prependOnceListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; eventNames(): (string | symbol)[]; }>; doesExist(): boolean; /** * Publishing a message to a record adds an (observable) entry in the audit log, but does not change * the record at all. This entries should be replicated and trigger subscription listeners. * @param id * @param message * @param options */ publish(message: any, options?: any): void; _writePublish(message: any, options?: any): void; validate(record: any, patch?: any): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly #id: Id; readonly #context: Context; #isCollection: boolean; post(new_record: any): Promise<any>; get isCollection(): boolean; connect(incomingMessages: import("./IterableEventQueue").IterableEventQueue, query?: {}): AsyncIterable<any>; getId(): Id; getContext(): Context; }; name: any; primaryStore: any; auditStore: any; primaryKey: any; tableName: any; tableId: any; indices: any; audit: any; databasePath: any; databaseName: any; attributes: any; replicate: any; sealed: any; splitSegments: any; createdTimeProperty: Attribute; updatedTimeProperty: Attribute; propertyResolvers: any; userResolvers: {}; sources: any[]; getResidencyById: (id: Id) => number | void; get expirationMS(): any; dbisDB: any; schemaDefined: any; /** * This defines a source for a table. This effectively makes a table into a cache, where the canonical * source of data (or source of truth) is provided here in the Resource argument. Additional options * can be provided to indicate how the caching should be handled. * @param source * @param options * @returns */ sourcedFrom(source: any, options: any): /*elided*/ any; get isCaching(): any; /** Indicates if the events should be revalidated when they are received. By default we do this if the get * method is overriden */ get shouldRevalidateEvents(): boolean; /** * Gets a resource instance, as defined by the Resource class, adding the table-specific handling * of also loading the stored record into the resource instance. * @param id * @param request * @param options An important option is ensureLoaded, which can be used to indicate that it is necessary for a caching table to load data from the source if there is not a local copy of the data in the table (usually not necessary for a delete, for example). * @returns */ getResource(id: Id, request: Context, resource_options?: any): Promise<{ #record: any; #changes: any; #version: number; #entry: Entry; #saveMode: boolean; #loadedFromSource: boolean; /** * This is a request to explicitly ensure that the record is loaded from source, rather than only using the local record. * This will load from source if the current record is expired, missing, or invalidated. * @returns */ ensureLoaded(): any; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. * @param query - If included, specifies a query to perform on the record */ get(query?: Query | string): Promise<object | void> | object | void; /** * Determine if the user is allowed to get/read data from the current resource * @param user The current, authenticated user * @param query The parsed query from the search part of the URL */ allowRead(user: any, query: any): any; /** * Determine if the user is allowed to update data from the current resource * @param user The current, authenticated user * @param updated_data * @param full_update */ allowUpdate(user: any, updated_data: any): boolean; /** * Determine if the user is allowed to create new data in the current resource * @param user The current, authenticated user * @param new_data */ allowCreate(user: any, new_data: {}): boolean; /** * Determine if the user is allowed to delete from the current resource * @param user The current, authenticated user */ allowDelete(user: any): boolean; /** * Start updating a record. The returned resource will record changes which are written * once the corresponding transaction is committed. These changes can (eventually) include CRDT type operations. * @param updates This can be a record to update the current resource with. * @param full_update The provided data in updates is the full intended record; any properties in the existing record that are not in the updates, should be removed */ update(updates?: any, full_update?: boolean): /*elided*/ any; addTo(property: any, value: any): void; subtractFrom(property: any, value: any): void; getMetadata(): Entry; getRecord(): any; getChanges(): any; _setChanges(changes: any): void; setRecord(record: any): void; invalidate(): void; _writeInvalidate(partial_record?: any, options?: any): void; _writeRelocate(options: any): void; /** * This is intended to acquire a lock on a record from the whole cluster. */ lock(): void; /** * Store the provided record data into the current resource. This is not written * until the corresponding transaction is committed. This will either immediately fail (synchronously) or always * succeed. That doesn't necessarily mean it will "win", another concurrent put could come "after" (monotonically, * even if not chronologically) this one. * @param record * @param options */ put(record: any): void; patch(record_update: any): void; _writeUpdate(record_update: any, full_update: boolean, options?: any): void; delete(request?: Query | string): Promise<boolean>; _writeDelete(options?: any): boolean; search(request: Query): AsyncIterable<any>; subscribe(request: SubscriptionRequest): Promise<{ listener: (key: any) => any; subscriptions: []; startTime?: number; end(): void; toJSON(): { name: string; }; resolveNext: Function; queue: any[]; hasDataListeners: boolean; drainCloseListener: boolean; currentDrainResolver: Function; push(message: any): void; send(message: any): void; getNextMessage(): any; waitForDrain(): Promise<boolean>; on(event_name: any, listener: any): /*elided*/ any; [Symbol.asyncIterator](): { queue: import("./IterableEventQueue").IterableEventQueue; push(message: any): void; next(): Promise<unknown> | { value: any; }; return(value: any): { value: any; done: boolean; }; throw(error: any): { done: boolean; }; }; [EventEmitter.captureRejectionSymbol]?<K>(error: Error, event: string | symbol, ...args: any[]): void; addListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; once<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; off<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeAllListeners(eventName?: string | symbol): /*elided*/ any; setMaxListeners(n: number): /*elided*/ any; getMaxListeners(): number; listeners<K>(eventName: string | symbol): Function[]; rawListeners<K>(eventName: string | symbol): Function[]; emit<K>(eventName: string | symbol, ...args: any[]): boolean; listenerCount<K>(eventName: string | symbol, listener?: Function): number; prependListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; prependOnceListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; eventNames(): (string | symbol)[]; }>; doesExist(): boolean; /** * Publishing a message to a record adds an (observable) entry in the audit log, but does not change * the record at all. This entries should be replicated and trigger subscription listeners. * @param id * @param message * @param options */ publish(message: any, options?: any): void; _writePublish(message: any, options?: any): void; validate(record: any, patch?: any): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly #id: Id; readonly #context: Context; #isCollection: boolean; post(new_record: any): Promise<any>; get isCollection(): boolean; connect(incomingMessages: import("./IterableEventQueue").IterableEventQueue, query?: {}): AsyncIterable<any>; getId(): Id; getContext(): Context; }> | { #record: any; #changes: any; #version: number; #entry: Entry; #saveMode: boolean; #loadedFromSource: boolean; /** * This is a request to explicitly ensure that the record is loaded from source, rather than only using the local record. * This will load from source if the current record is expired, missing, or invalidated. * @returns */ ensureLoaded(): any; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. * @param query - If included, specifies a query to perform on the record */ get(query?: Query | string): Promise<object | void> | object | void; /** * Determine if the user is allowed to get/read data from the current resource * @param user The current, authenticated user * @param query The parsed query from the search part of the URL */ allowRead(user: any, query: any): any; /** * Determine if the user is allowed to update data from the current resource * @param user The current, authenticated user * @param updated_data * @param full_update */ allowUpdate(user: any, updated_data: any): boolean; /** * Determine if the user is allowed to create new data in the current resource * @param user The current, authenticated user * @param new_data */ allowCreate(user: any, new_data: {}): boolean; /** * Determine if the user is allowed to delete from the current resource * @param user The current, authenticated user */ allowDelete(user: any): boolean; /** * Start updating a record. The returned resource will record changes which are written * once the corresponding transaction is committed. These changes can (eventually) include CRDT type operations. * @param updates This can be a record to update the current resource with. * @param full_update The provided data in updates is the full intended record; any properties in the existing record that are not in the updates, should be removed */ update(updates?: any, full_update?: boolean): /*elided*/ any; addTo(property: any, value: any): void; subtractFrom(property: any, value: any): void; getMetadata(): Entry; getRecord(): any; getChanges(): any; _setChanges(changes: any): void; setRecord(record: any): void; invalidate(): void; _writeInvalidate(partial_record?: any, options?: any): void; _writeRelocate(options: any): void; /** * This is intended to acquire a lock on a record from the whole cluster. */ lock(): void; /** * Store the provided record data into the current resource. This is not written * until the corresponding transaction is committed. This will either immediately fail (synchronously) or always * succeed. That doesn't necessarily mean it will "win", another concurrent put could come "after" (monotonically, * even if not chronologically) this one. * @param record * @param options */ put(record: any): void; patch(record_update: any): void; _writeUpdate(record_update: any, full_update: boolean, options?: any): void; delete(request?: Query | string): Promise<boolean>; _writeDelete(options?: any): boolean; search(request: Query): AsyncIterable<any>; subscribe(request: SubscriptionRequest): Promise<{ listener: (key: any) => any; subscriptions: []; startTime?: number; end(): void; toJSON(): { name: string; }; resolveNext: Function; queue: any[]; hasDataListeners: boolean; drainCloseListener: boolean; currentDrainResolver: Function; push(message: any): void; send(message: any): void; getNextMessage(): any; waitForDrain(): Promise<boolean>; on(event_name: any, listener: any): /*elided*/ any; [Symbol.asyncIterator](): { queue: import("./IterableEventQueue").IterableEventQueue; push(message: any): void; next(): Promise<unknown> | { value: any; }; return(value: any): { value: any; done: boolean; }; throw(error: any): { done: boolean; }; }; [EventEmitter.captureRejectionSymbol]?<K>(error: Error, event: string | symbol, ...args: any[]): void; addListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; once<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; off<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; removeAllListeners(eventName?: string | symbol): /*elided*/ any; setMaxListeners(n: number): /*elided*/ any; getMaxListeners(): number; listeners<K>(eventName: string | symbol): Function[]; rawListeners<K>(eventName: string | symbol): Function[]; emit<K>(eventName: string | symbol, ...args: any[]): boolean; listenerCount<K>(eventName: string | symbol, listener?: Function): number; prependListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; prependOnceListener<K>(eventName: string | symbol, listener: (...args: any[]) => void): /*elided*/ any; eventNames(): (string | symbol)[]; }>; doesExist(): boolean; /** * Publishing a message to a record adds an (observable) entry in the audit log, but does not change * the record at all. This entries should be replicated and trigger subscription listeners. * @param id * @param message * @param options */ publish(message: any, options?: any): void; _writePublish(message: any, options?: any): void; validate(record: any, patch?: any): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly #id: Id; readonly #context: Context; #isCollection: boolean; post(new_record: any): Promise<any>; get isCollection(): boolean; connect(incomingMessages: import("./IterableEventQueue").IterableEventQueue, query?: {}): AsyncIterable<any>; getId(): Id; getContext(): Context; }; _updateResource(resource: any, entry: any): void; getNewId(): any; /** * Set TTL expiration for records in this table. On retrieval, record timestamps are checked for expiration. * This also informs the scheduling for record eviction. * @param expiration_time Time in seconds until records expire (are stale) * @param eviction_time Time in seconds until records are evicted (removed) */ setTTLExpiration(expiration: number | { expiration: number; eviction?: number; scanInterval?: number; }): void; getResidencyRecord(id: any): any; setResidency(getResidency?: (record: object, context: Context) => ResidencyDefinition): void; setResidencyById(getResidencyById?: (id: Id) => number | void): void; getResidency(record: object, context: Context): number | void | string[]; /** * Turn on auditing at runtime */ enableAuditing(audit_enabled?: boolean): void; /** * Coerce the id as a string to the correct type for the primary key * @param id * @returns */ coerceId(id: string): number | string; dropTable(): Promise<void>; /** * Record the relocation of an entry (when a record is moved to a different node) * @param existing_entry * @param entry */ _recordRelocate(existing_entry: any, entry: any): boolean; /** * Evicting a record will remove it from a caching table. This is not considered a canonical data change, and it is assumed that retrieving this record from the source will still yield the same record, this is only removing the local copy of the record. */ evict(id: any, existing_record: any, existing_version: any): Promise<void>; operation(operation: any, context: any): any; /** * This is responsible for ordering and select()ing the attributes/properties from returned entries * @param select * @param context * @param filtered * @param ensure_loaded * @param can_skip * @returns */ transformToOrderedSelect(entries: any[], select: (string | SubSelect)[], sort: Sort, context: Context, read_txn: any, transformToRecord: Function): any; /** * This is responsible for select()ing the attributes/properties from returned entries * @param select * @param context * @param filtered * @param ensure_loaded * @param can_skip * @returns */ transformEntryForSelect(select: any, context: any, read_txn: any, filtered: any, ensure_loaded?: any, can_skip?: any): (entry: any) => any; /** * Subscribe on one thread unless this is a per-thread subscription * @param worker_index * @param options */ subscribeOnThisThread(worker_index: any, options: any): boolean; addAttributes(attributes_to_add: any): Promise<any>; removeAttributes(names: string[]): Promise<any>; /** * Get the size of the table in bytes (based on amount of pages stored in the database) * @param options */ getSize(): number; getAuditSize(): number; getStorageStats(): { available: number; free: number; size: number; }; getRecordCount(options?: any): Promise<{ recordCount: number; estimatedRange: number[]; } | { recordCount: number; estimatedRange?: undefined; }>; /** * When attributes have been changed, we update the accessors that are assigned to this table */ updatedAttributes(): void; setComputedAttribute(attribute_name: any, resolver: any): void; deleteHistory(end_time?: number, cleanup_deleted_records?: boolean): Promise<void>; getHistory(start_time?: number, end_time?: number): AsyncGenerator<{ id: import("ordered-binary").Key; localTime: any; version: any; type: any; value: any; user: import("ordered-binary").Key; operation: any; }, void, unknown>; getHistoryOfRecord(id: any): Promise<any[]>; cleanup(): void; transactions: import("./DatabaseTransaction").Transaction[] & { timestamp: number; }; directURLMapping: boolean; get(identifier: Id, context?: Context): Promise<Resource>; get(query: Query, context?: Context): Promise<AsyncIterable<object>>; put: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; patch: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; delete(identifier: Id, context?: Context): Promise<boolean>; delete(request: Context, context?: Context): Promise<object>; create(id_prefix: Id, record: any, context: Context): Promise<Id>; create(record: any, context: Context): Promise<Id>; invalidate: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; post: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; connect: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; subscribe(request: SubscriptionRequest): Promise<AsyncIterable<{ id: any; operation: string; value: object; }>>; publish: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; search: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; query: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; copy: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; move: { (id_or_query: string | Id, data_or_context?: any, context?: Context): any; reliesOnPrototype: boolean; }; isCollection(resource: any): any; parseQuery(search: any): any; parsePath(path: any, context: any, query: any): any; }; export declare function setServerUtilities(utilities: any): void; /** * Coerce a string to the type defined by the attribute * @param value * @param attribute * @returns */ export declare function coerceType(value: any, attribute: any): any; export {};