@fluidgrids/workflows 0.0.0-stage → 2026.1027.1

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.
Files changed (60) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +208 -2
  3. package/dist/assemble-CrpUyd_3.d.ts +587 -0
  4. package/dist/assemble-DEf3znGD.d.cts +587 -0
  5. package/dist/chunk-3M34AUIY.cjs +27 -0
  6. package/dist/chunk-5AW745OG.js +25 -0
  7. package/dist/chunk-5LUIDMF4.js +42 -0
  8. package/dist/chunk-6AIAC2FP.cjs +185 -0
  9. package/dist/chunk-6BEW4QD6.js +95 -0
  10. package/dist/chunk-6U446SKY.js +263 -0
  11. package/dist/chunk-7YR7DI7B.cjs +274 -0
  12. package/dist/chunk-CSYBNN3V.cjs +252 -0
  13. package/dist/chunk-E3APHA6O.cjs +199 -0
  14. package/dist/chunk-EEAIM35V.js +748 -0
  15. package/dist/chunk-G4UEPUGO.cjs +46 -0
  16. package/dist/chunk-IHKXLG6I.js +246 -0
  17. package/dist/chunk-MBBKGD7K.js +440 -0
  18. package/dist/chunk-OSPTJCDI.cjs +102 -0
  19. package/dist/chunk-TQF7MYEN.cjs +771 -0
  20. package/dist/chunk-TWOYB4Y7.cjs +76 -0
  21. package/dist/chunk-UB2BHU2M.js +174 -0
  22. package/dist/chunk-UJNHTS6D.js +181 -0
  23. package/dist/chunk-UWQWD7BP.cjs +931 -0
  24. package/dist/chunk-W32BSO3M.js +74 -0
  25. package/dist/chunk-XBIID5DY.js +893 -0
  26. package/dist/chunk-Y7NWR3WH.cjs +450 -0
  27. package/dist/codegen/index.cjs +18 -0
  28. package/dist/codegen/index.d.cts +15 -0
  29. package/dist/codegen/index.d.ts +15 -0
  30. package/dist/codegen/index.js +5 -0
  31. package/dist/expressions-DhHaqyar.d.cts +62 -0
  32. package/dist/expressions-DhHaqyar.d.ts +62 -0
  33. package/dist/hash-CZkTXPCX.d.cts +15 -0
  34. package/dist/hash-CZkTXPCX.d.ts +15 -0
  35. package/dist/index.cjs +117 -0
  36. package/dist/index.d.cts +27 -0
  37. package/dist/index.d.ts +27 -0
  38. package/dist/index.js +4 -0
  39. package/dist/ir/index.cjs +1246 -0
  40. package/dist/ir/index.d.cts +838 -0
  41. package/dist/ir/index.d.ts +838 -0
  42. package/dist/ir/index.js +1122 -0
  43. package/dist/lower-CXggJTIo.d.ts +67 -0
  44. package/dist/lower-DAzvsJCo.d.cts +67 -0
  45. package/dist/manifest-Bp9ZJlav.d.ts +60 -0
  46. package/dist/manifest-Duf-gHdO.d.cts +60 -0
  47. package/dist/node-evaluator-CH3XCJND.cjs +17 -0
  48. package/dist/node-evaluator-TULXA3WE.js +8 -0
  49. package/dist/quickjs/index.cjs +46 -0
  50. package/dist/quickjs/index.d.cts +18 -0
  51. package/dist/quickjs/index.d.ts +18 -0
  52. package/dist/quickjs/index.js +44 -0
  53. package/dist/roundtrip/index.cjs +1448 -0
  54. package/dist/roundtrip/index.d.cts +290 -0
  55. package/dist/roundtrip/index.d.ts +290 -0
  56. package/dist/roundtrip/index.js +1397 -0
  57. package/dist/types-Dr2Fn1Ax.d.cts +225 -0
  58. package/dist/types-Dr2Fn1Ax.d.ts +225 -0
  59. package/package.json +143 -4
  60. package/schema/ir-v2.schema.json +655 -0
