@objectstack/sdui-parser 17.1.0 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -39,7 +39,20 @@ interface ParseResult {
39
39
  type ManifestInputType = 'string' | 'number' | 'boolean' | 'enum' | 'array' | 'object' | 'color' | 'date' | 'code' | 'file' | 'slot';
40
40
  interface ManifestInput {
41
41
  name: string;
42
- type: ManifestInputType;
42
+ /**
43
+ * The input's coarse type: ONE kind, or an ARRAY of kinds when the key's
44
+ * contract is a union (objectui#3832).
45
+ *
46
+ * A value passes {@link validateTree}'s coarse check when ANY arm accepts it,
47
+ * and is reported when none does — the array widens what is legal, it does
48
+ * not switch the check off.
49
+ *
50
+ * The single-kind form is unchanged and stays the canonical spelling for a
51
+ * one-arm key: `manifestFromConfigs` collapses a one-element array back to
52
+ * the bare string, so a manifest gains arrays only where a union was really
53
+ * declared and every already-published entry serializes byte-identically.
54
+ */
55
+ type: ManifestInputType | ManifestInputType[];
43
56
  required?: boolean;
44
57
  /** allowed values for `enum` inputs */
45
58
  enum?: Array<string | {
@@ -94,9 +107,25 @@ interface ValidationResult {
94
107
  declare function parseJsx(source: string, options?: ParseOptions): ParseResult;
95
108
  /**
96
109
  * Interpret a braced attribute value `{...}`.
97
- * JSON-literal values (numbers, booleans, null, strings, arrays, objects with
98
- * quoted keys) are materialized. Anything else is kept as a deferred expression
99
- * marker `{ $expr }` typed and validated later, NEVER evaluated here.
110
+ *
111
+ * Strict-JSON values are materialized by `JSON.parse`, exactly as they always
112
+ * were. Beyond that, the JS **literal subset** below is materialized too
113
+ * (objectui#6614 Q1-A, maintainer ruling 2026-08-28). Anything left over — a
114
+ * genuine expression — is kept as the deferred marker `{ $expr }`: typed and
115
+ * validated later, drawing `inert-expression`, and NEVER evaluated here.
116
+ *
117
+ * ORDER IS LOAD-BEARING. `JSON.parse` runs FIRST and is untouched, so every
118
+ * input JSON accepts takes byte-identically the path it took before the literal
119
+ * subset existed. The reader below only ever sees strings `JSON.parse` has
120
+ * already thrown on, which makes strict-JSON invariance a property of the
121
+ * structure rather than of a test.
122
+ *
123
+ * LOCKSTEP: this grammar is the port of objectui's `packages/sdui-parser` copy
124
+ * (objectui#6614). The two copies must agree on the accepted grammar AND on
125
+ * diagnostic codes — if they drift, the save gate and the renderer speak
126
+ * different dialects and a page can save clean and render inert
127
+ * (objectstack#12719 states the invariant; #12977 carries this half of it).
128
+ * Change this block only together with the objectui copy.
100
129
  */
101
130
  declare function interpretBrace(raw: string): unknown;
102
131
 
@@ -111,6 +140,128 @@ declare function interpretBrace(raw: string): unknown;
111
140
 
112
141
  declare function validateTree(tree: SchemaElement | null, manifest: Manifest): ValidationResult;
113
142
 
143
+ /**
144
+ * Unconsumed dashboard-widget `options` keys (objectui#5709), ported into this
145
+ * copy in lockstep (objectstack#12810).
146
+ *
147
+ * `@objectstack/spec`'s `DashboardWidgetOptionsSchema` ends in `.passthrough()`
148
+ * ("declared query keys + open renderer extras"), so ANY key parses, validates
149
+ * and lints cleanly — including one no renderer reads. That is how a showcase
150
+ * dashboard shipped `options: { invert: true }` on a gauge with a comment
151
+ * saying what it was believed to do, and rendered the un-inverted measure with
152
+ * no diagnostic anywhere (objectui#5709). The 2026-08-23 maintainer ruling:
153
+ * open extras stay open — they just stop being SILENT. A key that reaches no
154
+ * renderer draws a WARNING naming the consumed set. Not an error: no gate
155
+ * weakening and no new red gates were ruled.
156
+ *
157
+ * ## LOCKSTEP — what is byte-equal here and what deliberately is not
158
+ *
159
+ * Two copies of this parser exist: objectui's `packages/sdui-parser` and this
160
+ * hoisted `@objectstack/sdui-parser`. The invariant they owe each other is that
161
+ * both agree on the accepted grammar AND on diagnostic codes — if they drift,
162
+ * the save gate and the renderer speak different dialects and a page can save
163
+ * clean and render inert, or the reverse (objectstack#12719, objectstack#12810).
164
+ *
165
+ * Everything from the `import` line below to end of file is a byte-equal port of
166
+ * objectui's `src/dashboard-widget-options.ts` SAVE FOR ONE TOKEN, called out
167
+ * at the site itself: the emitted `code` is spelled as an inline literal here
168
+ * and as the constant there, because this repo runs a vocabulary gate objectui
169
+ * does not. The emitted `code`, `severity`, `message` and the whole census
170
+ * scope are identical, and `__tests__/dashboard-widget-options.test.ts`
171
+ * re-derives that rather than trusting it — including an explicit pin that the
172
+ * literal equals `UNCONSUMED_WIDGET_OPTION`. Change these functions only
173
+ * together with the objectui copy.
174
+ *
175
+ * THIS HEADER is the one deliberate divergence, and it has to be: objectui's
176
+ * header cites the maintenance machinery that derives the census — its
177
+ * `DatasetWidget.tsx` / `DashboardRenderer.tsx` read sites, its
178
+ * `plugin-dashboard.mdx` claim and its two census tests. NONE of those files
179
+ * exists in this repo (measured: no dashboard renderer package here at all), so
180
+ * copying those sentences would ship claims this checkout cannot support and
181
+ * nothing here would ever notice them going false. What follows instead states
182
+ * where each half of the census is derivable, and from what.
183
+ *
184
+ * ## The accepted set, and where each half is authoritative
185
+ *
186
+ * The spec REQUIRES `dataset` on every widget (`DashboardWidgetSchema`, this
187
+ * repo: `packages/spec/src/ui/dashboard.zod.ts`), and both of objectui's
188
+ * dashboard surfaces route a dataset-bound widget to `DatasetWidget`. On that —
189
+ * the only spec-legal — path the renderer-consumed `options` keys are exactly
190
+ * the five the spec DECLARES:
191
+ *
192
+ * dateGranularity, sortBy, sortOrder, limit (query-affecting, framework#3588)
193
+ * stageOrder (funnel/pyramid stage order)
194
+ *
195
+ * plus ONE undeclared key with a real read site:
196
+ *
197
+ * description — the metric-card sub-caption channel. `translateDashboard`
198
+ * OVERLAYS the `widgets.{id}.subCaption` translation onto this key, and that
199
+ * pipeline lives IN THIS REPO: `packages/spec/src/system/i18n-resolver.ts`
200
+ * documents `WidgetLike.options` as "the renderer-extras bag …
201
+ * `translateDashboard` writes exactly one key into it — `description`"
202
+ * (objectstack#5428 item 4, objectstack#7862). Warning on a key the
203
+ * platform's own translation pipeline writes would be a false positive on
204
+ * legal metadata, so it is in the accepted set even though the dataset-bound
205
+ * render path does not currently display it.
206
+ *
207
+ * Notably NOT consumed on the path a widget really renders through:
208
+ * `thresholds` and `format`. Both were widely believed to work; both draw this
209
+ * warning, which is the point. Their closure claims are objectui's to derive —
210
+ * `thresholds` by a repo-wide read-site scan there, `format` by the bounded
211
+ * claim that the dataset-bound path formats from the MEASURE's own metadata —
212
+ * and they are NOT restated here as claims about this repo, which has no
213
+ * renderer to make them about.
214
+ *
215
+ * ## The drift risk that lives on THIS side
216
+ *
217
+ * The spec whose `.passthrough()` this reasons about ships from this repo. So
218
+ * the one way this list can go stale HERE is a new DECLARED key landing in
219
+ * `DashboardWidgetOptionsSchema` without landing in the array below: the key
220
+ * would be spec-legal, renderer-consumed on the objectui side, and warned about
221
+ * here — a false positive on legal metadata. `@objectstack/sdui-parser` takes
222
+ * no dependency on `@objectstack/spec` (it is dependency-free and hoistable by
223
+ * design), so that cross-check is not mechanized in this copy; the census test
224
+ * next door pins the array and names the spec file to re-read when it moves.
225
+ *
226
+ * ## Scope — where the warning deliberately does NOT fire
227
+ *
228
+ * - Widgets WITHOUT `dataset`: the legacy inline forms (`options.data`
229
+ * arrays, `provider: 'object'` bags) consume a much larger, spread-shaped
230
+ * key set, whose true reach is each child component's prop surface. That
231
+ * form is spec-illegal today (`dataset` is required) and its census would be
232
+ * the unmaintainable one; skipping it keeps every warning this module emits
233
+ * a statement about the path the widget actually renders through.
234
+ * - Widgets in the legacy COMPONENT format (`widget.component`): `options`
235
+ * is not part of that contract.
236
+ * - Widgets carrying the spec's own escape hatch
237
+ * `suppressWarnings: ['unconsumed-widget-option']` — the spec models
238
+ * per-widget diagnostic suppression (`DashboardWidgetSchema.suppressWarnings`,
239
+ * "Build diagnostic rule ids suppressed on this widget"), so an author with
240
+ * a genuine out-of-band consumer can say so in metadata.
241
+ */
242
+
243
+ /** The diagnostic `code` — also the id `suppressWarnings` suppresses. */
244
+ declare const UNCONSUMED_WIDGET_OPTION = "unconsumed-widget-option";
245
+ /**
246
+ * Component types that host a dashboard `widgets` array. Both resolve to the
247
+ * surfaces measured by the census above (`DashboardRenderer`,
248
+ * `DashboardGridLayout`), which share one dispatch (`widgetDispatch.ts`).
249
+ */
250
+ declare const DASHBOARD_WIDGET_HOST_TYPES: ReadonlySet<string>;
251
+ /**
252
+ * The accepted set: every `options` key with a renderer read site on the
253
+ * dataset-bound path, plus the sub-caption convention key. Alphabetical; the
254
+ * warning message prints it verbatim. Derivation and evidence: file header.
255
+ */
256
+ declare const CONSUMED_WIDGET_OPTION_KEYS: readonly string[];
257
+ /**
258
+ * Diagnostics for `options` keys no renderer consumes, over one dashboard-host
259
+ * node's `widgets` array. Pure and shallow by design: it never descends into
260
+ * `children` (the caller's walk owns that) and answers `[]` for every shape
261
+ * outside its census — see the scope notes in the file header.
262
+ */
263
+ declare function checkDashboardWidgetOptions(node: SchemaElement): Diagnostic[];
264
+
114
265
  /**
115
266
  * ObjectUI — codegen the JSX type surface from the registry manifest (ADR-0080 §3)
116
267
  *
@@ -136,7 +287,54 @@ declare function propsName(type: string): string;
136
287
  declare function generateBlockList(manifest: Manifest): string;
137
288
 
138
289
  /**
139
- * @object-ui/sdui-parserconstrained JSX-source SDUI SchemaNode tree (ADR-0080)
290
+ * ObjectUIthe arms of a manifest input's coarse type (objectui#3832)
291
+ *
292
+ * `ManifestInput.type` carries ONE coarse kind, or an ARRAY of kinds when the
293
+ * key's contract is a union. Every reader of that field needs the same two
294
+ * decisions made the same way — how to see the arms, and which single form to
295
+ * publish — so they live here once instead of at each call site. A reader that
296
+ * forgets is not loud: `switch (input.type)` handed an array falls through to
297
+ * the default branch and reports NOTHING, which looks exactly like a value that
298
+ * validated cleanly.
299
+ */
300
+
301
+ /** The eleven coarse kinds, as a runtime set for guarding untyped input. */
302
+ declare const MANIFEST_INPUT_TYPES: ReadonlySet<string>;
303
+ /**
304
+ * The declared arms of an input's coarse type, always as an array.
305
+ *
306
+ * Exported (not merely internal) so a third-party manifest consumer — a
307
+ * designer panel, a codegen, a validator of its own — reads the arms through
308
+ * the same accessor this package's own gate does, rather than re-deriving the
309
+ * `Array.isArray` branch and getting it subtly wrong on the union form.
310
+ */
311
+ declare function inputTypeArms(type: ManifestInput['type'] | undefined): ManifestInputType[];
312
+ /**
313
+ * Project a DECLARED type (which may come from an untyped registry config) into
314
+ * the canonical published form: a bare string for one arm, an array for a real
315
+ * union.
316
+ *
317
+ * Two deliberate asymmetries between the single and array forms:
318
+ *
319
+ * - A single unrecognized kind still becomes `'string'`. That coercion predates
320
+ * the union work and is kept exactly: with one arm there is no other
321
+ * information to fall back on, and a manifest whose `type` is off-vocabulary
322
+ * would make every consumer's switch silently inert.
323
+ * - An unrecognized arm INSIDE an array is DROPPED, not coerced. Promoting it
324
+ * to `'string'` would publish an arm the author never declared — a widening
325
+ * invented by the serializer — and the surviving arms already carry the
326
+ * declaration. If dropping empties the array, the single-arm fallback applies
327
+ * so the output is always a valid manifest.
328
+ *
329
+ * Collapsing one arm to the bare string is what keeps this a backward-compatible
330
+ * extension of `sdui.manifest.json`: every input declared today serializes to
331
+ * the byte-identical entry it does now, and arrays appear only where a union was
332
+ * really declared.
333
+ */
334
+ declare function canonicalizeInputType(type: unknown): ManifestInputType | ManifestInputType[];
335
+
336
+ /**
337
+ * @objectstack/sdui-parser — constrained JSX-source → SDUI SchemaNode tree (ADR-0080)
140
338
  *
141
339
  * Isomorphic, zero React. Run server-side as the authoritative save-time gate;
142
340
  * may also run client-side for live edit preview (re-validated on the server —
@@ -166,7 +364,14 @@ interface RegistryConfigLike {
166
364
  category?: string;
167
365
  inputs?: Array<{
168
366
  name: string;
169
- type: string;
367
+ /**
368
+ * One coarse kind, or the arms of a union (objectui#3832). Typed loosely
369
+ * (`string`) on purpose — this interface is the STRUCTURAL boundary that
370
+ * keeps this package free of a dependency on the registry, so an
371
+ * off-vocabulary value has to be representable here and is normalized by
372
+ * `canonicalizeInputType` on the way in.
373
+ */
374
+ type: string | string[];
170
375
  required?: boolean;
171
376
  enum?: Array<string | {
172
377
  value: unknown;
@@ -181,4 +386,4 @@ declare function manifestFromConfigs(configs: RegistryConfigLike[], opts?: {
181
386
  publicOnly?: boolean;
182
387
  }): Manifest;
183
388
 
184
- export { type CodegenOptions, type CompileResult, type Diagnostic, type Manifest, type ManifestComponent, type ManifestInput, type ManifestInputType, type ParseOptions, type ParseResult, type RegistryConfigLike, type SchemaElement, type SchemaNode, type Severity, type ValidationResult, compile, generateBlockList, generateDts, interpretBrace, manifestFromConfigs, parseJsx, propsName, validateTree };
389
+ export { CONSUMED_WIDGET_OPTION_KEYS, type CodegenOptions, type CompileResult, DASHBOARD_WIDGET_HOST_TYPES, type Diagnostic, MANIFEST_INPUT_TYPES, type Manifest, type ManifestComponent, type ManifestInput, type ManifestInputType, type ParseOptions, type ParseResult, type RegistryConfigLike, type SchemaElement, type SchemaNode, type Severity, UNCONSUMED_WIDGET_OPTION, type ValidationResult, canonicalizeInputType, checkDashboardWidgetOptions, compile, generateBlockList, generateDts, inputTypeArms, interpretBrace, manifestFromConfigs, parseJsx, propsName, validateTree };