@yahoo/uds-create-config 4.34.3 → 4.34.4
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/dist/entities/system/StyleProperty.js +17 -5
- package/dist/framework/Config.js +2 -2
- package/dist/framework/ref-graph.d.ts +11 -5
- package/dist/framework/ref-graph.js +38 -15
- package/dist/framework/snapshot.d.ts +13 -1
- package/dist/framework/snapshot.js +61 -21
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { isRef, kindOf, pathOf, ref, refSchema, sourceOf, splitRef } from "../..
|
|
|
2
2
|
import { defineEntity } from "../../framework/defineEntity.js";
|
|
3
3
|
import { cssValueIssue, isCssProperty } from "../../css/values.js";
|
|
4
4
|
import { camelCase } from "./style-bag.js";
|
|
5
|
+
import { createSliceMemo } from "../../framework/memo.js";
|
|
5
6
|
import { isIdentifier } from "../../framework/utils/identifier.js";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
//#region src/entities/system/StyleProperty.ts
|
|
@@ -289,11 +290,9 @@ function tokenRefOf(entry) {
|
|
|
289
290
|
* `spacing/2` — since that is one value with two spellings, not two values.
|
|
290
291
|
*/
|
|
291
292
|
const copiesNoToken = (config) => (ctx) => {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (value !== void 0 && !tokens.has(value)) tokens.set(value, token.path);
|
|
296
|
-
}
|
|
293
|
+
const literal = (entry) => typeof entry === "object" && entry !== null && "value" in entry;
|
|
294
|
+
if (!ctx.value.some(literal)) return;
|
|
295
|
+
const tokens = tokenPathsByValue(config);
|
|
297
296
|
ctx.value.forEach((entry, index) => {
|
|
298
297
|
if (typeof entry !== "object" || entry === null || !("value" in entry)) return;
|
|
299
298
|
const value = comparable(entry.value);
|
|
@@ -308,6 +307,19 @@ const copiesNoToken = (config) => (ctx) => {
|
|
|
308
307
|
});
|
|
309
308
|
});
|
|
310
309
|
};
|
|
310
|
+
/** Each token value, as `comparable` spells it, and the first token path that holds it. Kept per token
|
|
311
|
+
* slice, since a batch of style-property writes would otherwise list every token once per write. */
|
|
312
|
+
const tokenPathsByValue = createSliceMemo({
|
|
313
|
+
kinds: ["token"],
|
|
314
|
+
compute: (config) => {
|
|
315
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
316
|
+
for (const token of config.list("token")) {
|
|
317
|
+
const value = comparable(token.body.value);
|
|
318
|
+
if (value !== void 0 && !tokens.has(value)) tokens.set(value, token.path);
|
|
319
|
+
}
|
|
320
|
+
return tokens;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
311
323
|
/** A check refusing a ref to a token or group nothing declares: it offers no values, and the prop
|
|
312
324
|
* paints nothing. */
|
|
313
325
|
const namesDeclaredTokens = (config) => (ctx) => {
|
package/dist/framework/Config.js
CHANGED
|
@@ -1301,9 +1301,9 @@ var Config = class Config {
|
|
|
1301
1301
|
}
|
|
1302
1302
|
}
|
|
1303
1303
|
const land = (checked) => {
|
|
1304
|
-
let config = this;
|
|
1304
|
+
let config = checked ? this : this.derive({ snapshot: this.state.snapshot.drafting() });
|
|
1305
1305
|
for (const patch of patches) config = config.derive({ snapshot: config.computeApply(config.state.snapshot, patch, "write", checked) });
|
|
1306
|
-
return config;
|
|
1306
|
+
return checked ? config : config.derive({ snapshot: config.state.snapshot.sealed() });
|
|
1307
1307
|
};
|
|
1308
1308
|
const settle = () => {
|
|
1309
1309
|
try {
|
|
@@ -56,16 +56,22 @@ interface RefIndexChange {
|
|
|
56
56
|
}
|
|
57
57
|
/** Index `entries` in both directions. Pure — callers memoize (see `Snapshot.refs`). */
|
|
58
58
|
declare function buildRefIndex(entries: readonly RefGraphEntry[], linkKinds?: readonly string[], singletonKinds?: readonly string[]): RefIndex;
|
|
59
|
+
/** The maps and sets a batch of snapshot writes has copied, which its later writes change in place. */
|
|
60
|
+
interface SnapshotDraft {
|
|
61
|
+
readonly owned: WeakSet<object>;
|
|
62
|
+
}
|
|
59
63
|
/**
|
|
60
64
|
* Re-index only the entities in `changes`, returning a new index that shares nothing mutable with
|
|
61
|
-
* the old one. The top-level maps
|
|
62
|
-
*
|
|
63
|
-
*
|
|
65
|
+
* the old one. The top-level maps and each edge set a change touches are copied once, so the deep
|
|
66
|
+
* `collectRefs` walk runs for the changed entities alone rather than for the whole config. Once means
|
|
67
|
+
* once per call, or once per batch inside a `draft`, which then writes its own copies in place.
|
|
68
|
+
* Changes whose entities kept exactly the refs they had hand back the same index, since copying the
|
|
69
|
+
* top-level maps costs a walk of every node in the config.
|
|
64
70
|
*/
|
|
65
|
-
declare function updateRefIndex(index: RefIndex, changes: readonly RefIndexChange[], linkKinds?: readonly string[], singletonKinds?: readonly string[]): RefIndex;
|
|
71
|
+
declare function updateRefIndex(index: RefIndex, changes: readonly RefIndexChange[], linkKinds?: readonly string[], singletonKinds?: readonly string[], draft?: SnapshotDraft): RefIndex;
|
|
66
72
|
/** The query facade over a built index — the shape every consumer asks questions through. */
|
|
67
73
|
declare function refGraphOf(index: RefIndex): RefGraph;
|
|
68
74
|
/** Build and query in one step — for a caller with no snapshot to carry the index on. */
|
|
69
75
|
declare function buildRefGraph(entries: readonly RefGraphEntry[]): RefGraph;
|
|
70
76
|
//#endregion
|
|
71
|
-
export { RefGraph, RefGraphEntry, RefIndex, RefIndexChange, RefTarget, TraverseOptions, buildRefGraph, buildRefIndex, refGraphOf, updateRefIndex };
|
|
77
|
+
export { RefGraph, RefGraphEntry, RefIndex, RefIndexChange, RefTarget, SnapshotDraft, TraverseOptions, buildRefGraph, buildRefIndex, refGraphOf, updateRefIndex };
|
|
@@ -59,32 +59,47 @@ function buildRefIndex(entries, linkKinds = [], singletonKinds = []) {
|
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* Re-index only the entities in `changes`, returning a new index that shares nothing mutable with
|
|
62
|
-
* the old one. The top-level maps
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* the old one. The top-level maps and each edge set a change touches are copied once, so the deep
|
|
63
|
+
* `collectRefs` walk runs for the changed entities alone rather than for the whole config. Once means
|
|
64
|
+
* once per call, or once per batch inside a `draft`, which then writes its own copies in place.
|
|
65
|
+
* Changes whose entities kept exactly the refs they had hand back the same index, since copying the
|
|
66
|
+
* top-level maps costs a walk of every node in the config.
|
|
65
67
|
*/
|
|
66
|
-
function updateRefIndex(index, changes, linkKinds = [], singletonKinds = []) {
|
|
68
|
+
function updateRefIndex(index, changes, linkKinds = [], singletonKinds = [], draft) {
|
|
67
69
|
if (changes.length === 0) return index;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const next = changes.map(({ ref, data, extra }) => ({
|
|
71
|
+
ref,
|
|
72
|
+
targets: data === void 0 ? void 0 : edgesOf(ref, data, extra, linkKinds, singletonKinds)
|
|
73
|
+
}));
|
|
74
|
+
if (next.every(({ ref, targets }) => sameEdges(index.out.get(ref), targets))) return index;
|
|
75
|
+
const owned = draft?.owned ?? /* @__PURE__ */ new WeakSet();
|
|
76
|
+
const writable = (map) => {
|
|
77
|
+
if (owned.has(map)) return map;
|
|
78
|
+
const copy = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const [node, set] of map) copy.set(node, set);
|
|
80
|
+
owned.add(copy);
|
|
81
|
+
return copy;
|
|
82
|
+
};
|
|
83
|
+
const out = writable(index.out);
|
|
84
|
+
const incoming = writable(index.incoming);
|
|
85
|
+
/** Copy-on-write a single edge set, so sets shared with an earlier index are never mutated. A target
|
|
86
|
+
* many changes touch would otherwise be copied once per change. */
|
|
73
87
|
const mutable = (map, node) => {
|
|
74
|
-
const
|
|
88
|
+
const current = map.get(node);
|
|
89
|
+
if (current && owned.has(current)) return current;
|
|
90
|
+
const next = new Set(current);
|
|
91
|
+
owned.add(next);
|
|
75
92
|
map.set(node, next);
|
|
76
93
|
return next;
|
|
77
94
|
};
|
|
78
|
-
for (const { ref,
|
|
79
|
-
for (const target of
|
|
95
|
+
for (const { ref, targets } of next) {
|
|
96
|
+
for (const target of out.get(ref) ?? []) {
|
|
80
97
|
const back = mutable(incoming, target);
|
|
81
98
|
back.delete(ref);
|
|
82
99
|
if (back.size === 0) incoming.delete(target);
|
|
83
100
|
}
|
|
84
101
|
out.delete(ref);
|
|
85
|
-
if (
|
|
86
|
-
const targets = edgesOf(ref, data, extra, linkKinds, singletonKinds);
|
|
87
|
-
if (targets.size === 0) continue;
|
|
102
|
+
if (!targets || targets.size === 0) continue;
|
|
88
103
|
out.set(ref, targets);
|
|
89
104
|
for (const target of targets) mutable(incoming, target).add(ref);
|
|
90
105
|
}
|
|
@@ -93,6 +108,14 @@ function updateRefIndex(index, changes, linkKinds = [], singletonKinds = []) {
|
|
|
93
108
|
incoming
|
|
94
109
|
};
|
|
95
110
|
}
|
|
111
|
+
/** Whether an entity's edges are the ones it had, where no entry and an empty set both mean none. */
|
|
112
|
+
function sameEdges(before, after) {
|
|
113
|
+
const size = before?.size ?? 0;
|
|
114
|
+
if (size !== (after?.size ?? 0)) return false;
|
|
115
|
+
if (size === 0) return true;
|
|
116
|
+
for (const target of before ?? []) if (!after?.has(target)) return false;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
96
119
|
/** The query facade over a built index — the shape every consumer asks questions through. */
|
|
97
120
|
function refGraphOf(index) {
|
|
98
121
|
const { out, incoming } = index;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Entity } from "./Entity.js";
|
|
2
|
-
import { RefGraph } from "./ref-graph.js";
|
|
2
|
+
import { RefGraph, SnapshotDraft } from "./ref-graph.js";
|
|
3
3
|
import { EntityKind } from "./registry.js";
|
|
4
4
|
|
|
5
5
|
//#region src/framework/snapshot.d.ts
|
|
@@ -44,6 +44,18 @@ type EntityChange = {
|
|
|
44
44
|
declare class Snapshot {
|
|
45
45
|
#private;
|
|
46
46
|
private constructor();
|
|
47
|
+
/**
|
|
48
|
+
* The same snapshot, starting a batch of writes. A store or index map is copied the first time the
|
|
49
|
+
* batch writes it and written in place after that, so a batch of N writes copies each once instead
|
|
50
|
+
* of N times. Every write still returns a new snapshot, with a new slice for each kind it wrote.
|
|
51
|
+
*
|
|
52
|
+
* The batch owns its copies, so the snapshots it produced share them: reading one written earlier
|
|
53
|
+
* in the batch shows the batch's later writes. The caller keeps only the last and {@link sealed}s it.
|
|
54
|
+
* Nothing from before the batch is ever written.
|
|
55
|
+
*/
|
|
56
|
+
drafting(): Snapshot;
|
|
57
|
+
/** The same snapshot with its batch closed, so a later write copies again. */
|
|
58
|
+
sealed(): Snapshot;
|
|
47
59
|
/**
|
|
48
60
|
* The same snapshot, reading name-borne edges through `readers` from here on.
|
|
49
61
|
*
|
|
@@ -17,14 +17,36 @@ const emptySlice = () => Object.freeze({
|
|
|
17
17
|
groups: /* @__PURE__ */ new Map(),
|
|
18
18
|
overrides: /* @__PURE__ */ new Map()
|
|
19
19
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
const STORE_OF = {
|
|
21
|
+
item: "items",
|
|
22
|
+
group: "groups",
|
|
23
|
+
override: "overrides"
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* One kind's slice with `changes` applied. Only the stores a change touches are replaced. Outside a
|
|
27
|
+
* draft each is copied once per call; inside one, a store the draft already copied is written in place,
|
|
28
|
+
* so a batch copies each store once rather than once per patch.
|
|
29
|
+
*/
|
|
30
|
+
function changedSlice({ current, changes, draft }) {
|
|
31
|
+
const stores = {
|
|
32
|
+
items: current.items,
|
|
33
|
+
groups: current.groups,
|
|
34
|
+
overrides: current.overrides
|
|
35
|
+
};
|
|
36
|
+
const written = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const change of changes) {
|
|
38
|
+
const name = STORE_OF[change.target];
|
|
39
|
+
if (!written.has(name) && !draft?.owned.has(stores[name])) {
|
|
40
|
+
const copy = new Map(stores[name]);
|
|
41
|
+
draft?.owned.add(copy);
|
|
42
|
+
stores[name] = copy;
|
|
43
|
+
}
|
|
44
|
+
written.add(name);
|
|
45
|
+
const store = stores[name];
|
|
46
|
+
if (change.value === void 0) store.delete(change.path);
|
|
47
|
+
else store.set(change.path, change.value);
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({ ...stores });
|
|
28
50
|
}
|
|
29
51
|
var Snapshot = class Snapshot {
|
|
30
52
|
#nameEdges;
|
|
@@ -41,14 +63,36 @@ var Snapshot = class Snapshot {
|
|
|
41
63
|
* independent configs — a global singleton would make two unrelated configs that both lack a kind
|
|
42
64
|
* collide on one memo key, and each would serve the other's cached result. */
|
|
43
65
|
#empties;
|
|
66
|
+
/** The batch this snapshot was written in, when it was — see {@link drafting}. */
|
|
67
|
+
#draft;
|
|
44
68
|
#graph;
|
|
45
|
-
constructor(slices, refIndex, empties, nameEdges = /* @__PURE__ */ new Map(), linkKinds = [], singletonKinds = []) {
|
|
69
|
+
constructor(slices, refIndex, empties, nameEdges = /* @__PURE__ */ new Map(), linkKinds = [], singletonKinds = [], draft = void 0) {
|
|
46
70
|
this.#slices = slices;
|
|
47
71
|
this.#refIndex = refIndex;
|
|
48
72
|
this.#empties = empties;
|
|
49
73
|
this.#nameEdges = nameEdges;
|
|
50
74
|
this.#linkKinds = linkKinds;
|
|
51
75
|
this.#singletonKinds = singletonKinds;
|
|
76
|
+
this.#draft = draft;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The same snapshot, starting a batch of writes. A store or index map is copied the first time the
|
|
80
|
+
* batch writes it and written in place after that, so a batch of N writes copies each once instead
|
|
81
|
+
* of N times. Every write still returns a new snapshot, with a new slice for each kind it wrote.
|
|
82
|
+
*
|
|
83
|
+
* The batch owns its copies, so the snapshots it produced share them: reading one written earlier
|
|
84
|
+
* in the batch shows the batch's later writes. The caller keeps only the last and {@link sealed}s it.
|
|
85
|
+
* Nothing from before the batch is ever written.
|
|
86
|
+
*/
|
|
87
|
+
drafting() {
|
|
88
|
+
return this.#with({ draft: { owned: /* @__PURE__ */ new WeakSet() } });
|
|
89
|
+
}
|
|
90
|
+
/** The same snapshot with its batch closed, so a later write copies again. */
|
|
91
|
+
sealed() {
|
|
92
|
+
return this.#draft ? this.#with({ draft: void 0 }) : this;
|
|
93
|
+
}
|
|
94
|
+
#with({ draft }) {
|
|
95
|
+
return new Snapshot(this.#slices, this.#refIndex, this.#empties, this.#nameEdges, this.#linkKinds, this.#singletonKinds, draft);
|
|
52
96
|
}
|
|
53
97
|
/**
|
|
54
98
|
* The same snapshot, reading name-borne edges through `readers` from here on.
|
|
@@ -145,17 +189,12 @@ var Snapshot = class Snapshot {
|
|
|
145
189
|
else byKind.set(change.kind, [change]);
|
|
146
190
|
}
|
|
147
191
|
const slices = new Map(this.#slices);
|
|
148
|
-
for (const [kind, kindChanges] of byKind) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
for (const change of kindChanges) applyToSlice(draft, change);
|
|
156
|
-
slices.set(kind, Object.freeze(draft));
|
|
157
|
-
}
|
|
158
|
-
return new Snapshot(slices, updateRefIndex(this.#refIndex, refChanges(changes, this.#nameEdges), this.#linkKinds, this.#singletonKinds), this.#empties, this.#nameEdges, this.#linkKinds, this.#singletonKinds);
|
|
192
|
+
for (const [kind, kindChanges] of byKind) slices.set(kind, changedSlice({
|
|
193
|
+
current: this.slice(kind),
|
|
194
|
+
changes: kindChanges,
|
|
195
|
+
draft: this.#draft
|
|
196
|
+
}));
|
|
197
|
+
return new Snapshot(slices, updateRefIndex(this.#refIndex, refChanges(changes, this.#nameEdges), this.#linkKinds, this.#singletonKinds, this.#draft), this.#empties, this.#nameEdges, this.#linkKinds, this.#singletonKinds, this.#draft);
|
|
159
198
|
}
|
|
160
199
|
/**
|
|
161
200
|
* Move an entity to a new path, KEEPING ITS POSITION in the record.
|
|
@@ -176,6 +215,7 @@ var Snapshot = class Snapshot {
|
|
|
176
215
|
const rebuilt = /* @__PURE__ */ new Map();
|
|
177
216
|
for (const [path, entity] of store) if (path === from) rebuilt.set(to, value);
|
|
178
217
|
else if (path !== to) rebuilt.set(path, entity);
|
|
218
|
+
this.#draft?.owned.add(rebuilt);
|
|
179
219
|
const slices = new Map(this.#slices);
|
|
180
220
|
slices.set(kind, Object.freeze({
|
|
181
221
|
...current,
|
|
@@ -188,7 +228,7 @@ var Snapshot = class Snapshot {
|
|
|
188
228
|
ref: `${kind}:${to}`,
|
|
189
229
|
data: value.toJSON(),
|
|
190
230
|
extra: this.#nameEdges.get(kind)?.(value.toJSON())
|
|
191
|
-
}], this.#linkKinds, this.#singletonKinds), this.#empties, this.#nameEdges, this.#linkKinds, this.#singletonKinds);
|
|
231
|
+
}], this.#linkKinds, this.#singletonKinds, this.#draft), this.#empties, this.#nameEdges, this.#linkKinds, this.#singletonKinds, this.#draft);
|
|
192
232
|
}
|
|
193
233
|
/**
|
|
194
234
|
* Rearrange a record's keys — the operation that MOVES something, since order is key order.
|