同构 JS/TS 函数编排器
Isomorphic JS/TS Functions Orchestrator

原始链接: https://github.com/damianofalcioni/js-functions-orchestrator

## js-functions-orchestrator: 一个轻量级函数编排器 js-functions-orchestrator 是一个强大但轻量级(约100行代码)的库,用于在浏览器、Node.js、Bun 和 Deno 中编排 JavaScript/TypeScript 函数。它通过使用 JSON 定义编排逻辑,并利用 JSONata 实现灵活的输入/输出转换,从而能够构建低代码平台。 主要特性包括:极少的依赖项(仅 JSONata),固有的安全性(JSONata 表达式无需沙箱),可扩展的自定义状态管理,以及完整的 TypeScript 支持。 编排通过函数之间的连接来定义,可以选择从初始函数和用户定义的输入开始。连接可以将多个函数链接起来,使用 JSONata 转换输出,作为后续函数的输入。编排器循环遍历连接,在所有依赖项解决后执行函数。 该库为定义工作流和管理函数之间的数据流提供了清晰的结构,为构建复杂、适应性强的应用程序提供了强大的基础。示例展示了简单的函数链和循环场景。 在 npm 上找到它 (`npm install js-functions-orchestrator`),并在 GitHub Pages 上探索代码和在线演示。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 同构 JS/TS 函数编排器 (github.com/damianofalcioni) 13 分,由 damianofalcioni 1 天前发布 | 隐藏 | 过去 | 收藏 | 2 条评论 jeduan 1 天前 [–] 但是为什么回复 matthoiland 1 天前 | 父评论 [–] 你们的科学家太专注于他们是否能够做到,而没有停下来思考他们是否应该这样做。回复 考虑申请 YC 的 2026 年冬季批次!申请截止日期为 11 月 10 日 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

This library provide a simple yet powerful, fast, secure, and extensible orchestrator for your JavaScript/Typescript functions, working both in browsers and Node/Bun/Deno, that can be used as base for your own low-code platform. The orchestration logic is defined in a simple JSON and use the power of JSONata for input/output transformation.

Highlights:

  • Lighweight: Full orchestration logic is ~100LoC. No dependencies except JSONata.
  • Secure: User code provided as JSONata expression do not need to be sandboxed.
  • Extensible: Provide your own state mangement system or additional transition logic in a format different of JSONata (TODO).
  • Isomorphic: work on browser as well as on Node/Bun/Deno.
  • Typescript types available.
  • Open Source (MIT).
  • 100% Code Coverage.
npm install js-functions-orchestrator

Simple combination of two functions output as input for a third one:

graph TD;
    f1-->Connection_0;
    f2-->Connection_0;
    Connection_0-->f3;
Loading
import { Orchestrator } from 'js-functions-orchestrator';

const orchestrator = new Orchestrator({
  functions: {
    fn1: async ()=>'Hello', //sync or async functions
    fn2: async ()=>'World',
    fn3: (/** @type {string} */echo)=>echo
  }
});
const runResult = await orchestrator.run({
  connections: [{
    from: ['fn1', 'fn2'],
    transition: '{"to":[[$.from[0] & " " & $.from[1]]]}', //the result of fn1 (the string "Hello") is combined with the the result of fn2 (the string "World") and used as input for fn3
    to: ['fn3']
  }]
});

console.log(runResult);
/* output:
{
  results: { fn3: 'Hello World' },
  variables: { global: {}, locals: [ {} ] }
}
*/

More complex scenario with a loop:

graph TD;
    f1-->Connection_0;
    f2-->Connection_0;
    Connection_0-->f3;
    f3-->Connection_1;
    Connection_1-->f3;
    Connection_1-->f4;
Loading
import { Orchestrator } from 'js-functions-orchestrator';

const orchestrator = new Orchestrator({
  functions: {
    f1: async a=>a,
    f2: async a=>a,
    f3: async a=>a,
    f4: async a=>a
  }
});

