@zakkster/lite-signal 1.0.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/LICENSE.txt +21 -0
- package/README.md +585 -0
- package/Signal.d.ts +167 -0
- package/Signal.js +1007 -0
- package/llms.txt +192 -0
- package/package.json +58 -0
package/Signal.d.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zakkster/lite-signal — zero-GC reactive graph.
|
|
3
|
+
*
|
|
4
|
+
* Public type surface for the JavaScript implementation in `src/index.js`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─── Options ──────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/** Equality predicate. Returning `true` halts propagation. */
|
|
10
|
+
export type EqualsFn<T> = (a: T, b: T) => boolean;
|
|
11
|
+
|
|
12
|
+
/** Options accepted by {@link signal}. */
|
|
13
|
+
export interface SignalOptions<T> {
|
|
14
|
+
/** Custom equality predicate. Default: `Object.is`. */
|
|
15
|
+
equals?: EqualsFn<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Options accepted by {@link computed}. */
|
|
19
|
+
export interface ComputedOptions<T> {
|
|
20
|
+
/** Custom equality predicate. Default: `Object.is`. */
|
|
21
|
+
equals?: EqualsFn<T>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Scheduler trampoline. The implementation must call the supplied `run` callback
|
|
26
|
+
* at most once. `requestAnimationFrame`, `queueMicrotask`, and `setTimeout`
|
|
27
|
+
* shapes are all compatible (wrap them if their signature differs).
|
|
28
|
+
*/
|
|
29
|
+
export type EffectScheduler = (run: () => void) => void;
|
|
30
|
+
|
|
31
|
+
/** Options accepted by {@link effect}. */
|
|
32
|
+
export interface EffectOptions {
|
|
33
|
+
/** Optional scheduler. If supplied, the effect's first run and every
|
|
34
|
+
* subsequent re-run is deferred through this trampoline. */
|
|
35
|
+
scheduler?: EffectScheduler;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Idempotent dispose handle returned by {@link effect} and `.subscribe()`. */
|
|
39
|
+
export type Dispose = () => void;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Anything that can be passed to {@link Registry.dispose}: a {@link Signal},
|
|
43
|
+
* a {@link Computed}, or an effect's {@link Dispose} function. Passing an
|
|
44
|
+
* unrelated value is a safe no-op.
|
|
45
|
+
*/
|
|
46
|
+
export type Disposable<T = unknown> = Signal<T> | Computed<T> | Dispose;
|
|
47
|
+
|
|
48
|
+
// ─── Reactive primitive shapes ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** Reactive source of truth. */
|
|
51
|
+
export interface Signal<T> {
|
|
52
|
+
/** Read the current value, tracking the read if inside an effect or computed. */
|
|
53
|
+
(): T;
|
|
54
|
+
/** Read the value WITHOUT tracking. */
|
|
55
|
+
peek(): T;
|
|
56
|
+
/** Overwrite the value. No-op if equal under the signal's equality predicate. */
|
|
57
|
+
set(value: T): void;
|
|
58
|
+
/** Functional update: `set(fn(currentValue))`. Reads the current value without tracking. */
|
|
59
|
+
update(fn: (current: T) => T): void;
|
|
60
|
+
/** Subscribe to value changes. Fires immediately with the current value. */
|
|
61
|
+
subscribe(fn: (value: T) => void): Dispose;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Lazy memoised derivation. */
|
|
65
|
+
export interface Computed<T> {
|
|
66
|
+
/** Resolve the value, tracking the read if inside an effect or computed. */
|
|
67
|
+
(): T;
|
|
68
|
+
/** Resolve the value WITHOUT tracking. */
|
|
69
|
+
peek(): T;
|
|
70
|
+
/** Subscribe to value changes. Fires immediately with the current value. */
|
|
71
|
+
subscribe(fn: (value: T) => void): Dispose;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export interface RegistryStats {
|
|
77
|
+
/** Number of signals created in this registry's lifetime. */
|
|
78
|
+
signals: number;
|
|
79
|
+
/** Number of computeds created in this registry's lifetime. */
|
|
80
|
+
computeds: number;
|
|
81
|
+
/** Number of effects currently alive (decrements on dispose). */
|
|
82
|
+
effects: number;
|
|
83
|
+
/** Number of dependency links currently in use. */
|
|
84
|
+
activeLinks: number;
|
|
85
|
+
/** Number of dependency links available in the pool. */
|
|
86
|
+
pooledLinks: number;
|
|
87
|
+
/** Total link-pool capacity (grows under `"grow"` policy). */
|
|
88
|
+
linkPoolCapacity: number;
|
|
89
|
+
/** Total node-pool capacity (grows under `"grow"` policy). */
|
|
90
|
+
nodePoolCapacity: number;
|
|
91
|
+
/** Number of nodes currently allocated (signals + computeds + alive effects). */
|
|
92
|
+
activeNodes: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Errors ───────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/** Thrown when a pool ceiling is hit. */
|
|
98
|
+
export class CapacityError extends Error {
|
|
99
|
+
readonly name: "CapacityError";
|
|
100
|
+
readonly kind: "nodes" | "links";
|
|
101
|
+
readonly capacity: number;
|
|
102
|
+
constructor(kind: "nodes" | "links", capacity: number);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Registry ─────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export interface RegistryConfig {
|
|
108
|
+
/** Initial node-pool capacity. Default: 1024. */
|
|
109
|
+
maxNodes?: number;
|
|
110
|
+
/** Initial link-pool capacity. Default: `maxNodes * 4`. */
|
|
111
|
+
maxLinks?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Behaviour when a pool is exhausted:
|
|
114
|
+
* - `"throw"` (default): throw {@link CapacityError} immediately.
|
|
115
|
+
* - `"grow"`: double the pool. Links are bounded by `maxLinks * 16`.
|
|
116
|
+
*/
|
|
117
|
+
onCapacityExceeded?: "throw" | "grow";
|
|
118
|
+
/** Max effect-queue drain passes before a {@link CycleError} is thrown. Default: 100. */
|
|
119
|
+
maxFlushPasses?: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Isolated reactive graph. Created by {@link createRegistry}. */
|
|
123
|
+
export interface Registry {
|
|
124
|
+
signal<T>(initial: T, opts?: SignalOptions<T>): Signal<T>;
|
|
125
|
+
computed<T>(fn: () => T, opts?: ComputedOptions<T>): Computed<T>;
|
|
126
|
+
effect(fn: () => void, opts?: EffectOptions): Dispose;
|
|
127
|
+
/**
|
|
128
|
+
* Universal disposal for anything created by this registry: signals,
|
|
129
|
+
* computeds, or effect dispose handles. Cross-registry calls are silent
|
|
130
|
+
* no-ops (each registry owns its own private node-identity Symbol).
|
|
131
|
+
* Passing an unrelated value is also a safe no-op.
|
|
132
|
+
*/
|
|
133
|
+
dispose(api: Disposable): void;
|
|
134
|
+
batch<T>(fn: () => T): T;
|
|
135
|
+
untrack<T>(fn: () => T): T;
|
|
136
|
+
onCleanup(fn: () => void): void;
|
|
137
|
+
stats(): RegistryStats;
|
|
138
|
+
/** Reset everything: nodes, links, queues, global clock. Outstanding dispose
|
|
139
|
+
* closures become safe no-ops. Outstanding read/set closures still reference
|
|
140
|
+
* pool slots — they will silently misbehave; use a fresh registry afterwards. */
|
|
141
|
+
destroy(): void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create an isolated reactive registry.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* const r = createRegistry({ maxNodes: 4096, onCapacityExceeded: "grow" });
|
|
149
|
+
* const count = r.signal(0);
|
|
150
|
+
* r.effect(() => console.log(count()));
|
|
151
|
+
*/
|
|
152
|
+
export function createRegistry(config?: RegistryConfig): Registry;
|
|
153
|
+
|
|
154
|
+
/** Replace the default registry backing the top-level helpers. */
|
|
155
|
+
export function setDefaultRegistry(registry: Registry): void;
|
|
156
|
+
|
|
157
|
+
// ─── Top-level helpers (delegate to default registry) ────────────────────────
|
|
158
|
+
|
|
159
|
+
export function signal<T>(initial: T, opts?: SignalOptions<T>): Signal<T>;
|
|
160
|
+
export function computed<T>(fn: () => T, opts?: ComputedOptions<T>): Computed<T>;
|
|
161
|
+
export function effect(fn: () => void, opts?: EffectOptions): Dispose;
|
|
162
|
+
/** Universal disposal — see {@link Registry.dispose}. */
|
|
163
|
+
export function dispose(api: Disposable): void;
|
|
164
|
+
export function batch<T>(fn: () => T): T;
|
|
165
|
+
export function untrack<T>(fn: () => T): T;
|
|
166
|
+
export function onCleanup(fn: () => void): void;
|
|
167
|
+
export function stats(): RegistryStats;
|