@uniweb/core 0.17.0 → 0.18.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,6 +9,7 @@
9
9
  "./collection-address": "./src/query-address.js",
10
10
  "./query-address": "./src/query-address.js",
11
11
  "./data-paths": "./src/data-paths.js",
12
+ "./datastore": "./src/datastore.js",
12
13
  "./detail-url": "./src/detail-url.js",
13
14
  "./fetch-config": "./src/fetch-config.js",
14
15
  "./icon-corpus": "./src/icon-corpus.js",
package/src/datastore.js CHANGED
@@ -35,12 +35,21 @@
35
35
  * @returns {string} A stable JSON string usable as a cache-Map key
36
36
  */
37
37
  export function deriveCacheKey(request) {
38
- const { path, url, endpoint, schema, transform } = request || {}
38
+ // `as` is the binding key; `schema` is the name it had until 2026-09-02 and
39
+ // still arrives on every payload published before then. Normalised HERE so one
40
+ // logical request hashes to one key whichever spelling reached us — a consumer
41
+ // deriving a key must not get two entries for the same fetch.
42
+ const { path, url, endpoint, transform } = request || {}
43
+ const as = request?.as ?? request?.schema
39
44
  const method = request?.method && request.method.toUpperCase() !== 'GET'
40
45
  ? request.method.toUpperCase()
41
46
  : undefined
42
47
  const body = method === 'POST' ? request?.body : undefined
43
- return JSON.stringify({ path, url, endpoint, schema, transform, method, body })
48
+ // ⚠️ The field NAME is part of the hash, so renaming it moves every key ONCE.
49
+ // In-memory stores repopulate; a consumer with a persistent cache takes one
50
+ // cold pass. Chosen over hashing under the old name, which would have hidden
51
+ // the rename inside the one function whose job is to be canonical.
52
+ return JSON.stringify({ path, url, endpoint, as, transform, method, body })
44
53
  }
45
54
 
46
55
  export default class DataStore {
@@ -15,6 +15,13 @@
15
15
  */
16
16
 
17
17
  import { isFetchRefinement, resolveFetchConfigs } from './fetch-config.js'
18
+
19
+ /**
20
+ * A fetch config's binding key — the `content.data.<key>` a component reads.
21
+ * `as` is the name; `schema` is what it was called until 2026-09-02 and still
22
+ * arrives on any payload published before then. See `fetch-config.js`.
23
+ */
24
+ const bindingKeyOf = (cfg) => cfg?.as ?? cfg?.schema
18
25
  import { buildDetailConfig } from './detail-url.js'
19
26
 
20
27
  /**
@@ -201,7 +208,7 @@ export default class EntityStore {
201
208
  // collecting all cascade matches.
202
209
  if (requested === null && block.fetch) {
203
210
  const blockFetchList = Array.isArray(block.fetch) ? block.fetch : [block.fetch]
204
- const schemas = blockFetchList.filter((cfg) => cfg.schema).map((cfg) => cfg.schema)
211
+ const schemas = blockFetchList.filter(bindingKeyOf).map(bindingKeyOf)
205
212
  if (schemas.length > 0) requested = schemas
206
213
  }
207
214
 
@@ -299,7 +306,7 @@ export default class EntityStore {
299
306
  let requested = this._getRequestedSchemas(meta)
300
307
  if (requested === null && block.fetch) {
301
308
  const blockFetchList = Array.isArray(block.fetch) ? block.fetch : [block.fetch]
302
- const schemas = blockFetchList.filter((cfg) => cfg.schema).map((cfg) => cfg.schema)
309
+ const schemas = blockFetchList.filter(bindingKeyOf).map(bindingKeyOf)
303
310
  if (schemas.length > 0) requested = schemas
304
311
  }
305
312
  if (requested === null) return { data: null }
@@ -131,7 +131,7 @@ function applyDeferredDetail(cfg, queries, records) {
131
131
  // (`{ path: … }`) has no query at all, and its schema — inferred from the path —
132
132
  // is the only key there is. Two shapes, two answers; the deleted one had one
133
133
  // shape and pretended otherwise.
134
- const queryName = cfg.query || cfg.schema
134
+ const queryName = cfg.query || bindingKey(cfg)
135
135
  if (!queryName || !queries) return cfg
136
136
  const collConfig = queries[queryName]
137
137
  if (!collConfig || typeof collConfig !== 'object') return cfg
@@ -182,6 +182,28 @@ function resolveQuerySource(cfg, records) {
182
182
  return { ...cfg, path: queryDataUrl(cfg.query) }
183
183
  }
184
184
 
185
+ /**
186
+ * The binding key of a fetch config — the `content.data.<key>` a component reads.
187
+ *
188
+ * ⭐ **`as` is the name; `schema` is what it was called until 2026-09-02.** The old
189
+ * spelling still arrives on every payload published before then and on any seed
190
+ * built against an older release, so this is not a deprecation window — it is a
191
+ * permanent reader of stored data. *(Renaming it was not cosmetic: `schema`
192
+ * already means the MODEL REF one record over, on a `queries` declaration, and
193
+ * one word for two things is what let a binding-key override silently break
194
+ * detail resolution.)*
195
+ *
196
+ * ⛔ Do not "simplify" this to `cfg.as`. The `??` here is earned — it spans
197
+ * stored payloads we cannot rewrite — unlike the one deleted from
198
+ * `applyDeferredDetail`, which spanned two producers we control.
199
+ *
200
+ * @param {Object} cfg
201
+ * @returns {string|undefined}
202
+ */
203
+ function bindingKey(cfg) {
204
+ return cfg?.as ?? cfg?.schema
205
+ }
206
+
185
207
  /**
186
208
  * Resolve the applicable fetch configs from an ordered list of sources.
187
209
  *
@@ -224,14 +246,15 @@ export function resolveFetchConfigs(sources, options = {}) {
224
246
  if (!source) continue
225
247
  const configList = Array.isArray(source) ? source : [source]
226
248
  for (const cfg of configList) {
227
- if (!cfg?.schema) continue
228
- if (configs.has(cfg.schema)) continue
229
- if (!collectAll && !schemas.includes(cfg.schema)) continue
249
+ const key = bindingKey(cfg)
250
+ if (!key) continue
251
+ if (configs.has(key)) continue
252
+ if (!collectAll && !schemas.includes(key)) continue
230
253
  // Address first: localization and deferred-detail both key on `path`,
231
254
  // which a query ref does not have until this runs.
232
255
  const sourced = resolveQuerySource(cfg, records)
233
256
  const localized = localizeConfig(sourced, locale, defaultLocale)
234
- configs.set(cfg.schema, applyDeferredDetail(localized, queries, records))
257
+ configs.set(key, applyDeferredDetail(localized, queries, records))
235
258
  }
236
259
  }
237
260