@uniweb/build 0.16.7 → 0.16.9

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.9",
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) => {
@@ -100,6 +100,11 @@ const INFO_TO_SITE_YML = {
100
100
  fetcher: 'fetcher',
101
101
  build: 'build',
102
102
  search: 'search',
103
+ // Safe to project back because nothing STAMPS it: `submit` is authored-only,
104
+ // so a pull can never launder a deploy-derived value into authored config the
105
+ // way a key carried by both would. A host-supplied destination is resolved at
106
+ // render time and never enters `info`.
107
+ submit: 'submit',
103
108
  agents: 'agents',
104
109
  paths: 'paths',
105
110
  data: 'data',
package/src/uwx/site.js CHANGED
@@ -629,6 +629,13 @@ export async function siteProjectToDocument(siteRoot, opts = {}) {
629
629
  setIf(info, 'fetcher', siteYml.fetcher)
630
630
  setIf(info, 'build', siteYml.build)
631
631
  setIf(info, 'search', siteYml.search)
632
+ // `submit` — where this site's forms send submissions. Same family as
633
+ // `fetcher`/`search`: the site declares it, the runtime reads it, and it
634
+ // round-trips verbatim. It has to be listed HERE because this lane is an
635
+ // explicit allowlist while the bundle lane spreads all of site.yml — without
636
+ // the line a `submit:` block works on a static host and vanishes silently on
637
+ // the synced lane, which is the worst shape a config bug can take.
638
+ setIf(info, 'submit', siteYml.submit)
632
639
  // `agents` — the projections opt-out + route exclusions. Carried because the
633
640
  // app is a second PUBLISHER of projections and derives them from stored
634
641
  // content: without this block it cannot see `agents: false` or
@@ -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
  }