@uniweb/runtime 1.5.5 → 1.5.6

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/runtime",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,7 +36,7 @@
36
36
  "node": ">=22.12"
37
37
  },
38
38
  "dependencies": {
39
- "@uniweb/core": "~1.6.10",
39
+ "@uniweb/core": "~1.6.11",
40
40
  "@uniweb/theming": "~1.3.2"
41
41
  },
42
42
  "devDependencies": {
@@ -44,7 +44,7 @@
44
44
  "esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
45
45
  "vite": "^7.3.1",
46
46
  "vitest": "^4.1.7",
47
- "@uniweb/build": "1.20.3"
47
+ "@uniweb/build": "1.20.4"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": "^19.0.0",
@@ -82,13 +82,15 @@ export function createPageRenderer({ website, shell }) {
82
82
  * parametric pages made from them (`Website#forgetData`), and files these, all in this
83
83
  * synchronous call, so a Website reused across requests renders each against its own and
84
84
  * no other request runs in between. Absent, the render reads whatever the store holds.
85
- * @param {boolean} [options.sectionData] - also say which records each section showed, for the
86
- * page's Markdown: `sections`, which `renderPageMarkdown(page, { sections })`
87
- * (`@uniweb/projections`) writes after each section. Read from the store this render read, after
88
- * its HTML (`section-records.js`), so a render that does not ask does no more work than before.
85
+ * @param {boolean} [options.sectionData] - also say what the page's Markdown carries:
86
+ * `sections`, the records each section showed, and on a record page `record`, its own record as
87
+ * this render read it. Hand both to `renderPageMarkdown(page, { record, sections })`
88
+ * (`@uniweb/projections`). Read from the store this render read, after its HTML
89
+ * (`section-records.js`), so a render that does not ask does no more work than before.
89
90
  * @returns {{ outcome: 'rendered'|'notFound'|'failed', html: string|null,
90
91
  * page: Object|null, error: {type: string, message: string}|null,
91
- * sections?: Array<{ id: string, data: Object }> }} `sections` when asked, on a page with `html`
92
+ * sections?: Array<{ id: string, data: Object }>, record?: Object|null }} `sections` when asked,
93
+ * on a page with `html`; `record` beside it on a record page, null when its route names none
92
94
  * `notFound` means the URL names nothing, and `html` says whether there is a page to
93
95
  * send for it. Send `html` with your not-found status when it is set, and the shell
94
96
  * when it is not:
@@ -189,7 +191,11 @@ export function createPageRenderer({ website, shell }) {
189
191
  })
190
192
  const rendered = { outcome: missing ? 'notFound' : 'rendered', html, page, error: null }
191
193
  // In this same synchronous call, so the store holds this page's answers and no other request's.
192
- if (sectionData) rendered.sections = sectionRecords(page)
194
+ if (sectionData) {
195
+ const shown = sectionRecords(page)
196
+ rendered.sections = shown.sections
197
+ if (shown.record !== undefined) rendered.record = shown.record
198
+ }
193
199
  return rendered
194
200
  }
195
201
 
