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
package/dist/event.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { defer } from "./defer";
|
|
2
|
+
import { emitter } from "./emitter";
|
|
3
|
+
import { isPromiseLike } from "./isPromiseLike";
|
|
4
|
+
const noop = () => { };
|
|
5
|
+
/**
|
|
6
|
+
* Creates an event dispatcher for fire-and-forget notifications.
|
|
7
|
+
*
|
|
8
|
+
* @param options - Optional configuration
|
|
9
|
+
* @returns An Event object
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const onClick = event<MouseEvent>();
|
|
13
|
+
*
|
|
14
|
+
* onClick.on((e, ctx) => {
|
|
15
|
+
* console.log("Clicked at:", e.clientX, e.clientY);
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* onClick(mouseEvent); // dispatch
|
|
19
|
+
*/
|
|
20
|
+
export function event(options = {}) {
|
|
21
|
+
const listeners = new Set();
|
|
22
|
+
let isSealed = false;
|
|
23
|
+
let lastDispatch;
|
|
24
|
+
let nextDispatch;
|
|
25
|
+
const next = () => {
|
|
26
|
+
if (isSealed) {
|
|
27
|
+
if (lastDispatch) {
|
|
28
|
+
return lastDispatch.promise;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
nextDispatch = defer();
|
|
32
|
+
return nextDispatch.promise;
|
|
33
|
+
};
|
|
34
|
+
const fire = (payload, entry) => {
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const onError = emitter();
|
|
37
|
+
const ctx = createEventContext(controller, entry.prevController, () => entry.active);
|
|
38
|
+
entry.prevController = controller;
|
|
39
|
+
try {
|
|
40
|
+
const result = entry.fn(payload, ctx);
|
|
41
|
+
if (isPromiseLike(result)) {
|
|
42
|
+
result.then(undefined, (error) => {
|
|
43
|
+
onError.emitAndClear(error);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (_error) {
|
|
48
|
+
onError.emitAndClear(_error);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const dispatch = (payload) => {
|
|
52
|
+
if (isSealed)
|
|
53
|
+
return; // no-op if sealed
|
|
54
|
+
nextDispatch?.resolve(payload);
|
|
55
|
+
lastDispatch = {
|
|
56
|
+
payload,
|
|
57
|
+
promise: nextDispatch?.promise ?? Promise.resolve(payload),
|
|
58
|
+
};
|
|
59
|
+
nextDispatch = undefined;
|
|
60
|
+
if (options.sealed) {
|
|
61
|
+
isSealed = true;
|
|
62
|
+
}
|
|
63
|
+
const copyListeners = [...listeners];
|
|
64
|
+
for (const entry of copyListeners) {
|
|
65
|
+
fire(payload, entry);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const eventObj = Object.assign(dispatch, {
|
|
69
|
+
sealed: () => isSealed,
|
|
70
|
+
fired: () => !!lastDispatch,
|
|
71
|
+
latest() {
|
|
72
|
+
if (lastDispatch) {
|
|
73
|
+
return lastDispatch.promise;
|
|
74
|
+
}
|
|
75
|
+
return next();
|
|
76
|
+
},
|
|
77
|
+
on(listener) {
|
|
78
|
+
const entry = {
|
|
79
|
+
fn: listener,
|
|
80
|
+
active: true,
|
|
81
|
+
prevController: null,
|
|
82
|
+
};
|
|
83
|
+
if (isSealed && lastDispatch) {
|
|
84
|
+
fire(lastDispatch.payload, entry);
|
|
85
|
+
return noop;
|
|
86
|
+
}
|
|
87
|
+
listeners.add(entry);
|
|
88
|
+
return () => {
|
|
89
|
+
if (!entry.active)
|
|
90
|
+
return;
|
|
91
|
+
entry.active = false;
|
|
92
|
+
listeners.delete(entry);
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
then(onFulfilled, onRejected) {
|
|
96
|
+
return next().then(onFulfilled, onRejected);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return eventObj;
|
|
100
|
+
}
|
|
101
|
+
function createEventContext(controller, prevController, active) {
|
|
102
|
+
const cleanups = [];
|
|
103
|
+
const errorHandlers = [];
|
|
104
|
+
const ctx = {
|
|
105
|
+
...createAbortableContext(controller),
|
|
106
|
+
active,
|
|
107
|
+
abortPrevious() {
|
|
108
|
+
prevController?.abort();
|
|
109
|
+
},
|
|
110
|
+
onCleanup(cleanup) {
|
|
111
|
+
cleanups.push(cleanup);
|
|
112
|
+
},
|
|
113
|
+
onError(handler) {
|
|
114
|
+
errorHandlers.push(handler);
|
|
115
|
+
},
|
|
116
|
+
use(plugin, ...args) {
|
|
117
|
+
return plugin(ctx, ...args);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
return ctx;
|
|
121
|
+
}
|
|
122
|
+
function createAbortableContext(controller) {
|
|
123
|
+
const ctx = {
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
abort() {
|
|
126
|
+
controller.abort();
|
|
127
|
+
},
|
|
128
|
+
safe(promiseOrCallback) {
|
|
129
|
+
if (typeof promiseOrCallback === "function") {
|
|
130
|
+
return (...args) => {
|
|
131
|
+
if (!controller.signal.aborted) {
|
|
132
|
+
return promiseOrCallback(...args);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
promiseOrCallback.then((value) => {
|
|
138
|
+
if (!controller.signal.aborted) {
|
|
139
|
+
resolve(value);
|
|
140
|
+
}
|
|
141
|
+
}, (error) => {
|
|
142
|
+
if (!controller.signal.aborted) {
|
|
143
|
+
reject(error);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
fork() {
|
|
149
|
+
const childController = new AbortController();
|
|
150
|
+
const handler = () => {
|
|
151
|
+
controller.signal.removeEventListener("abort", handler);
|
|
152
|
+
childController.abort();
|
|
153
|
+
};
|
|
154
|
+
controller.signal.addEventListener("abort", handler);
|
|
155
|
+
return createAbortableContext(childController);
|
|
156
|
+
},
|
|
157
|
+
spawn() {
|
|
158
|
+
return createAbortableContext(new AbortController());
|
|
159
|
+
},
|
|
160
|
+
use(plugin, ...args) {
|
|
161
|
+
return plugin(ctx, ...args);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
return ctx;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.test.d.ts","sourceRoot":"","sources":["../src/event.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { event } from "./event";
|
|
3
|
+
describe("event", () => {
|
|
4
|
+
describe("dispatch", () => {
|
|
5
|
+
it("should dispatch event to listeners", () => {
|
|
6
|
+
const onClick = event();
|
|
7
|
+
const listener = vi.fn();
|
|
8
|
+
onClick.on(listener);
|
|
9
|
+
onClick(42);
|
|
10
|
+
expect(listener).toHaveBeenCalledWith(42, expect.any(Object));
|
|
11
|
+
});
|
|
12
|
+
it("should support void payload", () => {
|
|
13
|
+
const onReset = event();
|
|
14
|
+
const listener = vi.fn();
|
|
15
|
+
onReset.on(listener);
|
|
16
|
+
onReset();
|
|
17
|
+
expect(listener).toHaveBeenCalled();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("fired", () => {
|
|
21
|
+
it("should track if event has been fired", () => {
|
|
22
|
+
const onClick = event();
|
|
23
|
+
expect(onClick.fired()).toBe(false);
|
|
24
|
+
expect(!onClick.fired()).toBe(true); // use !fired() instead of notFired()
|
|
25
|
+
onClick(1);
|
|
26
|
+
expect(onClick.fired()).toBe(true);
|
|
27
|
+
expect(!onClick.fired()).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("sealed", () => {
|
|
31
|
+
it("should auto-seal after first dispatch", () => {
|
|
32
|
+
const onInit = event({ sealed: true });
|
|
33
|
+
expect(onInit.sealed()).toBe(false);
|
|
34
|
+
onInit("first");
|
|
35
|
+
expect(onInit.sealed()).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it("should ignore subsequent dispatches when sealed", () => {
|
|
38
|
+
const onInit = event({ sealed: true });
|
|
39
|
+
const listener = vi.fn();
|
|
40
|
+
onInit.on(listener);
|
|
41
|
+
onInit("first");
|
|
42
|
+
onInit("second");
|
|
43
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
44
|
+
expect(listener).toHaveBeenCalledWith("first", expect.any(Object));
|
|
45
|
+
});
|
|
46
|
+
it("should call late listeners immediately with sealed value", () => {
|
|
47
|
+
const onInit = event({ sealed: true });
|
|
48
|
+
onInit("config");
|
|
49
|
+
const lateListener = vi.fn();
|
|
50
|
+
onInit.on(lateListener);
|
|
51
|
+
expect(lateListener).toHaveBeenCalledWith("config", expect.any(Object));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("latest", () => {
|
|
55
|
+
it("should resolve immediately if already dispatched", async () => {
|
|
56
|
+
const onData = event();
|
|
57
|
+
onData(42);
|
|
58
|
+
const result = await onData.latest();
|
|
59
|
+
expect(result).toBe(42);
|
|
60
|
+
});
|
|
61
|
+
it("should wait for dispatch if not fired", async () => {
|
|
62
|
+
const onData = event();
|
|
63
|
+
const promise = onData.latest();
|
|
64
|
+
// Dispatch after a delay
|
|
65
|
+
setTimeout(() => onData(100), 10);
|
|
66
|
+
const result = await promise;
|
|
67
|
+
expect(result).toBe(100);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("thenable (await event)", () => {
|
|
71
|
+
it("should wait for next dispatch", async () => {
|
|
72
|
+
const onClick = event();
|
|
73
|
+
const promise = onClick.then((value) => value.toUpperCase());
|
|
74
|
+
setTimeout(() => onClick("hello"), 10);
|
|
75
|
+
const result = await promise;
|
|
76
|
+
expect(result).toBe("HELLO");
|
|
77
|
+
});
|
|
78
|
+
it("should resolve immediately if sealed", async () => {
|
|
79
|
+
const onInit = event({ sealed: true });
|
|
80
|
+
onInit("sealed-value");
|
|
81
|
+
const result = await onInit;
|
|
82
|
+
expect(result).toBe("sealed-value");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("on listener", () => {
|
|
86
|
+
it("should return unsubscribe function", () => {
|
|
87
|
+
const onClick = event();
|
|
88
|
+
const listener = vi.fn();
|
|
89
|
+
const unsubscribe = onClick.on(listener);
|
|
90
|
+
onClick(1);
|
|
91
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
92
|
+
unsubscribe();
|
|
93
|
+
onClick(2);
|
|
94
|
+
expect(listener).toHaveBeenCalledTimes(1); // not called again
|
|
95
|
+
});
|
|
96
|
+
it("should provide context with signal", () => {
|
|
97
|
+
const onClick = event();
|
|
98
|
+
onClick.on((_payload, ctx) => {
|
|
99
|
+
expect(ctx.signal).toBeInstanceOf(AbortSignal);
|
|
100
|
+
expect(ctx.signal.aborted).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
onClick(1);
|
|
103
|
+
});
|
|
104
|
+
it("should support abortPrevious", async () => {
|
|
105
|
+
const onSearch = event();
|
|
106
|
+
const abortedSignals = [];
|
|
107
|
+
onSearch.on((_query, ctx) => {
|
|
108
|
+
ctx.abortPrevious();
|
|
109
|
+
abortedSignals.push(ctx.signal.aborted);
|
|
110
|
+
});
|
|
111
|
+
onSearch("first");
|
|
112
|
+
onSearch("second");
|
|
113
|
+
// First call's signal should be aborted by second call
|
|
114
|
+
// Note: This depends on implementation details
|
|
115
|
+
});
|
|
116
|
+
it("should support active() check", () => {
|
|
117
|
+
const onClick = event();
|
|
118
|
+
let isActive = true;
|
|
119
|
+
const unsubscribe = onClick.on((_, ctx) => {
|
|
120
|
+
isActive = ctx.active();
|
|
121
|
+
});
|
|
122
|
+
onClick(1);
|
|
123
|
+
expect(isActive).toBe(true);
|
|
124
|
+
unsubscribe();
|
|
125
|
+
// After unsubscribe, active() would return false
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe("context methods", () => {
|
|
129
|
+
it("should support safe() for promises", async () => {
|
|
130
|
+
const onFetch = event();
|
|
131
|
+
const results = [];
|
|
132
|
+
onFetch.on(async (url, ctx) => {
|
|
133
|
+
const result = await ctx.safe(Promise.resolve(url + "-fetched"));
|
|
134
|
+
results.push(result);
|
|
135
|
+
});
|
|
136
|
+
onFetch("test");
|
|
137
|
+
// Wait for async
|
|
138
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
139
|
+
expect(results).toContain("test-fetched");
|
|
140
|
+
});
|
|
141
|
+
it("should support fork() for child contexts", () => {
|
|
142
|
+
const onClick = event();
|
|
143
|
+
onClick.on((_, ctx) => {
|
|
144
|
+
const child = ctx.fork();
|
|
145
|
+
expect(child.signal).toBeInstanceOf(AbortSignal);
|
|
146
|
+
expect(child.signal.aborted).toBe(false);
|
|
147
|
+
// Abort parent should abort child
|
|
148
|
+
ctx.abort();
|
|
149
|
+
expect(child.signal.aborted).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
onClick(1);
|
|
152
|
+
});
|
|
153
|
+
it("should support spawn() for independent contexts", () => {
|
|
154
|
+
const onClick = event();
|
|
155
|
+
onClick.on((_, ctx) => {
|
|
156
|
+
const independent = ctx.spawn();
|
|
157
|
+
expect(independent.signal).toBeInstanceOf(AbortSignal);
|
|
158
|
+
expect(independent.signal.aborted).toBe(false);
|
|
159
|
+
// Abort parent should NOT abort spawned context
|
|
160
|
+
ctx.abort();
|
|
161
|
+
expect(independent.signal.aborted).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
onClick(1);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=event.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.test.js","sourceRoot":"","sources":["../src/event.test.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Signal } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Hooks provide a way to inject behavior into reactive primitives without
|
|
4
|
+
* explicit parameter passing. Think of it like React Context, but for the
|
|
5
|
+
* internal reactive system.
|
|
6
|
+
*
|
|
7
|
+
* ## How It's Used
|
|
8
|
+
*
|
|
9
|
+
* 1. **effect()** uses `withHooks()` to set up dependency tracking:
|
|
10
|
+
* - Sets `track` to collect signals read during effect execution
|
|
11
|
+
* - Sets `scheduleCleanup` to register cleanup functions
|
|
12
|
+
*
|
|
13
|
+
* 2. **atom()** calls `getHooks().track?.(signal)` when read, allowing
|
|
14
|
+
* the current context to know which signals were accessed
|
|
15
|
+
*
|
|
16
|
+
* 3. **useRx()** (React) uses hooks to integrate with React's lifecycle
|
|
17
|
+
*
|
|
18
|
+
* ## Why This Pattern?
|
|
19
|
+
*
|
|
20
|
+
* Without hooks, we'd need to pass tracking context through every function:
|
|
21
|
+
* ```ts
|
|
22
|
+
* // Without hooks (verbose, error-prone):
|
|
23
|
+
* effect((ctx) => {
|
|
24
|
+
* const value = count.read(ctx.tracker); // Must pass tracker everywhere
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // With hooks (clean, automatic):
|
|
28
|
+
* effect(() => {
|
|
29
|
+
* const value = count(); // Tracking happens automatically
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export interface Hooks {
|
|
34
|
+
/**
|
|
35
|
+
* Called when a signal is read, allowing the current context to track it.
|
|
36
|
+
*
|
|
37
|
+
* Set by effect() to collect dependencies.
|
|
38
|
+
* When undefined, signal reads are not tracked (e.g., outside effects).
|
|
39
|
+
*
|
|
40
|
+
* @param signal - The signal that was read
|
|
41
|
+
* @param onCleanup - Optional cleanup callback (unused in current impl)
|
|
42
|
+
*/
|
|
43
|
+
track?: ((signal: Signal<any>, onCleanup?: VoidFunction) => void) | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Registers a cleanup function with the current context.
|
|
46
|
+
*
|
|
47
|
+
* Set by effect() to allow nested subscriptions/effects to register
|
|
48
|
+
* cleanup that runs when the parent effect re-runs or disposes.
|
|
49
|
+
*
|
|
50
|
+
* Also used by useRx() in React to register cleanup with useEffect.
|
|
51
|
+
*
|
|
52
|
+
* @param cleanup - Function to call for cleanup
|
|
53
|
+
*/
|
|
54
|
+
scheduleCleanup?: ((cleanup: VoidFunction) => void) | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Schedules a notification (effect re-run) for later execution.
|
|
57
|
+
*
|
|
58
|
+
* Default implementation runs synchronously: `(fn) => fn()`
|
|
59
|
+
*
|
|
60
|
+
* Can be overridden to:
|
|
61
|
+
* - Batch notifications to next microtask
|
|
62
|
+
* - Integrate with framework schedulers (React, Vue, etc.)
|
|
63
|
+
* - Debounce rapid updates
|
|
64
|
+
*
|
|
65
|
+
* @param fn - The notification function to schedule
|
|
66
|
+
*/
|
|
67
|
+
scheduleNotify: (fn: () => void) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Type for partial hook overrides.
|
|
71
|
+
* Used when you only want to override some hooks while keeping others.
|
|
72
|
+
*/
|
|
73
|
+
export type HookOverride = {
|
|
74
|
+
[K in keyof Hooks]?: Hooks[K] | undefined;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Executes a function with temporarily modified hooks.
|
|
78
|
+
*
|
|
79
|
+
* This is the core mechanism for setting up tracking context. It:
|
|
80
|
+
* 1. Saves current hooks
|
|
81
|
+
* 2. Merges in new hooks (or applies reducer function)
|
|
82
|
+
* 3. Executes the provided function
|
|
83
|
+
* 4. Restores previous hooks (even if function throws)
|
|
84
|
+
*
|
|
85
|
+
* The hooks are scoped to the execution of `fn` - any code called from
|
|
86
|
+
* within `fn` will see the modified hooks via `getHooks()`.
|
|
87
|
+
*
|
|
88
|
+
* @param hooksOrHooksReducer - New hooks to merge, or function to compute them
|
|
89
|
+
* @param fn - Function to execute with modified hooks
|
|
90
|
+
* @returns The return value of `fn`
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* // Inside effect():
|
|
95
|
+
* const signals = new Set<Signal<any>>();
|
|
96
|
+
*
|
|
97
|
+
* withHooks(
|
|
98
|
+
* {
|
|
99
|
+
* track(signal) {
|
|
100
|
+
* signals.add(signal); // Collect all read signals
|
|
101
|
+
* },
|
|
102
|
+
* scheduleCleanup: onCleanup,
|
|
103
|
+
* },
|
|
104
|
+
* () => effectFn(ctx) // User's effect function runs here
|
|
105
|
+
* );
|
|
106
|
+
*
|
|
107
|
+
* // Now `signals` contains all dependencies
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // Using reducer to access previous hooks:
|
|
113
|
+
* withHooks(
|
|
114
|
+
* (prev) => ({
|
|
115
|
+
* scheduleNotify: (fn) => {
|
|
116
|
+
* // Wrap previous scheduler with logging
|
|
117
|
+
* console.log("Scheduling notification");
|
|
118
|
+
* prev.scheduleNotify(fn);
|
|
119
|
+
* },
|
|
120
|
+
* }),
|
|
121
|
+
* () => someOperation()
|
|
122
|
+
* );
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function withHooks<T>(hooksOrHooksReducer: Partial<Hooks> | ((prev: Hooks) => Partial<Hooks>), fn: () => T): T;
|
|
126
|
+
/**
|
|
127
|
+
* Returns the currently active hooks.
|
|
128
|
+
*
|
|
129
|
+
* Used by reactive primitives to access the current context:
|
|
130
|
+
* - atom() calls `getHooks().track?.(this)` when read
|
|
131
|
+
* - effect() calls `getHooks().scheduleNotify(run)` to schedule re-runs
|
|
132
|
+
* - effectInstance() calls `getHooks().scheduleCleanup?.(dispose)` for cleanup
|
|
133
|
+
*
|
|
134
|
+
* @returns The current Hooks object
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* // Inside atom's read function:
|
|
139
|
+
* function read() {
|
|
140
|
+
* getHooks().track?.(this); // Register with current tracking context
|
|
141
|
+
* return value;
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function getHooks(): Hooks;
|
|
146
|
+
/**
|
|
147
|
+
* Sets the global hooks.
|
|
148
|
+
*
|
|
149
|
+
* @param hooks - The hooks to set
|
|
150
|
+
*/
|
|
151
|
+
export declare function setHooks(hooks: Partial<Hooks>): void;
|
|
152
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAMjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,KAAK;IACpB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE9E;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAEhE;;;;;;;;;;;OAWG;IACH,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;KAAG,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS;CAAE,CAAC;AA2BzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,mBAAmB,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC,EACvE,EAAE,EAAE,MAAM,CAAC,KAkBZ;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,UAEvB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,QAM7C"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Global State
|
|
3
|
+
// ============================================================================
|
|
4
|
+
/**
|
|
5
|
+
* The currently active hooks context.
|
|
6
|
+
*
|
|
7
|
+
* Default hooks:
|
|
8
|
+
* - track: undefined (no tracking outside effects)
|
|
9
|
+
* - scheduleCleanup: undefined (no automatic cleanup)
|
|
10
|
+
* - scheduleNotify: synchronous execution
|
|
11
|
+
*
|
|
12
|
+
* Modified temporarily by withHooks() during effect/useRx execution.
|
|
13
|
+
*/
|
|
14
|
+
let currentHooks = {
|
|
15
|
+
// Default: run notifications synchronously
|
|
16
|
+
scheduleNotify: (fn) => fn(),
|
|
17
|
+
};
|
|
18
|
+
let globalHooks = currentHooks;
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Hook Management Functions
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Executes a function with temporarily modified hooks.
|
|
24
|
+
*
|
|
25
|
+
* This is the core mechanism for setting up tracking context. It:
|
|
26
|
+
* 1. Saves current hooks
|
|
27
|
+
* 2. Merges in new hooks (or applies reducer function)
|
|
28
|
+
* 3. Executes the provided function
|
|
29
|
+
* 4. Restores previous hooks (even if function throws)
|
|
30
|
+
*
|
|
31
|
+
* The hooks are scoped to the execution of `fn` - any code called from
|
|
32
|
+
* within `fn` will see the modified hooks via `getHooks()`.
|
|
33
|
+
*
|
|
34
|
+
* @param hooksOrHooksReducer - New hooks to merge, or function to compute them
|
|
35
|
+
* @param fn - Function to execute with modified hooks
|
|
36
|
+
* @returns The return value of `fn`
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Inside effect():
|
|
41
|
+
* const signals = new Set<Signal<any>>();
|
|
42
|
+
*
|
|
43
|
+
* withHooks(
|
|
44
|
+
* {
|
|
45
|
+
* track(signal) {
|
|
46
|
+
* signals.add(signal); // Collect all read signals
|
|
47
|
+
* },
|
|
48
|
+
* scheduleCleanup: onCleanup,
|
|
49
|
+
* },
|
|
50
|
+
* () => effectFn(ctx) // User's effect function runs here
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* // Now `signals` contains all dependencies
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* // Using reducer to access previous hooks:
|
|
59
|
+
* withHooks(
|
|
60
|
+
* (prev) => ({
|
|
61
|
+
* scheduleNotify: (fn) => {
|
|
62
|
+
* // Wrap previous scheduler with logging
|
|
63
|
+
* console.log("Scheduling notification");
|
|
64
|
+
* prev.scheduleNotify(fn);
|
|
65
|
+
* },
|
|
66
|
+
* }),
|
|
67
|
+
* () => someOperation()
|
|
68
|
+
* );
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function withHooks(hooksOrHooksReducer, fn) {
|
|
72
|
+
// Save current hooks to restore later
|
|
73
|
+
const prevHooks = currentHooks;
|
|
74
|
+
// Merge new hooks into current (supports both object and reducer function)
|
|
75
|
+
currentHooks =
|
|
76
|
+
typeof hooksOrHooksReducer === "function"
|
|
77
|
+
? { ...currentHooks, ...hooksOrHooksReducer(currentHooks) }
|
|
78
|
+
: { ...currentHooks, ...hooksOrHooksReducer };
|
|
79
|
+
try {
|
|
80
|
+
// Execute function with new hooks active
|
|
81
|
+
return fn();
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
// Always restore previous hooks, even if fn throws
|
|
85
|
+
currentHooks = prevHooks;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns the currently active hooks.
|
|
90
|
+
*
|
|
91
|
+
* Used by reactive primitives to access the current context:
|
|
92
|
+
* - atom() calls `getHooks().track?.(this)` when read
|
|
93
|
+
* - effect() calls `getHooks().scheduleNotify(run)` to schedule re-runs
|
|
94
|
+
* - effectInstance() calls `getHooks().scheduleCleanup?.(dispose)` for cleanup
|
|
95
|
+
*
|
|
96
|
+
* @returns The current Hooks object
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* // Inside atom's read function:
|
|
101
|
+
* function read() {
|
|
102
|
+
* getHooks().track?.(this); // Register with current tracking context
|
|
103
|
+
* return value;
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export function getHooks() {
|
|
108
|
+
return currentHooks;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sets the global hooks.
|
|
112
|
+
*
|
|
113
|
+
* @param hooks - The hooks to set
|
|
114
|
+
*/
|
|
115
|
+
export function setHooks(hooks) {
|
|
116
|
+
if (currentHooks !== globalHooks) {
|
|
117
|
+
throw new Error("Cannot set hooks while inside a reactive context");
|
|
118
|
+
}
|
|
119
|
+
currentHooks = { ...currentHooks, ...hooks };
|
|
120
|
+
globalHooks = currentHooks;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAkFA,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;;;;GASG;AACH,IAAI,YAAY,GAAU;IACxB,2CAA2C;IAC3C,cAAc,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE;CAC7B,CAAC;AAEF,IAAI,WAAW,GAAG,YAAY,CAAC;AAE/B,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,SAAS,CACvB,mBAAuE,EACvE,EAAW;IAEX,sCAAsC;IACtC,MAAM,SAAS,GAAG,YAAY,CAAC;IAE/B,2EAA2E;IAC3E,YAAY;QACV,OAAO,mBAAmB,KAAK,UAAU;YACvC,CAAC,CAAC,EAAE,GAAG,YAAY,EAAE,GAAG,mBAAmB,CAAC,YAAY,CAAC,EAAE;YAC3D,CAAC,CAAC,EAAE,GAAG,YAAY,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAElD,IAAI,CAAC;QACH,yCAAyC;QACzC,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,mDAAmD;QACnD,YAAY,GAAG,SAAS,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,QAAQ;IACtB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAqB;IAC5C,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,YAAY,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,KAAK,EAAE,CAAC;IAC7C,WAAW,GAAG,YAAY,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../src/hooks.test.ts"],"names":[],"mappings":""}
|