@floken-io/engine 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,37 @@
3
3
  本包遵循 [Semantic Versioning](https://semver.org/),格式参考 [Keep a Changelog](https://keepachangelog.com/)。
4
4
  0.x 阶段跨包依赖写 `>=x.y.z <1.0.0`(不用 `^`)。
5
5
 
6
+ ## 0.0.2 — 2026-10-02
7
+
8
+ ### 新增
9
+
10
+ - ★ **宿主自定义扩展属性(ADR-009)**:`@floken-io/designer` 上配的自定义属性(`node.extension['acme:priority']`)
11
+ 现在能被条件直接用,不必再注入 `conditionHandler` 手写回查。分两级:
12
+ - **第一级(恒给,无需配置)**:`ConditionCtx` 增 `nodeExtensions`(当前节点)与
13
+ `targetExtensions`(该分支的**目标节点**)两个只读字段。键**带前缀**原样给出,排除 `floken:*`、**只含标量**。
14
+ - **第二级(opt-in)**:配置 `EngineConfig.extensionVars` 后并入求值上下文,
15
+ 挂成 `variables.node` / `variables.target`,条件可直接写 `target.priority = "high"`。
16
+ - ★ **类型还原可按声明做**:`extensionVars.casts`(如 `{ 'acme:slaHours': 'number' }`)。
17
+ 起因是 XML 往返后扩展属性的值**一律是字符串**(实测 `48 → "48"`),而 `"48" > 24` 会按字符串比较;
18
+ 引擎**不猜类型**(猜 = 静默错误),按声明转换,转不动就抛 `ENGINE_OPTION_INVALID`。
19
+
20
+ ### 变更
21
+
22
+ - `ConditionUnresolved` 内部哨兵增 `toNodeId`(原先只带源节点,取不到目标节点的扩展属性)。
23
+ 该哨兵**不是**公开 API 的一部分(引擎吞掉并重试),宿主不会收到它。
24
+
25
+ ### 兼容性
26
+
27
+ - **不声明 `extensionVars` 时行为与 0.0.1 逐字一致**:只读字段是纯增量,`variables` 一个键都不多。
28
+ - ⚠️ 已知代价(行为已钉死、文档如实写明):**不 opt-in 却在表达式里写 `target.*`
29
+ → 不报错、静默走另一条分支**(`target` 未定义时等值比较求值为 `false`,
30
+ 与 `amount > 5000` 缺变量求值为 `null` 而抛错的语义不同)。
31
+
32
+ ### 验证
33
+
34
+ - 单测 **701**(+16,新增 `test/extension-vars.test.ts`);冷启动探针 **103**(+5,跑真 `dist/`);
35
+ `verify` 六道门禁全绿。
36
+
6
37
  ## 0.0.1 — 2026-10-01
7
38
 
8
39
  首发。令牌制流程内核 + 中国式审批动作,覆盖 `03` 的阶段 **E1~E9**(685 单测 / `verify` 六道门禁全绿 /
@@ -1,4 +1,4 @@
1
- import { S as StateStore, T as TaskView, a as TaskProjection, D as DefinitionSource } from './spi-BABH0Tcs.js';
1
+ import { S as StateStore, T as TaskView, a as TaskProjection, D as DefinitionSource } from './spi-BVNWxdCQ.js';
2
2
  import { ProcessDefinition } from '@floken-io/moddle';
3
3
 
4
4
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as ActionRecord, I as InstanceStateHeader, b as TaskDelta, c as Token, d as InstanceState, T as TaskView, V as VoteOutcome, e as InstanceParent, E as EngineEvent, f as TimeoutSpec, g as TraceEntry, h as InstanceStatus, i as EventSink, j as InstanceEventName, S as StateStore, D as DefinitionSource, a as TaskProjection, k as ApproverSource, l as ServiceHandler, m as AuthResolver, F as FormProvider, C as ConditionHandler, n as DecisionHandler, o as Scheduler, p as ConditionCtx } from './spi-BABH0Tcs.js';
2
- export { q as ApproverCtx, r as AuditEntry, s as DecisionCtx, t as ENGINE_EVENT_NAMES, u as EngineEventName, v as FormCtx, w as INSTANCE_EVENT_NAMES, x as INSTANCE_STATUSES, y as InstanceEvent, z as InstanceStateBody, B as SPI_GROUPS, G as SPI_NAMES, H as STATE_SCHEMA_VERSION, J as ScheduleRequest, K as ServiceCtx, L as ServiceHandlerFn, M as SpiInterfaces, N as SpiName, O as TASK_EVENT_NAMES, P as TASK_STATUSES, Q as TERMINAL_STATUSES, R as TOKEN_STATES, U as TaskEvent, W as TaskEventName, X as TaskStatus, Y as TokenAwait, Z as TokenState, _ as isInstanceEvent, $ as isTaskEvent, a0 as isTerminalStatus, a1 as subjectTokenOf } from './spi-BABH0Tcs.js';
1
+ import { A as ActionRecord, I as InstanceStateHeader, b as TaskDelta, c as Token, d as InstanceState, T as TaskView, V as VoteOutcome, e as InstanceParent, E as EngineEvent, f as TimeoutSpec, g as TraceEntry, h as InstanceStatus, i as EventSink, j as InstanceEventName, S as StateStore, D as DefinitionSource, a as TaskProjection, k as ApproverSource, l as ServiceHandler, m as AuthResolver, F as FormProvider, C as ConditionHandler, n as DecisionHandler, o as Scheduler, p as ConditionCtx } from './spi-BVNWxdCQ.js';
2
+ export { q as ApproverCtx, r as AuditEntry, s as DecisionCtx, t as ENGINE_EVENT_NAMES, u as EngineEventName, v as FormCtx, w as INSTANCE_EVENT_NAMES, x as INSTANCE_STATUSES, y as InstanceEvent, z as InstanceStateBody, B as SPI_GROUPS, G as SPI_NAMES, H as STATE_SCHEMA_VERSION, J as ScheduleRequest, K as ServiceCtx, L as ServiceHandlerFn, M as SpiInterfaces, N as SpiName, O as TASK_EVENT_NAMES, P as TASK_STATUSES, Q as TERMINAL_STATUSES, R as TOKEN_STATES, U as TaskEvent, W as TaskEventName, X as TaskStatus, Y as TokenAwait, Z as TokenState, _ as isInstanceEvent, $ as isTaskEvent, a0 as isTerminalStatus, a1 as subjectTokenOf } from './spi-BVNWxdCQ.js';
3
3
  import { NormalizedApproval, ApprovalMode, OnReject, TimeoutAction, NormalizedTimeout } from '@floken-io/moddle';
4
4
 
5
5
  /**
@@ -1209,6 +1209,17 @@ interface ProcessGraph {
1209
1209
  * 而不是等触发 —— 那时已经写了一半状态。
1210
1210
  */
1211
1211
  boundaryOf(nodeId: string): readonly BoundaryBinding[];
1212
+ /**
1213
+ * ★ 该节点上的**宿主自定义**扩展属性(ADR-009 细则①②③④)。
1214
+ *
1215
+ * 给出去的是**原样键值**,引擎**不解释** `acme:priority` 是什么意思 ——
1216
+ * 解释权 100% 在宿主(源码里不得出现任何具体宿主前缀)。
1217
+ *
1218
+ * - 排除 `floken:*`(引擎自己的键)与 `_extensionElements`(第三方原样快照);
1219
+ * - **只留标量**;
1220
+ * - **一个都没有 → `undefined`**(不填空对象 —— 调用方要能区分「没配」与「配了但被过滤空」)。
1221
+ */
1222
+ extensionsOf(nodeId: string): Readonly<Record<string, unknown>> | undefined;
1212
1223
  }