@@ -24,7 +24,7 @@
24
24
  * - **Left out:** a key the section holds as a tagged block in its own content (` ```yaml:tag `, or
25
25
  * a concept block), which the page's Markdown already shows as it was written; a record page's
26
26
  * own record (a key whose fetch uses this page's record, `current: only`), which its Markdown
27
- * writes once, as the page's (`renderPageMarkdown`'s `record`); and a key with no value, `null` —
27
+ * writes once, as the page's, from `record` (`pageRecordOf`); and a key with no value, `null` —
28
28
  * nothing fills it, or its fetch failed, never ran or is still out.
29
29
  * - **Whose:** each body section and its child sections, under its own id, and the insets in a
30
30
  * section, under the id of the section they sit in, since a page's Markdown has no place of their
@@ -34,32 +34,67 @@
34
34
  */
35
35
  import { sectionDomId } from '@uniweb/core/section-id'
36
36
  import { declaredKeys } from '@uniweb/core/data-keys'
37
- import { blockFillsOf, blockRouteQuery, fillIsPageRecord } from '@uniweb/core/page-data'
37
+ import { blockFillsOf, blockRouteQuery, fillIsPageRecord, heldPageRecord } from '@uniweb/core/page-data'
38
38
  import { getComponentMeta, receivedData } from './prepare-props.js'
39
39
 
40
40
  /**
41
- * The records each of a page's sections showed, read after the page's render.
41
+ * The records each of a page's sections showed, and a record page's own record, read after the
42
+ * page's render: what its Markdown carries (`renderPageMarkdown(page, { record, sections })`).
42
43
  *
43
44
  * @param {Object} page - the Page just rendered, its Website the one `globalThis.uniweb` names
44
- * @returns {Array<{ id: string, data: Object }>} in page order, one entry per section, child section
45
- * or inset that showed any, `id` the section's DOM id (`sectionDomId`) and `data` its records by name
45
+ * @returns {{ sections: Array<{ id: string, data: Object }>, record: Object|null|undefined }}
46
+ * `sections` in page order, one entry per section, child section or inset that showed any, `id`
47
+ * the section's DOM id (`sectionDomId`) and `data` its records by name. `record` a record page's
48
+ * own (`pageRecordOf`): null when its route names none, undefined off a record page
46
49
  */
47
50
  export function sectionRecords(page) {
48
- const out = []
49
- for (const block of page?.getBodyBlocks?.() ?? []) visit(block, sectionDomId(block), out)
50
- return out
51
+ const sections = []
52
+ // The page's own record as its sections received it, which `sections` leaves out.
53
+ const shown = []
54
+ for (const block of page?.getBodyBlocks?.() ?? []) visit(block, sectionDomId(block), sections, shown)
55
+ return { sections, record: pageRecordOf(page, shown) }
51
56
  }
52
57
 
53
- function visit(block, id, out) {
58
+ function visit(block, id, sections, shown) {
54
59
  if (!block) return
55
- const data = recordsOf(block)
56
- if (data) out.push({ id, data })
57
- for (const inset of block.insets || []) visit(inset, id, out)
58
- for (const child of block.childBlocks || []) visit(child, sectionDomId(child), out)
60
+ const data = recordsOf(block, shown)
61
+ if (data) sections.push({ id, data })
62
+ for (const inset of block.insets || []) visit(inset, id, sections, shown)
63
+ for (const child of block.childBlocks || []) visit(child, sectionDomId(child), sections, shown)
64
+ }
65
+
66
+ /**
67
+ * ⭐ A RECORD PAGE'S OWN RECORD, AS ITS RENDER READ IT — the most of it the page held, so its
68
+ * Markdown carries what its sections drew: whole when a section received it whole, or when the
69
+ * record index holds it whole; else as a section received it; else the answer the page's own
70
+ * question got (`heldPageRecord`, `@uniweb/core/page-data`), which names the page and says whether
71
+ * it exists.
72
+ *
73
+ * @param {Object} page
74
+ * @param {Array<{ value: *, whole: boolean }>} shown
75
+ * @returns {Object|null|undefined}
76
+ */
77
+ function pageRecordOf(page, shown) {
78
+ if (!page?.dynamicContext) return undefined
79
+ const one = (value) => (Array.isArray(value) ? (value[0] ?? null) : value)
80
+ const whole = shown.find((s) => s.whole && one(s.value))
81
+ if (whole) return one(whole.value)
82
+ const website = page.website
83
+ const fetcher = website?.fetcher
84
+ const own = heldPageRecord(page, {
85
+ website,
86
+ peek: (request) => fetcher?.peek?.(request, { website, page, block: null }),
87
+ peekRecord: (id) => fetcher?.peekRecord?.(id),
88
+ })
89
+ const seen = shown.map((s) => one(s.value)).find(Boolean) ?? null
90
+ const id = (own ?? seen)?.$uuid
91
+ const held = typeof id === 'string' && id ? fetcher?.peekRecord?.(id) : null
92
+ if (held?.whole === true && held.record) return held.record
93
+ return seen ?? own
59
94
  }
60
95
 
61
96
  /** One block's records by name, or null when it showed none. */
62
- function recordsOf(block) {
97
+ function recordsOf(block, shown) {
63
98
  // A type the foundation doesn't have renders as an error box, and its render asks for no data.
64
99
  if (!globalThis.uniweb?.getComponent?.(block.type)) return null
65
100
  const meta = getComponentMeta(block.type)
@@ -89,8 +124,13 @@ function recordsOf(block) {
89
124
  route = blockRouteQuery(block)
90
125
  fills = blockFillsOf(block, meta, route)
91
126
  }
92
- const cfg = fills.get(key)?.cfg
93
- if (!cfg?.as || fillIsPageRecord(block, cfg, route)) continue
127
+ const fill = fills.get(key)
128
+ const cfg = fill?.cfg
129
+ if (!cfg?.as) continue
130
+ if (fillIsPageRecord(block, cfg, route)) {
131
+ shown.push({ value, whole: fill.whole === true })
132
+ continue
133
+ }
94
134
  name = cfg.as
95
135
  }
96
136
  if (Object.hasOwn(out, name) && !sameValue(out[name], value)) name = key