storion 0.17.0 → 0.18.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/CHANGELOG.md +130 -0
- package/dist/async/action.d.ts +32 -0
- package/dist/async/action.d.ts.map +1 -0
- package/dist/async/async.d.ts +65 -335
- package/dist/async/async.d.ts.map +1 -1
- package/dist/async/combine.d.ts +79 -0
- package/dist/async/combine.d.ts.map +1 -0
- package/dist/async/derive.d.ts +53 -0
- package/dist/async/derive.d.ts.map +1 -0
- package/dist/async/helpers.d.ts +84 -0
- package/dist/async/helpers.d.ts.map +1 -0
- package/dist/async/index.d.ts +2 -2
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +50 -16
- package/dist/async/mixin.d.ts +69 -0
- package/dist/async/mixin.d.ts.map +1 -0
- package/dist/async/state.d.ts +64 -0
- package/dist/async/state.d.ts.map +1 -0
- package/dist/async/then.d.ts +25 -0
- package/dist/async/then.d.ts.map +1 -0
- package/dist/async/types.d.ts +26 -7
- package/dist/async/types.d.ts.map +1 -1
- package/dist/async/utils.d.ts +69 -0
- package/dist/async/utils.d.ts.map +1 -0
- package/dist/async/wait.d.ts +30 -0
- package/dist/async/wait.d.ts.map +1 -0
- package/dist/async/wrappers.d.ts +95 -4
- package/dist/async/wrappers.d.ts.map +1 -1
- package/dist/async-D0oaIaAZ.js +753 -0
- package/dist/core/container.d.ts.map +1 -1
- package/dist/core/middleware.d.ts +3 -3
- package/dist/core/mixins.d.ts +111 -0
- package/dist/core/mixins.d.ts.map +1 -0
- package/dist/core/store.d.ts +6 -19
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/storeInstance.d.ts +13 -0
- package/dist/core/storeInstance.d.ts.map +1 -0
- package/dist/core/storeSpec.d.ts +13 -0
- package/dist/core/storeSpec.d.ts.map +1 -0
- package/dist/devtools/index.js +2 -2
- package/dist/{effect-CAIKjYsI.js → effect-YVnKUiyL.js} +399 -911
- package/dist/{emitter-j4rC71vY.js → emitter-Blea9fPg.js} +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/{index-QTI1wZAW.js → index-ZfO5JJi7.js} +31 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/meta/meta.d.ts +44 -10
- package/dist/meta/meta.d.ts.map +1 -1
- package/dist/meta/withMeta.d.ts +8 -1
- package/dist/meta/withMeta.d.ts.map +1 -1
- package/dist/{meta-40r-AZfe.js → meta-BW3y6_f6.js} +6 -0
- package/dist/network/index.js +143 -0
- package/dist/network/utils.d.ts.map +1 -1
- package/dist/persist/index.js +6 -1
- package/dist/persist/persist.d.ts +8 -11
- package/dist/persist/persist.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +276 -261
- package/dist/react/stable.d.ts.map +1 -1
- package/dist/react/strictMode.d.ts +15 -10
- package/dist/react/strictMode.d.ts.map +1 -1
- package/dist/react/strictModeTest.d.ts +12 -0
- package/dist/react/strictModeTest.d.ts.map +1 -0
- package/dist/react/useStore.d.ts +1 -53
- package/dist/react/useStore.d.ts.map +1 -1
- package/dist/storion.js +23 -19
- package/dist/types.d.ts +34 -93
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Updated React peer dependency requirement from `^18.0.0 || ^19.0.0` and development dependencies to React 19. The library now fully supports React 19's improved Suspense behavior and concurrent rendering.
|
|
13
|
+
|
|
14
|
+
- Fixed `isNetworkError()` to correctly detect `DOMException` errors in environments where `DOMException` doesn't extend `Error` (e.g., jsdom).
|
|
15
|
+
|
|
16
|
+
- **BREAKING**: Store `meta` property now accepts either a single `MetaEntry` or `meta.of(...)` for multiple entries (instead of raw arrays).
|
|
17
|
+
```ts
|
|
18
|
+
// Single meta - no change needed
|
|
19
|
+
meta: persist(),
|
|
20
|
+
|
|
21
|
+
// Multiple metas - use meta.of() instead of array
|
|
22
|
+
meta: meta.of(persist(), notPersisted.for("password")),
|
|
23
|
+
```
|
|
24
|
+
|
|
10
25
|
### Added
|
|
11
26
|
|
|
27
|
+
- Custom `StrictMode` component and `useStrictMode()` hook for detecting React StrictMode. Use `StrictMode` from `storion/react` instead of React's built-in one to enable proper handling of double rendering and effect calling:
|
|
28
|
+
```tsx
|
|
29
|
+
import { StoreProvider, StrictMode } from "storion/react";
|
|
30
|
+
|
|
31
|
+
// Use Storion's StrictMode for optimal strict mode handling
|
|
32
|
+
createRoot(document.getElementById("root")!).render(
|
|
33
|
+
<StrictMode>
|
|
34
|
+
<StoreProvider container={app}>
|
|
35
|
+
<App />
|
|
36
|
+
</StoreProvider>
|
|
37
|
+
</StrictMode>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Detect strict mode in components
|
|
41
|
+
import { useStrictMode } from "storion/react";
|
|
42
|
+
|
|
43
|
+
function MyComponent() {
|
|
44
|
+
const isStrictMode = useStrictMode();
|
|
45
|
+
// ...
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- `async.state()` now accepts a function overload for capturing synchronous execution results and Suspense patterns:
|
|
50
|
+
```ts
|
|
51
|
+
// With function - captures execution result
|
|
52
|
+
const pws = async.state(() => getValue());
|
|
53
|
+
console.log(pws.state.status); // "fulfilled" if returned, "rejected" if threw Error
|
|
54
|
+
|
|
55
|
+
// Suspense pattern - captures thrown promises
|
|
56
|
+
const pws = async.state(() => {
|
|
57
|
+
const cache = getFromCache(key);
|
|
58
|
+
if (!cache) throw fetchAndCache(key); // throws promise for Suspense
|
|
59
|
+
return cache;
|
|
60
|
+
});
|
|
61
|
+
console.log(pws.state.status); // "pending" if promise thrown, "fulfilled" if cached
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- `meta.of()` helper for type-safe arrays of metadata entries. Returns `{ metas: [...] }` for proper typing.
|
|
65
|
+
```ts
|
|
66
|
+
import { meta } from "storion";
|
|
67
|
+
|
|
68
|
+
const userStore = store({
|
|
69
|
+
state: { name: "", password: "" },
|
|
70
|
+
meta: meta.of(
|
|
71
|
+
persist(),
|
|
72
|
+
notPersisted.for("password"),
|
|
73
|
+
),
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `async.action()` now returns a `success(data)` method for directly setting state without executing the handler. Useful for optimistic updates, websocket/push data, SSR hydration, or testing. Respects `autoCancel` option: with `autoCancel: true` (default), cancels any in-flight request; with `autoCancel: false`, lets in-flight requests complete but prevents them from overwriting the manually set state.
|
|
78
|
+
```ts
|
|
79
|
+
const userQuery = async.action(focus('user'), fetchUser);
|
|
80
|
+
|
|
81
|
+
// Optimistic update
|
|
82
|
+
userQuery.success(optimisticData);
|
|
83
|
+
|
|
84
|
+
// Websocket push
|
|
85
|
+
websocket.on('user_updated', (data) => userQuery.success(data));
|
|
86
|
+
|
|
87
|
+
// SSR hydration
|
|
88
|
+
userQuery.success(window.__DATA__.user);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `observe()` wrapper for abortable functions to observe lifecycle events. The `onStart` callback can return an object with lifecycle callbacks: `onAbort`, `onSuccess`, `onError`, `onDone`.
|
|
92
|
+
```ts
|
|
93
|
+
import { abortable, observe } from "storion/async";
|
|
94
|
+
|
|
95
|
+
// Simple logging
|
|
96
|
+
const fetchUser = abortable(async (ctx, id: string) => { ... })
|
|
97
|
+
.use(observe((ctx, id) => {
|
|
98
|
+
console.log(`Fetching user ${id}`);
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
// Full lifecycle
|
|
102
|
+
const fetchData = abortable(async () => { ... })
|
|
103
|
+
.use(observe(() => ({
|
|
104
|
+
onAbort: () => console.log('Aborted'),
|
|
105
|
+
onSuccess: (data) => console.log('Success:', data),
|
|
106
|
+
onError: (err) => console.error('Error:', err),
|
|
107
|
+
onDone: () => console.log('Done'),
|
|
108
|
+
})));
|
|
109
|
+
|
|
110
|
+
// Loading indicator pattern
|
|
111
|
+
const fetchData = abortable(async () => { ... })
|
|
112
|
+
.use(observe(() => {
|
|
113
|
+
showLoading();
|
|
114
|
+
return { onDone: hideLoading };
|
|
115
|
+
}));
|
|
116
|
+
```
|
|
117
|
+
|
|
12
118
|
- `effect()` now automatically catches thrown promises (Suspense-like behavior). When `async.wait()` throws a promise inside an effect, the effect will automatically re-run when the promise resolves. Uses `ctx.nth` to detect staleness - if dependencies change before the promise resolves, the refresh is skipped. On promise rejection, the error is handled via `options.onError` (supports `keepAlive`, `failFast`, retry config, or custom handler).
|
|
13
119
|
```ts
|
|
14
120
|
effect(() => {
|
|
@@ -20,6 +126,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
126
|
});
|
|
21
127
|
```
|
|
22
128
|
|
|
129
|
+
- `mixins()` helper function for composing multiple mixins into a single selector. Supports array syntax for merging and object syntax for key mapping. Keys ending with "Mixin" suffix are automatically stripped.
|
|
130
|
+
```ts
|
|
131
|
+
import { useStore, mixins } from "storion/react";
|
|
132
|
+
|
|
133
|
+
// Object syntax - keys mapped to results, "Mixin" suffix stripped
|
|
134
|
+
const { t, language } = useStore(mixins({ tMixin, languageMixin }));
|
|
135
|
+
|
|
136
|
+
// Array syntax - merge multiple mixins
|
|
137
|
+
const data = useStore(mixins([userMixin, { count: countMixin }]));
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Changed
|
|
141
|
+
|
|
142
|
+
- **BREAKING**: Removed `useStore(MergeMixin)` and `useStore(MixinMap)` overloads. Use `useStore(mixins(...))` instead:
|
|
143
|
+
```ts
|
|
144
|
+
// Before
|
|
145
|
+
useStore({ tMixin, countMixin })
|
|
146
|
+
useStore([userMixin, { count: countMixin }])
|
|
147
|
+
|
|
148
|
+
// After
|
|
149
|
+
useStore(mixins({ tMixin, countMixin }))
|
|
150
|
+
useStore(mixins([userMixin, { count: countMixin }]))
|
|
151
|
+
```
|
|
152
|
+
|
|
23
153
|
### Fixed
|
|
24
154
|
|
|
25
155
|
- Store property subscriptions now correctly re-render when a property changes while its emitter temporarily has **0 listeners** (e.g. around render/commit timing windows), by buffering the last change and replaying it on the next subscription.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Focus } from '../types';
|
|
2
|
+
import { AsyncState, AsyncMode, AsyncHandler, AsyncOptions, AsyncActions } from './types';
|
|
3
|
+
import { Abortable } from './abortable';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal implementation for focus-based async.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function asyncWithFocus<T, M extends AsyncMode, TArgs extends any[]>(focus: Focus<AsyncState<T, M>>, handler: AsyncHandler<T, TArgs>, options?: AsyncOptions): AsyncActions<T, M, TArgs>;
|
|
10
|
+
/**
|
|
11
|
+
* Create async actions bound to a focus (lens) for async state management.
|
|
12
|
+
* Use *Query naming for read operations, *Mutation for write operations.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const userStore = store({
|
|
16
|
+
* name: 'user',
|
|
17
|
+
* state: { user: async.fresh<User>() },
|
|
18
|
+
* setup({ focus }) {
|
|
19
|
+
* const userQuery = async.action(focus('user'), async (ctx, id: string) => {
|
|
20
|
+
* const res = await fetch(`/api/users/${id}`, { signal: ctx.signal });
|
|
21
|
+
* return res.json();
|
|
22
|
+
* });
|
|
23
|
+
* return { fetchUser: userQuery.dispatch };
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
export declare function action<T, M extends AsyncMode, TArgs extends any[]>(focus: Focus<AsyncState<T, M>>, handler: AsyncHandler<T, TArgs>, options?: AsyncOptions): AsyncActions<T, M, TArgs>;
|
|
28
|
+
/**
|
|
29
|
+
* Create async actions with an Abortable (signal auto-injected).
|
|
30
|
+
*/
|
|
31
|
+
export declare function action<T, M extends AsyncMode, TArgs extends any[]>(focus: Focus<AsyncState<T, M>>, abortableFn: Abortable<TArgs, T>, options?: AsyncOptions): AsyncActions<T, M, TArgs>;
|
|
32
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../src/async/action.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAET,YAAY,EACZ,YAAY,EACZ,YAAY,EAKb,MAAM,SAAS,CAAC;AAIjB,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AA8B1D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,KAAK,SAAS,GAAG,EAAE,EACxE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC9B,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,EAC/B,OAAO,CAAC,EAAE,YAAY,GACrB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CA+T3B;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,KAAK,SAAS,GAAG,EAAE,EAChE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC9B,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,EAC/B,OAAO,CAAC,EAAE,YAAY,GACrB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;AAE7B;;GAEG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,KAAK,SAAS,GAAG,EAAE,EAChE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAC9B,WAAW,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,EAChC,OAAO,CAAC,EAAE,YAAY,GACrB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC"}
|
package/dist/async/async.d.ts
CHANGED
|
@@ -1,345 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { fresh, stale } from './state';
|
|
2
|
+
import { action } from './action';
|
|
3
|
+
import { mixin } from './mixin';
|
|
4
|
+
import { wait, hasData, isLoading, isError } from './wait';
|
|
5
|
+
import { all, any, race, settled } from './combine';
|
|
6
|
+
import { derive } from './derive';
|
|
7
|
+
import { chain } from './then';
|
|
8
|
+
import { delay, state } from './utils';
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Returns undefined if no pending promise.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getPendingPromise<T>(state: AsyncState<T, any>): Promise<T> | undefined;
|
|
10
|
-
/**
|
|
11
|
-
* Convert a value or parameterless function to a Promise.
|
|
12
|
-
* Handles: PromiseLike, sync values, functions returning either.
|
|
10
|
+
* Async module - reactive async state management.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* Wraps a synchronous or async function to always return a Promise.
|
|
24
|
-
* Ensures async execution even for synchronous functions.
|
|
25
|
-
*/
|
|
26
|
-
declare function promiseTry<T>(fn: () => T | PromiseLike<T>): Promise<Awaited<T>>;
|
|
27
|
-
/**
|
|
28
|
-
* Options for creating an async selector mixin.
|
|
12
|
+
* This module provides:
|
|
13
|
+
* - State creators: `async.fresh()`, `async.stale()`
|
|
14
|
+
* - Store-bound actions: `async.action()`
|
|
15
|
+
* - Component-local mixins: `async.mixin()`
|
|
16
|
+
* - Data extraction: `async.wait()`, `async.hasData()`, `async.isLoading()`, `async.isError()`
|
|
17
|
+
* - Combinators: `async.all()`, `async.any()`, `async.race()`, `async.settled()`
|
|
18
|
+
* - Derivation: `async.derive()`
|
|
19
|
+
* - Promise-like chaining: `async.chain()`
|
|
20
|
+
* - Utilities: `async.delay()`, `async.invoke()`, `async.state()`
|
|
29
21
|
*/
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Name of store for the async state. Defaults to `async:${handler.name || "anonymous"}`.
|
|
37
|
-
*/
|
|
38
|
-
name?: string;
|
|
39
|
-
/**
|
|
40
|
-
* Metadata for the async state. Defaults to empty array.
|
|
41
|
-
*/
|
|
42
|
-
meta?: MetaEntry<"result"> | MetaEntry<"result">[];
|
|
43
|
-
}
|
|
22
|
+
export type { AsyncMixinOptions, AsyncMixinResult } from './mixin';
|
|
23
|
+
export type { AsyncStateExtra } from './state';
|
|
24
|
+
export { asyncState, asyncStateFrom } from './state';
|
|
25
|
+
export { getPendingPromise } from './helpers';
|
|
26
|
+
export { toPromise } from './utils';
|
|
44
27
|
/**
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
export type AsyncMixinResult<T, M extends AsyncMode, TArgs extends any[]> = [
|
|
48
|
-
AsyncState<T, M>,
|
|
49
|
-
AsyncActions<T, M, TArgs>
|
|
50
|
-
];
|
|
51
|
-
/**
|
|
52
|
-
* Extra properties that can be added to async state.
|
|
53
|
-
* @internal
|
|
54
|
-
*/
|
|
55
|
-
interface AsyncStateExtra<T> {
|
|
56
|
-
__key?: AsyncKey<T>;
|
|
57
|
-
__requestId?: AsyncRequestId;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create a frozen AsyncState with the specified status.
|
|
61
|
-
* Users cannot modify properties directly - must use async actions.
|
|
62
|
-
*
|
|
63
|
-
* Overloads:
|
|
64
|
-
* - asyncState("fresh", "idle") - Fresh idle state
|
|
65
|
-
* - asyncState("fresh", "pending", extra?) - Fresh pending state
|
|
66
|
-
* - asyncState("fresh", "success", data) - Fresh success state
|
|
67
|
-
* - asyncState("fresh", "error", error, extra?) - Fresh error state
|
|
68
|
-
* - asyncState("stale", "idle", data) - Stale idle state
|
|
69
|
-
* - asyncState("stale", "pending", data, extra?) - Stale pending state
|
|
70
|
-
* - asyncState("stale", "success", data) - Stale success state
|
|
71
|
-
* - asyncState("stale", "error", data, error, extra?) - Stale error state
|
|
72
|
-
*/
|
|
73
|
-
export declare function asyncState<T = unknown>(mode: "fresh", status: "idle"): AsyncState<T, "fresh">;
|
|
74
|
-
export declare function asyncState<T = unknown>(mode: "fresh", status: "pending", extra?: AsyncStateExtra<T>): AsyncState<T, "fresh">;
|
|
75
|
-
export declare function asyncState<T>(mode: "fresh", status: "success", data: T): AsyncState<T, "fresh">;
|
|
76
|
-
export declare function asyncState<T = unknown>(mode: "fresh", status: "error", error: Error, extra?: AsyncStateExtra<T>): AsyncState<T, "fresh">;
|
|
77
|
-
export declare function asyncState<T>(mode: "stale", status: "idle", data: T): AsyncState<T, "stale">;
|
|
78
|
-
export declare function asyncState<T>(mode: "stale", status: "pending", data: T, extra?: AsyncStateExtra<T>): AsyncState<T, "stale">;
|
|
79
|
-
export declare function asyncState<T>(mode: "stale", status: "success", data: T): AsyncState<T, "stale">;
|
|
80
|
-
export declare function asyncState<T>(mode: "stale", status: "error", data: T, error: Error, extra?: AsyncStateExtra<T>): AsyncState<T, "stale">;
|
|
81
|
-
export declare namespace asyncState {
|
|
82
|
-
var from: typeof asyncStateFrom;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Create a new AsyncState based on a previous state, preserving mode and stale data.
|
|
86
|
-
* Useful for deriving new states while maintaining the mode semantics.
|
|
28
|
+
* Async namespace providing reactive async state management.
|
|
87
29
|
*
|
|
88
30
|
* @example
|
|
89
|
-
* //
|
|
90
|
-
* const
|
|
31
|
+
* // Create async state in store
|
|
32
|
+
* const userStore = store({
|
|
33
|
+
* name: 'user',
|
|
34
|
+
* state: { user: async.fresh<User>() },
|
|
35
|
+
* setup({ focus }) {
|
|
36
|
+
* const userQuery = async.action(focus('user'), async (ctx, id: string) => {
|
|
37
|
+
* const res = await fetch(`/api/users/${id}`, { signal: ctx.signal });
|
|
38
|
+
* return res.json();
|
|
39
|
+
* });
|
|
40
|
+
* return { fetchUser: userQuery.dispatch };
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
91
43
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Use async mixin in component
|
|
46
|
+
* const submitMutation = async.mixin(async (ctx, data: FormData) => {
|
|
47
|
+
* const res = await fetch('/api/submit', { method: 'POST', body: data });
|
|
48
|
+
* return res.json();
|
|
49
|
+
* });
|
|
94
50
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
51
|
+
* function Form() {
|
|
52
|
+
* const [state, { dispatch }] = useStore(({ mixin }) => mixin(submitMutation));
|
|
53
|
+
* return <button onClick={() => dispatch(formData)}>Submit</button>;
|
|
54
|
+
* }
|
|
97
55
|
*/
|
|
98
|
-
export declare
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* @example
|
|
118
|
-
* const userStore = store({
|
|
119
|
-
* name: 'user',
|
|
120
|
-
* state: { user: async.fresh<User>() },
|
|
121
|
-
* setup({ focus }) {
|
|
122
|
-
* const userQuery = async.action(focus('user'), async (ctx, id: string) => {
|
|
123
|
-
* const res = await fetch(`/api/users/${id}`, { signal: ctx.signal });
|
|
124
|
-
* return res.json();
|
|
125
|
-
* });
|
|
126
|
-
* return { fetchUser: userQuery.dispatch };
|
|
127
|
-
* },
|
|
128
|
-
* });
|
|
129
|
-
*/
|
|
130
|
-
function action<T, M extends AsyncMode, TArgs extends any[]>(focus: Focus<AsyncState<T, M>>, handler: AsyncHandler<T, TArgs>, options?: AsyncOptions): AsyncActions<T, M, TArgs>;
|
|
131
|
-
/**
|
|
132
|
-
* Create async actions with an Abortable (signal auto-injected).
|
|
133
|
-
*/
|
|
134
|
-
function action<T, M extends AsyncMode, TArgs extends any[]>(focus: Focus<AsyncState<T, M>>, abortableFn: Abortable<TArgs, T>, options?: AsyncOptions): AsyncActions<T, M, TArgs>;
|
|
135
|
-
/**
|
|
136
|
-
* Create an async selector mixin for component-local async state.
|
|
137
|
-
* Uses `scoped()` internally, so state is isolated per component and auto-disposed.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* const submitMutation = async.mixin(async (ctx, data: FormData) => {
|
|
141
|
-
* const res = await fetch('/api/submit', {
|
|
142
|
-
* method: 'POST',
|
|
143
|
-
* body: JSON.stringify(data),
|
|
144
|
-
* signal: ctx.signal,
|
|
145
|
-
* });
|
|
146
|
-
* return res.json();
|
|
147
|
-
* });
|
|
148
|
-
*
|
|
149
|
-
* function ContactForm() {
|
|
150
|
-
* const [state, { dispatch }] = useStore(({ mixin }) => mixin(submitMutation));
|
|
151
|
-
* return <button onClick={() => dispatch(formData)}>Submit</button>;
|
|
152
|
-
* }
|
|
153
|
-
*/
|
|
154
|
-
function mixin<T, TArgs extends any[]>(handler: AsyncHandler<T, TArgs>, options?: AsyncMixinOptions<T, "fresh">): SelectorMixin<AsyncMixinResult<T, "fresh", TArgs>>;
|
|
155
|
-
/**
|
|
156
|
-
* Create an async selector mixin with stale mode.
|
|
157
|
-
*/
|
|
158
|
-
function mixin<T, TArgs extends any[]>(handler: AsyncHandler<T, TArgs>, options: AsyncMixinOptions<T, "stale"> & {
|
|
159
|
-
initial: AsyncState<T, "stale">;
|
|
160
|
-
}): SelectorMixin<AsyncMixinResult<T, "stale", TArgs>>;
|
|
161
|
-
/**
|
|
162
|
-
* Create an async selector mixin with an Abortable (signal auto-injected).
|
|
163
|
-
*/
|
|
164
|
-
function mixin<T, TArgs extends any[]>(abortableFn: Abortable<TArgs, T>, options?: AsyncMixinOptions<T, "fresh">): SelectorMixin<AsyncMixinResult<T, "fresh", TArgs>>;
|
|
165
|
-
/**
|
|
166
|
-
* Create an async selector mixin with an Abortable and stale mode.
|
|
167
|
-
*/
|
|
168
|
-
function mixin<T, TArgs extends any[]>(abortableFn: Abortable<TArgs, T>, options: AsyncMixinOptions<T, "stale"> & {
|
|
169
|
-
initial: AsyncState<T, "stale">;
|
|
170
|
-
}): SelectorMixin<AsyncMixinResult<T, "stale", TArgs>>;
|
|
171
|
-
function delay<T = void>(ms: number, resolved?: T): CancellablePromise<T>;
|
|
172
|
-
/**
|
|
173
|
-
* Wraps a synchronous or async function to always return a Promise.
|
|
174
|
-
* Ensures async execution even for synchronous functions.
|
|
175
|
-
*
|
|
176
|
-
* This is the same utility used internally for dispatching handlers.
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* const promise = async.invoke(() => {
|
|
180
|
-
* // Sync or async code
|
|
181
|
-
* return someValue;
|
|
182
|
-
* });
|
|
183
|
-
*/
|
|
184
|
-
const invoke: typeof promiseTry;
|
|
185
|
-
/**
|
|
186
|
-
* Extract data from AsyncState, throws if not ready.
|
|
187
|
-
* - Success: returns data
|
|
188
|
-
* - Stale mode (idle/pending/error with data): returns stale data
|
|
189
|
-
* - Pending with promise: throws promise (for Suspense)
|
|
190
|
-
* - Otherwise: throws error or AsyncNotReadyError
|
|
191
|
-
*/
|
|
192
|
-
function wait<T, M extends AsyncMode>(state: AsyncState<T, M>): M extends "stale" ? T : T;
|
|
193
|
-
/**
|
|
194
|
-
* Returns the first successful result from an array or record of async states.
|
|
195
|
-
* Supports both AsyncState and PromiseWithState.
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```ts
|
|
199
|
-
* // Array form
|
|
200
|
-
* const [key, data] = async.race([state1, state2]);
|
|
201
|
-
*
|
|
202
|
-
* // Map form
|
|
203
|
-
* const [key, data] = async.race({ user: userState, posts: postsState });
|
|
204
|
-
* ```
|
|
205
|
-
*/
|
|
206
|
-
function race<const T extends readonly AsyncOrPromise[]>(states: T): [number, InferData<T[number]>];
|
|
207
|
-
function race<T extends Record<string, AsyncOrPromise>>(states: T): CombinedRaceResult<T>;
|
|
208
|
-
/**
|
|
209
|
-
* Get the state of a promise.
|
|
210
|
-
* @param promise - The promise to get the state of.
|
|
211
|
-
* @returns The state of the promise.
|
|
212
|
-
*/
|
|
213
|
-
function state<T>(promise: PromiseLike<T>): PromiseWithState<T>;
|
|
214
|
-
/**
|
|
215
|
-
* Returns all data if all states are ready.
|
|
216
|
-
* Supports both AsyncState and PromiseWithState, as array, record, or rest params.
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
219
|
-
* ```ts
|
|
220
|
-
* // Rest params (backward compatible)
|
|
221
|
-
* const [a, b, c] = async.all(state1, state2, state3);
|
|
222
|
-
*
|
|
223
|
-
* // Array form - returns tuple of data
|
|
224
|
-
* const [userData, postsData] = async.all([userState, postsState]);
|
|
225
|
-
*
|
|
226
|
-
* // Map form - returns record of data
|
|
227
|
-
* const { user, posts } = async.all({ user: userState, posts: postsState });
|
|
228
|
-
* ```
|
|
229
|
-
*/
|
|
230
|
-
function all<const T extends readonly AsyncOrPromise[]>(states: T): MapData<T>;
|
|
231
|
-
function all<T extends Record<string, AsyncOrPromise>>(states: T): MapRecordData<T>;
|
|
232
|
-
function all<const T extends readonly AsyncOrPromise[]>(...states: T): MapData<T>;
|
|
233
|
-
/**
|
|
234
|
-
* Returns the first ready data from multiple states.
|
|
235
|
-
* Supports both AsyncState and PromiseWithState, as array, record, or rest params.
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* ```ts
|
|
239
|
-
* // Rest params (backward compatible)
|
|
240
|
-
* const data = async.any(state1, state2, state3);
|
|
241
|
-
*
|
|
242
|
-
* // Array form
|
|
243
|
-
* const data = async.any([state1, state2, state3]);
|
|
244
|
-
*
|
|
245
|
-
* // Map form
|
|
246
|
-
* const data = async.any({ primary: primaryState, fallback: fallbackState });
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
function any<const T extends readonly AsyncOrPromise[]>(states: T): InferData<T[number]>;
|
|
250
|
-
function any<T extends Record<string, AsyncOrPromise>>(states: T): InferData<T[keyof T]>;
|
|
251
|
-
function any<const T extends readonly AsyncOrPromise[]>(...states: T): InferData<T[number]>;
|
|
252
|
-
/**
|
|
253
|
-
* Returns settled results for all states (never throws).
|
|
254
|
-
* Supports both AsyncState and PromiseWithState, as array, record, or rest params.
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* ```ts
|
|
258
|
-
* // Rest params (backward compatible)
|
|
259
|
-
* const results = async.settled(state1, state2);
|
|
260
|
-
*
|
|
261
|
-
* // Array form
|
|
262
|
-
* const results = async.settled([state1, state2]);
|
|
263
|
-
* // [{ status: "fulfilled", value: data1 }, { status: "rejected", reason: error }]
|
|
264
|
-
*
|
|
265
|
-
* // Map form
|
|
266
|
-
* const results = async.settled({ user: userState, posts: postsState });
|
|
267
|
-
* // { user: { status: "fulfilled", value: userData }, posts: { status: "pending" } }
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
function settled<const T extends readonly AsyncOrPromise[]>(states: T): MapCombinedSettledResult<T>;
|
|
271
|
-
function settled<T extends Record<string, AsyncOrPromise>>(states: T): {
|
|
272
|
-
[K in keyof T]: CombinedSettledResult<InferData<T[K]>>;
|
|
273
|
-
};
|
|
274
|
-
function settled<const T extends readonly AsyncOrPromise[]>(...states: T): MapCombinedSettledResult<T>;
|
|
275
|
-
/**
|
|
276
|
-
* Check if state has data available (success or stale).
|
|
277
|
-
*/
|
|
278
|
-
function hasData<T, M extends AsyncMode>(state: AsyncState<T, M>): state is AsyncState<T, M> & {
|
|
279
|
-
data: T;
|
|
280
|
-
};
|
|
281
|
-
/**
|
|
282
|
-
* Check if state is loading (pending status).
|
|
283
|
-
*/
|
|
284
|
-
function isLoading<T, M extends AsyncMode>(state: AsyncState<T, M>): state is AsyncState<T, M> & {
|
|
285
|
-
status: "pending";
|
|
286
|
-
};
|
|
287
|
-
/**
|
|
288
|
-
* Check if state has an error.
|
|
289
|
-
*/
|
|
290
|
-
function isError<T, M extends AsyncMode>(state: AsyncState<T, M>): state is AsyncState<T, M> & {
|
|
291
|
-
status: "error";
|
|
292
|
-
error: Error;
|
|
293
|
-
};
|
|
294
|
-
/**
|
|
295
|
-
* Derive an async state from other async states using a synchronous computation.
|
|
296
|
-
* The computation function uses `async.wait()` to extract data from async states,
|
|
297
|
-
* which throws promises when states are pending.
|
|
298
|
-
*
|
|
299
|
-
* Key behaviors:
|
|
300
|
-
* - If `computeFn` throws a promise (via `async.wait`), sets focus to pending and re-runs after promise settles
|
|
301
|
-
* - If `computeFn` throws an error, sets focus to error state
|
|
302
|
-
* - If `computeFn` returns a value, sets focus to success state
|
|
303
|
-
* - If `computeFn` returns a promise (not throws), throws an error - must be synchronous
|
|
304
|
-
* - Uses a single wrapper promise to avoid cascading re-renders
|
|
305
|
-
*
|
|
306
|
-
* @param focus - Focus to update with derived async state
|
|
307
|
-
* @param computeFn - Synchronous computation function that uses `async.wait()` to read async states
|
|
308
|
-
* @returns Dispose function to stop the derivation
|
|
309
|
-
*
|
|
310
|
-
* @example
|
|
311
|
-
* // Basic usage - derive from multiple async states
|
|
312
|
-
* async.derive(focus('c'), () => {
|
|
313
|
-
* const a = async.wait(state.a);
|
|
314
|
-
* const b = async.wait(state.b);
|
|
315
|
-
* return a + b;
|
|
316
|
-
* });
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* // Conditional dependencies
|
|
320
|
-
* async.derive(focus('result'), () => {
|
|
321
|
-
* const type = async.wait(state.type);
|
|
322
|
-
* if (type === 'user') {
|
|
323
|
-
* return async.wait(state.userData);
|
|
324
|
-
* } else {
|
|
325
|
-
* return async.wait(state.guestData);
|
|
326
|
-
* }
|
|
327
|
-
* });
|
|
328
|
-
*
|
|
329
|
-
* @example
|
|
330
|
-
* // For parallel waiting, use async.all
|
|
331
|
-
* async.derive(focus('combined'), () => {
|
|
332
|
-
* const [a, b, c] = async.all([state.a, state.b, state.c]);
|
|
333
|
-
* return { a, b, c };
|
|
334
|
-
* });
|
|
335
|
-
*
|
|
336
|
-
* @example
|
|
337
|
-
* // With stale mode - preserves data during loading/error
|
|
338
|
-
* async.derive(focus('result'), () => {
|
|
339
|
-
* return async.wait(state.userData);
|
|
340
|
-
* });
|
|
341
|
-
*/
|
|
342
|
-
function derive<T, M extends AsyncMode = "fresh">(focus: Focus<AsyncState<T, M>>, computeFn: () => T): VoidFunction;
|
|
343
|
-
}
|
|
344
|
-
export {};
|
|
56
|
+
export declare const async: {
|
|
57
|
+
fresh: typeof fresh;
|
|
58
|
+
stale: typeof stale;
|
|
59
|
+
action: typeof action;
|
|
60
|
+
mixin: typeof mixin;
|
|
61
|
+
wait: typeof wait;
|
|
62
|
+
hasData: typeof hasData;
|
|
63
|
+
isLoading: typeof isLoading;
|
|
64
|
+
isError: typeof isError;
|
|
65
|
+
all: typeof all;
|
|
66
|
+
any: typeof any;
|
|
67
|
+
race: typeof race;
|
|
68
|
+
settled: typeof settled;
|
|
69
|
+
derive: typeof derive;
|
|
70
|
+
chain: typeof chain;
|
|
71
|
+
delay: typeof delay;
|
|
72
|
+
invoke: typeof import('./helpers').promiseTry;
|
|
73
|
+
state: typeof state;
|
|
74
|
+
};
|
|
345
75
|
//# sourceMappingURL=async.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,KAAK,EAAU,KAAK,EAAE,MAAM,SAAS,CAAC;AAM/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;CAqCjB,CAAC"}
|