1213
1224
 
1214
1225
  /**
@@ -1790,6 +1801,42 @@ interface EngineConfig {
1790
1801
  clock?: () => string;
1791
1802
  /** INV-17 审计上限 */
1792
1803
  maxAuditEntries?: number;
1804
+ /**
1805
+ * ★ ADR-009:把宿主自定义扩展属性**并入求值上下文**(opt-in)。
1806
+ *
1807
+ * 不声明 → 只给 `ctx.nodeExtensions` / `ctx.targetExtensions` 两个**只读**字段,
1808
+ * **不并入** `variables`(0.0.1 语义逐字不变)。
1809
+ */
1810
+ extensionVars?: ExtensionVarsOption;
1811
+ }
1812
+ /**
1813
+ * ★ ADR-009 的 opt-in 配置。
1814
+ *
1815
+ * 为什么并入形态是**两个对象**而不是把键平铺进 `variables`:
1816
+ * 平铺的撞车面是「每个键」(表单字段 / 脚本产出的变量都可能同名),
1817
+ * 撞了就要决出胜负 —— 静默覆盖正是 §7.2 要防的头号事故。
1818
+ * 挂成 `variables.node` / `variables.target` 后,撞车面降到「两个名字」,
1819
+ * 且语义天然分层:**设计期配置** vs **运行期数据**。
1820
+ */
1821
+ interface ExtensionVarsOption {
1822
+ /**
1823
+ * ★ **当前节点**扩展属性并入后的顶层键名(默认 `'node'`)。
1824
+ *
1825
+ * ⚠️ 若流程变量里已有同名键 → 抛 `ENGINE_OPTION_INVALID`(**不静默覆盖**,细则⑦)。
1826
+ * 撞了就改这个名字,不要去改业务变量 —— 那会把业务数据挤掉。
1827
+ */
1828
+ key?: string;
1829
+ /** ★ **目标节点**扩展属性并入后的顶层键名(默认 `'target'`);冲突处置同 `key` */
1830
+ targetKey?: string;
1831
+ /**
1832
+ * ★ 类型还原表:`'acme:slaHours'` → `'number'`。
1833
+ *
1834
+ * 为什么必须有:`moddle` 从 XML 读回的扩展属性**一律是字符串**(实测 `48 → "48"`),
1835
+ * 而 `slaHours > 24` 遇到 `"48"` 会按字符串比较 → 抛错。
1836
+ * 引擎**不猜类型**(`"48"` 究竟是数字还是编号?猜 = 静默错误),按本表声明转换。
1837
+ * 二期改由模型内 `floken:extensionTypes` 声明块承载(仍是 `floken:` 命名空间)。
1838
+ */
1839
+ casts?: Readonly<Record<string, 'number' | 'boolean' | 'iso-date'>>;
1793
1840
  }
