[
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: ni55an\nopen_collective: rete\n"
  },
  {
    "path": ".github/workflows/build-push.yml",
    "content": "name: Build and Push\nrun-name: Build and Push to dist/${{ github.ref_name }}\n\non:\n  workflow_dispatch:\n\njobs:\n  push:\n    uses: retejs/.github/.github/workflows/build-push.yml@main\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches: [ \"main\", \"beta\" ]\n\njobs:\n  ci:\n    uses: retejs/.github/.github/workflows/ci.yml@main\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: CodeQL\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ \"main\", \"beta\" ]\n  pull_request:\n    branches: [ \"main\", \"beta\" ]\n\njobs:\n  codeql:\n    uses: retejs/.github/.github/workflows/codeql.yml@main\n"
  },
  {
    "path": ".github/workflows/commit-linter.yml",
    "content": "name: Commit linter\n\non:\n  pull_request:\n    branches: [ \"main\", \"beta\" ]\n\njobs:\n  lint:\n    uses: retejs/.github/.github/workflows/commit-linter.yml@main\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ \"main\", \"beta\" ]\n\njobs:\n  release:\n    uses: retejs/.github/.github/workflows/release.yml@main\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close stale issues and PRs\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '30 1 * * 0'\n\njobs:\n  stale:\n    uses: retejs/.github/.github/workflows/stale.yml@main\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/update-docs.yml",
    "content": "name: Update docs\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ \"main\" ]\n\njobs:\n  pull:\n    uses: retejs/.github/.github/workflows/update-docs.yml@main\n    secrets: inherit\n    with:\n      filename: '1.rete'\n      package: rete\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.idea/\n.vscode/\n.sonarlint\n.scannerwork\n.nyc_output\n/coverage\nnpm-debug.log\n/dist\ndocs\n.rete-cli\n.sonar\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [2.0.6](https://github.com/retejs/rete/compare/v2.0.5...v2.0.6) (2025-06-30)\n\n\n### Bug Fixes\n\n* improve concurrent behavior of removeNode and removeConnection ([4229f1f](https://github.com/retejs/rete/commit/4229f1f0772a581bce7174080fb05870109bdd62))\n* optimize node and connection removal logic ([0696cba](https://github.com/retejs/rete/commit/0696cbaa1a1b3fd2a14cc6dd0312a783300d0fe7))\n\n## [2.0.5](https://github.com/retejs/rete/compare/v2.0.4...v2.0.5) (2024-08-30)\n\n\n### Bug Fixes\n\n* build ([1f852d9](https://github.com/retejs/rete/commit/1f852d9e491522264d97de396a30d5f0faf2a681))\n\n## [2.0.4](https://github.com/retejs/rete/compare/v2.0.3...v2.0.4) (2024-08-30)\n\n\n### Bug Fixes\n\n* update cli and fix linting errors ([d219f95](https://github.com/retejs/rete/commit/d219f95cb0d46f79e8d7f5d70e4afcd578f35455))\n\n## [2.0.3](https://github.com/retejs/rete/compare/v2.0.2...v2.0.3) (2024-01-27)\n\n\n### Bug Fixes\n\n* **build:** source maps ([121775c](https://github.com/retejs/rete/commit/121775c90aac1db449b30284ba996eed1da1a03c))\n\n## [2.0.2](https://github.com/retejs/rete/compare/v2.0.1...v2.0.2) (2023-07-24)\n\n\n### Bug Fixes\n\n* **editor:** return copy of array in getNodes/getConnections ([369e85e](https://github.com/retejs/rete/commit/369e85e5d661cca5e9de86326c2245c0e2f38d5b))\n\n## v2.0.0-beta.8\n\nImprove Scope typing: validate signals in `use` method, infer return type in `emit` method\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "Check out the [Code of Conduct](https://retejs.org/docs/code-of-conduct)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Check out the [Contribution guide](https://retejs.org/docs/contribution)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2023 \"Ni55aN\" Vitaliy Stoliarov\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "Rete.js\n====\n[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)\n[![Discord](https://img.shields.io/discord/1081223198055604244?color=%237289da&label=Discord)](https://discord.gg/cxSFkPZdsV)\n\n**JavaScript framework for visual programming**\n\n![rete logo](https://raw.githubusercontent.com/retejs/rete/assets/preview.svg)\n\n#StandWithUkraine 💙💛\n----\n\n#RussiaInvadedUkraine on 24 of February 2022, at 5.00 AM the armed forces of the Russian Federation  attacked Ukraine. Please, Stand with Ukraine, stay tuned for updates on Ukraine’s official sources and channels in English and support Ukraine in its fight for freedom and democracy in Europe.\n\nHelp to defend Ukraine — donate to [Ukraine’s main charity fund](https://savelife.in.ua/en/donate/)\n\nHelp to defend Ukraine — donate to the [fund of the National Bank of Ukraine](https://ukraine.ua/news/donate-to-the-nbu-fund/)\n\n\nIntroduction [🎥](https://youtu.be/xqPLa6P194A)\n----\n\n**Rete.js** is a framework for creating visual interfaces and workflows. It provides out-of-the-box solutions for visualization using various libraries and frameworks, as well as solutions for processing graphs based on dataflow and control flow approaches.\n\n\nGetting started\n----\n\nUse [Rete Kit](https://retejs.org/docs/development/rete-kit) to quickly set up a Rete.js application. It lets you select a stack (React.js, Vue.js or Angular, Svelte) and the set of features\n\n```bash\nnpx rete-kit app\n```\n\nAlternatively, you can follow the [complete guide](https://retejs.org/docs/getting-started/)\n\nDocumentation\n----\n\n- [Introduction](https://retejs.org/docs)\n- [Guides](https://retejs.org/docs/guides/basic)\n- [Examples](https://retejs.org/examples)\n\n## Sponsors\n\nThank you to all our sponsors! [Become a sponsor](https://opencollective.com/rete#sponsor)\n\n<a href=\"https://opencollective.com/rete#sponsors\" target=\"_blank\"><img src=\"https://opencollective.com/rete/sponsors.svg?width=890\"></a>\n\n## Backers\n\nThank you to all our backers! [Become a backer](https://opencollective.com/rete#backer)\n\n<a href=\"https://opencollective.com/rete#backers\" target=\"_blank\"><img src=\"https://opencollective.com/rete/backers.svg?width=890\"></a>\n\n\n## Contributors\n\nThis project exists thanks to all the people who contribute. [Contribute](https://retejs.org/docs/contribution).\n\n<a href=\"https://github.com/retejs/rete/graphs/contributors\"><img src=\"https://opencollective.com/rete/contributors.svg?width=890\" /></a>\n\n## License\n\n[MIT](https://github.com/retejs/rete/blob/main/LICENSE)\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import tseslint from 'typescript-eslint';\nimport configs from 'rete-cli/configs/eslint.mjs';\n\nexport default tseslint.config(\n  ...configs\n)"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rete\",\n  \"version\": \"2.0.6\",\n  \"description\": \"JavaScript framework\",\n  \"scripts\": {\n    \"build\": \"rete build -c rete.config.ts\",\n    \"postinstall\": \"node postinstall.js\",\n    \"doc\": \"rete doc\",\n    \"lint\": \"rete lint\",\n    \"test\": \"rete test\",\n    \"perf\": \"rete perf\"\n  },\n  \"author\": \"Vitaliy Stoliarov\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"dataflow\",\n    \"visual programming\",\n    \"node editor\",\n    \"rete\",\n    \"Rete.js\"\n  ],\n  \"homepage\": \"https://retejs.org\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/retejs/rete.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/retejs/rete/issues\"\n  },\n  \"devDependencies\": {\n    \"jest-environment-jsdom\": \"^29.1.2\",\n    \"rete-cli\": \"^2.1.0\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.21.0\"\n  }\n}\n"
  },
  {
    "path": "postinstall.js",
    "content": "function getRectangle(width, height, color) {\n  const line = new Array(width).fill(' ').join('')\n\n  return new Array(height).fill(color(line)).join('\\n')\n}\n\nfunction drawText(x, y, text) {\n  const save = '\\033[s'\n  const restore = '\\033[u'\n  const up = n => '\\033['+n+'A'\n  const right = n => '\\033['+n+'C'\n\n  return `${save}${up(y)}${right(x)}${text}${restore}`\n}\n\nfunction black(text) {\n  return '\\x1b[30m' + text + '\\x1b[0m'\n}\n\nfunction white(text) {\n  return '\\x1b[37m' + text + '\\x1b[0m'\n}\n\nfunction bgBlue(text) {\n  return '\\x1b[44m' + text + '\\x1b[0m'\n}\n\nfunction bgYellow(text) {\n  return '\\x1b[43m' + text + '\\x1b[0m'\n}\n\nconst topText = 'Stand with Ukraine'\nconst bottomText = 'Please check the Rete.js\\'s README for details'\n\nconst top = getRectangle(50, 5, bgBlue)\nconst bottom = getRectangle(50, 5, bgYellow)\n\n// eslint-disable-next-line max-len, no-console\nconsole.log(`${top}\\n${drawText(16, 3, white(bgBlue(topText)))}${bottom}\\n${drawText(2, 3, black(bgYellow(bottomText)))}`)\n"
  },
  {
    "path": "rete.config.ts",
    "content": "import { ReteOptions } from 'rete-cli'\nimport copy from 'rollup-plugin-copy'\n\nexport default <ReteOptions>{\n  input: 'src/index.ts',\n  name: 'Rete',\n  globals: {\n    crypto: 'crypto'\n  },\n  plugins: [\n    copy({\n      targets: [\n        { src: 'postinstall.js', dest: 'dist' }\n      ]\n    })\n  ]\n}\n"
  },
  {
    "path": "src/editor.ts",
    "content": "import { Scope } from './scope'\nimport { BaseSchemes } from './types'\n\n/**\n * Signal types produced by NodeEditor instance\n * @typeParam Scheme - The scheme type\n * @priority 10\n * @group Primary\n */\nexport type Root<Scheme extends BaseSchemes> =\n  | { type: 'nodecreate', data: Scheme['Node'] }\n  | { type: 'nodecreated', data: Scheme['Node'] }\n  | { type: 'noderemove', data: Scheme['Node'] }\n  | { type: 'noderemoved', data: Scheme['Node'] }\n  | { type: 'connectioncreate', data: Scheme['Connection'] }\n  | { type: 'connectioncreated', data: Scheme['Connection'] }\n  | { type: 'connectionremove', data: Scheme['Connection'] }\n  | { type: 'connectionremoved', data: Scheme['Connection'] }\n  | { type: 'clear' }\n  | { type: 'clearcancelled' }\n  | { type: 'cleared' }\n\n/**\n * The NodeEditor class is the entry class. It is used to create and manage nodes and connections.\n * @typeParam Scheme - The scheme type\n * @priority 7\n * @group Primary\n */\nexport class NodeEditor<Scheme extends BaseSchemes> extends Scope<Root<Scheme>> {\n  private nodes: Scheme['Node'][] = []\n  private connections: Scheme['Connection'][] = []\n\n  constructor() {\n    super('NodeEditor')\n  }\n\n  /**\n   * Get a node by id\n   * @param id - The node id\n   * @returns The node or undefined\n   */\n  public getNode(id: Scheme['Node']['id']) {\n    return this.nodes.find(node => node.id === id)\n  }\n\n  /**\n   * Get all nodes\n   * @returns Copy of array with nodes\n   */\n  public getNodes() {\n    return this.nodes.slice()\n  }\n\n  /**\n   * Get all connections\n   * @returns Copy of array with onnections\n   */\n  public getConnections() {\n    return this.connections.slice()\n  }\n\n  /**\n   * Get a connection by id\n   * @param id - The connection id\n   * @returns The connection or undefined\n   */\n  public getConnection(id: Scheme['Connection']['id']) {\n    return this.connections.find(connection => connection.id === id)\n  }\n\n  /**\n   * Add a node\n   * @param data - The node data\n   * @returns Whether the node was added\n   * @throws If the node has already been added\n   * @emits nodecreate\n   * @emits nodecreated\n   */\n  async addNode(data: Scheme['Node']) {\n    if (this.getNode(data.id)) throw new Error('node has already been added')\n\n    if (!await this.emit({ type: 'nodecreate', data })) return false\n\n    this.nodes.push(data)\n\n    await this.emit({ type: 'nodecreated', data })\n    return true\n  }\n\n  /**\n   * Add a connection\n   * @param data - The connection data\n   * @returns Whether the connection was added\n   * @throws If the connection has already been added\n   * @emits connectioncreate\n   * @emits connectioncreated\n   */\n  async addConnection(data: Scheme['Connection']) {\n    if (this.getConnection(data.id)) throw new Error('connection has already been added')\n\n    if (!await this.emit({ type: 'connectioncreate', data })) return false\n\n    this.connections.push(data)\n\n    await this.emit({ type: 'connectioncreated', data })\n    return true\n  }\n\n  /**\n   * Remove a node\n   * @param id - The node id\n   * @returns Whether the node was removed\n   * @throws If the node cannot be found\n   * @emits noderemove\n   * @emits noderemoved\n   */\n  async removeNode(id: Scheme['Node']['id']) {\n    const node = this.nodes.find(n => n.id === id)\n\n    if (!node) throw new Error('cannot find node')\n\n    if (!await this.emit({ type: 'noderemove', data: node })) return false\n\n    const index = this.nodes.indexOf(node)\n\n    this.nodes.splice(index, 1)\n\n    await this.emit({ type: 'noderemoved', data: node })\n    return true\n  }\n\n  /**\n   * Remove a connection\n   * @param id - The connection id\n   * @returns Whether the connection was removed\n   * @throws If the connection cannot be found\n   * @emits connectionremove\n   * @emits connectionremoved\n   */\n  async removeConnection(id: Scheme['Connection']['id']) {\n    const connection = this.connections.find(c => c.id === id)\n\n    if (!connection) throw new Error('cannot find connection')\n\n    if (!await this.emit({ type: 'connectionremove', data: connection })) return false\n\n    const index = this.connections.indexOf(connection)\n\n    this.connections.splice(index, 1)\n\n    await this.emit({ type: 'connectionremoved', data: connection })\n    return true\n  }\n\n  /**\n   * Clear all nodes and connections\n   * @returns Whether the editor was cleared\n   * @emits clear\n   * @emits clearcancelled\n   * @emits cleared\n   */\n  async clear() {\n    if (!await this.emit({ type: 'clear' })) {\n      await this.emit({ type: 'clearcancelled' })\n      return false\n    }\n\n    for (const connection of this.connections.slice()) await this.removeConnection(connection.id)\n    for (const node of this.nodes.slice()) await this.removeNode(node.id)\n\n    await this.emit({ type: 'cleared' })\n    return true\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export * from './editor'\nexport * as ClassicPreset from './presets/classic'\nexport type { CanAssignSignal, NestedScope, Pipe, ScopeAsParameter } from './scope'\nexport { Scope, Signal } from './scope'\nexport * from './types'\nexport * from './utils'\n"
  },
  {
    "path": "src/presets/classic.ts",
    "content": "/**\n * Contains classes for classic scheme such as Node, Input, Output, Control, Socket, Connection\n * @module\n * @group Primary\n */\n\nimport { ConnectionBase, NodeBase } from '../types'\nimport { getUID } from '../utils'\n\ntype PortId = string\n\n/**\n * The socket class\n * @priority 7\n */\nexport class Socket {\n  /**\n   * @constructor\n   * @param name Name of the socket\n   */\n  constructor(public name: string) {\n\n  }\n}\n\n/**\n * General port class\n */\nexport class Port<S extends Socket> {\n  /**\n   * Port id, unique string generated by `getUID` function\n   */\n  id: PortId\n  /**\n   * Port index, used for sorting ports. Default is `0`\n   */\n  index?: number\n\n  /**\n   * @constructor\n   * @param socket Socket instance\n   * @param label Label of the port\n   * @param multipleConnections Whether the output port can have multiple connections\n   */\n  constructor(public socket: S, public label?: string, public multipleConnections?: boolean) {\n    this.id = getUID()\n  }\n}\n\n/**\n * The input port class\n * @priority 6\n */\nexport class Input<S extends Socket> extends Port<S> {\n  /**\n   * Control instance\n   */\n  control: Control | null = null\n  /**\n   * Whether the control is visible. Can be managed dynamically by extensions. Default is `true`\n   */\n  showControl = true\n\n  /**\n   * @constructor\n   * @param socket Socket instance\n   * @param label Label of the input port\n   * @param multipleConnections Whether the output port can have multiple connections. Default is `false`\n   */\n  constructor(public socket: S, public label?: string, public multipleConnections?: boolean) {\n    super(socket, label, multipleConnections)\n  }\n\n  /**\n   * Add control to the input port\n   * @param control Control instance\n   */\n  addControl(control: Control) {\n    if (this.control) throw new Error('control already added for this input')\n    this.control = control\n  }\n\n  /**\n   * Remove control from the input port\n   */\n  removeControl() {\n    this.control = null\n  }\n}\n\n/**\n * The output port class\n * @priority 5\n */\nexport class Output<S extends Socket> extends Port<S> {\n  /**\n   * @constructor\n   * @param socket Socket instance\n   * @param label Label of the output port\n   * @param multipleConnections Whether the output port can have multiple connections. Default is `true`\n   */\n  constructor(socket: S, label?: string, multipleConnections?: boolean) {\n    super(socket, label, multipleConnections !== false)\n  }\n}\n\n/**\n * General control class\n * @priority 5\n */\nexport class Control {\n  /**\n   * Control id, unique string generated by `getUID` function\n   */\n  id: string\n  /**\n   * Control index, used for sorting controls. Default is `0`\n   */\n  index?: number\n\n  constructor() {\n    this.id = getUID()\n  }\n}\n\n/**\n * Input control options\n */\ntype InputControlOptions<N> = {\n  /** Whether the control is readonly. Default is `false` */\n  readonly?: boolean\n  /** Initial value of the control */\n  initial?: N\n  /** Callback function that is called when the control value changes */\n  change?: (value: N) => void\n}\n/**\n * The input control class\n * @example new InputControl('text', { readonly: true, initial: 'hello' })\n */\nexport class InputControl<T extends 'text' | 'number', N = T extends 'text' ? string : number> extends Control {\n  value?: N\n  readonly: boolean\n\n  /**\n   * @constructor\n   * @param type Type of the control: `text` or `number`\n   * @param options Control options\n   */\n  constructor(public type: T, public options?: InputControlOptions<N>) {\n    super()\n    this.id = getUID()\n    this.readonly = options?.readonly ?? false\n\n    if (typeof options?.initial !== 'undefined') this.value = options.initial\n  }\n\n  /**\n   * Set control value\n   * @param value Value to set\n   */\n  setValue(value?: N) {\n    this.value = value\n    if (this.options?.change) this.options.change(value!)\n  }\n}\n\n/**\n * The node class\n * @priority 10\n * @example new Node('math')\n */\nexport class Node<\n  Inputs extends { [key in string]?: Socket } = { [key in string]?: Socket },\n  Outputs extends { [key in string]?: Socket } = { [key in string]?: Socket },\n  Controls extends { [key in string]?: Control } = { [key in string]?: Control }\n> implements NodeBase {\n  /**\n   * Node id, unique string generated by `getUID` function\n   */\n  id: NodeBase['id']\n  /**\n   * Node inputs\n   */\n  inputs: { [key in keyof Inputs]?: Input<Exclude<Inputs[key], undefined>> } = {}\n  /**\n   * Node outputs\n   */\n  outputs: { [key in keyof Outputs]?: Output<Exclude<Outputs[key], undefined>> } = {}\n  /**\n   * Node controls\n   */\n  controls: Controls = {} as Controls\n  /**\n   * Whether the node is selected. Default is `false`\n   */\n  selected?: boolean\n\n  constructor(public label: string) {\n    this.id = getUID()\n  }\n\n  hasInput<K extends keyof Inputs>(key: K) {\n    return Object.prototype.hasOwnProperty.call(this.inputs, key)\n  }\n\n  addInput<K extends keyof Inputs>(key: K, input: Input<Exclude<Inputs[K], undefined>>) {\n    if (this.hasInput(key)) throw new Error(`input with key '${String(key)}' already added`)\n\n    Object.defineProperty(this.inputs, key, { value: input, enumerable: true, configurable: true })\n  }\n\n  removeInput(key: keyof Inputs) {\n    delete this.inputs[key]\n  }\n\n  hasOutput<K extends keyof Outputs>(key: K) {\n    return Object.prototype.hasOwnProperty.call(this.outputs, key)\n  }\n\n  addOutput<K extends keyof Outputs>(key: K, output: Output<Exclude<Outputs[K], undefined>>) {\n    if (this.hasOutput(key)) throw new Error(`output with key '${String(key)}' already added`)\n\n    Object.defineProperty(this.outputs, key, { value: output, enumerable: true, configurable: true })\n  }\n\n  removeOutput(key: keyof Outputs) {\n    delete this.outputs[key]\n  }\n\n  hasControl<K extends keyof Controls>(key: K) {\n    return Object.prototype.hasOwnProperty.call(this.controls, key)\n  }\n\n  addControl<K extends keyof Controls>(key: K, control: Controls[K]) {\n    if (this.hasControl(key)) throw new Error(`control with key '${String(key)}' already added`)\n\n    Object.defineProperty(this.controls, key, { value: control, enumerable: true, configurable: true })\n  }\n\n  removeControl(key: keyof Controls) {\n    delete this.controls[key]\n  }\n}\n\n/**\n * The connection class\n * @priority 9\n */\nexport class Connection<\n  Source extends Node,\n  Target extends Node\n> implements ConnectionBase {\n  /**\n   * Connection id, unique string generated by `getUID` function\n   */\n  id: ConnectionBase['id']\n  /**\n   * Source node id\n   */\n  source: NodeBase['id']\n  /**\n   * Target node id\n   */\n  target: NodeBase['id']\n\n  /**\n   * @constructor\n   * @param source Source node instance\n   * @param sourceOutput Source node output key\n   * @param target Target node instance\n   * @param targetInput Target node input key\n   */\n  constructor(\n    source: Source,\n    public sourceOutput: keyof Source['outputs'],\n    target: Target,\n    public targetInput: keyof Target['inputs']\n  ) {\n    if (!source.outputs[sourceOutput as string]) {\n      throw new Error(`source node doesn't have output with a key ${String(sourceOutput)}`)\n    }\n    if (!target.inputs[targetInput as string]) {\n      throw new Error(`target node doesn't have input with a key ${String(targetInput)}`)\n    }\n\n    this.id = getUID()\n    this.source = source.id\n    this.target = target.id\n  }\n}\n"
  },
  {
    "path": "src/scope.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/naming-convention */\nimport {\n  AcceptPartialUnion, CanAssignSignal, GetAssignmentReferences, GetNonAssignableElements, Tail\n} from './utility-types'\n\nexport type { CanAssignSignal }\n\n/**\n * A middleware type that can modify the data\n * @typeParam T - The data type\n * @param data - The data to be modified\n * @returns The modified data or undefined\n * @example (data) => data + 1\n * @example (data) => undefined // will stop the execution\n * @internal\n */\nexport type Pipe<T> = (data: T) => Promise<undefined | T> | undefined | T\n\nexport type CanAssignEach<D extends any[], F extends any[]> = D extends [infer H1, ...infer Tail1]\n  ? (\n    F extends [infer H2, ...infer Tail2] ?\n      [CanAssignSignal<H1, H2>, ...CanAssignEach<Tail1, Tail2>]\n      : []\n  ) : []\n\nexport type ScopeAsParameter<S extends Scope<any, any[]>, Current extends any[]> = (CanAssignEach<[S['__scope']['produces'], ...S['__scope']['parents']], Current>[number] extends true\n  ? S\n  : 'Argument Scope does not provide expected signals'\n)\n\n/**\n * Validate the Scope signals and replace the parameter type with an error message if they are not assignable\n * @internal\n */\nexport type NestedScope<S extends Scope<any, any[]>, Current extends any[]> = (CanAssignEach<Current, S['__scope']['parents']>[number] extends true\n  ? S\n  : 'Parent signals do not satisfy the connected scope. Please use `.debug($ => $) for detailed assignment error'\n)\n\n/**\n * Provides 'debug' method to check the detailed assignment error message\n * @example .debug($ => $)\n * @internal\n */\nexport function useHelper<S extends Scope<any, any[]>, Signals>() {\n  type T1 = S['__scope']['parents'][number]\n  return {\n    debug<T extends GetNonAssignableElements<T1, Signals>>(_f: (p: GetAssignmentReferences<T, Signals>) => T) {\n      /* placeholder */\n    }\n  }\n}\n\n/**\n * A signal is a middleware chain that can be used to modify the data\n * @typeParam T - The data type\n * @internal\n */\nexport class Signal<T> {\n  pipes: Pipe<T>[] = []\n\n  addPipe(pipe: Pipe<T>) {\n    this.pipes.push(pipe)\n  }\n\n  async emit<Context extends T>(context: Context): Promise<Context | undefined> {\n    let current: Context | undefined = context\n\n    for (const pipe of this.pipes) {\n      current = await pipe(current) as Context\n\n      if (typeof current === 'undefined') return\n    }\n    return current\n  }\n}\n\ntype Type<T> = (new(...args: any[]) => T) | (abstract new (...args: any[]) => T)\n\n/**\n * Base class for all plugins and the core. Provides a signals mechanism to modify the data\n */\nexport class Scope<Produces, Parents extends unknown[] = []> {\n  signal = new Signal<AcceptPartialUnion<Produces | Parents[number]>>()\n  parent?: any // Parents['length'] extends 0 ? undefined : Scope<Parents[0], Tail<Parents>>\n  __scope!: {\n    produces: Produces\n    parents: Parents\n  }\n\n  constructor(public name: string) { }\n\n  addPipe(middleware: Pipe<Produces | Parents[number]>) {\n    this.signal.addPipe(middleware)\n  }\n\n  use<S extends Scope<any, any[]>>(scope: NestedScope<S, [Produces, ...Parents]>) {\n    if (!(scope instanceof Scope)) throw new Error('cannot use non-Scope instance')\n\n    scope.setParent(this)\n    this.addPipe(context => {\n      return scope.signal.emit(context)\n    })\n\n    return useHelper<S, Produces | Parents[number]>()\n  }\n\n  setParent(scope: Scope<Parents[0], Tail<Parents>>) {\n    this.parent = scope\n  }\n\n  emit<C extends Produces>(context: C): Promise<Extract<Produces, C> | undefined> {\n    return this.signal.emit(context) as Promise<Extract<Produces, C>>\n  }\n\n  hasParent(): boolean {\n    return Boolean(this.parent)\n  }\n\n  parentScope<T extends Parents[0], P extends Tail<Parents>>(): Scope<T, P>\n  parentScope<T>(type: Type<T>): T\n  parentScope<T>(type?: Type<T>): T {\n    if (!this.parent) throw new Error('cannot find parent')\n    if (type && this.parent instanceof type) return this.parent\n    if (type) throw new Error('actual parent is not instance of type')\n    return this.parent\n  }\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "/**\n * Node id type\n */\nexport type NodeId = string\n/**\n * Connection id type\n * @group Primary\n */\nexport type ConnectionId = string\n\n/**\n * The base node type\n * @group Primary\n */\nexport type NodeBase = { id: NodeId }\n/**\n * The base connection type\n * @group Primary\n */\nexport type ConnectionBase = { id: ConnectionId, source: NodeId, target: NodeId }\n\n/**\n * Get the schemes\n * @example GetSchemes<Node & { myProp: number }, Connection>\n * @group Primary\n */\nexport type GetSchemes<NodeData extends NodeBase, ConnectionData extends ConnectionBase> = { Node: NodeData, Connection: ConnectionData }\n\n/**\n * The base schemes\n * @group Primary\n */\nexport type BaseSchemes = GetSchemes<NodeBase, ConnectionBase>\n"
  },
  {
    "path": "src/utility-types.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\n// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\nexport type AcceptPartialUnion<T> = T | any\n\nexport type Tail<T extends any[]> = ((...args: T) => void) extends (head: any, ...tail: infer U) => any ? U : never\n\nexport type UnionToIntersection<U> = (\n  U extends never ? never : (arg: U) => never\n) extends (arg: infer I) => void\n  ? I\n  : never\n\ntype StrictExcludeInner<T, U> = 0 extends (\n  U extends T ? [T] extends [U] ? 0 : never : never\n) ? never : T\nexport type StrictExclude<T, U> = T extends unknown ? StrictExcludeInner<T, U> : never\n\nexport type UnionToTuple<T> = UnionToIntersection<\n  T extends never ? never : (t: T) => T\n> extends (_: never) => infer W\n  ? [...UnionToTuple<StrictExclude<T, W>>, W]\n  : []\n\nexport type FilterMatch<T extends any[], V> = T extends [infer Head, ...infer _Tail]\n  ? ([Head] extends [V]\n    ? [Head, ...FilterMatch<_Tail, V>]\n    : FilterMatch<_Tail, V>\n  ) : []\n\nexport type CanAssignToAnyOf<Provides, Requires> = FilterMatch<UnionToTuple<Provides>, Requires> extends [] ? false : true\n\nexport type CanAssignEachTupleElemmentToAnyOf<Provides, Requires extends any[]> = Requires extends [infer Head, ...infer _Tail]\n  ? CanAssignToAnyOf<Provides, Head> extends true ?\n    (_Tail extends []\n      ? true\n      : CanAssignEachTupleElemmentToAnyOf<Provides, _Tail>\n    ) : false\n  : false\n\nexport type CanAssignEachToAnyOf<Provides, Requires> = CanAssignEachTupleElemmentToAnyOf<Provides, UnionToTuple<Requires>>\n\nexport type CanAssignSignal<Provides, Requires> = CanAssignEachToAnyOf<Provides, Requires>\n\ntype ReplaceTupleTypes<T extends any[], U> = { [K in keyof T]: U }\nexport type FilterNever<T extends any[]> = T extends [infer Head, ...infer _Tail]\n  ? ([Head] extends [never] ? FilterNever<_Tail> : [Head, ...FilterNever<_Tail>])\n  : []\n\ntype KeepIfNonAssignable<T, Signals> = CanAssignToAnyOf<Signals, T> extends false ? T : never\n\nexport type GetAllNonValidElements<T extends any[], Signals> = T extends [infer Head, ...infer _Tail]\n  ? ([KeepIfNonAssignable<Head, Signals>, ...GetAllNonValidElements<_Tail, Signals>])\n  : []\n\nexport type GetNonAssignableElements<T, Signals>\n  = FilterNever<GetAllNonValidElements<UnionToTuple<T>, Signals>>\nexport type GetAssignmentReferences<AssignableElements extends any[], Signals> = ReplaceTupleTypes<AssignableElements, Signals>\n"
  },
  {
    "path": "src/utils.ts",
    "content": "const crypto = globalThis.crypto as (typeof globalThis.crypto | typeof import('node:crypto'))\n\n/**\n * @returns A unique id\n */\nexport function getUID(): string {\n  if ('randomBytes' in crypto) {\n    return crypto.randomBytes(8).toString('hex')\n  }\n\n  const bytes = crypto.getRandomValues(new Uint8Array(8))\n  const array = Array.from(bytes)\n  const hexPairs = array.map(b => b.toString(16).padStart(2, '0'))\n\n  return hexPairs.join('')\n}\n"
  },
  {
    "path": "test/index.perf.ts",
    "content": "import { describe, it } from '@jest/globals'\n\nimport { BaseSchemes } from '../src'\nimport { NodeEditor } from '../src/editor'\n\nconst iterations = 5\n\ndescribe('NodeEditor', () => {\n  for (let iteration = 0; iteration < iterations; iteration++) {\n    describe(`remove nodes (${iteration})`, () => {\n      // eslint-disable-next-line init-declarations\n      let editor!: NodeEditor<BaseSchemes>\n\n      const ids = new Array(5_000).fill(0)\n        // eslint-disable-next-line @typescript-eslint/naming-convention\n        .map((_, i) => i)\n      const idsToRemove = [...ids].reverse()\n\n      beforeEach(async () => {\n        editor = new NodeEditor<BaseSchemes>()\n        await Promise.all(ids.map(id => editor.addNode({ id: `s${id}` })))\n      })\n\n      it('basic', async () => {\n        await Promise.all(idsToRemove.map(id => editor.removeNode(`s${id}`)))\n      })\n    })\n  }\n\n  for (let iteration = 0; iteration < iterations; iteration++) {\n    describe(`remove connections (${iteration})`, () => {\n      // eslint-disable-next-line init-declarations\n      let editor!: NodeEditor<BaseSchemes>\n\n      const ids = new Array(3_000).fill(0)\n        // eslint-disable-next-line @typescript-eslint/naming-convention\n        .map((_, i) => i)\n      const idsToRemove = [...ids].reverse()\n\n      beforeEach(async () => {\n        editor = new NodeEditor<BaseSchemes>()\n        await Promise.all(ids.map(id => editor.addNode({ id: `s${id}` })))\n        await Promise.all(ids.map(id => editor.addNode({ id: `t${id}` })))\n        await Promise.all(ids.map(id => editor.addConnection({ id: `c${id}`, source: `s${id}`, target: `t${id}` })))\n      })\n\n      it('basic', async () => {\n        await Promise.all(idsToRemove.map(id => editor.removeConnection(`c${id}`)))\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "test/index.test.ts",
    "content": "import { describe, expect, it } from '@jest/globals'\n\nimport { NodeEditor } from '../src/editor'\n\ndescribe('NodeEditor', () => {\n  it('NodeEditor is instantiable', () => {\n    expect(new NodeEditor()).toBeInstanceOf(NodeEditor)\n  })\n\n  it('addNode should add a node', async () => {\n    const editor = new NodeEditor()\n    const nodeData = { id: '1', label: 'Node 1' }\n    const result = await editor.addNode(nodeData)\n    const nodes = editor.getNodes()\n\n    expect(result).toBe(true)\n    expect(nodes).toHaveLength(1)\n    expect(nodes[0]).toEqual(nodeData)\n  })\n\n  it('addNode should not add a node with duplicate id', async () => {\n    const editor = new NodeEditor()\n    const nodeData = { id: '1', label: 'Node 1' }\n\n    await editor.addNode(nodeData)\n\n    await expect(() => editor.addNode(nodeData)).rejects.toThrowError()\n  })\n\n  it('addConnection should add a connection', async () => {\n    const editor = new NodeEditor()\n    const connectionData = { id: '1', source: '1', target: '2' }\n\n    await editor.addNode({ id: '1' })\n    await editor.addNode({ id: '2' })\n    const result = await editor.addConnection(connectionData)\n    const connections = editor.getConnections()\n\n    expect(result).toBe(true)\n    expect(connections).toHaveLength(1)\n    expect(connections[0]).toEqual(connectionData)\n  })\n\n  it('addConnection should not add a connection with duplicate id', async () => {\n    const editor = new NodeEditor()\n    const connectionData = { id: '1', source: '1', target: '2' }\n\n    await editor.addNode({ id: '1' })\n    await editor.addNode({ id: '2' })\n    await editor.addConnection(connectionData)\n\n    await expect(() => editor.addConnection(connectionData)).rejects.toThrowError()\n  })\n\n  it('removeNode should remove a node', async () => {\n    const editor = new NodeEditor()\n    const nodeData = { id: '1', label: 'Node 1' }\n\n    await editor.addNode(nodeData)\n    await editor.removeNode('1')\n    const nodes = editor.getNodes()\n\n    expect(nodes).toHaveLength(0)\n  })\n\n  it('removeNode should remove specified nodes', async () => {\n    const editor = new NodeEditor()\n\n    const ids = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']\n\n    await Promise.all(ids.map(id => editor.addNode({ id: id })))\n\n    const removeIds = ['1', '2', '3', '4', '5']\n\n    await Promise.all(removeIds.map(id => editor.removeNode(id)))\n\n    const remainIds = editor.getNodes().map(n => n.id)\n\n    await editor.clear()\n\n    expect(remainIds).toEqual(['6', '7', '8', '9', '10'])\n  })\n\n  it('removeConnection should remove a connection', async () => {\n    const editor = new NodeEditor()\n    const connectionData = { id: '1', source: '1', target: '2' }\n\n    await editor.addNode({ id: '1' })\n    await editor.addNode({ id: '2' })\n    await editor.addConnection(connectionData)\n    await editor.removeConnection('1')\n    const connections = editor.getConnections()\n\n    expect(connections).toHaveLength(0)\n  })\n\n  it('removeConnection should remove specified connections', async () => {\n    const editor = new NodeEditor()\n\n    const ids = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']\n\n    await Promise.all(ids.map(id => editor.addNode({ id: `s${id}` })))\n    await Promise.all(ids.map(id => editor.addNode({ id: `t${id}` })))\n    await Promise.all(ids.map(id => editor.addConnection({ id: id, source: `s${id}`, target: `t${id}` })))\n\n    const removeIds = ['1', '2', '3', '4', '5']\n\n    await Promise.all(removeIds.map(id => editor.removeConnection(id)))\n\n    const remainIds = editor.getConnections().map(c => c.id)\n\n    await editor.clear()\n\n    expect(remainIds).toEqual(['6', '7', '8', '9', '10'])\n  })\n\n  it('should clear all nodes and connections', async () => {\n    const editor = new NodeEditor()\n\n    await editor.addNode({ id: '1' })\n    await editor.addNode({ id: '2' })\n    await editor.addConnection({ id: '1', source: '1', target: '2' })\n    await editor.clear()\n    const nodes = editor.getNodes()\n    const connections = editor.getConnections()\n\n    expect(nodes).toHaveLength(0)\n    expect(connections).toHaveLength(0)\n  })\n})\n\n"
  },
  {
    "path": "test/mocks/crypto.ts",
    "content": "import { jest } from '@jest/globals'\nimport { Buffer } from 'buffer'\n\nexport function mockCrypto(object: Record<string, unknown>) {\n  // eslint-disable-next-line no-undef\n  Object.defineProperty(globalThis, 'crypto', {\n    value: object,\n    writable: true\n  })\n}\n\nexport function mockCryptoFromArray(array: Uint8Array) {\n  mockCrypto({\n    getRandomValues: jest.fn().mockReturnValue(array)\n  })\n}\n\nexport function mockCryptoFromBuffer(buffer: Buffer) {\n  mockCrypto({\n    randomBytes: jest.fn().mockReturnValue(buffer)\n  })\n}\n\nexport function resetCrypto() {\n  // eslint-disable-next-line no-undef\n  Object.defineProperty(globalThis, 'crypto', {\n    // eslint-disable-next-line no-undefined\n    value: undefined,\n    writable: true\n  })\n}\n"
  },
  {
    "path": "test/presets/classic.test.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'\n\nimport { mockCryptoFromArray, resetCrypto } from '../mocks/crypto'\n\ndescribe('ClassicPreset', () => {\n  // eslint-disable-next-line init-declarations\n  let preset!: typeof import('../../src/presets/classic')\n\n  beforeEach(async () => {\n    mockCryptoFromArray(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))\n    preset = await import('../../src/presets/classic')\n  })\n\n  afterEach(() => {\n    resetCrypto()\n  })\n\n  describe('Node', () => {\n    it('is instantiable', () => {\n      expect(new preset.Node('A')).toBeInstanceOf(preset.Node)\n    })\n\n    it('should have an id', () => {\n      const node = new preset.Node('A')\n\n      expect(node.id).toBeDefined()\n    })\n\n    it('should have a label', () => {\n      const node = new preset.Node('A')\n\n      expect(node.label).toBe('A')\n    })\n\n    it('adds Input', () => {\n      const node = new preset.Node('A')\n      const input = new preset.Input(new preset.Socket('a'))\n\n      node.addInput('a', input)\n\n      expect(node.hasInput('a')).toBeTruthy()\n      expect(node.inputs.a).toBe(input)\n    })\n\n    it('throws error if Input already exists', () => {\n      const node = new preset.Node('A')\n\n      node.addInput('a', new preset.Input(new preset.Socket('a')))\n\n      expect(() => node.addInput('a', new preset.Input(new preset.Socket('a')))).toThrow()\n    })\n\n    it('removes Input', () => {\n      const node = new preset.Node('A')\n\n      node.addInput('a', new preset.Input(new preset.Socket('a')))\n      node.removeInput('a')\n\n      expect(node.hasInput('a')).toBeFalsy()\n    })\n\n    it('adds Output', () => {\n      const node = new preset.Node('A')\n      const output = new preset.Output(new preset.Socket('a'))\n\n      node.addOutput('a', output)\n\n      expect(node.hasOutput('a')).toBeTruthy()\n      expect(node.outputs.a).toBe(output)\n    })\n\n    it('throws error if Output already exists', () => {\n      const node = new preset.Node('A')\n\n      node.addOutput('a', new preset.Output(new preset.Socket('a')))\n\n      expect(() => node.addOutput('a', new preset.Output(new preset.Socket('a')))).toThrow()\n    })\n\n    it('removes Output', () => {\n      const node = new preset.Node('A')\n\n      node.addOutput('a', new preset.Output(new preset.Socket('a')))\n      node.removeOutput('a')\n\n      expect(node.hasOutput('a')).toBeFalsy()\n    })\n  })\n\n  describe('Connection', () => {\n    it('Connection throws error if input not found', () => {\n      const a = new preset.Node('A')\n      const b = new preset.Node('B')\n\n      a.addOutput('a', new preset.Output(new preset.Socket('a')))\n\n      expect(() => new preset.Connection(a, 'a', b, 'b')).toThrow()\n    })\n\n    it('Connection throws error if output not found', () => {\n      const a = new preset.Node('A')\n      const b = new preset.Node('B')\n\n      b.addInput('b', new preset.Input(new preset.Socket('b')))\n\n      expect(() => new preset.Connection(a, 'a', b, 'b')).toThrow()\n    })\n\n    it('Connection is instantiable', () => {\n      const a = new preset.Node('A')\n      const b = new preset.Node('B')\n      const output = new preset.Output(new preset.Socket('b'))\n      const input = new preset.Input(new preset.Socket('a'))\n\n      a.addOutput('a', output)\n      b.addInput('b', input)\n\n      expect(new preset.Connection(a, 'a', b, 'b')).toBeInstanceOf(preset.Connection)\n    })\n  })\n\n  describe('Control', () => {\n    it('adds Control to Node', () => {\n      const node = new preset.Node('A')\n\n      node.addControl('ctrl', new preset.Control())\n\n      expect(node.hasControl('ctrl')).toBeTruthy()\n    })\n\n    it('throws error if Control already exists', () => {\n      const node = new preset.Node('A')\n\n      node.addControl('ctrl', new preset.Control())\n\n      expect(() => node.addControl('ctrl', new preset.Control())).toThrow()\n    })\n\n    it('removes Control from Node', () => {\n      const node = new preset.Node('A')\n\n      node.addControl('ctrl', new preset.Control())\n      node.removeControl('ctrl')\n\n      expect(node.hasControl('ctrl')).toBeFalsy()\n    })\n\n    it('adds Control to Input', () => {\n      const input = new preset.Input(new preset.Socket('a'))\n\n      input.addControl(new preset.Control())\n\n      expect(input.control).toBeTruthy()\n    })\n\n    it('throws error if Control in Input already exists', () => {\n      const input = new preset.Input(new preset.Socket('a'))\n\n      input.addControl(new preset.Control())\n\n      expect(() => input.addControl(new preset.Control())).toThrow()\n    })\n\n    it('removes Control from Input', () => {\n      const input = new preset.Input(new preset.Socket('a'))\n\n      input.addControl(new preset.Control())\n      input.removeControl()\n\n      expect(input.control).toBeFalsy()\n    })\n  })\n})\n"
  },
  {
    "path": "test/scope.test.ts",
    "content": "import { describe, expect, it, jest } from '@jest/globals'\n\nimport { Scope } from '../src/scope'\n\ntype Parent = { parent: string }\ntype Child = { child: number }\n\ndescribe('Scope', () => {\n  it('should create a new Scope instance', () => {\n    const scope = new Scope('test')\n\n    expect(scope).toBeInstanceOf(Scope)\n  })\n\n  it('doesnt have a parent by default', () => {\n    const scope = new Scope<Parent>('test')\n\n    expect(scope.hasParent()).toBeFalsy()\n  })\n\n  describe('parent-child', () => {\n    it('should set a parent scope', () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n\n      child.setParent(parent)\n\n      expect(child.parentScope()).toBe(parent)\n    })\n\n    it('should use a nested scope', () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n\n      parent.use(child)\n      expect(child.hasParent()).toBeTruthy()\n      expect(child.parentScope()).toBe(parent)\n    })\n\n    it('should throw an error when using a non-Scope instance', () => {\n      const parent = new Scope<Parent>('parent')\n      const child = { signal: { emit: jest.fn() } }\n\n      expect(() => parent.use(child as any)).toThrowError('cannot use non-Scope instance')\n    })\n\n    it('should throw an error when trying to access a parent without one', () => {\n      const scope = new Scope<Parent>('test')\n\n      expect(() => scope.parentScope()).toThrowError('cannot find parent')\n    })\n\n    it('should throw an error when trying to access a parent with the wrong type', () => {\n      class WrongScope<T> extends Scope<T> { }\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n\n      parent.use(child)\n\n      expect(() => child.parentScope(WrongScope)).toThrowError('actual parent is not instance of type')\n    })\n  })\n\n  describe('addPipe', () => {\n    it('should emit a signal', async () => {\n      const scope = new Scope<Parent>('test')\n      const pipe = jest.fn<() => Parent>()\n\n      scope.addPipe(pipe)\n      await scope.emit({ parent: 'test' })\n\n      expect(pipe).toHaveBeenCalledWith({ parent: 'test' })\n    })\n\n    it('should return a promise from emit', () => {\n      const scope = new Scope<Parent>('test')\n      const signal = jest.fn<() => Parent>()\n\n      scope.addPipe(signal)\n      const result = scope.emit({ parent: 'test' })\n\n      expect(result).toBeInstanceOf(Promise)\n    })\n\n    it('should return the result of the signal', async () => {\n      const scope = new Scope<Parent>('test')\n      const signal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-result' })\n\n      scope.addPipe(signal)\n      const result = await scope.emit({ parent: 'test' })\n\n      expect(result).toEqual({ parent: 'test-result' })\n    })\n\n    it('should return undefined if the signal returns undefined', async () => {\n      const scope = new Scope('test')\n      // eslint-disable-next-line no-undefined\n      const signal = jest.fn().mockReturnValue(undefined)\n\n      scope.addPipe(signal)\n      const result = await scope.emit('test')\n\n      expect(result).toBeUndefined()\n    })\n\n    it('should return the result of the signal with a parent', async () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n      const signal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-parent' })\n\n      parent.addPipe(signal)\n      parent.use(child)\n      const result = await child.emit({ child: 1 })\n\n      expect(result).toEqual({ child: 1 })\n    })\n\n    it('should return the result of the signal with a parent and child', async () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n      const signal = jest.fn<() => Child>().mockReturnValue({ child: 1 })\n\n      parent.use(child)\n      child.addPipe(signal)\n      const result = await child.emit({ child: 2 })\n\n      expect(result).toEqual({ child: 1 })\n    })\n\n    it('should transfer signals from parent to child', async () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n      const parentSignal = jest.fn<() => Parent>().mockReturnValue({ parent: 'test-parent' })\n      const childSignal = jest.fn<() => Child>()\n\n      parent.addPipe(parentSignal)\n      child.addPipe(childSignal)\n      parent.use(child)\n\n      await parent.emit({ parent: 'test-parent' })\n\n      expect(childSignal).toHaveBeenCalledWith({ parent: 'test-parent' })\n    })\n\n    it('should prevent execution of child signal if parent signal returns undefined', async () => {\n      const parent = new Scope<Parent>('parent')\n      const child = new Scope<Child, [Parent]>('child')\n      // eslint-disable-next-line no-undefined\n      const parentSignal = jest.fn<() => Parent | undefined>().mockReturnValue(undefined)\n      const childSignal = jest.fn<() => Child>()\n\n      parent.addPipe(parentSignal)\n      child.addPipe(childSignal)\n      parent.use(child)\n\n      await parent.emit({ parent: 'test-parent' })\n\n      expect(childSignal).not.toHaveBeenCalled()\n    })\n  })\n})\n\n"
  },
  {
    "path": "test/utils.test.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'\nimport { Buffer } from 'buffer'\n\nimport { mockCryptoFromArray, mockCryptoFromBuffer, resetCrypto } from './mocks/crypto'\n\ndescribe('getUID', () => {\n  beforeEach(() => {\n    jest.resetModules()\n  })\n\n  afterEach(() => {\n    resetCrypto()\n  })\n\n  it('should return a unique id based on crypto.getRandomValues', async () => {\n    mockCryptoFromArray(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]))\n\n    const { getUID } = await import('../src/utils')\n    const uid = getUID()\n\n    expect(uid).toHaveLength(16)\n  })\n\n  it('should return a unique id based on crypto.randomBytes', async () => {\n    mockCryptoFromBuffer(Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]))\n\n    const { getUID } = await import('../src/utils')\n    const uid = getUID()\n\n    expect(uid).toHaveLength(16)\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"extends\": \"rete-cli/configs/tsconfig.json\",\n    \"compilerOptions\": {\n        \"target\": \"es5\",\n        \"downlevelIteration\": true,\n\t    \"isolatedModules\": false,\n        \"lib\": []\n    },\n    \"include\": [\"src\", \"test\"]\n}\n"
  }
]