@uipath/maestro-builder-sdk 6.56.0 → 6.58.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.
- package/dist/api-index.md +6 -2
- package/dist/api-members.md +1 -1
- package/dist/case/subscription.d.ts +4 -2
- package/dist/case/subscription.js +5 -2
- package/dist/cli/commands/flow.js +2 -0
- package/dist/core/actions.js +2 -2
- package/dist/core/binding-messages.d.ts +2 -1
- package/dist/core/binding-messages.js +3 -4
- package/dist/core/byoa.d.ts +3 -2
- package/dist/core/byoa.js +3 -3
- package/dist/core/carried-definitions.d.ts +0 -18
- package/dist/core/carried-definitions.js +3 -2
- package/dist/core/ceql.d.ts +2 -1
- package/dist/core/ceql.js +3 -2
- package/dist/core/compile-refusal.d.ts +21 -0
- package/dist/core/compile-refusal.js +26 -0
- package/dist/core/connector-inputs.js +2 -1
- package/dist/core/connector-resolve-error.d.ts +2 -1
- package/dist/core/connector-resolve-error.js +3 -4
- package/dist/core/connectors.js +2 -1
- package/dist/core/event-filters.js +2 -1
- package/dist/core/hitl-routing.d.ts +7 -82
- package/dist/core/hitl-routing.js +95 -6
- package/dist/core/library.js +4 -3
- package/dist/core/lookups.js +6 -5
- package/dist/core/nodes/registry.js +3 -2
- package/dist/core/nodes/versioned.js +3 -2
- package/dist/core/process-resource-sort-order.js +1 -1
- package/dist/core/read-text.d.ts +0 -28
- package/dist/core/read-text.js +3 -2
- package/dist/core/refusals.d.ts +465 -0
- package/dist/core/refusals.js +504 -0
- package/dist/core/script-function.d.ts +126 -0
- package/dist/core/script-function.js +511 -0
- package/dist/core/typescript-module.js +2 -1
- package/dist/decompile-cli.js +11 -3
- package/dist/decompile.d.ts +9 -0
- package/dist/decompile.js +76 -3
- package/dist/flow-expr-check.d.ts +2 -1
- package/dist/flow-expr-check.js +4 -2
- package/dist/flow-node-type.d.ts +21 -0
- package/dist/flow-sdk.js +17 -16
- package/dist/flow-step-tree.js +1 -1
- package/dist/flow-variables.js +3 -2
- package/dist/load.js +19 -5
- package/dist/nodes/agent/flow/inline-agent.js +7 -6
- package/dist/nodes/connector/flow/node-type.js +2 -1
- package/dist/nodes/data-fabric/flow/factories.js +2 -1
- package/dist/nodes/data-fabric/flow/verbs.js +2 -1
- package/dist/nodes/delay/flow/factories.js +2 -1
- package/dist/nodes/document/flow/ixp.js +5 -1
- package/dist/nodes/hitl/flow/check.js +4 -2
- package/dist/nodes/hitl/flow/node-type.js +6 -5
- package/dist/nodes/http/flow/factories.js +2 -1
- package/dist/nodes/http/flow/node-type.js +6 -5
- package/dist/nodes/process/flow/factories.js +2 -1
- package/dist/nodes/queue/flow/node-type.js +3 -2
- package/dist/nodes/script/flow/factories.d.ts +42 -1
- package/dist/nodes/script/flow/factories.js +8 -9
- package/dist/nodes/script/flow/node-type.js +71 -0
- package/dist/nodes/script/flow/runtime.d.ts +20 -0
- package/dist/nodes/script/flow/runtime.js +29 -0
- package/dist/nodes/script/flow/spec.d.ts +32 -1
- package/dist/nodes/transform/flow/node-type.js +2 -1
- package/dist/raw-node.js +3 -2
- package/dist/ref-resolve.d.ts +0 -36
- package/dist/ref-resolve.js +9 -8
- package/dist/schema-version.d.ts +0 -38
- package/dist/schema-version.js +3 -2
- package/dist/script-function-scope.d.ts +57 -0
- package/dist/script-function-scope.js +203 -0
- package/dist/sdk-resolve.js +2 -1
- package/dist/serialize.d.ts +5 -2
- package/dist/serialize.js +115 -83
- package/package.json +2 -2
|
@@ -20,6 +20,7 @@ import { jsonClone } from '../../../core/json-clone.js';
|
|
|
20
20
|
import { schemaId } from '../../../core/stable-id.js';
|
|
21
21
|
import { wireExprBody } from '../../../core/wire-expr.js';
|
|
22
22
|
import { checkHitl, checkHitlRead } from './check.js';
|
|
23
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
23
24
|
const BASE = 'uipath.human-in-the-loop';
|
|
24
25
|
const QUICK_FORM = 'uipath.human-in-the-loop.quick-form';
|
|
25
26
|
const ACTION_APP = 'uipath.human-in-the-loop.coded-action-app';
|
|
@@ -556,23 +557,23 @@ function hitlAt(typeName, version, factory) {
|
|
|
556
557
|
const pinned = ctx.pinnedVersion;
|
|
557
558
|
const exitModel = hitlExitModel(inputs, pinned);
|
|
558
559
|
if (typeName !== BASE && inputs?.exposeError === true) {
|
|
559
|
-
throw new
|
|
560
|
+
throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": exposeError needs the base human task's 1.2 definition, which adds the `
|
|
560
561
|
+ `error output — the '${inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
|
|
561
562
|
}
|
|
562
563
|
if (inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(typeName)) {
|
|
563
|
-
throw new
|
|
564
|
+
throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": outcomePorts: true, but the '${inputs?.variant}' variant reads its outcomes `
|
|
564
565
|
+ `from its app and leaves on its single 'completed' exit. Route on out('${ctx.step}', 'Action') downstream.`);
|
|
565
566
|
}
|
|
566
567
|
if (inputs?.legacyCompleted === true && (inputs?.outcomePorts !== undefined || inputs?.exposeError === true)) {
|
|
567
|
-
throw new
|
|
568
|
+
throw new CompileRefusal('HITL_LEGACY_COMPLETED_CONFLICT', `"${ctx.step}": legacyCompleted: true leaves the task on its single 'completed' handle, so it cannot also `
|
|
568
569
|
+ `${inputs?.exposeError === true ? 'expose the error output (exposeError)' : 'set outcomePorts'}. Drop one.`);
|
|
569
570
|
}
|
|
570
571
|
if (inputs?.legacyCompleted === true && typeName === BASE && (pinned === '1.1' || pinned === '1.2')) {
|
|
571
|
-
throw new
|
|
572
|
+
throw new CompileRefusal('HITL_LEGACY_COMPLETED_VERSION', `"${ctx.step}": legacyCompleted: true, but { version: '${pinned}' } pins the task's per-outcome definition, `
|
|
572
573
|
+ `which has no 'completed' handle (flow validate refuses the edge). Drop the pin or legacyCompleted.`);
|
|
573
574
|
}
|
|
574
575
|
if (inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
|
|
575
|
-
throw new
|
|
576
|
+
throw new CompileRefusal('HITL_EXPOSE_ERROR_VERSION', `"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
|
|
576
577
|
+ `Drop the pin or match it.`);
|
|
577
578
|
}
|
|
578
579
|
const { inputs: nodeInputs, outputs } = hitlInputs(inputs, typeName, ctx);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
|
|
4
4
|
*/
|
|
5
5
|
import { builtinActionSpec } from '../../../core/actions.js';
|
|
6
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
6
7
|
/**
|
|
7
8
|
* Declare an HTTP request action, explicitly choosing standalone or managed.
|
|
8
9
|
*
|
|
@@ -12,7 +13,7 @@ import { builtinActionSpec } from '../../../core/actions.js';
|
|
|
12
13
|
*/
|
|
13
14
|
export function http(inputs) {
|
|
14
15
|
if (typeof inputs.managed !== 'boolean') {
|
|
15
|
-
throw new
|
|
16
|
+
throw new CompileRefusal('HTTP_MANAGED_REQUIRED', 'http() requires managed: true or managed: false because they emit different platform nodes. '
|
|
16
17
|
+ 'Use true for Managed HTTP Request/core.action.http.v2 or when non-2xx statuses must reach .onError(); '
|
|
17
18
|
+ 'use false for the standalone core.action.http node.');
|
|
18
19
|
}
|
|
@@ -12,6 +12,7 @@ import { MANAGED_HTTP_ACTIVITY_TYPE_ID, MANAGED_HTTP_ESSENTIAL_CONFIGURATION, MA
|
|
|
12
12
|
import { isRecord } from '../../../core/node-envelope.js';
|
|
13
13
|
import { wireExprBody } from '../../../core/wire-expr.js';
|
|
14
14
|
import { checkHttpRead, checkHttpStep, plainHttpOnErrorMessage } from './check.js';
|
|
15
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
15
16
|
const PLAIN = 'core.action.http';
|
|
16
17
|
const MANAGED = 'core.action.http.v2';
|
|
17
18
|
/** The definition's `inputDefaults` at the version being emitted. */
|
|
@@ -67,7 +68,7 @@ function plainHttpInputs(i, ctx) {
|
|
|
67
68
|
if (i.connection !== undefined
|
|
68
69
|
|| i.folder !== undefined
|
|
69
70
|
|| i.targetConnector !== undefined) {
|
|
70
|
-
throw new
|
|
71
|
+
throw new CompileRefusal('HTTP_BINDING_REQUIRES_MANAGED', 'standalone http cannot use Integration Service connection settings; set managed: true');
|
|
71
72
|
}
|
|
72
73
|
return {
|
|
73
74
|
...defaults(ctx, PLAIN),
|
|
@@ -91,7 +92,7 @@ function managedHttpBinding(i, ctx) {
|
|
|
91
92
|
return undefined;
|
|
92
93
|
if (typeof i.connection !== 'string' || i.connection.trim() === ''
|
|
93
94
|
|| typeof i.folder !== 'string' || i.folder.trim() === '') {
|
|
94
|
-
throw new
|
|
95
|
+
throw new CompileRefusal('HTTP_MANAGED_BINDING_INCOMPLETE', 'managed http connection mode requires both a non-empty connection and folder');
|
|
95
96
|
}
|
|
96
97
|
// The same resolution a connector step gets: a self-referential binding is an
|
|
97
98
|
// error, a stub is a warning.
|
|
@@ -121,11 +122,11 @@ function managedHttpBinding(i, ctx) {
|
|
|
121
122
|
*/
|
|
122
123
|
function managedHttpInputs(i, binding, ctx) {
|
|
123
124
|
if (i.targetConnector !== undefined && !binding) {
|
|
124
|
-
throw new
|
|
125
|
+
throw new CompileRefusal('HTTP_TARGET_CONNECTOR_REQUIRES_BINDING', 'managed http targetConnector requires both a non-empty connection and folder');
|
|
125
126
|
}
|
|
126
127
|
if (i.targetConnector !== undefined
|
|
127
128
|
&& (typeof i.targetConnector !== 'string' || i.targetConnector.trim() === '')) {
|
|
128
|
-
throw new
|
|
129
|
+
throw new CompileRefusal('HTTP_TARGET_CONNECTOR_EMPTY', 'managed http targetConnector must be a non-empty connector key');
|
|
129
130
|
}
|
|
130
131
|
const url = ctx.render(i.url);
|
|
131
132
|
const bodyParameters = {
|
|
@@ -270,7 +271,7 @@ function liftHttp(node, ctx, managed) {
|
|
|
270
271
|
const connectionValue = detail.connectionId ?? detail.connectionResourceId ?? req.connection;
|
|
271
272
|
const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? Number(inputs.retryCount) : undefined;
|
|
272
273
|
if (retry !== undefined && !Number.isFinite(retry)) {
|
|
273
|
-
throw new
|
|
274
|
+
throw new CompileRefusal('DECOMPILE_NOT_A_NUMBER', `decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
|
|
274
275
|
}
|
|
275
276
|
const branchRows = Array.isArray(inputs.branches) ? inputs.branches : [];
|
|
276
277
|
const returns = liftReturns(node) ?? returnsFromReads(ctx.reads());
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
|
|
5
5
|
*/
|
|
6
6
|
import { builtinActionSpec } from '../../../core/actions.js';
|
|
7
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
7
8
|
/**
|
|
8
9
|
* Invoke a deployed RPA (robotic process automation) workflow — a classic
|
|
9
10
|
* UI-automation process, published to Orchestrator, run by a robot.
|
|
@@ -166,7 +167,7 @@ export function agenticProcess(inputs) {
|
|
|
166
167
|
// code — and silently dropping the returns would validate a read that can
|
|
167
168
|
// never be satisfied.
|
|
168
169
|
if (inputs.completion === 'fire-and-forget' && inputs.returns !== undefined) {
|
|
169
|
-
throw new
|
|
170
|
+
throw new CompileRefusal('FIRE_AND_FORGET_RETURNS', "agenticProcess(): completion 'fire-and-forget' publishes no output — remove `returns`, "
|
|
170
171
|
+ "or use completion: 'wait' to read the process result.");
|
|
171
172
|
}
|
|
172
173
|
return builtinActionSpec({ kind: 'agenticProcess', inputs });
|
|
@@ -3,6 +3,7 @@ import { jsonClone } from '../../../core/json-clone.js';
|
|
|
3
3
|
import { isRecord } from '../../../core/node-envelope.js';
|
|
4
4
|
import { checkQueueItem, itemDataIsString, queueItemExprMessage } from './check.js';
|
|
5
5
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './record.js';
|
|
6
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
6
7
|
const CREATE = 'core.action.queue.create';
|
|
7
8
|
const CREATE_AND_WAIT = 'core.action.queue.create-and-wait';
|
|
8
9
|
/**
|
|
@@ -39,7 +40,7 @@ function queueItemDef(spec, nodeType, stepName, ids, base) {
|
|
|
39
40
|
// The template at the version being emitted: the registry resolved it exactly
|
|
40
41
|
// (an unregistered pin is NODE_VERSION_UNREGISTERED before this runs).
|
|
41
42
|
if (!base)
|
|
42
|
-
throw new Error(`no bundled definition for "${nodeType}"`);
|
|
43
|
+
throw new Error(`internal SDK error: no bundled definition for "${nodeType}"`);
|
|
43
44
|
const def = jsonClone(base);
|
|
44
45
|
def.model.bindings.resourceKey = spec.key;
|
|
45
46
|
def.model.context = [
|
|
@@ -172,7 +173,7 @@ function itemDataString(item, step, typeName, version) {
|
|
|
172
173
|
return '';
|
|
173
174
|
for (const [k, v] of rows) {
|
|
174
175
|
if (v instanceof Expr)
|
|
175
|
-
throw new
|
|
176
|
+
throw new CompileRefusal('QUEUE_ITEM_EXPR_UNSUPPORTED', queueItemExprMessage(step, typeName, version, k));
|
|
176
177
|
}
|
|
177
178
|
return JSON.stringify(item);
|
|
178
179
|
}
|
|
@@ -3,7 +3,48 @@
|
|
|
3
3
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
|
|
4
4
|
*/
|
|
5
5
|
import { type ActionSpec } from '../../../core/actions.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { ScriptReturns } from '../../../core/script-return.js';
|
|
7
|
+
import type { ScriptInputs, ScriptValue } from './spec.js';
|
|
8
|
+
/**
|
|
9
|
+
* Declare an inline script action, as a TypeScript function the compiler checks.
|
|
10
|
+
*
|
|
11
|
+
* The function is never called. Its body, exactly as written, becomes the
|
|
12
|
+
* script; its parameter is an authoring-time name for the Flow runtime's own
|
|
13
|
+
* `$vars` global and is renamed to it. `returns` is the declaration that reaches
|
|
14
|
+
* the `.flow`, and tsc checks the function's return against it.
|
|
15
|
+
*
|
|
16
|
+
* The function may read only what the script runtime has (the ES2020 built-ins
|
|
17
|
+
* and `console`): a module constant, an import or a library throws here, because
|
|
18
|
+
* in the run it would be a `ReferenceError`.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* .step('post', script(parseMessage, { returns: { channel: 'string', threadTs: 'string' } }))
|
|
22
|
+
*
|
|
23
|
+
* function parseMessage(vars: FlowVars) {
|
|
24
|
+
* const m = /archives\/([A-Z0-9]+)\/p(\d{10})(\d{6})/.exec(String(vars.start.output.message));
|
|
25
|
+
* if (!m) throw new Error('not a Slack permalink');
|
|
26
|
+
* return { channel: m[1], threadTs: m[2] + '.' + m[3] };
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @param fn - The script, as a function of the variable scope (or of nothing).
|
|
31
|
+
* @param options - What the function returns (`returns`), as `script({ returns })` takes it.
|
|
32
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function script<V, const R extends ScriptReturns>(fn: (vars: V) => ScriptValue<R>, options: {
|
|
35
|
+
returns: R;
|
|
36
|
+
}): ActionSpec;
|
|
37
|
+
/**
|
|
38
|
+
* Declare an inline script action, as a TypeScript function, with no declared
|
|
39
|
+
* return: the body's type is inferred as it is for a string body.
|
|
40
|
+
*
|
|
41
|
+
* @param fn - The script, as a function of the variable scope (or of nothing).
|
|
42
|
+
* @param options - No `returns`; pass one to have tsc check the function against it.
|
|
43
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function script<V>(fn: (vars: V) => unknown, options?: {
|
|
46
|
+
returns?: never;
|
|
47
|
+
}): ActionSpec;
|
|
7
48
|
/**
|
|
8
49
|
* Declare an inline script action.
|
|
9
50
|
*
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
|
|
4
4
|
*/
|
|
5
5
|
import { builtinActionSpec } from '../../../core/actions.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return builtinActionSpec({ kind: 'script', inputs });
|
|
6
|
+
import { lowerScriptFunction } from '../../../core/script-function.js';
|
|
7
|
+
import { FLOW_SCRIPT_RUNTIME } from './runtime.js';
|
|
8
|
+
export function script(arg, options) {
|
|
9
|
+
if (typeof arg !== 'function')
|
|
10
|
+
return builtinActionSpec({ kind: 'script', inputs: arg });
|
|
11
|
+
const code = lowerScriptFunction(arg, FLOW_SCRIPT_RUNTIME, `script(${arg.name || 'function'})`);
|
|
12
|
+
const returns = options?.returns;
|
|
13
|
+
return builtinActionSpec({ kind: 'script', inputs: { code, ...(returns !== undefined ? { returns } : {}) } });
|
|
15
14
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { declaredScriptShape, inferScriptShape } from '../../../core/script-return.js';
|
|
8
8
|
import { jsonClone } from '../../../core/json-clone.js';
|
|
9
|
+
import { scriptBodyEarlyError, scriptBodySyntaxError, scriptBodyTypeScriptSyntax, tryLowerScriptFunction } from '../../../core/script-function.js';
|
|
10
|
+
import { FLOW_SCRIPT_RUNTIME } from './runtime.js';
|
|
9
11
|
import { checkScript } from './check.js';
|
|
10
12
|
/**
|
|
11
13
|
* Declare what a `core.action.script` node actually publishes.
|
|
@@ -83,6 +85,72 @@ function liftReturns(node, code, definitionType) {
|
|
|
83
85
|
}
|
|
84
86
|
return declared;
|
|
85
87
|
}
|
|
88
|
+
/** The fallback reason for a body that parses alone but not as the printed function, or comes back changed. */
|
|
89
|
+
const UNCHANGED = 'its text would not come back unchanged as a function body (a line comment on its last line, for one).';
|
|
90
|
+
/**
|
|
91
|
+
* The function a script step prints as, declared after the flow, or undefined
|
|
92
|
+
* when the step keeps the string form (`script({ code })`), with a warning.
|
|
93
|
+
*
|
|
94
|
+
* A designer-edited flow may hold a half-written script, and decompile still has
|
|
95
|
+
* to produce a source that builds, so a body that cannot become a function is a
|
|
96
|
+
* reason to keep the string, never to fail. It becomes one only when all of
|
|
97
|
+
* these hold (docs/SCRIPT_FUNCTION_DESIGN.md, "Decompile"):
|
|
98
|
+
*
|
|
99
|
+
* 1. it parses;
|
|
100
|
+
* 2. it has no TypeScript-only syntax, which the loader would erase or reread
|
|
101
|
+
* (`const n: number`, or `f<T>(x)`, which JavaScript reads as comparisons);
|
|
102
|
+
* 3. `script(fn)` would accept it: it reads only names on the runtime's list
|
|
103
|
+
* (a function that failed this would throw when the source loads);
|
|
104
|
+
* 4. it comes back from compile byte for byte. The function is printed with
|
|
105
|
+
* the body verbatim between its braces, which lowering returns exactly, so
|
|
106
|
+
* this holds for nearly every body that passes the others; it is checked
|
|
107
|
+
* because it is cheap, and a line comment on the last line is the case that
|
|
108
|
+
* fails it (it would swallow the closing brace);
|
|
109
|
+
* 5. the engine compiles it as a strict function of `$vars`: an early error
|
|
110
|
+
* (a duplicate `let`, a `const $vars`, an octal literal) that TypeScript's
|
|
111
|
+
* parser does not report would make the whole module unloadable.
|
|
112
|
+
*
|
|
113
|
+
* The parameter is named `$vars`, so nothing is renamed and the body's text is
|
|
114
|
+
* the node's text.
|
|
115
|
+
*/
|
|
116
|
+
function printAsFunction(code, ctx) {
|
|
117
|
+
if (ctx.scripts !== 'function')
|
|
118
|
+
return undefined;
|
|
119
|
+
const keep = (why) => {
|
|
120
|
+
ctx.warn(`script kept as a string: ${why} Fix the script and decompile again to get a typed function.`);
|
|
121
|
+
return undefined;
|
|
122
|
+
};
|
|
123
|
+
const syntax = scriptBodySyntaxError(code);
|
|
124
|
+
if (syntax)
|
|
125
|
+
return keep(`its body does not parse (${syntax}).`);
|
|
126
|
+
const typed = scriptBodyTypeScriptSyntax(code);
|
|
127
|
+
if (typed)
|
|
128
|
+
return keep(`it contains ${typed}, which a TypeScript loader would erase or reread, changing the script.`);
|
|
129
|
+
const name = ctx.functionName();
|
|
130
|
+
// With authoring's own rules: the printed function is lowered by `script(fn)`
|
|
131
|
+
// when the source is compiled back, so a body that rule refuses (`$vars?.x`,
|
|
132
|
+
// `$vars['x']`, an alias) must stay a string here, or compile would refuse it.
|
|
133
|
+
const lowered = tryLowerScriptFunction(`function ${name}($vars) {${code}}`, FLOW_SCRIPT_RUNTIME, { scopeReadsOnly: true });
|
|
134
|
+
if (!lowered.ok) {
|
|
135
|
+
// Worded as the SDK's list, not as a fault: whether a string body that reads
|
|
136
|
+
// the name really fails in the run is what #1411 has yet to establish.
|
|
137
|
+
if (lowered.free.length > 0) {
|
|
138
|
+
return keep(`it reads ${lowered.free.join(', ')}, which is not on the SDK's list of names the Flow script runtime provides.`);
|
|
139
|
+
}
|
|
140
|
+
// The body parses on its own (checked above) but not between the braces.
|
|
141
|
+
if (lowered.syntax)
|
|
142
|
+
return keep(UNCHANGED);
|
|
143
|
+
return keep(lowered.problems.join(' '));
|
|
144
|
+
}
|
|
145
|
+
if (lowered.code !== code)
|
|
146
|
+
return keep(UNCHANGED);
|
|
147
|
+
// Last, on what will actually be printed: an early error makes the module unloadable.
|
|
148
|
+
const early = scriptBodyEarlyError(code, FLOW_SCRIPT_RUNTIME);
|
|
149
|
+
if (early)
|
|
150
|
+
return keep(`as a function in an ES module (strict code) it would not load: ${early}.`);
|
|
151
|
+
ctx.declareFunction(`function ${name}($vars: ${ctx.typeHelper('FlowVars')}) {${code}}`);
|
|
152
|
+
return name;
|
|
153
|
+
}
|
|
86
154
|
/** Key-sorted copy, so a property map compares by content and not by order. */
|
|
87
155
|
function sortKeys(o) {
|
|
88
156
|
return Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
|
|
@@ -119,6 +187,9 @@ function scriptAt(version) {
|
|
|
119
187
|
const returns = r === undefined ? undefined
|
|
120
188
|
: typeof r === 'string' ? ctx.string(r)
|
|
121
189
|
: `{ ${Object.entries(r).map(([k, t]) => `${ctx.key(k)}: ${ctx.string(String(t))}`).join(', ')} }`;
|
|
190
|
+
const fn = printAsFunction(inputs.code, ctx);
|
|
191
|
+
if (fn !== undefined)
|
|
192
|
+
return { factory: 'script', positional: [fn], options: [['returns', returns]] };
|
|
122
193
|
return { factory: 'script', options: [['code', ctx.string(inputs.code)], ['returns', returns]] };
|
|
123
194
|
},
|
|
124
195
|
check: ({ inputs }, ctx) => checkScript(inputs, ctx),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Flow script runtime, as `script(fn)` lowers for it. Not part of the public
|
|
3
|
+
* surface: `index.ts` does not re-export this file.
|
|
4
|
+
*/
|
|
5
|
+
import type { ScriptRuntime } from '../../../core/script-function.js';
|
|
6
|
+
/**
|
|
7
|
+
* What a Flow script body can read: the language's ES2020 built-ins, `console`,
|
|
8
|
+
* and the runtime's `$vars`. From the Flow skill's `references/script.md` ("The
|
|
9
|
+
* JavaScript runtime"), which also lists what reads `undefined` in a real run and
|
|
10
|
+
* is therefore absent here: `Intl`, `URL`, `TextEncoder`, `Buffer`, `fetch`,
|
|
11
|
+
* `require`, `structuredClone`. Change this when the runtime changes.
|
|
12
|
+
*
|
|
13
|
+
* Annex B's `escape`/`unescape` are left off on purpose: they URL-encode, and
|
|
14
|
+
* `escape` is the likeliest name for an author's own HTML-escape helper, which
|
|
15
|
+
* the runtime's global would silently replace.
|
|
16
|
+
*
|
|
17
|
+
* Only `script(fn)` checks against it. A string body is not checked, until
|
|
18
|
+
* there is evidence the list is complete (#1411).
|
|
19
|
+
*/
|
|
20
|
+
export declare const FLOW_SCRIPT_RUNTIME: ScriptRuntime;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Flow script body can read: the language's ES2020 built-ins, `console`,
|
|
3
|
+
* and the runtime's `$vars`. From the Flow skill's `references/script.md` ("The
|
|
4
|
+
* JavaScript runtime"), which also lists what reads `undefined` in a real run and
|
|
5
|
+
* is therefore absent here: `Intl`, `URL`, `TextEncoder`, `Buffer`, `fetch`,
|
|
6
|
+
* `require`, `structuredClone`. Change this when the runtime changes.
|
|
7
|
+
*
|
|
8
|
+
* Annex B's `escape`/`unescape` are left off on purpose: they URL-encode, and
|
|
9
|
+
* `escape` is the likeliest name for an author's own HTML-escape helper, which
|
|
10
|
+
* the runtime's global would silently replace.
|
|
11
|
+
*
|
|
12
|
+
* Only `script(fn)` checks against it. A string body is not checked, until
|
|
13
|
+
* there is evidence the list is complete (#1411).
|
|
14
|
+
*/
|
|
15
|
+
export const FLOW_SCRIPT_RUNTIME = {
|
|
16
|
+
scopeGlobal: '$vars',
|
|
17
|
+
name: 'the Flow script runtime',
|
|
18
|
+
globals: new Set([
|
|
19
|
+
'undefined', 'NaN', 'Infinity', 'globalThis', 'console',
|
|
20
|
+
'Object', 'Function', 'Array', 'Number', 'Boolean', 'String', 'Symbol', 'BigInt', 'Date', 'RegExp', 'Math', 'JSON',
|
|
21
|
+
'Promise', 'Proxy', 'Reflect', 'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
22
|
+
'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
|
|
23
|
+
'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Atomics',
|
|
24
|
+
'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
|
|
25
|
+
'Float32Array', 'Float64Array', 'BigInt64Array', 'BigUint64Array',
|
|
26
|
+
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
27
|
+
'encodeURI', 'encodeURIComponent', 'decodeURI', 'decodeURIComponent', 'eval',
|
|
28
|
+
]),
|
|
29
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* in `FlowActionSpecs`. Moved from core/actions.ts in Phase 4 of
|
|
4
4
|
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md.
|
|
5
5
|
*/
|
|
6
|
-
import type { ScriptReturns } from '../../../core/script-return.js';
|
|
6
|
+
import type { ScriptReturns, ScriptReturnType } from '../../../core/script-return.js';
|
|
7
7
|
export interface ScriptInputs {
|
|
8
8
|
/** JavaScript body. Must `return` its result. Referenced via out('<step>'). */
|
|
9
9
|
code: string;
|
|
@@ -42,6 +42,37 @@ export interface ScriptInputs {
|
|
|
42
42
|
*/
|
|
43
43
|
returns?: ScriptReturns;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The runtime's variable scope, as a script function's parameter sees it.
|
|
47
|
+
*
|
|
48
|
+
* `$vars` is the Flow script runtime's own global, not something the SDK
|
|
49
|
+
* provides: the SDK never calls a script function, and `script(fn)` renames the
|
|
50
|
+
* parameter to `$vars` in the body it emits. This type is a loose name for what
|
|
51
|
+
* that global holds, so `vars.start.output.message` type-checks. Any other
|
|
52
|
+
* parameter type works too; the SDK cannot check it against the runtime.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* .step('parse', script(parse, { returns: { amount: 'number' } }))
|
|
56
|
+
*
|
|
57
|
+
* function parse(vars: FlowVars) {
|
|
58
|
+
* return { amount: Number(vars.start.output.amount) };
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export type FlowVars = Record<string, any>;
|
|
63
|
+
/** The TypeScript type one of {@link ScriptReturnType}'s names stands for. */
|
|
64
|
+
export type ScriptValueOf<T extends ScriptReturnType> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'array' ? readonly unknown[] : T extends 'object' ? object : unknown;
|
|
65
|
+
/**
|
|
66
|
+
* What a script function must return for a given `returns`: the type a name
|
|
67
|
+
* stands for, or, for a field map, an object with those fields.
|
|
68
|
+
*
|
|
69
|
+
* This is how `script(fn, { returns })` makes tsc check the function against the
|
|
70
|
+
* declaration that reaches the `.flow`: `returns: { total: 'number' }` over a
|
|
71
|
+
* function that returns a string `total` does not compile.
|
|
72
|
+
*/
|
|
73
|
+
export type ScriptValue<R extends ScriptReturns> = R extends ScriptReturnType ? ScriptValueOf<R> : {
|
|
74
|
+
[F in keyof R]: R[F] extends ScriptReturnType ? ScriptValueOf<R[F]> : never;
|
|
75
|
+
};
|
|
45
76
|
declare module '../../../core/actions.js' {
|
|
46
77
|
interface FlowActionSpecs {
|
|
47
78
|
/** The spec {@link script} builds. */
|
|
@@ -11,6 +11,7 @@ import { wireExprBody } from '../../../core/wire-expr.js';
|
|
|
11
11
|
import { jsonClone } from '../../../core/json-clone.js';
|
|
12
12
|
import { VARIANT_OPERATION } from './spec.js';
|
|
13
13
|
import { checkTransform } from './check.js';
|
|
14
|
+
import { CompileRefusal } from '../../../core/compile-refusal.js';
|
|
14
15
|
/**
|
|
15
16
|
* Emit a transform's `inputs` in the canonical shape the node's runtime reads:
|
|
16
17
|
* `collection` as a BARE variable path (not `=js:`-prefixed — it is a path
|
|
@@ -210,7 +211,7 @@ function transformAt(typeName, variant, version, factory) {
|
|
|
210
211
|
// (TRANSFORM_VARIANT_UNKNOWN). A compile that skipped check refuses here
|
|
211
212
|
// rather than emit the generic node, which would run and be the wrong node.
|
|
212
213
|
if (variant === undefined && inputs?.variant !== undefined && inputs.variant !== null) {
|
|
213
|
-
throw new
|
|
214
|
+
throw new CompileRefusal('TRANSFORM_VARIANT_UNKNOWN', `transform step "${ctx.step}": variant ${JSON.stringify(inputs.variant)} is not a Transform node type `
|
|
214
215
|
+ `(${VARIANTS.join(', ')}), so there is no node to emit. Fix the variant, or omit it for the generic Transform.`);
|
|
215
216
|
}
|
|
216
217
|
const base = ctx.definition(typeName, version)?.outputDefinition;
|
package/dist/raw-node.js
CHANGED
|
@@ -10,6 +10,7 @@ import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
|
10
10
|
import { rawNodeInputProblem, rawNodeTypedFamilyWarning } from './raw-node-advice.js';
|
|
11
11
|
import { forwardFanOut } from './core/definition-handles.js';
|
|
12
12
|
import { RAW_NODE_EXITS } from './core/raw-node-exits.js';
|
|
13
|
+
import { CompileRefusal } from './core/compile-refusal.js';
|
|
13
14
|
/**
|
|
14
15
|
* Place a node this SDK has no factory for, carrying its definition verbatim
|
|
15
16
|
* (`uipath.exotic.thing@2.1` and the manifest the platform served for it).
|
|
@@ -101,12 +102,12 @@ class RawNode extends FlowAction {
|
|
|
101
102
|
// type never reads is refused by the serializer, for every contribution.)
|
|
102
103
|
const refusal = connectorRawNodeRefusal(this.spec.nodeType);
|
|
103
104
|
if (refusal)
|
|
104
|
-
throw new
|
|
105
|
+
throw new CompileRefusal('RAW_NODE_CONNECTOR', refusal);
|
|
105
106
|
const version = context.requestedVersion ?? this.spec.version;
|
|
106
107
|
if (context.requestedVersion !== undefined && context.requestedVersion !== this.spec.version) {
|
|
107
108
|
// Versions are exact everywhere in this SDK: a contributor may not
|
|
108
109
|
// substitute its own for an explicit request (design §2.6).
|
|
109
|
-
throw new
|
|
110
|
+
throw new CompileRefusal('RAW_NODE_VERSION_MISMATCH', `rawNode("${this.spec.nodeType}") carries version ${this.spec.version}, but the step requests `
|
|
110
111
|
+ `${context.requestedVersion}. Pass the manifest for the requested version, or drop { version }.`);
|
|
111
112
|
}
|
|
112
113
|
return {
|
package/dist/ref-resolve.d.ts
CHANGED
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `$ref` resolution for SPLIT `.flow` files (design §5, Phase 5E).
|
|
3
|
-
*
|
|
4
|
-
* The format lets a `.flow` be split across sibling files with JSON Reference
|
|
5
|
-
* (`{ "$ref": "./layout.json" }`) — used to sideline layout or definitions, or
|
|
6
|
-
* to extract a subflow out of an oversized file
|
|
7
|
-
* (`flow-schema/REF_RESOLUTION.md`, MST-8579). Decompile has to READ those, so
|
|
8
|
-
* this resolves them into ONE object model before anything else looks at the
|
|
9
|
-
* document.
|
|
10
|
-
*
|
|
11
|
-
* Compile keeps writing a single file, deliberately: nothing in the designer or
|
|
12
|
-
* the skills splits files today, so write-through splitting would be a merge
|
|
13
|
-
* concern with no producer to round-trip against.
|
|
14
|
-
*
|
|
15
|
-
* The semantics below are the platform resolver's, mirrored rather than
|
|
16
|
-
* reinvented (`flow-schema/src/ref-resolver/`), because a file the platform
|
|
17
|
-
* reads one way and the SDK another is worse than no support at all:
|
|
18
|
-
*
|
|
19
|
-
* - **`$ref` replaces the whole object.** JSON Reference, not JSON Schema —
|
|
20
|
-
* sibling properties beside a `$ref` are dropped, not merged.
|
|
21
|
-
* - **Same-document fragments (`#/…`) are left ALONE.** They are legitimate
|
|
22
|
-
* internal pointers — a connector tool's `outputSchema` carries them — and
|
|
23
|
-
* they are not file references. Resolving them as filenames is the exact
|
|
24
|
-
* regression the platform's own guard exists to prevent.
|
|
25
|
-
* - **Relative paths only.** Absolute paths, Windows drive letters, protocol
|
|
26
|
-
* URIs and traversal above the referencing file's root are REFUSED, because
|
|
27
|
-
* a `.flow` is untrusted input and the alternative is a file-exfiltration
|
|
28
|
-
* primitive (`$ref: '/etc/passwd'`, `$ref: 'https://…'`).
|
|
29
|
-
* - **Any depth**, and resolved content is walked again for its own refs.
|
|
30
|
-
*
|
|
31
|
-
* Where this deliberately differs: an unresolvable ref is an ERROR here, not a
|
|
32
|
-
* warning. The platform's resolver collects non-fatal errors because a canvas
|
|
33
|
-
* can render a partial document and let a person fix it; decompile turns the
|
|
34
|
-
* document into SOURCE, and a silently-missing subtree becomes source that
|
|
35
|
-
* compiles to a different flow. Failing names the file that is missing.
|
|
36
|
-
*/
|
|
37
1
|
/** How this module reads a sibling file. Injected so it is testable and so the
|
|
38
2
|
* SDK core stays free of `node:fs`. */
|
|
39
3
|
export type RefReader = (resolvedPath: string) => string;
|
package/dist/ref-resolve.js
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* document into SOURCE, and a silently-missing subtree becomes source that
|
|
35
35
|
* compiles to a different flow. Failing names the file that is missing.
|
|
36
36
|
*/
|
|
37
|
+
import { CompileRefusal } from './core/compile-refusal.js';
|
|
37
38
|
/**
|
|
38
39
|
* Collapse `.` and `..` segments, preserving a leading `/` for POSIX absolute
|
|
39
40
|
* paths — the platform's `normalizePath`, same behavior.
|
|
@@ -60,13 +61,13 @@ function normalizePath(path) {
|
|
|
60
61
|
*/
|
|
61
62
|
export function resolveRefPath(baseUri, refPath) {
|
|
62
63
|
if (refPath.startsWith('#')) {
|
|
63
|
-
throw new
|
|
64
|
+
throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is a same-document fragment, not a file — it must not be resolved as a path.`);
|
|
64
65
|
}
|
|
65
66
|
if (refPath.startsWith('/') || /^[a-zA-Z]:/.test(refPath)) {
|
|
66
|
-
throw new
|
|
67
|
+
throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is an absolute path; only relative paths are allowed.`);
|
|
67
68
|
}
|
|
68
69
|
if (refPath.includes('://')) {
|
|
69
|
-
throw new
|
|
70
|
+
throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" is a protocol URI; only relative file paths are allowed.`);
|
|
70
71
|
}
|
|
71
72
|
const normalizedBase = baseUri.replace(/\\/g, '/');
|
|
72
73
|
const lastSlash = normalizedBase.lastIndexOf('/');
|
|
@@ -74,7 +75,7 @@ export function resolveRefPath(baseUri, refPath) {
|
|
|
74
75
|
const resolved = normalizePath(`${baseDir}${refPath}`);
|
|
75
76
|
const isAbsoluteBase = normalizedBase.startsWith('/') || /^[a-zA-Z]:\//.test(normalizedBase);
|
|
76
77
|
if (!isAbsoluteBase && resolved.startsWith('..')) {
|
|
77
|
-
throw new
|
|
78
|
+
throw new CompileRefusal('JSON_REF_PATH_INVALID', `$ref "${refPath}" escapes the project root.`);
|
|
78
79
|
}
|
|
79
80
|
return resolved;
|
|
80
81
|
}
|
|
@@ -115,11 +116,11 @@ export function resolveFlowRefs(doc, options) {
|
|
|
115
116
|
if (node.$ref.startsWith('#'))
|
|
116
117
|
return node;
|
|
117
118
|
if (depth >= maxDepth) {
|
|
118
|
-
throw new
|
|
119
|
+
throw new CompileRefusal('JSON_REF_TOO_DEEP', `$ref resolution exceeded ${maxDepth} levels at ${where} ("${node.$ref}").`);
|
|
119
120
|
}
|
|
120
121
|
const path = resolveRefPath(baseUri, node.$ref);
|
|
121
122
|
if (visiting.has(path)) {
|
|
122
|
-
throw new
|
|
123
|
+
throw new CompileRefusal('JSON_REF_CIRCULAR', `Circular $ref at ${where}: "${node.$ref}" is already being resolved.`);
|
|
123
124
|
}
|
|
124
125
|
let text;
|
|
125
126
|
try {
|
|
@@ -127,7 +128,7 @@ export function resolveFlowRefs(doc, options) {
|
|
|
127
128
|
}
|
|
128
129
|
catch (err) {
|
|
129
130
|
const why = err instanceof Error ? err.message : String(err);
|
|
130
|
-
throw new
|
|
131
|
+
throw new CompileRefusal('JSON_REF_UNREADABLE', `$ref at ${where} points at "${node.$ref}", which could not be read (${why}).`);
|
|
131
132
|
}
|
|
132
133
|
let parsed;
|
|
133
134
|
try {
|
|
@@ -135,7 +136,7 @@ export function resolveFlowRefs(doc, options) {
|
|
|
135
136
|
}
|
|
136
137
|
catch (err) {
|
|
137
138
|
const why = err instanceof Error ? err.message : String(err);
|
|
138
|
-
throw new
|
|
139
|
+
throw new CompileRefusal('JSON_REF_NOT_JSON', `$ref at ${where} points at "${node.$ref}", which is not valid JSON (${why}).`);
|
|
139
140
|
}
|
|
140
141
|
visiting.add(path);
|
|
141
142
|
try {
|
package/dist/schema-version.d.ts
CHANGED
|
@@ -1,41 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* schema-version — the artifact's FILE-FORMAT version, as something an author
|
|
3
|
-
* can declare and a decompile can PRESERVE.
|
|
4
|
-
*
|
|
5
|
-
* Every artifact family this SDK writes carries a version that describes the
|
|
6
|
-
* DOCUMENT's grammar rather than the author's release: Flow's top-level
|
|
7
|
-
* `version`, Case's top-level `version`, BPMN's `uipath:migrationVersion`. Until
|
|
8
|
-
* now each family pinned exactly one value in its serializer's format profile
|
|
9
|
-
* and overwrote whatever the input said, which is right for greenfield
|
|
10
|
-
* authoring and wrong for the brownfield loop:
|
|
11
|
-
*
|
|
12
|
-
* decompile(a 1.10 flow) -> compile -> a 1.9 flow, with nothing said.
|
|
13
|
-
*
|
|
14
|
-
* That silent rewrite is the hole this module closes. `.schemaVersion(v)` on
|
|
15
|
-
* each builder declares the version the emitted document targets, decompile
|
|
16
|
-
* emits the call so a round-trip re-declares what it read, and the serializer
|
|
17
|
-
* writes what was declared instead of the pin.
|
|
18
|
-
*
|
|
19
|
-
* ## A declared version is a CLAIM, not a switch
|
|
20
|
-
*
|
|
21
|
-
* The rule that shapes this whole module is the one CASE_PARITY §3.8 states for
|
|
22
|
-
* `.version()`: a document's version asserts that the whole document matches
|
|
23
|
-
* that schema. An author writing `.version('30.0.0')` against the old
|
|
24
|
-
* V20-emitting serializer produced an INVALID document, because the claim was
|
|
25
|
-
* false. So `.schemaVersion()` accepts only versions this SDK can actually
|
|
26
|
-
* write — {@link SchemaVersionPolicy.writable}, each row carrying the evidence
|
|
27
|
-
* that says so — and refuses anything else rather than stamping a claim it
|
|
28
|
-
* cannot back. Moving a document BETWEEN versions stays the product's job
|
|
29
|
-
* (`uip maestro flow migrate`), because a hop can carry data repairs this SDK
|
|
30
|
-
* has no way to reproduce.
|
|
31
|
-
*
|
|
32
|
-
* ## Comparing versions
|
|
33
|
-
*
|
|
34
|
-
* `'1.10' < '1.9'` as strings, which is exactly the wrong answer, and the same
|
|
35
|
-
* trap sits in Case's `'30.0.0'` vs `'9.0.0'`. Nothing here compares version
|
|
36
|
-
* strings with `<`; {@link compareSchemaVersions} splits on `.` and compares
|
|
37
|
-
* segments numerically, and every gate goes through it.
|
|
38
|
-
*/
|
|
39
1
|
/** The artifact families that carry a schema version. */
|
|
40
2
|
export type ArtifactFamily = 'flow' | 'case' | 'bpmn';
|
|
41
3
|
/** A version this SDK build can WRITE, with the evidence that says it can. */
|
package/dist/schema-version.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* strings with `<`; {@link compareSchemaVersions} splits on `.` and compares
|
|
37
37
|
* segments numerically, and every gate goes through it.
|
|
38
38
|
*/
|
|
39
|
+
import { CompileRefusal } from './core/compile-refusal.js';
|
|
39
40
|
/** A version string the schema families use: dotted, numeric, e.g. `1.9`, `1.10`, `30.0.0`. */
|
|
40
41
|
const SYNTAX = /^\d+(\.\d+)*$/;
|
|
41
42
|
/** True when `value` is shaped like a schema version at all. */
|
|
@@ -52,7 +53,7 @@ export function isSchemaVersionSyntax(value) {
|
|
|
52
53
|
*/
|
|
53
54
|
export function compareSchemaVersions(a, b) {
|
|
54
55
|
if (!isSchemaVersionSyntax(a) || !isSchemaVersionSyntax(b)) {
|
|
55
|
-
throw new Error(`compareSchemaVersions: "${a}" / "${b}" is not a dotted-numeric schema version.`);
|
|
56
|
+
throw new Error(`internal SDK error: compareSchemaVersions: "${a}" / "${b}" is not a dotted-numeric schema version.`);
|
|
56
57
|
}
|
|
57
58
|
const left = a.split('.').map(Number);
|
|
58
59
|
const right = b.split('.').map(Number);
|
|
@@ -134,7 +135,7 @@ export function resolveSchemaVersion(policy, requested) {
|
|
|
134
135
|
return policy.default;
|
|
135
136
|
const refusal = schemaVersionRefusal(policy, requested);
|
|
136
137
|
if (refusal)
|
|
137
|
-
throw new
|
|
138
|
+
throw new CompileRefusal('SCHEMA_VERSION_UNSUPPORTED', refusal);
|
|
138
139
|
return requested;
|
|
139
140
|
}
|
|
140
141
|
export function preserveSchemaVersion(policy, declared) {
|