Source

indexes/generator.ts

import {
  IndexMetadata,
  OrderDirection,
  PersistenceKeys,
  Repository,
} from "@decaf-ts/core";
import { CouchDBKeys } from "../constants";
import { DefaultSeparator } from "@decaf-ts/db-decorators";
import { Constructor, Model } from "@decaf-ts/decorator-validation";
import { CouchDBOperator } from "../query/constants";
import { CreateIndexRequest } from "../types";

/**
 * @description Generates a name for a CouchDB index
 * @summary Creates a standardized name for a CouchDB index by combining name parts, compositions, and direction
 * @param {string[]} name - Array of name parts for the index
 * @param {OrderDirection} [direction] - Optional sort direction for the index
 * @param {string[]} [compositions] - Optional additional attributes to include in the index name
 * @param {string} [separator=DefaultSeparator] - The separator to use between parts of the index name
 * @return {string} The generated index name
 * @memberOf module:for-couchdb
 */
function generateIndexName(
  name: string[],
  direction?: OrderDirection,
  compositions?: string[],
  separator = DefaultSeparator
) {
  return [
    ...name.map((n) => (n === CouchDBKeys.TABLE ? "table" : n)),
    ...(compositions || []),
    ...(direction ? [direction] : []),
    CouchDBKeys.INDEX,
  ].join(separator);
}

/**
 * @description Generates CouchDB index configurations for models
 * @summary Creates a set of CouchDB index configurations based on the metadata of the provided models
 * @template M - The model type that extends Model
 * @param models - Array of model constructors to generate indexes for
 * @return {CreateIndexRequest[]} Array of CouchDB index configurations
 * @function generateIndexes
 * @memberOf module:for-couchdb
 * @mermaid
 * sequenceDiagram
 *   participant Caller
 *   participant generateIndexes
 *   participant generateIndexName
 *   participant Repository
 *
 *   Caller->>generateIndexes: models
 *
 *   Note over generateIndexes: Create base table index
 *   generateIndexes->>generateIndexName: [CouchDBKeys.TABLE]
 *   generateIndexName-->>generateIndexes: tableName
 *   generateIndexes->>generateIndexes: Create table index config
 *
 *   loop For each model
 *     generateIndexes->>Repository: Get indexes metadata
 *     Repository-->>generateIndexes: index metadata
 *
 *     loop For each index in metadata
 *       Note over generateIndexes: Extract index properties
 *       generateIndexes->>Repository: Get table name
 *       Repository-->>generateIndexes: tableName
 *
 *       Note over generateIndexes: Define nested generate function
 *
 *       generateIndexes->>generateIndexes: Call generate() for default order
 *       Note over generateIndexes: Create index name and config
 *
 *       alt Has directions
 *         loop For each direction
 *           generateIndexes->>generateIndexes: Call generate(direction)
 *           Note over generateIndexes: Create ordered index config
 *         end
 *       end
 *     end
 *   end
 *
 *   generateIndexes-->>Caller: Array of index configurations
 */
export function generateIndexes<M extends Model>(
  models: Constructor<M>[]
): CreateIndexRequest[] {
  const tableName = generateIndexName([CouchDBKeys.TABLE]);
  const indexes: Record<string, CreateIndexRequest> = {};
  indexes[tableName] = {
    index: {
      fields: [CouchDBKeys.TABLE],
    },
    name: tableName,
    ddoc: tableName,
    type: "json",
  };

  models.forEach((m) => {
    const ind: Record<string, IndexMetadata> = Repository.indexes(m);
    Object.entries(ind).forEach(([key, value]) => {
      const k = Object.keys(value)[0];
      // eslint-disable-next-line prefer-const
      let { directions, compositions } = (value as any)[k];
      const tableName = Repository.table(m);
      compositions = compositions || [];

      function generate(sort?: OrderDirection) {
        const name = [
          tableName,
          key,
          ...(compositions as []),
          PersistenceKeys.INDEX,
        ].join(DefaultSeparator);

        indexes[name] = {
          index: {
            fields: [key, ...(compositions as []), CouchDBKeys.TABLE].reduce(
              (accum: any[], el) => {
                if (sort) {
                  const res: any = {};
                  res[el] = sort;
                  accum.push(res);
                } else {
                  accum.push(el);
                }
                return accum;
              },
              []
            ),
          },
          name: name,
          ddoc: name,
          type: "json",
        };
        if (!sort) {
          const tableFilter: Record<string, any> = {};
          tableFilter[CouchDBKeys.TABLE] = {};
          tableFilter[CouchDBKeys.TABLE][CouchDBOperator.EQUAL] = tableName;
          indexes[name].index.partial_filter_selector = tableFilter;
        }
      }

      generate();
      if (directions)
        (directions as unknown as OrderDirection[]).forEach((d) => generate(d));
    });
  });
  return Object.values(indexes);
}