atomirx 0.0.7 → 0.1.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 (138) hide show
  1. package/README.md +198 -2234
  2. package/bin/cli.js +90 -0
  3. package/dist/core/derived.d.ts +2 -2
  4. package/dist/core/effect.d.ts +3 -2
  5. package/dist/core/onCreateHook.d.ts +15 -2
  6. package/dist/core/onErrorHook.d.ts +4 -1
  7. package/dist/core/pool.d.ts +78 -0
  8. package/dist/core/pool.test.d.ts +1 -0
  9. package/dist/core/select-boolean.test.d.ts +1 -0
  10. package/dist/core/select-pool.test.d.ts +1 -0
  11. package/dist/core/select.d.ts +278 -86
  12. package/dist/core/types.d.ts +233 -1
  13. package/dist/core/withAbort.d.ts +95 -0
  14. package/dist/core/withReady.d.ts +3 -3
  15. package/dist/devtools/constants.d.ts +41 -0
  16. package/dist/devtools/index.cjs +1 -0
  17. package/dist/devtools/index.d.ts +29 -0
  18. package/dist/devtools/index.js +429 -0
  19. package/dist/devtools/registry.d.ts +98 -0
  20. package/dist/devtools/registry.test.d.ts +1 -0
  21. package/dist/devtools/setup.d.ts +61 -0
  22. package/dist/devtools/types.d.ts +311 -0
  23. package/dist/index-BZEnfIcB.cjs +1 -0
  24. package/dist/index-BbPZhsDl.js +1653 -0
  25. package/dist/index.cjs +1 -1
  26. package/dist/index.d.ts +4 -3
  27. package/dist/index.js +18 -14
  28. package/dist/onDispatchHook-C8yLzr-o.cjs +1 -0
  29. package/dist/onDispatchHook-SKbiIUaJ.js +5 -0
  30. package/dist/onErrorHook-BGGy3tqK.js +38 -0
  31. package/dist/onErrorHook-DHBASmYw.cjs +1 -0
  32. package/dist/react/index.cjs +1 -30
  33. package/dist/react/index.js +206 -791
  34. package/dist/react/onDispatchHook.d.ts +106 -0
  35. package/dist/react/useAction.d.ts +4 -1
  36. package/dist/react-devtools/DevToolsPanel.d.ts +93 -0
  37. package/dist/react-devtools/EntityDetails.d.ts +10 -0
  38. package/dist/react-devtools/EntityList.d.ts +15 -0
  39. package/dist/react-devtools/LogList.d.ts +12 -0
  40. package/dist/react-devtools/hooks.d.ts +50 -0
  41. package/dist/react-devtools/index.cjs +1 -0
  42. package/dist/react-devtools/index.d.ts +31 -0
  43. package/dist/react-devtools/index.js +1589 -0
  44. package/dist/react-devtools/styles.d.ts +148 -0
  45. package/package.json +26 -2
  46. package/skills/atomirx/SKILL.md +456 -0
  47. package/skills/atomirx/references/async-patterns.md +188 -0
  48. package/skills/atomirx/references/atom-patterns.md +238 -0
  49. package/skills/atomirx/references/deferred-loading.md +191 -0
  50. package/skills/atomirx/references/derived-patterns.md +428 -0
  51. package/skills/atomirx/references/effect-patterns.md +426 -0
  52. package/skills/atomirx/references/error-handling.md +140 -0
  53. package/skills/atomirx/references/hooks.md +322 -0
  54. package/skills/atomirx/references/pool-patterns.md +229 -0
  55. package/skills/atomirx/references/react-integration.md +411 -0
  56. package/skills/atomirx/references/rules.md +407 -0
  57. package/skills/atomirx/references/select-context.md +309 -0
  58. package/skills/atomirx/references/service-template.md +172 -0
  59. package/skills/atomirx/references/store-template.md +205 -0
  60. package/skills/atomirx/references/testing-patterns.md +431 -0
  61. package/coverage/base.css +0 -224
  62. package/coverage/block-navigation.js +0 -87
  63. package/coverage/clover.xml +0 -1440
  64. package/coverage/coverage-final.json +0 -14
  65. package/coverage/favicon.png +0 -0
  66. package/coverage/index.html +0 -131
  67. package/coverage/prettify.css +0 -1
  68. package/coverage/prettify.js +0 -2
  69. package/coverage/sort-arrow-sprite.png +0 -0
  70. package/coverage/sorter.js +0 -210
  71. package/coverage/src/core/atom.ts.html +0 -889
  72. package/coverage/src/core/batch.ts.html +0 -223
  73. package/coverage/src/core/define.ts.html +0 -805
  74. package/coverage/src/core/emitter.ts.html +0 -919
  75. package/coverage/src/core/equality.ts.html +0 -631
  76. package/coverage/src/core/hook.ts.html +0 -460
  77. package/coverage/src/core/index.html +0 -281
  78. package/coverage/src/core/isAtom.ts.html +0 -100
  79. package/coverage/src/core/isPromiseLike.ts.html +0 -133
  80. package/coverage/src/core/onCreateHook.ts.html +0 -138
  81. package/coverage/src/core/scheduleNotifyHook.ts.html +0 -94
  82. package/coverage/src/core/types.ts.html +0 -523
  83. package/coverage/src/core/withUse.ts.html +0 -253
  84. package/coverage/src/index.html +0 -116
  85. package/coverage/src/index.ts.html +0 -106
  86. package/dist/index-CBVj1kSj.js +0 -1350
  87. package/dist/index-Cxk9v0um.cjs +0 -1
  88. package/scripts/publish.js +0 -198
  89. package/src/core/atom.test.ts +0 -633
  90. package/src/core/atom.ts +0 -311
  91. package/src/core/atomState.test.ts +0 -342
  92. package/src/core/atomState.ts +0 -256
  93. package/src/core/batch.test.ts +0 -257
  94. package/src/core/batch.ts +0 -172
  95. package/src/core/define.test.ts +0 -343
  96. package/src/core/define.ts +0 -243
  97. package/src/core/derived.test.ts +0 -1215
  98. package/src/core/derived.ts +0 -450
  99. package/src/core/effect.test.ts +0 -802
  100. package/src/core/effect.ts +0 -188
  101. package/src/core/emitter.test.ts +0 -364
  102. package/src/core/emitter.ts +0 -392
  103. package/src/core/equality.test.ts +0 -392
  104. package/src/core/equality.ts +0 -182
  105. package/src/core/getAtomState.ts +0 -69
  106. package/src/core/hook.test.ts +0 -227
  107. package/src/core/hook.ts +0 -177
  108. package/src/core/isAtom.ts +0 -27
  109. package/src/core/isPromiseLike.test.ts +0 -72
  110. package/src/core/isPromiseLike.ts +0 -16
  111. package/src/core/onCreateHook.ts +0 -107
  112. package/src/core/onErrorHook.test.ts +0 -350
  113. package/src/core/onErrorHook.ts +0 -52
  114. package/src/core/promiseCache.test.ts +0 -241
  115. package/src/core/promiseCache.ts +0 -284
  116. package/src/core/scheduleNotifyHook.ts +0 -53
  117. package/src/core/select.ts +0 -729
  118. package/src/core/selector.test.ts +0 -799
  119. package/src/core/types.ts +0 -389
  120. package/src/core/withReady.test.ts +0 -534
  121. package/src/core/withReady.ts +0 -191
  122. package/src/core/withUse.test.ts +0 -249
  123. package/src/core/withUse.ts +0 -56
  124. package/src/index.test.ts +0 -80
  125. package/src/index.ts +0 -65
  126. package/src/react/index.ts +0 -21
  127. package/src/react/rx.test.tsx +0 -571
  128. package/src/react/rx.tsx +0 -531
  129. package/src/react/strictModeTest.tsx +0 -71
  130. package/src/react/useAction.test.ts +0 -987
  131. package/src/react/useAction.ts +0 -607
  132. package/src/react/useSelector.test.ts +0 -182
  133. package/src/react/useSelector.ts +0 -292
  134. package/src/react/useStable.test.ts +0 -553
  135. package/src/react/useStable.ts +0 -288
  136. package/tsconfig.json +0 -9
  137. package/v2.md +0 -725
  138. package/vite.config.ts +0 -39
