@weasel-js/prefs 1.9.4 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,160 +1,254 @@
1
- import { openRecords, openRecordsSync } from '@weasel-js/storage';
2
- import { isPlainObject } from '@weasel-js/registry';
3
- import { unitScale, formatUnit } from '@weasel-js/quantity';
1
+ import { prefLeafHoldingIn, prefLeafHolding, prefSchemaHolding, UNSEEN, prefConditionHolds, prefValueAtPath, isPrefSection, prefSectionLeaves, isPrefLeaf, prefLeafAt, prefAliasTarget, prefAliasedLeaf, pairRowsOf, prefConditionReadsItem, visiblePrefSubtree } from './chunk-5KB3WBJZ.js';
2
+ export { PREF_KINDS, VERSION_RECORD, checkPrefCondition, checkPrefConditions, filterPrefSubtree, flattenPrefValues, isBuiltinPref, isPrefCondition, isPrefLeaf, isPrefLinearUnit, isPrefSection, mapPrefConditionPaths, openPrefs, openPrefsSync, pairRowsOf, prefAliasName, prefAliasTarget, prefAliasedLeaf, prefConditionHolds, prefConditionPaths, prefConditionReadsItem, prefDisplayBounds, prefHoldsValue, prefIsEnabled, prefIsShown, prefLeafAt, prefLeaves, prefSchemaHolding, prefSectionLeaves, prefUnit, prefValueAtPath, prefVariantDefault, prefVariantOf, repairPrefValue, resolvePrefUnit, visiblePrefSubtree } from './chunk-5KB3WBJZ.js';
4
3
 
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;
4
+ // src/tabRuns.ts
5
+ function prefTabRuns(items, isTab) {
6
+ const out = [];
7
+ let run;
8
+ for (const item of items) {
9
+ if (!isTab(item)) {
10
+ run = void 0;
11
+ out.push({ item });
12
+ } else if (run) {
13
+ run.push(item);
14
+ } else {
15
+ out.push({ tabs: run = [item] });
16
+ }
16
17
  }
18
+ return out;
17
19
  }
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
20
+
21
+ // src/shown.ts
22
+ function prefShownValue(leaf, stored, siblings) {
23
+ if (leaf.kind !== "boolean" && leaf.kind !== "enum") return stored;
24
+ const { encoding } = leaf;
25
+ return encoding ? encoding.read(stored, siblings) : stored;
26
+ }
27
+ function prefShownReader(schema, read, item, over) {
28
+ return (path, from) => {
29
+ const stored = read(path, from);
30
+ const held = from === "item" ? item && prefLeafHoldingIn(item, path) : prefLeafHolding(prefSchemaHolding(schema, path, over), path);
31
+ if (held === void 0 || held === UNSEEN) return stored;
32
+ const object = held.within === void 0 ? void 0 : read(held.within, from);
33
+ return prefShownValue(held.leaf, stored, typeof object === "object" && object !== null ? object : void 0);
23
34
  };
24
35
  }
