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
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Type check file for abortable functions and wrapper chaining.
3
+ * This file should compile without errors - no runtime tests needed.
4
+ *
5
+ * Run: npx tsc --noEmit src/async/abortable.typeCheck.ts
6
+ */
7
+ import { abortable, } from "./abortable";
8
+ // Note: wrappers are not yet implemented, so this file is disabled
9
+ // import {
10
+ // retry,
11
+ // catchError,
12
+ // timeout,
13
+ // logging,
14
+ // debounce,
15
+ // throttle,
16
+ // } from "./wrappers";
17
+ // =============================================================================
18
+ // Basic abortable creation
19
+ // =============================================================================
20
+ // Simple function with no args
21
+ const noArgs = abortable(async () => "hello");
22
+ noArgs;
23
+ // Function with single arg
24
+ const singleArg = abortable(async ({}, id) => ({ id, name: "test" }));
25
+ singleArg;
26
+ // Function with multiple args
27
+ const multiArgs = abortable(async ({}, a, b, c) => a + b.length + (c ? 1 : 0));
28
+ multiArgs;
29
+ // Function using signal
30
+ const withSignal = abortable(async ({ signal }, url) => {
31
+ const res = await fetch(url, { signal });
32
+ return res.json();
33
+ });
34
+ withSignal;
35
+ // Function using safe
36
+ const withSafe = abortable(async ({ safe }, id) => {
37
+ const result = await safe(Promise.resolve({ id }));
38
+ return result;
39
+ });
40
+ withSafe;
41
+ // =============================================================================
42
+ // Direct call and .withSignal() method
43
+ // =============================================================================
44
+ // Direct call - should accept the args
45
+ void noArgs();
46
+ void singleArg("123");
47
+ void multiArgs(1, "test", true);
48
+ // .withSignal() method - should accept signal + args
49
+ const controller = new AbortController();
50
+ void noArgs.withSignal(controller.signal);
51
+ void singleArg.withSignal(controller.signal, "123");
52
+ void multiArgs.withSignal(controller.signal, 1, "test", true);
53
+ // =============================================================================
54
+ // Single wrapper chaining
55
+ // =============================================================================
56
+ // Note: Wrapper functions are not yet implemented
57
+ // These examples are commented out until wrappers are available
58
+ // // retry() preserves types
59
+ // const withRetry = singleArg.use(retry(3));
60
+ // withRetry satisfies Abortable<[string], { id: string; name: string }>;
61
+ // const _retryResult: Promise<{ id: string; name: string }> = withRetry("test");
62
+ // =============================================================================
63
+ // Multi-wrapper chaining
64
+ // =============================================================================
65
+ // Note: Wrapper chaining examples commented out until wrappers are available
66
+ // =============================================================================
67
+ // Custom wrapper creation
68
+ // =============================================================================
69
+ // Custom wrapper that doesn't change types
70
+ const customPassThrough = (next) => async (ctx, ...args) => {
71
+ console.log("before");
72
+ const result = await next(ctx, ...args);
73
+ console.log("after");
74
+ return result;
75
+ };
76
+ const withCustom = singleArg.use(customPassThrough);
77
+ withCustom;
78
+ // API service with abortable methods (used in commented-out examples)
79
+ const userApi = {
80
+ getUser: abortable(async ({ signal }, userId) => {
81
+ const res = await fetch(`/api/users/${userId}`, { signal });
82
+ return res.json();
83
+ }),
84
+ createUser: abortable(async ({ signal }, data) => {
85
+ const res = await fetch("/api/users", {
86
+ method: "POST",
87
+ body: JSON.stringify(data),
88
+ signal,
89
+ });
90
+ return res.json();
91
+ }),
92
+ getPost: abortable(async ({ signal }, postId) => {
93
+ const res = await fetch(`/api/posts/${postId}`, { signal });
94
+ return res.json();
95
+ }),
96
+ };
97
+ void userApi; // Mark as used for type checking
98
+ // Wrap with retry and error handling (commented out until wrappers available)
99
+ // const robustGetUser = userApi.getUser
100
+ // .use(retry({ retries: 3, delay: "backoff" }))
101
+ // .use(catchError((err) => console.error("Failed to get user:", err)))
102
+ // .use(timeout(10000));
103
+ // Use the wrapped function (commented out)
104
+ // async function fetchUserProfile(userId: string): Promise<User> {
105
+ // return robustGetUser(userId);
106
+ // }
107
+ // =============================================================================
108
+ // Type preservation tests - ensure types are NOT widened to `any`
109
+ // =============================================================================
110
+ // Type error tests commented out until wrappers are available
111
+ // =============================================================================
112
+ // Edge cases
113
+ // =============================================================================
114
+ // Void return type (used in commented-out examples)
115
+ const voidFn = abortable(async ({}, msg) => {
116
+ console.log(msg);
117
+ });
118
+ void voidFn; // Mark as used for type checking
119
+ const complexFn = abortable(async ({}, page, _limit) => {
120
+ return {
121
+ users: [],
122
+ meta: { total: 0, page, hasMore: false },
123
+ };
124
+ });
125
+ void complexFn; // Mark as used for type checking
126
+ // const wrappedComplex = complexFn
127
+ // .use(retry({ retries: 3, delay: 1000 }))
128
+ // .use(timeout(5000));
129
+ // wrappedComplex satisfies Abortable<[number, number], ComplexData>;
130
+ // Optional args (rest params style) - used in commented-out examples
131
+ const optionalArgsFn = abortable(async ({}, required, optional) => {
132
+ return `${required}-${optional ?? 0}`;
133
+ });
134
+ void optionalArgsFn; // Mark as used for type checking
135
+ // const wrappedOptional = optionalArgsFn.use(retry(3));
136
+ // wrappedOptional satisfies Abortable<[string, (number | undefined)?], string>;
137
+ console.log("All type checks passed!");
138
+ //# sourceMappingURL=abortable.typeCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortable.typeCheck.js","sourceRoot":"","sources":["../../src/async/abortable.typeCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Async utility functions.
3
+ * Re-exports from utils for convenience.
4
+ */
5
+ import { toPromise } from "./utils";
6
+ export { toPromise };
7
+ /**
8
+ * Delay utility for async operations.
9
+ */
10
+ export declare const delay: (ms: number) => Promise<void>;
11
+ /**
12
+ * Async utilities object.
13
+ */
14
+ export declare const async: {
15
+ delay: (ms: number) => Promise<void>;
16
+ toPromise: typeof toPromise;
17
+ };
18
+ //# sourceMappingURL=async.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.d.ts","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,IAAI,MAAM,KAAG,OAAO,CAAC,IAAI,CAE9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK;gBAPQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;;CAU/C,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Async utility functions.
3
+ * Re-exports from utils for convenience.
4
+ */
5
+ import { toPromise } from "./utils";
6
+ export { toPromise };
7
+ /**
8
+ * Delay utility for async operations.
9
+ */
10
+ export const delay = (ms) => {
11
+ return new Promise((resolve) => setTimeout(resolve, ms));
12
+ };
13
+ /**
14
+ * Async utilities object.
15
+ */
16
+ export const async = {
17
+ delay,
18
+ toPromise,
19
+ };
20
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async.js","sourceRoot":"","sources":["../../src/async/async.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK;IACL,SAAS;CACV,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * rexfect/async - Async utilities
3
+ *
4
+ * - read: Suspense mode for Signals (throws promise)
5
+ * - wait: Promise mode for Promise/Action (returns Promise)
6
+ * - loadable: Extract async state from Promise-valued signals
7
+ * - abortable: Wrap functions with cancellation support
8
+ */
9
+ export { read } from "./read";
10
+ export { wait } from "./wait";
11
+ export { loadable } from "./loadable";
12
+ export { abortable, isAbortable } from "./abortable";
13
+ export type { LoadableResult, LoadableReturn } from "./loadable";
14
+ export type { Abortable, AbortableContext, AbortableResult } from "./abortable";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/async/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGrD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * rexfect/async - Async utilities
3
+ *
4
+ * - read: Suspense mode for Signals (throws promise)
5
+ * - wait: Promise mode for Promise/Action (returns Promise)
6
+ * - loadable: Extract async state from Promise-valued signals
7
+ * - abortable: Wrap functions with cancellation support
8
+ */
9
+ export { read } from "./read";
10
+ export { wait } from "./wait";
11
+ export { loadable } from "./loadable";
12
+ export { abortable, isAbortable } from "./abortable";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/async/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Signal, LoadableResult, LoadableReturn } from "../types";
2
+ /**
3
+ * Extracts async state from a Promise-valued signal.
4
+ */
5
+ export declare function loadable<TValue extends Promise<any> | null | undefined>(signal: Signal<TValue>): LoadableReturn<TValue>;
6
+ export type { LoadableResult, LoadableReturn };
7
+ //# sourceMappingURL=loadable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadable.d.ts","sourceRoot":"","sources":["../../src/async/loadable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAgBvE;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,EACrE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GACrB,cAAc,CAAC,MAAM,CAAC,CAoCxB;AAGD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { atomInstance } from "../atom";
2
+ import { getHooks } from "../hooks";
3
+ import { trackPromise } from "./promiseCache";
4
+ function getPromiseState(promise) {
5
+ const entry = trackPromise(promise);
6
+ switch (entry.status) {
7
+ case "pending":
8
+ return { loading: true, data: undefined, error: undefined };
9
+ case "fulfilled":
10
+ return { loading: false, data: entry.value, error: undefined };
11
+ case "rejected":
12
+ return { loading: false, data: undefined, error: entry.error };
13
+ }
14
+ }
15
+ /**
16
+ * Extracts async state from a Promise-valued signal.
17
+ */
18
+ export function loadable(signal) {
19
+ const value = signal();
20
+ if (value === null || value === undefined) {
21
+ return null;
22
+ }
23
+ const promise = value;
24
+ const state = getPromiseState(promise);
25
+ // If pending, set up reactivity to re-run when promise settles
26
+ if (state.loading) {
27
+ const track = getHooks().track;
28
+ let onFinally;
29
+ if (track) {
30
+ let disposed = false;
31
+ const { signal, setter, dispose } = atomInstance({});
32
+ track(signal, () => {
33
+ disposed = true;
34
+ dispose();
35
+ });
36
+ onFinally = () => {
37
+ if (disposed)
38
+ return;
39
+ // trigger re-run
40
+ setter({});
41
+ };
42
+ }
43
+ // Force re-evaluation when promise settles
44
+ // Use .then with both handlers to avoid unhandled rejection warnings
45
+ promise.then(onFinally, () => {
46
+ // Call onFinally but also suppress the unhandled rejection
47
+ onFinally?.();
48
+ });
49
+ }
50
+ return state;
51
+ }
52
+ //# sourceMappingURL=loadable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadable.js","sourceRoot":"","sources":["../../src/async/loadable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,SAAS,eAAe,CAAI,OAAmB;IAC7C,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAEpC,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC9D,KAAK,WAAW;YACd,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QACjE,KAAK,UAAU;YACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAsB;IAEtB,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;IAEvB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,IAA8B,CAAC;IACxC,CAAC;IAED,MAAM,OAAO,GAAG,KAAqB,CAAC;IACtC,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAEvC,+DAA+D;IAC/D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,KAAK,CAAC;QAC/B,IAAI,SAAmC,CAAC;QACxC,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;YACrD,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,SAAS,GAAG,GAAG,EAAE;gBACf,IAAI,QAAQ;oBAAE,OAAO;gBACrB,iBAAiB;gBACjB,MAAM,CAAC,EAAE,CAAC,CAAC;YACb,CAAC,CAAC;QACJ,CAAC;QACD,2CAA2C;QAC3C,qEAAqE;QACrE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YAC3B,2DAA2D;YAC3D,SAAS,EAAE,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAA+B,CAAC;AACzC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=loadable.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadable.test.d.ts","sourceRoot":"","sources":["../../src/async/loadable.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,322 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { atom } from "../atom";
3
+ import { effect } from "../effect";
4
+ import { loadable } from "./loadable";
5
+ // Helper to create rejected promise without unhandled rejection warning
6
+ function rejectedPromise(error) {
7
+ const p = Promise.reject(error);
8
+ p.catch(() => { }); // Suppress warning
9
+ return p;
10
+ }
11
+ describe("loadable", () => {
12
+ it("should return null for null signal value", () => {
13
+ const [promise] = atom(null);
14
+ const result = loadable(promise);
15
+ expect(result).toBeNull();
16
+ });
17
+ it("should return null for undefined signal value", () => {
18
+ const [promise] = atom(undefined);
19
+ const result = loadable(promise);
20
+ expect(result).toBeNull();
21
+ });
22
+ it("should return loading state for pending promise", () => {
23
+ const [promise] = atom(new Promise(() => { })); // never resolves
24
+ const result = loadable(promise);
25
+ expect(result).not.toBeNull();
26
+ expect(result.loading).toBe(true);
27
+ expect(result.data).toBeUndefined();
28
+ expect(result.error).toBeUndefined();
29
+ });
30
+ it("should return data for resolved promise", async () => {
31
+ const [promise] = atom(Promise.resolve("hello"));
32
+ // First call to initialize tracking
33
+ loadable(promise);
34
+ // Wait for promise to settle and tracking to update
35
+ await new Promise((r) => setTimeout(r, 10));
36
+ const result = loadable(promise);
37
+ expect(result).not.toBeNull();
38
+ expect(result.loading).toBe(false);
39
+ expect(result.data).toBe("hello");
40
+ expect(result.error).toBeUndefined();
41
+ });
42
+ it("should return error for rejected promise", async () => {
43
+ const error = new Error("test error");
44
+ const [promise] = atom(rejectedPromise(error));
45
+ // First call to initialize tracking
46
+ loadable(promise);
47
+ // Wait for promise to settle and tracking to update
48
+ await new Promise((r) => setTimeout(r, 10));
49
+ const result = loadable(promise);
50
+ expect(result).not.toBeNull();
51
+ expect(result.loading).toBe(false);
52
+ expect(result.data).toBeUndefined();
53
+ expect(result.error).toBe(error);
54
+ });
55
+ it("should handle promise that resolves later", async () => {
56
+ let resolve;
57
+ const [promise] = atom(new Promise((r) => {
58
+ resolve = r;
59
+ }));
60
+ // Initially loading
61
+ let result = loadable(promise);
62
+ expect(result.loading).toBe(true);
63
+ // Resolve the promise
64
+ resolve("resolved");
65
+ await new Promise((r) => setTimeout(r, 10));
66
+ // Now should have data
67
+ result = loadable(promise);
68
+ expect(result.loading).toBe(false);
69
+ expect(result.data).toBe("resolved");
70
+ });
71
+ it("should cache promise state", async () => {
72
+ const [promise] = atom(Promise.resolve(42));
73
+ // First call to initialize tracking
74
+ loadable(promise);
75
+ // Wait for promise to settle
76
+ await new Promise((r) => setTimeout(r, 10));
77
+ // Multiple calls should return same cached state
78
+ const result1 = loadable(promise);
79
+ const result2 = loadable(promise);
80
+ expect(result1.data).toBe(42);
81
+ expect(result2.data).toBe(42);
82
+ });
83
+ it("should handle signal value change", async () => {
84
+ const [promise, setPromise] = atom(null);
85
+ // Initial null
86
+ expect(loadable(promise)).toBeNull();
87
+ // Set to pending promise
88
+ setPromise(Promise.resolve(1));
89
+ // First call to initialize tracking
90
+ loadable(promise);
91
+ // Wait for promise
92
+ await new Promise((r) => setTimeout(r, 10));
93
+ expect(loadable(promise).data).toBe(1);
94
+ // Change to new promise
95
+ setPromise(Promise.resolve(2));
96
+ // Initialize tracking for new promise
97
+ loadable(promise);
98
+ await new Promise((r) => setTimeout(r, 10));
99
+ expect(loadable(promise).data).toBe(2);
100
+ });
101
+ describe("reactive tracking", () => {
102
+ it("should re-run effect when promise resolves", async () => {
103
+ // Create a promise that we control
104
+ let resolve;
105
+ const [promise] = atom(new Promise((r) => {
106
+ resolve = r;
107
+ }));
108
+ // Track effect runs
109
+ const states = [];
110
+ const dispose = effect(() => {
111
+ const state = loadable(promise);
112
+ if (state) {
113
+ states.push({ loading: state.loading, data: state.data });
114
+ }
115
+ });
116
+ // First run should be loading
117
+ expect(states.length).toBe(1);
118
+ expect(states[0].loading).toBe(true);
119
+ expect(states[0].data).toBeUndefined();
120
+ // Resolve the promise
121
+ resolve("hello world");
122
+ // Wait for promise to settle and effect to re-run
123
+ await new Promise((r) => setTimeout(r, 10));
124
+ // Effect should have re-run with resolved data
125
+ expect(states.length).toBe(2);
126
+ expect(states[1].loading).toBe(false);
127
+ expect(states[1].data).toBe("hello world");
128
+ dispose();
129
+ });
130
+ it("should re-run effect when promise rejects", async () => {
131
+ // Create a promise that we control
132
+ let reject;
133
+ const p = new Promise((_, r) => {
134
+ reject = r;
135
+ });
136
+ // Suppress unhandled rejection warning
137
+ p.catch(() => { });
138
+ const [promise] = atom(p);
139
+ // Track effect runs
140
+ const states = [];
141
+ const dispose = effect(() => {
142
+ const state = loadable(promise);
143
+ if (state) {
144
+ states.push({ loading: state.loading, error: state.error });
145
+ }
146
+ });
147
+ // First run should be loading
148
+ expect(states.length).toBe(1);
149
+ expect(states[0].loading).toBe(true);
150
+ // Reject the promise
151
+ const error = new Error("loadable test rejection");
152
+ reject(error);
153
+ // Wait for promise to settle and effect to re-run
154
+ await new Promise((r) => setTimeout(r, 10));
155
+ // Effect should have re-run with error
156
+ expect(states.length).toBe(2);
157
+ expect(states[1].loading).toBe(false);
158
+ expect(states[1].error).toBe(error);
159
+ dispose();
160
+ });
161
+ it("should not trigger re-run after effect is disposed", async () => {
162
+ // Create a promise that we control
163
+ let resolve;
164
+ const [promise] = atom(new Promise((r) => {
165
+ resolve = r;
166
+ }));
167
+ // Track effect runs
168
+ const fn = vi.fn();
169
+ const dispose = effect(() => {
170
+ const state = loadable(promise);
171
+ fn(state);
172
+ });
173
+ // First run
174
+ expect(fn).toHaveBeenCalledTimes(1);
175
+ // Dispose the effect BEFORE promise resolves
176
+ dispose();
177
+ // Resolve the promise
178
+ resolve("hello");
179
+ // Wait for promise to settle
180
+ await new Promise((r) => setTimeout(r, 10));
181
+ // Effect should NOT have re-run (was disposed)
182
+ expect(fn).toHaveBeenCalledTimes(1);
183
+ });
184
+ it("should handle multiple promises in same effect", async () => {
185
+ // Create two promises we control
186
+ let resolve1;
187
+ let resolve2;
188
+ const [promise1] = atom(new Promise((r) => {
189
+ resolve1 = r;
190
+ }));
191
+ const [promise2] = atom(new Promise((r) => {
192
+ resolve2 = r;
193
+ }));
194
+ // Track effect runs
195
+ let runCount = 0;
196
+ let state1;
197
+ let state2;
198
+ const dispose = effect(() => {
199
+ runCount++;
200
+ state1 = loadable(promise1);
201
+ state2 = loadable(promise2);
202
+ });
203
+ // Initial run - both loading
204
+ expect(runCount).toBe(1);
205
+ expect(state1.loading).toBe(true);
206
+ expect(state2.loading).toBe(true);
207
+ // Resolve first promise
208
+ resolve1(100);
209
+ await new Promise((r) => setTimeout(r, 10));
210
+ // Effect should re-run, first resolved, second still loading
211
+ expect(runCount).toBe(2);
212
+ expect(state1.loading).toBe(false);
213
+ expect(state1.data).toBe(100);
214
+ expect(state2.loading).toBe(true);
215
+ // Resolve second promise
216
+ resolve2(200);
217
+ await new Promise((r) => setTimeout(r, 10));
218
+ // Effect should re-run again, both resolved
219
+ expect(runCount).toBe(3);
220
+ expect(state1.data).toBe(100);
221
+ expect(state2.loading).toBe(false);
222
+ expect(state2.data).toBe(200);
223
+ dispose();
224
+ });
225
+ it("should handle promise signal change during loading", async () => {
226
+ // Create first promise (never resolves)
227
+ const [promise, setPromise] = atom(new Promise(() => { }));
228
+ let runCount = 0;
229
+ let lastData;
230
+ const dispose = effect(() => {
231
+ runCount++;
232
+ const state = loadable(promise);
233
+ if (state && !state.loading) {
234
+ lastData = state.data;
235
+ }
236
+ });
237
+ // Initial run - loading
238
+ expect(runCount).toBe(1);
239
+ // Change to a new promise that resolves immediately
240
+ setPromise(Promise.resolve("new value"));
241
+ // Effect should re-run due to signal change
242
+ await new Promise((r) => setTimeout(r, 10));
243
+ // Should have the new resolved value
244
+ expect(lastData).toBe("new value");
245
+ dispose();
246
+ });
247
+ it("should not re-run for already resolved promise", async () => {
248
+ // Use an already-resolved promise
249
+ const [promise] = atom(Promise.resolve("already done"));
250
+ // Wait for cache to be populated
251
+ loadable(promise);
252
+ await new Promise((r) => setTimeout(r, 10));
253
+ // Now track effect runs
254
+ const fn = vi.fn();
255
+ const dispose = effect(() => {
256
+ const state = loadable(promise);
257
+ fn(state);
258
+ });
259
+ // Should only run once (data already available)
260
+ expect(fn).toHaveBeenCalledTimes(1);
261
+ expect(fn).toHaveBeenCalledWith(expect.objectContaining({
262
+ loading: false,
263
+ data: "already done",
264
+ }));
265
+ // Wait a bit to ensure no extra runs
266
+ await new Promise((r) => setTimeout(r, 20));
267
+ expect(fn).toHaveBeenCalledTimes(1);
268
+ dispose();
269
+ });
270
+ it("should not trigger update after disposal when promise resolves", async () => {
271
+ // Create a promise that we control
272
+ let resolve;
273
+ const [promise] = atom(new Promise((r) => {
274
+ resolve = r;
275
+ }));
276
+ // Track effect runs
277
+ const fn = vi.fn();
278
+ const dispose = effect(() => {
279
+ const state = loadable(promise);
280
+ fn(state?.loading);
281
+ });
282
+ // First run should be loading
283
+ expect(fn).toHaveBeenCalledTimes(1);
284
+ expect(fn).toHaveBeenLastCalledWith(true);
285
+ // Dispose BEFORE promise resolves - this tests the disposed check in onFinally (lines 80-81)
286
+ dispose();
287
+ // Now resolve the promise
288
+ resolve("resolved after dispose");
289
+ // Wait for promise to settle
290
+ await new Promise((r) => setTimeout(r, 20));
291
+ // Effect should NOT have re-run since it was disposed
292
+ expect(fn).toHaveBeenCalledTimes(1);
293
+ });
294
+ it("should not trigger update after disposal when promise rejects", async () => {
295
+ // Create a promise that we control
296
+ let reject;
297
+ const p = new Promise((_, r) => {
298
+ reject = r;
299
+ });
300
+ // Suppress unhandled rejection warning
301
+ p.catch(() => { });
302
+ const [promise] = atom(p);
303
+ // Track effect runs
304
+ const fn = vi.fn();
305
+ const dispose = effect(() => {
306
+ const state = loadable(promise);
307
+ fn(state?.loading);
308
+ });
309
+ // First run should be loading
310
+ expect(fn).toHaveBeenCalledTimes(1);
311
+ // Dispose BEFORE promise rejects
312
+ dispose();
313
+ // Now reject the promise
314
+ reject(new Error("rejected after dispose"));
315
+ // Wait for promise to settle
316
+ await new Promise((r) => setTimeout(r, 20));
317
+ // Effect should NOT have re-run since it was disposed
318
+ expect(fn).toHaveBeenCalledTimes(1);
319
+ });
320
+ });
321
+ });
322
+ //# sourceMappingURL=loadable.test.js.map