Source

utils.ts

import { OrderDirection, PersistenceKeys } from "@decaf-ts/core";
import { CouchDBKeys } from "./constants";
import { DefaultSeparator } from "@decaf-ts/db-decorators";
import { CouchDBOperator } from "./query/constants";
import { CreateIndexRequest, MangoSelector, SortOrder } from "./types";

/**
 * @description Re-authenticates a connection to CouchDB
 * @summary Refreshes the authentication for a CouchDB connection using the provided credentials
 * @param {any} con - The CouchDB connection object
 * @param {string} user - The username for authentication
 * @param {string} pass - The password for authentication
 * @return {Promise<any>} A promise that resolves to the authentication result
 * @function reAuth
 * @memberOf module:for-couchdb
 */
export async function reAuth(con: any, user: string, pass: string) {
  return con.auth(user, pass);
}

/**
 * @description Wraps a CouchDB database connection with automatic re-authentication
 * @summary Creates a proxy around a CouchDB database connection that automatically re-authenticates before each operation
 * @param {any} con - The CouchDB connection object
 * @param {string} dbName - The name of the database to use
 * @param {string} user - The username for authentication
 * @param {string} pass - The password for authentication
 * @return {any} The wrapped database connection object
 * @function wrapDocumentScope
 * @memberOf module:for-couchdb
 * @mermaid
 * sequenceDiagram
 *   participant Client
 *   participant wrapDocumentScope
 *   participant DB
 *   participant reAuth
 *   
 *   Client->>wrapDocumentScope: con, dbName, user, pass
 *   wrapDocumentScope->>DB: con.use(dbName)
 *   Note over wrapDocumentScope: Wrap DB methods with re-auth
 *   
 *   loop For each method (insert, get, put, destroy, find)
 *     wrapDocumentScope->>wrapDocumentScope: Store original method
 *     wrapDocumentScope->>wrapDocumentScope: Define new method with re-auth
 *   end
 *   
 *   wrapDocumentScope->>wrapDocumentScope: Add NATIVE property with con value
 *   wrapDocumentScope-->>Client: Return wrapped DB
 *   
 *   Note over Client: Later when client uses DB methods
 *   Client->>DB: Any wrapped method call
 *   DB->>reAuth: Authenticate before operation
 *   reAuth-->>DB: Authentication complete
 *   DB->>DB: Call original method
 *   DB-->>Client: Return result
 */
export function wrapDocumentScope(
  con: any,
  dbName: string,
  user: string,
  pass: string
): any {
  const db = con.use(dbName);
  ["insert", "get", "put", "destroy", "find"].forEach((k) => {
    const original = (db as Record<string, any>)[k];
    Object.defineProperty(db, k, {
      enumerable: false,
      configurable: true,
      value: async (...args: any[]) => {
        await reAuth(con, user, pass);
        return original.call(db, ...args);
      },
    });
  });
  Object.defineProperty(db, CouchDBKeys.NATIVE, {
    enumerable: false,
    configurable: false,
    writable: false,
    value: con,
  });
  return db;
}

/**
 * @description Tests if an attribute name is reserved in CouchDB
 * @summary Checks if an attribute name starts with an underscore, which indicates it's a reserved attribute in CouchDB
 * @param {string} attr - The attribute name to test
 * @return {RegExpMatchArray|null} The match result or null if no match
 * @function testReservedAttributes
 * @memberOf module:for-couchdb
 */
export function testReservedAttributes(attr: string) {
  const regexp = /^_.*$/g;
  return attr.match(regexp);
}

/**
 * @description Generates a name for a CouchDB index
 * @summary Creates a standardized name for a CouchDB index based on the table, attribute, compositions, and order
 * @param {string} attribute - The primary attribute for the index
 * @param {string} tableName - The name of the table
 * @param {string[]} [compositions] - Optional additional attributes to include in the index
 * @param {OrderDirection} [order] - Optional sort order for the index
 * @param {string} [separator=DefaultSeparator] - The separator to use between parts of the index name
 * @return {string} The generated index name
 * @function generateIndexName
 * @memberOf module:for-couchdb
 */
