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,392 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- strictEqual,
4
- shallowEqual,
5
- shallow2Equal,
6
- shallow3Equal,
7
- deepEqual,
8
- resolveEquality,
9
- equality,
10
- createStableFn,
11
- isStableFn,
12
- tryStabilize,
13
- } from "./equality";
14
-
15
- describe("equality", () => {
16
- describe("strictEqual", () => {
17
- it("should return true for same reference", () => {
18
- const obj = { a: 1 };
19
- expect(strictEqual(obj, obj)).toBe(true);
20
- });
21
-
22
- it("should return false for different references with same content", () => {
23
- expect(strictEqual({ a: 1 }, { a: 1 })).toBe(false);
24
- });
25
-
26
- it("should return true for same primitives", () => {
27
- expect(strictEqual(1, 1)).toBe(true);
28
- expect(strictEqual("hello", "hello")).toBe(true);
29
- expect(strictEqual(true, true)).toBe(true);
30
- });
31
-
32
- it("should handle NaN correctly (Object.is behavior)", () => {
33
- expect(strictEqual(NaN, NaN)).toBe(true);
34
- });
35
-
36
- it("should distinguish +0 and -0", () => {
37
- expect(strictEqual(0, -0)).toBe(false);
38
- });
39
- });
40
-
41
- describe("shallowEqual", () => {
42
- it("should return true for same reference", () => {
43
- const obj = { a: 1 };
44
- expect(shallowEqual(obj, obj)).toBe(true);
45
- });
46
-
47
- it("should return true for objects with same keys and values", () => {
48
- expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
49
- });
50
-
51
- it("should return false for objects with different keys", () => {
52
- expect(shallowEqual({ a: 1 }, { b: 1 })).toBe(false);
53
- });
54
-
55
- it("should return false for objects with different values", () => {
56
- expect(shallowEqual({ a: 1 }, { a: 2 })).toBe(false);
57
- });
58
-
59
- it("should return false for objects with different number of keys", () => {
60
- expect(shallowEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
61
- });
62
-
63
- it("should return true for arrays with same elements", () => {
64
- expect(shallowEqual([1, 2, 3], [1, 2, 3])).toBe(true);
65
- });
66
-
67
- it("should return false for arrays with different elements", () => {
68
- expect(shallowEqual([1, 2, 3], [1, 2, 4])).toBe(false);
69
- });
70
-
71
- it("should return false for arrays with different lengths", () => {
72
- expect(shallowEqual([1, 2], [1, 2, 3])).toBe(false);
73
- });
74
-
75
- it("should return false for nested objects with different references", () => {
76
- expect(shallowEqual({ nested: { a: 1 } }, { nested: { a: 1 } })).toBe(
77
- false
78
- );
79
- });
80
-
81
- it("should return true for nested objects with same reference", () => {
82
- const nested = { a: 1 };
83
- expect(shallowEqual({ nested }, { nested })).toBe(true);
84
- });
85
-
86
- it("should return false when comparing object to null", () => {
87
- expect(shallowEqual({ a: 1 }, null as any)).toBe(false);
88
- });
89
-
90
- it("should return false when comparing object to primitive", () => {
91
- expect(shallowEqual({ a: 1 }, 1 as any)).toBe(false);
92
- });
93
-
94
- it("should support custom item comparator", () => {
95
- const customEqual = (a: unknown, b: unknown) =>
96
- JSON.stringify(a) === JSON.stringify(b);
97
- expect(
98
- shallowEqual({ nested: { a: 1 } }, { nested: { a: 1 } }, customEqual)
99
- ).toBe(true);
100
- });
101
- });
102
-
103
- describe("shallow2Equal", () => {
104
- it("should compare 2 levels deep", () => {
105
- expect(
106
- shallow2Equal({ nested: { a: 1 } }, { nested: { a: 1 } })
107
- ).toBe(true);
108
- });
109
-
110
- it("should return false for 3 levels deep difference", () => {
111
- expect(
112
- shallow2Equal(
113
- { nested: { deep: { a: 1 } } },
114
- { nested: { deep: { a: 1 } } }
115
- )
116
- ).toBe(false);
117
- });
118
-
119
- it("should work with arrays of objects", () => {
120
- expect(shallow2Equal([{ id: 1 }], [{ id: 1 }])).toBe(true);
121
- });
122
- });
123
-
124
- describe("shallow3Equal", () => {
125
- it("should compare 3 levels deep", () => {
126
- expect(
127
- shallow3Equal(
128
- { nested: { deep: { a: 1 } } },
129
- { nested: { deep: { a: 1 } } }
130
- )
131
- ).toBe(true);
132
- });
133
-
134
- it("should return false for 4 levels deep difference", () => {
135
- expect(
136
- shallow3Equal(
137
- { l1: { l2: { l3: { a: 1 } } } },
138
- { l1: { l2: { l3: { a: 1 } } } }
139
- )
140
- ).toBe(false);
141
- });
142
- });
143
-
144
- describe("deepEqual", () => {
145
- it("should compare deeply nested objects", () => {
146
- expect(
147
- deepEqual(
148
- { a: { b: { c: { d: 1 } } } },
149
- { a: { b: { c: { d: 1 } } } }
150
- )
151
- ).toBe(true);
152
- });
153
-
154
- it("should return false for deeply nested differences", () => {
155
- expect(
156
- deepEqual(
157
- { a: { b: { c: { d: 1 } } } },
158
- { a: { b: { c: { d: 2 } } } }
159
- )
160
- ).toBe(false);
161
- });
162
-
163
- it("should handle arrays", () => {
164
- expect(deepEqual([1, [2, [3]]], [1, [2, [3]]])).toBe(true);
165
- });
166
-
167
- it("should handle Date objects", () => {
168
- const date = new Date("2024-01-01");
169
- expect(deepEqual(date, new Date("2024-01-01"))).toBe(true);
170
- });
171
- });
172
-
173
- describe("resolveEquality", () => {
174
- it("should return strictEqual for undefined", () => {
175
- expect(resolveEquality(undefined)).toBe(strictEqual);
176
- });
177
-
178
- it("should return strictEqual for 'strict'", () => {
179
- expect(resolveEquality("strict")).toBe(strictEqual);
180
- });
181
-
182
- it("should return shallowEqual for 'shallow'", () => {
183
- expect(resolveEquality("shallow")).toBe(shallowEqual);
184
- });
185
-
186
- it("should return shallow2Equal for 'shallow2'", () => {
187
- expect(resolveEquality("shallow2")).toBe(shallow2Equal);
188
- });
189
-
190
- it("should return shallow3Equal for 'shallow3'", () => {
191
- expect(resolveEquality("shallow3")).toBe(shallow3Equal);
192
- });
193
-
194
- it("should return deepEqual for 'deep'", () => {
195
- expect(resolveEquality("deep")).toBe(deepEqual);
196
- });
197
-
198
- it("should return custom function as-is", () => {
199
- const customFn = (a: number, b: number) => a === b;
200
- expect(resolveEquality(customFn)).toBe(customFn);
201
- });
202
- });
203
-
204
- describe("equality helper", () => {
205
- it("should be an alias for resolveEquality with shorthand", () => {
206
- expect(equality("strict")).toBe(strictEqual);
207
- expect(equality("shallow")).toBe(shallowEqual);
208
- expect(equality("deep")).toBe(deepEqual);
209
- });
210
- });
211
- });
212
-
213
- describe("StableFn", () => {
214
- describe("createStableFn", () => {
215
- it("should create a callable wrapper", () => {
216
- const fn = (x: number) => x * 2;
217
- const stable = createStableFn(fn);
218
-
219
- expect(stable(5)).toBe(10);
220
- });
221
-
222
- it("should preserve original function via getOriginal", () => {
223
- const fn = (x: number) => x * 2;
224
- const stable = createStableFn(fn);
225
-
226
- expect(stable.getOriginal()).toBe(fn);
227
- });
228
-
229
- it("should return current function via getCurrent", () => {
230
- const fn = (x: number) => x * 2;
231
- const stable = createStableFn(fn);
232
-
233
- expect(stable.getCurrent()).toBe(fn);
234
- });
235
-
236
- it("should allow updating current function via setCurrent", () => {
237
- const fn1 = (x: number) => x * 2;
238
- const fn2 = (x: number) => x * 3;
239
- const stable = createStableFn(fn1);
240
-
241
- stable.setCurrent(fn2);
242
-
243
- expect(stable(5)).toBe(15);
244
- expect(stable.getCurrent()).toBe(fn2);
245
- expect(stable.getOriginal()).toBe(fn1);
246
- });
247
- });
248
-
249
- describe("isStableFn", () => {
250
- it("should return true for StableFn", () => {
251
- const stable = createStableFn(() => 42);
252
- expect(isStableFn(stable)).toBe(true);
253
- });
254
-
255
- it("should return false for regular function", () => {
256
- expect(isStableFn(() => 42)).toBe(false);
257
- });
258
-
259
- it("should return false for non-function", () => {
260
- expect(isStableFn({ a: 1 })).toBe(false);
261
- expect(isStableFn(42)).toBe(false);
262
- expect(isStableFn("string")).toBe(false);
263
- });
264
-
265
- it("should return false for function with partial StableFn interface", () => {
266
- const partial = Object.assign(() => 42, { getOriginal: () => {} });
267
- expect(isStableFn(partial)).toBe(false);
268
- });
269
- });
270
- });
271
-
272
- describe("tryStabilize", () => {
273
- describe("first call (no previous value)", () => {
274
- it("should return value as-is for non-function", () => {
275
- const [result, wasStable] = tryStabilize(undefined, 42, strictEqual);
276
- expect(result).toBe(42);
277
- expect(wasStable).toBe(false);
278
- });
279
-
280
- it("should wrap function in StableFn", () => {
281
- const fn = () => 42;
282
- const [result, wasStable] = tryStabilize(undefined, fn, strictEqual);
283
-
284
- expect(isStableFn(result)).toBe(true);
285
- expect(result()).toBe(42);
286
- expect(wasStable).toBe(false);
287
- });
288
- });
289
-
290
- describe("subsequent calls with functions", () => {
291
- it("should update existing StableFn and return stable", () => {
292
- const fn1 = () => 1;
293
- const fn2 = () => 2;
294
-
295
- const [stable1] = tryStabilize(undefined, fn1, strictEqual);
296
- const [stable2, wasStable] = tryStabilize(
297
- { value: stable1 },
298
- fn2,
299
- strictEqual
300
- );
301
-
302
- expect(stable2).toBe(stable1); // Same reference
303
- expect(stable2()).toBe(2); // But calls new function
304
- expect(wasStable).toBe(true);
305
- });
306
-
307
- it("should create new StableFn if previous was not StableFn", () => {
308
- const fn = () => 42;
309
- const [result, wasStable] = tryStabilize(
310
- { value: "not a stable fn" as any },
311
- fn,
312
- strictEqual
313
- );
314
-
315
- expect(isStableFn(result)).toBe(true);
316
- expect(wasStable).toBe(false);
317
- });
318
- });
319
-
320
- describe("Date handling", () => {
321
- it("should return previous Date if timestamps match", () => {
322
- const date1 = new Date("2024-01-01");
323
- const date2 = new Date("2024-01-01");
324
-
325
- const [result, wasStable] = tryStabilize(
326
- { value: date1 },
327
- date2,
328
- strictEqual
329
- );
330
-
331
- expect(result).toBe(date1);
332
- expect(wasStable).toBe(true);
333
- });
334
-
335
- it("should return new Date if timestamps differ", () => {
336
- const date1 = new Date("2024-01-01");
337
- const date2 = new Date("2024-01-02");
338
-
339
- const [result, wasStable] = tryStabilize(
340
- { value: date1 },
341
- date2,
342
- strictEqual
343
- );
344
-
345
- expect(result).toBe(date2);
346
- expect(wasStable).toBe(false);
347
- });
348
-
349
- it("should return new Date if previous was not a Date", () => {
350
- const date = new Date("2024-01-01");
351
-
352
- const [result, wasStable] = tryStabilize(
353
- { value: "not a date" as any },
354
- date,
355
- strictEqual
356
- );
357
-
358
- expect(result).toBe(date);
359
- expect(wasStable).toBe(false);
360
- });
361
- });
362
-
363
- describe("equality-based stabilization", () => {
364
- it("should return previous value if equal", () => {
365
- const obj1 = { a: 1 };
366
- const obj2 = { a: 1 };
367
-
368
- const [result, wasStable] = tryStabilize(
369
- { value: obj1 },
370
- obj2,
371
- shallowEqual
372
- );
373
-
374
- expect(result).toBe(obj1);
375
- expect(wasStable).toBe(true);
376
- });
377
-
378
- it("should return new value if not equal", () => {
379
- const obj1 = { a: 1 };
380
- const obj2 = { a: 2 };
381
-
382
- const [result, wasStable] = tryStabilize(
383
- { value: obj1 },
384
- obj2,
385
- shallowEqual
386
- );
387
-
388
- expect(result).toBe(obj2);
389
- expect(wasStable).toBe(false);
390
- });
391
- });
392
- });
@@ -1,182 +0,0 @@
1
- /**
2
- * Equality utilities for comparing values.
3
- */
4
-
5
- import type { AnyFunc, Equality, EqualityShorthand } from "./types";
6
- import isEqual from "lodash/isEqual";
7
-
8
- /**
9
- * Strict equality (Object.is).
10
- */
11
- export function strictEqual<T>(a: T, b: T): boolean {
12
- return Object.is(a, b);
13
- }
14
-
15
- /**
16
- * Shallow equality for objects/arrays.
17
- * Compares by reference for each top-level key/index.
18
- *
19
- * @param itemEqual - Optional comparator for each item/value (defaults to Object.is)
20
- */
21
- export function shallowEqual<T>(
22
- a: T,
23
- b: T,
24
- itemEqual: (a: unknown, b: unknown) => boolean = Object.is
25
- ): boolean {
26
- if (Object.is(a, b)) return true;
27
- if (typeof a !== "object" || a === null) return false;
28
- if (typeof b !== "object" || b === null) return false;
29
-
30
- const keysA = Object.keys(a);
31
- const keysB = Object.keys(b);
32
-
33
- if (keysA.length !== keysB.length) return false;
34
-
35
- for (const key of keysA) {
36
- if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
37
- if (!itemEqual((a as any)[key], (b as any)[key])) return false;
38
- }
39
-
40
- return true;
41
- }
42
-
43
- /**
44
- * 2-level shallow equality.
45
- * Compares keys/length, then shallow compares each item/value.
46
- *
47
- * @example
48
- * [{ id: 1, data: obj }] vs [{ id: 1, data: obj }] // true (same obj ref)
49
- */
50
- export function shallow2Equal<T>(a: T, b: T): boolean {
51
- return shallowEqual(a, b, shallowEqual);
52
- }
53
-
54
- /**
55
- * 3-level shallow equality.
56
- * Compares keys/length, then shallow2 compares each item/value.
57
- *
58
- * @example
59
- * [{ id: 1, nested: { data: obj } }] vs [{ id: 1, nested: { data: obj } }] // true
60
- */
61
- export function shallow3Equal<T>(a: T, b: T): boolean {
62
- return shallowEqual(a, b, shallow2Equal);
63
- }
64
-
65
- /**
66
- * Deep equality.
67
- */
68
- export const deepEqual = isEqual;
69
-
70
- /**
71
- * Resolve equality strategy to a function.
72
- */
73
- export function resolveEquality<T>(
74
- e: Equality<T> | undefined
75
- ): (a: T, b: T) => boolean {
76
- if (!e || e === "strict") return strictEqual;
77
- if (e === "shallow") return shallowEqual;
78
- if (e === "shallow2") return shallow2Equal;
79
- if (e === "shallow3") return shallow3Equal;
80
- if (e === "deep") return deepEqual;
81
- return e;
82
- }
83
-
84
- export function equality(shorthand: EqualityShorthand) {
85
- return resolveEquality(shorthand);
86
- }
87
-
88
- // =============================================================================
89
- // Value Stabilization
90
- // =============================================================================
91
-
92
- export type StableFn<TArgs extends any[], TResult> = ((...args: TArgs) => TResult) & {
93
- getOriginal: () => (...args: TArgs) => TResult;
94
- getCurrent: () => (...args: TArgs) => TResult;
95
- setCurrent: (newFn: (...args: TArgs) => TResult) => void;
96
- };
97
-
98
- export function createStableFn<TArgs extends any[], TResult>(
99
- fn: (...args: TArgs) => TResult
100
- ): StableFn<TArgs, TResult> {
101
- const originalFn = fn;
102
- let currentFn = fn;
103
- return Object.assign(
104
- (...args: TArgs) => {
105
- return currentFn(...args);
106
- },
107
- {
108
- getOriginal: () => originalFn,
109
- getCurrent: () => currentFn,
110
- setCurrent(newFn: (...args: TArgs) => TResult) {
111
- currentFn = newFn;
112
- },
113
- }
114
- );
115
- }
116
-
117
- /**
118
- * Check if a value is a stable function wrapper.
119
- */
120
- export function isStableFn<TArgs extends any[], TResult>(
121
- value: unknown
122
- ): value is StableFn<TArgs, TResult> {
123
- return (
124
- typeof value === "function" &&
125
- "getOriginal" in value &&
126
- "getCurrent" in value &&
127
- "setCurrent" in value
128
- );
129
- }
130
-
131
- /**
132
- * Stabilize a value with automatic function wrapper support.
133
- *
134
- * - Functions: Creates/updates stable wrapper (reference never changes)
135
- * - Date objects: Compared by timestamp (uses deepEqual)
136
- * - Other values: Returns previous if equal per equalityFn
137
- *
138
- * @param prev - Previous value container (or undefined for first call)
139
- * @param next - New value
140
- * @param equalityFn - Equality function for non-function/non-date values
141
- * @returns Tuple of [stabilized value, wasStable]
142
- */
143
- export function tryStabilize<T>(
144
- prev: { value: T } | undefined,
145
- next: T,
146
- equalityFn: (a: T, b: T) => boolean
147
- ): [T, boolean] {
148
- // First time - no previous value
149
- if (!prev) {
150
- if (typeof next === "function") {
151
- return [createStableFn(next as AnyFunc) as T, false];
152
- }
153
- return [next, false];
154
- }
155
-
156
- // Handle functions with stable wrapper pattern
157
- if (typeof next === "function") {
158
- if (isStableFn(prev.value)) {
159
- // Update existing stable wrapper with new function
160
- prev.value.setCurrent(next as AnyFunc);
161
- return [prev.value as T, true];
162
- }
163
- // Previous wasn't a stable fn, create new wrapper
164
- return [createStableFn(next as AnyFunc) as T, false];
165
- }
166
-
167
- if (next && next instanceof Date) {
168
- if (prev.value && prev.value instanceof Date) {
169
- if (next.getTime() === prev.value.getTime()) {
170
- return [prev.value, true];
171
- }
172
- }
173
- return [next, false];
174
- }
175
-
176
- // Non-functions: use equality comparison
177
- if (equalityFn(prev.value, next)) {
178
- return [prev.value, true];
179
- }
180
-
181
- return [next, false];
182
- }
@@ -1,69 +0,0 @@
1
- import { isDerived } from "./isAtom";
2
- import { isPromiseLike } from "./isPromiseLike";
3
- import { trackPromise } from "./promiseCache";
4
- import { Atom, AtomState, DerivedAtom } from "./types";
5
-
6
- /**
7
- * Returns the current state of an atom as a discriminated union.
8
- *
9
- * For any atom (mutable or derived):
10
- * - If value is not a Promise: returns ready state
11
- * - If value is a Promise: tracks and returns its state (ready/error/loading)
12
- *
13
- * @param atom - The atom to get state from
14
- * @returns AtomState discriminated union (ready | error | loading)
15
- *
16
- * @example
17
- * ```ts
18
- * const state = getAtomState(myAtom$);
19
- *
20
- * switch (state.status) {
21
- * case "ready":
22
- * console.log(state.value); // T
23
- * break;
24
- * case "error":
25
- * console.log(state.error);
26
- * break;
27
- * case "loading":
28
- * console.log(state.promise);
29
- * break;
30
- * }
31
- * ```
32
- */
33
- export function getAtomState<T>(atom: Atom<T>): AtomState<Awaited<T>> {
34
- if (isDerived(atom)) {
35
- return (atom as DerivedAtom<Awaited<T>>).state();
36
- }
37
- const value = atom.get();
38
-
39
- // 1. Sync value - ready
40
- if (!isPromiseLike(value)) {
41
- return {
42
- status: "ready",
43
- value: value as Awaited<T>,
44
- };
45
- }
46
-
47
- // 2. Promise value - check state via promiseCache
48
- const state = trackPromise(value);
49
-
50
- switch (state.status) {
51
- case "fulfilled":
52
- return {
53
- status: "ready",
54
- value: state.value as Awaited<T>,
55
- };
56
-
57
- case "rejected":
58
- return {
59
- status: "error",
60
- error: state.error,
61
- };
62
-
63
- case "pending":
64
- return {
65
- status: "loading",
66
- promise: state.promise as Promise<Awaited<T>>,
67
- };
68
- }
69
- }