@@ -0,0 +1,450 @@
1
+ 'use strict';
2
+
3
+ var chunkTQF7MYEN_cjs = require('./chunk-TQF7MYEN.cjs');
4
+ var chunk7YR7DI7B_cjs = require('./chunk-7YR7DI7B.cjs');
5
+ var chunkE3APHA6O_cjs = require('./chunk-E3APHA6O.cjs');
6
+ var worker_threads = require('worker_threads');
7
+
8
+ // src/roundtrip/isolate.ts
9
+ var DETERMINISM_VIOLATION = "DeterminismViolation";
10
+ `(() => {
11
+ const NAME = ${JSON.stringify(DETERMINISM_VIOLATION)};
12
+ const fail = (what) => {
13
+ const error = new Error(
14
+ what + " is not allowed in workflow code: lowering must give the same IR on every build. Read times and random values at run time instead (for example wf.now)."
15
+ );
16
+ error.name = NAME;
17
+ throw error;
18
+ };
19
+ const lock = (target, key, value) =>
20
+ Object.defineProperty(target, key, { value, writable: false, enumerable: false, configurable: false });
21
+ const RealDate = Date;
22
+ const LockedDate = function Date(...args) {
23
+ if (new.target === undefined) return fail("Date()");
24
+ if (args.length === 0) return fail("new Date()");
25
+ return Reflect.construct(RealDate, args, new.target);
26
+ };
27
+ lock(LockedDate, "prototype", RealDate.prototype);
28
+ lock(RealDate.prototype, "constructor", LockedDate);
29
+ lock(LockedDate, "now", function now() { return fail("Date.now()"); });
30
+ lock(LockedDate, "parse", RealDate.parse);
31
+ lock(LockedDate, "UTC", RealDate.UTC);
32
+ lock(globalThis, "Date", LockedDate);
33
+ lock(Math, "random", function random() { return fail("Math.random()"); });
34
+ const crypto = globalThis.crypto;
35
+ if (crypto !== null && typeof crypto === "object") {
36
+ for (const name of ["getRandomValues", "randomUUID"]) {
37
+ if (typeof crypto[name] === "function") lock(crypto, name, function () { return fail("crypto." + name + "()"); });
38
+ }
39
+ }
40
+ const DateTimeFormat = typeof Intl === "object" && Intl !== null ? Intl.DateTimeFormat : undefined;
41
+ if (typeof DateTimeFormat === "function") {
42
+ const proto = DateTimeFormat.prototype;
43
+ const format = Object.getOwnPropertyDescriptor(proto, "format");
44
+ if (format && typeof format.get === "function") {
45
+ const get = format.get;
46
+ Object.defineProperty(proto, "format", {
47
+ get() {
48
+ const bound = get.call(this);
49
+ return (date) => (date === undefined ? fail("Intl.DateTimeFormat format()") : bound(date));
50
+ },
51
+ enumerable: false,
52
+ configurable: false,
53
+ });
54
+ }
55
+ const formatToParts = proto.formatToParts;
56
+ if (typeof formatToParts === "function") {
57
+ lock(proto, "formatToParts", function (date) {
58
+ return date === undefined ? fail("Intl.DateTimeFormat formatToParts()") : formatToParts.call(this, date);
59
+ });
60
+ }
61
+ }
62
+ })();
63
+ `;
64
+ var FAILURES = /* @__PURE__ */ new Set([
65
+ "error",
66
+ "timeout",
67
+ "determinism",
68
+ "no-collection",
69
+ "out-of-memory",
70
+ "crashed"
71
+ ]);
72
+ function isIsolateOutcome(value) {
73
+ if (!chunkE3APHA6O_cjs.isRecord(value)) return false;
74
+ if (value["ok"] === true) return typeof value["value"] === "string";
75
+ return value["ok"] === false && typeof value["failure"] === "string" && FAILURES.has(value["failure"]) && typeof value["message"] === "string";
76
+ }
77
+ function failure(kind, message) {
78
+ return { ok: false, failure: kind, message };
79
+ }
80
+
81
+ // src/roundtrip/isolate-worker.ts
82
+ var DEFAULT_STARTUP_TIMEOUT_MS = 1e4;
83
+ function runIsolateWorker(options) {
84
+ return new Promise((resolve) => {
85
+ let settled = false;
86
+ let deadline;
87
+ const worker = new worker_threads.Worker(options.source, {
88
+ eval: true,
89
+ workerData: options.workerData,
90
+ ...options.resourceLimits ? { resourceLimits: options.resourceLimits } : {}
91
+ });
92
+ const finish = (outcome) => {
93
+ if (settled) return;
94
+ settled = true;
95
+ clearTimeout(startup);
96
+ if (deadline !== void 0) clearTimeout(deadline);
97
+ void worker.terminate();
98
+ resolve(outcome);
99
+ };
100
+ const startup = setTimeout(
101
+ () => finish(failure("timeout", "The evaluation worker did not start in time")),
102
+ options.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS
103
+ );
104
+ worker.once("online", () => {
105
+ clearTimeout(startup);
106
+ deadline = setTimeout(
107
+ () => finish(
108
+ failure(
109
+ "timeout",
110
+ `Evaluating the workflow code timed out after ${options.timeoutMs} ms`
111
+ )
112
+ ),
113
+ options.timeoutMs
114
+ );
115
+ });
116
+ worker.once("message", (message) => {
117
+ finish(
118
+ isIsolateOutcome(message) ? message : failure("crashed", "The evaluation worker returned an unexpected message")
119
+ );
120
+ });
121
+ worker.once("error", (error) => {
122
+ const outOfMemory = "code" in error && error.code === "ERR_WORKER_OUT_OF_MEMORY";
123
+ finish(
124
+ outOfMemory ? failure("out-of-memory", "Evaluating the workflow code ran out of memory") : failure("crashed", `The evaluation worker failed: ${error.message}`)
125
+ );
126
+ });
127
+ worker.once("exit", (code) => {
128
+ finish(failure("crashed", `The evaluation worker exited (code ${code}) without a result`));
129
+ });
130
+ });
131
+ }
132
+
133
+ // src/generated/runtime-source.ts
134
+ var RUNTIME_SOURCE = 'function isRecord(value) {\n return typeof value === "object" && value !== null && !Array.isArray(value);\n}\nfunction setOwn(target, key, value) {\n Object.defineProperty(target, key, {\n value,\n enumerable: true,\n writable: true,\n configurable: true\n });\n}\nfunction ownValue(record, key) {\n return Object.hasOwn(record, key) ? record[key] : void 0;\n}\nfunction cloneJson(value) {\n return structuredCloneJson(value);\n}\nfunction structuredCloneJson(value) {\n if (Array.isArray(value)) return value.map(structuredCloneJson);\n if (isRecord(value)) {\n const out = {};\n for (const [key, child] of Object.entries(value)) {\n if (child !== void 0) setOwn(out, key, structuredCloneJson(child));\n }\n return out;\n }\n return value;\n}\n\nfunction op(nodeType, version, operation) {\n return {\n kind: "operation",\n nodeType,\n ...version !== void 0 ? { version } : {},\n ...operation !== void 0 ? { operation } : {}\n };\n}\nfunction trigger(nodeType, version, operation) {\n return { ...op(nodeType, version, operation), role: "trigger" };\n}\nfunction isOperation(value) {\n return typeof value === "object" && value !== null && value.kind === "operation" && typeof value.nodeType === "string";\n}\nvar triggers = {\n api: trigger("internal:api"),\n manual: trigger("internal:api"),\n schedule: trigger("internal:schedule"),\n webhook: trigger("internal:webhook"),\n form: trigger("internal:form"),\n errorTrigger: trigger("internal:error-trigger"),\n workflowCall: trigger("internal:workflow-call"),\n rssFeed: trigger("internal:rss-feed"),\n httpPoll: trigger("internal:http-poll"),\n databasePoll: trigger("internal:database-poll"),\n emailImap: trigger("internal:email-imap"),\n fileUpload: trigger("internal:file-upload"),\n recordChange: trigger("internal:record-change"),\n stateChange: trigger("internal:state-change")\n};\nvar internalNodes = {\n merge: op("internal:merge"),\n if: op("internal:if"),\n switch: op("internal:switch"),\n loop: op("internal:loop"),\n set: op("internal:set"),\n filter: op("internal:filter"),\n aggregate: op("internal:aggregate"),\n wait: op("internal:wait"),\n noop: op("internal:noop"),\n code: op("internal:code"),\n http: op("internal:http"),\n transform: op("internal:transform"),\n validate: op("internal:validate"),\n log: op("internal:log"),\n error: op("internal:error"),\n subworkflow: op("internal:subworkflow"),\n compareDatasets: op("internal:compare-datasets"),\n splitOut: op("internal:split-out"),\n limit: op("internal:limit"),\n dedupe: op("internal:dedupe"),\n sort: op("internal:sort"),\n dataTable: op("internal:datatable"),\n respondToWebhook: op("internal:respond-to-webhook"),\n sendAndWait: op("internal:send-and-wait"),\n codeWorkflow: op("internal:code-workflow"),\n waitForSignal: op("internal:wait-for-signal")\n};\nvar nodes = {\n internal: internalNodes,\n /**\n * Any node type, including ones the registry snapshot does not know. Typed\n * as JSON in and out.\n */\n custom(nodeType, operation, version) {\n return op(nodeType, version, operation);\n }\n};\n\nvar IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nfunction segmentFromKey(key) {\n return /^(0|[1-9][0-9]{0,8})$/.test(key) ? { kind: "index", index: Number(key) } : { kind: "key", key };\n}\nfunction formatPath(path) {\n return path.map((segment) => {\n if (segment.kind === "index") return `[${segment.index}]`;\n return IDENTIFIER.test(segment.key) ? `.${segment.key}` : `[${JSON.stringify(segment.key)}]`;\n }).join("");\n}\nfunction formatRefExpression(ref) {\n const path = formatPath(ref.path);\n switch (ref.root.kind) {\n case "node":\n return `$node[${JSON.stringify(ref.root.nodeId)}].item${path}`;\n case "json":\n return `$json${path}`;\n case "vars":\n return `$vars${path}`;\n case "now":\n return `$now${path}`;\n case "execution":\n return `$execution${path}`;\n }\n}\nfunction formatRefTemplate(ref) {\n return `{{ ${formatRefExpression(ref)} }}`;\n}\n\nvar REF_MODEL = /* @__PURE__ */ Symbol.for("fluidgrids.workflows.ref");\nvar PREDICATE = /* @__PURE__ */ Symbol.for("fluidgrids.workflows.predicate");\nvar PREDICATE_OPERATORS = {\n eq: { operator: "==", unary: false },\n neq: { operator: "!=", unary: false },\n gt: { operator: ">", unary: false },\n gte: { operator: ">=", unary: false },\n lt: { operator: "<", unary: false },\n lte: { operator: "<=", unary: false },\n contains: { operator: "contains", unary: false },\n notContains: { operator: "notContains", unary: false },\n startsWith: { operator: "startsWith", unary: false },\n endsWith: { operator: "endsWith", unary: false },\n matches: { operator: "regex", unary: false },\n hasProperty: { operator: "hasProperty", unary: false },\n isEmpty: { operator: "isEmpty", unary: true },\n isNotEmpty: { operator: "isNotEmpty", unary: true },\n isNull: { operator: "isNull", unary: true },\n isNotNull: { operator: "isNotNull", unary: true },\n isTrue: { operator: "isTrue", unary: true },\n isFalse: { operator: "isFalse", unary: true }\n};\nvar BuilderError = class extends Error {\n constructor(message) {\n super(message);\n this.name = "BuilderError";\n }\n};\nfunction isRuleValue(value) {\n return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);\n}\nfunction conditionField(ref) {\n const segments = ref.path.map((segment) => {\n const text = segment.kind === "index" ? String(segment.index) : segment.key;\n if (text === "" || text.includes(".")) {\n throw new BuilderError(\n `A condition cannot address the key "${text}" (condition fields are dot-separated); use a raw condition tree`\n );\n }\n return text;\n });\n switch (ref.root.kind) {\n case "node":\n if (ref.root.nodeId.includes(".")) {\n throw new BuilderError(\n `A condition cannot address node "${ref.root.nodeId}"; its id contains a dot`\n );\n }\n return ["WorkflowState", ref.root.nodeId, ...segments].join(".");\n case "json":\n return ["output", ...segments].join(".");\n default:\n throw new BuilderError(\n `A condition can compare node outputs and $json only, not $${ref.root.kind}; use an expression`\n );\n }\n}\nfunction makePredicate(target, operator, args, unary) {\n const rule = { type: "rule", field: conditionField(target), operator };\n if (!unary) {\n if (args.length < 1 || !isRuleValue(args[0])) {\n throw new BuilderError(\n `The "${operator}" comparison needs a string, number, boolean or null value`\n );\n }\n rule.value = args[0];\n } else if (args.length > 0) {\n throw new BuilderError(`The "${operator}" comparison takes no value`);\n }\n return { [PREDICATE]: rule };\n}\nfunction callOnRef(model, args) {\n const last = model.path[model.path.length - 1];\n if (last?.kind !== "key") {\n throw new BuilderError("A reference is not a function; call a comparison such as .gte(500)");\n }\n const parent = { root: model.root, path: model.path.slice(0, -1) };\n if (last.key === "is") {\n const [operator, ...rest] = args;\n if (typeof operator !== "string" || operator === "") {\n throw new BuilderError(".is(operator, value) needs an operator name");\n }\n return makePredicate(parent, operator, rest, rest.length === 0);\n }\n const known = PREDICATE_OPERATORS[last.key];\n if (!known) {\n throw new BuilderError(`"${last.key}" is not a comparison; references are data, not functions`);\n }\n return makePredicate(parent, known.operator, args, known.unary);\n}\nfunction makeRef(root, path = []) {\n const model = { root, path };\n const render = () => formatRefTemplate(model);\n const target = function fluidgridsRef() {\n return void 0;\n };\n const handler = {\n get(_target, property) {\n if (property === REF_MODEL) return model;\n if (property === Symbol.toPrimitive) return render;\n if (typeof property === "symbol") return void 0;\n if (property === "then") return void 0;\n if (property === "toJSON" || property === "toString" || property === "valueOf") return render;\n return makeRef(root, [...path, segmentFromKey(property)]);\n },\n apply(_target, _thisArg, args) {\n return callOnRef(model, args);\n },\n has(_target, property) {\n return property === REF_MODEL;\n },\n set() {\n throw new BuilderError("References are read-only");\n }\n };\n return new Proxy(target, handler);\n}\nfunction refModelOf(value) {\n if ((typeof value === "function" || typeof value === "object") && value !== null && REF_MODEL in value) {\n const model = value[REF_MODEL];\n return model;\n }\n return void 0;\n}\nfunction isPredicate(value) {\n return typeof value === "object" && value !== null && PREDICATE in value;\n}\nfunction predicateTree(predicate) {\n return predicate[PREDICATE];\n}\nfunction group(combinator, predicates) {\n if (predicates.length === 0)\n throw new BuilderError(`${combinator.toLowerCase()}() needs at least one condition`);\n for (const predicate of predicates) {\n if (!isPredicate(predicate))\n throw new BuilderError(`${combinator.toLowerCase()}() takes conditions only`);\n }\n return { [PREDICATE]: { type: "group", combinator, children: predicates.map(predicateTree) } };\n}\nfunction and(...predicates) {\n return group("AND", predicates);\n}\nfunction or(...predicates) {\n return group("OR", predicates);\n}\nfunction conditionTreeOf(predicate) {\n const tree = predicateTree(predicate);\n return tree.type === "group" ? tree : { type: "group", combinator: "AND", children: [tree] };\n}\nfunction expr(strings, ...values) {\n let out = strings[0] ?? "";\n values.forEach((value, index) => {\n const model = refModelOf(value);\n if (model) out += formatRefTemplate(model);\n else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {\n out += String(value);\n } else {\n throw new BuilderError("expr`\u2026` interpolates references, strings, numbers and booleans only");\n }\n out += strings[index + 1] ?? "";\n });\n return out;\n}\n\nvar RECORDING_FORMAT = "fluidgrids.tier1-recording";\nvar RECORDING_FORMAT_VERSION = 1;\nvar WORKFLOW_MARKER = "fluidgrids.tier1-workflow";\nfunction edge(from, options = {}) {\n return { kind: "edge", from, ...options };\n}\nfunction isTier1Workflow(value) {\n return isRecord(value) && value["__fluidgrids"] === WORKFLOW_MARKER && isRecord(value["recording"]);\n}\nfunction deepMerge(base, extra, path) {\n for (const [key, value] of Object.entries(extra)) {\n const existing = ownValue(base, key);\n if (isRecord(existing) && isRecord(value)) {\n deepMerge(existing, value, `${path}.${key}`);\n } else if (existing !== void 0) {\n throw new BuilderError(\n `extra sets ${path}.${key}, which the builder already wrote; use the typed option`\n );\n } else {\n setOwn(base, key, lowerValue(value, `${path}.${key}`));\n }\n }\n}\nfunction lowerValue(value, path) {\n const model = refModelOf(value);\n if (model) return formatRefTemplate(model);\n if (value === null || typeof value === "string" || typeof value === "boolean") return value;\n if (typeof value === "number") {\n if (!Number.isFinite(value)) throw new BuilderError(`${path} is not a finite number`);\n return value;\n }\n if (Array.isArray(value))\n return value.map((item, index) => lowerValue(item, `${path}[${index}]`));\n if (isPredicate(value)) {\n throw new BuilderError(`${path} is a condition; conditions belong in wf.if({ condition })`);\n }\n if (isRecord(value)) {\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n throw new BuilderError(`${path} is not a plain object`);\n }\n const out = {};\n for (const [key, child] of Object.entries(value)) {\n if (child === void 0) continue;\n setOwn(out, key, lowerValue(child, `${path}.${key}`));\n }\n return out;\n }\n throw new BuilderError(`${path} cannot be stored in a workflow definition (${typeof value})`);\n}\nfunction lowerObject(value, path) {\n const lowered = lowerValue(value, path);\n if (!isRecord(lowered)) throw new BuilderError(`${path} must be an object`);\n return lowered;\n}\nfunction slugify(text) {\n const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);\n return /^[a-z]/.test(slug) ? slug : `node-${slug || "x"}`;\n}\nfunction handleRef(nodeId, handle) {\n return { kind: "handle", nodeId, handle };\n}\nfunction nodeRef(id) {\n return {\n kind: "node",\n id,\n // An untyped reference serves every output type; the static type comes from the operation.\n output: makeRef({ kind: "node", nodeId: id }),\n handle: (name) => handleRef(id, name)\n };\n}\nfunction isNodeRef(value) {\n return isRecord(value) && value["kind"] === "node" && typeof value["id"] === "string";\n}\nfunction isHandleRef(value) {\n return isRecord(value) && value["kind"] === "handle" && typeof value["nodeId"] === "string";\n}\nfunction isEdgeSpec(value) {\n return isRecord(value) && value["kind"] === "edge" && "from" in value;\n}\nfunction sourceOf(from) {\n if (typeof from === "string") return { source: from };\n if (isHandleRef(from)) return { source: from.nodeId, sourceHandle: from.handle };\n if (isNodeRef(from)) return { source: from.id };\n throw new BuilderError("after takes node references, handles, node ids or edge(\u2026) entries");\n}\nfunction defaultEdgeId(source, target, sourceHandle, targetHandle) {\n return ["e", source, target, sourceHandle, targetHandle].filter((part) => Boolean(part)).join("-");\n}\nvar LIMIT_UNITS = {\n s: "seconds",\n m: "minutes",\n h: "hours",\n d: "days",\n w: "weeks"\n};\nfunction limitWait(limit) {\n if (typeof limit === "string") {\n const match = /^([1-9][0-9]*)([smhdw])$/.exec(limit);\n const unit = match?.[2] !== void 0 ? LIMIT_UNITS[match[2]] : void 0;\n if (!match?.[1] || !unit)\n throw new BuilderError(`limit "${limit}" must look like 30s, 15m, 2h, 3d or 1w`);\n return { amount: Number(match[1]), unit };\n }\n return { amount: limit.amount, unit: limit.unit };\n}\nvar WorkflowBuilder = class {\n constructor(autoIds) {\n this.autoIds = autoIds;\n }\n autoIds;\n /** `$json`: the current item\'s JSON. */\n json = makeRef({ kind: "json" });\n /** `$vars`: workflow variables. */\n vars = makeRef({ kind: "vars" });\n /** `$now`. */\n now = makeRef({ kind: "now" });\n /** `$execution`: run metadata. */\n execution = makeRef({ kind: "execution" });\n nodes = [];\n edges = [];\n notes = [];\n ids = /* @__PURE__ */ new Set();\n /** Ids given explicitly; a second edge with the same explicit id is an error. */\n edgeIds = /* @__PURE__ */ new Set();\n /** Edges whose id is the default, as indices into `edges`, with the unsuffixed default id. */\n defaultIds = [];\n edgeOrder;\n /** A reference to a node by id, for nodes declared later in the source. */\n ref(id) {\n return nodeRef(id);\n }\n /** A trigger node. */\n trigger(operation, options = {}) {\n return this.add(operation, options, {});\n }\n /** A store or internal node. */\n step(operation, options = {}) {\n return this.add(operation, options, {});\n }\n /** `internal:if`. */\n if(options) {\n const { condition, ...rest } = options;\n const sugar = {};\n if (condition !== void 0) {\n if (typeof condition === "string") sugar["condition"] = condition;\n else if (isPredicate(condition)) sugar["conditionTree"] = conditionTreeOf(condition);\n else\n throw new BuilderError(\n "condition takes a comparison such as x.output.total.gte(500), and(), or() or a string"\n );\n }\n const ref = this.add(internalNodes.if, rest, sugar);\n return { ...ref, true: ref.handle("true"), false: ref.handle("false") };\n }\n /** `internal:switch`. */\n switch(options) {\n const { value, cases, ...rest } = options;\n const sugar = {};\n if (value !== void 0) sugar["switchValue"] = value;\n if (cases !== void 0) sugar["cases"] = cases;\n const ref = this.add(internalNodes.switch, rest, sugar);\n return { ...ref, case: (handle) => ref.handle(handle), default: ref.handle("default") };\n }\n /**\n * `internal:loop`. With a body callback, the nodes the callback declares\n * after `batch` form the loop body and the builder adds the return edge from\n * the node the callback returns.\n */\n loop(options, body) {\n const { items, batchSize, ...rest } = options;\n const sugar = {};\n if (items !== void 0) sugar["sourceArray"] = items;\n if (batchSize !== void 0) sugar["batchSize"] = batchSize;\n const ref = this.add(internalNodes.loop, rest, sugar);\n const loopRef = { ...ref, body: ref.handle("loop"), done: ref.handle("done") };\n if (body) {\n const last = body(loopRef.body);\n if (last !== void 0) this.connect(last, ref.id, {});\n }\n return loopRef;\n }\n /** `internal:merge`. */\n merge(options) {\n const { inputs, mode, ...rest } = options;\n const sugar = {};\n if (mode !== void 0) sugar["mode"] = mode;\n if (inputs !== void 0 && rest.after !== void 0) {\n throw new BuilderError("merge takes inputs or after, not both");\n }\n const after = inputs?.map(\n (input, index) => edge(input, { targetHandle: `input-${index + 1}` })\n );\n return this.add(internalNodes.merge, after ? { ...rest, after } : rest, sugar);\n }\n /** `internal:wait`. */\n wait(options) {\n return this.add(internalNodes.wait, options, {});\n }\n /** `internal:wait-for-signal` (IR v2). */\n waitForSignal(options) {\n const { name, limit, ...rest } = options;\n const sugar = {};\n if (name !== void 0) sugar["signalName"] = name;\n if (limit !== void 0) sugar["limitWait"] = limitWait(limit);\n const ref = this.add(internalNodes.waitForSignal, rest, sugar);\n return { ...ref, received: ref.handle("received"), timedOut: ref.handle("timedOut") };\n }\n /** `internal:send-and-wait`. */\n sendAndWait(options) {\n const ref = this.add(internalNodes.sendAndWait, options, {});\n return {\n ...ref,\n approved: ref.handle("approved"),\n declined: ref.handle("declined"),\n timedOut: ref.handle("timedOut")\n };\n }\n /** `internal:subworkflow`. */\n subworkflow(options) {\n const { key, targetVersion, input, ...rest } = options;\n const sugar = {};\n if (key !== void 0) sugar["workflowKey"] = key;\n if (targetVersion !== void 0) sugar["version"] = targetVersion;\n if (input !== void 0) sugar["inputMapping"] = input;\n return this.add(internalNodes.subworkflow, rest, sugar);\n }\n /** `internal:validate`. */\n validate(options) {\n const ref = this.add(internalNodes.validate, options, {});\n return { ...ref, valid: ref.handle("valid"), invalid: ref.handle("invalid") };\n }\n /** `internal:compare-datasets`. */\n compareDatasets(options) {\n const ref = this.add(internalNodes.compareDatasets, options, {});\n return {\n ...ref,\n inAOnly: ref.handle("inAOnly"),\n inBOnly: ref.handle("inBOnly"),\n same: ref.handle("same"),\n different: ref.handle("different")\n };\n }\n /** `internal:set`. */\n set(options) {\n return this.add(internalNodes.set, options, {});\n }\n /** `internal:filter`. */\n filter(options) {\n return this.add(internalNodes.filter, options, {});\n }\n /** `internal:aggregate`. */\n aggregate(options) {\n return this.add(internalNodes.aggregate, options, {});\n }\n /** `internal:split-out`. */\n splitOut(options) {\n return this.add(internalNodes.splitOut, options, {});\n }\n /** `internal:limit`. */\n limit(options) {\n return this.add(internalNodes.limit, options, {});\n }\n /** `internal:dedupe`. */\n dedupe(options) {\n return this.add(internalNodes.dedupe, options, {});\n }\n /** `internal:sort`. */\n sort(options) {\n return this.add(internalNodes.sort, options, {});\n }\n /** `internal:datatable`. */\n dataTable(options) {\n return this.add(internalNodes.dataTable, options, {});\n }\n /** `internal:http`. */\n http(options) {\n return this.add(internalNodes.http, options, {});\n }\n /** `internal:code`. The code is a string (`args.code`), so it round-trips byte for byte. */\n code(options) {\n return this.add(internalNodes.code, options, {});\n }\n /** `internal:transform`. */\n transform(options) {\n return this.add(internalNodes.transform, options, {});\n }\n /** `internal:log`. */\n log(options) {\n return this.add(internalNodes.log, options, {});\n }\n /** `internal:respond-to-webhook`. */\n respondToWebhook(options) {\n return this.add(internalNodes.respondToWebhook, options, {});\n }\n /** `internal:error` (stop and error). */\n stopAndError(options) {\n return this.add(internalNodes.error, options, {});\n }\n /** `internal:noop`. */\n noop(options) {\n return this.add(internalNodes.noop, options, {});\n }\n /** A sticky note on the canvas. */\n note(content, options) {\n if (typeof content !== "string") throw new BuilderError("A note takes Markdown text");\n this.claimId(options.id, "sticky note");\n const near = options.near === void 0 ? void 0 : typeof options.near === "string" ? options.near : options.near.id;\n this.notes.push({\n note: {\n id: options.id,\n type: "sticky-note",\n width: 260,\n height: 170,\n zIndex: -1,\n data: { content, color: options.color ?? "yellow" }\n },\n ...near !== void 0 ? { near } : {}\n });\n }\n /** An edge added outside `after`, for example a loop\'s return edge. */\n connect(from, to, options = {}) {\n const target = typeof to === "string" ? to : to.id;\n this.addEdge(edge(from, options), target);\n }\n /**\n * Put `data.edges` in this exact order. Each entry names one edge, by id or,\n * for an edge without an id, by its position in declaration order; every edge\n * appears once. The lifter emits this when a stored definition\'s edge order\n * differs from the order the code declares its edges in.\n */\n edgeOrderIs(order) {\n if (this.edgeOrder) throw new BuilderError("edgeOrderIs() may be called once");\n this.edgeOrder = [...order];\n }\n /** The recording, once the callback has returned. */\n finish(key, document) {\n this.assignDefaultEdgeIds();\n return {\n format: RECORDING_FORMAT,\n formatVersion: RECORDING_FORMAT_VERSION,\n key,\n document,\n nodes: this.nodes,\n edges: this.edges,\n ...this.edgeOrder ? { edgeOrder: this.edgeOrder } : {},\n notes: this.notes\n };\n }\n claimId(id, what) {\n if (typeof id !== "string" || id === "") throw new BuilderError(`A ${what} needs an id`);\n if (this.ids.has(id)) throw new BuilderError(`Duplicate id "${id}"`);\n this.ids.add(id);\n }\n resolveId(options, operation) {\n if (options.id !== void 0) return options.id;\n if (!this.autoIds) {\n throw new BuilderError(`Every node needs an id (a ${operation.nodeType} node has none)`);\n }\n const base = slugify(options.label ?? operation.nodeType.split(/[:/]/).pop() ?? "node");\n let candidate = base;\n for (let suffix = 2; this.ids.has(candidate); suffix++) candidate = `${base}-${suffix}`;\n return candidate;\n }\n /**\n * Give every edge declared without an id its default id. Explicit ids are\n * claimed first, so a default never takes an id the code names; a default that\n * is taken gets `-2`, `-3`, \u2026 in declaration order. The result depends only on\n * the builder calls.\n */\n assignDefaultEdgeIds() {\n const taken = new Set(this.edgeIds);\n for (const { index, base } of this.defaultIds) {\n let id = base;\n for (let suffix = 2; taken.has(id); suffix++) id = `${base}-${suffix}`;\n taken.add(id);\n const built = this.edges[index];\n if (built) built.id = id;\n }\n this.defaultIds.length = 0;\n }\n addEdge(spec, target) {\n const { source, sourceHandle } = sourceOf(spec.from);\n const explicit = typeof spec.id === "string" ? spec.id : void 0;\n if (spec.id !== void 0 && spec.id !== false && explicit === void 0)\n throw new BuilderError("An edge id must be a string or false");\n if (explicit !== void 0) {\n if (this.edgeIds.has(explicit)) throw new BuilderError(`Duplicate edge id "${explicit}"`);\n this.edgeIds.add(explicit);\n }\n const base = spec.id === void 0 ? defaultEdgeId(source, target, sourceHandle, spec.targetHandle) : void 0;\n const id = explicit ?? base;\n if (base !== void 0) this.defaultIds.push({ index: this.edges.length, base });\n const built = id !== void 0 ? { id, source, target } : { source, target };\n if (sourceHandle !== void 0) built["sourceHandle"] = sourceHandle;\n if (spec.targetHandle !== void 0) built["targetHandle"] = spec.targetHandle;\n if (spec.type !== false) built["type"] = spec.type ?? "deletable";\n if (spec.label !== void 0) built["label"] = spec.label;\n const where = `edge ${id ?? `${source}->${target}`}`;\n if (spec.data !== void 0) built["data"] = lowerObject(spec.data, `${where} data`);\n if (spec.mapping !== void 0)\n built["mapping"] = lowerObject(spec.mapping, `${where} mapping`);\n if (spec.extra !== void 0)\n deepMerge(built, lowerObject(spec.extra, `${where} extra`), where);\n this.edges.push(built);\n }\n add(operation, options, sugar) {\n if (!isOperation(operation)) {\n throw new BuilderError(\n "The first argument must be a node operation (nodes.*, triggers.* or a generated client)"\n );\n }\n if (typeof options !== "object" || options === null || Array.isArray(options)) {\n throw new BuilderError("Node options must be an object");\n }\n const id = this.resolveId(options, operation);\n this.claimId(id, "node");\n const where = `node ${id}`;\n const node = { id, type: operation.nodeType };\n if (options.label !== void 0) node["label"] = options.label;\n const operationName = options.operation === false ? void 0 : options.operation ?? operation.operation;\n if (operationName !== void 0) node["operation_name"] = operationName;\n const version = options.version === false ? void 0 : options.version ?? operation.version;\n if (version !== void 0) node["version"] = version;\n const args = {};\n const explicitArgs = options.args === void 0 ? void 0 : lowerObject(options.args, `${where} args`);\n const sugarArgs = lowerObject(sugar, `${where} args`);\n if (explicitArgs || Object.keys(sugarArgs).length > 0) {\n for (const key of Object.keys(sugarArgs)) {\n if (explicitArgs && Object.hasOwn(explicitArgs, key)) {\n throw new BuilderError(`${where}: "${key}" is set twice (by an option and in args)`);\n }\n }\n args["state"] = { ...explicitArgs, ...sugarArgs };\n }\n if (options.connection !== void 0 && options.connections !== void 0) {\n throw new BuilderError(`${where}: use connection (names) or connections (ids), not both`);\n }\n const connectionNames = options.connection === void 0 ? void 0 : typeof options.connection === "string" ? [options.connection] : [...options.connection];\n if (connectionNames?.some((name) => typeof name !== "string" || name === "")) {\n throw new BuilderError(`${where}: connection names must be non-empty strings`);\n }\n if (options.connections !== void 0)\n args["connections"] = lowerValue(options.connections, `${where} connections`);\n if (options.credentials !== void 0)\n args["credentials"] = lowerValue(options.credentials, `${where} credentials`);\n if (options.context !== void 0)\n args["context"] = lowerObject(options.context, `${where} context`);\n if (Object.keys(args).length > 0 || connectionNames) node["args"] = args;\n const hasSugar = options.retry !== void 0 || options.timeout !== void 0 || options.onError !== void 0;\n if (options.errorConfig !== void 0 && hasSugar) {\n throw new BuilderError(`${where}: use errorConfig or retry / timeout / onError, not both`);\n }\n if (options.errorConfig !== void 0) {\n node["errorConfig"] = lowerObject(options.errorConfig, `${where} errorConfig`);\n } else if (hasSugar) {\n const errorConfig = {};\n if (typeof options.onError === "string") errorConfig["onError"] = options.onError;\n else if (options.onError !== void 0) {\n errorConfig["onError"] = "branch";\n errorConfig["errorBranchId"] = options.onError.branch;\n }\n if (options.retry !== void 0)\n errorConfig["retryConfig"] = lowerObject(options.retry, `${where} retry`);\n if (options.timeout !== void 0)\n errorConfig["timeout"] = lowerObject(options.timeout, `${where} timeout`);\n node["errorConfig"] = errorConfig;\n }\n if (options.executionSettings !== void 0) {\n const settings = lowerObject(options.executionSettings, `${where} executionSettings`);\n node["executionSettings"] = settings;\n if (settings["runOn"] !== void 0)\n node["data"] = { executionSettings: { runOn: settings["runOn"] } };\n }\n if (options.disabled !== void 0) node["disabled"] = options.disabled;\n if (options.extra !== void 0)\n deepMerge(node, lowerObject(options.extra, `${where} extra`), where);\n this.nodes.push({\n // Built field by field above from lowered JSON.\n node,\n ...connectionNames ? { connectionNames } : {}\n });\n const after = options.after === void 0 ? [] : Array.isArray(options.after) ? options.after : [options.after];\n for (const entry of after) {\n this.addEdge(isEdgeSpec(entry) ? entry : edge(entry), id);\n }\n return nodeRef(id);\n }\n};\nfunction defineWorkflow(options, build) {\n if (typeof options !== "object" || options === null || typeof options.key !== "string" || options.key === "") {\n throw new BuilderError("defineWorkflow needs a key");\n }\n const builder = new WorkflowBuilder(options.autoIds === true);\n build(builder);\n const document = { key: options.key };\n if (options.name !== void 0) document["name"] = options.name;\n if (options.description !== void 0) document["description"] = options.description;\n if (options.interface !== void 0)\n document["interface"] = lowerObject(options.interface, "interface");\n if (options.code !== void 0) document["code"] = lowerObject(options.code, "code");\n if (options.config !== void 0) document["config"] = lowerObject(options.config, "config");\n if (options.meta !== void 0) document["meta"] = lowerObject(options.meta, "meta");\n if (options.tags !== void 0) document["tags"] = lowerValue(options.tags, "tags");\n if (options.extra !== void 0)\n deepMerge(document, lowerObject(options.extra, "extra"), "workflow");\n const recording = builder.finish(options.key, document);\n return { __fluidgrids: WORKFLOW_MARKER, key: options.key, recording };\n}\n\nvar LAYOUT_COLUMN_WIDTH = 300;\nvar LAYOUT_ROW_HEIGHT = 170;\nfunction autoLayout(nodeIds, edges) {\n const depth = /* @__PURE__ */ new Map();\n const roots = nodeIds.filter((id) => !edges.some((edge2) => edge2.target === id));\n const queue = [...roots];\n for (const id of queue) depth.set(id, 0);\n const guardLimit = nodeIds.length * nodeIds.length + 1;\n for (let guard = 0; queue.length > 0 && guard < guardLimit; guard++) {\n const current = queue.shift();\n if (current === void 0) break;\n const currentDepth = depth.get(current) ?? 0;\n for (const edge2 of edges) {\n if (edge2.source !== current) continue;\n const next = currentDepth + 1;\n if (next > (depth.get(edge2.target) ?? -1) && next < nodeIds.length) {\n depth.set(edge2.target, next);\n queue.push(edge2.target);\n }\n }\n }\n const maxDepth = Math.max(0, ...depth.values());\n const columns = /* @__PURE__ */ new Map();\n for (const id of nodeIds) {\n const column = depth.get(id) ?? maxDepth + 1;\n columns.set(column, [...columns.get(column) ?? [], id]);\n }\n const positions = /* @__PURE__ */ new Map();\n for (const [column, ids] of columns) {\n ids.forEach((id, row) => {\n positions.set(id, {\n x: column * LAYOUT_COLUMN_WIDTH,\n // `+ 0` turns the -0 Math.round can return into 0, so JSON never sees -0.\n y: Math.round((row - (ids.length - 1) / 2) * LAYOUT_ROW_HEIGHT) + 0\n });\n });\n }\n return positions;\n}\n\nvar SDK_VERSION = "2026.1027.1";\n\nvar LAYOUT_FORMAT = "fluidgrids.layout";\nvar LAYOUT_FORMAT_VERSION = 1;\nvar UnresolvedConnectionsError = class extends Error {\n constructor(names) {\n super(`Create these connections in the workspace first: ${names.join(", ")}`);\n this.names = names;\n this.name = "UnresolvedConnectionsError";\n }\n names;\n};\nfunction resolveConnection(resolver, name) {\n if (!resolver) return void 0;\n if (typeof resolver === "function") return resolver(name);\n return Object.hasOwn(resolver, name) ? resolver[name] : void 0;\n}\nfunction parseLayoutSidecar(value) {\n if (!isRecord(value) || value["format"] !== LAYOUT_FORMAT || value["formatVersion"] !== LAYOUT_FORMAT_VERSION) {\n throw new BuilderError(\n `A layout file must have format "${LAYOUT_FORMAT}" and formatVersion ${LAYOUT_FORMAT_VERSION}`\n );\n }\n const positions = value["positions"];\n if (!isRecord(positions)) throw new BuilderError("A layout file needs a positions object");\n const parsed = {};\n for (const [id, position] of Object.entries(positions)) {\n if (!isRecord(position) || typeof position["x"] !== "number" || typeof position["y"] !== "number") {\n throw new BuilderError(`The layout position of "${id}" must be { x, y }`);\n }\n setOwn(parsed, id, { x: position["x"], y: position["y"] });\n }\n const sidecar = {\n format: LAYOUT_FORMAT,\n formatVersion: LAYOUT_FORMAT_VERSION,\n positions: parsed\n };\n if (value["stickyNotes"] !== void 0) {\n if (!Array.isArray(value["stickyNotes"]) || !value["stickyNotes"].every(isRecord)) {\n throw new BuilderError("stickyNotes in a layout file must be an array of objects");\n }\n sidecar.stickyNotes = cloneJson(value["stickyNotes"]);\n }\n if (value["metaLayout"] !== void 0) {\n if (!isRecord(value["metaLayout"]))\n throw new BuilderError("metaLayout in a layout file must be an object");\n sidecar.metaLayout = cloneJson(value["metaLayout"]);\n }\n return sidecar;\n}\nfunction orderEdges(edges, order) {\n if (!order) return edges;\n const indexById = /* @__PURE__ */ new Map();\n edges.forEach((item, index) => {\n if (typeof item.id === "string") indexById.set(item.id, index);\n });\n const indices = order.map(\n (entry) => typeof entry === "number" ? entry : indexById.get(entry) ?? -1\n );\n const valid = indices.length === edges.length && new Set(indices).size === indices.length && indices.every((index) => Number.isInteger(index) && index >= 0 && index < edges.length);\n if (!valid)\n throw new BuilderError(\n "edgeOrderIs() must name every edge exactly once, by id or declaration position"\n );\n return indices.map((index) => edges[index]);\n}\nfunction assembleRecording(recording, options = {}) {\n const missing = /* @__PURE__ */ new Set();\n const nodes2 = recording.nodes.map((recorded) => {\n const node = cloneJson(recorded.node);\n if (recorded.connectionNames) {\n const refs = [];\n for (const name of recorded.connectionNames) {\n const ref = resolveConnection(options.connections, name);\n if (ref) refs.push({ connectionId: ref.connectionId, adapter: ref.adapter });\n else missing.add(name);\n }\n const args = isRecord(node["args"]) ? node["args"] : {};\n args["connections"] = refs;\n node["args"] = args;\n }\n return node;\n });\n if (missing.size > 0) throw new UnresolvedConnectionsError([...missing].sort());\n const edges = orderEdges(cloneJson(recording.edges), recording.edgeOrder);\n const ids = nodes2.map((node) => String(node["id"]));\n const positions = options.layout?.positions ?? {};\n const needsAuto = ids.some((id) => !Object.hasOwn(positions, id));\n const auto = needsAuto ? autoLayout(ids, edges) : /* @__PURE__ */ new Map();\n for (const node of nodes2) {\n const id = String(node["id"]);\n const position = Object.hasOwn(positions, id) ? positions[id] : auto.get(id);\n if (position) node["position"] = { x: position.x, y: position.y };\n }\n const stickyNotes = options.layout?.stickyNotes !== void 0 || recording.notes.length > 0 ? [...options.layout?.stickyNotes ? cloneJson(options.layout.stickyNotes) : []] : void 0;\n for (const recorded of recording.notes) {\n const anchor = recorded.near !== void 0 ? nodes2.find((node) => node["id"] === recorded.near) : void 0;\n const anchorPosition = isRecord(anchor?.["position"]) ? anchor["position"] : { x: 0, y: 0 };\n const x = typeof anchorPosition["x"] === "number" ? anchorPosition["x"] : 0;\n const y = typeof anchorPosition["y"] === "number" ? anchorPosition["y"] : 0;\n stickyNotes?.push({ ...cloneJson(recorded.note), position: { x, y: y - 220 } });\n }\n const document = cloneJson(recording.document);\n const extraData = isRecord(document["data"]) ? document["data"] : {};\n for (const key of ["nodes", "edges", "stickyNotes"]) {\n if (Object.hasOwn(extraData, key))\n throw new BuilderError(`extra.data.${key} is written by the builder`);\n }\n delete document["data"];\n const authoring = options.authoring ?? {\n mode: "canvas",\n generator: "sdk-ts",\n generatorVersion: SDK_VERSION\n };\n const out = { schemaVersion: 2 };\n for (const key of ["key", "name", "description", "interface", "code"]) {\n if (document[key] !== void 0) out[key] = document[key];\n }\n out["authoring"] = authoring;\n out["data"] = {\n ...extraData,\n nodes: nodes2,\n edges,\n ...stickyNotes !== void 0 ? { stickyNotes } : {}\n };\n for (const key of ["config"]) if (document[key] !== void 0) out[key] = document[key];\n if (options.pinnedData !== void 0) out["pinnedData"] = cloneJson(options.pinnedData);\n if (options.layout?.metaLayout !== void 0) {\n const meta = isRecord(document["meta"]) ? document["meta"] : {};\n if (meta["layout"] !== void 0)\n throw new BuilderError("meta.layout comes from the layout file; do not set it in code");\n meta["layout"] = cloneJson(options.layout.metaLayout);\n document["meta"] = meta;\n }\n for (const [key, value] of Object.entries(document)) {\n if (!Object.hasOwn(out, key) && value !== void 0) setOwn(out, key, value);\n }\n return out;\n}\nfunction toIR(workflow, options = {}) {\n if (!isTier1Workflow(workflow))\n throw new BuilderError("toIR takes the value defineWorkflow returned");\n return assembleRecording(workflow.recording, options);\n}\n\nvar COLLECTION_FORMAT = "fluidgrids.tier1-collection";\nfunction collectWorkflowExports(modules) {\n const workflows = [];\n for (const [module, namespace] of modules) {\n for (const exportName of Object.keys(namespace).sort()) {\n const value = namespace[exportName];\n if (isTier1Workflow(value))\n workflows.push({ module, exportName, recording: value.recording });\n }\n }\n const collection = { format: COLLECTION_FORMAT, workflows };\n return JSON.stringify(collection);\n}\nexport {\n BuilderError,\n COLLECTION_FORMAT,\n LAYOUT_FORMAT,\n LAYOUT_FORMAT_VERSION,\n PREDICATE_OPERATORS,\n RECORDING_FORMAT,\n RECORDING_FORMAT_VERSION,\n SDK_VERSION,\n UnresolvedConnectionsError,\n WorkflowBuilder,\n and,\n assembleRecording,\n collectWorkflowExports,\n defaultEdgeId,\n defineWorkflow,\n edge,\n expr,\n internalNodes,\n isOperation,\n isTier1Workflow,\n nodes,\n op,\n or,\n parseLayoutSidecar,\n toIR,\n trigger,\n triggers\n};\n';
135
+
136
+ // src/roundtrip/lower.ts
137
+ var EVALUATION_GLOBAL = "__fluidgridsBundle";
138
+ var ESBUILD_VERSION = "0.28.2";
139
+ var LowerError = class extends Error {
140
+ constructor(message, reason) {
141
+ super(message);
142
+ this.reason = reason;
143
+ this.name = "LowerError";
144
+ }
145
+ reason;
146
+ };
147
+ var DeterminismViolationError = class extends LowerError {
148
+ constructor(message) {
149
+ super(message, "determinism");
150
+ this.name = "DeterminismViolation";
151
+ }
152
+ };
153
+ function outcomeToCollection(outcome) {
154
+ if (outcome.ok) return outcome.value;
155
+ switch (outcome.failure) {
156
+ case "determinism":
157
+ throw new DeterminismViolationError(outcome.message);
158
+ case "error":
159
+ throw new LowerError(`Evaluating the workflow code failed: ${outcome.message}`, "error");
160
+ default:
161
+ throw new LowerError(outcome.message, outcome.failure);
162
+ }
163
+ }
164
+ var DEFAULT_MAX_OUTPUT = 16 * 1024 * 1024;
165
+ var DEFAULT_MAX_SOURCE = 1024 * 1024;
166
+ var DEFAULT_MAX_BUNDLE = 1536 * 1024;
167
+ function utf8Length(text) {
168
+ let bytes = 0;
169
+ for (let index = 0; index < text.length; index++) {
170
+ const unit = text.charCodeAt(index);
171
+ if (unit < 128) bytes += 1;
172
+ else if (unit < 2048) bytes += 2;
173
+ else if (unit >= 55296 && unit <= 56319 && index + 1 < text.length) {
174
+ const next = text.charCodeAt(index + 1);
175
+ if (next >= 56320 && next <= 57343) {
176
+ bytes += 4;
177
+ index++;
178
+ } else bytes += 3;
179
+ } else bytes += 3;
180
+ }
181
+ return bytes;
182
+ }
183
+ var PROJECT_NS = "fluidgrids-project";
184
+ var SDK_NS = "fluidgrids-sdk";
185
+ var ENTRY_NS = "fluidgrids-entry";
186
+ var ENTRY_PATH = "__entry__.js";
187
+ function normalizePath(path) {
188
+ const parts = [];
189
+ for (const part of path.split("/")) {
190
+ if (part === "" || part === ".") continue;
191
+ if (part === "..") {
192
+ if (parts.length === 0) throw new LowerError(`"${path}" leaves the project`);
193
+ parts.pop();
194
+ } else parts.push(part);
195
+ }
196
+ return parts.join("/");
197
+ }
198
+ function dirname(path) {
199
+ const index = path.lastIndexOf("/");
200
+ return index === -1 ? "" : path.slice(0, index);
201
+ }
202
+ function resolveRelative(files, importer, specifier) {
203
+ const base = normalizePath(`${dirname(importer)}/${specifier}`);
204
+ const candidates = [base, `${base}.ts`, `${base}/index.ts`, base.replace(/\.(m?js)$/, ".ts")];
205
+ const found = candidates.find((candidate) => files.has(candidate));
206
+ if (!found)
207
+ throw new LowerError(`Cannot resolve "${specifier}" from ${importer || "the project"}`);
208
+ return found;
209
+ }
210
+ function defaultEntries(paths) {
211
+ return paths.filter((path) => path.endsWith(".ts") && !path.endsWith(".d.ts") && !path.endsWith(".test.ts")).filter(
212
+ (path) => !path.startsWith(".fluidgrids/") && !path.startsWith("node_modules/") && !path.startsWith("tests/")
213
+ ).sort();
214
+ }
215
+ function entrySource(entries) {
216
+ const imports = entries.map(
217
+ (path, index) => `import * as m${index} from ${JSON.stringify(`./${path}`)};`
218
+ );
219
+ const list = entries.map((path, index) => `[${JSON.stringify(path)}, m${index}]`).join(", ");
220
+ return `import { collectWorkflowExports } from "@fluidgrids/workflows";
221
+ ${imports.join("\n")}
222
+ export const __fluidgrids = collectWorkflowExports([${list}]);
223
+ `;
224
+ }
225
+ async function bundleProject(files, entries, esbuild) {
226
+ for (const entry of entries)
227
+ if (!files.has(entry)) throw new LowerError(`Entry ${entry} is not in the project files`);
228
+ const result = await esbuild.build({
229
+ entryPoints: [ENTRY_PATH],
230
+ bundle: true,
231
+ write: false,
232
+ format: "esm",
233
+ platform: "neutral",
234
+ target: "es2023",
235
+ charset: "utf8",
236
+ legalComments: "none",
237
+ logLevel: "silent",
238
+ plugins: [
239
+ {
240
+ name: "fluidgrids-project",
241
+ setup(build) {
242
+ build.onResolve({ filter: /.*/ }, (args) => {
243
+ if (args.kind === "entry-point") return { path: ENTRY_PATH, namespace: ENTRY_NS };
244
+ if (args.path === "@fluidgrids/workflows")
245
+ return { path: "workflows.mjs", namespace: SDK_NS };
246
+ if (args.path.startsWith("./") || args.path.startsWith("../")) {
247
+ const importer = args.namespace === PROJECT_NS ? args.importer : "";
248
+ try {
249
+ return { path: resolveRelative(files, importer, args.path), namespace: PROJECT_NS };
250
+ } catch (error) {
251
+ return {
252
+ errors: [{ text: error instanceof Error ? error.message : String(error) }]
253
+ };
254
+ }
255
+ }
256
+ return {
257
+ errors: [
258
+ {
259
+ text: `Workflow code may import project files and "@fluidgrids/workflows" only, not "${args.path}"`
260
+ }
261
+ ]
262
+ };
263
+ });
264
+ build.onLoad({ filter: /.*/, namespace: ENTRY_NS }, () => ({
265
+ contents: entrySource(entries),
266
+ loader: "js"
267
+ }));
268
+ build.onLoad({ filter: /.*/, namespace: SDK_NS }, () => ({
269
+ contents: RUNTIME_SOURCE,
270
+ loader: "js"
271
+ }));
272
+ build.onLoad({ filter: /.*/, namespace: PROJECT_NS }, (args) => {
273
+ const contents = files.get(args.path);
274
+ if (contents === void 0)
275
+ return { errors: [{ text: `${args.path} is not in the project files` }] };
276
+ return {
277
+ contents,
278
+ loader: args.path.endsWith(".json") ? "json" : args.path.endsWith(".js") ? "js" : "ts"
279
+ };
280
+ });
281
+ }
282
+ }
283
+ ]
284
+ });
285
+ const output = result.outputFiles?.[0];
286
+ if (!output) throw new LowerError("esbuild produced no bundle");
287
+ return output.text;
288
+ }
289
+ async function defaultEsbuild() {
290
+ return import('esbuild');
291
+ }
292
+ async function defaultEvaluator() {
293
+ const { createNodeVmEvaluator } = await import('./node-evaluator-CH3XCJND.cjs');
294
+ return createNodeVmEvaluator();
295
+ }
296
+ function fail(message) {
297
+ throw new LowerError(`The workflow code returned an invalid recording: ${message}`);
298
+ }
299
+ function isStringArray(value) {
300
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
301
+ }
302
+ function checkRecording(value) {
303
+ if (!chunkE3APHA6O_cjs.isRecord(value)) fail("not an object");
304
+ if (value["format"] !== chunkTQF7MYEN_cjs.RECORDING_FORMAT || value["formatVersion"] !== chunkTQF7MYEN_cjs.RECORDING_FORMAT_VERSION) {
305
+ fail("unknown recording format (is the bundle built with this SDK version?)");
306
+ }
307
+ if (typeof value["key"] !== "string" || value["key"] === "") fail("missing key");
308
+ if (!chunkE3APHA6O_cjs.isRecord(value["document"])) fail("missing document");
309
+ if (!Array.isArray(value["nodes"]) || !Array.isArray(value["edges"]) || !Array.isArray(value["notes"])) {
310
+ fail("nodes, edges and notes must be arrays");
311
+ }
312
+ for (const recorded of value["nodes"]) {
313
+ if (!chunkE3APHA6O_cjs.isRecord(recorded) || !chunkE3APHA6O_cjs.isRecord(recorded["node"])) fail("a node entry is not an object");
314
+ const node = recorded["node"];
315
+ if (typeof node["id"] !== "string" || typeof node["type"] !== "string")
316
+ fail("a node has no id or type");
317
+ if (recorded["connectionNames"] !== void 0 && !isStringArray(recorded["connectionNames"])) {
318
+ fail("connectionNames must be strings");
319
+ }
320
+ }
321
+ for (const edge of value["edges"]) {
322
+ if (!chunkE3APHA6O_cjs.isRecord(edge) || typeof edge["source"] !== "string" || typeof edge["target"] !== "string") {
323
+ fail("an edge has no source or target");
324
+ }
325
+ if (edge["id"] !== void 0 && typeof edge["id"] !== "string")
326
+ fail("an edge id must be a string");
327
+ }
328
+ for (const note of value["notes"]) {
329
+ if (!chunkE3APHA6O_cjs.isRecord(note) || !chunkE3APHA6O_cjs.isRecord(note["note"])) fail("a note entry is not an object");
330
+ if (note["near"] !== void 0 && typeof note["near"] !== "string")
331
+ fail("a note anchor must be a node id");
332
+ }
333
+ const order = value["edgeOrder"];
334
+ if (order !== void 0 && !(Array.isArray(order) && order.every((entry) => typeof entry === "string" || Number.isInteger(entry)))) {
335
+ fail("edgeOrder must list edge ids or positions");
336
+ }
337
+ return value;
338
+ }
339
+ function readJsonFile(files, path) {
340
+ const text = files.get(path);
341
+ if (text === void 0) return void 0;
342
+ try {
343
+ return JSON.parse(text);
344
+ } catch {
345
+ throw new LowerError(`${path} is not valid JSON`);
346
+ }
347
+ }
348
+ function toFileMap(files) {
349
+ const map = /* @__PURE__ */ new Map();
350
+ const entries = Array.isArray(files) ? files.map((file) => [file.path, file.content]) : Object.entries(files);
351
+ for (const [path, content] of entries) {
352
+ const normalized = normalizePath(path);
353
+ if (map.has(normalized)) throw new LowerError(`${normalized} is given twice`);
354
+ map.set(normalized, content);
355
+ }
356
+ return map;
357
+ }
358
+ async function lower(files, options = {}) {
359
+ const fileMap = toFileMap(files);
360
+ const maxSource = options.maxSourceBytes ?? DEFAULT_MAX_SOURCE;
361
+ let sourceBytes = 0;
362
+ for (const content of fileMap.values()) sourceBytes += utf8Length(content);
363
+ if (sourceBytes > maxSource)
364
+ throw new LowerError(
365
+ `The workflow sources are ${sourceBytes} bytes; the limit is ${maxSource}`,
366
+ "too-large"
367
+ );
368
+ const entries = options.entries ? options.entries.map(normalizePath) : defaultEntries([...fileMap.keys()]);
369
+ if (entries.length === 0) throw new LowerError("No workflow modules to lower");
370
+ const esbuild = options.esbuild ?? await defaultEsbuild();
371
+ const bundle = await bundleProject(fileMap, entries, esbuild);
372
+ const maxBundle = options.maxBundleBytes ?? DEFAULT_MAX_BUNDLE;
373
+ const bundleBytes = utf8Length(bundle);
374
+ if (bundleBytes > maxBundle)
375
+ throw new LowerError(
376
+ `The workflow bundle is ${bundleBytes} bytes; the limit is ${maxBundle}`,
377
+ "too-large"
378
+ );
379
+ const evaluator = options.evaluator ?? await defaultEvaluator();
380
+ const code = evaluator.format === "esm" ? bundle : (await esbuild.transform(bundle, {
381
+ format: "iife",
382
+ globalName: EVALUATION_GLOBAL,
383
+ target: "es2023",
384
+ platform: "neutral",
385
+ charset: "utf8",
386
+ legalComments: "none",
387
+ logLevel: "silent"
388
+ })).code;
389
+ const output = await evaluator.evaluate(code);
390
+ if (typeof output !== "string") throw new LowerError("The evaluation returned no collection");
391
+ if (output.length > (options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT))
392
+ throw new LowerError("The workflow code produced too much output", "too-large");
393
+ let parsed;
394
+ try {
395
+ parsed = JSON.parse(output);
396
+ } catch {
397
+ throw new LowerError("The evaluation returned invalid JSON");
398
+ }
399
+ if (!chunkE3APHA6O_cjs.isRecord(parsed) || parsed["format"] !== chunkTQF7MYEN_cjs.COLLECTION_FORMAT || !Array.isArray(parsed["workflows"])) {
400
+ fail("not a workflow collection");
401
+ }
402
+ const hash = chunk7YR7DI7B_cjs.sourceHash([...fileMap].map(([path, content]) => ({ path, content })));
403
+ const seen = /* @__PURE__ */ new Map();
404
+ const workflows = [];
405
+ for (const item of parsed["workflows"]) {
406
+ if (!chunkE3APHA6O_cjs.isRecord(item) || typeof item["module"] !== "string" || typeof item["exportName"] !== "string")
407
+ fail("bad entry");
408
+ const recording = checkRecording(item["recording"]);
409
+ const where = `${item["module"]}#${item["exportName"]}`;
410
+ const previous = seen.get(recording.key);
411
+ if (previous)
412
+ throw new LowerError(
413
+ `Workflow key "${recording.key}" is defined twice (${previous} and ${where})`
414
+ );
415
+ seen.set(recording.key, where);
416
+ const layoutValue = readJsonFile(fileMap, `workflows/${recording.key}.layout.json`);
417
+ const pinnedValue = readJsonFile(fileMap, `workflows/${recording.key}.pinned.json`);
418
+ if (pinnedValue !== void 0 && !chunkE3APHA6O_cjs.isRecord(pinnedValue))
419
+ throw new LowerError(`workflows/${recording.key}.pinned.json must be an object`);
420
+ const layout = layoutValue === void 0 ? void 0 : chunkTQF7MYEN_cjs.parseLayoutSidecar(layoutValue);
421
+ const definition = chunkTQF7MYEN_cjs.assembleRecording(recording, {
422
+ ...layout ? { layout } : {},
423
+ ...options.connections ? { connections: options.connections } : {},
424
+ ...pinnedValue !== void 0 ? { pinnedData: pinnedValue } : {},
425
+ authoring: {
426
+ mode: "canvas",
427
+ generator: "sdk-ts",
428
+ generatorVersion: chunkTQF7MYEN_cjs.SDK_VERSION,
429
+ sourceHash: hash
430
+ }
431
+ });
432
+ workflows.push({
433
+ key: recording.key,
434
+ module: item["module"],
435
+ exportName: item["exportName"],
436
+ definition
437
+ });
438
+ }
439
+ return { buildId: chunk7YR7DI7B_cjs.buildIdOf(bundle), bundle, sourceHash: hash, workflows };
440
+ }
441
+
442
+ exports.DeterminismViolationError = DeterminismViolationError;
443
+ exports.ESBUILD_VERSION = ESBUILD_VERSION;
444
+ exports.EVALUATION_GLOBAL = EVALUATION_GLOBAL;
445
+ exports.LowerError = LowerError;
446
+ exports.bundleProject = bundleProject;
447
+ exports.defaultEntries = defaultEntries;
448
+ exports.lower = lower;
449
+ exports.outcomeToCollection = outcomeToCollection;
450
+ exports.runIsolateWorker = runIsolateWorker;
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var chunkUWQWD7BP_cjs = require('../chunk-UWQWD7BP.cjs');
4
+ require('../chunk-OSPTJCDI.cjs');
5
+ require('../chunk-6AIAC2FP.cjs');
6
+ require('../chunk-CSYBNN3V.cjs');
7
+ require('../chunk-E3APHA6O.cjs');
8
+
9
+
10
+
11
+ Object.defineProperty(exports, "generateNodeClients", {
12
+ enumerable: true,
13
+ get: function () { return chunkUWQWD7BP_cjs.generateNodeClients; }
14
+ });
15
+ Object.defineProperty(exports, "registrySnapshotHash", {
16
+ enumerable: true,
17
+ get: function () { return chunkUWQWD7BP_cjs.registrySnapshotHash; }
18
+ });
@@ -0,0 +1,15 @@
1
+ import { N as NodeManifest } from '../manifest-Duf-gHdO.cjs';
2
+ import '../types-Dr2Fn1Ax.cjs';
3
+
4
+ interface GeneratedClients {
5
+ path: string;
6
+ content: string;
7
+ /** `sha256:<hex>` of the registry snapshot the file was generated from. */
8
+ registrySnapshot: string;
9
+ }
10
+ /** The registry snapshot hash: canonical JSON of every node's type, version and operation contracts. */
11
+ declare function registrySnapshotHash(manifests: readonly NodeManifest[]): string;
12
+ /** Generate `.fluidgrids/nodes.ts` or `.fluidgrids/nodes.py` for a registry snapshot. */
13
+ declare function generateNodeClients(manifests: readonly NodeManifest[], lang: 'ts' | 'py'): Promise<GeneratedClients>;
14
+
15
+ export { type GeneratedClients, generateNodeClients, registrySnapshotHash };
@@ -0,0 +1,15 @@
1
+ import { N as NodeManifest } from '../manifest-Bp9ZJlav.js';
2
+ import '../types-Dr2Fn1Ax.js';
3
+
4
+ interface GeneratedClients {
5
+ path: string;
6
+ content: string;
7
+ /** `sha256:<hex>` of the registry snapshot the file was generated from. */
8
+ registrySnapshot: string;
9
+ }
10
+ /** The registry snapshot hash: canonical JSON of every node's type, version and operation contracts. */
11
+ declare function registrySnapshotHash(manifests: readonly NodeManifest[]): string;
12
+ /** Generate `.fluidgrids/nodes.ts` or `.fluidgrids/nodes.py` for a registry snapshot. */
13
+ declare function generateNodeClients(manifests: readonly NodeManifest[], lang: 'ts' | 'py'): Promise<GeneratedClients>;
14
+
15
+ export { type GeneratedClients, generateNodeClients, registrySnapshotHash };
@@ -0,0 +1,5 @@
1
+ export { generateNodeClients, registrySnapshotHash } from '../chunk-XBIID5DY.js';
2
+ import '../chunk-6BEW4QD6.js';
3
+ import '../chunk-UB2BHU2M.js';
4
+ import '../chunk-IHKXLG6I.js';
5
+ import '../chunk-UJNHTS6D.js';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The `{{ … }}` forms that typed references lower to, and their parser.
3
+ *
4
+ * Typed references lower to the expression language the workers evaluate
5
+ * (`fluidgrids-workers/src/executor/expression-language.service.ts`). A node's
6
+ * output is `$node["<id>"].item` there: the builtin exposes `item`, `all()`,
7
+ * `first()` and `last()`, and has no `.json` property, so the
8
+ * `$node["id"].json.…` form written in parts of the spec would evaluate to
9
+ * `undefined`. The canonical form is therefore `{{ $node["<id>"].item.<path> }}`,
10
+ * with exactly one space inside each brace pair.
11
+ *
12
+ * Parsing is strict: a string is read back as a reference only if formatting
13
+ * the parsed reference reproduces the string exactly, so lifting never changes
14
+ * the text of an expression.
15
+ */
16
+ /** One path step: a property name or an array index. */
17
+ type PathSegment = {
18
+ kind: 'key';
19
+ key: string;
20
+ } | {
21
+ kind: 'index';
22
+ index: number;
23
+ };
24
+ /** The roots a typed reference can start from. */
25
+ type RefRoot = {
26
+ kind: 'node';
27
+ nodeId: string;
28
+ } | {
29
+ kind: 'json';
30
+ } | {
31
+ kind: 'vars';
32
+ } | {
33
+ kind: 'now';
34
+ } | {
35
+ kind: 'execution';
36
+ };
37
+ interface ValueRefModel {
38
+ root: RefRoot;
39
+ path: PathSegment[];
40
+ }
41
+ /** The bare expression for a reference (no braces). */
42
+ declare function formatRefExpression(ref: ValueRefModel): string;
43
+ /** `{{ <expression> }}` for a reference. */
44
+ declare function formatRefTemplate(ref: ValueRefModel): string;
45
+ /** Parse a bare reference expression; undefined unless it formats back identically. */
46
+ declare function parseRefExpression(expression: string): ValueRefModel | undefined;
47
+ /** One part of a template string: literal text or a reference. */
48
+ type TemplatePart = {
49
+ kind: 'text';
50
+ text: string;
51
+ } | {
52
+ kind: 'ref';
53
+ ref: ValueRefModel;
54
+ };
55
+ /**
56
+ * Split a string into literal text and `{{ ref }}` parts. Returns undefined when
57
+ * the string contains a `{{ … }}` that is not a canonical reference, or no
58
+ * reference at all.
59
+ */
60
+ declare function parseTemplate(text: string): TemplatePart[] | undefined;
61
+
62
+ export { type PathSegment as P, type RefRoot as R, type TemplatePart as T, type ValueRefModel as V, formatRefTemplate as a, parseTemplate as b, formatRefExpression as f, parseRefExpression as p };