@hatua/model 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/authoring.d.ts +47 -0
- package/dist/blocks.d.ts +119 -0
- package/dist/blocks.test.d.ts +1 -0
- package/dist/connections.d.ts +61 -0
- package/dist/connections.test.d.ts +1 -0
- package/dist/diagnostic.d.ts +60 -0
- package/dist/execution.d.ts +43 -0
- package/dist/fixtures.d.ts +10 -0
- package/dist/generated/diagnostics.d.ts +18 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +965 -0
- package/dist/loops.test.d.ts +1 -0
- package/dist/metadata.d.ts +45 -0
- package/dist/metadata.test.d.ts +1 -0
- package/dist/rules.conformance.test.d.ts +1 -0
- package/dist/scope.d.ts +162 -0
- package/dist/scope.test.d.ts +1 -0
- package/dist/segment.d.ts +107 -0
- package/dist/segment.test.d.ts +1 -0
- package/dist/slots.d.ts +161 -0
- package/dist/slots.test.d.ts +1 -0
- package/dist/tree.d.ts +311 -0
- package/dist/tree.test.d.ts +1 -0
- package/dist/validity.d.ts +167 -0
- package/dist/validity.test.d.ts +1 -0
- package/package.json +27 -5
- package/README.md +0 -5
package/dist/tree.d.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { Block, Branch, Step, Variable, WorkflowDefinition } from '@hatua/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Pure domain rules over the step tree. No state, no I/O, no YAML — those live
|
|
4
|
+
* in @hatua/document. Everything here is a function of the typed projection.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Which Board a Step sits on: a Block's id, or `null` for the root Board.
|
|
8
|
+
*
|
|
9
|
+
* `null` rather than a sentinel string because the root Board is not a Block and
|
|
10
|
+
* has no id to borrow — a string would have to be one no Block could ever be
|
|
11
|
+
* called, which is a reserved word this design spent ADR-0014 getting rid of.
|
|
12
|
+
*/
|
|
13
|
+
export type BoardId = string | null;
|
|
14
|
+
/**
|
|
15
|
+
* One drawable Step tree and the root that gives it its parameters.
|
|
16
|
+
*
|
|
17
|
+
* A document holds the root Board, whose root is `triggers:`, plus one per
|
|
18
|
+
* Block, whose root is its declared contract. Scope is computed against a Board
|
|
19
|
+
* and never across two, which is what keeps a call a cross-link with a contract
|
|
20
|
+
* rather than a jump (ADR-0013).
|
|
21
|
+
*/
|
|
22
|
+
export interface Board {
|
|
23
|
+
readonly id: BoardId;
|
|
24
|
+
/** The Block this Board belongs to. Absent on the root Board. */
|
|
25
|
+
readonly block?: Block;
|
|
26
|
+
readonly steps: readonly Step[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read one key of a document-supplied map.
|
|
30
|
+
*
|
|
31
|
+
* `Object.hasOwn` is the whole guarantee, and it is the same one `resolve.ts`
|
|
32
|
+
* gives for `{{ steps.s2.constructor }}`. A Workflow Definition is user-editable
|
|
33
|
+
* YAML and the schema's identifier rule permits underscores, so `__proto__` is a
|
|
34
|
+
* legal field key, a legal declaration key and a legal var key — and a bare
|
|
35
|
+
* `values[k]` there reads `Object.prototype` rather than nothing, which makes a
|
|
36
|
+
* missing value look present. Go has no prototype to find, so this is also what
|
|
37
|
+
* keeps the two languages saying the same thing about the same document.
|
|
38
|
+
*/
|
|
39
|
+
export declare const own: (values: Record<string, unknown> | undefined, key: string) => unknown;
|
|
40
|
+
/** A Step, and the Board it is on. Neither half identifies one alone. */
|
|
41
|
+
export interface StepRef {
|
|
42
|
+
readonly board: BoardId;
|
|
43
|
+
readonly id: string;
|
|
44
|
+
}
|
|
45
|
+
/** A position among a list of sibling Steps, named in domain terms rather than YAML paths. */
|
|
46
|
+
export interface InsertPoint {
|
|
47
|
+
/**
|
|
48
|
+
* Which Board the position is on: a Block's id, or absent for the root.
|
|
49
|
+
*
|
|
50
|
+
* Every path below is rooted here rather than at `['steps']`. That single
|
|
51
|
+
* parameter is what makes an edit on a Block's Board the same command as an
|
|
52
|
+
* edit on the root's — which is the property the extract-into-a-block gesture
|
|
53
|
+
* needs, and what keeps a Block built on the canvas and one hand-written in
|
|
54
|
+
* Text Mode the same document.
|
|
55
|
+
*/
|
|
56
|
+
board?: BoardId;
|
|
57
|
+
/**
|
|
58
|
+
* The container Step whose children receive it. Absent for the Board's
|
|
59
|
+
* root sequence.
|
|
60
|
+
*/
|
|
61
|
+
parentId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Which of a `core.fork`'s branches, by index. Absent for a `core.for_each`'s
|
|
64
|
+
* own nested `steps`, and absent at the root.
|
|
65
|
+
*/
|
|
66
|
+
branchIndex?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Which of a `core.try`'s two regions. Absent means the body under `steps:`,
|
|
69
|
+
* which is the same key a loop's children sit under.
|
|
70
|
+
*
|
|
71
|
+
* A named region rather than a second index, because the two are not a list:
|
|
72
|
+
* a try has exactly one body and exactly one handler, and an index would let
|
|
73
|
+
* a caller ask for the third one.
|
|
74
|
+
*/
|
|
75
|
+
region?: 'handler';
|
|
76
|
+
/** Position among the siblings. The list's length appends. */
|
|
77
|
+
index: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Every Board in the document, root first.
|
|
81
|
+
*
|
|
82
|
+
* This is the traversal that cannot forget a Block. A validator walking
|
|
83
|
+
* `doc.steps` sees a document with three Blocks in it and reports nothing about
|
|
84
|
+
* any of them, silently — so nothing here walks `doc.steps` directly.
|
|
85
|
+
*/
|
|
86
|
+
export declare function boards(doc: WorkflowDefinition): Generator<Board>;
|
|
87
|
+
/** One Board by id, or undefined when nothing declares it. */
|
|
88
|
+
export declare function boardOf(doc: WorkflowDefinition, id: BoardId): Board | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The verb that protects a region and falls back to a handler.
|
|
91
|
+
*
|
|
92
|
+
* The one container with two child regions: a body under `steps:` and a handler
|
|
93
|
+
* under `handler:`. Wrapping one Step is retry, wrapping a region is fallback,
|
|
94
|
+
* so one verb serves both (ADR-0013).
|
|
95
|
+
*
|
|
96
|
+
* Its retry policy — how many attempts, how long to wait — sits in `with:` as
|
|
97
|
+
* ordinary manifest fields, and deliberately NOT in a structural key. `until`
|
|
98
|
+
* had to leave `with:` because `FIELD_KIND_TYPES` has no mappable boolean, so a
|
|
99
|
+
* condition there would have type-checked as text. An attempt count is a number
|
|
100
|
+
* and `number` IS a mappable field kind, so the argument that moved `until` does
|
|
101
|
+
* not reach here at all — following it anyway would be copying a conclusion
|
|
102
|
+
* without its reason, and would cost a structural key, a diagnostic and a form
|
|
103
|
+
* control that the manifest already gives for nothing.
|
|
104
|
+
*
|
|
105
|
+
* It sits beside the region vocabulary rather than beside the other verbs
|
|
106
|
+
* because the only question anything asks it is which word goes over a region:
|
|
107
|
+
* `steps:` is one key holding a loop's children and a try's protected body
|
|
108
|
+
* alike, and this is what tells them apart.
|
|
109
|
+
*/
|
|
110
|
+
export declare const TRY_VERB = "core.try";
|
|
111
|
+
/**
|
|
112
|
+
* The verb that repeats its children until a condition holds.
|
|
113
|
+
*
|
|
114
|
+
* It sits beside `TRY_VERB` and the region vocabulary because the only question
|
|
115
|
+
* anything asks it here is whether a region always runs: a `core.repeat` tests
|
|
116
|
+
* its `until` AFTER the body, so the body runs at least once, while a
|
|
117
|
+
* `core.for_each`'s list may be empty and its body may never run. `steps:` is
|
|
118
|
+
* one key holding both, so the verb is what tells them apart.
|
|
119
|
+
*/
|
|
120
|
+
export declare const REPEAT_VERB = "core.repeat";
|
|
121
|
+
/** Which of a container's child regions a step list is. */
|
|
122
|
+
export type RegionKind = 'branch' | 'body' | 'handler';
|
|
123
|
+
/**
|
|
124
|
+
* One child region a container Step owns, and which region it is.
|
|
125
|
+
*
|
|
126
|
+
* A Fork contributes one per Branch, a `core.for_each` and a `core.repeat` one
|
|
127
|
+
* body each, and a `core.try` two — a body under `steps:` and a handler under
|
|
128
|
+
* `handler:` (ADR-0013).
|
|
129
|
+
*/
|
|
130
|
+
export interface Region {
|
|
131
|
+
readonly kind: RegionKind;
|
|
132
|
+
readonly steps: readonly Step[];
|
|
133
|
+
/**
|
|
134
|
+
* The word that goes over this region — `if` / `else if` / `else` / `and`
|
|
135
|
+
* over a Branch, `attempt` or `loop` over a body, `on failure` over a
|
|
136
|
+
* handler.
|
|
137
|
+
*
|
|
138
|
+
* Here rather than at each surface, because two surfaces draw every region:
|
|
139
|
+
* `<StepList>` puts it in a chip over the region and the canvas puts it in
|
|
140
|
+
* the band above it, and a word each works out for itself is a word they can
|
|
141
|
+
* disagree about. `kind` says which region this is and this says what it is
|
|
142
|
+
* called, so a reader gains both by construction.
|
|
143
|
+
*
|
|
144
|
+
* The verb decides the word and never whether the region exists. A `handler:`
|
|
145
|
+
* on a `core.fork` is meaningless and no runner reads it, but `walkSteps`
|
|
146
|
+
* still yields the Steps inside it, so a surface refusing to draw it makes
|
|
147
|
+
* those Steps unreachable rather than absent.
|
|
148
|
+
*/
|
|
149
|
+
readonly keyword: string;
|
|
150
|
+
/**
|
|
151
|
+
* Whether this region runs every time its Step does.
|
|
152
|
+
*
|
|
153
|
+
* A Branch does not — its `when` is answered at run time. A `core.try`'s body
|
|
154
|
+
* does, because it always starts; its handler does not, because it needs a
|
|
155
|
+
* failure. A `core.repeat`'s body does, because `until` is tested after it; a
|
|
156
|
+
* `core.for_each`'s does not, because the list may be empty.
|
|
157
|
+
*
|
|
158
|
+
* That is the same line `alwaysReturns` in validity.ts draws to decide whether
|
|
159
|
+
* a region discharges a Block's obligation to return — one question, "is this
|
|
160
|
+
* region guaranteed to run at all", asked here of geometry and there of
|
|
161
|
+
* validity. Two readings of it are two answers waiting to disagree.
|
|
162
|
+
*/
|
|
163
|
+
readonly always: boolean;
|
|
164
|
+
/** The Branch this region is. Absent on a body and on a handler. */
|
|
165
|
+
readonly branch?: Branch;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The regions one Step owns, in document order.
|
|
169
|
+
*
|
|
170
|
+
* The single answer to "what does this Step nest". Every traversal of the tree
|
|
171
|
+
* asks that question, and one that answers it for itself is one that can forget
|
|
172
|
+
* a region — a region no rule then sees, reported by nothing, in silence. That
|
|
173
|
+
* has to be spelled out once so a reader gains coverage of a new region by
|
|
174
|
+
* construction rather than by remembering to ask for it.
|
|
175
|
+
*
|
|
176
|
+
* The region is named rather than yielded as a bare list because a reader that
|
|
177
|
+
* says something different about each — the word over it, whether it always
|
|
178
|
+
* runs — still has to get its regions from here.
|
|
179
|
+
*/
|
|
180
|
+
export declare function regionsOf(step: Step): Generator<Region>;
|
|
181
|
+
/**
|
|
182
|
+
* Whether a Step owns child regions at all.
|
|
183
|
+
*
|
|
184
|
+
* Asked of `regionsOf` rather than of the three keys, so "container" and "what a
|
|
185
|
+
* container nests" cannot come apart — a fourth region would otherwise be
|
|
186
|
+
* walked by every reader while still reading as a leaf to whichever surface
|
|
187
|
+
* decides how tall a card is or whether it collapses.
|
|
188
|
+
*/
|
|
189
|
+
export declare const isContainer: (step: Step) => boolean;
|
|
190
|
+
/**
|
|
191
|
+
* What a Step is called on screen: its name, falling back to its id.
|
|
192
|
+
*
|
|
193
|
+
* An id is the one thing a Step always has, and it is what a user typed if they
|
|
194
|
+
* hand-wrote the file. Here rather than at each surface because the list, the
|
|
195
|
+
* canvas and every sentence a screen reader hears have to name one Step one
|
|
196
|
+
* way — two spellings is a card and a row that look like two Steps.
|
|
197
|
+
*/
|
|
198
|
+
export declare const nameOf: (step: Step) => string;
|
|
199
|
+
/**
|
|
200
|
+
* What makes a Step structural, in words: `core.fork · 2 branches`,
|
|
201
|
+
* `core.try · 1 step · handler`.
|
|
202
|
+
*
|
|
203
|
+
* Enumerated off `regionsOf` rather than off the three step keys, so a region
|
|
204
|
+
* added to the walk shows up in the summary by construction. A summary read off
|
|
205
|
+
* `steps:` alone says `core.try` on a try carrying only a handler — a card with
|
|
206
|
+
* a chevron and an `on failure` region under it, describing itself as a leaf.
|
|
207
|
+
*
|
|
208
|
+
* A leaf's summary is its verb and nothing else, which is why the canvas shows
|
|
209
|
+
* this only on the cards `isContainer` makes taller: `LAYOUT.nodeHeight` is "a
|
|
210
|
+
* card with a name and nothing else", so a leaf card has nowhere to put a row.
|
|
211
|
+
* One predicate decides the height and the content, and they cannot come apart.
|
|
212
|
+
*/
|
|
213
|
+
export declare function summaryOf(step: Step): string;
|
|
214
|
+
/**
|
|
215
|
+
* Depth-first walk of every step in one tree, parents before children.
|
|
216
|
+
*
|
|
217
|
+
* Every region a container owns is walked here and nowhere else. A region
|
|
218
|
+
* `regionsOf` forgets is a region no rule ever sees — the validator reports
|
|
219
|
+
* nothing about it, silently, which is the same failure as a validator that
|
|
220
|
+
* only ever looked at the root Board.
|
|
221
|
+
*/
|
|
222
|
+
export declare function walkSteps(steps: readonly Step[]): Generator<Step>;
|
|
223
|
+
/**
|
|
224
|
+
* Every Step in the document, tagged with the Board it is on.
|
|
225
|
+
*
|
|
226
|
+
* `walkSteps` is the primitive — "walk this list" — and this is what supplies it
|
|
227
|
+
* every list there is. A rule written against this one gains Block coverage by
|
|
228
|
+
* construction rather than by remembering to ask for it.
|
|
229
|
+
*/
|
|
230
|
+
export declare function walkDocument(doc: WorkflowDefinition): Generator<StepRef & {
|
|
231
|
+
step: Step;
|
|
232
|
+
}>;
|
|
233
|
+
/**
|
|
234
|
+
* One string naming one Step, for the places that need a flat key — a `Map`, a
|
|
235
|
+
* React key, a `data-` attribute.
|
|
236
|
+
*
|
|
237
|
+
* Minted here rather than concatenated at each call site: five hand-rolled
|
|
238
|
+
* spellings are five chances to pick a different separator, and two of them
|
|
239
|
+
* disagreeing is a diagnostic filed under a key nothing looks up. `/` is safe
|
|
240
|
+
* because the schema holds every id to an identifier, which cannot contain one.
|
|
241
|
+
*/
|
|
242
|
+
export declare const stepKey: ({ board, id }: StepRef) => string;
|
|
243
|
+
/**
|
|
244
|
+
* One Board as a string, for a reader that holds something per Board.
|
|
245
|
+
*
|
|
246
|
+
* Prefixed rather than the bare id, because the root Board is `null` and a
|
|
247
|
+
* Block whose id is the empty string would key the same — so the two would
|
|
248
|
+
* share whatever is held, and opening that Block would find the root's.
|
|
249
|
+
*
|
|
250
|
+
* Minted here for the reason `stepKey` is: a viewport keyed one way and a
|
|
251
|
+
* selection keyed another are two maps that disagree about which Board is which.
|
|
252
|
+
*/
|
|
253
|
+
export declare const boardKey: (id: BoardId) => string;
|
|
254
|
+
/**
|
|
255
|
+
* One child region, and the Step it hangs under.
|
|
256
|
+
*
|
|
257
|
+
* A `StepRef` widened by which of that Step's regions this is, because a
|
|
258
|
+
* `core.try` owns two and a Fork owns *n* — so a Step alone does not name one.
|
|
259
|
+
* The shape composes with what a `Band` already carries, so the canvas can name
|
|
260
|
+
* the region it is drawing without a second enumeration.
|
|
261
|
+
*
|
|
262
|
+
* Named by `kind` rather than by an ordinal into `regionsOf`, because anything
|
|
263
|
+
* held against a region — which one is folded shut — outlives the edits made
|
|
264
|
+
* while it is held. A body and a handler have a `kind` that names them
|
|
265
|
+
* outright, so neither takes a number and neither can move: adding a Branch to
|
|
266
|
+
* a Fork that also carries a `handler:` would have shifted an ordinal.
|
|
267
|
+
*
|
|
268
|
+
* **A Branch is not stable, and cannot be.** `branchIndex` is an ordinal into
|
|
269
|
+
* `branches:`, so inserting a Branch before it moves the fold onto its
|
|
270
|
+
* neighbour. There is nothing better to use: a Branch carries no id, and the
|
|
271
|
+
* schema refuses its `label` for identity because that is free text a user
|
|
272
|
+
* renames. Naming a Branch by *where it is* is the whole of what is available,
|
|
273
|
+
* and the narrower spelling buys the other two regions and not this one.
|
|
274
|
+
*/
|
|
275
|
+
export interface RegionRef {
|
|
276
|
+
readonly board: BoardId;
|
|
277
|
+
/** The container Step that owns the region. */
|
|
278
|
+
readonly id: string;
|
|
279
|
+
readonly kind: RegionKind;
|
|
280
|
+
/** Which of the owner's Branches this is. Absent on a body and on a handler. */
|
|
281
|
+
readonly branchIndex?: number;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* One string naming one region, for the places that need a flat key — a `Set`, a
|
|
285
|
+
* React key, a `data-` attribute.
|
|
286
|
+
*
|
|
287
|
+
* Minted beside `stepKey` and for the same reason: hand-rolled spellings are
|
|
288
|
+
* chances to pick a different separator, and two readers disagreeing about the
|
|
289
|
+
* spelling is a region folded under a key nothing looks up. `#` and `:` are safe
|
|
290
|
+
* because the schema holds every id to an identifier, which contains neither.
|
|
291
|
+
*/
|
|
292
|
+
export declare const regionKey: ({ board, id, kind, branchIndex }: RegionRef) => string;
|
|
293
|
+
/** A Step by Board and id. Both halves are needed: ids are Board-local. */
|
|
294
|
+
export declare function findStep(doc: WorkflowDefinition, ref: StepRef): Step | undefined;
|
|
295
|
+
/**
|
|
296
|
+
* The variables one Board declares: the workflow's at the root, a Block's inside
|
|
297
|
+
* one.
|
|
298
|
+
*
|
|
299
|
+
* This is the whole of "a `core.set_var` can never reach out of the Board it is
|
|
300
|
+
* on" — there is no second list to fall back to, so a Block naming a workflow
|
|
301
|
+
* variable is an unknown name rather than a scope a runner resolves differently.
|
|
302
|
+
*
|
|
303
|
+
* Exported because a runner has to answer the same question the builder does,
|
|
304
|
+
* and the Go SDK's `VarsOn` is this function: a rule restated at two call sites
|
|
305
|
+
* is two rules the day one of them gains a fallback.
|
|
306
|
+
*/
|
|
307
|
+
export declare const varsOn: (doc: WorkflowDefinition, board: BoardId) => readonly Variable[];
|
|
308
|
+
/** One Board's variable by key, or undefined when that Board declares none. */
|
|
309
|
+
export declare const variableOn: (doc: WorkflowDefinition, board: BoardId, key: string) => Variable | undefined;
|
|
310
|
+
/** Every step id on one Board, for detecting references to steps that vanished. */
|
|
311
|
+
export declare function stepIds(doc: WorkflowDefinition, board: BoardId): Set<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { FunctionDeclarations } from '@hatua/expressions';
|
|
2
|
+
import { ContextKey, Manifest, WorkflowDefinition } from '@hatua/schema';
|
|
3
|
+
import { ConnectionTypes } from './connections';
|
|
4
|
+
import { Diagnostic } from './diagnostic';
|
|
5
|
+
/**
|
|
6
|
+
* Whether a Workflow Definition is filled in enough to run — the rules that read
|
|
7
|
+
* a Step against its Component Manifest, the verbs Hatua interprets
|
|
8
|
+
* structurally, the contract a Block declares, and the Connections a `conn`
|
|
9
|
+
* field may hold.
|
|
10
|
+
*
|
|
11
|
+
* Here rather than in @hatua/services for the same reason `tree.ts` is: these
|
|
12
|
+
* are pure domain rules over the typed projection, so a Host's runner can hold
|
|
13
|
+
* a definition to exactly what the builder held it to. The store that watches
|
|
14
|
+
* them and the region that draws a dot are separate concerns and live
|
|
15
|
+
* elsewhere.
|
|
16
|
+
*
|
|
17
|
+
* Every code and what it blocks is declared in
|
|
18
|
+
* `schemas/definition-diagnostics.yaml` and generated into both languages,
|
|
19
|
+
* because `blocks` is part of the contract: a code that stopped Publish here and
|
|
20
|
+
* merely informed in Go would let a workflow publish from one builder and not
|
|
21
|
+
* another.
|
|
22
|
+
*
|
|
23
|
+
* Nothing below walks `doc.steps`. `walkDocument` yields every Step on every
|
|
24
|
+
* Board, so a rule written here covers a Block's steps by construction — the
|
|
25
|
+
* alternative is a validator that reports nothing about three Blocks, silently,
|
|
26
|
+
* because it only ever looked at the root.
|
|
27
|
+
*/
|
|
28
|
+
type ManifestIndex = ReadonlyMap<string, Manifest>;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a field is shown, and therefore whether it can be missing.
|
|
31
|
+
*
|
|
32
|
+
* `when: [otherKey, value]` shows a field only while another field equals a
|
|
33
|
+
* value — it is how one trigger component reshapes its form across schedule,
|
|
34
|
+
* API and upstream modes. Counting a hidden field as unfilled would mark a Step
|
|
35
|
+
* invalid for a field the user cannot see, let alone fill.
|
|
36
|
+
*
|
|
37
|
+
* Exported because the form that draws the fields has to ask the same question,
|
|
38
|
+
* and two copies of it are two answers waiting to disagree: a hidden field that
|
|
39
|
+
* starts blocking Publish, or a visible required one that stops being reported.
|
|
40
|
+
* `reference.ts` and `slots.ts` refuse the same duplication for the same
|
|
41
|
+
* reason — the rule lives once, in the package that owns the domain.
|
|
42
|
+
*/
|
|
43
|
+
export declare const fieldVisible: (field: Manifest["fields"][number], values: Record<string, unknown>) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Required fields with nothing in them, per Step.
|
|
46
|
+
*
|
|
47
|
+
* A call and a `core.return` are checked against a declaration rather than a
|
|
48
|
+
* manifest — `block.<id>`'s fields are the Block's `params`, and a return's are
|
|
49
|
+
* the Block's `outputs`. Every parameter is required: a declaration IS the
|
|
50
|
+
* contract, and an optional one would be a second concept for a Block to carry.
|
|
51
|
+
*/
|
|
52
|
+
export declare function missingRequiredFields(doc: WorkflowDefinition, manifests: ManifestIndex): Diagnostic[];
|
|
53
|
+
/**
|
|
54
|
+
* A Step or a Trigger whose verb nothing declares.
|
|
55
|
+
*
|
|
56
|
+
* The two roots fail differently, so they are two codes. A `component.*` verb
|
|
57
|
+
* nothing declares blocks editing, because building cannot produce it: the
|
|
58
|
+
* catalogue only offers what it declares, so it means a hand-edit or a Host that
|
|
59
|
+
* dropped a component — and in the second case the fields are gone too, so there
|
|
60
|
+
* is nothing to fill in. A `block.*` verb naming nothing blocks Publish only:
|
|
61
|
+
* renaming or deleting a block is ordinary building, and locking the document
|
|
62
|
+
* for editing over a name the user can fix three lines away would be absurd.
|
|
63
|
+
*
|
|
64
|
+
* Triggers are checked alongside Steps because the same mistake is possible in
|
|
65
|
+
* `triggers:` and has the same consequence: `missingRequiredFields` returns
|
|
66
|
+
* early for a manifest it cannot find, so without this a Trigger naming a verb
|
|
67
|
+
* nothing declares produces no diagnostic at all, while the Board panel draws
|
|
68
|
+
* it as a card that says its type is unknown.
|
|
69
|
+
*/
|
|
70
|
+
export declare function unknownComponents(doc: WorkflowDefinition, manifests: ManifestIndex): Diagnostic[];
|
|
71
|
+
/**
|
|
72
|
+
* The verbs Hatua interprets structurally, held to what they mean.
|
|
73
|
+
*
|
|
74
|
+
* These are read from the tree rather than from a manifest, because a manifest
|
|
75
|
+
* cannot express them: `core.fork`'s Branches and `core.for_each`'s body are
|
|
76
|
+
* positions in the document, not fields under `with:`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function malformedContainers(doc: WorkflowDefinition, manifests?: ManifestIndex): Diagnostic[];
|
|
79
|
+
/**
|
|
80
|
+
* The rules a Block carries: recursion, where a return may sit, and whether one
|
|
81
|
+
* is reached.
|
|
82
|
+
*
|
|
83
|
+
* Recursion is answered here rather than by a runner's depth limit because
|
|
84
|
+
* ADR-0013 refuses it at design time — "unbounded recursion is the jump problem
|
|
85
|
+
* wearing a contract's clothes" — so both builders refuse the same document
|
|
86
|
+
* rather than one of them discovering it in production.
|
|
87
|
+
*/
|
|
88
|
+
export declare function blockRules(doc: WorkflowDefinition): Diagnostic[];
|
|
89
|
+
/** Every step-level rule, in one pass, indexed by the Step it belongs to. */
|
|
90
|
+
export interface Validity {
|
|
91
|
+
/**
|
|
92
|
+
* Diagnostics for each Step that has any, keyed by `stepKey` — Board and id
|
|
93
|
+
* together, because a Block's `ret` and another Block's `ret` are two Steps.
|
|
94
|
+
*/
|
|
95
|
+
byStep: ReadonlyMap<string, Diagnostic[]>;
|
|
96
|
+
/** The same, for Triggers, which are not Steps and are drawn by another region. */
|
|
97
|
+
byTrigger: ReadonlyMap<string, Diagnostic[]>;
|
|
98
|
+
/** The same, for what belongs to a Block rather than to any Step in it. */
|
|
99
|
+
byBlock: ReadonlyMap<string, Diagnostic[]>;
|
|
100
|
+
/**
|
|
101
|
+
* The same, for what belongs to a Connection rather than to any Step using it.
|
|
102
|
+
*
|
|
103
|
+
* Only CONNECTION_NOT_ESTABLISHED files here: the other connection codes are
|
|
104
|
+
* raised at a field, so they name a Step or a Trigger as well and belong on
|
|
105
|
+
* the row the user can act on. A Connection declared and never wired is at
|
|
106
|
+
* fault on its own — it is unfinished whether or not anything points at it
|
|
107
|
+
* yet — and there is no Step to hang it on.
|
|
108
|
+
*
|
|
109
|
+
* A fourth map for the reason the second and third exist, and the fault is
|
|
110
|
+
* filed once here rather than raised again at every field that points at the
|
|
111
|
+
* Connection — the duplication `troubledBlocks` refuses for a call, one seam
|
|
112
|
+
* over: a Publish gate counting faults must not count one Connection five
|
|
113
|
+
* times because five Steps use it. The `conn` field that draws it looks the
|
|
114
|
+
* Connection up by the id it holds.
|
|
115
|
+
*/
|
|
116
|
+
byConnection: ReadonlyMap<string, Diagnostic[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Everything, in the order the rules ran.
|
|
119
|
+
*
|
|
120
|
+
* Returned rather than left to a caller to flatten out of `byStep`: a
|
|
121
|
+
* diagnostic about a Trigger has no `stepId`, so flattening the Step map
|
|
122
|
+
* silently drops it — and a Publish gate counting what it found there would
|
|
123
|
+
* pass a workflow whose Trigger is missing a required field.
|
|
124
|
+
*/
|
|
125
|
+
all: readonly Diagnostic[];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Every Template on every Board, checked against the type its field declares
|
|
129
|
+
* and the scope its Step can see.
|
|
130
|
+
*
|
|
131
|
+
* The fifth rule family, and the one that reaches into a different package:
|
|
132
|
+
* `@hatua/expressions` owns the grammar and the checking, and has done since
|
|
133
|
+
* before anything called it. What was missing was the walk — "which Templates
|
|
134
|
+
* are there, and what may each of them address" is a question about a Workflow
|
|
135
|
+
* Definition, which is this package's subject and not that one's.
|
|
136
|
+
*
|
|
137
|
+
* **Errors only.** An expression diagnostic carries a severity, and a warning
|
|
138
|
+
* informs without blocking anything (ADR-0009's gradual typing: an unprovable
|
|
139
|
+
* type defers to run time). A `Diagnostic` here carries `blocks` instead, whose
|
|
140
|
+
* two values both block something, so a warning has nothing to become. The
|
|
141
|
+
* Inspector calls `validate` itself and renders every severity; this pass exists
|
|
142
|
+
* to mark cards and gate Publish, and neither is a warning's business.
|
|
143
|
+
*
|
|
144
|
+
* **Steps only.** A Trigger's fields and a Variable's initial value are
|
|
145
|
+
* Templates too, and neither has a scope anyone has defined — `scopeFor` takes a
|
|
146
|
+
* `StepRef` because a scope is a position in a tree, and a Trigger is upstream
|
|
147
|
+
* of every position. Checking them against a scope invented here would be a
|
|
148
|
+
* second answer to a question the model has not been asked yet.
|
|
149
|
+
*/
|
|
150
|
+
export declare function expressionRules(doc: WorkflowDefinition, manifests: ManifestIndex, context?: readonly ContextKey[], functions?: FunctionDeclarations): Diagnostic[];
|
|
151
|
+
/**
|
|
152
|
+
* Every rule family over every Board, filed by the subject each one names.
|
|
153
|
+
*
|
|
154
|
+
* `connectionTypes` is the one input that is not the document or a manifest. A
|
|
155
|
+
* Connection stores an opaque `ref` and nothing more (ADR-0007), so its type
|
|
156
|
+
* comes from the Host, asynchronously, and may never come at all — a Host that
|
|
157
|
+
* wires no `ConnectionSource` is correctly configured, not broken.
|
|
158
|
+
*
|
|
159
|
+
* **Absence narrows this pass; it never stops it.** Handed no types, the two
|
|
160
|
+
* connection codes that need one go unreported and every other family runs
|
|
161
|
+
* exactly as it would have. The alternative — returning nothing until the
|
|
162
|
+
* Connections arrive — would leave a Host that wires no `ConnectionSource` with
|
|
163
|
+
* no validation whatsoever, silently, including the rules that have nothing to
|
|
164
|
+
* do with Connections. See ADR-0022.
|
|
165
|
+
*/
|
|
166
|
+
export declare function validateDefinition(doc: WorkflowDefinition, manifests: ManifestIndex, context?: readonly ContextKey[], connectionTypes?: ConnectionTypes): Validity;
|
|
167
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatua/model",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Internal to @hatua/react — published because it is an external of its build, not a supported API. Pure domain rules over the step tree: references, scope, validation.",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/pedromvgomes/hatua.git",
|
|
9
10
|
"directory": "source/packages/model"
|
|
10
11
|
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
11
22
|
"publishConfig": {
|
|
12
23
|
"access": "public"
|
|
13
24
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@hatua/schema": "0.1.0",
|
|
27
|
+
"@hatua/expressions": "0.1.0",
|
|
28
|
+
"@hatua/document": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"yaml": "^2.8.1"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "vite build",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run --passWithNoTests",
|
|
37
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary --coverage.reporter=lcovonly --passWithNoTests"
|
|
38
|
+
}
|
|
17
39
|
}
|