const runResult = await orchestrator.run({
  //initial set of functions that start the orchestration with the array of their input parameters
  inits: {
    f1: ['hello'],
    f2: ['world']
  },
  connections: [{
    from: ['f1', 'f2'],
    transition: '{"to": [[ $.from[0] & " " & $.from[1] ]]}',
    to: ['f3']
  }, {
    from: ['f3'],
    transition: '($i:=$.local.i; $i:=($i?$i:0)+1; $y:=$.global.t; {"global":{"t":1}, "local":{"i":$i}, "to": [[$.from[0] & " " & $string($i)], $i<5?[[$.from[0]]]:null]})',
    to: ['f4', 'f3']
  }]
});
console.log(runResult);
/* output:
{
  results: { f4: 'hello world 5' },
  variables: { global: { t: 1 }, locals: [ {}, { i: 5 } ] }
}
*/

More examples are available in the index.test.js.

Live at Github Pages

<html>
<script type="module">
import { Orchestrator } from 'https://esm.run/js-functions-orchestrator';

const orchestrator = new Orchestrator({
  functions: {
    //sync or async functions
    fn1: echo=>echo,
    fn2: async echo=>echo,
    fn3: echo=>echo
  }
});
const runResult = await orchestrator.run({
  inits: {
    fn1: ['Hello'],
    fn2: ['World']
  },
  connections: [{
    from: ['fn1', 'fn2'],
    transition: '{ "to":[[ $.from[0] & " " & $.from[1] ]] }', //the result of fn1 (the string "Hello") is combined with the the result of fn2 (the string "World") and used as input for fn3
    to: ['fn3']
  }]
});
document.body.innerText = JSON.stringify(runResult);
console.log(runResult);
/* output:
{
  results: { fn3: 'Hello World' },
  variables: { global: {}, locals: [ {} ] }
}
*/
</script>
</html>

The orchestration graph is defined only through a list of connections between JS functions, and optionally an initial set of starting functions with user defined inputs. A single connection can be from multiple JS functions to multiple JS functions and may include the transformation logic for the outputs of the from JS functions to the inputs of the to JS functions. After the initial execution of all the functions with user defined inputs, the different connections are sequentially looped and each connection start only when all the from JS functions have Promises of results. Once awaited, their results are provided to the transformation logic and the results of the transformation are the inputs for the different to JS functions that are then executed.

In more details the orchestration logic is the following:

  1. Initialization of starting functions with user defined inputs

    • The selected functions are executed and their result Promise stored
  2. Loop all connections

  3. If there are available results for every "from" function, the connection start

    1. Execute the transition
      • JSONata returning {"to":[…]}
      • Available $.from array, $.global object, and $.local object
    2. Store transition results as inputs for all the "connection.to" functions
    3. Delete all the "from" results
    4. Execute all the "to" functions with the available inputs from the transition
      • If input is "null" the function is not executed (loop exit condition)
  4. Do until no more connections can start

    • Note: incorrectly designed graphs can lead to infinite executions.
  5. Return all the remaining functions and connections results

{
    // Functions with user defined inputs. These functions will start the orchestration. When not defined, initial functions will be identified checking on the connections all the "from" functions that are never connected to a "to".
    "init": {
        // Key is the identifier of the function, value is the array of expected parameters.
        "fn1": [],
        "fn2": []
    },
    // List of existing connections between functions. The orchestrator will loop the connections untill no one can start.
    "connections": [{
        // A connection require a non empty "from" array, containing the identifier of the functions that origin the connection. The connection start only when all the functions in the "from" have been executed and have a resulting Promise. In this case all the "from" Promises are awaited, and their results are made available in the JSONata of the "transition".
        "from": ["fn1", "fn2"],
        //JSONata expression that must return at least the JSON { "to": [] }. "to" must be an array of the same size of the "connection.to" array, containing an array of input parameters (as array) for the relative "connection.to" function. Additionally it can return "global", and "local", to store respectively globally and locally scoped variables (a global variable is visible in all the connection transition, while a local variable only in the same transition but across multiple execution). If the transition is not provided the output of the "from" functions are provided directly as inputs to the "to" functions. In such case "from" and "to" array must be of the same size.
        "transition": "{\"to\": [[ $.from[0] & \" \" & $.from[1] ]]}",
        // List of functions that can consume the output of the "transition" as their inputs. The functions are executed and next connection is checked until no more connections can start. 
        "to": ["fn3"]
    }]
}
联系我们 contact @ memedata.com