@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.
@@ -1,4 +1,81 @@
1
- import { Display, UnitEntry, InfinityText, UnitSystem, Unit } from '@weasel-js/quantity';
1
+ import { StorageAdapter, SyncStorageAdapter } from '@weasel-js/storage';
2
+ import { UnitEntry, UnitSystem, Unit, Display, InfinityText } from '@weasel-js/quantity';
3
+
4
+ /** What a condition can compare a leaf's value against. `null` stands for
5
+ * nothing held: a missing value as well as a stored `null`. */
6
+ type PrefConditionValue = string | number | boolean | null;
7
+ /**
8
+ * One leaf's value, tested. `path` is the full path the value is read and
9
+ * written at (`'windows.paging'`), a field of an `object` or `union` leaf
10
+ * included (`'stroke.width'`, `'fill.type'`). With `from: 'item'` the path is
11
+ * read from the entry the test's node is drawn in instead: the entry of the
12
+ * nearest list or map the node is the `item` of, or sits inside the item of.
13
+ * `{ from: 'item', path: 'mode' }` on a field of a list's item reads `mode`
14
+ * of the same entry, and an empty path is the entry itself. The value compared is the one
15
+ * the leaf's control shows, where an `encoding` stores another. With
16
+ * several operators it holds while all of them do, so
17
+ * `{ path: 'count', atLeast: 1, below: 10 }` is a range.
18
+ *
19
+ * With no operator it holds while the value is set, which by kind is:
20
+ * a `boolean` that is on; a `number` other than 0; a `string`, a `color`, a
21
+ * `field`, or an `enum`'s option that is not the empty string; a `paint`
22
+ * that is not `null`; a `list` with an entry; a `map` with a key; an
23
+ * `object` or a `union` holding a field. Nothing held is never set, and the
24
+ * value of an app-defined kind is set by whichever of those its shape is,
25
+ * else while it is truthy.
26
+ */
27
+ interface PrefTest {
28
+ path: string;
29
+ /** Where `path` is read from: the entry the node is drawn in. Unset: the schema root. */
30
+ from?: 'item';
31
+ is?: PrefConditionValue;
32
+ isNot?: PrefConditionValue;
33
+ /** Holds while the value is any of these. */
34
+ in?: readonly PrefConditionValue[];
35
+ /** The ordering operators hold only for a number. */
36
+ above?: number;
37
+ below?: number;
38
+ atLeast?: number;
39
+ atMost?: number;
40
+ }
41
+ /** A condition a schema node holds in `showIf` or `enableIf`. An empty `all`
42
+ * holds and an empty `any` does not. */
43
+ type PrefCondition = PrefTest | {
44
+ all: readonly PrefCondition[];
45
+ } | {
46
+ any: readonly PrefCondition[];
47
+ } | {
48
+ not: PrefCondition;
49
+ };
50
+ /** Reads the value a test compares: at `path` from the schema root, or with `from`, from the entry being drawn. */
51
+ type PrefConditionReader = (path: string, from?: 'item') => unknown;
52
+ /** Whether `value` is a well-formed condition, all the way down. */
53
+ declare function isPrefCondition(value: unknown): value is PrefCondition;
54
+ /** Whether `condition` holds, given the value `read` finds at each path it names. */
55
+ declare function prefConditionHolds(condition: PrefCondition, read: PrefConditionReader): boolean;
56
+ /** Whether a node is drawn: its `showIf` holds, or it has none. A node
57
+ * answers for itself only; one inside a group that is not shown is not
58
+ * drawn either. */
59
+ declare function prefIsShown(node: {
60
+ showIf?: PrefCondition;
61
+ }, read: PrefConditionReader): boolean;
62
+ /** Whether a node's controls take input: its `enableIf` holds, or it has
63
+ * none. As {@link prefIsShown}, a node answers for itself only. */
64
+ declare function prefIsEnabled(node: {
65
+ enableIf?: PrefCondition;
66
+ }, read: PrefConditionReader): boolean;
67
+ /** Every path `condition` reads from the schema root, or with `from`, from the entry, once each, in the order met. */
68
+ declare function prefConditionPaths(condition: PrefCondition, from?: 'item'): string[];
69
+ /** Whether any test of `condition` reads from the entry, so that it has no answer until an entry is drawn. */
70
+ declare function prefConditionReadsItem(condition: PrefCondition): boolean;
71
+ /**
72
+ * `condition` with every path it reads put through `move`, for following a
73
+ * key that was renamed or a leaf that moved. `move` is told where a path is
74
+ * read from, and what it hands back is read from there too. A condition none
75
+ * of whose paths change comes back as the object it was given, and so does
76
+ * each part of one.
77
+ */
78
+ declare function mapPrefConditionPaths(condition: PrefCondition, move: (path: string, from?: 'item') => string): PrefCondition;
2
79
 
3
80
  /** What a {@link PrefAction}'s `run` is told. */
