@fluidgrids/workflows 0.0.0-stage → 2026.1027.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +187 -2
  3. package/dist/assemble-BJo9TECS.d.cts +569 -0
  4. package/dist/assemble-DMOuaxRI.d.ts +569 -0
  5. package/dist/chunk-265J2FCH.cjs +33 -0
  6. package/dist/chunk-27HASETB.cjs +250 -0
  7. package/dist/chunk-4DPEGDPW.js +74 -0
  8. package/dist/chunk-5LUIDMF4.js +42 -0
  9. package/dist/chunk-AU3YW7FZ.cjs +279 -0
  10. package/dist/chunk-BWSLWF2E.js +722 -0
  11. package/dist/chunk-BYJ7QTLY.cjs +76 -0
  12. package/dist/chunk-G4UEPUGO.cjs +46 -0
  13. package/dist/chunk-GO34UHSY.js +95 -0
  14. package/dist/chunk-IZQNUNSU.cjs +931 -0
  15. package/dist/chunk-KUTMY2IO.cjs +745 -0
  16. package/dist/chunk-LMX35GNQ.js +893 -0
  17. package/dist/chunk-LRM6JXJA.cjs +181 -0
  18. package/dist/chunk-LSKSBVL4.cjs +102 -0
  19. package/dist/chunk-LUXCXFOE.cjs +269 -0
  20. package/dist/chunk-PL73GJR4.js +174 -0
  21. package/dist/chunk-REV7ZOA4.js +170 -0
  22. package/dist/chunk-RRQ5HOBF.cjs +185 -0
  23. package/dist/chunk-S34NJE3A.js +262 -0
  24. package/dist/chunk-SRQRLWOS.js +31 -0
  25. package/dist/chunk-SS4LT2IP.js +268 -0
  26. package/dist/chunk-X4HKU3MX.js +244 -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 +1244 -0
  40. package/dist/ir/index.d.cts +828 -0
  41. package/dist/ir/index.d.ts +828 -0
  42. package/dist/ir/index.js +1120 -0
  43. package/dist/lower-DcOY8ux0.d.cts +49 -0
  44. package/dist/lower-Dw5grNrI.d.ts +49 -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-2GTUZVPK.cjs +17 -0
  48. package/dist/node-evaluator-RMYEWCXZ.js +8 -0
  49. package/dist/quickjs/index.cjs +92 -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 +90 -0
  53. package/dist/roundtrip/index.cjs +1375 -0
  54. package/dist/roundtrip/index.d.cts +287 -0
  55. package/dist/roundtrip/index.d.ts +287 -0
  56. package/dist/roundtrip/index.js +1328 -0
  57. package/dist/types-Dr2Fn1Ax.d.cts +225 -0
  58. package/dist/types-Dr2Fn1Ax.d.ts +225 -0
  59. package/package.json +117 -4
  60. package/schema/ir-v2.schema.json +655 -0