25
36
 
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
- );
37
+ // src/judge.ts
38
+ function entryValue({ item, value }, path) {
39
+ if (path === "") return value !== void 0 ? value : item.default;
40
+ const held = prefValueAtPath(value, path);
41
+ return held !== void 0 ? held : prefValueAtPath(item.default, path);
42
+ }
43
+ function prefJudge(schema, read, entry, over) {
44
+ const stored = (path, from) => from === "item" ? entry && entryValue(entry, path) : read(path);
45
+ const shown = prefShownReader(schema, stored, entry?.item, over);
46
+ return (condition) => prefConditionHolds(condition, shown);
47
+ }
48
+ function deferEntryConditions(holds) {
49
+ return (condition, field) => prefConditionReadsItem(condition) || holds(condition, field);
50
+ }
51
+ function prefReader(schema, values, over) {
52
+ const beyond = over && prefReader(over, values);
53
+ return (path) => {
54
+ const held = prefValueAtPath(values, path);
55
+ if (held !== void 0) return held;
56
+ if (beyond && prefSchemaHolding(schema, path, over) === over) return beyond(path);
57
+ if (isPrefSection(schema)) {
58
+ for (const [key, leaf] of prefSectionLeaves(schema.members)) {
59
+ if (path === key) return leaf.default;
60
+ if (path.startsWith(`${key}.`)) return prefValueAtPath(leaf.default, path.slice(key.length + 1));
61
+ }
62
+ return void 0;
63
+ }
64
+ const steps = path.split(".");
65
+ let node = schema;
66
+ for (let i = 0; i < steps.length; i++) {
67
+ if (!Object.hasOwn(node.children, steps[i])) return void 0;
68
+ const child = node.children[steps[i]];
69
+ if (!isPrefLeaf(child)) node = child;
70
+ else return i === steps.length - 1 ? child.default : prefValueAtPath(child.default, steps.slice(i + 1).join("."));
71
+ }
72
+ return void 0;
73
+ };
50
74
  }
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];
75
+
76
+ // src/scopeAt.ts
77
+ var PREF_ENTRY = "*";
78
+ var PREF_SCOPE = Object.freeze({ shown: true, enabled: true });
79
+ function prefScopeUnder(above, node, holds) {
80
+ const shown = above.shown && (node.showIf === void 0 || holds(node.showIf, "showIf"));
81
+ const enabled = above.enabled && (node.enableIf === void 0 || holds(node.enableIf, "enableIf"));
82
+ return shown === above.shown && enabled === above.enabled ? above : { shown, enabled };
83
+ }
84
+ function scopeAmong(held, rest, above, holds) {
85
+ for (const [key, child] of Object.entries(held)) {
86
+ if (!isPrefLeaf(child)) {
87
+ const found = scopeAmong(child.members, rest, prefScopeUnder(above, child, holds), holds);
88
+ if (found) return found;
89
+ } else if (rest === key) return prefScopeUnder(above, child, holds);
90
+ else if (rest.startsWith(`${key}.`)) return scopeInside(child, rest.slice(key.length + 1), prefScopeUnder(above, child, holds), holds);
57
91
  }
58
- return isPrefLeaf(node) ? node : void 0;
92
+ return void 0;
59
93
  }
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];
94
+ function scopeInside(leaf, rest, scope, holds) {
95
+ if (leaf.kind === "object") return scopeAmong(leaf.children, rest, scope, holds) ?? scope;
96
+ if (leaf.kind === "list" || leaf.kind === "map") {
97
+ const { item } = leaf;
98
+ if (item === void 0) return scope;
99
+ const inItem = deferEntryConditions(holds);
100
+ const entry = prefScopeUnder(scope, { enableIf: item.enableIf }, inItem);
101
+ const dot = rest.indexOf(".");
102
+ return dot < 0 ? entry : scopeInside(item, rest.slice(dot + 1), entry, inItem);
65
103
  }
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 };
104
+ if (leaf.kind !== "union") return scope;
105
+ for (const variant of Object.values(leaf.variants)) {
106
+ const found = scopeAmong(variant.children, rest, scope, holds);
107
+ if (found) return found;
78
108
  }
