UNPKG

@decaf-ts/for-nano

Version:

decaf-ts persistence adapter for CouchDB via nano

450 lines (449 loc) 21.1 kB
import { OperationKeys, PrimaryKeyType } from "@decaf-ts/db-decorators"; import { CouchDBAdapter, MangoQuery, ViewResponse } from "@decaf-ts/for-couchdb"; import { DocumentScope, ServerScope } from "nano"; import { Model } from "@decaf-ts/decorator-validation"; import { NanoConfig, NanoFlags } from "./types.d.cts"; import { Adapter, Context, ContextOf, ContextualArgs, MaybeContextualArg, RelationsMetadata, Repository } from "@decaf-ts/core"; import { NanoRepository } from "./NanoRepository.d.cts"; import { NanoDispatch } from "./NanoDispatch.d.cts"; import { Constructor } from "@decaf-ts/decoration"; /** * @description Sets the creator or updater field in a model based on the user in the context * @summary Callback function used in decorators to automatically set the created_by or updated_by fields * with the username from the context when a document is created or updated * @template M - Type extending Model * @template R - Type extending NanoRepository<M> * @template V - Type extending RelationsMetadata * @param {R} this - The repository instance * @param {Context<NanoFlags>} context - The operation context containing user information * @param {V} data - The relation metadata * @param key - The property key to set with the username * @param {M} model - The model instance being created or updated * @return {Promise<void>} A promise that resolves when the operation is complete * @function createdByOnNanoCreateUpdate * @memberOf module:for-nano * @mermaid * sequenceDiagram * participant F as createdByOnNanoCreateUpdate * participant C as Context * participant M as Model * F->>C: get("user") * C-->>F: user object * F->>M: set key to user.name * Note over F: If no user in context * F-->>F: throw UnsupportedError */ export declare function createdByOnNanoCreateUpdate<M extends Model, R extends NanoRepository<M>, V extends RelationsMetadata>(this: R, context: ContextOf<R>, data: V, key: keyof M, model: M): Promise<void>; /** * @description Adapter for interacting with Nano databases * @summary Provides a standardized interface for performing CRUD operations on Nano databases, * extending the CouchDB adapter with Nano-specific functionality. This adapter handles document * creation, reading, updating, and deletion, as well as bulk operations and index management. * @template DocumentScope - The Nano document scope type * @template NanoFlags - Configuration flags for Nano operations * @template Context - Context type for operations * @param {DocumentScope<any>} scope - The Nano document scope to use for database operations * @param {string} [alias] - Optional alias for the adapter * @class NanoAdapter * @example * ```typescript * // Connect to a Nano database * const server = NanoAdapter.connect('admin', 'password', 'localhost:5984'); * const db = server.db.use('my_database'); * * // Create an adapter instance * const adapter = new NanoAdapter(db); * * // Use the adapter for database operations * const document = await adapter.read('users', '123'); * ``` * @mermaid * classDiagram * class CouchDBAdapter { * +flags() * +Dispatch() * +index() * +create() * +read() * +update() * +delete() * } * class NanoAdapter { * +flags() * +Dispatch() * +index() * +create() * +createAll() * +read() * +readAll() * +update() * +updateAll() * +delete() * +deleteAll() * +raw() * +static connect() * +static createDatabase() * +static deleteDatabase() * +static createUser() * +static deleteUser() * +static decoration() * } * CouchDBAdapter <|-- NanoAdapter */ export declare class NanoAdapter extends CouchDBAdapter<NanoConfig, DocumentScope<any>, Context<NanoFlags>> { constructor(scope: NanoConfig, alias?: string); /** * @description Shuts down the adapter instance * @summary Cleans up internal resources and clears the cached Nano client instance * @return {Promise<void>} A promise that resolves when shutdown completes */ shutdown(...args: MaybeContextualArg<Context<NanoFlags>>): Promise<void>; /** * @description Lazily creates and returns the Nano DocumentScope client * @summary Uses the adapter configuration to establish a connection and wrap a database scope with credentials * @return {DocumentScope<any>} The ready-to-use Nano DocumentScope for the configured database */ protected getClient(): any; /** * @description Generates flags for database operations * @summary Creates a set of flags for a specific operation, including user information * @template M - Type extending Model * @param {OperationKeys} operation - The operation being performed (create, read, update, delete) * @param {Constructor<M>} model - The model constructor * @param {Partial<NanoFlags>} flags - Partial flags to be merged * @return {Promise<NanoFlags>} Complete flags for the operation */ protected flags<M extends Model>(operation: OperationKeys, model: Constructor<M>, flags: Partial<NanoFlags>, ...args: any[]): Promise<NanoFlags>; /** * @description Creates a new NanoDispatch instance * @summary Returns a dispatcher for handling Nano-specific operations * @return {NanoDispatch} A new NanoDispatch instance */ protected Dispatch(): NanoDispatch; repository<R extends Repository<any, Adapter<NanoConfig, DocumentScope<any>, MangoQuery, Context<NanoFlags>>>>(): Constructor<R>; /** * @description Creates database indexes for models * @summary Generates and creates indexes in the Nano database based on the provided models * @template M - Type extending Model * @param models - Model constructors to create indexes for * @return {Promise<void>} A promise that resolves when all indexes are created * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant G as generateIndexes * participant DB as Nano Database * A->>G: generateIndexes(models) * G-->>A: indexes * loop For each index * A->>DB: createIndex(index) * DB-->>A: response * Note over A: Check if index already exists * alt Index exists * A-->>A: throw ConflictError * end * end */ protected index<M extends Model>(...models: Constructor<M>[]): Promise<void>; /** * @description Creates a new document in the database * @summary Inserts a new document into the Nano database with the provided data * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @param {Record<string, any>} model - The document data to insert * @return {Promise<Record<string, any>>} A promise that resolves to the created document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: insert(model) * alt Success * DB-->>A: response with ok=true * A->>A: assignMetadata(model, response.rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: response with ok=false * A-->>A: throw InternalError * end */ create<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>>; /** * @description Creates multiple documents in the database * @summary Inserts multiple documents into the Nano database in a single bulk operation * @param {string} tableName - The name of the table/collection * @param {string[] | number[]} ids - Array of document identifiers * @param models - Array of document data to insert * @return A promise that resolves to an array of created documents with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: bulk({docs: models}) * alt Success * DB-->>A: response array * A->>A: Check if all responses have no errors * alt All OK * A->>A: assignMultipleMetadata(models, revs) * A-->>A: return documents with metadata * else Some errors * A->>A: Collect error messages * A-->>A: throw InternalError with collected messages * end * else Error * DB-->>A: error * A-->>A: throw parseError(e) * end */ createAll<M extends Model>(tableName: Constructor<M>, ids: PrimaryKeyType[], models: Record<string, any>[], ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>[]>; /** * @description Retrieves a document from the database * @summary Fetches a single document from the Nano database by its ID * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @return {Promise<Record<string, any>>} A promise that resolves to the retrieved document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>A: generateId(tableName, id) * A->>DB: get(_id) * alt Success * DB-->>A: record * A->>A: assignMetadata(record, record._rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * end */ read<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>>; /** * @description Retrieves multiple documents from the database * @summary Fetches multiple documents from the Nano database by their IDs in a single operation * @param {string} tableName - The name of the table/collection * @param {Array<string | number | bigint>} ids - Array of document identifiers * @return A promise that resolves to an array of retrieved documents with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>A: Map ids to generateId(tableName, id) * A->>DB: fetch({keys: mappedIds}, {}) * DB-->>A: results * A->>A: Process each result row * loop For each row * alt Row has error * A-->>A: throw InternalError * else Row has document * A->>A: assignMetadata(doc, doc._rev) * else No document * A-->>A: throw InternalError * end * end * A-->>A: return documents with metadata */ readAll<M extends Model>(tableName: Constructor<M>, ids: (string | number | bigint)[], ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>[]>; /** * @description Updates a document in the database * @summary Updates an existing document in the Nano database with the provided data * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @param {Record<string, any>} model - The updated document data * @return {Promise<Record<string, any>>} A promise that resolves to the updated document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: insert(model) * alt Success * DB-->>A: response with ok=true * A->>A: assignMetadata(model, response.rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: response with ok=false * A-->>A: throw InternalError * end */ update<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, model: Record<string, any>, ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>>; /** * @description Updates multiple documents in the database * @summary Performs a bulk update operation on the Nano database for the provided documents * @param {string} tableName - The name of the table/collection * @param {Array<string|number>} ids - Array of document identifiers * @param {Promise<Array<Record<string, any>>>} models - Array of updated document data * @return {Promise<Promise<Array<Record<string, any>>>>} A promise that resolves to the updated documents with metadata */ updateAll<M extends Model>(tableName: Constructor<M>, ids: PrimaryKeyType[], models: Record<string, any>[], ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>[]>; /** * @description Deletes a document from the database * @summary Removes a single document from the Nano database by its ID and returns the deleted document metadata * @param {string} tableName - The name of the table/collection * @param {string|number} id - The document identifier * @return {Promise<Record<string, any>>} A promise that resolves to the deleted document with metadata */ delete<M extends Model>(tableName: Constructor<M>, id: PrimaryKeyType, ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>>; /** * @description Deletes multiple documents from the database * @summary Performs a bulk delete operation for the provided IDs and returns the deleted documents metadata * @param {string} tableName - The name of the table/collection * @param {Array<string|number|bigint>} ids - Array of document identifiers to delete * @return {Promise<Array<Record<string, any>>>} A promise resolving to the deleted documents with metadata */ deleteAll<M extends Model>(tableName: Constructor<M>, ids: PrimaryKeyType[], ...args: ContextualArgs<Context<NanoFlags>>): Promise<Record<string, any>[]>; /** * @description Executes a raw Mango query against the database * @summary Runs a Mango query using Nano's find API and optionally returns only the documents array * @template R - The expected response or document array type * @param {MangoQuery} rawInput - The Mango query to execute * @param {boolean} [docsOnly=true] - Whether to return only the docs array or the full response * @return {Promise<R>} A promise that resolves to the query result, shaped according to docsOnly */ raw<R>(rawInput: MangoQuery, docsOnly?: boolean, ...args: ContextualArgs<Context<NanoFlags>>): Promise<R>; view<R>(ddoc: string, viewName: string, options: Record<string, any>, ..._args: ContextualArgs<Context<NanoFlags>>): Promise<ViewResponse<R>>; /** * @description Establishes a connection to a Nano (CouchDB) server * @summary Creates and returns a Nano ServerScope using the given credentials, host, and protocol * @param {string} user - Username used for authentication * @param {string} pass - Password used for authentication * @param {string} [host="localhost:5984"] - Host and port of the CouchDB server * @param {("http"|"https")} [protocol="http"] - Protocol to use for the connection * @return {ServerScope} The Nano ServerScope connection */ static connect(user: string, pass: string, host?: string, protocol?: "http" | "https", agent?: any): ServerScope; /** * @description Creates a new database on the Nano server * @summary Creates a new database with the specified name on the connected Nano server * @param {ServerScope} con - The Nano server connection * @param {string} name - The name of the database to create * @return {Promise<void>} A promise that resolves when the database is created * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Server * A->>DB: db.create(name) * alt Success * DB-->>A: result with ok=true * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: result with ok=false * A-->>A: throw parseError(error, reason) * end */ static createDatabase(con: ServerScope, name: string): Promise<void>; /** * @description Deletes a database from the Nano server * @summary Removes an existing database with the specified name from the connected Nano server * @param {ServerScope} con - The Nano server connection * @param {string} name - The name of the database to delete * @return {Promise<void>} A promise that resolves when the database is deleted * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Server * A->>DB: db.destroy(name) * alt Success * DB-->>A: result with ok=true * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: result with ok=false * A-->>A: throw InternalError * end */ static deleteDatabase(con: ServerScope, name: string): Promise<void>; /** * @description Closes the keep-alive agent associated with a Nano connection * @summary Destroys the custom HTTP/HTTPS agent that was created during connection setup * @param {ServerScope} con - The Nano server connection whose agent should be destroyed */ static closeConnection(con?: ServerScope | null): void; /** * @description Creates a new user and grants access to a database * @summary Creates a new user in the Nano server and configures security to grant the user access to a specific database * @param {ServerScope} con - The Nano server connection * @param {string} dbName - The name of the database to grant access to * @param {string} user - The username to create * @param {string} pass - The password for the new user * @param {string[]} [roles=["reader", "writer"]] - The roles to assign to the user * @return {Promise<void>} A promise that resolves when the user is created and granted access * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant U as _users Database * participant S as Security API * A->>A: Create user object * A->>U: insert(user) * alt Success * U-->>A: response with ok=true * A->>S: PUT _security with user permissions * alt Security Success * S-->>A: security response with ok=true * else Security Failure * S-->>A: security response with ok=false * A-->>A: throw InternalError * end * else Error * U-->>A: error * A-->>A: throw parseError(e) * else Not OK * U-->>A: response with ok=false * A-->>A: throw InternalError * end */ static createUser(con: ServerScope, dbName: string, user: string, pass: string, roles?: string[]): Promise<void>; /** * @description Deletes a user from the Nano server * @summary Removes an existing user from the Nano server * @param {ServerScope} con - The Nano server connection * @param {string} dbName - The name of the database (used for logging purposes) * @param {string} user - The username to delete * @return {Promise<void>} A promise that resolves when the user is deleted * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant U as _users Database * A->>A: Generate user ID * A->>U: get(id) * U-->>A: user document * A->>U: destroy(id, user._rev) * alt Success * U-->>A: success response * else Error * U-->>A: error * A-->>A: throw parseError(e) * end */ static deleteUser(con: ServerScope, dbName: string, user: string): Promise<void>; /** * @description Sets up decorations for Nano-specific model properties * @summary Configures decorators for created_by and updated_by fields in models to be automatically * populated with the user from the context when documents are created or updated * @return {void} * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant D as Decoration * participant R as Repository * A->>R: key(PersistenceKeys.CREATED_BY) * R-->>A: createdByKey * A->>D: flavouredAs("nano") * A->>D: for(createdByKey) * A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata) * A->>D: apply() * A->>R: key(PersistenceKeys.UPDATED_BY) * R-->>A: updatedByKey * A->>D: flavouredAs("nano") * A->>D: for(updatedByKey) * A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata) * A->>D: apply() */ static decoration(): void; }