@ngis/plugin-ui 0.4.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.
Files changed (79) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +25 -0
  3. package/dist/index.cjs +71 -0
  4. package/dist/index.d.ts +114 -0
  5. package/dist/index.mjs +69 -0
  6. package/dist/src/api/analysis.d.ts +140 -0
  7. package/dist/src/api/resources.d.ts +530 -0
  8. package/dist/src/api/scenes.d.ts +251 -0
  9. package/dist/src/api/users.d.ts +22 -0
  10. package/dist/src/api/workflows.d.ts +191 -0
  11. package/dist/src/components/plugin-ui/AttributeTable.d.ts +26 -0
  12. package/dist/src/components/plugin-ui/BatchTable.d.ts +47 -0
  13. package/dist/src/components/plugin-ui/ChartCanvas.d.ts +14 -0
  14. package/dist/src/components/plugin-ui/ExtentPicker.d.ts +22 -0
  15. package/dist/src/components/plugin-ui/FieldSelect.d.ts +23 -0
  16. package/dist/src/components/plugin-ui/Flow.d.ts +256 -0
  17. package/dist/src/components/plugin-ui/ModelSelect.d.ts +36 -0
  18. package/dist/src/components/plugin-ui/ParamForm.d.ts +63 -0
  19. package/dist/src/components/plugin-ui/PickerField.d.ts +52 -0
  20. package/dist/src/components/plugin-ui/ResourceSelect.d.ts +37 -0
  21. package/dist/src/components/plugin-ui/ResultActions.d.ts +48 -0
  22. package/dist/src/components/plugin-ui/RunButton.d.ts +49 -0
  23. package/dist/src/components/plugin-ui/StatChart.d.ts +66 -0
  24. package/dist/src/components/plugin-ui/TaskChrome.d.ts +35 -0
  25. package/dist/src/components/plugin-ui/TaskList.d.ts +29 -0
  26. package/dist/src/components/plugin-ui/TaskStatus.d.ts +55 -0
  27. package/dist/src/components/plugin-ui/batch-model.d.ts +442 -0
  28. package/dist/src/components/plugin-ui/flow-inputs.d.ts +61 -0
  29. package/dist/src/components/plugin-ui/flow-model.d.ts +303 -0
  30. package/dist/src/components/plugin-ui/flow-ports.d.ts +155 -0
  31. package/dist/src/components/plugin-ui/param-form-data.d.ts +122 -0
  32. package/dist/src/components/plugin-ui/param-form-model.d.ts +187 -0
  33. package/dist/src/components/plugin-ui/param-widgets.d.ts +88 -0
  34. package/dist/src/components/plugin-ui/picker-data.d.ts +141 -0
  35. package/dist/src/components/plugin-ui/picker-model.d.ts +150 -0
  36. package/dist/src/components/plugin-ui/recipe-model.d.ts +320 -0
  37. package/dist/src/components/plugin-ui/result-data.d.ts +112 -0
  38. package/dist/src/components/plugin-ui/result-model.d.ts +96 -0
  39. package/dist/src/components/plugin-ui/run-data.d.ts +118 -0
  40. package/dist/src/components/plugin-ui/run-model.d.ts +179 -0
  41. package/dist/src/components/plugin-ui/stat-chart-model.d.ts +168 -0
  42. package/dist/src/components/plugin-ui/validation-messages.d.ts +18 -0
  43. package/dist/src/components/plugin-ui/w2-contracts.d.ts +151 -0
  44. package/dist/src/components/plugin-ui/w3-contracts.d.ts +328 -0
  45. package/dist/src/components/plugin-ui/w4-contracts.d.ts +510 -0
  46. package/dist/src/components/ui/button.d.ts +12 -0
  47. package/dist/src/components/ui/checkbox.d.ts +4 -0
  48. package/dist/src/components/ui/column-resize-handle.d.ts +23 -0
  49. package/dist/src/components/ui/dropdown-menu.d.ts +25 -0
  50. package/dist/src/components/ui/input.d.ts +3 -0
  51. package/dist/src/components/ui/label.d.ts +4 -0
  52. package/dist/src/components/ui/popover.d.ts +10 -0
  53. package/dist/src/components/ui/select.d.ts +15 -0
  54. package/dist/src/components/ui/switch.d.ts +6 -0
  55. package/dist/src/domain/map/types.d.ts +128 -0
  56. package/dist/src/features/gis/components/GisAttributeTable.d.ts +56 -0
  57. package/dist/src/features/gis/hooks/useDatasetFeatureWindow.d.ts +51 -0
  58. package/dist/src/lib/api-error.d.ts +81 -0
  59. package/dist/src/lib/api.d.ts +82 -0
  60. package/dist/src/lib/auth-session.d.ts +22 -0
  61. package/dist/src/lib/hooks/useColumnResize.d.ts +57 -0
  62. package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
  63. package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
  64. package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
  65. package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
  66. package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
  67. package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
  68. package/dist/src/lib/utils.d.ts +2 -0
  69. package/dist/src/sdk/facets/types.d.ts +370 -0
  70. package/dist/src/sdk/host-api.d.ts +256 -0
  71. package/dist/src/sdk/map-api.d.ts +288 -0
  72. package/dist/src/sdk/panels.d.ts +130 -0
  73. package/dist/src/types/analysis.d.ts +205 -0
  74. package/dist/src/types/common.d.ts +17 -0
  75. package/dist/src/types/extensions.d.ts +560 -0
  76. package/dist/src/types/gis.d.ts +242 -0
  77. package/dist/src/types/market-social.d.ts +378 -0
  78. package/dist/src/types/workflow.d.ts +83 -0
  79. package/package.json +59 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * The flow engine's **React-free core** (Stage 14 Y5a — **F14.4**, **F14.5**,
