@north-light/api 0.3.524 → 0.3.526

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.
@@ -3,6 +3,8 @@ export interface DeclaredOutputShape {
3
3
  constraint: string;
4
4
  /** Named members of an `object` value. Declaring them closes the object. */
5
5
  children?: DeclaredOutputField[];
6
+ /** Shape of every member `children` does not declare. Absent means the object is closed. */
7
+ extra?: DeclaredOutputShape;
6
8
  /** Shape of each contained value of an `array` or `map` value. */
7
9
  items?: DeclaredOutputShape;
8
10
  /** Alternatives of a `union` value. */
@@ -31,7 +33,7 @@ export declare function validateDeclaredResult(output: DeclaredOutputField[], re
31
33
  } | null;
32
34
  /** Checks one JSON value against one shape, recursively: object members, array
33
35
  * items, map values, union variants, enum values and nullability. An object that
34
- * declares `children` is closed — an undeclared member is a mismatch at any
35
- * depth. Returns null, or the first mismatch with its full path from `path`.
36
+ * declares `children` is closed unless it carries `extra`: an undeclared
37
+ * member is a mismatch at any depth, or must fit `extra`. Returns null, or the first mismatch with its full path from `path`.
36
38
  * A declared type that is not recognized is accepted. */
37
39
  export declare function validateValue(shape: DeclaredOutputShape, value: unknown, path: string): ValueMismatch | null;
@@ -19,8 +19,8 @@ export function validateDeclaredResult(output, result) {
19
19
  }
20
20
  /** Checks one JSON value against one shape, recursively: object members, array
21
21
  * items, map values, union variants, enum values and nullability. An object that
22
- * declares `children` is closed — an undeclared member is a mismatch at any
23
- * depth. Returns null, or the first mismatch with its full path from `path`.
22
+ * declares `children` is closed unless it carries `extra`: an undeclared
23
+ * member is a mismatch at any depth, or must fit `extra`. Returns null, or the first mismatch with its full path from `path`.
24
24
  * A declared type that is not recognized is accepted. */
25
25
  export function validateValue(shape, value, path) {
26
26
  const atoms = shape.type.split('|').map((part) => part.trim()).filter(Boolean);
@@ -135,8 +135,15 @@ function atomMismatch(type, shape, v, path) {
135
135
  }
136
136
  const declared = new Set(shape.children.map((member) => member.name));
137
137
  for (const key of Object.keys(v)) {
138
- if (!declared.has(key))
138
+ if (declared.has(key))
139
+ continue;
140
+ if (shape.extra === undefined)
139
141
  return mismatch(`${path}.${key}`, 'no such member (the object is closed)', v[key]);
142
+ if (v[key] === undefined)
143
+ continue;
144
+ const inner = validateValue(shape.extra, v[key], `${path}.${key}`);
145
+ if (inner !== null)
146
+ return inner;
140
147
  }
141
148
  return null;
142
149
  }
@@ -44,9 +44,9 @@ const PARAM_KEYS_BY_KIND = {
44
44
  stdin: new Set(['kind', 'name', 'required', 'constraint']),
45
45
  'context-file': new Set(['kind', 'name', 'required', 'constraint', 'shape']),
46
46
  };
47
- const OUTPUT_KEYS = new Set(['name', 'type', 'required', 'constraint', 'children', 'items', 'variants', 'values']);
47
+ const OUTPUT_KEYS = new Set(['name', 'type', 'required', 'constraint', 'children', 'extra', 'items', 'variants', 'values']);
48
48
  /** An `items` element shape: an output field without a name or required. */
49
- const OUTPUT_SHAPE_KEYS = new Set(['type', 'constraint', 'children', 'items', 'variants', 'values']);
49
+ const OUTPUT_SHAPE_KEYS = new Set(['type', 'constraint', 'children', 'extra', 'items', 'variants', 'values']);
50
50
  /** UPPER_SNAKE_CASE environment variable name — the only form an ambient default
51
51
  * may name, so a typo'd lowercase key fails at authoring time rather than
52
52
  * silently never resolving. */
@@ -378,6 +378,25 @@ function validateOutputShape(f, fp, topLevel, issue) {
378
378
  return null;
379
379
  children = c;
380
380
  }
381
+ let extra;
382
+ if (f['extra'] !== undefined) {
383
+ const raw = f['extra'];
384
+ const ep = `${fp}.extra`;
385
+ if (base !== 'object' || f['children'] === undefined) {
386
+ issue('command_node_invalid', `extra is only valid on an object field that declares children`, `type ${type}${f['children'] === undefined ? ' without children' : ''}`, 'type "object" with children (optionally "| null")', 'Remove extra, or declare children; an object without children already accepts any members, and a map takes items.', ep);
387
+ return null;
388
+ }
389
+ if (!isRecord(raw)) {
390
+ issue('command_node_invalid', `output extra must be an object`, typeName(raw), 'a { type, constraint, ... } shape', 'Fix the extra shape.', ep);
391
+ return null;
392
+ }
393
+ if (!checkKeys(raw, OUTPUT_SHAPE_KEYS, ep, issue))
394
+ return null;
395
+ const shape = validateOutputShape(raw, ep, false, issue);
396
+ if (shape === null)
397
+ return null;
398
+ extra = shape;
399
+ }
381
400
  let items;
382
401
  if (f['items'] !== undefined) {
383
402
  const raw = f['items'];
@@ -433,6 +452,7 @@ function validateOutputShape(f, fp, topLevel, issue) {
433
452
  type,
434
453
  constraint: f['constraint'],
435
454
  ...(children !== undefined ? { children } : {}),
455
+ ...(extra !== undefined ? { extra } : {}),
436
456
  ...(items !== undefined ? { items } : {}),
437
457
  ...(variants !== undefined ? { variants } : {}),
438
458
  ...(values !== undefined ? { values } : {}),
@@ -16,6 +16,9 @@ export interface ManifestFieldShape {
16
16
  constraint: string;
17
17
  /** Named members; only when the base type is `object`. */
18
18
  children?: ManifestField[];
19
+ /** The shape every member not named in `children` must have; only when the base type is `object` and `children` is present.
20
+ * An object with `children` and no `extra` is closed. */
21
+ extra?: ManifestFieldShape;
19
22
  /** Value shape; only when the base type is `array` or `map`. */
20
23
  items?: ManifestFieldShape;
21
24
  /** Alternatives; required when the base type is `union`. */
package/dist/core/help.js CHANGED
@@ -327,11 +327,20 @@ function renderOutputFields(fields, indent, lines) {
327
327
  renderOutputShapeBody(f, `${indent} `, lines);
328
328
  }
329
329
  }
330
+ /** An open object's last row: the shape of the members it does not declare, then that shape's own body one indent deeper. */
331
+ function renderExtraRow(shape, indent, lines) {
332
+ const extra = shape.extra;
333
+ if (extra === undefined)
334
+ return;
335
+ lines.push(`${indent}other members: ${outputTypePhrase(extra)}.${outputValuesNote(extra)}${extra.constraint.length > 0 ? ` ${extra.constraint}` : ''}`);
336
+ renderOutputShapeBody(extra, `${indent} `, lines);
337
+ }
330
338
  /** The nested part of a shape: object children, or an array element's shape. */
331
339
  function renderOutputShapeBody(shape, indent, lines) {
332
340
  if (shape.children !== undefined && shape.children.length > 0) {
333
341
  renderOutputFields(shape.children, indent, lines);
334
342
  }
343
+ renderExtraRow(shape, indent, lines);
335
344
  for (const variant of shape.variants ?? []) {
336
345
  const note = outputValuesNote(variant);
337
346
  lines.push(`${indent}variant: ${outputTypePhrase(variant)}.${note}${variant.constraint.length > 0 ? ` ${variant.constraint}` : ''}`);
@@ -350,6 +359,11 @@ function renderOutputShapeBody(shape, indent, lines) {
350
359
  }
351
360
  if (item.children !== undefined && item.children.length > 0) {
352
361
  renderOutputFields(item.children, indent, lines);
362
+ renderExtraRow(item, indent, lines);
363
+ return;
364
+ }
365
+ if (item.extra !== undefined) {
366
+ renderExtraRow(item, indent, lines);
353
367
  return;
354
368
  }
355
369
  eachWord = outputBaseType(item.type) === 'map' ? 'each value' : 'each element';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/api",
3
- "version": "0.3.524",
3
+ "version": "0.3.526",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",