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/bin/cli.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, cpSync, readdirSync, statSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const COMMANDS = {
|
|
11
|
+
"add-skill": addSkill,
|
|
12
|
+
help: showHelp,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function showHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
atomirx CLI
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
npx atomirx <command>
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
add-skill Install atomirx Cursor skills to your project
|
|
24
|
+
help Show this help message
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
npx atomirx add-skill
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function addSkill() {
|
|
32
|
+
const sourceDir = join(__dirname, "..", "skills", "atomirx");
|
|
33
|
+
const targetDir = join(process.cwd(), ".cursor", "skills", "atomirx");
|
|
34
|
+
|
|
35
|
+
// Check if source skills exist
|
|
36
|
+
if (!existsSync(sourceDir)) {
|
|
37
|
+
console.error("❌ Skills not found in package. This may be a packaging issue.");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if target already exists
|
|
42
|
+
if (existsSync(targetDir)) {
|
|
43
|
+
console.log("⚠️ Skills already exist at .cursor/skills/atomirx");
|
|
44
|
+
console.log(" To reinstall, remove the directory first and run again.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create target directory
|
|
49
|
+
mkdirSync(targetDir, { recursive: true });
|
|
50
|
+
|
|
51
|
+
// Copy skills recursively
|
|
52
|
+
copyRecursive(sourceDir, targetDir);
|
|
53
|
+
|
|
54
|
+
console.log("✅ atomirx skills installed to .cursor/skills/atomirx");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("The skill includes:");
|
|
57
|
+
console.log(" - SKILL.md - Main skill guide");
|
|
58
|
+
console.log(" - references/ - Detailed pattern documentation");
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log("Your AI assistant will now have access to atomirx best practices!");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function copyRecursive(source, target) {
|
|
64
|
+
const entries = readdirSync(source);
|
|
65
|
+
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const sourcePath = join(source, entry);
|
|
68
|
+
const targetPath = join(target, entry);
|
|
69
|
+
const stat = statSync(sourcePath);
|
|
70
|
+
|
|
71
|
+
if (stat.isDirectory()) {
|
|
72
|
+
mkdirSync(targetPath, { recursive: true });
|
|
73
|
+
copyRecursive(sourcePath, targetPath);
|
|
74
|
+
} else {
|
|
75
|
+
cpSync(sourcePath, targetPath);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Main
|
|
81
|
+
const command = process.argv[2] || "help";
|
|
82
|
+
const handler = COMMANDS[command];
|
|
83
|
+
|
|
84
|
+
if (handler) {
|
|
85
|
+
handler();
|
|
86
|
+
} else {
|
|
87
|
+
console.error(`Unknown command: ${command}`);
|
|
88
|
+
showHelp();
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
package/dist/core/derived.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CreateInfo } from './onCreateHook';
|
|
2
2
|
import { ReactiveSelector, SelectContext } from './select';
|
|
3
3
|
import { DerivedAtom, DerivedOptions } from './types';
|
|
4
|
-
import {
|
|
4
|
+
import { WithReadyContext } from './withReady';
|
|
5
5
|
/**
|
|
6
6
|
* Internal options for derived atoms.
|
|
7
7
|
* These are not part of the public API.
|
|
@@ -21,7 +21,7 @@ export interface DerivedInternalOptions {
|
|
|
21
21
|
* Currently identical to `SelectContext`, but defined separately to allow
|
|
22
22
|
* future derived-specific extensions without breaking changes.
|
|
23
23
|
*/
|
|
24
|
-
export interface DerivedContext extends SelectContext,
|
|
24
|
+
export interface DerivedContext extends SelectContext, WithReadyContext {
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Creates a derived (computed) atom from source atom(s).
|
package/dist/core/effect.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ReactiveSelector, SelectContext } from './select';
|
|
2
2
|
import { EffectMeta, EffectOptions } from './types';
|
|
3
|
-
import {
|
|
3
|
+
import { WithAbortContext } from './withAbort';
|
|
4
|
+
import { WithReadyContext } from './withReady';
|
|
4
5
|
/**
|
|
5
6
|
* Context object passed to effect functions.
|
|
6
7
|
* Extends `SelectContext` with cleanup utilities.
|
|
7
8
|
*/
|
|
8
|
-
export interface EffectContext extends SelectContext,
|
|
9
|
+
export interface EffectContext extends SelectContext, WithReadyContext, WithAbortContext {
|
|
9
10
|
/**
|
|
10
11
|
* Register a cleanup function that runs before the next execution or on dispose.
|
|
11
12
|
* Multiple cleanup functions can be registered; they run in FIFO order.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from './effect';
|
|
2
|
-
import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta, EffectMeta } from './types';
|
|
2
|
+
import { MutableAtomMeta, DerivedAtomMeta, MutableAtom, DerivedAtom, ModuleMeta, EffectMeta, PoolMeta, Pool } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Information provided when a mutable atom is created.
|
|
5
5
|
*/
|
|
@@ -42,7 +42,7 @@ export interface EffectInfo {
|
|
|
42
42
|
/**
|
|
43
43
|
* Union type for atom/derived/effect creation info.
|
|
44
44
|
*/
|
|
45
|
-
export type CreateInfo = MutableInfo | DerivedInfo | EffectInfo;
|
|
45
|
+
export type CreateInfo = MutableInfo | DerivedInfo | EffectInfo | PoolInfo;
|
|
46
46
|
/**
|
|
47
47
|
* Information provided when a module (via define()) is created.
|
|
48
48
|
*/
|
|
@@ -56,6 +56,19 @@ export interface ModuleInfo {
|
|
|
56
56
|
/** The created module instance */
|
|
57
57
|
instance: unknown;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Information provided when a pool is created.
|
|
61
|
+
*/
|
|
62
|
+
export interface PoolInfo {
|
|
63
|
+
/** Discriminator for pools */
|
|
64
|
+
type: "pool";
|
|
65
|
+
/** Optional key from pool options (for debugging/devtools) */
|
|
66
|
+
key: string | undefined;
|
|
67
|
+
/** Optional metadata from pool options */
|
|
68
|
+
meta: PoolMeta | undefined;
|
|
69
|
+
/** The created pool instance */
|
|
70
|
+
instance: Pool<any, any>;
|
|
71
|
+
}
|
|
59
72
|
/**
|
|
60
73
|
* Global hook that fires whenever an atom or module is created.
|
|
61
74
|
*
|
|
@@ -4,7 +4,10 @@ import { CreateInfo } from './onCreateHook';
|
|
|
4
4
|
*/
|
|
5
5
|
export interface ErrorInfo {
|
|
6
6
|
/** The source that produced the error (atom, derived, or effect) */
|
|
7
|
-
source: CreateInfo
|
|
7
|
+
source: CreateInfo | {
|
|
8
|
+
type: string;
|
|
9
|
+
key?: string;
|
|
10
|
+
};
|
|
8
11
|
/** The error that was thrown */
|
|
9
12
|
error: unknown;
|
|
10
13
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AtomContext } from './atom';
|
|
2
|
+
import { Pool, PoolOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a pool - a collection of atoms indexed by params with automatic GC.
|
|
5
|
+
*
|
|
6
|
+
* A pool is similar to atomFamily in Jotai/Recoil, but with:
|
|
7
|
+
* - Automatic garbage collection based on `gcTime`
|
|
8
|
+
* - ScopedAtom pattern to prevent memory leaks from stale references
|
|
9
|
+
* - Promise-aware GC (never GC while value is a pending Promise)
|
|
10
|
+
* - GC timer resets on create, value change, and access
|
|
11
|
+
*
|
|
12
|
+
* ## Public API (Value-based)
|
|
13
|
+
*
|
|
14
|
+
* The public API works with values, not atoms:
|
|
15
|
+
* - `get(params)` - Get current value
|
|
16
|
+
* - `set(params, value)` - Set value
|
|
17
|
+
* - `has(params)` - Check if entry exists
|
|
18
|
+
* - `remove(params)` - Remove entry
|
|
19
|
+
* - `clear()` - Remove all entries
|
|
20
|
+
* - `forEach(cb)` - Iterate entries
|
|
21
|
+
* - `on(cb)` - Subscribe to pool events (create, change, remove)
|
|
22
|
+
*
|
|
23
|
+
* ## Reactive Context (via SelectContext.from())
|
|
24
|
+
*
|
|
25
|
+
* In derived/effect/useSelector, use `from(pool, params)` to get a ScopedAtom:
|
|
26
|
+
* ```ts
|
|
27
|
+
* derived(({ read, from }) => {
|
|
28
|
+
* const user$ = from(userPool, "user-1");
|
|
29
|
+
* return read(user$);
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* The ScopedAtom is automatically disposed after the computation,
|
|
34
|
+
* preventing memory leaks from stale atom references.
|
|
35
|
+
*
|
|
36
|
+
* @template P - The type of params used to index entries
|
|
37
|
+
* @template T - The type of value stored in each entry
|
|
38
|
+
* @param init - Factory function that creates initial value for params
|
|
39
|
+
* @param options - Pool configuration
|
|
40
|
+
* @returns A Pool instance
|
|
41
|
+
*
|
|
42
|
+
* @example Basic usage
|
|
43
|
+
* ```ts
|
|
44
|
+
* const userPool = pool(
|
|
45
|
+
* (id: string) => ({ name: "", email: "" }),
|
|
46
|
+
* { gcTime: 60_000 }
|
|
47
|
+
* );
|
|
48
|
+
*
|
|
49
|
+
* // Get/set values
|
|
50
|
+
* userPool.set("user-1", { name: "John", email: "john@example.com" });
|
|
51
|
+
* const user = userPool.get("user-1");
|
|
52
|
+
*
|
|
53
|
+
* // In reactive context
|
|
54
|
+
* derived(({ read, from }) => {
|
|
55
|
+
* const user$ = from(userPool, "user-1");
|
|
56
|
+
* return read(user$);
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example Async values
|
|
61
|
+
* ```ts
|
|
62
|
+
* const dataPool = pool(
|
|
63
|
+
* (id: string) => fetchData(id), // Returns Promise
|
|
64
|
+
* { gcTime: 300_000 }
|
|
65
|
+
* );
|
|
66
|
+
*
|
|
67
|
+
* // GC is paused while Promise is pending
|
|
68
|
+
* // After resolve/reject, GC timer starts
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function pool<T, P = unknown>(init: (() => T) | ((params: P, context: AtomContext) => T), options: PoolOptions<P>): Pool<P, T>;
|
|
72
|
+
/**
|
|
73
|
+
* Type guard to check if a value is a Pool.
|
|
74
|
+
*
|
|
75
|
+
* @param value - The value to check
|
|
76
|
+
* @returns true if value is a Pool instance
|
|
77
|
+
*/
|
|
78
|
+
export declare function isPool<P = unknown, T = unknown>(value: unknown): value is Pool<P, T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/core/select.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Atom, AtomValue, KeyedResult, Pipeable, SelectStateResult, SettledResult } from './types';
|
|
1
|
+
import { Atom, AtomValue, KeyedResult, Pipeable, Pool, ScopedAtom, SelectStateResult, SettledResult } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Result of a select computation.
|
|
4
4
|
*
|
|
@@ -11,14 +11,36 @@ export interface SelectResult<T> {
|
|
|
11
11
|
error: unknown;
|
|
12
12
|
/** Promise thrown during computation - indicates loading state */
|
|
13
13
|
promise: PromiseLike<unknown> | undefined;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Set of atoms that were accessed during computation.
|
|
16
|
+
* @deprecated Use _atomDeps instead. Kept for backward compatibility.
|
|
17
|
+
*/
|
|
15
18
|
dependencies: Set<Atom<unknown>>;
|
|
19
|
+
/**
|
|
20
|
+
* Set of atoms that were accessed during computation.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
_atomDeps: Set<Atom<unknown>>;
|
|
24
|
+
/**
|
|
25
|
+
* Map of pools to their accessed params.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
_poolDeps: Map<Pool<any, any>, Set<any>>;
|
|
16
29
|
}
|
|
17
30
|
/**
|
|
18
31
|
* Result type for safe() - error-first tuple.
|
|
19
32
|
* Either [undefined, T] for success or [unknown, undefined] for error.
|
|
20
33
|
*/
|
|
21
34
|
export type SafeResult<T> = [error: undefined, result: T] | [error: unknown, result: undefined];
|
|
35
|
+
/**
|
|
36
|
+
* Condition input type for and()/or() operators.
|
|
37
|
+
*
|
|
38
|
+
* Supports three forms:
|
|
39
|
+
* - `boolean` - Static value, no subscription
|
|
40
|
+
* - `Atom<unknown>` - Always read and subscribed
|
|
41
|
+
* - `() => boolean | Atom<unknown>` - Lazy evaluation, only called if needed
|
|
42
|
+
*/
|
|
43
|
+
export type Condition = boolean | Atom<unknown> | (() => boolean | Atom<unknown>);
|
|
22
44
|
/**
|
|
23
45
|
* Context object passed to selector functions.
|
|
24
46
|
* Provides utilities for reading atoms and handling async operations.
|
|
@@ -37,6 +59,33 @@ export interface SelectContext extends Pipeable {
|
|
|
37
59
|
* @returns The atom's current value (Awaited<T>)
|
|
38
60
|
*/
|
|
39
61
|
read<T>(atom: Atom<T>): Awaited<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Get a ScopedAtom from a pool for the given params.
|
|
64
|
+
* The ScopedAtom is only valid during this select() execution.
|
|
65
|
+
*
|
|
66
|
+
* When the pool entry is removed (GC or manual), the computation
|
|
67
|
+
* will automatically re-run to get the new atom.
|
|
68
|
+
*
|
|
69
|
+
* @param pool - The pool to get atom from
|
|
70
|
+
* @param params - The params to look up
|
|
71
|
+
* @returns A ScopedAtom wrapping the pool entry's atom
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* derived(({ read, from }) => {
|
|
76
|
+
* const user$ = from(userPool, "user-1");
|
|
77
|
+
* return read(user$);
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
from<P, T>(pool: Pool<P, T>, params: P): ScopedAtom<T>;
|
|
82
|
+
/**
|
|
83
|
+
* Track an atom as a dependency without reading its value.
|
|
84
|
+
* Useful when you need to subscribe to changes but already have the value.
|
|
85
|
+
*
|
|
86
|
+
* @param atom - The atom to track (can be ScopedAtom)
|
|
87
|
+
*/
|
|
88
|
+
track(atom: Atom<unknown>): void;
|
|
40
89
|
/**
|
|
41
90
|
* Wait for all atoms to resolve (like Promise.all).
|
|
42
91
|
* Array-based - pass atoms as an array.
|
|
@@ -205,11 +254,195 @@ export interface SelectContext extends Pipeable {
|
|
|
205
254
|
* ```
|
|
206
255
|
*/
|
|
207
256
|
state<T>(selector: () => T): SelectStateResult<T>;
|
|
257
|
+
/**
|
|
258
|
+
* Logical AND - returns true if ALL conditions are truthy.
|
|
259
|
+
* Short-circuits on first falsy value (lazy conditions after that are not evaluated).
|
|
260
|
+
*
|
|
261
|
+
* ## Condition Types
|
|
262
|
+
*
|
|
263
|
+
* - `boolean` - Static value, no subscription
|
|
264
|
+
* - `Atom<T>` - Always read and subscribed
|
|
265
|
+
* - `() => boolean | Atom<T>` - Lazy, only called if previous conditions are truthy
|
|
266
|
+
*
|
|
267
|
+
* ## Evaluation Flow
|
|
268
|
+
*
|
|
269
|
+
* ```
|
|
270
|
+
* and([a, b, () => c, () => d])
|
|
271
|
+
* │
|
|
272
|
+
* ▼
|
|
273
|
+
* a truthy? ─No──→ return false
|
|
274
|
+
* │Yes
|
|
275
|
+
* ▼
|
|
276
|
+
* b truthy? ─No──→ return false
|
|
277
|
+
* │Yes
|
|
278
|
+
* ▼
|
|
279
|
+
* call () => c
|
|
280
|
+
* c truthy? ─No──→ return false
|
|
281
|
+
* │Yes
|
|
282
|
+
* ▼
|
|
283
|
+
* call () => d
|
|
284
|
+
* d truthy? ─No──→ return false
|
|
285
|
+
* │Yes
|
|
286
|
+
* ▼
|
|
287
|
+
* return true
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* @param conditions - Array of conditions (booleans, atoms, or lazy functions)
|
|
291
|
+
* @returns true if all conditions are truthy, false otherwise
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* // Simple: all atoms must be truthy
|
|
296
|
+
* const canAccess = and([isLoggedIn$, hasPermission$]);
|
|
297
|
+
*
|
|
298
|
+
* // With static value
|
|
299
|
+
* const canEdit = and([FEATURE_ENABLED, isLoggedIn$, hasEditRole$]);
|
|
300
|
+
*
|
|
301
|
+
* // With lazy evaluation (only check permission if logged in)
|
|
302
|
+
* const canDelete = and([
|
|
303
|
+
* isLoggedIn$,
|
|
304
|
+
* () => hasDeletePermission$, // Only read if logged in
|
|
305
|
+
* ]);
|
|
306
|
+
*
|
|
307
|
+
* // Nested: (A && B) || C
|
|
308
|
+
* const result = or([and([a$, b$]), c$]);
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
and(conditions: Condition[]): boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Logical OR - returns true if ANY condition is truthy.
|
|
314
|
+
* Short-circuits on first truthy value (lazy conditions after that are not evaluated).
|
|
315
|
+
*
|
|
316
|
+
* ## Condition Types
|
|
317
|
+
*
|
|
318
|
+
* - `boolean` - Static value, no subscription
|
|
319
|
+
* - `Atom<T>` - Always read and subscribed
|
|
320
|
+
* - `() => boolean | Atom<T>` - Lazy, only called if previous conditions are falsy
|
|
321
|
+
*
|
|
322
|
+
* ## Evaluation Flow
|
|
323
|
+
*
|
|
324
|
+
* ```
|
|
325
|
+
* or([a, b, () => c, () => d])
|
|
326
|
+
* │
|
|
327
|
+
* ▼
|
|
328
|
+
* a truthy? ─Yes─→ return true
|
|
329
|
+
* │No
|
|
330
|
+
* ▼
|
|
331
|
+
* b truthy? ─Yes─→ return true
|
|
332
|
+
* │No
|
|
333
|
+
* ▼
|
|
334
|
+
* call () => c
|
|
335
|
+
* c truthy? ─Yes─→ return true
|
|
336
|
+
* │No
|
|
337
|
+
* ▼
|
|
338
|
+
* call () => d
|
|
339
|
+
* d truthy? ─Yes─→ return true
|
|
340
|
+
* │No
|
|
341
|
+
* ▼
|
|
342
|
+
* return false
|
|
343
|
+
* ```
|
|
344
|
+
*
|
|
345
|
+
* @param conditions - Array of conditions (booleans, atoms, or lazy functions)
|
|
346
|
+
* @returns true if any condition is truthy, false otherwise
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* // Simple: any atom truthy
|
|
351
|
+
* const hasData = or([cacheData$, apiData$]);
|
|
352
|
+
*
|
|
353
|
+
* // With lazy fallback chain
|
|
354
|
+
* const data = or([
|
|
355
|
+
* () => primaryData$, // Try primary first
|
|
356
|
+
* () => fallbackData$, // Only if primary is falsy
|
|
357
|
+
* ]);
|
|
358
|
+
*
|
|
359
|
+
* // Nested: A || (B && C)
|
|
360
|
+
* const result = or([a$, and([b$, c$])]);
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
or(conditions: Condition[]): boolean;
|
|
364
|
+
/**
|
|
365
|
+
* Read atoms or execute functions without tracking dependencies.
|
|
366
|
+
* Useful when you need to read a value but don't want to re-compute when it changes.
|
|
367
|
+
*
|
|
368
|
+
* Supports two forms:
|
|
369
|
+
* - `untrack(atom$)` - Read atom value without tracking
|
|
370
|
+
* - `untrack(() => expr)` - Execute function without tracking any reads inside
|
|
371
|
+
*
|
|
372
|
+
* ## Flow Diagram
|
|
373
|
+
*
|
|
374
|
+
* ```
|
|
375
|
+
* untrack(input)
|
|
376
|
+
* │
|
|
377
|
+
* ▼
|
|
378
|
+
* Is input an Atom?
|
|
379
|
+
* │
|
|
380
|
+
* ┌───┴───┐
|
|
381
|
+
* │Yes │No (function)
|
|
382
|
+
* ▼ ▼
|
|
383
|
+
* Read Set isUntracking = true
|
|
384
|
+
* without │
|
|
385
|
+
* tracking ▼
|
|
386
|
+
* │ Execute fn()
|
|
387
|
+
* │ │
|
|
388
|
+
* │ ▼
|
|
389
|
+
* │ Set isUntracking = false
|
|
390
|
+
* │ │
|
|
391
|
+
* └───────┴──→ Return value
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @param atom - Atom to read without tracking
|
|
395
|
+
* @returns The atom's current value
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* const combined$ = derived(({ read, untrack }) => {
|
|
400
|
+
* const count = read(count$); // Tracks count$
|
|
401
|
+
* const doubled = untrack(doubled$); // Does NOT track doubled$
|
|
402
|
+
* return count + doubled;
|
|
403
|
+
* });
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
untrack<T>(atom: Atom<T>): Awaited<T>;
|
|
407
|
+
/**
|
|
408
|
+
* Execute a function without tracking any atom reads inside.
|
|
409
|
+
*
|
|
410
|
+
* @param fn - Function to execute without tracking
|
|
411
|
+
* @returns The function's return value
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```ts
|
|
415
|
+
* const combined$ = derived(({ read, untrack }) => {
|
|
416
|
+
* const count = read(count$); // Tracks count$
|
|
417
|
+
* const tripled = untrack(() => {
|
|
418
|
+
* // None of these reads are tracked
|
|
419
|
+
* return read(a$) + read(b$) + read(c$);
|
|
420
|
+
* });
|
|
421
|
+
* return count + tripled;
|
|
422
|
+
* });
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
untrack<T>(fn: () => T): T;
|
|
208
426
|
}
|
|
209
427
|
/**
|
|
210
428
|
* Selector function type for context-based API.
|
|
211
429
|
*/
|
|
212
430
|
export type ReactiveSelector<T, C extends SelectContext = SelectContext> = (context: C) => T;
|
|
431
|
+
/**
|
|
432
|
+
* Output of select() - includes result and startTracking function.
|
|
433
|
+
*/
|
|
434
|
+
export interface SelectOutput<T> {
|
|
435
|
+
/** The computation result */
|
|
436
|
+
result: SelectResult<T>;
|
|
437
|
+
/**
|
|
438
|
+
* Start tracking dependencies and subscribe to changes.
|
|
439
|
+
* Call this after computation to set up subscriptions.
|
|
440
|
+
*
|
|
441
|
+
* @param onNotify - Callback to invoke when any dependency changes
|
|
442
|
+
* @returns Cleanup function to unsubscribe all
|
|
443
|
+
*/
|
|
444
|
+
startTracking(onNotify: VoidFunction): VoidFunction;
|
|
445
|
+
}
|
|
213
446
|
/**
|
|
214
447
|
* Custom error for when all atoms in `any()` are rejected.
|
|
215
448
|
*/
|
|
@@ -217,114 +450,73 @@ export declare class AllAtomsRejectedError extends Error {
|
|
|
217
450
|
readonly errors: unknown[];
|
|
218
451
|
constructor(errors: unknown[], message?: string);
|
|
219
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Type guard to check if a value is a ScopedAtom.
|
|
455
|
+
*/
|
|
456
|
+
export declare function isScopedAtom<T = unknown>(value: unknown): value is ScopedAtom<T>;
|
|
457
|
+
/**
|
|
458
|
+
* Type guard to check if a value is a Pool.
|
|
459
|
+
*/
|
|
460
|
+
export declare function isPool<P = unknown, T = unknown>(value: unknown): value is Pool<P, T>;
|
|
220
461
|
/**
|
|
221
462
|
* Selects/computes a value from atom(s) with dependency tracking.
|
|
222
463
|
*
|
|
223
464
|
* This is the core computation logic used by `derived()`. It:
|
|
224
|
-
* 1. Creates a context with `read`, `all`, `any`, `race`, `settled`, `safe` utilities
|
|
225
|
-
* 2. Tracks which atoms are accessed during computation
|
|
226
|
-
* 3. Returns a result with value/error/promise and
|
|
465
|
+
* 1. Creates a context with `read`, `from`, `all`, `any`, `race`, `settled`, `safe` utilities
|
|
466
|
+
* 2. Tracks which atoms and pools are accessed during computation
|
|
467
|
+
* 3. Returns a result with value/error/promise and a startTracking function
|
|
227
468
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* ## IMPORTANT: Selector Must Return Synchronous Value
|
|
231
|
-
*
|
|
232
|
-
* **The selector function MUST NOT return a Promise or PromiseLike value.**
|
|
233
|
-
*
|
|
234
|
-
* If your selector returns a Promise, it will throw an error. This is because:
|
|
235
|
-
* - `select()` is designed for synchronous derivation from atoms
|
|
236
|
-
* - Async atoms should be created using `atom(Promise)` directly
|
|
237
|
-
* - Use `read()` to read async atoms - it handles Suspense-style loading
|
|
469
|
+
* ## New API
|
|
238
470
|
*
|
|
239
471
|
* ```ts
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* // ✅ CORRECT - Create async atom and read with read()
|
|
244
|
-
* const data$ = atom(fetch('/api/data').then(r => r.json()));
|
|
245
|
-
* select(({ read }) => read(data$)); // Suspends until resolved
|
|
472
|
+
* const { result, startTracking } = select(fn, prevResult);
|
|
473
|
+
* const cleanup = startTracking(onNotify);
|
|
246
474
|
* ```
|
|
247
475
|
*
|
|
248
|
-
*
|
|
476
|
+
* The `startTracking` function sets up subscriptions to all dependencies
|
|
477
|
+
* (atoms and pool entries) and returns a cleanup function.
|
|
249
478
|
*
|
|
250
|
-
*
|
|
251
|
-
* Promises when atoms are loading (Suspense pattern). A try/catch will catch
|
|
252
|
-
* these Promises and break the Suspense mechanism.
|
|
479
|
+
* ## Pool Support via from()
|
|
253
480
|
*
|
|
481
|
+
* Use `from(pool, params)` to get a ScopedAtom from a pool:
|
|
254
482
|
* ```ts
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* return read(asyncAtom$);
|
|
259
|
-
* } catch (e) {
|
|
260
|
-
* return 'fallback'; // This catches BOTH errors AND loading promises!
|
|
261
|
-
* }
|
|
262
|
-
* });
|
|
263
|
-
*
|
|
264
|
-
* // ✅ CORRECT - Use safe() to catch errors but preserve Suspense
|
|
265
|
-
* select(({ read, safe }) => {
|
|
266
|
-
* const [err, data] = safe(() => {
|
|
267
|
-
* const raw = read(asyncAtom$); // Can throw Promise (Suspense)
|
|
268
|
-
* return JSON.parse(raw); // Can throw Error
|
|
269
|
-
* });
|
|
270
|
-
*
|
|
271
|
-
* if (err) return { error: err.message };
|
|
272
|
-
* return { data };
|
|
483
|
+
* const { result } = select(({ read, from }) => {
|
|
484
|
+
* const user$ = from(userPool, "user-1");
|
|
485
|
+
* return read(user$);
|
|
273
486
|
* });
|
|
274
487
|
* ```
|
|
275
488
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* - **Re-throws Promises** to preserve Suspense behavior
|
|
279
|
-
* - Returns `[undefined, result]` on success
|
|
280
|
-
*
|
|
281
|
-
* ## IMPORTANT: SelectContext Methods Are Synchronous Only
|
|
489
|
+
* ScopedAtoms are automatically disposed after select() completes,
|
|
490
|
+
* preventing memory leaks from stale atom references.
|
|
282
491
|
*
|
|
283
|
-
*
|
|
284
|
-
* called synchronously during selector execution.** They cannot be used in async
|
|
285
|
-
* callbacks like `setTimeout`, `Promise.then`, or event handlers.
|
|
492
|
+
* ## IMPORTANT: Selector Must Return Synchronous Value
|
|
286
493
|
*
|
|
287
|
-
*
|
|
288
|
-
* // ❌ WRONG - Calling read() in async callback
|
|
289
|
-
* select(({ read }) => {
|
|
290
|
-
* setTimeout(() => {
|
|
291
|
-
* read(atom$); // Error: called outside selection context
|
|
292
|
-
* }, 100);
|
|
293
|
-
* return 'value';
|
|
294
|
-
* });
|
|
494
|
+
* **The selector function MUST NOT return a Promise or PromiseLike value.**
|
|
295
495
|
*
|
|
296
|
-
*
|
|
297
|
-
* let savedRead;
|
|
298
|
-
* select(({ read }) => {
|
|
299
|
-
* savedRead = read; // Don't do this!
|
|
300
|
-
* return read(atom$);
|
|
301
|
-
* });
|
|
302
|
-
* savedRead(atom$); // Error: called outside selection context
|
|
496
|
+
* ## IMPORTANT: Do NOT Use try/catch - Use safe() Instead
|
|
303
497
|
*
|
|
304
|
-
*
|
|
305
|
-
* effect(({ read }) => {
|
|
306
|
-
* const config = read(config$);
|
|
307
|
-
* setTimeout(async () => {
|
|
308
|
-
* // Use atom.get() for async access
|
|
309
|
-
* const data = await asyncAtom$.get();
|
|
310
|
-
* console.log(data);
|
|
311
|
-
* }, 100);
|
|
312
|
-
* });
|
|
313
|
-
* ```
|
|
498
|
+
* **Never wrap `read()` calls in try/catch blocks.** Use `safe()` instead.
|
|
314
499
|
*
|
|
315
500
|
* @template T - The type of the computed value
|
|
316
501
|
* @param fn - Context-based selector function (must return sync value)
|
|
317
|
-
* @
|
|
318
|
-
* @
|
|
319
|
-
* @throws Error if context methods are called outside selection context
|
|
502
|
+
* @param prevResult - Previous result for diff-based subscription updates
|
|
503
|
+
* @returns SelectOutput with result and startTracking function
|
|
320
504
|
*
|
|
321
505
|
* @example
|
|
322
506
|
* ```ts
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
507
|
+
* // Basic usage
|
|
508
|
+
* const { result, startTracking } = select(({ read }) => {
|
|
509
|
+
* return read(count$) * 2;
|
|
510
|
+
* }, null);
|
|
511
|
+
*
|
|
512
|
+
* // With pool
|
|
513
|
+
* const { result, startTracking } = select(({ read, from }) => {
|
|
514
|
+
* const user$ = from(userPool, userId);
|
|
515
|
+
* return read(user$);
|
|
516
|
+
* }, prevResult);
|
|
517
|
+
*
|
|
518
|
+
* // Start tracking dependencies
|
|
519
|
+
* const cleanup = startTracking(() => recompute());
|
|
328
520
|
* ```
|
|
329
521
|
*/
|
|
330
|
-
export declare function select<T>(fn: ReactiveSelector<T>):
|
|
522
|
+
export declare function select<T>(fn: ReactiveSelector<T>, _prevResult?: SelectResult<T> | null): SelectOutput<T>;
|