@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,1397 @@
1
+ import { diffDefinitions } from '../chunk-W32BSO3M.js';
2
+ import { renderTypeScript, generateNodeClients, pyWorkflowName, NameAllocator, referencedLater, formatLines, formatImport, clientNames, pyString, snakeCase, pyNumber, PY_KEYWORDS } from '../chunk-XBIID5DY.js';
3
+ export { PY_LINE_LENGTH, TS_FORMAT_OPTIONS, TS_NODES_IMPORT } from '../chunk-XBIID5DY.js';
4
+ import { operationsOf, isTriggerOperation } from '../chunk-6BEW4QD6.js';
5
+ export { createNodeVmEvaluator } from '../chunk-5AW745OG.js';
6
+ import { lower } from '../chunk-MBBKGD7K.js';
7
+ export { DeterminismViolationError, ESBUILD_VERSION, EVALUATION_GLOBAL, LowerError, bundleProject, defaultEntries, lower } from '../chunk-MBBKGD7K.js';
8
+ import { assembleRecording, LAYOUT_FORMAT_VERSION, LAYOUT_FORMAT, defineWorkflow, nodes, trigger, op, internalNodes, triggers, edge, defaultEdgeId } from '../chunk-EEAIM35V.js';
9
+ import { normalizeDefinition, irHash } from '../chunk-6U446SKY.js';
10
+ import '../chunk-5LUIDMF4.js';
11
+ import { PREDICATE_OPERATORS, BuilderError, and, or, expr, RESERVED_REF_KEYS } from '../chunk-UB2BHU2M.js';
12
+ import { stableStringify } from '../chunk-IHKXLG6I.js';
13
+ import { isRecord, setOwn, deepEqual, parseTemplate } from '../chunk-UJNHTS6D.js';
14
+
15
+ // src/roundtrip/plan.ts
16
+ var TYPED_HANDLES = {
17
+ if: ["true", "false"],
18
+ loop: ["loop", "done"],
19
+ validate: ["valid", "invalid"],
20
+ sendAndWait: ["approved", "declined", "timedOut"],
21
+ waitForSignal: ["received", "timedOut"],
22
+ compareDatasets: ["inAOnly", "inBOnly", "same", "different"],
23
+ switch: ["default"]
24
+ };
25
+ var HANDLE_PROPERTY = {
26
+ loop: { loop: "body", done: "done" }
27
+ };
28
+ var HELPER_METHODS = {
29
+ "internal:if": "if",
30
+ "internal:switch": "switch",
31
+ "internal:loop": "loop",
32
+ "internal:merge": "merge",
33
+ "internal:wait": "wait",
34
+ "internal:wait-for-signal": "waitForSignal",
35
+ "internal:send-and-wait": "sendAndWait",
36
+ "internal:subworkflow": "subworkflow",
37
+ "internal:validate": "validate",
38
+ "internal:compare-datasets": "compareDatasets",
39
+ "internal:set": "set",
40
+ "internal:filter": "filter",
41
+ "internal:aggregate": "aggregate",
42
+ "internal:split-out": "splitOut",
43
+ "internal:limit": "limit",
44
+ "internal:dedupe": "dedupe",
45
+ "internal:sort": "sort",
46
+ "internal:datatable": "dataTable",
47
+ "internal:http": "http",
48
+ "internal:code": "code",
49
+ "internal:transform": "transform",
50
+ "internal:log": "log",
51
+ "internal:respond-to-webhook": "respondToWebhook",
52
+ "internal:error": "stopAndError",
53
+ "internal:noop": "noop"
54
+ };
55
+ var INTERNAL_DESCRIPTOR_NAMES = {
56
+ "internal:code-workflow": "codeWorkflow"
57
+ };
58
+ var TRIGGER_NAMES = {
59
+ "internal:api": "api",
60
+ "internal:schedule": "schedule",
61
+ "internal:webhook": "webhook",
62
+ "internal:form": "form",
63
+ "internal:error-trigger": "errorTrigger",
64
+ "internal:workflow-call": "workflowCall",
65
+ "internal:rss-feed": "rssFeed",
66
+ "internal:http-poll": "httpPoll",
67
+ "internal:database-poll": "databasePoll",
68
+ "internal:email-imap": "emailImap",
69
+ "internal:file-upload": "fileUpload",
70
+ "internal:record-change": "recordChange",
71
+ "internal:state-change": "stateChange"
72
+ };
73
+
74
+ // src/roundtrip/build-plan.ts
75
+ var LiftError = class extends Error {
76
+ constructor(message) {
77
+ super(message);
78
+ this.name = "LiftError";
79
+ }
80
+ };
81
+ var RULE_METHOD_BY_OPERATOR = new Map(
82
+ Object.entries(PREDICATE_OPERATORS).map(([method, spec]) => [
83
+ spec.operator,
84
+ { method, unary: spec.unary }
85
+ ])
86
+ );
87
+ function literalOrStructure(value2) {
88
+ if (Array.isArray(value2)) return { k: "array", items: value2.map(literalOrStructure) };
89
+ if (isRecord(value2))
90
+ return {
91
+ k: "object",
92
+ entries: Object.entries(value2).map(([key, child]) => [key, literalOrStructure(child)])
93
+ };
94
+ if (value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean") {
95
+ return { k: "literal", value: value2 };
96
+ }
97
+ throw new LiftError(`A ${typeof value2} cannot appear in a workflow definition`);
98
+ }
99
+ function expressible(ref) {
100
+ return ref.path.every(
101
+ (segment) => segment.kind === "index" || !RESERVED_REF_KEYS.has(segment.key)
102
+ );
103
+ }
104
+ function toPlanRef(ref) {
105
+ const target = ref.root.kind === "node" ? { kind: "node", nodeId: ref.root.nodeId } : { kind: ref.root.kind };
106
+ return { target, path: ref.path };
107
+ }
108
+ function liftValue(value2, path, nodeId, items) {
109
+ if (typeof value2 === "string") {
110
+ const parts = parseTemplate(value2);
111
+ if (parts && parts.every((part) => part.kind === "text" || expressible(part.ref))) {
112
+ const [only] = parts;
113
+ if (parts.length === 1 && only?.kind === "ref") return { k: "ref", ref: toPlanRef(only.ref) };
114
+ return {
115
+ k: "template",
116
+ parts: parts.map(
117
+ (part) => part.kind === "text" ? { text: part.text } : { ref: toPlanRef(part.ref) }
118
+ )
119
+ };
120
+ }
121
+ if (/\{\{[\s\S]*?\}\}/.test(value2)) {
122
+ items.push({
123
+ nodeId,
124
+ path,
125
+ kind: "expression-verbatim",
126
+ detail: `${value2.slice(0, 120)} kept as a string; not statically typed`
127
+ });
128
+ }
129
+ if (/\$\{[\s\S]*?\}/.test(value2)) {
130
+ items.push({ nodeId, path, kind: "legacy-syntax", detail: "${\u2026} template kept verbatim" });
131
+ }
132
+ return { k: "literal", value: value2 };
133
+ }
134
+ if (Array.isArray(value2))
135
+ return {
136
+ k: "array",
137
+ items: value2.map((item, index) => liftValue(item, `${path}[${index}]`, nodeId, items))
138
+ };
139
+ if (isRecord(value2)) {
140
+ return {
141
+ k: "object",
142
+ entries: Object.entries(value2).map(([key, child]) => [
143
+ key,
144
+ liftValue(child, `${path}.${key}`, nodeId, items)
145
+ ])
146
+ };
147
+ }
148
+ return literalOrStructure(value2);
149
+ }
150
+ function liftState(type, state, nodeId, items) {
151
+ if (type !== "internal:code" || typeof state["code"] !== "string")
152
+ return liftValue(state, "args.state", nodeId, items);
153
+ return {
154
+ k: "object",
155
+ entries: Object.entries(state).map(([key, child]) => [
156
+ key,
157
+ key === "code" ? literalOrStructure(child) : liftValue(child, `args.state.${key}`, nodeId, items)
158
+ ])
159
+ };
160
+ }
161
+ function fieldRef(field, nodeIds) {
162
+ const segments = field.split(".");
163
+ const toPath = (parts) => {
164
+ if (parts.some((part) => part === "")) return void 0;
165
+ return parts.map(
166
+ (part) => /^(0|[1-9][0-9]{0,8})$/.test(part) ? { kind: "index", index: Number(part) } : { kind: "key", key: part }
167
+ );
168
+ };
169
+ if (segments[0] === "WorkflowState" && segments.length >= 2) {
170
+ const nodeId = segments[1] ?? "";
171
+ if (!nodeIds.has(nodeId)) return void 0;
172
+ const path = toPath(segments.slice(2));
173
+ return path ? { target: { kind: "node", nodeId }, path } : void 0;
174
+ }
175
+ if (segments[0] === "output") {
176
+ const path = toPath(segments.slice(1));
177
+ return path ? { target: { kind: "json" }, path } : void 0;
178
+ }
179
+ return void 0;
180
+ }
181
+ function liftRule(rule, nodeIds) {
182
+ const keys = Object.keys(rule).sort();
183
+ const allowed = ["field", "operator", "type", "value"];
184
+ if (!keys.every((key) => allowed.includes(key)) || rule["type"] !== "rule") return void 0;
185
+ if (typeof rule["field"] !== "string" || typeof rule["operator"] !== "string" || rule["operator"] === "")
186
+ return void 0;
187
+ const ref = fieldRef(rule["field"], nodeIds);
188
+ if (!ref || !ref.path.every((segment) => segment.kind === "index" || !RESERVED_REF_KEYS.has(segment.key))) {
189
+ return void 0;
190
+ }
191
+ const hasValue = Object.hasOwn(rule, "value");
192
+ const value2 = rule["value"];
193
+ if (hasValue && !(value2 === null || ["string", "number", "boolean"].includes(typeof value2)))
194
+ return void 0;
195
+ const known = RULE_METHOD_BY_OPERATOR.get(rule["operator"]);
196
+ const method = known && known.unary === !hasValue ? known.method : "is";
197
+ return {
198
+ k: "rule",
199
+ ref,
200
+ method,
201
+ operator: rule["operator"],
202
+ hasValue,
203
+ ...hasValue ? { value: value2 } : {}
204
+ };
205
+ }
206
+ function liftCondition(tree, nodeIds, top) {
207
+ if (!isRecord(tree)) return void 0;
208
+ if (tree["type"] === "rule") return top ? void 0 : liftRule(tree, nodeIds);
209
+ if (tree["type"] !== "group") return void 0;
210
+ const keys = Object.keys(tree).sort();
211
+ if (keys.join(",") !== "children,combinator,type") return void 0;
212
+ const combinator = tree["combinator"];
213
+ if (combinator !== "AND" && combinator !== "OR") return void 0;
214
+ const children = tree["children"];
215
+ if (!Array.isArray(children) || children.length === 0) return void 0;
216
+ const plan = children.map((child) => liftCondition(child, nodeIds, false));
217
+ if (!plan.every((child) => child !== void 0)) return void 0;
218
+ if (top && combinator === "AND" && plan.length === 1 && plan[0]?.k === "rule") return plan[0];
219
+ return { k: "group", combinator: combinator === "AND" ? "and" : "or", children: plan };
220
+ }
221
+ function handleAccessor(method, handle) {
222
+ if (method && TYPED_HANDLES[method]?.includes(handle)) {
223
+ return { kind: "property", name: HANDLE_PROPERTY[method]?.[handle] ?? handle };
224
+ }
225
+ if (method === "switch") return { kind: "case" };
226
+ return { kind: "generic" };
227
+ }
228
+ function edgeSourceExpr(edge2, info) {
229
+ const source = String(edge2["source"]);
230
+ if (typeof edge2["sourceHandle"] === "string") {
231
+ return {
232
+ k: "handle",
233
+ nodeId: source,
234
+ handle: edge2["sourceHandle"],
235
+ accessor: handleAccessor(info.get(source)?.method, edge2["sourceHandle"])
236
+ };
237
+ }
238
+ return { k: "node", nodeId: source };
239
+ }
240
+ function richEdge(edge2, info, items) {
241
+ const source = String(edge2["source"]);
242
+ const target = String(edge2["target"]);
243
+ const sourceHandle = typeof edge2["sourceHandle"] === "string" ? edge2["sourceHandle"] : void 0;
244
+ const targetHandle = typeof edge2["targetHandle"] === "string" ? edge2["targetHandle"] : void 0;
245
+ const options2 = [];
246
+ const extra = [];
247
+ if (edge2["id"] === void 0) options2.push(["id", { k: "literal", value: false }]);
248
+ else if (edge2["id"] !== defaultEdgeId(source, target, sourceHandle, targetHandle))
249
+ options2.push(["id", literalOrStructure(edge2["id"])]);
250
+ if (edge2["type"] === void 0) options2.push(["type", { k: "literal", value: false }]);
251
+ else if (edge2["type"] !== "deletable") options2.push(["type", literalOrStructure(edge2["type"])]);
252
+ if (targetHandle !== void 0)
253
+ options2.push(["targetHandle", { k: "literal", value: targetHandle }]);
254
+ for (const [key, value2] of Object.entries(edge2)) {
255
+ if (["id", "source", "target", "sourceHandle", "targetHandle", "type"].includes(key)) {
256
+ if ((key === "sourceHandle" || key === "targetHandle") && typeof value2 !== "string")
257
+ extra.push([key, literalOrStructure(value2)]);
258
+ continue;
259
+ }
260
+ if (key === "label" && typeof value2 === "string")
261
+ options2.push(["label", { k: "literal", value: value2 }]);
262
+ else if ((key === "data" || key === "mapping") && isRecord(value2)) {
263
+ options2.push([key, literalOrStructure(value2)]);
264
+ if (key === "mapping") {
265
+ items.push({
266
+ nodeId: target,
267
+ path: `edges[${String(edge2["id"] ?? `${source}->${target}`)}].mapping`,
268
+ kind: "legacy-syntax",
269
+ detail: "Edge mapping kept verbatim"
270
+ });
271
+ }
272
+ } else extra.push([key, literalOrStructure(value2)]);
273
+ }
274
+ if (extra.length > 0) options2.push(["extra", { k: "object", entries: extra }]);
275
+ const from = edgeSourceExpr(edge2, info);
276
+ return options2.length === 0 ? from : { k: "edge", from, options: options2 };
277
+ }
278
+ function rawEdge(edge2) {
279
+ const options2 = [
280
+ [
281
+ "id",
282
+ edge2["id"] === void 0 ? { k: "literal", value: false } : literalOrStructure(edge2["id"])
283
+ ]
284
+ ];
285
+ if (typeof edge2["type"] === "string")
286
+ options2.push(["type", { k: "literal", value: edge2["type"] }]);
287
+ else options2.push(["type", { k: "literal", value: false }]);
288
+ const extra = [];
289
+ for (const [key, value2] of Object.entries(edge2)) {
290
+ if (key === "id" || key === "source" || key === "target" || key === "type") continue;
291
+ if (key === "sourceHandle" && typeof value2 === "string") continue;
292
+ extra.push([key, literalOrStructure(value2)]);
293
+ }
294
+ if (extra.length > 0) options2.push(["extra", { k: "object", entries: extra }]);
295
+ const source = String(edge2["source"]);
296
+ const from = typeof edge2["sourceHandle"] === "string" ? { k: "handle", nodeId: source, handle: edge2["sourceHandle"], accessor: { kind: "generic" } } : { k: "node", nodeId: source };
297
+ return { k: "edge", from, options: options2 };
298
+ }
299
+ function chooseOperation(node, hasIncoming, clients, manifests, items) {
300
+ const type = String(node["type"]);
301
+ const operationName = typeof node["operation_name"] === "string" ? node["operation_name"] : void 0;
302
+ const version = typeof node["version"] === "string" ? node["version"] : void 0;
303
+ const helper = HELPER_METHODS[type];
304
+ if (helper) return { method: helper };
305
+ const internalName = INTERNAL_DESCRIPTOR_NAMES[type];
306
+ if (internalName)
307
+ return { method: "step", operation: { kind: "internal", name: internalName, nodeType: type } };
308
+ const triggerName = TRIGGER_NAMES[type];
309
+ if (triggerName)
310
+ return { method: "trigger", operation: { kind: "trigger", name: triggerName, nodeType: type } };
311
+ const client = clients.get(type);
312
+ const manifest = manifests?.find(
313
+ (candidate) => candidate.nodeType === type && candidate.nodeVersion === client?.version
314
+ );
315
+ const operation = operationName && manifest ? operationsOf(manifest).find((candidate) => candidate.name === operationName) : void 0;
316
+ const operationKey = operationName ? client?.operations.get(operationName) : void 0;
317
+ if (client && operation && operationKey) {
318
+ const isTrigger = isTriggerOperation(operation);
319
+ return {
320
+ method: isTrigger && !hasIncoming ? "trigger" : "step",
321
+ operation: {
322
+ kind: "client",
323
+ group: client.ts,
324
+ operationKey: operationKey.ts,
325
+ nodeType: type,
326
+ operation: operation.name,
327
+ ...client.version !== void 0 ? { version: client.version } : {},
328
+ ...isTrigger ? { role: "trigger" } : {}
329
+ },
330
+ writesOperation: operation.name,
331
+ ...client.version !== void 0 ? { writesVersion: client.version } : {}
332
+ };
333
+ }
334
+ if (!type.startsWith("internal:")) {
335
+ items.push({
336
+ nodeId: String(node["id"]),
337
+ kind: "unknown-node",
338
+ detail: client ? `${type} has no operation "${operationName ?? ""}" in the registry snapshot; emitted as nodes.custom()` : `${type} is not in the registry snapshot; emitted as nodes.custom()`
339
+ });
340
+ }
341
+ return {
342
+ method: hasIncoming ? "step" : "trigger",
343
+ operation: {
344
+ kind: "custom",
345
+ nodeType: type,
346
+ ...operationName !== void 0 ? { operation: operationName } : {},
347
+ ...version !== void 0 ? { version } : {}
348
+ },
349
+ ...operationName !== void 0 ? { writesOperation: operationName } : {},
350
+ ...version !== void 0 ? { writesVersion: version } : {}
351
+ };
352
+ }
353
+ var ERROR_CONFIG_KEYS = /* @__PURE__ */ new Set(["onError", "errorBranchId", "retryConfig", "timeout"]);
354
+ function errorConfigOptions(errorConfig) {
355
+ if (!isRecord(errorConfig) || Object.keys(errorConfig).length === 0) return void 0;
356
+ if (!Object.keys(errorConfig).every((key) => ERROR_CONFIG_KEYS.has(key))) return void 0;
357
+ const options2 = [];
358
+ const onError = errorConfig["onError"];
359
+ const branch = errorConfig["errorBranchId"];
360
+ if (onError === "branch") {
361
+ if (typeof branch !== "string") return void 0;
362
+ options2.push([
363
+ "onError",
364
+ { k: "object", entries: [["branch", { k: "literal", value: branch }]] }
365
+ ]);
366
+ } else {
367
+ if (branch !== void 0) return void 0;
368
+ if (onError === "stop" || onError === "continue")
369
+ options2.push(["onError", { k: "literal", value: onError }]);
370
+ else if (onError !== void 0) return void 0;
371
+ }
372
+ if (errorConfig["retryConfig"] !== void 0) {
373
+ if (!isRecord(errorConfig["retryConfig"])) return void 0;
374
+ options2.push(["retry", literalOrStructure(errorConfig["retryConfig"])]);
375
+ }
376
+ if (errorConfig["timeout"] !== void 0) {
377
+ if (!isRecord(errorConfig["timeout"])) return void 0;
378
+ options2.push(["timeout", literalOrStructure(errorConfig["timeout"])]);
379
+ }
380
+ return options2;
381
+ }
382
+ function ambiguousConnectionNames(names) {
383
+ const idByName = /* @__PURE__ */ new Map();
384
+ const ambiguous = /* @__PURE__ */ new Set();
385
+ for (const [id, name] of Object.entries(names)) {
386
+ const previous = idByName.get(name);
387
+ if (previous === void 0) idByName.set(name, id);
388
+ else if (previous !== id) ambiguous.add(name);
389
+ }
390
+ return ambiguous;
391
+ }
392
+ function connectionOptions(value2, names, nodeId, items) {
393
+ if (Array.isArray(value2) && value2.length > 0 && names) {
394
+ const ambiguous = ambiguousConnectionNames(names);
395
+ const resolved = value2.map((ref) => {
396
+ if (!isRecord(ref) || Object.keys(ref).sort().join(",") !== "adapter,connectionId")
397
+ return void 0;
398
+ const id = ref["connectionId"];
399
+ return typeof id === "string" && Object.hasOwn(names, id) ? names[id] : void 0;
400
+ });
401
+ const shared = resolved.filter(
402
+ (name) => typeof name === "string" && ambiguous.has(name)
403
+ );
404
+ if (shared.length > 0) {
405
+ items.push({
406
+ nodeId,
407
+ path: "args.connections",
408
+ kind: "connection-ambiguous",
409
+ detail: `The connection name ${[...new Set(shared)].map((name) => `"${name}"`).join(", ")} belongs to more than one connection; the connection id is kept`
410
+ });
411
+ return ["connections", literalOrStructure(value2)];
412
+ }
413
+ if (resolved.every((name) => typeof name === "string")) {
414
+ return resolved.length === 1 ? ["connection", { k: "literal", value: resolved[0] ?? "" }] : [
415
+ "connection",
416
+ { k: "array", items: resolved.map((name) => ({ k: "literal", value: name })) }
417
+ ];
418
+ }
419
+ }
420
+ if (Array.isArray(value2) && value2.length > 0) {
421
+ items.push({
422
+ nodeId,
423
+ path: "args.connections",
424
+ kind: "connection-id-only",
425
+ detail: "No connection name resolves; the connection id is kept"
426
+ });
427
+ }
428
+ return ["connections", literalOrStructure(value2)];
429
+ }
430
+ function richNode(node, choice, incoming, nodeIds, options2, items) {
431
+ const id = String(node["id"]);
432
+ const out = [["id", { k: "literal", value: id }]];
433
+ const extra = [];
434
+ const operationName = node["operation_name"];
435
+ const version = node["version"];
436
+ if (node["label"] !== void 0) {
437
+ if (typeof node["label"] === "string")
438
+ out.push(["label", { k: "literal", value: node["label"] }]);
439
+ else extra.push(["label", literalOrStructure(node["label"])]);
440
+ }
441
+ if (incoming.length === 1 && incoming[0]) out.push(["after", incoming[0]]);
442
+ else if (incoming.length > 1) out.push(["after", { k: "array", items: incoming }]);
443
+ if (operationName !== choice.writesOperation) {
444
+ if (operationName === void 0) out.push(["operation", { k: "literal", value: false }]);
445
+ else if (typeof operationName === "string")
446
+ out.push(["operation", { k: "literal", value: operationName }]);
447
+ else {
448
+ out.push(["operation", { k: "literal", value: false }]);
449
+ extra.push(["operation_name", literalOrStructure(operationName)]);
450
+ }
451
+ }
452
+ if (version !== choice.writesVersion) {
453
+ if (version === void 0) out.push(["version", { k: "literal", value: false }]);
454
+ else if (typeof version === "string") out.push(["version", { k: "literal", value: version }]);
455
+ else {
456
+ out.push(["version", { k: "literal", value: false }]);
457
+ extra.push(["version", literalOrStructure(version)]);
458
+ }
459
+ }
460
+ const args = isRecord(node["args"]) ? node["args"] : void 0;
461
+ if (node["args"] !== void 0 && !args) extra.push(["args", literalOrStructure(node["args"])]);
462
+ if (args) {
463
+ const argsExtra = [];
464
+ let state = args["state"];
465
+ if (choice.method === "if" && isRecord(state) && state["conditionTree"] !== void 0) {
466
+ const condition = liftCondition(state["conditionTree"], nodeIds, true);
467
+ if (condition) {
468
+ out.push(["condition", condition]);
469
+ const rest = { ...state };
470
+ delete rest["conditionTree"];
471
+ state = Object.keys(rest).length > 0 ? rest : void 0;
472
+ }
473
+ }
474
+ if (state !== void 0) {
475
+ if (isRecord(state)) out.push(["args", liftState(String(node["type"]), state, id, items)]);
476
+ else argsExtra.push(["state", literalOrStructure(state)]);
477
+ }
478
+ for (const [key, value2] of Object.entries(args)) {
479
+ if (key === "state") continue;
480
+ if (key === "connections")
481
+ out.push(connectionOptions(value2, options2.connectionNames, id, items));
482
+ else if (key === "credentials" && Array.isArray(value2)) {
483
+ out.push(["credentials", literalOrStructure(value2)]);
484
+ items.push({
485
+ nodeId: id,
486
+ path: "args.credentials",
487
+ kind: "legacy-syntax",
488
+ detail: "Legacy credential references kept verbatim"
489
+ });
490
+ } else if (key === "context" && isRecord(value2))
491
+ out.push(["context", literalOrStructure(value2)]);
492
+ else argsExtra.push([key, literalOrStructure(value2)]);
493
+ }
494
+ if (argsExtra.length > 0 || Object.keys(args).length === 0 && args["state"] === void 0) {
495
+ extra.push(["args", { k: "object", entries: argsExtra }]);
496
+ }
497
+ }
498
+ if (node["errorConfig"] !== void 0) {
499
+ const sugar = errorConfigOptions(node["errorConfig"]);
500
+ if (sugar) out.push(...sugar);
501
+ else out.push(["errorConfig", literalOrStructure(node["errorConfig"])]);
502
+ }
503
+ const data = isRecord(node["data"]) ? { ...node["data"] } : void 0;
504
+ if (node["executionSettings"] !== void 0) {
505
+ const settings = node["executionSettings"];
506
+ if (isRecord(settings)) {
507
+ const mirror = isRecord(data?.["executionSettings"]) ? data["executionSettings"] : void 0;
508
+ const mirrored = settings["runOn"] !== void 0 && mirror !== void 0 && Object.keys(mirror).length === 1 && deepEqual(mirror["runOn"], settings["runOn"]);
509
+ if (settings["runOn"] === void 0 || mirrored) {
510
+ out.push(["executionSettings", literalOrStructure(settings)]);
511
+ if (mirrored && data) delete data["executionSettings"];
512
+ } else {
513
+ const withoutRunOn = { ...settings };
514
+ delete withoutRunOn["runOn"];
515
+ out.push(["executionSettings", literalOrStructure(withoutRunOn)]);
516
+ extra.push([
517
+ "executionSettings",
518
+ { k: "object", entries: [["runOn", literalOrStructure(settings["runOn"])]] }
519
+ ]);
520
+ }
521
+ } else extra.push(["executionSettings", literalOrStructure(settings)]);
522
+ }
523
+ if (node["disabled"] !== void 0) {
524
+ if (typeof node["disabled"] === "boolean")
525
+ out.push(["disabled", { k: "literal", value: node["disabled"] }]);
526
+ else extra.push(["disabled", literalOrStructure(node["disabled"])]);
527
+ }
528
+ if (data && Object.keys(data).length > 0) extra.push(["data", literalOrStructure(data)]);
529
+ else if (node["data"] !== void 0 && !isRecord(node["data"]))
530
+ extra.push(["data", literalOrStructure(node["data"])]);
531
+ else if (isRecord(node["data"]) && Object.keys(node["data"]).length === 0)
532
+ extra.push(["data", { k: "object", entries: [] }]);
533
+ for (const [key, value2] of Object.entries(node)) {
534
+ if ([
535
+ "id",
536
+ "type",
537
+ "label",
538
+ "position",
539
+ "operation_name",
540
+ "version",
541
+ "args",
542
+ "errorConfig",
543
+ "executionSettings",
544
+ "disabled",
545
+ "data"
546
+ ].includes(key)) {
547
+ continue;
548
+ }
549
+ extra.push([key, literalOrStructure(value2)]);
550
+ }
551
+ if (extra.length > 0) out.push(["extra", { k: "object", entries: extra }]);
552
+ return {
553
+ kind: "node",
554
+ nodeId: id,
555
+ method: choice.method,
556
+ ...choice.operation ? { operation: choice.operation } : {},
557
+ options: out
558
+ };
559
+ }
560
+ function rawNode(node, incoming, method) {
561
+ const id = String(node["id"]);
562
+ const type = String(node["type"]);
563
+ const operation = typeof node["operation_name"] === "string" ? node["operation_name"] : void 0;
564
+ const version = typeof node["version"] === "string" ? node["version"] : void 0;
565
+ const options2 = [["id", { k: "literal", value: id }]];
566
+ if (incoming.length === 1 && incoming[0]) options2.push(["after", incoming[0]]);
567
+ else if (incoming.length > 1) options2.push(["after", { k: "array", items: incoming }]);
568
+ const extra = [];
569
+ for (const [key, value2] of Object.entries(node)) {
570
+ if (key === "id" || key === "type" || key === "position") continue;
571
+ if (key === "operation_name" && operation !== void 0) continue;
572
+ if (key === "version" && version !== void 0) continue;
573
+ extra.push([key, literalOrStructure(value2)]);
574
+ }
575
+ if (extra.length > 0) options2.push(["extra", { k: "object", entries: extra }]);
576
+ return {
577
+ kind: "node",
578
+ nodeId: id,
579
+ method,
580
+ operation: {
581
+ kind: "custom",
582
+ nodeType: type,
583
+ ...operation !== void 0 ? { operation } : {},
584
+ ...version !== void 0 ? { version } : {}
585
+ },
586
+ options: options2
587
+ };
588
+ }
589
+ var WORKFLOW_OPTION_KEYS = ["name", "description", "config", "interface", "code", "tags", "meta"];
590
+ function workflowOptions(definition, key) {
591
+ const out = [["key", { k: "literal", value: key }]];
592
+ const extra = [];
593
+ for (const option of WORKFLOW_OPTION_KEYS) {
594
+ const value2 = definition[option];
595
+ if (value2 === void 0) continue;
596
+ if (option === "meta" && isRecord(value2)) {
597
+ const meta = { ...value2 };
598
+ delete meta["layout"];
599
+ if (Object.keys(meta).length > 0) out.push(["meta", literalOrStructure(meta)]);
600
+ continue;
601
+ }
602
+ const typed = (option === "name" || option === "description") && typeof value2 === "string" || option === "tags" && Array.isArray(value2) || ["config", "interface", "code"].includes(option) && isRecord(value2);
603
+ if (typed) out.push([option, literalOrStructure(value2)]);
604
+ else extra.push([option, literalOrStructure(value2)]);
605
+ }
606
+ const data = isRecord(definition["data"]) ? definition["data"] : {};
607
+ const dataExtra = Object.entries(data).filter(
608
+ ([dataKey]) => !["nodes", "edges", "stickyNotes"].includes(dataKey)
609
+ );
610
+ if (dataExtra.length > 0)
611
+ extra.push([
612
+ "data",
613
+ { k: "object", entries: dataExtra.map(([k, v]) => [k, literalOrStructure(v)]) }
614
+ ]);
615
+ for (const [docKey, value2] of Object.entries(definition)) {
616
+ if (["schemaVersion", "key", "authoring", "data", "pinnedData", ...WORKFLOW_OPTION_KEYS].includes(
617
+ docKey
618
+ ))
619
+ continue;
620
+ extra.push([docKey, literalOrStructure(value2)]);
621
+ }
622
+ if (extra.length > 0) out.push(["extra", { k: "object", entries: extra }]);
623
+ return out;
624
+ }
625
+ function buildPlan(normalized, options2, rawNodeIds = /* @__PURE__ */ new Set(), rawEdgeIndices = /* @__PURE__ */ new Set()) {
626
+ const items = [];
627
+ const data = isRecord(normalized["data"]) ? normalized["data"] : {};
628
+ const nodes2 = Array.isArray(data["nodes"]) ? data["nodes"] : [];
629
+ const edges = Array.isArray(data["edges"]) ? data["edges"] : [];
630
+ const key = typeof normalized["key"] === "string" && normalized["key"] ? normalized["key"] : options2.workflowKey;
631
+ if (!key) throw new LiftError("The definition has no key; pass workflowKey");
632
+ const nodeIds = /* @__PURE__ */ new Set();
633
+ for (const node of nodes2) {
634
+ if (!isRecord(node) || typeof node["id"] !== "string" || node["id"] === "" || typeof node["type"] !== "string") {
635
+ throw new LiftError("Every node needs a string id and type to be lifted");
636
+ }
637
+ if (nodeIds.has(node["id"])) throw new LiftError(`Node id "${node["id"]}" is used twice`);
638
+ nodeIds.add(node["id"]);
639
+ }
640
+ const edgeIds = /* @__PURE__ */ new Set();
641
+ for (const edge2 of edges) {
642
+ if (!isRecord(edge2) || typeof edge2["source"] !== "string" || typeof edge2["target"] !== "string") {
643
+ throw new LiftError("Every edge needs a string source and target to be lifted");
644
+ }
645
+ if (edge2["id"] === void 0) continue;
646
+ if (typeof edge2["id"] !== "string") throw new LiftError("Edge ids must be strings");
647
+ if (edgeIds.has(edge2["id"])) throw new LiftError(`Edge id "${edge2["id"]}" is used twice`);
648
+ edgeIds.add(edge2["id"]);
649
+ }
650
+ const nodeRecords = nodes2.filter(isRecord);
651
+ const edgeRecords = edges.filter(isRecord);
652
+ const clients = options2.registry ? clientNames(options2.registry) : /* @__PURE__ */ new Map();
653
+ const edgeIndex = /* @__PURE__ */ new Map();
654
+ edgeRecords.forEach((edge2, index) => edgeIndex.set(edge2, index));
655
+ const incomingByTarget = /* @__PURE__ */ new Map();
656
+ for (const edge2 of edgeRecords) {
657
+ const target = String(edge2["target"]);
658
+ if (nodeIds.has(target))
659
+ incomingByTarget.set(target, [...incomingByTarget.get(target) ?? [], edge2]);
660
+ }
661
+ const info = /* @__PURE__ */ new Map();
662
+ const choices = /* @__PURE__ */ new Map();
663
+ for (const node of nodeRecords) {
664
+ const id = String(node["id"]);
665
+ const raw = rawNodeIds.has(id);
666
+ const choice = raw ? { method: (incomingByTarget.get(id)?.length ?? 0) > 0 ? "step" : "trigger" } : chooseOperation(
667
+ node,
668
+ (incomingByTarget.get(id)?.length ?? 0) > 0,
669
+ clients,
670
+ options2.registry,
671
+ items
672
+ );
673
+ choices.set(id, choice);
674
+ info.set(id, { id, method: raw ? void 0 : choice.method });
675
+ }
676
+ const edgeExpr = (edge2) => rawEdgeIndices.has(edgeIndex.get(edge2) ?? -1) ? rawEdge(edge2) : richEdge(edge2, info, items);
677
+ const statements = [];
678
+ for (const node of nodeRecords) {
679
+ const id = String(node["id"]);
680
+ const incoming = (incomingByTarget.get(id) ?? []).map(edgeExpr);
681
+ const choice = choices.get(id) ?? { method: "step" };
682
+ if (rawNodeIds.has(id)) {
683
+ items.push({
684
+ nodeId: id,
685
+ kind: "raw-node",
686
+ detail: "Written as nodes.custom() with extra so it round-trips exactly"
687
+ });
688
+ statements.push(rawNode(node, incoming, choice.method === "trigger" ? "trigger" : "step"));
689
+ } else {
690
+ statements.push(richNode(node, choice, incoming, nodeIds, options2, items));
691
+ }
692
+ }
693
+ const declared = [];
694
+ for (const node of nodeRecords) {
695
+ for (const edge2 of incomingByTarget.get(String(node["id"])) ?? [])
696
+ declared.push(edgeIndex.get(edge2) ?? -1);
697
+ }
698
+ for (const edge2 of edgeRecords) {
699
+ if (nodeIds.has(String(edge2["target"]))) continue;
700
+ const expr2 = edgeExpr(edge2);
701
+ const fromExpr = expr2.k === "edge" ? expr2.from : expr2;
702
+ const edgeOptions = expr2.k === "edge" ? expr2.options : [];
703
+ statements.push({
704
+ kind: "connect",
705
+ from: fromExpr,
706
+ to: String(edge2["target"]),
707
+ options: edgeOptions
708
+ });
709
+ declared.push(edgeIndex.get(edge2) ?? -1);
710
+ }
711
+ if (declared.some((index, position) => index !== position)) {
712
+ const order = edgeRecords.map(
713
+ (edge2, index) => typeof edge2["id"] === "string" ? edge2["id"] : declared.indexOf(index)
714
+ );
715
+ statements.push({ kind: "edgeOrder", ids: order });
716
+ }
717
+ return {
718
+ plan: { key, workflowOptions: workflowOptions(normalized, key), statements },
719
+ items,
720
+ clients
721
+ };
722
+ }
723
+ function connectionsByName(connectionNames, definition) {
724
+ const out = {};
725
+ const ambiguous = ambiguousConnectionNames(connectionNames);
726
+ const data = isRecord(definition["data"]) ? definition["data"] : {};
727
+ for (const node of Array.isArray(data["nodes"]) ? data["nodes"] : []) {
728
+ const args = isRecord(node) && isRecord(node["args"]) ? node["args"] : void 0;
729
+ for (const ref of Array.isArray(args?.["connections"]) ? args["connections"] : []) {
730
+ if (!isRecord(ref) || typeof ref["connectionId"] !== "string" || typeof ref["adapter"] !== "string")
731
+ continue;
732
+ const id = ref["connectionId"];
733
+ const name = Object.hasOwn(connectionNames, id) ? connectionNames[id] : void 0;
734
+ if (name === void 0 || ambiguous.has(name)) continue;
735
+ const previous = Object.hasOwn(out, name) ? out[name] : void 0;
736
+ if (previous && previous.adapter !== ref["adapter"]) {
737
+ ambiguous.add(name);
738
+ delete out[name];
739
+ } else setOwn(out, name, { connectionId: id, adapter: ref["adapter"] });
740
+ }
741
+ }
742
+ return out;
743
+ }
744
+
745
+ // src/roundtrip/interpret.ts
746
+ function descriptor(operation) {
747
+ switch (operation.kind) {
748
+ case "trigger": {
749
+ const value2 = triggers[operation.name];
750
+ if (!value2) throw new LiftError(`Unknown trigger ${operation.name}`);
751
+ return value2;
752
+ }
753
+ case "internal": {
754
+ const value2 = internalNodes[operation.name];
755
+ if (!value2) throw new LiftError(`Unknown internal node ${operation.name}`);
756
+ return value2;
757
+ }
758
+ case "client":
759
+ return operation.role === "trigger" ? trigger(operation.nodeType, operation.version, operation.operation) : op(operation.nodeType, operation.version, operation.operation);
760
+ case "custom":
761
+ return nodes.custom(operation.nodeType, operation.operation, operation.version);
762
+ }
763
+ }
764
+ function refValue(ref, wf, runtime) {
765
+ let current;
766
+ if (ref.target.kind === "node")
767
+ current = (runtime.get(ref.target.nodeId) ?? wf.ref(ref.target.nodeId)).output;
768
+ else current = wf[ref.target.kind];
769
+ for (const segment of ref.path) {
770
+ current = current[segment.kind === "index" ? String(segment.index) : segment.key];
771
+ }
772
+ return current;
773
+ }
774
+ function value(expression2, wf, runtime) {
775
+ switch (expression2.k) {
776
+ case "literal":
777
+ return expression2.value;
778
+ case "object":
779
+ return Object.fromEntries(
780
+ expression2.entries.map(([key, child]) => [key, value(child, wf, runtime)])
781
+ );
782
+ case "array":
783
+ return expression2.items.map((item) => value(item, wf, runtime));
784
+ case "ref":
785
+ return refValue(expression2.ref, wf, runtime);
786
+ case "template": {
787
+ const strings = [""];
788
+ const values = [];
789
+ for (const part of expression2.parts) {
790
+ if ("text" in part) strings[strings.length - 1] += part.text;
791
+ else {
792
+ values.push(refValue(part.ref, wf, runtime));
793
+ strings.push("");
794
+ }
795
+ }
796
+ return expr(Object.assign([...strings], { raw: [...strings] }), ...values);
797
+ }
798
+ case "rule": {
799
+ const target = refValue(expression2.ref, wf, runtime);
800
+ if (expression2.method === "is") {
801
+ return expression2.hasValue ? target.is(expression2.operator, expression2.value) : target.is(expression2.operator);
802
+ }
803
+ const method = target[expression2.method];
804
+ return expression2.hasValue ? method(expression2.value) : method();
805
+ }
806
+ case "group": {
807
+ const children = expression2.children.map((child) => value(child, wf, runtime));
808
+ return expression2.combinator === "and" ? and(...children) : or(...children);
809
+ }
810
+ case "node":
811
+ return runtime.get(expression2.nodeId) ?? wf.ref(expression2.nodeId);
812
+ case "handle": {
813
+ const declared = runtime.get(expression2.nodeId);
814
+ if (declared && expression2.accessor.kind === "property")
815
+ return declared[expression2.accessor.name];
816
+ return (declared ?? wf.ref(expression2.nodeId)).handle(expression2.handle);
817
+ }
818
+ case "edge":
819
+ return edge(
820
+ value(expression2.from, wf, runtime),
821
+ value({ k: "object", entries: expression2.options }, wf, runtime)
822
+ );
823
+ }
824
+ }
825
+ function literalValue(expression2) {
826
+ switch (expression2.k) {
827
+ case "literal":
828
+ return expression2.value;
829
+ case "object":
830
+ return Object.fromEntries(
831
+ expression2.entries.map(([key, child]) => [key, literalValue(child)])
832
+ );
833
+ case "array":
834
+ return expression2.items.map(literalValue);
835
+ default:
836
+ throw new LiftError("Workflow options hold JSON only");
837
+ }
838
+ }
839
+ function options(entries, wf, runtime) {
840
+ return value({ k: "object", entries }, wf, runtime);
841
+ }
842
+ function interpretPlan(plan) {
843
+ const workflowOptions2 = literalValue({
844
+ k: "object",
845
+ entries: plan.workflowOptions
846
+ });
847
+ const workflow = defineWorkflow(workflowOptions2, (wf) => {
848
+ const runtime = /* @__PURE__ */ new Map();
849
+ for (const statement of plan.statements) {
850
+ if (statement.kind === "node") {
851
+ const nodeOptions = options(statement.options, wf, runtime);
852
+ const method = wf[statement.method];
853
+ if (typeof method !== "function")
854
+ throw new LiftError(`Unknown builder method ${statement.method}`);
855
+ const result = statement.operation ? method.call(wf, descriptor(statement.operation), nodeOptions) : method.call(wf, nodeOptions);
856
+ runtime.set(statement.nodeId, result);
857
+ } else if (statement.kind === "connect") {
858
+ wf.connect(
859
+ value(statement.from, wf, runtime),
860
+ statement.to,
861
+ options(statement.options, wf, runtime)
862
+ );
863
+ } else {
864
+ wf.edgeOrderIs(statement.ids);
865
+ }
866
+ }
867
+ });
868
+ return workflow.recording;
869
+ }
870
+
871
+ // src/roundtrip/render-py.ts
872
+ var PY_SDK_MODULE = "fluidgrids_workflows";
873
+ var PY_PROJECT_MODULE = "fluidgrids_workflows.project";
874
+ var PY_REF_METHODS = /* @__PURE__ */ new Set([
875
+ "eq",
876
+ "neq",
877
+ "gt",
878
+ "gte",
879
+ "lt",
880
+ "lte",
881
+ "contains",
882
+ "not_contains",
883
+ "starts_with",
884
+ "ends_with",
885
+ "matches",
886
+ "has_property",
887
+ "is_empty",
888
+ "is_not_empty",
889
+ "is_null",
890
+ "is_not_null",
891
+ "is_true",
892
+ "is_false",
893
+ "is_"
894
+ ]);
895
+ var COMPARISON_OPERATORS = {
896
+ eq: "==",
897
+ neq: "!=",
898
+ gt: ">",
899
+ gte: ">=",
900
+ lt: "<",
901
+ lte: "<="
902
+ };
903
+ var BUILDER_METHODS = {
904
+ if: "if_",
905
+ waitForSignal: "wait_for_signal",
906
+ sendAndWait: "send_and_wait",
907
+ compareDatasets: "compare_datasets",
908
+ splitOut: "split_out",
909
+ dataTable: "data_table",
910
+ respondToWebhook: "respond_to_webhook",
911
+ stopAndError: "stop_and_error",
912
+ edgeOrderIs: "edge_order_is"
913
+ };
914
+ var atom = (text) => ({ t: "atom", text });
915
+ function isPyIdentifier(name) {
916
+ return /^[A-Za-z][A-Za-z0-9_]*$/.test(name) && !PY_KEYWORDS.has(name);
917
+ }
918
+ function nodeSource(nodeId, ctx) {
919
+ const name = ctx.vars.get(nodeId);
920
+ if (name !== void 0 && ctx.declared.has(nodeId)) return atom(name);
921
+ return {
922
+ t: "call",
923
+ func: atom("wf.ref"),
924
+ args: [{ kind: "positional", value: atom(pyString(nodeId)) }]
925
+ };
926
+ }
927
+ function refExpr(ref, ctx) {
928
+ let current = ref.target.kind === "node" ? { t: "attr", base: nodeSource(ref.target.nodeId, ctx), name: "output" } : atom(`wf.${ref.target.kind}`);
929
+ for (const segment of ref.path) {
930
+ if (segment.kind === "index")
931
+ current = { t: "subscript", base: current, indices: [atom(String(segment.index))] };
932
+ else if (isPyIdentifier(segment.key) && !PY_REF_METHODS.has(segment.key))
933
+ current = { t: "attr", base: current, name: segment.key };
934
+ else current = { t: "subscript", base: current, indices: [atom(pyString(segment.key))] };
935
+ }
936
+ return current;
937
+ }
938
+ function literal(value2) {
939
+ if (value2 === null) return atom("None");
940
+ if (typeof value2 === "boolean") return atom(value2 ? "True" : "False");
941
+ if (typeof value2 === "number") return atom(pyNumber(value2));
942
+ return atom(pyString(value2));
943
+ }
944
+ function call(func, args) {
945
+ return { t: "call", func: atom(func), args };
946
+ }
947
+ function keywordName(option) {
948
+ return snakeCase(option);
949
+ }
950
+ function expression(planExpr, ctx) {
951
+ switch (planExpr.k) {
952
+ case "literal":
953
+ return literal(planExpr.value);
954
+ case "object":
955
+ return {
956
+ t: "dict",
957
+ entries: planExpr.entries.map(([key, child]) => [
958
+ atom(pyString(key)),
959
+ expression(child, ctx)
960
+ ])
961
+ };
962
+ case "array":
963
+ return { t: "list", items: planExpr.items.map((item) => expression(item, ctx)) };
964
+ case "ref":
965
+ return refExpr(planExpr.ref, ctx);
966
+ case "template":
967
+ ctx.used.add("expr");
968
+ return call(
969
+ "expr",
970
+ planExpr.parts.map((part) => ({
971
+ kind: "positional",
972
+ value: "text" in part ? atom(pyString(part.text)) : refExpr(part.ref, ctx)
973
+ }))
974
+ );
975
+ case "rule": {
976
+ const target = refExpr(planExpr.ref, ctx);
977
+ const comparison = COMPARISON_OPERATORS[planExpr.method];
978
+ if (comparison && planExpr.hasValue) {
979
+ return {
980
+ t: "compare",
981
+ left: target,
982
+ op: comparison,
983
+ right: literal(planExpr.value ?? null)
984
+ };
985
+ }
986
+ const method = planExpr.method === "is" ? "is_" : snakeCase(planExpr.method);
987
+ const args = [];
988
+ if (planExpr.method === "is")
989
+ args.push({ kind: "positional", value: atom(pyString(planExpr.operator)) });
990
+ if (planExpr.hasValue)
991
+ args.push({ kind: "positional", value: literal(planExpr.value ?? null) });
992
+ return { t: "call", func: { t: "attr", base: target, name: method }, args };
993
+ }
994
+ case "group": {
995
+ const name = planExpr.combinator === "and" ? "and_" : "or_";
996
+ ctx.used.add(name);
997
+ return call(
998
+ name,
999
+ planExpr.children.map((child) => ({ kind: "positional", value: expression(child, ctx) }))
1000
+ );
1001
+ }
1002
+ case "node":
1003
+ return nodeSource(planExpr.nodeId, ctx);
1004
+ case "handle": {
1005
+ const source = nodeSource(planExpr.nodeId, ctx);
1006
+ const declared = ctx.declared.has(planExpr.nodeId) && ctx.vars.has(planExpr.nodeId);
1007
+ if (declared && planExpr.accessor.kind === "property")
1008
+ return { t: "attr", base: source, name: snakeCase(planExpr.accessor.name) };
1009
+ const method = declared && planExpr.accessor.kind === "case" ? "case" : "handle";
1010
+ return {
1011
+ t: "call",
1012
+ func: { t: "attr", base: source, name: method },
1013
+ args: [{ kind: "positional", value: atom(pyString(planExpr.handle)) }]
1014
+ };
1015
+ }
1016
+ case "edge":
1017
+ ctx.used.add("edge");
1018
+ return call("edge", [
1019
+ { kind: "positional", value: expression(planExpr.from, ctx) },
1020
+ ...keywords(planExpr.options, ctx)
1021
+ ]);
1022
+ }
1023
+ }
1024
+ function keywords(options2, ctx) {
1025
+ return options2.map(([name, value2]) => ({
1026
+ kind: "keyword",
1027
+ name: keywordName(name),
1028
+ value: expression(value2, ctx)
1029
+ }));
1030
+ }
1031
+ function operationExpr(operation, ctx, clientPy) {
1032
+ switch (operation.kind) {
1033
+ case "trigger":
1034
+ ctx.used.add("triggers");
1035
+ return atom(`triggers.${snakeCase(operation.name)}`);
1036
+ case "internal":
1037
+ ctx.used.add("nodes");
1038
+ return atom(`nodes.internal.${snakeCase(operation.name)}`);
1039
+ case "client":
1040
+ ctx.used.add("n");
1041
+ return atom(clientPy(operation.nodeType, operation.operation));
1042
+ case "custom": {
1043
+ ctx.used.add("nodes");
1044
+ const args = [{ kind: "positional", value: atom(pyString(operation.nodeType)) }];
1045
+ if (operation.operation !== void 0)
1046
+ args.push({ kind: "positional", value: atom(pyString(operation.operation)) });
1047
+ if (operation.version !== void 0) {
1048
+ args.push(
1049
+ operation.operation !== void 0 ? { kind: "positional", value: atom(pyString(operation.version)) } : { kind: "keyword", name: "version", value: atom(pyString(operation.version)) }
1050
+ );
1051
+ }
1052
+ return call("nodes.custom", args);
1053
+ }
1054
+ }
1055
+ }
1056
+ function statementLines(statement, ctx, clientPy) {
1057
+ if (statement.kind === "edgeOrder") {
1058
+ return formatLines(
1059
+ "",
1060
+ call("wf.edge_order_is", [
1061
+ { kind: "positional", value: { t: "list", items: statement.ids.map((id) => literal(id)) } }
1062
+ ]),
1063
+ "",
1064
+ 1
1065
+ );
1066
+ }
1067
+ if (statement.kind === "connect") {
1068
+ const args2 = [
1069
+ { kind: "positional", value: expression(statement.from, ctx) },
1070
+ { kind: "positional", value: atom(pyString(statement.to)) },
1071
+ ...keywords(statement.options, ctx)
1072
+ ];
1073
+ return formatLines("", call("wf.connect", args2), "", 1);
1074
+ }
1075
+ const args = [
1076
+ ...statement.operation ? [{ kind: "positional", value: operationExpr(statement.operation, ctx, clientPy) }] : [],
1077
+ ...keywords(statement.options, ctx)
1078
+ ];
1079
+ const method = BUILDER_METHODS[statement.method] ?? snakeCase(statement.method);
1080
+ const callExpr = call(`wf.${method}`, args);
1081
+ ctx.declared.add(statement.nodeId);
1082
+ const name = ctx.vars.get(statement.nodeId);
1083
+ return formatLines(name !== void 0 ? `${name} = ` : "", callExpr, "", 1);
1084
+ }
1085
+ function renderPython(plan, clientPy, header = []) {
1086
+ const allocator = new NameAllocator("py");
1087
+ const vars = /* @__PURE__ */ new Map();
1088
+ for (const nodeId of referencedLater(plan)) vars.set(nodeId, allocator.forNode(nodeId));
1089
+ const ctx = { vars, declared: /* @__PURE__ */ new Set(), used: /* @__PURE__ */ new Set() };
1090
+ const body = plan.statements.flatMap((statement) => statementLines(statement, ctx, clientPy));
1091
+ const decorator = formatLines(
1092
+ "@",
1093
+ call("define_workflow", keywords(plan.workflowOptions, ctx)),
1094
+ "",
1095
+ 0
1096
+ );
1097
+ const sdkNames = ["and_", "define_workflow", "edge", "expr", "nodes", "or_", "triggers"].filter(
1098
+ (name) => name === "define_workflow" || ctx.used.has(name)
1099
+ );
1100
+ const lines = [
1101
+ ...header.map((line) => `# ${line}`),
1102
+ ...header.length > 0 ? [""] : [],
1103
+ ...formatImport(PY_SDK_MODULE, sdkNames),
1104
+ ...ctx.used.has("n") ? formatImport(PY_PROJECT_MODULE, ["nodes as n"]) : [],
1105
+ "",
1106
+ "",
1107
+ ...decorator,
1108
+ `def ${pyWorkflowName(plan.key)}(wf):`,
1109
+ ...body.length > 0 ? body : [" pass"],
1110
+ ""
1111
+ ];
1112
+ return lines.join("\n");
1113
+ }
1114
+
1115
+ // src/roundtrip/lift.ts
1116
+ function layoutOf(reference) {
1117
+ const data = isRecord(reference["data"]) ? reference["data"] : {};
1118
+ const positions = {};
1119
+ for (const node of Array.isArray(data["nodes"]) ? data["nodes"] : []) {
1120
+ if (!isRecord(node) || typeof node["id"] !== "string" || !isRecord(node["position"])) continue;
1121
+ const { x, y } = node["position"];
1122
+ if (typeof x === "number" && typeof y === "number") setOwn(positions, node["id"], { x, y });
1123
+ }
1124
+ const sidecar = {
1125
+ format: LAYOUT_FORMAT,
1126
+ formatVersion: LAYOUT_FORMAT_VERSION,
1127
+ positions
1128
+ };
1129
+ if (Array.isArray(data["stickyNotes"])) sidecar.stickyNotes = data["stickyNotes"];
1130
+ const meta = isRecord(reference["meta"]) ? reference["meta"] : void 0;
1131
+ if (isRecord(meta?.["layout"])) sidecar.metaLayout = meta["layout"];
1132
+ return sidecar;
1133
+ }
1134
+ function mismatches(reference, candidate) {
1135
+ const refData = isRecord(reference["data"]) ? reference["data"] : {};
1136
+ const candData = isRecord(candidate["data"]) ? candidate["data"] : {};
1137
+ const byId = (items) => new Map(
1138
+ (Array.isArray(items) ? items : []).filter(isRecord).map((item) => [String(item["id"]), item])
1139
+ );
1140
+ const nodes2 = /* @__PURE__ */ new Set();
1141
+ const candNodes = byId(candData["nodes"]);
1142
+ for (const [id, node] of byId(refData["nodes"]))
1143
+ if (!deepEqual(node, candNodes.get(id))) nodes2.add(id);
1144
+ const edges = /* @__PURE__ */ new Set();
1145
+ const refEdges = Array.isArray(refData["edges"]) ? refData["edges"] : [];
1146
+ const candEdges = Array.isArray(candData["edges"]) ? candData["edges"] : [];
1147
+ refEdges.forEach((edge2, index) => {
1148
+ if (!deepEqual(edge2, candEdges[index])) edges.add(index);
1149
+ });
1150
+ const strip = (doc) => {
1151
+ const copy = { ...doc };
1152
+ delete copy["authoring"];
1153
+ const data = isRecord(copy["data"]) ? { ...copy["data"] } : {};
1154
+ delete data["nodes"];
1155
+ delete data["edges"];
1156
+ copy["data"] = data;
1157
+ return copy;
1158
+ };
1159
+ return { nodes: nodes2, edges, document: !deepEqual(strip(reference), strip(candidate)) };
1160
+ }
1161
+ function tryRun(attempt) {
1162
+ try {
1163
+ return attempt();
1164
+ } catch (error) {
1165
+ if (error instanceof BuilderError) return void 0;
1166
+ throw error;
1167
+ }
1168
+ }
1169
+ function rejectedByBuilder(reference, run) {
1170
+ const nodes2 = /* @__PURE__ */ new Set();
1171
+ const edges = /* @__PURE__ */ new Set();
1172
+ const data = isRecord(reference["data"]) ? reference["data"] : {};
1173
+ const nodeIds = (Array.isArray(data["nodes"]) ? data["nodes"] : []).flatMap(
1174
+ (node) => isRecord(node) && typeof node["id"] === "string" ? [node["id"]] : []
1175
+ );
1176
+ const edgeCount = Array.isArray(data["edges"]) ? data["edges"].length : 0;
1177
+ const edgeIndices = Array.from({ length: edgeCount }, (_, index) => index);
1178
+ const allNodes = new Set(nodeIds);
1179
+ const allEdges = new Set(edgeIndices);
1180
+ for (const id of nodeIds) {
1181
+ const others = new Set(allNodes);
1182
+ others.delete(id);
1183
+ if (tryRun(() => run(others, allEdges)) === void 0) nodes2.add(id);
1184
+ }
1185
+ for (const index of edgeIndices) {
1186
+ const others = new Set(allEdges);
1187
+ others.delete(index);
1188
+ if (tryRun(() => run(allNodes, others)) === void 0) edges.add(index);
1189
+ }
1190
+ return { nodes: nodes2, edges };
1191
+ }
1192
+ function prepareLift(definition, options2 = {}) {
1193
+ const normalized = normalizeDefinition(definition);
1194
+ if (!isRecord(normalized) || !isRecord(normalized["data"]))
1195
+ throw new TypeError("A workflow definition must be an object with data");
1196
+ const reference = { ...normalized };
1197
+ const items = [];
1198
+ if (typeof reference["key"] !== "string" || reference["key"] === "") {
1199
+ if (!options2.workflowKey) throw new TypeError("The definition has no key; pass workflowKey");
1200
+ reference["key"] = options2.workflowKey;
1201
+ items.push({
1202
+ kind: "key-added",
1203
+ detail: `The definition has no key; the workflow key "${options2.workflowKey}" is written`
1204
+ });
1205
+ }
1206
+ delete reference["authoring"];
1207
+ const includeLayout = options2.includeLayout !== false;
1208
+ const layout = layoutOf(reference);
1209
+ if (!includeLayout)
1210
+ items.push({
1211
+ kind: "layout-omitted",
1212
+ detail: "Node positions, sticky notes and meta.layout are not exported"
1213
+ });
1214
+ const pinned = isRecord(reference["pinnedData"]) ? (
1215
+ // Checked to be a record; carried verbatim.
1216
+ reference["pinnedData"]
1217
+ ) : void 0;
1218
+ if (pinned && !options2.includePinnedData)
1219
+ items.push({ kind: "pinned-data-omitted", detail: "Pinned TEST data is not exported" });
1220
+ const connections = options2.connectionNames ? connectionsByName(options2.connectionNames, reference) : void 0;
1221
+ const run = (rawNodes, rawEdges) => {
1222
+ const build = buildPlan(reference, options2, rawNodes, rawEdges);
1223
+ const recording = interpretPlan(build.plan);
1224
+ const candidate = assembleRecording(recording, {
1225
+ layout,
1226
+ ...connections ? { connections } : {},
1227
+ ...pinned ? { pinnedData: pinned } : {}
1228
+ });
1229
+ return { build, candidate: normalizeDefinition(candidate) };
1230
+ };
1231
+ let rejected = { nodes: /* @__PURE__ */ new Set(), edges: /* @__PURE__ */ new Set() };
1232
+ let attempt = tryRun(() => run(rejected.nodes, rejected.edges));
1233
+ if (attempt === void 0) {
1234
+ rejected = rejectedByBuilder(reference, run);
1235
+ attempt = run(rejected.nodes, rejected.edges);
1236
+ }
1237
+ const first = mismatches(reference, attempt.candidate);
1238
+ if (first.nodes.size > 0 || first.edges.size > 0)
1239
+ attempt = run(
1240
+ /* @__PURE__ */ new Set([...rejected.nodes, ...first.nodes]),
1241
+ /* @__PURE__ */ new Set([...rejected.edges, ...first.edges])
1242
+ );
1243
+ const residual = mismatches(reference, attempt.candidate);
1244
+ for (const id of residual.nodes)
1245
+ items.push({
1246
+ nodeId: id,
1247
+ kind: "roundtrip-mismatch",
1248
+ detail: "This node does not lower back unchanged"
1249
+ });
1250
+ for (const index of residual.edges) {
1251
+ items.push({
1252
+ path: `data.edges[${index}]`,
1253
+ kind: "roundtrip-mismatch",
1254
+ detail: "This edge does not lower back unchanged"
1255
+ });
1256
+ }
1257
+ if (residual.document)
1258
+ items.push({
1259
+ kind: "roundtrip-mismatch",
1260
+ detail: "Workflow-level fields do not lower back unchanged"
1261
+ });
1262
+ const clients = attempt.build.clients;
1263
+ const usesClients = attempt.build.plan.statements.some(
1264
+ (statement) => statement.kind === "node" && statement.operation?.kind === "client"
1265
+ );
1266
+ return {
1267
+ // The normalised input with its key: a definition by construction.
1268
+ reference,
1269
+ plan: attempt.build.plan,
1270
+ items: [...items, ...attempt.build.items],
1271
+ ...includeLayout ? { layout } : {},
1272
+ ...pinned && options2.includePinnedData ? { pinnedData: pinned } : {},
1273
+ registry: options2.registry ?? [],
1274
+ clientPy: (nodeType, operation) => {
1275
+ const client = clients.get(nodeType);
1276
+ const names = client?.operations.get(operation);
1277
+ return client && names ? `n.${client.py}.${names.py}` : `nodes.custom(${JSON.stringify(nodeType)})`;
1278
+ },
1279
+ usesClients
1280
+ };
1281
+ }
1282
+ function headerLines(header) {
1283
+ if (!header) return [];
1284
+ const parts = [];
1285
+ if (header.workflowKey)
1286
+ parts.push(`Generated from ${header.workflowKey}${header.version ? ` ${header.version}` : ""}`);
1287
+ if (header.irHash) parts.push(`irHash ${header.irHash}`);
1288
+ if (header.exportedAt) parts.push(`exported ${header.exportedAt}`);
1289
+ const lines = parts.length > 0 ? [`${parts.join(", ")}. Edit freely and run \`fluidgrids push\`.`] : [];
1290
+ if (header.registrySnapshot) lines.push(`Node registry snapshot ${header.registrySnapshot}.`);
1291
+ return lines;
1292
+ }
1293
+ function sidecarFiles(prepared) {
1294
+ const key = prepared.plan.key;
1295
+ const files = [];
1296
+ if (prepared.layout) {
1297
+ files.push({
1298
+ path: `workflows/${key}.layout.json`,
1299
+ contentType: "application/json",
1300
+ content: stableStringify(prepared.layout)
1301
+ });
1302
+ }
1303
+ if (prepared.pinnedData) {
1304
+ files.push({
1305
+ path: `workflows/${key}.pinned.json`,
1306
+ contentType: "application/json",
1307
+ content: stableStringify(prepared.pinnedData)
1308
+ });
1309
+ }
1310
+ return files;
1311
+ }
1312
+ async function liftToTypeScript(definition, options2 = {}) {
1313
+ const prepared = prepareLift(definition, options2);
1314
+ const source = await renderTypeScript(prepared.plan, headerLines(options2.header));
1315
+ const files = [
1316
+ { path: `workflows/${prepared.plan.key}.ts`, contentType: "text/typescript", content: source },
1317
+ ...sidecarFiles(prepared)
1318
+ ];
1319
+ if (prepared.usesClients) {
1320
+ const clients = await generateNodeClients(prepared.registry, "ts");
1321
+ files.push({ path: clients.path, contentType: "text/typescript", content: clients.content });
1322
+ }
1323
+ return { files, items: prepared.items, reference: prepared.reference };
1324
+ }
1325
+ async function liftToPython(definition, options2 = {}) {
1326
+ const prepared = prepareLift(definition, options2);
1327
+ const source = renderPython(prepared.plan, prepared.clientPy, headerLines(options2.header));
1328
+ const files = [
1329
+ {
1330
+ path: `workflows/${pyWorkflowName(prepared.plan.key)}.py`,
1331
+ contentType: "text/x-python",
1332
+ content: source
1333
+ },
1334
+ ...sidecarFiles(prepared)
1335
+ ];
1336
+ if (prepared.usesClients) {
1337
+ const clients = await generateNodeClients(prepared.registry, "py");
1338
+ files.push({ path: clients.path, contentType: "text/x-python", content: clients.content });
1339
+ }
1340
+ return { files, items: prepared.items, reference: prepared.reference };
1341
+ }
1342
+
1343
+ // src/roundtrip/fidelity.ts
1344
+ var LOSSY_KINDS = /* @__PURE__ */ new Set([
1345
+ "layout-omitted",
1346
+ "pinned-data-omitted",
1347
+ "roundtrip-mismatch",
1348
+ "lowering-failed"
1349
+ ]);
1350
+ async function checkFidelity(files, reference, items, options2 = {}) {
1351
+ const all = [...items];
1352
+ const connections = options2.connectionNames && isRecord(reference) ? connectionsByName(options2.connectionNames, reference) : void 0;
1353
+ try {
1354
+ const result = await lower(
1355
+ files.map((file) => ({ path: file.path, content: file.content })),
1356
+ {
1357
+ ...options2.esbuild ? { esbuild: options2.esbuild } : {},
1358
+ ...options2.evaluator ? { evaluator: options2.evaluator } : {},
1359
+ ...connections ? { connections } : {}
1360
+ }
1361
+ );
1362
+ const lowered = result.workflows[0]?.definition;
1363
+ if (result.workflows.length !== 1 || !lowered) {
1364
+ all.push({
1365
+ kind: "lowering-failed",
1366
+ detail: `Lowering produced ${result.workflows.length} workflows instead of one`
1367
+ });
1368
+ } else if (irHash(lowered) !== irHash(reference)) {
1369
+ for (const change of diffDefinitions(reference, lowered)) {
1370
+ all.push({
1371
+ path: change.path,
1372
+ kind: "roundtrip-mismatch",
1373
+ detail: `Lowering the code ${change.kind} this value`
1374
+ });
1375
+ }
1376
+ }
1377
+ } catch (error) {
1378
+ all.push({
1379
+ kind: "lowering-failed",
1380
+ detail: error instanceof Error ? error.message : String(error)
1381
+ });
1382
+ }
1383
+ const level = all.some((item) => LOSSY_KINDS.has(item.kind)) ? "lossy" : "lossless";
1384
+ return { level, items: all };
1385
+ }
1386
+ async function exportWorkflowCode(definition, options2) {
1387
+ const ts = await liftToTypeScript(definition, options2);
1388
+ const fidelity = await checkFidelity(ts.files, ts.reference, ts.items, {
1389
+ ...options2.esbuild ? { esbuild: options2.esbuild } : {},
1390
+ ...options2.evaluator ? { evaluator: options2.evaluator } : {},
1391
+ ...options2.connectionNames ? { connectionNames: options2.connectionNames } : {}
1392
+ });
1393
+ const files = options2.format === "PYTHON" ? (await liftToPython(definition, options2)).files : ts.files;
1394
+ return { format: options2.format, files, fidelity, irHash: irHash(definition) };
1395
+ }
1396
+
1397
+ export { LiftError, PY_PROJECT_MODULE, PY_SDK_MODULE, checkFidelity, exportWorkflowCode, liftToPython, liftToTypeScript, prepareLift };