@dudousxd/nestjs-catalog 0.18.0 → 0.20.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/catalog.pipeline.d.ts +419 -3
- package/dist/catalog.pipeline.js +449 -27
- package/dist/client.d.ts +2 -2
- package/dist/client.js +23 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -4
- package/package.json +1 -1
package/dist/catalog.pipeline.js
CHANGED
|
@@ -9,14 +9,19 @@
|
|
|
9
9
|
* systems each believing they decide when a load runs.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.REDACTED_SECRET = exports.CATALOG_PIPELINE_STORE = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_CALL_CONTRACT = exports.WORKFLOW_EXECUTION_MODES = exports.WORKFLOW_STATUSES = exports.WORKFLOW_BRANCH_LABELS = exports.NODE_KIND_IS_REUSABLE = exports.REUSABLE_NODE_KINDS = exports.WORKFLOW_FILTER_MAX_VALUES = exports.WORKFLOW_FILTER_MAX_DEPTH = exports.WORKFLOW_FILTER_COLUMN_PATTERN = exports.WORKFLOW_FILTER_OPERATORS = exports.WORKFLOW_FILTER_PREDICATE_KINDS = exports.WORKFLOW_PREDICATE_KINDS = exports.WORKFLOW_ROW_GAP = exports.WORKFLOW_COLUMN_GAP = exports.WORKFLOW_NODE_HEIGHT = exports.WORKFLOW_NODE_WIDTH = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_NODE_KINDS = exports.WORKFLOW_SKIP_REASONS = exports.CODE_CONTEXT_CONTRACT = exports.TRANSFORM_RUNNER = exports.TRANSFORM_LANGUAGES = exports.CONNECTOR_KINDS = void 0;
|
|
12
|
+
exports.REDACTED_SECRET = exports.CATALOG_PIPELINE_STORE = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_CALL_CONTRACT = exports.WORKFLOW_EXECUTION_MODES = exports.WORKFLOW_STATUSES = exports.WORKFLOW_BRANCH_LABELS = exports.NODE_KIND_IS_REUSABLE = exports.REUSABLE_NODE_KINDS = exports.WORKFLOW_FILTER_MAX_VALUES = exports.WORKFLOW_FILTER_MAX_DEPTH = exports.WORKFLOW_FILTER_COLUMN_PATTERN = exports.WORKFLOW_FILTER_OPERATORS = exports.WORKFLOW_FILTER_PREDICATE_KINDS = exports.WORKFLOW_PREDICATE_KINDS = exports.WORKFLOW_CALL_MODES = exports.WORKFLOW_ROW_GAP = exports.WORKFLOW_COLUMN_GAP = exports.WORKFLOW_NODE_HEIGHT = exports.WORKFLOW_NODE_WIDTH = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_NODE_KINDS = exports.WORKFLOW_SKIP_REASONS = exports.CODE_CONTEXT_CONTRACT = exports.TRANSFORM_RUNNER = exports.TRANSFORM_LANGUAGES = exports.SOURCE_FORMATS = exports.CONNECTOR_KINDS = void 0;
|
|
13
13
|
exports.isConnectorKind = isConnectorKind;
|
|
14
|
+
exports.isSourceFormat = isSourceFormat;
|
|
15
|
+
exports.unreachableSourceFormat = unreachableSourceFormat;
|
|
14
16
|
exports.isTransformLanguage = isTransformLanguage;
|
|
15
17
|
exports.isWorkflowSkipReason = isWorkflowSkipReason;
|
|
16
18
|
exports.isWorkflowNodeKind = isWorkflowNodeKind;
|
|
17
19
|
exports.unreachableNodeKind = unreachableNodeKind;
|
|
18
20
|
exports.workflowColumnX = workflowColumnX;
|
|
19
21
|
exports.workflowRowY = workflowRowY;
|
|
22
|
+
exports.isWorkflowCallMode = isWorkflowCallMode;
|
|
23
|
+
exports.unreachableCallMode = unreachableCallMode;
|
|
24
|
+
exports.workflowCallMode = workflowCallMode;
|
|
20
25
|
exports.isWorkflowPredicateKind = isWorkflowPredicateKind;
|
|
21
26
|
exports.unreachablePredicateKind = unreachablePredicateKind;
|
|
22
27
|
exports.isWorkflowIfPredicate = isWorkflowIfPredicate;
|
|
@@ -34,6 +39,7 @@ exports.applyReusableNode = applyReusableNode;
|
|
|
34
39
|
exports.isReusableNodeBody = isReusableNodeBody;
|
|
35
40
|
exports.reusableNodeBodyOf = reusableNodeBodyOf;
|
|
36
41
|
exports.describeVersionPin = describeVersionPin;
|
|
42
|
+
exports.describeLiveVersion = describeLiveVersion;
|
|
37
43
|
exports.isWorkflowBranchLabel = isWorkflowBranchLabel;
|
|
38
44
|
exports.isWorkflowStatus = isWorkflowStatus;
|
|
39
45
|
exports.isWorkflowExecutionMode = isWorkflowExecutionMode;
|
|
@@ -47,6 +53,8 @@ exports.workflowGraphHash = workflowGraphHash;
|
|
|
47
53
|
exports.isWorkflowNode = isWorkflowNode;
|
|
48
54
|
exports.isWorkflowEdge = isWorkflowEdge;
|
|
49
55
|
exports.supportsWorkflows = supportsWorkflows;
|
|
56
|
+
exports.supportsWorkflowReleases = supportsWorkflowReleases;
|
|
57
|
+
exports.liveWorkflowVersion = liveWorkflowVersion;
|
|
50
58
|
exports.supportsTransformRevisions = supportsTransformRevisions;
|
|
51
59
|
exports.supportsTransformPins = supportsTransformPins;
|
|
52
60
|
exports.supportsReusableNodes = supportsReusableNodes;
|
|
@@ -90,6 +98,57 @@ exports.CONNECTOR_KINDS = [
|
|
|
90
98
|
function isConnectorKind(value) {
|
|
91
99
|
return exports.CONNECTOR_KINDS.some((kind) => kind === value);
|
|
92
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* How the bytes behind a `file` or `s3` connector are read as records.
|
|
103
|
+
*
|
|
104
|
+
* A list rather than a loose string, for the reason {@link CONNECTOR_KINDS} is
|
|
105
|
+
* one: this used to be compared against string literals in the parser and
|
|
106
|
+
* spelled out again in a dropdown, and the two had no way to disagree loudly.
|
|
107
|
+
* The parser's chain also *ended* in JSON, so a format it did not recognise was
|
|
108
|
+
* not refused — it was read as JSON, and a spreadsheet handed to `JSON.parse`
|
|
109
|
+
* fails with a syntax error that names a byte offset rather than the format.
|
|
110
|
+
*
|
|
111
|
+
* `xlsx` is the odd one and is named for what it is: the only member whose
|
|
112
|
+
* payload is binary. The other three are text, and everything that reads them
|
|
113
|
+
* decodes the bytes first. Anything deciding something *per format* narrows
|
|
114
|
+
* against this and answers {@link unreachableSourceFormat}.
|
|
115
|
+
*/
|
|
116
|
+
exports.SOURCE_FORMATS = [
|
|
117
|
+
/** Delimited text with a header row. The delimiter is configurable. */
|
|
118
|
+
'csv',
|
|
119
|
+
/** One JSON value per line. */
|
|
120
|
+
'ndjson',
|
|
121
|
+
/** A JSON document, optionally with the array nested in an envelope. */
|
|
122
|
+
'json',
|
|
123
|
+
/**
|
|
124
|
+
* A spreadsheet workbook — binary, and the only member that is.
|
|
125
|
+
*
|
|
126
|
+
* Named for the modern extension, but the reader identifies the container
|
|
127
|
+
* from its own bytes, so the legacy `.xls` and the macro-enabled `.xlsm` are
|
|
128
|
+
* this format too rather than three names for one decision.
|
|
129
|
+
*/
|
|
130
|
+
'xlsx',
|
|
131
|
+
];
|
|
132
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
133
|
+
function isSourceFormat(value) {
|
|
134
|
+
return exports.SOURCE_FORMATS.some((format) => format === value);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The format that never compiles quietly.
|
|
138
|
+
*
|
|
139
|
+
* The {@link unreachableNodeKind} of formats, and it exists for the same reason:
|
|
140
|
+
* a member added to {@link SOURCE_FORMATS} without a branch in the parser should
|
|
141
|
+
* be a type error naming the file, not a connector that offers a format in a
|
|
142
|
+
* dropdown and then reads the file as JSON.
|
|
143
|
+
*
|
|
144
|
+
* It throws as well as failing to compile, because a connector config is JSON
|
|
145
|
+
* that outlives the build that wrote it: a `format` stored by a newer deployment
|
|
146
|
+
* and read by an older one is possible, and falling back to a default for it
|
|
147
|
+
* would be exactly the silent path this closes.
|
|
148
|
+
*/
|
|
149
|
+
function unreachableSourceFormat(format, where) {
|
|
150
|
+
throw new Error(`${where} does not handle a source format of ${JSON.stringify(format)}. The format list and every decision made per format are meant to move together.`);
|
|
151
|
+
}
|
|
93
152
|
/**
|
|
94
153
|
* TypeScript is Node's own type stripping, so it costs no compiler and no build
|
|
95
154
|
* step — and types are erased, never checked. A transform with a wrong type
|
|
@@ -318,6 +377,109 @@ function workflowColumnX(column) {
|
|
|
318
377
|
function workflowRowY(row) {
|
|
319
378
|
return row * (exports.WORKFLOW_NODE_HEIGHT + exports.WORKFLOW_ROW_GAP);
|
|
320
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* What a {@link WorkflowCallNode} puts on the wire, and the whole of it.
|
|
382
|
+
*
|
|
383
|
+
* ## Why there is a second mode at all
|
|
384
|
+
*
|
|
385
|
+
* The catalog could not call a workflow that does not know about the catalog.
|
|
386
|
+
* A `call` node wraps the author's `config` in a {@link WorkflowCallEnvelope},
|
|
387
|
+
* so a workflow that already exists — one registered years before this package,
|
|
388
|
+
* whose body reads `data["proc"]` — receives `{catalog: {...}, input: {proc:
|
|
389
|
+
* ...}}` and dies on the first key it looks for. The only repair available was
|
|
390
|
+
* to edit the callee, which inverts the dependency exactly the wrong way round:
|
|
391
|
+
* every workflow anybody wanted to call would have to start depending on this
|
|
392
|
+
* package's contract, and a Python workflow registered in another repository
|
|
393
|
+
* would have to be changed to be reachable from a graph.
|
|
394
|
+
*
|
|
395
|
+
* ## Why the nesting is not being loosened instead
|
|
396
|
+
*
|
|
397
|
+
* The envelope nests for one stated reason, which is on {@link
|
|
398
|
+
* WorkflowCallEnvelope}: an author's parameter called `runId` must not be able
|
|
399
|
+
* to shadow the run id. That reason is sound and it is not being weakened. It
|
|
400
|
+
* simply does not reach the plain mode, because a plain call sends **no catalog
|
|
401
|
+
* metadata at all** — there is no `runId`, no `nodeId`, no contract number on
|
|
402
|
+
* the wire, so there is nothing a parameter could shadow. The flat payload is
|
|
403
|
+
* not the envelope with its guard removed; it is a different, smaller promise.
|
|
404
|
+
*
|
|
405
|
+
* ## What the plain mode costs, which is not small
|
|
406
|
+
*
|
|
407
|
+
* No `runId` and no `nodeId` means the callee has no key to stage rows under.
|
|
408
|
+
* Rows travel through the stage store addressed by `(runId, nodeId, batch)` —
|
|
409
|
+
* see {@link WorkflowStageRef} — and a callee that was told neither cannot
|
|
410
|
+
* write where the next node would read. So a plain call **cannot return rows to
|
|
411
|
+
* the graph**, and that is not a convention anybody could follow more carefully:
|
|
412
|
+
* it is arithmetic. `validateWorkflow` refuses a plain call with an outbound
|
|
413
|
+
* edge for exactly this reason (`call-plain-has-output`), and its return value
|
|
414
|
+
* is not read as a row count — see {@link readWorkflowCallOutput} for the shape
|
|
415
|
+
* that is deliberately *not* consulted on this path.
|
|
416
|
+
*
|
|
417
|
+
* A plain call is therefore for its **effect**: run the thing, and let something
|
|
418
|
+
* else in the graph produce what gets committed.
|
|
419
|
+
*
|
|
420
|
+
* ## Why a mode on the node rather than a second node kind or a boolean
|
|
421
|
+
*
|
|
422
|
+
* A boolean is the shape {@link WORKFLOW_PREDICATE_KINDS} argues against one
|
|
423
|
+
* level down, and for the reason it gives: a `plain?: boolean` beside a future
|
|
424
|
+
* third wire format is two optional flags whose combinations nobody defined, and
|
|
425
|
+
* every reader invents its own rule for which wins. A closed list with an
|
|
426
|
+
* exhaustiveness guard ({@link unreachableCallMode}) makes a third format a
|
|
427
|
+
* compile error naming the files that have to answer for it.
|
|
428
|
+
*
|
|
429
|
+
* A second node *kind* was the other candidate and it is too big. The kind list
|
|
430
|
+
* is deliberately small and every entry earns it by doing something no wiring
|
|
431
|
+
* can express (see {@link WORKFLOW_NODE_KINDS}). A plain call does the same
|
|
432
|
+
* thing a call does at the level the graph reasons about — it hands this
|
|
433
|
+
* position to a workflow somebody else registered, pinned by name and version.
|
|
434
|
+
* What differs is the payload. Splitting the kind would duplicate `callName`,
|
|
435
|
+
* `callVersion`, `config`, the pin check, the picker, the plan entry and the
|
|
436
|
+
* canvas node for a difference of one field, and every place that today writes
|
|
437
|
+
* `node.kind === 'call'` would have to remember to write both — which is the
|
|
438
|
+
* hand-maintained list going quiet that {@link NODE_KIND_IS_REUSABLE} exists to
|
|
439
|
+
* stop.
|
|
440
|
+
*/
|
|
441
|
+
exports.WORKFLOW_CALL_MODES = [
|
|
442
|
+
/**
|
|
443
|
+
* The child is handed a {@link WorkflowCallEnvelope}: the catalog's metadata
|
|
444
|
+
* under `catalog`, the author's parameters under `input`. The callee can stage
|
|
445
|
+
* rows back for the graph, and it has to have been written for this catalog.
|
|
446
|
+
*/
|
|
447
|
+
'envelope',
|
|
448
|
+
/**
|
|
449
|
+
* The child is handed {@link WorkflowCallNode.config} verbatim, with nothing
|
|
450
|
+
* added and nothing wrapped. The callee needs to know nothing about the
|
|
451
|
+
* catalog — and cannot return rows to it.
|
|
452
|
+
*/
|
|
453
|
+
'plain',
|
|
454
|
+
];
|
|
455
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
456
|
+
function isWorkflowCallMode(value) {
|
|
457
|
+
return exports.WORKFLOW_CALL_MODES.some((mode) => mode === value);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* {@link unreachableNodeKind}, one level down, and for the identical reason.
|
|
461
|
+
*
|
|
462
|
+
* Every branch over {@link WorkflowCallMode} ends here, so a third wire format
|
|
463
|
+
* added to the list without a rule for building its payload, hashing it,
|
|
464
|
+
* validating it or reading its answer is a type error naming the file. It throws
|
|
465
|
+
* as well, because a mode arrives as JSON out of a column and a build older than
|
|
466
|
+
* the data is a thing that happens.
|
|
467
|
+
*/
|
|
468
|
+
function unreachableCallMode(mode, where) {
|
|
469
|
+
throw new Error(`${where} has no rule for the call mode ${JSON.stringify(mode)}. It was added to WORKFLOW_CALL_MODES without teaching this code what to put on the wire for it, and guessing would send a workflow a payload nobody authored.`);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* The mode this call node runs in, with the default applied once.
|
|
473
|
+
*
|
|
474
|
+
* Absent means `'envelope'` — that is what every node stored before the field
|
|
475
|
+
* existed is, and reading it as anything else would silently change what a
|
|
476
|
+
* deployment's graphs already do. One function so that the store, the runner,
|
|
477
|
+
* the hash and the canvas cannot each carry their own `?? 'envelope'` and have
|
|
478
|
+
* one of them drift.
|
|
479
|
+
*/
|
|
480
|
+
function workflowCallMode(node) {
|
|
481
|
+
return node.callMode ?? 'envelope';
|
|
482
|
+
}
|
|
321
483
|
/**
|
|
322
484
|
* The kinds of test an {@link WorkflowIfNode} can make.
|
|
323
485
|
*
|
|
@@ -977,6 +1139,44 @@ function describeVersionPin(version, subject) {
|
|
|
977
1139
|
detail: `This node runs v${version} of ${subject} and stays there while it is edited elsewhere. Moving to a newer version is an edit to this graph, so it has a diff and a version of its own. A pin to a version that has been superseded more than ${catalog_workspace_1.CATALOG_REVISION_LIMIT} times can no longer be produced, and the run fails saying so rather than quietly using the latest.`,
|
|
978
1140
|
};
|
|
979
1141
|
}
|
|
1142
|
+
/**
|
|
1143
|
+
* The same question about a whole graph: does it follow its latest save, or does
|
|
1144
|
+
* it run a version somebody chose?
|
|
1145
|
+
*
|
|
1146
|
+
* Shares {@link VersionPinCopy} and its two labels with
|
|
1147
|
+
* {@link describeVersionPin} deliberately. A console that said "pinned to v6"
|
|
1148
|
+
* about a transform node and invented different words for a graph would be two
|
|
1149
|
+
* vocabularies for one idea, and the reader would have to work out whether they
|
|
1150
|
+
* meant the same thing — which is the confusion the whole notion of a pin exists
|
|
1151
|
+
* to remove.
|
|
1152
|
+
*
|
|
1153
|
+
* The *detail* differs, and only where the facts do. Two of them:
|
|
1154
|
+
*
|
|
1155
|
+
* - A node's pin can outlive the revision it names, because `catalog_revision`
|
|
1156
|
+
* is capped. A graph's cannot: releases are never evicted, precisely because
|
|
1157
|
+
* the one a live pointer names is the graph production is running. So this
|
|
1158
|
+
* copy makes no eviction caveat, and must not acquire one.
|
|
1159
|
+
* - Following the latest is *cheap* for a node — everybody moves together, which
|
|
1160
|
+
* is often the point. For a graph it means editing is deploying, which is the
|
|
1161
|
+
* hazard this field was added to remove. So the unpinned sentence here is a
|
|
1162
|
+
* warning where the node's is a trade-off.
|
|
1163
|
+
*/
|
|
1164
|
+
function describeLiveVersion(workflow) {
|
|
1165
|
+
if (workflow.liveVersion === undefined) {
|
|
1166
|
+
return {
|
|
1167
|
+
pinned: false,
|
|
1168
|
+
label: 'follows the latest',
|
|
1169
|
+
detail: `This graph runs whatever its latest save holds, currently v${workflow.version}. Editing it is therefore the same act as deploying it: the next scheduled window runs what was last saved, with nobody having decided that it should. Releasing a version and setting it live is what separates the two.`,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
return {
|
|
1173
|
+
pinned: true,
|
|
1174
|
+
label: `running v${workflow.liveVersion}`,
|
|
1175
|
+
detail: workflow.liveVersion === workflow.version
|
|
1176
|
+
? `This graph runs the released v${workflow.liveVersion}, which is also its latest save. Editing it from here changes nothing about what runs until a new version is released and set live.`
|
|
1177
|
+
: `This graph runs the released v${workflow.liveVersion} while its latest save is v${workflow.version}. The edits since then are stored and are not running; setting a newer release live is what deploys them, and setting an older one live is a rollback.`,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
980
1180
|
/**
|
|
981
1181
|
* Which side of an {@link WorkflowIfNode} a wire leaves by.
|
|
982
1182
|
*
|
|
@@ -1128,6 +1328,18 @@ exports.WORKFLOW_CALL_CONTRACT = 1;
|
|
|
1128
1328
|
* schema for a workflow's output anywhere in the durable contract, and no way
|
|
1129
1329
|
* to reach one if there were. So the check is here, at the one moment the
|
|
1130
1330
|
* answer exists, and it names what it saw.
|
|
1331
|
+
*
|
|
1332
|
+
* ## Not called at all for a plain call, which is a decision and not an omission
|
|
1333
|
+
*
|
|
1334
|
+
* A plain call is handed no run id and no node id, so its callee could not have
|
|
1335
|
+
* staged anything under `(runId, nodeId, batch)` even if it wanted to. Running
|
|
1336
|
+
* this over its answer would be worse than pointless in both directions: a
|
|
1337
|
+
* callee that happens to return `{batches, rowCount}` meaning something else
|
|
1338
|
+
* entirely would have this graph go and read a stage that does not exist, and a
|
|
1339
|
+
* callee returning one of the two would fail the node over a key it was never
|
|
1340
|
+
* told about. So the plain path reports zero rows unconditionally and reads
|
|
1341
|
+
* nothing — see `WorkflowRunnerService.callOutput`, which is where that is
|
|
1342
|
+
* enforced rather than merely intended.
|
|
1131
1343
|
*/
|
|
1132
1344
|
function readWorkflowCallOutput(value) {
|
|
1133
1345
|
if (typeof value !== 'object' || value === null)
|
|
@@ -1182,6 +1394,13 @@ exports.WORKFLOW_ISSUE_CODES = [
|
|
|
1182
1394
|
'dead-end',
|
|
1183
1395
|
'transform-not-named',
|
|
1184
1396
|
'call-not-named',
|
|
1397
|
+
/**
|
|
1398
|
+
* A plain call wired into something. See {@link WORKFLOW_CALL_MODES}: a plain
|
|
1399
|
+
* call is told no run id and no node id, so it has nowhere to stage rows and
|
|
1400
|
+
* always passes on none — and every node that can sit downstream of a call
|
|
1401
|
+
* consumes rows and nothing else.
|
|
1402
|
+
*/
|
|
1403
|
+
'call-plain-has-output',
|
|
1185
1404
|
'if-not-named',
|
|
1186
1405
|
'if-threshold-invalid',
|
|
1187
1406
|
'if-needs-one-input',
|
|
@@ -1238,11 +1457,17 @@ function validateWorkflow(graph) {
|
|
|
1238
1457
|
return issues;
|
|
1239
1458
|
const { outgoing, incoming } = buildAdjacency(nodes, edges);
|
|
1240
1459
|
const originators = nodes.filter(originatesRows);
|
|
1460
|
+
// Deliberately a *different* set from `originators` — see `runsWithoutInput`.
|
|
1461
|
+
const roots = nodes.filter(runsWithoutInput);
|
|
1241
1462
|
const sinks = nodes.filter((node) => node.kind === 'sink');
|
|
1242
1463
|
checkNodeWiring(nodes, incoming, outgoing, issues);
|
|
1243
1464
|
checkEndpoints(originators, sinks, issues);
|
|
1244
1465
|
checkBranches(edges, byId, issues);
|
|
1245
|
-
|
|
1466
|
+
checkPlainCallOutputs(nodes, outgoing, byId, issues);
|
|
1467
|
+
// `roots` rather than `originators`, because this walks the graph forwards to
|
|
1468
|
+
// find which sinks a filter can narrow and a plain call is a perfectly
|
|
1469
|
+
// ordinary ancestor of nothing at all — it just never contributes rows.
|
|
1470
|
+
checkFilterNarrowing({ nodes, edges }, roots, outgoing, issues);
|
|
1246
1471
|
const looped = findCycle(nodes, incoming, outgoing);
|
|
1247
1472
|
if (looped) {
|
|
1248
1473
|
issues.push({
|
|
@@ -1254,27 +1479,127 @@ function validateWorkflow(graph) {
|
|
|
1254
1479
|
// only unreachable *because* of the cycle, which points at the wrong boxes.
|
|
1255
1480
|
return issues;
|
|
1256
1481
|
}
|
|
1257
|
-
checkReachability(nodes,
|
|
1482
|
+
checkReachability(nodes, roots, sinks, incoming, outgoing, issues);
|
|
1258
1483
|
return issues;
|
|
1259
1484
|
}
|
|
1485
|
+
/**
|
|
1486
|
+
* A plain call may not feed anything, and this is where that is refused.
|
|
1487
|
+
*
|
|
1488
|
+
* ## The rule, stated once
|
|
1489
|
+
*
|
|
1490
|
+
* **A call node in `plain` mode must have no outbound edge.** Not "must not feed
|
|
1491
|
+
* a sink", not "must not be the only thing feeding a sink" — no outbound edge at
|
|
1492
|
+
* all, and the reason it collapses that far is that there is no weaker version
|
|
1493
|
+
* of it that means anything. Every node kind that can sit downstream of a call —
|
|
1494
|
+
* transform, if, filter, sink — consumes rows and *only* rows. There is no
|
|
1495
|
+
* ordering-only wire in this model. So "a plain call with downstream nodes
|
|
1496
|
+
* expecting rows" and "a plain call with an outbound edge" are the same set.
|
|
1497
|
+
*
|
|
1498
|
+
* ## How it lands against the rest of the validator
|
|
1499
|
+
*
|
|
1500
|
+
* Three rules had to move for this one to be statable, and each moved in a way
|
|
1501
|
+
* that is narrower than it looks:
|
|
1502
|
+
*
|
|
1503
|
+
* - **`no-source`.** A plain call is no longer something that "reads": see
|
|
1504
|
+
* {@link originatesRows}. Without that, a graph of `plain call → sink` would
|
|
1505
|
+
* have passed the check that a graph has something producing rows, and then
|
|
1506
|
+
* committed an empty snapshot over whatever was live. It is refused here
|
|
1507
|
+
* first, and would be refused by `no-source` even if this check were deleted.
|
|
1508
|
+
* - **`dead-end` — every path reaches the sink.** A plain call reaches no sink
|
|
1509
|
+
* by construction, so it is exempt, and the exemption is exactly one node
|
|
1510
|
+
* wide: nothing can be *behind* a plain call, because a plain call has no
|
|
1511
|
+
* outbound edge, so no other node's route to the sink can run through one.
|
|
1512
|
+
* The message `dead-end` carries is "it would be computed and thrown away",
|
|
1513
|
+
* and that is precisely what a plain call is not — its effect is the point,
|
|
1514
|
+
* and it has already happened by the time the sink commits.
|
|
1515
|
+
* - **the one-sink rule.** Untouched. A graph still needs a sink and still needs
|
|
1516
|
+
* something that originates rows, and a plain call is now neither, so plain
|
|
1517
|
+
* calls cannot be a graph on their own — the rows come from a source or from
|
|
1518
|
+
* an envelope call, exactly as before.
|
|
1519
|
+
*
|
|
1520
|
+
* What a legal plain call looks like, then: `source → sink` with `source →
|
|
1521
|
+
* plainCall` beside it — the effect runs after the source and the load commits
|
|
1522
|
+
* the source's rows — or a plain call with nothing wired to it at all, which
|
|
1523
|
+
* runs at some point in the topological order and is reported like any other
|
|
1524
|
+
* node. Wiring a source into a plain call that is the source's *only* outbound
|
|
1525
|
+
* edge is still refused, by `dead-end`, pointed at the source: those rows really
|
|
1526
|
+
* would be fetched and dropped.
|
|
1527
|
+
*/
|
|
1528
|
+
function checkPlainCallOutputs(nodes, outgoing, byId, issues) {
|
|
1529
|
+
for (const node of nodes) {
|
|
1530
|
+
if (node.kind !== 'call' || workflowCallMode(node) !== 'plain')
|
|
1531
|
+
continue;
|
|
1532
|
+
const fed = outgoing.get(node.id) ?? [];
|
|
1533
|
+
if (fed.length === 0)
|
|
1534
|
+
continue;
|
|
1535
|
+
issues.push({
|
|
1536
|
+
code: 'call-plain-has-output',
|
|
1537
|
+
nodeIds: [node.id, ...fed],
|
|
1538
|
+
message: `Call node "${node.name}" (${node.id}) is a plain call and is wired into ${listNodes(fed, byId)}, which cannot work. A plain call sends this node's parameters to ${node.callName || 'the workflow it calls'} verbatim and nothing else — no run id and no node id — so the workflow it calls is told no key to write rows under and this node always passes on zero. Everything downstream would run on an empty input and the load would commit an empty snapshot without anything failing. Unwire it: a plain call is run for its effect, and something else in this graph produces what gets committed. If the workflow you are calling is meant to produce rows for this graph, it needs the envelope instead, which is what tells it where to put them.`,
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
/** `"a" (n1) and "b" (n2)`, for a message that has to name several boxes. */
|
|
1543
|
+
function listNodes(ids, byId) {
|
|
1544
|
+
return ids
|
|
1545
|
+
.map((id) => {
|
|
1546
|
+
const node = byId.get(id);
|
|
1547
|
+
return node ? `"${node.name}" (${id})` : `"${id}"`;
|
|
1548
|
+
})
|
|
1549
|
+
.join(' and ');
|
|
1550
|
+
}
|
|
1260
1551
|
/**
|
|
1261
1552
|
* Whether a node can produce rows without anything wired into it.
|
|
1262
1553
|
*
|
|
1263
|
-
* A source obviously can.
|
|
1264
|
-
* `call` kind changes rather than extends: the workflow it hands off to
|
|
1265
|
-
* itself read from a system, so a graph of `call → sink` is a real pipeline
|
|
1266
|
-
* refusing it for having "no source" would be false. What is not weakened is
|
|
1554
|
+
* A source obviously can. An **envelope call** node can too, and this is the one
|
|
1555
|
+
* rule the `call` kind changes rather than extends: the workflow it hands off to
|
|
1556
|
+
* may itself read from a system, so a graph of `call → sink` is a real pipeline
|
|
1557
|
+
* and refusing it for having "no source" would be false. What is not weakened is
|
|
1267
1558
|
* that a graph still needs *something* that originates rows and *something*
|
|
1268
1559
|
* that commits them — a graph of transforms alone is still refused.
|
|
1269
1560
|
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1561
|
+
* A **plain** call is not one of them, and that is the load-bearing half of this
|
|
1562
|
+
* function now. A plain call is told no run id and no node id, so it has no key
|
|
1563
|
+
* to stage rows under and cannot produce any — see {@link WORKFLOW_CALL_MODES}.
|
|
1564
|
+
* Counting it here would let `plain call → sink` past `no-source` and commit an
|
|
1565
|
+
* empty snapshot over whatever was live, which is the exact silence this file is
|
|
1566
|
+
* arranged against.
|
|
1567
|
+
*
|
|
1568
|
+
* ## This used to be the reachability root set as well, and no longer is
|
|
1569
|
+
*
|
|
1570
|
+
* It said so, and the reason it gave was that it made the root set conservative.
|
|
1571
|
+
* The plain mode splits the two questions, because the answers genuinely differ:
|
|
1572
|
+
* a plain call **runs** with nothing wired into it (so it is a root, and calling
|
|
1573
|
+
* it unreachable would be false — it would run) and **produces nothing** (so it
|
|
1574
|
+
* is not something that reads). One function answering both would have to be
|
|
1575
|
+
* wrong about one of them. See {@link runsWithoutInput} for the other half.
|
|
1274
1576
|
*/
|
|
1275
1577
|
function originatesRows(node) {
|
|
1578
|
+
if (node.kind === 'source')
|
|
1579
|
+
return true;
|
|
1580
|
+
return node.kind === 'call' && workflowCallMode(node) === 'envelope';
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Whether a node runs whether or not anything is wired into it.
|
|
1584
|
+
*
|
|
1585
|
+
* The root set for reachability, which used to be {@link originatesRows} and is
|
|
1586
|
+
* now its own question — see the note there. Every call node is one of these,
|
|
1587
|
+
* both modes: a call with no inbound edge sits at in-degree zero in the
|
|
1588
|
+
* topological order and is dispatched like anything else, so reporting it as
|
|
1589
|
+
* "not reachable from any source, so it would never run" would be a message that
|
|
1590
|
+
* is simply untrue.
|
|
1591
|
+
*
|
|
1592
|
+
* Every call node rather than only the unwired ones, for the reason the old
|
|
1593
|
+
* function gave: it keeps a mid-graph call node from making everything
|
|
1594
|
+
* downstream of it look unreachable when its own upstream is fine.
|
|
1595
|
+
*/
|
|
1596
|
+
function runsWithoutInput(node) {
|
|
1276
1597
|
return node.kind === 'source' || node.kind === 'call';
|
|
1277
1598
|
}
|
|
1599
|
+
/** Whether this node is a call that can never hand rows back to the graph. */
|
|
1600
|
+
function isPlainCall(node) {
|
|
1601
|
+
return node.kind === 'call' && workflowCallMode(node) === 'plain';
|
|
1602
|
+
}
|
|
1278
1603
|
/**
|
|
1279
1604
|
* Index the nodes by id, reporting the ids that cannot be used as one.
|
|
1280
1605
|
*
|
|
@@ -1811,12 +2136,17 @@ function peelTails(leftover, outgoing) {
|
|
|
1811
2136
|
}
|
|
1812
2137
|
return leftover;
|
|
1813
2138
|
}
|
|
1814
|
-
/**
|
|
1815
|
-
|
|
1816
|
-
|
|
2139
|
+
/**
|
|
2140
|
+
* Nodes that nothing reading reaches, and nodes that reach no sink.
|
|
2141
|
+
*
|
|
2142
|
+
* `roots` is {@link runsWithoutInput} and not {@link originatesRows}: this asks
|
|
2143
|
+
* what would *run*, and a plain call runs whether anything feeds it or not.
|
|
2144
|
+
*/
|
|
2145
|
+
function checkReachability(nodes, roots, sinks, incoming, outgoing, issues) {
|
|
2146
|
+
const reachableFromSources = walk(roots.map((node) => node.id), outgoing);
|
|
1817
2147
|
const reachesASink = walk(sinks.map((sink) => sink.id), incoming);
|
|
1818
2148
|
for (const node of nodes) {
|
|
1819
|
-
if (
|
|
2149
|
+
if (roots.length > 0 && !reachableFromSources.has(node.id)) {
|
|
1820
2150
|
issues.push({
|
|
1821
2151
|
code: 'unreachable',
|
|
1822
2152
|
nodeIds: [node.id],
|
|
@@ -1824,6 +2154,15 @@ function checkReachability(nodes, originators, sinks, incoming, outgoing, issues
|
|
|
1824
2154
|
});
|
|
1825
2155
|
continue;
|
|
1826
2156
|
}
|
|
2157
|
+
// The one exemption the plain mode buys, and it is exactly one node wide.
|
|
2158
|
+
// A plain call reaches no sink by construction — `call-plain-has-output`
|
|
2159
|
+
// refuses it an outbound edge — so `dead-end` would fire on every one of
|
|
2160
|
+
// them, with a message ("it would be computed and thrown away") that is
|
|
2161
|
+
// false about it: the effect it was run for has happened. Nothing can hide
|
|
2162
|
+
// behind this, because nothing can be downstream of a plain call, so no
|
|
2163
|
+
// other node's route to the sink can run through one.
|
|
2164
|
+
if (isPlainCall(node))
|
|
2165
|
+
continue;
|
|
1827
2166
|
if (sinks.length > 0 && !reachesASink.has(node.id)) {
|
|
1828
2167
|
issues.push({
|
|
1829
2168
|
code: 'dead-end',
|
|
@@ -2059,6 +2398,13 @@ function canonicalNode(node) {
|
|
|
2059
2398
|
node.callName,
|
|
2060
2399
|
node.callVersion,
|
|
2061
2400
|
sortedEntries(node.config),
|
|
2401
|
+
// Appended only for the non-default mode, exactly as `edge.branch` above
|
|
2402
|
+
// is appended only when there is a label. Every call node in every
|
|
2403
|
+
// deployment today is an envelope call — whether it says so or says
|
|
2404
|
+
// nothing — so every one of them hashes to the string it always did and
|
|
2405
|
+
// no stored graph is renumbered by picking up this release. See
|
|
2406
|
+
// `canonicalCallMode` for why the two spellings must fold together.
|
|
2407
|
+
...canonicalCallMode(workflowCallMode(node)),
|
|
2062
2408
|
]);
|
|
2063
2409
|
}
|
|
2064
2410
|
if (node.kind === 'if') {
|
|
@@ -2103,6 +2449,27 @@ function canonicalNode(node) {
|
|
|
2103
2449
|
* no version bump and no diff — which is precisely the silence this feature was
|
|
2104
2450
|
* built to end.
|
|
2105
2451
|
*/
|
|
2452
|
+
/**
|
|
2453
|
+
* The call mode, as zero or one trailing hash component.
|
|
2454
|
+
*
|
|
2455
|
+
* Zero for `envelope`, and that is the opposite choice from {@link
|
|
2456
|
+
* canonicalReuse} beside it — for the opposite reason. There, absent and present
|
|
2457
|
+
* mean genuinely different things ("follow the latest" against "pinned to v1"),
|
|
2458
|
+
* so they must hash differently. Here absent and `'envelope'` are one behaviour
|
|
2459
|
+
* spelled two ways: a node that says `callMode: 'envelope'` puts precisely the
|
|
2460
|
+
* same bytes on the wire as a node that says nothing. A fingerprint that told
|
|
2461
|
+
* them apart would report an edit when a canvas normalised the field, which is
|
|
2462
|
+
* the cosmetic version bump {@link workflowGraphHash} exists to not do.
|
|
2463
|
+
*
|
|
2464
|
+
* `plain` earns its component, because it changes what the child receives.
|
|
2465
|
+
*/
|
|
2466
|
+
function canonicalCallMode(mode) {
|
|
2467
|
+
if (mode === 'envelope')
|
|
2468
|
+
return [];
|
|
2469
|
+
if (mode === 'plain')
|
|
2470
|
+
return ['plain'];
|
|
2471
|
+
return unreachableCallMode(mode, 'workflowGraphHash');
|
|
2472
|
+
}
|
|
2106
2473
|
function canonicalReuse(node) {
|
|
2107
2474
|
if (node.useId === undefined)
|
|
2108
2475
|
return [];
|
|
@@ -2211,18 +2578,8 @@ function isWorkflowNode(value) {
|
|
|
2211
2578
|
if (kind === 'sink') {
|
|
2212
2579
|
return typeof Reflect.get(value, 'targetType') === 'string';
|
|
2213
2580
|
}
|
|
2214
|
-
if (kind === 'call')
|
|
2215
|
-
|
|
2216
|
-
// a stored call node missing its version is a node that would run whatever
|
|
2217
|
-
// is registered today, which is the failure the pin exists to remove — and
|
|
2218
|
-
// a graph that half-narrows is a load that runs nine nodes of ten.
|
|
2219
|
-
const config = Reflect.get(value, 'config');
|
|
2220
|
-
return (typeof Reflect.get(value, 'callName') === 'string' &&
|
|
2221
|
-
typeof Reflect.get(value, 'callVersion') === 'string' &&
|
|
2222
|
-
typeof config === 'object' &&
|
|
2223
|
-
config !== null &&
|
|
2224
|
-
!Array.isArray(config));
|
|
2225
|
-
}
|
|
2581
|
+
if (kind === 'call')
|
|
2582
|
+
return isCallNodeShape(value);
|
|
2226
2583
|
if (kind === 'if') {
|
|
2227
2584
|
// The predicate in full, refused rather than defaulted: a gate read back
|
|
2228
2585
|
// without a test it recognises would have to invent one, and inventing one
|
|
@@ -2241,6 +2598,34 @@ function isWorkflowNode(value) {
|
|
|
2241
2598
|
}
|
|
2242
2599
|
return isWorkflowNodeKindUnhandled(kind);
|
|
2243
2600
|
}
|
|
2601
|
+
/**
|
|
2602
|
+
* Everything a `call` node carries, checked as strictly as a source's.
|
|
2603
|
+
*
|
|
2604
|
+
* Its own function rather than a branch of {@link isWorkflowNode}, which the
|
|
2605
|
+
* complexity bound will not hold any more of — and the split is where it should
|
|
2606
|
+
* be, because this is the kind with the most to check.
|
|
2607
|
+
*
|
|
2608
|
+
* A stored call node missing its version is a node that would run whatever is
|
|
2609
|
+
* registered today, which is the failure the pin exists to remove, and a graph
|
|
2610
|
+
* that half-narrows is a load that runs nine nodes of ten. A `callMode` that is
|
|
2611
|
+
* present and unrecognised is refused rather than dropped, for the reason an
|
|
2612
|
+
* unrecognised `edge.branch` is: reading it back as the default would turn a
|
|
2613
|
+
* plain call into an envelope call silently, and the callee would be handed a
|
|
2614
|
+
* payload nobody authored. Absent is accepted and always will be — it is what
|
|
2615
|
+
* every call node written before the field existed carries, and it means the
|
|
2616
|
+
* envelope, which is what those nodes have always sent.
|
|
2617
|
+
*/
|
|
2618
|
+
function isCallNodeShape(value) {
|
|
2619
|
+
const callMode = Reflect.get(value, 'callMode');
|
|
2620
|
+
if (callMode !== undefined && !isWorkflowCallMode(callMode))
|
|
2621
|
+
return false;
|
|
2622
|
+
const config = Reflect.get(value, 'config');
|
|
2623
|
+
return (typeof Reflect.get(value, 'callName') === 'string' &&
|
|
2624
|
+
typeof Reflect.get(value, 'callVersion') === 'string' &&
|
|
2625
|
+
typeof config === 'object' &&
|
|
2626
|
+
config !== null &&
|
|
2627
|
+
!Array.isArray(config));
|
|
2628
|
+
}
|
|
2244
2629
|
/**
|
|
2245
2630
|
* Whether a stored `narrows` is one this build can read.
|
|
2246
2631
|
*
|
|
@@ -2352,6 +2737,43 @@ function supportsWorkflows(store) {
|
|
|
2352
2737
|
// is the surface a scheduling incident already came through once.
|
|
2353
2738
|
typeof store.saveWorkflowSchedule === 'function');
|
|
2354
2739
|
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Whether this store can mint a release and be pointed at one.
|
|
2742
|
+
*
|
|
2743
|
+
* All four asked for by name, for the reason `supportsWorkflows` asks for
|
|
2744
|
+
* `publishWorkflow` and `saveWorkflowSchedule` by name rather than assuming they
|
|
2745
|
+
* arrive together: a store with the mint and not the pointer would narrow
|
|
2746
|
+
* cleanly here, let somebody release a graph, and then fail on the call that was
|
|
2747
|
+
* supposed to make it run.
|
|
2748
|
+
*
|
|
2749
|
+
* {@link getWorkflowAt} is the one whose absence is least visible and most
|
|
2750
|
+
* expensive. A store that could hold a `liveVersion` and not resolve it would
|
|
2751
|
+
* point a scheduled load at a version it cannot produce — and the only place
|
|
2752
|
+
* that shows up is a cron window that stops firing.
|
|
2753
|
+
*/
|
|
2754
|
+
function supportsWorkflowReleases(store) {
|
|
2755
|
+
return (typeof store.releaseWorkflow === 'function' &&
|
|
2756
|
+
typeof store.listWorkflowReleases === 'function' &&
|
|
2757
|
+
typeof store.getWorkflowAt === 'function' &&
|
|
2758
|
+
typeof store.setLiveWorkflowVersion === 'function');
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Which version of this graph a run gets when the caller names none.
|
|
2762
|
+
*
|
|
2763
|
+
* The one implementation of "follow the latest unless something is live", shared
|
|
2764
|
+
* by the scheduler and by the manual run route so the two cannot disagree about
|
|
2765
|
+
* what a cron does and what the button next to it does. That divergence is not
|
|
2766
|
+
* hypothetical: the schedule used to live on the connector and on the workflow
|
|
2767
|
+
* at once, and the whole of `ConnectorScheduler`'s docblock is about what it
|
|
2768
|
+
* cost to have two copies of one answer.
|
|
2769
|
+
*
|
|
2770
|
+
* Not a fallback in the defensive sense. `liveVersion` absent is a stated
|
|
2771
|
+
* position — this graph follows its head — and this function is where that
|
|
2772
|
+
* position is turned into a number, not where a missing value is patched over.
|
|
2773
|
+
*/
|
|
2774
|
+
function liveWorkflowVersion(workflow) {
|
|
2775
|
+
return workflow.liveVersion ?? workflow.version;
|
|
2776
|
+
}
|
|
2355
2777
|
/**
|
|
2356
2778
|
* Whether this store keeps a transform's history.
|
|
2357
2779
|
*
|
package/dist/client.d.ts
CHANGED
|
@@ -153,8 +153,8 @@ export declare const catalogRoutes: {
|
|
|
153
153
|
readonly traces: () => string;
|
|
154
154
|
readonly trace: (id: string) => string;
|
|
155
155
|
};
|
|
156
|
-
export type { CatalogConnection, CatalogConnector, ConnectionCheck, CatalogTransform, CatalogWorkflow, CatalogWorkflowCapabilities, ConnectorKind, ConnectorRun, TransformLanguage, TransformResult, CallableWorkflowRef, WorkflowBranchLabel, WorkflowCallEnvelope, WorkflowCallNode, WorkflowCallOutput, WorkflowEdge, WorkflowExecutionMode, WorkflowFilterAll, WorkflowFilterAny, WorkflowFilterComparison, WorkflowFilterGroup, WorkflowFilterNode, WorkflowFilterOneOf, WorkflowFilterOperator, WorkflowFilterPredicate, WorkflowFilterPredicateKind, WorkflowFilterPresence, WorkflowFilterValue, WorkflowGraph, WorkflowEnvPredicate, WorkflowIfNode, WorkflowIfPredicate, WorkflowIssueCode, WorkflowNode, WorkflowNodeKind, WorkflowNodeOutcome, WorkflowPredicateKind, WorkflowRowCountPredicate, WorkflowRunOrderEntry, WorkflowSinkNode, WorkflowSkipReason, WorkflowSourceNode, WorkflowStageRef, WorkflowTransformNode, WorkflowValidationIssue, CatalogReusableNode, CatalogReusableNodeUse, ReusableNodeBody, ReusableNodeKind, ReusableNodeRef, ReusableSinkBody, ReusableSourceBody, VersionPinCopy, } from './catalog.pipeline';
|
|
157
|
-
export { CONNECTOR_KINDS, isConnectorKind, isTransformLanguage, isWorkflowEdge, isWorkflowNode, REDACTED_SECRET, TRANSFORM_LANGUAGES, readWorkflowCallOutput, WORKFLOW_CALL_CONTRACT, applyReusableNode, reusableNodeBodyOf, isReusableNodeBody, isReusableNodeKind, REUSABLE_NODE_KINDS, NODE_KIND_IS_REUSABLE, nodeKindIsReusable, unreachableReusableNodeKind, describeVersionPin, } from './catalog.pipeline';
|
|
156
|
+
export type { CatalogConnection, CatalogConnector, ConnectionCheck, CatalogTransform, CatalogWorkflow, CatalogWorkflowCapabilities, CatalogWorkflowRelease, ConnectorKind, ConnectorRun, TransformLanguage, TransformResult, CallableWorkflowRef, WorkflowBranchLabel, WorkflowCallEnvelope, WorkflowCallMode, WorkflowCallNode, WorkflowCallOutput, WorkflowEdge, WorkflowExecutionMode, WorkflowFilterAll, WorkflowFilterAny, WorkflowFilterComparison, WorkflowFilterGroup, WorkflowFilterNode, WorkflowFilterOneOf, WorkflowFilterOperator, WorkflowFilterPredicate, WorkflowFilterPredicateKind, WorkflowFilterPresence, WorkflowFilterValue, WorkflowGraph, WorkflowEnvPredicate, WorkflowIfNode, WorkflowIfPredicate, WorkflowIssueCode, WorkflowNode, WorkflowNodeKind, WorkflowNodeOutcome, WorkflowPredicateKind, WorkflowRowCountPredicate, WorkflowRunOrderEntry, WorkflowSinkNode, WorkflowSkipReason, WorkflowSourceNode, WorkflowStageRef, WorkflowTransformNode, WorkflowValidationIssue, CatalogReusableNode, CatalogReusableNodeUse, ReusableNodeBody, ReusableNodeKind, ReusableNodeRef, ReusableSinkBody, ReusableSourceBody, SourceFormat, VersionPinCopy, } from './catalog.pipeline';
|
|
157
|
+
export { CONNECTOR_KINDS, isConnectorKind, isSourceFormat, isTransformLanguage, SOURCE_FORMATS, isWorkflowEdge, isWorkflowNode, liveWorkflowVersion, REDACTED_SECRET, TRANSFORM_LANGUAGES, readWorkflowCallOutput, WORKFLOW_CALL_CONTRACT, WORKFLOW_CALL_MODES, isWorkflowCallMode, unreachableCallMode, workflowCallMode, applyReusableNode, reusableNodeBodyOf, isReusableNodeBody, isReusableNodeKind, REUSABLE_NODE_KINDS, NODE_KIND_IS_REUSABLE, nodeKindIsReusable, unreachableReusableNodeKind, describeLiveVersion, describeVersionPin, } from './catalog.pipeline';
|
|
158
158
|
/**
|
|
159
159
|
* The workflow validator, shipped to the browser deliberately.
|
|
160
160
|
*
|