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
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 { 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 {};