@weasel-js/prefs 0.0.0-stage → 1.9.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/LICENSE +21 -0
- package/README.md +29 -2
- package/dist/index.d.ts +153 -0
- package/dist/index.js +637 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +20 -0
- package/dist/react.js +34 -0
- package/dist/react.js.map +1 -0
- package/dist/store-CNFnA_3c.d.ts +572 -0
- package/package.json +58 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { openRecords, openRecordsSync } from '@weasel-js/storage';
|
|
2
|
+
import { isPlainObject } from '@weasel-js/registry';
|
|
3
|
+
import { unitScale, formatUnit } from '@weasel-js/quantity';
|
|
4
|
+
|
|
5
|
+
// src/alias.ts
|
|
6
|
+
function prefAliasTarget(alias, leafAt) {
|
|
7
|
+
const passed = /* @__PURE__ */ new Set();
|
|
8
|
+
let path = alias.of;
|
|
9
|
+
for (; ; ) {
|
|
10
|
+
if (passed.has(path)) return void 0;
|
|
11
|
+
passed.add(path);
|
|
12
|
+
const leaf = leafAt(path);
|
|
13
|
+
if (leaf === void 0) return void 0;
|
|
14
|
+
if (leaf.kind !== "alias") return { path, leaf };
|
|
15
|
+
path = leaf.of;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function prefAliasedLeaf(alias, target) {
|
|
19
|
+
return {
|
|
20
|
+
...target,
|
|
21
|
+
name: alias.name === "" ? target.name : alias.name,
|
|
22
|
+
description: alias.description === "" ? target.description : alias.description
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/helpers.ts
|
|
27
|
+
function prefDisplayBounds(p) {
|
|
28
|
+
if (!p.unit) return { min: p.min, max: p.max, step: p.step ?? 1 };
|
|
29
|
+
const { toDisplay } = p.unit;
|
|
30
|
+
const lo = p.min === void 0 ? void 0 : toDisplay(p.min);
|
|
31
|
+
const hi = p.max === void 0 ? void 0 : toDisplay(p.max);
|
|
32
|
+
const flipped = lo !== void 0 && hi !== void 0 && lo > hi;
|
|
33
|
+
const step = p.step === void 0 ? 1 : Math.abs(toDisplay(p.step) - toDisplay(0)) || p.step;
|
|
34
|
+
return { min: flipped ? hi : lo, max: flipped ? lo : hi, step };
|
|
35
|
+
}
|
|
36
|
+
function isPrefLeaf(node) {
|
|
37
|
+
return "kind" in node;
|
|
38
|
+
}
|
|
39
|
+
var VALUELESS = /* @__PURE__ */ new Set(["action", "alias", "label", "separator"]);
|
|
40
|
+
function prefHoldsValue(leaf) {
|
|
41
|
+
return !VALUELESS.has(leaf.kind);
|
|
42
|
+
}
|
|
43
|
+
function isPrefSection(node) {
|
|
44
|
+
return !isPrefLeaf(node) && "members" in node;
|
|
45
|
+
}
|
|
46
|
+
function prefSectionLeaves(members) {
|
|
47
|
+
return Object.entries(members).flatMap(
|
|
48
|
+
([key, child]) => isPrefLeaf(child) ? [[key, child]] : prefSectionLeaves(child.members)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function prefLeafAt(schema, path) {
|
|
52
|
+
if (isPrefSection(schema)) return prefSectionLeaves(schema.members).find(([key]) => key === path)?.[1];
|
|
53
|
+
let node = schema;
|
|
54
|
+
for (const seg of path.split(".")) {
|
|
55
|
+
if (isPrefLeaf(node) || !Object.hasOwn(node.children, seg)) return void 0;
|
|
56
|
+
node = node.children[seg];
|
|
57
|
+
}
|
|
58
|
+
return isPrefLeaf(node) ? node : void 0;
|
|
59
|
+
}
|
|
60
|
+
function prefValueAtPath(values, path) {
|
|
61
|
+
let cur = values;
|
|
62
|
+
for (const seg of path.split(".")) {
|
|
63
|
+
if (cur == null || typeof cur !== "object" || !Object.hasOwn(cur, seg)) return void 0;
|
|
64
|
+
cur = cur[seg];
|
|
65
|
+
}
|
|
66
|
+
return cur;
|
|
67
|
+
}
|
|
68
|
+
function visibleFields(fields, keepEmpty) {
|
|
69
|
+
const out = {};
|
|
70
|
+
for (const [key, child] of Object.entries(fields)) {
|
|
71
|
+
if (isPrefLeaf(child)) {
|
|
72
|
+
const kept = visiblePrefSubtree(child, false, keepEmpty);
|
|
73
|
+
if (kept) out[key] = kept;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const members = visibleFields(child.members, keepEmpty);
|
|
77
|
+
if (Object.keys(members).length > 0 || keepEmpty) out[key] = { ...child, members };
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function visiblePrefSubtree(node, showHidden, keepEmpty = false) {
|
|
82
|
+
if (isPrefLeaf(node)) {
|
|
83
|
+
if (node.hidden && !showHidden) return null;
|
|
84
|
+
if (showHidden || node.kind !== "object") return node;
|
|
85
|
+
return { ...node, children: visibleFields(node.children, keepEmpty) };
|
|
86
|
+
}
|
|
87
|
+
const group = node;
|
|
88
|
+
const children = {};
|
|
89
|
+
for (const [key, child] of Object.entries(group.children)) {
|
|
90
|
+
const kept = visiblePrefSubtree(child, showHidden, keepEmpty);
|
|
91
|
+
if (kept) children[key] = kept;
|
|
92
|
+
}
|
|
93
|
+
if (Object.keys(children).length === 0 && !keepEmpty) return null;
|
|
94
|
+
return { ...node, children };
|
|
95
|
+
}
|
|
96
|
+
var VERSION_RECORD = "$version";
|
|
97
|
+
function prefLeaves(schema) {
|
|
98
|
+
const out = /* @__PURE__ */ new Map();
|
|
99
|
+
const walk = (group, prefix) => {
|
|
100
|
+
for (const [key, child] of Object.entries(group.children)) {
|
|
101
|
+
if (key.includes(".")) {
|
|
102
|
+
throw new Error(`[prefs] schema key "${key}" contains "."; group keys are path segments`);
|
|
103
|
+
}
|
|
104
|
+
if (prefix === "" && key === VERSION_RECORD) {
|
|
105
|
+
throw new Error(`[prefs] schema key "${VERSION_RECORD}" is reserved`);
|
|
106
|
+
}
|
|
107
|
+
const path = prefix === "" ? key : `${prefix}.${key}`;
|
|
108
|
+
if (isPrefLeaf(child)) out.set(path, child);
|
|
109
|
+
else walk(child, path);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
walk(schema, "");
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function setOwnPrefValue(tree, key, value) {
|
|
116
|
+
Object.defineProperty(tree, key, { value, writable: true, enumerable: true, configurable: true });
|
|
117
|
+
}
|
|
118
|
+
function assignPrefValueAtPath(root, path, value) {
|
|
119
|
+
const parts = path.split(".");
|
|
120
|
+
let cursor = root;
|
|
121
|
+
for (const part of parts.slice(0, -1)) {
|
|
122
|
+
if (!Object.hasOwn(cursor, part) || cursor[part] == null) setOwnPrefValue(cursor, part, {});
|
|
123
|
+
cursor = cursor[part];
|
|
124
|
+
}
|
|
125
|
+
setOwnPrefValue(cursor, parts[parts.length - 1], value);
|
|
126
|
+
}
|
|
127
|
+
function flattenPrefValues(schema, tree) {
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const path of prefLeaves(schema).keys()) {
|
|
130
|
+
const value = prefValueAtPath(tree, path);
|
|
131
|
+
if (value !== void 0) out.push([path, value]);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
function prefLeafMatches(pref, path, query) {
|
|
136
|
+
if (pref.name.toLowerCase().includes(query)) return true;
|
|
137
|
+
if (pref.description?.toLowerCase().includes(query)) return true;
|
|
138
|
+
return path.toLowerCase().includes(query);
|
|
139
|
+
}
|
|
140
|
+
function filterPrefSubtree(node, query, path = "", leafAt) {
|
|
141
|
+
const q = query.trim().toLowerCase();
|
|
142
|
+
if (q === "") return node;
|
|
143
|
+
const find = leafAt ?? ((p) => isPrefLeaf(node) ? void 0 : prefLeafAt(node, p));
|
|
144
|
+
if (isPrefLeaf(node)) {
|
|
145
|
+
const target = node.kind === "alias" ? prefAliasTarget(node, find) : void 0;
|
|
146
|
+
const drawn = target ? prefAliasedLeaf(node, target.leaf) : node;
|
|
147
|
+
return prefLeafMatches(drawn, path, q) ? node : null;
|
|
148
|
+
}
|
|
149
|
+
const group = node;
|
|
150
|
+
if (group.name.toLowerCase().includes(q)) return node;
|
|
151
|
+
const children = {};
|
|
152
|
+
for (const [key, child] of Object.entries(group.children)) {
|
|
153
|
+
const kept = filterPrefSubtree(child, q, path === "" ? key : `${path}.${key}`, find);
|
|
154
|
+
if (kept) children[key] = kept;
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(children).length === 0) return null;
|
|
157
|
+
return { ...node, children };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/object.ts
|
|
161
|
+
var isObject = (v) => typeof v === "object" && v !== null;
|
|
162
|
+
function prefObjectBase(pref, held) {
|
|
163
|
+
if (isObject(held)) return held;
|
|
164
|
+
return pref.fromScalar?.(held) ?? (isObject(pref.default) ? pref.default : {});
|
|
165
|
+
}
|
|
166
|
+
function prefObjectWithField(pref, held, key, value) {
|
|
167
|
+
const base = prefObjectBase(pref, held);
|
|
168
|
+
if (value !== void 0) return { ...base, [key]: value };
|
|
169
|
+
const { [key]: _dropped, ...rest } = base;
|
|
170
|
+
return rest;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/union.ts
|
|
174
|
+
function prefVariantOf(leaf, value) {
|
|
175
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
176
|
+
const key = value[leaf.tag];
|
|
177
|
+
if (typeof key !== "string" || !Object.hasOwn(leaf.variants, key)) return void 0;
|
|
178
|
+
return [key, leaf.variants[key]];
|
|
179
|
+
}
|
|
180
|
+
function prefVariantDefault(leaf, key) {
|
|
181
|
+
const held = leaf.variants[key]?.default;
|
|
182
|
+
const fields = held !== null && typeof held === "object" ? held : {};
|
|
183
|
+
return { ...fields, [leaf.tag]: key };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/type.ts
|
|
187
|
+
function prefType(name, leaf) {
|
|
188
|
+
return { ...leaf, type: name };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/groups.ts
|
|
192
|
+
function prefGroupIsPage(group) {
|
|
193
|
+
return group.as === void 0 || group.as === "page";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/stamp.ts
|
|
197
|
+
var isVersion = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
198
|
+
function stampRecord(value, at) {
|
|
199
|
+
return { $at: at, value };
|
|
200
|
+
}
|
|
201
|
+
function isStampedRecord(record) {
|
|
202
|
+
if (record === null || typeof record !== "object") return false;
|
|
203
|
+
const keys = Object.keys(record);
|
|
204
|
+
return keys.length === 2 && Object.hasOwn(record, "value") && isVersion(record.$at);
|
|
205
|
+
}
|
|
206
|
+
function readRecord(record, unstamped) {
|
|
207
|
+
return isStampedRecord(record) ? { at: record.$at, value: record.value, stamped: true } : { at: unstamped, value: record, stamped: false };
|
|
208
|
+
}
|
|
209
|
+
function recordValue(record) {
|
|
210
|
+
return isStampedRecord(record) ? record.value : record;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/migrate.ts
|
|
214
|
+
var isVersion2 = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
215
|
+
function runPrefsMigrations(cache, migrations, { stampAll = true } = {}) {
|
|
216
|
+
const target = migrations.length;
|
|
217
|
+
const raw = cache.get(VERSION_RECORD);
|
|
218
|
+
if (cache.has(VERSION_RECORD) && !isVersion2(raw)) {
|
|
219
|
+
console.warn(
|
|
220
|
+
`[prefs] "${cache.prefix}" has an unreadable ${VERSION_RECORD} (${JSON.stringify(raw)}); opening read-only`
|
|
221
|
+
);
|
|
222
|
+
cache.stopWriting();
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const stored = cache.has(VERSION_RECORD) ? raw : 0;
|
|
226
|
+
const held = cache.entries().filter(([name]) => name !== VERSION_RECORD).map(([name, record]) => ({ name, ...readRecord(record, Math.min(stored, target)) }));
|
|
227
|
+
const newest = Math.max(stored, ...held.map((r) => r.at));
|
|
228
|
+
if (newest > target) {
|
|
229
|
+
console.warn(
|
|
230
|
+
`[prefs] "${cache.prefix}" was written at version ${newest}, newer than this build's ${target}; opening read-only`
|
|
231
|
+
);
|
|
232
|
+
cache.stopWriting();
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
if (!cache.writable) return false;
|
|
236
|
+
const at = new Map(held.filter((r) => r.at < target).map((r) => [r.name, r.at]));
|
|
237
|
+
const records = new Map(held.filter((r) => at.has(r.name)).map((r) => [r.name, structuredClone(r.value)]));
|
|
238
|
+
for (let v = Math.min(target, ...at.values()); v < target; v++) {
|
|
239
|
+
const due = new Map([...records].filter(([name]) => at.get(name) <= v));
|
|
240
|
+
const before = [...due.keys()];
|
|
241
|
+
try {
|
|
242
|
+
migrations[v](due);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.warn(
|
|
245
|
+
`[prefs] migration ${v} \u2192 ${v + 1} for "${cache.prefix}" threw; opening read-only on the unmigrated records`,
|
|
246
|
+
error
|
|
247
|
+
);
|
|
248
|
+
cache.stopWriting();
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
for (const name of before) if (!due.has(name)) records.delete(name);
|
|
252
|
+
for (const [name, value] of due) {
|
|
253
|
+
records.set(name, value);
|
|
254
|
+
at.set(name, v + 1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
let wrote = false;
|
|
258
|
+
for (const r of held) {
|
|
259
|
+
if (at.has(r.name)) {
|
|
260
|
+
if (!records.has(r.name)) cache.delete(r.name);
|
|
261
|
+
wrote = true;
|
|
262
|
+
} else if (stampAll && !r.stamped) {
|
|
263
|
+
cache.set(r.name, stampRecord(r.value, target));
|
|
264
|
+
wrote = true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const [name, value] of records) {
|
|
268
|
+
if (value === void 0) cache.delete(name);
|
|
269
|
+
else cache.set(name, stampRecord(value, target));
|
|
270
|
+
}
|
|
271
|
+
if (stored !== target) {
|
|
272
|
+
cache.set(VERSION_RECORD, target);
|
|
273
|
+
wrote = true;
|
|
274
|
+
}
|
|
275
|
+
return wrote;
|
|
276
|
+
}
|
|
277
|
+
function watchPrefsVersion(cache, migrations) {
|
|
278
|
+
const target = migrations.length;
|
|
279
|
+
let versioned = cache.has(VERSION_RECORD);
|
|
280
|
+
let stopped = false;
|
|
281
|
+
const migrate = () => {
|
|
282
|
+
const cleared = versioned && !cache.has(VERSION_RECORD);
|
|
283
|
+
if (cleared) cache.set(VERSION_RECORD, target);
|
|
284
|
+
else if (!runPrefsMigrations(cache, migrations, { stampAll: false })) return;
|
|
285
|
+
versioned = true;
|
|
286
|
+
void cache.flush();
|
|
287
|
+
};
|
|
288
|
+
void cache.whenRead().then(() => {
|
|
289
|
+
if (!stopped && cache.writable) migrate();
|
|
290
|
+
});
|
|
291
|
+
const heard = (version) => {
|
|
292
|
+
if (!isVersion2(version)) {
|
|
293
|
+
console.warn(
|
|
294
|
+
`[prefs] "${cache.prefix}" has an unreadable ${VERSION_RECORD} (${JSON.stringify(version)}) from another writer; no longer persisting`
|
|
295
|
+
);
|
|
296
|
+
cache.stopWriting();
|
|
297
|
+
} else if (version > target) {
|
|
298
|
+
console.warn(
|
|
299
|
+
`[prefs] "${cache.prefix}" was migrated to version ${version} by another writer; this build knows ${target}; no longer persisting`
|
|
300
|
+
);
|
|
301
|
+
cache.stopWriting();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const stopOverruled = cache.onOverruled((changes) => {
|
|
305
|
+
for (const [name, value] of changes) if (name === VERSION_RECORD && value !== void 0) heard(value);
|
|
306
|
+
});
|
|
307
|
+
const stopChanges = cache.subscribe((changes) => {
|
|
308
|
+
const remote = changes.filter((change) => change.origin === "remote");
|
|
309
|
+
for (const change of remote) {
|
|
310
|
+
if (change.name === VERSION_RECORD && change.value !== void 0) heard(change.value);
|
|
311
|
+
}
|
|
312
|
+
if (remote.length > 0 && cache.writable) migrate();
|
|
313
|
+
versioned ||= cache.has(VERSION_RECORD);
|
|
314
|
+
});
|
|
315
|
+
return () => {
|
|
316
|
+
stopped = true;
|
|
317
|
+
stopOverruled();
|
|
318
|
+
stopChanges();
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function repairPrefValue(leaf, stored, validators) {
|
|
322
|
+
if (leaf.kind === "number") {
|
|
323
|
+
if (stored === "Infinity") stored = Infinity;
|
|
324
|
+
else if (stored === "-Infinity") stored = -Infinity;
|
|
325
|
+
}
|
|
326
|
+
const validate = validators?.[leaf.kind];
|
|
327
|
+
if (validate) {
|
|
328
|
+
try {
|
|
329
|
+
const out = validate(stored, leaf);
|
|
330
|
+
return out === void 0 ? leaf.default : out;
|
|
331
|
+
} catch {
|
|
332
|
+
return leaf.default;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
switch (leaf.kind) {
|
|
336
|
+
case "number":
|
|
337
|
+
return repairNumber(leaf, stored);
|
|
338
|
+
case "boolean":
|
|
339
|
+
return leaf.encoding || typeof stored === "boolean" ? stored : leaf.default;
|
|
340
|
+
case "string":
|
|
341
|
+
case "color":
|
|
342
|
+
case "field":
|
|
343
|
+
return typeof stored === "string" ? stored : leaf.default;
|
|
344
|
+
case "enum": {
|
|
345
|
+
const e = leaf;
|
|
346
|
+
if (e.encoding) return stored;
|
|
347
|
+
return e.options.some((o) => o.value === stored) ? stored : leaf.default;
|
|
348
|
+
}
|
|
349
|
+
case "object":
|
|
350
|
+
if (isPlainObject(stored)) return repairFields(leaf, stored, validators);
|
|
351
|
+
return leaf.fromScalar ? stored : leaf.default;
|
|
352
|
+
case "map":
|
|
353
|
+
return repairMap(leaf, stored, validators);
|
|
354
|
+
case "union": {
|
|
355
|
+
const variant = prefVariantOf(leaf, stored);
|
|
356
|
+
return variant ? repairFields(variant[1], stored, validators) : leaf.default;
|
|
357
|
+
}
|
|
358
|
+
case "list":
|
|
359
|
+
return repairList(leaf, stored, validators);
|
|
360
|
+
default:
|
|
361
|
+
return stored;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function repairFields(leaf, stored, validators) {
|
|
365
|
+
let out;
|
|
366
|
+
for (const [key, field] of prefSectionLeaves(leaf.children)) {
|
|
367
|
+
if (!Object.hasOwn(stored, key) || stored[key] === void 0) continue;
|
|
368
|
+
const read = repairPrefValue(field, stored[key], validators);
|
|
369
|
+
if (read !== stored[key]) (out ??= { ...stored })[key] = read;
|
|
370
|
+
}
|
|
371
|
+
return out ?? stored;
|
|
372
|
+
}
|
|
373
|
+
function repairMap(leaf, stored, validators) {
|
|
374
|
+
if (!isPlainObject(stored)) return leaf.default;
|
|
375
|
+
const held = stored;
|
|
376
|
+
let out;
|
|
377
|
+
for (const [key, value] of Object.entries(held)) {
|
|
378
|
+
const read = repairPrefValue(leaf.item, value, validators);
|
|
379
|
+
if (read !== value) (out ??= { ...held })[key] = read;
|
|
380
|
+
}
|
|
381
|
+
return out ?? stored;
|
|
382
|
+
}
|
|
383
|
+
function repairList(leaf, stored, validators) {
|
|
384
|
+
if (!Array.isArray(stored)) return leaf.default;
|
|
385
|
+
if (leaf.minItems !== void 0 && stored.length < leaf.minItems) return leaf.default;
|
|
386
|
+
const kept = leaf.maxItems !== void 0 && stored.length > leaf.maxItems ? stored.slice(0, leaf.maxItems) : stored;
|
|
387
|
+
const read = kept.map((entry) => repairPrefValue(leaf.item, entry, validators));
|
|
388
|
+
return kept === stored && read.every((entry, i) => entry === stored[i]) ? stored : read;
|
|
389
|
+
}
|
|
390
|
+
function repairNumber(leaf, stored) {
|
|
391
|
+
if (typeof stored !== "number" || Number.isNaN(stored)) return leaf.default;
|
|
392
|
+
const { endless } = leaf;
|
|
393
|
+
if (stored === Infinity && (endless === "max" || endless === "both")) return stored;
|
|
394
|
+
if (stored === -Infinity && (endless === "min" || endless === "both")) return stored;
|
|
395
|
+
let v = stored;
|
|
396
|
+
if (leaf.min !== void 0 && v < leaf.min) v = leaf.min;
|
|
397
|
+
if (leaf.max !== void 0 && v > leaf.max) v = leaf.max;
|
|
398
|
+
return Number.isFinite(v) ? v : leaf.default;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/store.ts
|
|
402
|
+
var encodeInfinity = (leaf, value) => {
|
|
403
|
+
if (leaf.kind === "number") return value === Infinity || value === -Infinity ? String(value) : value;
|
|
404
|
+
if (leaf.kind === "list" && Array.isArray(value)) {
|
|
405
|
+
const { item } = leaf;
|
|
406
|
+
return value.map((entry) => encodeInfinity(item, entry));
|
|
407
|
+
}
|
|
408
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
409
|
+
const held = value;
|
|
410
|
+
if (leaf.kind === "map") {
|
|
411
|
+
const { item } = leaf;
|
|
412
|
+
return Object.fromEntries(Object.entries(held).map(([key, entry]) => [key, encodeInfinity(item, entry)]));
|
|
413
|
+
}
|
|
414
|
+
const fields = leaf.kind === "union" ? prefVariantOf(leaf, held)?.[1] : leaf;
|
|
415
|
+
if (fields?.kind !== "object") return value;
|
|
416
|
+
let out;
|
|
417
|
+
for (const [key, field] of prefSectionLeaves(fields.children)) {
|
|
418
|
+
if (!Object.hasOwn(held, key)) continue;
|
|
419
|
+
const encoded = encodeInfinity(field, held[key]);
|
|
420
|
+
if (encoded !== held[key]) (out ??= { ...held })[key] = encoded;
|
|
421
|
+
}
|
|
422
|
+
return out ?? value;
|
|
423
|
+
};
|
|
424
|
+
function createPrefsStore(schema, cache, validators, onClose, version = 0) {
|
|
425
|
+
const leaves = new Map([...prefLeaves(schema)].filter(([, leaf]) => prefHoldsValue(leaf)));
|
|
426
|
+
let snapshot = null;
|
|
427
|
+
let previous = null;
|
|
428
|
+
const build = () => {
|
|
429
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
430
|
+
const raw = /* @__PURE__ */ new Map();
|
|
431
|
+
const unset = /* @__PURE__ */ new Set();
|
|
432
|
+
const tree = {};
|
|
433
|
+
for (const [path, leaf] of leaves) {
|
|
434
|
+
const has = cache.has(path);
|
|
435
|
+
let value;
|
|
436
|
+
if (!has) {
|
|
437
|
+
value = leaf.default;
|
|
438
|
+
unset.add(path);
|
|
439
|
+
} else {
|
|
440
|
+
const record = cache.get(path);
|
|
441
|
+
const stored = recordValue(record);
|
|
442
|
+
raw.set(path, record);
|
|
443
|
+
value = previous?.raw.has(path) && previous.raw.get(path) === record ? previous.byPath.get(path) : repairPrefValue(leaf, stored, validators);
|
|
444
|
+
}
|
|
445
|
+
byPath.set(path, value);
|
|
446
|
+
assignPrefValueAtPath(tree, path, value);
|
|
447
|
+
}
|
|
448
|
+
previous = { raw, byPath };
|
|
449
|
+
return { byPath, tree, unset };
|
|
450
|
+
};
|
|
451
|
+
const current = () => snapshot ??= build();
|
|
452
|
+
let storedTree = null;
|
|
453
|
+
const buildStored = () => {
|
|
454
|
+
const tree = {};
|
|
455
|
+
const branches = new WeakSet([tree]);
|
|
456
|
+
for (const [name, record] of cache.entries()) {
|
|
457
|
+
if (name === VERSION_RECORD) continue;
|
|
458
|
+
const value = recordValue(record);
|
|
459
|
+
const parts = name.split(".");
|
|
460
|
+
let cursor = tree;
|
|
461
|
+
for (const part of parts.slice(0, -1)) {
|
|
462
|
+
const next = cursor[part];
|
|
463
|
+
if (typeof next === "object" && next !== null && branches.has(next)) {
|
|
464
|
+
cursor = next;
|
|
465
|
+
} else {
|
|
466
|
+
const branch = {};
|
|
467
|
+
branches.add(branch);
|
|
468
|
+
setOwnPrefValue(cursor, part, branch);
|
|
469
|
+
cursor = branch;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const last = parts[parts.length - 1];
|
|
473
|
+
if (!branches.has(cursor[last])) setOwnPrefValue(cursor, last, value);
|
|
474
|
+
}
|
|
475
|
+
return tree;
|
|
476
|
+
};
|
|
477
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
478
|
+
const stopCache = cache.subscribe((changes) => {
|
|
479
|
+
const records = changes.filter((c) => c.name !== VERSION_RECORD);
|
|
480
|
+
if (records.length === 0) return;
|
|
481
|
+
storedTree = null;
|
|
482
|
+
const mine = records.filter((c) => leaves.has(c.name));
|
|
483
|
+
if (mine.length > 0) snapshot = null;
|
|
484
|
+
const now = current();
|
|
485
|
+
const out = mine.map((c) => ({ path: c.name, value: now.byPath.get(c.name), origin: c.origin }));
|
|
486
|
+
for (const listener of [...listeners]) listener(out);
|
|
487
|
+
});
|
|
488
|
+
return {
|
|
489
|
+
schema,
|
|
490
|
+
get: (path) => current().byPath.get(path),
|
|
491
|
+
set: (path, value) => {
|
|
492
|
+
const leaf = leaves.get(path);
|
|
493
|
+
if (!leaf) {
|
|
494
|
+
console.warn(`[prefs] no leaf at "${path}"; ignoring the write`);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const stored = encodeInfinity(leaf, value);
|
|
498
|
+
cache.set(path, stored === void 0 ? void 0 : stampRecord(stored, version));
|
|
499
|
+
},
|
|
500
|
+
reset: (path) => {
|
|
501
|
+
for (const p of leaves.keys()) {
|
|
502
|
+
if (path === void 0 || p === path || p.startsWith(`${path}.`)) cache.delete(p);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
isSet: (path) => cache.has(path),
|
|
506
|
+
values: () => current().tree,
|
|
507
|
+
unset: () => current().unset,
|
|
508
|
+
stored: () => storedTree ??= buildStored(),
|
|
509
|
+
subscribe: (listener) => {
|
|
510
|
+
listeners.add(listener);
|
|
511
|
+
return () => {
|
|
512
|
+
listeners.delete(listener);
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
get writable() {
|
|
516
|
+
return cache.writable;
|
|
517
|
+
},
|
|
518
|
+
read: () => cache.read(),
|
|
519
|
+
whenRead: () => cache.whenRead(),
|
|
520
|
+
flush: () => cache.flush(),
|
|
521
|
+
close: async () => {
|
|
522
|
+
onClose?.();
|
|
523
|
+
stopCache();
|
|
524
|
+
listeners.clear();
|
|
525
|
+
await cache.close();
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/open.ts
|
|
531
|
+
async function openPrefs(schema, options) {
|
|
532
|
+
const cache = await openRecords({ storage: options.storage, prefix: options.prefix });
|
|
533
|
+
const stop = watch(cache, options);
|
|
534
|
+
if (runPrefsMigrations(cache, options.migrations ?? [])) await cache.flush();
|
|
535
|
+
return createPrefsStore(schema, cache, options.validators, stop, options.migrations?.length ?? 0);
|
|
536
|
+
}
|
|
537
|
+
function openPrefsSync(schema, options) {
|
|
538
|
+
const cache = openRecordsSync({ storage: options.storage, prefix: options.prefix });
|
|
539
|
+
const stop = watch(cache, options);
|
|
540
|
+
if (runPrefsMigrations(cache, options.migrations ?? [])) void cache.flush();
|
|
541
|
+
return createPrefsStore(schema, cache, options.validators, stop, options.migrations?.length ?? 0);
|
|
542
|
+
}
|
|
543
|
+
var watch = (cache, options) => watchPrefsVersion(cache, options.migrations ?? []);
|
|
544
|
+
|
|
545
|
+
// src/kinds.ts
|
|
546
|
+
var ONE = { compound: false, several: false };
|
|
547
|
+
var MANY = { compound: true, several: true };
|
|
548
|
+
var PREF_KIND_TRAITS = {
|
|
549
|
+
number: ONE,
|
|
550
|
+
boolean: ONE,
|
|
551
|
+
string: ONE,
|
|
552
|
+
enum: ONE,
|
|
553
|
+
color: ONE,
|
|
554
|
+
paint: ONE,
|
|
555
|
+
object: MANY,
|
|
556
|
+
list: MANY,
|
|
557
|
+
map: MANY,
|
|
558
|
+
union: MANY,
|
|
559
|
+
field: ONE,
|
|
560
|
+
action: { compound: false, several: true },
|
|
561
|
+
alias: ONE,
|
|
562
|
+
label: ONE,
|
|
563
|
+
separator: ONE
|
|
564
|
+
};
|
|
565
|
+
var traitsOf = (leaf) => Object.hasOwn(PREF_KIND_TRAITS, leaf.kind) ? PREF_KIND_TRAITS[leaf.kind] : ONE;
|
|
566
|
+
function prefIsCompound(leaf) {
|
|
567
|
+
return traitsOf(leaf).compound;
|
|
568
|
+
}
|
|
569
|
+
function prefDrawsSeveral(leaf) {
|
|
570
|
+
return traitsOf(leaf).several;
|
|
571
|
+
}
|
|
572
|
+
function prefUnit(system, display, opts) {
|
|
573
|
+
let self;
|
|
574
|
+
try {
|
|
575
|
+
self = unitScale(system, display);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
throw new Error(`prefUnit: ${e instanceof Error ? e.message : String(e)}`);
|
|
578
|
+
}
|
|
579
|
+
const scale = opts?.precision === void 0 ? void 0 : 10 ** opts.precision;
|
|
580
|
+
const suffix = opts?.suffix ?? display;
|
|
581
|
+
const accepts = { [suffix]: 1 };
|
|
582
|
+
for (const name of Object.keys(system.units)) {
|
|
583
|
+
const other = unitScale(system, name);
|
|
584
|
+
const factor = other.factor / self.factor;
|
|
585
|
+
const offset = (other.offset - self.offset) / self.factor;
|
|
586
|
+
accepts[name] = offset === 0 ? factor : { factor, offset };
|
|
587
|
+
}
|
|
588
|
+
const toDisplay = (stored) => {
|
|
589
|
+
const shown = (stored - self.offset) / self.factor;
|
|
590
|
+
return scale === void 0 ? shown : Math.round(shown * scale) / scale;
|
|
591
|
+
};
|
|
592
|
+
return {
|
|
593
|
+
toDisplay,
|
|
594
|
+
fromDisplay: (shown) => shown * self.factor + self.offset,
|
|
595
|
+
suffix,
|
|
596
|
+
accepts,
|
|
597
|
+
format: (stored) => `${formatUnit(stored, display, system, {
|
|
598
|
+
precision: opts?.precision ?? 2,
|
|
599
|
+
suffix: false
|
|
600
|
+
})}${suffix}`
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
var PREF_KINDS = {
|
|
604
|
+
number: true,
|
|
605
|
+
boolean: true,
|
|
606
|
+
string: true,
|
|
607
|
+
enum: true,
|
|
608
|
+
color: true,
|
|
609
|
+
paint: true,
|
|
610
|
+
object: true,
|
|
611
|
+
list: true,
|
|
612
|
+
map: true,
|
|
613
|
+
union: true,
|
|
614
|
+
field: true,
|
|
615
|
+
action: true,
|
|
616
|
+
alias: true,
|
|
617
|
+
label: true,
|
|
618
|
+
separator: true
|
|
619
|
+
};
|
|
620
|
+
function pairRowsOf(leaves) {
|
|
621
|
+
const rows = /* @__PURE__ */ new Map();
|
|
622
|
+
for (const [path, leaf] of leaves) {
|
|
623
|
+
if (!leaf.pair || rows.has(path)) continue;
|
|
624
|
+
const row = { key: path, label: leaf.pair.label ?? leaf.name };
|
|
625
|
+
rows.set(path, row);
|
|
626
|
+
const partners = typeof leaf.pair.with === "string" ? [leaf.pair.with] : leaf.pair.with;
|
|
627
|
+
for (const partner of partners) if (!rows.has(partner)) rows.set(partner, row);
|
|
628
|
+
}
|
|
629
|
+
return rows;
|
|
630
|
+
}
|
|
631
|
+
function isBuiltinPref(leaf) {
|
|
632
|
+
return Object.hasOwn(PREF_KINDS, leaf.kind);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export { PREF_KINDS, PREF_KIND_TRAITS, VERSION_RECORD, filterPrefSubtree, flattenPrefValues, isBuiltinPref, isPrefLeaf, isPrefSection, openPrefs, openPrefsSync, pairRowsOf, prefAliasTarget, prefAliasedLeaf, prefDisplayBounds, prefDrawsSeveral, prefGroupIsPage, prefHoldsValue, prefIsCompound, prefLeafAt, prefLeaves, prefObjectBase, prefObjectWithField, prefSectionLeaves, prefType, prefUnit, prefValueAtPath, prefVariantDefault, prefVariantOf, repairPrefValue, visiblePrefSubtree };
|
|
636
|
+
//# sourceMappingURL=index.js.map
|
|
637
|
+
//# sourceMappingURL=index.js.map
|