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
TypeScript
/**
* 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 {};