79
- return out;
109
+ return scope;
80
110
  }
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;
111
+ function scopeAt(schema, path, holds) {
112
+ let scope = prefScopeUnder(PREF_SCOPE, schema, holds);
113
+ if (path === "") return scope;
114
+ if (isPrefSection(schema)) return scopeAmong(schema.members, path, scope, holds) ?? scope;
115
+ let node = schema;
116
+ const steps = path.split(".");
117
+ for (let i = 0; i < steps.length; i++) {
118
+ if (!Object.hasOwn(node.children, steps[i])) return scope;
119
+ const child = node.children[steps[i]];
120
+ scope = prefScopeUnder(scope, child, holds);
121
+ if (isPrefLeaf(child)) return i === steps.length - 1 ? scope : scopeInside(child, steps.slice(i + 1).join("."), scope, holds);
122
+ node = child;
92
123
  }
93
- if (Object.keys(children).length === 0 && !keepEmpty) return null;
94
- return { ...node, children };
124
+ return scope;
95
125
  }
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;
126
+ function prefAliasScope(own, alias, over, holds) {
127
+ const target = prefAliasTarget(alias, (path) => prefLeafAt(over, path));
128
+ if (target === void 0) return own;
129
+ const theirs = scopeAt(over, target.path, holds);
130
+ const shown = own.shown && theirs.shown;
131
+ const enabled = own.enabled && theirs.enabled;
132
+ return shown === own.shown && enabled === own.enabled ? own : { shown, enabled };
114
133
  }
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);
134
+ function prefScopeAt(schema, path, holds, { aliasOver, aliasHolds } = {}) {
135
+ const own = scopeAt(schema, path, holds);
136
+ const leaf = prefLeafAt(schema, path);
137
+ return leaf?.kind === "alias" ? prefAliasScope(own, leaf, aliasOver ?? schema, aliasHolds ?? holds) : own;
126
138
  }
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
+ // src/scope.ts
141
+ var under = (prefix, key) => prefix === "" ? key : `${prefix}.${key}`;
142
+ function leafWalk({ keepEmpty, keepHidden, inItem, aliasOver, aliasHolds }) {
143
+ const disabled = /* @__PURE__ */ new Set();
144
+ const hidden = /* @__PURE__ */ new Set();
145
+ const leaf = (node, path, above, holds) => {
146
+ const own = prefScopeUnder(above, node, holds);
147
+ const scope = node.kind === "alias" && aliasOver ? prefAliasScope(own, node, aliasOver, aliasHolds ?? holds) : own;
148
+ return scope.shown || keepHidden ? inside(node, path, scope, holds) : null;
149
+ };
150
+ const inside = (node, path, scope, holds) => {
151
+ if (!scope.shown) hidden.add(path);
152
+ if (!scope.enabled) disabled.add(path);
153
+ if (node.kind === "object") {
154
+ const children = members(node.children, path, scope, holds);
155
+ return children === node.children ? node : { ...node, children };
156
+ }
157
+ if (node.kind === "list" || node.kind === "map") {
158
+ const { item } = node;
159
+ if (item === void 0) return node;
160
+ const entry = inside(item, under(path, PREF_ENTRY), prefScopeUnder(scope, { enableIf: item.enableIf }, inItem), inItem);
161
+ return entry === item ? node : { ...node, item: entry };
162
+ }
163
+ if (node.kind !== "union") return node;
164
+ const { variants } = node;
165
+ let changed = false;
166
+ const next = {};
167
+ for (const [key, variant] of Object.entries(variants)) {
168
+ const children = members(variant.children, path, scope, holds);
169
+ changed ||= children !== variant.children;
170
+ next[key] = children === variant.children ? variant : { ...variant, children };
171
+ }
172
+ return changed ? { ...node, variants: next } : node;
173
+ };
174
+ const members = (held, prefix, above, holds) => {
175
+ let changed = false;
176
+ const out = {};
177
+ for (const [key, child] of Object.entries(held)) {
178
+ const kept = isPrefLeaf(child) ? leaf(child, under(prefix, key), above, holds) : section(child, prefix, above, holds);
179
+ changed ||= kept !== child;
180
+ if (kept !== null) out[key] = kept;
181
+ }
182
+ return changed ? out : held;
183
+ };
184
+ const section = (node, prefix, above, holds) => {
185
+ const scope = prefScopeUnder(above, node, holds);
186
+ if (!scope.shown && !keepHidden) return null;
187
+ const kept = members(node.members, prefix, scope, holds);
188
+ if (kept === node.members) return node;
189
+ return Object.keys(kept).length === 0 && !keepEmpty ? null : { ...node, members: kept };
190
+ };
191
+ return { disabled, hidden, leaf, inside, members };
192
+ }
193
+ function applyPrefConditions(schema, holds, { keepEmpty = false, keepHidden = false, aliasOver, aliasHolds } = {}) {
194
+ const walk = leafWalk({ keepEmpty, keepHidden, inItem: deferEntryConditions(holds), aliasOver: aliasOver ?? schema, aliasHolds });
195
+ const { disabled, hidden } = walk;
196
+ const group = (node, path, above) => {
197
+ const scope2 = prefScopeUnder(above, node, holds);
198
+ if (!scope2.shown && !keepHidden) return null;
199
+ if (!scope2.shown && path !== "") hidden.add(path);
200
+ if (!scope2.enabled && path !== "") disabled.add(path);
201
+ let changed = false;
202
+ const children = {};
203
+ for (const [key, child] of Object.entries(node.children)) {
204
+ const at = under(path, key);
205
+ const kept2 = isPrefLeaf(child) ? walk.leaf(child, at, scope2, holds) : group(child, at, scope2);
206
+ changed ||= kept2 !== child;
207
+ if (kept2 !== null) children[key] = kept2;
208
+ }
209
+ if (!changed) return node;
210
+ return Object.keys(children).length === 0 && !keepEmpty && path !== "" ? null : { ...node, children };
211
+ };
212
+ if (!isPrefSection(schema)) return { shown: group(schema, "", PREF_SCOPE), disabled, hidden };
213
+ const scope = prefScopeUnder(PREF_SCOPE, schema, holds);
214
+ if (!scope.shown && !keepHidden) return { shown: null, disabled, hidden };
215
+ const kept = walk.members(schema.members, "", scope, holds);
216
+ return { shown: kept === schema.members ? schema : { ...schema, members: kept }, disabled, hidden };
217
+ }
218
+ var ALWAYS = () => true;
219
+ function applyPrefEntryConditions(item, path, holds, { keepEmpty = false, keepHidden = false } = {}) {
220
+ const asked = (condition, field) => !prefConditionReadsItem(condition) || holds(condition, field);
221
+ const walk = leafWalk({ keepEmpty, keepHidden, inItem: ALWAYS });
222
+ const scope = prefScopeUnder(PREF_SCOPE, { enableIf: item.enableIf }, asked);
223
+ return { item: walk.inside(item, path, scope, asked), disabled: walk.disabled, hidden: walk.hidden };
139
224
  }
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 };
225
+
226
+ // src/aliasTargets.ts
227
+ function visibleFieldsOf(leaf) {
228
+ if (leaf.kind !== "object") return leaf;
229
+ const shown = visiblePrefSubtree({ ...leaf, hidden: false }, false, true);
230
+ return { ...leaf, children: shown.children };
231
+ }
232
+ function prefAliasTargets(over, judge, { showHidden = false, markHidden = false } = {}) {
233
+ const holds = judge();
234
+ let applied;
235
+ const get = () => applied ??= applyPrefConditions(over, holds, { keepEmpty: true, keepHidden: markHidden });
236
+ const leafAt = (path) => {
237
+ const { shown } = get();
238
+ const leaf = shown === null ? void 0 : prefLeafAt(shown, path);
239
+ return leaf === void 0 || showHidden ? leaf : visibleFieldsOf(leaf);
240
+ };
241
+ return {
242
+ holds,
243
+ leafAt,
244
+ shown: (alias) => {
245
+ const target = prefAliasTarget(alias, leafAt);
246
+ return target && { path: target.path, leaf: prefAliasedLeaf(alias, target.leaf) };
247
+ },
248
+ disabledAt: (path) => get().disabled.has(path),
249
+ ...markHidden ? { hiddenAt: (path) => get().hidden.has(path) } : {},
250
+ entryAt: (item, path, value) => applyPrefEntryConditions(item, path, judge({ item, value }), { keepHidden: markHidden })
251
+ };
158
252
  }
