@uipath/maestro-builder-sdk 5.2.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/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/bindings-v2.d.ts +37 -0
- package/dist/bindings-v2.js +155 -0
- package/dist/bindings.d.ts +6 -0
- package/dist/bindings.js +6 -0
- package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
- package/dist/bpmn/bpmn-expr-check.js +218 -0
- package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
- package/dist/bpmn/bpmn-sdk.js +1476 -0
- package/dist/bpmn/check-cli.d.ts +3 -0
- package/dist/bpmn/check-cli.js +32 -0
- package/dist/bpmn/check.d.ts +25 -0
- package/dist/bpmn/check.js +430 -0
- package/dist/bpmn/compile-cli.d.ts +3 -0
- package/dist/bpmn/compile-cli.js +60 -0
- package/dist/bpmn/connector.d.ts +56 -0
- package/dist/bpmn/connector.js +199 -0
- package/dist/bpmn/decompile-cli.d.ts +8 -0
- package/dist/bpmn/decompile-cli.js +54 -0
- package/dist/bpmn/decompile.d.ts +6 -0
- package/dist/bpmn/decompile.js +1108 -0
- package/dist/bpmn/format-profile.d.ts +27 -0
- package/dist/bpmn/format-profile.js +11 -0
- package/dist/bpmn/intsvc.d.ts +77 -0
- package/dist/bpmn/intsvc.js +155 -0
- package/dist/bpmn/load.d.ts +4 -0
- package/dist/bpmn/load.js +22 -0
- package/dist/bpmn/merge-cli.d.ts +3 -0
- package/dist/bpmn/merge-cli.js +45 -0
- package/dist/bpmn/merge.d.ts +19 -0
- package/dist/bpmn/merge.js +293 -0
- package/dist/bpmn/registry-types.json +1869 -0
- package/dist/bpmn/serialize.d.ts +28 -0
- package/dist/bpmn/serialize.js +757 -0
- package/dist/bpmn/tidy-cli.d.ts +2 -0
- package/dist/bpmn/tidy-cli.js +39 -0
- package/dist/bpmn/typed-node.d.ts +382 -0
- package/dist/bpmn/typed-node.js +551 -0
- package/dist/bpmn/uipath-moddle.v1.json +823 -0
- package/dist/bpmn/xml-ranges.d.ts +45 -0
- package/dist/bpmn/xml-ranges.js +151 -0
- package/dist/case/case-expr-check.d.ts +35 -0
- package/dist/case/case-expr-check.js +85 -0
- package/dist/case/case-sdk.d.ts +1282 -0
- package/dist/case/case-sdk.js +923 -0
- package/dist/case/check-cli.d.ts +3 -0
- package/dist/case/check-cli.js +31 -0
- package/dist/case/check.d.ts +18 -0
- package/dist/case/check.js +456 -0
- package/dist/case/compile-cli.d.ts +3 -0
- package/dist/case/compile-cli.js +291 -0
- package/dist/case/decompile-cli.d.ts +9 -0
- package/dist/case/decompile-cli.js +117 -0
- package/dist/case/decompile.d.ts +187 -0
- package/dist/case/decompile.js +1170 -0
- package/dist/case/entry-points.d.ts +79 -0
- package/dist/case/entry-points.js +118 -0
- package/dist/case/format-profile.d.ts +60 -0
- package/dist/case/format-profile.js +79 -0
- package/dist/case/ids.d.ts +24 -0
- package/dist/case/ids.js +76 -0
- package/dist/case/load.d.ts +12 -0
- package/dist/case/load.js +32 -0
- package/dist/case/preserve.d.ts +133 -0
- package/dist/case/preserve.js +105 -0
- package/dist/case/serialize.d.ts +10 -0
- package/dist/case/serialize.js +1414 -0
- package/dist/check-cli.d.ts +3 -0
- package/dist/check-cli.js +118 -0
- package/dist/check.d.ts +146 -0
- package/dist/check.js +4898 -0
- package/dist/cli/commands/bpmn.d.ts +2 -0
- package/dist/cli/commands/bpmn.js +73 -0
- package/dist/cli/commands/case.d.ts +2 -0
- package/dist/cli/commands/case.js +60 -0
- package/dist/cli/commands/flow.d.ts +2 -0
- package/dist/cli/commands/flow.js +99 -0
- package/dist/cli/commands/registry.d.ts +10 -0
- package/dist/cli/commands/registry.js +170 -0
- package/dist/cli/common.d.ts +30 -0
- package/dist/cli/common.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +42 -0
- package/dist/compile-cli.d.ts +3 -0
- package/dist/compile-cli.js +160 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +201 -0
- package/dist/core/actions.d.ts +3244 -0
- package/dist/core/actions.js +1062 -0
- package/dist/core/binding-messages.d.ts +1 -0
- package/dist/core/binding-messages.js +16 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +67 -0
- package/dist/core/ceql.d.ts +73 -0
- package/dist/core/ceql.js +272 -0
- package/dist/core/cli-spelling.d.ts +39 -0
- package/dist/core/cli-spelling.js +62 -0
- package/dist/core/connector-raw-node.d.ts +21 -0
- package/dist/core/connector-raw-node.js +47 -0
- package/dist/core/connectors.d.ts +184 -0
- package/dist/core/connectors.js +128 -0
- package/dist/core/expr-check.d.ts +93 -0
- package/dist/core/expr-check.js +186 -0
- package/dist/core/expr.d.ts +323 -0
- package/dist/core/expr.js +400 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +10 -0
- package/dist/core/library.d.ts +440 -0
- package/dist/core/library.js +1183 -0
- package/dist/core/list-envelope.d.ts +45 -0
- package/dist/core/list-envelope.js +97 -0
- package/dist/core/lookup-store.d.ts +22 -0
- package/dist/core/lookup-store.js +85 -0
- package/dist/core/lookups.d.ts +227 -0
- package/dist/core/lookups.js +231 -0
- package/dist/core/node-classes.d.ts +234 -0
- package/dist/core/node-classes.js +158 -0
- package/dist/core/script-return.d.ts +68 -0
- package/dist/core/script-return.js +197 -0
- package/dist/core/stable-id.d.ts +28 -0
- package/dist/core/stable-id.js +37 -0
- package/dist/core-definitions.json +16249 -0
- package/dist/decompile-cli.d.ts +3 -0
- package/dist/decompile-cli.js +131 -0
- package/dist/decompile.d.ts +138 -0
- package/dist/decompile.js +2482 -0
- package/dist/emit.d.ts +10 -0
- package/dist/emit.js +60 -0
- package/dist/event-filters.d.ts +130 -0
- package/dist/event-filters.js +137 -0
- package/dist/flow-expr-check.d.ts +17 -0
- package/dist/flow-expr-check.js +193 -0
- package/dist/flow-sdk.d.ts +1341 -0
- package/dist/flow-sdk.js +1164 -0
- package/dist/flow-variables.d.ts +13 -0
- package/dist/flow-variables.js +42 -0
- package/dist/format-profile.d.ts +72 -0
- package/dist/format-profile.js +97 -0
- package/dist/generators/_connections.py +287 -0
- package/dist/generators/_events.py +355 -0
- package/dist/generators/_lookups.py +114 -0
- package/dist/generators/_naming.py +185 -0
- package/dist/generators/_objects.py +299 -0
- package/dist/generators/_resolve.py +273 -0
- package/dist/generators/convert_library_to_md.py +1441 -0
- package/dist/generators/extract_library.py +125 -0
- package/dist/generators/generate_connectors_ts.py +936 -0
- package/dist/generators/generate_library_json.py +2527 -0
- package/dist/generators/generate_triggers_ts.py +110 -0
- package/dist/generators/materialize_registry.py +155 -0
- package/dist/generators/prepare_connector.py +1688 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/ixp-definition.json +1982 -0
- package/dist/library.d.ts +6 -0
- package/dist/library.js +6 -0
- package/dist/load.d.ts +16 -0
- package/dist/load.js +95 -0
- package/dist/merge-cli.d.ts +3 -0
- package/dist/merge-cli.js +47 -0
- package/dist/merge.d.ts +125 -0
- package/dist/merge.js +0 -0
- package/dist/migrate.d.ts +15 -0
- package/dist/migrate.js +245 -0
- package/dist/node-runtime.d.ts +3 -0
- package/dist/node-runtime.js +22 -0
- package/dist/package-name.d.ts +17 -0
- package/dist/package-name.js +17 -0
- package/dist/ref-resolve.d.ts +77 -0
- package/dist/ref-resolve.js +170 -0
- package/dist/registry/cache.d.ts +55 -0
- package/dist/registry/cache.js +134 -0
- package/dist/registry/curated-overlay.d.ts +16 -0
- package/dist/registry/curated-overlay.js +62 -0
- package/dist/registry/extractor.d.ts +22 -0
- package/dist/registry/extractor.js +18 -0
- package/dist/registry/materialize-bin.d.ts +2 -0
- package/dist/registry/materialize-bin.js +7 -0
- package/dist/registry/materialize-cli.d.ts +1 -0
- package/dist/registry/materialize-cli.js +46 -0
- package/dist/registry/materializer.d.ts +23 -0
- package/dist/registry/materializer.js +34 -0
- package/dist/registry/path-cli.d.ts +1 -0
- package/dist/registry/path-cli.js +56 -0
- package/dist/registry/prepare-cli.d.ts +62 -0
- package/dist/registry/prepare-cli.js +185 -0
- package/dist/registry/pull-cli.d.ts +1 -0
- package/dist/registry/pull-cli.js +274 -0
- package/dist/registry/python.d.ts +26 -0
- package/dist/registry/python.js +46 -0
- package/dist/registry/search-cli.d.ts +52 -0
- package/dist/registry/search-cli.js +159 -0
- package/dist/schema-version.d.ts +165 -0
- package/dist/schema-version.js +148 -0
- package/dist/serialize.d.ts +64 -0
- package/dist/serialize.js +6398 -0
- package/lib/README.md +112 -0
- package/lib/library-json.samples/index.json +158 -0
- package/lib/library-json.samples/invariant-collections.json +83 -0
- package/lib/library-json.samples/list-envelopes.json +123 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
- package/package.json +85 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/connectors — the typed connector-descriptor surface.
|
|
3
|
+
*
|
|
4
|
+
* A *descriptor* is a tiny, generated value that identifies one connector
|
|
5
|
+
* operation (`nodeType` + `version`) and carries its input/output **types** as
|
|
6
|
+
* phantom brands (zero runtime cost). The generated per-connector modules
|
|
7
|
+
* (materialized from the connector library — see `lib/`) export one descriptor
|
|
8
|
+
* per op, so authoring a connector step is statically checked:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { connector } from './sdk';
|
|
12
|
+
* import { CreateIssue } from '@uipath/flow-connectors/uipath-atlassian-jira';
|
|
13
|
+
* connector(CreateIssue, { fields: { project: { key: 'TS' }, summary: '…' } },
|
|
14
|
+
* { connection: 'jira', folder: 'shared' });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The descriptor is a **typed index over the library**, not a replacement: it
|
|
18
|
+
* only carries the id + types; `serialize()` still resolves the full node
|
|
19
|
+
* payload from `library-json` via `Library.resolve()`. Both are generated from
|
|
20
|
+
* the same library snapshot. Generated inputs expose natural nested objects and
|
|
21
|
+
* arrays; `buildConnectorInputs` validates them and projects them back to the
|
|
22
|
+
* registry's flat field ids when emitting Flow JSON.
|
|
23
|
+
*/
|
|
24
|
+
import type { Expr } from './expr.js';
|
|
25
|
+
import type { LookupSpec, LookupToken } from './lookups.js';
|
|
26
|
+
/**
|
|
27
|
+
* What a generated `Inputs` property accepts: the value, or an expression.
|
|
28
|
+
*
|
|
29
|
+
* The generator used to type a string field as `string`, full stop. That was
|
|
30
|
+
* never what the SDK accepts — `input('x')`, `out('step','f')`, `` tmpl`…` ``
|
|
31
|
+
* and `` js`…` `` all produce an {@link Expr}, and passing one is the ordinary
|
|
32
|
+
* way to author a connector step. So `connector(CreateIssue, { summary:
|
|
33
|
+
* input('summary') })` did not typecheck.
|
|
34
|
+
*
|
|
35
|
+
* Nothing caught it because no shipped example uses a descriptor (they all use
|
|
36
|
+
* the string form, which takes `Record<string, unknown>`), and because `compile`
|
|
37
|
+
* runs under `--experimental-strip-types` — types are erased, never checked.
|
|
38
|
+
* The typed surface was decorative.
|
|
39
|
+
*
|
|
40
|
+
* Arrays distribute, so BOTH shapes an author reaches for are legal: the whole
|
|
41
|
+
* collection as one expression (`tags: input('tags')`) and a literal array with
|
|
42
|
+
* an expression in a slot (`tags: ['a', out('s','b')]`). Without the
|
|
43
|
+
* distribution the second form forced every element to be a bare literal.
|
|
44
|
+
*
|
|
45
|
+
* This does NOT loosen which FIELDS are accepted, which is the property the
|
|
46
|
+
* closed `Inputs` type exists for. Only which value forms, and to exactly the
|
|
47
|
+
* set the serializer already renders.
|
|
48
|
+
*/
|
|
49
|
+
export type ConnectorValue<T> = Expr | (T extends readonly (infer E)[] ? Array<E | Expr> : T);
|
|
50
|
+
/**
|
|
51
|
+
* {@link ConnectorValue} for a field that also accepts an unresolved lookup.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately separate. If every property accepted a {@link LookupToken}, a
|
|
54
|
+
* `lookup(...)` could be attached to a field with no `reference` at all, and
|
|
55
|
+
* the mistake would survive to `prepare`, which has nothing to resolve it
|
|
56
|
+
* against. Confining the token to fields that declare a lookup makes that a
|
|
57
|
+
* compile error at the call site instead.
|
|
58
|
+
*/
|
|
59
|
+
export type ConnectorLookupValue<T> = LookupToken | (T extends readonly (infer E)[] ? Array<E | Expr | LookupToken> : T | Expr);
|
|
60
|
+
/** Runtime metadata a generated descriptor carries. `nodeType` is `uipath.connector.<key>.<action>` (no `@version`). */
|
|
61
|
+
export interface ConnectorMeta {
|
|
62
|
+
nodeType: string;
|
|
63
|
+
version?: string;
|
|
64
|
+
requiresConnection?: boolean;
|
|
65
|
+
requiresFolderKey?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Which OBJECT this descriptor addresses, for a **generic** operation whose
|
|
68
|
+
* nodeType covers many (`…netsuite.get-record` covers 182). Baked in by the
|
|
69
|
+
* generator so `connector(D, …)` needs no `{ object }`; an explicit
|
|
70
|
+
* `{ object }` still wins.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* Precedence is the call option first, the descriptor's baked constant second.
|
|
74
|
+
*/
|
|
75
|
+
objectName?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The operation's resolvable lookup fields, keyed by field name — generated
|
|
78
|
+
* from each input's `reference` block. Absent when the operation has none.
|
|
79
|
+
*
|
|
80
|
+
* Consumed by {@link lookup}, and readable as plain source: the generator
|
|
81
|
+
* also writes the same facts as a comment above the descriptor, because the
|
|
82
|
+
* agents this surface is for run with `grep` and no language server, so a
|
|
83
|
+
* `.byEmail` discoverable only by hovering is invisible.
|
|
84
|
+
*
|
|
85
|
+
* Degenerate lookups are deliberately absent — see `_lookups.py`.
|
|
86
|
+
*/
|
|
87
|
+
lookups?: Readonly<Record<string, LookupSpec>>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A generated, typed connector descriptor: {@link ConnectorMeta} branded with
|
|
91
|
+
* phantom input/output types. `__inputs`/`__outputs` never materialize at
|
|
92
|
+
* runtime — they exist only so `connector(descriptor, inputs)` can infer types.
|
|
93
|
+
*/
|
|
94
|
+
export type ConnectorDescriptor<I = Record<string, unknown>, O = Record<string, unknown>> = ConnectorMeta & {
|
|
95
|
+
readonly __inputs?: I;
|
|
96
|
+
readonly __outputs?: O;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Brand a descriptor literal with its input/output types (zero runtime cost).
|
|
100
|
+
* Used by codegen: `descriptor<CreateIssueInputs, CreateIssueOutputs>()({ … } as const)`.
|
|
101
|
+
* Curried so the generator names `I`/`O` explicitly while TS still narrows the
|
|
102
|
+
* literal (`nodeType`, flags) from `as const`.
|
|
103
|
+
*
|
|
104
|
+
* @returns A branding function: call it with the descriptor literal (`as const`) to get
|
|
105
|
+
* that literal typed as a {@link ConnectorDescriptor}.
|
|
106
|
+
*/
|
|
107
|
+
export declare function descriptor<I = Record<string, unknown>, O = Record<string, unknown>>(): <const D extends ConnectorMeta>(d: D) => D & {
|
|
108
|
+
readonly __inputs?: I;
|
|
109
|
+
readonly __outputs?: O;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Split a connector `nodeType` into its `{ key, action }`.
|
|
113
|
+
*
|
|
114
|
+
* @internal Public for compatibility; not an authoring factory.
|
|
115
|
+
*/
|
|
116
|
+
export declare function splitConnectorNodeType(nodeType: string): {
|
|
117
|
+
key: string;
|
|
118
|
+
action: string;
|
|
119
|
+
};
|
|
120
|
+
/** Runtime metadata a generated trigger descriptor carries. */
|
|
121
|
+
export interface TriggerMeta {
|
|
122
|
+
/** Connector key, e.g. `'uipath-atlassian-jira'`. */
|
|
123
|
+
connector: string;
|
|
124
|
+
/** Curated event id, e.g. `'issue-created'` (the last segment of the nodeType). */
|
|
125
|
+
event: string;
|
|
126
|
+
version?: string;
|
|
127
|
+
/** Human label from the registry catalog, e.g. `'Issue Created'` (docs only). */
|
|
128
|
+
displayName?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A generated, typed connector-trigger descriptor: {@link TriggerMeta} branded
|
|
132
|
+
* with phantom `where`/output types. Offline both are loose; a connection-scoped
|
|
133
|
+
* generator can narrow them. `__where`/`__output` never materialize at runtime.
|
|
134
|
+
*/
|
|
135
|
+
export type TriggerDescriptor<W = Record<string, string>, O = Record<string, unknown>> = TriggerMeta & {
|
|
136
|
+
readonly __where?: W;
|
|
137
|
+
readonly __output?: O;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Brand a trigger descriptor literal with its where/output types. Used by codegen:
|
|
141
|
+
* `triggerDescriptor<IssueCreatedWhere>()({ … } as const)`. Curried so the
|
|
142
|
+
* generator names `W`/`O` explicitly while TS narrows the literal.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* It also stamps a non-enumerable runtime marker — see `isTriggerDescriptor`
|
|
146
|
+
* for why a type-level-only brand was not enough.
|
|
147
|
+
*
|
|
148
|
+
* @returns A branding function: call it with the descriptor literal (`as const`) to get
|
|
149
|
+
* that literal typed as a {@link TriggerDescriptor}.
|
|
150
|
+
*/
|
|
151
|
+
export declare function triggerDescriptor<W = Record<string, string>, O = Record<string, unknown>>(): <const D extends TriggerMeta>(d: D) => D & TriggerDescriptor<W, O>;
|
|
152
|
+
/**
|
|
153
|
+
* Distinguish the typed `onEvent(descriptor, opts)` form from the stringly
|
|
154
|
+
* `onEvent(subscription)` form.
|
|
155
|
+
*
|
|
156
|
+
* ## This used to test for the ABSENCE of `where`, and that was load-bearing on an
|
|
157
|
+
* assumption that turned out to be false
|
|
158
|
+
*
|
|
159
|
+
* The old rule was *"both carry `connector` + `event`; only an `EventSubscription`
|
|
160
|
+
* carries `where`, so its ABSENCE marks a bare descriptor"*. It held only while
|
|
161
|
+
* every connector event HAD event parameters. The HTTP Webhook connector has none
|
|
162
|
+
* — it is scoped by the connection, so `{ connector: 'uipath-http-webhook',
|
|
163
|
+
* event: 'http-webhook', connection: 'bWebhook', folder: 'bFolder' }` is a
|
|
164
|
+
* complete, correct subscription with no `where` at all — and the old test read it
|
|
165
|
+
* as a descriptor. The consequence was not a type error: `subscriptionFrom` then
|
|
166
|
+
* rebuilt the subscription from the descriptor's three fields and **silently
|
|
167
|
+
* dropped `connection`, `folder` and `filters`**, emitting a start trigger bound
|
|
168
|
+
* to no connection. 162 of 164 deployed connector start triggers carry a
|
|
169
|
+
* `detail.connectionId`; ours carried none, and every rung stayed green because a
|
|
170
|
+
* local run resolves connectors from the library rather than from the binding.
|
|
171
|
+
*
|
|
172
|
+
* So the test is now POSITIVE and belt-and-braces:
|
|
173
|
+
*
|
|
174
|
+
* 1. a descriptor is what `triggerDescriptor()` stamped (the only producer — the
|
|
175
|
+
* generated `<key>.triggers.ts` modules all go through it);
|
|
176
|
+
* 2. and nothing carrying a SUBSCRIPTION-ONLY key is ever a descriptor, so an
|
|
177
|
+
* unbranded hand-written subscription cannot be misread either.
|
|
178
|
+
*
|
|
179
|
+
* Rule of thumb this cost: **never discriminate a union on a field that is allowed
|
|
180
|
+
* to be absent.**
|
|
181
|
+
*
|
|
182
|
+
* @internal Public for compatibility; not an authoring factory.
|
|
183
|
+
*/
|
|
184
|
+
export declare function isTriggerDescriptor(x: unknown): x is TriggerDescriptor;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/connectors — the typed connector-descriptor surface.
|
|
3
|
+
*
|
|
4
|
+
* A *descriptor* is a tiny, generated value that identifies one connector
|
|
5
|
+
* operation (`nodeType` + `version`) and carries its input/output **types** as
|
|
6
|
+
* phantom brands (zero runtime cost). The generated per-connector modules
|
|
7
|
+
* (materialized from the connector library — see `lib/`) export one descriptor
|
|
8
|
+
* per op, so authoring a connector step is statically checked:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { connector } from './sdk';
|
|
12
|
+
* import { CreateIssue } from '@uipath/flow-connectors/uipath-atlassian-jira';
|
|
13
|
+
* connector(CreateIssue, { fields: { project: { key: 'TS' }, summary: '…' } },
|
|
14
|
+
* { connection: 'jira', folder: 'shared' });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The descriptor is a **typed index over the library**, not a replacement: it
|
|
18
|
+
* only carries the id + types; `serialize()` still resolves the full node
|
|
19
|
+
* payload from `library-json` via `Library.resolve()`. Both are generated from
|
|
20
|
+
* the same library snapshot. Generated inputs expose natural nested objects and
|
|
21
|
+
* arrays; `buildConnectorInputs` validates them and projects them back to the
|
|
22
|
+
* registry's flat field ids when emitting Flow JSON.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Brand a descriptor literal with its input/output types (zero runtime cost).
|
|
26
|
+
* Used by codegen: `descriptor<CreateIssueInputs, CreateIssueOutputs>()({ … } as const)`.
|
|
27
|
+
* Curried so the generator names `I`/`O` explicitly while TS still narrows the
|
|
28
|
+
* literal (`nodeType`, flags) from `as const`.
|
|
29
|
+
*
|
|
30
|
+
* @returns A branding function: call it with the descriptor literal (`as const`) to get
|
|
31
|
+
* that literal typed as a {@link ConnectorDescriptor}.
|
|
32
|
+
*/
|
|
33
|
+
export function descriptor() {
|
|
34
|
+
// Returns `D & brand`, NOT `D & ConnectorDescriptor<I, O>`. The two are
|
|
35
|
+
// equivalent for assignability — `D extends ConnectorMeta` already — but not
|
|
36
|
+
// for `keyof`: re-intersecting `ConnectorMeta` puts its wide
|
|
37
|
+
// `lookups?: Record<string, LookupSpec>` beside the literal's narrow one, and
|
|
38
|
+
// `keyof (A & B)` is the UNION of their keys, so `keyof D['lookups']`
|
|
39
|
+
// collapsed to `string`. That silently disabled every lookup field-name check
|
|
40
|
+
// — `lookup(D, 'nonexistent')` type-checked clean and failed at run time.
|
|
41
|
+
return (d) => d;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Split a connector `nodeType` into its `{ key, action }`.
|
|
45
|
+
*
|
|
46
|
+
* @internal Public for compatibility; not an authoring factory.
|
|
47
|
+
*/
|
|
48
|
+
export function splitConnectorNodeType(nodeType) {
|
|
49
|
+
const rest = nodeType.replace(/^uipath\.connector\./, '');
|
|
50
|
+
const dot = rest.lastIndexOf('.');
|
|
51
|
+
if (dot <= 0)
|
|
52
|
+
throw new Error(`invalid connector nodeType "${nodeType}" (expected uipath.connector.<key>.<action>).`);
|
|
53
|
+
return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The runtime marker `triggerDescriptor()` stamps on a descriptor. Non-enumerable,
|
|
57
|
+
* so it never reaches a `JSON.stringify`, an emitted `.flow`, or a spread — it
|
|
58
|
+
* exists only so `isTriggerDescriptor` can answer POSITIVELY.
|
|
59
|
+
*/
|
|
60
|
+
const TRIGGER_DESCRIPTOR = Symbol.for('uipath.flow-sdk.triggerDescriptor');
|
|
61
|
+
/** Keys only an `EventSubscription` ever carries. */
|
|
62
|
+
const SUBSCRIPTION_ONLY_KEYS = ['where', 'object', 'filters', 'connection', 'folder'];
|
|
63
|
+
/**
|
|
64
|
+
* Brand a trigger descriptor literal with its where/output types. Used by codegen:
|
|
65
|
+
* `triggerDescriptor<IssueCreatedWhere>()({ … } as const)`. Curried so the
|
|
66
|
+
* generator names `W`/`O` explicitly while TS narrows the literal.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* It also stamps a non-enumerable runtime marker — see `isTriggerDescriptor`
|
|
70
|
+
* for why a type-level-only brand was not enough.
|
|
71
|
+
*
|
|
72
|
+
* @returns A branding function: call it with the descriptor literal (`as const`) to get
|
|
73
|
+
* that literal typed as a {@link TriggerDescriptor}.
|
|
74
|
+
*/
|
|
75
|
+
export function triggerDescriptor() {
|
|
76
|
+
return (d) => {
|
|
77
|
+
// A descriptor literal is freshly written at its call site, so defining a
|
|
78
|
+
// property on it is safe; guard anyway for a frozen or reused object.
|
|
79
|
+
try {
|
|
80
|
+
Object.defineProperty(d, TRIGGER_DESCRIPTOR, { value: true, enumerable: false });
|
|
81
|
+
}
|
|
82
|
+
catch { /* a frozen literal still works — SUBSCRIPTION_ONLY_KEYS decides */ }
|
|
83
|
+
return d;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Distinguish the typed `onEvent(descriptor, opts)` form from the stringly
|
|
88
|
+
* `onEvent(subscription)` form.
|
|
89
|
+
*
|
|
90
|
+
* ## This used to test for the ABSENCE of `where`, and that was load-bearing on an
|
|
91
|
+
* assumption that turned out to be false
|
|
92
|
+
*
|
|
93
|
+
* The old rule was *"both carry `connector` + `event`; only an `EventSubscription`
|
|
94
|
+
* carries `where`, so its ABSENCE marks a bare descriptor"*. It held only while
|
|
95
|
+
* every connector event HAD event parameters. The HTTP Webhook connector has none
|
|
96
|
+
* — it is scoped by the connection, so `{ connector: 'uipath-http-webhook',
|
|
97
|
+
* event: 'http-webhook', connection: 'bWebhook', folder: 'bFolder' }` is a
|
|
98
|
+
* complete, correct subscription with no `where` at all — and the old test read it
|
|
99
|
+
* as a descriptor. The consequence was not a type error: `subscriptionFrom` then
|
|
100
|
+
* rebuilt the subscription from the descriptor's three fields and **silently
|
|
101
|
+
* dropped `connection`, `folder` and `filters`**, emitting a start trigger bound
|
|
102
|
+
* to no connection. 162 of 164 deployed connector start triggers carry a
|
|
103
|
+
* `detail.connectionId`; ours carried none, and every rung stayed green because a
|
|
104
|
+
* local run resolves connectors from the library rather than from the binding.
|
|
105
|
+
*
|
|
106
|
+
* So the test is now POSITIVE and belt-and-braces:
|
|
107
|
+
*
|
|
108
|
+
* 1. a descriptor is what `triggerDescriptor()` stamped (the only producer — the
|
|
109
|
+
* generated `<key>.triggers.ts` modules all go through it);
|
|
110
|
+
* 2. and nothing carrying a SUBSCRIPTION-ONLY key is ever a descriptor, so an
|
|
111
|
+
* unbranded hand-written subscription cannot be misread either.
|
|
112
|
+
*
|
|
113
|
+
* Rule of thumb this cost: **never discriminate a union on a field that is allowed
|
|
114
|
+
* to be absent.**
|
|
115
|
+
*
|
|
116
|
+
* @internal Public for compatibility; not an authoring factory.
|
|
117
|
+
*/
|
|
118
|
+
export function isTriggerDescriptor(x) {
|
|
119
|
+
if (typeof x !== 'object' || x === null)
|
|
120
|
+
return false;
|
|
121
|
+
if (typeof x.connector !== 'string')
|
|
122
|
+
return false;
|
|
123
|
+
if (typeof x.event !== 'string')
|
|
124
|
+
return false;
|
|
125
|
+
if (SUBSCRIPTION_ONLY_KEYS.some((k) => k in x))
|
|
126
|
+
return false;
|
|
127
|
+
return x[TRIGGER_DESCRIPTOR] === true;
|
|
128
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/expr-check — a FORMAT-AGNOSTIC first-level check of the references inside
|
|
3
|
+
* an expression, shared by Flow, Case, and BPMN.
|
|
4
|
+
*
|
|
5
|
+
* This is the piece each SDK's `build()` runs BEFORE it hands back the model to
|
|
6
|
+
* be serialized: cheap, high-confidence checks that catch a broken expression at
|
|
7
|
+
* the moment the SDK code runs, rather than three steps later at
|
|
8
|
+
* `uip maestro flow validate`. Its guiding rule is **conservative**: only flag
|
|
9
|
+
* what it can be certain of, so it never produces a misleading error. Deeper,
|
|
10
|
+
* heavier checks (does step X actually output `.foo`, is the comparison
|
|
11
|
+
* type-sound) belong to the `validate` CLI, which can afford them.
|
|
12
|
+
*
|
|
13
|
+
* The first thing it checks: every `$vars.<root>` reference resolves to a name the
|
|
14
|
+
* runtime will provide — a declared input, variable, or step/task/node. A
|
|
15
|
+
* reference to nothing is a definite bug (a typo, or a renamed step), and the
|
|
16
|
+
* runtime's variable bag is pre-declared, so an unknown root reads `null` forever
|
|
17
|
+
* and every guard built on it silently takes the wrong path.
|
|
18
|
+
*
|
|
19
|
+
* The second: the expression PARSES as JavaScript. That sounds like something a
|
|
20
|
+
* later rung would catch, and nothing did — `validate` checks the artifact's
|
|
21
|
+
* contract, not the contents of an expression string, so
|
|
22
|
+
* `$vars.searchIssues.output.issues[*]` reached a live run and faulted there
|
|
23
|
+
* (`[400008] Failed to evaluate the input collection variable`). `[*]` is
|
|
24
|
+
* JSONPath; the runtime evaluates JavaScript. Reference resolution alone could
|
|
25
|
+
* not see it, because the root (`searchIssues`) was perfectly real.
|
|
26
|
+
*
|
|
27
|
+
* It is deliberately NOT tied to any one format's model: a caller passes the set
|
|
28
|
+
* of valid roots (which it alone knows how to compute) and the expression string,
|
|
29
|
+
* and gets back diagnostics with no location. The caller stamps the location
|
|
30
|
+
* (which node / task / field) it came from.
|
|
31
|
+
*/
|
|
32
|
+
/** A `$vars.*` reference found in an expression. */
|
|
33
|
+
export interface ExprRef {
|
|
34
|
+
/** The first segment after `$vars.` — the name the reference is rooted at. */
|
|
35
|
+
root: string;
|
|
36
|
+
/** The remaining dotted / indexed segments, e.g. `['output', 'items', '[0]', 'id']`. */
|
|
37
|
+
path: string[];
|
|
38
|
+
/** The whole matched reference text, for messages. */
|
|
39
|
+
raw: string;
|
|
40
|
+
}
|
|
41
|
+
/** A problem found in an expression. Location is stamped by the caller. */
|
|
42
|
+
export interface ExprDiagnostic {
|
|
43
|
+
level: 'error' | 'warning';
|
|
44
|
+
code: string;
|
|
45
|
+
message: string;
|
|
46
|
+
suggestion?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What the caller knows and the checker does not: which `$vars` roots are valid.
|
|
50
|
+
*
|
|
51
|
+
* `roots` is every name a reference may be rooted at — declared inputs, working
|
|
52
|
+
* variables, and the names of steps/tasks/nodes whose output is referenceable.
|
|
53
|
+
* `noun` names them in the message (`'input, variable, or step'` for a flow), so
|
|
54
|
+
* the same checker reads naturally for a case ('field or task') or a bpmn model.
|
|
55
|
+
*/
|
|
56
|
+
export interface ExprScope {
|
|
57
|
+
roots: Set<string>;
|
|
58
|
+
noun?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The reference NAMESPACE this check owns — the leading token whose roots are
|
|
61
|
+
* author-declared and therefore checkable. Flow uses `$vars` (its default); Case
|
|
62
|
+
* uses `vars` (a case gate reads a declared variable as `=js:vars.<name>`). Every
|
|
63
|
+
* OTHER namespace the engine provides (`outputs`, `result`, `metadata`,
|
|
64
|
+
* `orchestrator`, …) is a runtime built-in, so a reference into one is left alone
|
|
65
|
+
* rather than flagged.
|
|
66
|
+
*/
|
|
67
|
+
namespace?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Parse the expression as JavaScript and report a syntax error.
|
|
70
|
+
*
|
|
71
|
+
* OFF by default, because "expression" does not mean the same thing in all
|
|
72
|
+
* three formats and only the caller knows. A Flow `Expr` carries the bare
|
|
73
|
+
* JavaScript the runtime evaluates, so it parses. A BPMN condition is
|
|
74
|
+
* `=vars.total` and a Case gate is `=js:vars.total` — both carry a leading
|
|
75
|
+
* `=` sigil and are not JavaScript as given, so parsing them would report a
|
|
76
|
+
* syntax error on every well-formed expression in those formats.
|
|
77
|
+
*/
|
|
78
|
+
syntax?: 'javascript';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every `<namespace>.*` reference in an expression, in source order, with literals
|
|
82
|
+
* masked so a reference-shaped substring inside a string is not counted.
|
|
83
|
+
* `namespace` defaults to `$vars` (Flow); Case passes `case`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function extractRefs(expr: string, namespace?: string): ExprRef[];
|
|
86
|
+
/**
|
|
87
|
+
* First-level check of ONE expression: every reference's root must resolve.
|
|
88
|
+
*
|
|
89
|
+
* Returns a diagnostic per unresolved root (deduped, so `$vars.x + $vars.x` is one
|
|
90
|
+
* message). An empty result means "nothing this check is sure is wrong" — never
|
|
91
|
+
* "fully valid"; the `validate` CLI is what proves the rest.
|
|
92
|
+
*/
|
|
93
|
+
export declare function checkExpression(expr: string, scope: ExprScope): ExprDiagnostic[];
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/expr-check — a FORMAT-AGNOSTIC first-level check of the references inside
|
|
3
|
+
* an expression, shared by Flow, Case, and BPMN.
|
|
4
|
+
*
|
|
5
|
+
* This is the piece each SDK's `build()` runs BEFORE it hands back the model to
|
|
6
|
+
* be serialized: cheap, high-confidence checks that catch a broken expression at
|
|
7
|
+
* the moment the SDK code runs, rather than three steps later at
|
|
8
|
+
* `uip maestro flow validate`. Its guiding rule is **conservative**: only flag
|
|
9
|
+
* what it can be certain of, so it never produces a misleading error. Deeper,
|
|
10
|
+
* heavier checks (does step X actually output `.foo`, is the comparison
|
|
11
|
+
* type-sound) belong to the `validate` CLI, which can afford them.
|
|
12
|
+
*
|
|
13
|
+
* The first thing it checks: every `$vars.<root>` reference resolves to a name the
|
|
14
|
+
* runtime will provide — a declared input, variable, or step/task/node. A
|
|
15
|
+
* reference to nothing is a definite bug (a typo, or a renamed step), and the
|
|
16
|
+
* runtime's variable bag is pre-declared, so an unknown root reads `null` forever
|
|
17
|
+
* and every guard built on it silently takes the wrong path.
|
|
18
|
+
*
|
|
19
|
+
* The second: the expression PARSES as JavaScript. That sounds like something a
|
|
20
|
+
* later rung would catch, and nothing did — `validate` checks the artifact's
|
|
21
|
+
* contract, not the contents of an expression string, so
|
|
22
|
+
* `$vars.searchIssues.output.issues[*]` reached a live run and faulted there
|
|
23
|
+
* (`[400008] Failed to evaluate the input collection variable`). `[*]` is
|
|
24
|
+
* JSONPath; the runtime evaluates JavaScript. Reference resolution alone could
|
|
25
|
+
* not see it, because the root (`searchIssues`) was perfectly real.
|
|
26
|
+
*
|
|
27
|
+
* It is deliberately NOT tied to any one format's model: a caller passes the set
|
|
28
|
+
* of valid roots (which it alone knows how to compute) and the expression string,
|
|
29
|
+
* and gets back diagnostics with no location. The caller stamps the location
|
|
30
|
+
* (which node / task / field) it came from.
|
|
31
|
+
*/
|
|
32
|
+
import * as ts from 'typescript';
|
|
33
|
+
/**
|
|
34
|
+
* A JSONPath wildcard/filter step — the spelling that reaches for JavaScript and
|
|
35
|
+
* misses. Used only to add a suggestion; the parse decides.
|
|
36
|
+
*/
|
|
37
|
+
const JSONPATH_WILDCARD = /\[\s*(?:\*|\?|'[^']*'|"[^"]*")\s*\]|\.\.[A-Za-z_$]/;
|
|
38
|
+
/**
|
|
39
|
+
* The first syntax error in `expr` read as a JavaScript EXPRESSION, or
|
|
40
|
+
* `undefined` when it parses.
|
|
41
|
+
*
|
|
42
|
+
* Wrapped in parentheses so it is parsed in expression position: a bare
|
|
43
|
+
* `{ a: 1 }` is a block with a label otherwise, and object literals are ordinary
|
|
44
|
+
* here. The newline before the closing paren keeps a trailing line comment from
|
|
45
|
+
* swallowing it.
|
|
46
|
+
*
|
|
47
|
+
* `ts.createSourceFile` is a parse, not an evaluation — nothing in `expr` runs.
|
|
48
|
+
* TypeScript is already a runtime dependency of this package (see
|
|
49
|
+
* `core/script-return.ts`, which infers a script's return type the same way), so
|
|
50
|
+
* this adds no dependency.
|
|
51
|
+
*/
|
|
52
|
+
function syntaxError(expr) {
|
|
53
|
+
const source = ts.createSourceFile('expression.ts', `(${expr}\n)`, ts.ScriptTarget.ES2020,
|
|
54
|
+
/* setParentNodes */ false);
|
|
55
|
+
// `parseDiagnostics` is where the scanner records syntax problems. It is not on
|
|
56
|
+
// the public `SourceFile` type, hence the cast; there is no public API that
|
|
57
|
+
// parses a fragment and hands back its syntax diagnostics without also running
|
|
58
|
+
// a full program's type-check.
|
|
59
|
+
const problems = source.parseDiagnostics;
|
|
60
|
+
if (!problems || problems.length === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
return ts.flattenDiagnosticMessageText(problems[0].messageText, ' ');
|
|
63
|
+
}
|
|
64
|
+
const DEFAULT_NAMESPACE = '$vars';
|
|
65
|
+
/**
|
|
66
|
+
* Blank out the CONTENTS of single- and double-quoted string literals, preserving
|
|
67
|
+
* length and the quotes, so a `$vars.`-looking substring INSIDE a literal is not
|
|
68
|
+
* mistaken for a reference.
|
|
69
|
+
*
|
|
70
|
+
* `js`${input('a')} === ${'$vars.notAName'}`` renders to
|
|
71
|
+
* `$vars.a === "$vars.notAName"`; only the first is a reference. Template literals
|
|
72
|
+
* (backticks) are LEFT INTACT on purpose — the SDK's `tmpl` emits real references
|
|
73
|
+
* inside their `${…}`, and a literal `$vars.` in the surrounding URL/message text
|
|
74
|
+
* is not a thing the serializer produces. Escapes are honored so a quote inside a
|
|
75
|
+
* literal does not end it early.
|
|
76
|
+
*/
|
|
77
|
+
function maskStringLiterals(expr) {
|
|
78
|
+
return expr.replace(/'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/g, (m) => m[0] + ' '.repeat(m.length - 2) + m[m.length - 1]);
|
|
79
|
+
}
|
|
80
|
+
const SEGMENT = /\??\.([A-Za-z_$][\w$]*)|\[(\d+)\]/g;
|
|
81
|
+
function escapeRegExp(s) {
|
|
82
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A `<namespace>.<root><path>` reference matcher. The root is a JS identifier;
|
|
86
|
+
* the path is any run of `.ident`, `?.ident`, or `[<digits>]` selectors — the
|
|
87
|
+
* same shape the platform's variable-path grammar admits. The leading
|
|
88
|
+
* `(?<![\w.$])` keeps `case` from matching inside `mycase` or as the `.case`
|
|
89
|
+
* member of something else — only a namespace at a token boundary counts (the
|
|
90
|
+
* engine's own `DetectIdentifiers` uses the same guard).
|
|
91
|
+
*/
|
|
92
|
+
function refRegex(namespace) {
|
|
93
|
+
return new RegExp(`(?<![\\w.$])${escapeRegExp(namespace)}\\.([A-Za-z_$][\\w$]*)((?:\\??\\.[A-Za-z_$][\\w$]*|\\[\\d+\\])*)`, 'g');
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Every `<namespace>.*` reference in an expression, in source order, with literals
|
|
97
|
+
* masked so a reference-shaped substring inside a string is not counted.
|
|
98
|
+
* `namespace` defaults to `$vars` (Flow); Case passes `case`.
|
|
99
|
+
*/
|
|
100
|
+
export function extractRefs(expr, namespace = DEFAULT_NAMESPACE) {
|
|
101
|
+
const masked = maskStringLiterals(expr);
|
|
102
|
+
const refs = [];
|
|
103
|
+
for (const m of masked.matchAll(refRegex(namespace))) {
|
|
104
|
+
const [raw, root, tail] = m;
|
|
105
|
+
const path = tail
|
|
106
|
+
? Array.from(tail.matchAll(SEGMENT), (s) => (s[2] !== undefined ? `[${s[2]}]` : s[1]))
|
|
107
|
+
: [];
|
|
108
|
+
refs.push({ root, path, raw });
|
|
109
|
+
}
|
|
110
|
+
return refs;
|
|
111
|
+
}
|
|
112
|
+
/** True when `a` and `b` are equal or one edit (insert/delete/substitute) apart. */
|
|
113
|
+
function editDistanceLE1(a, b) {
|
|
114
|
+
if (a === b)
|
|
115
|
+
return true;
|
|
116
|
+
const la = a.length;
|
|
117
|
+
const lb = b.length;
|
|
118
|
+
if (Math.abs(la - lb) > 1)
|
|
119
|
+
return false;
|
|
120
|
+
let i = 0;
|
|
121
|
+
while (i < la && i < lb && a[i] === b[i])
|
|
122
|
+
i++;
|
|
123
|
+
if (la === lb)
|
|
124
|
+
return a.slice(i + 1) === b.slice(i + 1); // substitution
|
|
125
|
+
return la > lb ? a.slice(i + 1) === b.slice(i) : a.slice(i) === b.slice(i + 1); // insert/delete
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The declared root `name` is most likely a typo of, if any: a case-only mismatch
|
|
129
|
+
* (strongest signal, any length) or — for names long enough that an edit is
|
|
130
|
+
* unlikely to be a coincidence (≥ 4 chars) — one edit away.
|
|
131
|
+
*/
|
|
132
|
+
function nearMiss(name, roots) {
|
|
133
|
+
const lower = name.toLowerCase();
|
|
134
|
+
let oneEdit;
|
|
135
|
+
for (const r of roots) {
|
|
136
|
+
const rl = r.toLowerCase();
|
|
137
|
+
if (rl === lower)
|
|
138
|
+
return r; // case-only difference — the strongest typo signal
|
|
139
|
+
if (oneEdit === undefined && Math.max(rl.length, lower.length) >= 4 && editDistanceLE1(lower, rl)) {
|
|
140
|
+
oneEdit = r;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return oneEdit;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* First-level check of ONE expression: every reference's root must resolve.
|
|
147
|
+
*
|
|
148
|
+
* Returns a diagnostic per unresolved root (deduped, so `$vars.x + $vars.x` is one
|
|
149
|
+
* message). An empty result means "nothing this check is sure is wrong" — never
|
|
150
|
+
* "fully valid"; the `validate` CLI is what proves the rest.
|
|
151
|
+
*/
|
|
152
|
+
export function checkExpression(expr, scope) {
|
|
153
|
+
const namespace = scope.namespace ?? DEFAULT_NAMESPACE;
|
|
154
|
+
const noun = scope.noun ?? 'input, variable, or step';
|
|
155
|
+
const diags = [];
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
// Syntax first: an expression that does not parse cannot have meaningful
|
|
158
|
+
// references, and reporting both would just be noise on one root cause.
|
|
159
|
+
const syntax = scope.syntax === 'javascript' ? syntaxError(expr) : undefined;
|
|
160
|
+
if (syntax) {
|
|
161
|
+
return [{
|
|
162
|
+
level: 'error',
|
|
163
|
+
code: 'EXPR_SYNTAX',
|
|
164
|
+
message: `Expression is not valid JavaScript: ${syntax}`,
|
|
165
|
+
...(JSONPATH_WILDCARD.test(expr)
|
|
166
|
+
? {
|
|
167
|
+
suggestion: 'This looks like JSONPath. The runtime evaluates JavaScript, so index or map '
|
|
168
|
+
+ 'explicitly — `…issues` for the whole array, `…issues.map(i => i.key)` for one field.',
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
}];
|
|
172
|
+
}
|
|
173
|
+
for (const ref of extractRefs(expr, namespace)) {
|
|
174
|
+
if (scope.roots.has(ref.root) || seen.has(ref.root))
|
|
175
|
+
continue;
|
|
176
|
+
seen.add(ref.root);
|
|
177
|
+
const near = nearMiss(ref.root, scope.roots);
|
|
178
|
+
diags.push({
|
|
179
|
+
level: 'error',
|
|
180
|
+
code: 'EXPR_UNDECLARED_REF',
|
|
181
|
+
message: `Expression references \`${namespace}.${ref.root}\`, but no ${noun} named "${ref.root}" is declared.`,
|
|
182
|
+
...(near ? { suggestion: near } : {}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return diags;
|
|
186
|
+
}
|