@decaf-ts/core
Version:
Core persistence module for the decaf framework
333 lines (332 loc) • 15.9 kB
TypeScript
import { RamConfig, RamContext, RamFlags, RamStorage, RawRamQuery } from "./types";
import { RamStatement } from "./RamStatement";
import { Repository } from "../repository/Repository";
import { Dispatch } from "../persistence/Dispatch";
import { Adapter, AdapterFlags, RawResult } from "../persistence";
import { Model } from "@decaf-ts/decorator-validation";
import { BaseError, OperationKeys, PrimaryKeyType } from "@decaf-ts/db-decorators";
import type { Constructor } from "@decaf-ts/decoration";
import { RamPaginator } from "./RamPaginator";
/**
* @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, 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<R extends Repository<any, Adapter<any, any, any, any>>>(): Constructor<R>;
/**
* @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>;
protected Dispatch(): Dispatch<RamAdapter>;
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, ...args: [...any[], RamContext]): {
record: Record<string, any>;
id: string;
transient?: Record<string, any>;
};
/**
* @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 {Constructor<M>} clazz - The model class or name
* @param {PrimaryKeyType} id - The primary key value
* @return {M} The reconstructed model instance
*/
revert<M extends Model>(obj: Record<string, any>, clazz: Constructor<M>, id: PrimaryKeyType, transient?: Record<string, any>, ...args: [...any[], RamContext]): 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} clazz - 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<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ctx: RamContext): 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 {Constructor} clazz - The name of the table to retrieve from
* @param {PrimaryKeyType} 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<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, ctx: RamContext): 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<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ctx: RamContext): 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<M extends Model>(clazz: Constructor<M>, id: PrimaryKeyType, ctx: RamContext): 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, D extends boolean>(rawInput: RawRamQuery<any>, docsOnly: D | undefined, ctx: RamContext): Promise<RawResult<R, D>>;
/**
* @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>>(overrides?: Partial<AdapterFlags>): RamStatement<M, any, Adapter<any, any, RawRamQuery<M>, RamContext>>;
Paginator<M extends Model<boolean>>(query: RawRamQuery, size: number, clazz: Constructor<M>): RamPaginator<M>;
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;
}