3
+ * **F14.10**, **F14.1**).
4
+ *
5
+ * No React, no host, no clock, no API client, no DOM. That is not the house
6
+ * style repeated for a fourth time; it is the load-bearing claim of this wave.
7
+ * F14.10's semantics — a node is enabled iff every required in-port is
8
+ * satisfied, the reason is generated from the unsatisfied port's label, and
9
+ * there are exactly three disclosure states — are **entirely decidable from
10
+ * declarative data**. A core that needed React to decide them would make
11
+ * "every plugin-ui component stays host-renderable from declarative data" an
12
+ * assertion instead of a proof, and Stage 15's worker tier is the boundary that
13
+ * would eventually collect on the difference. So: `<Flow>` (Y5b) holds the
14
+ * React state and calls this module; this module decides everything.
15
+ *
16
+ * ## What this module decides, and the three questions behind it
17
+ *
18
+ * 1. **What is bound.** {@link resolveFlow} walks F14.4's four value forms
19
+ * (static literal, `{$context}`, `{$input}`, `"@ref"`) and three reference
20
+ * forms (`"@id"`, `"@id.member"`, `...ngisFlowSpread("@id")`) and answers one
21
+ * {@link NgisPortResolution} per in-port.
22
+ * 2. **What is enabled.** Satisfaction of the *required* in-ports, in
23
+ * declaration order, yields {@link NgisNodeEnablement} — the boolean, the
24
+ * disclosure state and the generated reason, as data.
25
+ * 3. **What is broken.** Anything F14.4 calls a *compile error* is a
26
+ * {@link NgisFlowDefect}: a developer-facing sentence, never a rendered
27
+ * string. See "Defects are data" below for why they are not thrown here.
28
+ *
29
+ * ## Why `{$context}` does not go through `compileWhenExpression`
30
+ *
31
+ * F14.10 says evaluator reuse is mandatory and names `compileWhenExpression`.
32
+ * It cannot apply to *value* resolution, and the reason is in the signature:
33
+ * `CompiledWhenExpression.evaluate(read)` returns a **`boolean`**
34
+ * (`src/sdk/context/when.ts:44`). There is no value channel to reuse. F14.4's
35
+ * own text is the resolution — form 2's payload is a *key*, not an expression
36
+ * ("the same `key := ident ("." ident)*` production `when` already lexes"), and
37
+ * a key is read, not evaluated. So this module adds **no grammar at all**: it
38
+ * hands the key to a reader. The clause's intent (there is no third grammar) is
39
+ * kept exactly; the named mechanism is simply the wrong shape for the job, and
40
+ * a `when`-shaped wrapper around a key read would have been the third grammar
41
+ * the clause forbids.
42
+ *
43
+ * The reader is a **function, not a store** (`(key) => unknown`), and that is
44
+ * what keeps this module pure. An unknown key is `undefined`, per F14.4.
45
+ *
46
+ * ## Defects are data, and the dev-mode throw is a separate call
47
+ *
48
+ * F14.4 calls an ambiguous `"@id"`, a non-`record` spread source and a
49
+ * duplicated spread key *compile errors*, and F14.5 calls a duplicate node id a
50
+ * development-mode error. All four are authoring mistakes with no honest
51
+ * end-user rendering: {@link NgisFlowReason} is a frozen two-arm union and
52
+ * neither arm can say "this reference is ambiguous". Throwing from inside
53
+ * resolution would also make the model partial, which a pure core that Y5b
54
+ * calls during render cannot afford. So resolution is **total** and collects
55
+ * {@link NgisFlowDefect}s; {@link assertFlowIsWellFormed} is the dev-gated
56
+ * throw, and its message is where F14.4's "lists the available members" lives.
57
+ *
58
+ * ## The two reconciliations worth reading before changing anything below
59
+ *
60
+ * **`"@run"` has two meanings in F14.4 and the destination decides which.** The
61
+ * general rule makes `"@id"` the node's *default output port* — for a
62
+ * `RunButton` that is `taskId: task`, so `"@run"` is a task handle. The
63
+ * "named outputs of a task node" bullet makes `"@run"` *the `NgisTaskOutput`
64
+ * with `primary === true`, else the first* — a file. Both are frozen text and
65
+ * they are not the same value. They are reconciled by the **destination port's
66
+ * kind**: a `task` in-port receives the handle, any other kind receives the
67
+ * primary output's `fileId` through {@link ngisPrimaryTaskOutput}. That is the
68
+ * only reading under which both sentences are true, and it is also what an
69
+ * author means by binding `"@run"` to an `AttributeTable`'s `resource` port.
70
+ *
71
+ * **Port ids are the member namespace — except where a reserved task member
72
+ * shadows one.** YD41 froze a port id as a *member name*, and `RunButton`'s out
73
+ * port is named `taskId`, which is also the first of F14.4's four reserved
74
+ * members. **Amended at the W3 merge (`V-226`):** the reserved member wins, so
75
+ * `"@run.taskId"` is the **id string**, not the handle. This paragraph
76
+ * previously said the opposite and it was the reading under which F14.22's
77
+ * `<TaskStatus taskId="@run.taskId">` received an object where both F14.4
78
+ * ("`taskId` **(string)**") and the seam (port kind `task` is "a task handle
79
+ * **id**") promise a string. The whole handle is still what a bare `"@run"`
80
+ * yields to a `task`-kind in-port, `.outputs.<name>` and named outputs are
81
+ * unchanged, and a colliding output stays reachable as `"@run.outputs.<name>"`.
82
+ */
83
+ import type { NgisDataType, NgisTaskOutput } from "../../sdk/facets/types";
84
+ import { type NgisFlowNode, type NgisFlowRef, type NgisNodeEnablement, type NgisPortKind, type NgisPortResolution, type NgisPortSpec, type NgisResolvedValue, type NgisTaskRefValue, type NgisTypeVerdict } from "./w3-contracts";
85
+ /**
86
+ * F14.14's public rows for this session, and **exactly** those. Every one is
87
+ * *declared* in the kickoff seam `w3-contracts.ts` and *published* from here,
88
+ * so the barrels never import the seam and the seam stays a wave artifact
89
+ * (`packages/plugin-ui/src/index.ts`'s "W3 note"). A consumer that needs a seam
90
+ * name which is not a public row — `isNgisUnbound`, `NgisPortResolution`,
91
+ * `NGIS_PORT_ATTRIBUTES` — imports it from `./w3-contracts` by relative path,
92
+ * exactly as W2's consumers import `w2-contracts.ts`.
93
+ */
94
+ export { ngisFlowSpread, ngisTypeVerdict } from "./w3-contracts";
95
+ export type { NgisDisclosureState, NgisFlowNode, NgisFlowReason, NgisFlowRef, NgisFlowSnapshot, NgisInputBinding, NgisNodeEnablement, NgisPortDirection, NgisPortKind, NgisPortSpec, NgisTypeVerdict, } from "./w3-contracts";
96
+ import type { NgisFlowSnapshot } from "./w3-contracts";
97
+ /**
98
+ * The scope's nodes, keyed. **Last-wins on `id`** (F14.5): the surviving node is
99
+ * the last declaration, held at the position of the *first*, so re-declaring a
100
+ * node does not reorder the scope and the generated-reason order stays stable
101
+ * across a re-render. A collision is recorded rather than swallowed —
102
+ * {@link resolveFlow} turns each into a `duplicate-node-id` defect and
103
+ * {@link assertFlowIsWellFormed} throws on it outside production.
104
+ */
105
+ export interface NgisFlowRegistry {
106
+ readonly nodes: readonly NgisFlowNode[];
107
+ readonly byId: ReadonlyMap<string, NgisFlowNode>;
108
+ /** Ids declared more than once, in first-collision order. */
109
+ readonly duplicateIds: readonly string[];
110
+ }
111
+ export declare function createFlowRegistry(declared: readonly NgisFlowNode[]): NgisFlowRegistry;
112
+ export type NgisFlowDefectCode =
113
+ /** F14.5: two nodes claim one `id` in a scope. */
114
+ "duplicate-node-id"
115
+ /** A reference names a node the scope does not hold. */
116
+ | "unknown-node"
117
+ /** A reference names neither an out port, a reserved task member nor a task
118
+ * output name. `members` lists what exists. */
119
+ | "unknown-member"
120
+ /** F14.4: `"@id"` on a node with several out ports and no `"value"` port.
121
+ * `members` lists the available ones — never a guess. */
122
+ | "ambiguous-default-output"
123
+ /** F14.4: a spread whose source port is not kind `record`. */
124
+ | "spread-source-not-record"
125
+ /** F14.4: two spreads contributing the same record key. */
126
+ | "spread-key-conflict"
127
+ /** The verdict does not cover this: a `task` out bound to a `resource` in. */
128
+ | "port-kind-mismatch"
129
+ /** `ngisTypeVerdict === "fail"` (F14.1). Also an `invalid` resolution. */
130
+ | "type-mismatch"
131
+ /** A value arrived in a shape `NgisResolvedValue` cannot hold. */
132
+ | "unsupported-value"
133
+ /** F14.4 cuts ordering edges, so a data cycle is always an authoring bug. */
134
+ | "cycle";
135
+ /**
136
+ * One authoring mistake. `message` is a **developer-facing English sentence**,
137
+ * never a message key and never rendered to an end user — F13.11 keeps plugin
138
+ * strings literal and F14.10 requires user-facing reasons to be *generated*, so
139
+ * these two channels stay apart on purpose. The user-facing consequence of a
140
+ * defect is whatever {@link NgisNodeEnablement} says.
141
+ */
142
+ export interface NgisFlowDefect {
143
+ readonly code: NgisFlowDefectCode;
144
+ readonly nodeId: string;
145
+ /** The in-port the defect was found resolving, or `null` for a scope-level one. */
146
+ readonly portId: string | null;
147
+ readonly message: string;
148
+ /** For `unknown-member` / `ambiguous-default-output`: what does exist. */
149
+ readonly members: readonly string[];
150
+ }
151
+ /**
152
+ * A task handle, structurally. Checked rather than assumed because the out-port
153
+ * value arrives from Y5b's React state, i.e. from a component, i.e. from data
154
+ * this module did not build.
155
+ */
156
+ export declare function isNgisTaskRefValue(value: unknown): value is NgisTaskRefValue;
157
+ /**
158
+ * Whether an arbitrary value is one `NgisResolvedValue` can hold.
159
+ *
160
+ * The array arm needs both readings and they are distinguishable: F14.5's
161
+ * `resources` kind is `readonly string[]` and its `extent` kind is a
162
+ * four-number tuple, so an all-string array is the former and a four-number
163
+ * array the latter. Anything else — a mixed array, a five-number array, a
164
+ * nested record — is **not** representable, and `NgisContextValue` really can
165
+ * produce one (`readonly (string | number)[]`, `src/sdk/context/keys.ts:20`).
166
+ * That is why this is a guard and not a cast.
167
+ */
168
+ export declare function isNgisResolvedValue(value: unknown): value is NgisResolvedValue;
169
+ /**
170
+ * F14.4's default output port: the one whose id is `"value"` if the node
171
+ * declares it, otherwise its **single** out port. `null` means the reference is
172
+ * ambiguous (or the node publishes nothing) and F14.4 forbids guessing.
173
+ *
174
+ * The `"value"` literal comes from `NGIS_W2_PORT_IDS`, not from a second copy —
175
+ * a W3 module re-declaring the reserved member vocabulary is the defect class
176
+ * `V-127` names, and the seam test guards it.
177
+ */
178
+ export declare function defaultOutputPort(ports: readonly NgisPortSpec[]): NgisPortSpec | null;
179
+ export declare function portKindsCompatible(into: NgisPortKind, from: NgisPortKind): boolean;
180
+ /**
181
+ * The client-side binding check over two *declared* type sets (F14.1/F14.5).
182
+ *
183
+ * Empty or absent on either side means "any type" and therefore `"pass"` —
184
+ * F14.5's own rule, and the reason YD44's bidirectional wildcard matters.
185
+ *
186
+ * When both sides declare several types the verdict is the **most permissive**
187
+ * over the cross product, and that is a deliberate asymmetry with the server:
188
+ * a `"fail"` here must mean *no* value the source can hold is acceptable,
189
+ * because `host.models.validate` is the authority on the actual dataset type
190
+ * and blocking a binding that could legitimately resolve would reject real
191
+ * graphs offline. `"warn"` never blocks either way.
192
+ */
193
+ export declare function bindingDataTypeVerdict(expected: readonly NgisDataType[] | undefined, actual: readonly NgisDataType[] | undefined): NgisTypeVerdict;
194
+ /**
195
+ * **F14.4's one `"@run"` rule, canonically (`V-127`, YD45).** The output with
196
+ * `primary === true`, else the first, else `null`.
197
+ *
198
+ * Generic over the row shape so the two host helpers delegate without a cast:
199
+ * `NgisResultOutput` is assignable to `NgisTaskOutput`, not the reverse.
200
+ *
201
+ * **The `fileId === null` case is decided here, explicitly, because the three
202
+ * pre-existing implementations disagreed about it.** This rule does **not**
203
+ * filter. F14.4 says "the `NgisTaskOutput` with `primary === true`; if none, the
204
+ * first" and says of the sibling `"@run.<name>"` rule that an absent name is
205
+ * unsatisfied "never silently the primary" — the whole section's principle is
206
+ * *never substitute*. Filtering first would silently return a **non-primary**
207
+ * output whenever the primary's file id has not been written yet, which is a
208
+ * real and common intermediate state (`fileId` fills in as a task integrates),
209
+ * so the substitution would be both invisible and transient. Selecting the
210
+ * primary and letting a `null` file id read as *unsatisfied, awaiting upstream*
211
+ * says the true thing: the right output is known and its file is not ready.
212
+ *
213
+ * Consequence, recorded rather than glossed: `first-party-plugins/`
214
+ * `terrain-quick-look/src/results.ts:62` filters *before* selecting, so making
215
+ * it delegate would change a shipping behaviour. It does not delegate and
216
+ * `V-127` stays open for that one call site — see the session note.
217
+ */
218
+ export declare function ngisPrimaryTaskOutput<T extends NgisTaskOutput>(outputs: readonly T[]): T | null;
219
+ /**
220
+ * Split `"@dem"` / `"@run.taskId"` into a node id and a member — **the one
221
+ * implementation of the rule** (`V-262`, closed by F15.14's sibling work).
222
+ *
223
+ * A node id **may** contain dots (it is an author-supplied `id` prop), which
224
+ * makes `"@a.b"` ambiguous in exactly the way `X9-I-5` described for context
225
+ * keys. The tie is broken deterministically and in the author's favour: a body
226
+ * that names a node **exactly** is that node with no member, and only otherwise
227
+ * does the first `.` split. A node literally named `a.b` sitting beside a node
228
+ * `a` with a member `b` is therefore addressable, and the dotted node wins.
229
+ *
230
+ * `isKnownNode` rather than a registry, so `recipe-model.ts` — which holds a
231
+ * plain `Map` and used to mirror this by hand — delegates instead of copying.
232
+ * Callers that must reject an unknown source check `isKnownNode(sourceId)`
233
+ * themselves; this function reports what the reference *says*, and reporting
234
+ * "unknown node" with the candidate list is the caller's job.
235
+ */
236
+ export declare function ngisSplitFlowRef(ref: NgisFlowRef, isKnownNode: (id: string) => boolean): {
237
+ sourceId: string;
238
+ member: string;
239
+ };
240
+ /** F14.4 form 2's reader. A function, not a store — that is the purity seam. */
241
+ export type NgisFlowContextReader = (key: string) => unknown;
242
+ /**
243
+ * Everything outside the graph that resolution reads. Y5b owns all three and
244
+ * this module owns none of them.
245
+ */
246
+ export interface NgisFlowState {
247
+ /**
248
+ * Out-port values, keyed by {@link ngisPortRef}. An absent key is a port that
249
+ * **has not produced yet**, which is what separates F14.10's `pending` from
250
+ * its `blocked`; a present-but-unbound value (`null`, `""`, `[]`) is the user
251
+ * having cleared a control, and {@link isNgisUnbound} treats the two alike.
252
+ *
253
+ * **`null` is in the type because the doc above requires it, and only the
254
+ * merged tree showed that it was not** (W3 merge, `V-223`). Y5a's type read
255
+ * `NgisResolvedValue | undefined`, which cannot hold the `null` this very
256
+ * comment describes, while Y5b's `<Flow>` `publish` writes exactly that `null`
257
+ * when a control is cleared. Neither branch could fail on its own: Y5a never
258
+ * built a scope and Y5b resolved against a stub whose type admitted `null`.
259
+ * Widened here rather than narrowing `publish`, because collapsing "cleared"
260
+ * onto "never produced" is what turns `blocked` into `pending` and stops the
261
+ * graph dimming.
262
+ */
263
+ readonly outputs: Readonly<Record<string, NgisResolvedValue | null | undefined>>;
264
+ /** F14.4 form 2. Absent ⇒ every `{$context}` reads `undefined`. */
265
+ readonly context?: NgisFlowContextReader;
266
+ /** F14.4 form 3. **Recipe-only, and enforcing that is Y6's** — this module
267
+ * resolves `{$input}` against whatever map it is handed. */
268
+ readonly promoted?: Readonly<Record<string, NgisResolvedValue>>;
269
+ }
270
+ /** What one pass over a scope answers. */
271
+ export interface NgisFlowResolution {
272
+ readonly registry: NgisFlowRegistry;
273
+ /** One entry per **in**-port, keyed by {@link ngisPortRef}. Out ports hold
274
+ * values rather than resolutions, and `NgisPortResolution` is defined by the
275
+ * seam as "what binding resolution answers for one in-port". */
276
+ readonly resolved: Readonly<Record<string, NgisPortResolution>>;
277
+ /** Registry order. */
278
+ readonly enablement: readonly NgisNodeEnablement[];
279
+ readonly enablementByNode: ReadonlyMap<string, NgisNodeEnablement>;
280
+ readonly defects: readonly NgisFlowDefect[];
281
+ }
282
+ /**
283
+ * Resolve a whole scope: every in-port, every node's enablement, every defect.
284
+ *
285
+ * **Total — it never throws.** A malformed graph produces defects and disabled
286
+ * nodes, and {@link assertFlowIsWellFormed} is the dev-mode escalation.
287
+ */
288
+ export declare function resolveFlow(nodes: readonly NgisFlowNode[], state: NgisFlowState): NgisFlowResolution;
289
+ /**
290
+ * The serialization hook Y6 consumes (F14.6's input, not F14.6's schema).
291
+ *
292
+ * Deliberately the graph **as declared** plus the resolutions at snapshot time.
293
+ * It is not pre-shaped into recipe fields: F14.6 owns the recipe schema, and a
294
+ * snapshot that already spoke it would put half of Y6's mapping in W3, where no
295
+ * clause authorizes it. Everything Y6 needs to derive promoted inputs, node
296
+ * `params` and the `{graph,view}` envelope is present, and nothing more.
297
+ *
298
+ * The result is plain JSON — no `Map`, no `undefined`, no function — so it
299
+ * round-trips through `JSON.stringify`/`parse` unchanged. That is the property
300
+ * Y6 and Y7 actually depend on and it is asserted, not assumed.
301
+ */
302
+ export declare function flowSnapshot(resolution: NgisFlowResolution): NgisFlowSnapshot;
303
+ export declare function assertFlowIsWellFormed(resolution: NgisFlowResolution): void;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * **F14.5's per-component port table, as data** (Stage 14 Y5b, W3).
3
+ *
4
+ * One builder per shipped component, pure and React-free, for the reason every
5
+ * other model/data split in this package exists: the table is frozen, Y2–Y4 may
6
+ * add a port but never remove or retype one, and a test that reads the table
7
+ * without a DOM is what makes that claim checkable. `Flow.tsx` calls these; no
8
+ * component hand-writes an `NgisPortSpec`.
9
+ *
10
+ * ## What a port id is, and what a node id is (YD41)
11
+ *
12
+ * A port id is a **member name, unique within its node** — which is why four
13
+ * components can all publish `value` and four can all read `taskId`. The
14
+ * component's `id` prop is the **node** id and is scope-unique. The reserved
15
+ * member vocabulary is `NGIS_W2_PORT_IDS` in `w2-contracts.ts`, used here and
16
+ * **not re-declared**: a second copy of `value`/`taskId`/`parent`/`resource` is
17
+ * the `V-127` defect class.
18
+ *
19
+ * ## Labels, and why the fallback is the node id
20
+ *
21
+ * `NgisPortSpec.label` is the **generated-reason text source** (F14.10) and is
22
+ * never a message key — a literal plugin string or the UI schema's `label`,
23
+ * whose provenance is `x-display-name` → `title` → `name`. Every component takes
24
+ * an optional `label` prop, so when an author omits it the port still needs
25
+ * something a sentence can be built from; the node id is the only other thing
26
+ * the author actually wrote (`<ResourceSelect id="dem"/>` → *"Choose dem
27
+ * first"*). Falling back to the port id would produce *"Choose value first"* for
28
+ * every picker in the flow, which names nothing.
29
+ */
30
+ import type { NgisDataType } from "../../sdk/facets/types";
31
+ import type { NgisToolUiSchema } from "../../types/analysis";
32
+ import type { NgisPortSpec } from "./w3-contracts";
33
+ /**
34
+ * The `data-slot` each node reports.
35
+ *
36
+ * **CORRECTION.** `NgisFlowNode.slot` is documented as "from
37
+ * `NGIS_W2_DATA_SLOTS` or `NGIS_W3_DATA_SLOTS`", but **four of the eleven
38
+ * components render no component-specific `data-slot` at all**:
39
+ * `ResourceSelect`, `ModelSelect`, `FieldSelect` and `ExtentPicker` render only
40
+ * `PickerField`'s shared `ngis-picker-field` (`ExtentPicker` adds
41
+ * `ngis-extent-value` to an inner `<p>`), and `NGIS_W2_DATA_SLOTS` has no entry
42
+ * for any of them. So those four report the slot they really render. Minting
43
+ * four new slot names is *not* the fix: `verify-externals.mjs` fingerprints
44
+ * plugin-ui by these strings, W3's declared slots are `flow` and
45
+ * `flowInspector`, and the engine never branches on `slot` anyway — it is
46
+ * descriptive, and an honest description beats an invented table entry.
47
+ */
48
+ export declare const NGIS_PICKER_SLOT = "ngis-picker-field";
49
+ export declare const NGIS_FLOW_SLOTS: {
50
+ readonly resourceSelect: "ngis-picker-field";
51
+ readonly modelSelect: "ngis-picker-field";
52
+ readonly fieldSelect: "ngis-picker-field";
53
+ readonly extentPicker: "ngis-picker-field";
54
+ readonly paramForm: "ngis-param-form";
55
+ readonly runButton: "ngis-run-button";
56
+ readonly taskStatus: "ngis-task-status";
57
+ readonly taskList: "ngis-task-list";
58
+ readonly resultActions: "ngis-result-actions";
59
+ readonly attributeTable: "ngis-attribute-table";
60
+ readonly statChart: "ngis-stat-chart";
61
+ };
62
+ export interface ResourcePortOptions {
63
+ readonly nodeId: string;
64
+ readonly label?: string;
65
+ readonly multiple?: boolean;
66
+ readonly types?: readonly NgisDataType[];
67
+ }
68
+ /** `value: resource`, or `resources` when `multiple` (F14.5). */
69
+ export declare function resourceSelectPorts(options: ResourcePortOptions): readonly NgisPortSpec[];
70
+ /** `value: value`, `valueType: "string"` — the tool id (F14.5). */
71
+ export declare function modelSelectPorts(options: {
72
+ readonly nodeId: string;
73
+ readonly label?: string;
74
+ }): readonly NgisPortSpec[];
75
+ /**
76
+ * `value: value` out, `parent: resource` in — and `parentPortId` names that
77
+ * **sibling in-port on the same node** (YD41), not a port on another node. The
78
+ * parent's value therefore arrives through one binding and one resolution path,
79
+ * which is what stops a `FieldSelect` having two notions of its parent.
80
+ */
81
+ export declare function fieldSelectPorts(options: {
82
+ readonly nodeId: string;
83
+ readonly label?: string;
84
+ /** Whether the parent must be bound for this node to be enabled. */
85
+ readonly parentRequired?: boolean;
86
+ }): readonly NgisPortSpec[];
87
+ /** `value: extent` (F14.5). `NgisBounds` is `[west, south, east, north]`. */
88
+ export declare function extentPickerPorts(options: {
89
+ readonly nodeId: string;
90
+ readonly label?: string;
91
+ }): readonly NgisPortSpec[];
92
+ /**
93
+ * `value: record` out, plus **one `in` port per required UI-schema param not
94
+ * satisfied internally** (F14.5).
95
+ *
96
+ * "Not satisfied internally" is the `hide` list: F14.22 writes
97
+ * `hide={["ELEV"]}` precisely because that param is bound by a flow port
98
+ * instead of rendered as a field. A param the form *renders* is satisfied by the
99
+ * user inside the form, so it is not an in port — otherwise every generated form
100
+ * would report itself blocked on its own fields, and F14.9's client validation
101
+ * already owns that verdict.
102
+ */
103
+ export declare function paramFormPorts(options: {
104
+ readonly nodeId: string;
105
+ readonly label?: string;
106
+ readonly schema: NgisToolUiSchema | null;
107
+ readonly hide?: readonly string[];
108
+ }): readonly NgisPortSpec[];
109
+ /**
110
+ * `taskId: task` out, plus **one `in` port per bound tool input** (F14.5).
111
+ *
112
+ * "Bound" is literal: the ports are the keys of the author's `inputs`, minus the
113
+ * spread directives. A spread contributes *values* at resolve time and its
114
+ * record's keys are not known until then, so it cannot contribute a declared
115
+ * port — and inventing one per required schema param whenever a spread is
116
+ * present would give the button in ports the author never wrote.
117
+ *
118
+ * Everything about a bound input other than its name comes from the tool's UI
119
+ * schema, which is F14.10's provenance chain (`NgisToolUiParam.label` =
120
+ * `x-display-name` → `title` → `name`) reaching the reason unchanged. A name the
121
+ * schema does not know still gets a port — **surfaced, never dropped**, the
122
+ * `ExtensionSettingsForm` rule — as an optional `value` port, so it cannot
123
+ * silently block a run.
124
+ */
125
+ export declare function runButtonPorts(options: {
126
+ readonly nodeId: string;
127
+ readonly label?: string;
128
+ readonly schema: NgisToolUiSchema | null;
129
+ /** `inputs` keys with the spread directives already removed. */
130
+ readonly inputNames: readonly string[];
131
+ }): readonly NgisPortSpec[];
132
+ /** `taskId: task` in — `TaskStatus`, `TaskList` and `ResultActions` (F14.5). */
133
+ export declare function taskIdInPorts(options: {
134
+ readonly nodeId: string;
135
+ readonly label?: string;
136
+ }): readonly NgisPortSpec[];
137
+ /** `resource: resource` in (F14.5). The value is a **file** id. */
138
+ export declare function attributeTablePorts(options: {
139
+ readonly nodeId: string;
140
+ readonly label?: string;
141
+ }): readonly NgisPortSpec[];
142
+ /**
143
+ * `taskId: task` **or** `resource: resource` in (F14.5) — whichever the author
144
+ * declared, which for this component is "passed the prop at all".
145
+ *
146
+ * Declaring neither is legal and means the spec is the only gate: nothing on the
147
+ * 0.4 surface can derive an `NgisChartSpec` from a task or a resource, so the
148
+ * port is a satisfaction gate rather than a data source (`statChartGate`).
149
+ */
150
+ export declare function statChartPorts(options: {
151
+ readonly nodeId: string;
152
+ readonly label?: string;
153
+ readonly hasTaskPort: boolean;
154
+ readonly hasResourcePort: boolean;
155
+ }): readonly NgisPortSpec[];
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `<ParamForm>`'s host reads (Stage 14 Y3 — F14.2, F14.3, F14.9).
3
+ *
4
+ * Every function takes the 0.4 facade and returns plain data, for the same
5
+ * reason Y2's `picker-data.ts` does: the wave's real exit criterion is that the
6
+ * form works **through the facade `createGisPluginHostFactory` mints**, and that
7
+ * is only assertable offline if the host calls are functions a node test can
8
+ * call. A facade assembled anywhere else is an ungated 0.4 surface every unit
9
+ * test calls green.
10
+ *
11
+ * ## `ParamForm` reads `uiSchema`, never `schema`
12
+ *
13
+ * `host.models.schema()` is the raw, un-normalized passthrough of
14
+ * `GET /api/analysis/tools/{id}/schema` and F14.2 keeps it byte-verbatim — but
15
+ * **ParamForm must never call it**, because a client-side re-derivation of
16
+ * widgets from `x-*` keys is exactly the second normalizer this stage exists to
17
+ * kill. `legacy-normalizer.test.ts` proves the absence rather than asserting it
18
+ * in prose.
19
+ *
20
+ * ## `models.validate` is Y4a's leg, and it throws on this branch
21
+ *
22
+ * F14.9's `validate-server` phase calls `host.models.validate`. That leg is
23
+ * wired by Y4 in the same wave; until then the facet's guard throws
24
+ * `NgisFacetUnavailableError` — **deliberately, because a stub that resolved
25
+ * empty would be indistinguishable from a clean validation**. So
26
+ * {@link validateSubmission} tolerates the rejection as a *surfaced notice* and
27
+ * never as a crash, and never as a pass: `kind: "unavailable"` is its own arm,
28
+ * and the form says server validation did not run.
29
+ */
30
+ import { type NgisHostAPI } from "../../sdk/host-api";
31
+ import { type NgisExecutionValidation, type NgisToolUiParam, type NgisToolUiSchema } from "../../types/analysis";
32
+ import { type NgisParamFormState } from "./param-form-model";
33
+ import { type NgisRunSubmission } from "./w2-contracts";
34
+ /**
35
+ * Why a union rather than `NgisToolUiSchema | null`: each arm is a state the
36
+ * author has to be able to tell apart, and collapsing "you were refused" into
37
+ * "this tool has no schema" is the silent-empty-state failure the stage's stub
38
+ * rule exists to prevent.
39
+ */
40
+ export type NgisUiSchemaLoad =
41
+ /** The tool, or its schema, does not exist. */
42
+ {
43
+ readonly kind: "missing";
44
+ readonly toolId: string;
45
+ }
46
+ /**
47
+ * `ui_schema_version` is not the one this build understands. **A reader that
48
+ * does not know the version refuses; it never best-efforts a newer shape**
49
+ * (F14.2, and the same rule `recipeVersion` carries in F14.6).
50
+ */
51
+ | {
52
+ readonly kind: "unsupported-version";
53
+ readonly version: number;
54
+ }
55
+ /** A scope refusal, a transport failure, or the W1 stub before Y3 wired it. */
56
+ | {
57
+ readonly kind: "refused";
58
+ readonly message: string;
59
+ } | {
60
+ readonly kind: "ready";
61
+ readonly schema: NgisToolUiSchema;
62
+ };
63
+ export declare function loadToolUiSchema(host: NgisHostAPI, toolId: string): Promise<NgisUiSchemaLoad>;
64
+ /**
65
+ * The param a `field-select` follows — QGIS's `parentLayerParameterName`, whose
66
+ * UI-schema spelling is the passthrough extension `x-parent-param`.
67
+ *
68
+ * F14.2 emits `field-select` for no builtin, so this is only ever reached by an
69
+ * author's `widgets` override or a recipe's promoted input. When the extension
70
+ * is absent, the parent is the schema's **first `role: "input"` param**, which
71
+ * is the only defensible guess: a form with one dataset input has exactly one
72
+ * candidate, and a form with several is told to name it. `null` when there is no
73
+ * input param at all — reported, never guessed at.
74
+ */
75
+ export declare function parentParamNameFor(schema: NgisToolUiSchema, param: NgisToolUiParam): string | null;
76
+ /**
77
+ * The runtime option lists F14.9's *mutate* phase repopulates from — today only
78
+ * `field-select`'s. Keyed by param name, so `mutateParamForm` stays pure and the
79
+ * fetch stays here.
80
+ *
81
+ * A parent that cannot be resolved yields **no entry** rather than an empty
82
+ * list. An empty list would mean "this parent has no fields", which `mutate`
83
+ * would honour by clearing every unaltered value — so the difference between
84
+ * *unknown* and *empty* is a data-loss difference, not a cosmetic one.
85
+ */
86
+ export declare function loadDependentOptionLists(host: NgisHostAPI, schema: NgisToolUiSchema, state: NgisParamFormState, widgetOf: (param: NgisToolUiParam) => NgisToolUiParam["widget"]): Promise<Readonly<Record<string, readonly string[]>>>;
87
+ export type NgisServerValidation = {
88
+ readonly kind: "ok";
89
+ readonly validation: NgisExecutionValidation;
90
+ }
91
+ /**
92
+ * `host.models.validate` is not implemented on this facade. **Not a pass.**
93
+ * The form renders the reason and the submit stays governed by the client
94
+ * rules alone — which is the honest position, because nothing was checked.
95
+ */
96
+ | {
97
+ readonly kind: "unavailable";
98
+ readonly message: string;
99
+ }
100
+ /** The call was made and failed — a scope refusal, a 4xx, a transport error. */
101
+ | {
102
+ readonly kind: "failed";
103
+ readonly message: string;
104
+ };
105
+ export interface NgisValidateOptions {
106
+ readonly sceneId?: string;
107
+ readonly outputMode?: "ADD_LAYER" | "UPDATE_RESOURCE";
108
+ readonly targetResourceId?: string;
109
+ }
110
+ /**
111
+ * F14.9's `validate-server`. The request is built from the **same**
112
+ * `NgisRunSubmission` Y4 will submit, coerced through the **same**
113
+ * `toWireParams`, so a validate that passes cannot be followed by a run that
114
+ * fails on a differently-spelled `false`.
115
+ *
116
+ * `outputNames` ride in `params`: the analysis API takes an output name as an
117
+ * ordinary parameter key (`execution_validation.py` auto-fills a missing one),
118
+ * and `validate-execution` has no separate slot for them.
119
+ */
120
+ export declare function validateSubmission(host: NgisHostAPI, submission: NgisRunSubmission, options?: NgisValidateOptions): Promise<NgisServerValidation>;
121
+ /** The whole of `validate-server`'s input, derived in one place. */
122
+ export declare function submissionOf(schema: NgisToolUiSchema, state: NgisParamFormState): NgisRunSubmission;