@uniweb/core 0.14.0 → 0.15.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 +4 -3
- package/src/datastore.js +24 -1
- package/src/index.js +23 -7
- package/src/schemas.js +17 -2
- package/src/uniweb.js +31 -13
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
8
8
|
"./base-path": "./src/base-path.js",
|
|
9
|
-
"./collection-address": "./src/
|
|
9
|
+
"./collection-address": "./src/query-address.js",
|
|
10
|
+
"./query-address": "./src/query-address.js",
|
|
10
11
|
"./data-paths": "./src/data-paths.js",
|
|
11
12
|
"./detail-url": "./src/detail-url.js",
|
|
12
13
|
"./fetch-config": "./src/fetch-config.js",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"vitest": "^4.1.7"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@uniweb/semantic-parser": "^1.
|
|
45
|
+
"@uniweb/semantic-parser": "^1.4.0",
|
|
45
46
|
"@uniweb/theming": "^0.1.15"
|
|
46
47
|
},
|
|
47
48
|
"scripts": {
|
package/src/datastore.js
CHANGED
|
@@ -62,7 +62,7 @@ export default class DataStore {
|
|
|
62
62
|
*
|
|
63
63
|
* Two forms:
|
|
64
64
|
* - `subscribe(fn)` — fires after every successful `set()` (all keys).
|
|
65
|
-
* - `subscribe(key, fn)` — fires only when `set(key, ...)` is called.
|
|
65
|
+
* - `subscribe(key, fn)` — fires only when `set(key, ...)` or `delete(key)` is called.
|
|
66
66
|
*
|
|
67
67
|
* The global form is useful for debugging / blanket observers. The keyed
|
|
68
68
|
* form is what Layer-3 kit hooks (`useFetched`, `useCacheEntry`) use so
|
|
@@ -131,6 +131,29 @@ export default class DataStore {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Drop one entry, and any in-flight record for the same key.
|
|
136
|
+
*
|
|
137
|
+
* Fires the key's subscribers (and the global ones) so an observer re-reads
|
|
138
|
+
* and sees the absence, exactly as it would see a write. Exists for the case
|
|
139
|
+
* `clear()` is too blunt for: one consumer's entries must leave memory — a
|
|
140
|
+
* viewer's records at sign-out — while everything else stays warm.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} key
|
|
143
|
+
* @returns {boolean} true if an entry was removed
|
|
144
|
+
*/
|
|
145
|
+
delete(key) {
|
|
146
|
+
const had = this._cache.delete(key)
|
|
147
|
+
this._inflight.delete(key)
|
|
148
|
+
if (!had) return false
|
|
149
|
+
for (const fn of this._listeners) fn()
|
|
150
|
+
const keyed = this._keyedListeners.get(key)
|
|
151
|
+
if (keyed) {
|
|
152
|
+
for (const fn of keyed) fn()
|
|
153
|
+
}
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
/**
|
|
135
158
|
* In-flight fetch registry — used by the dispatcher to dedup concurrent
|
|
136
159
|
* requests and collect abort signals so the underlying fetch is cancelled
|
package/src/index.js
CHANGED
|
@@ -20,15 +20,21 @@ export { default as ObservableState } from './observable-state.js'
|
|
|
20
20
|
|
|
21
21
|
// Utilities
|
|
22
22
|
export { substitutePlaceholders } from './substitute-placeholders.js'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
// ⛔ `resolveQueryAddress` / `resolveRecordAddressPattern` are NOT re-exported
|
|
24
|
+
// here. They are read by `./fetch-config.js` and by nothing else in any repo
|
|
25
|
+
// (measured 2026-09-01 over every .js/.jsx/.mjs/.ts/.tsx file in the
|
|
26
|
+
// workspace). A consumer that needs them imports `@uniweb/core/query-address`,
|
|
27
|
+
// which is a declared subpath and a zero-dependency leaf — the same reach
|
|
28
|
+
// `route-match` and `section-id` already have. Re-exporting an internal from
|
|
29
|
+
// the package entry is not free: the import-map bridge enumerates this file's
|
|
30
|
+
// surface and emits a live named re-export for every one, so nothing here can
|
|
31
|
+
// ever be tree-shaken on the hosted lane.
|
|
27
32
|
export { resolveFetchConfigs } from './fetch-config.js'
|
|
28
33
|
export { buildDetailConfig } from './detail-url.js'
|
|
34
|
+
// `isWildcardLanguages` is likewise internal — `./locale-config.js` reads it
|
|
35
|
+
// and nothing else does. Same subpath escape hatch: `@uniweb/core/locale-config`.
|
|
29
36
|
export {
|
|
30
37
|
normalizeLanguageList,
|
|
31
|
-
isWildcardLanguages,
|
|
32
38
|
resolveDefaultLocale,
|
|
33
39
|
resolvePublishableLocales,
|
|
34
40
|
validateLanguageConfig
|
|
@@ -42,8 +48,18 @@ export {
|
|
|
42
48
|
isDataUrl
|
|
43
49
|
} from './data-paths.js'
|
|
44
50
|
export { evaluate as evaluateWhere, match as matchWhere } from './where.js'
|
|
45
|
-
export { isRichSchema
|
|
46
|
-
|
|
51
|
+
export { isRichSchema } from './schemas.js'
|
|
52
|
+
// ⛔ `normalizeSchema` MOVED to `@uniweb/schemas/editor-form` (2026-09-01) —
|
|
53
|
+
// it is editor-only and never called `isRichSchema`. Still exported here for
|
|
54
|
+
// ONE more release so `frontend` (a `workspace:*` pin, so a removal is live
|
|
55
|
+
// at commit time) can migrate its single import. Delete both lines then.
|
|
56
|
+
export { normalizeSchema } from './schemas.js'
|
|
57
|
+
// ⛔ `Tracker` is NOT on the package entry. It is a FEATURE, not part of the
|
|
58
|
+
// object graph this package exists to define, and putting it here made every
|
|
59
|
+
// consumer of core carry 1,576 gzip of it -- press, unipress, `@uniweb/api`
|
|
60
|
+
// and every SSR isolate included -- for a class only the browser runtime ever
|
|
61
|
+
// wires. Its one importer already reaches it correctly, through the
|
|
62
|
+
// `@uniweb/core/tracker` subpath (`runtime/src/wire-foundation.js`).
|
|
47
63
|
// Also available as the zero-dependency leaves `@uniweb/core/services` and
|
|
48
64
|
// `@uniweb/core/base-path` — which is how `@uniweb/runtime` reaches them,
|
|
49
65
|
// since it must not pull the package root into an SSR/Worker bundle.
|
package/src/schemas.js
CHANGED
|
@@ -37,6 +37,18 @@ export function isRichSchema(schema) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
+
* ⛔ **DEPRECATED — MOVED to `@uniweb/schemas/editor-form` (2026-09-01).**
|
|
41
|
+
* This copy stays only so `frontend`, which pins core as `workspace:*` and
|
|
42
|
+
* would break at commit time, can migrate its one import. Delete it once that
|
|
43
|
+
* lands; nothing else in any repo reads it.
|
|
44
|
+
*
|
|
45
|
+
* ⭐ **It never belonged here.** It does not call `isRichSchema` and never did
|
|
46
|
+
* — it re-inlines the same three checks — so nothing but adjacency held it in
|
|
47
|
+
* this file. The two answer different questions for different audiences:
|
|
48
|
+
* `isRichSchema` is a render-time dispatch predicate (`prepare-props.js`,
|
|
49
|
+
* `runtime-schema.js`) and stays; this is read only by an editor, while core
|
|
50
|
+
* ships to every site in every lane and is not tree-shaken on the hosted one.
|
|
51
|
+
*
|
|
40
52
|
* Normalize any authored `data:` schema shape to the rich form the editor
|
|
41
53
|
* renders, or null when it is not a single form at all.
|
|
42
54
|
*
|
|
@@ -48,8 +60,11 @@ export function isRichSchema(schema) {
|
|
|
48
60
|
* { fields: { name: spec } } a RESOLVED NAMED REF → FALSE
|
|
49
61
|
* { name: spec } meta.js inline field map → false
|
|
50
62
|
*
|
|
51
|
-
* The middle row is the important one and the reason this helper
|
|
52
|
-
*
|
|
63
|
+
* The middle row is the important one and the reason this helper exists at all
|
|
64
|
+
* (it once read "the reason it is in `core` rather than in the editor" — that
|
|
65
|
+
* was the wrong conclusion from a right observation: the gap is real, closing
|
|
66
|
+
* it in one shared place is right, and `@uniweb/schemas` is that place).
|
|
67
|
+
* A named ref (`'@/article'`, `'@std/person'`) is the
|
|
53
68
|
* FIRST authoring form the docs show, and `validateAndNormalizeSchema` in the
|
|
54
69
|
* build resolves it to `{ fields: <MAP> }` — a map, not an array. So filtering
|
|
55
70
|
* with `isRichSchema` discards not merely "simple" schemas but the primary
|
package/src/uniweb.js
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import Website from './website.js'
|
|
15
|
-
import Tracker from './tracker.js'
|
|
16
15
|
|
|
17
16
|
export default class Uniweb {
|
|
18
17
|
/**
|
|
@@ -72,21 +71,40 @@ export default class Uniweb {
|
|
|
72
71
|
// Populated by prerender before rendering, read synchronously by Icon.
|
|
73
72
|
this.iconCache = new Map()
|
|
74
73
|
|
|
75
|
-
// Site tracking — one event stream.
|
|
74
|
+
// Site tracking — one event stream. The runtime installs it in L2
|
|
75
|
+
// (`wire-foundation.js` → `wireTracker`), and only for a site that declares
|
|
76
|
+
// a destination. It cannot be configured here even though `activeWebsite`
|
|
77
|
+
// already exists two lines up, because the address is resolved against
|
|
78
|
+
// `website.basePath` and **that is still `''` at this point** —
|
|
79
|
+
// `setBasePath()` runs later, from the runtime. Resolving here would
|
|
80
|
+
// silently drop the base prefix on every subdirectory deployment.
|
|
76
81
|
//
|
|
77
|
-
// ⛔
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
82
|
+
// ⛔ **Core does not construct a Tracker, and must not start.** Until
|
|
83
|
+
// 2026-09-01 this line was `new Tracker()` — a working no-op, justified as
|
|
84
|
+
// letting `uniweb.tracking.track(…)` run unguarded in every lane. Two
|
|
85
|
+
// things were wrong with that. It put a *feature* in the object graph,
|
|
86
|
+
// which is not what this class is for; and the guard it bought was never
|
|
87
|
+
// taken up — every reader in the workspace already optional-chains
|
|
88
|
+
// (`block.js`'s `track()`, and the runtime's `usePageView`,
|
|
89
|
+
// `useSectionViews`, `useOutboundClicks`, `useSectionClicks`), so the
|
|
90
|
+
// invariant lived only in the comment. Cost of carrying it: 1,576 gzip in
|
|
91
|
+
// core's chunk, which press, unipress, `@uniweb/api` and every SSR isolate
|
|
92
|
+
// paid for a class they never wire.
|
|
83
93
|
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// site. Same slot-declared-here-so-seal-permits-assignment pattern as
|
|
94
|
+
// ⇒ **Read it as nullable.** `globalThis.uniweb?.tracking?.track(…)` is
|
|
95
|
+
// the call shape, and it is what every existing caller already writes.
|
|
96
|
+
// Same slot-declared-here-so-seal-permits-assignment pattern as
|
|
88
97
|
// `defaultInsets` above.
|
|
89
|
-
this.tracking =
|
|
98
|
+
this.tracking = null
|
|
99
|
+
|
|
100
|
+
// Reserved for `@uniweb/api` — the one client instance per page. That
|
|
101
|
+
// package is bundled into each foundation, so a page with a primary
|
|
102
|
+
// foundation and extensions holds several copies of its code; the first to
|
|
103
|
+
// need a client creates one and parks it here, and every other copy adopts
|
|
104
|
+
// it instead of creating another. Core implements nothing on it: declared
|
|
105
|
+
// null so the seal permits the assignment, nothing more. Same pattern as
|
|
106
|
+
// `defaultInsets` above.
|
|
107
|
+
this.api = null
|
|
90
108
|
|
|
91
109
|
Object.seal(this)
|
|
92
110
|
}
|