rexfect 0.0.7
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 +1756 -0
- package/dist/abortableContext.d.ts +3 -0
- package/dist/abortableContext.d.ts.map +1 -0
- package/dist/abortableContext.js +48 -0
- package/dist/abortableContext.js.map +1 -0
- package/dist/action.d.ts +64 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +208 -0
- package/dist/action.js.map +1 -0
- package/dist/action.test.d.ts +2 -0
- package/dist/action.test.d.ts.map +1 -0
- package/dist/action.test.js +189 -0
- package/dist/action.test.js.map +1 -0
- package/dist/async/abortable-guard.d.ts +25 -0
- package/dist/async/abortable-guard.d.ts.map +1 -0
- package/dist/async/abortable-guard.js +33 -0
- package/dist/async/abortable-guard.js.map +1 -0
- package/dist/async/abortable.d.ts +331 -0
- package/dist/async/abortable.d.ts.map +1 -0
- package/dist/async/abortable.js +410 -0
- package/dist/async/abortable.js.map +1 -0
- package/dist/async/abortable.test.d.ts +2 -0
- package/dist/async/abortable.test.d.ts.map +1 -0
- package/dist/async/abortable.test.js +535 -0
- package/dist/async/abortable.test.js.map +1 -0
- package/dist/async/abortable.typeCheck.d.ts +8 -0
- package/dist/async/abortable.typeCheck.d.ts.map +1 -0
- package/dist/async/abortable.typeCheck.js +138 -0
- package/dist/async/abortable.typeCheck.js.map +1 -0
- package/dist/async/async.d.ts +18 -0
- package/dist/async/async.d.ts.map +1 -0
- package/dist/async/async.js +20 -0
- package/dist/async/async.js.map +1 -0
- package/dist/async/index.d.ts +15 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +13 -0
- package/dist/async/index.js.map +1 -0
- package/dist/async/loadable.d.ts +7 -0
- package/dist/async/loadable.d.ts.map +1 -0
- package/dist/async/loadable.js +52 -0
- package/dist/async/loadable.js.map +1 -0
- package/dist/async/loadable.test.d.ts +2 -0
- package/dist/async/loadable.test.d.ts.map +1 -0
- package/dist/async/loadable.test.js +322 -0
- package/dist/async/loadable.test.js.map +1 -0
- package/dist/async/promiseCache.d.ts +14 -0
- package/dist/async/promiseCache.d.ts.map +1 -0
- package/dist/async/promiseCache.js +29 -0
- package/dist/async/promiseCache.js.map +1 -0
- package/dist/async/read.d.ts +120 -0
- package/dist/async/read.d.ts.map +1 -0
- package/dist/async/read.js +286 -0
- package/dist/async/read.js.map +1 -0
- package/dist/async/read.test.d.ts +2 -0
- package/dist/async/read.test.d.ts.map +1 -0
- package/dist/async/read.test.js +419 -0
- package/dist/async/read.test.js.map +1 -0
- package/dist/async/read.typeCheck.d.ts +6 -0
- package/dist/async/read.typeCheck.d.ts.map +1 -0
- package/dist/async/read.typeCheck.js +101 -0
- package/dist/async/read.typeCheck.js.map +1 -0
- package/dist/async/safe.d.ts +230 -0
- package/dist/async/safe.d.ts.map +1 -0
- package/dist/async/safe.js +247 -0
- package/dist/async/safe.js.map +1 -0
- package/dist/async/safe.test.d.ts +2 -0
- package/dist/async/safe.test.d.ts.map +1 -0
- package/dist/async/safe.test.js +447 -0
- package/dist/async/safe.test.js.map +1 -0
- package/dist/async/utils.d.ts +17 -0
- package/dist/async/utils.d.ts.map +1 -0
- package/dist/async/utils.js +38 -0
- package/dist/async/utils.js.map +1 -0
- package/dist/async/wait.d.ts +120 -0
- package/dist/async/wait.d.ts.map +1 -0
- package/dist/async/wait.js +112 -0
- package/dist/async/wait.js.map +1 -0
- package/dist/async/wait.test.d.ts +2 -0
- package/dist/async/wait.test.d.ts.map +1 -0
- package/dist/async/wait.test.js +122 -0
- package/dist/async/wait.test.js.map +1 -0
- package/dist/async/wait.typeCheck.d.ts +6 -0
- package/dist/async/wait.typeCheck.d.ts.map +1 -0
- package/dist/async/wait.typeCheck.js +104 -0
- package/dist/async/wait.typeCheck.js.map +1 -0
- package/dist/atom.d.ts +46 -0
- package/dist/atom.d.ts.map +1 -0
- package/dist/atom.js +86 -0
- package/dist/atom.js.map +1 -0
- package/dist/atom.test.d.ts +2 -0
- package/dist/atom.test.d.ts.map +1 -0
- package/dist/atom.test.js +75 -0
- package/dist/atom.test.js.map +1 -0
- package/dist/batch.d.ts +15 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +45 -0
- package/dist/batch.js.map +1 -0
- package/dist/defer.d.ts +56 -0
- package/dist/defer.d.ts.map +1 -0
- package/dist/defer.js +49 -0
- package/dist/defer.js.map +1 -0
- package/dist/effect.d.ts +91 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +311 -0
- package/dist/effect.js.map +1 -0
- package/dist/effect.test.d.ts +2 -0
- package/dist/effect.test.d.ts.map +1 -0
- package/dist/effect.test.js +123 -0
- package/dist/effect.test.js.map +1 -0
- package/dist/emitter.d.ts +129 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +164 -0
- package/dist/emitter.js.map +1 -0
- package/dist/emitter.test.d.ts +2 -0
- package/dist/emitter.test.d.ts.map +1 -0
- package/dist/emitter.test.js +259 -0
- package/dist/emitter.test.js.map +1 -0
- package/dist/equality.d.ts +66 -0
- package/dist/equality.d.ts.map +1 -0
- package/dist/equality.js +145 -0
- package/dist/equality.js.map +1 -0
- package/dist/event.d.ts +18 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +166 -0
- package/dist/event.js.map +1 -0
- package/dist/event.test.d.ts +2 -0
- package/dist/event.test.d.ts.map +1 -0
- package/dist/event.test.js +167 -0
- package/dist/event.test.js.map +1 -0
- package/dist/hooks.d.ts +152 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +122 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.test.d.ts +2 -0
- package/dist/hooks.test.d.ts.map +1 -0
- package/dist/hooks.test.js +99 -0
- package/dist/hooks.test.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/isPromiseLike.d.ts +10 -0
- package/dist/isPromiseLike.d.ts.map +1 -0
- package/dist/isPromiseLike.js +15 -0
- package/dist/isPromiseLike.js.map +1 -0
- package/dist/pick.d.ts +22 -0
- package/dist/pick.d.ts.map +1 -0
- package/dist/pick.js +46 -0
- package/dist/pick.js.map +1 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +8 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useRx.d.ts +14 -0
- package/dist/react/useRx.d.ts.map +1 -0
- package/dist/react/useRx.js +110 -0
- package/dist/react/useRx.js.map +1 -0
- package/dist/react/useRx.test.d.ts +2 -0
- package/dist/react/useRx.test.d.ts.map +1 -0
- package/dist/react/useRx.test.js +457 -0
- package/dist/react/useRx.test.js.map +1 -0
- package/dist/strictModeTest.d.ts +11 -0
- package/dist/strictModeTest.d.ts.map +1 -0
- package/dist/strictModeTest.js +41 -0
- package/dist/strictModeTest.js.map +1 -0
- package/dist/types.d.ts +606 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/untrack.d.ts +14 -0
- package/dist/untrack.d.ts.map +1 -0
- package/dist/untrack.js +17 -0
- package/dist/untrack.js.map +1 -0
- package/dist/utils/withUse.d.ts +10 -0
- package/dist/utils/withUse.d.ts.map +1 -0
- package/dist/utils/withUse.js +21 -0
- package/dist/utils/withUse.js.map +1 -0
- package/dist/utils/withUse.test.d.ts +2 -0
- package/dist/utils/withUse.test.d.ts.map +1 -0
- package/dist/utils/withUse.test.js +233 -0
- package/dist/utils/withUse.test.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +119 -0
- package/dist/utils.test.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abortableContext.d.ts","sourceRoot":"","sources":["../src/abortableContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,eAAe,EAC3B,cAAc,CAAC,EAAE,eAAe,GAAG,IAAI,GACtC,gBAAgB,CA0DlB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function abortableContext(controller, prevController) {
|
|
2
|
+
const ctx = {
|
|
3
|
+
signal: controller.signal,
|
|
4
|
+
abort() {
|
|
5
|
+
controller.abort();
|
|
6
|
+
},
|
|
7
|
+
abortPrev(message) {
|
|
8
|
+
prevController?.abort(message);
|
|
9
|
+
},
|
|
10
|
+
safe(promiseOrCallback) {
|
|
11
|
+
if (typeof promiseOrCallback === "function") {
|
|
12
|
+
return (...args) => {
|
|
13
|
+
if (!controller.signal.aborted) {
|
|
14
|
+
return promiseOrCallback(...args);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
promiseOrCallback.then((value) => {
|
|
20
|
+
if (!controller.signal.aborted) {
|
|
21
|
+
resolve(value);
|
|
22
|
+
}
|
|
23
|
+
}, (error) => {
|
|
24
|
+
if (!controller.signal.aborted) {
|
|
25
|
+
reject(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
fork() {
|
|
31
|
+
const childController = new AbortController();
|
|
32
|
+
const handler = () => {
|
|
33
|
+
controller.signal.removeEventListener("abort", handler);
|
|
34
|
+
childController.abort();
|
|
35
|
+
};
|
|
36
|
+
controller.signal.addEventListener("abort", handler);
|
|
37
|
+
return abortableContext(childController);
|
|
38
|
+
},
|
|
39
|
+
spawn() {
|
|
40
|
+
return abortableContext(new AbortController());
|
|
41
|
+
},
|
|
42
|
+
use(plugin, ...args) {
|
|
43
|
+
return plugin(ctx, ...args);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
return ctx;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=abortableContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abortableContext.js","sourceRoot":"","sources":["../src/abortableContext.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAC9B,UAA2B,EAC3B,cAAuC;IAEvC,MAAM,GAAG,GAAqB;QAC5B,MAAM,EAAE,UAAU,CAAC,MAAM;QAEzB,KAAK;YACH,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QAED,SAAS,CAAC,OAAgB;YACxB,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,CACF,iBAA8D;YAE9D,IAAI,OAAO,iBAAiB,KAAK,UAAU,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,IAAW,EAAE,EAAE;oBACxB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC/B,OAAO,iBAAiB,CAAC,GAAG,IAAI,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACxC,iBAAiB,CAAC,IAAI,CACpB,CAAC,KAAK,EAAE,EAAE;oBACR,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC/B,OAAO,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;gBACH,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;oBACR,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC/B,MAAM,CAAC,KAAK,CAAC,CAAC;oBAChB,CAAC;gBACH,CAAC,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI;YACF,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,UAAU,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACxD,eAAe,CAAC,KAAK,EAAE,CAAC;YAC1B,CAAC,CAAC;YACF,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACrD,OAAO,gBAAgB,CAAC,eAAe,CAAC,CAAC;QAC3C,CAAC;QAED,KAAK;YACH,OAAO,gBAAgB,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI;YACjB,OAAO,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;IAEF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ActionOptions, Action } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Creates an action - the core primitive for dispatch and notification.
|
|
4
|
+
*
|
|
5
|
+
* ## Two Patterns
|
|
6
|
+
*
|
|
7
|
+
* **Void action** (`action<T>()`):
|
|
8
|
+
* - For notifications/events (use "on" prefix naming: `onClick`, `onSubmit`)
|
|
9
|
+
* - Dispatching returns void
|
|
10
|
+
* - Listeners receive the payload
|
|
11
|
+
*
|
|
12
|
+
* **Action with handler** (`action<T, R>(handler)`):
|
|
13
|
+
* - For commands that return results (use verb naming: `addToCart`, `fetchUser`)
|
|
14
|
+
* - Handler executes first, returns result to caller
|
|
15
|
+
* - Listeners still receive the original payload (not the result)
|
|
16
|
+
*
|
|
17
|
+
* ## Key Features
|
|
18
|
+
*
|
|
19
|
+
* - **Thenable**: Actions implement PromiseLike, so `await action` waits for next dispatch
|
|
20
|
+
* - **Sealed**: With `{ sealed: true }`, action fires once then ignores subsequent calls
|
|
21
|
+
* - **Late subscribers**: On sealed actions, late `.on()` calls fire immediately with last payload
|
|
22
|
+
* - **Simple**: Handlers and listeners are plain functions - no context needed
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Void action for notifications (use "on" prefix naming)
|
|
26
|
+
* const onClick = action<MouseEvent>();
|
|
27
|
+
* onClick.on((e) => {
|
|
28
|
+
* console.log("Clicked at:", e.clientX, e.clientY);
|
|
29
|
+
* });
|
|
30
|
+
* onClick(mouseEvent); // dispatch → void
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Action with handler (use verb naming)
|
|
34
|
+
* const addToCart = action((product: Product) => {
|
|
35
|
+
* const item = createCartItem(product);
|
|
36
|
+
* setCart([...cart(), item]);
|
|
37
|
+
* return item; // returned to caller
|
|
38
|
+
* });
|
|
39
|
+
* const item = addToCart(product); // dispatch → CartItem
|
|
40
|
+
* addToCart.on((product) => {
|
|
41
|
+
* // Listeners receive payload, not result
|
|
42
|
+
* analytics.track('add_to_cart', product);
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Async handler (for cancellation, use abortable wrapper)
|
|
47
|
+
* const searchAbortable = abortable(async ({ signal, abort }, query: string) => {
|
|
48
|
+
* abort(); // Cancel previous
|
|
49
|
+
* const res = await fetch(`/search?q=${query}`, { signal });
|
|
50
|
+
* return res.json();
|
|
51
|
+
* });
|
|
52
|
+
* const search = action((query: string) => searchAbortable(query));
|
|
53
|
+
* const results = await search("hello");
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // Sealed action for one-time initialization
|
|
57
|
+
* const onAppReady = action<Config>({ sealed: true });
|
|
58
|
+
* onAppReady(config); // fires listeners
|
|
59
|
+
* onAppReady(config); // no-op (already sealed)
|
|
60
|
+
* onAppReady.on(cb); // late subscriber → cb fires immediately with last payload
|
|
61
|
+
*/
|
|
62
|
+
export declare function action<T = void>(options?: ActionOptions): Action<T, void>;
|
|
63
|
+
export declare function action<H extends (payload: any) => any>(handler: H, options?: ActionOptions): Action<Parameters<H>[0], ReturnType<H>>;
|
|
64
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAuBrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,MAAM,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC3E,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,EACpD,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/action.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { batch } from "./batch";
|
|
2
|
+
import { defer } from "./defer";
|
|
3
|
+
/** No-op function returned for sealed action late subscribers */
|
|
4
|
+
const noop = () => { };
|
|
5
|
+
export function action(handlerOrOptions, maybeOptions) {
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Parse arguments: action(options?) or action(handler, options?)
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const handler = typeof handlerOrOptions === "function" ? handlerOrOptions : undefined;
|
|
10
|
+
const options = typeof handlerOrOptions === "object"
|
|
11
|
+
? handlerOrOptions
|
|
12
|
+
: maybeOptions ?? {};
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Internal State
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Set of active listener entries.
|
|
18
|
+
* Lazily created on first `.on()` call to avoid allocation for actions
|
|
19
|
+
* that are only used for their handler (no listeners).
|
|
20
|
+
*/
|
|
21
|
+
let listeners;
|
|
22
|
+
/** True after first dispatch if options.sealed is true */
|
|
23
|
+
let isSealed = false;
|
|
24
|
+
/** True after dispose() is called */
|
|
25
|
+
let disposed = false;
|
|
26
|
+
/**
|
|
27
|
+
* Stores the most recent dispatch payload and its associated promise.
|
|
28
|
+
* Used for:
|
|
29
|
+
* - `.latest()` to return last value without waiting
|
|
30
|
+
* - Late subscribers on sealed actions
|
|
31
|
+
* - `.fired()` check
|
|
32
|
+
*/
|
|
33
|
+
let lastDispatch;
|
|
34
|
+
/**
|
|
35
|
+
* Deferred promise for the NEXT dispatch.
|
|
36
|
+
* Created lazily when someone awaits the action or calls `.then()`.
|
|
37
|
+
* Resolved when dispatch() is called, then cleared.
|
|
38
|
+
*/
|
|
39
|
+
let nextDispatch;
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Internal Helpers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Returns a promise that resolves on the next dispatch.
|
|
45
|
+
* If already sealed, returns the last dispatch promise immediately.
|
|
46
|
+
*/
|
|
47
|
+
const next = () => {
|
|
48
|
+
// Sealed actions always resolve to the last payload
|
|
49
|
+
if (isSealed && lastDispatch) {
|
|
50
|
+
return lastDispatch.promise;
|
|
51
|
+
}
|
|
52
|
+
// Create deferred if not exists (lazy)
|
|
53
|
+
nextDispatch = defer();
|
|
54
|
+
return nextDispatch.promise;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Invokes a single listener with the given payload.
|
|
58
|
+
*
|
|
59
|
+
* Simple function call - no context, no error handling.
|
|
60
|
+
* Errors propagate normally (can be caught by caller if needed).
|
|
61
|
+
*/
|
|
62
|
+
const fire = (payload, entry) => {
|
|
63
|
+
if (!entry.active)
|
|
64
|
+
return;
|
|
65
|
+
entry.fn(payload);
|
|
66
|
+
};
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Dispatch Function (the callable part of the action)
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Main dispatch function - called when you invoke the action: `myAction(payload)`.
|
|
72
|
+
*
|
|
73
|
+
* Execution order:
|
|
74
|
+
* 1. Execute handler (if exists) and capture result (wrapped in batch)
|
|
75
|
+
* 2. If sealed/disposed, return result early (skip listeners)
|
|
76
|
+
* 3. Resolve any pending `await action` promises
|
|
77
|
+
* 4. Store this dispatch as lastDispatch
|
|
78
|
+
* 5. If options.sealed, mark as sealed (subsequent dispatches become no-ops)
|
|
79
|
+
* 6. Fire all listeners with the payload
|
|
80
|
+
* 7. Return handler result (or undefined for void actions)
|
|
81
|
+
*/
|
|
82
|
+
const dispatch = (payload) => {
|
|
83
|
+
// Step 1: Execute handler first (if exists), get result
|
|
84
|
+
// Handler runs in a batch to coalesce any atom updates it makes
|
|
85
|
+
let result = undefined;
|
|
86
|
+
if (handler) {
|
|
87
|
+
result = batch(() => handler(payload));
|
|
88
|
+
}
|
|
89
|
+
// Step 2: Early exit if sealed or disposed
|
|
90
|
+
// Note: handler still executes even if sealed (for actions with handlers)
|
|
91
|
+
// but listeners don't fire
|
|
92
|
+
if (isSealed || disposed)
|
|
93
|
+
return result;
|
|
94
|
+
// Step 3: Resolve any awaiting promises
|
|
95
|
+
nextDispatch?.resolve(payload);
|
|
96
|
+
// Step 4: Store this dispatch
|
|
97
|
+
lastDispatch = {
|
|
98
|
+
payload,
|
|
99
|
+
promise: nextDispatch?.promise ?? Promise.resolve(payload),
|
|
100
|
+
};
|
|
101
|
+
// Clear the deferred - next await will create a new one
|
|
102
|
+
nextDispatch = undefined;
|
|
103
|
+
// Step 5: Seal if configured
|
|
104
|
+
if (options.sealed) {
|
|
105
|
+
isSealed = true;
|
|
106
|
+
}
|
|
107
|
+
// Step 6: Fire all listeners
|
|
108
|
+
// Copy the set to avoid issues if listeners unsubscribe during iteration
|
|
109
|
+
if (listeners?.size) {
|
|
110
|
+
const copyListeners = [...listeners];
|
|
111
|
+
for (const entry of copyListeners) {
|
|
112
|
+
fire(payload, entry);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Step 7: Return handler result
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Build the Action Object
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
/**
|
|
122
|
+
* The action object combines the dispatch function with additional methods.
|
|
123
|
+
* This uses Object.assign to make dispatch callable while adding properties.
|
|
124
|
+
*/
|
|
125
|
+
const actionObj = Object.assign(dispatch, {
|
|
126
|
+
as() {
|
|
127
|
+
return dispatch;
|
|
128
|
+
},
|
|
129
|
+
/** Returns true if this action is sealed (no more dispatches will fire listeners) */
|
|
130
|
+
sealed: () => isSealed,
|
|
131
|
+
/** Returns true if this action has been dispatched at least once */
|
|
132
|
+
fired: () => !!lastDispatch,
|
|
133
|
+
/**
|
|
134
|
+
* Returns a promise that resolves with the last dispatched payload,
|
|
135
|
+
* or waits for the next dispatch if never fired.
|
|
136
|
+
*
|
|
137
|
+
* Unlike `await action` (which always waits for NEXT dispatch),
|
|
138
|
+
* `.latest()` returns immediately if there's already a value.
|
|
139
|
+
*/
|
|
140
|
+
latest() {
|
|
141
|
+
if (lastDispatch) {
|
|
142
|
+
return lastDispatch.promise;
|
|
143
|
+
}
|
|
144
|
+
return next();
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Subscribes a listener to this action.
|
|
148
|
+
*
|
|
149
|
+
* @param listener - Called on each dispatch with payload
|
|
150
|
+
* @returns Unsubscribe function
|
|
151
|
+
*
|
|
152
|
+
* Special behavior for sealed actions:
|
|
153
|
+
* - If already sealed AND fired, listener is called immediately
|
|
154
|
+
* with the last payload, and returns a no-op unsubscribe
|
|
155
|
+
*/
|
|
156
|
+
on(listener) {
|
|
157
|
+
// Create entry for this subscription
|
|
158
|
+
const entry = {
|
|
159
|
+
fn: listener,
|
|
160
|
+
active: true,
|
|
161
|
+
};
|
|
162
|
+
// Late subscriber on sealed action: fire immediately
|
|
163
|
+
if (isSealed && lastDispatch) {
|
|
164
|
+
fire(lastDispatch.payload, entry);
|
|
165
|
+
return noop; // No need to unsubscribe - won't fire again
|
|
166
|
+
}
|
|
167
|
+
// Lazy create listeners Set on first subscription
|
|
168
|
+
if (!listeners)
|
|
169
|
+
listeners = new Set();
|
|
170
|
+
listeners.add(entry);
|
|
171
|
+
// Return unsubscribe function
|
|
172
|
+
return () => {
|
|
173
|
+
if (!entry.active)
|
|
174
|
+
return; // Already unsubscribed
|
|
175
|
+
entry.active = false;
|
|
176
|
+
listeners?.delete(entry);
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
/**
|
|
180
|
+
* Makes the action thenable (PromiseLike).
|
|
181
|
+
*
|
|
182
|
+
* This enables `await action` to wait for the next dispatch.
|
|
183
|
+
* Always waits for the NEXT dispatch, even if action has fired before.
|
|
184
|
+
* (Use `.latest()` to get the last value immediately if available)
|
|
185
|
+
*/
|
|
186
|
+
then(onFulfilled, onRejected) {
|
|
187
|
+
return next().then(onFulfilled, onRejected);
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Disposes the action, clearing all listeners and state.
|
|
191
|
+
*
|
|
192
|
+
* After dispose:
|
|
193
|
+
* - Dispatch still works (handler executes) but listeners don't fire
|
|
194
|
+
* - New `.on()` calls still work but listeners won't fire
|
|
195
|
+
* - `await action` will hang forever
|
|
196
|
+
*/
|
|
197
|
+
dispose() {
|
|
198
|
+
if (disposed)
|
|
199
|
+
return;
|
|
200
|
+
disposed = true;
|
|
201
|
+
listeners?.clear();
|
|
202
|
+
lastDispatch = undefined;
|
|
203
|
+
nextDispatch = undefined;
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return actionObj;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.test.d.ts","sourceRoot":"","sources":["../src/action.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { action } from "./action";
|
|
3
|
+
describe("action", () => {
|
|
4
|
+
describe("void action (no handler)", () => {
|
|
5
|
+
describe("dispatch", () => {
|
|
6
|
+
it("should dispatch action to listeners", () => {
|
|
7
|
+
const onClick = action();
|
|
8
|
+
const listener = vi.fn();
|
|
9
|
+
onClick.on(listener);
|
|
10
|
+
onClick(42);
|
|
11
|
+
expect(listener).toHaveBeenCalledWith(42);
|
|
12
|
+
});
|
|
13
|
+
it("should support void payload", () => {
|
|
14
|
+
const onReset = action();
|
|
15
|
+
const listener = vi.fn();
|
|
16
|
+
onReset.on(listener);
|
|
17
|
+
onReset();
|
|
18
|
+
expect(listener).toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
it("should return void for void actions", () => {
|
|
21
|
+
const onClick = action();
|
|
22
|
+
const result = onClick(42);
|
|
23
|
+
expect(result).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("fired", () => {
|
|
27
|
+
it("should track if action has been fired", () => {
|
|
28
|
+
const onClick = action();
|
|
29
|
+
expect(onClick.fired()).toBe(false);
|
|
30
|
+
expect(!onClick.fired()).toBe(true);
|
|
31
|
+
onClick(1);
|
|
32
|
+
expect(onClick.fired()).toBe(true);
|
|
33
|
+
expect(!onClick.fired()).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe("sealed", () => {
|
|
37
|
+
it("should auto-seal after first dispatch", () => {
|
|
38
|
+
const onInit = action({ sealed: true });
|
|
39
|
+
expect(onInit.sealed()).toBe(false);
|
|
40
|
+
onInit("first");
|
|
41
|
+
expect(onInit.sealed()).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it("should ignore subsequent dispatches when sealed", () => {
|
|
44
|
+
const onInit = action({ sealed: true });
|
|
45
|
+
const listener = vi.fn();
|
|
46
|
+
onInit.on(listener);
|
|
47
|
+
onInit("first");
|
|
48
|
+
onInit("second");
|
|
49
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
50
|
+
expect(listener).toHaveBeenCalledWith("first");
|
|
51
|
+
});
|
|
52
|
+
it("should call late listeners immediately with sealed value", () => {
|
|
53
|
+
const onInit = action({ sealed: true });
|
|
54
|
+
onInit("config");
|
|
55
|
+
const lateListener = vi.fn();
|
|
56
|
+
onInit.on(lateListener);
|
|
57
|
+
expect(lateListener).toHaveBeenCalledWith("config");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("latest", () => {
|
|
61
|
+
it("should resolve immediately if already dispatched", async () => {
|
|
62
|
+
const onData = action();
|
|
63
|
+
onData(42);
|
|
64
|
+
const result = await onData.latest();
|
|
65
|
+
expect(result).toBe(42);
|
|
66
|
+
});
|
|
67
|
+
it("should wait for dispatch if not fired", async () => {
|
|
68
|
+
const onData = action();
|
|
69
|
+
const promise = onData.latest();
|
|
70
|
+
setTimeout(() => onData(100), 10);
|
|
71
|
+
const result = await promise;
|
|
72
|
+
expect(result).toBe(100);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("thenable (await action)", () => {
|
|
76
|
+
it("should wait for next dispatch", async () => {
|
|
77
|
+
const onClick = action();
|
|
78
|
+
const promise = onClick.then((value) => value.toUpperCase());
|
|
79
|
+
setTimeout(() => onClick("hello"), 10);
|
|
80
|
+
const result = await promise;
|
|
81
|
+
expect(result).toBe("HELLO");
|
|
82
|
+
});
|
|
83
|
+
it("should resolve immediately if sealed", async () => {
|
|
84
|
+
const onInit = action({ sealed: true });
|
|
85
|
+
onInit("sealed-value");
|
|
86
|
+
const result = await onInit;
|
|
87
|
+
expect(result).toBe("sealed-value");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe("on listener", () => {
|
|
91
|
+
it("should return unsubscribe function", () => {
|
|
92
|
+
const onClick = action();
|
|
93
|
+
const listener = vi.fn();
|
|
94
|
+
const unsubscribe = onClick.on(listener);
|
|
95
|
+
onClick(1);
|
|
96
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
97
|
+
unsubscribe();
|
|
98
|
+
onClick(2);
|
|
99
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("action with handler", () => {
|
|
104
|
+
it("should execute handler and return result", () => {
|
|
105
|
+
const addToCart = action((product) => {
|
|
106
|
+
return {
|
|
107
|
+
...product,
|
|
108
|
+
cartItemId: `cart-${product.id}`,
|
|
109
|
+
addedAt: Date.now(),
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
const result = addToCart({ id: "prod-1", price: 100 });
|
|
113
|
+
expect(result.cartItemId).toBe("cart-prod-1");
|
|
114
|
+
expect(result.id).toBe("prod-1");
|
|
115
|
+
expect(result.price).toBe(100);
|
|
116
|
+
expect(result.addedAt).toBeTypeOf("number");
|
|
117
|
+
});
|
|
118
|
+
it("should notify listeners with original payload", () => {
|
|
119
|
+
const listener = vi.fn();
|
|
120
|
+
const addToCart = action((product) => {
|
|
121
|
+
return { ...product, processed: true };
|
|
122
|
+
});
|
|
123
|
+
addToCart.on(listener);
|
|
124
|
+
addToCart({ id: "prod-1" });
|
|
125
|
+
// Listener receives original payload, not the result
|
|
126
|
+
expect(listener).toHaveBeenCalledWith({ id: "prod-1" });
|
|
127
|
+
});
|
|
128
|
+
it("should execute handler synchronously", () => {
|
|
129
|
+
const order = [];
|
|
130
|
+
const doSomething = action((value) => {
|
|
131
|
+
order.push(`handler:${value}`);
|
|
132
|
+
return value.toUpperCase();
|
|
133
|
+
});
|
|
134
|
+
doSomething.on((value) => {
|
|
135
|
+
order.push(`listener:${value}`);
|
|
136
|
+
});
|
|
137
|
+
const result = doSomething("test");
|
|
138
|
+
expect(result).toBe("TEST");
|
|
139
|
+
expect(order).toEqual(["handler:test", "listener:test"]);
|
|
140
|
+
});
|
|
141
|
+
it("should still support await for payload", async () => {
|
|
142
|
+
const addToCart = action((product) => {
|
|
143
|
+
return { ...product, cartItemId: `cart-${product.id}` };
|
|
144
|
+
});
|
|
145
|
+
const promise = addToCart.then((payload) => payload.id);
|
|
146
|
+
setTimeout(() => addToCart({ id: "prod-1" }), 10);
|
|
147
|
+
const result = await promise;
|
|
148
|
+
expect(result).toBe("prod-1"); // Gets payload.id, not result
|
|
149
|
+
});
|
|
150
|
+
it("should support async handlers", async () => {
|
|
151
|
+
const fetchUser = action(async (id) => {
|
|
152
|
+
// Simulating async operation
|
|
153
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
154
|
+
return { id, name: `User ${id}` };
|
|
155
|
+
});
|
|
156
|
+
const resultPromise = fetchUser("123");
|
|
157
|
+
expect(resultPromise).toBeInstanceOf(Promise);
|
|
158
|
+
const result = await resultPromise;
|
|
159
|
+
expect(result).toEqual({ id: "123", name: "User 123" });
|
|
160
|
+
});
|
|
161
|
+
it("should execute handler even when sealed", () => {
|
|
162
|
+
let handlerCallCount = 0;
|
|
163
|
+
const onInit = action((value) => {
|
|
164
|
+
handlerCallCount++;
|
|
165
|
+
return value.toUpperCase();
|
|
166
|
+
}, { sealed: true });
|
|
167
|
+
const result1 = onInit("first");
|
|
168
|
+
expect(result1).toBe("FIRST");
|
|
169
|
+
expect(handlerCallCount).toBe(1);
|
|
170
|
+
// Handler still executes even when sealed
|
|
171
|
+
const result2 = onInit("second");
|
|
172
|
+
expect(result2).toBe("SECOND");
|
|
173
|
+
expect(handlerCallCount).toBe(2);
|
|
174
|
+
});
|
|
175
|
+
it("should not notify listeners after sealed", () => {
|
|
176
|
+
const listener = vi.fn();
|
|
177
|
+
const onInit = action((value) => value.toUpperCase(), {
|
|
178
|
+
sealed: true,
|
|
179
|
+
});
|
|
180
|
+
onInit.on(listener);
|
|
181
|
+
onInit("first");
|
|
182
|
+
onInit("second");
|
|
183
|
+
// Listener only called once (sealed after first)
|
|
184
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(listener).toHaveBeenCalledWith("first");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
//# sourceMappingURL=action.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.test.js","sourceRoot":"","sources":["../src/action.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abortable type guard.
|
|
3
|
+
*
|
|
4
|
+
* Extracted to a separate file to avoid circular dependencies
|
|
5
|
+
* between safe.ts and abortable.ts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Symbol used to identify Abortable functions.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare const abortableSymbol: unique symbol;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a value is an Abortable function.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* if (isAbortable(fn)) {
|
|
18
|
+
* fn.withSignal(signal, ...args);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function isAbortable(fn: unknown): fn is {
|
|
23
|
+
withSignal: Function;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=abortable-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abortable-guard.d.ts","sourceRoot":"","sources":["../../src/async/abortable-guard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;GAGG;AACH,eAAO,MAAM,eAAe,eAA0B,CAAC;AAMvD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI;IAAE,UAAU,EAAE,QAAQ,CAAA;CAAE,CAMvE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abortable type guard.
|
|
3
|
+
*
|
|
4
|
+
* Extracted to a separate file to avoid circular dependencies
|
|
5
|
+
* between safe.ts and abortable.ts.
|
|
6
|
+
*/
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// SYMBOL
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Symbol used to identify Abortable functions.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export const abortableSymbol = Symbol.for("abortable");
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// TYPE GUARD
|
|
17
|
+
// =============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* Check if a value is an Abortable function.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* if (isAbortable(fn)) {
|
|
24
|
+
* fn.withSignal(signal, ...args);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function isAbortable(fn) {
|
|
29
|
+
return (typeof fn === "function" &&
|
|
30
|
+
abortableSymbol in fn &&
|
|
31
|
+
fn[abortableSymbol] === true);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=abortable-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abortable-guard.js","sourceRoot":"","sources":["../../src/async/abortable-guard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAEvD,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,EAAW;IACrC,OAAO,CACL,OAAO,EAAE,KAAK,UAAU;QACxB,eAAe,IAAI,EAAE;QACpB,EAAU,CAAC,eAAe,CAAC,KAAK,IAAI,CACtC,CAAC;AACJ,CAAC"}
|