@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
|
@@ -27,6 +27,39 @@ export type ConnectorKind = (typeof CONNECTOR_KINDS)[number];
|
|
|
27
27
|
* entirely. Anything narrowing a stored value narrows against *this*.
|
|
28
28
|
*/
|
|
29
29
|
export declare function isConnectorKind(value: unknown): value is ConnectorKind;
|
|
30
|
+
/**
|
|
31
|
+
* How the bytes behind a `file` or `s3` connector are read as records.
|
|
32
|
+
*
|
|
33
|
+
* A list rather than a loose string, for the reason {@link CONNECTOR_KINDS} is
|
|
34
|
+
* one: this used to be compared against string literals in the parser and
|
|
35
|
+
* spelled out again in a dropdown, and the two had no way to disagree loudly.
|
|
36
|
+
* The parser's chain also *ended* in JSON, so a format it did not recognise was
|
|
37
|
+
* not refused — it was read as JSON, and a spreadsheet handed to `JSON.parse`
|
|
38
|
+
* fails with a syntax error that names a byte offset rather than the format.
|
|
39
|
+
*
|
|
40
|
+
* `xlsx` is the odd one and is named for what it is: the only member whose
|
|
41
|
+
* payload is binary. The other three are text, and everything that reads them
|
|
42
|
+
* decodes the bytes first. Anything deciding something *per format* narrows
|
|
43
|
+
* against this and answers {@link unreachableSourceFormat}.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SOURCE_FORMATS: readonly ["csv", "ndjson", "json", "xlsx"];
|
|
46
|
+
export type SourceFormat = (typeof SOURCE_FORMATS)[number];
|
|
47
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
48
|
+
export declare function isSourceFormat(value: unknown): value is SourceFormat;
|
|
49
|
+
/**
|
|
50
|
+
* The format that never compiles quietly.
|
|
51
|
+
*
|
|
52
|
+
* The {@link unreachableNodeKind} of formats, and it exists for the same reason:
|
|
53
|
+
* a member added to {@link SOURCE_FORMATS} without a branch in the parser should
|
|
54
|
+
* be a type error naming the file, not a connector that offers a format in a
|
|
55
|
+
* dropdown and then reads the file as JSON.
|
|
56
|
+
*
|
|
57
|
+
* It throws as well as failing to compile, because a connector config is JSON
|
|
58
|
+
* that outlives the build that wrote it: a `format` stored by a newer deployment
|
|
59
|
+
* and read by an older one is possible, and falling back to a default for it
|
|
60
|
+
* would be exactly the silent path this closes.
|
|
61
|
+
*/
|
|
62
|
+
export declare function unreachableSourceFormat(format: never, where: string): never;
|
|
30
63
|
/**
|
|
31
64
|
* What a published workflow runs as. **Not an authored object.**
|
|
32
65
|
*
|
|
@@ -1042,6 +1075,13 @@ export interface WorkflowSinkNode extends WorkflowNodeBase, ReusableNodeRef {
|
|
|
1042
1075
|
* answers are two documented shapes, and anything else fails the node naming
|
|
1043
1076
|
* the workflow, the version and the child run id.
|
|
1044
1077
|
*
|
|
1078
|
+
* All of the paragraph above describes the **envelope** mode, which is the
|
|
1079
|
+
* default and what every stored call node is. {@link callMode} names the other
|
|
1080
|
+
* one: a plain call sends {@link config} verbatim, so a workflow that has never
|
|
1081
|
+
* heard of this catalog can be called without being edited — and gives up the
|
|
1082
|
+
* ability to hand rows back, because it is told no key to stage them under. See
|
|
1083
|
+
* {@link WORKFLOW_CALL_MODES}.
|
|
1084
|
+
*
|
|
1045
1085
|
* ## `config` is not a credential store
|
|
1046
1086
|
*
|
|
1047
1087
|
* Named `config` rather than `input` so it travels the same path a source
|
|
@@ -1067,9 +1107,109 @@ export interface WorkflowCallNode extends WorkflowNodeBase {
|
|
|
1067
1107
|
* to *this* graph and is a number. This one identifies somebody else's code.
|
|
1068
1108
|
*/
|
|
1069
1109
|
callVersion: string;
|
|
1070
|
-
/**
|
|
1110
|
+
/**
|
|
1111
|
+
* Parameters the author typed. Where they land depends on
|
|
1112
|
+
* {@link WorkflowCallNode.callMode}: under `input` in an envelope call, and as
|
|
1113
|
+
* the whole of the child's payload in a plain one.
|
|
1114
|
+
*/
|
|
1071
1115
|
config: Record<string, unknown>;
|
|
1116
|
+
/**
|
|
1117
|
+
* Whether the child is handed a {@link WorkflowCallEnvelope} or the bare
|
|
1118
|
+
* {@link config}. See {@link WORKFLOW_CALL_MODES}, which is where the choice
|
|
1119
|
+
* is argued.
|
|
1120
|
+
*
|
|
1121
|
+
* Absent means `'envelope'`, which is what every call node stored before this
|
|
1122
|
+
* field existed is and what every one of them has always done. Read it through
|
|
1123
|
+
* {@link workflowCallMode} rather than defaulting it a second time — one
|
|
1124
|
+
* default, no second copy to drift.
|
|
1125
|
+
*/
|
|
1126
|
+
callMode?: WorkflowCallMode;
|
|
1072
1127
|
}
|
|
1128
|
+
/**
|
|
1129
|
+
* What a {@link WorkflowCallNode} puts on the wire, and the whole of it.
|
|
1130
|
+
*
|
|
1131
|
+
* ## Why there is a second mode at all
|
|
1132
|
+
*
|
|
1133
|
+
* The catalog could not call a workflow that does not know about the catalog.
|
|
1134
|
+
* A `call` node wraps the author's `config` in a {@link WorkflowCallEnvelope},
|
|
1135
|
+
* so a workflow that already exists — one registered years before this package,
|
|
1136
|
+
* whose body reads `data["proc"]` — receives `{catalog: {...}, input: {proc:
|
|
1137
|
+
* ...}}` and dies on the first key it looks for. The only repair available was
|
|
1138
|
+
* to edit the callee, which inverts the dependency exactly the wrong way round:
|
|
1139
|
+
* every workflow anybody wanted to call would have to start depending on this
|
|
1140
|
+
* package's contract, and a Python workflow registered in another repository
|
|
1141
|
+
* would have to be changed to be reachable from a graph.
|
|
1142
|
+
*
|
|
1143
|
+
* ## Why the nesting is not being loosened instead
|
|
1144
|
+
*
|
|
1145
|
+
* The envelope nests for one stated reason, which is on {@link
|
|
1146
|
+
* WorkflowCallEnvelope}: an author's parameter called `runId` must not be able
|
|
1147
|
+
* to shadow the run id. That reason is sound and it is not being weakened. It
|
|
1148
|
+
* simply does not reach the plain mode, because a plain call sends **no catalog
|
|
1149
|
+
* metadata at all** — there is no `runId`, no `nodeId`, no contract number on
|
|
1150
|
+
* the wire, so there is nothing a parameter could shadow. The flat payload is
|
|
1151
|
+
* not the envelope with its guard removed; it is a different, smaller promise.
|
|
1152
|
+
*
|
|
1153
|
+
* ## What the plain mode costs, which is not small
|
|
1154
|
+
*
|
|
1155
|
+
* No `runId` and no `nodeId` means the callee has no key to stage rows under.
|
|
1156
|
+
* Rows travel through the stage store addressed by `(runId, nodeId, batch)` —
|
|
1157
|
+
* see {@link WorkflowStageRef} — and a callee that was told neither cannot
|
|
1158
|
+
* write where the next node would read. So a plain call **cannot return rows to
|
|
1159
|
+
* the graph**, and that is not a convention anybody could follow more carefully:
|
|
1160
|
+
* it is arithmetic. `validateWorkflow` refuses a plain call with an outbound
|
|
1161
|
+
* edge for exactly this reason (`call-plain-has-output`), and its return value
|
|
1162
|
+
* is not read as a row count — see {@link readWorkflowCallOutput} for the shape
|
|
1163
|
+
* that is deliberately *not* consulted on this path.
|
|
1164
|
+
*
|
|
1165
|
+
* A plain call is therefore for its **effect**: run the thing, and let something
|
|
1166
|
+
* else in the graph produce what gets committed.
|
|
1167
|
+
*
|
|
1168
|
+
* ## Why a mode on the node rather than a second node kind or a boolean
|
|
1169
|
+
*
|
|
1170
|
+
* A boolean is the shape {@link WORKFLOW_PREDICATE_KINDS} argues against one
|
|
1171
|
+
* level down, and for the reason it gives: a `plain?: boolean` beside a future
|
|
1172
|
+
* third wire format is two optional flags whose combinations nobody defined, and
|
|
1173
|
+
* every reader invents its own rule for which wins. A closed list with an
|
|
1174
|
+
* exhaustiveness guard ({@link unreachableCallMode}) makes a third format a
|
|
1175
|
+
* compile error naming the files that have to answer for it.
|
|
1176
|
+
*
|
|
1177
|
+
* A second node *kind* was the other candidate and it is too big. The kind list
|
|
1178
|
+
* is deliberately small and every entry earns it by doing something no wiring
|
|
1179
|
+
* can express (see {@link WORKFLOW_NODE_KINDS}). A plain call does the same
|
|
1180
|
+
* thing a call does at the level the graph reasons about — it hands this
|
|
1181
|
+
* position to a workflow somebody else registered, pinned by name and version.
|
|
1182
|
+
* What differs is the payload. Splitting the kind would duplicate `callName`,
|
|
1183
|
+
* `callVersion`, `config`, the pin check, the picker, the plan entry and the
|
|
1184
|
+
* canvas node for a difference of one field, and every place that today writes
|
|
1185
|
+
* `node.kind === 'call'` would have to remember to write both — which is the
|
|
1186
|
+
* hand-maintained list going quiet that {@link NODE_KIND_IS_REUSABLE} exists to
|
|
1187
|
+
* stop.
|
|
1188
|
+
*/
|
|
1189
|
+
export declare const WORKFLOW_CALL_MODES: readonly ["envelope", "plain"];
|
|
1190
|
+
export type WorkflowCallMode = (typeof WORKFLOW_CALL_MODES)[number];
|
|
1191
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
1192
|
+
export declare function isWorkflowCallMode(value: unknown): value is WorkflowCallMode;
|
|
1193
|
+
/**
|
|
1194
|
+
* {@link unreachableNodeKind}, one level down, and for the identical reason.
|
|
1195
|
+
*
|
|
1196
|
+
* Every branch over {@link WorkflowCallMode} ends here, so a third wire format
|
|
1197
|
+
* added to the list without a rule for building its payload, hashing it,
|
|
1198
|
+
* validating it or reading its answer is a type error naming the file. It throws
|
|
1199
|
+
* as well, because a mode arrives as JSON out of a column and a build older than
|
|
1200
|
+
* the data is a thing that happens.
|
|
1201
|
+
*/
|
|
1202
|
+
export declare function unreachableCallMode(mode: never, where: string): never;
|
|
1203
|
+
/**
|
|
1204
|
+
* The mode this call node runs in, with the default applied once.
|
|
1205
|
+
*
|
|
1206
|
+
* Absent means `'envelope'` — that is what every node stored before the field
|
|
1207
|
+
* existed is, and reading it as anything else would silently change what a
|
|
1208
|
+
* deployment's graphs already do. One function so that the store, the runner,
|
|
1209
|
+
* the hash and the canvas cannot each carry their own `?? 'envelope'` and have
|
|
1210
|
+
* one of them drift.
|
|
1211
|
+
*/
|
|
1212
|
+
export declare function workflowCallMode(node: WorkflowCallNode): WorkflowCallMode;
|
|
1073
1213
|
/**
|
|
1074
1214
|
* The kinds of test an {@link WorkflowIfNode} can make.
|
|
1075
1215
|
*
|
|
@@ -1919,6 +2059,29 @@ export interface VersionPinCopy {
|
|
|
1919
2059
|
detail: string;
|
|
1920
2060
|
}
|
|
1921
2061
|
export declare function describeVersionPin(version: number | undefined, subject: string): VersionPinCopy;
|
|
2062
|
+
/**
|
|
2063
|
+
* The same question about a whole graph: does it follow its latest save, or does
|
|
2064
|
+
* it run a version somebody chose?
|
|
2065
|
+
*
|
|
2066
|
+
* Shares {@link VersionPinCopy} and its two labels with
|
|
2067
|
+
* {@link describeVersionPin} deliberately. A console that said "pinned to v6"
|
|
2068
|
+
* about a transform node and invented different words for a graph would be two
|
|
2069
|
+
* vocabularies for one idea, and the reader would have to work out whether they
|
|
2070
|
+
* meant the same thing — which is the confusion the whole notion of a pin exists
|
|
2071
|
+
* to remove.
|
|
2072
|
+
*
|
|
2073
|
+
* The *detail* differs, and only where the facts do. Two of them:
|
|
2074
|
+
*
|
|
2075
|
+
* - A node's pin can outlive the revision it names, because `catalog_revision`
|
|
2076
|
+
* is capped. A graph's cannot: releases are never evicted, precisely because
|
|
2077
|
+
* the one a live pointer names is the graph production is running. So this
|
|
2078
|
+
* copy makes no eviction caveat, and must not acquire one.
|
|
2079
|
+
* - Following the latest is *cheap* for a node — everybody moves together, which
|
|
2080
|
+
* is often the point. For a graph it means editing is deploying, which is the
|
|
2081
|
+
* hazard this field was added to remove. So the unpinned sentence here is a
|
|
2082
|
+
* warning where the node's is a trade-off.
|
|
2083
|
+
*/
|
|
2084
|
+
export declare function describeLiveVersion(workflow: CatalogWorkflow): VersionPinCopy;
|
|
1922
2085
|
/**
|
|
1923
2086
|
* Which side of an {@link WorkflowIfNode} a wire leaves by.
|
|
1924
2087
|
*
|
|
@@ -2062,6 +2225,51 @@ export interface CatalogWorkflow {
|
|
|
2062
2225
|
version: number;
|
|
2063
2226
|
/** Fingerprint of the graph at this version. See {@link workflowGraphHash}. */
|
|
2064
2227
|
graphHash: string;
|
|
2228
|
+
/**
|
|
2229
|
+
* Which released version a run of this graph gets when nobody names one.
|
|
2230
|
+
*
|
|
2231
|
+
* **Absent means follow the latest**, which is what every graph in every
|
|
2232
|
+
* deployment does today and what every existing graph keeps doing until
|
|
2233
|
+
* somebody points this at something. Absence is a real answer rather than a
|
|
2234
|
+
* state waiting to be filled in — the same stance {@link WorkflowCallNode}
|
|
2235
|
+
* takes from the other side, where a call that named no version would run
|
|
2236
|
+
* whichever one is registered when the load happens.
|
|
2237
|
+
*
|
|
2238
|
+
* Present, it names a {@link CatalogWorkflowRelease} — and from that moment
|
|
2239
|
+
* **editing this graph stops being deploying it**. A save bumps
|
|
2240
|
+
* {@link version} as it always did; the next scheduled window still runs the
|
|
2241
|
+
* released version this names, because a cron tick that executed whatever the
|
|
2242
|
+
* canvas happened to hold is a deploy nobody performed. Moving it is
|
|
2243
|
+
* {@link CatalogWorkflowReleaseStore.setLiveWorkflowVersion}, which is a
|
|
2244
|
+
* deliberate act by a named principal, and moving it *backwards* is the whole
|
|
2245
|
+
* of rollback.
|
|
2246
|
+
*
|
|
2247
|
+
* ## Why this is a column here and not a row in an environments table
|
|
2248
|
+
*
|
|
2249
|
+
* Because the row is already per-environment. Environments in this catalog are
|
|
2250
|
+
* physically isolated — one database each, see `catalog.environment.ts` — so
|
|
2251
|
+
* this `catalog_workflow` row exists once per environment already, and a
|
|
2252
|
+
* second dimension keyed on the environment id would be a table whose every
|
|
2253
|
+
* query filtered on a constant. The environment is the connection, and it has
|
|
2254
|
+
* been since environments were added.
|
|
2255
|
+
*
|
|
2256
|
+
* That is also why the field is not called `productionVersion`. "Production"
|
|
2257
|
+
* in this catalog is the *name of an environment* — see `CatalogEnvironment`
|
|
2258
|
+
* in `catalog.environment.ts` — so a column called that, on a row which
|
|
2259
|
+
* already lives inside exactly one environment, would read as naming a
|
|
2260
|
+
* different one.
|
|
2261
|
+
*
|
|
2262
|
+
* ## Why it does not cross a promotion
|
|
2263
|
+
*
|
|
2264
|
+
* `planPromotion` is explicit that version numbers do not cross: a version
|
|
2265
|
+
* counts edits made in the environment it lives in, so dev's v7 and
|
|
2266
|
+
* production's v7 are unrelated numbers. A pointer *to* a version inherits
|
|
2267
|
+
* that argument whole — carrying this field would point the target's live
|
|
2268
|
+
* pointer at whatever its own seventh edit happened to be. So
|
|
2269
|
+
* `PromotableWorkflow` does not carry it, and a promoted graph arrives
|
|
2270
|
+
* following the latest, exactly as a newly created one does.
|
|
2271
|
+
*/
|
|
2272
|
+
liveVersion?: number;
|
|
2065
2273
|
/**
|
|
2066
2274
|
* The type the sink writes.
|
|
2067
2275
|
*
|
|
@@ -2296,6 +2504,18 @@ export interface WorkflowCallOutput {
|
|
|
2296
2504
|
* schema for a workflow's output anywhere in the durable contract, and no way
|
|
2297
2505
|
* to reach one if there were. So the check is here, at the one moment the
|
|
2298
2506
|
* answer exists, and it names what it saw.
|
|
2507
|
+
*
|
|
2508
|
+
* ## Not called at all for a plain call, which is a decision and not an omission
|
|
2509
|
+
*
|
|
2510
|
+
* A plain call is handed no run id and no node id, so its callee could not have
|
|
2511
|
+
* staged anything under `(runId, nodeId, batch)` even if it wanted to. Running
|
|
2512
|
+
* this over its answer would be worse than pointless in both directions: a
|
|
2513
|
+
* callee that happens to return `{batches, rowCount}` meaning something else
|
|
2514
|
+
* entirely would have this graph go and read a stage that does not exist, and a
|
|
2515
|
+
* callee returning one of the two would fail the node over a key it was never
|
|
2516
|
+
* told about. So the plain path reports zero rows unconditionally and reads
|
|
2517
|
+
* nothing — see `WorkflowRunnerService.callOutput`, which is where that is
|
|
2518
|
+
* enforced rather than merely intended.
|
|
2299
2519
|
*/
|
|
2300
2520
|
export declare function readWorkflowCallOutput(value: unknown): WorkflowCallOutput | undefined;
|
|
2301
2521
|
/**
|
|
@@ -2421,7 +2641,7 @@ export interface CallableWorkflowBlock {
|
|
|
2421
2641
|
}
|
|
2422
2642
|
export declare function callableWorkflowBlock(ref: CallableWorkflowRef): CallableWorkflowBlock | undefined;
|
|
2423
2643
|
/** Every way a graph can be refused. Exported so a canvas can key off the code. */
|
|
2424
|
-
export declare const WORKFLOW_ISSUE_CODES: readonly ["empty", "invalid-node-id", "duplicate-node-id", "edge-endpoint-missing", "self-edge", "duplicate-edge", "cycle", "no-source", "source-has-input", "no-sink", "duplicate-sink-type", "sink-has-output", "unreachable", "dead-end", "transform-not-named", "call-not-named", "if-not-named", "if-threshold-invalid", "if-needs-one-input", "branch-not-labelled", "branch-on-plain-edge", "filter-predicate-invalid", "filter-narrows-unacknowledged", "filter-narrows-nothing", "version-pin-invalid"];
|
|
2644
|
+
export declare const WORKFLOW_ISSUE_CODES: readonly ["empty", "invalid-node-id", "duplicate-node-id", "edge-endpoint-missing", "self-edge", "duplicate-edge", "cycle", "no-source", "source-has-input", "no-sink", "duplicate-sink-type", "sink-has-output", "unreachable", "dead-end", "transform-not-named", "call-not-named", "call-plain-has-output", "if-not-named", "if-threshold-invalid", "if-needs-one-input", "branch-not-labelled", "branch-on-plain-edge", "filter-predicate-invalid", "filter-narrows-unacknowledged", "filter-narrows-nothing", "version-pin-invalid"];
|
|
2425
2645
|
export type WorkflowIssueCode = (typeof WORKFLOW_ISSUE_CODES)[number];
|
|
2426
2646
|
export interface WorkflowValidationIssue {
|
|
2427
2647
|
code: WorkflowIssueCode;
|
|
@@ -2718,6 +2938,172 @@ export interface CatalogWorkflowStore {
|
|
|
2718
2938
|
enabled?: boolean;
|
|
2719
2939
|
}, changedBy: string): Promise<CatalogWorkflow>;
|
|
2720
2940
|
}
|
|
2941
|
+
/**
|
|
2942
|
+
* One version of a graph, kept exactly as it was, because somebody said so.
|
|
2943
|
+
*
|
|
2944
|
+
* ## The archive `CatalogWorkflow` argues against, and why this is not it
|
|
2945
|
+
*
|
|
2946
|
+
* {@link CatalogWorkflow} states plainly that a graph is not revisioned, and the
|
|
2947
|
+
* decisive reason it gives is the counter: {@link CatalogWorkflow.version} is
|
|
2948
|
+
* bumped on **draft** edits by design, so archiving one body per version would
|
|
2949
|
+
* store every autosave of a canvas somebody is still dragging boxes around on —
|
|
2950
|
+
* and under a bounded archive that noise would evict the versions that actually
|
|
2951
|
+
* ran. **That argument is correct and nothing here weakens it.** It is an
|
|
2952
|
+
* argument against archiving *saves*, and this archives *releases*: a release is
|
|
2953
|
+
* minted only by `releaseWorkflow`, which is a route a person presses, and a
|
|
2954
|
+
* canvas autosave does not reach it. So the counter stays cheap to inflate, the
|
|
2955
|
+
* archive stays keyed on a deliberate act, and the two now compose because they
|
|
2956
|
+
* are counting different things.
|
|
2957
|
+
*
|
|
2958
|
+
* The second half of that docblock — that a graph is a structure and a line
|
|
2959
|
+
* differ over serialised JSON would report a dragged box as a change — is also
|
|
2960
|
+
* untouched. This is not a diff feature. It stores the graph so a version can be
|
|
2961
|
+
* *run*, and diffing graphs remains a graph problem deserving a screen that
|
|
2962
|
+
* draws one.
|
|
2963
|
+
*
|
|
2964
|
+
* ## Why not `catalog_revision`, which already archives versioned bodies
|
|
2965
|
+
*
|
|
2966
|
+
* Two reasons, and the second is the one that decides it.
|
|
2967
|
+
*
|
|
2968
|
+
* `CatalogRevision.body` is text a person typed — a transform's code, a saved
|
|
2969
|
+
* query's SQL — and every route over it is built to render text. A graph is
|
|
2970
|
+
* nodes and edges, and folding it into that column would put JSON nobody wrote
|
|
2971
|
+
* in front of a differ built for source.
|
|
2972
|
+
*
|
|
2973
|
+
* The one that decides it is {@link CATALOG_REVISION_LIMIT}. That cap is right
|
|
2974
|
+
* for code, for the reason its own docblock gives: revisions grow with how often
|
|
2975
|
+
* somebody edits, which nobody meters. A release does not grow that way — it
|
|
2976
|
+
* grows with how often somebody deliberately ships — and, crucially, a release
|
|
2977
|
+
* is the thing a live pointer names. An eviction rule over this table could
|
|
2978
|
+
* delete the graph that production is *currently running*, turning
|
|
2979
|
+
* {@link CatalogWorkflow.liveVersion} into a pin nothing can honour and stopping
|
|
2980
|
+
* a working pipeline on a retention policy. **So releases are never evicted.**
|
|
2981
|
+
* That makes this an unbounded table, which this codebase is careful about — and
|
|
2982
|
+
* it earns it on the same test `catalog_audit_event` and `catalog_connector_run`
|
|
2983
|
+
* pass: one row per thing a person deliberately did, at a rate an operator can
|
|
2984
|
+
* read off their own change process.
|
|
2985
|
+
*
|
|
2986
|
+
* ## Immutable, and there is no route that removes one
|
|
2987
|
+
*
|
|
2988
|
+
* Nothing edits a release and nothing deletes one. That is the strongest form of
|
|
2989
|
+
* "refuse to delete the version that is live": there is no operation to refuse.
|
|
2990
|
+
* The one exception is {@link CatalogWorkflowStore.deleteWorkflow}, which takes
|
|
2991
|
+
* the graph, its connector and its whole run history — releases go with it,
|
|
2992
|
+
* because nothing survives that could still name one. (The opposite call is made
|
|
2993
|
+
* for a transform, whose revisions outlive it precisely *because* runs that ran
|
|
2994
|
+
* them survive.)
|
|
2995
|
+
*/
|
|
2996
|
+
export interface CatalogWorkflowRelease {
|
|
2997
|
+
/**
|
|
2998
|
+
* `{workflowId}:{version}`, derived rather than random.
|
|
2999
|
+
*
|
|
3000
|
+
* The same construction `revisionKey` uses in the MikroORM store and for the
|
|
3001
|
+
* same property: releasing the same version twice cannot append a second copy,
|
|
3002
|
+
* because the second write would collide with the first rather than land
|
|
3003
|
+
* beside it.
|
|
3004
|
+
*/
|
|
3005
|
+
id: string;
|
|
3006
|
+
workflowId: string;
|
|
3007
|
+
/**
|
|
3008
|
+
* The {@link CatalogWorkflow.version} this release IS.
|
|
3009
|
+
*
|
|
3010
|
+
* The same number, not a parallel sequence. A release sequence of its own was
|
|
3011
|
+
* the alternative and it is worse in the one place it matters: a run records
|
|
3012
|
+
* `workflowVersion`, so a second numbering would mean a run naming "v3" and an
|
|
3013
|
+
* operator reading "release 3" could be two different graphs, which is exactly
|
|
3014
|
+
* the ambiguity the edit counter was made cheap to avoid.
|
|
3015
|
+
*/
|
|
3016
|
+
version: number;
|
|
3017
|
+
/** Fingerprint of the graph as released. See {@link workflowGraphHash}. */
|
|
3018
|
+
graphHash: string;
|
|
3019
|
+
nodes: WorkflowNode[];
|
|
3020
|
+
edges: WorkflowEdge[];
|
|
3021
|
+
/** The type the sink committed at this version. */
|
|
3022
|
+
targetType: string;
|
|
3023
|
+
/** Whatever the releaser wanted to say about it. */
|
|
3024
|
+
notes?: string;
|
|
3025
|
+
releasedBy: string;
|
|
3026
|
+
releasedAt: string;
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Minting releases, and pointing at one.
|
|
3030
|
+
*
|
|
3031
|
+
* Its own interface mixed in optionally, for the reason {@link
|
|
3032
|
+
* CatalogWorkflowStore} is: a store written against the previous shape must keep
|
|
3033
|
+
* compiling, and "this deployment cannot hold releases" has to be a sentence a
|
|
3034
|
+
* UI can say rather than a method missing at run time.
|
|
3035
|
+
*
|
|
3036
|
+
* The split from `CatalogWorkflowStore` is not only compatibility. These four
|
|
3037
|
+
* are the only members in this file that can change *what a cron executes*
|
|
3038
|
+
* without touching a graph, and keeping them behind their own predicate means a
|
|
3039
|
+
* store can hold graphs without acquiring that power by accident.
|
|
3040
|
+
*/
|
|
3041
|
+
export interface CatalogWorkflowReleaseStore {
|
|
3042
|
+
/**
|
|
3043
|
+
* Freeze the graph as it currently stands, under its current version.
|
|
3044
|
+
*
|
|
3045
|
+
* **The only thing that mints one.** Not `saveWorkflow`, which is the autosave
|
|
3046
|
+
* this whole feature exists to stop being a deploy; not `publishWorkflow`,
|
|
3047
|
+
* which is idempotent and is called by a promotion apply — a release minted as
|
|
3048
|
+
* a side effect of promoting configuration into an environment would be a
|
|
3049
|
+
* release nobody in that environment chose.
|
|
3050
|
+
*
|
|
3051
|
+
* Refuses a draft, for the reason `WORKFLOW_STATUSES` already gives about what
|
|
3052
|
+
* a connector may point at and what a promotion may carry: what gets shipped
|
|
3053
|
+
* should be something a person declared finished.
|
|
3054
|
+
*
|
|
3055
|
+
* **Idempotent per version.** Releasing a graph whose current version is
|
|
3056
|
+
* already released answers with the existing release rather than minting a
|
|
3057
|
+
* second or overwriting its notes — the graph has not changed, so a second
|
|
3058
|
+
* release would be a record of an event that did not happen, and re-attributing
|
|
3059
|
+
* the first one would erase who actually shipped it.
|
|
3060
|
+
*/
|
|
3061
|
+
releaseWorkflow(id: string, releasedBy: string, options?: {
|
|
3062
|
+
notes?: string;
|
|
3063
|
+
}): Promise<CatalogWorkflowRelease>;
|
|
3064
|
+
/** Newest first. */
|
|
3065
|
+
listWorkflowReleases(id: string): Promise<CatalogWorkflowRelease[]>;
|
|
3066
|
+
/**
|
|
3067
|
+
* The graph as it was at a released version, or `undefined`.
|
|
3068
|
+
*
|
|
3069
|
+
* `undefined` for a version that was never released as much as for a workflow
|
|
3070
|
+
* that does not exist, and callers must treat the two the same way: **fail,
|
|
3071
|
+
* never fall back to the latest.** Running the current graph because the named
|
|
3072
|
+
* one could not be produced is precisely the substitution a pin is written
|
|
3073
|
+
* down to prevent.
|
|
3074
|
+
*
|
|
3075
|
+
* Answers with a whole {@link CatalogWorkflow} rather than the bare release,
|
|
3076
|
+
* because that is what a run needs: the released graph, carried on the row's
|
|
3077
|
+
* present identity. The graph fields — nodes, edges, `targetType`,
|
|
3078
|
+
* `graphHash`, `version` — come from the release. The operational ones —
|
|
3079
|
+
* `name`, `status`, `schedule`, `enabled`, `liveVersion` — come from the row
|
|
3080
|
+
* as it is now, because they are not part of what was released. A cron somebody
|
|
3081
|
+
* changed this morning applies to the version that is live, not to whatever
|
|
3082
|
+
* cron was on the row the day it was released.
|
|
3083
|
+
*/
|
|
3084
|
+
getWorkflowAt(id: string, version: number): Promise<CatalogWorkflow | undefined>;
|
|
3085
|
+
/**
|
|
3086
|
+
* Point {@link CatalogWorkflow.liveVersion} at a released version — or clear it.
|
|
3087
|
+
*
|
|
3088
|
+
* One method for going live, for rolling back and for going back to following
|
|
3089
|
+
* the latest, because they are one act with a different argument. Rollback in
|
|
3090
|
+
* particular is not a separate mechanism to build and test: it is this call
|
|
3091
|
+
* with a smaller number, and it works because the older graph is still stored.
|
|
3092
|
+
*
|
|
3093
|
+
* Refuses a version with no release behind it, naming what there is. A pointer
|
|
3094
|
+
* accepted at a number nothing can produce would be a pipeline that stops at
|
|
3095
|
+
* its next window, discovered by a load failing rather than by the person who
|
|
3096
|
+
* typed it.
|
|
3097
|
+
*
|
|
3098
|
+
* `undefined` clears the pointer and takes the graph back to following the
|
|
3099
|
+
* latest. Allowed rather than refused — it is the state every graph in every
|
|
3100
|
+
* deployment is in, so refusing would strand anything that ever went live —
|
|
3101
|
+
* and stated as a decision because it hands back exactly the hazard the
|
|
3102
|
+
* pointer removes: from that moment, saving the graph changes what the next
|
|
3103
|
+
* window runs.
|
|
3104
|
+
*/
|
|
3105
|
+
setLiveWorkflowVersion(id: string, version: number | undefined, changedBy: string): Promise<CatalogWorkflow>;
|
|
3106
|
+
}
|
|
2721
3107
|
/**
|
|
2722
3108
|
* Where the rows between two nodes actually sit.
|
|
2723
3109
|
*
|
|
@@ -2767,6 +3153,36 @@ export interface CatalogStageStore {
|
|
|
2767
3153
|
* does, because a flag is a claim and a method is the thing itself.
|
|
2768
3154
|
*/
|
|
2769
3155
|
export declare function supportsWorkflows(store: CatalogPipelineStore): store is CatalogPipelineStore & CatalogWorkflowStore;
|
|
3156
|
+
/**
|
|
3157
|
+
* Whether this store can mint a release and be pointed at one.
|
|
3158
|
+
*
|
|
3159
|
+
* All four asked for by name, for the reason `supportsWorkflows` asks for
|
|
3160
|
+
* `publishWorkflow` and `saveWorkflowSchedule` by name rather than assuming they
|
|
3161
|
+
* arrive together: a store with the mint and not the pointer would narrow
|
|
3162
|
+
* cleanly here, let somebody release a graph, and then fail on the call that was
|
|
3163
|
+
* supposed to make it run.
|
|
3164
|
+
*
|
|
3165
|
+
* {@link getWorkflowAt} is the one whose absence is least visible and most
|
|
3166
|
+
* expensive. A store that could hold a `liveVersion` and not resolve it would
|
|
3167
|
+
* point a scheduled load at a version it cannot produce — and the only place
|
|
3168
|
+
* that shows up is a cron window that stops firing.
|
|
3169
|
+
*/
|
|
3170
|
+
export declare function supportsWorkflowReleases(store: CatalogPipelineStore): store is CatalogPipelineStore & CatalogWorkflowReleaseStore;
|
|
3171
|
+
/**
|
|
3172
|
+
* Which version of this graph a run gets when the caller names none.
|
|
3173
|
+
*
|
|
3174
|
+
* The one implementation of "follow the latest unless something is live", shared
|
|
3175
|
+
* by the scheduler and by the manual run route so the two cannot disagree about
|
|
3176
|
+
* what a cron does and what the button next to it does. That divergence is not
|
|
3177
|
+
* hypothetical: the schedule used to live on the connector and on the workflow
|
|
3178
|
+
* at once, and the whole of `ConnectorScheduler`'s docblock is about what it
|
|
3179
|
+
* cost to have two copies of one answer.
|
|
3180
|
+
*
|
|
3181
|
+
* Not a fallback in the defensive sense. `liveVersion` absent is a stated
|
|
3182
|
+
* position — this graph follows its head — and this function is where that
|
|
3183
|
+
* position is turned into a number, not where a missing value is patched over.
|
|
3184
|
+
*/
|
|
3185
|
+
export declare function liveWorkflowVersion(workflow: CatalogWorkflow): number;
|
|
2770
3186
|
/**
|
|
2771
3187
|
* Whether this store keeps a transform's history.
|
|
2772
3188
|
*
|
|
@@ -2834,7 +3250,7 @@ export type CatalogLoadExpectationStore = Required<Pick<CatalogPipelineStore, 'l
|
|
|
2834
3250
|
* an editor whose save has nowhere to go.
|
|
2835
3251
|
*/
|
|
2836
3252
|
export declare function supportsLoadExpectations(store: CatalogPipelineStore): store is CatalogPipelineStore & CatalogLoadExpectationStore;
|
|
2837
|
-
export interface CatalogPipelineStore extends Partial<CatalogWorkflowStore>, Partial<CatalogStageStore> {
|
|
3253
|
+
export interface CatalogPipelineStore extends Partial<CatalogWorkflowStore>, Partial<CatalogWorkflowReleaseStore>, Partial<CatalogStageStore> {
|
|
2838
3254
|
listConnectors(): Promise<CatalogConnector[]>;
|
|
2839
3255
|
getConnector(id: string): Promise<CatalogConnector | undefined>;
|
|
2840
3256
|
saveConnector(input: Omit<CatalogConnector, 'id' | 'createdAt' | 'updatedAt' | 'createdBy'> & {
|