@dudousxd/nestjs-catalog 0.10.0 → 0.12.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.controller.js +36 -2
- package/dist/catalog.pipeline.d.ts +156 -13
- package/dist/catalog.pipeline.js +92 -2
- package/dist/catalog.secrets.d.ts +283 -0
- package/dist/catalog.secrets.js +209 -0
- package/dist/catalog.workspace.d.ts +154 -0
- package/dist/catalog.workspace.js +59 -1
- package/dist/client.d.ts +13 -2
- package/dist/client.js +23 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +15 -2
- package/dist/transform-runner.d.ts +22 -5
- package/dist/transform-runner.js +126 -9
- package/package.json +1 -1
|
@@ -82,10 +82,12 @@ function createCatalogController(path, guards, decorators = []) {
|
|
|
82
82
|
registry;
|
|
83
83
|
service;
|
|
84
84
|
traces;
|
|
85
|
-
|
|
85
|
+
workspace;
|
|
86
|
+
constructor(registry, service, traces, workspace) {
|
|
86
87
|
this.registry = registry;
|
|
87
88
|
this.service = service;
|
|
88
89
|
this.traces = traces;
|
|
90
|
+
this.workspace = workspace;
|
|
89
91
|
}
|
|
90
92
|
requireTraces() {
|
|
91
93
|
if (!this.traces) {
|
|
@@ -283,6 +285,28 @@ function createCatalogController(path, guards, decorators = []) {
|
|
|
283
285
|
response.setHeader('content-disposition', `attachment; filename="${filename}"`);
|
|
284
286
|
return (0, catalog_query_cache_1.toCsv)(result);
|
|
285
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Every SQL this query has ever been, newest first.
|
|
290
|
+
*
|
|
291
|
+
* `catalog:read`, the same scope `GET saved-queries/:id` asks for, because
|
|
292
|
+
* it hands back the same field one version older. Gating history harder than
|
|
293
|
+
* the current text would be gating the diff rather than the data — the
|
|
294
|
+
* caller can already read what the statement says today, and the authoring
|
|
295
|
+
* scope that `POST`/`PATCH` require is about *choosing what SQL runs*, which
|
|
296
|
+
* reading an old body is not.
|
|
297
|
+
*
|
|
298
|
+
* An empty list is a real answer: a query nobody has edited since revisions
|
|
299
|
+
* shipped may have nothing recorded. A store that keeps none at all says so
|
|
300
|
+
* rather than answering `[]`, because "we do not keep these" and "nothing
|
|
301
|
+
* has changed" are different facts and a screen would draw them the same.
|
|
302
|
+
*/
|
|
303
|
+
savedQueryRevisions(id) {
|
|
304
|
+
const workspace = this.workspace;
|
|
305
|
+
if (!workspace || !(0, catalog_workspace_1.supportsSavedQueryRevisions)(workspace)) {
|
|
306
|
+
throw new common_1.BadRequestException("This catalog's workspace store keeps no revisions, so a saved query's SQL can be read but not compared with what it used to be.");
|
|
307
|
+
}
|
|
308
|
+
return workspace.listSavedQueryRevisions(id);
|
|
309
|
+
}
|
|
286
310
|
dashboards() {
|
|
287
311
|
return this.service.listDashboards();
|
|
288
312
|
}
|
|
@@ -555,6 +579,14 @@ function createCatalogController(path, guards, decorators = []) {
|
|
|
555
579
|
__metadata("design:paramtypes", [String, Object]),
|
|
556
580
|
__metadata("design:returntype", Promise)
|
|
557
581
|
], CatalogController.prototype, "exportSavedQuery", null);
|
|
582
|
+
__decorate([
|
|
583
|
+
(0, common_1.Get)('saved-queries/:id/revisions'),
|
|
584
|
+
(0, catalog_route_auth_1.RequireScopes)('catalog:read'),
|
|
585
|
+
__param(0, (0, common_1.Param)('id')),
|
|
586
|
+
__metadata("design:type", Function),
|
|
587
|
+
__metadata("design:paramtypes", [String]),
|
|
588
|
+
__metadata("design:returntype", void 0)
|
|
589
|
+
], CatalogController.prototype, "savedQueryRevisions", null);
|
|
558
590
|
__decorate([
|
|
559
591
|
(0, common_1.Get)('dashboards'),
|
|
560
592
|
(0, catalog_route_auth_1.RequireScopes)('catalog:read'),
|
|
@@ -667,8 +699,10 @@ function createCatalogController(path, guards, decorators = []) {
|
|
|
667
699
|
(0, common_1.Controller)(path),
|
|
668
700
|
__param(2, (0, common_1.Optional)()),
|
|
669
701
|
__param(2, (0, common_1.Inject)(catalog_workspace_1.CATALOG_TRACE_STORE)),
|
|
702
|
+
__param(3, (0, common_1.Optional)()),
|
|
703
|
+
__param(3, (0, common_1.Inject)(catalog_workspace_1.CATALOG_WORKSPACE_STORE)),
|
|
670
704
|
__metadata("design:paramtypes", [catalog_registry_base_1.CatalogRegistry,
|
|
671
|
-
catalog_service_1.CatalogService, Object])
|
|
705
|
+
catalog_service_1.CatalogService, Object, Object])
|
|
672
706
|
], CatalogController);
|
|
673
707
|
if (guards.length > 0) {
|
|
674
708
|
(0, common_1.UseGuards)(...guards)(CatalogController);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* schedules, retries and checkpoints, and writing a second one would mean two
|
|
8
8
|
* systems each believing they decide when a load runs.
|
|
9
9
|
*/
|
|
10
|
+
import type { CatalogRevision } from './catalog.workspace';
|
|
10
11
|
/**
|
|
11
12
|
* Where a connector pulls from.
|
|
12
13
|
*
|
|
@@ -136,6 +137,14 @@ export declare function isTransformLanguage(value: unknown): value is TransformL
|
|
|
136
137
|
* Versioned, because a load that produced surprising numbers is investigated
|
|
137
138
|
* afterwards, and "which code ran" is the first question. Bumping the version
|
|
138
139
|
* on every change costs a row and answers it.
|
|
140
|
+
*
|
|
141
|
+
* The version used to be the *whole* answer, and it was half of one: it named
|
|
142
|
+
* code that no longer existed anywhere, because one row per transform is
|
|
143
|
+
* overwritten in place. Each version's code is now recorded as a
|
|
144
|
+
* {@link CatalogRevision}, read through
|
|
145
|
+
* {@link CatalogPipelineStore.listTransformRevisions}, so the number on a run
|
|
146
|
+
* and the text it names are both retrievable. `version` still counts saves that
|
|
147
|
+
* changed the code and nothing else — see `saveTransform`.
|
|
139
148
|
*/
|
|
140
149
|
export interface CatalogTransform {
|
|
141
150
|
id: string;
|
|
@@ -223,17 +232,26 @@ export interface ConnectorRun {
|
|
|
223
232
|
error?: string;
|
|
224
233
|
startedAt: string;
|
|
225
234
|
finishedAt?: string;
|
|
226
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* Which transform version ran, so a surprising load can be traced to code.
|
|
237
|
+
*
|
|
238
|
+
* Traced to the code itself, now, and not only to a number: the version this
|
|
239
|
+
* names is the version of a {@link CatalogRevision}, so `transforms/:id/revisions`
|
|
240
|
+
* answers with the body that produced these rows. Until that route existed
|
|
241
|
+
* this field could only establish *that* the transform had been edited since.
|
|
242
|
+
*/
|
|
227
243
|
transformVersion?: number;
|
|
228
244
|
/** Which workflow ran, when the connector delegated to one. */
|
|
229
245
|
workflowId?: string;
|
|
230
246
|
/**
|
|
231
247
|
* Which *version* of it ran.
|
|
232
248
|
*
|
|
233
|
-
* The same question `transformVersion` answers, asked of the graph
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
249
|
+
* The same question `transformVersion` answers, asked of the graph — and no
|
|
250
|
+
* longer answered as well, which is worth knowing before relying on it. A
|
|
251
|
+
* workflow keeps only its latest shape (see {@link CatalogWorkflow} for why it
|
|
252
|
+
* is excluded from revisions while a transform is not), so this number
|
|
253
|
+
* connects a run to the graph that produced it and is the only way to know a
|
|
254
|
+
* graph has changed since. It cannot produce the graph.
|
|
237
255
|
*/
|
|
238
256
|
workflowVersion?: number;
|
|
239
257
|
/**
|
|
@@ -435,20 +453,77 @@ export interface WorkflowGraph {
|
|
|
435
453
|
* the code; for a workflow it means the code *and* the wiring, so both the
|
|
436
454
|
* graph version and the per-node transform versions are recorded on the run.
|
|
437
455
|
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
456
|
+
* **Only the latest shape is kept, and unlike a transform it is not
|
|
457
|
+
* revisioned.** That asymmetry is a decision rather than an oversight, and this
|
|
458
|
+
* is where somebody looking for the missing feature will look, so it is argued
|
|
459
|
+
* here.
|
|
460
|
+
*
|
|
461
|
+
* A transform's code and a saved query's SQL are text a person typed, and
|
|
462
|
+
* {@link CatalogRevision} archives text: two bodies, a line differ, done. A
|
|
463
|
+
* graph is a structure. Its "body" would be JSON nobody wrote, and a text diff
|
|
464
|
+
* over it is dominated by key order and canvas positions — it would report a
|
|
465
|
+
* dragged box as a change to what the load does, which is the opposite of what
|
|
466
|
+
* {@link workflowGraphHash} is careful to exclude. Diffing graphs is a graph
|
|
467
|
+
* problem and deserves a screen that draws one, not a line differ pointed at
|
|
468
|
+
* serialised nodes.
|
|
469
|
+
*
|
|
470
|
+
* The decisive reason is the counter. {@link version} is bumped on **draft**
|
|
471
|
+
* edits deliberately — see the note on it — so that a run's `workflowVersion`
|
|
472
|
+
* can never mean two different graphs. Archiving one body per version would
|
|
473
|
+
* therefore store every autosave of a canvas somebody is still dragging boxes
|
|
474
|
+
* around on, and under the per-subject cap that {@link CATALOG_REVISION_LIMIT}
|
|
475
|
+
* imposes, that noise would evict the versions that actually ran. A counter
|
|
476
|
+
* designed to be cheap to inflate and an archive designed to be bounded do not
|
|
477
|
+
* compose; making them compose means keying the archive on behaviour rather than
|
|
478
|
+
* on saves, which is what `graphHash` already is, and that is a different
|
|
479
|
+
* feature from this one.
|
|
480
|
+
*
|
|
481
|
+
* So the limitation stays, stated plainly: an edited graph cannot be
|
|
482
|
+
* reconstructed from an old run, only identified as different. A diff screen
|
|
483
|
+
* answers for the code and the SQL and not for the wiring.
|
|
444
484
|
*/
|
|
485
|
+
/**
|
|
486
|
+
* Whether this graph is still being drawn, or is something somebody declared
|
|
487
|
+
* finished.
|
|
488
|
+
*
|
|
489
|
+
* The distinction exists because validation used to be the gate on *saving*, and
|
|
490
|
+
* that made an unfinished graph unstorable: `saveWorkflow` refused anything
|
|
491
|
+
* `validateWorkflow` had an issue with, so a canvas with one node on it could
|
|
492
|
+
* not be written down at all and closing the tab lost it. Worse, it made the
|
|
493
|
+
* canvas lie about ordinary work — clicking "+ Sink" produces a node that is
|
|
494
|
+
* unreachable from any source and names no type, both true and both useless one
|
|
495
|
+
* second after the click, because a just-added node is unwired by construction.
|
|
496
|
+
*
|
|
497
|
+
* So the gate moved rather than loosened. Validation is now the gate on
|
|
498
|
+
* publishing, and the same `validateWorkflow` still decides — a draft is not a
|
|
499
|
+
* graph that skipped the rules, it is a graph nobody has claimed is finished
|
|
500
|
+
* yet. Everything that consumes a workflow asks for `ready`: a connector may
|
|
501
|
+
* only point at one, and a promotion may only carry one. What crosses an
|
|
502
|
+
* environment should be something a person declared done.
|
|
503
|
+
*/
|
|
504
|
+
export declare const WORKFLOW_STATUSES: readonly ["draft", "ready"];
|
|
505
|
+
export type WorkflowStatus = (typeof WORKFLOW_STATUSES)[number];
|
|
506
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
507
|
+
export declare function isWorkflowStatus(value: unknown): value is WorkflowStatus;
|
|
445
508
|
export interface CatalogWorkflow {
|
|
446
509
|
id: string;
|
|
447
510
|
name: string;
|
|
448
511
|
description?: string;
|
|
449
512
|
nodes: WorkflowNode[];
|
|
450
513
|
edges: WorkflowEdge[];
|
|
451
|
-
/**
|
|
514
|
+
/** See {@link WORKFLOW_STATUSES}. A graph is `draft` until somebody publishes it. */
|
|
515
|
+
status: WorkflowStatus;
|
|
516
|
+
/**
|
|
517
|
+
* Bumped whenever the graph's behaviour changes. Never on a rename or a move.
|
|
518
|
+
*
|
|
519
|
+
* Bumped on a **draft** edit too, which looks like exactly the inflation this
|
|
520
|
+
* rule exists to prevent and is not. The counter's job is to make
|
|
521
|
+
* {@link ConnectorRun.workflowVersion} answer "which shape ran": freezing it
|
|
522
|
+
* while a graph is drafted would let a run recorded at v4 and a later run also
|
|
523
|
+
* at v4 mean two different graphs, which is the one thing that field must
|
|
524
|
+
* never do. Drafting therefore inflates a number nobody reads — cheap — rather
|
|
525
|
+
* than making a number somebody does read ambiguous.
|
|
526
|
+
*/
|
|
452
527
|
version: number;
|
|
453
528
|
/** Fingerprint of the graph at this version. See {@link workflowGraphHash}. */
|
|
454
529
|
graphHash: string;
|
|
@@ -660,7 +735,24 @@ export interface CatalogWorkflowStore {
|
|
|
660
735
|
listWorkflows(): Promise<CatalogWorkflow[]>;
|
|
661
736
|
getWorkflow(id: string): Promise<CatalogWorkflow | undefined>;
|
|
662
737
|
/**
|
|
663
|
-
* Validates
|
|
738
|
+
* Writes. Validates only what it must.
|
|
739
|
+
*
|
|
740
|
+
* A **draft** is written without validating, which is the whole of the change
|
|
741
|
+
* and the reason {@link WORKFLOW_STATUSES} exists: a graph you have not
|
|
742
|
+
* finished has to be storable, or closing the tab loses it. A **ready**
|
|
743
|
+
* workflow is still validated on every save, because it is the one that runs.
|
|
744
|
+
*
|
|
745
|
+
* `status` is not an input. A save cannot promote a draft to ready — that is
|
|
746
|
+
* {@link publishWorkflow}, which exists so there is one place that validates
|
|
747
|
+
* and one place that can explain why it refused. A save of an already-ready
|
|
748
|
+
* workflow keeps it ready, and **refuses an edit that would make it invalid**
|
|
749
|
+
* rather than quietly demoting it to draft. Demotion was the other option and
|
|
750
|
+
* it is the one that loses a running pipeline silently: a connector may only
|
|
751
|
+
* point at a ready graph, so a save that dropped the status would disable a
|
|
752
|
+
* scheduled load with nothing said to anybody. Refusing puts the error in
|
|
753
|
+
* front of the person who is editing, at the moment they edit. To park a
|
|
754
|
+
* broken idea on a live graph, {@link unpublishWorkflow} it first and be told
|
|
755
|
+
* which connectors that stops.
|
|
664
756
|
*
|
|
665
757
|
* `version`, `graphHash` and `targetType` are not inputs: the first two are
|
|
666
758
|
* derived from the graph and the third from the sink, and accepting them from
|
|
@@ -670,6 +762,34 @@ export interface CatalogWorkflowStore {
|
|
|
670
762
|
id?: string;
|
|
671
763
|
description?: string;
|
|
672
764
|
}, createdBy: string): Promise<CatalogWorkflow>;
|
|
765
|
+
/**
|
|
766
|
+
* Declare a graph finished: validate it, and make it `ready`.
|
|
767
|
+
*
|
|
768
|
+
* A transition rather than a field on save, and the argument is that this is
|
|
769
|
+
* the only shape with somewhere to put the refusal. "Ready" is a claim that
|
|
770
|
+
* has to be checked, and a check that fails owes an explanation naming the
|
|
771
|
+
* nodes — `validateWorkflow` produces exactly that, and a boolean field on a
|
|
772
|
+
* save request has nowhere to return it that is not an error on an operation
|
|
773
|
+
* the caller thought was about something else. It also makes the audit
|
|
774
|
+
* question answerable: publishing is an act with an actor, and a field set in
|
|
775
|
+
* passing during an autosave is not.
|
|
776
|
+
*
|
|
777
|
+
* Idempotent on an already-ready graph, because the honest answer to "publish
|
|
778
|
+
* this thing that is published" is the graph, not an error.
|
|
779
|
+
*/
|
|
780
|
+
publishWorkflow(id: string, publishedBy: string): Promise<CatalogWorkflow>;
|
|
781
|
+
/**
|
|
782
|
+
* Take a graph back to `draft`.
|
|
783
|
+
*
|
|
784
|
+
* **Refuses while any connector still runs it**, exactly as
|
|
785
|
+
* {@link deleteWorkflow} does and for the same reason: a connector may only
|
|
786
|
+
* point at a ready graph, so unpublishing one out from under a schedule breaks
|
|
787
|
+
* a load that was working, and the operator needs to know *which* connectors
|
|
788
|
+
* to point elsewhere first. Refusing here rather than cascading is deliberate —
|
|
789
|
+
* disabling somebody's connectors as a side effect of an edit to something
|
|
790
|
+
* else is precisely the silent action this status exists to prevent.
|
|
791
|
+
*/
|
|
792
|
+
unpublishWorkflow(id: string, unpublishedBy: string): Promise<CatalogWorkflow>;
|
|
673
793
|
/** Refuses while any connector still runs it. */
|
|
674
794
|
deleteWorkflow(id: string): Promise<boolean>;
|
|
675
795
|
/** Which connectors run it. Named, so a refusal can say. */
|
|
@@ -724,6 +844,13 @@ export interface CatalogStageStore {
|
|
|
724
844
|
* does, because a flag is a claim and a method is the thing itself.
|
|
725
845
|
*/
|
|
726
846
|
export declare function supportsWorkflows(store: CatalogPipelineStore): store is CatalogPipelineStore & CatalogWorkflowStore;
|
|
847
|
+
/**
|
|
848
|
+
* Whether this store keeps a transform's history.
|
|
849
|
+
*
|
|
850
|
+
* The method rather than a flag, exactly as {@link supportsWorkflows} argues: a
|
|
851
|
+
* flag is a claim and a method is the thing itself.
|
|
852
|
+
*/
|
|
853
|
+
export declare function supportsTransformRevisions(store: CatalogPipelineStore): store is CatalogPipelineStore & Required<Pick<CatalogPipelineStore, 'listTransformRevisions'>>;
|
|
727
854
|
export declare function supportsWorkflowStages(store: CatalogPipelineStore): store is CatalogPipelineStore & CatalogStageStore;
|
|
728
855
|
export interface CatalogPipelineStore extends Partial<CatalogWorkflowStore>, Partial<CatalogStageStore> {
|
|
729
856
|
listConnectors(): Promise<CatalogConnector[]>;
|
|
@@ -762,6 +889,22 @@ export interface CatalogPipelineStore extends Partial<CatalogWorkflowStore>, Par
|
|
|
762
889
|
description?: string;
|
|
763
890
|
}, createdBy: string): Promise<CatalogTransform>;
|
|
764
891
|
deleteTransform(id: string): Promise<boolean>;
|
|
892
|
+
/**
|
|
893
|
+
* Every version of this transform's code, newest first.
|
|
894
|
+
*
|
|
895
|
+
* **Optional**, mixed in for the same reason {@link CatalogWorkflowStore} is:
|
|
896
|
+
* a store written against the previous shape of this interface still
|
|
897
|
+
* implements it, and turning that into a compile error would be a breaking
|
|
898
|
+
* change for an additive feature. {@link supportsTransformRevisions} is how a
|
|
899
|
+
* caller asks, so a deployment whose store keeps no history gets a sentence
|
|
900
|
+
* rather than a method that is missing at run time.
|
|
901
|
+
*
|
|
902
|
+
* The list is what makes `transformVersion` on a run mean something: the
|
|
903
|
+
* revision whose {@link CatalogRevision.version} equals it holds the code that
|
|
904
|
+
* produced those rows. Bounded — see {@link CATALOG_REVISION_LIMIT} for what
|
|
905
|
+
* that bound costs.
|
|
906
|
+
*/
|
|
907
|
+
listTransformRevisions?(id: string): Promise<CatalogRevision[]>;
|
|
765
908
|
startRun(input: {
|
|
766
909
|
connectorId: string;
|
|
767
910
|
snapshotId: string;
|
package/dist/catalog.pipeline.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* systems each believing they decide when a load runs.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.CATALOG_PIPELINE_STORE = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_EXECUTION_MODES = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_NODE_KINDS = exports.TRANSFORM_RUNNER = exports.TRANSFORM_LANGUAGES = exports.CONNECTOR_KINDS = void 0;
|
|
12
|
+
exports.CATALOG_PIPELINE_STORE = exports.WORKFLOW_ISSUE_CODES = exports.WORKFLOW_EXECUTION_MODES = exports.WORKFLOW_STATUSES = exports.WORKFLOW_NODE_ID_PATTERN = exports.WORKFLOW_NODE_KINDS = exports.TRANSFORM_RUNNER = exports.TRANSFORM_LANGUAGES = exports.CONNECTOR_KINDS = void 0;
|
|
13
13
|
exports.isConnectorKind = isConnectorKind;
|
|
14
14
|
exports.isTransformLanguage = isTransformLanguage;
|
|
15
15
|
exports.isWorkflowNodeKind = isWorkflowNodeKind;
|
|
16
|
+
exports.isWorkflowStatus = isWorkflowStatus;
|
|
16
17
|
exports.isWorkflowExecutionMode = isWorkflowExecutionMode;
|
|
17
18
|
exports.validateWorkflow = validateWorkflow;
|
|
18
19
|
exports.workflowRunOrder = workflowRunOrder;
|
|
@@ -20,6 +21,7 @@ exports.workflowGraphHash = workflowGraphHash;
|
|
|
20
21
|
exports.isWorkflowNode = isWorkflowNode;
|
|
21
22
|
exports.isWorkflowEdge = isWorkflowEdge;
|
|
22
23
|
exports.supportsWorkflows = supportsWorkflows;
|
|
24
|
+
exports.supportsTransformRevisions = supportsTransformRevisions;
|
|
23
25
|
exports.supportsWorkflowStages = supportsWorkflowStages;
|
|
24
26
|
exports.isPipelineStore = isPipelineStore;
|
|
25
27
|
/**
|
|
@@ -136,6 +138,79 @@ function isWorkflowNodeKind(value) {
|
|
|
136
138
|
* separator would let one node read another's rows.
|
|
137
139
|
*/
|
|
138
140
|
exports.WORKFLOW_NODE_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
141
|
+
/**
|
|
142
|
+
* An authored graph of steps ending in one commit.
|
|
143
|
+
*
|
|
144
|
+
* Versioned the way {@link CatalogTransform} is, and for the same question: a
|
|
145
|
+
* load that produced surprising numbers is investigated afterwards, and "what
|
|
146
|
+
* ran" is the first thing asked. For a single-transform connector that means
|
|
147
|
+
* the code; for a workflow it means the code *and* the wiring, so both the
|
|
148
|
+
* graph version and the per-node transform versions are recorded on the run.
|
|
149
|
+
*
|
|
150
|
+
* **Only the latest shape is kept, and unlike a transform it is not
|
|
151
|
+
* revisioned.** That asymmetry is a decision rather than an oversight, and this
|
|
152
|
+
* is where somebody looking for the missing feature will look, so it is argued
|
|
153
|
+
* here.
|
|
154
|
+
*
|
|
155
|
+
* A transform's code and a saved query's SQL are text a person typed, and
|
|
156
|
+
* {@link CatalogRevision} archives text: two bodies, a line differ, done. A
|
|
157
|
+
* graph is a structure. Its "body" would be JSON nobody wrote, and a text diff
|
|
158
|
+
* over it is dominated by key order and canvas positions — it would report a
|
|
159
|
+
* dragged box as a change to what the load does, which is the opposite of what
|
|
160
|
+
* {@link workflowGraphHash} is careful to exclude. Diffing graphs is a graph
|
|
161
|
+
* problem and deserves a screen that draws one, not a line differ pointed at
|
|
162
|
+
* serialised nodes.
|
|
163
|
+
*
|
|
164
|
+
* The decisive reason is the counter. {@link version} is bumped on **draft**
|
|
165
|
+
* edits deliberately — see the note on it — so that a run's `workflowVersion`
|
|
166
|
+
* can never mean two different graphs. Archiving one body per version would
|
|
167
|
+
* therefore store every autosave of a canvas somebody is still dragging boxes
|
|
168
|
+
* around on, and under the per-subject cap that {@link CATALOG_REVISION_LIMIT}
|
|
169
|
+
* imposes, that noise would evict the versions that actually ran. A counter
|
|
170
|
+
* designed to be cheap to inflate and an archive designed to be bounded do not
|
|
171
|
+
* compose; making them compose means keying the archive on behaviour rather than
|
|
172
|
+
* on saves, which is what `graphHash` already is, and that is a different
|
|
173
|
+
* feature from this one.
|
|
174
|
+
*
|
|
175
|
+
* So the limitation stays, stated plainly: an edited graph cannot be
|
|
176
|
+
* reconstructed from an old run, only identified as different. A diff screen
|
|
177
|
+
* answers for the code and the SQL and not for the wiring.
|
|
178
|
+
*/
|
|
179
|
+
/**
|
|
180
|
+
* Whether this graph is still being drawn, or is something somebody declared
|
|
181
|
+
* finished.
|
|
182
|
+
*
|
|
183
|
+
* The distinction exists because validation used to be the gate on *saving*, and
|
|
184
|
+
* that made an unfinished graph unstorable: `saveWorkflow` refused anything
|
|
185
|
+
* `validateWorkflow` had an issue with, so a canvas with one node on it could
|
|
186
|
+
* not be written down at all and closing the tab lost it. Worse, it made the
|
|
187
|
+
* canvas lie about ordinary work — clicking "+ Sink" produces a node that is
|
|
188
|
+
* unreachable from any source and names no type, both true and both useless one
|
|
189
|
+
* second after the click, because a just-added node is unwired by construction.
|
|
190
|
+
*
|
|
191
|
+
* So the gate moved rather than loosened. Validation is now the gate on
|
|
192
|
+
* publishing, and the same `validateWorkflow` still decides — a draft is not a
|
|
193
|
+
* graph that skipped the rules, it is a graph nobody has claimed is finished
|
|
194
|
+
* yet. Everything that consumes a workflow asks for `ready`: a connector may
|
|
195
|
+
* only point at one, and a promotion may only carry one. What crosses an
|
|
196
|
+
* environment should be something a person declared done.
|
|
197
|
+
*/
|
|
198
|
+
exports.WORKFLOW_STATUSES = [
|
|
199
|
+
/**
|
|
200
|
+
* Being drawn. Saves without validating, and cannot run, be scheduled, or be
|
|
201
|
+
* promoted. An incomplete node here is the normal state rather than an alarm.
|
|
202
|
+
*/
|
|
203
|
+
'draft',
|
|
204
|
+
/**
|
|
205
|
+
* Declared finished, and validated at the moment it was declared. This is the
|
|
206
|
+
* only status a connector may point at and the only one a promotion carries.
|
|
207
|
+
*/
|
|
208
|
+
'ready',
|
|
209
|
+
];
|
|
210
|
+
/** Same reason as {@link isConnectorKind}: one list, no second copy to drift. */
|
|
211
|
+
function isWorkflowStatus(value) {
|
|
212
|
+
return exports.WORKFLOW_STATUSES.some((status) => status === value);
|
|
213
|
+
}
|
|
139
214
|
/**
|
|
140
215
|
* How a workflow run is executed here.
|
|
141
216
|
*
|
|
@@ -634,7 +709,22 @@ function isWorkflowEdge(value) {
|
|
|
634
709
|
function supportsWorkflows(store) {
|
|
635
710
|
return (typeof store.listWorkflows === 'function' &&
|
|
636
711
|
typeof store.getWorkflow === 'function' &&
|
|
637
|
-
typeof store.saveWorkflow === 'function'
|
|
712
|
+
typeof store.saveWorkflow === 'function' &&
|
|
713
|
+
// Asked for by name like the rest, rather than assumed to come with
|
|
714
|
+
// `saveWorkflow`. Promotion publishes what it saves, so a store that has the
|
|
715
|
+
// save and not the transition would narrow cleanly here and then fail one
|
|
716
|
+
// call later, in the middle of an apply that has already written types and
|
|
717
|
+
// transforms into the target.
|
|
718
|
+
typeof store.publishWorkflow === 'function');
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Whether this store keeps a transform's history.
|
|
722
|
+
*
|
|
723
|
+
* The method rather than a flag, exactly as {@link supportsWorkflows} argues: a
|
|
724
|
+
* flag is a claim and a method is the thing itself.
|
|
725
|
+
*/
|
|
726
|
+
function supportsTransformRevisions(store) {
|
|
727
|
+
return typeof store.listTransformRevisions === 'function';
|
|
638
728
|
}
|
|
639
729
|
function supportsWorkflowStages(store) {
|
|
640
730
|
return typeof store.writeStage === 'function' && typeof store.readStage === 'function';
|