UNPKG

@decaf-ts/core

Version:

Core persistence module for the decaf framework

460 lines (459 loc) 24.2 kB
import type { FlagsOf as ContextualFlagsOf } from "@decaf-ts/db-decorators"; import { BaseError, BulkCrudOperationKeys, OperationKeys, PrimaryKeyType } from "@decaf-ts/db-decorators"; import { type Observer } from "../interfaces/Observer"; import { Model, ModelConstructor } from "@decaf-ts/decorator-validation"; import { SequenceOptions } from "../interfaces/SequenceOptions"; import { RawPagedExecutor } from "../interfaces/RawExecutor"; import type { Repository } from "../repository/Repository"; import type { Sequence } from "./Sequence"; import { ErrorParser } from "../interfaces"; import { Statement } from "../query/Statement"; import { Impersonatable } from "@decaf-ts/logging"; import type { Dispatch } from "./Dispatch"; import { AdapterDispatch, type AdapterFlags, type EventIds, FlagsOf, Migration, type ObserverFilter, PersistenceObservable, PersistenceObserver, PreparedModel, RawResult } from "./types"; import { ObserverHandler } from "./ObserverHandler"; import { Context } from "./Context"; import { type Constructor } from "@decaf-ts/decoration"; import { ContextualArgs, ContextualizedArgs, ContextualLoggedClass } from "../utils/ContextualLoggedClass"; import { Paginator } from "../query/Paginator"; import { PreparedStatement } from "../query/index"; export type AdapterSubClass<A> = A extends Adapter<any, any, any, any> ? A : never; /** * @description Abstract Facade class for persistence adapters * @summary Provides the foundation for all database adapters in the persistence layer. This class * implements several interfaces to provide a consistent API for database operations, observer * pattern support, and error handling. It manages adapter registration, CRUD operations, and * observer notifications. * @template CONFIG - The underlying persistence driver config * @template QUERY - The query object type used by the adapter * @template FLAGS - The repository flags type * @template CONTEXT - The context type * @param {CONFIG} _config - The underlying persistence driver config * @param {string} flavour - The identifier for this adapter type * @param {string} [_alias] - Optional alternative name for this adapter * @class Adapter * @example * ```typescript * // Implementing a concrete adapter * class PostgresAdapter extends Adapter<pg.PoolConfig, pg.Query, PostgresFlags, PostgresContext> { * constructor(client: pg.PoolConfig) { * super(client, 'postgres'); * } * * async initialize() { * // Set up the adapter * await this.native.connect(); * } * * async create(tableName, id, model) { * // Implementation for creating records * const columns = Object.keys(model).join(', '); * const values = Object.values(model); * const placeholders = values.map((_, i) => `$${i+1}`).join(', '); * * const query = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`; * const result = await this.native.query(query, values); * return result.rows[0]; * } * * // Other required method implementations... * } * * // Using the adapter * const pgClient = new pg.Client(connectionString); * const adapter = new PostgresAdapter(pgClient); * await adapter.initialize(); * * // Set as the default adapter * Adapter.setCurrent('postgres'); * * // Perform operations * const user = await adapter.create('users', 1, { name: 'John', email: 'john@example.com' }); * ``` * @mermaid * classDiagram * class Adapter { * +Y native * +string flavour * +string alias * +create(tableName, id, model) * +read(tableName, id) * +update(tableName, id, model) * +delete(tableName, id) * +observe(observer, filter) * +unObserve(observer) * +static current * +static get(flavour) * +static setCurrent(flavour) * } * * class RawExecutor { * +raw(query) * } * * class Observable { * +observe(observer, filter) * +unObserve(observer) * +updateObservers(table, event, id) * } * * class Observer { * +refresh(table, event, id) * } * * class ErrorParser { * +parseError(err) * } * * Adapter --|> RawExecutor * Adapter --|> Observable * Adapter --|> Observer * Adapter --|> ErrorParser */ export declare abstract class Adapter<CONF, CONN, QUERY, CONTEXT extends Context<AdapterFlags> = Context> extends ContextualLoggedClass<CONTEXT> implements RawPagedExecutor<QUERY>, PersistenceObservable<CONTEXT>, PersistenceObserver<CONTEXT>, Impersonatable<any, [Partial<CONF>, ...any[]]>, ErrorParser { private readonly _config; readonly flavour: string; private readonly _alias?; private static _currentFlavour; private static _cache; private static _baseRepository; private static _baseSequence; private static _baseDispatch; protected dispatch?: AdapterDispatch<typeof this>; protected readonly observerHandler?: ObserverHandler; protected _client?: CONN; /** * @description Gets the native persistence config * @summary Provides access to the underlying persistence driver config * @template CONF * @return {CONF} The native persistence driver config */ get config(): CONF; /** * @description Gets the adapter's alias or flavor name * @summary Returns the alias if set, otherwise returns the flavor name * @return {string} The adapter's identifier */ get alias(): string; /** * @description Gets the repository constructor for this adapter * @summary Returns the constructor for creating repositories that work with this adapter * @template M - The model type * @return {Constructor<Repository<any, Adapter<CONF, CONN, QUERY, CONTEXT>>>} The repository constructor */ repository<R extends Repository<any, Adapter<CONF, CONN, QUERY, CONTEXT>>>(): Constructor<R>; protected shutdownProxies(k?: string): Promise<void>; /** * @description Shuts down the adapter * @summary Performs any necessary cleanup tasks, such as closing connections * When overriding this method, ensure to call the base method first * @return {Promise<void>} A promise that resolves when shutdown is complete */ shutdown(): Promise<void>; /** * @description Creates a new adapter instance * @summary Initializes the adapter with the native driver and registers it in the adapter cache */ protected constructor(_config: CONF, flavour: string, _alias?: string | undefined); /** * @description Creates a new statement builder for a model * @summary Returns a statement builder that can be used to construct queries for a specific model * @template M - The model type * @return {Statement} A statement builder for the model */ abstract Statement<M extends Model>(overrides?: Partial<AdapterFlags>): Statement<M, Adapter<CONF, CONN, QUERY, CONTEXT>, any>; abstract Paginator<M extends Model>(query: QUERY | PreparedStatement<M>, size: number, clazz: Constructor<M>): Paginator<M, any, QUERY>; /** * @description Creates a new dispatch instance * @summary Factory method that creates a dispatch instance for this adapter * @return {Dispatch} A new dispatch instance */ protected Dispatch(): Dispatch<Adapter<CONF, CONN, QUERY, CONTEXT>>; /** * @description Creates a new observer handler * @summary Factory method that creates an observer handler for this adapter * @return {ObserverHandler} A new observer handler instance */ protected ObserverHandler(): ObserverHandler; /** * @description Checks if an attribute name is reserved * @summary Determines if a given attribute name is reserved and cannot be used as a column name * @param {string} attr - The attribute name to check * @return {boolean} True if the attribute is reserved, false otherwise */ protected isReserved(attr: string): boolean; /** * @description Parses a database error into a standardized error * @summary Converts database-specific errors into standardized application errors * @param {Error} err - The original database error * @param args * @return {BaseError} A standardized error */ abstract parseError<E extends BaseError>(err: Error, ...args: any[]): E; /** * @description Initializes the adapter * @summary Performs any necessary setup for the adapter, such as establishing connections * @param {...any[]} args - Initialization arguments * @return {Promise<void>} A promise that resolves when initialization is complete */ initialize(...args: any[]): Promise<void>; /** * @description Creates a sequence generator * @summary Factory method that creates a sequence generator for generating sequential values * @param {SequenceOptions} options - Configuration options for the sequence * @return {Promise<Sequence>} A promise that resolves to a new sequence instance */ Sequence(options: SequenceOptions): Promise<Sequence>; /** * @description Creates repository flags for an operation * @summary Generates a set of flags that describe a database operation, combining default flags with overrides * @template F - The Repository Flags type * @template M - The model type * @param {OperationKeys} operation - The type of operation being performed * @param {Constructor<M>} model - The model constructor * @param {Partial<F>} flags - Custom flag overrides * @param {...any[]} args - Additional arguments * @return {Promise<F>} The complete set of flags */ protected flags<M extends Model>(operation: OperationKeys | string, model: Constructor<M> | Constructor<M>[], flags: Partial<FlagsOf<CONTEXT>>, ...args: any[]): Promise<FlagsOf<CONTEXT>>; /** * @description The context constructor for this adapter * @summary Reference to the context class constructor used by this adapter */ protected readonly Context: Constructor<CONTEXT>; /** * @description Creates a context for a database operation * @summary Generates a context object that describes a database operation, used for tracking and auditing * @template F - The Repository flags type * @template M - The model type * @param {OperationKeys.CREATE|OperationKeys.READ|OperationKeys.UPDATE|OperationKeys.DELETE} operation - The type of operation * @param {Partial<F>} overrides - Custom flag overrides * @param {Constructor<M>} model - The model constructor * @param {...any[]} args - Additional arguments * @return {Promise<C>} A promise that resolves to the context object */ context<M extends Model>(operation: OperationKeys.CREATE | OperationKeys.READ | OperationKeys.UPDATE | OperationKeys.DELETE | string, overrides: Partial<ContextualFlagsOf<CONTEXT>>, model: Constructor<M> | Constructor<M>[], ...args: any[]): Promise<CONTEXT>; /** * @description Prepares a model for persistence * @summary Converts a model instance into a format suitable for database storage, * handling column mapping and separating transient properties * handling column mapping and separating transient properties * @template M - The model type * @param {M} model - The model instance to prepare * @param args - optional args for subclassing purposes * @return The prepared data */ prepare<M extends Model>(model: M, ...args: ContextualArgs<CONTEXT>): PreparedModel; /** * @description Converts database data back into a model instance * @summary Reconstructs a model instance from database data, handling column mapping * and reattaching transient properties * @template M - The model type * @param obj - The database record * @param {Constructor<M>} clazz - The model class or name * @param pk - The primary key property name * @param {string|number|bigint} id - The primary key value * @param [transient] - Transient properties to reattach * @param [args] - options args for subclassing purposes * @return {M} The reconstructed model instance */ revert<M extends Model>(obj: Record<string, any>, clazz: Constructor<M>, id: PrimaryKeyType, transient?: Record<string, any>, ...args: ContextualArgs<CONTEXT>): M; /** * @description Creates a new record in the database * @summary Inserts a new record with the given ID and data into the specified table * @param {string} clazz - The name of the table to insert into * @param {PrimaryKeyType} id - The identifier for the new record * @param model - The data to insert * @param {any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to the created record */ abstract create<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>>; /** * @description Creates multiple records in the database * @summary Inserts multiple records with the given IDs and data into the specified table * @param {string} tableName - The name of the table to insert into * @param id - The identifiers for the new records * @param model - The data to insert for each record * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to an array of created records */ createAll<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType[], model: Record<string, any>[], ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>[]>; /** * @description Retrieves a record from the database * @summary Fetches a record with the given ID from the specified table * @param {string} tableName - The name of the table to read from * @param {string|number|bigint} id - The identifier of the record to retrieve * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to the retrieved record */ abstract read<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>>; /** * @description Retrieves multiple records from the database * @summary Fetches multiple records with the given IDs from the specified table * @param {string} tableName - The name of the table to read from * @param id - The identifiers of the records to retrieve * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to an array of retrieved records */ readAll<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType[], ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>[]>; /** * @description Updates a record in the database * @summary Modifies an existing record with the given ID in the specified table * @template M - The model type * @param {Constructor<M>} tableName - The name of the table to update * @param {PrimaryKeyType} id - The identifier of the record to update * @param model - The new data for the record * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to the updated record */ abstract update<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>>; /** * @description Updates multiple records in the database * @summary Modifies multiple existing records with the given IDs in the specified table * @param {Constructor<M>} tableName - The name of the table to update * @param {string[]|number[]} id - The identifiers of the records to update * @param model - The new data for each record * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to an array of updated records */ updateAll<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType[], model: Record<string, any>[], ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>[]>; /** * @description Deletes a record from the database * @summary Removes a record with the given ID from the specified table * @param {string} tableName - The name of the table to delete from * @param {string|number|bigint} id - The identifier of the record to delete * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to the deleted record */ abstract delete<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>>; /** * @description Deletes multiple records from the database * @summary Removes multiple records with the given IDs from the specified table * @param {string} tableName - The name of the table to delete from * @param id - The identifiers of the records to delete * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return A promise that resolves to an array of deleted records */ deleteAll<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType[], ...args: ContextualArgs<CONTEXT>): Promise<Record<string, any>[]>; /** * @description Executes a raw query against the database * @summary Allows executing database-specific queries directly * @template Q - The raw query type * @template R - The return type of the query * @param {Q} rawInput - The query to execute * @param {...any[]} args - Additional arguments specific to the adapter implementation * @return {Promise<R>} A promise that resolves to the query result */ abstract raw<R, D extends boolean>(rawInput: QUERY, docsOnly: D, ...args: ContextualArgs<CONTEXT>): Promise<RawResult<R, D>>; /** * @description Registers an observer for database events * @summary Adds an observer to be notified about database changes. The observer can optionally * provide a filter function to receive only specific events. * @param {Observer} observer - The observer to register * @param {ObserverFilter} [filter] - Optional filter function to determine which events the observer receives * @return {void} */ observe(observer: Observer, filter?: ObserverFilter): void; /** * @description Unregisters an observer * @summary Removes a previously registered observer so it no longer receives database event notifications * @param {Observer} observer - The observer to unregister * @return {void} */ unObserve(observer: Observer): void; /** * @description Notifies all observers about a database event * @summary Sends notifications to all registered observers about a change in the database, * filtering based on each observer's filter function * @param {string} table - The name of the table where the change occurred * @param {OperationKeys|BulkCrudOperationKeys|string} event - The type of operation that occurred * @param {EventIds} id - The identifier(s) of the affected record(s) * @param {...any[]} args - Additional arguments to pass to the observers * @return {Promise<void>} A promise that resolves when all observers have been notified */ updateObservers<M extends Model>(table: Constructor<M> | string, event: OperationKeys | BulkCrudOperationKeys | string, id: EventIds, ...args: ContextualArgs<CONTEXT>): Promise<void>; /** * @description Refreshes data based on a database event * @summary Implementation of the Observer interface method that delegates to updateObservers * @param {string} table - The name of the table where the change occurred * @param {OperationKeys|BulkCrudOperationKeys|string} event - The type of operation that occurred * @param {EventIds} id - The identifier(s) of the affected record(s) * @param {...any[]} args - Additional arguments related to the event * @return {Promise<void>} A promise that resolves when the refresh is complete */ refresh<M extends Model>(table: Constructor<M> | string, event: OperationKeys | BulkCrudOperationKeys | string, id: EventIds, ...args: ContextualArgs<CONTEXT>): Promise<void>; /** * @description Gets a string representation of the adapter * @summary Returns a human-readable string identifying this adapter * @return {string} A string representation of the adapter */ toString(): string; /** * @description Gets the adapter flavor associated with a model * @summary Retrieves the adapter flavor that should be used for a specific model class * @template M - The model type * @param {Constructor<M>} model - The model constructor * @return {string} The adapter flavor name */ static flavourOf<M extends Model>(model: Constructor<M>): string; static get currentFlavour(): string; /** * @description Gets the current default adapter * @summary Retrieves the adapter that is currently set as the default for operations * @return {Adapter<any, any, any, any>} The current adapter */ static get current(): Adapter<any, any, any, any> | undefined; /** * @description Gets an adapter by flavor * @summary Retrieves a registered adapter by its flavor name * @template CONF - The database driver config * @template CONN - The database driver instance * @template QUERY - The query type * @template CONTEXT - The context type * @param {string} flavour - The flavor name of the adapter to retrieve * @return {Adapter<CONF, CONN, QUERY, CONTEXT> | undefined} The adapter instance or undefined if not found */ static get<A extends Adapter<any, any, any, any>>(flavour?: any): A | undefined; /** * @description Sets the current default adapter * @summary Changes which adapter is used as the default for operations * @param {string} flavour - The flavor name of the adapter to set as current * @return {void} */ static setCurrent(flavour: string): void; /** * @description Gets all models associated with an adapter flavor * @summary Retrieves all model constructors that are configured to use a specific adapter flavor * @template M - The model type * @param {string} flavour - The adapter flavor to find models for * @return An array of model constructors */ static models<M extends Model>(flavour: string): ModelConstructor<M>[]; static decoration(): void; /** * @description retrieves the context from args and returns it, the logger and the args (with context at the end) * @summary NOTE: if the last argument was a context, this removes the context from the arg list * @param args * @param method */ static logCtx<CONTEXT extends Context<any>, ARGS extends any[] = any[]>(this: any, args: ARGS, method: string): ContextualizedArgs<CONTEXT, ARGS>; static logCtx<CONTEXT extends Context<any>, ARGS extends any[] = any[]>(this: any, args: ARGS, method: (...args: any[]) => any): ContextualizedArgs<CONTEXT, ARGS>; protected proxies?: Record<string, typeof this>; /** * @description Returns the client instance for the adapter * @summary This method should be overridden by subclasses to return the client instance for the adapter. * @template CON - The type of the client instance * @return {CON} The client instance for the adapter * @abstract * @function getClient * @memberOf module:core * @instance * @protected */ protected abstract getClient(): CONN; get client(): CONN; for(config: Partial<CONF>, ...args: any[]): this; migrations(): Constructor<Migration<any, this>>[]; protected getQueryRunner(): Promise<CONN>; migrate(...args: [...any[], CONTEXT]): Promise<void>; }