@uipath/maestro-builder-sdk 6.18.9 → 6.19.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 (111) hide show
  1. package/dist/api-index.md +185 -184
  2. package/dist/api-members.md +504 -473
  3. package/dist/bpmn/uipath-moddle.v1.json +5 -0
  4. package/dist/case/decompile-cli.js +2 -8
  5. package/dist/check.js +177 -650
  6. package/dist/core/actions.d.ts +119 -760
  7. package/dist/core/actions.js +0 -306
  8. package/dist/core/error-port.d.ts +6 -0
  9. package/dist/core/error-port.js +9 -0
  10. package/dist/core/node-classes.d.ts +4 -63
  11. package/dist/core/node-classes.js +0 -112
  12. package/dist/core/nodes/family.d.ts +36 -0
  13. package/dist/core/nodes/family.js +1 -0
  14. package/dist/core/nodes/node-type.d.ts +136 -0
  15. package/dist/core/nodes/node-type.js +1 -0
  16. package/dist/core/nodes/registry.d.ts +57 -0
  17. package/dist/core/nodes/registry.js +122 -0
  18. package/dist/core/nodes/versioned.d.ts +50 -0
  19. package/dist/core/nodes/versioned.js +46 -0
  20. package/dist/core/typed-raw-node.d.ts +13 -62
  21. package/dist/core/typed-raw-node.js +40 -154
  22. package/dist/decompile-cli.js +2 -9
  23. package/dist/decompile.js +92 -271
  24. package/dist/flow-definitions.d.ts +10 -0
  25. package/dist/flow-definitions.js +35 -0
  26. package/dist/flow-node-registry.d.ts +13 -0
  27. package/dist/flow-node-registry.js +19 -0
  28. package/dist/flow-node-type.d.ts +192 -0
  29. package/dist/flow-node-type.js +1 -0
  30. package/dist/flow-sdk.d.ts +7 -0
  31. package/dist/flow-sdk.js +8 -0
  32. package/dist/nodes/data-fabric/flow/check.d.ts +13 -0
  33. package/dist/nodes/data-fabric/flow/check.js +164 -0
  34. package/dist/nodes/data-fabric/flow/factories.d.ts +113 -0
  35. package/dist/nodes/data-fabric/flow/factories.js +134 -0
  36. package/dist/nodes/data-fabric/flow/family.d.ts +7 -0
  37. package/dist/nodes/data-fabric/flow/family.js +9 -0
  38. package/dist/nodes/data-fabric/flow/index.d.ts +8 -0
  39. package/dist/nodes/data-fabric/flow/index.js +8 -0
  40. package/dist/nodes/data-fabric/flow/raw.d.ts +32 -0
  41. package/dist/nodes/data-fabric/flow/raw.js +100 -0
  42. package/dist/nodes/data-fabric/flow/shared.d.ts +77 -0
  43. package/dist/nodes/data-fabric/flow/shared.js +174 -0
  44. package/dist/nodes/data-fabric/flow/spec.d.ts +238 -0
  45. package/dist/nodes/data-fabric/flow/spec.js +1 -0
  46. package/dist/nodes/data-fabric/flow/verbs.d.ts +29 -0
  47. package/dist/nodes/data-fabric/flow/verbs.js +239 -0
  48. package/dist/nodes/delay/flow/check.d.ts +16 -0
  49. package/dist/nodes/delay/flow/check.js +86 -0
  50. package/dist/nodes/delay/flow/factories.d.ts +28 -0
  51. package/dist/nodes/delay/flow/factories.js +38 -0
  52. package/dist/nodes/delay/flow/family.d.ts +3 -0
  53. package/dist/nodes/delay/flow/family.js +8 -0
  54. package/dist/nodes/delay/flow/index.d.ts +3 -0
  55. package/dist/nodes/delay/flow/index.js +3 -0
  56. package/dist/nodes/delay/flow/node-type.d.ts +13 -0
  57. package/dist/nodes/delay/flow/node-type.js +71 -0
  58. package/dist/nodes/delay/flow/spec.d.ts +43 -0
  59. package/dist/nodes/delay/flow/spec.js +16 -0
  60. package/dist/nodes/document/flow/factories.d.ts +27 -0
  61. package/dist/nodes/document/flow/factories.js +30 -0
  62. package/dist/nodes/document/flow/family.d.ts +3 -0
  63. package/dist/nodes/document/flow/family.js +7 -0
  64. package/dist/nodes/document/flow/index.d.ts +3 -0
  65. package/dist/nodes/document/flow/index.js +3 -0
  66. package/dist/nodes/document/flow/node-type.d.ts +14 -0
  67. package/dist/nodes/document/flow/node-type.js +150 -0
  68. package/dist/nodes/document/flow/spec.d.ts +65 -0
  69. package/dist/nodes/document/flow/spec.js +1 -0
  70. package/dist/nodes/mock/flow/factories.d.ts +51 -0
  71. package/dist/nodes/mock/flow/factories.js +47 -0
  72. package/dist/nodes/mock/flow/family.d.ts +3 -0
  73. package/dist/nodes/mock/flow/family.js +7 -0
  74. package/dist/nodes/mock/flow/index.d.ts +2 -0
  75. package/dist/nodes/mock/flow/index.js +2 -0
  76. package/dist/nodes/mock/flow/node-type.d.ts +11 -0
  77. package/dist/nodes/mock/flow/node-type.js +51 -0
  78. package/dist/nodes/script/flow/check.d.ts +19 -0
  79. package/dist/nodes/script/flow/check.js +49 -0
  80. package/dist/nodes/script/flow/factories.d.ts +14 -0
  81. package/dist/nodes/script/flow/factories.js +15 -0
  82. package/dist/nodes/script/flow/family.d.ts +3 -0
  83. package/dist/nodes/script/flow/family.js +8 -0
  84. package/dist/nodes/script/flow/index.d.ts +3 -0
  85. package/dist/nodes/script/flow/index.js +3 -0
  86. package/dist/nodes/script/flow/node-type.d.ts +7 -0
  87. package/dist/nodes/script/flow/node-type.js +125 -0
  88. package/dist/nodes/script/flow/spec.d.ts +52 -0
  89. package/dist/nodes/script/flow/spec.js +1 -0
  90. package/dist/nodes/transform/flow/check.d.ts +6 -0
  91. package/dist/nodes/transform/flow/check.js +229 -0
  92. package/dist/nodes/transform/flow/factories.d.ts +52 -0
  93. package/dist/nodes/transform/flow/factories.js +53 -0
  94. package/dist/nodes/transform/flow/family.d.ts +3 -0
  95. package/dist/nodes/transform/flow/family.js +8 -0
  96. package/dist/nodes/transform/flow/index.d.ts +3 -0
  97. package/dist/nodes/transform/flow/index.js +3 -0
  98. package/dist/nodes/transform/flow/node-type.d.ts +7 -0
  99. package/dist/nodes/transform/flow/node-type.js +244 -0
  100. package/dist/nodes/transform/flow/spec.d.ts +116 -0
  101. package/dist/nodes/transform/flow/spec.js +10 -0
  102. package/dist/raw-node-advice.d.ts +16 -0
  103. package/dist/raw-node-advice.js +36 -0
  104. package/dist/raw-node.d.ts +71 -0
  105. package/dist/raw-node.js +116 -0
  106. package/dist/registry/uip/launch.d.ts +11 -1
  107. package/dist/registry/uip/launch.js +50 -43
  108. package/dist/registry/uip/pipeline-front-door.d.ts +2 -0
  109. package/dist/registry/uip/pipeline-front-door.js +88 -0
  110. package/dist/serialize.js +138 -492
  111. package/package.json +2 -2
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The placeholder node type, `core.logic.mock` (bundled 1.0.0 and 1.0). Moved
3
+ * from serialize.ts / check.ts / decompile.ts in Phase 4 of
4
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, codes and messages
5
+ * unchanged.
6
+ */
7
+ import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
8
+ export type MockSpec = {
9
+ kind: 'mock';
10
+ };
11
+ export declare const mockNodeType: FlowVersionedNodeType<MockSpec>;
@@ -0,0 +1,51 @@
1
+ function mockAt(version) {
2
+ return {
3
+ kind: 'mock', typeName: 'core.logic.mock', version, factory: 'mock', definition: 'bundled',
4
+ rawNode: { factory: 'mock()' },
5
+ // A placeholder is a pure definition node: the definition supplies `model`
6
+ // and the single `output` (`source: "null"`), and there is nothing else to
7
+ // write. `inputs: {}` rather than an omitted key because that is what the
8
+ // vendored Studio-Web golden writes (`shared/sample-flows/new.flow`, all
9
+ // three of its mock nodes) and what 36 of the 74 archived instances carry.
10
+ //
11
+ // Nothing here can open a fixture channel: a runner that honours one looks
12
+ // for `fixture`/`output`/`value` in the node's `inputs`, and the factory
13
+ // takes no arguments, so `inputs` is always the empty object. That
14
+ // is the #125 fence, made unrepresentable rather than documented.
15
+ serialize: () => ({ inputs: {} }),
16
+ deserialize: () => ({ kind: 'mock' }),
17
+ // A `mock()` is a PLACEHOLDER: `core.logic.mock`'s definition declares one
18
+ // output whose `source` is the literal `"null"`, so the value is null at run
19
+ // time whatever the flow does. Unlike the other no-output rules, the node
20
+ // DOES declare an output slot — which is exactly why the read is silent: it
21
+ // resolves in the designer, it resolves in `variables.nodes`, and it hands
22
+ // the downstream step a null.
23
+ //
24
+ // WHY THIS RULE LIVES IN THE AUTHORING CHECKER rather than in a pass over the
25
+ // compiled artifact (convention #191). Reading a placeholder's output is the
26
+ // ARCHIVE's dominant idiom, not a rarity: 55 of the 62 archived mock-carrying
27
+ // artifacts do it — 110 read sites, 87 of them inside a script's body and 19
28
+ // in a decision's expression — and **39 of those 55 come from runs that
29
+ // scored 1.0**. That is an author writing the downstream code for the real
30
+ // node the placeholder stands in for, and a `.flow`-level rule refusing it
31
+ // would refuse the deployed corpus (lesson 47). An SDK author is in the
32
+ // other position: they wrote `mock()` here, in this file, and the SDK can
33
+ // say at compile time that the read is dead.
34
+ // Measured 2026-08-02 over 4,033 distinct archived artifacts —
35
+ // runs/flow-sdk-cleanup-2026-08-02/step0/mock-output-read-sites.txt.
36
+ // A placeholder carries no authored values.
37
+ expressions: () => [],
38
+ output: () => ({
39
+ kind: 'none',
40
+ code: 'MOCK_HAS_NO_OUTPUT',
41
+ message: ({ node, ref, reader }) => `Placeholder "${node}" publishes nothing, but "${ref}" reads it in "${reader}". `
42
+ + 'A mock() is a marker for a node that does not exist yet — its output is null at run time, always. '
43
+ + 'Keep the placeholder while the real node does not exist, and have the reader use a value you supply instead (a flow input, as example/ClaimScaffold.flow.ts does). '
44
+ + 'Replace the placeholder only with the real node, once `registry search` finds it; a script returning fixed values is not the real node.',
45
+ }),
46
+ };
47
+ }
48
+ export const mockNodeType = {
49
+ kind: 'mock', typeName: 'core.logic.mock', factory: 'mock', defaultVersion: '1.0.0',
50
+ versions: { '1.0.0': mockAt('1.0.0'), '1.0': mockAt('1.0') },
51
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The Script family's rules. Moved verbatim from check.ts in Phase 4 of
3
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; codes and messages unchanged.
4
+ */
5
+ import type { DiagnosticCatalog } from '../../../core/nodes/family.js';
6
+ import type { FlowCheckContext } from '../../../flow-node-type.js';
7
+ export declare const SCRIPT_DIAGNOSTICS: DiagnosticCatalog;
8
+ /**
9
+ * A script step's `returns` — the author overriding what the body looks like.
10
+ *
11
+ * TypeScript already refuses a bad value at the call site, so this is the fence
12
+ * for everything that does NOT go through the type checker: a `.flow.ts` loaded
13
+ * as plain JS, a generated flow, a value read from config. Both mistakes below
14
+ * reach the emitted file silently and are then the designer's problem instead of
15
+ * the author's, which is precisely the failure this whole feature exists to undo.
16
+ */
17
+ export declare function checkScript(inputs: {
18
+ returns?: unknown;
19
+ }, ctx: FlowCheckContext): void;
@@ -0,0 +1,49 @@
1
+ export const SCRIPT_DIAGNOSTICS = {
2
+ SCRIPT_RETURNS_UNKNOWN_TYPE: { severity: 'error', summary: '`returns` names a type the wire format does not know' },
3
+ SCRIPT_EMPTY_RETURNS: { severity: 'warning', summary: '`returns: {}` names no fields' },
4
+ };
5
+ /** The type vocabulary a script's `returns` may name — what the wire format accepts. */
6
+ const SCRIPT_RETURN_TYPES = new Set(['string', 'number', 'boolean', 'object', 'array']);
7
+ /**
8
+ * A script step's `returns` — the author overriding what the body looks like.
9
+ *
10
+ * TypeScript already refuses a bad value at the call site, so this is the fence
11
+ * for everything that does NOT go through the type checker: a `.flow.ts` loaded
12
+ * as plain JS, a generated flow, a value read from config. Both mistakes below
13
+ * reach the emitted file silently and are then the designer's problem instead of
14
+ * the author's, which is precisely the failure this whole feature exists to undo.
15
+ */
16
+ export function checkScript(inputs, ctx) {
17
+ const step = ctx.step;
18
+ const returns = inputs?.returns;
19
+ if (returns === undefined)
20
+ return;
21
+ if (typeof returns === 'string') {
22
+ if (!SCRIPT_RETURN_TYPES.has(returns)) {
23
+ ctx.report('SCRIPT_RETURNS_UNKNOWN_TYPE', `Script step "${step}" declares \`returns: ${JSON.stringify(returns)}\`, which is not a type the `
24
+ + `wire format knows. Use one of ${[...SCRIPT_RETURN_TYPES].map((t) => `"${t}"`).join(', ')}, or a `
25
+ + `field map like \`{ total: "number" }\` to name an object's fields.`);
26
+ }
27
+ return;
28
+ }
29
+ if (typeof returns !== 'object' || returns === null || Array.isArray(returns)) {
30
+ ctx.report('SCRIPT_RETURNS_UNKNOWN_TYPE', `Script step "${step}"'s \`returns\` is ${JSON.stringify(returns)}. It takes either a type name or a `
31
+ + `map of field names to types (\`{ total: "number" }\`).`);
32
+ return;
33
+ }
34
+ const fields = returns;
35
+ // Same call as `APIWF_EMPTY_RETURNS` one surface over: an empty map declares
36
+ // nothing, emits nothing, and reads exactly like omitting the key — so it is
37
+ // almost always a half-finished edit rather than a statement.
38
+ if (Object.keys(fields).length === 0) {
39
+ ctx.report('SCRIPT_EMPTY_RETURNS', `Script step "${step}" declares \`returns: {}\`, which names no fields and emits nothing — the same `
40
+ + `as omitting it. Name the fields, or drop the key and let the body speak for itself.`);
41
+ return;
42
+ }
43
+ for (const [name, type] of Object.entries(fields)) {
44
+ if (typeof type !== 'string' || !SCRIPT_RETURN_TYPES.has(type)) {
45
+ ctx.report('SCRIPT_RETURNS_UNKNOWN_TYPE', `Script step "${step}" declares field "${name}" as ${JSON.stringify(type)}, which is not a type the `
46
+ + `wire format knows. Use one of ${[...SCRIPT_RETURN_TYPES].map((t) => `"${t}"`).join(', ')}.`);
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The Script factory, `script()`. Moved from core/actions.ts in Phase 4 of
3
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
4
+ */
5
+ import { type ActionSpec } from '../../../core/actions.js';
6
+ import type { ScriptInputs } from './spec.js';
7
+ /**
8
+ * Declare an inline script action.
9
+ *
10
+ * @param inputs - The JavaScript body (`code`, which must `return`) and what it
11
+ * returns (`returns`).
12
+ * @returns An action spec to pass to `.step(name, …)`.
13
+ */
14
+ export declare function script(inputs: ScriptInputs): ActionSpec;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The Script factory, `script()`. Moved from core/actions.ts in Phase 4 of
3
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
4
+ */
5
+ import { builtinActionSpec } from '../../../core/actions.js';
6
+ /**
7
+ * Declare an inline script action.
8
+ *
9
+ * @param inputs - The JavaScript body (`code`, which must `return`) and what it
10
+ * returns (`returns`).
11
+ * @returns An action spec to pass to `.step(name, …)`.
12
+ */
13
+ export function script(inputs) {
14
+ return builtinActionSpec({ kind: 'script', inputs });
15
+ }
@@ -0,0 +1,3 @@
1
+ /** The Script node family, as flow-node-registry.ts lists it: `core.action.script`. */
2
+ import type { FlowNodeFamily } from '../../../flow-node-type.js';
3
+ export declare const scriptFlowFamily: FlowNodeFamily;
@@ -0,0 +1,8 @@
1
+ import { SCRIPT_DIAGNOSTICS } from './check.js';
2
+ import { scriptNodeType } from './node-type.js';
3
+ export const scriptFlowFamily = {
4
+ id: 'script',
5
+ title: 'Script',
6
+ types: [scriptNodeType],
7
+ diagnostics: SCRIPT_DIAGNOSTICS,
8
+ };
@@ -0,0 +1,3 @@
1
+ /** The Script family's public surface, which the package entry re-exports. */
2
+ export * from './spec.js';
3
+ export * from './factories.js';
@@ -0,0 +1,3 @@
1
+ /** The Script family's public surface, which the package entry re-exports. */
2
+ export * from './spec.js';
3
+ export * from './factories.js';
@@ -0,0 +1,7 @@
1
+ import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
2
+ import type { ScriptInputs } from './spec.js';
3
+ export type ScriptSpec = {
4
+ kind: 'script';
5
+ inputs: ScriptInputs;
6
+ };
7
+ export declare const scriptNodeType: FlowVersionedNodeType<ScriptSpec>;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The Script node type, `core.action.script` (bundled 1.0.0 and 1.1). Moved from
3
+ * serialize.ts / check.ts / decompile.ts in Phase 4 of
4
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, the lift's printed
5
+ * source, codes and messages unchanged.
6
+ */
7
+ import { declaredScriptShape, inferScriptShape } from '../../../core/script-return.js';
8
+ import { jsonClone } from '../../../core/json-file.js';
9
+ import { checkScript } from './check.js';
10
+ /**
11
+ * Declare what a `core.action.script` node actually publishes.
12
+ *
13
+ * Same instance-vs-definition split as `httpOutputs` above, for the same reason
14
+ * one level over: the platform's definition says `output: {type: 'object'}`
15
+ * because a node TYPE cannot know what a particular body returns, so the
16
+ * designer types every script read as `Record<string, any>` and rejects mapping
17
+ * one into a `string` flow output — on a flow that runs. What the body returns
18
+ * is knowable here, at author time, and this is where it gets written down.
19
+ *
20
+ * `returns` wins; otherwise the body is read (see `inferScriptShape`, which
21
+ * answers only when every `return` agrees). When neither says anything the
22
+ * definition's open object stands, byte-identical to what this emitted before.
23
+ */
24
+ function scriptOutputs(base, returns, code) {
25
+ const shape = returns !== undefined ? declaredScriptShape(returns) : inferScriptShape(code);
26
+ // Nothing knowable, or knowably the open object the definition already
27
+ // declares — leave the node exactly as it was.
28
+ if (!shape || (shape.type === 'object' && !shape.properties))
29
+ return base;
30
+ if (!base || typeof base !== 'object')
31
+ return base;
32
+ const out = jsonClone(base);
33
+ if (!out.output)
34
+ return out;
35
+ out.output.type = shape.type;
36
+ if (shape.properties)
37
+ out.output.properties = shape.properties;
38
+ return out;
39
+ }
40
+ /**
41
+ * The `returns` a script step's author declared, recovered from the node, or nothing.
42
+ *
43
+ * Recovers what the author declared by DIFFERENCE: the node says what its output
44
+ * is, `inferScriptShape` says what re-compiling the body would declare on its
45
+ * own, and only a gap between the two needs writing down. Fields are recovered
46
+ * the same way — a property map the body does not produce is one the author gave
47
+ * as `returns: {…}`.
48
+ */
49
+ function liftReturns(node, code) {
50
+ const out = node.outputs?.output;
51
+ const declared = typeof out?.type === 'string' ? out.type : undefined;
52
+ if (!declared)
53
+ return undefined;
54
+ const props = out?.properties;
55
+ // What re-compiling this body with NO `returns` would declare. Mirror
56
+ // `scriptOutputs` exactly: an inference of a bare `object` (or none at all)
57
+ // leaves the definition's own `{type:'object'}` standing, untouched.
58
+ const inferred = inferScriptShape(code);
59
+ const effective = inferred && !(inferred.type === 'object' && !inferred.properties)
60
+ ? inferred
61
+ : { type: 'object', properties: undefined };
62
+ const same = effective.type === declared
63
+ && JSON.stringify(sortKeys(effective.properties ?? {})) === JSON.stringify(sortKeys(props ?? {}));
64
+ if (same)
65
+ return undefined;
66
+ // Not what the body alone would say — so the author declared it, and it has to
67
+ // be written back or the round-trip loses it. That includes declaring the OPEN
68
+ // object over a body that looks typed, which is a real thing to want.
69
+ if (props && Object.keys(props).length > 0) {
70
+ // A field map is the richer statement, so it wins when there is one.
71
+ const entries = Object.entries(props)
72
+ .filter(([, v]) => typeof v?.type === 'string')
73
+ .map(([k, v]) => [k, v.type]);
74
+ if (entries.length > 0)
75
+ return Object.fromEntries(entries);
76
+ }
77
+ return declared;
78
+ }
79
+ /** Key-sorted copy, so a property map compares by content and not by order. */
80
+ function sortKeys(o) {
81
+ return Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
82
+ }
83
+ function scriptAt(version) {
84
+ return {
85
+ kind: 'script', typeName: 'core.action.script', version, factory: 'script', definition: 'bundled',
86
+ rawNode: { factory: 'script()' },
87
+ serialize: ({ inputs }, ctx) => {
88
+ // A script BODY is a plain string, not an Expr, and it reads step outputs
89
+ // the same way everything else does — so it needs the rename too, or a
90
+ // reused child's script would read the first copy's node.
91
+ const base = ctx.definition('core.action.script', version)?.outputDefinition;
92
+ const typed = scriptOutputs(base, inputs.returns, inputs.code);
93
+ return {
94
+ inputs: { script: ctx.renameRefs(inputs.code) },
95
+ ...(typed !== base ? { outputs: typed } : {}),
96
+ };
97
+ },
98
+ deserialize: (node) => {
99
+ const raw = node.inputs?.script;
100
+ const code = typeof raw === 'string' ? raw : '';
101
+ // `returns` is recovered only when the node's declared output is something
102
+ // re-compiling the body would NOT produce on its own: serialize infers from
103
+ // the body first, so a script whose type is already plain in its code
104
+ // round-trips without it.
105
+ const returns = liftReturns(node, code);
106
+ return { kind: 'script', inputs: { code, ...(returns !== undefined ? { returns } : {}) } };
107
+ },
108
+ print: ({ inputs }, ctx) => {
109
+ const r = inputs.returns;
110
+ const returns = r === undefined ? undefined
111
+ : typeof r === 'string' ? ctx.string(r)
112
+ : `{ ${Object.entries(r).map(([k, t]) => `${ctx.key(k)}: ${ctx.string(String(t))}`).join(', ')} }`;
113
+ return { factory: 'script', options: [['code', ctx.string(inputs.code)], ['returns', returns]] };
114
+ },
115
+ check: ({ inputs }, ctx) => checkScript(inputs, ctx),
116
+ // The body is not an Expr, but the `$vars.<name>` references it contains are
117
+ // checked like any other (flow-builder-sdk#20).
118
+ expressions: ({ inputs }) => [{ code: inputs.code }],
119
+ output: () => ({ kind: 'opaque' }),
120
+ };
121
+ }
122
+ export const scriptNodeType = {
123
+ kind: 'script', typeName: 'core.action.script', factory: 'script', defaultVersion: '1.0.0',
124
+ versions: { '1.0.0': scriptAt('1.0.0'), '1.1': scriptAt('1.1') },
125
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The Script family's authoring type (`core.action.script`), and its kind's entry
3
+ * in `FlowActionSpecs`. Moved from core/actions.ts in Phase 4 of
4
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md.
5
+ */
6
+ import type { ScriptReturns } from '../../../core/script-return.js';
7
+ export interface ScriptInputs {
8
+ /** JavaScript body. Must `return` its result. Referenced via out('<step>'). */
9
+ code: string;
10
+ /**
11
+ * What the body RETURNS — the type of `out('<step>')`.
12
+ *
13
+ * The platform's definition can only say `output: {type: 'object'}` (a node
14
+ * type cannot know what a particular body returns), so the designer types
15
+ * every script read as `Record<string, any>` and reports mapping one into a
16
+ * `string` output as `Type 'Record<string, any>' is not assignable to type
17
+ * 'string'` — on a flow that is correct and runs.
18
+ *
19
+ * **Usually you do not need this.** The compiler reads the body and declares
20
+ * what it plainly returns, so `return "pending-approval"` is already a
21
+ * `string`. Reach for `returns` when the body does not say so syntactically —
22
+ * a value that comes back from a runtime read, or an object assembled
23
+ * dynamically:
24
+ *
25
+ * ```ts
26
+ * .step('policyLimit', script({
27
+ * code: 'return $vars.fetchPolicy.output.body.limit;',
28
+ * returns: 'number',
29
+ * }))
30
+ * ```
31
+ *
32
+ * For an object return, name the FIELDS instead — the spelling every other
33
+ * action's `returns` uses, and what licenses `out('<step>', '<field>')`:
34
+ *
35
+ * ```ts
36
+ * returns: { total: 'number', currency: 'string' }
37
+ * ```
38
+ *
39
+ * An explicit `returns` always wins over what the body looks like. Nothing is
40
+ * inferred when the body does not plainly agree with itself, and an unknown
41
+ * shape stays the definition's open object rather than being guessed at.
42
+ */
43
+ returns?: ScriptReturns;
44
+ }
45
+ declare module '../../../core/actions.js' {
46
+ interface FlowActionSpecs {
47
+ /** The spec {@link script} builds. */
48
+ script: {
49
+ inputs: ScriptInputs;
50
+ };
51
+ }
52
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { DiagnosticCatalog } from '../../../core/nodes/family.js';
2
+ import type { FlowCheckContext } from '../../../flow-node-type.js';
3
+ import { type TransformInputs } from './spec.js';
4
+ /** Every code the Transform rules emit; all are errors. */
5
+ export declare const TRANSFORM_DIAGNOSTICS: DiagnosticCatalog;
6
+ export declare function checkTransform(inputs: TransformInputs, ctx: FlowCheckContext): void;
@@ -0,0 +1,229 @@
1
+ /**
2
+ * The Transform family's rules. Moved verbatim from check.ts in Phase 4 of
3
+ * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; codes and messages unchanged.
4
+ */
5
+ import { Expr } from '../../../core/expr.js';
6
+ import { ownEntry } from '../../../core/own-entry.js';
7
+ import { VARIANT_OPERATION } from './spec.js';
8
+ /** Every code the Transform rules emit; all are errors. */
9
+ export const TRANSFORM_DIAGNOSTICS = {
10
+ TRANSFORM_AGG_NO_ALIAS: { severity: 'error', summary: 'an aggregation with no alias' },
11
+ TRANSFORM_AGG_NO_FIELD: { severity: 'error', summary: 'an aggregation other than count with no field' },
12
+ TRANSFORM_BAD_VOCAB: { severity: 'error', summary: 'a condition, transformation or aggregation the runtime does not know' },
13
+ TRANSFORM_COLLECTION_LITERAL: { severity: 'error', summary: 'a literal collection, which cannot be a path' },
14
+ TRANSFORM_COLLECTION_NOT_PATH: { severity: 'error', summary: 'a computed collection, which resolves to nothing deployed' },
15
+ TRANSFORM_COLLECTION_NOT_REF: { severity: 'error', summary: 'a raw value as the collection' },
16
+ TRANSFORM_COLLECTION_SCALAR: { severity: 'error', summary: 'a collection declared as a scalar, which passes through untouched' },
17
+ TRANSFORM_CONTAINS_ARRAY_VALUE: { severity: 'error', summary: 'a contains rule given an array value' },
18
+ TRANSFORM_EMPTY_FILTER: { severity: 'error', summary: 'a filter with no rules' },
19
+ TRANSFORM_EMPTY_MAP: { severity: 'error', summary: 'a map with no mappings' },
20
+ TRANSFORM_FILTER_NO_FIELD: { severity: 'error', summary: 'a filter rule with no field' },
21
+ TRANSFORM_FILTER_VALUE_EXPR: { severity: 'error', summary: 'a filter value the runtime will not evaluate' },
22
+ TRANSFORM_MAP_NO_FIELD: { severity: 'error', summary: 'a mapping with no field' },
23
+ TRANSFORM_NO_COLLECTION: { severity: 'error', summary: 'no collection' },
24
+ TRANSFORM_NO_GROUP_FIELD: { severity: 'error', summary: 'a groupBy with no field' },
25
+ TRANSFORM_NO_OPERATIONS: { severity: 'error', summary: 'no operations' },
26
+ TRANSFORM_UNKNOWN_OP: { severity: 'error', summary: 'an operation type the runtime skips' },
27
+ TRANSFORM_VARIANT_ONE_OP: { severity: 'error', summary: 'a variant with more or fewer than one operation' },
28
+ TRANSFORM_VARIANT_OP_MISMATCH: { severity: 'error', summary: 'a variant whose operation does not match it' },
29
+ TRANSFORM_VARIANT_UNKNOWN: { severity: 'error', summary: 'a variant no runtime resolves' },
30
+ };
31
+ export function checkTransform(inputs, ctx) {
32
+ const step = ctx.step;
33
+ const at = (code, message, suggestion) => ctx.report(code, message, suggestion || undefined);
34
+ // ── collection must be a bare variable path ──
35
+ const collection = inputs?.collection;
36
+ if (collection === undefined || collection === null) {
37
+ at('TRANSFORM_NO_COLLECTION', `Transform "${step}" has no collection. Point it at the variable or step output holding the array.`, `collection: v('catalog')`);
38
+ }
39
+ else if (!(collection instanceof Expr)) {
40
+ // Types are stripped at run time, so `collection: [{…}]` or
41
+ // `collection: 'catalog'` reaches here as a raw value.
42
+ at('TRANSFORM_COLLECTION_NOT_REF', `Transform "${step}" was given a ${Array.isArray(collection) ? 'raw array' : `raw ${typeof collection}`} as its `
43
+ + `collection. That field is a variable PATH — the runtime turns the string into a lookup, so a value there `
44
+ + `resolves to nothing and the transform silently sees an empty collection.`, typeof collection === 'string' ? `collection: v('${collection}')` : `collection: v('<varName>')`);
45
+ }
46
+ else if (collection.literal) {
47
+ at('TRANSFORM_COLLECTION_LITERAL', `Transform "${step}" has a literal collection. Inline data cannot be a path — keep the array in a variable `
48
+ + `default (\`.var('items', types.array, [...])\`) and reference it, or read an upstream step's output.`, `collection: v('items')`);
49
+ }
50
+ else if (!/^\$vars\.[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(collection.js.trim())) {
51
+ // A computed expression (js`…`, tmpl`…`, `.concat(…)`) is the trap that
52
+ // survives every local rung: a local run resolves it to a value, so
53
+ // it runs correctly there and resolves to NOTHING once deployed, where the
54
+ // string is used as a lookup path.
55
+ at('TRANSFORM_COLLECTION_NOT_PATH', `Transform "${step}"'s collection must be a plain reference, but it is the expression `
56
+ + `"${collection.js.trim().slice(0, 60)}". The deployed runtime uses that string as a lookup PATH, so a `
57
+ + `computed expression resolves to nothing there even though it runs locally. Compute it in a script step `
58
+ + `first, then point the transform at that step's output.`, `collection: out('<scriptStep>')`);
59
+ }
60
+ else {
61
+ // A collection that is not an array is PASSED THROUGH untouched — no error,
62
+ // no operations applied (faithful to v1). The result is a transform that
63
+ // looks like it ran and changed nothing.
64
+ // Only a DECLARED scalar can be judged here: a step output's type is not
65
+ // known offline, so this stays quiet about those rather than guessing.
66
+ const declared = ctx.declaredRef(collection.js);
67
+ const refName = declared?.name;
68
+ const declaredType = declared?.type;
69
+ if (declaredType && ['string', 'number', 'boolean'].includes(declaredType)) {
70
+ at('TRANSFORM_COLLECTION_SCALAR', `Transform "${step}"'s collection is "${refName}", declared as ${declaredType}. A transform runs its `
71
+ + `operations over an ARRAY; anything else is passed through untouched — no error, nothing applied — so `
72
+ + `the step would look like it ran and change nothing.`, `Declare it as types.array, or point the collection at the array field: collection: out('<step>', '<field>')`);
73
+ }
74
+ }
75
+ // ── the operation chain ──
76
+ const ops = inputs?.operations;
77
+ if (!Array.isArray(ops) || ops.length === 0) {
78
+ at('TRANSFORM_NO_OPERATIONS', `Transform "${step}" has no operations, so it passes its collection straight through. Add at least one `
79
+ + `filter / map / groupBy operation, or drop the node.`, `operations: [{ type: 'filter', filters: [{ field: 'status', condition: 'equals', value: 'open' }] }]`);
80
+ return;
81
+ }
82
+ checkVariant(step, inputs, ops, at);
83
+ ops.forEach((op, i) => {
84
+ const where = `operations[${i}]`;
85
+ // Read the discriminant before the narrowing below exhausts the union — at
86
+ // run time it can be anything (an author typo survives type erasure).
87
+ const kind = op?.type;
88
+ if (op?.type === 'filter') {
89
+ if (!Array.isArray(op.filters) || op.filters.length === 0) {
90
+ at('TRANSFORM_EMPTY_FILTER', `Transform "${step}" ${where} is a filter with no rules — the runtime passes every item through it.`);
91
+ return;
92
+ }
93
+ for (const [j, f] of op.filters.entries()) {
94
+ if (!f?.field && f?.field !== '')
95
+ at('TRANSFORM_FILTER_NO_FIELD', `Transform "${step}" ${where}.filters[${j}] has no field, so the rule is dropped and the filter `
96
+ + `matches everything.`);
97
+ checkVocab(step, `${where}.filters[${j}].condition`, f?.condition, FILTER_CONDITIONS, 'filter condition', 'passes every item through the rule', ctx);
98
+ if (typeof f?.value === 'string' && LOOKS_LIKE_EXPRESSION.test(f.value.trim()))
99
+ at('TRANSFORM_FILTER_VALUE_EXPR', `Transform "${step}" ${where}.filters[${j}] compares against ${JSON.stringify(f.value)}. Filter values `
100
+ + `are LITERAL only — the runtime evaluates nothing here, so every item would be compared against that `
101
+ + `text and the filter would return nothing. Use a literal, or do the comparison in a script step.`, `value: 500`);
102
+ // `contains` is TWO operators wearing one name — the runtime does
103
+ // `Array.includes` when the FIELD holds an array and `String.includes`
104
+ // otherwise, faithful to v1. Which
105
+ // one runs is decided by the data, so nothing static can pick for you —
106
+ // but an ARRAY on the compare side is wrong under both readings
107
+ // (`['a','b']` is stringified to `a,b` for the substring test, and
108
+ // `includes` looks for the array itself as one element), and it is the
109
+ // shape an author reaches for when they mean "any of these".
110
+ if ((f?.condition === 'contains' || f?.condition === 'not_contains') && Array.isArray(f?.value))
111
+ at('TRANSFORM_CONTAINS_ARRAY_VALUE', `Transform "${step}" ${where}.filters[${j}] uses "${f.condition}" against an ARRAY. "contains" compares `
112
+ + `ONE value: if the item's field holds an array it tests membership of that value, otherwise it tests `
113
+ + `for that value as a substring — either way an array on the right matches nothing, silently. For `
114
+ + `"any of these", use one rule per value with the filter's "or" operation.`, `condition: 'contains', value: ${JSON.stringify(f.value[0] ?? 'x')}`);
115
+ }
116
+ return;
117
+ }
118
+ if (op?.type === 'map') {
119
+ if (!Array.isArray(op.mappings) || op.mappings.length === 0) {
120
+ at('TRANSFORM_EMPTY_MAP', `Transform "${step}" ${where} is a map with no mappings — with keepOriginalFields off that turns every `
121
+ + `item into {}.`);
122
+ return;
123
+ }
124
+ for (const [j, m] of op.mappings.entries()) {
125
+ if (!m?.field)
126
+ at('TRANSFORM_MAP_NO_FIELD', `Transform "${step}" ${where}.mappings[${j}] has no field, so nothing is carried into the output.`);
127
+ // Omitted is fine — it defaults to `copy`.
128
+ if (m?.transformation !== undefined)
129
+ checkVocab(step, `${where}.mappings[${j}].transformation`, m.transformation, TRANSFORMATIONS, 'transformation', 'copies the value unchanged', ctx);
130
+ }
131
+ return;
132
+ }
133
+ if (op?.type === 'groupBy') {
134
+ if (!op.groupByField)
135
+ at('TRANSFORM_NO_GROUP_FIELD', `Transform "${step}" ${where} is a groupBy with no groupByField, so every item lands in one anonymous `
136
+ + `group.`);
137
+ for (const [j, a] of (op.aggregations ?? []).entries()) {
138
+ if (!a?.alias?.trim())
139
+ at('TRANSFORM_AGG_NO_ALIAS', `Transform "${step}" ${where}.aggregations[${j}] has no alias. The runtime drops an unaliased `
140
+ + `aggregation, so its result would simply be absent from the output.`);
141
+ if (a?.operation !== 'count' && !a?.field?.trim())
142
+ at('TRANSFORM_AGG_NO_FIELD', `Transform "${step}" ${where}.aggregations[${j}] is "${a?.operation}" with no field. Only "count" `
143
+ + `works without one; the runtime drops the rest, so the aggregate would be missing from the output.`);
144
+ checkVocab(step, `${where}.aggregations[${j}].operation`, a?.operation, AGGREGATION_OPERATIONS, 'aggregation operation', 'yields null for that aggregate', ctx);
145
+ }
146
+ return;
147
+ }
148
+ at('TRANSFORM_UNKNOWN_OP', `Transform "${step}" ${where} has type ${JSON.stringify(kind)}. The runtime skips an operation it does `
149
+ + `not recognize, so the collection would pass through unchanged. Use 'filter', 'map', or 'groupBy'.`);
150
+ });
151
+ }
152
+ /**
153
+ * Diagnose the `variant` discriminator on a transform step.
154
+ *
155
+ * A variant emits a single-purpose node type (`core.action.transform.filter`,
156
+ * `.map`, `.group-by`) instead of the generic one. The platform runs all four
157
+ * node types through the SAME script generators and dispatches on the
158
+ * OPERATION's type, never the node's — so a mismatch here does not fault and
159
+ * does not change the answer. It produces a node that lies: a `.filter` node
160
+ * that actually maps, whose operation the designer's filter editor cannot even
161
+ * show. That is exactly the class this SDK catches at rung 1, because no rung
162
+ * below it ever will.
163
+ */
164
+ function checkVariant(step, inputs, ops, at) {
165
+ const variant = inputs?.variant;
166
+ if (variant === undefined || variant === null)
167
+ return; // generic — chains freely
168
+ const variants = Object.keys(VARIANT_OPERATION);
169
+ if (typeof variant !== 'string' || !variants.includes(variant)) {
170
+ at('TRANSFORM_VARIANT_UNKNOWN', `Transform "${step}" has variant ${JSON.stringify(variant)}, which is not a Transform node type. `
171
+ + `The emitted node would be "core.action.transform.${String(variant)}", which no runtime resolves. `
172
+ + `Valid: ${variants.join(', ')} — or omit \`variant\` for the generic chaining Transform.`, nearestIn(variant, variants));
173
+ return;
174
+ }
175
+ const expected = ownEntry(VARIANT_OPERATION, variant);
176
+ if (ops.length !== 1) {
177
+ at('TRANSFORM_VARIANT_ONE_OP', `Transform "${step}" is the "${variant}" variant but carries ${ops.length} operations. A variant node runs `
178
+ + `exactly one — the designer's ${variant} editor shows a single ${expected} operation and nothing else. `
179
+ + `Drop \`variant\` to chain them in one generic Transform, or split them across one node per step.`);
180
+ return;
181
+ }
182
+ const got = ops[0]?.type;
183
+ if (got !== expected) {
184
+ at('TRANSFORM_VARIANT_OP_MISMATCH', `Transform "${step}" is the "${variant}" variant but its operation is ${JSON.stringify(got)}, not `
185
+ + `"${expected}". The node type does not drive the runtime — the OPERATION does — so this would emit a `
186
+ + `node labelled "${variant}" that actually does ${JSON.stringify(got)}, and the designer's ${variant} `
187
+ + `editor could not show it. Match them, or drop \`variant\` for the generic Transform.`, typeof got === 'string' && Object.values(VARIANT_OPERATION).includes(got)
188
+ ? `variant: '${variants.find((v) => ownEntry(VARIANT_OPERATION, v) === got)}'`
189
+ : `type: '${expected}'`);
190
+ }
191
+ }
192
+ /** Nearest member of a closed vocabulary, for a "did you mean" suggestion. */
193
+ function nearestIn(value, vocab) {
194
+ const v = typeof value === 'string' ? value.toLowerCase() : '';
195
+ return vocab.find((c) => c.replace(/[-_]/g, '') === v.replace(/[^a-z]/g, ''))
196
+ ?? vocab.find((c) => c.startsWith(v) || (v !== '' && v.startsWith(c)));
197
+ }
198
+ /** A string that is an expression the transform runtime will NOT evaluate. */
199
+ const LOOKS_LIKE_EXPRESSION = /^(=js:|\$vars\.|\{\s*\$vars\.)/;
200
+ // The three closed vocabularies a transform operation carries. TypeScript types
201
+ // them, but an authored `.flow.ts` runs with types ERASED — so `condition:
202
+ // 'greater'` reaches the runtime, whose generated script falls through on a name
203
+ // it does not know and is silent about it:
204
+ // condition → the rule passes every item
205
+ // transformation → the value is copied unchanged
206
+ // aggregation → the aggregate is null
207
+ // Green at every rung, wrong answer. Checked here so the author hears it at
208
+ // rung 1 — a flow that never went through this SDK gets no such warning.
209
+ const FILTER_CONDITIONS = [
210
+ 'equals', 'not_equals',
211
+ 'greater_than', 'less_than', 'greater_equal', 'less_equal',
212
+ 'contains', 'not_contains',
213
+ 'starts_with', 'not_starts_with',
214
+ 'ends_with', 'not_ends_with',
215
+ 'matches_regex',
216
+ 'in', 'not_in',
217
+ 'is_null', 'is_not_null',
218
+ 'is_empty', 'is_not_empty',
219
+ ];
220
+ const TRANSFORMATIONS = ['copy', 'uppercase', 'lowercase', 'trim'];
221
+ const AGGREGATION_OPERATIONS = ['count', 'sum', 'average', 'min', 'max', 'first', 'last', 'collect'];
222
+ /** Flag a value that is not a member of a closed vocabulary, suggesting the nearest. */
223
+ function checkVocab(step, at, value, vocab, what, effect, ctx) {
224
+ if (typeof value === 'string' && vocab.includes(value))
225
+ return;
226
+ const near = nearestIn(value, vocab);
227
+ ctx.report('TRANSFORM_BAD_VOCAB', `Transform "${step}" ${at} is ${JSON.stringify(value)}, which is not a valid ${what}. The runtime does not `
228
+ + `recognize it and ${effect}, so the result would be silently wrong. Valid: ${vocab.join(', ')}.`, near || undefined);
229
+ }