@@ -0,0 +1,1120 @@
1
+ export { diffDefinitions } from '../chunk-4DPEGDPW.js';
2
+ import { findManifest, operationsOf, fieldsSchema } from '../chunk-GO34UHSY.js';
3
+ export { fieldSchema, fieldsSchema, findManifest, isTriggerOperation, operationsOf } from '../chunk-GO34UHSY.js';
4
+ import { usesMergedParameters, CODE_WORKFLOW_NODE_TYPE, INTERNAL_TRIGGER_TYPES, V2_NODE_TYPES, normalizeDefinition } from '../chunk-SS4LT2IP.js';
5
+ export { CODE_WORKFLOW_NODE_TYPE, INTERNAL_TRIGGER_TYPES, WAIT_FOR_SIGNAL_NODE_TYPE, buildIdOf, hashedProjection, irHash, normalizeDefinition, sourceHash, usesMergedParameters } from '../chunk-SS4LT2IP.js';
6
+ export { LAYOUT_COLUMN_WIDTH, LAYOUT_ROW_HEIGHT, autoLayout } from '../chunk-5LUIDMF4.js';
7
+ export { CanonicalJsonError, canonicalJson, sha256Hex, sha256Tagged, stableStringify } from '../chunk-X4HKU3MX.js';
8
+ import { isRecord, hasLegacyTemplate } from '../chunk-REV7ZOA4.js';
9
+ export { formatRefExpression, formatRefTemplate, parseRefExpression, parseTemplate } from '../chunk-REV7ZOA4.js';
10
+ import { Ajv2020 } from 'ajv/dist/2020.js';
11
+ import { CronExpressionParser } from 'cron-parser';
12
+
13
+ // src/ir/schema.ts
14
+ var JSON_SCHEMA_OBJECT = { type: "object" };
15
+ var IR_V2_SCHEMA_ID = "https://docs.fluidgrids.ai/schemas/workflow-ir-v2.schema.json";
16
+ var IR_V2_SCHEMA = {
17
+ $schema: "https://json-schema.org/draft/2020-12/schema",
18
+ $id: IR_V2_SCHEMA_ID,
19
+ title: "FluidGrids workflow definition (IR schema version 2)",
20
+ description: "The canonical workflow definition (definitionJson). Version 2 is a strict superset of version 1: a document without schemaVersion is version 1 and validates here unchanged.",
21
+ type: "object",
22
+ required: ["data"],
23
+ properties: {
24
+ schemaVersion: { enum: [1, 2], description: "Absent means 1." },
25
+ key: { type: "string" },
26
+ name: { type: "string" },
27
+ description: { type: ["string", "null"] },
28
+ interface: { $ref: "#/$defs/interface" },
29
+ code: { $ref: "#/$defs/code" },
30
+ authoring: { $ref: "#/$defs/authoring" },
31
+ data: {
32
+ type: "object",
33
+ required: ["nodes", "edges"],
34
+ properties: {
35
+ nodes: { type: "array", items: { $ref: "#/$defs/node" } },
36
+ edges: { type: "array", items: { $ref: "#/$defs/edge" } },
37
+ stickyNotes: { type: "array", items: { type: "object" } }
38
+ }
39
+ },
40
+ config: { $ref: "#/$defs/config" },
41
+ pinnedData: { type: "object" },
42
+ meta: {
43
+ type: "object",
44
+ properties: {
45
+ layout: {
46
+ type: "object",
47
+ description: "Positions and sizes keyed by node id, preserved verbatim by every code tool."
48
+ }
49
+ }
50
+ },
51
+ tags: { type: "array" }
52
+ },
53
+ $defs: {
54
+ position: {
55
+ type: "object",
56
+ required: ["x", "y"],
57
+ properties: { x: { type: "number" }, y: { type: "number" } }
58
+ },
59
+ connectionRef: {
60
+ type: "object",
61
+ required: ["connectionId", "adapter"],
62
+ properties: { connectionId: { type: "string" }, adapter: { type: "string" } }
63
+ },
64
+ retryConfig: {
65
+ type: "object",
66
+ properties: {
67
+ maxRetries: { type: "number", minimum: 0 },
68
+ retryDelay: { type: "number", minimum: 0 },
69
+ backoffMultiplier: { type: "number" },
70
+ maxDelay: { type: "number", minimum: 0 },
71
+ retryOnErrorTypes: { type: "array", items: { type: "string" } },
72
+ nonRetryableErrorTypes: { type: "array", items: { type: "string" } }
73
+ }
74
+ },
75
+ errorConfig: {
76
+ type: "object",
77
+ properties: {
78
+ onError: { type: "string" },
79
+ errorBranchId: { type: "string" },
80
+ retryConfig: { $ref: "#/$defs/retryConfig" },
81
+ timeout: {
82
+ type: "object",
83
+ properties: {
84
+ duration: { type: "number", minimum: 0 },
85
+ onTimeout: { type: "string" },
86
+ timeoutBranchId: { type: "string" }
87
+ }
88
+ }
89
+ }
90
+ },
91
+ executionSettings: {
92
+ type: "object",
93
+ properties: {
94
+ alwaysOutputData: { type: "boolean" },
95
+ executeOnce: { type: "boolean" },
96
+ runOn: { type: "object" }
97
+ }
98
+ },
99
+ node: {
100
+ type: "object",
101
+ required: ["id", "type"],
102
+ properties: {
103
+ id: { type: "string", minLength: 1 },
104
+ type: { type: "string", minLength: 1 },
105
+ label: { type: "string" },
106
+ position: { $ref: "#/$defs/position" },
107
+ operation_name: { type: "string" },
108
+ version: { type: "string" },
109
+ args: {
110
+ type: "object",
111
+ properties: {
112
+ state: { type: "object" },
113
+ context: { type: "object" },
114
+ connections: { type: "array", items: { $ref: "#/$defs/connectionRef" } },
115
+ credentials: { type: "array", items: { type: "object" } }
116
+ }
117
+ },
118
+ errorConfig: { $ref: "#/$defs/errorConfig" },
119
+ executionSettings: { $ref: "#/$defs/executionSettings" },
120
+ disabled: { type: "boolean" },
121
+ data: { type: "object" },
122
+ config: { type: "object" }
123
+ },
124
+ allOf: [
125
+ {
126
+ if: { properties: { type: { const: "internal:code-workflow" } }, required: ["type"] },
127
+ then: {
128
+ required: ["args"],
129
+ properties: {
130
+ args: {
131
+ required: ["state"],
132
+ properties: {
133
+ state: {
134
+ type: "object",
135
+ required: ["workflowFn"],
136
+ properties: {
137
+ workflowFn: { type: "string", minLength: 1 },
138
+ inputMapping: {},
139
+ taskQueue: { type: "string", minLength: 1 },
140
+ waitForCompletion: { type: "boolean" },
141
+ timeout: { type: "number", minimum: 0 },
142
+ failOnError: { type: "boolean" },
143
+ parentClosePolicy: { enum: ["TERMINATE", "ABANDON", "REQUEST_CANCEL"] }
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ },
151
+ {
152
+ if: { properties: { type: { const: "internal:wait-for-signal" } }, required: ["type"] },
153
+ then: {
154
+ required: ["args"],
155
+ properties: {
156
+ args: {
157
+ required: ["state"],
158
+ properties: {
159
+ state: {
160
+ type: "object",
161
+ required: ["signalName"],
162
+ properties: {
163
+ signalName: { type: "string", minLength: 1 },
164
+ limitWait: {
165
+ type: "object",
166
+ required: ["amount", "unit"],
167
+ properties: {
168
+ amount: { type: "number", exclusiveMinimum: 0 },
169
+ unit: { type: "string" }
170
+ }
171
+ },
172
+ payloadSchema: JSON_SCHEMA_OBJECT
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ]
181
+ },
182
+ edge: {
183
+ type: "object",
184
+ required: ["source", "target"],
185
+ properties: {
186
+ id: { type: "string" },
187
+ source: { type: "string", minLength: 1 },
188
+ target: { type: "string", minLength: 1 },
189
+ sourceHandle: { type: ["string", "null"] },
190
+ targetHandle: { type: ["string", "null"] },
191
+ type: { type: "string" },
192
+ label: {},
193
+ data: { type: "object" },
194
+ mapping: { type: "object" }
195
+ }
196
+ },
197
+ config: {
198
+ type: "object",
199
+ properties: {
200
+ maxParallelism: { type: "number", minimum: 1 },
201
+ timeout: { type: "number", minimum: 0, description: "Run timeout in seconds." },
202
+ executionTimeout: {
203
+ type: ["number", "string"],
204
+ description: "Seconds or a duration string."
205
+ },
206
+ workflowTaskTimeout: {
207
+ type: ["number", "string"],
208
+ description: "Seconds or a duration string."
209
+ },
210
+ versioningBehavior: { enum: ["PINNED", "AUTO_UPGRADE"] }
211
+ }
212
+ },
213
+ interface: {
214
+ type: "object",
215
+ additionalProperties: false,
216
+ properties: {
217
+ input: JSON_SCHEMA_OBJECT,
218
+ output: JSON_SCHEMA_OBJECT,
219
+ signals: {
220
+ type: "object",
221
+ additionalProperties: {
222
+ type: "object",
223
+ additionalProperties: false,
224
+ properties: { payload: JSON_SCHEMA_OBJECT, description: { type: "string" } }
225
+ }
226
+ },
227
+ queries: {
228
+ type: "object",
229
+ additionalProperties: {
230
+ type: "object",
231
+ additionalProperties: false,
232
+ properties: {
233
+ args: JSON_SCHEMA_OBJECT,
234
+ result: JSON_SCHEMA_OBJECT,
235
+ description: { type: "string" }
236
+ }
237
+ }
238
+ },
239
+ updates: {
240
+ type: "object",
241
+ additionalProperties: {
242
+ type: "object",
243
+ additionalProperties: false,
244
+ properties: {
245
+ args: JSON_SCHEMA_OBJECT,
246
+ result: JSON_SCHEMA_OBJECT,
247
+ description: { type: "string" }
248
+ }
249
+ }
250
+ },
251
+ open: {
252
+ type: "object",
253
+ additionalProperties: false,
254
+ properties: {
255
+ signals: { type: "boolean" },
256
+ queries: { type: "boolean" },
257
+ updates: { type: "boolean" }
258
+ }
259
+ }
260
+ }
261
+ },
262
+ code: {
263
+ type: "object",
264
+ additionalProperties: false,
265
+ required: ["language", "buildId"],
266
+ properties: {
267
+ language: { enum: ["typescript", "python"] },
268
+ sdk: { type: "string" },
269
+ buildId: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
270
+ bundleRef: { type: "string" },
271
+ sourceRef: { type: "string" },
272
+ entry: { type: "string" },
273
+ exports: {
274
+ type: "object",
275
+ additionalProperties: false,
276
+ properties: {
277
+ workflows: {
278
+ type: "object",
279
+ additionalProperties: {
280
+ type: "object",
281
+ properties: {
282
+ interface: { $ref: "#/$defs/interface" },
283
+ taskQueue: { type: "string" }
284
+ }
285
+ }
286
+ },
287
+ activities: {
288
+ type: "object",
289
+ additionalProperties: {
290
+ type: "object",
291
+ properties: {
292
+ taskQueue: { type: "string" },
293
+ input: JSON_SCHEMA_OBJECT,
294
+ output: JSON_SCHEMA_OBJECT
295
+ }
296
+ }
297
+ }
298
+ }
299
+ },
300
+ determinism: { enum: ["isolate", "worker"] }
301
+ }
302
+ },
303
+ authoring: {
304
+ type: "object",
305
+ additionalProperties: false,
306
+ properties: {
307
+ mode: { enum: ["canvas", "code", "hybrid"] },
308
+ generator: { enum: ["builder", "sdk-ts", "sdk-py", "ai", "import", "lift"] },
309
+ generatorVersion: { type: "string" },
310
+ sourceHash: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
311
+ liftedFrom: {
312
+ type: "object",
313
+ additionalProperties: false,
314
+ properties: {
315
+ workflowKey: { type: "string" },
316
+ version: { type: "string" },
317
+ fidelity: { enum: ["lossless", "lossy"] }
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ };
324
+
325
+ // src/ir/executor-view.ts
326
+ var UNREAD_INTERNAL_PARAMETER_KEYS = /* @__PURE__ */ new Set([
327
+ "label",
328
+ "operation_name",
329
+ "args",
330
+ "errorConfig",
331
+ "executionSettings",
332
+ "disabled",
333
+ "nodeVersion"
334
+ ]);
335
+ function stringOrEmpty(value) {
336
+ return typeof value === "string" && value ? value : "";
337
+ }
338
+ function executorNodeView(node) {
339
+ const record = isRecord(node) ? node : {};
340
+ const data = isRecord(record["data"]) ? record["data"] : void 0;
341
+ const args = isRecord(record["args"]) ? record["args"] : void 0;
342
+ const dataArgs = isRecord(data?.["args"]) ? data["args"] : void 0;
343
+ const type = typeof record["type"] === "string" ? record["type"] : "";
344
+ const merged = usesMergedParameters(type);
345
+ let parameters;
346
+ if (merged) {
347
+ const config = isRecord(record["config"]) ? record["config"] : {};
348
+ const dataState2 = isRecord(dataArgs?.["state"]) ? dataArgs["state"] : {};
349
+ const rootState = isRecord(args?.["state"]) ? args["state"] : {};
350
+ const all = { ...data, ...config, ...dataState2, ...rootState };
351
+ for (const key of UNREAD_INTERNAL_PARAMETER_KEYS) {
352
+ if (!Object.hasOwn(config, key) && !Object.hasOwn(dataState2, key) && !Object.hasOwn(rootState, key)) {
353
+ delete all[key];
354
+ }
355
+ }
356
+ parameters = all;
357
+ } else {
358
+ parameters = args?.["state"] || dataArgs?.["state"] || data?.["state"];
359
+ }
360
+ const rootSettings = isRecord(record["executionSettings"]) ? record["executionSettings"] : void 0;
361
+ const dataSettings = isRecord(data?.["executionSettings"]) ? data["executionSettings"] : void 0;
362
+ const dataState = isRecord(dataArgs?.["state"]) ? dataArgs["state"] : void 0;
363
+ const nodeData = isRecord(data?.["nodeData"]) ? data["nodeData"] : void 0;
364
+ const argsContext = isRecord(args?.["context"]) ? args["context"] : void 0;
365
+ const contextNode = isRecord(argsContext?.["node"]) ? argsContext["node"] : void 0;
366
+ return {
367
+ id: record["id"],
368
+ type: record["type"],
369
+ operation: stringOrEmpty(record["operation_name"]) || stringOrEmpty(data?.["operation_name"]) || stringOrEmpty(data?.["operation"]),
370
+ legacyOperation: stringOrEmpty(record["operation_name"]) || stringOrEmpty(data?.["operation_name"]),
371
+ version: merged ? record["version"] || "1.0.0" : data?.["version"] || record["version"],
372
+ parameters,
373
+ connections: args?.["connections"] || dataArgs?.["connections"],
374
+ credentials: args?.["credentials"] || dataArgs?.["credentials"],
375
+ errorConfig: record["errorConfig"] ?? data?.["errorConfig"] ?? dataState?.["errorConfig"],
376
+ executeOnce: rootSettings?.["executeOnce"] === true || dataSettings?.["executeOnce"] === true,
377
+ runOn: dataSettings?.["runOn"],
378
+ disabled: record["disabled"] === true || data?.["disabled"] === true,
379
+ nodeDataNodeType: nodeData?.["nodeType"],
380
+ argsContextNodeType: contextNode?.["type"]
381
+ };
382
+ }
383
+ function executorView(definition) {
384
+ if (!isRecord(definition) || !isRecord(definition["data"])) return [];
385
+ const nodes = definition["data"]["nodes"];
386
+ return Array.isArray(nodes) ? nodes.map(executorNodeView) : [];
387
+ }
388
+
389
+ // src/ir/duration.ts
390
+ var SHORT = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/;
391
+ var ISO = /^P(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
392
+ var SECOND_MS = 1e3;
393
+ var MINUTE_MS = 60 * SECOND_MS;
394
+ var HOUR_MS = 60 * MINUTE_MS;
395
+ var DAY_MS = 24 * HOUR_MS;
396
+ var WEEK_MS = 7 * DAY_MS;
397
+ var UNIT_MS = {
398
+ ms: 1,
399
+ s: SECOND_MS,
400
+ m: MINUTE_MS,
401
+ h: HOUR_MS,
402
+ d: DAY_MS,
403
+ w: WEEK_MS
404
+ };
405
+ function durationMs(value, numberUnit) {
406
+ if (typeof value === "number") {
407
+ if (!Number.isFinite(value) || value < 0) return void 0;
408
+ return numberUnit === "s" ? value * 1e3 : value;
409
+ }
410
+ if (typeof value !== "string") return void 0;
411
+ const text = value.trim();
412
+ const short = SHORT.exec(text);
413
+ if (short?.[1] !== void 0 && short[2] !== void 0) {
414
+ const unit = UNIT_MS[short[2]];
415
+ return unit === void 0 ? void 0 : Number(short[1]) * unit;
416
+ }
417
+ const iso = ISO.exec(text);
418
+ if (iso && text !== "P" && text !== "PT") {
419
+ const [, weeks, days, hours, minutes, seconds] = iso;
420
+ const parts = [weeks, days, hours, minutes, seconds];
421
+ if (parts.every((part) => part === void 0)) return void 0;
422
+ return Number(weeks ?? 0) * WEEK_MS + Number(days ?? 0) * DAY_MS + Number(hours ?? 0) * HOUR_MS + Number(minutes ?? 0) * MINUTE_MS + Number(seconds ?? 0) * SECOND_MS;
423
+ }
424
+ return void 0;
425
+ }
426
+
427
+ // src/ir/schedule.ts
428
+ var FIRE_TIMES = 10;
429
+ var MAX_ITERATIONS = 2e4;
430
+ var CALENDAR_FIELDS = [
431
+ { name: "second", min: 0, max: 59, fallback: "0" },
432
+ { name: "minute", min: 0, max: 59, fallback: "0" },
433
+ { name: "hour", min: 0, max: 23, fallback: "0" },
434
+ { name: "dayOfMonth", min: 1, max: 31, fallback: "*" },
435
+ { name: "month", min: 1, max: 12, fallback: "*" }
436
+ ];
437
+ function rangeValues(value, min, max, field) {
438
+ const values = [];
439
+ const entries = Array.isArray(value) ? value : [value];
440
+ for (const entry of entries) {
441
+ if (typeof entry === "number") {
442
+ if (!Number.isInteger(entry) || entry < min || entry > max)
443
+ throw new Error(`${field} ${entry} is out of range`);
444
+ values.push(entry);
445
+ continue;
446
+ }
447
+ if (!isRecord(entry) || typeof entry["start"] !== "number") {
448
+ throw new Error(`${field} entries must be numbers or { start, end, step }`);
449
+ }
450
+ const start = entry["start"];
451
+ const end = typeof entry["end"] === "number" ? entry["end"] : start;
452
+ const step = typeof entry["step"] === "number" ? entry["step"] : 1;
453
+ if (![start, end, step].every(Number.isInteger) || start < min || end > max || end < start || step < 1) {
454
+ throw new Error(`${field} range is invalid`);
455
+ }
456
+ for (let current = start; current <= end; current += step) values.push(current);
457
+ }
458
+ if (values.length === 0) throw new Error(`${field} has no values`);
459
+ return [...new Set(values)].sort((a, b) => a - b);
460
+ }
461
+ function cronField(value, field) {
462
+ if (value === void 0) return field.fallback;
463
+ return rangeValues(value, field.min, field.max, field.name).join(",");
464
+ }
465
+ function timezoneOf(spec, fallback) {
466
+ const tz = spec["timezone"] ?? fallback;
467
+ return typeof tz === "string" && tz !== "" ? tz : void 0;
468
+ }
469
+ function cronTimes(expression, now, tz, accept) {
470
+ const interval = CronExpressionParser.parse(expression, {
471
+ currentDate: now,
472
+ ...tz ? { tz } : {}
473
+ });
474
+ const out = [];
475
+ for (let i = 0; i < MAX_ITERATIONS && out.length < FIRE_TIMES; i++) {
476
+ if (!interval.hasNext()) break;
477
+ const date = interval.next().toDate();
478
+ if (accept(date)) out.push(date.toISOString());
479
+ }
480
+ return out;
481
+ }
482
+ function localParts(date, tz) {
483
+ if (!tz) return { weekday: date.getUTCDay(), year: date.getUTCFullYear() };
484
+ const parts = new Intl.DateTimeFormat("en-US", {
485
+ timeZone: tz,
486
+ weekday: "short",
487
+ year: "numeric"
488
+ }).formatToParts(date);
489
+ const weekdayName = parts.find((part) => part.type === "weekday")?.value ?? "Sun";
490
+ const year = Number(parts.find((part) => part.type === "year")?.value ?? date.getUTCFullYear());
491
+ const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].indexOf(weekdayName);
492
+ return { weekday, year };
493
+ }
494
+ function calendarTimes(entries, now, tz) {
495
+ const all = [];
496
+ for (const entry of entries) {
497
+ if (!isRecord(entry)) throw new Error("each calendar entry must be an object");
498
+ const expression = CALENDAR_FIELDS.map((field) => cronField(entry[field.name], field)).join(
499
+ " "
500
+ );
501
+ const weekdays = entry["dayOfWeek"] === void 0 ? void 0 : new Set(rangeValues(entry["dayOfWeek"], 0, 6, "dayOfWeek"));
502
+ const years = entry["year"] === void 0 ? void 0 : new Set(rangeValues(entry["year"], 1970, 9999, "year"));
503
+ if (years && Math.max(...years) < localParts(now, tz).year) continue;
504
+ all.push(
505
+ ...cronTimes(`${expression} *`, now, tz, (date) => {
506
+ const local = localParts(date, tz);
507
+ return (!weekdays || weekdays.has(local.weekday)) && (!years || years.has(local.year));
508
+ })
509
+ );
510
+ }
511
+ return [...new Set(all)].sort().slice(0, FIRE_TIMES);
512
+ }
513
+ function intervalTimes(entries, now) {
514
+ const all = [];
515
+ for (const entry of entries) {
516
+ if (!isRecord(entry)) throw new Error("each interval entry must be an object");
517
+ const every = durationMs(entry["every"], "s");
518
+ const offset = entry["offset"] === void 0 ? 0 : durationMs(entry["offset"], "s");
519
+ if (every === void 0 || every <= 0)
520
+ throw new Error("interval.every must be a positive duration");
521
+ if (offset === void 0 || offset >= every)
522
+ throw new Error("interval.offset must be a duration below every");
523
+ let next = Math.floor((now.getTime() - offset) / every) * every + offset;
524
+ if (next <= now.getTime()) next += every;
525
+ for (let i = 0; i < FIRE_TIMES; i++) all.push(next + i * every);
526
+ }
527
+ return [...new Set(all)].sort((a, b) => a - b).slice(0, FIRE_TIMES).map((time) => new Date(time).toISOString());
528
+ }
529
+ function checkScheduleSpec(spec, options = {}) {
530
+ const now = options.now ?? /* @__PURE__ */ new Date();
531
+ if (!isRecord(spec)) return { ok: false, problem: "spec must be an object", nextFireTimes: [] };
532
+ const kinds = ["cron", "calendar", "interval"].filter((kind) => spec[kind] !== void 0);
533
+ if (kinds.length !== 1) {
534
+ return {
535
+ ok: false,
536
+ problem: "spec must have exactly one of cron, calendar or interval",
537
+ nextFireTimes: []
538
+ };
539
+ }
540
+ const tz = timezoneOf(spec, options.timezone);
541
+ try {
542
+ let times;
543
+ if (kinds[0] === "cron") {
544
+ if (typeof spec["cron"] !== "string") throw new Error("cron must be a string");
545
+ times = cronTimes(spec["cron"], now, tz, () => true);
546
+ } else if (kinds[0] === "calendar") {
547
+ if (!Array.isArray(spec["calendar"]) || spec["calendar"].length === 0) {
548
+ throw new Error("calendar must be a non-empty list");
549
+ }
550
+ times = calendarTimes(spec["calendar"], now, tz);
551
+ } else {
552
+ if (!Array.isArray(spec["interval"]) || spec["interval"].length === 0) {
553
+ throw new Error("interval must be a non-empty list");
554
+ }
555
+ times = intervalTimes(spec["interval"], now);
556
+ }
557
+ if (times.length === 0)
558
+ return { ok: false, problem: "the spec never fires", nextFireTimes: [] };
559
+ return { ok: true, nextFireTimes: times };
560
+ } catch (error) {
561
+ return {
562
+ ok: false,
563
+ problem: error instanceof Error ? error.message : String(error),
564
+ nextFireTimes: []
565
+ };
566
+ }
567
+ }
568
+
569
+ // src/ir/validate.ts
570
+ var SLUG = /^[a-z][a-z0-9-]{0,62}$/;
571
+ var MAX_WORKFLOW_TASK_TIMEOUT_MS = 12e4;
572
+ var MAX_START_DELAY_MS = 30 * 864e5;
573
+ var SECRET_KEYS = /* @__PURE__ */ new Set(["secretToken", "authPassword", "authToken", "authApiKey"]);
574
+ var AUTH_SECRET_KEYS = /* @__PURE__ */ new Set(["password", "token", "apiKey"]);
575
+ var compiled;
576
+ function schemaTools() {
577
+ if (!compiled) {
578
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
579
+ compiled = { ajv, validateDocument: ajv.compile(IR_V2_SCHEMA) };
580
+ }
581
+ return compiled;
582
+ }
583
+ function isValidJsonSchema(schema) {
584
+ if (!isRecord(schema) && typeof schema !== "boolean") return false;
585
+ const { ajv } = schemaTools();
586
+ return ajv.validateSchema(schema) === true;
587
+ }
588
+ function isSecretKey(parentKey, key) {
589
+ return SECRET_KEYS.has(key) || parentKey === "auth" && AUTH_SECRET_KEYS.has(key);
590
+ }
591
+ function findSecretKeys(value, path, parentKey, out) {
592
+ if (Array.isArray(value)) {
593
+ value.forEach((item, index) => findSecretKeys(item, `${path}[${index}]`, parentKey, out));
594
+ return;
595
+ }
596
+ if (!isRecord(value)) return;
597
+ for (const [key, child] of Object.entries(value)) {
598
+ if (isSecretKey(parentKey, key)) out.push(`${path}.${key}`);
599
+ findSecretKeys(child, `${path}.${key}`, key, out);
600
+ }
601
+ }
602
+ function findLegacyTemplates(value, path, out) {
603
+ if (typeof value === "string") {
604
+ if (hasLegacyTemplate(value)) out.push(path);
605
+ return;
606
+ }
607
+ if (Array.isArray(value)) {
608
+ value.forEach((item, index) => findLegacyTemplates(item, `${path}[${index}]`, out));
609
+ return;
610
+ }
611
+ if (isRecord(value)) {
612
+ for (const [key, child] of Object.entries(value))
613
+ findLegacyTemplates(child, `${path}.${key}`, out);
614
+ }
615
+ }
616
+ var V2_SCHEMA_PATHS = ["/interface", "/code", "/authoring", "/schemaVersion"];
617
+ function isHardSchemaError(error) {
618
+ if (error.schemaPath.includes("/allOf/")) return true;
619
+ if (V2_SCHEMA_PATHS.some(
620
+ (prefix) => error.instancePath === prefix || error.instancePath.startsWith(`${prefix}/`)
621
+ )) {
622
+ return true;
623
+ }
624
+ const path = error.instancePath;
625
+ if (path === "" || path === "/data")
626
+ return error.keyword === "required" || error.keyword === "type";
627
+ if (/^\/data\/(nodes|edges)$/.test(path)) return error.keyword === "type";
628
+ if (/^\/data\/nodes\/\d+$/.test(path))
629
+ return error.keyword === "required" || error.keyword === "type";
630
+ if (/^\/data\/edges\/\d+$/.test(path))
631
+ return error.keyword === "required" || error.keyword === "type";
632
+ return /^\/data\/nodes\/\d+\/(id|type)$/.test(path) || /^\/data\/edges\/\d+\/(source|target)$/.test(path);
633
+ }
634
+ function legacyMirrors(node) {
635
+ const found = [];
636
+ const type = typeof node["type"] === "string" ? node["type"] : "";
637
+ const merged = usesMergedParameters(type);
638
+ const data = isRecord(node["data"]) ? node["data"] : void 0;
639
+ const dataArgs = isRecord(data?.["args"]) ? data["args"] : void 0;
640
+ if (data) {
641
+ for (const key of [
642
+ "label",
643
+ "operation_name",
644
+ "errorConfig",
645
+ "disabled",
646
+ "state",
647
+ "nodeVersion"
648
+ ]) {
649
+ if (data[key] !== void 0) found.push(`data.${key}`);
650
+ }
651
+ if (!merged && data["version"] !== void 0) found.push("data.version");
652
+ for (const key of ["state", "connections", "credentials", "context"]) {
653
+ if (dataArgs?.[key] !== void 0) found.push(`data.args.${key}`);
654
+ }
655
+ const settings2 = isRecord(data["executionSettings"]) ? data["executionSettings"] : void 0;
656
+ if (settings2) {
657
+ const onlyRunOnMirror = Object.keys(settings2).length === 1 && settings2["runOn"] !== void 0 && isRecord(node["executionSettings"]) && JSON.stringify(node["executionSettings"]["runOn"]) === JSON.stringify(settings2["runOn"]);
658
+ if (!onlyRunOnMirror) found.push("data.executionSettings");
659
+ }
660
+ }
661
+ if (merged && node["config"] !== void 0) found.push("config");
662
+ const args = isRecord(node["args"]) ? node["args"] : void 0;
663
+ if (args?.["credentials"] !== void 0) found.push("args.credentials");
664
+ const settings = isRecord(node["executionSettings"]) ? node["executionSettings"] : void 0;
665
+ if (settings?.["runOn"] !== void 0 && !isRecord(data?.["executionSettings"]))
666
+ found.push("executionSettings.runOn");
667
+ return found;
668
+ }
669
+ function validate(definition, options = {}) {
670
+ const errors = [];
671
+ const warnings = [];
672
+ const taskQueues = /* @__PURE__ */ new Set();
673
+ const strict = options.strict === true;
674
+ const fail = (issue) => {
675
+ errors.push(issue);
676
+ };
677
+ const warnOrFail = (issue) => {
678
+ (strict ? errors : warnings).push(issue);
679
+ };
680
+ if (!isRecord(definition)) {
681
+ return {
682
+ valid: false,
683
+ errors: [{ code: "definition.required", message: "A workflow definition must be an object" }],
684
+ warnings,
685
+ taskQueues: []
686
+ };
687
+ }
688
+ const data = isRecord(definition["data"]) ? definition["data"] : void 0;
689
+ if (!data)
690
+ fail({ code: "definition.dataRequired", message: "The definition needs a data object" });
691
+ const rawNodes = data?.["nodes"];
692
+ const rawEdges = data?.["edges"];
693
+ if (data && !Array.isArray(rawNodes))
694
+ fail({ code: "definition.nodesRequired", message: "data.nodes must be an array" });
695
+ if (data && !Array.isArray(rawEdges))
696
+ fail({ code: "definition.edgesRequired", message: "data.edges must be an array" });
697
+ if (errors.length > 0) return { valid: false, errors, warnings, taskQueues: [] };
698
+ const schemaVersion = definition["schemaVersion"];
699
+ if (schemaVersion !== void 0 && schemaVersion !== 1 && schemaVersion !== 2) {
700
+ fail({
701
+ code: "schema.version",
702
+ message: "schemaVersion must be 1, 2 or absent",
703
+ path: "schemaVersion"
704
+ });
705
+ }
706
+ const { validateDocument } = schemaTools();
707
+ if (!validateDocument(definition)) {
708
+ for (const error of validateDocument.errors ?? []) {
709
+ const issue = {
710
+ code: "schema.violation",
711
+ message: `${error.instancePath || "/"} ${error.message ?? "is invalid"}`,
712
+ path: error.instancePath
713
+ };
714
+ if (isHardSchemaError(error)) fail(issue);
715
+ else warnOrFail(issue);
716
+ }
717
+ }
718
+ const nodes = rawNodes.map((node) => isRecord(node) ? node : {});
719
+ const edges = rawEdges.map((edge) => isRecord(edge) ? edge : {});
720
+ if (nodes.length === 0 && !options.allowEmpty)
721
+ fail({ code: "node.atLeastOne", message: "Add at least one node" });
722
+ const ids = /* @__PURE__ */ new Set();
723
+ nodes.forEach((node, index) => {
724
+ const id = typeof node["id"] === "string" && node["id"] ? node["id"] : void 0;
725
+ if (!id)
726
+ fail({
727
+ code: "node.missingId",
728
+ message: `Node ${index} has no id`,
729
+ path: `data.nodes[${index}]`
730
+ });
731
+ else if (ids.has(id))
732
+ warnOrFail({
733
+ code: "node.duplicateId",
734
+ message: `Node id "${id}" is used twice`,
735
+ nodeId: id
736
+ });
737
+ else ids.add(id);
738
+ if (typeof node["type"] !== "string" || !node["type"]) {
739
+ fail({
740
+ code: "node.missingType",
741
+ message: `Node ${id ?? index} has no type`,
742
+ ...id ? { nodeId: id } : {}
743
+ });
744
+ }
745
+ });
746
+ const pairs = /* @__PURE__ */ new Set();
747
+ const incoming = /* @__PURE__ */ new Map();
748
+ edges.forEach((edge, index) => {
749
+ const source = typeof edge["source"] === "string" ? edge["source"] : void 0;
750
+ const target = typeof edge["target"] === "string" ? edge["target"] : void 0;
751
+ const path = `data.edges[${index}]`;
752
+ if (!source) fail({ code: "edge.missingSource", message: `Edge ${index} has no source`, path });
753
+ if (!target) fail({ code: "edge.missingTarget", message: `Edge ${index} has no target`, path });
754
+ if (source && !ids.has(source))
755
+ fail({ code: "edge.sourceNotFound", message: `Edge source "${source}" is not a node`, path });
756
+ if (target && !ids.has(target))
757
+ fail({ code: "edge.targetNotFound", message: `Edge target "${target}" is not a node`, path });
758
+ if (source && source === target)
759
+ fail({ code: "edge.selfLoop", message: `Node "${source}" connects to itself`, path });
760
+ const pair = `${source}-${target}`;
761
+ if (pairs.has(pair))
762
+ fail({ code: "edge.duplicatePair", message: `Duplicate edge ${pair}`, path });
763
+ pairs.add(pair);
764
+ if (target) incoming.set(target, (incoming.get(target) ?? 0) + 1);
765
+ });
766
+ const code = isRecord(definition["code"]) ? definition["code"] : void 0;
767
+ const exportsBlock = isRecord(code?.["exports"]) ? code["exports"] : void 0;
768
+ const workflowExports = isRecord(exportsBlock?.["workflows"]) ? exportsBlock["workflows"] : {};
769
+ const activityExports = isRecord(exportsBlock?.["activities"]) ? exportsBlock["activities"] : {};
770
+ if (code?.["determinism"] === "isolate" && Object.keys(activityExports).length > 0) {
771
+ fail({
772
+ code: "code.isolateActivities",
773
+ message: "A managed (isolate) bundle cannot declare activities; there is nowhere to run them",
774
+ path: "code.exports.activities"
775
+ });
776
+ }
777
+ for (const [name, exported] of [
778
+ ...Object.entries(workflowExports),
779
+ ...Object.entries(activityExports)
780
+ ]) {
781
+ if (isRecord(exported) && typeof exported["taskQueue"] === "string")
782
+ taskQueues.add(exported["taskQueue"]);
783
+ if (isRecord(exported) && isRecord(exported["interface"])) {
784
+ checkInterface(exported["interface"], `code.exports.*.${name}.interface`, fail);
785
+ }
786
+ }
787
+ if (isRecord(definition["interface"])) checkInterface(definition["interface"], "interface", fail);
788
+ for (const block of ["code", "interface"]) {
789
+ const found = [];
790
+ findSecretKeys(definition[block], block, void 0, found);
791
+ for (const path of found) {
792
+ fail({
793
+ code: "secrets.secretShapedKey",
794
+ message: `${path} looks like a secret; ${block} may not hold secrets`,
795
+ path
796
+ });
797
+ }
798
+ }
799
+ for (const node of nodes) {
800
+ const id = typeof node["id"] === "string" ? node["id"] : void 0;
801
+ const at = id ? { nodeId: id } : {};
802
+ const type = typeof node["type"] === "string" ? node["type"] : "";
803
+ const args = isRecord(node["args"]) ? node["args"] : void 0;
804
+ const state = isRecord(args?.["state"]) ? args["state"] : void 0;
805
+ if (type === CODE_WORKFLOW_NODE_TYPE) {
806
+ const workflowFn = state?.["workflowFn"];
807
+ if (!code) {
808
+ fail({
809
+ code: "codeWorkflow.codeRequired",
810
+ message: "A code-workflow node needs a code bundle on the definition",
811
+ ...at
812
+ });
813
+ } else if (typeof workflowFn !== "string" || !Object.hasOwn(workflowExports, workflowFn)) {
814
+ fail({
815
+ code: "codeWorkflow.unknownFunction",
816
+ message: `workflowFn "${String(workflowFn)}" is not exported by the bundle`,
817
+ ...at,
818
+ path: "args.state.workflowFn"
819
+ });
820
+ }
821
+ if (typeof state?.["taskQueue"] === "string") taskQueues.add(state["taskQueue"]);
822
+ }
823
+ const isTrigger = INTERNAL_TRIGGER_TYPES.has(type) || id !== void 0 && (incoming.get(id) ?? 0) === 0;
824
+ if (isTrigger && state?.["startDelay"] !== void 0) {
825
+ const delay = durationMs(state["startDelay"], "s");
826
+ if (delay === void 0) {
827
+ fail({
828
+ code: "trigger.invalidDuration",
829
+ message: "startDelay is not a duration",
830
+ ...at,
831
+ path: "args.state.startDelay"
832
+ });
833
+ } else if (delay > MAX_START_DELAY_MS) {
834
+ fail({
835
+ code: "trigger.startDelayTooLong",
836
+ message: "startDelay may be at most 30 days",
837
+ ...at,
838
+ path: "args.state.startDelay"
839
+ });
840
+ }
841
+ }
842
+ if (type === "internal:schedule" && state?.["spec"] !== void 0) {
843
+ const check = checkScheduleSpec(state["spec"], {
844
+ timezone: state["timezone"],
845
+ ...options.now ? { now: options.now } : {}
846
+ });
847
+ if (!check.ok) {
848
+ fail({
849
+ code: "schedule.invalidSpec",
850
+ message: `Schedule spec: ${check.problem ?? "invalid"}`,
851
+ ...at,
852
+ path: "args.state.spec"
853
+ });
854
+ }
855
+ }
856
+ if (strict) {
857
+ if (id && !SLUG.test(id)) {
858
+ fail({
859
+ code: "strict.nodeIdNotSlug",
860
+ message: `Node id "${id}" must match ^[a-z][a-z0-9-]{0,62}$`,
861
+ nodeId: id
862
+ });
863
+ }
864
+ for (const mirror of legacyMirrors(node)) {
865
+ fail({
866
+ code: "strict.legacyMirror",
867
+ message: `Node relies on the legacy location ${mirror}`,
868
+ ...at,
869
+ path: mirror
870
+ });
871
+ }
872
+ const templates = [];
873
+ const args2 = isRecord(node["args"]) ? node["args"] : void 0;
874
+ const scanned = type === "internal:code" && args2 && isRecord(args2["state"]) ? {
875
+ ...args2,
876
+ state: Object.fromEntries(
877
+ Object.entries(args2["state"]).filter(([key]) => key !== "code")
878
+ )
879
+ } : node["args"];
880
+ findLegacyTemplates(scanned, "args", templates);
881
+ findLegacyTemplates(node["config"], "config", templates);
882
+ findLegacyTemplates(node["data"], "data", templates);
883
+ for (const path of templates) {
884
+ fail({
885
+ code: "strict.legacyExpression",
886
+ message: `${path} uses a \${\u2026} template; use {{ \u2026 }}`,
887
+ ...at,
888
+ path
889
+ });
890
+ }
891
+ }
892
+ }
893
+ if (strict) {
894
+ edges.forEach((edge, index) => {
895
+ const templates = [];
896
+ findLegacyTemplates(edge["mapping"], "mapping", templates);
897
+ for (const path of templates) {
898
+ fail({
899
+ code: "strict.legacyExpression",
900
+ message: `Edge ${index} ${path} uses a \${\u2026} template`,
901
+ path: `data.edges[${index}].${path}`
902
+ });
903
+ }
904
+ });
905
+ }
906
+ const config = isRecord(definition["config"]) ? definition["config"] : void 0;
907
+ if (config) {
908
+ const timeout = config["timeout"] === void 0 ? void 0 : durationMs(config["timeout"], "s");
909
+ for (const key of ["executionTimeout", "workflowTaskTimeout"]) {
910
+ if (config[key] !== void 0 && durationMs(config[key], "s") === void 0) {
911
+ fail({
912
+ code: "config.invalidDuration",
913
+ message: `config.${key} is not a duration`,
914
+ path: `config.${key}`
915
+ });
916
+ }
917
+ }
918
+ const executionTimeout = config["executionTimeout"] === void 0 ? void 0 : durationMs(config["executionTimeout"], "s");
919
+ if (executionTimeout !== void 0 && timeout !== void 0 && executionTimeout < timeout) {
920
+ fail({
921
+ code: "config.executionTimeoutBelowTimeout",
922
+ message: "config.executionTimeout must be at least config.timeout",
923
+ path: "config.executionTimeout"
924
+ });
925
+ }
926
+ const taskTimeout = config["workflowTaskTimeout"] === void 0 ? void 0 : durationMs(config["workflowTaskTimeout"], "s");
927
+ if (taskTimeout !== void 0 && taskTimeout > MAX_WORKFLOW_TASK_TIMEOUT_MS) {
928
+ fail({
929
+ code: "config.workflowTaskTimeoutTooLong",
930
+ message: "config.workflowTaskTimeout may be at most 120 seconds",
931
+ path: "config.workflowTaskTimeout"
932
+ });
933
+ }
934
+ }
935
+ if (options.isTaskQueueKnown) {
936
+ for (const name of taskQueues) {
937
+ if (name !== "managed" && !options.isTaskQueueKnown(name)) {
938
+ fail({
939
+ code: "taskQueue.unknown",
940
+ message: `Task queue "${name}" does not exist in this workspace`
941
+ });
942
+ }
943
+ }
944
+ }
945
+ warnings.push(...shadowedInputs(nodes));
946
+ return { valid: errors.length === 0, errors, warnings, taskQueues: [...taskQueues].sort() };
947
+ }
948
+ function checkInterface(block, path, fail) {
949
+ const schemas = [];
950
+ if (block["input"] !== void 0) schemas.push([`${path}.input`, block["input"]]);
951
+ if (block["output"] !== void 0) schemas.push([`${path}.output`, block["output"]]);
952
+ for (const group of ["signals", "queries", "updates"]) {
953
+ const members = isRecord(block[group]) ? block[group] : {};
954
+ for (const [name, member] of Object.entries(members)) {
955
+ if (!isRecord(member)) continue;
956
+ for (const part of ["payload", "args", "result"]) {
957
+ if (member[part] !== void 0)
958
+ schemas.push([`${path}.${group}.${name}.${part}`, member[part]]);
959
+ }
960
+ }
961
+ }
962
+ for (const [where, schema] of schemas) {
963
+ if (!isValidJsonSchema(schema)) {
964
+ fail({
965
+ code: "interface.invalidSchema",
966
+ message: `${where} is not a valid JSON Schema 2020-12`,
967
+ path: where
968
+ });
969
+ }
970
+ }
971
+ }
972
+ function shadowedInputs(nodes) {
973
+ const warnings = [];
974
+ for (const node of nodes) {
975
+ const data = isRecord(node["data"]) ? node["data"] : void 0;
976
+ const declared = isRecord(data?.["inputs"]) ? data["inputs"] : void 0;
977
+ if (!declared) continue;
978
+ const dataArgs = isRecord(data?.["args"]) ? data["args"] : void 0;
979
+ const args = isRecord(node["args"]) ? node["args"] : void 0;
980
+ const staticParams = {
981
+ ...isRecord(node["config"]) ? node["config"] : {},
982
+ ...isRecord(dataArgs?.["state"]) ? dataArgs["state"] : {},
983
+ ...isRecord(args?.["state"]) ? args["state"] : {}
984
+ };
985
+ for (const key of Object.keys(declared)) {
986
+ if (!Object.hasOwn(staticParams, key)) continue;
987
+ warnings.push({
988
+ code: "node.shadowedInput",
989
+ message: `Node "${String(node["id"])}" sets "${key}" statically and in data.inputs; the static value wins`,
990
+ ...typeof node["id"] === "string" ? { nodeId: node["id"] } : {}
991
+ });
992
+ }
993
+ }
994
+ return warnings;
995
+ }
996
+
997
+ // src/ir/envelope.ts
998
+ var WORKFLOW_DOCUMENT_FORMAT = "fluidgrids.workflow";
999
+ var V2_CONFIG_KEYS = ["executionTimeout", "workflowTaskTimeout", "versioningBehavior"];
1000
+ function isV2Definition(definition) {
1001
+ if (!isRecord(definition)) return false;
1002
+ if (definition["schemaVersion"] === 2) return true;
1003
+ for (const key of ["interface", "code", "authoring"]) {
1004
+ if (definition[key] !== void 0) return true;
1005
+ }
1006
+ const meta = definition["meta"];
1007
+ if (isRecord(meta) && meta["layout"] !== void 0) return true;
1008
+ const config = definition["config"];
1009
+ if (isRecord(config) && V2_CONFIG_KEYS.some((key) => config[key] !== void 0)) return true;
1010
+ const data = definition["data"];
1011
+ const nodes = isRecord(data) && Array.isArray(data["nodes"]) ? data["nodes"] : [];
1012
+ return nodes.some(
1013
+ (node) => isRecord(node) && typeof node["type"] === "string" && V2_NODE_TYPES.has(node["type"])
1014
+ );
1015
+ }
1016
+ function envelopeSchemaVersion(definition) {
1017
+ return isV2Definition(definition) ? 2 : 1;
1018
+ }
1019
+ function exportDefinition(definition) {
1020
+ return isV2Definition(definition) ? normalizeDefinition(definition) : definition;
1021
+ }
1022
+ function buildExportDocument(input) {
1023
+ return {
1024
+ format: WORKFLOW_DOCUMENT_FORMAT,
1025
+ schemaVersion: envelopeSchemaVersion(input.definitionJson),
1026
+ exportedAt: input.exportedAt,
1027
+ workflowKey: input.workflowKey,
1028
+ version: input.version,
1029
+ name: input.name,
1030
+ description: input.description,
1031
+ definitionJson: exportDefinition(input.definitionJson),
1032
+ context: input.context,
1033
+ tags: input.tags,
1034
+ tagsV2: input.tagsV2,
1035
+ icon: input.icon,
1036
+ category: input.category,
1037
+ nodeReferences: input.nodeReferences
1038
+ };
1039
+ }
1040
+ function serializeWorkflowDocument(document, versionCreatedAt) {
1041
+ return `${JSON.stringify({ ...document, exportedAt: versionCreatedAt.toISOString() }, null, 2)}
1042
+ `;
1043
+ }
1044
+ function readWorkflowDocument(value) {
1045
+ if (!isRecord(value)) return null;
1046
+ if (value["format"] === WORKFLOW_DOCUMENT_FORMAT || "definitionJson" in value) {
1047
+ const definitionJson = value["definitionJson"];
1048
+ if (!isRecord(definitionJson)) return null;
1049
+ const schemaVersion = value["schemaVersion"];
1050
+ return {
1051
+ definitionJson,
1052
+ workflowKey: typeof value["workflowKey"] === "string" ? value["workflowKey"] : null,
1053
+ version: typeof value["version"] === "string" ? value["version"] : null,
1054
+ schemaVersion: schemaVersion === 1 || schemaVersion === 2 ? schemaVersion : null
1055
+ };
1056
+ }
1057
+ return { definitionJson: value, workflowKey: null, version: null, schemaVersion: null };
1058
+ }
1059
+ function parseWorkflowDocument(text) {
1060
+ try {
1061
+ return readWorkflowDocument(JSON.parse(text));
1062
+ } catch {
1063
+ return null;
1064
+ }
1065
+ }
1066
+
1067
+ // src/ir/interface.ts
1068
+ var FIELD_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "object", "array"]);
1069
+ function workflowInputsSchema(workflowInputs) {
1070
+ const properties = {};
1071
+ const required = [];
1072
+ for (const line of workflowInputs.split(/\r?\n/)) {
1073
+ const trimmed = line.trim();
1074
+ if (!trimmed) continue;
1075
+ const [rawName, rawType] = trimmed.split(":", 2);
1076
+ const name = (rawName ?? "").trim();
1077
+ if (!name) continue;
1078
+ const type = (rawType ?? "").trim().toLowerCase();
1079
+ properties[name] = FIELD_TYPES.has(type) ? { type } : {};
1080
+ required.push(name);
1081
+ }
1082
+ return {
1083
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1084
+ type: "object",
1085
+ properties,
1086
+ ...required.length > 0 ? { required } : {}
1087
+ };
1088
+ }
1089
+ function deriveInterface(definition, options = {}) {
1090
+ if (!isRecord(definition)) return {};
1091
+ if (isRecord(definition["interface"])) return definition["interface"];
1092
+ const data = isRecord(definition["data"]) ? definition["data"] : {};
1093
+ const nodes = Array.isArray(data["nodes"]) ? data["nodes"].filter(isRecord) : [];
1094
+ const edges = Array.isArray(data["edges"]) ? data["edges"].filter(isRecord) : [];
1095
+ const derived = {};
1096
+ const call = nodes.find((node) => node["type"] === "internal:workflow-call");
1097
+ const callArgs = isRecord(call?.["args"]) ? call["args"] : void 0;
1098
+ const callState = isRecord(callArgs?.["state"]) ? callArgs["state"] : void 0;
1099
+ if (callState?.["inputSource"] === "fields" && typeof callState["workflowInputs"] === "string") {
1100
+ derived.input = workflowInputsSchema(callState["workflowInputs"]);
1101
+ }
1102
+ const sources = new Set(edges.map((edge) => edge["source"]));
1103
+ const terminals = nodes.filter((node) => !sources.has(node["id"]));
1104
+ const terminal = terminals.length === 1 ? terminals[0] : void 0;
1105
+ if (terminal && options.manifests && typeof terminal["type"] === "string") {
1106
+ const manifest = findManifest(options.manifests, terminal["type"]);
1107
+ const operationName = typeof terminal["operation_name"] === "string" ? terminal["operation_name"] : void 0;
1108
+ const operations = manifest ? operationsOf(manifest) : [];
1109
+ const operation = operationName ? operations.find((candidate) => candidate.name === operationName) : operations[0];
1110
+ if (operation?.output_fields) {
1111
+ derived.output = {
1112
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1113
+ ...fieldsSchema(operation.output_fields)
1114
+ };
1115
+ }
1116
+ }
1117
+ return derived;
1118
+ }
1119
+
1120
+ export { IR_V2_SCHEMA, IR_V2_SCHEMA_ID, WORKFLOW_DOCUMENT_FORMAT, buildExportDocument, checkScheduleSpec, deriveInterface, durationMs, envelopeSchemaVersion, executorNodeView, executorView, exportDefinition, isV2Definition, isValidJsonSchema, parseWorkflowDocument, readWorkflowDocument, serializeWorkflowDocument, validate, workflowInputsSchema };