rask-ui 0.20.5 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/createComputed.d.ts +4 -0
  2. package/dist/createComputed.d.ts.map +1 -0
  3. package/dist/createComputed.js +69 -0
  4. package/dist/createEffect.d.ts +2 -0
  5. package/dist/createEffect.d.ts.map +1 -0
  6. package/dist/createEffect.js +29 -0
  7. package/dist/createRouter.d.ts +8 -0
  8. package/dist/createRouter.d.ts.map +1 -0
  9. package/dist/createRouter.js +27 -0
  10. package/dist/createState.d.ts +2 -0
  11. package/dist/createState.d.ts.map +1 -1
  12. package/dist/createState.js +40 -5
  13. package/dist/createTask.d.ts +31 -0
  14. package/dist/createTask.d.ts.map +1 -0
  15. package/dist/createTask.js +79 -0
  16. package/dist/createView.d.ts +18 -44
  17. package/dist/createView.d.ts.map +1 -1
  18. package/dist/createView.js +57 -48
  19. package/dist/error.d.ts +3 -14
  20. package/dist/error.d.ts.map +1 -1
  21. package/dist/error.js +14 -15
  22. package/dist/jsx.d.ts +10 -256
  23. package/dist/patchInferno.d.ts +6 -0
  24. package/dist/patchInferno.d.ts.map +1 -0
  25. package/dist/patchInferno.js +53 -0
  26. package/dist/scheduler.d.ts +4 -0
  27. package/dist/scheduler.d.ts.map +1 -0
  28. package/dist/scheduler.js +107 -0
  29. package/dist/tests/batch.test.d.ts +2 -0
  30. package/dist/tests/batch.test.d.ts.map +1 -0
  31. package/dist/tests/batch.test.js +244 -0
  32. package/dist/tests/createComputed.test.d.ts +2 -0
  33. package/dist/tests/createComputed.test.d.ts.map +1 -0
  34. package/dist/tests/createComputed.test.js +257 -0
  35. package/dist/tests/createContext.test.d.ts +2 -0
  36. package/dist/tests/createContext.test.d.ts.map +1 -0
  37. package/dist/tests/createContext.test.js +136 -0
  38. package/dist/tests/createEffect.test.d.ts +2 -0
  39. package/dist/tests/createEffect.test.d.ts.map +1 -0
  40. package/dist/tests/createEffect.test.js +467 -0
  41. package/dist/tests/createState.test.d.ts.map +1 -0
  42. package/dist/tests/createState.test.js +144 -0
  43. package/dist/tests/createTask.test.d.ts +2 -0
  44. package/dist/tests/createTask.test.d.ts.map +1 -0
  45. package/dist/tests/createTask.test.js +322 -0
  46. package/dist/tests/createView.test.d.ts.map +1 -0
  47. package/dist/{createView.test.js → tests/createView.test.js} +40 -40
  48. package/dist/tests/error.test.d.ts +2 -0
  49. package/dist/tests/error.test.d.ts.map +1 -0
  50. package/dist/tests/error.test.js +168 -0
  51. package/dist/tests/observation.test.d.ts.map +1 -0
  52. package/dist/tests/observation.test.js +341 -0
  53. package/dist/types.d.ts +2 -1
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/useComputed.d.ts +5 -0
  56. package/dist/useComputed.d.ts.map +1 -0
  57. package/dist/useComputed.js +69 -0
  58. package/dist/useQuery.d.ts +25 -0
  59. package/dist/useQuery.d.ts.map +1 -0
  60. package/dist/useQuery.js +25 -0
  61. package/dist/useSuspendAsync.d.ts +18 -0
  62. package/dist/useSuspendAsync.d.ts.map +1 -0
  63. package/dist/useSuspendAsync.js +37 -0
  64. package/dist/useTask.d.ts +25 -0
  65. package/dist/useTask.d.ts.map +1 -0
  66. package/dist/useTask.js +70 -0
  67. package/package.json +1 -1
  68. package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
  69. package/dist/asyncState.d.ts +0 -16
  70. package/dist/asyncState.d.ts.map +0 -1
  71. package/dist/asyncState.js +0 -24
  72. package/dist/context.d.ts +0 -5
  73. package/dist/context.d.ts.map +0 -1
  74. package/dist/context.js +0 -29
  75. package/dist/createAsync.test.d.ts +0 -2
  76. package/dist/createAsync.test.d.ts.map +0 -1
  77. package/dist/createAsync.test.js +0 -110
  78. package/dist/createMutation.test.d.ts +0 -2
  79. package/dist/createMutation.test.d.ts.map +0 -1
  80. package/dist/createMutation.test.js +0 -168
  81. package/dist/createQuery.test.d.ts +0 -2
  82. package/dist/createQuery.test.d.ts.map +0 -1
  83. package/dist/createQuery.test.js +0 -156
  84. package/dist/createRef.d.ts +0 -6
  85. package/dist/createRef.d.ts.map +0 -1
  86. package/dist/createRef.js +0 -8
  87. package/dist/createState.test.d.ts.map +0 -1
  88. package/dist/createState.test.js +0 -111
  89. package/dist/createView.test.d.ts.map +0 -1
  90. package/dist/observation.test.d.ts.map +0 -1
  91. package/dist/observation.test.js +0 -150
  92. package/dist/suspense.d.ts +0 -25
  93. package/dist/suspense.d.ts.map +0 -1
  94. package/dist/suspense.js +0 -97
  95. package/dist/test-setup.d.ts +0 -16
  96. package/dist/test-setup.d.ts.map +0 -1
  97. package/dist/test-setup.js +0 -40
  98. package/dist/test.d.ts +0 -2
  99. package/dist/test.d.ts.map +0 -1
  100. package/dist/test.js +0 -24
  101. /package/dist/{createState.test.d.ts → tests/createState.test.d.ts} +0 -0
  102. /package/dist/{createView.test.d.ts → tests/createView.test.d.ts} +0 -0
  103. /package/dist/{observation.test.d.ts → tests/observation.test.d.ts} +0 -0
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "./jsx-runtime";
2
+ import { describe, it, expect } from "vitest";
3
+ import { ErrorBoundary } from "../error";
4
+ import { render } from "../";
5
+ describe("ErrorBoundary", () => {
6
+ it("should render children when no error occurs", async () => {
7
+ function SafeChild() {
8
+ return () => _jsx("div", { children: "Safe content" });
9
+ }
10
+ function TestComponent() {
11
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(SafeChild, {}) }));
12
+ }
13
+ const container = document.createElement("div");
14
+ document.body.appendChild(container);
15
+ render(_jsx(TestComponent, {}), container);
16
+ await new Promise((resolve) => setTimeout(resolve, 10));
17
+ expect(container.textContent).toContain("Safe content");
18
+ expect(container.textContent).not.toContain("Error:");
19
+ document.body.removeChild(container);
20
+ });
21
+ it("should catch errors thrown in child components", async () => {
22
+ function ThrowingChild() {
23
+ return () => {
24
+ throw new Error("Child component error");
25
+ return _jsx("div", {});
26
+ };
27
+ }
28
+ function TestComponent() {
29
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(ThrowingChild, {}) }));
30
+ }
31
+ const container = document.createElement("div");
32
+ document.body.appendChild(container);
33
+ render(_jsx(TestComponent, {}), container);
34
+ await new Promise((resolve) => setTimeout(resolve, 10));
35
+ expect(container.textContent).toContain("Error:");
36
+ expect(container.textContent).toContain("Child component error");
37
+ document.body.removeChild(container);
38
+ });
39
+ it("should render custom error UI", async () => {
40
+ function ThrowingChild() {
41
+ return () => {
42
+ throw new Error("Something went wrong");
43
+ return _jsx("div", {});
44
+ };
45
+ }
46
+ function TestComponent() {
47
+ return () => (_jsx(ErrorBoundary, { error: (error) => (_jsxs("div", { class: "error-ui", children: [_jsx("h1", { children: "Oops!" }), _jsx("p", { children: String(error) })] })), children: _jsx(ThrowingChild, {}) }));
48
+ }
49
+ const container = document.createElement("div");
50
+ document.body.appendChild(container);
51
+ render(_jsx(TestComponent, {}), container);
52
+ await new Promise((resolve) => setTimeout(resolve, 10));
53
+ const errorUI = document.querySelector(".error-ui");
54
+ expect(errorUI).not.toBeNull();
55
+ expect(errorUI?.querySelector("h1")?.textContent).toBe("Oops!");
56
+ expect(errorUI?.textContent).toContain("Something went wrong");
57
+ document.body.removeChild(container);
58
+ });
59
+ it("should handle multiple children", async () => {
60
+ function SafeChild1() {
61
+ return () => _jsx("div", { children: "Child 1" });
62
+ }
63
+ function SafeChild2() {
64
+ return () => _jsx("div", { children: "Child 2" });
65
+ }
66
+ function TestComponent() {
67
+ return () => (_jsxs(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: [_jsx(SafeChild1, {}), _jsx(SafeChild2, {})] }));
68
+ }
69
+ const container = document.createElement("div");
70
+ document.body.appendChild(container);
71
+ render(_jsx(TestComponent, {}), container);
72
+ await new Promise((resolve) => setTimeout(resolve, 10));
73
+ expect(container.textContent).toContain("Child 1");
74
+ expect(container.textContent).toContain("Child 2");
75
+ document.body.removeChild(container);
76
+ });
77
+ it("should catch errors from nested children", async () => {
78
+ function DeepChild() {
79
+ return () => {
80
+ throw new Error("Deep error");
81
+ return _jsx("div", {});
82
+ };
83
+ }
84
+ function MiddleChild() {
85
+ return () => _jsx(DeepChild, {});
86
+ }
87
+ function TestComponent() {
88
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Caught: ", String(error)] }), children: _jsx(MiddleChild, {}) }));
89
+ }
90
+ const container = document.createElement("div");
91
+ document.body.appendChild(container);
92
+ render(_jsx(TestComponent, {}), container);
93
+ await new Promise((resolve) => setTimeout(resolve, 10));
94
+ expect(container.textContent).toContain("Caught:");
95
+ expect(container.textContent).toContain("Deep error");
96
+ document.body.removeChild(container);
97
+ });
98
+ it("should allow nested error boundaries", async () => {
99
+ function ThrowingChild() {
100
+ return () => {
101
+ throw new Error("Inner error");
102
+ return _jsx("div", {});
103
+ };
104
+ }
105
+ function TestComponent() {
106
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Outer: ", String(error)] }), children: _jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Inner: ", String(error)] }), children: _jsx(ThrowingChild, {}) }) }));
107
+ }
108
+ const container = document.createElement("div");
109
+ document.body.appendChild(container);
110
+ render(_jsx(TestComponent, {}), container);
111
+ await new Promise((resolve) => setTimeout(resolve, 10));
112
+ // Inner boundary should catch the error
113
+ expect(container.textContent).toContain("Inner:");
114
+ expect(container.textContent).not.toContain("Outer:");
115
+ document.body.removeChild(container);
116
+ });
117
+ it("should handle string errors", async () => {
118
+ function ThrowingChild() {
119
+ return () => {
120
+ throw "String error";
121
+ return _jsx("div", {});
122
+ };
123
+ }
124
+ function TestComponent() {
125
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(ThrowingChild, {}) }));
126
+ }
127
+ const container = document.createElement("div");
128
+ document.body.appendChild(container);
129
+ render(_jsx(TestComponent, {}), container);
130
+ await new Promise((resolve) => setTimeout(resolve, 10));
131
+ expect(container.textContent).toContain("String error");
132
+ document.body.removeChild(container);
133
+ });
134
+ it("should handle object errors", async () => {
135
+ function ThrowingChild() {
136
+ return () => {
137
+ throw { message: "Custom error object", code: 500 };
138
+ return _jsx("div", {});
139
+ };
140
+ }
141
+ function TestComponent() {
142
+ return () => (_jsx(ErrorBoundary, { error: (error) => (_jsxs("div", { children: ["Error: ", error.message, " (Code: ", error.code, ")"] })), children: _jsx(ThrowingChild, {}) }));
143
+ }
144
+ const container = document.createElement("div");
145
+ document.body.appendChild(container);
146
+ render(_jsx(TestComponent, {}), container);
147
+ await new Promise((resolve) => setTimeout(resolve, 10));
148
+ expect(container.textContent).toContain("Custom error object");
149
+ expect(container.textContent).toContain("500");
150
+ document.body.removeChild(container);
151
+ });
152
+ it("should switch back to children if error is cleared", async () => {
153
+ // Note: This test demonstrates the current behavior
154
+ // In practice, error clearing would require additional implementation
155
+ function SafeChild() {
156
+ return () => _jsx("div", { children: "Safe content" });
157
+ }
158
+ function TestComponent() {
159
+ return () => (_jsx(ErrorBoundary, { error: (error) => _jsxs("div", { children: ["Error: ", String(error)] }), children: _jsx(SafeChild, {}) }));
160
+ }
161
+ const container = document.createElement("div");
162
+ document.body.appendChild(container);
163
+ render(_jsx(TestComponent, {}), container);
164
+ await new Promise((resolve) => setTimeout(resolve, 10));
165
+ expect(container.textContent).toContain("Safe content");
166
+ document.body.removeChild(container);
167
+ });
168
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observation.test.d.ts","sourceRoot":"","sources":["../../src/tests/observation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,341 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { Signal, Observer, getCurrentObserver } from "../observation";
3
+ describe("Signal-Observer System (Optimized)", () => {
4
+ describe("Observer", () => {
5
+ it("should track signals during observation", () => {
6
+ const callback = vi.fn();
7
+ const observer = new Observer(callback);
8
+ const signal = new Signal();
9
+ const dispose = observer.observe();
10
+ observer.subscribeSignal(signal);
11
+ dispose();
12
+ signal.notify();
13
+ return new Promise((resolve) => {
14
+ queueMicrotask(() => {
15
+ expect(callback).toHaveBeenCalledTimes(1);
16
+ resolve(undefined);
17
+ });
18
+ });
19
+ });
20
+ it("should handle multiple signals", async () => {
21
+ const callback = vi.fn();
22
+ const observer = new Observer(callback);
23
+ const signal1 = new Signal();
24
+ const signal2 = new Signal();
25
+ const dispose = observer.observe();
26
+ observer.subscribeSignal(signal1);
27
+ observer.subscribeSignal(signal2);
28
+ dispose();
29
+ signal1.notify();
30
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
31
+ expect(callback).toHaveBeenCalledTimes(1);
32
+ signal2.notify();
33
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
34
+ expect(callback).toHaveBeenCalledTimes(2);
35
+ });
36
+ it("should clear signals when observing again", async () => {
37
+ let callCount = 0;
38
+ const observer = new Observer(() => {
39
+ callCount++;
40
+ });
41
+ const signal1 = new Signal();
42
+ const signal2 = new Signal();
43
+ // First observation
44
+ let dispose = observer.observe();
45
+ observer.subscribeSignal(signal1);
46
+ dispose();
47
+ // Second observation - should clear previous signals
48
+ dispose = observer.observe();
49
+ observer.subscribeSignal(signal2);
50
+ dispose();
51
+ // Notify first signal - should not trigger observer
52
+ signal1.notify();
53
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
54
+ expect(callCount).toBe(0);
55
+ // Notify second signal - should trigger observer
56
+ signal2.notify();
57
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
58
+ expect(callCount).toBe(1);
59
+ });
60
+ it("should dispose of all signal subscriptions", async () => {
61
+ const callback = vi.fn();
62
+ const observer = new Observer(callback);
63
+ const signal = new Signal();
64
+ const dispose = observer.observe();
65
+ observer.subscribeSignal(signal);
66
+ dispose();
67
+ observer.dispose();
68
+ signal.notify();
69
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
70
+ expect(callback).not.toHaveBeenCalled();
71
+ });
72
+ it("should not notify after disposal", async () => {
73
+ const callback = vi.fn();
74
+ const observer = new Observer(callback);
75
+ const signal = new Signal();
76
+ const dispose = observer.observe();
77
+ observer.subscribeSignal(signal);
78
+ dispose();
79
+ observer.dispose();
80
+ signal.notify();
81
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
82
+ expect(callback).not.toHaveBeenCalled();
83
+ });
84
+ it("should set current observer during observation", () => {
85
+ const observer = new Observer(() => { });
86
+ expect(getCurrentObserver()).toBeUndefined();
87
+ const dispose = observer.observe();
88
+ expect(getCurrentObserver()).toBe(observer);
89
+ dispose();
90
+ expect(getCurrentObserver()).toBeUndefined();
91
+ });
92
+ it("should handle nested observations with stack", () => {
93
+ const observer1 = new Observer(() => { });
94
+ const observer2 = new Observer(() => { });
95
+ const dispose1 = observer1.observe();
96
+ expect(getCurrentObserver()).toBe(observer1);
97
+ const dispose2 = observer2.observe();
98
+ expect(getCurrentObserver()).toBe(observer2);
99
+ dispose2();
100
+ expect(getCurrentObserver()).toBe(observer1);
101
+ dispose1();
102
+ expect(getCurrentObserver()).toBeUndefined();
103
+ });
104
+ });
105
+ describe("Signal", () => {
106
+ it("should notify all subscribed observers", async () => {
107
+ const signal = new Signal();
108
+ const callback1 = vi.fn();
109
+ const callback2 = vi.fn();
110
+ const observer1 = new Observer(callback1);
111
+ const observer2 = new Observer(callback2);
112
+ const dispose1 = observer1.observe();
113
+ observer1.subscribeSignal(signal);
114
+ dispose1();
115
+ const dispose2 = observer2.observe();
116
+ observer2.subscribeSignal(signal);
117
+ dispose2();
118
+ signal.notify();
119
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
120
+ expect(callback1).toHaveBeenCalledTimes(1);
121
+ expect(callback2).toHaveBeenCalledTimes(1);
122
+ });
123
+ it("should handle empty signal (no subscribers)", () => {
124
+ const signal = new Signal();
125
+ // Should not throw
126
+ expect(() => signal.notify()).not.toThrow();
127
+ });
128
+ });
129
+ describe("Optimization: Epoch Barrier", () => {
130
+ it("should NOT notify new subscriptions created during notify", async () => {
131
+ const signal = new Signal();
132
+ const callback1 = vi.fn();
133
+ const callback2 = vi.fn();
134
+ const observer1 = new Observer(callback1);
135
+ const observer2 = new Observer(() => {
136
+ callback2();
137
+ // Subscribe a new observer DURING notification
138
+ const dispose = observer3.observe();
139
+ observer3.subscribeSignal(signal);
140
+ dispose();
141
+ });
142
+ const observer3 = new Observer(vi.fn());
143
+ const dispose1 = observer1.observe();
144
+ observer1.subscribeSignal(signal);
145
+ dispose1();
146
+ const dispose2 = observer2.observe();
147
+ observer2.subscribeSignal(signal);
148
+ dispose2();
149
+ signal.notify();
150
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
151
+ // observer1 and observer2 should fire
152
+ expect(callback1).toHaveBeenCalledTimes(1);
153
+ expect(callback2).toHaveBeenCalledTimes(1);
154
+ // observer3 should NOT fire in the same notify pass (added during notify)
155
+ // This is tested implicitly - if it fired, we'd see 3 calls above
156
+ });
157
+ it("should notify new subscriptions on NEXT notify", async () => {
158
+ const signal = new Signal();
159
+ let observer3Created = false;
160
+ const callback3 = vi.fn();
161
+ const observer3 = new Observer(callback3);
162
+ const observer1 = new Observer(() => {
163
+ if (!observer3Created) {
164
+ observer3Created = true;
165
+ const dispose = observer3.observe();
166
+ observer3.subscribeSignal(signal);
167
+ dispose();
168
+ }
169
+ });
170
+ const dispose1 = observer1.observe();
171
+ observer1.subscribeSignal(signal);
172
+ dispose1();
173
+ // First notify - observer3 gets created but should not fire
174
+ signal.notify();
175
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
176
+ expect(callback3).toHaveBeenCalledTimes(0);
177
+ // Second notify - observer3 should now fire
178
+ signal.notify();
179
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
180
+ expect(callback3).toHaveBeenCalledTimes(1);
181
+ });
182
+ });
183
+ describe("Optimization: Safe Unsubscribe During Notify", () => {
184
+ it("should handle observer disposing itself during notification", async () => {
185
+ const signal = new Signal();
186
+ const callback1 = vi.fn();
187
+ const callback2 = vi.fn();
188
+ const observer1 = new Observer(() => {
189
+ callback1();
190
+ observer1.dispose(); // Dispose itself during notification
191
+ });
192
+ const observer2 = new Observer(callback2);
193
+ const dispose1 = observer1.observe();
194
+ observer1.subscribeSignal(signal);
195
+ dispose1();
196
+ const dispose2 = observer2.observe();
197
+ observer2.subscribeSignal(signal);
198
+ dispose2();
199
+ // First notify - both should fire, observer1 disposes itself
200
+ signal.notify();
201
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
202
+ expect(callback1).toHaveBeenCalledTimes(1);
203
+ expect(callback2).toHaveBeenCalledTimes(1);
204
+ // Second notify - only observer2 should fire
205
+ signal.notify();
206
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
207
+ expect(callback1).toHaveBeenCalledTimes(1); // Still 1
208
+ expect(callback2).toHaveBeenCalledTimes(2); // Incremented
209
+ });
210
+ it("should handle observer clearing signals during notification", async () => {
211
+ const signal1 = new Signal();
212
+ const signal2 = new Signal();
213
+ const callback = vi.fn();
214
+ const observer = new Observer(() => {
215
+ callback();
216
+ // Re-observe, which clears all signals
217
+ const dispose = observer.observe();
218
+ observer.subscribeSignal(signal2);
219
+ dispose();
220
+ });
221
+ const dispose = observer.observe();
222
+ observer.subscribeSignal(signal1);
223
+ dispose();
224
+ signal1.notify();
225
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
226
+ expect(callback).toHaveBeenCalledTimes(1);
227
+ // Should not respond to signal1 anymore
228
+ signal1.notify();
229
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
230
+ expect(callback).toHaveBeenCalledTimes(1);
231
+ // Should respond to signal2
232
+ signal2.notify();
233
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
234
+ expect(callback).toHaveBeenCalledTimes(2);
235
+ });
236
+ });
237
+ describe("Optimization: Memory Efficiency", () => {
238
+ it("should clean up subscriptions when observer is disposed", async () => {
239
+ const signal = new Signal();
240
+ const observers = Array.from({ length: 100 }, () => new Observer(() => { }));
241
+ // Subscribe all observers
242
+ observers.forEach((observer) => {
243
+ const dispose = observer.observe();
244
+ observer.subscribeSignal(signal);
245
+ dispose();
246
+ });
247
+ // Dispose half of them
248
+ observers.slice(0, 50).forEach((observer) => observer.dispose());
249
+ // Notify - should not throw or have issues
250
+ expect(() => signal.notify()).not.toThrow();
251
+ // Cleanup
252
+ observers.slice(50).forEach((observer) => observer.dispose());
253
+ });
254
+ it("should handle multiple observe/clearSignals cycles", () => {
255
+ const observer = new Observer(() => { });
256
+ const signal1 = new Signal();
257
+ const signal2 = new Signal();
258
+ const signal3 = new Signal();
259
+ // First cycle
260
+ let dispose = observer.observe();
261
+ observer.subscribeSignal(signal1);
262
+ dispose();
263
+ // Second cycle - clears signal1
264
+ dispose = observer.observe();
265
+ observer.subscribeSignal(signal2);
266
+ dispose();
267
+ // Third cycle - clears signal2
268
+ dispose = observer.observe();
269
+ observer.subscribeSignal(signal3);
270
+ dispose();
271
+ // Should not throw
272
+ expect(() => {
273
+ signal1.notify();
274
+ signal2.notify();
275
+ signal3.notify();
276
+ }).not.toThrow();
277
+ });
278
+ });
279
+ describe("Edge Cases", () => {
280
+ it("should handle rapid consecutive notifies", async () => {
281
+ const signal = new Signal();
282
+ const callback = vi.fn();
283
+ const observer = new Observer(callback);
284
+ const dispose = observer.observe();
285
+ observer.subscribeSignal(signal);
286
+ dispose();
287
+ // Rapid notifies
288
+ signal.notify();
289
+ signal.notify();
290
+ signal.notify();
291
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
292
+ // Due to batching, exact count depends on implementation
293
+ // But should have been called at least once
294
+ expect(callback).toHaveBeenCalled();
295
+ });
296
+ it("should not notify after subscribeSignal on disposed observer", async () => {
297
+ const signal = new Signal();
298
+ const callback = vi.fn();
299
+ const observer = new Observer(callback);
300
+ observer.dispose();
301
+ const dispose = observer.observe();
302
+ observer.subscribeSignal(signal);
303
+ dispose();
304
+ signal.notify();
305
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
306
+ expect(callback).not.toHaveBeenCalled();
307
+ });
308
+ it("should handle complex subscription graph", async () => {
309
+ // Create a diamond dependency pattern
310
+ const signal1 = new Signal();
311
+ const signal2 = new Signal();
312
+ const signal3 = new Signal();
313
+ const calls = [];
314
+ const observer1 = new Observer(() => {
315
+ calls.push("o1");
316
+ signal2.notify();
317
+ signal3.notify();
318
+ });
319
+ const observer2 = new Observer(() => calls.push("o2"));
320
+ const observer3 = new Observer(() => calls.push("o3"));
321
+ // observer1 listens to signal1
322
+ let dispose = observer1.observe();
323
+ observer1.subscribeSignal(signal1);
324
+ dispose();
325
+ // observer2 listens to signal2
326
+ dispose = observer2.observe();
327
+ observer2.subscribeSignal(signal2);
328
+ dispose();
329
+ // observer3 listens to signal3
330
+ dispose = observer3.observe();
331
+ observer3.subscribeSignal(signal3);
332
+ dispose();
333
+ signal1.notify();
334
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
335
+ // All should have been called
336
+ expect(calls).toContain("o1");
337
+ expect(calls).toContain("o2");
338
+ expect(calls).toContain("o3");
339
+ });
340
+ });
341
+ });
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ClipboardEvent as InfernoClipboardEvent, ChangeEvent as InfernoChangeEvent, DragEvent as InfernoDragEvent, FocusEvent as InfernoFocusEvent, FormEvent as InfernoFormEvent, InfernoMouseEvent, InfernoAnimationEvent, InfernoKeyboardEvent, InfernoPointerEvent, InfernoTouchEvent, InfernoWheelEvent, InfernoTransitionEvent, CompositionEvent as InfernoCompositionEvent } from "inferno";
1
+ import type { ClipboardEvent as InfernoClipboardEvent, ChangeEvent as InfernoChangeEvent, DragEvent as InfernoDragEvent, FocusEvent as InfernoFocusEvent, FormEvent as InfernoFormEvent, InfernoMouseEvent, InfernoAnimationEvent, InfernoKeyboardEvent, InfernoPointerEvent, InfernoTouchEvent, InfernoWheelEvent, InfernoTransitionEvent, CompositionEvent as InfernoCompositionEvent, Inferno } from "inferno";
2
2
  declare global {
3
3
  namespace Rask {
4
4
  type MouseEvent<T = Element> = InfernoMouseEvent<T>;
@@ -14,6 +14,7 @@ declare global {
14
14
  type FocusEvent<T = Element> = InfernoFocusEvent<T>;
15
15
  type FormEvent<T = Element> = InfernoFormEvent<T>;
16
16
  type CompositionEvent<T = Element> = InfernoCompositionEvent<T>;
17
+ type ElementProps<T extends keyof JSX.IntrinsicElements> = Omit<JSX.IntrinsicElements[T], keyof Inferno.Attributes>;
17
18
  }
18
19
  }
19
20
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,IAAI,qBAAqB,EACvC,WAAW,IAAI,kBAAkB,EACjC,SAAS,IAAI,gBAAgB,EAC7B,UAAU,IAAI,iBAAiB,EAC/B,SAAS,IAAI,gBAAgB,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,IAAI,uBAAuB,EAC5C,MAAM,SAAS,CAAC;AAEjB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI,CAAC;QAEb,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACjE,KAAY,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC/D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACrE,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC7D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI,uBAAuB,CAAC,CAAC,CAAC,CAAC;KACxE;CACF;AAGD,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,IAAI,qBAAqB,EACvC,WAAW,IAAI,kBAAkB,EACjC,SAAS,IAAI,gBAAgB,EAC7B,UAAU,IAAI,iBAAiB,EAC/B,SAAS,IAAI,gBAAgB,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,IAAI,uBAAuB,EAC3C,OAAO,EACR,MAAM,SAAS,CAAC;AAEjB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI,CAAC;QAEb,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACjE,KAAY,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC/D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACrE,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC7D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI,uBAAuB,CAAC,CAAC,CAAC,CAAC;QACvE,KAAY,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,iBAAiB,IAAI,IAAI,CACpE,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EACxB,MAAM,OAAO,CAAC,UAAU,CACzB,CAAC;KACH;CACF;AAGD,OAAO,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type Computed<T extends Record<string, () => any>> = {
2
+ [K in keyof T]: ReturnType<T[K]>;
3
+ };
4
+ export declare function useComputed<T extends Record<string, () => any>>(computed: T): Computed<T>;
5
+ //# sourceMappingURL=useComputed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComputed.d.ts","sourceRoot":"","sources":["../src/useComputed.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,IAAI;KACzD,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACjC,CAAC;AAEF,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,EAC7D,QAAQ,EAAE,CAAC,GACV,QAAQ,CAAC,CAAC,CAAC,CA0Eb"}
@@ -0,0 +1,69 @@
1
+ import { getCurrentComponent, useCleanup } from "./component";
2
+ import { INSPECT_MARKER, INSPECTOR_ENABLED } from "./inspect";
3
+ import { getCurrentObserver, Observer, Signal } from "./observation";
4
+ export function useComputed(computed) {
5
+ const currentComponent = getCurrentComponent();
6
+ const proxy = {};
7
+ let notifyInspectorRef = {};
8
+ for (const prop in computed) {
9
+ let isDirty = true;
10
+ let value;
11
+ const signal = new Signal();
12
+ const computedObserver = new Observer(() => {
13
+ isDirty = true;
14
+ signal.notify();
15
+ if (INSPECTOR_ENABLED) {
16
+ notifyInspectorRef.current?.notify({
17
+ type: "computed",
18
+ path: notifyInspectorRef.current.path.concat(prop),
19
+ isDirty: true,
20
+ value,
21
+ });
22
+ }
23
+ });
24
+ useCleanup(() => computedObserver.dispose());
25
+ Object.defineProperty(proxy, prop, {
26
+ enumerable: true,
27
+ configurable: true,
28
+ get() {
29
+ const currentObserver = getCurrentObserver();
30
+ if (currentObserver) {
31
+ currentObserver.subscribeSignal(signal);
32
+ }
33
+ if (isDirty) {
34
+ const stopObserving = computedObserver.observe();
35
+ value = computed[prop]();
36
+ stopObserving();
37
+ isDirty = false;
38
+ if (INSPECTOR_ENABLED) {
39
+ notifyInspectorRef.current?.notify({
40
+ type: "computed",
41
+ path: notifyInspectorRef.current.path.concat(prop),
42
+ isDirty: false,
43
+ value,
44
+ });
45
+ }
46
+ return value;
47
+ }
48
+ return value;
49
+ },
50
+ });
51
+ }
52
+ if (INSPECTOR_ENABLED) {
53
+ Object.defineProperty(proxy, INSPECT_MARKER, {
54
+ enumerable: false,
55
+ configurable: false,
56
+ get() {
57
+ return !notifyInspectorRef.current;
58
+ },
59
+ set: (value) => {
60
+ Object.defineProperty(notifyInspectorRef, "current", {
61
+ get() {
62
+ return value.current;
63
+ },
64
+ });
65
+ },
66
+ });
67
+ }
68
+ return proxy;
69
+ }
@@ -0,0 +1,25 @@
1
+ type QueryState<T, I = null> = {
2
+ isPending: true;
3
+ error: null;
4
+ value: I;
5
+ } | {
6
+ isPending: false;
7
+ error: null;
8
+ value: T;
9
+ } | {
10
+ isPending: false;
11
+ error: string;
12
+ value: I | null;
13
+ };
14
+ export type Query<T, P, I = null> = [
15
+ QueryState<T, I>,
16
+ [
17
+ P
18
+ ] extends [never] ? (params: P) => void : () => void
19
+ ];
20
+ export declare function useQuery<T>(query: () => Promise<T>): Query<T, never>;
21
+ export declare function useQuery<T>(query: () => Promise<T>, initialValue: T): Query<T, never, T>;
22
+ export declare function useQuery<T, P>(query: (params: P) => Promise<T>): Query<T, never>;
23
+ export declare function useQuery<T, P>(query: (params: P) => Promise<T>, initialValue: T): Query<T, never, T>;
24
+ export {};
25
+ //# sourceMappingURL=useQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../src/useQuery.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,IACvB;IACE,SAAS,EAAE,IAAI,CAAC;IAChB,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,CAAC,CAAC;CACV,GACD;IACE,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;CACjB,CAAC;AAEN,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI;IAClC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;IAChB;QAAC,CAAC;KAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;CACvD,CAAC;AAEF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACtE,wBAAgB,QAAQ,CAAC,CAAC,EACxB,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACvB,YAAY,EAAE,CAAC,GACd,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACtB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC/B,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACnB,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAC3B,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAChC,YAAY,EAAE,CAAC,GACd,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { useEffect } from "./useEffect";
2
+ import { useTask } from "./useTask";
3
+ export function useQuery(query, initialValue) {
4
+ const [taskState, runTask] = useTask(query);
5
+ let lastValue = initialValue === undefined ? null : initialValue;
6
+ useEffect(() => {
7
+ if (taskState.result) {
8
+ lastValue = taskState.result;
9
+ }
10
+ });
11
+ return [
12
+ {
13
+ get value() {
14
+ return taskState.result || lastValue;
15
+ },
16
+ get isPending() {
17
+ return taskState.isPending;
18
+ },
19
+ get error() {
20
+ return taskState.error;
21
+ },
22
+ },
23
+ runTask,
24
+ ];
25
+ }