UNPKG

@decaf-ts/core

Version:

Core persistence module for the decaf framework

338 lines (337 loc) 15.8 kB
import { RamFlags, RawRamQuery, RamStorage, RamRepository, RamConfig } from "./types"; import { RamStatement } from "./RamStatement"; import { RamContext } from "./RamContext"; import { Adapter, Sequence } from "../persistence"; import { SequenceOptions } from "../interfaces"; import { Constructor, Model } from "@decaf-ts/decorator-validation"; import { BaseError, OperationKeys } from "@decaf-ts/db-decorators"; /** * @description In-memory adapter for data persistence * @summary The RamAdapter provides an in-memory implementation of the persistence layer. * It stores data in JavaScript Maps and provides CRUD operations and query capabilities. * This adapter is useful for testing, prototyping, and applications that don't require * persistent storage across application restarts. * @class RamAdapter * @category Ram * @example * ```typescript * // Create a new RAM adapter * const adapter = new RamAdapter('myRamAdapter'); * * // Create a repository for a model * const userRepo = new (adapter.repository<User>())(User, adapter); * * // Perform CRUD operations * const user = new User({ name: 'John', email: 'john@example.com' }); * await userRepo.create(user); * const retrievedUser = await userRepo.findById(user.id); * ``` * @mermaid * sequenceDiagram * participant Client * participant Repository * participant RamAdapter * participant Storage as In-Memory Storage * * Client->>Repository: create(model) * Repository->>RamAdapter: create(tableName, id, model) * RamAdapter->>RamAdapter: lock.acquire() * RamAdapter->>Storage: set(id, model) * RamAdapter->>RamAdapter: lock.release() * RamAdapter-->>Repository: model * Repository-->>Client: model * * Client->>Repository: findById(id) * Repository->>RamAdapter: read(tableName, id) * RamAdapter->>Storage: get(id) * Storage-->>RamAdapter: model * RamAdapter-->>Repository: model * Repository-->>Client: model */ export declare class RamAdapter extends Adapter<RamConfig, RamStorage, RawRamQuery<any>, RamFlags, RamContext> { constructor(conf?: RamConfig, alias?: string); /** * @description Gets the repository constructor for a model * @summary Returns a constructor for creating repositories that work with the specified model type. * This method overrides the base implementation to provide RAM-specific repository functionality. * @template M - The model type for the repository * @return {Constructor<RamRepository<M>>} A constructor for creating RAM repositories */ repository<M extends Model<boolean>>(): Constructor<RamRepository<M>>; /** * @description Creates operation flags with UUID * @summary Extends the base flags with a UUID for user identification. * This method ensures that all operations have a unique identifier for tracking purposes. * @template M - The model type for the operation * @param {OperationKeys} operation - The type of operation being performed * @param {Constructor<M>} model - The model constructor * @param {Partial<RamFlags>} flags - Partial flags to be extended * @return {Promise<RamFlags>} Complete flags with UUID */ flags<M extends Model<boolean>>(operation: OperationKeys, model: Constructor<M>, flags: Partial<RamFlags>): Promise<RamFlags>; Context: typeof RamContext; private indexes; private lock; /** * @description Indexes models in the RAM adapter * @summary A no-op indexing method for the RAM adapter. * Since RAM adapter doesn't require explicit indexing, this method simply resolves immediately. * @param models - Models to be indexed (unused) * @return {Promise<any>} A promise that resolves when indexing is complete */ index(...models: Record<string, any>[]): Promise<any>; /** * @description Prepares a model for storage * @summary Converts a model instance to a format suitable for storage in the RAM adapter. * This method extracts the primary key and creates a record without the primary key field. * @template M - The model type being prepared * @param {M} model - The model instance to prepare * @param pk - The primary key property name * @return Object containing the record and ID */ prepare<M extends Model>(model: M, pk: keyof M): { record: Record<string, any>; id: string; }; /** * @description Converts a stored record back to a model instance * @summary Reconstructs a model instance from a stored record by adding back the primary key. * This method is the inverse of the prepare method. * @template M - The model type to revert to * @param {Record<string, any>} obj - The stored record * @param {string | Constructor<M>} clazz - The model class or name * @param pk - The primary key property name * @param {string | number} id - The primary key value * @return {M} The reconstructed model instance */ revert<M extends Model>(obj: Record<string, any>, clazz: string | Constructor<M>, pk: keyof M, id: string | number): M; /** * @description Creates a new record in the in-memory storage * @summary Stores a new record in the specified table with the given ID. * This method acquires a lock to ensure thread safety, creates the table if it doesn't exist, * checks for conflicts, and stores the model. * @param {string} tableName - The name of the table to store the record in * @param {string | number} id - The unique identifier for the record * @param {Record<string, any>} model - The record data to store * @return {Promise<Record<string, any>>} A promise that resolves to the stored record * @mermaid * sequenceDiagram * participant Caller * participant RamAdapter * participant Storage as In-Memory Storage * * Caller->>RamAdapter: create(tableName, id, model) * RamAdapter->>RamAdapter: lock.acquire() * RamAdapter->>Storage: has(tableName) * alt Table doesn't exist * RamAdapter->>Storage: set(tableName, new Map()) * end * RamAdapter->>Storage: has(id) * alt Record exists * RamAdapter-->>Caller: throw ConflictError * end * RamAdapter->>Storage: set(id, model) * RamAdapter->>RamAdapter: lock.release() * RamAdapter-->>Caller: model */ create(tableName: string, id: string | number, model: Record<string, any>): Promise<Record<string, any>>; /** * @description Retrieves a record from in-memory storage * @summary Fetches a record with the specified ID from the given table. * This method checks if the table and record exist and throws appropriate errors if not. * @param {string} tableName - The name of the table to retrieve from * @param {string | number} id - The unique identifier of the record to retrieve * @return {Promise<Record<string, any>>} A promise that resolves to the retrieved record * @mermaid * sequenceDiagram * participant Caller * participant RamAdapter * participant Storage as In-Memory Storage * * Caller->>RamAdapter: read(tableName, id) * RamAdapter->>Storage: has(tableName) * alt Table doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: has(id) * alt Record doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: get(id) * Storage-->>RamAdapter: record * RamAdapter-->>Caller: record */ read(tableName: string, id: string | number): Promise<Record<string, any>>; /** * @description Updates an existing record in the in-memory storage * @summary Updates a record with the specified ID in the given table. * This method acquires a lock to ensure thread safety, checks if the table and record exist, * and updates the record with the new data. * @param {string} tableName - The name of the table containing the record * @param {string | number} id - The unique identifier of the record to update * @param {Record<string, any>} model - The new record data * @return {Promise<Record<string, any>>} A promise that resolves to the updated record * @mermaid * sequenceDiagram * participant Caller * participant RamAdapter * participant Storage as In-Memory Storage * * Caller->>RamAdapter: update(tableName, id, model) * RamAdapter->>RamAdapter: lock.acquire() * RamAdapter->>Storage: has(tableName) * alt Table doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: has(id) * alt Record doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: set(id, model) * RamAdapter->>RamAdapter: lock.release() * RamAdapter-->>Caller: model */ update(tableName: string, id: string | number, model: Record<string, any>): Promise<Record<string, any>>; /** * @description Deletes a record from the in-memory storage * @summary Removes a record with the specified ID from the given table. * This method acquires a lock to ensure thread safety, checks if the table and record exist, * retrieves the record before deletion, and then removes it from storage. * @param {string} tableName - The name of the table containing the record * @param {string | number} id - The unique identifier of the record to delete * @return {Promise<Record<string, any>>} A promise that resolves to the deleted record * @mermaid * sequenceDiagram * participant Caller * participant RamAdapter * participant Storage as In-Memory Storage * * Caller->>RamAdapter: delete(tableName, id) * RamAdapter->>RamAdapter: lock.acquire() * RamAdapter->>Storage: has(tableName) * alt Table doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: has(id) * alt Record doesn't exist * RamAdapter-->>Caller: throw NotFoundError * end * RamAdapter->>Storage: get(id) * Storage-->>RamAdapter: record * RamAdapter->>Storage: delete(id) * RamAdapter->>RamAdapter: lock.release() * RamAdapter-->>Caller: record */ delete(tableName: string, id: string | number): Promise<Record<string, any>>; /** * @description Gets or creates a table in the in-memory storage * @summary Retrieves the Map representing a table for a given model or table name. * If the table doesn't exist, it creates a new one. This is a helper method used * by other methods to access the correct storage location. * @template M - The model type for the table * @param {string | Constructor<M>} from - The model class or table name * @return {Map<string | number, any> | undefined} The table Map or undefined */ protected tableFor<M extends Model>(from: string | Constructor<M>): Map<string | number, any> | undefined; /** * @description Executes a raw query against the in-memory storage * @summary Performs a query operation on the in-memory data store using the provided query specification. * This method supports filtering, sorting, pagination, and field selection. * @template R - The return type of the query * @param {RawRamQuery<any>} rawInput - The query specification * @return {Promise<R>} A promise that resolves to the query results * @mermaid * sequenceDiagram * participant Caller * participant RamAdapter * participant Storage as In-Memory Storage * * Caller->>RamAdapter: raw(rawInput) * RamAdapter->>RamAdapter: tableFor(from) * alt Table doesn't exist * RamAdapter-->>Caller: throw InternalError * end * RamAdapter->>RamAdapter: findPrimaryKey(new from()) * RamAdapter->>Storage: entries() * Storage-->>RamAdapter: entries * loop For each entry * RamAdapter->>RamAdapter: revert(r, from, id, pk) * end * alt Where condition exists * RamAdapter->>RamAdapter: result.filter(where) * end * alt Sort condition exists * RamAdapter->>RamAdapter: result.sort(sort) * end * alt Skip specified * RamAdapter->>RamAdapter: result.slice(skip) * end * alt Limit specified * RamAdapter->>RamAdapter: result.slice(0, limit) * end * alt Select fields specified * loop For each result * RamAdapter->>RamAdapter: Filter to selected fields * end * end * RamAdapter-->>Caller: result */ raw<R>(rawInput: RawRamQuery<any>): Promise<R>; /** * @description Parses and converts errors to appropriate types * @summary Ensures that errors are of the correct type for consistent error handling. * If the error is already a BaseError, it's returned as is; otherwise, it's wrapped in an InternalError. * @template V - The expected error type, extending BaseError * @param {Error} err - The error to parse * @return {V} The parsed error of the expected type */ parseError<V extends BaseError>(err: Error): V; /** * @description Creates a new statement builder for queries * @summary Factory method that creates a new RamStatement instance for building queries. * This method allows for fluent query construction against the RAM adapter. * @template M - The model type for the statement * @return {RamStatement<M, any>} A new statement builder instance */ Statement<M extends Model<boolean>>(): RamStatement<M, any>; /** * @description Creates a new sequence for generating sequential IDs * @summary Factory method that creates a new RamSequence instance for ID generation. * This method provides a way to create auto-incrementing sequences for entity IDs. * @param {SequenceOptions} options - Configuration options for the sequence * @return {Promise<Sequence>} A promise that resolves to the new sequence instance */ Sequence(options: SequenceOptions): Promise<Sequence>; for(config: Partial<RamConfig>, ...args: any[]): typeof this; /** * @description Sets up RAM-specific decorations for model properties * @summary Configures decorations for createdBy and updatedBy fields in the RAM adapter. * This static method is called during initialization to set up handlers that automatically * populate these fields with the current user's UUID during create and update operations. * @return {void} * @mermaid * sequenceDiagram * participant RamAdapter * participant Decoration * participant Repository * * RamAdapter->>Repository: key(PersistenceKeys.CREATED_BY) * Repository-->>RamAdapter: createdByKey * RamAdapter->>Repository: key(PersistenceKeys.UPDATED_BY) * Repository-->>RamAdapter: updatedByKey * * RamAdapter->>Decoration: flavouredAs(RamFlavour) * Decoration-->>RamAdapter: DecoratorBuilder * RamAdapter->>Decoration: for(createdByKey) * RamAdapter->>Decoration: define(onCreate, propMetadata) * RamAdapter->>Decoration: apply() * * RamAdapter->>Decoration: flavouredAs(RamFlavour) * Decoration-->>RamAdapter: DecoratorBuilder * RamAdapter->>Decoration: for(updatedByKey) * RamAdapter->>Decoration: define(onCreate, propMetadata) * RamAdapter->>Decoration: apply() */ static decoration(): void; protected getClient(): RamStorage; }