@zakkster/lite-signal 1.2.0 → 1.2.2
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/CHANGELOG.md +331 -68
- package/README.md +244 -155
- package/Signal.d.ts +74 -20
- package/Signal.js +191 -85
- package/llms.txt +189 -66
- package/package.json +7 -3
package/Signal.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zakkster/lite-signal
|
|
2
|
+
* @zakkster/lite-signal -- zero-GC reactive graph.
|
|
3
3
|
*
|
|
4
4
|
* Public type surface for the JavaScript implementation in `Signal.js`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// --- Options ------------------------------------------------------------------
|
|
8
8
|
|
|
9
9
|
/** Equality predicate. Returning `true` halts propagation. */
|
|
10
10
|
export type EqualsFn<T> = (a: T, b: T) => boolean;
|
|
@@ -45,7 +45,7 @@ export type Dispose = () => void;
|
|
|
45
45
|
*/
|
|
46
46
|
export type Disposable<T = unknown> = Signal<T> | Computed<T> | Dispose;
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// --- Reactive primitive shapes ------------------------------------------------
|
|
49
49
|
|
|
50
50
|
/** Reactive source of truth. */
|
|
51
51
|
export interface Signal<T> {
|
|
@@ -71,7 +71,7 @@ export interface Computed<T> {
|
|
|
71
71
|
subscribe(fn: (value: T) => void): Dispose;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// --- Diagnostics --------------------------------------------------------------
|
|
75
75
|
|
|
76
76
|
export interface RegistryStats {
|
|
77
77
|
/** Number of signals created in this registry's lifetime. */
|
|
@@ -92,7 +92,7 @@ export interface RegistryStats {
|
|
|
92
92
|
activeNodes: number;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
//
|
|
95
|
+
// --- Observer-lifecycle introspection (1.1.4) ---------------------------------
|
|
96
96
|
|
|
97
97
|
/** Whether a described node is a signal, a computed, or an effect. */
|
|
98
98
|
export type NodeKind = "signal" | "computed" | "effect";
|
|
@@ -109,9 +109,9 @@ export interface NodeDescriptor {
|
|
|
109
109
|
|
|
110
110
|
/** Transition callbacks for {@link Registry.observeObservers}. */
|
|
111
111
|
export interface ObserveObserversHooks {
|
|
112
|
-
/** Fired on the 0
|
|
112
|
+
/** Fired on the 0->1 observer transition (after registration). */
|
|
113
113
|
onConnect?: () => void;
|
|
114
|
-
/** Fired on the 1
|
|
114
|
+
/** Fired on the 1->0 observer transition. */
|
|
115
115
|
onDisconnect?: () => void;
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -121,7 +121,36 @@ export type Unobserve = () => void;
|
|
|
121
121
|
/** Anything carrying a node identity that the introspection surface can read. */
|
|
122
122
|
export type ReactiveHandle = Signal<any> | Computed<any>;
|
|
123
123
|
|
|
124
|
-
//
|
|
124
|
+
// --- Graph-mutation hook (1.2.1) ----------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Opcode passed as the first argument to a {@link GraphMutationListener}.
|
|
128
|
+
*
|
|
129
|
+
* - `1` node create. `(intA, intB) = (node.id, node.flags)`.
|
|
130
|
+
* - `2` node dispose. `(intA, intB) = (node.id, node.flags)` -- fires for every node
|
|
131
|
+
* disposed, including cascaded owner-tree children.
|
|
132
|
+
* - `3` link add. `(intA, intB) = (source.id, target.id)`.
|
|
133
|
+
* - `4` link remove. `(intA, intB) = (source.id, target.id)` -- `-1` if the link
|
|
134
|
+
* was already nulled (defensive, rare).
|
|
135
|
+
* - `5` recompute. `(intA, intB) = (node.id, 0)` -- fires just before an effect
|
|
136
|
+
* re-run or a computed re-eval.
|
|
137
|
+
*/
|
|
138
|
+
export type GraphMutationOpcode = 1 | 2 | 3 | 4 | 5;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Listener registered with {@link Registry.onGraphMutation}. Called synchronously
|
|
142
|
+
* inside each mutation point with three integers -- no objects allocated.
|
|
143
|
+
*
|
|
144
|
+
* **Contract: observe only.** Listeners MUST NOT throw and MUST NOT mutate the
|
|
145
|
+
* graph from inside the callback. Both will corrupt the engine's state. Wrap any
|
|
146
|
+
* downstream work in a microtask if it could touch the registry.
|
|
147
|
+
*/
|
|
148
|
+
export type GraphMutationListener = (opcode: GraphMutationOpcode, intA: number, intB: number) => void;
|
|
149
|
+
|
|
150
|
+
/** Idempotent unsubscriber returned by {@link Registry.onGraphMutation}. */
|
|
151
|
+
export type GraphMutationUnsubscribe = () => void;
|
|
152
|
+
|
|
153
|
+
// --- Errors -------------------------------------------------------------------
|
|
125
154
|
|
|
126
155
|
/** Thrown when a pool ceiling is hit. */
|
|
127
156
|
export class CapacityError extends Error {
|
|
@@ -131,7 +160,7 @@ export class CapacityError extends Error {
|
|
|
131
160
|
constructor(kind: "nodes" | "links", capacity: number);
|
|
132
161
|
}
|
|
133
162
|
|
|
134
|
-
//
|
|
163
|
+
// --- Registry -----------------------------------------------------------------
|
|
135
164
|
|
|
136
165
|
export interface RegistryConfig {
|
|
137
166
|
/** Initial node-pool capacity. Default: 1024. */
|
|
@@ -168,8 +197,8 @@ export interface Registry {
|
|
|
168
197
|
isTracking(): boolean;
|
|
169
198
|
/** O(1): does this source have at least one live observer right now? A `peek` does not count. */
|
|
170
199
|
hasObservers(handle: ReactiveHandle): boolean;
|
|
171
|
-
/** Auto-pause hook: fires `onConnect` on the 0
|
|
172
|
-
* on 1
|
|
200
|
+
/** Auto-pause hook: fires `onConnect` on the 0->1 observer transition and `onDisconnect`
|
|
201
|
+
* on 1->0, after registration (transition-only -- no immediate fire if already observed).
|
|
173
202
|
* Re-tracking a persistently-read source does not churn. Returns an idempotent unobserve.
|
|
174
203
|
* @throws TypeError if `handle` is not a reactive handle. */
|
|
175
204
|
observeObservers(handle: ReactiveHandle, hooks?: ObserveObserversHooks): Unobserve;
|
|
@@ -177,16 +206,35 @@ export interface Registry {
|
|
|
177
206
|
forEachObserver(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
178
207
|
/** Walk the sources (dependencies) of `handle`. No-op on a non-handle. */
|
|
179
208
|
forEachSource(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
180
|
-
/**
|
|
209
|
+
/** Walk the owned children of `handle` -- nodes whose lifetime is bound to this
|
|
210
|
+
* one by the 1.2 owner tree (1.2.1+). Top-level handles and signals have no
|
|
211
|
+
* owned children; effects/computeds may own nested observers created inside
|
|
212
|
+
* their bodies. No-op on a non-handle or stale handle. */
|
|
213
|
+
forEachOwned(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
214
|
+
/** Descriptor of `handle`'s owner, or `undefined` for top-level handles, stale
|
|
215
|
+
* handles, or non-handles (1.2.1+). The owner is the effect or computed inside
|
|
216
|
+
* whose body `handle` was created -- the node that will cascade-dispose it
|
|
217
|
+
* on re-run or explicit dispose. */
|
|
218
|
+
ownerOf(handle: ReactiveHandle): NodeDescriptor | undefined;
|
|
219
|
+
/** Stable node id of `handle` (1.1.5+), or undefined for a non-handle or stale handle. */
|
|
181
220
|
nodeId(handle: ReactiveHandle): number | undefined;
|
|
182
|
-
/** The own descriptor of `handle` (1.1.5+), or undefined for a non-handle.
|
|
183
|
-
* the returned descriptor may be passed back into forEachObserver/
|
|
221
|
+
/** The own descriptor of `handle` (1.1.5+), or undefined for a non-handle or stale handle.
|
|
222
|
+
* Re-walkable: the returned descriptor may be passed back into forEachObserver/
|
|
223
|
+
* forEachSource/forEachOwned/ownerOf. Descriptors are gen-stamped (1.2.1+): a
|
|
224
|
+
* descriptor obtained pre-recycle goes stale and walks as undefined post-recycle. */
|
|
184
225
|
describe(handle: ReactiveHandle): NodeDescriptor | undefined;
|
|
226
|
+
/** Register a single graph-mutation listener (1.2.1+). Replaces any existing
|
|
227
|
+
* listener and returns an unsubscribe that restores the previous one on call.
|
|
228
|
+
* Listener is invoked synchronously at each mutation point with three integers:
|
|
229
|
+
* `(opcode, intA, intB)` -- see {@link GraphMutationOpcode}. Cost when no
|
|
230
|
+
* listener is registered: one branch-predicted `null` check per mutation point.
|
|
231
|
+
* @throws TypeError if `fn` is not a function or null. */
|
|
232
|
+
onGraphMutation(fn: GraphMutationListener | null): GraphMutationUnsubscribe;
|
|
185
233
|
onCleanup(fn: () => void): void;
|
|
186
234
|
stats(): RegistryStats;
|
|
187
235
|
/** Reset everything: nodes, links, queues, global clock. Outstanding dispose
|
|
188
236
|
* closures become safe no-ops. Outstanding read/set closures still reference
|
|
189
|
-
* pool slots
|
|
237
|
+
* pool slots -- they will silently misbehave; use a fresh registry afterwards. */
|
|
190
238
|
destroy(): void;
|
|
191
239
|
}
|
|
192
240
|
|
|
@@ -203,12 +251,12 @@ export function createRegistry(config?: RegistryConfig): Registry;
|
|
|
203
251
|
/** Replace the default registry backing the top-level helpers. */
|
|
204
252
|
export function setDefaultRegistry(registry: Registry): void;
|
|
205
253
|
|
|
206
|
-
//
|
|
254
|
+
// --- Top-level helpers (delegate to default registry) ------------------------
|
|
207
255
|
|
|
208
256
|
export function signal<T>(initial: T, opts?: SignalOptions<T>): Signal<T>;
|
|
209
257
|
export function computed<T>(fn: () => T, opts?: ComputedOptions<T>): Computed<T>;
|
|
210
258
|
export function effect(fn: () => void, opts?: EffectOptions): Dispose;
|
|
211
|
-
/** Universal disposal
|
|
259
|
+
/** Universal disposal -- see {@link Registry.dispose}. */
|
|
212
260
|
export function dispose(api: Disposable): void;
|
|
213
261
|
export function batch<T>(fn: () => T): T;
|
|
214
262
|
export function untrack<T>(fn: () => T): T;
|
|
@@ -222,10 +270,16 @@ export function observeObservers(handle: ReactiveHandle, hooks?: ObserveObserver
|
|
|
222
270
|
export function forEachObserver(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
223
271
|
/** Top-level binding of {@link Registry.forEachSource}. */
|
|
224
272
|
export function forEachSource(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
273
|
+
/** Top-level binding of {@link Registry.forEachOwned} (1.2.1+). */
|
|
274
|
+
export function forEachOwned(handle: ReactiveHandle, fn: (descriptor: NodeDescriptor) => void): void;
|
|
275
|
+
/** Top-level binding of {@link Registry.ownerOf} (1.2.1+). */
|
|
276
|
+
export function ownerOf(handle: ReactiveHandle): NodeDescriptor | undefined;
|
|
225
277
|
/** Top-level binding of {@link Registry.nodeId}. */
|
|
226
278
|
export function nodeId(handle: ReactiveHandle): number | undefined;
|
|
227
279
|
/** Top-level binding of {@link Registry.describe}. */
|
|
228
280
|
export function describe(handle: ReactiveHandle): NodeDescriptor | undefined;
|
|
281
|
+
/** Top-level binding of {@link Registry.onGraphMutation} (1.2.1+). */
|
|
282
|
+
export function onGraphMutation(fn: GraphMutationListener | null): GraphMutationUnsubscribe;
|
|
229
283
|
export function onCleanup(fn: () => void): void;
|
|
230
284
|
export function stats(): RegistryStats;
|
|
231
285
|
export declare function destroy(): void;
|
|
@@ -242,7 +296,7 @@ export interface WatchOptions {
|
|
|
242
296
|
|
|
243
297
|
/**
|
|
244
298
|
* Track a reactive source and run a callback whenever its projected value
|
|
245
|
-
* changes. The callback receives `(newValue, oldValue, stop)`
|
|
299
|
+
* changes. The callback receives `(newValue, oldValue, stop)` -- the third
|
|
246
300
|
* argument is a dispose function that can be called from inside the callback
|
|
247
301
|
* to terminate the watcher.
|
|
248
302
|
*
|
|
@@ -282,10 +336,10 @@ export function when(
|
|
|
282
336
|
* Promise-returning variant of {@link when}. The returned promise resolves
|
|
283
337
|
* when `predicate` first returns a truthy value.
|
|
284
338
|
*
|
|
285
|
-
*
|
|
339
|
+
* ! **HOT-PATH WARNING -- DO NOT USE PER FRAME.** This function calls
|
|
286
340
|
* `new Promise(...)`, which is a heap allocation (one Promise object plus
|
|
287
341
|
* executor closure plus internal infrastructure per call). Promises require
|
|
288
|
-
* heap allocation by the language spec
|
|
342
|
+
* heap allocation by the language spec -- this cost is unavoidable.
|
|
289
343
|
*
|
|
290
344
|
* **Use for:** high-level scene/UI orchestration, boot sequences, awaiting
|
|
291
345
|
* user input or network state, level transitions. Anything that runs once
|