4
81
  interface PrefActionContext {
@@ -52,6 +129,12 @@ declare function prefAliasTarget(alias: PrefAlias, leafAt: (path: string) => Pre
52
129
  path: string;
53
130
  leaf: PrefLeaf;
54
131
  } | undefined;
132
+ /**
133
+ * What an alias is called wherever one is drawn or named: its own name, else
134
+ * that of `target`, the leaf it shows, else `Alias` for one that shows
135
+ * nothing.
136
+ */
137
+ declare function prefAliasName(alias: PrefAlias, target?: PrefLeaf): string;
55
138
  /** A leaf drawn through an alias: the target, under the alias's own name and description where it gives them. */
56
139
  declare function prefAliasedLeaf(alias: PrefAlias, target: PrefLeaf): PrefLeaf;
57
140
 
@@ -110,13 +193,66 @@ declare function prefVariantOf(leaf: PrefUnion, value: unknown): [key: string, v
110
193
  /** The value a union takes on switching to the variant at `key`: that variant's default, tagged. */
111
194
  declare function prefVariantDefault(leaf: PrefUnion, key: string): Record<string, unknown>;
112
195
 
113
- /** The value types a built-in pref leaf can hold. */
114
- type PrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color' | 'paint' | 'object' | 'list' | 'map' | 'union' | 'field' | 'action' | 'alias' | 'label' | 'separator';
196
+ /** What both forms of a unit say about the text around the number. */
197
+ interface PrefUnitText {
198
+ /** Shown after the input, e.g. `'°'`. */
199
+ suffix?: string;
200
+ /** Suffixes a person may type, each mapped to the scale that turns a
201
+ * number in that unit into a display number: `{ mm: 0.1, cm: 1 }` for a
202
+ * field showing centimeters. A unit that disagrees with the display unit
203
+ * about zero carries an `offset` as well. */
204
+ accepts?: Readonly<Record<string, UnitEntry>>;
205
+ }
115
206
  /**
116
- * Leaves that share one row in a compact property UI (weasel-ui
117
- * `SelectionPanel`, labkit's `ControlPanel`), declared on the first of them.
118
- * Purely presentational.
207
+ * A display unit as data, for a conversion that is linear: `stored = display * scale + offset`, the direction a
208
+ * `UnitEntry` runs toward its base, so `scale` is what one display unit is worth in stored units. Degrees shown
209
+ * over radians stored is `{ suffix: '°', scale: Math.PI / 180 }`, and `{ scale: 1 }` changes nothing. Being data,
210
+ * it is written to JSON, printed in a schema literal, and edited in `PrefSchemaEditor` like any other attribute.
211
+ */
212
+ interface PrefLinearUnit extends PrefUnitText {
213
+ /** Stored units per display unit. Never 0. */
214
+ scale: number;
215
+ /** The stored value a display of 0 stands for. Absent is 0. */
216
+ offset?: number;
217
+ /** Decimal places the shown value is rounded to. Absent, the value shows unrounded and formats to two places.
218
+ * What is typed is stored unrounded either way. */
219
+ precision?: number;
220
+ }
221
+ /** A display unit as code, for a conversion that is not linear. It cannot be written to JSON or edited as data. */
222
+ interface PrefFunctionUnit extends PrefUnitText {
223
+ toDisplay: (stored: number) => number;
224
+ fromDisplay: (display: number) => number;
225
+ /** The stored value as display text, suffix included. */
226
+ format?: (stored: number) => string;
227
+ }
228
+ /** Display-unit conversion for number leaves whose stored value uses a
229
+ * canonical unit the user shouldn't see (e.g. radians stored, degrees
230
+ * shown). Read one through {@link resolvePrefUnit}, which answers for both forms. */
231
+ type PrefNumberUnit = PrefLinearUnit | PrefFunctionUnit;
232
+ /** A unit of either form as the functions that convert and format with it. */
233
+ interface ResolvedPrefUnit extends PrefFunctionUnit {
234
+ format: (stored: number) => string;
235
+ }
236
+ /** Whether `unit` is the data form, and so can be written out and edited. */
237
+ declare function isPrefLinearUnit(unit: PrefNumberUnit): unit is PrefLinearUnit;
238
+ /** How to convert and format with `unit`, whichever form it was written in. */
239
+ declare function resolvePrefUnit(unit: PrefNumberUnit): ResolvedPrefUnit;
240
+ /**
241
+ * A display unit built from a {@link UnitSystem}: values are stored in the
242
+ * system's base and shown in `display`, rounded to `precision` places when
243
+ * given, and every unit in the system is accepted as typed text. `suffix`
244
+ * defaults to the unit's name and is accepted too. Every unit of a system is
245
+ * linear, so what comes back is data.
119
246
  */
247
+ declare function prefUnit(system: UnitSystem, display: Unit, opts?: {
248
+ precision?: number;
249
+ suffix?: string;
250
+ }): PrefLinearUnit;
251
+
252
+ /** The value types a built-in pref leaf can hold. */
253
+ type PrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color' | 'paint' | 'object' | 'list' | 'map' | 'union' | 'field' | 'action' | 'alias' | 'label' | 'separator';
254
+ /** Leaves that share one row in a compact property UI, declared on the first
255
+ * of them. Purely presentational. */
120
256
  interface PrefPair {
121
257
  /** The other leaves on the row, by the full path their values are read and
122
258
  * written at (`'pose.y'`, `'camera.type'`), in order. Each must follow the
@@ -136,6 +272,11 @@ interface PrefBase<K extends string, Value> {
136
272
  default: Value;
137
273
  /** Hide from a host app's settings UI by default. */
138
274
  hidden?: boolean;
275
+ /** Drawn only while this holds. Presentational: the value stays stored
276
+ * and readable while the leaf is not drawn. */
277
+ showIf?: PrefCondition;
278
+ /** Drawn, and disabled while this does not hold. */
279
+ enableIf?: PrefCondition;
139
280
  /** Nothing held where this leaf's value is read means `default`, because
140
281
  * whoever reads the value takes it that way (`pose.rotation ?? 0`). A
141
282
  * surface over sparse settings reads every leaf so already. One over a
@@ -182,32 +323,6 @@ type PrefBooleanControl = 'checkbox' | 'switch' | 'toggle';
182
323
  type PrefStringControl = 'input' | 'textarea';
183
324
  /** How a schema-driven UI should present an enum pref. */
184
325
  type PrefEnumControl = 'select' | 'radio' | 'toggle' | 'menu';
185
- /** Display-unit conversion for number leaves whose stored value uses a
186
- * canonical unit the user shouldn't see (e.g. radians stored, degrees
187
- * shown). The stored value stays canonical; UIs convert at the edge. */
188
- interface PrefNumberUnit {
189
- toDisplay: (stored: number) => number;
190
- fromDisplay: (display: number) => number;
191
- /** Shown after the input, e.g. `'°'`. */
192
- suffix?: string;
193
- /** Suffixes a person may type, each mapped to the scale that turns a
194
- * number in that unit into a display number: `{ mm: 0.1, cm: 1 }` for a
195
- * field showing centimeters. A unit that disagrees with the display unit
196
- * about zero carries an `offset` as well. */
197
- accepts?: Readonly<Record<string, UnitEntry>>;
198
- /** The stored value as display text, suffix included. */
199
- format?: (stored: number) => string;
200
- }
201
- /**
202
- * A display unit built from a {@link UnitSystem}: values are stored in the
203
- * system's base and shown in `display`, rounded to `precision` places when
204
- * given, and every unit in the system is accepted as typed text. `suffix`
205
- * defaults to the unit's name and is accepted too.
206
- */
207
- declare function prefUnit(system: UnitSystem, display: Unit, opts?: {
208
- precision?: number;
209
- suffix?: string;
210
- }): PrefNumberUnit;
211
326
  /** A numeric pref, optionally bounded and stepped, and optionally stored in a
212
327
  * different unit from the one shown. */
213
328
  interface PrefNumber extends PrefBase<'number', number> {
@@ -251,8 +366,7 @@ interface PrefString extends PrefBase<'string', string> {
251
366
  *
252
367
  * A dash array is the case that needs it: `Stroke.dash` stores lengths, and
253
368
  * the thing a person chooses is a style. The presets scale by the stroke's
254
- * width, so both directions are given the object's other fields — a style is
255
- * meaningless without the width it is a multiple of.
369
+ * width, so both directions are given the object's other fields.
256
370
  */
257
371
  interface PrefEnumEncoding<T extends string = string> {
258
372
  /**
@@ -315,13 +429,12 @@ interface PrefColor extends PrefBase<'color', string> {
315
429
  }
316
430
  /**
317
431
  * Open leaf: any node with a `kind` outside the built-ins. Schema-driven
318
- * UIs (weasel-ui `PrefsForm` / `SelectionPanel`) dispatch it to an
319
- * app-supplied renderer. Deliberately NOT index-signatured so concrete
320
- * app interfaces stay assignable. Mirrors weasel-ui's `PrefCustom`.
432
+ * UIs dispatch it to an app-supplied renderer. Deliberately NOT
433
+ * index-signatured so concrete app interfaces stay assignable.
321
434
  */
322
435
  type PrefCustom = PrefBase<string, unknown>;
323
436
  /**
324
- * A whole `FillStyle`, not a color inside one. Use it wherever the value is
437
+ * A whole `Paint`, not a color inside one. Use it wherever the value is
325
438
  * the tagged paint union — a solid color, a pattern, a gradient — rather
326
439
  * than a hex string.
327
440
  *
@@ -336,21 +449,16 @@ interface PrefPaint extends PrefBase<'paint', unknown> {
336
449
  alpha?: boolean;
337
450
  }
338
451
  /**
339
- * A leaf whose value is one object, with its own fields hanging off it.
340
- *
341
- * A compound value — a stroke, a shadow, a pattern spec — could be described
342
- * as several sibling leaves addressing into it (`data.stroke.width`,
343
- * `data.stroke.cap`). It shouldn't be: each control would then write one field
344
- * of a value it can only half see, and writing into something that is not an
345
- * object yet corrupts it. Here the fields are `children` of one leaf, and
346
- * every edit commits the parent object whole.
452
+ * A leaf whose value is one object, with its own fields hanging off it as
453
+ * `children`; every edit commits the parent object whole. Sibling leaves
454
+ * addressing into the value (`data.stroke.width`, `data.stroke.cap`) would
455
+ * each write one field of a value they can only half see, and writing into
456
+ * something that is not an object yet corrupts it.
347
457
  *
348
458
  * `children` paths are relative to the object. They are ordinary leaves, so a
349
459
  * field that is itself a union (a stroke's `paint`) declares the kind that
350
460
  * edits that union. A child may also be a {@link PrefSection}, which
351
- * organizes the fields under a heading without contributing to the path. A
352
- * `TextStyle` needs it: its character and paragraph fields belong to one
353
- * value but read as two lists.
461
+ * organizes the fields under a heading without contributing to the path.
354
462
  */
355
463
  interface PrefObject extends PrefBase<'object', unknown> {
356
464
  children: Record<string, PrefLeaf | PrefSection>;
@@ -377,8 +485,7 @@ type BuiltinPref = PrefNumber | PrefBoolean | PrefString | PrefEnum | PrefColor
377
485
  /**
378
486
  * The built-in kinds, as a table. A kind added to {@link PrefKind} is a
379
487
  * compile error here, and from here it is one in every renderer's `never`
380
- * guard — the only thing standing between a new kind and rendering as
381
- * nothing in four places at once.
488
+ * guard.
382
489
  */
383
490
  declare const PREF_KINDS: Record<PrefKind, true>;
384
491
  /** Which row each pairing among `leaves` puts a path on: a `key` the row's
@@ -427,6 +534,10 @@ interface PrefGroup {
427
534
  /** How the group is drawn. Unset, its depth decides: a top-level group is a
428
535
  * `page`, and one inside another is a `section`. */
429
536
  as?: PrefGroupAs;
537
+ /** Drawn, with everything under it, only while this holds. */
538
+ showIf?: PrefCondition;
539
+ /** Everything under it is disabled while this does not hold. */
540
+ enableIf?: PrefCondition;
430
541
  children: Record<string, PrefLeaf | PrefGroup>;
431
542
  }
432
543
  /**
@@ -445,6 +556,10 @@ interface PrefSection {
445
556
  * `page`, since nothing that draws sections has a rail. Unset: a heading
446
557
  * over its rows. */
447
558
  as?: PrefSectionAs;
559
+ /** As a {@link PrefGroup}'s. */
560
+ showIf?: PrefCondition;
561
+ /** As a {@link PrefGroup}'s. */
562
+ enableIf?: PrefCondition;
448
563
  members: Record<string, PrefLeaf | PrefSection>;
449
564
  }
450
565
  /** Whether a top-level group gets a page of its own, and not a place on the page of the root's own leaves. */
@@ -528,6 +643,18 @@ type PrefValueOf<L> = L extends {
528
643
  /** What the leaf at `P` holds. */
529
644
  type PrefValueAt<G, P extends string> = PrefValueOf<PrefAtPath<G, P>>;
530
645
 
646
+ /** Takes stored values from one version to the next: rename, transform or
647
+ * delete entries in `records`, keyed by leaf path. It sees the values, not
648
+ * the stamps they are stored under, and only those at its own version. */
649
+ type PrefsMigration = (records: Map<string, unknown>) => void;
650
+
651
+ /** Decides what a stored value reads as for one kind of leaf: the value to
652
+ * read, or `undefined` for "invalid, read the default". */
653
+ type PrefValidator = (stored: unknown, leaf: PrefLeaf) => unknown;
654
+ /** What `stored` reads as for `leaf`. Never writes anything back: a schema
655
+ * rolled back finds the original still in storage. */
656
+ declare function repairPrefValue(leaf: PrefLeaf, stored: unknown, validators?: Readonly<Record<string, PrefValidator>>): unknown;
657
+
531
658
  /** One leaf changing, as a store subscriber hears it: the value readers now
532
659
  * see, and whether this store or another writer made the change. */
533
660
  interface PrefChange {
@@ -556,8 +683,13 @@ interface PrefsStore<S extends PrefGroup> {
556
683
  subscribe(listener: (changes: PrefChange[]) => void): () => void;
557
684
  /** False while the store cannot persist: storage has not been read yet, or
558
685
  * was written by a newer schema. Changes still apply for this session, and
559
- * ones made before a late read lands are persisted when it does. */
686
+ * ones made before a late read lands are persisted when it does. False for
687
+ * good once the store is closed. */
560
688
  readonly writable: boolean;
689
+ /** True once `close` was called. A write after that changes what this store
690
+ * reads and reaches neither storage nor its subscribers, and the first one
691
+ * warns. */
692
+ readonly closed: boolean;
561
693
  /** Try again now to read storage that could not be read at open, ahead of
562
694
  * the store's own retries. Resolves whether storage has been read. */
563
695
  read(): Promise<boolean>;
@@ -569,4 +701,24 @@ interface PrefsStore<S extends PrefGroup> {
569
701
  close(): Promise<void>;
570
702
  }
571
703
 
572
- export { type PrefList as A, type BuiltinPref as B, type PrefMap as C, type PrefNumberControl as D, type PrefNumberUnit as E, type PrefPaint as F, type PrefPair as G, type PrefPath as H, type PrefSectionAs as I, type PrefSeparator as J, type PrefString as K, type PrefStringControl as L, type PrefUnion as M, type PrefValueAt as N, type PrefValueOf as O, type PrefLeaf as P, isBuiltinPref as Q, pairRowsOf as R, prefAliasTarget as S, prefAliasedLeaf as T, prefGroupIsPage as U, prefUnit as V, prefVariantDefault as W, prefVariantOf as X, type PrefGroup as a, type PrefSection as b, type PrefNumber as c, type PrefObject as d, type PrefsStore as e, type PrefKind as f, PREF_KINDS as g, type PrefAction as h, type PrefActionContext as i, type PrefActionControl as j, type PrefAlias as k, type PrefAtPath as l, type PrefBase as m, type PrefBoolean as n, type PrefBooleanControl as o, type PrefBooleanEncoding as p, type PrefChange as q, type PrefColor as r, type PrefCustom as s, type PrefEnum as t, type PrefEnumBase as u, type PrefEnumControl as v, type PrefEnumEncoding as w, type PrefField as x, type PrefGroupAs as y, type PrefLabel as z };
704
+ /** Options for `openPrefs` and `openPrefsSync`. */
705
+ interface PrefsOptions {
706
+ storage: StorageAdapter;
707
+ /** Prepended to every record's key: `'myapp.prefs.'`. */
708
+ prefix: string;
709
+ /** `migrations[i]` takes stored records from version `i` to `i + 1`. The
710
+ * current version is `migrations.length`. */
711
+ migrations?: readonly PrefsMigration[];
712
+ /** Validators for app-defined kinds, keyed by `kind`. One given for a
713
+ * built-in kind replaces that kind's own rule. */
714
+ validators?: Readonly<Record<string, PrefValidator>>;
715
+ }
716
+ /** Load every stored value for `schema`, migrate it, and return the store. */
717
+ declare function openPrefs<S extends PrefGroup>(schema: S, options: PrefsOptions): Promise<PrefsStore<S>>;
718
+ /** `openPrefs` for an adapter that reads synchronously: the store is ready
719
+ * on return, so values can be read before anything renders. */
720
+ declare function openPrefsSync<S extends PrefGroup>(schema: S, options: PrefsOptions & {
721
+ storage: SyncStorageAdapter;
722
+ }): PrefsStore<S>;
723
+
724
+ export { isPrefCondition as $, type PrefEnumEncoding as A, type BuiltinPref as B, type PrefField as C, type PrefFunctionUnit as D, type PrefGroupAs as E, type PrefLabel as F, type PrefLinearUnit as G, type PrefNumberControl as H, type PrefNumberUnit as I, type PrefPaint as J, type PrefPair as K, type PrefPath as L, type PrefSectionAs as M, type PrefSeparator as N, type PrefString as O, type PrefLeaf as P, type PrefStringControl as Q, type PrefTest as R, type PrefUnion as S, type PrefValidator as T, type PrefValueAt as U, type PrefValueOf as V, type PrefsMigration as W, type PrefsOptions as X, type PrefsStore as Y, type ResolvedPrefUnit as Z, isBuiltinPref as _, type PrefGroup as a, isPrefLinearUnit as a0, mapPrefConditionPaths as a1, openPrefs as a2, openPrefsSync as a3, pairRowsOf as a4, prefAliasName as a5, prefAliasTarget as a6, prefAliasedLeaf as a7, prefConditionHolds as a8, prefConditionPaths as a9, prefConditionReadsItem as aa, prefGroupIsPage as ab, prefIsEnabled as ac, prefIsShown as ad, prefUnit as ae, prefVariantDefault as af, prefVariantOf as ag, repairPrefValue as ah, resolvePrefUnit as ai, type PrefSection as b, type PrefNumber as c, type PrefList as d, type PrefMap as e, type PrefCondition as f, type PrefConditionReader as g, type PrefAlias as h, type PrefObject as i, type PrefKind as j, PREF_KINDS as k, type PrefAction as l, type PrefActionContext as m, type PrefActionControl as n, type PrefAtPath as o, type PrefBase as p, type PrefBoolean as q, type PrefBooleanControl as r, type PrefBooleanEncoding as s, type PrefChange as t, type PrefColor as u, type PrefConditionValue as v, type PrefCustom as w, type PrefEnum as x, type PrefEnumBase as y, type PrefEnumControl as z };
package/dist/react.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { a as PrefGroup, H as PrefPath, e as PrefsStore, N as PrefValueAt } from './store-CNFnA_3c.js';
1
+ import { a as PrefGroup, L as PrefPath, Y as PrefsStore, U as PrefValueAt, X as PrefsOptions } from './open-BEY-aOGe.js';
2
+ import { SyncStorageAdapter } from '@weasel-js/storage';
2
3
  import '@weasel-js/quantity';
3
4
 
4
5
  /** One leaf of `store`, as React state. */
@@ -17,4 +18,27 @@ declare function usePrefsValues<S extends PrefGroup>(store: PrefsStore<S>): {
17
18
  reset: (path?: string) => void;
18
19
  };
19
20
 
20
- export { usePref, usePrefsValues };
21
+ type SyncOptions = PrefsOptions & {
22
+ storage: SyncStorageAdapter;
23
+ };
24
+ /**
25
+ * A store for `schema`, open while the component is mounted: opened during
26
+ * the first render, so that render has values, and closed, with its queued
27
+ * writes sent, when the component unmounts.
28
+ *
29
+ * Components naming the same `storage`, `prefix`, and `schema` share one
30
+ * store, which closes when the last of them unmounts. Components naming the
31
+ * same `storage` and `prefix` with different schemas each get a store of
32
+ * their own over the same records, so a write through one is read by the
33
+ * others at once, and each hears it as a `local` change. They must pass the
34
+ * same `migrations`: the records are migrated once, when the first opens.
35
+ *
36
+ * `storage` and `schema` are compared by identity and `prefix` by value, so
37
+ * keep the first two out of the render body. A change to any of the three
38
+ * closes the store and returns the one for the new three. The `options`
39
+ * object itself may be new every render: `migrations` and `validators` are
40
+ * read when a store opens, and a change to either alone opens nothing.
41
+ */
42
+ declare function usePrefsStore<S extends PrefGroup>(schema: S, options: SyncOptions): PrefsStore<S>;
43
+
44
+ export { usePref, usePrefsStore, usePrefsValues };
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { openPrefsCacheSync, createPrefsStore } from './chunk-5KB3WBJZ.js';
1
2
  import { useCallback, useSyncExternalStore } from 'react';
2
3
 
3
- // src/hooks.ts
4
4
  function usePref(store, path) {
5
5
  const subscribe = useCallback((onChange) => store.subscribe(onChange), [store]);
6
6
  const getSnapshot = () => store.get(path);
@@ -28,7 +28,71 @@ function usePrefsValues(store) {
28
28
  const reset = useCallback((path) => store.reset(path), [store]);
29
29
  return { values, set, unset, stored, reset };
30
30
  }
31
+ var shared = /* @__PURE__ */ new WeakMap();
32
+ function sharedAt(storage, prefix) {
33
+ let byPrefix = shared.get(storage);
34
+ if (!byPrefix) shared.set(storage, byPrefix = /* @__PURE__ */ new Map());
35
+ let entry = byPrefix.get(prefix);
36
+ if (!entry) byPrefix.set(prefix, entry = { open: null, bySchema: /* @__PURE__ */ new Map() });
37
+ return entry;
38
+ }
39
+ function heldAt(at, schema) {
40
+ let entry = at.bySchema.get(schema);
41
+ if (!entry) at.bySchema.set(schema, entry = { store: null, holders: 0 });
42
+ return entry;
43
+ }
44
+ var lent = (cache) => Object.create(cache, {
45
+ close: {
46
+ value: async () => {
47
+ await cache.flush();
48
+ }
49
+ }
50
+ });
51
+ function closeUnheldSoon(storage, prefix, schema, entry) {
52
+ queueMicrotask(() => {
53
+ if (entry.holders > 0) return;
54
+ void entry.store?.close();
55
+ entry.store = null;
56
+ const byPrefix = shared.get(storage);
57
+ const at = byPrefix?.get(prefix);
58
+ if (!at || !byPrefix) return;
59
+ if (at.bySchema.get(schema) === entry) at.bySchema.delete(schema);
60
+ if (at.bySchema.size > 0) return;
61
+ at.open?.stop();
62
+ void at.open?.cache.close();
63
+ byPrefix.delete(prefix);
64
+ });
65
+ }
66
+ function acquire(schema, options) {
67
+ const { storage, prefix } = options;
68
+ const at = sharedAt(storage, prefix);
69
+ const entry = heldAt(at, schema);
70
+ if (!entry.store) {
71
+ at.open ??= openPrefsCacheSync(options);
72
+ entry.store = createPrefsStore(
73
+ schema,
74
+ lent(at.open.cache),
75
+ options.validators,
76
+ void 0,
77
+ options.migrations?.length ?? 0
78
+ );
79
+ if (entry.holders === 0) closeUnheldSoon(storage, prefix, schema, entry);
80
+ }
81
+ return entry.store;
82
+ }
83
+ function usePrefsStore(schema, options) {
84
+ const { storage, prefix } = options;
85
+ const hold = useCallback(() => {
86
+ const entry = heldAt(sharedAt(storage, prefix), schema);
87
+ entry.holders++;
88
+ return () => {
89
+ if (--entry.holders === 0) closeUnheldSoon(storage, prefix, schema, entry);
90
+ };
91
+ }, [storage, prefix, schema]);
92
+ const current = () => acquire(schema, options);
93
+ return useSyncExternalStore(hold, current, current);
94
+ }
31
95
 
32
- export { usePref, usePrefsValues };
96
+ export { usePref, usePrefsStore, usePrefsValues };
33
97
  //# sourceMappingURL=react.js.map
34
98
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks.ts"],"names":[],"mappings":";;;AAMO,SAAS,OAAA,CACd,OACA,IAAA,EAIA;AACA,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,CAAC,QAAA,KAAyB,KAAA,CAAM,UAAU,QAAQ,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAC1F,EAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AACxC,EAAA,MAAM,KAAA,GAAQ,oBAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,WAAW,CAAA;AACtE,EAAA,MAAM,GAAA,GAAM,WAAA;AAAA,IACV,CAAC,IAAA,KAA+E;AAC9E,MAAA,KAAA,CAAM,GAAA;AAAA,QACJ,IAAA;AAAA,QACA,OAAO,SAAS,UAAA,GACX,IAAA,CAAwD,MAAM,GAAA,CAAI,IAAI,CAAC,CAAA,GACxE;AAAA,OACN;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAO,IAAI;AAAA,GACd;AACA,EAAA,OAAO,CAAC,OAAO,GAAG,CAAA;AACpB;AAKO,SAAS,eAAoC,KAAA,EAMlD;AACA,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,CAAC,QAAA,KAAyB,KAAA,CAAM,UAAU,QAAQ,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAC1F,EAAA,MAAM,SAAS,oBAAA,CAAqB,SAAA,EAAW,KAAA,CAAM,MAAA,EAAQ,MAAM,MAAM,CAAA;AACzE,EAAA,MAAM,QAAQ,oBAAA,CAAqB,SAAA,EAAW,KAAA,CAAM,KAAA,EAAO,MAAM,KAAK,CAAA;AACtE,EAAA,MAAM,SAAS,oBAAA,CAAqB,SAAA,EAAW,KAAA,CAAM,MAAA,EAAQ,MAAM,MAAM,CAAA;AACzE,EAAA,MAAM,GAAA,GAAM,WAAA;AAAA,IACV,CAAC,IAAA,EAAc,KAAA,KAAmB,KAAA,CAAM,GAAA,CAAI,MAAqB,KAAc,CAAA;AAAA,IAC/E,CAAC,KAAK;AAAA,GACR;AACA,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,CAAC,IAAA,KAAkB,KAAA,CAAM,MAAM,IAAI,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACvE,EAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,KAAA,EAAO,QAAQ,KAAA,EAAM;AAC7C","file":"react.js","sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport type { PrefPath, PrefValueAt } from './paths';\nimport type { PrefGroup } from './groups';\nimport type { PrefsStore } from './store';\n\n/** One leaf of `store`, as React state. */\nexport function usePref<S extends PrefGroup, P extends PrefPath<S>>(\n store: PrefsStore<S>,\n path: P,\n): [\n PrefValueAt<S, P>,\n (next: PrefValueAt<S, P> | ((prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)) => void,\n] {\n const subscribe = useCallback((onChange: () => void) => store.subscribe(onChange), [store]);\n const getSnapshot = () => store.get(path);\n const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const set = useCallback(\n (next: PrefValueAt<S, P> | ((prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)) => {\n store.set(\n path,\n typeof next === 'function'\n ? (next as (prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)(store.get(path))\n : next,\n );\n },\n [store, path],\n );\n return [value, set];\n}\n\n/** The whole of `store` as React state, shaped for `PrefsForm`: `values`,\n * `onChange={set}`, `auto={unset}`, and `reset` for `onAutoChange`. `stored`\n * is `store.stored()`. */\nexport function usePrefsValues<S extends PrefGroup>(store: PrefsStore<S>): {\n values: Record<string, unknown>;\n set: (path: string, value: unknown) => void;\n unset: ReadonlySet<string>;\n stored: Record<string, unknown>;\n reset: (path?: string) => void;\n} {\n const subscribe = useCallback((onChange: () => void) => store.subscribe(onChange), [store]);\n const values = useSyncExternalStore(subscribe, store.values, store.values);\n const unset = useSyncExternalStore(subscribe, store.unset, store.unset);\n const stored = useSyncExternalStore(subscribe, store.stored, store.stored);\n const set = useCallback(\n (path: string, value: unknown) => store.set(path as PrefPath<S>, value as never),\n [store],\n );\n const reset = useCallback((path?: string) => store.reset(path), [store]);\n return { values, set, unset, stored, reset };\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks.ts","../src/usePrefsStore.ts"],"names":["useCallback","useSyncExternalStore"],"mappings":"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","file":"react.js","sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport type { PrefPath, PrefValueAt } from './paths';\nimport type { PrefGroup } from './groups';\nimport type { PrefsStore } from './store';\n\n/** One leaf of `store`, as React state. */\nexport function usePref<S extends PrefGroup, P extends PrefPath<S>>(\n store: PrefsStore<S>,\n path: P,\n): [\n PrefValueAt<S, P>,\n (next: PrefValueAt<S, P> | ((prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)) => void,\n] {\n const subscribe = useCallback((onChange: () => void) => store.subscribe(onChange), [store]);\n const getSnapshot = () => store.get(path);\n const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const set = useCallback(\n (next: PrefValueAt<S, P> | ((prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)) => {\n store.set(\n path,\n typeof next === 'function'\n ? (next as (prev: PrefValueAt<S, P>) => PrefValueAt<S, P>)(store.get(path))\n : next,\n );\n },\n [store, path],\n );\n return [value, set];\n}\n\n/** The whole of `store` as React state, shaped for `PrefsForm`: `values`,\n * `onChange={set}`, `auto={unset}`, and `reset` for `onAutoChange`. `stored`\n * is `store.stored()`. */\nexport function usePrefsValues<S extends PrefGroup>(store: PrefsStore<S>): {\n values: Record<string, unknown>;\n set: (path: string, value: unknown) => void;\n unset: ReadonlySet<string>;\n stored: Record<string, unknown>;\n reset: (path?: string) => void;\n} {\n const subscribe = useCallback((onChange: () => void) => store.subscribe(onChange), [store]);\n const values = useSyncExternalStore(subscribe, store.values, store.values);\n const unset = useSyncExternalStore(subscribe, store.unset, store.unset);\n const stored = useSyncExternalStore(subscribe, store.stored, store.stored);\n const set = useCallback(\n (path: string, value: unknown) => store.set(path as PrefPath<S>, value as never),\n [store],\n );\n const reset = useCallback((path?: string) => store.reset(path), [store]);\n return { values, set, unset, stored, reset };\n}\n","import type { OwnedRecordCache, StorageAdapter, SyncStorageAdapter } from '@weasel-js/storage';\nimport { useCallback, useSyncExternalStore } from 'react';\nimport type { PrefGroup } from './groups';\nimport { openPrefsCacheSync, type PrefsOptions } from './open';\nimport { createPrefsStore, type PrefsStore } from './store';\n\n/** The store open for one schema, and how many mounted components hold it. */\ninterface Held {\n store: PrefsStore<PrefGroup> | null;\n holders: number;\n}\n\n/** The one cache open on a prefix, and a store over it for each schema in use. */\ninterface Shared {\n open: { cache: OwnedRecordCache; stop: () => void } | null;\n bySchema: Map<PrefGroup, Held>;\n}\n\ntype SyncOptions = PrefsOptions & { storage: SyncStorageAdapter };\n\nconst shared = new WeakMap<StorageAdapter, Map<string, Shared>>();\n\nfunction sharedAt(storage: StorageAdapter, prefix: string): Shared {\n let byPrefix = shared.get(storage);\n if (!byPrefix) shared.set(storage, (byPrefix = new Map()));\n let entry = byPrefix.get(prefix);\n if (!entry) byPrefix.set(prefix, (entry = { open: null, bySchema: new Map() }));\n return entry;\n}\n\nfunction heldAt(at: Shared, schema: PrefGroup): Held {\n let entry = at.bySchema.get(schema);\n if (!entry) at.bySchema.set(schema, (entry = { store: null, holders: 0 }));\n return entry;\n}\n\n/** `cache` for one store among several: closing it sends queued writes and\n * leaves the cache open for the others. */\nconst lent = (cache: OwnedRecordCache): OwnedRecordCache =>\n Object.create(cache, {\n close: {\n value: async (): Promise<void> => {\n await cache.flush();\n },\n },\n }) as OwnedRecordCache;\n\n/** Close a store nothing holds, and the cache once no store is over it, a\n * microtask on: after the remount StrictMode and fast refresh make in the\n * same tick, which holds it again. */\nfunction closeUnheldSoon(storage: StorageAdapter, prefix: string, schema: PrefGroup, entry: Held): void {\n queueMicrotask(() => {\n if (entry.holders > 0) return;\n void entry.store?.close();\n entry.store = null;\n const byPrefix = shared.get(storage);\n const at = byPrefix?.get(prefix);\n if (!at || !byPrefix) return;\n if (at.bySchema.get(schema) === entry) at.bySchema.delete(schema);\n if (at.bySchema.size > 0) return;\n at.open?.stop();\n // `close` sends queued writes before its first await, and a sync adapter's\n // write lands in that call, so a cache opened next reads them.\n void at.open?.cache.close();\n byPrefix.delete(prefix);\n });\n}\n\nfunction acquire<S extends PrefGroup>(schema: S, options: SyncOptions): PrefsStore<S> {\n const { storage, prefix } = options;\n const at = sharedAt(storage, prefix);\n const entry = heldAt(at, schema);\n if (!entry.store) {\n at.open ??= openPrefsCacheSync(options);\n entry.store = createPrefsStore(\n schema,\n lent(at.open.cache),\n options.validators,\n undefined,\n options.migrations?.length ?? 0,\n ) as unknown as PrefsStore<PrefGroup>;\n // Opened by a render that may never mount.\n if (entry.holders === 0) closeUnheldSoon(storage, prefix, schema, entry);\n }\n return entry.store as unknown as PrefsStore<S>;\n}\n\n/**\n * A store for `schema`, open while the component is mounted: opened during\n * the first render, so that render has values, and closed, with its queued\n * writes sent, when the component unmounts.\n *\n * Components naming the same `storage`, `prefix`, and `schema` share one\n * store, which closes when the last of them unmounts. Components naming the\n * same `storage` and `prefix` with different schemas each get a store of\n * their own over the same records, so a write through one is read by the\n * others at once, and each hears it as a `local` change. They must pass the\n * same `migrations`: the records are migrated once, when the first opens.\n *\n * `storage` and `schema` are compared by identity and `prefix` by value, so\n * keep the first two out of the render body. A change to any of the three\n * closes the store and returns the one for the new three. The `options`\n * object itself may be new every render: `migrations` and `validators` are\n * read when a store opens, and a change to either alone opens nothing.\n */\nexport function usePrefsStore<S extends PrefGroup>(schema: S, options: SyncOptions): PrefsStore<S> {\n const { storage, prefix } = options;\n const hold = useCallback(() => {\n const entry = heldAt(sharedAt(storage, prefix), schema);\n entry.holders++;\n return () => {\n if (--entry.holders === 0) closeUnheldSoon(storage, prefix, schema, entry);\n };\n }, [storage, prefix, schema]);\n // Read again once held, so a store closed between render and mount is\n // replaced before anything uses it.\n const current = () => acquire(schema, options);\n return useSyncExternalStore(hold, current, current);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/prefs",
3
- "version": "1.9.4",
3
+ "version": "1.10.0",
4
4
  "description": "A preferences schema, a store that loads, validates, migrates and persists values against it, and React hooks over the store.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,9 +41,9 @@
41
41
  "build": "tsup"
42
42
  },
43
43
  "dependencies": {
44
- "@weasel-js/quantity": "1.9.4",
45
- "@weasel-js/registry": "1.9.4",
46
- "@weasel-js/storage": "1.9.4"
44
+ "@weasel-js/quantity": "1.10.0",
45
+ "@weasel-js/registry": "1.10.0",
46
+ "@weasel-js/storage": "1.10.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": ">=18"