@particle-academy/fancy-flow 0.59.0 → 0.63.0
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/dist/{HumanPrompt-Bw09OyS3.d.ts → HumanPrompt-BKThiyjB.d.ts} +1 -1
- package/dist/{HumanPrompt-DV5-xnDa.d.cts → HumanPrompt-Bpp3JiVH.d.cts} +1 -1
- package/dist/{chunk-M7M6GLFV.js → chunk-5H54OTKT.js} +3 -3
- package/dist/{chunk-M7M6GLFV.js.map → chunk-5H54OTKT.js.map} +1 -1
- package/dist/{chunk-MX4SJDNI.js → chunk-EO6444T2.js} +4 -4
- package/dist/{chunk-MX4SJDNI.js.map → chunk-EO6444T2.js.map} +1 -1
- package/dist/{chunk-HBE4LVPX.js → chunk-JF6WCRBU.js} +9 -7
- package/dist/chunk-JF6WCRBU.js.map +1 -0
- package/dist/{chunk-LGLOBK6Q.js → chunk-MBVX4ZRB.js} +3 -3
- package/dist/{chunk-LGLOBK6Q.js.map → chunk-MBVX4ZRB.js.map} +1 -1
- package/dist/{chunk-FGYG5JVW.js → chunk-MEIDPBKE.js} +3 -3
- package/dist/{chunk-FGYG5JVW.js.map → chunk-MEIDPBKE.js.map} +1 -1
- package/dist/{chunk-A3TLZS5B.js → chunk-RIAFHQT5.js} +3 -3
- package/dist/{chunk-A3TLZS5B.js.map → chunk-RIAFHQT5.js.map} +1 -1
- package/dist/{chunk-BHM5HGXH.js → chunk-UM4C46AF.js} +38 -14
- package/dist/chunk-UM4C46AF.js.map +1 -0
- package/dist/connectors.d.cts +1 -1
- package/dist/connectors.d.ts +1 -1
- package/dist/durable.cjs +7 -5
- package/dist/durable.cjs.map +1 -1
- package/dist/durable.js +2 -2
- package/dist/engine.cjs +138 -29
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +3 -2
- package/dist/engine.d.ts +3 -2
- package/dist/engine.js +6 -5
- package/dist/engine.js.map +1 -1
- package/dist/expr-BfXDrs4H.d.cts +149 -0
- package/dist/expr-BfXDrs4H.d.ts +149 -0
- package/dist/fields/react-fancy.d.cts +2 -2
- package/dist/fields/react-fancy.d.ts +2 -2
- package/dist/index.cjs +44 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -84
- package/dist/index.d.ts +7 -84
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.cts +3 -3
- package/dist/registry/index.d.ts +3 -3
- package/dist/{registry-1qhmRtvC.d.cts → registry-CntAdUcY.d.cts} +1 -1
- package/dist/{registry-D2qocEsY.d.ts → registry-DYoV-MQi.d.ts} +1 -1
- package/dist/registry.cjs +7 -5
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +2 -2
- package/dist/runtime.cjs +7 -5
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +3 -3
- package/dist/schema.cjs +7 -5
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/screens.cjs +7 -5
- package/dist/screens.cjs.map +1 -1
- package/dist/screens.js +4 -4
- package/dist/{types-CLSUZW8c.d.ts → types-Ckhz-YwC.d.ts} +7 -2
- package/dist/{types-BLVpNNLN.d.cts → types-D71SKA5A.d.cts} +7 -2
- package/dist/ux.cjs +7 -5
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.d.cts +1 -1
- package/dist/ux.d.ts +1 -1
- package/dist/ux.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BHM5HGXH.js.map +0 -1
- package/dist/chunk-HBE4LVPX.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `{{ }}` resolution — the TypeScript twin of `FancyFlow\Nodes\Support\Expr`.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is opt-in and not wired into `runFlow`
|
|
5
|
+
*
|
|
6
|
+
* The PHP runtime resolves expressions inside its batteries-included executors.
|
|
7
|
+
* The JS runtime never has: `runFlow` hands `node.data.config` to your executor
|
|
8
|
+
* verbatim, so every host that uses `{{ }}` today already resolves it itself.
|
|
9
|
+
* Turning resolution on inside `runFlow` would interpolate a second time over
|
|
10
|
+
* values those hosts had already substituted — silently, and only for graphs
|
|
11
|
+
* that happen to contain `{{` in their DATA.
|
|
12
|
+
*
|
|
13
|
+
* So this exports the semantics and changes no behaviour. Call it yourself:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const url = evaluateExpression(node.data.config.url, inputs);
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ## The semantics are the PHP file's, deliberately
|
|
20
|
+
*
|
|
21
|
+
* This is not a general expression language and must not grow into one. It
|
|
22
|
+
* resolves a dot-path against a context and nothing else — no arithmetic, no
|
|
23
|
+
* comparisons, no calls. Hosts that want real expressions override the executor
|
|
24
|
+
* (the PHP docblock points at symfony/expression-language for the same reason).
|
|
25
|
+
*
|
|
26
|
+
* Divergence here is a correctness bug rather than a style difference: the same
|
|
27
|
+
* graph is authored once and may run on either runtime. `suites/shared/expr`
|
|
28
|
+
* in `@particle-academy/fancy-conformance` is the fixture table both sides run,
|
|
29
|
+
* so parity is a test result instead of a claim.
|
|
30
|
+
*/
|
|
31
|
+
/** Anything a context or a resolved value can be. */
|
|
32
|
+
type ExprValue = unknown;
|
|
33
|
+
/** The context an expression resolves against — the executor's `inputs`. */
|
|
34
|
+
type ExprContext = Record<string, ExprValue>;
|
|
35
|
+
/**
|
|
36
|
+
* What a resolution attempt ANSWERS — did the path resolve, and to what.
|
|
37
|
+
*
|
|
38
|
+
* `resolvePath` cannot express this, and that is the defect it exists to fix:
|
|
39
|
+
* it returns `null` both for "this path does not exist" and for "this path
|
|
40
|
+
* exists and holds null". One value standing for two states.
|
|
41
|
+
*
|
|
42
|
+
* At the interpolation layer the collapse is worse, because `null` stringifies
|
|
43
|
+
* to `""`. A consumer put it exactly:
|
|
44
|
+
*
|
|
45
|
+
* > "An unresolvable path yields `''`, so a wrong field is indistinguishable
|
|
46
|
+
* > from an empty one at runtime."
|
|
47
|
+
*
|
|
48
|
+
* A misspelled field renders as an empty string, which looks precisely like a
|
|
49
|
+
* field that is legitimately empty. The graph runs, the node succeeds, and the
|
|
50
|
+
* output is quietly missing a value nobody is told about — worst on
|
|
51
|
+
* LLM-authored graphs, where the field name was guessed in the first place.
|
|
52
|
+
*
|
|
53
|
+
* Same shape as the four `??` collapses fixed across all four runtimes on
|
|
54
|
+
* 2026-08-26 (absent vs null), one layer up: **presence is the only correct
|
|
55
|
+
* test, and a return value that cannot express presence cannot be tested for
|
|
56
|
+
* it.** Hence a second return channel rather than a cleverer sentinel — every
|
|
57
|
+
* sentinel is a legal value for somebody.
|
|
58
|
+
*/
|
|
59
|
+
interface Resolution {
|
|
60
|
+
/** Whether the path resolved at all. `false` means it does not exist. */
|
|
61
|
+
resolved: boolean;
|
|
62
|
+
/** The value, when `resolved`. `null` when not — do not read it blind. */
|
|
63
|
+
value: ExprValue;
|
|
64
|
+
}
|
|
65
|
+
/** Thrown by the `"throw"` policy when a path does not resolve. */
|
|
66
|
+
declare class UnresolvedPathError extends Error {
|
|
67
|
+
readonly path: string;
|
|
68
|
+
constructor(path: string);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What evaluation does with a path that does not resolve.
|
|
72
|
+
*
|
|
73
|
+
* - `"empty"` — today's behaviour, and the DEFAULT. Interpolates to `""`; a
|
|
74
|
+
* whole expression yields `null`. Unchanged so that widening this API breaks
|
|
75
|
+
* nobody: 40 call sites across the runtimes assume it.
|
|
76
|
+
* - `"keep"` — leave the `{{ … }}` text in place. The failure becomes VISIBLE
|
|
77
|
+
* in the output without stopping the run, which is what you want for
|
|
78
|
+
* human-reviewed content: a rendered `{{ in.recipient_naem }}` is self-
|
|
79
|
+
* diagnosing in a way an absence never is.
|
|
80
|
+
* - `"throw"` — refuse. For hosts that would rather fail a run than deliver a
|
|
81
|
+
* silently incomplete result.
|
|
82
|
+
*
|
|
83
|
+
* Opt-in before default at the request of the consumer who reported it — the
|
|
84
|
+
* host with the most LLM-authored graphs, and so both the biggest beneficiary
|
|
85
|
+
* and the right place for it to break first if it is going to.
|
|
86
|
+
*/
|
|
87
|
+
type UnresolvedPolicy = "empty" | "keep" | "throw";
|
|
88
|
+
/** Options for {@link evaluateExpression} / {@link evaluateConfig}. */
|
|
89
|
+
interface EvaluateOptions {
|
|
90
|
+
/** What to do with a path that does not resolve. Default `"empty"`. */
|
|
91
|
+
onUnresolved?: UnresolvedPolicy;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a dot-path, reporting WHETHER it resolved.
|
|
95
|
+
*
|
|
96
|
+
* The same walk as `resolvePath` — deliberately, so the two can never disagree
|
|
97
|
+
* about what resolves; `resolvePath` is defined in terms of this one below.
|
|
98
|
+
*
|
|
99
|
+
* A note on JS having two absent values: a key present with the value
|
|
100
|
+
* `undefined` reports `resolved: false`, matching `resolvePath`'s long-standing
|
|
101
|
+
* behaviour. It cannot arise from graph data (JSON has no `undefined`), and
|
|
102
|
+
* changing it would make the two functions disagree for no reachable gain.
|
|
103
|
+
*/
|
|
104
|
+
declare function tryResolvePath(path: string, context: ExprContext): Resolution;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a dot-path against the context, honouring the `$json` / `$input`
|
|
107
|
+
* alias.
|
|
108
|
+
*
|
|
109
|
+
* Both aliases point at the `in` port value when the context has one, and at
|
|
110
|
+
* the whole context otherwise — the same fallback the PHP does, which is what
|
|
111
|
+
* makes `{{ $json.x }}` work on a trigger node that has no upstream input.
|
|
112
|
+
*
|
|
113
|
+
* A path that does not resolve returns `null`, never `undefined`: PHP has one
|
|
114
|
+
* absent value and JS has two, and letting the difference leak would make the
|
|
115
|
+
* two runtimes disagree about `{{ missing }}` for no useful reason.
|
|
116
|
+
*/
|
|
117
|
+
declare function resolvePath(path: string, context: ExprContext): ExprValue;
|
|
118
|
+
/**
|
|
119
|
+
* Evaluate a template against a context.
|
|
120
|
+
*
|
|
121
|
+
* A string that is EXACTLY one expression returns the resolved value with its
|
|
122
|
+
* type intact — `{{ $json.count }}` gives you a number, not `"3"`. Anything
|
|
123
|
+
* else interpolates each run as text. That distinction is load-bearing: it is
|
|
124
|
+
* what lets a config field carry either a value or a sentence.
|
|
125
|
+
*
|
|
126
|
+
* Non-string templates pass through untouched, so this is safe to map over a
|
|
127
|
+
* whole config object.
|
|
128
|
+
*/
|
|
129
|
+
declare function evaluateExpression(template: ExprValue, context: ExprContext, options?: EvaluateOptions): ExprValue;
|
|
130
|
+
/**
|
|
131
|
+
* Truthiness for branch / switch decisions.
|
|
132
|
+
*
|
|
133
|
+
* Mirrors PHP's rules rather than JavaScript's, because the graph is authored
|
|
134
|
+
* once and may run on either side. The two disagree in exactly the places a
|
|
135
|
+
* workflow hits: `"0"` and `"false"` are truthy in JS and falsy here, and an
|
|
136
|
+
* empty array is truthy in JS and falsy here.
|
|
137
|
+
*/
|
|
138
|
+
declare function truthy(value: ExprValue): boolean;
|
|
139
|
+
/** Coerce a value to text the way interpolation does. */
|
|
140
|
+
declare function text(value: ExprValue): string;
|
|
141
|
+
/**
|
|
142
|
+
* Resolve every string in a config object, one level of nesting at a time.
|
|
143
|
+
*
|
|
144
|
+
* The convenience most hosts actually want, and the shape their hand-rolled
|
|
145
|
+
* version usually takes. Opt-in like the rest of this module.
|
|
146
|
+
*/
|
|
147
|
+
declare function evaluateConfig<T extends Record<string, ExprValue>>(config: T, context: ExprContext, options?: EvaluateOptions): T;
|
|
148
|
+
|
|
149
|
+
export { type EvaluateOptions as E, type Resolution as R, UnresolvedPathError as U, type ExprContext as a, type ExprValue as b, type UnresolvedPolicy as c, evaluateExpression as d, evaluateConfig as e, truthy as f, tryResolvePath as g, resolvePath as r, text as t };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-
|
|
1
|
+
import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-Bpp3JiVH.cjs';
|
|
2
2
|
import 'react';
|
|
3
3
|
import '../types-JFYjPJAG.cjs';
|
|
4
4
|
import '@xyflow/react';
|
|
5
|
-
import '../types-
|
|
5
|
+
import '../types-D71SKA5A.cjs';
|
|
6
6
|
import '../pause-9iT4tCEV.cjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-
|
|
1
|
+
import { H as HumanFieldRenderers, C as ConfigFieldRenderFn } from '../HumanPrompt-BKThiyjB.js';
|
|
2
2
|
import 'react';
|
|
3
3
|
import '../types-JFYjPJAG.js';
|
|
4
4
|
import '@xyflow/react';
|
|
5
|
-
import '../types-
|
|
5
|
+
import '../types-Ckhz-YwC.js';
|
|
6
6
|
import '../pause-9iT4tCEV.js';
|
|
7
7
|
|
|
8
8
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -11445,7 +11445,7 @@ function activatedPorts(node, result) {
|
|
|
11445
11445
|
return { ports: [r.__port], value: r.value };
|
|
11446
11446
|
}
|
|
11447
11447
|
if (typeof r.branch === "string") {
|
|
11448
|
-
return { ports: [r.branch], value: r.value
|
|
11448
|
+
return { ports: [r.branch], value: Object.prototype.hasOwnProperty.call(r, "value") ? r.value : r };
|
|
11449
11449
|
}
|
|
11450
11450
|
}
|
|
11451
11451
|
const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
|
|
@@ -11607,8 +11607,9 @@ var KINDS = [
|
|
|
11607
11607
|
// ───────────── Triggers ─────────────
|
|
11608
11608
|
{
|
|
11609
11609
|
name: "@particle-academy/manual_trigger",
|
|
11610
|
-
//
|
|
11611
|
-
|
|
11610
|
+
// Returns the raw inputs MAP, not the `in` port -- flat at an entry
|
|
11611
|
+
// point, port-keyed the moment the node has an inbound edge.
|
|
11612
|
+
emits: "input-map-merged",
|
|
11612
11613
|
aliases: ["manual_trigger", "@fancy/manual_trigger"],
|
|
11613
11614
|
category: "trigger",
|
|
11614
11615
|
label: "Manual",
|
|
@@ -11641,8 +11642,9 @@ var KINDS = [
|
|
|
11641
11642
|
{ path: "cron", type: "string", description: "The cron expression that fired." },
|
|
11642
11643
|
{ path: "timezone", type: "string", description: "The timezone it was evaluated in." }
|
|
11643
11644
|
],
|
|
11644
|
-
//
|
|
11645
|
-
|
|
11645
|
+
// Merges $ctx->inputs ITSELF beside cron/timezone -- the MAP, whose
|
|
11646
|
+
// shape depends on position, not each port's payload the way `merge` does.
|
|
11647
|
+
emits: "input-map-merged",
|
|
11646
11648
|
aliases: ["schedule_trigger", "@fancy/schedule_trigger"],
|
|
11647
11649
|
category: "trigger",
|
|
11648
11650
|
label: "Schedule",
|
|
@@ -15874,9 +15876,19 @@ function interpolate(template, resolve) {
|
|
|
15874
15876
|
}
|
|
15875
15877
|
}
|
|
15876
15878
|
var FALSY_STRINGS = /* @__PURE__ */ new Set(["", "0", "false", "no", "off", "null"]);
|
|
15877
|
-
|
|
15879
|
+
var UnresolvedPathError = class extends Error {
|
|
15880
|
+
constructor(path) {
|
|
15881
|
+
super(
|
|
15882
|
+
`Expression path "${path}" did not resolve. Under the "throw" policy an unresolvable path is an error rather than an empty string.`
|
|
15883
|
+
);
|
|
15884
|
+
this.path = path;
|
|
15885
|
+
this.name = "UnresolvedPathError";
|
|
15886
|
+
}
|
|
15887
|
+
};
|
|
15888
|
+
function tryResolvePath(path, context) {
|
|
15889
|
+
const unresolved = { resolved: false, value: null };
|
|
15878
15890
|
const trimmed = path.trim();
|
|
15879
|
-
if (trimmed === "") return
|
|
15891
|
+
if (trimmed === "") return unresolved;
|
|
15880
15892
|
const segments = trimmed.split(".");
|
|
15881
15893
|
let cursor;
|
|
15882
15894
|
const head = segments[0];
|
|
@@ -15887,19 +15899,33 @@ function resolvePath(path, context) {
|
|
|
15887
15899
|
cursor = context;
|
|
15888
15900
|
}
|
|
15889
15901
|
for (const segment of segments) {
|
|
15890
|
-
if (cursor === null || cursor === void 0) return
|
|
15891
|
-
if (typeof cursor !== "object") return
|
|
15902
|
+
if (cursor === null || cursor === void 0) return unresolved;
|
|
15903
|
+
if (typeof cursor !== "object") return unresolved;
|
|
15892
15904
|
const next = cursor[segment];
|
|
15893
|
-
if (next === void 0) return
|
|
15905
|
+
if (next === void 0) return unresolved;
|
|
15894
15906
|
cursor = next;
|
|
15895
15907
|
}
|
|
15896
|
-
return cursor === void 0 ? null : cursor;
|
|
15908
|
+
return { resolved: true, value: cursor === void 0 ? null : cursor };
|
|
15909
|
+
}
|
|
15910
|
+
function resolvePath(path, context) {
|
|
15911
|
+
return tryResolvePath(path, context).value;
|
|
15897
15912
|
}
|
|
15898
|
-
function evaluateExpression(template, context) {
|
|
15913
|
+
function evaluateExpression(template, context, options = {}) {
|
|
15899
15914
|
if (typeof template !== "string") return template;
|
|
15915
|
+
const policy = options.onUnresolved ?? "empty";
|
|
15900
15916
|
const whole = wholeExpression(template.trim());
|
|
15901
|
-
if (whole !== null)
|
|
15902
|
-
|
|
15917
|
+
if (whole !== null) {
|
|
15918
|
+
const r = tryResolvePath(whole, context);
|
|
15919
|
+
if (r.resolved) return r.value;
|
|
15920
|
+
if (policy === "throw") throw new UnresolvedPathError(whole);
|
|
15921
|
+
return policy === "keep" ? template : null;
|
|
15922
|
+
}
|
|
15923
|
+
return interpolate(template, (path) => {
|
|
15924
|
+
const r = tryResolvePath(path, context);
|
|
15925
|
+
if (r.resolved) return stringify(r.value);
|
|
15926
|
+
if (policy === "throw") throw new UnresolvedPathError(path);
|
|
15927
|
+
return policy === "keep" ? `{{${path}}}` : "";
|
|
15928
|
+
});
|
|
15903
15929
|
}
|
|
15904
15930
|
function truthy(value) {
|
|
15905
15931
|
if (typeof value === "boolean") return value;
|
|
@@ -15923,10 +15949,10 @@ function stringify(value) {
|
|
|
15923
15949
|
return "";
|
|
15924
15950
|
}
|
|
15925
15951
|
}
|
|
15926
|
-
function evaluateConfig(config, context) {
|
|
15952
|
+
function evaluateConfig(config, context, options = {}) {
|
|
15927
15953
|
const out = {};
|
|
15928
15954
|
for (const [key, value] of Object.entries(config)) {
|
|
15929
|
-
out[key] = Array.isArray(value) ? value.map((v2) => evaluateExpression(v2, context)) : value !== null && typeof value === "object" ? evaluateConfig(value, context) : evaluateExpression(value, context);
|
|
15955
|
+
out[key] = Array.isArray(value) ? value.map((v2) => evaluateExpression(v2, context, options)) : value !== null && typeof value === "object" ? evaluateConfig(value, context, options) : evaluateExpression(value, context, options);
|
|
15930
15956
|
}
|
|
15931
15957
|
return out;
|
|
15932
15958
|
}
|
|
@@ -15963,6 +15989,7 @@ exports.RegistryNode = RegistryNode;
|
|
|
15963
15989
|
exports.RichInputPreview = RichInputPreview;
|
|
15964
15990
|
exports.SubgraphNode = SubgraphNode;
|
|
15965
15991
|
exports.TriggerNode = TriggerNode;
|
|
15992
|
+
exports.UnresolvedPathError = UnresolvedPathError;
|
|
15966
15993
|
exports.WORKFLOW_SCHEMA_URL = WORKFLOW_SCHEMA_URL;
|
|
15967
15994
|
exports.WORKFLOW_SCHEMA_VERSION = WORKFLOW_SCHEMA_VERSION;
|
|
15968
15995
|
exports.alignNodes = alignNodes;
|
|
@@ -16014,6 +16041,7 @@ exports.runCohort = runCohort;
|
|
|
16014
16041
|
exports.runFlow = runFlow;
|
|
16015
16042
|
exports.text = text;
|
|
16016
16043
|
exports.truthy = truthy;
|
|
16044
|
+
exports.tryResolvePath = tryResolvePath;
|
|
16017
16045
|
exports.useFlowEditor = useFlowEditor;
|
|
16018
16046
|
exports.useFlowEditorOptional = useFlowEditorOptional;
|
|
16019
16047
|
exports.useFlowHistory = useFlowHistory;
|