atomirx 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/dist/core/types.d.ts
CHANGED
|
@@ -12,6 +12,14 @@ export declare const SYMBOL_ATOM: unique symbol;
|
|
|
12
12
|
* Symbol to identify derived atoms.
|
|
13
13
|
*/
|
|
14
14
|
export declare const SYMBOL_DERIVED: unique symbol;
|
|
15
|
+
/**
|
|
16
|
+
* Symbol to identify scoped atoms (temporary wrappers for pool atoms).
|
|
17
|
+
*/
|
|
18
|
+
export declare const SYMBOL_SCOPED: unique symbol;
|
|
19
|
+
/**
|
|
20
|
+
* Symbol to identify pool instances.
|
|
21
|
+
*/
|
|
22
|
+
export declare const SYMBOL_POOL: unique symbol;
|
|
15
23
|
/**
|
|
16
24
|
* Interface for objects that support the `.use()` plugin pattern.
|
|
17
25
|
*
|
|
@@ -33,6 +41,7 @@ export interface Pipeable {
|
|
|
33
41
|
use<TNew = void>(plugin: (source: this) => TNew): void extends TNew ? this : TNew extends object ? TNew extends {
|
|
34
42
|
use: any;
|
|
35
43
|
} ? TNew : Pipeable & TNew : TNew;
|
|
44
|
+
use<TPlugin extends object>(plugin: TPlugin): TPlugin extends any[] ? this : this & TPlugin;
|
|
36
45
|
}
|
|
37
46
|
/**
|
|
38
47
|
* Optional metadata for atoms.
|
|
@@ -113,6 +122,11 @@ export interface MutableAtom<T> extends Atom<T>, Pipeable {
|
|
|
113
122
|
* ```
|
|
114
123
|
*/
|
|
115
124
|
dirty(): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Dispose the atom, aborting any pending operations and running cleanup functions.
|
|
127
|
+
* @internal - Used by pool when removing entries.
|
|
128
|
+
*/
|
|
129
|
+
_dispose(): void;
|
|
116
130
|
}
|
|
117
131
|
/**
|
|
118
132
|
* A derived (computed) atom that always returns Promise<T> for its value.
|
|
@@ -154,6 +168,11 @@ export interface DerivedAtom<T, F extends boolean = false> extends Atom<Promise<
|
|
|
154
168
|
* - With fallback: T (guaranteed)
|
|
155
169
|
*/
|
|
156
170
|
readonly staleValue: F extends true ? T : T | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* Dispose the derived atom, cleaning up all subscriptions.
|
|
173
|
+
* @internal - Reserved for future use.
|
|
174
|
+
*/
|
|
175
|
+
_dispose(): void;
|
|
157
176
|
}
|
|
158
177
|
/**
|
|
159
178
|
* Union type for any atom (mutable or derived).
|
|
@@ -319,9 +338,9 @@ export interface EffectOptions {
|
|
|
319
338
|
onError?: (error: unknown) => void;
|
|
320
339
|
}
|
|
321
340
|
export interface AtomirxMeta {
|
|
341
|
+
key?: string;
|
|
322
342
|
}
|
|
323
343
|
export interface EffectMeta extends AtomirxMeta {
|
|
324
|
-
key?: string;
|
|
325
344
|
}
|
|
326
345
|
/**
|
|
327
346
|
* A function that returns a value when called.
|
|
@@ -362,3 +381,216 @@ export interface ModuleMeta {
|
|
|
362
381
|
}
|
|
363
382
|
export type Listener<T> = (value: T) => void;
|
|
364
383
|
export type SingleOrMultipleListeners<T> = Listener<T> | Listener<T>[];
|
|
384
|
+
/**
|
|
385
|
+
* Event emitted by pool's `on()` method.
|
|
386
|
+
*
|
|
387
|
+
* @template P - The type of params used to index entries
|
|
388
|
+
* @template T - The type of value stored in each entry
|
|
389
|
+
*/
|
|
390
|
+
export type PoolEvent<P, T> = {
|
|
391
|
+
type: "create";
|
|
392
|
+
params: P;
|
|
393
|
+
value: T;
|
|
394
|
+
} | {
|
|
395
|
+
type: "change";
|
|
396
|
+
params: P;
|
|
397
|
+
value: T;
|
|
398
|
+
} | {
|
|
399
|
+
type: "remove";
|
|
400
|
+
params: P;
|
|
401
|
+
value: T;
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* A scoped atom is a temporary wrapper around a real atom from a pool.
|
|
405
|
+
* It is only valid during a select() context and throws if accessed outside.
|
|
406
|
+
*
|
|
407
|
+
* ScopedAtoms prevent memory leaks by ensuring pool atom references
|
|
408
|
+
* cannot be stored and used after the computation completes.
|
|
409
|
+
*
|
|
410
|
+
* @template T - The type of value stored in the underlying atom
|
|
411
|
+
*/
|
|
412
|
+
export interface ScopedAtom<T> extends Atom<T> {
|
|
413
|
+
/** Symbol marker to identify scoped atom instances */
|
|
414
|
+
readonly [SYMBOL_SCOPED]: true;
|
|
415
|
+
/**
|
|
416
|
+
* Get the underlying real atom.
|
|
417
|
+
* @internal
|
|
418
|
+
* @throws Error if called outside select() context
|
|
419
|
+
*/
|
|
420
|
+
_getAtom(): Atom<T>;
|
|
421
|
+
/**
|
|
422
|
+
* Mark this scoped atom as disposed.
|
|
423
|
+
* After disposal, any method call will throw.
|
|
424
|
+
* @internal
|
|
425
|
+
*/
|
|
426
|
+
_dispose(): void;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Configuration options for creating a pool.
|
|
430
|
+
*
|
|
431
|
+
* @template P - The type of params used to index pool entries (must be object)
|
|
432
|
+
*/
|
|
433
|
+
export interface PoolOptions<P> {
|
|
434
|
+
/**
|
|
435
|
+
* Time in milliseconds before an unused entry is garbage collected.
|
|
436
|
+
* The GC timer resets on:
|
|
437
|
+
* - Entry creation
|
|
438
|
+
* - Value change
|
|
439
|
+
* - Access (get/set)
|
|
440
|
+
*
|
|
441
|
+
* GC is paused while the entry's value is a pending Promise.
|
|
442
|
+
*/
|
|
443
|
+
gcTime: number;
|
|
444
|
+
/**
|
|
445
|
+
* Equality strategy for params comparison (default: "shallow").
|
|
446
|
+
* Used to determine if two params objects refer to the same cache entry.
|
|
447
|
+
*
|
|
448
|
+
* With default "shallow" equality:
|
|
449
|
+
* - `{ a: 1, b: 2 }` and `{ b: 2, a: 1 }` are considered equal (same entry)
|
|
450
|
+
* - Property order doesn't matter
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```ts
|
|
454
|
+
* // Default shallow equality
|
|
455
|
+
* const userPool = pool((params: { id: string }) => fetchUser(params.id), {
|
|
456
|
+
* gcTime: 60_000,
|
|
457
|
+
* });
|
|
458
|
+
* userPool.get({ id: "1" });
|
|
459
|
+
* userPool.get({ id: "1" }); // Same entry (shallow equal)
|
|
460
|
+
*
|
|
461
|
+
* // Custom equality
|
|
462
|
+
* const pool = pool((params: { a: number; b: number }) => params.a + params.b, {
|
|
463
|
+
* gcTime: 60_000,
|
|
464
|
+
* equals: (a, b) => a.a === b.a && a.b === b.b,
|
|
465
|
+
* });
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
equals?: Equality<P>;
|
|
469
|
+
/**
|
|
470
|
+
* Optional metadata for the pool.
|
|
471
|
+
*/
|
|
472
|
+
meta?: PoolMeta;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Metadata for pool instances.
|
|
476
|
+
*/
|
|
477
|
+
export interface PoolMeta extends AtomirxMeta {
|
|
478
|
+
key?: string;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* A pool is a collection of atoms indexed by params.
|
|
482
|
+
* Similar to atomFamily in Jotai/Recoil, but with automatic GC
|
|
483
|
+
* and ScopedAtom pattern to prevent memory leaks.
|
|
484
|
+
*
|
|
485
|
+
* @template P - The type of params used to index entries
|
|
486
|
+
* @template T - The type of value stored in each entry
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* // Create a pool
|
|
491
|
+
* const userPool = pool(
|
|
492
|
+
* (id: string) => fetchUser(id),
|
|
493
|
+
* { gcTime: 60_000 }
|
|
494
|
+
* );
|
|
495
|
+
*
|
|
496
|
+
* // Public API (value-based)
|
|
497
|
+
* userPool.get("user-1"); // T | undefined
|
|
498
|
+
* userPool.set("user-1", newUser);
|
|
499
|
+
* userPool.remove("user-1");
|
|
500
|
+
*
|
|
501
|
+
* // In reactive context
|
|
502
|
+
* derived(({ read, from }) => {
|
|
503
|
+
* const user$ = from(userPool, "user-1");
|
|
504
|
+
* return read(user$);
|
|
505
|
+
* });
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
export interface Pool<P, T> {
|
|
509
|
+
/** Symbol marker to identify pool instances */
|
|
510
|
+
readonly [SYMBOL_POOL]: true;
|
|
511
|
+
/** Optional metadata for the pool */
|
|
512
|
+
readonly meta?: PoolMeta;
|
|
513
|
+
/**
|
|
514
|
+
* Get the current value for params.
|
|
515
|
+
* Creates entry if it doesn't exist.
|
|
516
|
+
* Extends GC timer on access.
|
|
517
|
+
*
|
|
518
|
+
* @param params - The params to look up
|
|
519
|
+
* @returns The current value
|
|
520
|
+
*/
|
|
521
|
+
get(params: P): T;
|
|
522
|
+
/**
|
|
523
|
+
* Set the value for params.
|
|
524
|
+
* Creates entry if it doesn't exist.
|
|
525
|
+
* Extends GC timer on access.
|
|
526
|
+
*
|
|
527
|
+
* @param params - The params to set
|
|
528
|
+
* @param value - The new value or reducer function
|
|
529
|
+
*/
|
|
530
|
+
set(params: P, value: T | ((prev: T) => T)): void;
|
|
531
|
+
/**
|
|
532
|
+
* Check if an entry exists for params.
|
|
533
|
+
*
|
|
534
|
+
* @param params - The params to check
|
|
535
|
+
* @returns true if entry exists
|
|
536
|
+
*/
|
|
537
|
+
has(params: P): boolean;
|
|
538
|
+
/**
|
|
539
|
+
* Remove an entry from the pool.
|
|
540
|
+
* Triggers onRemove listeners.
|
|
541
|
+
*
|
|
542
|
+
* @param params - The params to remove
|
|
543
|
+
*/
|
|
544
|
+
remove(params: P): void;
|
|
545
|
+
/**
|
|
546
|
+
* Remove all entries from the pool.
|
|
547
|
+
* Triggers onRemove listeners for each entry.
|
|
548
|
+
*/
|
|
549
|
+
clear(): void;
|
|
550
|
+
size(): number;
|
|
551
|
+
/**
|
|
552
|
+
* Iterate over all entries in the pool.
|
|
553
|
+
*
|
|
554
|
+
* @param callback - Called for each entry with (value, params)
|
|
555
|
+
*/
|
|
556
|
+
forEach(callback: (value: T, params: P) => void): void;
|
|
557
|
+
/**
|
|
558
|
+
* Subscribe to pool events.
|
|
559
|
+
*
|
|
560
|
+
* Event types:
|
|
561
|
+
* - `create` - New entry created
|
|
562
|
+
* - `change` - Existing entry value changed
|
|
563
|
+
* - `remove` - Entry removed (manual or GC)
|
|
564
|
+
*
|
|
565
|
+
* @param listener - Called with event object containing type, params, and value
|
|
566
|
+
* @returns Unsubscribe function
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* const unsub = userPool.on((event) => {
|
|
571
|
+
* switch (event.type) {
|
|
572
|
+
* case "create":
|
|
573
|
+
* console.log("Created:", event.params, event.value);
|
|
574
|
+
* break;
|
|
575
|
+
* case "change":
|
|
576
|
+
* console.log("Changed:", event.params, event.value);
|
|
577
|
+
* break;
|
|
578
|
+
* case "remove":
|
|
579
|
+
* console.log("Removed:", event.params, event.value);
|
|
580
|
+
* break;
|
|
581
|
+
* }
|
|
582
|
+
* });
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
on(listener: (event: PoolEvent<P, T>) => void): VoidFunction;
|
|
586
|
+
/**
|
|
587
|
+
* Get the underlying atom for params.
|
|
588
|
+
* @internal - Use `from(pool, params)` in SelectContext instead.
|
|
589
|
+
*/
|
|
590
|
+
_getAtom(params: P): MutableAtom<T>;
|
|
591
|
+
/**
|
|
592
|
+
* Subscribe to removal of a specific entry.
|
|
593
|
+
* @internal - Used by SelectContext for automatic recomputation.
|
|
594
|
+
*/
|
|
595
|
+
_onRemove(params: P, listener: VoidFunction): VoidFunction;
|
|
596
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SelectContext } from './select';
|
|
2
|
+
/**
|
|
3
|
+
* Extended context providing abort capabilities.
|
|
4
|
+
*
|
|
5
|
+
* Added to the select context when using `withAbort()` extension.
|
|
6
|
+
*/
|
|
7
|
+
export interface WithAbortContext {
|
|
8
|
+
/**
|
|
9
|
+
* AbortSignal that is automatically aborted when the effect/derived
|
|
10
|
+
* re-runs or is disposed. Use this to cancel async operations.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* effect(({ read, onCleanup }) => {
|
|
15
|
+
* const ctx = context.use(withAbort(onCleanup));
|
|
16
|
+
* fetch('/api/data', { signal: ctx.signal });
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
readonly signal: AbortSignal;
|
|
21
|
+
/**
|
|
22
|
+
* Manually trigger abort. Safe to call multiple times - subsequent
|
|
23
|
+
* calls after the first abort are no-ops.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* effect(({ read, onCleanup }) => {
|
|
28
|
+
* const ctx = context.use(withAbort(onCleanup));
|
|
29
|
+
* if (shouldCancel) ctx.abort();
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
abort(): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Context extension that provides an AbortSignal for cancellation.
|
|
37
|
+
*
|
|
38
|
+
* Use this to cancel async operations (fetch, timers, etc.) when an
|
|
39
|
+
* effect or derived atom re-runs or is disposed. The signal is
|
|
40
|
+
* automatically aborted when the cleanup function runs.
|
|
41
|
+
*
|
|
42
|
+
* ## Usage Pattern
|
|
43
|
+
*
|
|
44
|
+
* The `withAbort` function takes an `onCleanup` registration function
|
|
45
|
+
* and returns a context extender that can be used with `.use()`:
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* effect(({ read, onCleanup }) => {
|
|
49
|
+
* const ctx = context.use(withAbort(onCleanup));
|
|
50
|
+
*
|
|
51
|
+
* // Signal is aborted when effect re-runs or disposes
|
|
52
|
+
* fetch('/api/data', { signal: ctx.signal })
|
|
53
|
+
* .then(r => r.json())
|
|
54
|
+
* .then(data => results$.set(data));
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* ## Key Features
|
|
59
|
+
*
|
|
60
|
+
* - **Automatic cleanup**: Signal aborts when `onCleanup` fires
|
|
61
|
+
* - **Manual abort**: Call `ctx.abort()` to cancel immediately
|
|
62
|
+
* - **Safe re-abort**: Multiple `abort()` calls are no-ops
|
|
63
|
+
*
|
|
64
|
+
* @param onCleanup - Cleanup registration function (from effect context)
|
|
65
|
+
* @returns Context extender function for use with `.use()`
|
|
66
|
+
*
|
|
67
|
+
* @example Basic fetch cancellation
|
|
68
|
+
* ```ts
|
|
69
|
+
* effect(({ read, onCleanup }) => {
|
|
70
|
+
* const userId = read(userId$);
|
|
71
|
+
* const ctx = context.use(withAbort(onCleanup));
|
|
72
|
+
*
|
|
73
|
+
* fetch(`/api/users/${userId}`, { signal: ctx.signal })
|
|
74
|
+
* .then(r => r.json())
|
|
75
|
+
* .then(user => user$.set(user))
|
|
76
|
+
* .catch(err => {
|
|
77
|
+
* if (err.name !== 'AbortError') throw err;
|
|
78
|
+
* });
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example With timeout
|
|
83
|
+
* ```ts
|
|
84
|
+
* effect(({ read, onCleanup }) => {
|
|
85
|
+
* const ctx = context.use(withAbort(onCleanup));
|
|
86
|
+
*
|
|
87
|
+
* // Abort after 5 seconds
|
|
88
|
+
* const timeout = setTimeout(() => ctx.abort(), 5000);
|
|
89
|
+
* onCleanup(() => clearTimeout(timeout));
|
|
90
|
+
*
|
|
91
|
+
* fetch('/api/slow', { signal: ctx.signal });
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function withAbort(onCleanup: (cleanup: VoidFunction) => void): <TContext extends SelectContext>(context: TContext) => TContext & WithAbortContext;
|
package/dist/core/withReady.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Atom } from './types';
|
|
|
4
4
|
* Extension interface that adds `ready()` method to SelectContext.
|
|
5
5
|
* Used in derived atoms and effects to wait for non-null values.
|
|
6
6
|
*/
|
|
7
|
-
export interface
|
|
7
|
+
export interface WithReadyContext {
|
|
8
8
|
/**
|
|
9
9
|
* Wait for an atom to have a non-null/non-undefined value.
|
|
10
10
|
*
|
|
@@ -109,7 +109,7 @@ export interface WithReadySelectContext {
|
|
|
109
109
|
* @example
|
|
110
110
|
* ```ts
|
|
111
111
|
* // Used internally by derived() - you don't need to call this directly
|
|
112
|
-
* const result = select((context) => fn(context.use(withReady())));
|
|
112
|
+
* const { result } = select((context) => fn(context.use(withReady())));
|
|
113
113
|
* ```
|
|
114
114
|
*/
|
|
115
|
-
export declare function withReady(): <TContext extends SelectContext>(context: TContext) => TContext &
|
|
115
|
+
export declare function withReady(): <TContext extends SelectContext>(context: TContext) => TContext & WithReadyContext;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalStorage key for devtools preferences.
|
|
3
|
+
* Global across all domains.
|
|
4
|
+
*/
|
|
5
|
+
export declare const STORAGE_KEY_PREFERENCES = "atomirx-devtools-preferences";
|
|
6
|
+
/**
|
|
7
|
+
* Maximum history entries per entity.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_MAX_HISTORY_SIZE = 50;
|
|
10
|
+
/**
|
|
11
|
+
* Prefix for generating entity IDs.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ENTITY_ID_PREFIX = "atomirx";
|
|
14
|
+
/**
|
|
15
|
+
* Version for storage schema (for future migrations).
|
|
16
|
+
*/
|
|
17
|
+
export declare const STORAGE_VERSION = 1;
|
|
18
|
+
/**
|
|
19
|
+
* Z-index for devtools panel.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEVTOOLS_Z_INDEX = 999999;
|
|
22
|
+
/**
|
|
23
|
+
* Minimum panel size in pixels.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MIN_PANEL_SIZE = 200;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum panel size in pixels.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_PANEL_SIZE = 800;
|
|
30
|
+
/**
|
|
31
|
+
* Default panel size in pixels.
|
|
32
|
+
*/
|
|
33
|
+
export declare const DEFAULT_PANEL_SIZE = 300;
|
|
34
|
+
/**
|
|
35
|
+
* Floating button size in pixels.
|
|
36
|
+
*/
|
|
37
|
+
export declare const FLOATING_BUTTON_SIZE = 48;
|
|
38
|
+
/**
|
|
39
|
+
* Animation duration in milliseconds.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ANIMATION_DURATION = 200;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var D=Object.defineProperty;var L=(r,t,o)=>t in r?D(r,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):r[t]=o;var a=(r,t,o)=>L(r,typeof t!="symbol"?t+"":t,o);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("../onErrorHook-DHBASmYw.cjs"),m=require("../onDispatchHook-C8yLzr-o.cjs");var g=typeof document<"u"?document.currentScript:null;const R="atomirx-devtools-preferences",E=50,S="atomirx",A=1,k=999999,I=200,b=800,C=300,w=48,T=200;let N=0;function f(r){return`${S}-${r}-${++N}`}class P{constructor(t=E){a(this,"_entities",new Map);a(this,"_logs",[]);a(this,"_listeners",new Set);a(this,"_maxHistorySize");a(this,"_maxLogSize",200);a(this,"_logIdCounter",0);a(this,"_version",0);a(this,"_cachedEntities",null);a(this,"_cachedLogs",null);a(this,"_notifyScheduled",!1);this._maxHistorySize=t}get entities(){return this._cachedEntities||(this._cachedEntities=new Map(this._entities)),this._cachedEntities}get logs(){return this._cachedLogs||(this._cachedLogs=[...this._logs]),this._cachedLogs}get version(){return this._version}addLog(t){const o={...t,id:`log-${++this._logIdCounter}`};this._logs=[o,...this._logs].slice(0,this._maxLogSize),this._notifyListeners()}clearLogs(){this._logs=[],this._notifyListeners()}registerMutable(t,o){const n=f("mutable"),e=Date.now(),s={id:n,type:"mutable",key:o,createdAt:e,lastUpdatedAt:e,changeCount:0,instanceRef:new WeakRef(t),subscriberCount:0,history:[]};return this._entities.set(n,s),this._notifyListeners(),this._trackMutableChanges(n,t),n}registerDerived(t,o,n=[]){const e=f("derived"),s=Date.now(),i={id:e,type:"derived",key:o,createdAt:s,lastUpdatedAt:s,changeCount:0,instanceRef:new WeakRef(t),dependencyIds:n,subscriberCount:0,history:[]};return this._entities.set(e,i),this._notifyListeners(),this._trackDerivedChanges(e,t),e}registerEffect(t,o,n=[]){const e=f("effect"),s=Date.now(),i={id:e,type:"effect",key:o,createdAt:s,lastUpdatedAt:s,changeCount:0,instanceRef:new WeakRef(t),dependencyIds:n,runCount:0,isActive:!0};return this._entities.set(e,i),this._notifyListeners(),e}registerPool(t,o,n){const e=f("pool"),s=Date.now(),i={id:e,type:"pool",key:o,createdAt:s,lastUpdatedAt:s,changeCount:0,instanceRef:new WeakRef(t),entryCount:0,gcTime:n};return this._entities.set(e,i),this._notifyListeners(),this._trackPoolChanges(e,t),e}registerModule(t,o){const n=f("module"),e=Date.now(),s={id:n,type:"module",key:o,createdAt:e,lastUpdatedAt:e,changeCount:0,instanceRef:new WeakRef(t)};return this._entities.set(n,s),this._notifyListeners(),n}_trackMutableChanges(t,o){let n=this._serializeValue(o.get());o.on(()=>{var h;const e=this._entities.get(t);if(!e)return;const s=this._serializeValue(o.get()),i=Date.now(),_={timestamp:i,previousValue:n,newValue:s};e.history=[_,...e.history].slice(0,this._maxHistorySize),e.changeCount++,e.lastUpdatedAt=i,n=s,this._notifyListeners();const u=(h=o.meta)==null?void 0:h.key;u&&this.addLog({type:"mutable.change",timestamp:i,atomKey:u})})}_trackDerivedChanges(t,o){let n=this._serializeValue(o.staleValue);o.on(()=>{var h;const e=this._entities.get(t);if(!e)return;const s=this._serializeValue(o.staleValue),i=Date.now(),_={timestamp:i,previousValue:n,newValue:s};e.history=[_,...e.history].slice(0,this._maxHistorySize),e.changeCount++,e.lastUpdatedAt=i,n=s,this._notifyListeners();const u=(h=o.meta)==null?void 0:h.key;u&&this.addLog({type:"derived.change",timestamp:i,atomKey:u})})}_trackPoolChanges(t,o){o.on(n=>{var i;const e=this._entities.get(t);if(!e)return;(n.type==="create"||n.type==="change")&&(e.changeCount++,e.lastUpdatedAt=Date.now()),this._updatePoolEntryCount(t,o),this._notifyListeners();const s=(i=o.meta)==null?void 0:i.key;if(s){const _={create:"pool.create",change:"pool.set",remove:"pool.remove"},u=this._serializeParams(n.params);this.addLog({type:_[n.type],timestamp:Date.now(),poolKey:s,params:u.slice(0,50)})}})}_serializeParams(t){try{return JSON.stringify(t)}catch{return String(t)}}_updatePoolEntryCount(t,o){const n=this._entities.get(t);n&&(n.entryCount=o.size())}_serializeValue(t){if(t===void 0)return"undefined";if(t===null)return"null";try{const o=new WeakSet;return JSON.stringify(t,(n,e)=>{if(typeof e=="object"&&e!==null){if(o.has(e))return"[Circular]";o.add(e)}return typeof e=="function"?"[Function]":typeof e=="symbol"?e.toString():e instanceof Error?`[Error: ${e.message}]`:e instanceof Promise?"[Promise]":e},2)}catch{return"[unserializable]"}}getByType(t){const o=[];for(const n of this._entities.values())n.type===t&&o.push(n);return o}get(t){return this._entities.get(t)}subscribe(t){return this._listeners.add(t),()=>{this._listeners.delete(t)}}getStats(){let t=0,o=0,n=0,e=0,s=0;for(const i of this._entities.values())switch(i.type){case"mutable":t++;break;case"derived":o++;break;case"effect":n++;break;case"pool":e++;break;case"module":s++;break}return{mutableCount:t,derivedCount:o,effectCount:n,poolCount:e,moduleCount:s,totalCount:t+o+n+e+s}}clear(){this._entities.clear(),this._notifyListeners()}_notifyListeners(){this._version++,this._cachedEntities=null,this._cachedLogs=null,!this._notifyScheduled&&(this._notifyScheduled=!0,queueMicrotask(()=>{this._notifyScheduled=!1;for(const t of this._listeners)try{t()}catch{}}))}}let l=null;function p(r){return l||(l=new P(r)),l}function O(){l==null||l.clear(),l=null}function U(){l=null}const v={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1};function M(){try{if(typeof process<"u"&&process.env)return process.env.NODE_ENV==="production"}catch{}try{if(typeof{url:typeof document>"u"?require("url").pathToFileURL(__filename).href:g&&g.tagName.toUpperCase()==="SCRIPT"&&g.src||new URL("devtools/index.cjs",document.baseURI).href}<"u"&&v)return!0}catch{}return!1}let d=!1,c=null;function z(r={}){const{maxHistorySize:t=E,enableInProduction:o=!1}=r;if(M()&&!o)return console.warn("[atomirx-devtools] Devtools disabled in production. Use { enableInProduction: true } to enable."),()=>{};if(d&&c)return c;const n=p(t);return y.onCreateHook.override(e=>s=>{switch(e==null||e(s),s.type){case"mutable":n.registerMutable(s.instance,s.key);break;case"derived":n.registerDerived(s.instance,s.key);break;case"effect":n.registerEffect(s.instance,s.key);break;case"pool":n.registerPool(s.instance,s.key,0);break;case"module":typeof s.instance=="object"&&s.instance!==null&&n.registerModule(s.instance,s.key);break}}),m.onDispatchHook.override(e=>s=>{var i;e==null||e(s),(i=s.meta)!=null&&i.key&&n.addLog({type:"action.dispatch",timestamp:Date.now(),actionKey:s.meta.key,deps:JSON.stringify(s.deps).slice(0,100)})}),y.onErrorHook.override(e=>s=>{e==null||e(s),n.addLog({type:"error",timestamp:Date.now(),sourceType:s.source.type,sourceKey:s.source.key,error:String(s.error).slice(0,200)})}),d=!0,c=()=>{y.onCreateHook.reset(),m.onDispatchHook.reset(),y.onErrorHook.reset(),n.clear(),n.clearLogs(),d=!1,c=null},c}function F(){return d?p():null}function V(){return d}function H(){c&&c(),U(),d=!1,c=null}const x={isOpen:!1,position:"bottom",activeTab:"atoms",panelSize:300,searchText:{atoms:"",effects:"",pools:"",modules:"",logs:""},atomFilter:"all",selectedEntityId:null,showAtomValues:!1};exports.ANIMATION_DURATION=T;exports.DEFAULT_MAX_HISTORY_SIZE=E;exports.DEFAULT_PANEL_SIZE=C;exports.DEFAULT_PREFERENCES=x;exports.DEVTOOLS_Z_INDEX=k;exports.ENTITY_ID_PREFIX=S;exports.FLOATING_BUTTON_SIZE=w;exports.MAX_PANEL_SIZE=b;exports.MIN_PANEL_SIZE=I;exports.STORAGE_KEY_PREFERENCES=R;exports.STORAGE_VERSION=A;exports._resetDevtools=H;exports.getDevtoolsRegistry=F;exports.getRegistry=p;exports.isDevtoolsEnabled=V;exports.resetRegistry=O;exports.setupDevtools=z;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomirx Devtools - Core API
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core devtools functionality for tracking
|
|
5
|
+
* and inspecting atomirx entities (atoms, derived, effects, pools, modules).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*
|
|
9
|
+
* @example Basic usage
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { setupDevtools, getDevtoolsRegistry } from 'atomirx/devtools';
|
|
12
|
+
*
|
|
13
|
+
* // Enable devtools (call once at app startup)
|
|
14
|
+
* setupDevtools();
|
|
15
|
+
*
|
|
16
|
+
* // Access registry for custom integrations
|
|
17
|
+
* const registry = getDevtoolsRegistry();
|
|
18
|
+
* if (registry) {
|
|
19
|
+
* registry.subscribe(() => {
|
|
20
|
+
* console.log('Entities changed:', registry.getStats());
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export { setupDevtools, getDevtoolsRegistry, isDevtoolsEnabled, _resetDevtools, } from './setup';
|
|
26
|
+
export { getRegistry, resetRegistry } from './registry';
|
|
27
|
+
export type { EntityType, EntityInfo, BaseEntityInfo, MutableEntityInfo, DerivedEntityInfo, EffectEntityInfo, PoolEntityInfo, ModuleEntityInfo, DevtoolsRegistry, DevtoolsStats, DevtoolsOptions, ChangeHistoryEntry, LogEntry, LogEntryType, ActionDispatchLogEntry, ErrorLogEntry, PoolCreateLogEntry, NewLogEntry, PanelPosition, DevtoolsTab, AtomFilter, DevtoolsPreferences, DevtoolsAtomState, } from './types';
|
|
28
|
+
export { DEFAULT_PREFERENCES } from './types';
|
|
29
|
+
export { STORAGE_KEY_PREFERENCES, DEFAULT_MAX_HISTORY_SIZE, ENTITY_ID_PREFIX, STORAGE_VERSION, DEVTOOLS_Z_INDEX, MIN_PANEL_SIZE, MAX_PANEL_SIZE, DEFAULT_PANEL_SIZE, FLOATING_BUTTON_SIZE, ANIMATION_DURATION, } from './constants';
|