@weasel-js/prefs 1.9.4 → 1.10.1

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.
@@ -0,0 +1,1180 @@
1
+ import { formatUnit, unitScale } from '@weasel-js/quantity';
2
+ import { isPlainObject, createEmitter } from '@weasel-js/registry';
3
+ import { hexToRgba, interpolateSrgb, rgbaToHex } from '@weasel-js/paint';
4
+ import { openRecords, openRecordsSync } from '@weasel-js/storage';
5
+
6
+ // src/alias.ts
7
+ function prefAliasTarget(alias, leafAt) {
8
+ const passed = /* @__PURE__ */ new Set();
9
+ let path = alias.of;
10
+ for (; ; ) {
11
+ if (passed.has(path)) return void 0;
12
+ passed.add(path);
13
+ const leaf = leafAt(path);
14
+ if (leaf === void 0) return void 0;
15
+ if (leaf.kind !== "alias") return { path, leaf };
16
+ path = leaf.of;
17
+ }
18
+ }
19
+ function prefAliasName(alias, target) {
20
+ if (alias.name !== "") return alias.name;
21
+ return target === void 0 ? "Alias" : target.name;
22
+ }
23
+ function prefAliasedLeaf(alias, target) {
24
+ return {
25
+ ...target,
26
+ name: prefAliasName(alias, target),
27
+ description: alias.description === "" ? target.description : alias.description
28
+ };
29
+ }
30
+ function isPrefLinearUnit(unit) {
31
+ return typeof unit.toDisplay !== "function";
32
+ }
33
+ var SHOWN = "shown";
34
+ function decimalPlaces(n) {
35
+ const text = String(n);
36
+ if (text.includes("e")) return void 0;
37
+ const dot = text.indexOf(".");
38
+ return dot === -1 ? 0 : text.length - dot - 1;
39
+ }
40
+ var DECIMAL_PLACES = 12;
41
+ function storedOf(shown, scale, offset) {
42
+ const raw = shown * scale + offset;
43
+ const [a, b, c] = [decimalPlaces(shown), decimalPlaces(scale), decimalPlaces(offset)];
44
+ if (a === void 0 || b === void 0 || c === void 0 || !Number.isFinite(raw)) return raw;
45
+ const places = Math.max(a + b, c);
46
+ return places > DECIMAL_PLACES ? raw : Number(raw.toFixed(places));
47
+ }
48
+ function resolvePrefUnit(unit) {
49
+ if (!isPrefLinearUnit(unit)) {
50
+ return { ...unit, format: unit.format ?? ((stored) => `${unit.toDisplay(stored)}${unit.suffix ?? ""}`) };
51
+ }
52
+ const { scale, offset = 0, precision, suffix, accepts } = unit;
53
+ const round = precision === void 0 ? void 0 : 10 ** precision;
54
+ const system = { base: "stored", units: { [SHOWN]: { factor: scale, offset } } };
55
+ return {
56
+ toDisplay: (stored) => {
57
+ const shown = (stored - offset) / scale;
58
+ return round === void 0 ? shown : Math.round(shown * round) / round;
59
+ },
60
+ fromDisplay: (shown) => storedOf(shown, scale, offset),
61
+ format: (stored) => `${formatUnit(stored, SHOWN, system, { precision: precision ?? 2, suffix: false })}${suffix ?? ""}`,
62
+ ...suffix === void 0 ? {} : { suffix },
63
+ ...accepts === void 0 ? {} : { accepts }
64
+ };
65
+ }
66
+ function prefUnit(system, display, opts) {
67
+ let self;
68
+ try {
69
+ self = unitScale(system, display);
70
+ } catch (e) {
71
+ throw new Error(`prefUnit: ${e instanceof Error ? e.message : String(e)}`);
72
+ }
73
+ const suffix = opts?.suffix ?? display;
74
+ const accepts = { [suffix]: 1 };
75
+ for (const name of Object.keys(system.units)) {
76
+ const other = unitScale(system, name);
77
+ const factor = other.factor / self.factor;
78
+ const offset = (other.offset - self.offset) / self.factor;
79
+ accepts[name] = offset === 0 ? factor : { factor, offset };
80
+ }
81
+ return {
82
+ suffix,
83
+ scale: self.factor,
84
+ ...self.offset === 0 ? {} : { offset: self.offset },
85
+ accepts,
86
+ ...opts?.precision === void 0 ? {} : { precision: opts.precision }
87
+ };
88
+ }
89
+
90
+ // src/helpers.ts
91
+ function prefDisplayBounds(p) {
92
+ if (!p.unit) return { min: p.min, max: p.max, step: p.step ?? 1 };
93
+ const { toDisplay } = resolvePrefUnit(p.unit);
94
+ const lo = p.min === void 0 ? void 0 : toDisplay(p.min);
95
+ const hi = p.max === void 0 ? void 0 : toDisplay(p.max);
96
+ const flipped = lo !== void 0 && hi !== void 0 && lo > hi;
97
+ const step = p.step === void 0 ? 1 : Math.abs(toDisplay(p.step) - toDisplay(0)) || p.step;
98
+ return { min: flipped ? hi : lo, max: flipped ? lo : hi, step };
99
+ }
100
+ function isPrefLeaf(node) {
101
+ return "kind" in node;
102
+ }
103
+ var VALUELESS = /* @__PURE__ */ new Set(["action", "alias", "label", "separator"]);
104
+ function prefHoldsValue(leaf) {
105
+ return !VALUELESS.has(leaf.kind);
106
+ }
107
+ function isPrefSection(node) {
108
+ return !isPrefLeaf(node) && "members" in node;
109
+ }
110
+ function prefSectionLeaves(members) {
111
+ return Object.entries(members).flatMap(
112
+ ([key, child]) => isPrefLeaf(child) ? [[key, child]] : prefSectionLeaves(child.members)
113
+ );
114
+ }
115
+ function prefLeafAt(schema, path) {
116
+ if (isPrefSection(schema)) return prefSectionLeaves(schema.members).find(([key]) => key === path)?.[1];
117
+ let node = schema;
118
+ for (const seg of path.split(".")) {
119
+ if (isPrefLeaf(node) || !Object.hasOwn(node.children, seg)) return void 0;
120
+ node = node.children[seg];
121
+ }
122
+ return isPrefLeaf(node) ? node : void 0;
123
+ }
124
+ function prefValueAtPath(values, path) {
125
+ let cur = values;
126
+ for (const seg of path.split(".")) {
127
+ if (cur == null || typeof cur !== "object" || !Object.hasOwn(cur, seg)) return void 0;
128
+ cur = cur[seg];
129
+ }
130
+ return cur;
131
+ }
132
+ function visibleFields(fields, keepEmpty) {
133
+ const out = {};
134
+ for (const [key, child] of Object.entries(fields)) {
135
+ if (isPrefLeaf(child)) {
136
+ const kept = visiblePrefSubtree(child, false, keepEmpty);
137
+ if (kept) out[key] = kept;
138
+ continue;
139
+ }
140
+ const members = visibleFields(child.members, keepEmpty);
141
+ if (Object.keys(members).length > 0 || keepEmpty) out[key] = { ...child, members };
142
+ }
143
+ return out;
144
+ }
145
+ function visiblePrefSubtree(node, showHidden, keepEmpty = false) {
146
+ if (isPrefLeaf(node)) {
147
+ if (node.hidden && !showHidden) return null;
148
+ if (showHidden || node.kind !== "object") return node;
149
+ return { ...node, children: visibleFields(node.children, keepEmpty) };
150
+ }
151
+ const group = node;
152
+ const children = {};
153
+ for (const [key, child] of Object.entries(group.children)) {
154
+ const kept = visiblePrefSubtree(child, showHidden, keepEmpty);
155
+ if (kept) children[key] = kept;
156
+ }
157
+ if (Object.keys(children).length === 0 && !keepEmpty) return null;
158
+ return { ...node, children };
159
+ }
160
+ var VERSION_RECORD = "$version";
161
+ function prefLeaves(schema) {
162
+ const out = /* @__PURE__ */ new Map();
163
+ const walk = (group, prefix) => {
164
+ for (const [key, child] of Object.entries(group.children)) {
165
+ if (key.includes(".")) {
166
+ throw new Error(`[prefs] schema key "${key}" contains "."; group keys are path segments`);
167
+ }
168
+ if (prefix === "" && key === VERSION_RECORD) {
169
+ throw new Error(`[prefs] schema key "${VERSION_RECORD}" is reserved`);
170
+ }
171
+ const path = prefix === "" ? key : `${prefix}.${key}`;
172
+ if (isPrefLeaf(child)) out.set(path, child);
173
+ else walk(child, path);
174
+ }
175
+ };
176
+ walk(schema, "");
177
+ return out;
178
+ }
179
+ function setOwnPrefValue(tree, key, value) {
180
+ Object.defineProperty(tree, key, { value, writable: true, enumerable: true, configurable: true });
181
+ }
182
+ function assignPrefValueAtPath(root, path, value) {
183
+ const parts = path.split(".");
184
+ let cursor = root;
185
+ for (const part of parts.slice(0, -1)) {
186
+ if (!Object.hasOwn(cursor, part) || cursor[part] == null) setOwnPrefValue(cursor, part, {});
187
+ cursor = cursor[part];
188
+ }
189
+ setOwnPrefValue(cursor, parts[parts.length - 1], value);
190
+ }
191
+ function flattenPrefValues(schema, tree) {
192
+ const out = [];
193
+ for (const path of prefLeaves(schema).keys()) {
194
+ const value = prefValueAtPath(tree, path);
195
+ if (value !== void 0) out.push([path, value]);
196
+ }
197
+ return out;
198
+ }
199
+ function prefLeafMatches(pref, path, query) {
200
+ if (pref.name.toLowerCase().includes(query)) return true;
201
+ if (pref.description?.toLowerCase().includes(query)) return true;
202
+ return path.toLowerCase().includes(query);
203
+ }
204
+ function filterPrefSubtree(node, query, path = "", over) {
205
+ const q = query.trim().toLowerCase();
206
+ if (q === "") return node;
207
+ const within = over ?? (isPrefLeaf(node) ? void 0 : node);
208
+ const find = (p) => within && prefLeafAt(within, p);
209
+ if (isPrefLeaf(node)) {
210
+ const target = node.kind === "alias" ? prefAliasTarget(node, find) : void 0;
211
+ const drawn = target ? prefAliasedLeaf(node, target.leaf) : node;
212
+ return prefLeafMatches(drawn, path, q) ? node : null;
213
+ }
214
+ const group = node;
215
+ if (group.name.toLowerCase().includes(q)) return node;
216
+ const children = {};
217
+ for (const [key, child] of Object.entries(group.children)) {
218
+ const kept = filterPrefSubtree(child, q, path === "" ? key : `${path}.${key}`, within);
219
+ if (kept) children[key] = kept;
220
+ }
221
+ if (Object.keys(children).length === 0) return null;
222
+ return { ...node, children };
223
+ }
224
+ var ORDERINGS = ["above", "below", "atLeast", "atMost"];
225
+ var TEST_KEYS = /* @__PURE__ */ new Set(["path", "from", "is", "isNot", "in", ...ORDERINGS]);
226
+ var isValue = (v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean";
227
+ function formOf(value) {
228
+ if (!isPlainObject(value)) return void 0;
229
+ const keys = Object.keys(value);
230
+ if (keys.length === 1 && (keys[0] === "all" || keys[0] === "any")) return Array.isArray(value[keys[0]]) ? keys[0] : void 0;
231
+ if (keys.length === 1 && keys[0] === "not") return "not";
232
+ if (typeof value.path !== "string" || !keys.every((key) => TEST_KEYS.has(key))) return void 0;
233
+ if (value.from !== void 0 && value.from !== "item") return void 0;
234
+ if (value.is !== void 0 && !isValue(value.is)) return void 0;
235
+ if (value.isNot !== void 0 && !isValue(value.isNot)) return void 0;
236
+ if (value.in !== void 0 && !(Array.isArray(value.in) && value.in.every(isValue))) return void 0;
237
+ return ORDERINGS.every((op) => value[op] === void 0 || typeof value[op] === "number") ? "test" : void 0;
238
+ }
239
+ function partsOf(condition, form) {
240
+ const held = condition;
241
+ return form === "test" ? [] : form === "not" ? [held.not] : held[form];
242
+ }
243
+ function isPrefCondition(value) {
244
+ const form = formOf(value);
245
+ return form !== void 0 && partsOf(value, form).every(isPrefCondition);
246
+ }
247
+ var same = (held, value) => value === null ? held == null : held === value;
248
+ function isSet(held) {
249
+ if (Array.isArray(held)) return held.length > 0;
250
+ if (isPlainObject(held)) return Object.keys(held).length > 0;
251
+ return Boolean(held);
252
+ }
253
+ function testHolds(test, held) {
254
+ const ops = Object.keys(test).filter((key) => key !== "path" && key !== "from" && test[key] !== void 0);
255
+ if (ops.length === 0) return isSet(held);
256
+ if (test.is !== void 0 && !same(held, test.is)) return false;
257
+ if (test.isNot !== void 0 && same(held, test.isNot)) return false;
258
+ if (test.in !== void 0 && !test.in.some((value) => same(held, value))) return false;
259
+ if (!ORDERINGS.some((op) => test[op] !== void 0)) return true;
260
+ if (typeof held !== "number") return false;
261
+ return (test.above === void 0 || held > test.above) && (test.below === void 0 || held < test.below) && (test.atLeast === void 0 || held >= test.atLeast) && (test.atMost === void 0 || held <= test.atMost);
262
+ }
263
+ function prefConditionHolds(condition, read) {
264
+ if ("all" in condition) return condition.all.every((part) => prefConditionHolds(part, read));
265
+ if ("any" in condition) return condition.any.some((part) => prefConditionHolds(part, read));
266
+ if ("not" in condition) return !prefConditionHolds(condition.not, read);
267
+ return testHolds(condition, read(condition.path, condition.from));
268
+ }
269
+ function prefIsShown(node, read) {
270
+ return node.showIf === void 0 || prefConditionHolds(node.showIf, read);
271
+ }
272
+ function prefIsEnabled(node, read) {
273
+ return node.enableIf === void 0 || prefConditionHolds(node.enableIf, read);
274
+ }
275
+ function prefConditionPaths(condition, from) {
276
+ const out = /* @__PURE__ */ new Set();
277
+ const walk = (c) => {
278
+ if ("all" in c) c.all.forEach(walk);
279
+ else if ("any" in c) c.any.forEach(walk);
280
+ else if ("not" in c) walk(c.not);
281
+ else if (c.from === from) out.add(c.path);
282
+ };
283
+ walk(condition);
284
+ return [...out];
285
+ }
286
+ function prefConditionReadsItem(condition) {
287
+ if ("all" in condition) return condition.all.some(prefConditionReadsItem);
288
+ if ("any" in condition) return condition.any.some(prefConditionReadsItem);
289
+ if ("not" in condition) return prefConditionReadsItem(condition.not);
290
+ return condition.from === "item";
291
+ }
292
+ function mapPrefConditionPaths(condition, move) {
293
+ const each = (parts) => {
294
+ const next = parts.map((part) => mapPrefConditionPaths(part, move));
295
+ return next.every((part, i) => part === parts[i]) ? parts : next;
296
+ };
297
+ if ("all" in condition) {
298
+ const all = each(condition.all);
299
+ return all === condition.all ? condition : { all };
300
+ }
301
+ if ("any" in condition) {
302
+ const any = each(condition.any);
303
+ return any === condition.any ? condition : { any };
304
+ }
305
+ if ("not" in condition) {
306
+ const not = mapPrefConditionPaths(condition.not, move);
307
+ return not === condition.not ? condition : { not };
308
+ }
309
+ const path = move(condition.path, condition.from);
310
+ return path === condition.path ? condition : { ...condition, path };
311
+ }
312
+
313
+ // src/schema.ts
314
+ var PREF_KINDS = {
315
+ number: true,
316
+ boolean: true,
317
+ string: true,
318
+ enum: true,
319
+ color: true,
320
+ paint: true,
321
+ object: true,
322
+ list: true,
323
+ map: true,
324
+ union: true,
325
+ field: true,
326
+ action: true,
327
+ alias: true,
328
+ label: true,
329
+ separator: true
330
+ };
331
+ function pairRowsOf(leaves) {
332
+ const rows = /* @__PURE__ */ new Map();
333
+ for (const [path, leaf] of leaves) {
334
+ if (!leaf.pair || rows.has(path)) continue;
335
+ const row = { key: path, label: leaf.pair.label ?? leaf.name };
336
+ rows.set(path, row);
337
+ const partners = typeof leaf.pair.with === "string" ? [leaf.pair.with] : leaf.pair.with;
338
+ for (const partner of partners) if (!rows.has(partner)) rows.set(partner, row);
339
+ }
340
+ return rows;
341
+ }
342
+ function isBuiltinPref(leaf) {
343
+ return Object.hasOwn(PREF_KINDS, leaf.kind);
344
+ }
345
+
346
+ // src/holding.ts
347
+ var UNSEEN = /* @__PURE__ */ Symbol("unseen");
348
+ var under = (at, key) => at === "" ? key : `${at}.${key}`;
349
+ function amongFields(fields, path, at) {
350
+ const exact = fields.find(([key]) => key === path);
351
+ if (exact) return { leaf: exact[1], ...at === void 0 ? {} : { within: at } };
352
+ const outer = fields.find(([key]) => path.startsWith(`${key}.`));
353
+ return outer && insideLeaf(outer[1], path.slice(outer[0].length + 1), under(at ?? "", outer[0]));
354
+ }
355
+ function insideLeaf(leaf, rest, at) {
356
+ if (leaf.kind === "object") return amongFields(prefSectionLeaves(leaf.children), rest, at);
357
+ if (leaf.kind === "union") {
358
+ const { tag, variants } = leaf;
359
+ if (rest === tag) {
360
+ const options = Object.keys(variants).map((value) => ({ value, label: variants[value].name }));
361
+ return { leaf: { kind: "enum", name: tag, description: "", default: void 0, options }, within: at };
362
+ }
363
+ for (const variant of Object.values(variants)) {
364
+ const found = amongFields(prefSectionLeaves(variant.children), rest, at);
365
+ if (found !== void 0) return found;
366
+ }
367
+ return void 0;
368
+ }
369
+ return !isBuiltinPref(leaf) || leaf.kind === "list" || leaf.kind === "map" || leaf.kind === "paint" ? UNSEEN : void 0;
370
+ }
371
+ function prefLeafHolding(schema, path) {
372
+ if (isPrefSection(schema)) return amongFields(prefSectionLeaves(schema.members), path, void 0);
373
+ const steps = path.split(".");
374
+ let node = schema;
375
+ for (let i = 0; i < steps.length; i++) {
376
+ if (isPrefLeaf(node)) return insideLeaf(node, steps.slice(i).join("."), steps.slice(0, i).join("."));
377
+ if (!Object.hasOwn(node.children, steps[i])) return void 0;
378
+ node = node.children[steps[i]];
379
+ }
380
+ return isPrefLeaf(node) ? { leaf: node } : void 0;
381
+ }
382
+ function prefSchemaHolding(schema, path, over) {
383
+ return over === void 0 || prefLeafHolding(schema, path) !== void 0 ? schema : over;
384
+ }
385
+ function prefLeafHoldingIn(item, path) {
386
+ return path === "" ? { leaf: item } : insideLeaf(item, path, "");
387
+ }
388
+
389
+ // src/conditionCheck.ts
390
+ var show = (value) => typeof value === "string" ? `'${value}'` : String(value);
391
+ function neverHolds(leaf, value) {
392
+ if (value === null || !isBuiltinPref(leaf)) return void 0;
393
+ switch (leaf.kind) {
394
+ case "number":
395
+ return typeof value === "number" ? void 0 : "it is a number";
396
+ case "boolean":
397
+ return typeof value === "boolean" ? void 0 : "it is a boolean";
398
+ case "string":
399
+ case "color":
400
+ case "field":
401
+ return typeof value === "string" ? void 0 : `it is a ${leaf.kind === "string" ? "string" : `${leaf.kind}, held as a string`}`;
402
+ case "enum": {
403
+ const { options } = leaf;
404
+ if (options.some((option) => option.value === value)) return void 0;
405
+ return `its options are ${options.map((option) => show(option.value)).join(", ")}`;
406
+ }
407
+ case "object":
408
+ return leaf.fromScalar ? void 0 : "it is an object";
409
+ default:
410
+ return `it is a ${leaf.kind}`;
411
+ }
412
+ }
413
+ function testProblems(test, schema, site, over) {
414
+ const { path } = test;
415
+ if (test.from === "item" && site.within === void 0) {
416
+ return [{ code: "no-item", path, problem: `"${path}" is read from an entry, and this is inside no list or map` }];
417
+ }
418
+ const found = leafProblems(test, schema, site, over);
419
+ if (test.in?.length !== 0) return found;
420
+ return [...found, { code: "never", path, problem: `"${path}" is one of nothing, which never holds` }];
421
+ }
422
+ function leafProblems(test, schema, site, over) {
423
+ const { path } = test;
424
+ const inEntry = test.from === "item";
425
+ const held = inEntry ? prefLeafHoldingIn(site.within.item, path) : prefLeafHolding(prefSchemaHolding(schema, path, over), path);
426
+ if (held === void 0) return [{ code: "no-leaf", path, problem: `no leaf holds a value at "${path}"${inEntry ? " in the entry" : ""}` }];
427
+ if (held === UNSEEN) return [];
428
+ const { leaf } = held;
429
+ const named = inEntry ? path === "" ? "the entry" : `the entry's "${path}"` : `"${path}"`;
430
+ if (!prefHoldsValue(leaf)) return [{ code: "no-value", path, problem: `${named} is a${leaf.kind === "separator" || leaf.kind === "label" ? "" : "n"} ${leaf.kind}, which holds no value` }];
431
+ const out = [];
432
+ const compared = [test.is, test.isNot, ...test.in ?? []].filter((value) => value !== void 0);
433
+ for (const value of compared) {
434
+ const why = neverHolds(leaf, value);
435
+ if (why !== void 0) out.push({ code: "value", path, problem: `${named} never holds ${show(value)}: ${why}` });
436
+ }
437
+ const ordering = ORDERINGS.find((op) => test[op] !== void 0);
438
+ if (ordering !== void 0 && isBuiltinPref(leaf) && leaf.kind !== "number") {
439
+ out.push({ code: "value", path, problem: `${named} is a${leaf.kind === "object" || leaf.kind === "enum" ? "n" : ""} ${leaf.kind}, and "${ordering}" compares numbers` });
440
+ }
441
+ return out;
442
+ }
443
+ function checkPrefCondition(condition, schema, site = {}, over) {
444
+ if (site.itemShowIf && site.within) {
445
+ const { kind } = site.within;
446
+ const problem = `a ${kind}'s item is drawn with each entry and is never hidden: put the condition on the entry's fields, or on the ${kind}`;
447
+ return [{ code: "item-show", problem }, ...checkPrefCondition(condition, schema, { within: site.within }, over)];
448
+ }
449
+ const form = formOf(condition);
450
+ if (form === void 0) {
451
+ const shown = typeof condition === "function" ? "a function" : JSON.stringify(condition);
452
+ return [{ code: "malformed", problem: `not a condition: ${shown}` }];
453
+ }
454
+ if (form === "test") return testProblems(condition, schema, site, over);
455
+ return partsOf(condition, form).flatMap((part) => checkPrefCondition(part, schema, site, over));
456
+ }
457
+ function checkPrefConditions(schema, over) {
458
+ const out = [];
459
+ const own = (node, at, within, isItem = false) => {
460
+ for (const field of ["showIf", "enableIf"]) {
461
+ if (node[field] === void 0) continue;
462
+ const site = { ...within ? { within } : {}, ...isItem && field === "showIf" ? { itemShowIf: true } : {} };
463
+ for (const problem of checkPrefCondition(node[field], schema, site, over)) out.push({ ...problem, at, field });
464
+ }
465
+ };
466
+ const under2 = (at, key) => at === "" ? key : `${at}.${key}`;
467
+ const leaf = (node, at, within, isItem = false) => {
468
+ own(node, at, within, isItem);
469
+ if (node.kind === "object") members(node.children, at, true, within);
470
+ else if (node.kind === "list" || node.kind === "map") {
471
+ const holder = node;
472
+ if (holder.item !== void 0) leaf(holder.item, under2(at, "item"), holder, true);
473
+ } else if (node.kind === "union") {
474
+ for (const [key, variant] of Object.entries(node.variants)) leaf(variant, under2(at, key), within);
475
+ }
476
+ };
477
+ const members = (held, at, nested, within) => {
478
+ for (const [key, child] of Object.entries(held)) {
479
+ const here = nested ? under2(at, key) : key;
480
+ if (isPrefLeaf(child)) leaf(child, here, within);
481
+ else {
482
+ own(child, here, within);
483
+ members(child.members, nested ? at : "", nested, within);
484
+ }
485
+ }
486
+ };
487
+ const group = (node, at) => {
488
+ own(node, at, void 0);
489
+ for (const [key, child] of Object.entries(node.children)) {
490
+ if (isPrefLeaf(child)) leaf(child, under2(at, key), void 0);
491
+ else group(child, under2(at, key));
492
+ }
493
+ };
494
+ if (isPrefSection(schema)) {
495
+ own(schema, "", void 0);
496
+ members(schema.members, "", false, void 0);
497
+ } else group(schema, "");
498
+ return out;
499
+ }
500
+ function isPrefLocus(value) {
501
+ if (!isPlainObject(value)) return false;
502
+ const keys = Object.keys(value);
503
+ return keys.length === 2 && typeof value.$by === "string" && isPlainObject(value.$of);
504
+ }
505
+ function selectionWeights(selection) {
506
+ return Object.fromEntries(Object.entries(selection).map(([path, option]) => [path, { [option]: 1 }]));
507
+ }
508
+ var member = (locus, option, absent) => Object.hasOwn(locus.$of, option) ? locus.$of[option] : absent;
509
+ function weighLocus(stored, weights, absent) {
510
+ const out = [];
511
+ const walk = (value, weight) => {
512
+ if (!isPrefLocus(value)) {
513
+ out.push({ value, weight });
514
+ return;
515
+ }
516
+ for (const [option, share] of Object.entries(weights[value.$by] ?? {})) {
517
+ if (share > 0) walk(member(value, option, absent), weight * share);
518
+ }
519
+ };
520
+ walk(stored, 1);
521
+ return out;
522
+ }
523
+ function memberAt(stored, selection, absent) {
524
+ let value = stored;
525
+ while (isPrefLocus(value)) {
526
+ const option = selection[value.$by];
527
+ if (option === void 0) return absent;
528
+ value = member(value, option, absent);
529
+ }
530
+ return value;
531
+ }
532
+ function writeMember(stored, selection, next) {
533
+ if (!isPrefLocus(stored)) return next;
534
+ const option = selection[stored.$by];
535
+ if (option === void 0) return stored;
536
+ return { $by: stored.$by, $of: { ...stored.$of, [option]: writeMember(stored.$of[option], selection, next) } };
537
+ }
538
+ function splitLocus(stored, by, options, order) {
539
+ const across = (value) => ({ $by: by, $of: Object.fromEntries(options.map((option) => [option, value])) });
540
+ if (!isPrefLocus(stored)) return across(stored);
541
+ if (stored.$by === by) return stored;
542
+ if (order.indexOf(stored.$by) > order.indexOf(by)) return across(stored);
543
+ return mapMembers(stored, (inner) => splitLocus(inner, by, options, order));
544
+ }
545
+ function joinLocus(stored, by, selection, absent) {
546
+ if (!isPrefLocus(stored)) return stored;
547
+ if (stored.$by !== by) return mapMembers(stored, (inner) => joinLocus(inner, by, selection, absent));
548
+ const option = selection[by];
549
+ return option === void 0 ? absent : member(stored, option, absent);
550
+ }
551
+ function mapMembers(locus, fn) {
552
+ let out;
553
+ for (const [option, value] of Object.entries(locus.$of)) {
554
+ const next = fn(value);
555
+ if (next !== value) (out ??= { ...locus.$of })[option] = next;
556
+ }
557
+ return out ? { $by: locus.$by, $of: out } : locus;
558
+ }
559
+ function mapLocusMembers(stored, fn) {
560
+ return isPrefLocus(stored) ? mapMembers(stored, (inner) => mapLocusMembers(inner, fn)) : fn(stored);
561
+ }
562
+ function locusSelectors(stored) {
563
+ const out = /* @__PURE__ */ new Set();
564
+ const walk = (value) => {
565
+ if (!isPrefLocus(value)) return;
566
+ out.add(value.$by);
567
+ for (const inner of Object.values(value.$of)) walk(inner);
568
+ };
569
+ walk(stored);
570
+ return out;
571
+ }
572
+ function repairLocus(stored, allowed, optionsOf, repairMember) {
573
+ const BAD = /* @__PURE__ */ Symbol("bad");
574
+ const walk = (value, met) => {
575
+ if (!isPrefLocus(value)) return repairMember(value);
576
+ const options = allowed.includes(value.$by) && !met.includes(value.$by) ? optionsOf(value.$by) : void 0;
577
+ if (!options) return BAD;
578
+ const of = {};
579
+ for (const option of options) {
580
+ if (!Object.hasOwn(value.$of, option)) continue;
581
+ const read2 = walk(value.$of[option], [...met, value.$by]);
582
+ if (read2 === BAD) return BAD;
583
+ of[option] = read2;
584
+ }
585
+ return { $by: value.$by, $of: of };
586
+ };
587
+ const read = walk(stored, []);
588
+ return read === BAD ? void 0 : read;
589
+ }
590
+ var heaviest = (members) => members.reduce((most, m) => m.weight > most.weight ? m : most).value;
591
+ var blendNumber = (members, leaf) => {
592
+ let sum = 0;
593
+ let total = 0;
594
+ for (const { value, weight } of members) {
595
+ if (typeof value !== "number" || !Number.isFinite(value)) return heaviest(members);
596
+ sum += value * weight;
597
+ total += weight;
598
+ }
599
+ return sum / total;
600
+ };
601
+ var blendColor = (members, leaf) => {
602
+ let mixed;
603
+ let total = 0;
604
+ for (const { value, weight } of members) {
605
+ let next;
606
+ try {
607
+ next = hexToRgba(value);
608
+ } catch {
609
+ return heaviest(members);
610
+ }
611
+ total += weight;
612
+ if (!mixed) {
613
+ mixed = next;
614
+ continue;
615
+ }
616
+ const t = weight / total;
617
+ const [r, g, b] = interpolateSrgb([mixed[0], mixed[1], mixed[2]], [next[0], next[1], next[2]], t, "oklch");
618
+ mixed = [r, g, b, mixed[3] + (next[3] - mixed[3]) * t];
619
+ }
620
+ return rgbaToHex(mixed);
621
+ };
622
+ var PREF_BLENDS = { number: blendNumber, color: blendColor };
623
+ function blendPref(leaf, members, blends) {
624
+ if (members.length === 0) return leaf.default;
625
+ if (members.length === 1) return members[0].value;
626
+ const blend = (blends && Object.hasOwn(blends, leaf.kind) ? blends[leaf.kind] : void 0) ?? (Object.hasOwn(PREF_BLENDS, leaf.kind) ? PREF_BLENDS[leaf.kind] : void 0) ?? heaviest;
627
+ return blend(members, leaf);
628
+ }
629
+
630
+ // src/splitCheck.ts
631
+ var refusal = (leaf) => {
632
+ if (!leaf) return ["no-leaf", "names no leaf"];
633
+ if (leaf.kind !== "enum") return ["not-enum", `names a ${leaf.kind} leaf, and only an enum has options to split by`];
634
+ const selector = leaf;
635
+ if (selector.encoding) return ["encoded", "names an enum with an encoding, whose stored value is not its option"];
636
+ if (selector.clearable) return ["clearable", "names a clearable enum, which may hold none of its options"];
637
+ if (selector.splitBy?.length) return ["split", "names a leaf that may itself be split"];
638
+ return void 0;
639
+ };
640
+ function prefSelectors(leaves) {
641
+ const out = /* @__PURE__ */ new Map();
642
+ for (const leaf of leaves.values()) {
643
+ for (const path of leaf.splitBy ?? []) {
644
+ const selector = leaves.get(path);
645
+ if (out.has(path) || refusal(selector)) continue;
646
+ out.set(path, selector.options.map((option) => option.value));
647
+ }
648
+ }
649
+ return out;
650
+ }
651
+ function checkPrefSplits(schema) {
652
+ const leaves = prefLeaves(schema);
653
+ const out = [];
654
+ for (const [at, leaf] of leaves) {
655
+ for (const path of locusSelectors(leaf.defaultLocus)) {
656
+ if (leaf.splitBy?.includes(path)) continue;
657
+ out.push({ code: "default", at, path, problem: `its defaultLocus is split by "${path}", which its splitBy does not list` });
658
+ }
659
+ if (!leaf.splitBy?.length) continue;
660
+ if (!prefHoldsValue(leaf)) {
661
+ out.push({ code: "valueless", at, problem: `a ${leaf.kind} leaf holds no value to split` });
662
+ continue;
663
+ }
664
+ leaf.splitBy.forEach((path, i) => {
665
+ if (leaf.splitBy.indexOf(path) !== i) {
666
+ out.push({ code: "twice", at, path, problem: `"${path}" is listed twice` });
667
+ return;
668
+ }
669
+ const why = refusal(leaves.get(path));
670
+ if (why) out.push({ code: why[0], at, path, problem: `"${path}" ${why[1]}` });
671
+ });
672
+ }
673
+ return out;
674
+ }
675
+
676
+ // src/union.ts
677
+ function prefVariantOf(leaf, value) {
678
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
679
+ const key = value[leaf.tag];
680
+ if (typeof key !== "string" || !Object.hasOwn(leaf.variants, key)) return void 0;
681
+ return [key, leaf.variants[key]];
682
+ }
683
+ function prefVariantDefault(leaf, key) {
684
+ const held = leaf.variants[key]?.default;
685
+ const fields = held !== null && typeof held === "object" ? held : {};
686
+ return { ...fields, [leaf.tag]: key };
687
+ }
688
+ function repairPrefValue(leaf, stored, validators) {
689
+ if (leaf.kind === "number") {
690
+ if (stored === "Infinity") stored = Infinity;
691
+ else if (stored === "-Infinity") stored = -Infinity;
692
+ }
693
+ const validate = validators?.[leaf.kind];
694
+ if (validate) {
695
+ try {
696
+ const out = validate(stored, leaf);
697
+ return out === void 0 ? leaf.default : out;
698
+ } catch {
699
+ return leaf.default;
700
+ }
701
+ }
702
+ switch (leaf.kind) {
703
+ case "number":
704
+ return repairNumber(leaf, stored);
705
+ case "boolean":
706
+ return leaf.encoding || typeof stored === "boolean" ? stored : leaf.default;
707
+ case "string":
708
+ case "color":
709
+ case "field":
710
+ return typeof stored === "string" ? stored : leaf.default;
711
+ case "enum": {
712
+ const e = leaf;
713
+ if (e.encoding) return stored;
714
+ return e.options.some((o) => o.value === stored) ? stored : leaf.default;
715
+ }
716
+ case "object":
717
+ if (isPlainObject(stored)) return repairFields(leaf, stored, validators);
718
+ return leaf.fromScalar ? stored : leaf.default;
719
+ case "map":
720
+ return repairMap(leaf, stored, validators);
721
+ case "union": {
722
+ const variant = prefVariantOf(leaf, stored);
723
+ return variant ? repairFields(variant[1], stored, validators) : leaf.default;
724
+ }
725
+ case "list":
726
+ return repairList(leaf, stored, validators);
727
+ default:
728
+ return stored;
729
+ }
730
+ }
731
+ function repairFields(leaf, stored, validators) {
732
+ let out;
733
+ for (const [key, field] of prefSectionLeaves(leaf.children)) {
734
+ if (!Object.hasOwn(stored, key) || stored[key] === void 0) continue;
735
+ const read = repairPrefValue(field, stored[key], validators);
736
+ if (read !== stored[key]) (out ??= { ...stored })[key] = read;
737
+ }
738
+ return out ?? stored;
739
+ }
740
+ function repairMap(leaf, stored, validators) {
741
+ if (!isPlainObject(stored)) return leaf.default;
742
+ const held = stored;
743
+ let out;
744
+ for (const [key, value] of Object.entries(held)) {
745
+ const read = repairPrefValue(leaf.item, value, validators);
746
+ if (read !== value) (out ??= { ...held })[key] = read;
747
+ }
748
+ return out ?? stored;
749
+ }
750
+ function repairList(leaf, stored, validators) {
751
+ if (!Array.isArray(stored)) return leaf.default;
752
+ if (leaf.minItems !== void 0 && stored.length < leaf.minItems) return leaf.default;
753
+ const kept = leaf.maxItems !== void 0 && stored.length > leaf.maxItems ? stored.slice(0, leaf.maxItems) : stored;
754
+ const read = kept.map((entry) => repairPrefValue(leaf.item, entry, validators));
755
+ return kept === stored && read.every((entry, i) => entry === stored[i]) ? stored : read;
756
+ }
757
+ function repairNumber(leaf, stored) {
758
+ if (typeof stored !== "number" || Number.isNaN(stored)) return leaf.default;
759
+ const { endless } = leaf;
760
+ if (stored === Infinity && (endless === "max" || endless === "both")) return stored;
761
+ if (stored === -Infinity && (endless === "min" || endless === "both")) return stored;
762
+ let v = stored;
763
+ if (leaf.min !== void 0 && v < leaf.min) v = leaf.min;
764
+ if (leaf.max !== void 0 && v > leaf.max) v = leaf.max;
765
+ return Number.isFinite(v) ? v : leaf.default;
766
+ }
767
+
768
+ // src/stamp.ts
769
+ var isVersion = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
770
+ function stampRecord(value, at) {
771
+ return { $at: at, value };
772
+ }
773
+ function isStampedRecord(record) {
774
+ if (record === null || typeof record !== "object") return false;
775
+ const keys = Object.keys(record);
776
+ return keys.length === 2 && Object.hasOwn(record, "value") && isVersion(record.$at);
777
+ }
778
+ function readRecord(record, unstamped) {
779
+ return isStampedRecord(record) ? { at: record.$at, value: record.value, stamped: true } : { at: unstamped, value: record, stamped: false };
780
+ }
781
+ function recordValue(record) {
782
+ return isStampedRecord(record) ? record.value : record;
783
+ }
784
+
785
+ // src/migrate.ts
786
+ var isVersion2 = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
787
+ function runPrefsMigrations(cache, migrations, { stampAll = true } = {}) {
788
+ const target = migrations.length;
789
+ const raw = cache.get(VERSION_RECORD);
790
+ if (cache.has(VERSION_RECORD) && !isVersion2(raw)) {
791
+ console.warn(
792
+ `[prefs] "${cache.prefix}" has an unreadable ${VERSION_RECORD} (${JSON.stringify(raw)}); opening read-only`
793
+ );
794
+ cache.stopWriting();
795
+ return false;
796
+ }
797
+ const stored = cache.has(VERSION_RECORD) ? raw : 0;
798
+ const held = cache.entries().filter(([name]) => name !== VERSION_RECORD).map(([name, record]) => ({ name, ...readRecord(record, Math.min(stored, target)) }));
799
+ const newest = Math.max(stored, ...held.map((r) => r.at));
800
+ if (newest > target) {
801
+ console.warn(
802
+ `[prefs] "${cache.prefix}" was written at version ${newest}, newer than this build's ${target}; opening read-only`
803
+ );
804
+ cache.stopWriting();
805
+ return false;
806
+ }
807
+ if (!cache.writable) return false;
808
+ const at = new Map(held.filter((r) => r.at < target).map((r) => [r.name, r.at]));
809
+ const records = new Map(held.filter((r) => at.has(r.name)).map((r) => [r.name, structuredClone(r.value)]));
810
+ for (let v = Math.min(target, ...at.values()); v < target; v++) {
811
+ const due = new Map([...records].filter(([name]) => at.get(name) <= v));
812
+ const before = [...due.keys()];
813
+ try {
814
+ migrations[v](due);
815
+ } catch (error) {
816
+ console.warn(
817
+ `[prefs] migration ${v} \u2192 ${v + 1} for "${cache.prefix}" threw; opening read-only on the unmigrated records`,
818
+ error
819
+ );
820
+ cache.stopWriting();
821
+ return false;
822
+ }
823
+ for (const name of before) if (!due.has(name)) records.delete(name);
824
+ for (const [name, value] of due) {
825
+ records.set(name, value);
826
+ at.set(name, v + 1);
827
+ }
828
+ }
829
+ let wrote = false;
830
+ for (const r of held) {
831
+ if (at.has(r.name)) {
832
+ if (!records.has(r.name)) cache.delete(r.name);
833
+ wrote = true;
834
+ } else if (stampAll && !r.stamped) {
835
+ cache.set(r.name, stampRecord(r.value, target));
836
+ wrote = true;
837
+ }
838
+ }
839
+ for (const [name, value] of records) {
840
+ if (value === void 0) cache.delete(name);
841
+ else cache.set(name, stampRecord(value, target));
842
+ }
843
+ if (stored !== target) {
844
+ cache.set(VERSION_RECORD, target);
845
+ wrote = true;
846
+ }
847
+ return wrote;
848
+ }
849
+ function watchPrefsVersion(cache, migrations) {
850
+ const target = migrations.length;
851
+ let versioned = cache.has(VERSION_RECORD);
852
+ let stopped = false;
853
+ const migrate = () => {
854
+ const cleared = versioned && !cache.has(VERSION_RECORD);
855
+ if (cleared) cache.set(VERSION_RECORD, target);
856
+ else if (!runPrefsMigrations(cache, migrations, { stampAll: false })) return;
857
+ versioned = true;
858
+ void cache.flush();
859
+ };
860
+ void cache.whenRead().then(() => {
861
+ if (!stopped && cache.writable) migrate();
862
+ });
863
+ const heard = (version) => {
864
+ if (!isVersion2(version)) {
865
+ console.warn(
866
+ `[prefs] "${cache.prefix}" has an unreadable ${VERSION_RECORD} (${JSON.stringify(version)}) from another writer; no longer persisting`
867
+ );
868
+ cache.stopWriting();
869
+ } else if (version > target) {
870
+ console.warn(
871
+ `[prefs] "${cache.prefix}" was migrated to version ${version} by another writer; this build knows ${target}; no longer persisting`
872
+ );
873
+ cache.stopWriting();
874
+ }
875
+ };
876
+ const stopOverruled = cache.onOverruled((changes) => {
877
+ for (const [name, value] of changes) if (name === VERSION_RECORD && value !== void 0) heard(value);
878
+ });
879
+ const stopChanges = cache.subscribe((changes) => {
880
+ const remote = changes.filter((change) => change.origin === "remote");
881
+ for (const change of remote) {
882
+ if (change.name === VERSION_RECORD && change.value !== void 0) heard(change.value);
883
+ }
884
+ if (remote.length > 0 && cache.writable) migrate();
885
+ versioned ||= cache.has(VERSION_RECORD);
886
+ });
887
+ return () => {
888
+ stopped = true;
889
+ stopOverruled();
890
+ stopChanges();
891
+ };
892
+ }
893
+
894
+ // src/snapshot.ts
895
+ function readStored(source, leaf, stored) {
896
+ const repair = (value) => repairPrefValue(leaf, value, source.validators);
897
+ if (!leaf.splitBy?.length || !isPrefLocus(stored)) return repair(stored);
898
+ const read = repairLocus(stored, leaf.splitBy, (path) => source.selectors.get(path), repair);
899
+ return read === void 0 ? leaf.default : read;
900
+ }
901
+ function resolveHeld(source, leaf, held, weights) {
902
+ return isPrefLocus(held) ? blendPref(leaf, weighLocus(held, weights, leaf.default), source.blends) : held;
903
+ }
904
+ function createSnapshots(source) {
905
+ const { leaves, cache, selectors } = source;
906
+ let snapshot = null;
907
+ let previous = null;
908
+ const unsetLoci = /* @__PURE__ */ new Map();
909
+ const unsetHeld = (path, leaf) => {
910
+ if (leaf.defaultLocus === void 0) return leaf.default;
911
+ if (!unsetLoci.has(path)) unsetLoci.set(path, readStored(source, leaf, leaf.defaultLocus));
912
+ return unsetLoci.get(path);
913
+ };
914
+ const build = () => {
915
+ const held = /* @__PURE__ */ new Map();
916
+ const raw = /* @__PURE__ */ new Map();
917
+ const unset = /* @__PURE__ */ new Set();
918
+ for (const [path, leaf] of leaves) {
919
+ if (!cache.has(path)) {
920
+ held.set(path, unsetHeld(path, leaf));
921
+ unset.add(path);
922
+ continue;
923
+ }
924
+ const record = cache.get(path);
925
+ raw.set(path, record);
926
+ const same2 = previous?.raw.has(path) && previous.raw.get(path) === record;
927
+ held.set(path, same2 ? previous.held.get(path) : readStored(source, leaf, recordValue(record)));
928
+ }
929
+ previous = { raw, held };
930
+ const selection = {};
931
+ for (const path of selectors.keys()) selection[path] = held.get(path);
932
+ const weights = selectionWeights(selection);
933
+ const byPath = /* @__PURE__ */ new Map();
934
+ const splitBy = /* @__PURE__ */ new Map();
935
+ const loci = /* @__PURE__ */ new Map();
936
+ const tree = {};
937
+ for (const [path, leaf] of leaves) {
938
+ const value = held.get(path);
939
+ if (isPrefLocus(value)) loci.set(path, value);
940
+ for (const selector of locusSelectors(value)) splitBy.set(selector, [...splitBy.get(selector) ?? [], path]);
941
+ const read = resolveHeld(source, leaf, value, weights);
942
+ byPath.set(path, read);
943
+ assignPrefValueAtPath(tree, path, read);
944
+ }
945
+ return { byPath, held, loci, selection, splitBy, tree, unset };
946
+ };
947
+ return {
948
+ current: () => snapshot ??= build(),
949
+ peek: () => snapshot,
950
+ drop: () => {
951
+ snapshot = null;
952
+ }
953
+ };
954
+ }
955
+ function weightsUnder(selectors, selection, given) {
956
+ const out = {};
957
+ for (const [path, options] of selectors) {
958
+ const own = Object.hasOwn(given, path) ? given[path] : void 0;
959
+ out[path] = own ? Object.fromEntries(options.filter((option) => (own[option] ?? 0) > 0).map((option) => [option, own[option]])) : { [selection[path]]: 1 };
960
+ }
961
+ return out;
962
+ }
963
+ function selectionAt(selectors, selection, at) {
964
+ const out = { ...selection };
965
+ for (const [path, option] of Object.entries(at)) {
966
+ if (selectors.get(path)?.includes(option)) out[path] = option;
967
+ }
968
+ return out;
969
+ }
970
+
971
+ // src/storeWrites.ts
972
+ var encodeInfinity = (leaf, value) => {
973
+ if (leaf.kind === "number") return value === Infinity || value === -Infinity ? String(value) : value;
974
+ if (leaf.kind === "list" && Array.isArray(value)) {
975
+ const { item } = leaf;
976
+ return value.map((entry) => encodeInfinity(item, entry));
977
+ }
978
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
979
+ const held = value;
980
+ if (leaf.kind === "map") {
981
+ const { item } = leaf;
982
+ return Object.fromEntries(Object.entries(held).map(([key, entry]) => [key, encodeInfinity(item, entry)]));
983
+ }
984
+ const fields = leaf.kind === "union" ? prefVariantOf(leaf, held)?.[1] : leaf;
985
+ if (fields?.kind !== "object") return value;
986
+ let out;
987
+ for (const [key, field] of prefSectionLeaves(fields.children)) {
988
+ if (!Object.hasOwn(held, key)) continue;
989
+ const encoded = encodeInfinity(field, held[key]);
990
+ if (encoded !== held[key]) (out ??= { ...held })[key] = encoded;
991
+ }
992
+ return out ?? value;
993
+ };
994
+ function prefWrites({ leaves, cache, selectors, version, selection, warnIfClosed }) {
995
+ const unsetLocus = (leaf) => {
996
+ if (leaf.defaultLocus === void 0 || !leaf.splitBy?.length) return void 0;
997
+ return repairLocus(leaf.defaultLocus, leaf.splitBy, (by) => selectors.get(by), (member2) => encodeInfinity(leaf, member2));
998
+ };
999
+ const rawAt = (path, leaf) => cache.has(path) ? recordValue(cache.get(path)) : unsetLocus(leaf) ?? encodeInfinity(leaf, leaf.default);
1000
+ const store = (path, stored) => {
1001
+ cache.set(path, stored === void 0 ? void 0 : stampRecord(stored, version));
1002
+ };
1003
+ const splitOf = (path, by) => {
1004
+ warnIfClosed(path);
1005
+ const leaf = leaves.get(path);
1006
+ const options = selectors.get(by);
1007
+ if (leaf?.splitBy?.includes(by) && options) return { leaf, options };
1008
+ console.warn(`[prefs] "${path}" cannot be split by "${by}"; ignoring`);
1009
+ return void 0;
1010
+ };
1011
+ const set = (path, value, write) => {
1012
+ warnIfClosed(path);
1013
+ const leaf = leaves.get(path);
1014
+ if (!leaf) {
1015
+ console.warn(`[prefs] no leaf at "${path}"; ignoring the write`);
1016
+ return;
1017
+ }
1018
+ if (leaf.splitBy?.length && isPrefLocus(value)) {
1019
+ store(path, mapLocusMembers(value, (member2) => encodeInfinity(leaf, member2)));
1020
+ return;
1021
+ }
1022
+ const stored = encodeInfinity(leaf, value);
1023
+ const had = cache.has(path) ? recordValue(cache.get(path)) : unsetLocus(leaf);
1024
+ const intoMember = stored !== void 0 && leaf.splitBy?.length && isPrefLocus(had);
1025
+ const under2 = write?.at ? selectionAt(selectors, selection(), write.at) : selection();
1026
+ store(path, intoMember ? writeMember(had, under2, stored) : stored);
1027
+ };
1028
+ const split = (path, by) => {
1029
+ const found = splitOf(path, by);
1030
+ if (!found) return;
1031
+ const had = rawAt(path, found.leaf);
1032
+ const next = splitLocus(had, by, found.options, found.leaf.splitBy);
1033
+ if (next !== had) store(path, next);
1034
+ };
1035
+ const join = (path, by) => {
1036
+ const found = splitOf(path, by);
1037
+ if (!found) return;
1038
+ const had = rawAt(path, found.leaf);
1039
+ const next = joinLocus(had, by, selection(), encodeInfinity(found.leaf, found.leaf.default));
1040
+ if (next !== had) store(path, next);
1041
+ };
1042
+ return { set, split, join };
1043
+ }
1044
+
1045
+ // src/store.ts
1046
+ function storedRecords(cache) {
1047
+ const tree = {};
1048
+ const branches = new WeakSet([tree]);
1049
+ for (const [name, record] of cache.entries()) {
1050
+ if (name === VERSION_RECORD) continue;
1051
+ const value = recordValue(record);
1052
+ const parts = name.split(".");
1053
+ let cursor = tree;
1054
+ for (const part of parts.slice(0, -1)) {
1055
+ const next = cursor[part];
1056
+ if (typeof next === "object" && next !== null && branches.has(next)) {
1057
+ cursor = next;
1058
+ } else {
1059
+ const branch = {};
1060
+ branches.add(branch);
1061
+ setOwnPrefValue(cursor, part, branch);
1062
+ cursor = branch;
1063
+ }
1064
+ }
1065
+ const last = parts[parts.length - 1];
1066
+ if (!branches.has(cursor[last])) setOwnPrefValue(cursor, last, value);
1067
+ }
1068
+ return tree;
1069
+ }
1070
+ function createPrefsStore(schema, cache, validators, onClose, version = 0, blends) {
1071
+ const leaves = new Map([...prefLeaves(schema)].filter(([, leaf]) => prefHoldsValue(leaf)));
1072
+ for (const { field, at, problem } of checkPrefConditions(schema)) {
1073
+ console.warn(`[prefs] ${field} of ${at === "" ? "the schema" : `"${at}"`}: ${problem}`);
1074
+ }
1075
+ for (const { at, problem } of checkPrefSplits(schema)) console.warn(`[prefs] splitBy of "${at}": ${problem}`);
1076
+ const source = { leaves, cache, selectors: prefSelectors(leaves), validators, blends };
1077
+ const { current, peek, drop } = createSnapshots(source);
1078
+ let storedTree = null;
1079
+ let changed = createEmitter();
1080
+ const stopCache = cache.subscribe((changes) => {
1081
+ const records = changes.filter((c) => c.name !== VERSION_RECORD);
1082
+ if (records.length === 0) return;
1083
+ storedTree = null;
1084
+ const mine = records.filter((c) => leaves.has(c.name));
1085
+ const before = peek();
1086
+ if (mine.length > 0) drop();
1087
+ const now = current();
1088
+ const origins = new Map(mine.map((c) => [c.name, c.origin]));
1089
+ for (const c of mine) {
1090
+ for (const path of [...before?.splitBy.get(c.name) ?? [], ...now.splitBy.get(c.name) ?? []]) {
1091
+ if (!origins.has(path)) origins.set(path, c.origin);
1092
+ }
1093
+ }
1094
+ changed.emit([...origins].map(([path, origin]) => ({ path, value: now.byPath.get(path), origin })));
1095
+ });
1096
+ let closed = false;
1097
+ let warned = false;
1098
+ const warnIfClosed = (path) => {
1099
+ if (!closed || warned) return;
1100
+ warned = true;
1101
+ console.warn(`[prefs] write to a closed store${path === void 0 ? "" : ` at "${path}"`}; it will not persist`);
1102
+ };
1103
+ return {
1104
+ schema,
1105
+ ...prefWrites({ leaves, cache, selectors: source.selectors, version, selection: () => current().selection, warnIfClosed }),
1106
+ get: (path, read) => {
1107
+ const now = current();
1108
+ if (!read?.weights) return now.byPath.get(path);
1109
+ const leaf = leaves.get(path);
1110
+ const weights = weightsUnder(source.selectors, now.selection, read.weights);
1111
+ return leaf ? resolveHeld(source, leaf, now.held.get(path), weights) : void 0;
1112
+ },
1113
+ locus: (path) => {
1114
+ const held = current().held.get(path);
1115
+ return isPrefLocus(held) ? held : void 0;
1116
+ },
1117
+ loci: () => current().loci,
1118
+ reset: (path) => {
1119
+ warnIfClosed(path);
1120
+ for (const p of leaves.keys()) {
1121
+ if (path === void 0 || p === path || p.startsWith(`${path}.`)) cache.delete(p);
1122
+ }
1123
+ },
1124
+ isSet: (path) => cache.has(path),
1125
+ values: (read) => {
1126
+ const now = current();
1127
+ if (!read?.at) return now.tree;
1128
+ const selection = selectionAt(source.selectors, now.selection, read.at);
1129
+ const weights = selectionWeights(selection);
1130
+ const tree = {};
1131
+ for (const [path, leaf] of leaves) {
1132
+ const set = source.selectors.has(path) ? selection[path] : resolveHeld(source, leaf, now.held.get(path), weights);
1133
+ assignPrefValueAtPath(tree, path, set);
1134
+ }
1135
+ return tree;
1136
+ },
1137
+ unset: () => current().unset,
1138
+ stored: () => storedTree ??= storedRecords(cache),
1139
+ subscribe: (listener) => changed.subscribe(listener),
1140
+ get writable() {
1141
+ return !closed && cache.writable;
1142
+ },
1143
+ get closed() {
1144
+ return closed;
1145
+ },
1146
+ read: () => cache.read(),
1147
+ whenRead: () => cache.whenRead(),
1148
+ flush: () => cache.flush(),
1149
+ close: async () => {
1150
+ closed = true;
1151
+ onClose?.();
1152
+ stopCache();
1153
+ changed = createEmitter();
1154
+ await cache.close();
1155
+ }
1156
+ };
1157
+ }
1158
+
1159
+ // src/open.ts
1160
+ async function openPrefs(schema, options) {
1161
+ const cache = await openRecords({ storage: options.storage, prefix: options.prefix });
1162
+ const stop = watch(cache, options);
1163
+ if (runPrefsMigrations(cache, options.migrations ?? [])) await cache.flush();
1164
+ return createPrefsStore(schema, cache, options.validators, stop, options.migrations?.length ?? 0, options.blends);
1165
+ }
1166
+ function openPrefsSync(schema, options) {
1167
+ const { cache, stop } = openPrefsCacheSync(options);
1168
+ return createPrefsStore(schema, cache, options.validators, stop, options.migrations?.length ?? 0, options.blends);
1169
+ }
1170
+ function openPrefsCacheSync(options) {
1171
+ const cache = openRecordsSync({ storage: options.storage, prefix: options.prefix });
1172
+ const stop = watch(cache, options);
1173
+ if (runPrefsMigrations(cache, options.migrations ?? [])) void cache.flush();
1174
+ return { cache, stop };
1175
+ }
1176
+ var watch = (cache, options) => watchPrefsVersion(cache, options.migrations ?? []);
1177
+
1178
+ export { PREF_BLENDS, PREF_KINDS, UNSEEN, VERSION_RECORD, checkPrefCondition, checkPrefConditions, checkPrefSplits, createPrefsStore, filterPrefSubtree, flattenPrefValues, isBuiltinPref, isPrefCondition, isPrefLeaf, isPrefLinearUnit, isPrefLocus, isPrefSection, locusSelectors, mapPrefConditionPaths, memberAt, openPrefs, openPrefsCacheSync, openPrefsSync, pairRowsOf, prefAliasName, prefAliasTarget, prefAliasedLeaf, prefConditionHolds, prefConditionPaths, prefConditionReadsItem, prefDisplayBounds, prefHoldsValue, prefIsEnabled, prefIsShown, prefLeafAt, prefLeafHolding, prefLeafHoldingIn, prefLeaves, prefSchemaHolding, prefSectionLeaves, prefSelectors, prefUnit, prefValueAtPath, prefVariantDefault, prefVariantOf, repairPrefValue, resolvePrefUnit, visiblePrefSubtree };
1179
+ //# sourceMappingURL=chunk-ASSX3JHY.js.map
1180
+ //# sourceMappingURL=chunk-ASSX3JHY.js.map