@uniweb/build 0.16.7 → 0.16.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.16.7",
3
+ "version": "0.16.8",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -59,15 +59,15 @@
59
59
  "js-yaml": "^4.1.0",
60
60
  "sharp": "^0.35.3",
61
61
  "yaml": "^2.5.0",
62
- "@uniweb/content-writer": "0.3.3",
62
+ "@uniweb/theming": "0.1.15",
63
63
  "@uniweb/projections": "0.2.4",
64
- "@uniweb/theming": "0.1.15"
64
+ "@uniweb/content-writer": "0.3.3"
65
65
  },
66
66
  "optionalDependencies": {
67
67
  "@uniweb/content-reader": "1.2.2",
68
- "@uniweb/runtime": "0.9.2",
69
- "@uniweb/semantic-parser": "1.2.1",
70
- "@uniweb/schemas": "0.2.4"
68
+ "@uniweb/runtime": "0.9.3",
69
+ "@uniweb/schemas": "0.2.5",
70
+ "@uniweb/semantic-parser": "1.2.1"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -600,10 +600,32 @@ function normalizeField(field, ref, path) {
600
600
 
601
601
  // Structural kinds.
602
602
  if (out.type === 'object') {
603
- if (field.fields === undefined) {
604
- throw new Error(`Data schema '${ref}': object field '${path}' must declare nested 'fields'.`)
603
+ // Two ways to describe an object, and they answer different questions:
604
+ //
605
+ // fields the object's KNOWN keys — `{ street, city }`
606
+ // values an OPEN MAP whose keys belong to the author and whose values all
607
+ // conform to one shape — `{ <anything>: <a field spec> }`
608
+ //
609
+ // `values` is to an object what `items` is to an array, and it exists for
610
+ // the same reason: a form's `fields` is a map keyed by author-chosen names
611
+ // (see `@std/form`), which could not be described at all until this landed.
612
+ // Requested by the editor team 2026-07-31 — the shape they needed and could
613
+ // not state in this vocabulary either.
614
+ if (field.values !== undefined && field.fields !== undefined) {
615
+ throw new Error(
616
+ `Data schema '${ref}': object field '${path}' declares both 'fields' and 'values'. ` +
617
+ `Use 'fields' for known keys, 'values' for an open map — not both.`
618
+ )
619
+ }
620
+ if (field.values !== undefined) {
621
+ out.values = normalizeField(field.values, ref, `${path}{}`)
622
+ } else if (field.fields === undefined) {
623
+ throw new Error(
624
+ `Data schema '${ref}': object field '${path}' must declare nested 'fields' (known keys) or 'values' (an open map).`
625
+ )
626
+ } else {
627
+ out.fields = normalizeFields(field.fields, ref, path)
605
628
  }
606
- out.fields = normalizeFields(field.fields, ref, path)
607
629
  } else if (out.type === 'array') {
608
630
  // `items` (the element type) is recommended but optional — an array with
609
631
  // no declared element type is an untyped list.
@@ -637,6 +659,7 @@ export function collectNestedRefs(schema) {
637
659
  if (typeof field.options === 'string') found.add(field.options)
638
660
  if (field.fields) walkFields(field.fields)
639
661
  if (field.items) walkFields({ _: field.items })
662
+ if (field.values) walkFields({ _: field.values })
640
663
  }
641
664
  }
642
665
  const walkSections = (sections) => {
@@ -118,6 +118,21 @@ function validateValue(def, value, path) {
118
118
  out.push(violation(path, 'type', `expected object, got ${typeName(value)}`))
119
119
  } else if (def.fields) {
120
120
  out.push(...validateFields(def.fields, value, path))
121
+ } else if (def.values) {
122
+ // An OPEN MAP: the keys are the author's, every value conforms to one
123
+ // shape. `values` is to an object what `items` is to an array.
124
+ //
125
+ // Note what this deliberately does NOT do: reject a key. It cannot — the
126
+ // keys are the whole point — and it must not reject unexpected keys
127
+ // WITHIN a value either, which falls out of `validateFields` walking the
128
+ // schema's fields rather than the data's. That tolerance is load-bearing
129
+ // for `@std/form`: a form definition may carry per-field keys the current
130
+ // builder cannot author (hand-written, or from a newer editor), and the
131
+ // editor's boundary passes them through untouched. A stricter check here
132
+ // would fail builds on good content.
133
+ for (const [key, item] of Object.entries(value)) {
134
+ out.push(...validateValue(def.values, item, `${path}.${key}`))
135
+ }
121
136
  }
122
137
  return out
123
138
  }