@ngis/plugin-sdk 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.
- package/LICENSE +13 -0
- package/README.md +31 -0
- package/dist/index.cjs +76 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.mjs +102 -0
- package/dist/src/api/analysis.d.ts +140 -0
- package/dist/src/api/resources.d.ts +530 -0
- package/dist/src/api/scenes.d.ts +251 -0
- package/dist/src/api/users.d.ts +22 -0
- package/dist/src/domain/algorithms/deck-layer-style.d.ts +34 -0
- package/dist/src/domain/algorithms/maplibre-style.d.ts +63 -0
- package/dist/src/domain/algorithms/raster-color-ramps.d.ts +37 -0
- package/dist/src/domain/algorithms/scene-layer-type.d.ts +25 -0
- package/dist/src/domain/algorithms/symbol-compile.d.ts +100 -0
- package/dist/src/domain/algorithms/symbol-params.d.ts +90 -0
- package/dist/src/domain/map/bounds.d.ts +8 -0
- package/dist/src/domain/map/constants.d.ts +40 -0
- package/dist/src/domain/map/layer-builders.d.ts +32 -0
- package/dist/src/domain/map/rendered-layers.d.ts +30 -0
- package/dist/src/domain/map/style-ready.d.ts +24 -0
- package/dist/src/domain/map/tile-url.d.ts +20 -0
- package/dist/src/domain/map/types.d.ts +128 -0
- package/dist/src/domain/models/deck-layer-style.d.ts +211 -0
- package/dist/src/domain/models/scene-layer-type.d.ts +23 -0
- package/dist/src/domain/models/symbol.d.ts +424 -0
- package/dist/src/lib/api.d.ts +82 -0
- package/dist/src/lib/auth-session.d.ts +22 -0
- package/dist/src/lib/raster-color-layer/RasterColorLayer.d.ts +48 -0
- package/dist/src/lib/raster-color-layer/TileGrid.d.ts +31 -0
- package/dist/src/lib/raster-color-layer/colorRamp.d.ts +18 -0
- package/dist/src/lib/raster-color-layer/shaders/raster-color.frag.d.ts +2 -0
- package/dist/src/lib/raster-color-layer/shaders/raster-color.vert.d.ts +2 -0
- package/dist/src/lib/raster-color-layer/zoomInterpolate.d.ts +2 -0
- package/dist/src/sdk/commands.d.ts +113 -0
- package/dist/src/sdk/context/keys.d.ts +148 -0
- package/dist/src/sdk/context/publishers.d.ts +198 -0
- package/dist/src/sdk/context/store.d.ts +78 -0
- package/dist/src/sdk/context/when.d.ts +69 -0
- package/dist/src/sdk/contributions/dispatch.d.ts +78 -0
- package/dist/src/sdk/contributions/panel-id.d.ts +25 -0
- package/dist/src/sdk/contributions/registry.d.ts +199 -0
- package/dist/src/sdk/contributions/symbol-renderers.d.ts +107 -0
- package/dist/src/sdk/contributions/types.d.ts +288 -0
- package/dist/src/sdk/contributions/views-bridge.d.ts +92 -0
- package/dist/src/sdk/define-plugin.d.ts +17 -0
- package/dist/src/sdk/facets/storage.d.ts +152 -0
- package/dist/src/sdk/facets/types.d.ts +370 -0
- package/dist/src/sdk/host-api.d.ts +256 -0
- package/dist/src/sdk/map-api.d.ts +288 -0
- package/dist/src/sdk/panels.d.ts +130 -0
- package/dist/src/sdk/plugin.d.ts +145 -0
- package/dist/src/sdk/rail-tools.d.ts +58 -0
- package/dist/src/sdk/scopes.d.ts +158 -0
- package/dist/src/systems/renderer/DeckSceneRenderer.d.ts +26 -0
- package/dist/src/systems/renderer/FlowFieldLayer.d.ts +68 -0
- package/dist/src/systems/renderer/LayerRendererRegistry.d.ts +132 -0
- package/dist/src/systems/renderer/authenticated-deck-load.d.ts +4 -0
- package/dist/src/systems/renderer/deck/deck-layer-builders.d.ts +29 -0
- package/dist/src/systems/renderer/deck/deck-layer-helpers.d.ts +21 -0
- package/dist/src/systems/renderer/deck/mvt-point-aggregation-layer.d.ts +107 -0
- package/dist/src/systems/renderer/flow-field/flow-field-resources.d.ts +84 -0
- package/dist/src/systems/renderer/flow-field/flow-field-shaders.d.ts +4 -0
- package/dist/src/systems/renderer/symbol/SpriteRegistry.d.ts +34 -0
- package/dist/src/systems/renderer/symbol/entries.d.ts +67 -0
- package/dist/src/types/analysis.d.ts +205 -0
- package/dist/src/types/common.d.ts +17 -0
- package/dist/src/types/extensions.d.ts +560 -0
- package/dist/src/types/gis.d.ts +242 -0
- package/dist/src/types/market-social.d.ts +378 -0
- package/package.json +47 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage 13 (capability contract) — the frozen type text for `contributes` v1.
|
|
3
|
+
*
|
|
4
|
+
* **Authored at the W2 kickoff, not by a session.** X4a parses these shapes,
|
|
5
|
+
* X4b's loader path consumes the parse result, and X3's settings renderer
|
|
6
|
+
* consumes `NgisSettingsContribution`. Three concurrent agents needing the same
|
|
7
|
+
* type names is exactly the collision that bit W1 (X1 and X2 both declared
|
|
8
|
+
* `NgisResourceType` and clashed on the exported *name*, invisible to either
|
|
9
|
+
* agent alone until merge). So the names live here, owned by the kickoff, and
|
|
10
|
+
* **no W2 session declares any of them**.
|
|
11
|
+
*
|
|
12
|
+
* Every shape below is **F13.3 verbatim**
|
|
13
|
+
* (`docs/capability_contract_stage/README.md` → "Frozen contracts (X0)").
|
|
14
|
+
* A session that needs a change here stops and raises it — it does not diverge
|
|
15
|
+
* locally, because XB1's validator (already merged) and X4a's parser must agree
|
|
16
|
+
* fixture-for-fixture on the six frozen manifests in `src/sdk/__fixtures__/`.
|
|
17
|
+
*
|
|
18
|
+
* Type-only module: no runtime imports, no React. The caps are `const` because
|
|
19
|
+
* they are the same numbers XB1 enforces at publish time — a drift between the
|
|
20
|
+
* two is a silent-truncation bug (F13.10 makes over-cap a *rejection*, never a
|
|
21
|
+
* truncation).
|
|
22
|
+
*/
|
|
23
|
+
/** F13.10. Enforced by XB1 at publish time and X4a at parse time. */
|
|
24
|
+
export declare const NGIS_MAX_STATUS_ITEMS_PER_PLUGIN = 2;
|
|
25
|
+
/** F13.10. Host row capacity; the rest collapse into a `+N` popover. */
|
|
26
|
+
export declare const NGIS_MAX_VISIBLE_STATUS_ITEMS = 6;
|
|
27
|
+
/** F13.10. Per plugin, per slot. */
|
|
28
|
+
export declare const NGIS_MAX_MENU_ITEMS_PER_PLUGIN_PER_SLOT = 5;
|
|
29
|
+
/**
|
|
30
|
+
* Every contribution point carries these. `when` false ⇒ not rendered.
|
|
31
|
+
* `enabledWhen` false ⇒ rendered, disabled, with `disabledReason` (or a generic
|
|
32
|
+
* host string). `views` takes `when` only — a disabled-but-visible panel has no
|
|
33
|
+
* meaning, so `NgisViewContribution` does not extend this.
|
|
34
|
+
*/
|
|
35
|
+
export interface NgisGatedContribution {
|
|
36
|
+
when?: string;
|
|
37
|
+
enabledWhen?: string;
|
|
38
|
+
disabledReason?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface NgisStatusItemContribution extends NgisGatedContribution {
|
|
41
|
+
/** Required, unique within the plugin. */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Literal, single-locale (F13.11) — plugin strings are never `labelKey`s. */
|
|
44
|
+
label?: string;
|
|
45
|
+
/** lucide icon name. */
|
|
46
|
+
icon?: string;
|
|
47
|
+
tooltip?: string;
|
|
48
|
+
/** A `capabilities.commands` id. */
|
|
49
|
+
command?: string;
|
|
50
|
+
/** Rendered as the item's count. */
|
|
51
|
+
badge?: {
|
|
52
|
+
contextKey: string;
|
|
53
|
+
};
|
|
54
|
+
/** Default 0; higher sorts first. Order is `priority` desc, then plugin id,
|
|
55
|
+
* then item id — deterministic, never install-order (F13.10). */
|
|
56
|
+
priority?: number;
|
|
57
|
+
}
|
|
58
|
+
/** v1 slots — exactly these two. No `map/context`, no `panel/title` (XD19). */
|
|
59
|
+
export type NgisMenuSlot = "layer/context" | "resource/context";
|
|
60
|
+
/** Groups render in this fixed order, separated; within a group, `order` asc
|
|
61
|
+
* then item id. Host items always precede plugin items. */
|
|
62
|
+
export type NgisMenuGroup = "navigation" | "modification" | "export" | "plugin";
|
|
63
|
+
export interface NgisMenuItemContribution extends NgisGatedContribution {
|
|
64
|
+
/** Required — a `capabilities.commands` id. The split rule: `contributes`
|
|
65
|
+
* carries only data, and anything needing code references it by id. */
|
|
66
|
+
command: string;
|
|
67
|
+
/** Required; literal (F13.11). */
|
|
68
|
+
label: string;
|
|
69
|
+
/** Default `"plugin"`. */
|
|
70
|
+
group?: NgisMenuGroup;
|
|
71
|
+
order?: number;
|
|
72
|
+
}
|
|
73
|
+
export type NgisMenuContributions = {
|
|
74
|
+
readonly [S in NgisMenuSlot]?: readonly NgisMenuItemContribution[];
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The two existing panel slots (`PanelSlot` from `./panels`, restated here so
|
|
78
|
+
* this module stays import-free).
|
|
79
|
+
*
|
|
80
|
+
* **Amended 2026-07-27 (W2, X4a finding).** F13.3 originally froze this as
|
|
81
|
+
* `"left" | "right"` while describing it as "the two existing panel slots".
|
|
82
|
+
* Both values were wrong: `PanelSlot` has been `"right" | "bottom"` since C7
|
|
83
|
+
* (`src/sdk/panels.ts:36`, and `workspacePanelRegistry` is constructed with
|
|
84
|
+
* exactly those two at `:240`). There is no `left` slot — the GIS workspace's
|
|
85
|
+
* left region is the scene sidebar, a fixed layout column rather than a
|
|
86
|
+
* registry slot — and `bottom` is a real slot a view could legitimately target.
|
|
87
|
+
*
|
|
88
|
+
* The orchestrator amended F13.3 to the union below rather than mapping `left`
|
|
89
|
+
* onto `right`, because rendering a view in a dock its author did not ask for
|
|
90
|
+
* is the failure mode F13.9 elsewhere calls out as worse than refusing.
|
|
91
|
+
* `left` is now an out-of-allowlist value and refuses like any other.
|
|
92
|
+
* `PackageManifestValidator.VIEW_SLOTS` changed with it; **no fixture changed**,
|
|
93
|
+
* since `m2-full-0.4.json` is the only fixture declaring a view slot and it
|
|
94
|
+
* declares `"right"`, which is valid under both unions.
|
|
95
|
+
*/
|
|
96
|
+
export type NgisViewSlot = "right" | "bottom";
|
|
97
|
+
export interface NgisViewContribution {
|
|
98
|
+
id: string;
|
|
99
|
+
slot: NgisViewSlot;
|
|
100
|
+
/** Literal (F13.11). */
|
|
101
|
+
title: string;
|
|
102
|
+
icon?: string;
|
|
103
|
+
order?: number;
|
|
104
|
+
/** `when` only — see `NgisGatedContribution`. */
|
|
105
|
+
when?: string;
|
|
106
|
+
}
|
|
107
|
+
export interface NgisToolPreset {
|
|
108
|
+
id: string;
|
|
109
|
+
label: string;
|
|
110
|
+
/** String-valued, matching the task API — the facade does not coerce. */
|
|
111
|
+
params: Readonly<Record<string, string>>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* F13.9. Launch + presets + enablement **only**. A stanza cannot expose a free
|
|
115
|
+
* parameter; generated forms are Stage 14's `ParamForm`. Mounts through the
|
|
116
|
+
* existing rail "More tools" overflow seam — no new host real estate.
|
|
117
|
+
*/
|
|
118
|
+
export interface NgisToolContribution extends NgisGatedContribution {
|
|
119
|
+
/** Required, unique within the plugin. */
|
|
120
|
+
id: string;
|
|
121
|
+
/** Required — a backend analysis/workflow tool id. */
|
|
122
|
+
toolId: string;
|
|
123
|
+
/** Required; literal (F13.11). */
|
|
124
|
+
label: string;
|
|
125
|
+
icon?: string;
|
|
126
|
+
presets?: readonly NgisToolPreset[];
|
|
127
|
+
/** Only honoured with exactly one preset. */
|
|
128
|
+
autoRun?: boolean;
|
|
129
|
+
}
|
|
130
|
+
/** Selects the storage leg (F13.7). */
|
|
131
|
+
export type NgisSettingsScope = "user" | "scene";
|
|
132
|
+
export type NgisSettingsPropertyType = "string" | "number" | "integer" | "boolean";
|
|
133
|
+
/**
|
|
134
|
+
* The JSON-Schema **subset** X3 renders — flat object only. No nested objects,
|
|
135
|
+
* no arrays, no `$ref`, no `oneOf`/`allOf`, no `pattern`. Widget mapping is
|
|
136
|
+
* total and fixed, so there is no "unsupported widget" fallback to design:
|
|
137
|
+
*
|
|
138
|
+
* | schema | widget |
|
|
139
|
+
* |---|---|
|
|
140
|
+
* | `enum` present | `<Select>` |
|
|
141
|
+
* | `boolean` | `<Switch>` |
|
|
142
|
+
* | `number` / `integer` | number `<Input>` (with `min`/`max` when given) |
|
|
143
|
+
* | `string` | text `<Input>` |
|
|
144
|
+
*
|
|
145
|
+
* Anything outside the subset is a validation error at publish (XB1) and a
|
|
146
|
+
* parse refusal (X4a) — the renderer never sees it.
|
|
147
|
+
*/
|
|
148
|
+
export interface NgisSettingsProperty {
|
|
149
|
+
type: NgisSettingsPropertyType;
|
|
150
|
+
title?: string;
|
|
151
|
+
description?: string;
|
|
152
|
+
default?: string | number | boolean;
|
|
153
|
+
/** string/number only. */
|
|
154
|
+
enum?: readonly (string | number)[];
|
|
155
|
+
/** number/integer only. */
|
|
156
|
+
minimum?: number;
|
|
157
|
+
/** number/integer only. */
|
|
158
|
+
maximum?: number;
|
|
159
|
+
order?: number;
|
|
160
|
+
}
|
|
161
|
+
export interface NgisSettingsContribution {
|
|
162
|
+
scope: NgisSettingsScope;
|
|
163
|
+
properties: Readonly<Record<string, NgisSettingsProperty>>;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A T3 symbol renderer a plugin **declares**. Stage 24, F24.13, which **amends
|
|
167
|
+
* F13.3**: `contributes` gained a sixth key.
|
|
168
|
+
*
|
|
169
|
+
* The split rule holds — this is data, not code. What the bundle exports is
|
|
170
|
+
* `symbolRenderers: Record<id, (ctx: SymbolRenderContext) => Layer[]>`; what the
|
|
171
|
+
* manifest declares is only enough to resolve a symbol reference, draw a param
|
|
172
|
+
* form and intersect ACLs **before any bundle is fetched**. That ordering is the
|
|
173
|
+
* whole point: `plugin:<extId>@<version>/<rendererId>` must be answerable from
|
|
174
|
+
* the manifest alone, or resolution would have to activate every plugin that
|
|
175
|
+
* might own the renderer.
|
|
176
|
+
*
|
|
177
|
+
* Declaring a renderer does **not** force eager activation (F13.4 still holds).
|
|
178
|
+
* The plugin activates on the first *resolution* of a symbol that names it.
|
|
179
|
+
*
|
|
180
|
+
* Shapes are re-declared here rather than imported from
|
|
181
|
+
* `@/domain/models/symbol`: the SDK's contribution types are the plugin-facing
|
|
182
|
+
* surface and must not depend on the symbol registry's domain model, and the
|
|
183
|
+
* manifest's `params`/`slots` are a *declaration subset* — no `layers`, no
|
|
184
|
+
* `fallback`, no `animation`. The two are held together by the fixture pair
|
|
185
|
+
* (`m7`/`m8`), not by a shared import.
|
|
186
|
+
*/
|
|
187
|
+
export type NgisSymbolRendererGeometry = "point" | "line" | "polygon";
|
|
188
|
+
export type NgisSymbolRendererParamType = "number" | "color" | "boolean" | "enum" | "angle" | "percent";
|
|
189
|
+
export interface NgisSymbolRendererParam {
|
|
190
|
+
type: NgisSymbolRendererParamType;
|
|
191
|
+
label?: string;
|
|
192
|
+
default: string | number | boolean;
|
|
193
|
+
min?: number;
|
|
194
|
+
max?: number;
|
|
195
|
+
step?: number;
|
|
196
|
+
unit?: string;
|
|
197
|
+
/** `enum` only. */
|
|
198
|
+
values?: readonly string[];
|
|
199
|
+
}
|
|
200
|
+
/** The nine slot names of F24.5. A renderer declares which it reads; a slot it
|
|
201
|
+
* does not declare is not offered a binding in the scene editor. */
|
|
202
|
+
export type NgisSymbolRendererSlot = "size" | "color" | "angle" | "startTime" | "phase" | "data0" | "data1" | "data2" | "data3";
|
|
203
|
+
export interface NgisSymbolRendererContribution {
|
|
204
|
+
/** Unique within the plugin. The reference a symbol writes is
|
|
205
|
+
* `plugin:<extId>@<version>/<id>`. */
|
|
206
|
+
id: string;
|
|
207
|
+
geometry: NgisSymbolRendererGeometry;
|
|
208
|
+
label?: string;
|
|
209
|
+
params?: Readonly<Record<string, NgisSymbolRendererParam>>;
|
|
210
|
+
slots?: readonly NgisSymbolRendererSlot[];
|
|
211
|
+
}
|
|
212
|
+
/** F24.13's cap. Declared here so the two validators quote one number. */
|
|
213
|
+
export declare const NGIS_MAX_SYMBOL_RENDERERS_PER_PLUGIN = 8;
|
|
214
|
+
/**
|
|
215
|
+
* The top-level `contributes` block. A **sibling of `capabilities`**, not a
|
|
216
|
+
* capability key (XD3): `KNOWN_CAPABILITY_KEYS` stays
|
|
217
|
+
* `panels|railTools|layerTypes|commands` in both repos, unchanged.
|
|
218
|
+
*
|
|
219
|
+
* The split rule: `capabilities` carries **code** (render functions, handlers,
|
|
220
|
+
* renderer entries); `contributes` carries **only data**.
|
|
221
|
+
*/
|
|
222
|
+
export interface NgisContributes {
|
|
223
|
+
statusItems?: readonly NgisStatusItemContribution[];
|
|
224
|
+
menus?: NgisMenuContributions;
|
|
225
|
+
views?: readonly NgisViewContribution[];
|
|
226
|
+
tools?: readonly NgisToolContribution[];
|
|
227
|
+
settings?: NgisSettingsContribution;
|
|
228
|
+
/** F24.13 (Stage 24) — amends F13.3 five-key list. Max
|
|
229
|
+
* `NGIS_MAX_SYMBOL_RENDERERS_PER_PLUGIN`; duplicate `id` is a validation
|
|
230
|
+
* error in both repos. */
|
|
231
|
+
symbolRenderers?: readonly NgisSymbolRendererContribution[];
|
|
232
|
+
}
|
|
233
|
+
/** F13.5's ten grantable scopes. Frozen at the W1 kickoff as a **flat array of
|
|
234
|
+
* scope strings** on the manifest — duplicates are a validation error, order is
|
|
235
|
+
* insignificant, an empty array means the same as an absent key. */
|
|
236
|
+
export type NgisPermissionScope = "scene:read" | "resources:read" | "models:read" | "models:run" | "tasks:read" | "tasks:cancel" | "viz:write" | "storage:user" | "storage:scene" | "ui:notify";
|
|
237
|
+
/**
|
|
238
|
+
* F13.4. Auto-derived from the manifest; a plugin never writes an activation
|
|
239
|
+
* list. A plugin declaring `capabilities.panels`, `railTools` or `layerTypes` —
|
|
240
|
+
* the three code-carrying points with no declarative twin — **activates
|
|
241
|
+
* eagerly**, which is why `"eager"` is a member of this union rather than a
|
|
242
|
+
* separate flag: the loader branches on one value.
|
|
243
|
+
*/
|
|
244
|
+
export type NgisActivationEvent = "eager" | `onCommand:${string}` | `onView:${string}` | `onTool:${string}` | `onSceneType:${string}` | `onResourceType:${string}`;
|
|
245
|
+
/**
|
|
246
|
+
* One refusal, naming its field by full path (`contributes.menus.layer/context[2].command`)
|
|
247
|
+
* — the same discipline XB1's validator errors already follow, so a manifest
|
|
248
|
+
* rejected by the backend and one refused by the parser read the same way.
|
|
249
|
+
*/
|
|
250
|
+
export interface NgisContributionParseError {
|
|
251
|
+
/** Full dotted/bracketed path from the manifest root. */
|
|
252
|
+
path: string;
|
|
253
|
+
message: string;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* **Total, never throwing.** A malformed `contributes` block must not take the
|
|
257
|
+
* workspace down — the plugin loads with `contributes: null` and its errors
|
|
258
|
+
* surfaced, exactly as a bundle that fails to negotiate becomes a `"failed"`
|
|
259
|
+
* status rather than a boot crash (G-D criterion 6, structural).
|
|
260
|
+
*/
|
|
261
|
+
export interface NgisContributesParseResult {
|
|
262
|
+
/** `null` when the block is absent **or** refused — `errors` distinguishes. */
|
|
263
|
+
contributes: NgisContributes | null;
|
|
264
|
+
/** F13.4's derived set. Empty only for a manifest with no capabilities and no
|
|
265
|
+
* contributions at all. */
|
|
266
|
+
activationEvents: readonly NgisActivationEvent[];
|
|
267
|
+
/** Empty on success. Non-empty ⇒ `contributes` is `null`. */
|
|
268
|
+
errors: readonly NgisContributionParseError[];
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The seam between X4a (which implements it) and X4b (which calls it from the
|
|
272
|
+
* restructured load path).
|
|
273
|
+
*
|
|
274
|
+
* X4b takes this **by injection** so it compiles and unit-tests standalone
|
|
275
|
+
* against a fake, exactly as X1's publishers and X2's facet sources were
|
|
276
|
+
* dependency-injected in W1. The stub in `./index.ts` satisfies the type until
|
|
277
|
+
* X4a's implementation merges; **X4b must never import that stub in shipping
|
|
278
|
+
* code** — it takes the port as a parameter.
|
|
279
|
+
*/
|
|
280
|
+
export interface NgisContributionsPort {
|
|
281
|
+
/**
|
|
282
|
+
* @param manifest the raw manifest `JsonNode` as it arrives on the boot /
|
|
283
|
+
* scene-resolve payload — parsed **before any bundle is fetched** (XD4).
|
|
284
|
+
* That passthrough is the lazy-activation substrate and needs zero backend
|
|
285
|
+
* change.
|
|
286
|
+
*/
|
|
287
|
+
parseContributes(manifest: unknown): NgisContributesParseResult;
|
|
288
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `views` → workspace-panel bridge (Stage 13 X4a, F13.3 / F13.4).
|
|
3
|
+
*
|
|
4
|
+
* A `views` contribution is **data only** (XD3's split rule): an id, a slot, a
|
|
5
|
+
* literal title, an icon name, an order and a `when`. It carries no render
|
|
6
|
+
* function, because the bundle that would provide one has not been imported
|
|
7
|
+
* yet — the manifest arrives on the boot / scene-resolve payload and the whole
|
|
8
|
+
* point of F13.4 is that opening the view is what *triggers* the import.
|
|
9
|
+
*
|
|
10
|
+
* So this bridge registers a real `WorkspacePanelRegistry` entry per visible
|
|
11
|
+
* view — which is what puts it in the right dock's tab strip and the rail's
|
|
12
|
+
* panel group — and renders a **host placeholder** as its body. Opening it
|
|
13
|
+
* fires `onViewOpened`, the seam X4b wires to the derived `onView:<id>`
|
|
14
|
+
* activation event; once the plugin activates, its real
|
|
15
|
+
* `capabilities.panels` registration replaces the placeholder under the same
|
|
16
|
+
* slot (the panel registry is last-wins on id).
|
|
17
|
+
*
|
|
18
|
+
* ## The hand-off is one-way (`V-124`, X9c)
|
|
19
|
+
*
|
|
20
|
+
* "Replaces the placeholder" was only half true. `sync()` used to re-register
|
|
21
|
+
* the placeholder **unconditionally** on every contribution notification, and
|
|
22
|
+
* because `registerPanel` is last-wins on id, an activated plugin's real panel
|
|
23
|
+
* was reverted to the host placeholder the next time anything changed in the
|
|
24
|
+
* registry — a plugin whose panel worked when opened and became an empty title
|
|
25
|
+
* a moment later. The comment justifying the unconditional call was legitimate
|
|
26
|
+
* (title/icon/order move with the manifest), so the fix is not to stop
|
|
27
|
+
* re-registering: it is to re-register only over **this bridge's own**
|
|
28
|
+
* registration, checked by identity against `panels.getPanel(slot, id)`.
|
|
29
|
+
*
|
|
30
|
+
* ## The slot union — amended, not mapped
|
|
31
|
+
*
|
|
32
|
+
* F13.3 originally froze `views[].slot` as `"left" | "right"` while calling
|
|
33
|
+
* them "the two existing panel slots". Both values were wrong: `PanelSlot` has
|
|
34
|
+
* been `"right" | "bottom"` since C7 (`src/sdk/panels.ts:36`). X4a found it,
|
|
35
|
+
* the orchestrator verified it independently and **amended F13.3 to
|
|
36
|
+
* `"right" | "bottom"`** — so `NgisViewSlot` and `PanelSlot` are now the same
|
|
37
|
+
* two values and this bridge does no mapping at all. A `bottom` view registers
|
|
38
|
+
* into the bottom dock, a `right` view into the right dock, and `left` is
|
|
39
|
+
* refused by the parser like any other out-of-allowlist value.
|
|
40
|
+
*
|
|
41
|
+
* `toPanelSlot` survives as an explicit, tested identity so that a future
|
|
42
|
+
* divergence between the two unions fails here rather than silently coercing.
|
|
43
|
+
*/
|
|
44
|
+
import type { NgisUnsubscribe } from "../map-api";
|
|
45
|
+
import { type PanelRenderFn, type PanelSlot, type WorkspacePanelRegistry } from "../panels";
|
|
46
|
+
import { type NgisContributionRegistry, type NgisRegisteredView } from "./registry";
|
|
47
|
+
import type { NgisViewSlot } from "./types";
|
|
48
|
+
/**
|
|
49
|
+
* Both names now live in `./panel-id.ts` — a dependency-free module — because
|
|
50
|
+
* F14.12 made the id a contract `activation.ts` must build too, and importing
|
|
51
|
+
* it from here would drag `react-dom/client` into the boot-payload parse path.
|
|
52
|
+
* Re-exported rather than re-implemented: one declaration, every existing
|
|
53
|
+
* import path unchanged (`V-127`'s lesson, applied before it happened).
|
|
54
|
+
*/
|
|
55
|
+
export { CONTRIBUTED_VIEW_PANEL_PREFIX, contributedViewPanelId } from "./panel-id";
|
|
56
|
+
/**
|
|
57
|
+
* Maps a declared `NgisViewSlot` onto a real `PanelSlot`.
|
|
58
|
+
*
|
|
59
|
+
* An identity since the F13.3 amendment — the two unions are the same two
|
|
60
|
+
* values. Kept as a named, tested function rather than inlined so that if the
|
|
61
|
+
* unions ever diverge again the compiler catches it in one place.
|
|
62
|
+
*/
|
|
63
|
+
export declare function toPanelSlot(slot: NgisViewSlot): PanelSlot;
|
|
64
|
+
export interface ContributedViewPanelsOptions {
|
|
65
|
+
registry?: NgisContributionRegistry;
|
|
66
|
+
panels?: WorkspacePanelRegistry;
|
|
67
|
+
/**
|
|
68
|
+
* Builds the placeholder body for a contributed view. Defaults to a bare
|
|
69
|
+
* text node — the framework-agnostic `PanelRenderFn` contract, so this module
|
|
70
|
+
* needs no React and stays node-testable. The React mount passes a localized
|
|
71
|
+
* `GIS.contrib.*` string in.
|
|
72
|
+
*/
|
|
73
|
+
placeholderText?: (view: NgisRegisteredView) => string;
|
|
74
|
+
/** Full render override, for a host that wants more than a text placeholder. */
|
|
75
|
+
renderPanel?: (view: NgisRegisteredView) => PanelRenderFn;
|
|
76
|
+
/**
|
|
77
|
+
* Fired once per open of a contributed panel. **X4b's seam**: this is where
|
|
78
|
+
* the derived `onView:<id>` activation event is dispatched, which is what
|
|
79
|
+
* imports the bundle. Left injectable (and defaulting to a no-op) because
|
|
80
|
+
* the load path is X4b's file ownership, not this session's.
|
|
81
|
+
*/
|
|
82
|
+
onViewOpened?: (pluginId: string, viewId: string) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Keeps `workspacePanelRegistry` in sync with the contribution registry's
|
|
86
|
+
* visible `views`, and reports opens.
|
|
87
|
+
*
|
|
88
|
+
* Returns a disposer that unregisters every panel it created. Idempotent under
|
|
89
|
+
* repeated calls: registering the same panel id twice is a replace, not a
|
|
90
|
+
* duplicate (C7 semantics).
|
|
91
|
+
*/
|
|
92
|
+
export declare function bindContributedViewPanels(options?: ContributedViewPanelsOptions): NgisUnsubscribe;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineNgisPlugin()` — a typing-only identity helper for plugin authors
|
|
3
|
+
* (Stage 11 E3, DevKit). Exists purely so an author writing an `NgisPlugin`
|
|
4
|
+
* object gets full contextual type inference/autocomplete on `manifest` and
|
|
5
|
+
* `capabilities` (and, if a future capability adds a generic parameter, on
|
|
6
|
+
* that too) without hand-annotating the object's type — the same ergonomic
|
|
7
|
+
* pattern as `defineConfig()` helpers in other tools' SDKs.
|
|
8
|
+
*
|
|
9
|
+
* Zero runtime behavior beyond returning its argument: it does not register,
|
|
10
|
+
* validate, or negotiate anything (that stays the runtime's job, per
|
|
11
|
+
* `plugin-runtime.ts`). Re-exported by `@ngis/plugin-sdk` (the external
|
|
12
|
+
* DevKit distribution, `docs/design-extension-platform.md` §13) alongside the
|
|
13
|
+
* rest of `./plugin`'s types — this file is its single source of truth, not
|
|
14
|
+
* hand-copied into the published package.
|
|
15
|
+
*/
|
|
16
|
+
import type { NgisPlugin } from "./plugin";
|
|
17
|
+
export declare function defineNgisPlugin(plugin: NgisPlugin): NgisPlugin;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host.storage` (Stage 13 X3) — the two persistence legs of F13.7.
|
|
3
|
+
*
|
|
4
|
+
* | | user leg | scene leg |
|
|
5
|
+
* |---|---|---|
|
|
6
|
+
* | Backing | `ngis_extension_install.settings` (XB2, V4) | `ngis_scene.extension_config` → `plugins[].settings` |
|
|
7
|
+
* | Key | (user, extension) | (scene, extension) |
|
|
8
|
+
* | Write path | `PUT /api/extensions/{extId}/settings` | the composition `PUT`, echoing `revision` |
|
|
9
|
+
* | Who may write | the installer | **the scene owner only** |
|
|
10
|
+
* | Cap | 32 KiB serialized, server-enforced (`400`) | 32 KiB, **new writes only** |
|
|
11
|
+
* | Lifecycle | dies with the install row; survives disable | lives and dies with the composition entry |
|
|
12
|
+
*
|
|
13
|
+
* ### Three rules this module exists to keep, each a data-loss risk
|
|
14
|
+
*
|
|
15
|
+
* 1. **The composition `PUT` carries an opaque `revision` and it must ride the
|
|
16
|
+
* round-trip.** {@link createNgisStorageFacet}'s scene leg is a
|
|
17
|
+
* read-modify-write that *spreads* the composition it just read
|
|
18
|
+
* (`{...composition}`, `{...entry}`), so `revision`, `layout` and every
|
|
19
|
+
* field this client has never heard of survive. Rebuilding the object
|
|
20
|
+
* literally would drop `revision` silently and restore last-write-wins —
|
|
21
|
+
* which is a verified, real data-loss bug (Stage 12 row V-17), not a
|
|
22
|
+
* theoretical one. A refusal of a stale revision surfaces as
|
|
23
|
+
* `NgisStorageError("conflict")`.
|
|
24
|
+
* 2. **Existing `plugins[].settings` rows are unvalidated free-form and must
|
|
25
|
+
* keep parsing.** Nothing here validates on *read*: `get()` returns whatever
|
|
26
|
+
* the entry holds, including shapes no settings schema would accept. The
|
|
27
|
+
* 32 KiB cap is a **write-path** rule. A change that makes an old row
|
|
28
|
+
* unreadable is a data-loss bug.
|
|
29
|
+
* 3. **Refusals are surfaced, never masked.** `canWrite()` reports the truth so
|
|
30
|
+
* a settings form can render read-only instead of failing on submit, and
|
|
31
|
+
* `set()` never silently no-ops — it resolves or it rejects.
|
|
32
|
+
*
|
|
33
|
+
* ### The one place the contract could not be met (recorded, not worked around)
|
|
34
|
+
*
|
|
35
|
+
* F13.7's scene-leg row *"Read by non-installer: yes — a viewer of a shared
|
|
36
|
+
* scene reads the owner's settings"* has **no endpoint behind it this wave**.
|
|
37
|
+
* `GET /api/scenes/{id}/extensions` is owner-only (`403`), and W2 froze
|
|
38
|
+
* `SceneExtensionEntryDto` unchanged, so the scene-resolve payload carries no
|
|
39
|
+
* `settings`. A viewer therefore reads `null` and `canWrite()` is `false`. The
|
|
40
|
+
* UI states that in words rather than pretending the plugin has no settings;
|
|
41
|
+
* the missing projection is backlog row **V-49**.
|
|
42
|
+
*/
|
|
43
|
+
import type { NgisUnsubscribe } from "../map-api";
|
|
44
|
+
import type { NgisStorageFacet, NgisStorageLeg, NgisStorageWriteFailure } from "../host-api";
|
|
45
|
+
import type { NgisContextConfigSource } from "../context/publishers";
|
|
46
|
+
import type { NgisContextValue } from "../context/keys";
|
|
47
|
+
import type { SceneExtensionComposition } from "../../types/extensions";
|
|
48
|
+
/**
|
|
49
|
+
* The 32 KiB cap (XD11), measured on the **serialized** form — the same thing
|
|
50
|
+
* the server measures. Not exported from `@/sdk`: the barrel's X3 hunk lists
|
|
51
|
+
* the names this session may claim there, and this constant is an
|
|
52
|
+
* implementation detail the settings UI imports by path.
|
|
53
|
+
*/
|
|
54
|
+
export declare const NGIS_STORAGE_MAX_BYTES: number;
|
|
55
|
+
/**
|
|
56
|
+
* F13.2's typed write refusal. The reason union is **closed** — `read-only`,
|
|
57
|
+
* `too-large`, `conflict` — so a caller can branch on it exhaustively.
|
|
58
|
+
*/
|
|
59
|
+
export declare class NgisStorageError extends Error {
|
|
60
|
+
readonly reason: NgisStorageWriteFailure;
|
|
61
|
+
constructor(reason: NgisStorageWriteFailure, message?: string);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The user leg's transport — XB2's install-row endpoints, whose contract is
|
|
65
|
+
* frozen in the W2 kickoff §8:
|
|
66
|
+
*
|
|
67
|
+
* - `GET /api/extensions/{extId}/settings` → `200 {"settings": <object|null>}`;
|
|
68
|
+
* **`404` when the caller has no install row** (hence `installed`, which is
|
|
69
|
+
* what `canWrite()` reports — the leg is writable exactly while installed).
|
|
70
|
+
* - `PUT /api/extensions/{extId}/settings` — the settings object *is* the body,
|
|
71
|
+
* bare; the response is the same `{"settings": …}` envelope the `GET` uses.
|
|
72
|
+
* `PUT null` clears. Over-cap is **`400`, not `413`** (the gateway owns 413,
|
|
73
|
+
* and the client must be able to tell "your JSON is too big" from "your
|
|
74
|
+
* request was too big"), and `400` is also the answer for a non-object body.
|
|
75
|
+
*
|
|
76
|
+
* `write` returns the **stored** value, which is *not* byte-comparable with
|
|
77
|
+
* what was sent: the column is Postgres `jsonb` and normalizes key order,
|
|
78
|
+
* whitespace, duplicate keys and number formatting. Nothing in this module
|
|
79
|
+
* diffs the echo against the request — a client that did would decide it had
|
|
80
|
+
* failed to save and re-save forever.
|
|
81
|
+
*
|
|
82
|
+
* Injected rather than imported so this module compiles and unit-tests against
|
|
83
|
+
* a fake.
|
|
84
|
+
*/
|
|
85
|
+
export interface NgisUserStorageSource {
|
|
86
|
+
read(extId: string): Promise<{
|
|
87
|
+
installed: boolean;
|
|
88
|
+
settings: unknown | null;
|
|
89
|
+
}>;
|
|
90
|
+
write(extId: string, value: unknown): Promise<{
|
|
91
|
+
settings: unknown | null;
|
|
92
|
+
}>;
|
|
93
|
+
/** Optional push channel. Absent ⇒ `onChange` still fires on local writes. */
|
|
94
|
+
subscribe?(listener: () => void): NgisUnsubscribe;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The scene leg's transport — the existing composition `GET`/`PUT` on
|
|
98
|
+
* `ngis-scene`. `readComposition` reports `forbidden` rather than throwing on a
|
|
99
|
+
* `403`, because "you are a viewer" is a normal state this leg must render, not
|
|
100
|
+
* a failure.
|
|
101
|
+
*/
|
|
102
|
+
export interface NgisSceneStorageSource {
|
|
103
|
+
getSceneId(): string | null;
|
|
104
|
+
readComposition(sceneId: string): Promise<{
|
|
105
|
+
forbidden: boolean;
|
|
106
|
+
composition: SceneExtensionComposition | null;
|
|
107
|
+
}>;
|
|
108
|
+
/** Full replace. The body must be the document `readComposition` returned,
|
|
109
|
+
* spread and patched — never rebuilt. */
|
|
110
|
+
writeComposition(sceneId: string, body: SceneExtensionComposition): Promise<SceneExtensionComposition>;
|
|
111
|
+
subscribe?(listener: () => void): NgisUnsubscribe;
|
|
112
|
+
}
|
|
113
|
+
export interface NgisStorageSource {
|
|
114
|
+
/** `NgisHostAPI.pluginId` — the extension id both legs are keyed by. */
|
|
115
|
+
pluginId: string;
|
|
116
|
+
user: NgisUserStorageSource;
|
|
117
|
+
scene: NgisSceneStorageSource;
|
|
118
|
+
}
|
|
119
|
+
/** Serialized byte length — what the cap is measured against. */
|
|
120
|
+
export declare function serializedByteLength(value: unknown): number;
|
|
121
|
+
export declare function createNgisStorageFacet(source: NgisStorageSource): NgisStorageFacet;
|
|
122
|
+
/**
|
|
123
|
+
* Flattens one plugin's stored settings into the flat map
|
|
124
|
+
* `publishConfigContextKeys` turns into `config:<pluginId>:<key>` context keys
|
|
125
|
+
* (F14.11 — the publisher owns the namespacing, and it also owns the skip for a
|
|
126
|
+
* settings key that cannot *be* namespaced).
|
|
127
|
+
*
|
|
128
|
+
* Only what `NgisContextValue` can hold survives: scalars, `null`, and arrays
|
|
129
|
+
* of string/number. A nested object or a mixed array is **skipped**, not
|
|
130
|
+
* stringified — a `when` expression comparing against a JSON blob would be a
|
|
131
|
+
* footgun, and an absent key already evaluates falsy by design (F13.6).
|
|
132
|
+
*/
|
|
133
|
+
export declare function flattenConfigValues(value: unknown): Record<string, NgisContextValue>;
|
|
134
|
+
/**
|
|
135
|
+
* The real `config:*` key source X1 left a stub for
|
|
136
|
+
* (`emptyContextConfigSource`, `src/sdk/context/publishers.ts`).
|
|
137
|
+
*
|
|
138
|
+
* `NgisContextConfigSource.read()` is **synchronous** while every storage leg is
|
|
139
|
+
* async, so this holds a cache: `track()` reads the leg once, subscribes to it,
|
|
140
|
+
* and republishes on every change. Untracking removes the plugin's keys, which
|
|
141
|
+
* `publishConfigContextKeys` turns into `undefined` writes — an uninstalled
|
|
142
|
+
* plugin's `config:*` keys stop existing rather than lingering at their last
|
|
143
|
+
* value.
|
|
144
|
+
*
|
|
145
|
+
* Deliberately **not** exported from `@/sdk`: the host call site that would
|
|
146
|
+
* wire it (`GisWorkspace`'s facade construction) is X4b's file this wave, so
|
|
147
|
+
* the barrel export lands with the session that mounts it.
|
|
148
|
+
*/
|
|
149
|
+
export declare function createStorageConfigContextSource(): {
|
|
150
|
+
source: NgisContextConfigSource;
|
|
151
|
+
track(pluginId: string, leg: NgisStorageLeg): NgisUnsubscribe;
|
|
152
|
+
};
|