Source

utils/utils.ts

import { CrudOperations, InternalError } from "@decaf-ts/db-decorators";
import { OperationKeys } from "@decaf-ts/db-decorators";
import type { ModelConstructor } from "@decaf-ts/decorator-validation";
import { Constructor, Metadata } from "@decaf-ts/decoration";
import { MigrationRuleError } from "../persistence/errors";

export function injectableServiceKey(
  name: string | symbol | Constructor
): string {
  if (!name) throw new InternalError(`No name provided`);
  return typeof name === "string"
    ? name.replaceAll(".", "-")
    : Metadata.Symbol(Metadata.constr(name as ModelConstructor<any>))
        .toString()
        .replaceAll(".", "-");
}

export function promiseSequence<T>(
  tasks: (() => Promise<T>)[],
  continueOnError?: false
): Promise<T[]>;
export function promiseSequence<T>(
  tasks: (() => Promise<T>)[],
  continueOnError: true
): Promise<PromiseSettledResult<T>[]>;
export async function promiseSequence<T>(
  tasks: (() => Promise<T>)[],
  continueOnError = false
): Promise<T[] | PromiseSettledResult<T>[]> {
  if (!continueOnError) {
    const results: T[] = [];
    for (const task of tasks) {
      results.push(await task());
    }
    return results;
  }

  const settled: PromiseSettledResult<T>[] = [];
  for (const task of tasks) {
    try {
      settled.push({ status: "fulfilled", value: await task() });
    } catch (reason) {
      settled.push({ status: "rejected", reason });
    }
  }
  return settled;
}

export function isOperationBlocked(
  ModelConstructor: ModelConstructor<any>,
  op: CrudOperations
): boolean {
  const { handler, args }: { handler: any; args: CrudOperations[] } =
    (Metadata.get(
      ModelConstructor as any,
      OperationKeys.REFLECT + OperationKeys.BLOCK
    ) || {}) as {
      handler: (
        operations: CrudOperations[],
        operation: CrudOperations
      ) => boolean;
      args: any[];
    };

  return !handler ? false : (handler(...args, op) ?? false);
}

/**
 * @description Normalizes imports to handle both CommonJS and ESModule formats.
 * @summary Utility function to handle module import differences between formats.
 *
 * @template T - Type of the imported module.
 * @param {Promise<T>} importPromise - Promise returned by dynamic import.
 * @return {Promise<T>} Normalized module.
 *
 * @function normalizeImport
 *
 * @memberOf module:core
 */
export async function normalizeImport<T>(
  importPromise: Promise<T>
): Promise<T> {
  // CommonJS's `module.exports` is wrapped as `default` in ESModule.
  return importPromise.then((m: any) => (m.default || m) as T);
}

export function prefixMethod(
  obj: any,
  after: (...args: any[]) => any,
  prefix: (...args: any[]) => any,
  afterName?: string
) {
  async function wrapper(this: any, ...args: any[]) {
    let results: any[];
    try {
      results = await Promise.resolve(prefix.call(this, ...args));
    } catch (e: unknown) {
      if (e instanceof MigrationRuleError) return;
      throw e;
    }
    return Promise.resolve(after.apply(this, results));
  }

  const wrapped = wrapper.bind(obj);
  const name = afterName ? afterName : after.name;
  Object.defineProperty(wrapped, "name", {
    enumerable: true,
    configurable: true,
    writable: false,
    value: name,
  });
  obj[name] = wrapped;
}