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.
Files changed (190) hide show
  1. package/README.md +1756 -0
  2. package/dist/abortableContext.d.ts +3 -0
  3. package/dist/abortableContext.d.ts.map +1 -0
  4. package/dist/abortableContext.js +48 -0
  5. package/dist/abortableContext.js.map +1 -0
  6. package/dist/action.d.ts +64 -0
  7. package/dist/action.d.ts.map +1 -0
  8. package/dist/action.js +208 -0
  9. package/dist/action.js.map +1 -0
  10. package/dist/action.test.d.ts +2 -0
  11. package/dist/action.test.d.ts.map +1 -0
  12. package/dist/action.test.js +189 -0
  13. package/dist/action.test.js.map +1 -0
  14. package/dist/async/abortable-guard.d.ts +25 -0
  15. package/dist/async/abortable-guard.d.ts.map +1 -0
  16. package/dist/async/abortable-guard.js +33 -0
  17. package/dist/async/abortable-guard.js.map +1 -0
  18. package/dist/async/abortable.d.ts +331 -0
  19. package/dist/async/abortable.d.ts.map +1 -0
  20. package/dist/async/abortable.js +410 -0
  21. package/dist/async/abortable.js.map +1 -0
  22. package/dist/async/abortable.test.d.ts +2 -0
  23. package/dist/async/abortable.test.d.ts.map +1 -0
  24. package/dist/async/abortable.test.js +535 -0
  25. package/dist/async/abortable.test.js.map +1 -0
  26. package/dist/async/abortable.typeCheck.d.ts +8 -0
  27. package/dist/async/abortable.typeCheck.d.ts.map +1 -0
  28. package/dist/async/abortable.typeCheck.js +138 -0
  29. package/dist/async/abortable.typeCheck.js.map +1 -0
  30. package/dist/async/async.d.ts +18 -0
  31. package/dist/async/async.d.ts.map +1 -0
  32. package/dist/async/async.js +20 -0
  33. package/dist/async/async.js.map +1 -0
  34. package/dist/async/index.d.ts +15 -0
  35. package/dist/async/index.d.ts.map +1 -0
  36. package/dist/async/index.js +13 -0
  37. package/dist/async/index.js.map +1 -0
  38. package/dist/async/loadable.d.ts +7 -0
  39. package/dist/async/loadable.d.ts.map +1 -0
  40. package/dist/async/loadable.js +52 -0
  41. package/dist/async/loadable.js.map +1 -0
  42. package/dist/async/loadable.test.d.ts +2 -0
  43. package/dist/async/loadable.test.d.ts.map +1 -0
  44. package/dist/async/loadable.test.js +322 -0
  45. package/dist/async/loadable.test.js.map +1 -0
  46. package/dist/async/promiseCache.d.ts +14 -0
  47. package/dist/async/promiseCache.d.ts.map +1 -0
  48. package/dist/async/promiseCache.js +29 -0
  49. package/dist/async/promiseCache.js.map +1 -0
  50. package/dist/async/read.d.ts +120 -0
  51. package/dist/async/read.d.ts.map +1 -0
  52. package/dist/async/read.js +286 -0
  53. package/dist/async/read.js.map +1 -0
  54. package/dist/async/read.test.d.ts +2 -0
  55. package/dist/async/read.test.d.ts.map +1 -0
  56. package/dist/async/read.test.js +419 -0
  57. package/dist/async/read.test.js.map +1 -0
  58. package/dist/async/read.typeCheck.d.ts +6 -0
  59. package/dist/async/read.typeCheck.d.ts.map +1 -0
  60. package/dist/async/read.typeCheck.js +101 -0
  61. package/dist/async/read.typeCheck.js.map +1 -0
  62. package/dist/async/safe.d.ts +230 -0
  63. package/dist/async/safe.d.ts.map +1 -0
  64. package/dist/async/safe.js +247 -0
  65. package/dist/async/safe.js.map +1 -0
  66. package/dist/async/safe.test.d.ts +2 -0
  67. package/dist/async/safe.test.d.ts.map +1 -0
  68. package/dist/async/safe.test.js +447 -0
  69. package/dist/async/safe.test.js.map +1 -0
  70. package/dist/async/utils.d.ts +17 -0
  71. package/dist/async/utils.d.ts.map +1 -0
  72. package/dist/async/utils.js +38 -0
  73. package/dist/async/utils.js.map +1 -0
  74. package/dist/async/wait.d.ts +120 -0
  75. package/dist/async/wait.d.ts.map +1 -0
  76. package/dist/async/wait.js +112 -0
  77. package/dist/async/wait.js.map +1 -0
  78. package/dist/async/wait.test.d.ts +2 -0
  79. package/dist/async/wait.test.d.ts.map +1 -0
  80. package/dist/async/wait.test.js +122 -0
  81. package/dist/async/wait.test.js.map +1 -0
  82. package/dist/async/wait.typeCheck.d.ts +6 -0
  83. package/dist/async/wait.typeCheck.d.ts.map +1 -0
  84. package/dist/async/wait.typeCheck.js +104 -0
  85. package/dist/async/wait.typeCheck.js.map +1 -0
  86. package/dist/atom.d.ts +46 -0
  87. package/dist/atom.d.ts.map +1 -0
  88. package/dist/atom.js +86 -0
  89. package/dist/atom.js.map +1 -0
  90. package/dist/atom.test.d.ts +2 -0
  91. package/dist/atom.test.d.ts.map +1 -0
  92. package/dist/atom.test.js +75 -0
  93. package/dist/atom.test.js.map +1 -0
  94. package/dist/batch.d.ts +15 -0
  95. package/dist/batch.d.ts.map +1 -0
  96. package/dist/batch.js +45 -0
  97. package/dist/batch.js.map +1 -0
  98. package/dist/defer.d.ts +56 -0
  99. package/dist/defer.d.ts.map +1 -0
  100. package/dist/defer.js +49 -0
  101. package/dist/defer.js.map +1 -0
  102. package/dist/effect.d.ts +91 -0
  103. package/dist/effect.d.ts.map +1 -0
  104. package/dist/effect.js +311 -0
  105. package/dist/effect.js.map +1 -0
  106. package/dist/effect.test.d.ts +2 -0
  107. package/dist/effect.test.d.ts.map +1 -0
  108. package/dist/effect.test.js +123 -0
  109. package/dist/effect.test.js.map +1 -0
  110. package/dist/emitter.d.ts +129 -0
  111. package/dist/emitter.d.ts.map +1 -0
  112. package/dist/emitter.js +164 -0
  113. package/dist/emitter.js.map +1 -0
  114. package/dist/emitter.test.d.ts +2 -0
  115. package/dist/emitter.test.d.ts.map +1 -0
  116. package/dist/emitter.test.js +259 -0
  117. package/dist/emitter.test.js.map +1 -0
  118. package/dist/equality.d.ts +66 -0
  119. package/dist/equality.d.ts.map +1 -0
  120. package/dist/equality.js +145 -0
  121. package/dist/equality.js.map +1 -0
  122. package/dist/event.d.ts +18 -0
  123. package/dist/event.d.ts.map +1 -0
  124. package/dist/event.js +166 -0
  125. package/dist/event.js.map +1 -0
  126. package/dist/event.test.d.ts +2 -0
  127. package/dist/event.test.d.ts.map +1 -0
  128. package/dist/event.test.js +167 -0
  129. package/dist/event.test.js.map +1 -0
  130. package/dist/hooks.d.ts +152 -0
  131. package/dist/hooks.d.ts.map +1 -0
  132. package/dist/hooks.js +122 -0
  133. package/dist/hooks.js.map +1 -0
  134. package/dist/hooks.test.d.ts +2 -0
  135. package/dist/hooks.test.d.ts.map +1 -0
  136. package/dist/hooks.test.js +99 -0
  137. package/dist/hooks.test.js.map +1 -0
  138. package/dist/index.d.ts +33 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +35 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/isPromiseLike.d.ts +10 -0
  143. package/dist/isPromiseLike.d.ts.map +1 -0
  144. package/dist/isPromiseLike.js +15 -0
  145. package/dist/isPromiseLike.js.map +1 -0
  146. package/dist/pick.d.ts +22 -0
  147. package/dist/pick.d.ts.map +1 -0
  148. package/dist/pick.js +46 -0
  149. package/dist/pick.js.map +1 -0
  150. package/dist/react/index.d.ts +8 -0
  151. package/dist/react/index.d.ts.map +1 -0
  152. package/dist/react/index.js +8 -0
  153. package/dist/react/index.js.map +1 -0
  154. package/dist/react/useRx.d.ts +14 -0
  155. package/dist/react/useRx.d.ts.map +1 -0
  156. package/dist/react/useRx.js +110 -0
  157. package/dist/react/useRx.js.map +1 -0
  158. package/dist/react/useRx.test.d.ts +2 -0
  159. package/dist/react/useRx.test.d.ts.map +1 -0
  160. package/dist/react/useRx.test.js +457 -0
  161. package/dist/react/useRx.test.js.map +1 -0
  162. package/dist/strictModeTest.d.ts +11 -0
  163. package/dist/strictModeTest.d.ts.map +1 -0
  164. package/dist/strictModeTest.js +41 -0
  165. package/dist/strictModeTest.js.map +1 -0
  166. package/dist/types.d.ts +606 -0
  167. package/dist/types.d.ts.map +1 -0
  168. package/dist/types.js +5 -0
  169. package/dist/types.js.map +1 -0
  170. package/dist/untrack.d.ts +14 -0
  171. package/dist/untrack.d.ts.map +1 -0
  172. package/dist/untrack.js +17 -0
  173. package/dist/untrack.js.map +1 -0
  174. package/dist/utils/withUse.d.ts +10 -0
  175. package/dist/utils/withUse.d.ts.map +1 -0
  176. package/dist/utils/withUse.js +21 -0
  177. package/dist/utils/withUse.js.map +1 -0
  178. package/dist/utils/withUse.test.d.ts +2 -0
  179. package/dist/utils/withUse.test.d.ts.map +1 -0
  180. package/dist/utils/withUse.test.js +233 -0
  181. package/dist/utils/withUse.test.js.map +1 -0
  182. package/dist/utils.d.ts +7 -0
  183. package/dist/utils.d.ts.map +1 -0
  184. package/dist/utils.js +7 -0
  185. package/dist/utils.js.map +1 -0
  186. package/dist/utils.test.d.ts +2 -0
  187. package/dist/utils.test.d.ts.map +1 -0
  188. package/dist/utils.test.js +119 -0
  189. package/dist/utils.test.js.map +1 -0
  190. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=event.test.d.ts.map
@@ -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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../src/hooks.test.ts"],"names":[],"mappings":""}