@@ -1,553 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { useStable } from "./useStable";
3
- import { isStableFn } from "../core/equality";
4
- import { wrappers } from "./strictModeTest";
5
-
6
- describe.each(wrappers)("useStable - $mode", ({ renderHook }) => {
7
- describe("basic stabilization", () => {
8
- it("should return stable object reference across renders", () => {
9
- const { result, rerender } = renderHook(
10
- (props) =>
11
- useStable({
12
- name: props.name,
13
- count: props.count,
14
- }),
15
- { initialProps: { name: "John", count: 1 } }
16
- );
17
-
18
- const firstResult = result.current;
19
-
20
- rerender({ name: "John", count: 1 });
21
-
22
- expect(result.current).toBe(firstResult);
23
- });
24
-
25
- it("should update property when value changes", () => {
26
- const { result, rerender } = renderHook(
27
- (props) =>
28
- useStable({
29
- name: props.name,
30
- }),
31
- { initialProps: { name: "John" } }
32
- );
33
-
34
- expect(result.current.name).toBe("John");
35
-
36
- rerender({ name: "Jane" });
37
-
38
- expect(result.current.name).toBe("Jane");
39
- });
40
- });
41
-
42
- describe("function stabilization", () => {
43
- it("should wrap functions in stable wrappers", () => {
44
- const callback = vi.fn(() => 42);
45
-
46
- const { result } = renderHook(() =>
47
- useStable({
48
- callback,
49
- })
50
- );
51
-
52
- expect(isStableFn(result.current.callback)).toBe(true);
53
- expect(result.current.callback()).toBe(42);
54
- expect(callback).toHaveBeenCalled();
55
- });
56
-
57
- it("should maintain stable function reference across renders", () => {
58
- const { result, rerender } = renderHook(
59
- (props: { callback: () => number }) =>
60
- useStable({
61
- callback: props.callback,
62
- }),
63
- { initialProps: { callback: () => 1 } }
64
- );
65
-
66
- const firstCallback = result.current.callback;
67
-
68
- rerender({ callback: () => 2 });
69
-
70
- expect(result.current.callback).toBe(firstCallback);
71
- });
72
-
73
- it("should call latest function implementation", () => {
74
- const { result, rerender } = renderHook(
75
- (props: { callback: () => number }) =>
76
- useStable({
77
- callback: props.callback,
78
- }),
79
- { initialProps: { callback: () => 1 } }
80
- );
81
-
82
- expect(result.current.callback()).toBe(1);
83
-
84
- rerender({ callback: () => 2 });
85
-
86
- expect(result.current.callback()).toBe(2);
87
- });
88
-
89
- it("should handle multiple functions", () => {
90
- const { result, rerender } = renderHook(
91
- (props: { onClick: () => string; onSubmit: () => string }) =>
92
- useStable({
93
- onClick: props.onClick,
94
- onSubmit: props.onSubmit,
95
- }),
96
- {
97
- initialProps: {
98
- onClick: () => "click1",
99
- onSubmit: () => "submit1",
100
- },
101
- }
102
- );
103
-
104
- const firstOnClick = result.current.onClick;
105
- const firstOnSubmit = result.current.onSubmit;
106
-
107
- rerender({
108
- onClick: () => "click2",
109
- onSubmit: () => "submit2",
110
- });
111
-
112
- expect(result.current.onClick).toBe(firstOnClick);
113
- expect(result.current.onSubmit).toBe(firstOnSubmit);
114
- expect(result.current.onClick()).toBe("click2");
115
- expect(result.current.onSubmit()).toBe("submit2");
116
- });
117
- });
118
-
119
- describe("array stabilization (default: shallow)", () => {
120
- it("should stabilize array with same items (shallow equal)", () => {
121
- const { result, rerender } = renderHook(
122
- (props) =>
123
- useStable({
124
- items: props.items,
125
- }),
126
- { initialProps: { items: [1, 2, 3] } }
127
- );
128
-
129
- const firstItems = result.current.items;
130
-
131
- rerender({ items: [1, 2, 3] });
132
-
133
- expect(result.current.items).toBe(firstItems);
134
- });
135
-
136
- it("should update array when items change", () => {
137
- const { result, rerender } = renderHook(
138
- (props) =>
139
- useStable({
140
- items: props.items,
141
- }),
142
- { initialProps: { items: [1, 2, 3] } }
143
- );
144
-
145
- const firstItems = result.current.items;
146
-
147
- rerender({ items: [1, 2, 4] });
148
-
149
- expect(result.current.items).not.toBe(firstItems);
150
- expect(result.current.items).toEqual([1, 2, 4]);
151
- });
152
-
153
- it("should update array when length changes", () => {
154
- const { result, rerender } = renderHook(
155
- (props) =>
156
- useStable({
157
- items: props.items,
158
- }),
159
- { initialProps: { items: [1, 2, 3] } }
160
- );
161
-
162
- const firstItems = result.current.items;
163
-
164
- rerender({ items: [1, 2] });
165
-
166
- expect(result.current.items).not.toBe(firstItems);
167
- });
168
-
169
- it("should work as useEffect dependency", () => {
170
- const effectFn = vi.fn();
171
-
172
- const { result, rerender } = renderHook(
173
- (props) => {
174
- const stable = useStable({
175
- items: props.items,
176
- });
177
-
178
- // Simulate useEffect behavior - track if dependency changed
179
- effectFn(stable.items);
180
-
181
- return stable;
182
- },
183
- { initialProps: { items: [1, 2, 3] } }
184
- );
185
-
186
- const firstItems = result.current.items;
187
-
188
- // Same content - should be stable
189
- rerender({ items: [1, 2, 3] });
190
-
191
- // Effect should receive same reference
192
- expect(effectFn).toHaveBeenLastCalledWith(firstItems);
193
- });
194
- });
195
-
196
- describe("object stabilization (default: shallow)", () => {
197
- it("should stabilize object with same shallow values", () => {
198
- const { result, rerender } = renderHook(
199
- (props) =>
200
- useStable({
201
- person: props.person,
202
- }),
203
- { initialProps: { person: { name: "John", age: 30 } } }
204
- );
205
-
206
- const firstPerson = result.current.person;
207
-
208
- rerender({ person: { name: "John", age: 30 } });
209
-
210
- expect(result.current.person).toBe(firstPerson);
211
- });
212
-
213
- it("should update object when shallow values change", () => {
214
- const { result, rerender } = renderHook(
215
- (props) =>
216
- useStable({
217
- person: props.person,
218
- }),
219
- { initialProps: { person: { name: "John", age: 30 } } }
220
- );
221
-
222
- const firstPerson = result.current.person;
223
-
224
- rerender({ person: { name: "Jane", age: 30 } });
225
-
226
- expect(result.current.person).not.toBe(firstPerson);
227
- });
228
-
229
- it("should NOT stabilize nested objects by default (shallow comparison)", () => {
230
- const { result, rerender } = renderHook(
231
- (props) =>
232
- useStable({
233
- data: props.data,
234
- }),
235
- {
236
- initialProps: {
237
- data: { nested: { value: 1 } },
238
- },
239
- }
240
- );
241
-
242
- const firstData = result.current.data;
243
-
244
- // Different nested object reference, even with same content
245
- rerender({ data: { nested: { value: 1 } } });
246
-
247
- // Should NOT be stable because nested object has different reference
248
- expect(result.current.data).not.toBe(firstData);
249
- });
250
- });
251
-
252
- describe("Date stabilization (default: deep/timestamp)", () => {
253
- it("should stabilize Date with same timestamp", () => {
254
- const { result, rerender } = renderHook(
255
- (props) =>
256
- useStable({
257
- date: props.date,
258
- }),
259
- { initialProps: { date: new Date("2024-01-01") } }
260
- );
261
-
262
- const firstDate = result.current.date;
263
-
264
- rerender({ date: new Date("2024-01-01") });
265
-
266
- expect(result.current.date).toBe(firstDate);
267
- });
268
-
269
- it("should update Date when timestamp changes", () => {
270
- const { result, rerender } = renderHook(
271
- (props) =>
272
- useStable({
273
- date: props.date,
274
- }),
275
- { initialProps: { date: new Date("2024-01-01") } }
276
- );
277
-
278
- const firstDate = result.current.date;
279
-
280
- rerender({ date: new Date("2024-01-02") });
281
-
282
- expect(result.current.date).not.toBe(firstDate);
283
- });
284
- });
285
-
286
- describe("primitive stabilization (default: strict)", () => {
287
- it("should stabilize primitives with strict equality", () => {
288
- const { result, rerender } = renderHook(
289
- (props) =>
290
- useStable({
291
- count: props.count,
292
- name: props.name,
293
- active: props.active,
294
- }),
295
- { initialProps: { count: 42, name: "test", active: true } }
296
- );
297
-
298
- const firstResult = result.current;
299
-
300
- rerender({ count: 42, name: "test", active: true });
301
-
302
- expect(result.current).toBe(firstResult);
303
- expect(result.current.count).toBe(42);
304
- expect(result.current.name).toBe("test");
305
- expect(result.current.active).toBe(true);
306
- });
307
- });
308
-
309
- describe("custom equals option", () => {
310
- it("should use custom equals for specified properties", () => {
311
- const { result, rerender } = renderHook(
312
- (props) =>
313
- useStable(
314
- {
315
- data: props.data,
316
- },
317
- { data: "deep" }
318
- ),
319
- {
320
- initialProps: {
321
- data: { nested: { value: 1 } },
322
- },
323
- }
324
- );
325
-
326
- const firstData = result.current.data;
327
-
328
- // With deep equals, nested objects with same content should be stable
329
- rerender({ data: { nested: { value: 1 } } });
330
-
331
- expect(result.current.data).toBe(firstData);
332
- });
333
-
334
- it("should override default equals", () => {
335
- const { result, rerender } = renderHook(
336
- (props) =>
337
- useStable(
338
- {
339
- items: props.items,
340
- },
341
- { items: "strict" }
342
- ),
343
- { initialProps: { items: [1, 2, 3] } }
344
- );
345
-
346
- const firstItems = result.current.items;
347
-
348
- // With strict equals, same content but different reference should NOT be stable
349
- rerender({ items: [1, 2, 3] });
350
-
351
- expect(result.current.items).not.toBe(firstItems);
352
- });
353
-
354
- it("should support custom equals function", () => {
355
- const { result, rerender } = renderHook(
356
- (props) =>
357
- useStable(
358
- {
359
- user: props.user,
360
- },
361
- { user: (a, b) => a?.id === b?.id }
362
- ),
363
- { initialProps: { user: { id: 1, name: "John" } } }
364
- );
365
-
366
- const firstUser = result.current.user;
367
-
368
- // Same id, different name - should be stable with custom equals
369
- rerender({ user: { id: 1, name: "Jane" } });
370
-
371
- expect(result.current.user).toBe(firstUser);
372
-
373
- // Different id - should NOT be stable
374
- rerender({ user: { id: 2, name: "Jane" } });
375
-
376
- expect(result.current.user).not.toBe(firstUser);
377
- });
378
-
379
- it("should ignore equals option for functions", () => {
380
- const { result, rerender } = renderHook(
381
- (props: { callback: () => number }) =>
382
- useStable(
383
- {
384
- callback: props.callback,
385
- },
386
- // Functions are excluded from equals type, so this is ignored at runtime
387
- { callback: "deep" } as any
388
- ),
389
- { initialProps: { callback: () => 1 } }
390
- );
391
-
392
- const firstCallback = result.current.callback;
393
-
394
- rerender({ callback: () => 2 });
395
-
396
- // Function should still be stabilized regardless of equals option
397
- expect(result.current.callback).toBe(firstCallback);
398
- expect(isStableFn(result.current.callback)).toBe(true);
399
- });
400
- });
401
-
402
- describe("mixed properties", () => {
403
- it("should handle mixed property types correctly", () => {
404
- type Props = {
405
- person: { name: string; address: { city: string } };
406
- date: Date;
407
- items: number[];
408
- callback: () => string;
409
- count: number;
410
- };
411
-
412
- const { result, rerender } = renderHook(
413
- (props: Props) =>
414
- useStable(
415
- {
416
- person: props.person,
417
- date: props.date,
418
- items: props.items,
419
- callback: props.callback,
420
- count: props.count,
421
- },
422
- { person: "deep" }
423
- ),
424
- {
425
- initialProps: {
426
- person: { name: "John", address: { city: "NYC" } },
427
- date: new Date("2024-01-01"),
428
- items: [1, 2, 3],
429
- callback: () => "hello",
430
- count: 42,
431
- },
432
- }
433
- );
434
-
435
- const first = {
436
- person: result.current.person,
437
- date: result.current.date,
438
- items: result.current.items,
439
- callback: result.current.callback,
440
- count: result.current.count,
441
- };
442
-
443
- // Rerender with same logical values but new references
444
- rerender({
445
- person: { name: "John", address: { city: "NYC" } },
446
- date: new Date("2024-01-01"),
447
- items: [1, 2, 3],
448
- callback: () => "world",
449
- count: 42,
450
- });
451
-
452
- // person: deep equals - should be stable
453
- expect(result.current.person).toBe(first.person);
454
-
455
- // date: timestamp comparison - should be stable
456
- expect(result.current.date).toBe(first.date);
457
-
458
- // items: shallow equals - should be stable
459
- expect(result.current.items).toBe(first.items);
460
-
461
- // callback: always stabilized - should be stable reference
462
- expect(result.current.callback).toBe(first.callback);
463
-
464
- // count: strict equals - should be stable
465
- expect(result.current.count).toBe(first.count);
466
-
467
- // But callback should call new implementation
468
- expect(result.current.callback()).toBe("world");
469
- });
470
- });
471
-
472
- describe("edge cases", () => {
473
- it("should handle null values", () => {
474
- const { result, rerender } = renderHook(
475
- (props) =>
476
- useStable({
477
- value: props.value,
478
- }),
479
- { initialProps: { value: null as string | null } }
480
- );
481
-
482
- expect(result.current.value).toBe(null);
483
-
484
- rerender({ value: "hello" });
485
-
486
- expect(result.current.value).toBe("hello");
487
-
488
- rerender({ value: null });
489
-
490
- expect(result.current.value).toBe(null);
491
- });
492
-
493
- it("should handle undefined values", () => {
494
- const { result, rerender } = renderHook(
495
- (props) =>
496
- useStable({
497
- value: props.value,
498
- }),
499
- { initialProps: { value: undefined as string | undefined } }
500
- );
501
-
502
- expect(result.current.value).toBe(undefined);
503
-
504
- rerender({ value: "hello" });
505
-
506
- expect(result.current.value).toBe("hello");
507
- });
508
-
509
- it("should handle empty object", () => {
510
- const { result } = renderHook(() => useStable({}));
511
-
512
- expect(result.current).toEqual({});
513
- });
514
-
515
- it("should handle adding new properties on rerender", () => {
516
- const { result, rerender } = renderHook((props) => useStable(props), {
517
- initialProps: { a: 1 } as { a: number; b?: number },
518
- });
519
-
520
- expect(result.current.a).toBe(1);
521
- expect(result.current.b).toBeUndefined();
522
-
523
- rerender({ a: 1, b: 2 });
524
-
525
- expect(result.current.a).toBe(1);
526
- expect(result.current.b).toBe(2);
527
- });
528
- });
529
-
530
- describe("type safety", () => {
531
- it("should preserve property types", () => {
532
- const { result } = renderHook(() =>
533
- useStable({
534
- name: "John",
535
- age: 30,
536
- items: [1, 2, 3],
537
- callback: (x: number) => x * 2,
538
- })
539
- );
540
-
541
- // TypeScript should infer these types correctly
542
- const name: string = result.current.name;
543
- const age: number = result.current.age;
544
- const items: number[] = result.current.items;
545
- const doubled: number = result.current.callback(5);
546
-
547
- expect(name).toBe("John");
548
- expect(age).toBe(30);
549
- expect(items).toEqual([1, 2, 3]);
550
- expect(doubled).toBe(10);
551
- });
552
- });
553
- });