export function generateIndexName(
  attribute: string,
  tableName: string,
  compositions?: string[],
  order?: OrderDirection,
  separator = DefaultSeparator
): string {
  const attr = [PersistenceKeys.INDEX, tableName, attribute];
  if (compositions) attr.push(...compositions);
  if (order) attr.push(order);
  return attr.join(separator);
}

/**
 * @description Generates a CouchDB index configuration
 * @summary Creates a complete CreateIndexRequest object for defining a CouchDB index based on specified parameters
 * @param {string} attribute - The primary attribute for the index
 * @param {string} tableName - The name of the table
 * @param {string[]} [compositions] - Optional additional attributes to include in the index
 * @param {OrderDirection} [order] - Optional sort order for the index
 * @param {string} [separator=DefaultSeparator] - The separator to use between parts of the index name
 * @return {CreateIndexRequest} The complete index configuration object
 * @function generateIndexDoc
 * @memberOf module:for-couchdb
 * @mermaid
 * sequenceDiagram
 *   participant Caller
 *   participant generateIndexDoc
 *   participant generateIndexName
 *   
 *   Caller->>generateIndexDoc: attribute, tableName, compositions, order, separator
 *   
 *   Note over generateIndexDoc: Create partial filter selector
 *   generateIndexDoc->>generateIndexDoc: Set up filter for tableName
 *   
 *   alt order is specified
 *     Note over generateIndexDoc: Create ordered fields array
 *     generateIndexDoc->>generateIndexDoc: Create orderProp for attribute
 *     generateIndexDoc->>generateIndexDoc: Map compositions to ordered props
 *     generateIndexDoc->>generateIndexDoc: Create sortedTable for table field
 *     generateIndexDoc->>generateIndexDoc: Combine all ordered fields
 *   else
 *     Note over generateIndexDoc: Create simple fields array
 *     generateIndexDoc->>generateIndexDoc: Use attribute, compositions, and table as strings
 *   end
 *   
 *   generateIndexDoc->>generateIndexName: Generate index name
 *   generateIndexName-->>generateIndexDoc: Return name
 *   
 *   Note over generateIndexDoc: Create final index request
 *   generateIndexDoc-->>Caller: Return CreateIndexRequest
 */
export function generateIndexDoc(
  attribute: string,
  tableName: string,
  compositions?: string[],
  order?: OrderDirection,
  separator = DefaultSeparator
): CreateIndexRequest {
  const partialFilterSelector: MangoSelector = {};
  partialFilterSelector[CouchDBKeys.TABLE] = {} as MangoSelector;
  (partialFilterSelector[CouchDBKeys.TABLE] as MangoSelector)[
    CouchDBOperator.EQUAL
  ] = tableName;
  let fields: SortOrder[];
  if (order) {
    const orderProp: SortOrder = {};
    orderProp[attribute] = order as "asc" | "desc";
    const sortedCompositions: SortOrder[] = (compositions || []).map((c) => {
      const r: SortOrder = {};
      r[c] = order as "asc" | "desc";
      return r;
    });
    const sortedTable: SortOrder = {};
    sortedTable[CouchDBKeys.TABLE] = order as "asc" | "desc";
    fields = [orderProp, ...sortedCompositions, sortedTable];
  } else {
    fields = [attribute, ...(compositions || []), CouchDBKeys.TABLE];
  }
  const name = generateIndexName(
    attribute,
    tableName,
    compositions,
    order,
    separator
  );
  return {
    index: {
      fields: fields,
      // partial_filter_selector: partialFilterSelector,
    },
    ddoc: [name, CouchDBKeys.DDOC].join(separator),
    name: name,
  };
}