UNPKG

harperdb

Version:

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

549 lines (548 loc) 25.9 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 { type Database } from 'lmdb'; import type { ResourceInterface, SubscriptionRequest, Id, Context, Sort, SubSelect, RequestTargetOrId } from './ResourceInterface.ts'; import type { User } from '../security/user.ts'; import { type RecordObject, type Entry } from './RecordEncoder.ts'; import { RequestTarget } from './RequestTarget.ts'; export type Attribute = { name: string; type: string; assignCreatedTime?: boolean; assignUpdatedTime?: boolean; expiresAt?: boolean; isPrimaryKey?: boolean; }; 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 <Record extends object = any>(identifier: Id, source: any): { "__#private@#record": any; "__#private@#changes": any; "__#private@#version"?: number; "__#private@#entry"?: Entry; "__#private@#saveMode"?: boolean; "__#private@#loadedFromSource"?: boolean; getProperty: (name: string) => any; /** * 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(): unknown; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. */ get(): /*elided*/ any | undefined; /** * This retrieves the data of this resource. * @param target - If included, is an identifier/query that specifies the requested target to retrieve and query */ get(target: RequestTargetOrId): Record | AsyncIterable<Record> | Promise<Record | AsyncIterable<Record>>; /** * Determine if the user is allowed to get/read data from the current resource */ allowRead(user: User, target: RequestTarget, context: Context): boolean; /** * Determine if the user is allowed to update data from the current resource */ allowUpdate(user: User, updatedData: Record, context: Context): boolean; /** * Determine if the user is allowed to create new data in the current resource */ allowCreate(user: User, newData: Record, context: Context): boolean; /** * Determine if the user is allowed to delete from the current resource */ allowDelete(user: User, target: RequestTarget, context: Context): 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. */ update(updates: Record & RecordObject, fullUpdate: true): any; update(updates: Partial<Record & RecordObject>, target?: RequestTarget): any; update(target: RequestTarget, updates?: any): 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(target: RequestTargetOrId): void; _writeInvalidate(id: Id, partialRecord?: any, options?: any): void; _writeRelocate(id: Id, 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. */ put(target: RequestTarget, record: Record & RecordObject): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; create(target: RequestTarget, record: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<Record & Partial<RecordObject>>; patch(target: RequestTarget, recordUpdate: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; _writeUpdate(id: Id, recordUpdate: any, fullUpdate: boolean, options?: any): void; delete(target: RequestTargetOrId): Promise<boolean>; _writeDelete(id: Id, options?: any): boolean; search(target: RequestTarget): AsyncIterable<Record & Partial<RecordObject>>; subscribe(request: SubscriptionRequest): Promise<AsyncIterable<Record>>; 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(target: RequestTarget, message: Record, options?: any): void; _writePublish(id: Id, message: any, options?: any): void; validate(record: any, patch?: boolean): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly "__#private@#id": Id; readonly "__#private@#context": Context; "__#private@#isCollection": boolean; post(target: RequestTargetOrId, newRecord: Partial<Record & RecordObject>): Promise<Record & Partial<RecordObject>>; get isCollection(): boolean; connect(target: RequestTarget, incomingMessages: import("./IterableEventQueue.js").IterableEventQueue): AsyncIterable<any>; getId(): Id; getContext(): Context | import("./ResourceInterface.ts").SourceContext; }; 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: (/*elided*/ any)[]; sourceOptions: any; intermediateSource: boolean; 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<Record extends object = any>(id: Id, request: Context, resourceOptions?: any): Promise<{ "__#private@#record": any; "__#private@#changes": any; "__#private@#version"?: number; "__#private@#entry"?: Entry; "__#private@#saveMode"?: boolean; "__#private@#loadedFromSource"?: boolean; getProperty: (name: string) => any; /** * 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(): unknown; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. */ get(): /*elided*/ any; /** * This retrieves the data of this resource. * @param target - If included, is an identifier/query that specifies the requested target to retrieve and query */ get(target: RequestTargetOrId): Record | AsyncIterable<Record> | Promise<Record | AsyncIterable<Record>>; /** * Determine if the user is allowed to get/read data from the current resource */ allowRead(user: User, target: RequestTarget, context: Context): boolean; /** * Determine if the user is allowed to update data from the current resource */ allowUpdate(user: User, updatedData: Record, context: Context): boolean; /** * Determine if the user is allowed to create new data in the current resource */ allowCreate(user: User, newData: Record, context: Context): boolean; /** * Determine if the user is allowed to delete from the current resource */ allowDelete(user: User, target: RequestTarget, context: Context): 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. */ update(updates: Record & RecordObject, fullUpdate: true): any; update(updates: Partial<Record & RecordObject>, target?: RequestTarget): any; update(target: RequestTarget, updates?: any): 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(target: RequestTargetOrId): void; _writeInvalidate(id: Id, partialRecord?: any, options?: any): void; _writeRelocate(id: Id, 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. */ put(target: RequestTarget, record: Record & RecordObject): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; create(target: RequestTarget, record: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<Record & Partial<RecordObject>>; patch(target: RequestTarget, recordUpdate: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; _writeUpdate(id: Id, recordUpdate: any, fullUpdate: boolean, options?: any): void; delete(target: RequestTargetOrId): Promise<boolean>; _writeDelete(id: Id, options?: any): boolean; search(target: RequestTarget): AsyncIterable<Record & Partial<RecordObject>>; subscribe(request: SubscriptionRequest): Promise<AsyncIterable<Record>>; 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(target: RequestTarget, message: Record, options?: any): void; _writePublish(id: Id, message: any, options?: any): void; validate(record: any, patch?: boolean): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly "__#private@#id": Id; readonly "__#private@#context": Context; "__#private@#isCollection": boolean; post(target: RequestTargetOrId, newRecord: Partial<Record & RecordObject>): Promise<Record & Partial<RecordObject>>; get isCollection(): boolean; connect(target: RequestTarget, incomingMessages: import("./IterableEventQueue.js").IterableEventQueue): AsyncIterable<any>; getId(): Id; getContext(): Context | import("./ResourceInterface.ts").SourceContext; }> | { "__#private@#record": any; "__#private@#changes": any; "__#private@#version"?: number; "__#private@#entry"?: Entry; "__#private@#saveMode"?: boolean; "__#private@#loadedFromSource"?: boolean; getProperty: (name: string) => any; /** * 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(): unknown; /** * This retrieves the data of this resource. By default, with no argument, just return `this`. */ get(): /*elided*/ any; /** * This retrieves the data of this resource. * @param target - If included, is an identifier/query that specifies the requested target to retrieve and query */ get(target: RequestTargetOrId): Record | AsyncIterable<Record> | Promise<Record | AsyncIterable<Record>>; /** * Determine if the user is allowed to get/read data from the current resource */ allowRead(user: User, target: RequestTarget, context: Context): boolean; /** * Determine if the user is allowed to update data from the current resource */ allowUpdate(user: User, updatedData: Record, context: Context): boolean; /** * Determine if the user is allowed to create new data in the current resource */ allowCreate(user: User, newData: Record, context: Context): boolean; /** * Determine if the user is allowed to delete from the current resource */ allowDelete(user: User, target: RequestTarget, context: Context): 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. */ update(updates: Record & RecordObject, fullUpdate: true): any; update(updates: Partial<Record & RecordObject>, target?: RequestTarget): any; update(target: RequestTarget, updates?: any): 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(target: RequestTargetOrId): void; _writeInvalidate(id: Id, partialRecord?: any, options?: any): void; _writeRelocate(id: Id, 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. */ put(target: RequestTarget, record: Record & RecordObject): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; create(target: RequestTarget, record: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<Record & Partial<RecordObject>>; patch(target: RequestTarget, recordUpdate: Partial<Record & RecordObject>): void | (Record & Partial<RecordObject>) | Promise<void | (Record & Partial<RecordObject>)>; _writeUpdate(id: Id, recordUpdate: any, fullUpdate: boolean, options?: any): void; delete(target: RequestTargetOrId): Promise<boolean>; _writeDelete(id: Id, options?: any): boolean; search(target: RequestTarget): AsyncIterable<Record & Partial<RecordObject>>; subscribe(request: SubscriptionRequest): Promise<AsyncIterable<Record>>; 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(target: RequestTarget, message: Record, options?: any): void; _writePublish(id: Id, message: any, options?: any): void; validate(record: any, patch?: boolean): void; getUpdatedTime(): number; wasLoadedFromSource(): boolean | void; readonly "__#private@#id": Id; readonly "__#private@#context": Context; "__#private@#isCollection": boolean; post(target: RequestTargetOrId, newRecord: Partial<Record & RecordObject>): Promise<Record & Partial<RecordObject>>; get isCollection(): boolean; connect(target: RequestTarget, incomingMessages: import("./IterableEventQueue.js").IterableEventQueue): AsyncIterable<any>; getId(): Id; getContext(): Context | import("./ResourceInterface.ts").SourceContext; }; _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 expirationTime Time in seconds until records expire (are stale) * @param evictionTime Time in seconds until records are evicted (removed) */ setTTLExpiration(expiration: number | { expiration: number; eviction?: number; scanInterval?: number; }): void; getResidencyRecord(id: Id): 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(): 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), return true if it is now located locally * @param existingEntry * @param entry */ _recordRelocate(existingEntry: 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, existingRecord: any, existingVersion: any): any; 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 canSkip * @returns */ transformToOrderedSelect(entries: any[], select: (string | SubSelect)[], sort: Sort, context: Context, readTxn: 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 canSkip * @returns */ transformEntryForSelect(select: any, context: any, readTxn: any, filtered: any, ensure_loaded?: any, canSkip?: any): (entry: Entry) => any; /** * Subscribe on one thread unless this is a per-thread subscription * @param workerIndex * @param options */ subscribeOnThisThread(workerIndex: any, options: any): boolean; addAttributes(attributesToAdd: 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(endTime?: number, cleanupDeletedRecords?: boolean): Promise<void>; getHistory(startTime?: number, endTime?: 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.ts").Transaction[] & { timestamp: number; }; directURLMapping: boolean; loadAsInstance: boolean; get: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; put: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; patch: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; delete: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; create(idPrefix: Id, record: any, context: Context): Promise<Id>; create(record: any, context: Context): Promise<Id>; invalidate: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; post: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; update: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; connect: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; subscribe: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; publish: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; search: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; query: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; copy: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; move: { (idOrQuery: string | Id | import("./ResourceInterface.ts").Query, dataOrContext?: any, context?: Context): any; reliesOnPrototype: boolean; }; isCollection(resource: any): any; parseQuery(search: any, query: any): any; parsePath(path: any, context: any, query: any): any; }; /** * 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 {};