1794
1841
  interface StartOptions {
1795
1842
  /** ✅ 必填(AC-E10:实例绑定定义版本,改版不影响在途) */
package/dist/index.js CHANGED
@@ -1377,6 +1377,11 @@ function boundaryTokenOf(state, fire, host) {
1377
1377
  // src/nodes/graph.ts
1378
1378
  var WAITING_NODE_TYPES = ["userTask"];
1379
1379
  var APPROVAL_EXT_KEY = "floken:approval";
1380
+ var FLOKEN_EXT_PREFIX = "floken:";
1381
+ var RAW_SNAPSHOT_KEY = "_extensionElements";
1382
+ function isScalarValue(v) {
1383
+ return v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean";
1384
+ }
1380
1385
  function isWaitingNode(type) {
1381
1386
  return WAITING_NODE_TYPES.includes(type);
1382
1387
  }
@@ -1431,6 +1436,7 @@ function createProcessGraph(definition, processId, definitionVersion) {
1431
1436
  throw definitionMissing(processId, definitionVersion);
1432
1437
  }
1433
1438
  const approvalCache = /* @__PURE__ */ new Map();
1439
+ const extCache = /* @__PURE__ */ new Map();
1434
1440
  const boundaries = /* @__PURE__ */ new Map();
1435
1441
  for (const n of flatNodes) {
1436
1442
  if (n === null || typeof n !== "object" || n.type !== "boundaryEvent") continue;
@@ -1515,6 +1521,23 @@ function createProcessGraph(definition, processId, definitionVersion) {
1515
1521
  return value;
1516
1522
  },
1517
1523
  hasApproval: (nodeId) => nodes.get(nodeId)?.extension?.[APPROVAL_EXT_KEY] !== void 0,
1524
+ extensionsOf(nodeId) {
1525
+ if (extCache.has(nodeId)) return extCache.get(nodeId);
1526
+ const raw = nodes.get(nodeId)?.extension;
1527
+ let value;
1528
+ if (raw !== void 0 && typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
1529
+ const out2 = {};
1530
+ for (const [key, v] of Object.entries(raw)) {
1531
+ if (key.startsWith(FLOKEN_EXT_PREFIX)) continue;
1532
+ if (key === RAW_SNAPSHOT_KEY) continue;
1533
+ if (!isScalarValue(v)) continue;
1534
+ out2[key] = v;
1535
+ }
1536
+ value = Object.keys(out2).length > 0 ? Object.freeze(out2) : void 0;
1537
+ }
1538
+ extCache.set(nodeId, value);
1539
+ return value;
1540
+ },
1518
1541
  scriptOf: (nodeId) => {
1519
1542
  const s = nodes.get(nodeId)?.script;
1520
1543
  if (typeof s !== "string") return void 0;
@@ -2753,6 +2776,14 @@ var ConditionUnresolved = class extends Error {
2753
2776
  flowId;
2754
2777
  expression;
2755
2778
  nodeId;
2779
+ /**
2780
+ * ★ 该条件所在顺序流的**目标节点**(ADR-009 细则②)。
2781
+ *
2782
+ * 只有带上它,重跑时才能取出「这条分支通向的那个节点」的自定义扩展属性。
2783
+ * 拿不到时为 `undefined`(宿主在门 2 自提供 `conditionsOf` 闭包时不会发生本哨兵,
2784
+ * 但引擎自己的调用点**恒有** `OutFlow.to`)。
2785
+ */
2786
+ toNodeId;
2756
2787
  /**
2757
2788
  * ★ **到达该网关那一刻**的变量快照(T17)。
2758
2789
  *
@@ -2761,16 +2792,17 @@ var ConditionUnresolved = class extends Error {
2761
2792
  * 就被忽略了 —— 表现为「脚本把 amount 改成了 9000,网关却按旧值走了分支」。
2762
2793
  */
2763
2794
  variables;
2764
- constructor(flowId, expression, nodeId, variables = {}) {
2795
+ constructor(flowId, expression, nodeId, variables = {}, toNodeId = void 0) {
2765
2796
  super(`condition of flow '${flowId}' is not resolved yet`);
2766
2797
  this.flowId = flowId;
2767
2798
  this.expression = expression;
2768
2799
  this.nodeId = nodeId;
2769
2800
  this.variables = variables;
2801
+ this.toNodeId = toNodeId;
2770
2802
  }
2771
2803
  };
2772
- function unresolvedCondition(flowId, expression, nodeId, variables = {}) {
2773
- return new ConditionUnresolved(flowId, expression, nodeId, variables);
2804
+ function unresolvedCondition(flowId, expression, nodeId, variables = {}, toNodeId = void 0) {
2805
+ return new ConditionUnresolved(flowId, expression, nodeId, variables, toNodeId);
2774
2806
  }
2775
2807
  function asUnresolved(e) {
2776
2808
  if (e instanceof ConditionUnresolved) return e;
@@ -2819,7 +2851,8 @@ var ENGINE_CONFIG_KEYS = [
2819
2851
  "scheduler",
2820
2852
  "hooks",
2821
2853
  "clock",
2822
- "maxAuditEntries"
2854
+ "maxAuditEntries",
2855
+ "extensionVars"
2823
2856
  ];
2824
2857
  function assertEngineConfig(config) {
2825
2858
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
@@ -2862,6 +2895,45 @@ function assertEngineConfig(config) {
2862
2895
  throw optionInvalid("maxAuditEntries", "must be a positive integer", n);
2863
2896
  }
2864
2897
  }
2898
+ assertExtensionVars(c.extensionVars);
2899
+ }
2900
+ function assertExtensionVars(v) {
2901
+ if (v === void 0) return;
2902
+ if (typeof v !== "object" || v === null || Array.isArray(v)) {
2903
+ throw optionInvalid("extensionVars", "must be a plain object", v);
2904
+ }
2905
+ const o = v;
2906
+ const allowed = ["key", "targetKey", "casts"];
2907
+ for (const key of Object.keys(o)) {
2908
+ if (!allowed.includes(key)) {
2909
+ throw optionUnknown(`extensionVars.${key}`, allowed);
2910
+ }
2911
+ }
2912
+ const assertKeyName = (field) => {
2913
+ const k = o[field];
2914
+ if (k === void 0) return;
2915
+ if (typeof k !== "string" || k.length === 0 || k.includes(".")) {
2916
+ throw optionInvalid(`extensionVars.${field}`, 'must be a non-empty string without "."', k);
2917
+ }
2918
+ };
2919
+ assertKeyName("key");
2920
+ assertKeyName("targetKey");
2921
+ if (o.casts !== void 0) {
2922
+ const casts = o.casts;
2923
+ if (typeof casts !== "object" || casts === null || Array.isArray(casts)) {
2924
+ throw optionInvalid("extensionVars.casts", "must be a plain object", casts);
2925
+ }
2926
+ const kinds = ["number", "boolean", "iso-date"];
2927
+ for (const [key, kind] of Object.entries(casts)) {
2928
+ if (!kinds.includes(kind)) {
2929
+ throw optionInvalid(
2930
+ `extensionVars.casts['${key}']`,
2931
+ `must be one of ${kinds.join(" | ")}`,
2932
+ kind
2933
+ );
2934
+ }
2935
+ }
2936
+ }
2865
2937
  }
2866
2938
  function assertStartOptions(opts) {
2867
2939
  if (typeof opts !== "object" || opts === null || Array.isArray(opts)) {
@@ -2907,6 +2979,70 @@ function newInstanceId() {
2907
2979
  const rand = Math.random().toString(36).slice(2, 10);
2908
2980
  return `pi_${Date.now().toString(36)}_${instanceSeq.toString(36)}_${rand}`;
2909
2981
  }
2982
+ function applyExtensionCast(key, value, kind) {
2983
+ if (kind === "number") {
2984
+ if (typeof value === "number") return value;
2985
+ if (typeof value === "string" && value.trim() !== "") {
2986
+ const n = Number(value);
2987
+ if (!Number.isNaN(n)) return n;
2988
+ }
2989
+ } else if (kind === "boolean") {
2990
+ if (typeof value === "boolean") return value;
2991
+ if (value === "true") return true;
2992
+ if (value === "false") return false;
2993
+ } else {
2994
+ return value;
2995
+ }
2996
+ throw optionInvalid(
2997
+ `extensionVars.casts['${key}']`,
2998
+ `declared as '${kind}' but the value cannot be converted`,
2999
+ value
3000
+ );
3001
+ }
3002
+ function stripExtPrefix(key) {
3003
+ const i = key.indexOf(":");
3004
+ return i === -1 ? key : key.slice(i + 1);
3005
+ }
3006
+ function castExtensionBag(bag, casts) {
3007
+ if (bag === void 0) return void 0;
3008
+ const out = {};
3009
+ for (const [key, value] of Object.entries(bag)) {
3010
+ const kind = casts?.[key];
3011
+ const v = kind === void 0 ? value : applyExtensionCast(key, value, kind);
3012
+ const name = stripExtPrefix(key);
3013
+ if (Object.prototype.hasOwnProperty.call(out, name)) {
3014
+ throw optionInvalid(
3015
+ "extensionVars",
3016
+ `two extension keys collapse to the same variable name '${name}' after stripping the namespace prefix`,
3017
+ { name, bag: Object.keys(bag) }
3018
+ );
3019
+ }
3020
+ out[name] = v;
3021
+ }
3022
+ return out;
3023
+ }
3024
+ function mergeExtensionVars(base, nodeExt, targetExt, opt) {
3025
+ if (opt === void 0) return base;
3026
+ const casts = opt.casts;
3027
+ const nodeBag = castExtensionBag(nodeExt, casts);
3028
+ const targetBag = castExtensionBag(targetExt, casts);
3029
+ if (nodeBag === void 0 && targetBag === void 0) return base;
3030
+ const out = { ...base };
3031
+ const put = (name, bag) => {
3032
+ if (bag === void 0) return;
3033
+ if (Object.prototype.hasOwnProperty.call(out, name)) {
3034
+ throw optionInvalid(
3035
+ "extensionVars",
3036
+ `cannot merge extension vars: a variable named '${name}' already exists \u2014 rename via extensionVars.key / targetKey`,
3037
+ { name, existing: out[name] }
3038
+ );
3039
+ }
3040
+ out[name] = bag;
3041
+ };
3042
+ put(opt.key ?? "node", nodeBag);
3043
+ put(opt.targetKey ?? "target", targetBag);
3044
+ return out;
3045
+ }
2910
3046
  function createEngine(config) {
2911
3047
  assertEngineConfig(config);
2912
3048
  const clock = config.clock ?? defaultClock;
@@ -2916,6 +3052,7 @@ function createEngine(config) {
2916
3052
  const scheduler = config.scheduler;
2917
3053
  const approverSource = config.approverSource ?? DEFAULT_APPROVER_SOURCE;
2918
3054
  const condition = config.conditionHandler ?? createFeelConditionHandler();
3055
+ const extensionVars = config.extensionVars;
2919
3056
  const handlers = config.handlers;
2920
3057
  const decisionHandler = config.decisionHandler;
2921
3058
  const maxAuditEntries = config.maxAuditEntries;
@@ -2943,7 +3080,7 @@ function createEngine(config) {
2943
3080
  if (flow.expression === void 0) return true;
2944
3081
  const value = conditions.get(flow.id);
2945
3082
  if (value === void 0) {
2946
- throw unresolvedCondition(flow.id, flow.expression, nodeId, variables);
3083
+ throw unresolvedCondition(flow.id, flow.expression, nodeId, variables, flow.to);
2947
3084
  }
2948
3085
  return value;
2949
3086
  };
@@ -3029,13 +3166,18 @@ function createEngine(config) {
3029
3166
  instanceId: state.instanceId
3030
3167
  });
3031
3168
  }
3169
+ const nodeExt = graph.extensionsOf(pending.nodeId);
3170
+ const targetExt = pending.toNodeId === void 0 ? void 0 : graph.extensionsOf(pending.toNodeId);
3032
3171
  conditions.set(
3033
3172
  pending.flowId,
3034
3173
  await evaluateCondition(condition, pending.expression, {
3035
3174
  instanceId: state.instanceId,
3036
3175
  nodeId: pending.nodeId,
3037
3176
  // ★ 用**到达该网关那一刻**的变量快照(哨兵带来),不是提交前的旧值
3038
- variables: pending.variables
3177
+ // ⚠️ 扩展属性只在**求值上下文**里并入,绝不写回 state(ADR-009 细则⑨)
3178
+ variables: mergeExtensionVars(pending.variables, nodeExt, targetExt, extensionVars),
3179
+ nodeExtensions: nodeExt,
3180
+ targetExtensions: targetExt
3039
3181
  })
3040
3182
  );
3041
3183
  }
@@ -521,6 +521,25 @@ interface ConditionCtx {
521
521
  instanceId: string;
522
522
  nodeId: string;
523
523
  variables: Readonly<Record<string, unknown>>;
524
+ /**
525
+ * ★ **当前节点**(`nodeId`,即条件所在的网关 / 活动)上的宿主自定义扩展属性(ADR-009)。
526
+ *
527
+ * - 键**带前缀**原样给出(`acme:priority`),引擎**不解释**其语义;
528
+ * - 排除 `floken:*`(引擎自己的键);**只含标量**;
529
+ * - 没有 → `undefined`(不填空对象)。
530
+ *
531
+ * ⚠️ 它**恒给**(不需任何配置);"自动并入 `variables`"才需要 opt-in(见 `EngineConfig.extensionVars`)。
532
+ */
533
+ nodeExtensions: Readonly<Record<string, unknown>> | undefined;
534
+ /**
535
+ * ★ 该条件所在**顺序流的目标节点**上的自定义扩展属性(ADR-009 细则②)。
536
+ *
537
+ * 为什么必须两个都给:条件挂在**顺序流**上,而"加急等级"这类业务属性通常挂在
538
+ * **审批节点**(目标)而不是网关(当前节点)上 —— 只给一个,另一个场景就得回退到手写 handler。
539
+ *
540
+ * ⚠️ 求值时**拿不到目标**(如宿主在门 2 自提供 `conditionsOf` 闭包)→ `undefined`。
541
+ */
542
+ targetExtensions: Readonly<Record<string, unknown>> | undefined;
524
543
  }
525
544
  /**
526
545
  * 网关分支 / 顺序流条件求值。**不注入 = 内置 `@floken-io/feel`**(S-FEEL 子集)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floken-io/engine",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Token-based process engine with Chinese-style approval actions for floken",
5
5
  "repository": { "type": "git", "url": "git+https://github.com/floken-io/floken-engine.git" },
6
6
  "bugs": { "url": "https://github.com/floken-io/floken-engine/issues" },