storion 0.2.3 → 0.3.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 +705 -573
- package/dist/async/async.d.ts +87 -0
- package/dist/async/async.d.ts.map +1 -0
- package/dist/async/index.d.ts +13 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +451 -0
- package/dist/async/types.d.ts +275 -0
- package/dist/async/types.d.ts.map +1 -0
- package/dist/collection.d.ts +42 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/core/container.d.ts +33 -2
- package/dist/core/container.d.ts.map +1 -1
- package/dist/core/createResolver.d.ts +47 -0
- package/dist/core/createResolver.d.ts.map +1 -0
- package/dist/core/equality.d.ts +23 -3
- package/dist/core/equality.d.ts.map +1 -1
- package/dist/core/fnWrapper.d.ts +54 -0
- package/dist/core/fnWrapper.d.ts.map +1 -0
- package/dist/core/pick.d.ts +6 -6
- package/dist/core/pick.d.ts.map +1 -1
- package/dist/core/store.d.ts +8 -8
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/storeContext.d.ts +63 -0
- package/dist/core/storeContext.d.ts.map +1 -0
- package/dist/devtools/controller.d.ts +4 -0
- package/dist/devtools/controller.d.ts.map +1 -0
- package/dist/devtools/index.d.ts +16 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/devtools/index.js +229 -0
- package/dist/devtools/middleware.d.ts +22 -0
- package/dist/devtools/middleware.d.ts.map +1 -0
- package/dist/devtools/types.d.ts +116 -0
- package/dist/devtools/types.d.ts.map +1 -0
- package/dist/devtools-panel/DevtoolsPanel.d.ts +17 -0
- package/dist/devtools-panel/DevtoolsPanel.d.ts.map +1 -0
- package/dist/devtools-panel/components/CompareModal.d.ts +10 -0
- package/dist/devtools-panel/components/CompareModal.d.ts.map +1 -0
- package/dist/devtools-panel/components/EventEntry.d.ts +14 -0
- package/dist/devtools-panel/components/EventEntry.d.ts.map +1 -0
- package/dist/devtools-panel/components/EventFilterBar.d.ts +10 -0
- package/dist/devtools-panel/components/EventFilterBar.d.ts.map +1 -0
- package/dist/devtools-panel/components/EventsTab.d.ts +15 -0
- package/dist/devtools-panel/components/EventsTab.d.ts.map +1 -0
- package/dist/devtools-panel/components/ResizeHandle.d.ts +8 -0
- package/dist/devtools-panel/components/ResizeHandle.d.ts.map +1 -0
- package/dist/devtools-panel/components/StoreEntry.d.ts +13 -0
- package/dist/devtools-panel/components/StoreEntry.d.ts.map +1 -0
- package/dist/devtools-panel/components/StoresTab.d.ts +12 -0
- package/dist/devtools-panel/components/StoresTab.d.ts.map +1 -0
- package/dist/devtools-panel/components/TabLayout.d.ts +48 -0
- package/dist/devtools-panel/components/TabLayout.d.ts.map +1 -0
- package/dist/devtools-panel/components/icons.d.ts +27 -0
- package/dist/devtools-panel/components/icons.d.ts.map +1 -0
- package/dist/devtools-panel/components/index.d.ts +15 -0
- package/dist/devtools-panel/components/index.d.ts.map +1 -0
- package/dist/devtools-panel/hooks.d.ts +23 -0
- package/dist/devtools-panel/hooks.d.ts.map +1 -0
- package/dist/devtools-panel/index.d.ts +25 -0
- package/dist/devtools-panel/index.d.ts.map +1 -0
- package/dist/devtools-panel/index.js +3326 -0
- package/dist/devtools-panel/mount.d.ts +41 -0
- package/dist/devtools-panel/mount.d.ts.map +1 -0
- package/dist/devtools-panel/styles.d.ts +50 -0
- package/dist/devtools-panel/styles.d.ts.map +1 -0
- package/dist/devtools-panel/types.d.ts +15 -0
- package/dist/devtools-panel/types.d.ts.map +1 -0
- package/dist/devtools-panel/utils.d.ts +21 -0
- package/dist/devtools-panel/utils.d.ts.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/is.d.ts +69 -0
- package/dist/is.d.ts.map +1 -0
- package/dist/react/create.d.ts +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +209 -33
- package/dist/react/useLocalStore.d.ts.map +1 -1
- package/dist/react/useStore.d.ts +2 -2
- package/dist/react/useStore.d.ts.map +1 -1
- package/dist/react/withStore.d.ts +140 -0
- package/dist/react/withStore.d.ts.map +1 -0
- package/dist/{index-rLf6DusB.js → store-XP2pujaJ.js} +537 -740
- package/dist/storion.js +740 -9
- package/dist/trigger.d.ts +40 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/types.d.ts +516 -50
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -1
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export declare const STORION_TYPE: unique symbol;
|
|
2
|
+
/**
|
|
3
|
+
* Kind identifiers for Storion objects.
|
|
4
|
+
* Used with STORION_SYMBOL for runtime type discrimination.
|
|
5
|
+
*/
|
|
6
|
+
export type StorionKind = "store.spec" | "store.action" | "container" | "store" | "focus" | "store.context" | "selector.context" | "async.meta";
|
|
7
|
+
/**
|
|
8
|
+
* Base interface for all Storion objects with runtime type discrimination.
|
|
9
|
+
*/
|
|
10
|
+
export interface StorionObject<K extends StorionKind = StorionKind> {
|
|
11
|
+
readonly [STORION_TYPE]: K;
|
|
12
|
+
}
|
|
1
13
|
/**
|
|
2
14
|
* Storion - Type-safe reactive state management
|
|
3
15
|
*
|
|
@@ -6,16 +18,14 @@
|
|
|
6
18
|
/**
|
|
7
19
|
* Base constraint for state objects.
|
|
8
20
|
*/
|
|
9
|
-
export
|
|
10
|
-
[key: string]: unknown;
|
|
11
|
-
}
|
|
21
|
+
export type StateBase = object | Record<string, unknown>;
|
|
12
22
|
/**
|
|
13
23
|
* Base constraint for actions.
|
|
14
24
|
*/
|
|
15
25
|
export interface ActionsBase {
|
|
16
26
|
[key: string]: (...args: any[]) => any;
|
|
17
27
|
}
|
|
18
|
-
export type EqualityShorthand = "strict" | "shallow" | "deep";
|
|
28
|
+
export type EqualityShorthand = "strict" | "shallow" | "shallow2" | "shallow3" | "deep";
|
|
19
29
|
/**
|
|
20
30
|
* Equality strategies for change detection.
|
|
21
31
|
*/
|
|
@@ -26,6 +36,120 @@ export type Equality<T = unknown> = EqualityShorthand | ((a: T, b: T) => boolean
|
|
|
26
36
|
export type EqualityMap<T> = {
|
|
27
37
|
[K in keyof T]?: Equality<T[K]>;
|
|
28
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Extracts nested object paths as dot-notation strings.
|
|
41
|
+
* Stops at arrays (no index support).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* type State = { profile: { name: string; address: { city: string } } };
|
|
45
|
+
* type P = StatePath<State>; // "profile" | "profile.name" | "profile.address" | "profile.address.city"
|
|
46
|
+
*/
|
|
47
|
+
export type StatePath<T, Prefix extends string = ""> = T extends object ? T extends unknown[] ? never : {
|
|
48
|
+
[K in keyof T & string]: NonNullable<T[K]> extends object ? NonNullable<T[K]> extends unknown[] ? `${Prefix}${K}` : `${Prefix}${K}` | StatePath<NonNullable<T[K]>, `${Prefix}${K}.`> : `${Prefix}${K}`;
|
|
49
|
+
}[keyof T & string] : never;
|
|
50
|
+
/**
|
|
51
|
+
* Gets the type at a nested path.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* type State = { profile: { address: { city: string } } };
|
|
55
|
+
* type City = PathValue<State, "profile.address.city">; // string
|
|
56
|
+
*/
|
|
57
|
+
export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<NonNullable<T[K]>, Rest> : never : P extends keyof T ? T[P] : never;
|
|
58
|
+
/**
|
|
59
|
+
* Non-nullable type utility.
|
|
60
|
+
*/
|
|
61
|
+
export type NonNullish<TValue, TFlag extends true | false = true> = TFlag extends true ? Exclude<TValue, undefined | null> : TValue;
|
|
62
|
+
/**
|
|
63
|
+
* Focus change event payload.
|
|
64
|
+
*/
|
|
65
|
+
export interface FocusChangeEvent<T> {
|
|
66
|
+
next: T;
|
|
67
|
+
prev: T;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Focus options for configuring getter/setter behavior.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // With fallback for nullable values
|
|
74
|
+
* const focus = ctx.focus("profile", {
|
|
75
|
+
* fallback: () => ({ name: "Guest" })
|
|
76
|
+
* });
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* // With custom equality for change detection
|
|
80
|
+
* const focus = ctx.focus("items", {
|
|
81
|
+
* equality: "shallow"
|
|
82
|
+
* });
|
|
83
|
+
*/
|
|
84
|
+
export interface FocusOptions<T> {
|
|
85
|
+
/**
|
|
86
|
+
* Fallback factory for when the focused value is nullish.
|
|
87
|
+
* Applied to both getter (returns fallback) and setter (reducer receives fallback).
|
|
88
|
+
*/
|
|
89
|
+
fallback?: () => NonNullish<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Equality strategy for change detection in on() listener.
|
|
92
|
+
* Defaults to strict equality (===).
|
|
93
|
+
*/
|
|
94
|
+
equality?: Equality<T>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Focus tuple: [getter, setter] with an on() method for subscribing to changes.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const [getName, setName] = ctx.focus("profile.name");
|
|
101
|
+
*
|
|
102
|
+
* // Get current value
|
|
103
|
+
* const name = getName();
|
|
104
|
+
*
|
|
105
|
+
* // Set value directly
|
|
106
|
+
* setName("Jane");
|
|
107
|
+
*
|
|
108
|
+
* // Set value with reducer (returns new value)
|
|
109
|
+
* setName(prev => prev.toUpperCase());
|
|
110
|
+
*
|
|
111
|
+
* // Set value with produce (immer-style, mutate draft)
|
|
112
|
+
* setName(draft => { draft.nested = "value"; });
|
|
113
|
+
*
|
|
114
|
+
* // Listen to changes
|
|
115
|
+
* const unsubscribe = ctx.focus("profile.name").on(({ next, prev }) => {
|
|
116
|
+
* console.log(`Name changed from ${prev} to ${next}`);
|
|
117
|
+
* });
|
|
118
|
+
*/
|
|
119
|
+
export type Focus<TValue> = [
|
|
120
|
+
/** Get the current value at the focused path */
|
|
121
|
+
getter: () => TValue,
|
|
122
|
+
/**
|
|
123
|
+
* Set the value at the focused path.
|
|
124
|
+
* - Direct value: `set(newValue)`
|
|
125
|
+
* - Reducer: `set(prev => newValue)` - returns new value
|
|
126
|
+
* - Produce: `set(draft => { draft.x = y })` - immer-style mutation, no return
|
|
127
|
+
*/
|
|
128
|
+
setter: (valueOrReducerOrProduce: TValue | ((prev: TValue) => TValue | void)) => void
|
|
129
|
+
] & {
|
|
130
|
+
readonly [STORION_TYPE]: "focus";
|
|
131
|
+
/**
|
|
132
|
+
* Subscribe to changes at the focused path.
|
|
133
|
+
* Uses the configured equality to determine if value has changed.
|
|
134
|
+
*
|
|
135
|
+
* @param listener - Called with { next, prev } when value changes
|
|
136
|
+
* @returns Unsubscribe function
|
|
137
|
+
*/
|
|
138
|
+
on(listener: (event: FocusChangeEvent<TValue>) => void): VoidFunction;
|
|
139
|
+
/**
|
|
140
|
+
* Create a new Focus relative to the current path.
|
|
141
|
+
*
|
|
142
|
+
* @param relativePath - Dot-notation path relative to current focus
|
|
143
|
+
* @param options - Focus options for the new focus
|
|
144
|
+
* @returns A new Focus at the combined path
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* const userFocus = focus("user");
|
|
148
|
+
* const addressFocus = userFocus.to("address");
|
|
149
|
+
* const cityFocus = userFocus.to("address.city");
|
|
150
|
+
*/
|
|
151
|
+
to<TChild>(relativePath: string, options?: FocusOptions<TChild>): Focus<TChild>;
|
|
152
|
+
};
|
|
29
153
|
/**
|
|
30
154
|
* Controls when a store instance is automatically disposed.
|
|
31
155
|
*
|
|
@@ -96,14 +220,32 @@ export type ReactiveActions<TActions extends ActionsBase> = {
|
|
|
96
220
|
[K in keyof TActions]: ReactiveAction<TActions, K>;
|
|
97
221
|
};
|
|
98
222
|
/**
|
|
99
|
-
* Store specification (definition).
|
|
223
|
+
* Store specification (definition) that is also a factory function.
|
|
100
224
|
* A spec describes HOW to create a store instance - it holds no state.
|
|
225
|
+
*
|
|
226
|
+
* Can be called directly as a factory: `spec(resolver) => instance`
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const counterSpec = store({ count: state(0) });
|
|
231
|
+
*
|
|
232
|
+
* // Use with container (recommended)
|
|
233
|
+
* const instance = container.get(counterSpec);
|
|
234
|
+
*
|
|
235
|
+
* // Or call directly as factory
|
|
236
|
+
* const instance = counterSpec(resolver);
|
|
237
|
+
* ```
|
|
101
238
|
*/
|
|
102
|
-
export interface StoreSpec<TState extends StateBase = StateBase, TActions extends ActionsBase = ActionsBase> {
|
|
239
|
+
export interface StoreSpec<TState extends StateBase = StateBase, TActions extends ActionsBase = ActionsBase> extends StorionObject<"store.spec"> {
|
|
103
240
|
/** Store name for debugging */
|
|
104
241
|
readonly name: string;
|
|
105
242
|
/** Store options (state, setup, lifetime, etc.) */
|
|
106
243
|
readonly options: StoreOptions<TState, TActions>;
|
|
244
|
+
/**
|
|
245
|
+
* Factory function - creates a new store instance.
|
|
246
|
+
* Called by container/resolver internally.
|
|
247
|
+
*/
|
|
248
|
+
(resolver: Resolver): StoreInstance<TState, TActions>;
|
|
107
249
|
}
|
|
108
250
|
/**
|
|
109
251
|
* A reusable mixin for store setup.
|
|
@@ -129,10 +271,61 @@ export type StoreMixin<TState extends StateBase, TResult, TArgs extends unknown[
|
|
|
129
271
|
* }, 0);
|
|
130
272
|
*/
|
|
131
273
|
export type SelectorMixin<TResult, TArgs extends unknown[] = []> = (context: SelectorContext, ...args: TArgs) => TResult;
|
|
274
|
+
/**
|
|
275
|
+
* Tuple returned by get() with both array destructuring and named properties.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* // Array destructuring
|
|
279
|
+
* const [state, actions] = get(counterSpec);
|
|
280
|
+
*
|
|
281
|
+
* // Named properties
|
|
282
|
+
* const tuple = get(counterSpec);
|
|
283
|
+
* tuple.state.count;
|
|
284
|
+
* tuple.actions.increment();
|
|
285
|
+
*/
|
|
286
|
+
export type StoreTuple<S extends StateBase, A extends ActionsBase> = readonly [
|
|
287
|
+
Readonly<S>,
|
|
288
|
+
A
|
|
289
|
+
] & {
|
|
290
|
+
readonly state: Readonly<S>;
|
|
291
|
+
readonly actions: A;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Update function with action creator.
|
|
295
|
+
*
|
|
296
|
+
* Can be called directly to update state, or use `.action()` to create
|
|
297
|
+
* action functions that wrap updates.
|
|
298
|
+
*/
|
|
299
|
+
export interface StoreUpdate<TState extends StateBase> {
|
|
300
|
+
/**
|
|
301
|
+
* Update state using Immer-style updater function.
|
|
302
|
+
*/
|
|
303
|
+
(updater: (draft: TState) => void): void;
|
|
304
|
+
/**
|
|
305
|
+
* Update state with partial object (shallow merge).
|
|
306
|
+
*/
|
|
307
|
+
(partial: Partial<TState>): void;
|
|
308
|
+
/**
|
|
309
|
+
* Create an action function that wraps an updater.
|
|
310
|
+
* Throws error if the updater returns a PromiseLike (async not supported).
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* // No arguments
|
|
314
|
+
* increment: update.action(draft => {
|
|
315
|
+
* draft.count++;
|
|
316
|
+
* }),
|
|
317
|
+
*
|
|
318
|
+
* // With arguments
|
|
319
|
+
* addItem: update.action((draft, name: string, price: number) => {
|
|
320
|
+
* draft.items.push({ name, price });
|
|
321
|
+
* }),
|
|
322
|
+
*/
|
|
323
|
+
action<TArgs extends unknown[]>(updater: (draft: TState, ...args: TArgs) => void): (...args: TArgs) => void;
|
|
324
|
+
}
|
|
132
325
|
/**
|
|
133
326
|
* Context provided to the setup() function.
|
|
134
327
|
*/
|
|
135
|
-
export interface StoreContext<TState extends StateBase> {
|
|
328
|
+
export interface StoreContext<TState extends StateBase = StateBase> extends StorionObject<"store.context"> {
|
|
136
329
|
/**
|
|
137
330
|
* Mutable reactive state proxy.
|
|
138
331
|
* Writes trigger subscriber notifications.
|
|
@@ -141,30 +334,93 @@ export interface StoreContext<TState extends StateBase> {
|
|
|
141
334
|
readonly state: TState;
|
|
142
335
|
/**
|
|
143
336
|
* Get another store's state and actions.
|
|
144
|
-
* Returns
|
|
337
|
+
* Returns tuple with both array destructuring and named properties.
|
|
145
338
|
* Creates dependency - store is created if not exists.
|
|
146
339
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
340
|
+
* @example
|
|
341
|
+
* // Array destructuring
|
|
342
|
+
* const [state, actions] = get(counterSpec);
|
|
343
|
+
*
|
|
344
|
+
* // Named properties
|
|
345
|
+
* const tuple = get(counterSpec);
|
|
346
|
+
* tuple.state.count;
|
|
149
347
|
*/
|
|
150
|
-
|
|
348
|
+
get<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreTuple<S, A>;
|
|
151
349
|
/**
|
|
152
|
-
*
|
|
350
|
+
* Get a service or factory instance.
|
|
351
|
+
* Creates and caches the instance using the factory function.
|
|
352
|
+
* Returns the instance directly (not a tuple).
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* const db = get(indexedDBService);
|
|
356
|
+
* await db.users.getAll();
|
|
357
|
+
*/
|
|
358
|
+
get<T>(factory: (...args: any[]) => T): T;
|
|
359
|
+
/**
|
|
360
|
+
* Create a child store instance that is automatically disposed
|
|
361
|
+
* when the parent store is disposed.
|
|
362
|
+
*
|
|
363
|
+
* Unlike `get()`, this returns the full StoreInstance with access to
|
|
364
|
+
* id, subscribe(), dispose(), etc.
|
|
365
|
+
*
|
|
366
|
+
* Use this when you need a store with the same lifecycle as the parent,
|
|
367
|
+
* or when you need full instance access.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* setup: (ctx) => {
|
|
371
|
+
* // Create a child store - disposed when parent disposes
|
|
372
|
+
* const childInstance = ctx.create(childSpec);
|
|
373
|
+
*
|
|
374
|
+
* return {
|
|
375
|
+
* getChildState: () => childInstance.state,
|
|
376
|
+
* disposeChild: () => childInstance.dispose(),
|
|
377
|
+
* };
|
|
378
|
+
* }
|
|
379
|
+
*/
|
|
380
|
+
create<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreInstance<S, A>;
|
|
381
|
+
/**
|
|
382
|
+
* Create a service or factory instance that is automatically disposed
|
|
383
|
+
* when the parent store is disposed (if the instance has a dispose method).
|
|
384
|
+
*
|
|
385
|
+
* Unlike `get()` which caches instances, `create()` always creates fresh instances.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* setup: (ctx) => {
|
|
389
|
+
* // Create a fresh service instance - disposed with parent
|
|
390
|
+
* const db = ctx.create(indexedDBService);
|
|
391
|
+
*
|
|
392
|
+
* return {
|
|
393
|
+
* clearData: () => db.clearAll(),
|
|
394
|
+
* };
|
|
395
|
+
* }
|
|
396
|
+
*/
|
|
397
|
+
create<T>(factory: (...args: any[]) => T): T;
|
|
398
|
+
/**
|
|
399
|
+
* Update state using Immer-style updater function or partial object.
|
|
400
|
+
*
|
|
401
|
+
* Also provides `.action()` to create action functions that wrap updates.
|
|
153
402
|
*
|
|
154
403
|
* @example
|
|
404
|
+
* // Direct update with updater function
|
|
155
405
|
* update(draft => {
|
|
156
406
|
* draft.items.push({ id: 1, name: 'New Item' });
|
|
157
407
|
* draft.count++;
|
|
158
408
|
* });
|
|
159
|
-
*/
|
|
160
|
-
update(updater: (draft: TState) => void): void;
|
|
161
|
-
/**
|
|
162
|
-
* Update state with partial object (shallow merge).
|
|
163
409
|
*
|
|
164
|
-
*
|
|
410
|
+
* // Direct update with partial object
|
|
165
411
|
* update({ count: 10, name: 'Updated' });
|
|
412
|
+
*
|
|
413
|
+
* // Create action with update.action()
|
|
414
|
+
* increment: update.action(draft => {
|
|
415
|
+
* draft.count++;
|
|
416
|
+
* }),
|
|
417
|
+
*
|
|
418
|
+
* // Action with arguments
|
|
419
|
+
* addItem: update.action((draft, name: string) => {
|
|
420
|
+
* draft.items.push({ id: Date.now(), name });
|
|
421
|
+
* }),
|
|
166
422
|
*/
|
|
167
|
-
update
|
|
423
|
+
update: StoreUpdate<TState>;
|
|
168
424
|
/**
|
|
169
425
|
* Check if state has been modified since setup completed.
|
|
170
426
|
*
|
|
@@ -183,15 +439,46 @@ export interface StoreContext<TState extends StateBase> {
|
|
|
183
439
|
*/
|
|
184
440
|
reset(): void;
|
|
185
441
|
/**
|
|
186
|
-
*
|
|
442
|
+
* Apply a mixin to compose reusable logic.
|
|
187
443
|
* Mixins receive the same context and can return actions or values.
|
|
188
444
|
* Only callable during setup phase.
|
|
189
445
|
*
|
|
190
446
|
* @example
|
|
191
|
-
* const counter = ctx.
|
|
192
|
-
* const multiplier = ctx.
|
|
447
|
+
* const counter = ctx.mixin(counterMixin);
|
|
448
|
+
* const multiplier = ctx.mixin(multiplyMixin, 2);
|
|
193
449
|
*/
|
|
194
|
-
|
|
450
|
+
mixin<TResult, TArgs extends unknown[]>(mixin: StoreMixin<TState, TResult, TArgs>, ...args: TArgs): TResult;
|
|
451
|
+
/**
|
|
452
|
+
* Create a lens-like accessor for a nested state path.
|
|
453
|
+
* Returns a [getter, setter] tuple with an on() method for subscribing to changes.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* // Basic usage - get/set value
|
|
457
|
+
* const [getName, setName] = focus("profile.name");
|
|
458
|
+
* const name = getName();
|
|
459
|
+
* setName("Jane");
|
|
460
|
+
*
|
|
461
|
+
* // With reducer
|
|
462
|
+
* setName(prev => prev.toUpperCase());
|
|
463
|
+
*
|
|
464
|
+
* // With fallback for nullable values
|
|
465
|
+
* const [getProfile, setProfile] = focus("profile", {
|
|
466
|
+
* fallback: () => ({ name: "Guest" })
|
|
467
|
+
* });
|
|
468
|
+
*
|
|
469
|
+
* // Subscribe to changes
|
|
470
|
+
* const unsubscribe = focus("profile.name").on(({ next, prev }) => {
|
|
471
|
+
* console.log(`Changed from ${prev} to ${next}`);
|
|
472
|
+
* });
|
|
473
|
+
*
|
|
474
|
+
* // Can be returned as actions
|
|
475
|
+
* return { nameFocus: focus("name"), profileFocus: focus("profile") };
|
|
476
|
+
*/
|
|
477
|
+
focus<P extends StatePath<TState>>(path: P): Focus<PathValue<TState, P>>;
|
|
478
|
+
focus<P extends StatePath<TState>>(path: P, options: FocusOptions<PathValue<TState, P>> & {
|
|
479
|
+
fallback: () => NonNullish<PathValue<TState, P>>;
|
|
480
|
+
}): Focus<NonNullish<PathValue<TState, P>>>;
|
|
481
|
+
focus<P extends StatePath<TState>>(path: P, options: FocusOptions<PathValue<TState, P>>): Focus<PathValue<TState, P>>;
|
|
195
482
|
}
|
|
196
483
|
/**
|
|
197
484
|
* Options for defining a store.
|
|
@@ -277,7 +564,7 @@ export interface StoreOptions<TState extends StateBase, TActions extends Actions
|
|
|
277
564
|
* Created and managed by the container.
|
|
278
565
|
* Provides access to state, actions, and lifecycle management.
|
|
279
566
|
*/
|
|
280
|
-
export interface StoreInstance<TState extends StateBase = StateBase, TActions extends ActionsBase = ActionsBase> {
|
|
567
|
+
export interface StoreInstance<TState extends StateBase = StateBase, TActions extends ActionsBase = ActionsBase> extends StorionObject<"store"> {
|
|
281
568
|
/** Unique identifier for this instance */
|
|
282
569
|
readonly id: string;
|
|
283
570
|
/** The store specification that created this instance */
|
|
@@ -286,7 +573,7 @@ export interface StoreInstance<TState extends StateBase = StateBase, TActions ex
|
|
|
286
573
|
readonly state: Readonly<TState>;
|
|
287
574
|
/** Bound actions with reactive last() method */
|
|
288
575
|
readonly actions: ReactiveActions<TActions>;
|
|
289
|
-
/** Store instances this store depends on (via
|
|
576
|
+
/** Store instances this store depends on (via get() in setup) */
|
|
290
577
|
readonly deps: readonly StoreInstance<any, any>[];
|
|
291
578
|
/**
|
|
292
579
|
* Subscribe to state changes.
|
|
@@ -404,55 +691,189 @@ export interface StoreInstance<TState extends StateBase = StateBase, TActions ex
|
|
|
404
691
|
* return instance;
|
|
405
692
|
* };
|
|
406
693
|
*/
|
|
407
|
-
export type StoreMiddleware = <S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>, next: (spec: StoreSpec<S, A>) => StoreInstance<S, A>) => StoreInstance<S, A>;
|
|
694
|
+
export type StoreMiddleware = <S extends StateBase = StateBase, A extends ActionsBase = ActionsBase>(spec: StoreSpec<S, A>, next: (spec: StoreSpec<S, A>) => StoreInstance<S, A>) => StoreInstance<S, A>;
|
|
408
695
|
/**
|
|
409
696
|
* Container options.
|
|
410
697
|
*/
|
|
411
698
|
export interface ContainerOptions {
|
|
412
|
-
/**
|
|
413
|
-
|
|
414
|
-
/** Default equality for stores */
|
|
415
|
-
defaultEquality?: Equality;
|
|
699
|
+
/** Auto dispose options for all stores */
|
|
700
|
+
autoDispose?: AutoDisposeOptions;
|
|
416
701
|
/** Middleware chain for intercepting store creation */
|
|
417
702
|
middleware?: StoreMiddleware[];
|
|
418
703
|
}
|
|
419
704
|
/**
|
|
420
|
-
*
|
|
421
|
-
*
|
|
705
|
+
* Factory function that creates an instance using the resolver.
|
|
706
|
+
* The factory itself is used as the cache key (by reference).
|
|
707
|
+
*
|
|
708
|
+
* StoreSpec is a specialized factory that returns StoreInstance.
|
|
709
|
+
*/
|
|
710
|
+
export type Factory<T = any> = (resolver: Resolver) => T;
|
|
711
|
+
/**
|
|
712
|
+
* Context passed to resolver middleware functions.
|
|
713
|
+
*/
|
|
714
|
+
export interface MiddlewareContext<T = any> {
|
|
715
|
+
/** The factory being invoked */
|
|
716
|
+
readonly factory: Factory<T>;
|
|
717
|
+
/** The resolver instance */
|
|
718
|
+
readonly resolver: Resolver;
|
|
719
|
+
/** Call the next middleware or the factory itself */
|
|
720
|
+
readonly next: () => T;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Resolver middleware function that can intercept factory creation.
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```ts
|
|
727
|
+
* const loggingMiddleware: Middleware = (ctx) => {
|
|
728
|
+
* console.log("Creating:", ctx.factory.name);
|
|
729
|
+
* const result = ctx.next();
|
|
730
|
+
* console.log("Created:", result);
|
|
731
|
+
* return result;
|
|
732
|
+
* };
|
|
733
|
+
*
|
|
734
|
+
* // Type-specific middleware using is() guards
|
|
735
|
+
* const storeMiddleware: Middleware = (ctx) => {
|
|
736
|
+
* if (is(ctx.factory, "store.spec")) {
|
|
737
|
+
* // Apply store-specific logic
|
|
738
|
+
* }
|
|
739
|
+
* return ctx.next();
|
|
740
|
+
* };
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
export type Middleware = <T>(ctx: MiddlewareContext<T>) => T;
|
|
744
|
+
/**
|
|
745
|
+
* Options for creating a resolver.
|
|
746
|
+
*/
|
|
747
|
+
export interface ResolverOptions {
|
|
748
|
+
/** Middleware to apply to factory creation */
|
|
749
|
+
middleware?: Middleware[];
|
|
750
|
+
/** Parent resolver for hierarchical lookup */
|
|
751
|
+
parent?: Resolver;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Resolver interface for factory-based dependency injection.
|
|
755
|
+
* Provides caching, override support, and scoped resolvers.
|
|
756
|
+
*
|
|
757
|
+
* StoreContainer extends this with store-specific lifecycle methods.
|
|
422
758
|
*/
|
|
423
|
-
export interface
|
|
759
|
+
export interface Resolver {
|
|
424
760
|
/**
|
|
425
|
-
* Get a
|
|
426
|
-
*
|
|
761
|
+
* Get or create a cached instance from a factory.
|
|
762
|
+
* Returns the same instance on subsequent calls.
|
|
427
763
|
*/
|
|
428
|
-
get<
|
|
764
|
+
get<T>(factory: Factory<T>): T;
|
|
429
765
|
/**
|
|
430
|
-
*
|
|
766
|
+
* Create a fresh instance from a factory (bypasses cache).
|
|
431
767
|
*/
|
|
432
|
-
|
|
768
|
+
create<T>(factory: Factory<T>): T;
|
|
433
769
|
/**
|
|
434
|
-
*
|
|
770
|
+
* Override a factory with a custom implementation.
|
|
771
|
+
* Useful for testing or environment-specific behavior.
|
|
772
|
+
* Clears the cached instance if one exists.
|
|
435
773
|
*/
|
|
436
|
-
|
|
774
|
+
set<T>(factory: Factory<T>, override: Factory<T>): void;
|
|
775
|
+
/**
|
|
776
|
+
* Check if a factory has a cached instance.
|
|
777
|
+
*/
|
|
778
|
+
has(factory: Factory): boolean;
|
|
779
|
+
/**
|
|
780
|
+
* Get a cached instance if it exists, otherwise return undefined.
|
|
781
|
+
* Does NOT create the instance if not cached.
|
|
782
|
+
*/
|
|
783
|
+
tryGet<T>(factory: Factory<T>): T | undefined;
|
|
784
|
+
/**
|
|
785
|
+
* Delete a cached instance.
|
|
786
|
+
* Returns true if an instance was deleted.
|
|
787
|
+
*/
|
|
788
|
+
delete(factory: Factory): boolean;
|
|
789
|
+
/**
|
|
790
|
+
* Clear all cached instances.
|
|
791
|
+
*/
|
|
792
|
+
clear(): void;
|
|
793
|
+
/**
|
|
794
|
+
* Create a child resolver that inherits from this resolver.
|
|
795
|
+
* Child can override factories without affecting parent.
|
|
796
|
+
*/
|
|
797
|
+
scope(options?: ResolverOptions): Resolver;
|
|
437
798
|
}
|
|
799
|
+
export type AutoDisposeOptions = {
|
|
800
|
+
/** Grace period in ms before disposing the store (default: 100) */
|
|
801
|
+
gracePeriodMs?: number;
|
|
802
|
+
};
|
|
438
803
|
/**
|
|
439
|
-
* Store container - manages store instances.
|
|
804
|
+
* Store container - manages store instances with resolver pattern.
|
|
440
805
|
*
|
|
441
806
|
* The container is responsible for:
|
|
442
807
|
* - Creating instances on first access (lazy)
|
|
443
808
|
* - Caching instances (singleton per spec)
|
|
444
809
|
* - Resolving dependencies between stores
|
|
445
810
|
* - Managing lifetime and disposal
|
|
811
|
+
* - Supporting DI via set() overrides
|
|
812
|
+
* - Scoped containers via scope()
|
|
813
|
+
*
|
|
814
|
+
* Note: StoreContainer has store-specific method signatures.
|
|
815
|
+
* For generic factory DI, use createResolver() instead.
|
|
446
816
|
*/
|
|
447
|
-
export interface StoreContainer extends
|
|
817
|
+
export interface StoreContainer extends StorionObject<"container"> {
|
|
818
|
+
/**
|
|
819
|
+
* Get a store instance by spec (cached).
|
|
820
|
+
* First call creates instance, subsequent calls return cached.
|
|
821
|
+
*/
|
|
822
|
+
get<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreInstance<S, A>;
|
|
823
|
+
/**
|
|
824
|
+
* Get a service or factory instance (cached).
|
|
825
|
+
* Returns the instance directly (not a StoreInstance).
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* const db = container.get(indexedDBService);
|
|
829
|
+
* await db.users.getAll();
|
|
830
|
+
*/
|
|
831
|
+
get<T>(factory: Factory<T>): T;
|
|
832
|
+
/**
|
|
833
|
+
* Get a store instance by its unique ID.
|
|
834
|
+
*/
|
|
835
|
+
get(id: string): StoreInstance<any, any> | undefined;
|
|
836
|
+
/**
|
|
837
|
+
* Create a fresh store instance (bypasses cache).
|
|
838
|
+
* Useful for child stores or temporary instances.
|
|
839
|
+
*/
|
|
840
|
+
create<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreInstance<S, A>;
|
|
841
|
+
/**
|
|
842
|
+
* Create a fresh factory instance (bypasses cache).
|
|
843
|
+
* Returns the instance directly.
|
|
844
|
+
*/
|
|
845
|
+
create<T>(factory: Factory<T>): T;
|
|
846
|
+
/**
|
|
847
|
+
* Override a spec with a custom implementation.
|
|
848
|
+
* Useful for testing or environment-specific behavior.
|
|
849
|
+
*/
|
|
850
|
+
set<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>, override: StoreSpec<S, A>): void;
|
|
851
|
+
/**
|
|
852
|
+
* Check if a store instance is cached.
|
|
853
|
+
*/
|
|
854
|
+
has(spec: StoreSpec<any, any>): boolean;
|
|
855
|
+
/**
|
|
856
|
+
* Get cached instance if exists, otherwise undefined.
|
|
857
|
+
* Does NOT create the instance.
|
|
858
|
+
*/
|
|
859
|
+
tryGet<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreInstance<S, A> | undefined;
|
|
448
860
|
/**
|
|
449
|
-
*
|
|
861
|
+
* Remove a cached instance.
|
|
862
|
+
*/
|
|
863
|
+
delete(spec: StoreSpec<any, any>): boolean;
|
|
864
|
+
/**
|
|
865
|
+
* Clear all cached instances (disposes them).
|
|
450
866
|
*/
|
|
451
867
|
clear(): void;
|
|
452
868
|
/**
|
|
453
869
|
* Dispose a specific store instance.
|
|
454
870
|
*/
|
|
455
871
|
dispose(spec: StoreSpec<any, any>): boolean;
|
|
872
|
+
/**
|
|
873
|
+
* Create a child container that inherits from this one.
|
|
874
|
+
* Child can override specs without affecting parent.
|
|
875
|
+
*/
|
|
876
|
+
scope(options?: ContainerOptions): StoreContainer;
|
|
456
877
|
/**
|
|
457
878
|
* Subscribe to store creation events.
|
|
458
879
|
*/
|
|
@@ -468,25 +889,70 @@ export interface StoreContainer extends StoreResolver {
|
|
|
468
889
|
* Provides access to stores within a selector function.
|
|
469
890
|
* Returns tuple [readonlyState, actions] with tracking proxy.
|
|
470
891
|
*/
|
|
471
|
-
export interface SelectorContext {
|
|
892
|
+
export interface SelectorContext extends StorionObject<"selector.context"> {
|
|
472
893
|
/**
|
|
473
894
|
* Get a store's state and actions.
|
|
474
895
|
*
|
|
475
|
-
* Returns
|
|
476
|
-
* for fine-grained re-render optimization.
|
|
896
|
+
* Returns tuple with both array destructuring and named properties.
|
|
897
|
+
* Includes tracking proxy for fine-grained re-render optimization.
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* // Array destructuring
|
|
901
|
+
* const [state, actions] = get(counterSpec);
|
|
902
|
+
*
|
|
903
|
+
* // Named properties
|
|
904
|
+
* const tuple = get(counterSpec);
|
|
905
|
+
* tuple.state.count;
|
|
906
|
+
* tuple.actions.increment();
|
|
477
907
|
*
|
|
478
908
|
* @param spec - Store specification
|
|
479
|
-
* @returns Tuple of [trackingState, actions]
|
|
909
|
+
* @returns Tuple of [trackingState, actions] with .state and .actions props
|
|
480
910
|
*/
|
|
481
|
-
|
|
911
|
+
get<S extends StateBase, A extends ActionsBase>(spec: StoreSpec<S, A>): StoreTuple<S, A>;
|
|
482
912
|
/**
|
|
483
|
-
*
|
|
913
|
+
* Get a service or factory instance.
|
|
914
|
+
* Creates and caches the instance using the factory function.
|
|
915
|
+
* Returns the instance directly (not a tuple).
|
|
916
|
+
*
|
|
917
|
+
* @example
|
|
918
|
+
* const db = get(indexedDBService);
|
|
919
|
+
* await db.users.getAll();
|
|
920
|
+
*/
|
|
921
|
+
get<T>(factory: (...args: any[]) => T): T;
|
|
922
|
+
/**
|
|
923
|
+
* Apply a mixin to compose reusable selector logic.
|
|
484
924
|
* Mixins receive the same context and can return computed values.
|
|
485
925
|
*
|
|
486
926
|
* @example
|
|
487
|
-
* const total = ctx.
|
|
927
|
+
* const total = ctx.mixin(sumMixin, [store1, store2]);
|
|
928
|
+
*/
|
|
929
|
+
mixin<TResult, TArgs extends unknown[]>(mixin: SelectorMixin<TResult, TArgs>, ...args: TArgs): TResult;
|
|
930
|
+
/**
|
|
931
|
+
* Unique identifier for this selector context (per component instance).
|
|
932
|
+
* Useful for scoping operations with trigger().
|
|
933
|
+
*
|
|
934
|
+
* @example
|
|
935
|
+
* const { data } = useStore(({ get, id }) => {
|
|
936
|
+
* const store = get(dataStore);
|
|
937
|
+
* // Each component instance gets unique scope
|
|
938
|
+
* trigger(id, store.actions.dispatch, [params], params);
|
|
939
|
+
* return { data: store.state.data };
|
|
940
|
+
* });
|
|
941
|
+
*/
|
|
942
|
+
readonly id: object;
|
|
943
|
+
/**
|
|
944
|
+
* Run a callback once when the component mounts.
|
|
945
|
+
* The callback is executed immediately during render (before paint).
|
|
946
|
+
*
|
|
947
|
+
* @example
|
|
948
|
+
* useStore(({ get, once }) => {
|
|
949
|
+
* const mutation = get(submitStore);
|
|
950
|
+
* // Reset mutation state on mount
|
|
951
|
+
* once(() => mutation.actions.reset());
|
|
952
|
+
* return mutation;
|
|
953
|
+
* });
|
|
488
954
|
*/
|
|
489
|
-
|
|
955
|
+
once(callback: () => void): void;
|
|
490
956
|
}
|
|
491
957
|
/**
|
|
492
958
|
* Selector function type.
|