@coherent.js/state 1.1.0 → 2.0.0-rc.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/README.md +84 -15
- package/dist/index.js +929 -335
- package/dist/index.js.map +3 -3
- package/dist/reactive-state.js +468 -111
- package/dist/reactive-state.js.map +2 -2
- package/dist/state-manager.js +141 -38
- package/dist/state-manager.js.map +3 -3
- package/dist/state-persistence.js +239 -143
- package/dist/state-persistence.js.map +2 -2
- package/dist/state-validation.js +70 -43
- package/dist/state-validation.js.map +2 -2
- package/package.json +1 -4
- package/types/index.d.ts +143 -36
package/types/index.d.ts
CHANGED
|
@@ -15,10 +15,18 @@ export type Watcher<T = unknown> = (
|
|
|
15
15
|
) => void;
|
|
16
16
|
|
|
17
17
|
export interface ObservableOptions {
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Re-assigning the same object still notifies, since it may have been
|
|
20
|
+
* mutated in place; defaults to `true`. Identical primitives never notify.
|
|
21
|
+
*/
|
|
19
22
|
deep?: boolean;
|
|
20
23
|
/** Invoke watchers on subscribe; defaults to `true` */
|
|
21
24
|
immediate?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Receives errors thrown by watchers (and middleware); defaults to
|
|
27
|
+
* {@link globalErrorHandler}. The other watchers still run.
|
|
28
|
+
*/
|
|
29
|
+
onError?: (error: unknown, context: { type: string; [key: string]: unknown }) => void;
|
|
22
30
|
[option: string]: unknown;
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -26,7 +34,8 @@ export interface ObservableOptions {
|
|
|
26
34
|
* A single reactive value.
|
|
27
35
|
*
|
|
28
36
|
* Read and write through the `value` accessor — assigning notifies watchers
|
|
29
|
-
* and invalidates any computed that read it.
|
|
37
|
+
* and invalidates any computed that read it. Watchers run after the write
|
|
38
|
+
* (after the outermost {@link batch}), each in isolation.
|
|
30
39
|
*
|
|
31
40
|
* ```ts
|
|
32
41
|
* const count = observable(0);
|
|
@@ -41,16 +50,25 @@ export class Observable<T = unknown> {
|
|
|
41
50
|
get value(): T;
|
|
42
51
|
set value(newValue: T);
|
|
43
52
|
|
|
53
|
+
/** Read the value without recording a computed dependency */
|
|
54
|
+
peek(): T;
|
|
55
|
+
|
|
44
56
|
/** Subscribe to changes; returns an unwatch function */
|
|
45
57
|
watch(callback: Watcher<T>, options?: { immediate?: boolean }): () => void;
|
|
46
58
|
|
|
47
|
-
/** Remove
|
|
48
|
-
unwatch(
|
|
59
|
+
/** Remove a watcher, by the callback passed to {@link watch} */
|
|
60
|
+
unwatch(callback: Watcher<T>): void;
|
|
49
61
|
|
|
50
|
-
/** Remove every
|
|
62
|
+
/** Remove every watcher */
|
|
51
63
|
unwatchAll(): void;
|
|
52
64
|
}
|
|
53
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Defer watcher notifications until `fn` returns; each watcher then runs
|
|
68
|
+
* once, with the final value. Batches nest. `fn` must be synchronous.
|
|
69
|
+
*/
|
|
70
|
+
export function batch<T>(fn: () => T): T;
|
|
71
|
+
|
|
54
72
|
/** Raised by the reactive primitives. */
|
|
55
73
|
export class StateError extends Error {
|
|
56
74
|
constructor(message: string, options?: Record<string, unknown>);
|
|
@@ -107,30 +125,37 @@ export interface HistoryEntry {
|
|
|
107
125
|
export class ReactiveState {
|
|
108
126
|
constructor(initialState?: Record<string, unknown>, options?: ReactiveStateOptions);
|
|
109
127
|
|
|
110
|
-
/** Current value of a key, or `undefined` */
|
|
128
|
+
/** Current value of a key or dot path (`'user.name'`), or `undefined` */
|
|
111
129
|
get<T = unknown>(key: string): T | undefined;
|
|
112
130
|
|
|
113
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Write a key; `false` when middleware cancelled the write. A dot path
|
|
133
|
+
* writes a copy of the parent object, notifying watchers of both.
|
|
134
|
+
*/
|
|
114
135
|
set(key: string, value: unknown, options?: ReactiveStateOptions): boolean;
|
|
115
136
|
|
|
116
|
-
/** Whether the key exists */
|
|
137
|
+
/** Whether the key (or dot path) exists */
|
|
117
138
|
has(key: string): boolean;
|
|
118
139
|
|
|
119
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Drop a key (or dot path) and the key's watchers; computed properties that
|
|
142
|
+
* read it update. `false` when it was absent.
|
|
143
|
+
*/
|
|
120
144
|
delete(key: string): boolean;
|
|
121
145
|
|
|
122
146
|
/** Drop every key */
|
|
123
147
|
clear(): void;
|
|
124
148
|
|
|
125
149
|
/** Define a computed property derived from other keys */
|
|
126
|
-
computed(key: string, getter: () =>
|
|
150
|
+
computed<T = unknown>(key: string, getter: () => T, options?: ObservableOptions): Observable<T>;
|
|
127
151
|
|
|
128
152
|
/** Current value of a computed property, or `undefined` */
|
|
129
153
|
getComputed<T = unknown>(key: string): T | undefined;
|
|
130
154
|
|
|
131
155
|
/**
|
|
132
|
-
* Watch one key, or a getter expression
|
|
133
|
-
*
|
|
156
|
+
* Watch one key, a dot path, or a getter expression (re-evaluated when what
|
|
157
|
+
* it reads changes). Returns an unwatch function, and throws
|
|
158
|
+
* {@link StateError} for a key that does not exist.
|
|
134
159
|
*/
|
|
135
160
|
watch<T = unknown>(
|
|
136
161
|
key: string | (() => T),
|
|
@@ -138,7 +163,10 @@ export class ReactiveState {
|
|
|
138
163
|
options?: { immediate?: boolean }
|
|
139
164
|
): () => void;
|
|
140
165
|
|
|
141
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Apply several writes as one: watchers run once afterwards, with the final
|
|
168
|
+
* values, and history records a single entry
|
|
169
|
+
*/
|
|
142
170
|
batch<T>(updates: ((state: this) => T) | Record<string, unknown>): T | undefined;
|
|
143
171
|
|
|
144
172
|
/** Subscribe to one or more keys; returns an unsubscribe function */
|
|
@@ -187,7 +215,9 @@ export function observable<T = unknown>(value: T, options?: ObservableOptions):
|
|
|
187
215
|
|
|
188
216
|
/**
|
|
189
217
|
* Create a read-only observable derived from other observables. Dependencies
|
|
190
|
-
* are tracked automatically; assigning to `value
|
|
218
|
+
* are tracked automatically and it recomputes lazily; assigning to `value`,
|
|
219
|
+
* or reading it from its own getter (directly or through others), throws
|
|
220
|
+
* {@link StateError}.
|
|
191
221
|
*/
|
|
192
222
|
export function computed<T = unknown>(
|
|
193
223
|
getter: () => T,
|
|
@@ -265,32 +295,59 @@ export const globalStateManager: {
|
|
|
265
295
|
// ============================================================================
|
|
266
296
|
|
|
267
297
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
298
|
+
* Run `fn` in a fresh, isolated context scope — one per request or render.
|
|
299
|
+
* On Node (AsyncLocalStorage) the scope follows `fn`'s async work, so wrap
|
|
300
|
+
* each request, and each consumer of a streaming render, in it. `values`
|
|
301
|
+
* seeds the scope.
|
|
302
|
+
*/
|
|
303
|
+
export function runWithContext<T>(fn: () => T, values?: Record<string, unknown>): T;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Push a context value for the rest of the current runWithContext() scope,
|
|
307
|
+
* remembering the previous one so {@link restoreContext} can unwind it. On
|
|
308
|
+
* Node it throws outside runWithContext(), where the value would leak into
|
|
309
|
+
* other requests.
|
|
270
310
|
*/
|
|
271
311
|
export function provideContext(key: string, value: unknown): void;
|
|
272
312
|
|
|
313
|
+
/** A context provider created by {@link createContextProvider}. */
|
|
314
|
+
export interface ContextProvider<C = unknown> {
|
|
315
|
+
/**
|
|
316
|
+
* Called by the renderer as a zero-argument component: returns the children
|
|
317
|
+
* with their function components and function-valued props evaluated in
|
|
318
|
+
* the context.
|
|
319
|
+
*/
|
|
320
|
+
(): C;
|
|
321
|
+
/**
|
|
322
|
+
* Run `renderFunction(children)` with the context provided. On Node an
|
|
323
|
+
* async render function keeps the context across its awaits.
|
|
324
|
+
*/
|
|
325
|
+
<R>(renderFunction: (children: C) => R): R;
|
|
326
|
+
}
|
|
327
|
+
|
|
273
328
|
/**
|
|
274
|
-
* Wrap children in a context.
|
|
275
|
-
*
|
|
329
|
+
* Wrap children in a context. Place the result in a component tree; the
|
|
330
|
+
* children see `value`, their siblings do not.
|
|
276
331
|
*/
|
|
277
|
-
export function createContextProvider<C = unknown
|
|
332
|
+
export function createContextProvider<C = unknown>(
|
|
278
333
|
key: string,
|
|
279
334
|
value: unknown,
|
|
280
335
|
children: C
|
|
281
|
-
):
|
|
336
|
+
): ContextProvider<C>;
|
|
282
337
|
|
|
283
338
|
/** Pop one context value, restoring what {@link provideContext} replaced. */
|
|
284
339
|
export function restoreContext(key: string): void;
|
|
285
340
|
|
|
286
341
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* leak into the next.
|
|
342
|
+
* Drop every context provided in the current execution. Values set through
|
|
343
|
+
* {@link globalStateManager} are left alone.
|
|
290
344
|
*/
|
|
291
345
|
export function clearAllContexts(): void;
|
|
292
346
|
|
|
293
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* Read the current context value. Falls back to {@link globalStateManager}
|
|
349
|
+
* when no context was provided for `key`.
|
|
350
|
+
*/
|
|
294
351
|
export function useContext<T = unknown>(key: string): T | undefined;
|
|
295
352
|
|
|
296
353
|
// ============================================================================
|
|
@@ -300,8 +357,13 @@ export function useContext<T = unknown>(key: string): T | undefined;
|
|
|
300
357
|
/** Where persistent state is written. */
|
|
301
358
|
export type StorageKind = 'localStorage' | 'sessionStorage' | 'indexedDB' | 'memory';
|
|
302
359
|
|
|
303
|
-
/**
|
|
360
|
+
/**
|
|
361
|
+
* Storage backend contract. `set` resolves `true` once stored; rejecting or
|
|
362
|
+
* resolving `false` is reported through `onError`. An adapter whose
|
|
363
|
+
* `available` is `false` is never written to.
|
|
364
|
+
*/
|
|
304
365
|
export interface PersistenceAdapter {
|
|
366
|
+
available?: boolean;
|
|
305
367
|
get(key: string): Promise<string | null> | string | null;
|
|
306
368
|
set(key: string, value: string): Promise<boolean> | boolean;
|
|
307
369
|
remove(key: string): Promise<boolean> | boolean;
|
|
@@ -309,8 +371,14 @@ export interface PersistenceAdapter {
|
|
|
309
371
|
}
|
|
310
372
|
|
|
311
373
|
export interface PersistentStateOptions {
|
|
312
|
-
/**
|
|
374
|
+
/**
|
|
375
|
+
* Backend; defaults to `'localStorage'`. On the server (no `window`) the
|
|
376
|
+
* browser backends read and write nothing, since Web Storage there would be
|
|
377
|
+
* shared by every request.
|
|
378
|
+
*/
|
|
313
379
|
storage?: StorageKind;
|
|
380
|
+
/** Custom backend, used as is (also on the server); overrides `storage` */
|
|
381
|
+
adapter?: PersistenceAdapter | null;
|
|
314
382
|
/** Storage key; defaults to `'coherent-state'` */
|
|
315
383
|
key?: string;
|
|
316
384
|
/** Coalesce writes; defaults to `true` */
|
|
@@ -323,8 +391,12 @@ export interface PersistentStateOptions {
|
|
|
323
391
|
include?: string[] | null;
|
|
324
392
|
/** Persist everything except these keys */
|
|
325
393
|
exclude?: string[] | null;
|
|
326
|
-
/**
|
|
394
|
+
/**
|
|
395
|
+
* XOR-obfuscate the payload with `encryptionKey`. This is obfuscation, not
|
|
396
|
+
* encryption: the key ships to the browser. Never store secrets.
|
|
397
|
+
*/
|
|
327
398
|
encrypt?: boolean;
|
|
399
|
+
/** Required when `encrypt` is `true`; there is no default key */
|
|
328
400
|
encryptionKey?: string | null;
|
|
329
401
|
onSave?: ((state: Record<string, unknown>) => void) | null;
|
|
330
402
|
onLoad?: ((state: Record<string, unknown>) => void) | null;
|
|
@@ -332,11 +404,25 @@ export interface PersistentStateOptions {
|
|
|
332
404
|
/** Tag stored payloads with `version` and run `migrate` on mismatch */
|
|
333
405
|
versioning?: boolean;
|
|
334
406
|
version?: string;
|
|
335
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Receives the stored payload as serialized by `serialize`, returns it
|
|
409
|
+
* migrated (still serialized) for `deserialize`
|
|
410
|
+
*/
|
|
411
|
+
migrate?: ((serializedState: string, fromVersion: string, toVersion: string) => string) | null;
|
|
336
412
|
/** Discard stored state older than this many ms */
|
|
337
413
|
ttl?: number | null;
|
|
338
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* Mirror updates to other tabs holding the same `key`, over a
|
|
416
|
+
* BroadcastChannel named after it. Off on the server.
|
|
417
|
+
*/
|
|
339
418
|
crossTab?: boolean;
|
|
419
|
+
/** IndexedDB database name (`storage: 'indexedDB'`); defaults to `'coherent-db'` */
|
|
420
|
+
dbName?: string;
|
|
421
|
+
/**
|
|
422
|
+
* IndexedDB object store name (`storage: 'indexedDB'`); defaults to
|
|
423
|
+
* `'state'`. Added to an existing database in a version upgrade.
|
|
424
|
+
*/
|
|
425
|
+
storeName?: string;
|
|
340
426
|
}
|
|
341
427
|
|
|
342
428
|
/** A state container backed by storage. */
|
|
@@ -357,20 +443,31 @@ export interface PersistentState {
|
|
|
357
443
|
subscribe(
|
|
358
444
|
listener: (state: Record<string, unknown>, oldState: Record<string, unknown>) => void
|
|
359
445
|
): () => void;
|
|
360
|
-
/** Force an immediate write */
|
|
361
|
-
persist(): Promise<
|
|
446
|
+
/** Force an immediate write; `false` when nothing was stored */
|
|
447
|
+
persist(): Promise<boolean>;
|
|
362
448
|
/** Reload from storage; `false` when nothing was stored */
|
|
363
449
|
restore(): Promise<boolean>;
|
|
364
450
|
/** Remove the stored payload */
|
|
365
451
|
clearStorage(): Promise<void>;
|
|
366
452
|
load(): Promise<Record<string, unknown> | null>;
|
|
367
|
-
|
|
453
|
+
/** Write now; `false` when nothing was stored (failures go to `onError`) */
|
|
454
|
+
save(): Promise<boolean>;
|
|
455
|
+
/**
|
|
456
|
+
* Flush a pending debounced save, close the cross-tab channel and drop
|
|
457
|
+
* listeners.
|
|
458
|
+
*/
|
|
459
|
+
destroy(): Promise<void>;
|
|
460
|
+
/**
|
|
461
|
+
* Settles once the automatic restore on creation is done: `true` when
|
|
462
|
+
* stored state was restored. Keys set before then keep their new value.
|
|
463
|
+
*/
|
|
464
|
+
readonly ready: Promise<boolean>;
|
|
368
465
|
readonly adapter: PersistenceAdapter;
|
|
369
466
|
}
|
|
370
467
|
|
|
371
468
|
/**
|
|
372
469
|
* Create a state container that persists to storage. Unless the backend is
|
|
373
|
-
* `'memory'`, stored state is restored on creation.
|
|
470
|
+
* `'memory'`, stored state is restored on creation; await `ready` for it.
|
|
374
471
|
*/
|
|
375
472
|
export function createPersistentState(
|
|
376
473
|
initialState?: Record<string, unknown>,
|
|
@@ -450,7 +547,11 @@ export interface ValidatedStateOptions {
|
|
|
450
547
|
validators?: Record<string, Validator | Validator[]>;
|
|
451
548
|
/** Reject writes that fail validation */
|
|
452
549
|
strict?: boolean;
|
|
453
|
-
/**
|
|
550
|
+
/**
|
|
551
|
+
* Convert values to the declared type where that is unambiguous: numeric
|
|
552
|
+
* strings to numbers, `'true'`/`'false'`/`'1'`/`'0'` to booleans, numbers
|
|
553
|
+
* and booleans to strings. Anything else stays a type error.
|
|
554
|
+
*/
|
|
454
555
|
coerce?: boolean;
|
|
455
556
|
onError?: ((errors: ValidationError[]) => void) | null;
|
|
456
557
|
/** Validate on write; defaults to `true` */
|
|
@@ -458,7 +559,10 @@ export interface ValidatedStateOptions {
|
|
|
458
559
|
/** Validate on read; defaults to `false` */
|
|
459
560
|
validateOnGet?: boolean;
|
|
460
561
|
required?: string[];
|
|
461
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Permit keys an object schema's `properties` do not mention; defaults to
|
|
564
|
+
* `true`. A schema's own `additionalProperties: false` always rejects them.
|
|
565
|
+
*/
|
|
462
566
|
allowUnknown?: boolean;
|
|
463
567
|
}
|
|
464
568
|
|
|
@@ -578,7 +682,10 @@ export class ListState<T = unknown> {
|
|
|
578
682
|
export class ModalState<D = unknown, R = unknown> {
|
|
579
683
|
constructor(initialState?: Record<string, unknown>);
|
|
580
684
|
|
|
581
|
-
/**
|
|
685
|
+
/**
|
|
686
|
+
* Open with data; resolves once closed. Opening again while open replaces
|
|
687
|
+
* the modal, resolving the earlier promise with `null`.
|
|
688
|
+
*/
|
|
582
689
|
open(data?: D): Promise<R | null>;
|
|
583
690
|
/** Close, resolving the pending `open()` */
|
|
584
691
|
close(result?: R | null): void;
|