@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,748 @@
1
+ import { autoLayout } from './chunk-5LUIDMF4.js';
2
+ import { makeRef, isPredicate, conditionTreeOf, BuilderError, refModelOf } from './chunk-UB2BHU2M.js';
3
+ import { isRecord, setOwn, cloneJson, ownValue, formatRefTemplate } from './chunk-UJNHTS6D.js';
4
+
5
+ // src/builder/descriptors.ts
6
+ function op(nodeType, version, operation) {
7
+ return {
8
+ kind: "operation",
9
+ nodeType,
10
+ ...version !== void 0 ? { version } : {},
11
+ ...operation !== void 0 ? { operation } : {}
12
+ };
13
+ }
14
+ function trigger(nodeType, version, operation) {
15
+ return { ...op(nodeType, version, operation), role: "trigger" };
16
+ }
17
+ function isOperation(value) {
18
+ return typeof value === "object" && value !== null && value.kind === "operation" && typeof value.nodeType === "string";
19
+ }
20
+ var triggers = {
21
+ api: trigger("internal:api"),
22
+ manual: trigger("internal:api"),
23
+ schedule: trigger("internal:schedule"),
24
+ webhook: trigger("internal:webhook"),
25
+ form: trigger("internal:form"),
26
+ errorTrigger: trigger("internal:error-trigger"),
27
+ workflowCall: trigger("internal:workflow-call"),
28
+ rssFeed: trigger("internal:rss-feed"),
29
+ httpPoll: trigger("internal:http-poll"),
30
+ databasePoll: trigger("internal:database-poll"),
31
+ emailImap: trigger("internal:email-imap"),
32
+ fileUpload: trigger("internal:file-upload"),
33
+ recordChange: trigger("internal:record-change"),
34
+ stateChange: trigger("internal:state-change")
35
+ };
36
+ var internalNodes = {
37
+ merge: op("internal:merge"),
38
+ if: op("internal:if"),
39
+ switch: op("internal:switch"),
40
+ loop: op("internal:loop"),
41
+ set: op("internal:set"),
42
+ filter: op("internal:filter"),
43
+ aggregate: op("internal:aggregate"),
44
+ wait: op("internal:wait"),
45
+ noop: op("internal:noop"),
46
+ code: op("internal:code"),
47
+ http: op("internal:http"),
48
+ transform: op("internal:transform"),
49
+ validate: op("internal:validate"),
50
+ log: op("internal:log"),
51
+ error: op("internal:error"),
52
+ subworkflow: op("internal:subworkflow"),
53
+ compareDatasets: op("internal:compare-datasets"),
54
+ splitOut: op("internal:split-out"),
55
+ limit: op("internal:limit"),
56
+ dedupe: op("internal:dedupe"),
57
+ sort: op("internal:sort"),
58
+ dataTable: op("internal:datatable"),
59
+ respondToWebhook: op("internal:respond-to-webhook"),
60
+ sendAndWait: op("internal:send-and-wait"),
61
+ codeWorkflow: op("internal:code-workflow"),
62
+ waitForSignal: op("internal:wait-for-signal")
63
+ };
64
+ var nodes = {
65
+ internal: internalNodes,
66
+ /**
67
+ * Any node type, including ones the registry snapshot does not know. Typed
68
+ * as JSON in and out.
69
+ */
70
+ custom(nodeType, operation, version) {
71
+ return op(nodeType, version, operation);
72
+ }
73
+ };
74
+
75
+ // src/builder/workflow.ts
76
+ var RECORDING_FORMAT = "fluidgrids.tier1-recording";
77
+ var RECORDING_FORMAT_VERSION = 1;
78
+ var WORKFLOW_MARKER = "fluidgrids.tier1-workflow";
79
+ function edge(from, options = {}) {
80
+ return { kind: "edge", from, ...options };
81
+ }
82
+ function isTier1Workflow(value) {
83
+ return isRecord(value) && value["__fluidgrids"] === WORKFLOW_MARKER && isRecord(value["recording"]);
84
+ }
85
+ function deepMerge(base, extra, path) {
86
+ for (const [key, value] of Object.entries(extra)) {
87
+ const existing = ownValue(base, key);
88
+ if (isRecord(existing) && isRecord(value)) {
89
+ deepMerge(existing, value, `${path}.${key}`);
90
+ } else if (existing !== void 0) {
91
+ throw new BuilderError(
92
+ `extra sets ${path}.${key}, which the builder already wrote; use the typed option`
93
+ );
94
+ } else {
95
+ setOwn(base, key, lowerValue(value, `${path}.${key}`));
96
+ }
97
+ }
98
+ }
99
+ function lowerValue(value, path) {
100
+ const model = refModelOf(value);
101
+ if (model) return formatRefTemplate(model);
102
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
103
+ if (typeof value === "number") {
104
+ if (!Number.isFinite(value)) throw new BuilderError(`${path} is not a finite number`);
105
+ return value;
106
+ }
107
+ if (Array.isArray(value))
108
+ return value.map((item, index) => lowerValue(item, `${path}[${index}]`));
109
+ if (isPredicate(value)) {
110
+ throw new BuilderError(`${path} is a condition; conditions belong in wf.if({ condition })`);
111
+ }
112
+ if (isRecord(value)) {
113
+ const proto = Object.getPrototypeOf(value);
114
+ if (proto !== Object.prototype && proto !== null) {
115
+ throw new BuilderError(`${path} is not a plain object`);
116
+ }
117
+ const out = {};
118
+ for (const [key, child] of Object.entries(value)) {
119
+ if (child === void 0) continue;
120
+ setOwn(out, key, lowerValue(child, `${path}.${key}`));
121
+ }
122
+ return out;
123
+ }
124
+ throw new BuilderError(`${path} cannot be stored in a workflow definition (${typeof value})`);
125
+ }
126
+ function lowerObject(value, path) {
127
+ const lowered = lowerValue(value, path);
128
+ if (!isRecord(lowered)) throw new BuilderError(`${path} must be an object`);
129
+ return lowered;
130
+ }
131
+ function slugify(text) {
132
+ const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
133
+ return /^[a-z]/.test(slug) ? slug : `node-${slug || "x"}`;
134
+ }
135
+ function handleRef(nodeId, handle) {
136
+ return { kind: "handle", nodeId, handle };
137
+ }
138
+ function nodeRef(id) {
139
+ return {
140
+ kind: "node",
141
+ id,
142
+ // An untyped reference serves every output type; the static type comes from the operation.
143
+ output: makeRef({ kind: "node", nodeId: id }),
144
+ handle: (name) => handleRef(id, name)
145
+ };
146
+ }
147
+ function isNodeRef(value) {
148
+ return isRecord(value) && value["kind"] === "node" && typeof value["id"] === "string";
149
+ }
150
+ function isHandleRef(value) {
151
+ return isRecord(value) && value["kind"] === "handle" && typeof value["nodeId"] === "string";
152
+ }
153
+ function isEdgeSpec(value) {
154
+ return isRecord(value) && value["kind"] === "edge" && "from" in value;
155
+ }
156
+ function sourceOf(from) {
157
+ if (typeof from === "string") return { source: from };
158
+ if (isHandleRef(from)) return { source: from.nodeId, sourceHandle: from.handle };
159
+ if (isNodeRef(from)) return { source: from.id };
160
+ throw new BuilderError("after takes node references, handles, node ids or edge(\u2026) entries");
161
+ }
162
+ function defaultEdgeId(source, target, sourceHandle, targetHandle) {
163
+ return ["e", source, target, sourceHandle, targetHandle].filter((part) => Boolean(part)).join("-");
164
+ }
165
+ var LIMIT_UNITS = {
166
+ s: "seconds",
167
+ m: "minutes",
168
+ h: "hours",
169
+ d: "days",
170
+ w: "weeks"
171
+ };
172
+ function limitWait(limit) {
173
+ if (typeof limit === "string") {
174
+ const match = /^([1-9][0-9]*)([smhdw])$/.exec(limit);
175
+ const unit = match?.[2] !== void 0 ? LIMIT_UNITS[match[2]] : void 0;
176
+ if (!match?.[1] || !unit)
177
+ throw new BuilderError(`limit "${limit}" must look like 30s, 15m, 2h, 3d or 1w`);
178
+ return { amount: Number(match[1]), unit };
179
+ }
180
+ return { amount: limit.amount, unit: limit.unit };
181
+ }
182
+ var WorkflowBuilder = class {
183
+ constructor(autoIds) {
184
+ this.autoIds = autoIds;
185
+ }
186
+ autoIds;
187
+ /** `$json`: the current item's JSON. */
188
+ json = makeRef({ kind: "json" });
189
+ /** `$vars`: workflow variables. */
190
+ vars = makeRef({ kind: "vars" });
191
+ /** `$now`. */
192
+ now = makeRef({ kind: "now" });
193
+ /** `$execution`: run metadata. */
194
+ execution = makeRef({ kind: "execution" });
195
+ nodes = [];
196
+ edges = [];
197
+ notes = [];
198
+ ids = /* @__PURE__ */ new Set();
199
+ /** Ids given explicitly; a second edge with the same explicit id is an error. */
200
+ edgeIds = /* @__PURE__ */ new Set();
201
+ /** Edges whose id is the default, as indices into `edges`, with the unsuffixed default id. */
202
+ defaultIds = [];
203
+ edgeOrder;
204
+ /** A reference to a node by id, for nodes declared later in the source. */
205
+ ref(id) {
206
+ return nodeRef(id);
207
+ }
208
+ /** A trigger node. */
209
+ trigger(operation, options = {}) {
210
+ return this.add(operation, options, {});
211
+ }
212
+ /** A store or internal node. */
213
+ step(operation, options = {}) {
214
+ return this.add(operation, options, {});
215
+ }
216
+ /** `internal:if`. */
217
+ if(options) {
218
+ const { condition, ...rest } = options;
219
+ const sugar = {};
220
+ if (condition !== void 0) {
221
+ if (typeof condition === "string") sugar["condition"] = condition;
222
+ else if (isPredicate(condition)) sugar["conditionTree"] = conditionTreeOf(condition);
223
+ else
224
+ throw new BuilderError(
225
+ "condition takes a comparison such as x.output.total.gte(500), and(), or() or a string"
226
+ );
227
+ }
228
+ const ref = this.add(internalNodes.if, rest, sugar);
229
+ return { ...ref, true: ref.handle("true"), false: ref.handle("false") };
230
+ }
231
+ /** `internal:switch`. */
232
+ switch(options) {
233
+ const { value, cases, ...rest } = options;
234
+ const sugar = {};
235
+ if (value !== void 0) sugar["switchValue"] = value;
236
+ if (cases !== void 0) sugar["cases"] = cases;
237
+ const ref = this.add(internalNodes.switch, rest, sugar);
238
+ return { ...ref, case: (handle) => ref.handle(handle), default: ref.handle("default") };
239
+ }
240
+ /**
241
+ * `internal:loop`. With a body callback, the nodes the callback declares
242
+ * after `batch` form the loop body and the builder adds the return edge from
243
+ * the node the callback returns.
244
+ */
245
+ loop(options, body) {
246
+ const { items, batchSize, ...rest } = options;
247
+ const sugar = {};
248
+ if (items !== void 0) sugar["sourceArray"] = items;
249
+ if (batchSize !== void 0) sugar["batchSize"] = batchSize;
250
+ const ref = this.add(internalNodes.loop, rest, sugar);
251
+ const loopRef = { ...ref, body: ref.handle("loop"), done: ref.handle("done") };
252
+ if (body) {
253
+ const last = body(loopRef.body);
254
+ if (last !== void 0) this.connect(last, ref.id, {});
255
+ }
256
+ return loopRef;
257
+ }
258
+ /** `internal:merge`. */
259
+ merge(options) {
260
+ const { inputs, mode, ...rest } = options;
261
+ const sugar = {};
262
+ if (mode !== void 0) sugar["mode"] = mode;
263
+ if (inputs !== void 0 && rest.after !== void 0) {
264
+ throw new BuilderError("merge takes inputs or after, not both");
265
+ }
266
+ const after = inputs?.map(
267
+ (input, index) => edge(input, { targetHandle: `input-${index + 1}` })
268
+ );
269
+ return this.add(internalNodes.merge, after ? { ...rest, after } : rest, sugar);
270
+ }
271
+ /** `internal:wait`. */
272
+ wait(options) {
273
+ return this.add(internalNodes.wait, options, {});
274
+ }
275
+ /** `internal:wait-for-signal` (IR v2). */
276
+ waitForSignal(options) {
277
+ const { name, limit, ...rest } = options;
278
+ const sugar = {};
279
+ if (name !== void 0) sugar["signalName"] = name;
280
+ if (limit !== void 0) sugar["limitWait"] = limitWait(limit);
281
+ const ref = this.add(internalNodes.waitForSignal, rest, sugar);
282
+ return { ...ref, received: ref.handle("received"), timedOut: ref.handle("timedOut") };
283
+ }
284
+ /** `internal:send-and-wait`. */
285
+ sendAndWait(options) {
286
+ const ref = this.add(internalNodes.sendAndWait, options, {});
287
+ return {
288
+ ...ref,
289
+ approved: ref.handle("approved"),
290
+ declined: ref.handle("declined"),
291
+ timedOut: ref.handle("timedOut")
292
+ };
293
+ }
294
+ /** `internal:subworkflow`. */
295
+ subworkflow(options) {
296
+ const { key, targetVersion, input, ...rest } = options;
297
+ const sugar = {};
298
+ if (key !== void 0) sugar["workflowKey"] = key;
299
+ if (targetVersion !== void 0) sugar["version"] = targetVersion;
300
+ if (input !== void 0) sugar["inputMapping"] = input;
301
+ return this.add(internalNodes.subworkflow, rest, sugar);
302
+ }
303
+ /** `internal:validate`. */
304
+ validate(options) {
305
+ const ref = this.add(internalNodes.validate, options, {});
306
+ return { ...ref, valid: ref.handle("valid"), invalid: ref.handle("invalid") };
307
+ }
308
+ /** `internal:compare-datasets`. */
309
+ compareDatasets(options) {
310
+ const ref = this.add(internalNodes.compareDatasets, options, {});
311
+ return {
312
+ ...ref,
313
+ inAOnly: ref.handle("inAOnly"),
314
+ inBOnly: ref.handle("inBOnly"),
315
+ same: ref.handle("same"),
316
+ different: ref.handle("different")
317
+ };
318
+ }
319
+ /** `internal:set`. */
320
+ set(options) {
321
+ return this.add(internalNodes.set, options, {});
322
+ }
323
+ /** `internal:filter`. */
324
+ filter(options) {
325
+ return this.add(internalNodes.filter, options, {});
326
+ }
327
+ /** `internal:aggregate`. */
328
+ aggregate(options) {
329
+ return this.add(internalNodes.aggregate, options, {});
330
+ }
331
+ /** `internal:split-out`. */
332
+ splitOut(options) {
333
+ return this.add(internalNodes.splitOut, options, {});
334
+ }
335
+ /** `internal:limit`. */
336
+ limit(options) {
337
+ return this.add(internalNodes.limit, options, {});
338
+ }
339
+ /** `internal:dedupe`. */
340
+ dedupe(options) {
341
+ return this.add(internalNodes.dedupe, options, {});
342
+ }
343
+ /** `internal:sort`. */
344
+ sort(options) {
345
+ return this.add(internalNodes.sort, options, {});
346
+ }
347
+ /** `internal:datatable`. */
348
+ dataTable(options) {
349
+ return this.add(internalNodes.dataTable, options, {});
350
+ }
351
+ /** `internal:http`. */
352
+ http(options) {
353
+ return this.add(internalNodes.http, options, {});
354
+ }
355
+ /** `internal:code`. The code is a string (`args.code`), so it round-trips byte for byte. */
356
+ code(options) {
357
+ return this.add(internalNodes.code, options, {});
358
+ }
359
+ /** `internal:transform`. */
360
+ transform(options) {
361
+ return this.add(internalNodes.transform, options, {});
362
+ }
363
+ /** `internal:log`. */
364
+ log(options) {
365
+ return this.add(internalNodes.log, options, {});
366
+ }
367
+ /** `internal:respond-to-webhook`. */
368
+ respondToWebhook(options) {
369
+ return this.add(internalNodes.respondToWebhook, options, {});
370
+ }
371
+ /** `internal:error` (stop and error). */
372
+ stopAndError(options) {
373
+ return this.add(internalNodes.error, options, {});
374
+ }
375
+ /** `internal:noop`. */
376
+ noop(options) {
377
+ return this.add(internalNodes.noop, options, {});
378
+ }
379
+ /** A sticky note on the canvas. */
380
+ note(content, options) {
381
+ if (typeof content !== "string") throw new BuilderError("A note takes Markdown text");
382
+ this.claimId(options.id, "sticky note");
383
+ const near = options.near === void 0 ? void 0 : typeof options.near === "string" ? options.near : options.near.id;
384
+ this.notes.push({
385
+ note: {
386
+ id: options.id,
387
+ type: "sticky-note",
388
+ width: 260,
389
+ height: 170,
390
+ zIndex: -1,
391
+ data: { content, color: options.color ?? "yellow" }
392
+ },
393
+ ...near !== void 0 ? { near } : {}
394
+ });
395
+ }
396
+ /** An edge added outside `after`, for example a loop's return edge. */
397
+ connect(from, to, options = {}) {
398
+ const target = typeof to === "string" ? to : to.id;
399
+ this.addEdge(edge(from, options), target);
400
+ }
401
+ /**
402
+ * Put `data.edges` in this exact order. Each entry names one edge, by id or,
403
+ * for an edge without an id, by its position in declaration order; every edge
404
+ * appears once. The lifter emits this when a stored definition's edge order
405
+ * differs from the order the code declares its edges in.
406
+ */
407
+ edgeOrderIs(order) {
408
+ if (this.edgeOrder) throw new BuilderError("edgeOrderIs() may be called once");
409
+ this.edgeOrder = [...order];
410
+ }
411
+ /** The recording, once the callback has returned. */
412
+ finish(key, document) {
413
+ this.assignDefaultEdgeIds();
414
+ return {
415
+ format: RECORDING_FORMAT,
416
+ formatVersion: RECORDING_FORMAT_VERSION,
417
+ key,
418
+ document,
419
+ nodes: this.nodes,
420
+ edges: this.edges,
421
+ ...this.edgeOrder ? { edgeOrder: this.edgeOrder } : {},
422
+ notes: this.notes
423
+ };
424
+ }
425
+ claimId(id, what) {
426
+ if (typeof id !== "string" || id === "") throw new BuilderError(`A ${what} needs an id`);
427
+ if (this.ids.has(id)) throw new BuilderError(`Duplicate id "${id}"`);
428
+ this.ids.add(id);
429
+ }
430
+ resolveId(options, operation) {
431
+ if (options.id !== void 0) return options.id;
432
+ if (!this.autoIds) {
433
+ throw new BuilderError(`Every node needs an id (a ${operation.nodeType} node has none)`);
434
+ }
435
+ const base = slugify(options.label ?? operation.nodeType.split(/[:/]/).pop() ?? "node");
436
+ let candidate = base;
437
+ for (let suffix = 2; this.ids.has(candidate); suffix++) candidate = `${base}-${suffix}`;
438
+ return candidate;
439
+ }
440
+ /**
441
+ * Give every edge declared without an id its default id. Explicit ids are
442
+ * claimed first, so a default never takes an id the code names; a default that
443
+ * is taken gets `-2`, `-3`, … in declaration order. The result depends only on
444
+ * the builder calls.
445
+ */
446
+ assignDefaultEdgeIds() {
447
+ const taken = new Set(this.edgeIds);
448
+ for (const { index, base } of this.defaultIds) {
449
+ let id = base;
450
+ for (let suffix = 2; taken.has(id); suffix++) id = `${base}-${suffix}`;
451
+ taken.add(id);
452
+ const built = this.edges[index];
453
+ if (built) built.id = id;
454
+ }
455
+ this.defaultIds.length = 0;
456
+ }
457
+ addEdge(spec, target) {
458
+ const { source, sourceHandle } = sourceOf(spec.from);
459
+ const explicit = typeof spec.id === "string" ? spec.id : void 0;
460
+ if (spec.id !== void 0 && spec.id !== false && explicit === void 0)
461
+ throw new BuilderError("An edge id must be a string or false");
462
+ if (explicit !== void 0) {
463
+ if (this.edgeIds.has(explicit)) throw new BuilderError(`Duplicate edge id "${explicit}"`);
464
+ this.edgeIds.add(explicit);
465
+ }
466
+ const base = spec.id === void 0 ? defaultEdgeId(source, target, sourceHandle, spec.targetHandle) : void 0;
467
+ const id = explicit ?? base;
468
+ if (base !== void 0) this.defaultIds.push({ index: this.edges.length, base });
469
+ const built = id !== void 0 ? { id, source, target } : { source, target };
470
+ if (sourceHandle !== void 0) built["sourceHandle"] = sourceHandle;
471
+ if (spec.targetHandle !== void 0) built["targetHandle"] = spec.targetHandle;
472
+ if (spec.type !== false) built["type"] = spec.type ?? "deletable";
473
+ if (spec.label !== void 0) built["label"] = spec.label;
474
+ const where = `edge ${id ?? `${source}->${target}`}`;
475
+ if (spec.data !== void 0) built["data"] = lowerObject(spec.data, `${where} data`);
476
+ if (spec.mapping !== void 0)
477
+ built["mapping"] = lowerObject(spec.mapping, `${where} mapping`);
478
+ if (spec.extra !== void 0)
479
+ deepMerge(built, lowerObject(spec.extra, `${where} extra`), where);
480
+ this.edges.push(built);
481
+ }
482
+ add(operation, options, sugar) {
483
+ if (!isOperation(operation)) {
484
+ throw new BuilderError(
485
+ "The first argument must be a node operation (nodes.*, triggers.* or a generated client)"
486
+ );
487
+ }
488
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
489
+ throw new BuilderError("Node options must be an object");
490
+ }
491
+ const id = this.resolveId(options, operation);
492
+ this.claimId(id, "node");
493
+ const where = `node ${id}`;
494
+ const node = { id, type: operation.nodeType };
495
+ if (options.label !== void 0) node["label"] = options.label;
496
+ const operationName = options.operation === false ? void 0 : options.operation ?? operation.operation;
497
+ if (operationName !== void 0) node["operation_name"] = operationName;
498
+ const version = options.version === false ? void 0 : options.version ?? operation.version;
499
+ if (version !== void 0) node["version"] = version;
500
+ const args = {};
501
+ const explicitArgs = options.args === void 0 ? void 0 : lowerObject(options.args, `${where} args`);
502
+ const sugarArgs = lowerObject(sugar, `${where} args`);
503
+ if (explicitArgs || Object.keys(sugarArgs).length > 0) {
504
+ for (const key of Object.keys(sugarArgs)) {
505
+ if (explicitArgs && Object.hasOwn(explicitArgs, key)) {
506
+ throw new BuilderError(`${where}: "${key}" is set twice (by an option and in args)`);
507
+ }
508
+ }
509
+ args["state"] = { ...explicitArgs, ...sugarArgs };
510
+ }
511
+ if (options.connection !== void 0 && options.connections !== void 0) {
512
+ throw new BuilderError(`${where}: use connection (names) or connections (ids), not both`);
513
+ }
514
+ const connectionNames = options.connection === void 0 ? void 0 : typeof options.connection === "string" ? [options.connection] : [...options.connection];
515
+ if (connectionNames?.some((name) => typeof name !== "string" || name === "")) {
516
+ throw new BuilderError(`${where}: connection names must be non-empty strings`);
517
+ }
518
+ if (options.connections !== void 0)
519
+ args["connections"] = lowerValue(options.connections, `${where} connections`);
520
+ if (options.credentials !== void 0)
521
+ args["credentials"] = lowerValue(options.credentials, `${where} credentials`);
522
+ if (options.context !== void 0)
523
+ args["context"] = lowerObject(options.context, `${where} context`);
524
+ if (Object.keys(args).length > 0 || connectionNames) node["args"] = args;
525
+ const hasSugar = options.retry !== void 0 || options.timeout !== void 0 || options.onError !== void 0;
526
+ if (options.errorConfig !== void 0 && hasSugar) {
527
+ throw new BuilderError(`${where}: use errorConfig or retry / timeout / onError, not both`);
528
+ }
529
+ if (options.errorConfig !== void 0) {
530
+ node["errorConfig"] = lowerObject(options.errorConfig, `${where} errorConfig`);
531
+ } else if (hasSugar) {
532
+ const errorConfig = {};
533
+ if (typeof options.onError === "string") errorConfig["onError"] = options.onError;
534
+ else if (options.onError !== void 0) {
535
+ errorConfig["onError"] = "branch";
536
+ errorConfig["errorBranchId"] = options.onError.branch;
537
+ }
538
+ if (options.retry !== void 0)
539
+ errorConfig["retryConfig"] = lowerObject(options.retry, `${where} retry`);
540
+ if (options.timeout !== void 0)
541
+ errorConfig["timeout"] = lowerObject(options.timeout, `${where} timeout`);
542
+ node["errorConfig"] = errorConfig;
543
+ }
544
+ if (options.executionSettings !== void 0) {
545
+ const settings = lowerObject(options.executionSettings, `${where} executionSettings`);
546
+ node["executionSettings"] = settings;
547
+ if (settings["runOn"] !== void 0)
548
+ node["data"] = { executionSettings: { runOn: settings["runOn"] } };
549
+ }
550
+ if (options.disabled !== void 0) node["disabled"] = options.disabled;
551
+ if (options.extra !== void 0)
552
+ deepMerge(node, lowerObject(options.extra, `${where} extra`), where);
553
+ this.nodes.push({
554
+ // Built field by field above from lowered JSON.
555
+ node,
556
+ ...connectionNames ? { connectionNames } : {}
557
+ });
558
+ const after = options.after === void 0 ? [] : Array.isArray(options.after) ? options.after : [options.after];
559
+ for (const entry of after) {
560
+ this.addEdge(isEdgeSpec(entry) ? entry : edge(entry), id);
561
+ }
562
+ return nodeRef(id);
563
+ }
564
+ };
565
+ function defineWorkflow(options, build) {
566
+ if (typeof options !== "object" || options === null || typeof options.key !== "string" || options.key === "") {
567
+ throw new BuilderError("defineWorkflow needs a key");
568
+ }
569
+ const builder = new WorkflowBuilder(options.autoIds === true);
570
+ build(builder);
571
+ const document = { key: options.key };
572
+ if (options.name !== void 0) document["name"] = options.name;
573
+ if (options.description !== void 0) document["description"] = options.description;
574
+ if (options.interface !== void 0)
575
+ document["interface"] = lowerObject(options.interface, "interface");
576
+ if (options.code !== void 0) document["code"] = lowerObject(options.code, "code");
577
+ if (options.config !== void 0) document["config"] = lowerObject(options.config, "config");
578
+ if (options.meta !== void 0) document["meta"] = lowerObject(options.meta, "meta");
579
+ if (options.tags !== void 0) document["tags"] = lowerValue(options.tags, "tags");
580
+ if (options.extra !== void 0)
581
+ deepMerge(document, lowerObject(options.extra, "extra"), "workflow");
582
+ const recording = builder.finish(options.key, document);
583
+ return { __fluidgrids: WORKFLOW_MARKER, key: options.key, recording };
584
+ }
585
+
586
+ // src/generated/version.ts
587
+ var SDK_VERSION = "2026.1027.1";
588
+
589
+ // src/builder/assemble.ts
590
+ var LAYOUT_FORMAT = "fluidgrids.layout";
591
+ var LAYOUT_FORMAT_VERSION = 1;
592
+ var UnresolvedConnectionsError = class extends Error {
593
+ constructor(names) {
594
+ super(`Create these connections in the workspace first: ${names.join(", ")}`);
595
+ this.names = names;
596
+ this.name = "UnresolvedConnectionsError";
597
+ }
598
+ names;
599
+ };
600
+ function resolveConnection(resolver, name) {
601
+ if (!resolver) return void 0;
602
+ if (typeof resolver === "function") return resolver(name);
603
+ return Object.hasOwn(resolver, name) ? resolver[name] : void 0;
604
+ }
605
+ function parseLayoutSidecar(value) {
606
+ if (!isRecord(value) || value["format"] !== LAYOUT_FORMAT || value["formatVersion"] !== LAYOUT_FORMAT_VERSION) {
607
+ throw new BuilderError(
608
+ `A layout file must have format "${LAYOUT_FORMAT}" and formatVersion ${LAYOUT_FORMAT_VERSION}`
609
+ );
610
+ }
611
+ const positions = value["positions"];
612
+ if (!isRecord(positions)) throw new BuilderError("A layout file needs a positions object");
613
+ const parsed = {};
614
+ for (const [id, position] of Object.entries(positions)) {
615
+ if (!isRecord(position) || typeof position["x"] !== "number" || typeof position["y"] !== "number") {
616
+ throw new BuilderError(`The layout position of "${id}" must be { x, y }`);
617
+ }
618
+ setOwn(parsed, id, { x: position["x"], y: position["y"] });
619
+ }
620
+ const sidecar = {
621
+ format: LAYOUT_FORMAT,
622
+ formatVersion: LAYOUT_FORMAT_VERSION,
623
+ positions: parsed
624
+ };
625
+ if (value["stickyNotes"] !== void 0) {
626
+ if (!Array.isArray(value["stickyNotes"]) || !value["stickyNotes"].every(isRecord)) {
627
+ throw new BuilderError("stickyNotes in a layout file must be an array of objects");
628
+ }
629
+ sidecar.stickyNotes = cloneJson(value["stickyNotes"]);
630
+ }
631
+ if (value["metaLayout"] !== void 0) {
632
+ if (!isRecord(value["metaLayout"]))
633
+ throw new BuilderError("metaLayout in a layout file must be an object");
634
+ sidecar.metaLayout = cloneJson(value["metaLayout"]);
635
+ }
636
+ return sidecar;
637
+ }
638
+ function orderEdges(edges, order) {
639
+ if (!order) return edges;
640
+ const indexById = /* @__PURE__ */ new Map();
641
+ edges.forEach((item, index) => {
642
+ if (typeof item.id === "string") indexById.set(item.id, index);
643
+ });
644
+ const indices = order.map(
645
+ (entry) => typeof entry === "number" ? entry : indexById.get(entry) ?? -1
646
+ );
647
+ const valid = indices.length === edges.length && new Set(indices).size === indices.length && indices.every((index) => Number.isInteger(index) && index >= 0 && index < edges.length);
648
+ if (!valid)
649
+ throw new BuilderError(
650
+ "edgeOrderIs() must name every edge exactly once, by id or declaration position"
651
+ );
652
+ return indices.map((index) => edges[index]);
653
+ }
654
+ function assembleRecording(recording, options = {}) {
655
+ const missing = /* @__PURE__ */ new Set();
656
+ const nodes2 = recording.nodes.map((recorded) => {
657
+ const node = cloneJson(recorded.node);
658
+ if (recorded.connectionNames) {
659
+ const refs = [];
660
+ for (const name of recorded.connectionNames) {
661
+ const ref = resolveConnection(options.connections, name);
662
+ if (ref) refs.push({ connectionId: ref.connectionId, adapter: ref.adapter });
663
+ else missing.add(name);
664
+ }
665
+ const args = isRecord(node["args"]) ? node["args"] : {};
666
+ args["connections"] = refs;
667
+ node["args"] = args;
668
+ }
669
+ return node;
670
+ });
671
+ if (missing.size > 0) throw new UnresolvedConnectionsError([...missing].sort());
672
+ const edges = orderEdges(cloneJson(recording.edges), recording.edgeOrder);
673
+ const ids = nodes2.map((node) => String(node["id"]));
674
+ const positions = options.layout?.positions ?? {};
675
+ const needsAuto = ids.some((id) => !Object.hasOwn(positions, id));
676
+ const auto = needsAuto ? autoLayout(ids, edges) : /* @__PURE__ */ new Map();
677
+ for (const node of nodes2) {
678
+ const id = String(node["id"]);
679
+ const position = Object.hasOwn(positions, id) ? positions[id] : auto.get(id);
680
+ if (position) node["position"] = { x: position.x, y: position.y };
681
+ }
682
+ const stickyNotes = options.layout?.stickyNotes !== void 0 || recording.notes.length > 0 ? [...options.layout?.stickyNotes ? cloneJson(options.layout.stickyNotes) : []] : void 0;
683
+ for (const recorded of recording.notes) {
684
+ const anchor = recorded.near !== void 0 ? nodes2.find((node) => node["id"] === recorded.near) : void 0;
685
+ const anchorPosition = isRecord(anchor?.["position"]) ? anchor["position"] : { x: 0, y: 0 };
686
+ const x = typeof anchorPosition["x"] === "number" ? anchorPosition["x"] : 0;
687
+ const y = typeof anchorPosition["y"] === "number" ? anchorPosition["y"] : 0;
688
+ stickyNotes?.push({ ...cloneJson(recorded.note), position: { x, y: y - 220 } });
689
+ }
690
+ const document = cloneJson(recording.document);
691
+ const extraData = isRecord(document["data"]) ? document["data"] : {};
692
+ for (const key of ["nodes", "edges", "stickyNotes"]) {
693
+ if (Object.hasOwn(extraData, key))
694
+ throw new BuilderError(`extra.data.${key} is written by the builder`);
695
+ }
696
+ delete document["data"];
697
+ const authoring = options.authoring ?? {
698
+ mode: "canvas",
699
+ generator: "sdk-ts",
700
+ generatorVersion: SDK_VERSION
701
+ };
702
+ const out = { schemaVersion: 2 };
703
+ for (const key of ["key", "name", "description", "interface", "code"]) {
704
+ if (document[key] !== void 0) out[key] = document[key];
705
+ }
706
+ out["authoring"] = authoring;
707
+ out["data"] = {
708
+ ...extraData,
709
+ nodes: nodes2,
710
+ edges,
711
+ ...stickyNotes !== void 0 ? { stickyNotes } : {}
712
+ };
713
+ for (const key of ["config"]) if (document[key] !== void 0) out[key] = document[key];
714
+ if (options.pinnedData !== void 0) out["pinnedData"] = cloneJson(options.pinnedData);
715
+ if (options.layout?.metaLayout !== void 0) {
716
+ const meta = isRecord(document["meta"]) ? document["meta"] : {};
717
+ if (meta["layout"] !== void 0)
718
+ throw new BuilderError("meta.layout comes from the layout file; do not set it in code");
719
+ meta["layout"] = cloneJson(options.layout.metaLayout);
720
+ document["meta"] = meta;
721
+ }
722
+ for (const [key, value] of Object.entries(document)) {
723
+ if (!Object.hasOwn(out, key) && value !== void 0) setOwn(out, key, value);
724
+ }
725
+ return out;
726
+ }
727
+ function toIR(workflow, options = {}) {
728
+ if (!isTier1Workflow(workflow))
729
+ throw new BuilderError("toIR takes the value defineWorkflow returned");
730
+ return assembleRecording(workflow.recording, options);
731
+ }
732
+
733
+ // src/builder/collect.ts
734
+ var COLLECTION_FORMAT = "fluidgrids.tier1-collection";
735
+ function collectWorkflowExports(modules) {
736
+ const workflows = [];
737
+ for (const [module, namespace] of modules) {
738
+ for (const exportName of Object.keys(namespace).sort()) {
739
+ const value = namespace[exportName];
740
+ if (isTier1Workflow(value))
741
+ workflows.push({ module, exportName, recording: value.recording });
742
+ }
743
+ }
744
+ const collection = { format: COLLECTION_FORMAT, workflows };
745
+ return JSON.stringify(collection);
746
+ }
747
+
748
+ export { COLLECTION_FORMAT, LAYOUT_FORMAT, LAYOUT_FORMAT_VERSION, RECORDING_FORMAT, RECORDING_FORMAT_VERSION, SDK_VERSION, UnresolvedConnectionsError, WorkflowBuilder, assembleRecording, collectWorkflowExports, defaultEdgeId, defineWorkflow, edge, internalNodes, isOperation, isTier1Workflow, nodes, op, parseLayoutSidecar, toIR, trigger, triggers };