atomirx 0.0.8 → 0.1.1
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.
- package/README.md +198 -2234
- package/bin/cli.js +90 -0
- package/dist/core/derived.d.ts +2 -2
- package/dist/core/effect.d.ts +3 -2
- package/dist/core/onCreateHook.d.ts +15 -2
- package/dist/core/onErrorHook.d.ts +4 -1
- package/dist/core/pool.d.ts +78 -0
- package/dist/core/pool.test.d.ts +1 -0
- package/dist/core/select-boolean.test.d.ts +1 -0
- package/dist/core/select-pool.test.d.ts +1 -0
- package/dist/core/select.d.ts +278 -86
- package/dist/core/types.d.ts +233 -1
- package/dist/core/withAbort.d.ts +95 -0
- package/dist/core/withReady.d.ts +3 -3
- package/dist/devtools/constants.d.ts +41 -0
- package/dist/devtools/index.cjs +1 -0
- package/dist/devtools/index.d.ts +29 -0
- package/dist/devtools/index.js +429 -0
- package/dist/devtools/registry.d.ts +98 -0
- package/dist/devtools/registry.test.d.ts +1 -0
- package/dist/devtools/setup.d.ts +61 -0
- package/dist/devtools/types.d.ts +311 -0
- package/dist/index-BZEnfIcB.cjs +1 -0
- package/dist/index-BbPZhsDl.js +1653 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +18 -14
- package/dist/onDispatchHook-C8yLzr-o.cjs +1 -0
- package/dist/onDispatchHook-SKbiIUaJ.js +5 -0
- package/dist/onErrorHook-BGGy3tqK.js +38 -0
- package/dist/onErrorHook-DHBASmYw.cjs +1 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.js +191 -151
- package/dist/react/onDispatchHook.d.ts +106 -0
- package/dist/react/useAction.d.ts +4 -1
- package/dist/react-devtools/DevToolsPanel.d.ts +93 -0
- package/dist/react-devtools/EntityDetails.d.ts +10 -0
- package/dist/react-devtools/EntityList.d.ts +15 -0
- package/dist/react-devtools/LogList.d.ts +12 -0
- package/dist/react-devtools/hooks.d.ts +50 -0
- package/dist/react-devtools/index.cjs +1 -0
- package/dist/react-devtools/index.d.ts +31 -0
- package/dist/react-devtools/index.js +1589 -0
- package/dist/react-devtools/styles.d.ts +148 -0
- package/package.json +26 -2
- package/skills/atomirx/SKILL.md +456 -0
- package/skills/atomirx/references/async-patterns.md +188 -0
- package/skills/atomirx/references/atom-patterns.md +238 -0
- package/skills/atomirx/references/deferred-loading.md +191 -0
- package/skills/atomirx/references/derived-patterns.md +428 -0
- package/skills/atomirx/references/effect-patterns.md +426 -0
- package/skills/atomirx/references/error-handling.md +140 -0
- package/skills/atomirx/references/hooks.md +322 -0
- package/skills/atomirx/references/pool-patterns.md +229 -0
- package/skills/atomirx/references/react-integration.md +411 -0
- package/skills/atomirx/references/rules.md +407 -0
- package/skills/atomirx/references/select-context.md +309 -0
- package/skills/atomirx/references/service-template.md +172 -0
- package/skills/atomirx/references/store-template.md +205 -0
- package/skills/atomirx/references/testing-patterns.md +431 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -1440
- package/coverage/coverage-final.json +0 -14
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/core/atom.ts.html +0 -889
- package/coverage/src/core/batch.ts.html +0 -223
- package/coverage/src/core/define.ts.html +0 -805
- package/coverage/src/core/emitter.ts.html +0 -919
- package/coverage/src/core/equality.ts.html +0 -631
- package/coverage/src/core/hook.ts.html +0 -460
- package/coverage/src/core/index.html +0 -281
- package/coverage/src/core/isAtom.ts.html +0 -100
- package/coverage/src/core/isPromiseLike.ts.html +0 -133
- package/coverage/src/core/onCreateHook.ts.html +0 -138
- package/coverage/src/core/scheduleNotifyHook.ts.html +0 -94
- package/coverage/src/core/types.ts.html +0 -523
- package/coverage/src/core/withUse.ts.html +0 -253
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -106
- package/dist/index-CBVj1kSj.js +0 -1350
- package/dist/index-Cxk9v0um.cjs +0 -1
- package/scripts/publish.js +0 -198
- package/src/core/atom.test.ts +0 -633
- package/src/core/atom.ts +0 -311
- package/src/core/atomState.test.ts +0 -342
- package/src/core/atomState.ts +0 -256
- package/src/core/batch.test.ts +0 -257
- package/src/core/batch.ts +0 -172
- package/src/core/define.test.ts +0 -343
- package/src/core/define.ts +0 -243
- package/src/core/derived.test.ts +0 -1215
- package/src/core/derived.ts +0 -450
- package/src/core/effect.test.ts +0 -802
- package/src/core/effect.ts +0 -188
- package/src/core/emitter.test.ts +0 -364
- package/src/core/emitter.ts +0 -392
- package/src/core/equality.test.ts +0 -392
- package/src/core/equality.ts +0 -182
- package/src/core/getAtomState.ts +0 -69
- package/src/core/hook.test.ts +0 -227
- package/src/core/hook.ts +0 -177
- package/src/core/isAtom.ts +0 -27
- package/src/core/isPromiseLike.test.ts +0 -72
- package/src/core/isPromiseLike.ts +0 -16
- package/src/core/onCreateHook.ts +0 -107
- package/src/core/onErrorHook.test.ts +0 -350
- package/src/core/onErrorHook.ts +0 -52
- package/src/core/promiseCache.test.ts +0 -241
- package/src/core/promiseCache.ts +0 -284
- package/src/core/scheduleNotifyHook.ts +0 -53
- package/src/core/select.ts +0 -729
- package/src/core/selector.test.ts +0 -799
- package/src/core/types.ts +0 -389
- package/src/core/withReady.test.ts +0 -534
- package/src/core/withReady.ts +0 -191
- package/src/core/withUse.test.ts +0 -249
- package/src/core/withUse.ts +0 -56
- package/src/index.test.ts +0 -80
- package/src/index.ts +0 -65
- package/src/react/index.ts +0 -21
- package/src/react/rx.test.tsx +0 -571
- package/src/react/rx.tsx +0 -531
- package/src/react/strictModeTest.tsx +0 -71
- package/src/react/useAction.test.ts +0 -987
- package/src/react/useAction.ts +0 -607
- package/src/react/useSelector.test.ts +0 -182
- package/src/react/useSelector.ts +0 -292
- package/src/react/useStable.test.ts +0 -553
- package/src/react/useStable.ts +0 -288
- package/tsconfig.json +0 -9
- package/v2.md +0 -725
- package/vite.config.ts +0 -42
package/src/core/withReady.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { isPromiseLike } from "./isPromiseLike";
|
|
2
|
-
import { trackPromise } from "./promiseCache";
|
|
3
|
-
import { SelectContext } from "./select";
|
|
4
|
-
import { AnyFunc, Atom } from "./types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Extension interface that adds `ready()` method to SelectContext.
|
|
8
|
-
* Used in derived atoms and effects to wait for non-null values.
|
|
9
|
-
*/
|
|
10
|
-
export interface WithReadySelectContext {
|
|
11
|
-
/**
|
|
12
|
-
* Wait for an atom to have a non-null/non-undefined value.
|
|
13
|
-
*
|
|
14
|
-
* If the value is null/undefined, the computation suspends until the atom
|
|
15
|
-
* changes to a non-null value, then automatically resumes.
|
|
16
|
-
*
|
|
17
|
-
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
18
|
-
*
|
|
19
|
-
* @param atom - The atom to read and wait for
|
|
20
|
-
* @returns The non-null value (type excludes null | undefined)
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* // Wait for currentArticleId to be set before computing
|
|
25
|
-
* const currentArticle$ = derived(({ ready, read }) => {
|
|
26
|
-
* const id = ready(currentArticleId$); // Suspends if null
|
|
27
|
-
* const cache = read(articleCache$);
|
|
28
|
-
* return cache[id];
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
ready<T>(
|
|
33
|
-
atom: Atom<T>
|
|
34
|
-
): T extends PromiseLike<any> ? never : Exclude<T, null | undefined>;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Wait for a selected value from an atom to be non-null/non-undefined.
|
|
38
|
-
*
|
|
39
|
-
* If the selected value is null/undefined, the computation suspends until the
|
|
40
|
-
* selected value changes to a non-null value, then automatically resumes.
|
|
41
|
-
*
|
|
42
|
-
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
43
|
-
*
|
|
44
|
-
* @param atom - The atom to read
|
|
45
|
-
* @param selector - Function to extract/transform the value
|
|
46
|
-
* @returns The non-null selected value
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```ts
|
|
50
|
-
* // Wait for user's email to be set
|
|
51
|
-
* const emailDerived$ = derived(({ ready }) => {
|
|
52
|
-
* const email = ready(user$, u => u.email); // Suspends if email is null
|
|
53
|
-
* return `Contact: ${email}`;
|
|
54
|
-
* });
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
ready<T, R>(
|
|
58
|
-
atom: Atom<T>,
|
|
59
|
-
selector: (current: Awaited<T>) => R
|
|
60
|
-
): R extends PromiseLike<any> ? never : Exclude<R, null | undefined>;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Execute a function and wait for its result to be non-null/non-undefined.
|
|
64
|
-
*
|
|
65
|
-
* If the function returns null/undefined, the computation suspends until
|
|
66
|
-
* re-executed with a non-null result.
|
|
67
|
-
*
|
|
68
|
-
* **IMPORTANT: Only use in `derived()` or `effect()` context**
|
|
69
|
-
*
|
|
70
|
-
* **NOTE:** This overload is designed for use with async combinators like
|
|
71
|
-
* `all()`, `race()`, `any()`, `settled()` where promises come from stable
|
|
72
|
-
* atom sources. It does NOT support dynamic promise creation (returning a
|
|
73
|
-
* new Promise from the callback). For async selectors that return promises,
|
|
74
|
-
* use `ready(atom$, selector?)` instead.
|
|
75
|
-
*
|
|
76
|
-
* @param fn - Synchronous function to execute and wait for
|
|
77
|
-
* @returns The non-null result (excludes null | undefined)
|
|
78
|
-
* @throws {Error} If the callback returns a Promise
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* // Wait for a computed value to be ready
|
|
83
|
-
* const result$ = derived(({ ready, read }) => {
|
|
84
|
-
* const value = ready(() => computeExpensiveValue(read(input$)));
|
|
85
|
-
* return `Result: ${value}`;
|
|
86
|
-
* });
|
|
87
|
-
* ```
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```ts
|
|
91
|
-
* // Use with async combinators (all, race, any, settled)
|
|
92
|
-
* const combined$ = derived(({ ready, all }) => {
|
|
93
|
-
* const [user, posts] = ready(() => all(user$, posts$));
|
|
94
|
-
* return { user, posts };
|
|
95
|
-
* });
|
|
96
|
-
* ```
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* // For async selectors, use ready(atom$, selector?) instead:
|
|
101
|
-
* const data$ = derived(({ ready }) => {
|
|
102
|
-
* const data = ready(source$, (val) => fetchData(val.id));
|
|
103
|
-
* return data;
|
|
104
|
-
* });
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
ready<T>(
|
|
108
|
-
fn: () => T
|
|
109
|
-
): T extends PromiseLike<any> ? never : Exclude<Awaited<T>, null | undefined>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Internal helper that suspends computation if value is null/undefined.
|
|
114
|
-
*/
|
|
115
|
-
function waitForValue<T>(value: T): any {
|
|
116
|
-
if (value === undefined || value === null) {
|
|
117
|
-
throw new Promise(() => {});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Handle async selectors: when the selector returns a Promise,
|
|
121
|
-
// we track its state and handle suspension/resolution accordingly
|
|
122
|
-
if (isPromiseLike(value)) {
|
|
123
|
-
const p = trackPromise(value);
|
|
124
|
-
|
|
125
|
-
// Promise is still pending - suspend computation by throwing
|
|
126
|
-
// the tracked promise. This enables React Suspense integration.
|
|
127
|
-
if (p.status === "pending") {
|
|
128
|
-
throw p.promise;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Promise resolved successfully - return the resolved value.
|
|
132
|
-
// Note: This bypasses null/undefined checking for async results,
|
|
133
|
-
// allowing async selectors to return any value including null.
|
|
134
|
-
if (p.status === "fulfilled") {
|
|
135
|
-
return p.value;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Promise rejected - propagate the error
|
|
139
|
-
throw p.error;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// For sync values (no selector, or selector returned sync value),
|
|
143
|
-
// check for null/undefined and suspend if not ready
|
|
144
|
-
|
|
145
|
-
return value as Exclude<T, null | undefined>;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Plugin that adds `ready()` method to a SelectContext.
|
|
150
|
-
*
|
|
151
|
-
* `ready()` enables a "reactive suspension" pattern where derived atoms
|
|
152
|
-
* wait for required values before computing. This is useful for:
|
|
153
|
-
*
|
|
154
|
-
* - Route-based entity loading (`/article/:id` - wait for ID to be set)
|
|
155
|
-
* - Authentication-gated content (wait for user to be logged in)
|
|
156
|
-
* - Conditional data dependencies (wait for prerequisite data)
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* ```ts
|
|
160
|
-
* // Used internally by derived() - you don't need to call this directly
|
|
161
|
-
* const result = select((context) => fn(context.use(withReady())));
|
|
162
|
-
* ```
|
|
163
|
-
*/
|
|
164
|
-
export function withReady() {
|
|
165
|
-
return <TContext extends SelectContext>(
|
|
166
|
-
context: TContext
|
|
167
|
-
): TContext & WithReadySelectContext => {
|
|
168
|
-
return {
|
|
169
|
-
...context,
|
|
170
|
-
ready: (
|
|
171
|
-
atomOrFn: Atom<any> | AnyFunc,
|
|
172
|
-
selector?: (current: any) => any
|
|
173
|
-
): any => {
|
|
174
|
-
if (typeof atomOrFn === "function") {
|
|
175
|
-
const value = atomOrFn();
|
|
176
|
-
if (isPromiseLike(value)) {
|
|
177
|
-
throw new Error(
|
|
178
|
-
"ready(callback) overload does not support async callbacks. Use ready(atom, selector?) instead."
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
return waitForValue(value);
|
|
182
|
-
}
|
|
183
|
-
const value = context.read(atomOrFn);
|
|
184
|
-
// we allow selector to return a promise, and wait for that promise if it is not resolved yet
|
|
185
|
-
const selected = selector ? selector(value) : value;
|
|
186
|
-
|
|
187
|
-
return waitForValue(selected);
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
}
|
package/src/core/withUse.test.ts
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { withUse } from "./withUse";
|
|
3
|
-
|
|
4
|
-
describe("withUse", () => {
|
|
5
|
-
describe("basic functionality", () => {
|
|
6
|
-
it("should add use method to object", () => {
|
|
7
|
-
const obj = { value: 1 };
|
|
8
|
-
const result = withUse(obj);
|
|
9
|
-
|
|
10
|
-
expect(result.value).toBe(1);
|
|
11
|
-
expect(typeof result.use).toBe("function");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should preserve original object properties", () => {
|
|
15
|
-
const obj = { a: 1, b: "hello", c: true };
|
|
16
|
-
const result = withUse(obj);
|
|
17
|
-
|
|
18
|
-
expect(result.a).toBe(1);
|
|
19
|
-
expect(result.b).toBe("hello");
|
|
20
|
-
expect(result.c).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should work with arrays", () => {
|
|
24
|
-
const arr = [1, 2, 3];
|
|
25
|
-
const result = withUse(arr);
|
|
26
|
-
|
|
27
|
-
expect(result[0]).toBe(1);
|
|
28
|
-
expect(result.length).toBe(3);
|
|
29
|
-
expect(typeof result.use).toBe("function");
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("use() transformations", () => {
|
|
34
|
-
it("should transform object with plugin", () => {
|
|
35
|
-
const obj = withUse({ value: 10 });
|
|
36
|
-
|
|
37
|
-
const transformed = obj.use((source) => ({
|
|
38
|
-
...source,
|
|
39
|
-
doubled: source.value * 2,
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
expect(transformed.value).toBe(10);
|
|
43
|
-
expect(transformed.doubled).toBe(20);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("should return source when plugin returns void", () => {
|
|
47
|
-
const obj = withUse({ value: 1 });
|
|
48
|
-
const sideEffect = vi.fn();
|
|
49
|
-
|
|
50
|
-
const result = obj.use((source) => {
|
|
51
|
-
sideEffect(source.value);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
expect(sideEffect).toHaveBeenCalledWith(1);
|
|
55
|
-
expect(result).toBe(obj);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should return source when plugin returns undefined", () => {
|
|
59
|
-
const obj = withUse({ value: 1 });
|
|
60
|
-
|
|
61
|
-
const result = obj.use(() => undefined);
|
|
62
|
-
|
|
63
|
-
expect(result).toBe(obj);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("should return source when plugin returns null", () => {
|
|
67
|
-
const obj = withUse({ value: 1 });
|
|
68
|
-
|
|
69
|
-
const result = obj.use(() => null as any);
|
|
70
|
-
|
|
71
|
-
expect(result).toBe(obj);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should return source when plugin returns false", () => {
|
|
75
|
-
const obj = withUse({ value: 1 });
|
|
76
|
-
|
|
77
|
-
const result = obj.use(() => false as any);
|
|
78
|
-
|
|
79
|
-
expect(result).toBe(obj);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should return source when plugin returns empty string", () => {
|
|
83
|
-
const obj = withUse({ value: 1 });
|
|
84
|
-
|
|
85
|
-
const result = obj.use(() => "" as any);
|
|
86
|
-
|
|
87
|
-
expect(result).toBe(obj);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("should return source when plugin returns 0", () => {
|
|
91
|
-
const obj = withUse({ value: 1 });
|
|
92
|
-
|
|
93
|
-
const result = obj.use(() => 0 as any);
|
|
94
|
-
|
|
95
|
-
expect(result).toBe(obj);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("chaining", () => {
|
|
100
|
-
it("should allow chaining by wrapping result with use method", () => {
|
|
101
|
-
const obj = withUse({ value: 1 });
|
|
102
|
-
|
|
103
|
-
// First transformation - result gets wrapped with use()
|
|
104
|
-
const withA = obj.use((source) => ({ ...source, a: "first" }));
|
|
105
|
-
expect(withA.value).toBe(1);
|
|
106
|
-
expect(withA.a).toBe("first");
|
|
107
|
-
expect(typeof withA.use).toBe("function");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should pass the transformed object to the next use() in chain", () => {
|
|
111
|
-
const obj = withUse({ value: 1 });
|
|
112
|
-
|
|
113
|
-
// When chaining, each use() receives the result of the previous transformation
|
|
114
|
-
const result = obj
|
|
115
|
-
.use((source) => ({ original: source.value, a: "first" }))
|
|
116
|
-
.use((source) => ({ ...source, b: "second" }));
|
|
117
|
-
|
|
118
|
-
expect(result.original).toBe(1);
|
|
119
|
-
expect(result.a).toBe("first");
|
|
120
|
-
expect(result.b).toBe("second");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("should wrap result with withUse if it does not have use method", () => {
|
|
124
|
-
const obj = withUse({ value: 1 });
|
|
125
|
-
|
|
126
|
-
const result = obj.use(() => ({ newProp: "test" }));
|
|
127
|
-
|
|
128
|
-
expect(result.newProp).toBe("test");
|
|
129
|
-
expect(typeof result.use).toBe("function");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("should return as-is if result already has use method", () => {
|
|
133
|
-
const obj = withUse({ value: 1 });
|
|
134
|
-
const existingWithUse = withUse({ other: 2 });
|
|
135
|
-
|
|
136
|
-
const result = obj.use(() => existingWithUse);
|
|
137
|
-
|
|
138
|
-
expect(result).toBe(existingWithUse);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("should allow fluent chaining with independent transformations", () => {
|
|
142
|
-
const obj = withUse({ value: 1 });
|
|
143
|
-
|
|
144
|
-
// Each use() receives the result of the previous use()
|
|
145
|
-
const result = obj
|
|
146
|
-
.use((source) => ({ value: source.value, doubled: source.value * 2 }))
|
|
147
|
-
.use((source) => ({ ...source, tripled: source.value * 3 }));
|
|
148
|
-
|
|
149
|
-
expect(result.value).toBe(1);
|
|
150
|
-
expect(result.doubled).toBe(2);
|
|
151
|
-
expect(result.tripled).toBe(3);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe("primitive return values", () => {
|
|
156
|
-
it("should return primitive number directly", () => {
|
|
157
|
-
const obj = withUse({ value: 1 });
|
|
158
|
-
|
|
159
|
-
const result = obj.use(() => 42);
|
|
160
|
-
|
|
161
|
-
expect(result).toBe(42);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("should return primitive string directly", () => {
|
|
165
|
-
const obj = withUse({ value: 1 });
|
|
166
|
-
|
|
167
|
-
const result = obj.use(() => "hello");
|
|
168
|
-
|
|
169
|
-
expect(result).toBe("hello");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("should return primitive boolean directly", () => {
|
|
173
|
-
const obj = withUse({ value: 1 });
|
|
174
|
-
|
|
175
|
-
const result = obj.use(() => true);
|
|
176
|
-
|
|
177
|
-
expect(result).toBe(true);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe("function return values", () => {
|
|
182
|
-
it("should wrap function result with withUse if no use method", () => {
|
|
183
|
-
const obj = withUse({ value: 1 });
|
|
184
|
-
const fn = () => 42;
|
|
185
|
-
|
|
186
|
-
const result = obj.use(() => fn);
|
|
187
|
-
|
|
188
|
-
expect(result()).toBe(42);
|
|
189
|
-
expect(typeof result.use).toBe("function");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it("should return function as-is if it has use method", () => {
|
|
193
|
-
const obj = withUse({ value: 1 });
|
|
194
|
-
const fnWithUse = Object.assign(() => 42, { use: () => {} });
|
|
195
|
-
|
|
196
|
-
const result = obj.use(() => fnWithUse);
|
|
197
|
-
|
|
198
|
-
expect(result).toBe(fnWithUse);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("real-world patterns", () => {
|
|
203
|
-
it("should support adding methods to an object", () => {
|
|
204
|
-
const counter = withUse({ count: 0 });
|
|
205
|
-
|
|
206
|
-
const enhanced = counter.use((source) => ({
|
|
207
|
-
...source,
|
|
208
|
-
increment: () => {
|
|
209
|
-
source.count++;
|
|
210
|
-
},
|
|
211
|
-
decrement: () => {
|
|
212
|
-
source.count--;
|
|
213
|
-
},
|
|
214
|
-
}));
|
|
215
|
-
|
|
216
|
-
enhanced.increment();
|
|
217
|
-
expect(counter.count).toBe(1);
|
|
218
|
-
|
|
219
|
-
enhanced.decrement();
|
|
220
|
-
expect(counter.count).toBe(0);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("should support middleware-like pattern", () => {
|
|
224
|
-
const logger: string[] = [];
|
|
225
|
-
|
|
226
|
-
const api = withUse({
|
|
227
|
-
fetch: (url: string) => `data from ${url}`,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const withLogging = api.use((source) => ({
|
|
231
|
-
...source,
|
|
232
|
-
fetch: (url: string) => {
|
|
233
|
-
logger.push(`fetching: ${url}`);
|
|
234
|
-
const result = source.fetch(url);
|
|
235
|
-
logger.push(`fetched: ${result}`);
|
|
236
|
-
return result;
|
|
237
|
-
},
|
|
238
|
-
}));
|
|
239
|
-
|
|
240
|
-
const result = withLogging.fetch("/api/users");
|
|
241
|
-
|
|
242
|
-
expect(result).toBe("data from /api/users");
|
|
243
|
-
expect(logger).toEqual([
|
|
244
|
-
"fetching: /api/users",
|
|
245
|
-
"fetched: data from /api/users",
|
|
246
|
-
]);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
});
|
package/src/core/withUse.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { Pipeable } from "./types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Adds a chainable `.use()` method to any object, enabling plugin-based transformations.
|
|
5
|
-
*
|
|
6
|
-
* The `.use()` method accepts a plugin function that receives the source object
|
|
7
|
-
* and can return a transformed version. Supports several return patterns:
|
|
8
|
-
*
|
|
9
|
-
* - **Void/falsy**: Returns the original source unchanged (side-effect only plugins)
|
|
10
|
-
* - **Object/function with `.use`**: Returns as-is (already chainable)
|
|
11
|
-
* - **Object/function without `.use`**: Wraps with `withUse()` for continued chaining
|
|
12
|
-
* - **Primitive**: Returns the value directly
|
|
13
|
-
*
|
|
14
|
-
* @template TSource - The type of the source object being enhanced
|
|
15
|
-
* @param source - The object to add `.use()` method to
|
|
16
|
-
* @returns The source object with `.use()` method attached
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* // Basic usage with atom tuple
|
|
20
|
-
* const mappable = withUse([signal, setter]);
|
|
21
|
-
* const transformed = mappable.use(([sig, set]) => ({
|
|
22
|
-
* sig,
|
|
23
|
-
* set: (v: string) => set(Number(v))
|
|
24
|
-
* }));
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* // Chaining multiple transformations
|
|
28
|
-
* atom(0)
|
|
29
|
-
* .use(([sig, set]) => [sig, (v: number) => set(v * 2)])
|
|
30
|
-
* .use(([sig, set]) => [sig, (v: number) => set(v + 1)]);
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* // Side-effect only plugin (returns void)
|
|
34
|
-
* mappable.use((source) => {
|
|
35
|
-
* console.log('Source:', source);
|
|
36
|
-
* // returns undefined - original source is returned
|
|
37
|
-
* });
|
|
38
|
-
*/
|
|
39
|
-
export function withUse<TSource extends object>(source: TSource) {
|
|
40
|
-
return Object.assign(source, {
|
|
41
|
-
use<TNew = void>(plugin: (source: NoInfer<TSource>) => TNew): any {
|
|
42
|
-
const result = plugin(source);
|
|
43
|
-
// Void/falsy: return original source (side-effect only plugins)
|
|
44
|
-
if (!result) return source;
|
|
45
|
-
// Object or function: check if already has .use(), otherwise wrap
|
|
46
|
-
if (typeof result === "object" || typeof result === "function") {
|
|
47
|
-
if ("use" in result) {
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
return withUse(result);
|
|
51
|
-
}
|
|
52
|
-
// Primitive values: return directly (not chainable)
|
|
53
|
-
return result;
|
|
54
|
-
},
|
|
55
|
-
}) as TSource & Pipeable;
|
|
56
|
-
}
|
package/src/index.test.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
atom,
|
|
4
|
-
batch,
|
|
5
|
-
define,
|
|
6
|
-
derived,
|
|
7
|
-
effect,
|
|
8
|
-
emitter,
|
|
9
|
-
isAtom,
|
|
10
|
-
isDerived,
|
|
11
|
-
select,
|
|
12
|
-
getAtomState,
|
|
13
|
-
isPending,
|
|
14
|
-
} from "./index";
|
|
15
|
-
|
|
16
|
-
describe("atomirx exports", () => {
|
|
17
|
-
it("should export atom", () => {
|
|
18
|
-
expect(typeof atom).toBe("function");
|
|
19
|
-
const count = atom(0);
|
|
20
|
-
expect(count.get()).toBe(0);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should export batch", () => {
|
|
24
|
-
expect(typeof batch).toBe("function");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should export define", () => {
|
|
28
|
-
expect(typeof define).toBe("function");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("should export derived", async () => {
|
|
32
|
-
expect(typeof derived).toBe("function");
|
|
33
|
-
const count = atom(5);
|
|
34
|
-
const doubled = derived(({ read }) => read(count) * 2);
|
|
35
|
-
expect(await doubled.get()).toBe(10);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should export effect", () => {
|
|
39
|
-
expect(typeof effect).toBe("function");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("should export emitter", () => {
|
|
43
|
-
expect(typeof emitter).toBe("function");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("should export isAtom", () => {
|
|
47
|
-
expect(typeof isAtom).toBe("function");
|
|
48
|
-
const count = atom(0);
|
|
49
|
-
expect(isAtom(count)).toBe(true);
|
|
50
|
-
expect(isAtom({})).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should export isDerived", () => {
|
|
54
|
-
expect(typeof isDerived).toBe("function");
|
|
55
|
-
const count = atom(0);
|
|
56
|
-
const doubled = derived(({ read }) => read(count) * 2);
|
|
57
|
-
expect(isDerived(count)).toBe(false);
|
|
58
|
-
expect(isDerived(doubled)).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should export select", () => {
|
|
62
|
-
expect(typeof select).toBe("function");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("should export getAtomState", () => {
|
|
66
|
-
expect(typeof getAtomState).toBe("function");
|
|
67
|
-
const count = atom(42);
|
|
68
|
-
const state = getAtomState(count);
|
|
69
|
-
expect(state.status).toBe("ready");
|
|
70
|
-
if (state.status === "ready") {
|
|
71
|
-
expect(state.value).toBe(42);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should export isPending", () => {
|
|
76
|
-
expect(typeof isPending).toBe("function");
|
|
77
|
-
expect(isPending(42)).toBe(false);
|
|
78
|
-
expect(isPending(new Promise(() => {}))).toBe(true);
|
|
79
|
-
});
|
|
80
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Core
|
|
2
|
-
export { atom, readonly } from "./core/atom";
|
|
3
|
-
export { batch } from "./core/batch";
|
|
4
|
-
export { define } from "./core/define";
|
|
5
|
-
export { derived, type DerivedContext } from "./core/derived";
|
|
6
|
-
export { effect, type EffectContext } from "./core/effect";
|
|
7
|
-
export { emitter } from "./core/emitter";
|
|
8
|
-
export { isAtom, isDerived } from "./core/isAtom";
|
|
9
|
-
export { select, AllAtomsRejectedError } from "./core/select";
|
|
10
|
-
|
|
11
|
-
// Promise utilities
|
|
12
|
-
export { getAtomState } from "./core/getAtomState";
|
|
13
|
-
export {
|
|
14
|
-
isPending,
|
|
15
|
-
isFulfilled,
|
|
16
|
-
isRejected,
|
|
17
|
-
trackPromise,
|
|
18
|
-
unwrap,
|
|
19
|
-
} from "./core/promiseCache";
|
|
20
|
-
|
|
21
|
-
// Types
|
|
22
|
-
export type {
|
|
23
|
-
Atom,
|
|
24
|
-
AtomMeta,
|
|
25
|
-
AtomOptions,
|
|
26
|
-
AtomState,
|
|
27
|
-
AtomValue,
|
|
28
|
-
AnyAtom,
|
|
29
|
-
DerivedAtom,
|
|
30
|
-
DerivedAtomMeta,
|
|
31
|
-
DerivedOptions,
|
|
32
|
-
EffectOptions,
|
|
33
|
-
Equality,
|
|
34
|
-
EqualityShorthand,
|
|
35
|
-
Getter,
|
|
36
|
-
KeyedResult,
|
|
37
|
-
MutableAtom,
|
|
38
|
-
MutableAtomMeta,
|
|
39
|
-
Pipeable,
|
|
40
|
-
SelectStateResult,
|
|
41
|
-
SettledResult,
|
|
42
|
-
} from "./core/types";
|
|
43
|
-
|
|
44
|
-
export { onCreateHook } from "./core/onCreateHook";
|
|
45
|
-
export type {
|
|
46
|
-
CreateInfo,
|
|
47
|
-
MutableInfo,
|
|
48
|
-
DerivedInfo,
|
|
49
|
-
EffectInfo,
|
|
50
|
-
ModuleInfo,
|
|
51
|
-
} from "./core/onCreateHook";
|
|
52
|
-
|
|
53
|
-
export { onErrorHook } from "./core/onErrorHook";
|
|
54
|
-
export type { ErrorInfo } from "./core/onErrorHook";
|
|
55
|
-
|
|
56
|
-
export type {
|
|
57
|
-
SelectContext,
|
|
58
|
-
SelectResult,
|
|
59
|
-
ReactiveSelector as ContextSelectorFn,
|
|
60
|
-
SafeResult,
|
|
61
|
-
} from "./core/select";
|
|
62
|
-
|
|
63
|
-
export type { PromiseState, CombinedPromiseMeta } from "./core/promiseCache";
|
|
64
|
-
|
|
65
|
-
export { promisesEqual } from "./core/promiseCache";
|
package/src/react/index.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export { useSelector } from "./useSelector";
|
|
2
|
-
export { useStable } from "./useStable";
|
|
3
|
-
export type { UseStableResult } from "./useStable";
|
|
4
|
-
export { useAction } from "./useAction";
|
|
5
|
-
export { rx } from "./rx";
|
|
6
|
-
export type { RxOptions } from "./rx";
|
|
7
|
-
|
|
8
|
-
export type {
|
|
9
|
-
ActionState,
|
|
10
|
-
ActionIdleState,
|
|
11
|
-
ActionLoadingState,
|
|
12
|
-
ActionSuccessState,
|
|
13
|
-
ActionErrorState,
|
|
14
|
-
ActionStateWithoutIdle,
|
|
15
|
-
AbortablePromise,
|
|
16
|
-
UseActionOptions,
|
|
17
|
-
ActionContext,
|
|
18
|
-
ActionApi,
|
|
19
|
-
Action,
|
|
20
|
-
} from "./useAction";
|
|
21
|
-
export * from "../index";
|