159
253
 
160
254
  // src/object.ts
@@ -170,19 +264,6 @@ function prefObjectWithField(pref, held, key, value) {
170
264
  return rest;
171
265
  }
172
266
 
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
267
  // src/type.ts
187
268
  function prefType(name, leaf) {
188
269
  return { ...leaf, type: name };
@@ -193,354 +274,21 @@ function prefGroupIsPage(group) {
193
274
  return group.as === void 0 || group.as === "page";
194
275
  }
195
276
 
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;
277
+ // src/pair.ts
278
+ function pairRuns(items, leafOf) {
279
+ const leaves = items.map(leafOf);
280
+ const pairs = pairRowsOf(leaves.filter((entry) => entry !== void 0));
281
+ const keys = leaves.map((entry) => entry === void 0 ? void 0 : pairs.get(entry[0]));
282
+ const runs = [];
283
+ for (let i = 0; i < items.length; ) {
284
+ const pair = keys[i];
285
+ let j = i + 1;
286
+ while (pair !== void 0 && keys[j]?.key === pair.key) j += 1;
287
+ runs.push(j - i > 1 ? { items: items.slice(i, j), pair } : { items: [items[i]] });
288
+ i = j;
224
289
  }
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);
290
+ return runs;
536
291
  }
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
292
 
545
293
  // src/kinds.ts
546
294
  var ONE = { compound: false, several: false };
@@ -569,69 +317,7 @@ function prefIsCompound(leaf) {
569
317
  function prefDrawsSeveral(leaf) {
570
318
  return traitsOf(leaf).several;
571
319
  }
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
320
 
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 };
321
+ export { PREF_ENTRY, PREF_KIND_TRAITS, PREF_SCOPE, applyPrefConditions, applyPrefEntryConditions, pairRuns, prefAliasTargets, prefDrawsSeveral, prefGroupIsPage, prefIsCompound, prefJudge, prefObjectBase, prefObjectWithField, prefReader, prefScopeAt, prefScopeUnder, prefShownReader, prefShownValue, prefTabRuns, prefType };
636
322
  //# sourceMappingURL=index.js.map
637
323
  //# sourceMappingURL=index.js.map