@object-ui/sdui-parser 17.7.0 → 18.0.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.
@@ -26,23 +26,18 @@
26
26
  * dateGranularity, sortBy, sortOrder, limit (query-affecting, framework#3588)
27
27
  * stageOrder (funnel/pyramid stage order)
28
28
  *
29
- * plus ONE undeclared key with a real read site:
30
- *
31
- * description — the metric-card sub-caption channel. Read at
32
- * `widgetSubCaption.ts` (`(widget.options as …)?.description`, objectui#4032
33
- * item 4 — that read sat inline in `DashboardRenderer.tsx` until
34
- * objectui#8889 moved it, verbatim, into the hook BOTH dashboard surfaces
35
- * now call, so the authored and bundle channels compose at one decision
36
- * point) and, since objectui#7293, at `DatasetWidget.tsx`'s
37
- * metric branch, which renders it in the caption row; the server's
38
- * `translateDashboard` OVERLAYS the `widgets.{id}.subCaption` translation
39
- * onto this key (objectstack#8056, objectstack#5428 item-4: 「两个作者字段两个
40
- * key」). It entered the accepted set on the translation-pipeline evidence
41
- * alone — warning on a key the platform's own pipeline writes would be a
42
- * false positive on legal metadata — while the dataset-bound render path
43
- * displayed it nowhere. #7293 closed that gap, so `description` is now
44
- * accepted for the same reason as the declared five and the accepted set no
45
- * longer outruns the measured read set.
29
+ * and nothing else. ⛔ Not `description`: it was the metric-card sub-caption
30
+ * (objectui#4032 item 4, objectui#7293), read by `DatasetWidget.tsx`'s metric
31
+ * branch and a resolver both dashboard surfaces called, and fed by the
32
+ * server's `translateDashboard` overlay of the `widgets.{id}.subCaption`
33
+ * translation. The spec never declared it. objectui#11389 (ruling C, which
34
+ * reverses objectstack#5428 item 4) retires it at both ends: objectstack
35
+ * first, in `@objectstack/spec` 17.7.0 (the overlay is gone and the
36
+ * translation key is a tombstone), then the readers here. So an authored
37
+ * `options.description` reaches no renderer and draws this warning like any
38
+ * other unconsumed key. A widget keeps one authored description,
39
+ * `widget.description`, the card-header subtitle, which is not an `options`
40
+ * key and is not judged here.
46
41
  *
47
42
  * Notably NOT consumed on the path a widget really renders through:
48
43
  * `thresholds` and `format`. Both were widely believed to work; both draw this
@@ -70,10 +65,9 @@
70
65
  * legitimate code gets deleted by the next person who hits it, which puts
71
66
  * the claim back where it started. Leg 2 of
72
67
  * `__tests__/dashboard-widget-options-census.test.ts` derives exactly this
73
- * bound: the DatasetWidget read set is the declared set plus the
74
- * sub-caption key, and neither `format` nor `thresholds` is in it. Since
75
- * objectui#7293 that absence is asserted in its OWN right rather than
76
- * riding on an equality whose right-hand side can grow.
68
+ * bound: the DatasetWidget read set is the declared set, and neither
69
+ * `format` nor `thresholds` is in it. That absence is asserted in its OWN
70
+ * right rather than riding on an equality whose right-hand side can grow.
77
71
  *
78
72
  * ## Scope — where the warning deliberately does NOT fire
79
73
  *
@@ -99,7 +93,8 @@
99
93
  * `@objectstack/spec`, re-extracts the `options.<key>` reads from
100
94
  * `DatasetWidget.tsx` source text (and fails loudly if that file gains a
101
95
  * consumption shape the extractor cannot see — a spread, a destructuring, a
102
- * computed access), re-checks the sub-caption read site, and trips on any NEW
96
+ * computed access), re-checks that the retired sub-caption key has no read
97
+ * site left in `plugin-dashboard`, and trips on any NEW
103
98
  * file in `packages/*\/src` or `apps/*\/src` that starts reading
104
99
  * `widget.options`. A renderer change that adds or removes a consumed key
105
100
  * fails that test until this list is updated — the cost of keeping this
@@ -116,8 +111,9 @@ export declare const UNCONSUMED_WIDGET_OPTION = "unconsumed-widget-option";
116
111
  export declare const DASHBOARD_WIDGET_HOST_TYPES: ReadonlySet<string>;
117
112
  /**
118
113
  * The accepted set: every `options` key with a renderer read site on the
119
- * dataset-bound path, plus the sub-caption convention key. Alphabetical; the
120
- * warning message prints it verbatim. Derivation and evidence: file header.
114
+ * dataset-bound path, which is exactly the set the spec declares.
115
+ * Alphabetical; the warning message prints it verbatim. Derivation and
116
+ * evidence: file header.
121
117
  */
122
118
  export declare const CONSUMED_WIDGET_OPTION_KEYS: readonly string[];
123
119
  /**
@@ -11,12 +11,12 @@ export const DASHBOARD_WIDGET_HOST_TYPES = new Set([
11
11
  ]);
12
12
  /**
13
13
  * The accepted set: every `options` key with a renderer read site on the
14
- * dataset-bound path, plus the sub-caption convention key. Alphabetical; the
15
- * warning message prints it verbatim. Derivation and evidence: file header.
14
+ * dataset-bound path, which is exactly the set the spec declares.
15
+ * Alphabetical; the warning message prints it verbatim. Derivation and
16
+ * evidence: file header.
16
17
  */
17
18
  export const CONSUMED_WIDGET_OPTION_KEYS = [
18
19
  'dateGranularity',
19
- 'description',
20
20
  'limit',
21
21
  'sortBy',
22
22
  'sortOrder',
package/dist/index.d.ts CHANGED
@@ -29,6 +29,15 @@ export interface CompileResult {
29
29
  * → validate against the manifest → derive `requires` + binding sites.
30
30
  */
31
31
  export declare function compile(source: string, manifest: Manifest): CompileResult;
32
+ /**
33
+ * The shape of one `@object-ui/types` `NodeSlotDeclaration`, restated
34
+ * structurally because this package imports nothing at runtime: a position in
35
+ * that package's key-path grammar, and whether its spelling is retired.
36
+ */
37
+ export interface NodeSlotDeclarationLike {
38
+ readonly path: string;
39
+ readonly retired?: boolean;
40
+ }
32
41
  export interface RegistryConfigLike {
33
42
  type: string;
34
43
  namespace?: string;
@@ -139,8 +148,21 @@ export declare function assertFullyLoaded(configs: RegistryConfigLike[]): void;
139
148
  * no renderer read and sailed through it green (objectstack#4413; corrected in
140
149
  * objectstack#4472). Evidence about the render path has to come from the render
141
150
  * path — see `apps/console/src/__tests__/public-block-binding-reach.test.tsx`.
151
+ *
152
+ * `opts.slotsFor` is the one exception to "copied from the registration", and
153
+ * it is a DECLARATION too (objectui#11170): hand it `nodeSlotsFor` from
154
+ * `@object-ui/types` and every entry carries `slots`, the node-slot positions
155
+ * that type's renderer reads besides `children`, so `validateTree` judges the
156
+ * nodes under them. This package has no runtime dependency and so cannot
157
+ * import the declaration itself; the producers that build a live manifest
158
+ * (`getJsxManifest` in `@object-ui/components`' page renderer, the console's
159
+ * manifest generator) pass it. A RETIRED position is left out here: this is
160
+ * the authoring tier, and the spec's own authoring walks skip the tombstoned
161
+ * spellings for the same reason. Without the option no `slots` key is written,
162
+ * so every manifest built before the option existed serialises byte-identically.
142
163
  */
143
164
  export declare function manifestFromConfigs(configs: RegistryConfigLike[], opts?: {
144
165
  only?: Set<string>;
145
166
  publicOnly?: boolean;
167
+ slotsFor?: (type: string) => readonly NodeSlotDeclarationLike[];
146
168
  }): Manifest;
package/dist/index.js CHANGED
@@ -77,6 +77,18 @@ export function assertFullyLoaded(configs) {
77
77
  * no renderer read and sailed through it green (objectstack#4413; corrected in
78
78
  * objectstack#4472). Evidence about the render path has to come from the render
79
79
  * path — see `apps/console/src/__tests__/public-block-binding-reach.test.tsx`.
80
+ *
81
+ * `opts.slotsFor` is the one exception to "copied from the registration", and
82
+ * it is a DECLARATION too (objectui#11170): hand it `nodeSlotsFor` from
83
+ * `@object-ui/types` and every entry carries `slots`, the node-slot positions
84
+ * that type's renderer reads besides `children`, so `validateTree` judges the
85
+ * nodes under them. This package has no runtime dependency and so cannot
86
+ * import the declaration itself; the producers that build a live manifest
87
+ * (`getJsxManifest` in `@object-ui/components`' page renderer, the console's
88
+ * manifest generator) pass it. A RETIRED position is left out here: this is
89
+ * the authoring tier, and the spec's own authoring walks skip the tombstoned
90
+ * spellings for the same reason. Without the option no `slots` key is written,
91
+ * so every manifest built before the option existed serialises byte-identically.
80
92
  */
81
93
  export function manifestFromConfigs(configs, opts = {}) {
82
94
  const components = {};
@@ -85,10 +97,17 @@ export function manifestFromConfigs(configs, opts = {}) {
85
97
  continue;
86
98
  if (opts.publicOnly && c.tier !== 'public')
87
99
  continue;
100
+ const slots = opts.slotsFor
101
+ ? opts.slotsFor(c.type).filter((s) => !s.retired).map((s) => s.path)
102
+ : undefined;
88
103
  components[c.type] = {
89
104
  type: c.type,
90
105
  namespace: c.namespace,
91
106
  isContainer: c.isContainer,
107
+ // `undefined` is dropped by `JSON.stringify`, so a manifest built with
108
+ // no resolver — or an entry with no slots — serialises exactly as before
109
+ // this key existed.
110
+ slots: slots && slots.length > 0 ? slots : undefined,
92
111
  // The html tier's stamp, and ONLY that stamp (objectui#10735): a
93
112
  // registration's `'public'` / `'internal'` is registry mechanics the
94
113
  // manifest never carried, and `undefined` is dropped by `JSON.stringify`,
package/dist/parse.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * A small recursive-descent parser for a CONSTRAINED JSX subset. It is
5
5
  * deliberately not a full JS/JSX parser: a bounded grammar is the point
6
6
  * (Markdoc model) — it shrinks the attack surface and the expressible-but-wrong
7
- * space. Output is the existing SDUI `SchemaNode` tree. Nothing is executed.
7
+ * space. Output is an SDUI node tree (`ParsedSchemaNode`). Nothing is executed.
8
8
  *
9
9
  * Grammar (informal):
10
10
  * document := element (exactly one root)
package/dist/parse.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * A small recursive-descent parser for a CONSTRAINED JSX subset. It is
5
5
  * deliberately not a full JS/JSX parser: a bounded grammar is the point
6
6
  * (Markdoc model) — it shrinks the attack surface and the expressible-but-wrong
7
- * space. Output is the existing SDUI `SchemaNode` tree. Nothing is executed.
7
+ * space. Output is an SDUI node tree (`ParsedSchemaNode`). Nothing is executed.
8
8
  *
9
9
  * Grammar (informal):
10
10
  * document := element (exactly one root)
package/dist/types.d.ts CHANGED
@@ -2,14 +2,24 @@
2
2
  * ObjectUI — SDUI JSX-source parser (ADR-0080)
3
3
  *
4
4
  * Types shared by the constrained JSX-source compiler. The parser turns a
5
- * constrained JSX *text* into the existing SDUI `SchemaNode`
6
- * tree. It PARSES — it never executes. No `import`, no `eval`, no JS.
5
+ * constrained JSX *text* into an SDUI node tree (`ParsedSchemaNode`, below).
6
+ * It PARSES — it never executes. No `import`, no `eval`, no JS.
7
7
  */
8
- /** A node in the compiled SDUI tree. Mirrors `@object-ui/types` BaseSchema. */
9
- export type SchemaNode = SchemaElement | string;
8
+ /**
9
+ * A node in the compiled SDUI tree, as parsed: an element or a text child.
10
+ *
11
+ * Not `SchemaNode`: `@object-ui/types` publishes that name for the closed
12
+ * AUTHORING face, a union discriminated on a literal `type` with no index
13
+ * signature and with primitive children admitted. This is the parser's open,
14
+ * as-parsed data (any `type` string, the keys as authored), neither is
15
+ * assignable to the other, and this package takes no runtime dependency on
16
+ * `@object-ui/types` to re-export from. One exported name stood for both until
17
+ * this declaration was renamed (objectui#6349, batch 11).
18
+ */
19
+ export type ParsedSchemaNode = SchemaElement | string;
10
20
  export interface SchemaElement {
11
21
  type: string;
12
- children?: SchemaNode[];
22
+ children?: ParsedSchemaNode[];
13
23
  [prop: string]: unknown;
14
24
  }
15
25
  export type Severity = 'error' | 'warning';
@@ -133,6 +143,19 @@ export interface ManifestComponent {
133
143
  * carry no such entry (objectui#9910; `acceptsChildren` in `validate.ts`).
134
144
  */
135
145
  inputs: ManifestInput[];
146
+ /**
147
+ * The NODE SLOTS this component's renderer reads besides `children`
148
+ * (objectui#11170): positions in the key-path grammar `@object-ui/types`'
149
+ * `NODE_SLOT_DECLARATIONS` states — `trigger`, `items[].content`,
150
+ * `regions[].components`. `validateTree` descends each one the way it
151
+ * descends `children`, so a slot-held node is judged too. Projected from
152
+ * that one declaration by `manifestFromConfigs`'s `slotsFor` option; the
153
+ * authoring tier leaves the declaration's RETIRED positions out, as the
154
+ * spec's own authoring walks do. ⛔ This tier keeps no slot list of its own:
155
+ * a manifest built without the option carries none, and the walk then
156
+ * follows `children` alone.
157
+ */
158
+ slots?: readonly string[];
136
159
  /**
137
160
  * LAYOUT containment (objectui#6804, objectui#9910 Q2-A): the flag the
138
161
  * react-page JSX scope builder skips and the public layout ledger lists.
package/dist/types.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * ObjectUI — SDUI JSX-source parser (ADR-0080)
3
3
  *
4
4
  * Types shared by the constrained JSX-source compiler. The parser turns a
5
- * constrained JSX *text* into the existing SDUI `SchemaNode`
6
- * tree. It PARSES — it never executes. No `import`, no `eval`, no JS.
5
+ * constrained JSX *text* into an SDUI node tree (`ParsedSchemaNode`, below).
6
+ * It PARSES — it never executes. No `import`, no `eval`, no JS.
7
7
  */
8
8
  export {};
@@ -8,6 +8,13 @@
8
8
  * `requires` (plugin provenance) and binding sites the SERVER must resolve
9
9
  * against object schema (we cannot resolve objects/fields here — that check is
10
10
  * framework-side by design).
11
+ *
12
+ * The walk descends `children` on every node and, for a component the
13
+ * manifest knows, the NODE SLOTS its entry carries (`ManifestComponent.slots`,
14
+ * objectui#11170) — the positions besides `children` through which that
15
+ * renderer hands nodes back to `SchemaRenderer`, projected from the one
16
+ * declaration in `@object-ui/types` by `manifestFromConfigs`. ⛔ No slot list
17
+ * lives here.
11
18
  */
12
19
  import type { Manifest, ManifestComponent, SchemaElement, ManifestValidationResult } from './types.js';
13
20
  /**
package/dist/validate.js CHANGED
@@ -8,6 +8,13 @@
8
8
  * `requires` (plugin provenance) and binding sites the SERVER must resolve
9
9
  * against object schema (we cannot resolve objects/fields here — that check is
10
10
  * framework-side by design).
11
+ *
12
+ * The walk descends `children` on every node and, for a component the
13
+ * manifest knows, the NODE SLOTS its entry carries (`ManifestComponent.slots`,
14
+ * objectui#11170) — the positions besides `children` through which that
15
+ * renderer hands nodes back to `SchemaRenderer`, projected from the one
16
+ * declaration in `@object-ui/types` by `manifestFromConfigs`. ⛔ No slot list
17
+ * lives here.
11
18
  */
12
19
  import { inputTypeArms } from './input-type.js';
13
20
  import { checkDashboardWidgetOptions } from './dashboard-widget-options.js';
@@ -126,6 +133,46 @@ const BASE_PROPS = basePropNames('every-node');
126
133
  /** The `'where-undeclared'` members of {@link SDUI_BASE_PROPS}. */
127
134
  const WHERE_UNDECLARED_BASE_PROPS = basePropNames('where-undeclared');
128
135
  const isExpr = (v) => typeof v === 'object' && v !== null && '$expr' in v;
136
+ const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
137
+ const isElement = (v) => isPlainObject(v) && typeof v.type === 'string';
138
+ /**
139
+ * The element nodes a slot position holds on `node`, in document order.
140
+ *
141
+ * The position is spelled in `@object-ui/types`' key-path grammar, which that
142
+ * package's `nodeSlotValues` implements for the other two readers and whose
143
+ * header states it: keys separated by `.`, a key followed by `[]` meaning each
144
+ * element of the array under it, and the value at the end being one node or a
145
+ * list of nodes. Restated here because this package imports nothing at
146
+ * runtime; the components-side census for objectui#11170 holds the two walks
147
+ * to the same answer over the same fixtures. Only element-shaped values are
148
+ * returned — a string child is legal in a slot and has nothing to validate.
149
+ */
150
+ function slotElements(node, path) {
151
+ let holders = [node];
152
+ for (const segment of path.split('.')) {
153
+ const each = segment.endsWith('[]');
154
+ const key = each ? segment.slice(0, -2) : segment;
155
+ const next = [];
156
+ for (const holder of holders) {
157
+ if (!isPlainObject(holder))
158
+ continue;
159
+ const value = holder[key];
160
+ if (!each)
161
+ next.push(value);
162
+ else if (Array.isArray(value))
163
+ next.push(...value);
164
+ }
165
+ holders = next;
166
+ }
167
+ const found = [];
168
+ for (const holder of holders) {
169
+ for (const value of Array.isArray(holder) ? holder : [holder]) {
170
+ if (isElement(value))
171
+ found.push(value);
172
+ }
173
+ }
174
+ return found;
175
+ }
129
176
  export function validateTree(tree, manifest) {
130
177
  const diagnostics = [];
131
178
  const requires = new Set();
@@ -263,6 +310,14 @@ export function validateTree(tree, manifest) {
263
310
  }
264
311
  if (node.children)
265
312
  node.children.forEach(visit);
313
+ // The node slots this component's renderer reads besides `children`
314
+ // (objectui#11170), as its manifest entry declares them. An unknown
315
+ // component has no entry and so no slots: its `unknown-component` above is
316
+ // the answer, and nothing under it is judged.
317
+ for (const path of comp?.slots ?? []) {
318
+ for (const element of slotElements(node, path))
319
+ visit(element);
320
+ }
266
321
  };
267
322
  if (tree)
268
323
  visit(tree);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/sdui-parser",
3
- "version": "17.7.0",
3
+ "version": "18.0.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -25,9 +25,10 @@
25
25
  "dist"
26
26
  ],
27
27
  "devDependencies": {
28
- "@objectstack/spec": "^17.0.0",
29
- "@object-ui/core": "17.7.0",
30
- "@object-ui/react": "17.7.0"
28
+ "@objectstack/spec": "^18.0.0",
29
+ "@object-ui/core": "18.0.0",
30
+ "@object-ui/react": "18.0.0",
31
+ "@object-ui/types": "18.0.0"
31
32
  },
32
33
  "scripts": {
33
34
  "build": "tsc && node ../../scripts/check-dist-completeness.mjs",