@yoltra/core 0.3.0 → 0.5.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.es.md +1 -1
- package/README.md +169 -7
- package/dist/types/entity/entityAdapter.d.ts +118 -0
- package/dist/types/eventBus/EventBus.d.ts +18 -5
- package/dist/types/eventBus/LooseEventBus.d.ts +72 -49
- package/dist/types/eventBus/index.d.ts +2 -2
- package/dist/types/index.d.ts +16 -8
- package/dist/types/persistence/adapters.d.ts +33 -0
- package/dist/types/persistence/persist.d.ts +126 -0
- package/dist/types/reducer/Reducer.d.ts +1 -1
- package/dist/types/serialize/codec.d.ts +119 -0
- package/dist/types/store/Store.d.ts +78 -15
- package/dist/types/types.d.ts +164 -32
- package/dist/types/utils/detectChangedProps.d.ts +6 -0
- package/dist/types/utils/immutability.d.ts +25 -2
- package/dist/types/utils/index.d.ts +2 -2
- package/dist/yoltra.cjs +11 -0
- package/dist/yoltra.cjs.map +1 -0
- package/dist/yoltra.mjs +2391 -0
- package/dist/yoltra.mjs.map +1 -0
- package/dist/yoltra.umd.js +2 -2
- package/dist/yoltra.umd.js.map +1 -0
- package/package.json +33 -13
- package/dist/yoltra.cjs.js +0 -11
- package/dist/yoltra.esm.js +0 -1744
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saving state, and starting from saved state.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The two halves happen on opposite sides of the store's existence, which is why this is two
|
|
6
|
+
* functions rather than one. {@link hydrate} produces *initial slice state*, so the store is
|
|
7
|
+
* born hydrated; {@link persist} subscribes to a store that already exists.
|
|
8
|
+
*
|
|
9
|
+
* Restoring after construction is the obvious alternative and the wrong one. It means applying
|
|
10
|
+
* a whole-state snapshot to a live store, which emits a change across every path: a visible
|
|
11
|
+
* flash on boot, a burst of instrumentation entries describing changes nobody made, and
|
|
12
|
+
* effects observing a transition that never happened.
|
|
13
|
+
*
|
|
14
|
+
* @module @yoltra/core
|
|
15
|
+
*/
|
|
16
|
+
/** Where persisted state lives. Bring your own; core imports no platform global. */
|
|
17
|
+
export interface PersistenceAdapter {
|
|
18
|
+
read(key: string): string | null | Promise<string | null>;
|
|
19
|
+
write(key: string, value: string): void | Promise<void>;
|
|
20
|
+
remove(key: string): void | Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** Where a failure happened, so a handler can tell a bad write from a bad payload. */
|
|
23
|
+
export type PersistencePhase = "read" | "write" | "decode" | "migrate";
|
|
24
|
+
/** Shared configuration. */
|
|
25
|
+
export interface PersistOptions {
|
|
26
|
+
/** Storage key. */
|
|
27
|
+
readonly key: string;
|
|
28
|
+
readonly adapter: PersistenceAdapter;
|
|
29
|
+
/**
|
|
30
|
+
* Schema version of what is written.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Compared on read. A mismatch is handed to {@link PersistOptions.migrate}, and without one
|
|
34
|
+
* the stored value is discarded rather than trusted — reducers change, and a snapshot
|
|
35
|
+
* written against an older shape is not merely stale, it may not be valid state at all.
|
|
36
|
+
*/
|
|
37
|
+
readonly version: number;
|
|
38
|
+
/** Slices to persist. Every slice by default. */
|
|
39
|
+
readonly slices?: readonly string[];
|
|
40
|
+
/** Coalescing window for writes, in milliseconds. Defaults to 250. */
|
|
41
|
+
readonly throttleMs?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Upgrades a payload written by an older version.
|
|
44
|
+
*
|
|
45
|
+
* @returns The slices to restore, or `null` to start fresh.
|
|
46
|
+
*/
|
|
47
|
+
readonly migrate?: (persisted: unknown, from: number) => Record<string, unknown> | null;
|
|
48
|
+
/**
|
|
49
|
+
* Called on any failure.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Persistence never throws into the application it is persisting. A store that will not
|
|
53
|
+
* start because storage holds stale JSON is worse than one that starts fresh, and a full
|
|
54
|
+
* disk should not take down a page.
|
|
55
|
+
*/
|
|
56
|
+
readonly onError?: (error: unknown, phase: PersistencePhase) => void;
|
|
57
|
+
}
|
|
58
|
+
/** What {@link hydrate} recovered. */
|
|
59
|
+
export interface Hydration {
|
|
60
|
+
/** Slice states to start from. Empty when there was nothing usable to restore. */
|
|
61
|
+
readonly slices: Readonly<Record<string, unknown>>;
|
|
62
|
+
/** `true` when a payload was found, decoded and accepted. */
|
|
63
|
+
readonly restored: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reads persisted state, ready to seed a store.
|
|
67
|
+
*
|
|
68
|
+
* @remarks
|
|
69
|
+
* Every read-side failure — missing, unparseable, wrong version with no migration, a
|
|
70
|
+
* migration that declines — resolves to "nothing to restore" and reports through
|
|
71
|
+
* {@link PersistOptions.onError}. Nothing throws.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const hydration = await hydrate({ key: 'app', adapter, version: 3 });
|
|
76
|
+
* const store = createStore({
|
|
77
|
+
* name: 'App',
|
|
78
|
+
* reducer: withHydration({ todos: todosSpec }, hydration),
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function hydrate(options: PersistOptions & {
|
|
85
|
+
readonly source?: string;
|
|
86
|
+
}): Promise<Hydration>;
|
|
87
|
+
/** A reducer spec, as far as hydration cares: something carrying an initial `state`. */
|
|
88
|
+
interface HasState {
|
|
89
|
+
state: unknown;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Replaces each reducer's initial state with what was restored for it.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Slices absent from the payload keep their declared defaults, so adding a reducer does not
|
|
96
|
+
* invalidate everything written before it existed.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare function withHydration<R extends Record<string, HasState>>(reducers: R, hydration: Hydration): R;
|
|
101
|
+
/** The store surface persistence needs, which is two methods wide. */
|
|
102
|
+
export interface PersistableStore {
|
|
103
|
+
getState(): unknown;
|
|
104
|
+
instrument(observer: (info: {
|
|
105
|
+
changedPaths?: readonly string[];
|
|
106
|
+
}) => void): () => void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Writes state as it changes.
|
|
110
|
+
*
|
|
111
|
+
* @returns A function that stops persisting and flushes anything pending.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* Driven by `instrument` rather than the coarse subscription, so a change confined to a slice
|
|
115
|
+
* that is not persisted costs nothing at all. Writes are coalesced on the trailing edge.
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export declare function persist(store: PersistableStore, options: PersistOptions): () => void;
|
|
120
|
+
/**
|
|
121
|
+
* Serializes a store for handoff, for example from a server render to the client.
|
|
122
|
+
*
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
125
|
+
export declare function dehydrate(store: Pick<PersistableStore, "getState">, options: Pick<PersistOptions, "version" | "slices">): string;
|
|
126
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventMapBase, EventUnion, ReducerFunction } from '../types';
|
|
1
|
+
import { EventMapBase, EventUnion, ReducerFunction } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Thin wrapper around a pure reducer function (stateful event consumer):
|
|
4
4
|
* given a state `S` and an event (from {@link EventUnion | `EventUnion<EM>`}),
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless encoding of store state for the wire.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The wire is JSON, and `JSON.stringify` is not a safe way to put arbitrary state on it. It does
|
|
6
|
+
* not fail on the values it cannot represent — it quietly destroys them. A `Map` becomes `{}`, a
|
|
7
|
+
* `Set` becomes `{}`, a `Date` becomes a string, `undefined` disappears from objects entirely,
|
|
8
|
+
* and a `BigInt` or a cycle throws from inside a handler nobody awaits.
|
|
9
|
+
*
|
|
10
|
+
* Silent destruction is the dangerous half. The panel showed `{}` where a `Map` lived, which is
|
|
11
|
+
* merely wrong; but time-travel then sent that `{}` back and applied it to the running store,
|
|
12
|
+
* replacing a live `Map` with an empty object in the user's own application. A debugging tool
|
|
13
|
+
* corrupting the program it is inspecting is the worst failure available to it.
|
|
14
|
+
*
|
|
15
|
+
* Values are therefore tagged rather than coerced. Anything JSON can carry travels unchanged;
|
|
16
|
+
* anything it cannot is wrapped in a marker object that {@link decodeState} reverses exactly.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Options for {@link encodeState}. */
|
|
21
|
+
export interface EncodeOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Redacts a value before it leaves the process.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* State frequently holds tokens, session material and personal data, and devtools traffic
|
|
27
|
+
* crosses a socket to another process. Return the replacement value, or the value itself to
|
|
28
|
+
* keep it. Applied before encoding, so a redacted value is encoded like any other.
|
|
29
|
+
*/
|
|
30
|
+
readonly sanitize?: (path: string, value: unknown) => unknown;
|
|
31
|
+
/**
|
|
32
|
+
* Maximum number of nodes to encode. Beyond it, subtrees are replaced by a truncation marker.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* A snapshot larger than the hub's frame cap is rejected outright, which reads to the user as
|
|
36
|
+
* a panel that hangs. Truncating visibly is a better failure: the panel renders, and says
|
|
37
|
+
* where it stopped. Defaults to 100000.
|
|
38
|
+
*/
|
|
39
|
+
readonly maxNodes?: number;
|
|
40
|
+
}
|
|
41
|
+
/** Reports what an encode had to compromise. Empty when nothing was lost. */
|
|
42
|
+
export interface EncodeReport {
|
|
43
|
+
/** Node budget was exhausted and some subtrees were replaced by markers. */
|
|
44
|
+
readonly truncated: boolean;
|
|
45
|
+
/** Values no JSON representation exists for, by path — functions, symbols, DOM nodes. */
|
|
46
|
+
readonly unsupported: readonly string[];
|
|
47
|
+
}
|
|
48
|
+
/** Result of {@link encodeState}. */
|
|
49
|
+
export interface EncodeResult {
|
|
50
|
+
readonly value: unknown;
|
|
51
|
+
readonly report: EncodeReport;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Encodes a value into something `JSON.stringify` can carry losslessly.
|
|
55
|
+
*
|
|
56
|
+
* @param input - Any value, including one holding `Map`, `Set`, `Date`, `BigInt` or cycles.
|
|
57
|
+
* @param options - Redaction and size limits.
|
|
58
|
+
* @returns The encoded value plus a report of anything that could not be represented.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const { value } = encodeState({ index: new Map([['a', 1]]) });
|
|
63
|
+
* JSON.stringify(value); // safe, and decodeState restores the Map
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export declare function encodeState(input: unknown, options?: EncodeOptions): EncodeResult;
|
|
69
|
+
/**
|
|
70
|
+
* Reverses {@link encodeState}.
|
|
71
|
+
*
|
|
72
|
+
* @param input - A value produced by `encodeState` (typically after a JSON round trip).
|
|
73
|
+
* @returns The original structure, with `Map`, `Set`, `Date` and friends restored.
|
|
74
|
+
*
|
|
75
|
+
* @remarks
|
|
76
|
+
* Unsupported markers decode to `undefined`: a function cannot be reconstructed, and inventing a
|
|
77
|
+
* placeholder would be worse than an absent value. Cycles are restored by resolving references
|
|
78
|
+
* after the tree is built, so a decoded structure is cyclic exactly where the original was.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
export declare function decodeState(input: unknown): unknown;
|
|
83
|
+
/** Outcome of {@link encodeStateBounded}. */
|
|
84
|
+
export interface BoundedEncodeResult {
|
|
85
|
+
/** The encoded value, small enough to send. */
|
|
86
|
+
readonly value: unknown;
|
|
87
|
+
/** `true` when the state did not fit and parts were replaced by markers. */
|
|
88
|
+
readonly truncated: boolean;
|
|
89
|
+
/** Explains what was dropped, for display beside a partial tree. */
|
|
90
|
+
readonly note?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Encodes a value, shrinking it until its serialized form fits within `maxBytes`.
|
|
94
|
+
*
|
|
95
|
+
* @param input - Any value.
|
|
96
|
+
* @param maxBytes - Byte budget for the serialized form.
|
|
97
|
+
* @param options - Passed through to {@link encodeState}.
|
|
98
|
+
*
|
|
99
|
+
* @returns The encoded value and whether anything had to be dropped.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* A frame larger than the hub's cap is not merely slow — it is rejected, and the connection with
|
|
103
|
+
* it, so the client reconnects, asks again, is refused again, and the panel sits waiting through
|
|
104
|
+
* a loop with nothing on screen to explain it. The size therefore has to be bounded before the
|
|
105
|
+
* frame is sent rather than discovered afterwards.
|
|
106
|
+
*
|
|
107
|
+
* Node count is a poor proxy for bytes: a hundred nodes holding base64 blobs outweigh a hundred
|
|
108
|
+
* thousand holding integers. So this measures the encoded output and, when it is too large,
|
|
109
|
+
* scales the node budget by how far over it went and measures again. Scaling by the overshoot
|
|
110
|
+
* rather than halving matters: from a default of a hundred thousand nodes, repeated halving
|
|
111
|
+
* needs a dozen rounds to reach the hundreds, so a state that could have been shown in part
|
|
112
|
+
* would have been abandoned instead.
|
|
113
|
+
*
|
|
114
|
+
* Truncation is reported rather than performed silently. A partial tree presented as the state is
|
|
115
|
+
* worse than no tree at all: a debugger that quietly lies about state is not a debugger.
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export declare function encodeStateBounded(input: unknown, maxBytes: number, options?: EncodeOptions): BoundedEncodeResult;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Event, EventMapBase, EventKey, EventUnion, Change, DeepReadonly, EffectSpec,
|
|
1
|
+
import { Event, EventMapBase, EventKey, EventUnion, Change, DeepReadonly, EffectSpec, EventMeta, MiddlewareInput, ReducersMapAny, ReducerSpec, StateFromReducers, StoreInstance, StoreSpec, Unsubscribe, EMFromReducersStrict, Emit, EmitOptions, InstrumentationObserver, EventPhase, NarrowedEventHandler, When } from '../types.js';
|
|
2
2
|
export declare class Store<EM extends EventMapBase, R extends string, S extends Record<R, any>> implements StoreInstance<R, S, EM> {
|
|
3
3
|
/**
|
|
4
4
|
* Store name (used by DevTools & diagnostics).
|
|
@@ -47,7 +47,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
47
47
|
private readonly listeners;
|
|
48
48
|
/**
|
|
49
49
|
* Registered effect handlers keyed by `"channel::type"` for O(1) lookup.
|
|
50
|
-
* Used for effects with explicit `keys`
|
|
50
|
+
* Used for effects with explicit `keys` targeting.
|
|
51
51
|
*
|
|
52
52
|
* @internal
|
|
53
53
|
*/
|
|
@@ -102,12 +102,33 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
102
102
|
* @internal
|
|
103
103
|
*/
|
|
104
104
|
private readonly replayEnabled;
|
|
105
|
+
/**
|
|
106
|
+
* Produces the `id` for each emitted event. Defaults to `crypto.randomUUID()`; overridable
|
|
107
|
+
* via {@link StoreSpec.idFactory} for runtimes lacking it or for deterministic tests.
|
|
108
|
+
*
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
private readonly idFactory;
|
|
105
112
|
/**
|
|
106
113
|
* Optional hook invoked when an effect throws/rejects. See
|
|
107
114
|
* {@link StoreSpec.onEffectError}. `await emit()` never rejects on effect
|
|
108
115
|
* failure — this is how callers observe effect errors.
|
|
109
116
|
*/
|
|
110
117
|
private readonly onEffectError?;
|
|
118
|
+
/**
|
|
119
|
+
* Optional hook invoked when a reducer throws. See {@link StoreSpec.onReducerError}. The
|
|
120
|
+
* failing slice is isolated rather than the event being rolled back, so this is the only
|
|
121
|
+
* signal that a reducer misbehaved.
|
|
122
|
+
*/
|
|
123
|
+
private readonly onReducerError?;
|
|
124
|
+
/**
|
|
125
|
+
* `slice:channel:type` combinations already warned about for payload aliasing.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* Development-only diagnostics have to stay quiet enough to be read. One warning names the
|
|
129
|
+
* pattern; repeating it once per event would bury it.
|
|
130
|
+
*/
|
|
131
|
+
private readonly warnedPayloadAliases;
|
|
111
132
|
/**
|
|
112
133
|
* Pending events awaiting the **synchronous** reduce phase (middleware +
|
|
113
134
|
* reducers + subscribers + coarse listeners). Drained by {@link drainReduce}.
|
|
@@ -317,6 +338,11 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
317
338
|
* For each changed **leaf path** (via {@link detectChangedProps}), emits that leaf and
|
|
318
339
|
* all of its **ancestors** once (e.g., `"data"`, `"data.123"`, `"data.123.title"`).
|
|
319
340
|
*
|
|
341
|
+
* A slice whose state **is** a single value — a primitive, a `Map`/`Set`, a `Date` — has no
|
|
342
|
+
* leaf below its root, and `detectChangedProps` reports its change as the empty path `""`.
|
|
343
|
+
* That path is emitted as-is, so `connect({ reducer, property: "" })` (and any `**` pattern)
|
|
344
|
+
* hears it. It has no ancestors to walk.
|
|
345
|
+
*
|
|
320
346
|
* **State Immutability**: When a slice changes, a new state object is created via
|
|
321
347
|
* shallow spread: `{ ...this.state, [sliceName]: newSlice }`. This ensures that
|
|
322
348
|
* `this.state` reference changes, enabling efficient change detection via `===`.
|
|
@@ -327,6 +353,30 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
327
353
|
*
|
|
328
354
|
* @internal
|
|
329
355
|
*/
|
|
356
|
+
/**
|
|
357
|
+
* Reduces one slice and contains any error it raises.
|
|
358
|
+
*
|
|
359
|
+
* @returns `true` when the slice changed.
|
|
360
|
+
*
|
|
361
|
+
* @remarks
|
|
362
|
+
* The single funnel both dispatch paths go through, which is the point. Keyed reducers run
|
|
363
|
+
* through `reducerBus`, whose handler loop caught and logged; pattern reducers were called
|
|
364
|
+
* straight from the drain, so their errors escaped to the caller instead. The same bug in the
|
|
365
|
+
* same reducer therefore produced two different outcomes depending on how the slice happened
|
|
366
|
+
* to be targeted — a keyed reducer's throw let the event commit and its effects run, while a
|
|
367
|
+
* pattern reducer's throw aborted the commit and notified nobody, not even the uncommitted
|
|
368
|
+
* subscribers a veto would have reached.
|
|
369
|
+
*
|
|
370
|
+
* The semantics are now the same either way: **the failing slice is isolated.** Its state is
|
|
371
|
+
* unchanged, every other slice still reduces, and the event still commits if anything else
|
|
372
|
+
* changed. Rolling the whole event back would be tidier in principle, but fine-grained
|
|
373
|
+
* subscribers are notified inside `forwardEvent` as each slice commits, so an event that
|
|
374
|
+
* reverted afterwards would have already told components about a value that no longer exists.
|
|
375
|
+
* Isolation keeps every notification truthful.
|
|
376
|
+
*
|
|
377
|
+
* @internal
|
|
378
|
+
*/
|
|
379
|
+
private forwardEventGuarded;
|
|
330
380
|
private forwardEvent;
|
|
331
381
|
/**
|
|
332
382
|
* Returns a structured introspection snapshot for DevTools UIs.
|
|
@@ -400,6 +450,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
400
450
|
type: string;
|
|
401
451
|
payload: any;
|
|
402
452
|
id: string;
|
|
453
|
+
meta?: EventMeta;
|
|
403
454
|
}>): void;
|
|
404
455
|
/**
|
|
405
456
|
* Emits a typed event `(channel, type, payload)`.
|
|
@@ -605,13 +656,21 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
605
656
|
/**
|
|
606
657
|
* Registers a middleware (runs **before** reducers).
|
|
607
658
|
*
|
|
608
|
-
* @param mw - Middleware `(state, event, emit) => boolean
|
|
609
|
-
*
|
|
659
|
+
* @param mw - Middleware `(state, event, emit) => boolean`. Return `false` to cancel event
|
|
660
|
+
* propagation.
|
|
610
661
|
* @returns Unsubscribe function that removes this middleware.
|
|
611
662
|
*
|
|
663
|
+
* @remarks
|
|
664
|
+
* **Synchronous, and that is the contract.** The reduce phase completes before `emit()`
|
|
665
|
+
* returns, so the commit decision has to be available in the same tick. An `async` middleware
|
|
666
|
+
* returns a Promise, every Promise is truthy, and the veto would therefore never fire — the
|
|
667
|
+
* event would commit while the middleware was still deciding. The type rejects it; this note
|
|
668
|
+
* exists because the examples here used to teach it. Do authorization and validation here, and
|
|
669
|
+
* anything that needs to await in an effect.
|
|
670
|
+
*
|
|
612
671
|
* @example Logging middleware
|
|
613
672
|
* ```ts
|
|
614
|
-
* const off = store.registerMiddleware(
|
|
673
|
+
* const off = store.registerMiddleware((state, event) => {
|
|
615
674
|
* console.log('Event:', event.channel, event.type, event.payload);
|
|
616
675
|
* return true; // allow
|
|
617
676
|
* });
|
|
@@ -628,12 +687,12 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
628
687
|
*
|
|
629
688
|
* @public
|
|
630
689
|
*/
|
|
631
|
-
registerMiddleware(mw:
|
|
690
|
+
registerMiddleware(mw: MiddlewareInput<DeepReadonly<S>, EM>): Unsubscribe;
|
|
632
691
|
/**
|
|
633
692
|
* Dynamically **adds** a named slice reducer at runtime.
|
|
634
693
|
*
|
|
635
694
|
* @param name - New slice name (must not already exist).
|
|
636
|
-
* @param spec - Reducer spec (state,
|
|
695
|
+
* @param spec - Reducer spec (state, when, reducer).
|
|
637
696
|
* @returns Disposer function that **removes** the slice (and its state).
|
|
638
697
|
*
|
|
639
698
|
* @example
|
|
@@ -657,7 +716,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
657
716
|
*
|
|
658
717
|
* Effects are **keyed** by `(channel, type)` for O(1) lookup (no scanning all effects).
|
|
659
718
|
*
|
|
660
|
-
* @param spec - Effect specification with `
|
|
719
|
+
* @param spec - Effect specification with `when` targeting and `effect` (handler).
|
|
661
720
|
* @returns Unsubscribe function.
|
|
662
721
|
*
|
|
663
722
|
* @example Logging effect
|
|
@@ -723,7 +782,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
723
782
|
*
|
|
724
783
|
* @public
|
|
725
784
|
*/
|
|
726
|
-
replaceMiddleware(next:
|
|
785
|
+
replaceMiddleware(next: MiddlewareInput<DeepReadonly<S>, EM>[]): void;
|
|
727
786
|
/**
|
|
728
787
|
* Replaces all registered **effects** (HMR-friendly).
|
|
729
788
|
*
|
|
@@ -780,7 +839,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
780
839
|
*/
|
|
781
840
|
hotReplace(partial: {
|
|
782
841
|
reducer?: Record<R, ReducerSpec<S[R], EM>>;
|
|
783
|
-
middleware?:
|
|
842
|
+
middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];
|
|
784
843
|
effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
|
|
785
844
|
preserveState?: boolean;
|
|
786
845
|
}): void;
|
|
@@ -789,7 +848,7 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
789
848
|
* and wires `(channel, type)` listeners on the reducer bus.
|
|
790
849
|
*
|
|
791
850
|
* @param name - Slice name.
|
|
792
|
-
* @param rSpec - Reducer spec (state,
|
|
851
|
+
* @param rSpec - Reducer spec (state, when, reducer).
|
|
793
852
|
* @param opts - `{ preserveState: boolean }` whether to keep existing state.
|
|
794
853
|
*
|
|
795
854
|
* @internal
|
|
@@ -806,9 +865,9 @@ export declare class Store<EM extends EventMapBase, R extends string, S extends
|
|
|
806
865
|
*/
|
|
807
866
|
private unmountSlice;
|
|
808
867
|
/**
|
|
809
|
-
* Normalizes event targeting from `when`
|
|
868
|
+
* Normalizes event targeting from `when` to an array of EventKeys.
|
|
810
869
|
*
|
|
811
|
-
* @param spec - Object with optional `when`
|
|
870
|
+
* @param spec - Object with an optional `when` matcher.
|
|
812
871
|
* @returns Array of `[channel, type]` pairs.
|
|
813
872
|
*
|
|
814
873
|
* @internal
|
|
@@ -887,13 +946,15 @@ export declare function createStore<S extends Record<string, any>, EM extends Ev
|
|
|
887
946
|
reducer?: {
|
|
888
947
|
[K in keyof S]?: ReducerSpec<S[K], EM>;
|
|
889
948
|
};
|
|
890
|
-
middleware?:
|
|
949
|
+
middleware?: MiddlewareInput<DeepReadonly<S>, EM>[];
|
|
891
950
|
effects?: Array<EffectSpec<DeepReadonly<S>, EM>>;
|
|
892
951
|
dedupWindowMs?: number;
|
|
952
|
+
idFactory?: () => string;
|
|
893
953
|
devtools?: {
|
|
894
954
|
allowReplay?: boolean;
|
|
895
955
|
};
|
|
896
956
|
onEffectError?: (error: unknown, event: EventUnion<EM>) => void;
|
|
957
|
+
onReducerError?: (error: unknown, event: EventUnion<EM>, slice: string) => void;
|
|
897
958
|
}): StoreInstance<keyof S & string, S, EM>;
|
|
898
959
|
/**
|
|
899
960
|
* Creates a store with types inferred from the reducers map.
|
|
@@ -926,13 +987,15 @@ export declare function createStore<S extends Record<string, any>, EM extends Ev
|
|
|
926
987
|
export declare function createStore<RM extends ReducersMapAny>(cfg: {
|
|
927
988
|
name: string;
|
|
928
989
|
reducer: RM;
|
|
929
|
-
middleware?:
|
|
990
|
+
middleware?: MiddlewareInput<DeepReadonly<StateFromReducers<RM>>, EMFromReducersStrict<RM>>[];
|
|
930
991
|
effects?: Array<EffectSpec<DeepReadonly<StateFromReducers<RM>>, EMFromReducersStrict<RM>>>;
|
|
931
992
|
dedupWindowMs?: number;
|
|
993
|
+
idFactory?: () => string;
|
|
932
994
|
devtools?: {
|
|
933
995
|
allowReplay?: boolean;
|
|
934
996
|
};
|
|
935
997
|
onEffectError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>) => void;
|
|
998
|
+
onReducerError?: (error: unknown, event: EventUnion<EMFromReducersStrict<RM>>, slice: string) => void;
|
|
936
999
|
}): StoreInstance<keyof RM & string, StateFromReducers<RM>, EMFromReducersStrict<RM>>;
|
|
937
1000
|
/**
|
|
938
1001
|
* Utility to define **typed** `(channel, events[])` definitions for reducer specs.
|