Source

registers/registerCMD.ts

/**
 * @module commands/register
 */

import { map } from "@thi.ng/transducers"
import { isFunction } from "@thi.ng/checks"

import { CMD_SUB$, CMD_ARGS, CMD_RESO, CMD_ERRO, CMD_SRC$, CMD_WORK } from "@-0/keys"

import { command$, out$ } from "../core"

import { xKeyError, stringify_w_functions, diff_keys } from "@-0/utils"

const feedCMD$fromSource$ = cmd => {
  const sub$ = cmd[CMD_SUB$]
  const args = cmd[CMD_ARGS]
  const isFn = isFunction(args)
  const deliver = x => ({ [CMD_SUB$]: sub$, [CMD_ARGS]: args(x) })
  const delivery = { [CMD_SUB$]: sub$, [CMD_ARGS]: args }

  const feed = $ => (isFn ? map(x => $.next(deliver(x))) : map(() => $.next(delivery)))

  return cmd[CMD_SRC$].subscribe(feed(command$))
}

let registered = new Map()

const err_str = "command Registration `registerCMD`"
/**
 *
 *
 * Takes a Command object with some additional information
 * and returns a Command `run`able in a Task or as-is.
 *
 * ### Example
 *
 * ```js
 * const genie = {
 *   sub$: "GENIE",
 *   args: "your wish"
 *   work: x => console.log("🧞 says:", x, "is my command")
 * }
 *
 * const GENIE = registerCMD(genie)
 *
 * run(GENIE)
 * // 🧞 says: your wish is my command
 * ```
 *
 * A Command object can have four keys:
 *  1. `sub$` (required)
 *  2. `args` (optional, sets default) during registration
 *  3. `work` (required)
 *  4. `src$` (optional, enables stream to feed Command)
 *
 */
export function registerCMD(command) {
  const sub$ = command[CMD_SUB$]
  const args = command[CMD_ARGS]
  const erro = command[CMD_ERRO]
  const reso = command[CMD_RESO]
  const src$ = command[CMD_SRC$]
  const work = command[CMD_WORK]

  const knowns = [CMD_SUB$, CMD_ARGS, CMD_RESO, CMD_ERRO, CMD_SRC$, CMD_WORK]
  const [unknowns] = diff_keys(knowns, command)
  // console.log({ knowns, unknowns })

  if (unknowns.length > 0) {
    throw new Error(xKeyError(err_str, command, unknowns, sub$, undefined))
  }

  if (src$) feedCMD$fromSource$(command)

  // @ts-ignore
  out$.subscribeTopic(
    sub$,
    { next: work, error: console.warn },
    map(puck => puck[CMD_ARGS])
  )

  const CMD = reso
    ? {
        [CMD_SUB$]: sub$,
        [CMD_ARGS]: args,
        [CMD_RESO]: reso,
        [CMD_ERRO]: erro
      }
    : { [CMD_SUB$]: sub$, [CMD_ARGS]: args }
  // Set.add not supported by IE
  if (registered.set) {
    if (registered.has(sub$)) {
      throw new Error(
        `

  🔥 duplicate \`sub$\` value detected in Command:
  ${stringify_w_functions(CMD)}
  existing registered Commands:
  ${JSON.stringify([...registered.keys()], null, 2)}
  🔥 Please use a different/unique Command \`sub$\` string

  🔎 Inspect existing Commands using js Map API \`registerCMD.all\`
  🔎 (\`registerCMD.all.entries()\`, \`registerCMD.all.has("X")\`, etc.)

        `
      )
    }
    registered.set(sub$, CMD)
  }
  return CMD
}
/**
 * enables inspection of the existing Command registrations
 * if using Chrome, there's an additional advantage of being
 * able to find the `[[FunctionLocation]]` of the Command
 */
registerCMD.all = registered