@ecopages/signals 0.3.0-alpha.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 +109 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +22 -0
- package/dist/src/async-state/index.d.ts +90 -0
- package/dist/src/computed.d.ts +72 -0
- package/dist/src/dependency.d.ts +9 -0
- package/dist/src/effect.d.ts +9 -0
- package/dist/src/runtime.d.ts +12 -0
- package/dist/src/signal-node.d.ts +26 -0
- package/dist/src/state.d.ts +49 -0
- package/dist/src/store.d.ts +23 -0
- package/dist/src/tracking.d.ts +16 -0
- package/dist/src/types.d.ts +108 -0
- package/dist/src/watch.d.ts +9 -0
- package/dist/src/watcher.d.ts +42 -0
- package/package.json +60 -0
- package/size-budget.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Ecopages Signals
|
|
2
|
+
|
|
3
|
+
`@ecopages/signals` is a renderer-agnostic signals package that can be used standalone or underneath Radiant.
|
|
4
|
+
|
|
5
|
+
Its core model is based on the [TC39 Signals proposal](https://github.com/tc39/proposal-signals/tree/main), with a smaller surface area and a few convenience helpers for application code today.
|
|
6
|
+
|
|
7
|
+
The public entrypoint remains `index.ts`, while the implementation now lives in focused modules under `src/` so the core runtime, effects, watcher support, and store logic can evolve independently.
|
|
8
|
+
|
|
9
|
+
## Current Scope
|
|
10
|
+
|
|
11
|
+
This package currently provides:
|
|
12
|
+
|
|
13
|
+
- `State<T>` for writable values
|
|
14
|
+
- `Computed<T>` for lazily derived values
|
|
15
|
+
- `currentComputed()` for advanced derived helpers that need the active computed context
|
|
16
|
+
- `effect(...)` for reactive side effects with scheduled re-execution
|
|
17
|
+
- `watch(...)` for observing derived values with previous-value access
|
|
18
|
+
- `untrack(...)` and `peek(...)` for non-tracking reads
|
|
19
|
+
- `subtle.Watcher` plus `watched` and `unwatched` hooks for low-level invalidation workflows
|
|
20
|
+
- `createStore(...)` for deep reactive object and array state
|
|
21
|
+
- `isStore(...)` for detecting signal-backed store proxies
|
|
22
|
+
- `snapshot(...)` for materializing plain nested data
|
|
23
|
+
- automatic dependency discovery during `Computed` evaluation
|
|
24
|
+
- subscription support for renderer or framework adapters
|
|
25
|
+
|
|
26
|
+
## Source Layout
|
|
27
|
+
|
|
28
|
+
The package is organized around a stable public barrel and smaller implementation files:
|
|
29
|
+
|
|
30
|
+
- `index.ts` re-exports the public API and exposes `subtle`
|
|
31
|
+
- `src/types.ts` defines the public contracts, options, and low-level symbols
|
|
32
|
+
- `src/state.ts` implements writable `State` signals
|
|
33
|
+
- `src/computed.ts` implements lazy derived `Computed` signals and active-computation helpers
|
|
34
|
+
- `src/effect.ts` and `src/watch.ts` implement effect scheduling and derived-value observation
|
|
35
|
+
- `src/watcher.ts` implements proposal-shaped low-level watchers
|
|
36
|
+
- `src/tracking.ts` contains non-tracking read helpers
|
|
37
|
+
- `src/store.ts` contains deep store proxying and snapshot materialization
|
|
38
|
+
|
|
39
|
+
## Design Position
|
|
40
|
+
|
|
41
|
+
This package is renderer-agnostic.
|
|
42
|
+
|
|
43
|
+
- It does not know about JSX.
|
|
44
|
+
- It does not know about Radiant components.
|
|
45
|
+
- It is meant to work both as a standalone package and underneath adapters in those packages.
|
|
46
|
+
|
|
47
|
+
## TC39 Relationship
|
|
48
|
+
|
|
49
|
+
This package is based on the current TC39 Signals proposal and tracks the same broad model around:
|
|
50
|
+
|
|
51
|
+
- `State` and `Computed` signal classes
|
|
52
|
+
- lazy pull-based recomputation with cached values
|
|
53
|
+
- automatic dependency discovery during computed evaluation
|
|
54
|
+
- custom equality functions for writable and computed signals
|
|
55
|
+
- untracked reads as an escape hatch
|
|
56
|
+
|
|
57
|
+
It is not a drop-in implementation of the current proposal draft.
|
|
58
|
+
|
|
59
|
+
It is best understood as proposal-aligned in its core semantics, but not yet fully API-compatible with the draft surface.
|
|
60
|
+
|
|
61
|
+
- It exposes convenience helpers such as `effect(...)`, `watch(...)`, `createStore(...)`, and `snapshot(...)` directly.
|
|
62
|
+
- It currently exposes manual `subscribe(...)` hooks for adapter and library integration.
|
|
63
|
+
- It exposes a proposal-shaped `subtle.Watcher` API, while still keeping the existing convenience helpers.
|
|
64
|
+
- Its `subtle.Watcher` follows the proposal-style re-arm behavior, where calling `watch(...)` resets the pending set and notification latch for the next invalidation cycle.
|
|
65
|
+
- It does not yet expose the full TC39 subtle introspection surface.
|
|
66
|
+
|
|
67
|
+
## API Notes
|
|
68
|
+
|
|
69
|
+
- `subscribe(...)` is intended for adapter-style push integration. Application-level derived work is usually better expressed with `Computed`, `effect(...)`, or `watch(...)`.
|
|
70
|
+
- `watch(...)` is built on top of a computed signal plus an effect, so it inherits computed equality behavior and effect scheduling.
|
|
71
|
+
- `subtle.Watcher` reports staleness rather than recalculated values. Calling `watch(...)` is both registration and reset.
|
|
72
|
+
- `createStore(...)` wraps nested plain objects and arrays, while `snapshot(...)` detaches the current plain value graph for logging, serialization, or comparison.
|
|
73
|
+
|
|
74
|
+
## Example
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { Computed, State, createStore, effect, watch } from '@ecopages/signals';
|
|
78
|
+
|
|
79
|
+
const count = new State(0);
|
|
80
|
+
const parity = new Computed(() => ((count.get() & 1) === 0 ? 'even' : 'odd'));
|
|
81
|
+
const store = createStore({ profile: { name: 'Ada' } });
|
|
82
|
+
|
|
83
|
+
const dispose = effect(() => {
|
|
84
|
+
console.log(parity.get(), store.profile.name);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const stopWatching = watch(
|
|
88
|
+
() => store.profile.name,
|
|
89
|
+
(nextName, previousName) => {
|
|
90
|
+
console.log(previousName, '->', nextName);
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
count.set(1);
|
|
95
|
+
store.profile.name = 'Grace';
|
|
96
|
+
dispose();
|
|
97
|
+
stopWatching();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Limits
|
|
101
|
+
|
|
102
|
+
This implementation is still smaller than the current TC39 proposal draft.
|
|
103
|
+
|
|
104
|
+
- no full TC39 `Watcher` and subtle semantics surface yet
|
|
105
|
+
- no full proposal-style subtle introspection helpers yet
|
|
106
|
+
- no batching or transaction model
|
|
107
|
+
- no framework-owned disposal tree or component ownership integration yet
|
|
108
|
+
|
|
109
|
+
Those omissions are deliberate. The goal is to keep a small, useful standalone package while leaving room to align further as the proposal evolves.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { asyncState, type AsyncStateConfig, type AsyncStateFetcherOptions, type AsyncStateResult, type AsyncStateSourcedConfig, type AsyncStatus, } from './src/async-state';
|
|
2
|
+
export { Computed, computed, currentComputed } from './src/computed';
|
|
3
|
+
export { trackDependency } from './src/dependency';
|
|
4
|
+
export { effect } from './src/effect';
|
|
5
|
+
export { State, state } from './src/state';
|
|
6
|
+
export { createStore, isStore, snapshot } from './src/store';
|
|
7
|
+
export { peek, untrack } from './src/tracking';
|
|
8
|
+
export { type DependencyNode, type EffectCallback, type EffectCleanup, type EffectOptions, type EffectScheduler, type Signal, type SignalOptions, type SignalStore, type SignalSubscriber, type WatchOptions, type WritableSignal, watched, unwatched, } from './src/types';
|
|
9
|
+
export { Watcher } from './src/watcher';
|
|
10
|
+
export { watch } from './src/watch';
|
|
11
|
+
import { currentComputed } from './src/computed';
|
|
12
|
+
import { trackDependency } from './src/dependency';
|
|
13
|
+
import { untrack } from './src/tracking';
|
|
14
|
+
import { watched, unwatched } from './src/types';
|
|
15
|
+
import { Watcher } from './src/watcher';
|
|
16
|
+
/**
|
|
17
|
+
* Proposal-shaped low-level APIs for framework and adapter authors.
|
|
18
|
+
*
|
|
19
|
+
* This groups the lower-level pieces that mirror the TC39 proposal naming
|
|
20
|
+
* without forcing application code onto the subtle surface by default.
|
|
21
|
+
*/
|
|
22
|
+
export declare const subtle: Readonly<{
|
|
23
|
+
Watcher: typeof Watcher;
|
|
24
|
+
currentComputed: typeof currentComputed;
|
|
25
|
+
trackDependency: typeof trackDependency;
|
|
26
|
+
untrack: typeof untrack;
|
|
27
|
+
watched: typeof watched;
|
|
28
|
+
unwatched: typeof unwatched;
|
|
29
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var v,c,y=0,E=function(j,G){return Object.is(j,G)},G0=(j)=>{queueMicrotask(j)};function U(){if(y>0)throw Error("Cannot read or write signals during a Watcher notification.")}function J0(){return c??null}function Q0(){return c}function d(j){c=j}function z(){return v}function W(j){v=j}function x(j){v?.(j)}function X0(j){y+=1;try{j()}finally{y-=1}}var B=Symbol.for("@ecopages/signals.subtle.watched"),O=Symbol.for("@ecopages/signals.subtle.unwatched");class _{subscribers=new Set;version=0;watcherListeners=new Set;onWatched;onUnwatched;constructor(j={}){this.onWatched=j[B],this.onUnwatched=j[O]}addWatcher(j){let G=this.watcherListeners.size===0;if(this.watcherListeners.add(j),G)try{this.handleFirstWatcherAdded(),this.onWatched?.call(this)}catch(J){throw this.watcherListeners.delete(j),J}return()=>{if(!this.watcherListeners.delete(j))return;if(this.watcherListeners.size===0)this.handleLastWatcherRemoved(),this.onUnwatched?.call(this)}}getVersion(){return this.version}getWatcherCount(){return this.watcherListeners.size}connectToActiveComputed(){x(this)}handleFirstWatcherAdded(){}handleLastWatcherRemoved(){}publish(j){for(let G of this.subscribers)G(j)}notifyWatchers(){let j=[];for(let G of this.watcherListeners)try{G()}catch(J){j.push(J)}if(j.length===1)throw j[0];if(j.length>1)throw AggregateError(j,"Multiple watcher notifications failed.")}}function T(j){if(!(j instanceof _))throw TypeError("Expected a signal created by @ecopages/signals.");return j}class H extends _{equals;value;constructor(j,G={}){super(G);this.value=j,this.equals=G.equals??E}get(){return U(),this.connectToActiveComputed(),this.value}set(j){if(U(),this.equals.call(this,this.value,j))return;this.value=j,this.version+=1;let G;try{this.notifyWatchers()}catch(J){G=J}if(this.publish(j),G)throw G}update(j){this.set(j(this.value))}subscribe(j){return this.subscribers.add(j),()=>{this.subscribers.delete(j)}}}function P(j,G){return new H(j,G)}var u=Symbol.for("@ecopages/signals.computed-no-error");class A extends _{compute;dependencyUnsubscribers=new Map;dependencyWatcherUnsubscribers=new Map;equals;dependencies=new Map;computing=!1;error=void 0;hasError=!1;initialized=!1;pendingDependencies=new Map;stale=!0;value;constructor(j,G={}){super(G);this.compute=j,this.equals=G.equals??E}get(){if(U(),this.refreshIfNeeded(),this.connectToActiveComputed(),this.hasError)throw this.error;return this.value}subscribe(j){let G=this.subscribers.size===0;if(this.subscribers.add(j),G)try{this.refreshIfNeeded(),this.syncDependencySubscriptions()}catch(J){throw this.subscribers.delete(j),J}return()=>{if(this.subscribers.delete(j),this.subscribers.size===0)this.clearDependencySubscriptions()}}clearDependencySubscriptions(){for(let j of this.dependencyUnsubscribers.values())j();this.dependencyUnsubscribers.clear()}clearDependencyWatcherSubscriptions(){for(let j of this.dependencyWatcherUnsubscribers.values())j();this.dependencyWatcherUnsubscribers.clear()}handleDependencyChange=()=>{if(this.stale=!0,this.subscribers.size===0)return;let j=this.version;if(this.refreshIfNeeded(),this.version!==j&&!this.hasError)this.publish(this.value)};handleDependencyWatcherChange=()=>{this.stale=!0,this.notifyWatchers()};haveDependenciesChanged(){for(let[j,G]of this.dependencies)if(j.get(),j.getVersion()!==G)return!0;return!1}recompute(){if(this.computing)throw Error("Cannot read a computed signal recursively.");let j=z(),G=Q0(),J=this.value,Q=this.error,Z=this.initialized,X,I=new Map,$=u;this.computing=!0,this.pendingDependencies=new Map;try{d(this),W((D)=>{this.trackDependency(D)}),X=this.compute.call(this)}catch(D){$=D}finally{I=this.pendingDependencies,d(G),W(j),this.pendingDependencies=new Map,this.computing=!1}this.dependencies=I,this.stale=!1;let Y=!Z||($===u?this.hasError||!this.equals.call(this,J,X):!this.hasError||Q!==$);if($===u)this.value=X,this.error=void 0,this.hasError=!1;else this.error=$,this.hasError=!0;if(this.initialized=!0,Y)this.version+=1;if(this.subscribers.size>0)this.syncDependencySubscriptions();if(this.getWatcherCount()>0)this.syncDependencyWatcherSubscriptions()}refreshIfNeeded(){if(!this.initialized||this.stale||this.haveDependenciesChanged())this.recompute()}syncDependencySubscriptions(){for(let[j,G]of this.dependencyUnsubscribers){if(this.dependencies.has(j))continue;G(),this.dependencyUnsubscribers.delete(j)}for(let j of this.dependencies.keys()){if(this.dependencyUnsubscribers.has(j))continue;this.dependencyUnsubscribers.set(j,j.subscribe(this.handleDependencyChange))}}syncDependencyWatcherSubscriptions(){for(let[j,G]of this.dependencyWatcherUnsubscribers){if(this.dependencies.has(j))continue;G(),this.dependencyWatcherUnsubscribers.delete(j)}for(let j of this.dependencies.keys()){if(this.dependencyWatcherUnsubscribers.has(j))continue;this.dependencyWatcherUnsubscribers.set(j,j.addWatcher(this.handleDependencyWatcherChange))}}trackDependency(j){this.pendingDependencies.set(j,j.getVersion())}handleFirstWatcherAdded(){this.refreshIfNeeded(),this.syncDependencyWatcherSubscriptions()}handleLastWatcherRemoved(){this.clearDependencyWatcherSubscriptions()}}function q0(j,G){return new A(j,G)}function i(){return J0()}class Z0{callback;cleanup;dependencies=new Map;disposed=!1;queued=!1;scheduler;constructor(j,G){this.callback=j;this.scheduler=G.scheduler??G0}dispose(){if(this.disposed)return;this.disposed=!0,this.cleanup?.(),this.cleanup=void 0;for(let j of this.dependencies.values())j();this.dependencies.clear()}run=()=>{if(this.disposed)return;this.queued=!1,this.cleanup?.(),this.cleanup=void 0;let j=new Set,G=z();try{W((Q)=>{j.add(Q)});let J=this.callback();if(typeof J==="function")this.cleanup=J}finally{W(G)}this.syncDependencies(j)};handleDependencyChange=()=>{if(this.disposed||this.queued)return;this.queued=!0,this.scheduler(this.run)};syncDependencies(j){for(let[G,J]of this.dependencies){if(j.has(G))continue;J(),this.dependencies.delete(G)}for(let G of j){if(this.dependencies.has(G))continue;this.dependencies.set(G,G.subscribe(this.handleDependencyChange))}}}function s(j,G={}){let J=new Z0(j,G);return J.run(),()=>{J.dispose()}}function l(j,G,J={}){let Q=new A(j,{equals:J.equals}),Z,X=!1;return s(()=>{let I=Q.get();if(X)G(I,Z);else if(J.immediate)G(I,void 0);Z=I,X=!0},{scheduler:J.scheduler})}function H0(j){let G=j.source!==void 0,J=G?j.source:void 0,Q=j.fetcher,Z=j.staleTime??0,X=j.pendingDelay??0,I=P(j.initialValue),$=P("idle"),Y=P(void 0),D=new Map,K=null,N=0,C,g=[],R=()=>{if(C!==void 0)clearTimeout(C),C=void 0},W0=()=>{C=void 0,$.set("pending")},Y0=()=>{if(R(),Y.set(void 0),X<=0){$.set("pending");return}C=setTimeout(W0,X)},b=(M)=>{let q=G&&Z>0,n=G?JSON.stringify(M):"";if(q){let F=D.get(n);if(F&&Date.now()-F.timestamp<Z){N+=1,K?.abort(),K=null,R(),I.set(F.value),$.set("success"),Y.set(void 0),j.onSuccess?.(F.value),j.onSettled?.(F.value,void 0);return}}let e=++N;K?.abort(),K=new AbortController,Y0();let p=K,j0={signal:p.signal};(G?Q(M,j0):Q(j0)).then((F)=>{if(e!==N||p.signal.aborted)return;if(R(),q)D.set(n,{value:F,timestamp:Date.now()});I.set(F),$.set("success"),K=null,j.onSuccess?.(F),j.onSettled?.(F,void 0)},(F)=>{if(e!==N||p.signal.aborted)return;if(F instanceof DOMException&&F.name==="AbortError")return;R(),Y.set(F),$.set("error"),K=null,j.onError?.(F),j.onSettled?.(void 0,F)})};if(J){let M=l(J,(q)=>{if(q===!1||q===null||q===void 0)return;b(q)},{immediate:!0});g.push(M)}else b();return{data:I,status:$,error:Y,refetch(){if(J){let M=J();if(M===!1||M===null||M===void 0)return;b(M)}else b()},abort(){K?.abort(),K=null},dispose(){R(),K?.abort(),K=null;for(let M of g)M();g.length=0}}}function o(j){U(),x(j)}function m(j){let G=z();W(void 0);try{return j()}finally{W(G)}}function w(j){return m(()=>j.get())}var F0=Symbol.for("@ecopages/signals.store-branch"),L=Symbol.for("@ecopages/signals.store-absent"),r=new WeakMap,a=Object.prototype.hasOwnProperty;function V(j){return typeof j==="object"&&j!==null&&r.has(j)}function D0(j){return K0(j).proxy}function t(j){if(V(j))return O0(C0(j));return f(j)}function z0(j){return{[F0]:!0,node:K0(j)}}function I0(j){let G=V(j)?t(j):j;if(U0(G))return z0(G);return G}function K0(j){let G=Array.isArray(j)?"array":"object",J=G==="array"?[]:{},Q={entries:new Map,proxy:void 0,shape:new H(0),target:J,valueType:G};for(let Z of Reflect.ownKeys(j)){let X=new H(I0(j[Z]));Q.entries.set(Z,X),Reflect.set(J,Z,S(w(X)))}if(Array.isArray(j))J.length=j.length;return Q.proxy=new Proxy(J,_0(Q)),r.set(Q.proxy,Q),Q}function _0(j){return{defineProperty(G,J,Q){if("value"in Q)return $0(j,J,Q.value);let Z=Reflect.defineProperty(G,J,Q);if(Z)j.shape.update((X)=>X+1);return Z},deleteProperty(G,J){return M0(j,J)},get(G,J,Q){if(j.valueType==="array"&&J==="length")return j.shape.get(),Reflect.get(G,J,Q);if(a.call(G,J)){let X=k(j,J).get();if(X===L)return;return S(X)}return Reflect.get(G,J,Q)},getOwnPropertyDescriptor(G,J){return j.shape.get(),Reflect.getOwnPropertyDescriptor(G,J)},has(G,J){return j.shape.get(),Reflect.has(G,J)},ownKeys(G){return j.shape.get(),Reflect.ownKeys(G)},set(G,J,Q){return $0(j,J,Q)}}}function C0(j){let G=r.get(j);if(!G)throw Error("Value is not a signal store.");return G}function $0(j,G,J){if(j.valueType==="array"&&G==="length")return B0(j,J);let Q=k(j,G),Z=a.call(j.target,G),X=j.valueType==="array"?j.target.length:void 0,I=I0(J);if(Q.set(I),Reflect.set(j.target,G,S(I)),!Z)j.shape.update(($)=>$+1);if(j.valueType==="array"){let $=j.target.length;if(X!==$)j.shape.update((Y)=>Y+1)}return!0}function M0(j,G){if(!a.call(j.target,G))return!0;if(k(j,G).set(L),Reflect.deleteProperty(j.target,G),j.valueType==="array"&&G==="length")j.target.length=0;return j.shape.update((Q)=>Q+1),!0}function k(j,G){let J=j.entries.get(G);if(!J)J=new H(L),j.entries.set(G,J);return J}function R0(j){return typeof j==="object"&&j!==null&&F0 in j}function U0(j){if(j===null||typeof j!=="object")return!1;if(Array.isArray(j))return!0;let G=Object.getPrototypeOf(j);return G===Object.prototype||G===null}function B0(j,G){let J=Number(G);if(!Number.isInteger(J)||J<0)throw RangeError("Invalid array length.");let Q=j.target,Z=Q.length;if(J<Z)for(let X=Z-1;X>=J;X-=1)M0(j,String(X));if(Q.length=J,Z!==J)j.shape.update((X)=>X+1);return!0}function f(j){if(Array.isArray(j))return j.map((G)=>f(G));if(V(j))return t(j);if(U0(j)){let G={};for(let J of Reflect.ownKeys(j))G[J]=f(j[J]);return G}return j}function O0(j){let G=j.valueType==="array"?[]:{};for(let J of Reflect.ownKeys(j.target)){let Q=k(j,J),Z=w(Q);if(Z===L)continue;Reflect.set(G,J,f(S(Z)))}if(j.valueType==="array")G.length=j.target.length;return G}function S(j){if(j===L)return;if(R0(j))return j.node.proxy;return j}class h{notifyCallback;notified=!1;pendingSignals=new Map;signals=new Map;unsubscribers=new Map;constructor(j){this.notifyCallback=j}getPending(){return Array.from(this.pendingSignals.values())}unwatch(...j){U();for(let G of j){let J=T(G);if(!this.unsubscribers.get(J))throw Error("Signal is not watched by this watcher.")}for(let G of j){let J=T(G);this.unsubscribers.get(J)?.(),this.pendingSignals.delete(J),this.signals.delete(J),this.unsubscribers.delete(J)}if(this.signals.size===0)this.pendingSignals.clear(),this.notified=!1}watch(...j){U();for(let G of j){let J=T(G);if(this.signals.has(J))continue;this.signals.set(J,G),this.unsubscribers.set(J,J.addWatcher(()=>{this.handleSignalChange(J)}))}this.pendingSignals.clear(),this.notified=!1}handleSignalChange(j){let G=this.signals.get(j);if(!G)return;if(this.pendingSignals.set(j,G),this.notified)return;this.notified=!0,X0(()=>{this.notifyCallback.call(this)})}}var X1=Object.freeze({Watcher:h,currentComputed:i,trackDependency:o,untrack:m,watched:B,unwatched:O});export{B as watched,l as watch,O as unwatched,m as untrack,o as trackDependency,X1 as subtle,P as state,t as snapshot,w as peek,V as isStore,s as effect,i as currentComputed,D0 as createStore,q0 as computed,H0 as asyncState,h as Watcher,H as State,A as Computed};
|
|
2
|
+
|
|
3
|
+
//# debugId=78ED94C3C2E5FD6764756E2164756E21
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/runtime.ts", "../src/types.ts", "../src/signal-node.ts", "../src/state.ts", "../src/computed.ts", "../src/effect.ts", "../src/watch.ts", "../src/async-state/index.ts", "../src/dependency.ts", "../src/tracking.ts", "../src/store.ts", "../src/watcher.ts", "../index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { Computed } from './computed';\nimport type { DependencyNode, EffectScheduler, SignalEquals } from './types';\n\nlet activeDependencyRecorder: ((dependency: DependencyNode) => void) | undefined;\nlet activeComputedSignal: Computed<any> | undefined;\nlet frozenWatcherDepth = 0;\n\nexport const defaultEquals: SignalEquals<unknown> = function (previousValue, nextValue) {\n\treturn Object.is(previousValue, nextValue);\n};\n\nexport const scheduleMicrotask: EffectScheduler = (run) => {\n\tqueueMicrotask(run);\n};\n\nexport function assertSignalAccessAllowed(): void {\n\tif (frozenWatcherDepth > 0) {\n\t\tthrow new Error('Cannot read or write signals during a Watcher notification.');\n\t}\n}\n\nexport function currentComputedSignal(): Computed<unknown> | null {\n\treturn activeComputedSignal ?? null;\n}\n\nexport function getActiveComputedSignal(): Computed<any> | undefined {\n\treturn activeComputedSignal;\n}\n\nexport function setActiveComputedSignal(nextSignal: Computed<any> | undefined): void {\n\tactiveComputedSignal = nextSignal;\n}\n\nexport function getActiveDependencyRecorder(): ((dependency: DependencyNode) => void) | undefined {\n\treturn activeDependencyRecorder;\n}\n\nexport function setActiveDependencyRecorder(nextRecorder: ((dependency: DependencyNode) => void) | undefined): void {\n\tactiveDependencyRecorder = nextRecorder;\n}\n\nexport function trackActiveDependency(dependency: DependencyNode): void {\n\tactiveDependencyRecorder?.(dependency);\n}\n\nexport function runWatcherNotify(callback: () => void): void {\n\tfrozenWatcherDepth += 1;\n\n\ttry {\n\t\tcallback();\n\t} finally {\n\t\tfrozenWatcherDepth -= 1;\n\t}\n}\n",
|
|
6
|
+
"import type { Computed } from './computed';\n\n/**\n * Imperative listener invoked after a signal publishes a new exposed value.\n *\n * Subscribers are separate from automatic dependency tracking and are mainly\n * useful for adapters, bridge code, or tests that need push-based updates.\n */\nexport type SignalSubscriber<Value> = (value: Value) => void;\n\n/** Hook invoked when a signal becomes watched through `subtle.Watcher`. */\nexport const watched = Symbol.for('@ecopages/signals.subtle.watched');\n\n/** Hook invoked when a signal stops being watched through `subtle.Watcher`. */\nexport const unwatched = Symbol.for('@ecopages/signals.subtle.unwatched');\n\nexport type SignalEquals<Value> = (this: Signal<Value>, previousValue: Value, nextValue: Value) => boolean;\n\nexport type ComputedCallback<Value> = (this: Computed<Value>) => Value;\n\n/**\n * Optional configuration shared by writable and computed signals.\n *\n * These hooks control equality and low-level watcher lifecycle integration.\n */\nexport interface SignalOptions<Value> {\n\t/**\n\t * Equality comparison used to suppress redundant updates.\n\t *\n\t * Defaults to `Object.is`.\n\t */\n\tequals?: SignalEquals<Value>;\n\n\t/** Called when the signal becomes watched through `subtle.Watcher`. */\n\t[watched]?: (this: Signal<Value>) => void;\n\n\t/** Called when the signal is no longer watched through `subtle.Watcher`. */\n\t[unwatched]?: (this: Signal<Value>) => void;\n}\n\n/**\n * Read-only signal contract.\n *\n * Reading from a signal participates in dependency discovery when a computed\n * signal or effect is currently collecting dependencies.\n */\nexport interface Signal<Value> {\n\t/**\n\t * Returns the current value.\n\t *\n\t * Reads performed during a tracked computation register this signal as a\n\t * dependency of that computation.\n\t */\n\tget(): Value;\n\n\t/**\n\t * Subscribes to exposed value changes.\n\t *\n\t * Subscribers are only called when the signal's value changes according to\n\t * its configured equality function.\n\t */\n\tsubscribe(notify: SignalSubscriber<Value>): () => void;\n}\n\n/**\n * Read-write signal contract.\n *\n * Writable signals expose direct writes in addition to dependency-tracked\n * reads.\n */\nexport interface WritableSignal<Value> extends Signal<Value> {\n\t/**\n\t * Replaces the current value.\n\t *\n\t * No invalidation happens when the configured equality function reports the\n\t * new value as equal to the current one.\n\t */\n\tset(nextValue: Value): void;\n\n\t/**\n\t * Replaces the current value by deriving the next one from the current one.\n\t */\n\tupdate(updater: (value: Value) => Value): void;\n}\n\n/** Scheduler used to defer effect re-execution. */\nexport type EffectScheduler = (run: () => void) => void;\n\n/** Cleanup function returned from an effect body. */\nexport type EffectCleanup = void | (() => void);\n\n/** Callback executed by an effect. */\nexport type EffectCallback = () => EffectCleanup;\n\n/**\n * Configuration for an effect.\n *\n * Effects default to microtask scheduling so multiple synchronous writes can\n * collapse into a single rerun.\n */\nexport interface EffectOptions {\n\t/** Scheduler used after a dependency changes. Defaults to a microtask queue. */\n\tscheduler?: EffectScheduler;\n}\n\n/**\n * Configuration for `watch(...)`.\n *\n * Watches reuse computed-style equality and effect-style scheduling.\n */\nexport interface WatchOptions<Value> extends SignalOptions<Value> {\n\t/**\n\t * When `true`, invokes the callback during the initial run with an undefined\n\t * previous value.\n\t */\n\timmediate?: boolean;\n\n\t/** Scheduler used after the watched value changes. */\n\tscheduler?: EffectScheduler;\n}\n\n/** Marker interface returned from `createStore(...)`. */\nexport type SignalStore<Value extends object> = Value;\n\nexport interface DependencyNode extends Signal<unknown> {\n\taddWatcher(notify: () => void): () => void;\n\tgetVersion(): number;\n}\n",
|
|
7
|
+
"import { trackActiveDependency } from './runtime';\nimport {\n\twatched,\n\tunwatched,\n\ttype DependencyNode,\n\ttype Signal,\n\ttype SignalOptions,\n\ttype SignalSubscriber,\n} from './types';\n\n/**\n * Shared signal implementation used by writable and computed signals.\n *\n * It centralizes subscriber delivery, monotonic versioning, and the low-level\n * watcher hooks consumed by `subtle.Watcher`.\n */\nexport abstract class SignalNode<Value> implements Signal<Value>, DependencyNode {\n\tprotected readonly subscribers = new Set<SignalSubscriber<Value>>();\n\tprotected version = 0;\n\tprivate readonly watcherListeners = new Set<() => void>();\n\tprivate readonly onWatched: ((this: Signal<Value>) => void) | undefined;\n\tprivate readonly onUnwatched: ((this: Signal<Value>) => void) | undefined;\n\n\tprotected constructor(options: SignalOptions<Value> = {}) {\n\t\tthis.onWatched = options[watched];\n\t\tthis.onUnwatched = options[unwatched];\n\t}\n\n\tabstract get(): Value;\n\tabstract subscribe(notify: SignalSubscriber<Value>): () => void;\n\n\tpublic addWatcher(notify: () => void): () => void {\n\t\tconst wasEmpty = this.watcherListeners.size === 0;\n\t\tthis.watcherListeners.add(notify);\n\n\t\tif (wasEmpty) {\n\t\t\ttry {\n\t\t\t\tthis.handleFirstWatcherAdded();\n\t\t\t\tthis.onWatched?.call(this);\n\t\t\t} catch (error) {\n\t\t\t\tthis.watcherListeners.delete(notify);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (!this.watcherListeners.delete(notify)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.watcherListeners.size === 0) {\n\t\t\t\tthis.handleLastWatcherRemoved();\n\t\t\t\tthis.onUnwatched?.call(this);\n\t\t\t}\n\t\t};\n\t}\n\n\tpublic getVersion(): number {\n\t\treturn this.version;\n\t}\n\n\tpublic getWatcherCount(): number {\n\t\treturn this.watcherListeners.size;\n\t}\n\n\tprotected connectToActiveComputed(): void {\n\t\ttrackActiveDependency(this);\n\t}\n\n\tprotected handleFirstWatcherAdded(): void {}\n\n\tprotected handleLastWatcherRemoved(): void {}\n\n\tprotected publish(nextValue: Value): void {\n\t\tfor (const subscriber of this.subscribers) {\n\t\t\tsubscriber(nextValue);\n\t\t}\n\t}\n\n\tprotected notifyWatchers(): void {\n\t\tconst errors: unknown[] = [];\n\n\t\tfor (const listener of this.watcherListeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch (error) {\n\t\t\t\terrors.push(error);\n\t\t\t}\n\t\t}\n\n\t\tif (errors.length === 1) {\n\t\t\tthrow errors[0];\n\t\t}\n\n\t\tif (errors.length > 1) {\n\t\t\tthrow new AggregateError(errors, 'Multiple watcher notifications failed.');\n\t\t}\n\t}\n}\n\nexport function resolveSignalNode(signal: Signal<unknown>): SignalNode<unknown> {\n\tif (!(signal instanceof SignalNode)) {\n\t\tthrow new TypeError('Expected a signal created by @ecopages/signals.');\n\t}\n\n\treturn signal;\n}\n",
|
|
8
|
+
"import { assertSignalAccessAllowed, defaultEquals } from './runtime';\nimport { SignalNode } from './signal-node';\nimport type { SignalEquals, SignalOptions, SignalSubscriber, WritableSignal } from './types';\n\n/**\n * Writable state signal.\n *\n * State signals are the smallest unit of mutable reactive data in this package.\n */\nexport class State<Value> extends SignalNode<Value> implements WritableSignal<Value> {\n\tprivate readonly equals: SignalEquals<Value>;\n\tprivate value: Value;\n\n\t/**\n\t * Creates a writable signal with an initial value.\n\t *\n\t * The optional `equals` callback can suppress redundant writes, and the\n\t * watcher hooks integrate with `subtle.Watcher` lifecycle events.\n\t */\n\tconstructor(initialValue: Value, options: SignalOptions<Value> = {}) {\n\t\tsuper(options);\n\t\tthis.value = initialValue;\n\t\tthis.equals = (options.equals ?? defaultEquals) as SignalEquals<Value>;\n\t}\n\n\t/**\n\t * Returns the current value and records this state as a dependency when a\n\t * computation is actively collecting dependencies.\n\t */\n\tpublic get(): Value {\n\t\tassertSignalAccessAllowed();\n\t\tthis.connectToActiveComputed();\n\t\treturn this.value;\n\t}\n\n\t/**\n\t * Replaces the current value.\n\t *\n\t * Watchers are notified before imperative subscribers so low-level invalidation\n\t * can observe the stale transition before push listeners run.\n\t */\n\tpublic set(nextValue: Value): void {\n\t\tassertSignalAccessAllowed();\n\n\t\tif (this.equals.call(this, this.value, nextValue)) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.value = nextValue;\n\t\tthis.version += 1;\n\n\t\tlet watcherError: unknown;\n\n\t\ttry {\n\t\t\tthis.notifyWatchers();\n\t\t} catch (error) {\n\t\t\twatcherError = error;\n\t\t}\n\n\t\tthis.publish(nextValue);\n\n\t\tif (watcherError) {\n\t\t\tthrow watcherError;\n\t\t}\n\t}\n\n\t/**\n\t * Updates the current value using the previous one and then forwards to\n\t * `set(...)` so equality checks and notifications stay consistent.\n\t */\n\tpublic update(updater: (value: Value) => Value): void {\n\t\tthis.set(updater(this.value));\n\t}\n\n\t/**\n\t * Registers an imperative subscriber.\n\t *\n\t * Subscriptions do not trigger an immediate call and are independent from the\n\t * automatic dependency tracking used by computed values and effects.\n\t */\n\tpublic subscribe(notify: SignalSubscriber<Value>): () => void {\n\t\tthis.subscribers.add(notify);\n\n\t\treturn () => {\n\t\t\tthis.subscribers.delete(notify);\n\t\t};\n\t}\n}\n\n/**\n * Creates a writable state signal.\n *\n * This is equivalent to `new State(...)` and can be useful in codebases that\n * prefer factory-style construction.\n */\nexport function state<Value>(initialValue: Value, options?: SignalOptions<Value>): State<Value> {\n\treturn new State(initialValue, options);\n}\n",
|
|
9
|
+
"import {\n\tassertSignalAccessAllowed,\n\tcurrentComputedSignal,\n\tdefaultEquals,\n\tgetActiveComputedSignal,\n\tgetActiveDependencyRecorder,\n\tsetActiveComputedSignal,\n\tsetActiveDependencyRecorder,\n} from './runtime';\nimport { SignalNode } from './signal-node';\nimport type { ComputedCallback, DependencyNode, SignalEquals, SignalOptions, SignalSubscriber } from './types';\n\nconst COMPUTED_NO_ERROR = Symbol.for('@ecopages/signals.computed-no-error');\n\n/**\n * Lazily derived signal backed by other signals read during evaluation.\n *\n * Dependencies are discovered automatically every time the computation runs,\n * and the last successful value or thrown error is cached until one of those\n * dependencies changes.\n */\nexport class Computed<Value> extends SignalNode<Value> {\n\tprivate readonly compute: ComputedCallback<Value>;\n\tprivate readonly dependencyUnsubscribers = new Map<DependencyNode, () => void>();\n\tprivate readonly dependencyWatcherUnsubscribers = new Map<DependencyNode, () => void>();\n\tprivate readonly equals: SignalEquals<Value>;\n\tprivate dependencies = new Map<DependencyNode, number>();\n\tprivate computing = false;\n\tprivate error: unknown = undefined;\n\tprivate hasError = false;\n\tprivate initialized = false;\n\tprivate pendingDependencies = new Map<DependencyNode, number>();\n\tprivate stale = true;\n\tprivate value!: Value;\n\n\t/**\n\t * Creates a lazily evaluated derived signal.\n\t *\n\t * The compute function only reruns when the cached dependency versions become\n\t * stale, and the optional equality callback controls whether a recomputation\n\t * publishes a new exposed value.\n\t */\n\tconstructor(compute: ComputedCallback<Value>, options: SignalOptions<Value> = {}) {\n\t\tsuper(options);\n\t\tthis.compute = compute;\n\t\tthis.equals = (options.equals ?? defaultEquals) as SignalEquals<Value>;\n\t}\n\n\t/**\n\t * Returns the cached value, recomputing lazily when tracked dependencies have\n\t * changed.\n\t *\n\t * If the latest evaluation threw, the cached error is rethrown until a\n\t * dependency invalidates the computed signal.\n\t */\n\tpublic get(): Value {\n\t\tassertSignalAccessAllowed();\n\t\tthis.refreshIfNeeded();\n\t\tthis.connectToActiveComputed();\n\n\t\tif (this.hasError) {\n\t\t\tthrow this.error;\n\t\t}\n\n\t\treturn this.value;\n\t}\n\n\t/**\n\t * Subscribes to changes in the computed signal's exposed value.\n\t *\n\t * The first subscriber eagerly initializes dependency subscriptions so future\n\t * source writes can invalidate and republish this computed signal.\n\t */\n\tpublic subscribe(notify: SignalSubscriber<Value>): () => void {\n\t\tconst wasEmpty = this.subscribers.size === 0;\n\t\tthis.subscribers.add(notify);\n\n\t\tif (wasEmpty) {\n\t\t\ttry {\n\t\t\t\tthis.refreshIfNeeded();\n\t\t\t\tthis.syncDependencySubscriptions();\n\t\t\t} catch (error) {\n\t\t\t\tthis.subscribers.delete(notify);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tthis.subscribers.delete(notify);\n\n\t\t\tif (this.subscribers.size === 0) {\n\t\t\t\tthis.clearDependencySubscriptions();\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate clearDependencySubscriptions(): void {\n\t\tfor (const unsubscribe of this.dependencyUnsubscribers.values()) {\n\t\t\tunsubscribe();\n\t\t}\n\n\t\tthis.dependencyUnsubscribers.clear();\n\t}\n\n\tprivate clearDependencyWatcherSubscriptions(): void {\n\t\tfor (const unsubscribe of this.dependencyWatcherUnsubscribers.values()) {\n\t\t\tunsubscribe();\n\t\t}\n\n\t\tthis.dependencyWatcherUnsubscribers.clear();\n\t}\n\n\tprivate handleDependencyChange = () => {\n\t\tthis.stale = true;\n\n\t\tif (this.subscribers.size === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst previousVersion = this.version;\n\t\tthis.refreshIfNeeded();\n\n\t\tif (this.version !== previousVersion && !this.hasError) {\n\t\t\tthis.publish(this.value);\n\t\t}\n\t};\n\n\tprivate handleDependencyWatcherChange = () => {\n\t\tthis.stale = true;\n\t\tthis.notifyWatchers();\n\t};\n\n\tprivate haveDependenciesChanged(): boolean {\n\t\tfor (const [dependency, version] of this.dependencies) {\n\t\t\tdependency.get();\n\n\t\t\tif (dependency.getVersion() !== version) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate recompute(): void {\n\t\tif (this.computing) {\n\t\t\tthrow new Error('Cannot read a computed signal recursively.');\n\t\t}\n\n\t\tconst previousActiveDependencyRecorder = getActiveDependencyRecorder();\n\t\tconst previousActiveComputedSignal = getActiveComputedSignal();\n\t\tconst previousValue = this.value;\n\t\tconst previousError = this.error;\n\t\tconst wasInitialized = this.initialized;\n\t\tlet nextValue!: Value;\n\t\tlet nextDependencies = new Map<DependencyNode, number>();\n\t\tlet nextError: typeof COMPUTED_NO_ERROR | unknown = COMPUTED_NO_ERROR;\n\n\t\tthis.computing = true;\n\t\tthis.pendingDependencies = new Map();\n\n\t\ttry {\n\t\t\tsetActiveComputedSignal(this);\n\t\t\tsetActiveDependencyRecorder((dependency) => {\n\t\t\t\tthis.trackDependency(dependency);\n\t\t\t});\n\t\t\tnextValue = this.compute.call(this);\n\t\t} catch (error) {\n\t\t\tnextError = error;\n\t\t} finally {\n\t\t\tnextDependencies = this.pendingDependencies;\n\t\t\tsetActiveComputedSignal(previousActiveComputedSignal);\n\t\t\tsetActiveDependencyRecorder(previousActiveDependencyRecorder);\n\t\t\tthis.pendingDependencies = new Map();\n\t\t\tthis.computing = false;\n\t\t}\n\n\t\tthis.dependencies = nextDependencies;\n\t\tthis.stale = false;\n\t\tconst hasChanged =\n\t\t\t!wasInitialized ||\n\t\t\t(nextError === COMPUTED_NO_ERROR\n\t\t\t\t? this.hasError || !this.equals.call(this, previousValue, nextValue)\n\t\t\t\t: !this.hasError || previousError !== nextError);\n\n\t\tif (nextError === COMPUTED_NO_ERROR) {\n\t\t\tthis.value = nextValue;\n\t\t\tthis.error = undefined;\n\t\t\tthis.hasError = false;\n\t\t} else {\n\t\t\tthis.error = nextError;\n\t\t\tthis.hasError = true;\n\t\t}\n\n\t\tthis.initialized = true;\n\n\t\tif (hasChanged) {\n\t\t\tthis.version += 1;\n\t\t}\n\n\t\tif (this.subscribers.size > 0) {\n\t\t\tthis.syncDependencySubscriptions();\n\t\t}\n\n\t\tif (this.getWatcherCount() > 0) {\n\t\t\tthis.syncDependencyWatcherSubscriptions();\n\t\t}\n\t}\n\n\tprivate refreshIfNeeded(): void {\n\t\tif (!this.initialized || this.stale || this.haveDependenciesChanged()) {\n\t\t\tthis.recompute();\n\t\t}\n\t}\n\n\tprivate syncDependencySubscriptions(): void {\n\t\tfor (const [dependency, unsubscribe] of this.dependencyUnsubscribers) {\n\t\t\tif (this.dependencies.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tunsubscribe();\n\t\t\tthis.dependencyUnsubscribers.delete(dependency);\n\t\t}\n\n\t\tfor (const dependency of this.dependencies.keys()) {\n\t\t\tif (this.dependencyUnsubscribers.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.dependencyUnsubscribers.set(dependency, dependency.subscribe(this.handleDependencyChange));\n\t\t}\n\t}\n\n\tprivate syncDependencyWatcherSubscriptions(): void {\n\t\tfor (const [dependency, unsubscribe] of this.dependencyWatcherUnsubscribers) {\n\t\t\tif (this.dependencies.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tunsubscribe();\n\t\t\tthis.dependencyWatcherUnsubscribers.delete(dependency);\n\t\t}\n\n\t\tfor (const dependency of this.dependencies.keys()) {\n\t\t\tif (this.dependencyWatcherUnsubscribers.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.dependencyWatcherUnsubscribers.set(\n\t\t\t\tdependency,\n\t\t\t\tdependency.addWatcher(this.handleDependencyWatcherChange),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate trackDependency(dependency: DependencyNode): void {\n\t\tthis.pendingDependencies.set(dependency, dependency.getVersion());\n\t}\n\n\tprotected override handleFirstWatcherAdded(): void {\n\t\tthis.refreshIfNeeded();\n\t\tthis.syncDependencyWatcherSubscriptions();\n\t}\n\n\tprotected override handleLastWatcherRemoved(): void {\n\t\tthis.clearDependencyWatcherSubscriptions();\n\t}\n}\n\n/**\n * Creates a computed signal.\n *\n * This is equivalent to `new Computed(...)` and can be convenient in codebases\n * that prefer functional construction over classes.\n */\nexport function computed<Value>(\n\tcomputeValue: ComputedCallback<Value>,\n\toptions?: SignalOptions<Value>,\n): Computed<Value> {\n\treturn new Computed(computeValue, options);\n}\n\n/**\n * Returns the computed signal currently being evaluated, if any.\n *\n * This is mainly useful inside advanced derived helpers that need access to\n * the active computed during dependency discovery.\n */\nexport function currentComputed(): Computed<unknown> | null {\n\treturn currentComputedSignal();\n}\n",
|
|
10
|
+
"import { getActiveDependencyRecorder, scheduleMicrotask, setActiveDependencyRecorder } from './runtime';\nimport type { DependencyNode, EffectCallback, EffectOptions, EffectScheduler } from './types';\n\nclass EffectRunner {\n\tprivate cleanup: (() => void) | undefined;\n\tprivate readonly dependencies = new Map<DependencyNode, () => void>();\n\tprivate disposed = false;\n\tprivate queued = false;\n\tprivate readonly scheduler: EffectScheduler;\n\n\tconstructor(\n\t\tprivate readonly callback: EffectCallback,\n\t\toptions: EffectOptions,\n\t) {\n\t\tthis.scheduler = options.scheduler ?? scheduleMicrotask;\n\t}\n\n\tpublic dispose(): void {\n\t\tif (this.disposed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.disposed = true;\n\t\tthis.cleanup?.();\n\t\tthis.cleanup = undefined;\n\n\t\tfor (const unsubscribe of this.dependencies.values()) {\n\t\t\tunsubscribe();\n\t\t}\n\n\t\tthis.dependencies.clear();\n\t}\n\n\tpublic run = (): void => {\n\t\tif (this.disposed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.queued = false;\n\t\tthis.cleanup?.();\n\t\tthis.cleanup = undefined;\n\n\t\tconst nextDependencies = new Set<DependencyNode>();\n\t\tconst previousActiveDependencyRecorder = getActiveDependencyRecorder();\n\n\t\ttry {\n\t\t\tsetActiveDependencyRecorder((dependency) => {\n\t\t\t\tnextDependencies.add(dependency);\n\t\t\t});\n\t\t\tconst result = this.callback();\n\n\t\t\tif (typeof result === 'function') {\n\t\t\t\tthis.cleanup = result;\n\t\t\t}\n\t\t} finally {\n\t\t\tsetActiveDependencyRecorder(previousActiveDependencyRecorder);\n\t\t}\n\n\t\tthis.syncDependencies(nextDependencies);\n\t};\n\n\tprivate handleDependencyChange = () => {\n\t\tif (this.disposed || this.queued) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.queued = true;\n\t\tthis.scheduler(this.run);\n\t};\n\n\tprivate syncDependencies(nextDependencies: Set<DependencyNode>): void {\n\t\tfor (const [dependency, unsubscribe] of this.dependencies) {\n\t\t\tif (nextDependencies.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tunsubscribe();\n\t\t\tthis.dependencies.delete(dependency);\n\t\t}\n\n\t\tfor (const dependency of nextDependencies) {\n\t\t\tif (this.dependencies.has(dependency)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.dependencies.set(dependency, dependency.subscribe(this.handleDependencyChange));\n\t\t}\n\t}\n}\n\n/**\n * Runs a reactive side effect and reschedules it when one of the signals read\n * during execution changes.\n *\n * The callback may return a cleanup function, which runs before the next\n * execution and again when the returned disposer is called.\n */\nexport function effect(callback: EffectCallback, options: EffectOptions = {}): () => void {\n\tconst runner = new EffectRunner(callback, options);\n\trunner.run();\n\treturn () => {\n\t\trunner.dispose();\n\t};\n}\n",
|
|
11
|
+
"import { Computed } from './computed';\nimport { effect } from './effect';\nimport type { WatchOptions } from './types';\n\n/**\n * Watches a derived value and invokes `notify` when the exposed value changes\n * under the configured equality function.\n *\n * This helper combines a computed signal with an effect so callers can observe\n * derived state without manually managing previous values.\n */\nexport function watch<Value>(\n\tread: () => Value,\n\tnotify: (nextValue: Value, previousValue: Value | undefined) => void,\n\toptions: WatchOptions<Value> = {},\n): () => void {\n\tconst watchedValue = new Computed(read, { equals: options.equals });\n\tlet previousValue: Value | undefined;\n\tlet initialized = false;\n\n\treturn effect(\n\t\t() => {\n\t\t\tconst nextValue = watchedValue.get();\n\n\t\t\tif (initialized) {\n\t\t\t\tnotify(nextValue, previousValue);\n\t\t\t} else if (options.immediate) {\n\t\t\t\tnotify(nextValue, undefined);\n\t\t\t}\n\n\t\t\tpreviousValue = nextValue;\n\t\t\tinitialized = true;\n\t\t},\n\t\t{ scheduler: options.scheduler },\n\t);\n}\n",
|
|
12
|
+
"import { state } from '../state';\nimport { watch } from '../watch';\nimport type { Signal } from '../types';\n\n/** Lifecycle status of an async operation. */\nexport type AsyncStatus = 'idle' | 'pending' | 'success' | 'error';\n\n/** Options passed to the fetcher function. */\nexport interface AsyncStateFetcherOptions {\n\tsignal: AbortSignal;\n}\n\ntype UnsourcedFetcher<T> = (options: AsyncStateFetcherOptions) => Promise<T>;\ntype SourcedFetcher<S, T> = (sourceValue: S, options: AsyncStateFetcherOptions) => Promise<T>;\n\ninterface AsyncStateBaseConfig<T> {\n\t/** Seed value for `data` before the first successful resolution. */\n\tinitialValue?: T;\n\n\t/**\n\t * Milliseconds a successful response is considered fresh. While fresh,\n\t * source changes that resolve to an already-cached key skip the network\n\t * entirely and serve the cached value synchronously.\n\t *\n\t * Defaults to `0` (always refetch). Set to `Infinity` to cache forever.\n\t *\n\t * Only meaningful for sourced queries — the source value is serialized\n\t * with `JSON.stringify` to produce the cache key.\n\t */\n\tstaleTime?: number;\n\n\t/**\n\t * Milliseconds to wait before transitioning `status` to `'pending'`.\n\t *\n\t * If the response arrives within this window the status jumps straight\n\t * from its previous value to `'success'` or `'error'`, avoiding a\n\t * flash-of-loading-state for fast responses.\n\t *\n\t * Defaults to `0` (transition immediately).\n\t */\n\tpendingDelay?: number;\n\n\t/** Called after each successful resolution, including cache hits. */\n\tonSuccess?: (data: T) => void;\n\n\t/** Called after each failed resolution. Not called for aborted requests. */\n\tonError?: (error: unknown) => void;\n\n\t/**\n\t * Called after each resolution, whether successful or failed.\n\t *\n\t * Exactly one of `data` / `error` is defined per call.\n\t */\n\tonSettled?: (data: T | undefined, error: unknown) => void;\n}\n\n/** Configuration for an unsourced `asyncState` that fetches immediately. */\nexport interface AsyncStateConfig<T> extends AsyncStateBaseConfig<T> {\n\tfetcher: UnsourcedFetcher<T>;\n\tsource?: undefined;\n}\n\n/** Configuration for a sourced `asyncState` driven by a reactive source. */\nexport interface AsyncStateSourcedConfig<S, T> extends AsyncStateBaseConfig<T> {\n\t/**\n\t * Reactive source function. The fetcher is invoked whenever `source`\n\t * emits a new truthy value. Falsy values (`false`, `null`, `undefined`)\n\t * disable fetching and preserve the current state.\n\t */\n\tsource: () => S | false | null | undefined;\n\n\tfetcher: SourcedFetcher<S, T>;\n}\n\n/** Reactive handle returned by `asyncState`. */\nexport interface AsyncStateResult<T> {\n\t/** Latest resolved value. Retains last success while refetching. */\n\treadonly data: Signal<T | undefined>;\n\n\t/** Current lifecycle status. */\n\treadonly status: Signal<AsyncStatus>;\n\n\t/** Latest error. Cleared when a new fetch starts. */\n\treadonly error: Signal<unknown>;\n\n\t/** Trigger a new fetch, aborting any in-flight request. */\n\trefetch(): void;\n\n\t/** Abort the current in-flight request without changing status. */\n\tabort(): void;\n\n\t/** Dispose all subscriptions and abort any pending request. */\n\tdispose(): void;\n}\n\n/**\n * Creates a reactive async state that fetches immediately on creation.\n *\n * Call `.refetch()` to re-execute. The previous in-flight request is aborted\n * automatically and `AbortError` exceptions are silently discarded.\n */\nexport function asyncState<T>(config: AsyncStateConfig<T>): AsyncStateResult<T>;\n\n/**\n * Creates a reactive async state driven by a reactive `source`.\n *\n * The fetcher is invoked whenever `source` emits a new truthy value. Falsy\n * values (`false`, `null`, `undefined`) disable fetching and preserve the\n * current state.\n */\nexport function asyncState<S, T>(config: AsyncStateSourcedConfig<S, T>): AsyncStateResult<T>;\n\nexport function asyncState<S, T>(config: AsyncStateConfig<T> | AsyncStateSourcedConfig<S, T>): AsyncStateResult<T> {\n\tconst hasSource = config.source !== undefined;\n\tconst source = hasSource ? (config as AsyncStateSourcedConfig<S, T>).source : undefined;\n\tconst fetcher = config.fetcher as UnsourcedFetcher<T> | SourcedFetcher<S, T>;\n\n\tconst staleTime = config.staleTime ?? 0;\n\tconst pendingDelay = config.pendingDelay ?? 0;\n\n\tconst dataState = state<T | undefined>(config.initialValue);\n\tconst statusState = state<AsyncStatus>('idle');\n\tconst errorState = state<unknown>(undefined);\n\n\tconst cache = new Map<string, { value: T; timestamp: number }>();\n\n\tlet activeController: AbortController | null = null;\n\tlet requestVersion = 0;\n\tlet pendingTimer: ReturnType<typeof setTimeout> | undefined;\n\tconst disposers: (() => void)[] = [];\n\n\tconst clearPendingTimer = () => {\n\t\tif (pendingTimer !== undefined) {\n\t\t\tclearTimeout(pendingTimer);\n\t\t\tpendingTimer = undefined;\n\t\t}\n\t};\n\n\tconst commitPending = () => {\n\t\tpendingTimer = undefined;\n\t\tstatusState.set('pending');\n\t};\n\n\tconst schedulePending = () => {\n\t\tclearPendingTimer();\n\t\terrorState.set(undefined);\n\n\t\tif (pendingDelay <= 0) {\n\t\t\tstatusState.set('pending');\n\t\t\treturn;\n\t\t}\n\n\t\tpendingTimer = setTimeout(commitPending, pendingDelay);\n\t};\n\n\tconst execute = (sourceValue?: S) => {\n\t\tconst canUseCache = hasSource && staleTime > 0;\n\t\tconst cacheKey = hasSource ? JSON.stringify(sourceValue) : '';\n\n\t\tif (canUseCache) {\n\t\t\tconst cached = cache.get(cacheKey);\n\t\t\tif (cached && Date.now() - cached.timestamp < staleTime) {\n\t\t\t\trequestVersion += 1;\n\t\t\t\tactiveController?.abort();\n\t\t\t\tactiveController = null;\n\t\t\t\tclearPendingTimer();\n\t\t\t\tdataState.set(cached.value);\n\t\t\t\tstatusState.set('success');\n\t\t\t\terrorState.set(undefined);\n\t\t\t\tconfig.onSuccess?.(cached.value);\n\t\t\t\tconfig.onSettled?.(cached.value, undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst version = ++requestVersion;\n\n\t\tactiveController?.abort();\n\t\tactiveController = new AbortController();\n\n\t\tschedulePending();\n\n\t\tconst controller = activeController;\n\t\tconst fetcherOptions: AsyncStateFetcherOptions = { signal: controller.signal };\n\n\t\tconst promise = hasSource\n\t\t\t? (fetcher as SourcedFetcher<S, T>)(sourceValue as S, fetcherOptions)\n\t\t\t: (fetcher as UnsourcedFetcher<T>)(fetcherOptions);\n\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tif (version !== requestVersion || controller.signal.aborted) return;\n\t\t\t\tclearPendingTimer();\n\n\t\t\t\tif (canUseCache) {\n\t\t\t\t\tcache.set(cacheKey, { value, timestamp: Date.now() });\n\t\t\t\t}\n\n\t\t\t\tdataState.set(value);\n\t\t\t\tstatusState.set('success');\n\t\t\t\tactiveController = null;\n\t\t\t\tconfig.onSuccess?.(value);\n\t\t\t\tconfig.onSettled?.(value, undefined);\n\t\t\t},\n\t\t\t(err) => {\n\t\t\t\tif (version !== requestVersion || controller.signal.aborted) return;\n\t\t\t\tif (err instanceof DOMException && err.name === 'AbortError') return;\n\t\t\t\tclearPendingTimer();\n\t\t\t\terrorState.set(err);\n\t\t\t\tstatusState.set('error');\n\t\t\t\tactiveController = null;\n\t\t\t\tconfig.onError?.(err);\n\t\t\t\tconfig.onSettled?.(undefined, err);\n\t\t\t},\n\t\t);\n\t};\n\n\tif (source) {\n\t\tconst stopWatch = watch(\n\t\t\tsource,\n\t\t\t(value) => {\n\t\t\t\tif (value === false || value === null || value === undefined) return;\n\t\t\t\texecute(value as S);\n\t\t\t},\n\t\t\t{ immediate: true },\n\t\t);\n\t\tdisposers.push(stopWatch);\n\t} else {\n\t\texecute();\n\t}\n\n\treturn {\n\t\tdata: dataState,\n\t\tstatus: statusState,\n\t\terror: errorState,\n\t\trefetch() {\n\t\t\tif (source) {\n\t\t\t\tconst value = source();\n\t\t\t\tif (value === false || value === null || value === undefined) return;\n\t\t\t\texecute(value as S);\n\t\t\t} else {\n\t\t\t\texecute();\n\t\t\t}\n\t\t},\n\t\tabort() {\n\t\t\tactiveController?.abort();\n\t\t\tactiveController = null;\n\t\t},\n\t\tdispose() {\n\t\t\tclearPendingTimer();\n\t\t\tactiveController?.abort();\n\t\t\tactiveController = null;\n\t\t\tfor (const fn of disposers) fn();\n\t\t\tdisposers.length = 0;\n\t\t},\n\t};\n}\n",
|
|
13
|
+
"import { assertSignalAccessAllowed, trackActiveDependency } from './runtime';\nimport type { DependencyNode } from './types';\n\n/**\n * Registers a custom dependency node with the currently active computation.\n *\n * This is intended for framework and renderer adapters that need plain reactive\n * sources to participate in `Computed`, `effect(...)`, or watcher dependency\n * discovery without first wrapping those sources in a full signal instance.\n */\nexport function trackDependency(dependency: DependencyNode): void {\n\tassertSignalAccessAllowed();\n\ttrackActiveDependency(dependency);\n}\n",
|
|
14
|
+
"import { getActiveDependencyRecorder, setActiveDependencyRecorder } from './runtime';\nimport type { Signal } from './types';\n\n/**\n * Reads signals without registering their reads as dependencies of the current\n * computed signal or effect.\n *\n * This is the escape hatch for code that needs the latest value without making\n * future writes retrigger the active reactive computation.\n */\nexport function untrack<Value>(read: () => Value): Value {\n\tconst previousActiveDependencyRecorder = getActiveDependencyRecorder();\n\tsetActiveDependencyRecorder(undefined);\n\n\ttry {\n\t\treturn read();\n\t} finally {\n\t\tsetActiveDependencyRecorder(previousActiveDependencyRecorder);\n\t}\n}\n\n/**\n * Reads a signal's current value without tracking it as a dependency.\n *\n * This is a convenience wrapper around `untrack(...)` for the common case of a\n * single signal read.\n */\nexport function peek<Value>(signal: Signal<Value>): Value {\n\treturn untrack(() => signal.get());\n}\n",
|
|
15
|
+
"import { State } from './state';\nimport { peek } from './tracking';\nimport type { SignalStore } from './types';\n\nconst STORE_BRANCH_SYMBOL = Symbol.for('@ecopages/signals.store-branch');\nconst STORE_ABSENT = Symbol.for('@ecopages/signals.store-absent');\n\ntype StoreBranch = {\n\treadonly [STORE_BRANCH_SYMBOL]: true;\n\treadonly node: StoreNode<any>;\n};\n\ntype StoreEntryValue = StoreBranch | typeof STORE_ABSENT | unknown;\n\ntype StoreNode<Value extends object> = {\n\tentries: Map<PropertyKey, State<StoreEntryValue>>;\n\tproxy: Value;\n\tshape: State<number>;\n\ttarget: Value;\n\tvalueType: 'array' | 'object';\n};\n\nconst storeNodeLookup = new WeakMap<object, StoreNode<object>>();\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Returns `true` when `value` is a deep reactive store created by this package.\n *\n * This is useful for adapters that need to accept either plain objects or\n * signal-backed store proxies.\n */\nexport function isStore(value: unknown): value is SignalStore<object> {\n\treturn typeof value === 'object' && value !== null && storeNodeLookup.has(value);\n}\n\n/**\n * Creates a deep reactive store backed by nested state signals.\n *\n * Plain object and array branches are wrapped recursively so property reads,\n * keyed access, and structural changes can participate in dependency tracking.\n */\nexport function createStore<Value extends object>(initialValue: Value): SignalStore<Value> {\n\treturn createStoreNode(initialValue).proxy;\n}\n\n/**\n * Materializes the current plain snapshot of a signal store or nested store\n * value.\n *\n * The returned value is detached from the reactive proxy graph, making it safe\n * to serialize or compare outside the live store.\n */\nexport function snapshot<Value>(value: Value): Value {\n\tif (isStore(value)) {\n\t\treturn snapshotStoreNode(getStoreNode(value)) as Value;\n\t}\n\n\treturn snapshotNestedValue(value);\n}\n\nfunction createStoreBranch(value: object): StoreBranch {\n\treturn {\n\t\t[STORE_BRANCH_SYMBOL]: true,\n\t\tnode: createStoreNode(value),\n\t};\n}\n\nfunction createStoreEntryValue(value: unknown): StoreEntryValue {\n\tconst normalizedValue = isStore(value) ? snapshot(value) : value;\n\n\tif (isStoreBranchValue(normalizedValue)) {\n\t\treturn createStoreBranch(normalizedValue);\n\t}\n\n\treturn normalizedValue;\n}\n\nfunction createStoreNode<Value extends object>(initialValue: Value): StoreNode<Value> {\n\tconst valueType = Array.isArray(initialValue) ? 'array' : 'object';\n\tconst target = (valueType === 'array' ? [] : {}) as Value;\n\tconst node: StoreNode<Value> = {\n\t\tentries: new Map(),\n\t\tproxy: undefined as unknown as Value,\n\t\tshape: new State(0),\n\t\ttarget,\n\t\tvalueType,\n\t};\n\n\tfor (const key of Reflect.ownKeys(initialValue)) {\n\t\tconst entry = new State<StoreEntryValue>(\n\t\t\tcreateStoreEntryValue((initialValue as Record<PropertyKey, unknown>)[key]),\n\t\t);\n\t\tnode.entries.set(key, entry);\n\t\tReflect.set(target as object, key, unwrapStoreEntryValue(peek(entry)));\n\t}\n\n\tif (Array.isArray(initialValue)) {\n\t\t(target as unknown as any[]).length = initialValue.length;\n\t}\n\n\tnode.proxy = new Proxy(target, createStoreProxyHandler(node));\n\tstoreNodeLookup.set(node.proxy as object, node as StoreNode<object>);\n\treturn node;\n}\n\nfunction createStoreProxyHandler<Value extends object>(node: StoreNode<Value>): ProxyHandler<Value> {\n\treturn {\n\t\tdefineProperty(target, key, descriptor) {\n\t\t\tif ('value' in descriptor) {\n\t\t\t\treturn applyStoreSet(node, key, descriptor.value);\n\t\t\t}\n\n\t\t\tconst result = Reflect.defineProperty(target, key, descriptor);\n\n\t\t\tif (result) {\n\t\t\t\tnode.shape.update((value) => value + 1);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\n\t\tdeleteProperty(_target, key) {\n\t\t\treturn deleteStoreProperty(node, key);\n\t\t},\n\n\t\tget(target, key, receiver) {\n\t\t\tif (node.valueType === 'array' && key === 'length') {\n\t\t\t\tnode.shape.get();\n\t\t\t\treturn Reflect.get(target, key, receiver);\n\t\t\t}\n\n\t\t\tif (hasOwnProperty.call(target, key)) {\n\t\t\t\tconst entry = ensureStoreEntry(node, key);\n\t\t\t\tconst value = entry.get();\n\n\t\t\t\tif (value === STORE_ABSENT) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\treturn unwrapStoreEntryValue(value);\n\t\t\t}\n\n\t\t\treturn Reflect.get(target, key, receiver);\n\t\t},\n\n\t\tgetOwnPropertyDescriptor(target, key) {\n\t\t\tnode.shape.get();\n\t\t\treturn Reflect.getOwnPropertyDescriptor(target, key);\n\t\t},\n\n\t\thas(target, key) {\n\t\t\tnode.shape.get();\n\t\t\treturn Reflect.has(target, key);\n\t\t},\n\n\t\townKeys(target) {\n\t\t\tnode.shape.get();\n\t\t\treturn Reflect.ownKeys(target);\n\t\t},\n\n\t\tset(_target, key, value) {\n\t\t\treturn applyStoreSet(node, key, value);\n\t\t},\n\t};\n}\n\nfunction getStoreNode(value: object): StoreNode<object> {\n\tconst node = storeNodeLookup.get(value);\n\n\tif (!node) {\n\t\tthrow new Error('Value is not a signal store.');\n\t}\n\n\treturn node;\n}\n\nfunction applyStoreSet<Value extends object>(node: StoreNode<Value>, key: PropertyKey, value: unknown): boolean {\n\tif (node.valueType === 'array' && key === 'length') {\n\t\treturn setStoreArrayLength(node as StoreNode<any[]>, value);\n\t}\n\n\tconst entry = ensureStoreEntry(node, key);\n\tconst hadOwn = hasOwnProperty.call(node.target, key);\n\tconst previousLength = node.valueType === 'array' ? (node.target as unknown as any[]).length : undefined;\n\tconst nextEntryValue = createStoreEntryValue(value);\n\tentry.set(nextEntryValue);\n\tReflect.set(node.target as object, key, unwrapStoreEntryValue(nextEntryValue));\n\n\tif (!hadOwn) {\n\t\tnode.shape.update((current) => current + 1);\n\t}\n\n\tif (node.valueType === 'array') {\n\t\tconst nextLength = (node.target as unknown as any[]).length;\n\n\t\tif (previousLength !== nextLength) {\n\t\t\tnode.shape.update((current) => current + 1);\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction deleteStoreProperty<Value extends object>(node: StoreNode<Value>, key: PropertyKey): boolean {\n\tif (!hasOwnProperty.call(node.target, key)) {\n\t\treturn true;\n\t}\n\n\tconst entry = ensureStoreEntry(node, key);\n\tentry.set(STORE_ABSENT);\n\tReflect.deleteProperty(node.target as object, key);\n\tif (node.valueType === 'array' && key === 'length') {\n\t\t(node.target as unknown as any[]).length = 0;\n\t}\n\tnode.shape.update((current) => current + 1);\n\treturn true;\n}\n\nfunction ensureStoreEntry<Value extends object>(node: StoreNode<Value>, key: PropertyKey): State<StoreEntryValue> {\n\tlet entry = node.entries.get(key);\n\n\tif (!entry) {\n\t\tentry = new State<StoreEntryValue>(STORE_ABSENT);\n\t\tnode.entries.set(key, entry);\n\t}\n\n\treturn entry;\n}\n\nfunction isStoreBranch(value: unknown): value is StoreBranch {\n\treturn typeof value === 'object' && value !== null && STORE_BRANCH_SYMBOL in value;\n}\n\nfunction isStoreBranchValue(value: unknown): value is object {\n\tif (value === null || typeof value !== 'object') {\n\t\treturn false;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn true;\n\t}\n\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nfunction setStoreArrayLength(node: StoreNode<any[]>, value: unknown): boolean {\n\tconst nextLength = Number(value);\n\n\tif (!Number.isInteger(nextLength) || nextLength < 0) {\n\t\tthrow new RangeError('Invalid array length.');\n\t}\n\n\tconst target = node.target as unknown as any[];\n\tconst previousLength = target.length;\n\n\tif (nextLength < previousLength) {\n\t\tfor (let index = previousLength - 1; index >= nextLength; index -= 1) {\n\t\t\tdeleteStoreProperty(node, String(index));\n\t\t}\n\t}\n\n\ttarget.length = nextLength;\n\n\tif (previousLength !== nextLength) {\n\t\tnode.shape.update((current) => current + 1);\n\t}\n\n\treturn true;\n}\n\nfunction snapshotNestedValue<Value>(value: Value): Value {\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item) => snapshotNestedValue(item)) as Value;\n\t}\n\n\tif (isStore(value)) {\n\t\treturn snapshot(value);\n\t}\n\n\tif (isStoreBranchValue(value)) {\n\t\tconst result: Record<PropertyKey, unknown> = {};\n\n\t\tfor (const key of Reflect.ownKeys(value)) {\n\t\t\tresult[key] = snapshotNestedValue((value as Record<PropertyKey, unknown>)[key]);\n\t\t}\n\n\t\treturn result as Value;\n\t}\n\n\treturn value;\n}\n\nfunction snapshotStoreNode<Value extends object>(node: StoreNode<Value>): Value {\n\tconst result = (node.valueType === 'array' ? [] : {}) as Value;\n\n\tfor (const key of Reflect.ownKeys(node.target)) {\n\t\tconst entry = ensureStoreEntry(node, key);\n\t\tconst value = peek(entry);\n\n\t\tif (value === STORE_ABSENT) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tReflect.set(result as object, key, snapshotNestedValue(unwrapStoreEntryValue(value)));\n\t}\n\n\tif (node.valueType === 'array') {\n\t\t(result as unknown as any[]).length = (node.target as unknown as any[]).length;\n\t}\n\n\treturn result;\n}\n\nfunction unwrapStoreEntryValue(value: StoreEntryValue): unknown {\n\tif (value === STORE_ABSENT) {\n\t\treturn undefined;\n\t}\n\n\tif (isStoreBranch(value)) {\n\t\treturn value.node.proxy;\n\t}\n\n\treturn value;\n}\n",
|
|
16
|
+
"import { assertSignalAccessAllowed, runWatcherNotify } from './runtime';\nimport { resolveSignalNode } from './signal-node';\nimport type { Signal } from './types';\n\ntype WatchableSignal = ReturnType<typeof resolveSignalNode>;\n\n/**\n * Low-level watcher used to schedule work when watched signals may have become\n * stale.\n *\n * This follows the proposal-shaped watcher model: every call to `watch(...)`\n * re-arms the watcher by clearing the pending set and resetting its single\n * notification latch for the next invalidation cycle.\n */\nexport class Watcher {\n\tprivate notified = false;\n\tprivate readonly pendingSignals = new Map<WatchableSignal, Signal<unknown>>();\n\tprivate readonly signals = new Map<WatchableSignal, Signal<unknown>>();\n\tprivate readonly unsubscribers = new Map<WatchableSignal, () => void>();\n\n\t/**\n\t * Creates a watcher whose callback runs once per invalidation cycle until the\n\t * watcher is re-armed with `watch(...)`.\n\t */\n\tconstructor(private readonly notifyCallback: (this: Watcher) => void) {}\n\n\t/**\n\t * Returns the watched signals invalidated since the last `watch(...)` reset.\n\t *\n\t * The returned array preserves insertion order for the current pending set.\n\t */\n\tpublic getPending(): Signal<unknown>[] {\n\t\treturn Array.from(this.pendingSignals.values());\n\t}\n\n\t/**\n\t * Stops watching the provided signals.\n\t *\n\t * Removing the last watched signal also clears the pending set and resets the\n\t * notification latch.\n\t */\n\tpublic unwatch(...signals: Signal<unknown>[]): void {\n\t\tassertSignalAccessAllowed();\n\n\t\tfor (const signal of signals) {\n\t\t\tconst node = resolveSignalNode(signal);\n\t\t\tconst unsubscribe = this.unsubscribers.get(node);\n\n\t\t\tif (!unsubscribe) {\n\t\t\t\tthrow new Error('Signal is not watched by this watcher.');\n\t\t\t}\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst node = resolveSignalNode(signal);\n\t\t\tconst unsubscribe = this.unsubscribers.get(node);\n\n\t\t\tunsubscribe?.();\n\t\t\tthis.pendingSignals.delete(node);\n\t\t\tthis.signals.delete(node);\n\t\t\tthis.unsubscribers.delete(node);\n\t\t}\n\n\t\tif (this.signals.size === 0) {\n\t\t\tthis.pendingSignals.clear();\n\t\t\tthis.notified = false;\n\t\t}\n\t}\n\n\t/**\n\t * Starts watching the provided signals.\n\t *\n\t * Re-watching is also a reset point, so `getPending()` only reports\n\t * invalidations that happen after this call.\n\t */\n\tpublic watch(...signals: Signal<unknown>[]): void {\n\t\tassertSignalAccessAllowed();\n\n\t\tfor (const signal of signals) {\n\t\t\tconst node = resolveSignalNode(signal);\n\n\t\t\tif (this.signals.has(node)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.signals.set(node, signal);\n\t\t\tthis.unsubscribers.set(\n\t\t\t\tnode,\n\t\t\t\tnode.addWatcher(() => {\n\t\t\t\t\tthis.handleSignalChange(node);\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.pendingSignals.clear();\n\t\tthis.notified = false;\n\t}\n\n\tprivate handleSignalChange(node: WatchableSignal): void {\n\t\tconst signal = this.signals.get(node);\n\n\t\tif (!signal) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.pendingSignals.set(node, signal);\n\n\t\tif (this.notified) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.notified = true;\n\t\trunWatcherNotify(() => {\n\t\t\tthis.notifyCallback.call(this);\n\t\t});\n\t}\n}\n",
|
|
17
|
+
"export {\n\tasyncState,\n\ttype AsyncStateConfig,\n\ttype AsyncStateFetcherOptions,\n\ttype AsyncStateResult,\n\ttype AsyncStateSourcedConfig,\n\ttype AsyncStatus,\n} from './src/async-state';\nexport { Computed, computed, currentComputed } from './src/computed';\nexport { trackDependency } from './src/dependency';\nexport { effect } from './src/effect';\nexport { State, state } from './src/state';\nexport { createStore, isStore, snapshot } from './src/store';\nexport { peek, untrack } from './src/tracking';\nexport {\n\ttype DependencyNode,\n\ttype EffectCallback,\n\ttype EffectCleanup,\n\ttype EffectOptions,\n\ttype EffectScheduler,\n\ttype Signal,\n\ttype SignalOptions,\n\ttype SignalStore,\n\ttype SignalSubscriber,\n\ttype WatchOptions,\n\ttype WritableSignal,\n\twatched,\n\tunwatched,\n} from './src/types';\nexport { Watcher } from './src/watcher';\nexport { watch } from './src/watch';\n\nimport { currentComputed } from './src/computed';\nimport { trackDependency } from './src/dependency';\nimport { untrack } from './src/tracking';\nimport { watched, unwatched } from './src/types';\nimport { Watcher } from './src/watcher';\n\n/**\n * Proposal-shaped low-level APIs for framework and adapter authors.\n *\n * This groups the lower-level pieces that mirror the TC39 proposal naming\n * without forcing application code onto the subtle surface by default.\n */\nexport const subtle = Object.freeze({\n\tWatcher,\n\tcurrentComputed,\n\ttrackDependency,\n\tuntrack,\n\twatched,\n\tunwatched,\n});\n"
|
|
18
|
+
],
|
|
19
|
+
"mappings": "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",
|
|
20
|
+
"debugId": "78ED94C3C2E5FD6764756E2164756E21",
|
|
21
|
+
"names": []
|
|
22
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Signal } from '../types';
|
|
2
|
+
/** Lifecycle status of an async operation. */
|
|
3
|
+
export type AsyncStatus = 'idle' | 'pending' | 'success' | 'error';
|
|
4
|
+
/** Options passed to the fetcher function. */
|
|
5
|
+
export interface AsyncStateFetcherOptions {
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
type UnsourcedFetcher<T> = (options: AsyncStateFetcherOptions) => Promise<T>;
|
|
9
|
+
type SourcedFetcher<S, T> = (sourceValue: S, options: AsyncStateFetcherOptions) => Promise<T>;
|
|
10
|
+
interface AsyncStateBaseConfig<T> {
|
|
11
|
+
/** Seed value for `data` before the first successful resolution. */
|
|
12
|
+
initialValue?: T;
|
|
13
|
+
/**
|
|
14
|
+
* Milliseconds a successful response is considered fresh. While fresh,
|
|
15
|
+
* source changes that resolve to an already-cached key skip the network
|
|
16
|
+
* entirely and serve the cached value synchronously.
|
|
17
|
+
*
|
|
18
|
+
* Defaults to `0` (always refetch). Set to `Infinity` to cache forever.
|
|
19
|
+
*
|
|
20
|
+
* Only meaningful for sourced queries — the source value is serialized
|
|
21
|
+
* with `JSON.stringify` to produce the cache key.
|
|
22
|
+
*/
|
|
23
|
+
staleTime?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Milliseconds to wait before transitioning `status` to `'pending'`.
|
|
26
|
+
*
|
|
27
|
+
* If the response arrives within this window the status jumps straight
|
|
28
|
+
* from its previous value to `'success'` or `'error'`, avoiding a
|
|
29
|
+
* flash-of-loading-state for fast responses.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to `0` (transition immediately).
|
|
32
|
+
*/
|
|
33
|
+
pendingDelay?: number;
|
|
34
|
+
/** Called after each successful resolution, including cache hits. */
|
|
35
|
+
onSuccess?: (data: T) => void;
|
|
36
|
+
/** Called after each failed resolution. Not called for aborted requests. */
|
|
37
|
+
onError?: (error: unknown) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Called after each resolution, whether successful or failed.
|
|
40
|
+
*
|
|
41
|
+
* Exactly one of `data` / `error` is defined per call.
|
|
42
|
+
*/
|
|
43
|
+
onSettled?: (data: T | undefined, error: unknown) => void;
|
|
44
|
+
}
|
|
45
|
+
/** Configuration for an unsourced `asyncState` that fetches immediately. */
|
|
46
|
+
export interface AsyncStateConfig<T> extends AsyncStateBaseConfig<T> {
|
|
47
|
+
fetcher: UnsourcedFetcher<T>;
|
|
48
|
+
source?: undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Configuration for a sourced `asyncState` driven by a reactive source. */
|
|
51
|
+
export interface AsyncStateSourcedConfig<S, T> extends AsyncStateBaseConfig<T> {
|
|
52
|
+
/**
|
|
53
|
+
* Reactive source function. The fetcher is invoked whenever `source`
|
|
54
|
+
* emits a new truthy value. Falsy values (`false`, `null`, `undefined`)
|
|
55
|
+
* disable fetching and preserve the current state.
|
|
56
|
+
*/
|
|
57
|
+
source: () => S | false | null | undefined;
|
|
58
|
+
fetcher: SourcedFetcher<S, T>;
|
|
59
|
+
}
|
|
60
|
+
/** Reactive handle returned by `asyncState`. */
|
|
61
|
+
export interface AsyncStateResult<T> {
|
|
62
|
+
/** Latest resolved value. Retains last success while refetching. */
|
|
63
|
+
readonly data: Signal<T | undefined>;
|
|
64
|
+
/** Current lifecycle status. */
|
|
65
|
+
readonly status: Signal<AsyncStatus>;
|
|
66
|
+
/** Latest error. Cleared when a new fetch starts. */
|
|
67
|
+
readonly error: Signal<unknown>;
|
|
68
|
+
/** Trigger a new fetch, aborting any in-flight request. */
|
|
69
|
+
refetch(): void;
|
|
70
|
+
/** Abort the current in-flight request without changing status. */
|
|
71
|
+
abort(): void;
|
|
72
|
+
/** Dispose all subscriptions and abort any pending request. */
|
|
73
|
+
dispose(): void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a reactive async state that fetches immediately on creation.
|
|
77
|
+
*
|
|
78
|
+
* Call `.refetch()` to re-execute. The previous in-flight request is aborted
|
|
79
|
+
* automatically and `AbortError` exceptions are silently discarded.
|
|
80
|
+
*/
|
|
81
|
+
export declare function asyncState<T>(config: AsyncStateConfig<T>): AsyncStateResult<T>;
|
|
82
|
+
/**
|
|
83
|
+
* Creates a reactive async state driven by a reactive `source`.
|
|
84
|
+
*
|
|
85
|
+
* The fetcher is invoked whenever `source` emits a new truthy value. Falsy
|
|
86
|
+
* values (`false`, `null`, `undefined`) disable fetching and preserve the
|
|
87
|
+
* current state.
|
|
88
|
+
*/
|
|
89
|
+
export declare function asyncState<S, T>(config: AsyncStateSourcedConfig<S, T>): AsyncStateResult<T>;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SignalNode } from './signal-node';
|
|
2
|
+
import type { ComputedCallback, SignalOptions, SignalSubscriber } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Lazily derived signal backed by other signals read during evaluation.
|
|
5
|
+
*
|
|
6
|
+
* Dependencies are discovered automatically every time the computation runs,
|
|
7
|
+
* and the last successful value or thrown error is cached until one of those
|
|
8
|
+
* dependencies changes.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Computed<Value> extends SignalNode<Value> {
|
|
11
|
+
private readonly compute;
|
|
12
|
+
private readonly dependencyUnsubscribers;
|
|
13
|
+
private readonly dependencyWatcherUnsubscribers;
|
|
14
|
+
private readonly equals;
|
|
15
|
+
private dependencies;
|
|
16
|
+
private computing;
|
|
17
|
+
private error;
|
|
18
|
+
private hasError;
|
|
19
|
+
private initialized;
|
|
20
|
+
private pendingDependencies;
|
|
21
|
+
private stale;
|
|
22
|
+
private value;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a lazily evaluated derived signal.
|
|
25
|
+
*
|
|
26
|
+
* The compute function only reruns when the cached dependency versions become
|
|
27
|
+
* stale, and the optional equality callback controls whether a recomputation
|
|
28
|
+
* publishes a new exposed value.
|
|
29
|
+
*/
|
|
30
|
+
constructor(compute: ComputedCallback<Value>, options?: SignalOptions<Value>);
|
|
31
|
+
/**
|
|
32
|
+
* Returns the cached value, recomputing lazily when tracked dependencies have
|
|
33
|
+
* changed.
|
|
34
|
+
*
|
|
35
|
+
* If the latest evaluation threw, the cached error is rethrown until a
|
|
36
|
+
* dependency invalidates the computed signal.
|
|
37
|
+
*/
|
|
38
|
+
get(): Value;
|
|
39
|
+
/**
|
|
40
|
+
* Subscribes to changes in the computed signal's exposed value.
|
|
41
|
+
*
|
|
42
|
+
* The first subscriber eagerly initializes dependency subscriptions so future
|
|
43
|
+
* source writes can invalidate and republish this computed signal.
|
|
44
|
+
*/
|
|
45
|
+
subscribe(notify: SignalSubscriber<Value>): () => void;
|
|
46
|
+
private clearDependencySubscriptions;
|
|
47
|
+
private clearDependencyWatcherSubscriptions;
|
|
48
|
+
private handleDependencyChange;
|
|
49
|
+
private handleDependencyWatcherChange;
|
|
50
|
+
private haveDependenciesChanged;
|
|
51
|
+
private recompute;
|
|
52
|
+
private refreshIfNeeded;
|
|
53
|
+
private syncDependencySubscriptions;
|
|
54
|
+
private syncDependencyWatcherSubscriptions;
|
|
55
|
+
private trackDependency;
|
|
56
|
+
protected handleFirstWatcherAdded(): void;
|
|
57
|
+
protected handleLastWatcherRemoved(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a computed signal.
|
|
61
|
+
*
|
|
62
|
+
* This is equivalent to `new Computed(...)` and can be convenient in codebases
|
|
63
|
+
* that prefer functional construction over classes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function computed<Value>(computeValue: ComputedCallback<Value>, options?: SignalOptions<Value>): Computed<Value>;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the computed signal currently being evaluated, if any.
|
|
68
|
+
*
|
|
69
|
+
* This is mainly useful inside advanced derived helpers that need access to
|
|
70
|
+
* the active computed during dependency discovery.
|
|
71
|
+
*/
|
|
72
|
+
export declare function currentComputed(): Computed<unknown> | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DependencyNode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Registers a custom dependency node with the currently active computation.
|
|
4
|
+
*
|
|
5
|
+
* This is intended for framework and renderer adapters that need plain reactive
|
|
6
|
+
* sources to participate in `Computed`, `effect(...)`, or watcher dependency
|
|
7
|
+
* discovery without first wrapping those sources in a full signal instance.
|
|
8
|
+
*/
|
|
9
|
+
export declare function trackDependency(dependency: DependencyNode): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EffectCallback, EffectOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a reactive side effect and reschedules it when one of the signals read
|
|
4
|
+
* during execution changes.
|
|
5
|
+
*
|
|
6
|
+
* The callback may return a cleanup function, which runs before the next
|
|
7
|
+
* execution and again when the returned disposer is called.
|
|
8
|
+
*/
|
|
9
|
+
export declare function effect(callback: EffectCallback, options?: EffectOptions): () => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Computed } from './computed';
|
|
2
|
+
import type { DependencyNode, EffectScheduler, SignalEquals } from './types';
|
|
3
|
+
export declare const defaultEquals: SignalEquals<unknown>;
|
|
4
|
+
export declare const scheduleMicrotask: EffectScheduler;
|
|
5
|
+
export declare function assertSignalAccessAllowed(): void;
|
|
6
|
+
export declare function currentComputedSignal(): Computed<unknown> | null;
|
|
7
|
+
export declare function getActiveComputedSignal(): Computed<any> | undefined;
|
|
8
|
+
export declare function setActiveComputedSignal(nextSignal: Computed<any> | undefined): void;
|
|
9
|
+
export declare function getActiveDependencyRecorder(): ((dependency: DependencyNode) => void) | undefined;
|
|
10
|
+
export declare function setActiveDependencyRecorder(nextRecorder: ((dependency: DependencyNode) => void) | undefined): void;
|
|
11
|
+
export declare function trackActiveDependency(dependency: DependencyNode): void;
|
|
12
|
+
export declare function runWatcherNotify(callback: () => void): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DependencyNode, type Signal, type SignalOptions, type SignalSubscriber } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Shared signal implementation used by writable and computed signals.
|
|
4
|
+
*
|
|
5
|
+
* It centralizes subscriber delivery, monotonic versioning, and the low-level
|
|
6
|
+
* watcher hooks consumed by `subtle.Watcher`.
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class SignalNode<Value> implements Signal<Value>, DependencyNode {
|
|
9
|
+
protected readonly subscribers: Set<SignalSubscriber<Value>>;
|
|
10
|
+
protected version: number;
|
|
11
|
+
private readonly watcherListeners;
|
|
12
|
+
private readonly onWatched;
|
|
13
|
+
private readonly onUnwatched;
|
|
14
|
+
protected constructor(options?: SignalOptions<Value>);
|
|
15
|
+
abstract get(): Value;
|
|
16
|
+
abstract subscribe(notify: SignalSubscriber<Value>): () => void;
|
|
17
|
+
addWatcher(notify: () => void): () => void;
|
|
18
|
+
getVersion(): number;
|
|
19
|
+
getWatcherCount(): number;
|
|
20
|
+
protected connectToActiveComputed(): void;
|
|
21
|
+
protected handleFirstWatcherAdded(): void;
|
|
22
|
+
protected handleLastWatcherRemoved(): void;
|
|
23
|
+
protected publish(nextValue: Value): void;
|
|
24
|
+
protected notifyWatchers(): void;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveSignalNode(signal: Signal<unknown>): SignalNode<unknown>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SignalNode } from './signal-node';
|
|
2
|
+
import type { SignalOptions, SignalSubscriber, WritableSignal } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Writable state signal.
|
|
5
|
+
*
|
|
6
|
+
* State signals are the smallest unit of mutable reactive data in this package.
|
|
7
|
+
*/
|
|
8
|
+
export declare class State<Value> extends SignalNode<Value> implements WritableSignal<Value> {
|
|
9
|
+
private readonly equals;
|
|
10
|
+
private value;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a writable signal with an initial value.
|
|
13
|
+
*
|
|
14
|
+
* The optional `equals` callback can suppress redundant writes, and the
|
|
15
|
+
* watcher hooks integrate with `subtle.Watcher` lifecycle events.
|
|
16
|
+
*/
|
|
17
|
+
constructor(initialValue: Value, options?: SignalOptions<Value>);
|
|
18
|
+
/**
|
|
19
|
+
* Returns the current value and records this state as a dependency when a
|
|
20
|
+
* computation is actively collecting dependencies.
|
|
21
|
+
*/
|
|
22
|
+
get(): Value;
|
|
23
|
+
/**
|
|
24
|
+
* Replaces the current value.
|
|
25
|
+
*
|
|
26
|
+
* Watchers are notified before imperative subscribers so low-level invalidation
|
|
27
|
+
* can observe the stale transition before push listeners run.
|
|
28
|
+
*/
|
|
29
|
+
set(nextValue: Value): void;
|
|
30
|
+
/**
|
|
31
|
+
* Updates the current value using the previous one and then forwards to
|
|
32
|
+
* `set(...)` so equality checks and notifications stay consistent.
|
|
33
|
+
*/
|
|
34
|
+
update(updater: (value: Value) => Value): void;
|
|
35
|
+
/**
|
|
36
|
+
* Registers an imperative subscriber.
|
|
37
|
+
*
|
|
38
|
+
* Subscriptions do not trigger an immediate call and are independent from the
|
|
39
|
+
* automatic dependency tracking used by computed values and effects.
|
|
40
|
+
*/
|
|
41
|
+
subscribe(notify: SignalSubscriber<Value>): () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a writable state signal.
|
|
45
|
+
*
|
|
46
|
+
* This is equivalent to `new State(...)` and can be useful in codebases that
|
|
47
|
+
* prefer factory-style construction.
|
|
48
|
+
*/
|
|
49
|
+
export declare function state<Value>(initialValue: Value, options?: SignalOptions<Value>): State<Value>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SignalStore } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Returns `true` when `value` is a deep reactive store created by this package.
|
|
4
|
+
*
|
|
5
|
+
* This is useful for adapters that need to accept either plain objects or
|
|
6
|
+
* signal-backed store proxies.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isStore(value: unknown): value is SignalStore<object>;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a deep reactive store backed by nested state signals.
|
|
11
|
+
*
|
|
12
|
+
* Plain object and array branches are wrapped recursively so property reads,
|
|
13
|
+
* keyed access, and structural changes can participate in dependency tracking.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createStore<Value extends object>(initialValue: Value): SignalStore<Value>;
|
|
16
|
+
/**
|
|
17
|
+
* Materializes the current plain snapshot of a signal store or nested store
|
|
18
|
+
* value.
|
|
19
|
+
*
|
|
20
|
+
* The returned value is detached from the reactive proxy graph, making it safe
|
|
21
|
+
* to serialize or compare outside the live store.
|
|
22
|
+
*/
|
|
23
|
+
export declare function snapshot<Value>(value: Value): Value;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Signal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Reads signals without registering their reads as dependencies of the current
|
|
4
|
+
* computed signal or effect.
|
|
5
|
+
*
|
|
6
|
+
* This is the escape hatch for code that needs the latest value without making
|
|
7
|
+
* future writes retrigger the active reactive computation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function untrack<Value>(read: () => Value): Value;
|
|
10
|
+
/**
|
|
11
|
+
* Reads a signal's current value without tracking it as a dependency.
|
|
12
|
+
*
|
|
13
|
+
* This is a convenience wrapper around `untrack(...)` for the common case of a
|
|
14
|
+
* single signal read.
|
|
15
|
+
*/
|
|
16
|
+
export declare function peek<Value>(signal: Signal<Value>): Value;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Computed } from './computed';
|
|
2
|
+
/**
|
|
3
|
+
* Imperative listener invoked after a signal publishes a new exposed value.
|
|
4
|
+
*
|
|
5
|
+
* Subscribers are separate from automatic dependency tracking and are mainly
|
|
6
|
+
* useful for adapters, bridge code, or tests that need push-based updates.
|
|
7
|
+
*/
|
|
8
|
+
export type SignalSubscriber<Value> = (value: Value) => void;
|
|
9
|
+
/** Hook invoked when a signal becomes watched through `subtle.Watcher`. */
|
|
10
|
+
export declare const watched: unique symbol;
|
|
11
|
+
/** Hook invoked when a signal stops being watched through `subtle.Watcher`. */
|
|
12
|
+
export declare const unwatched: unique symbol;
|
|
13
|
+
export type SignalEquals<Value> = (this: Signal<Value>, previousValue: Value, nextValue: Value) => boolean;
|
|
14
|
+
export type ComputedCallback<Value> = (this: Computed<Value>) => Value;
|
|
15
|
+
/**
|
|
16
|
+
* Optional configuration shared by writable and computed signals.
|
|
17
|
+
*
|
|
18
|
+
* These hooks control equality and low-level watcher lifecycle integration.
|
|
19
|
+
*/
|
|
20
|
+
export interface SignalOptions<Value> {
|
|
21
|
+
/**
|
|
22
|
+
* Equality comparison used to suppress redundant updates.
|
|
23
|
+
*
|
|
24
|
+
* Defaults to `Object.is`.
|
|
25
|
+
*/
|
|
26
|
+
equals?: SignalEquals<Value>;
|
|
27
|
+
/** Called when the signal becomes watched through `subtle.Watcher`. */
|
|
28
|
+
[watched]?: (this: Signal<Value>) => void;
|
|
29
|
+
/** Called when the signal is no longer watched through `subtle.Watcher`. */
|
|
30
|
+
[unwatched]?: (this: Signal<Value>) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read-only signal contract.
|
|
34
|
+
*
|
|
35
|
+
* Reading from a signal participates in dependency discovery when a computed
|
|
36
|
+
* signal or effect is currently collecting dependencies.
|
|
37
|
+
*/
|
|
38
|
+
export interface Signal<Value> {
|
|
39
|
+
/**
|
|
40
|
+
* Returns the current value.
|
|
41
|
+
*
|
|
42
|
+
* Reads performed during a tracked computation register this signal as a
|
|
43
|
+
* dependency of that computation.
|
|
44
|
+
*/
|
|
45
|
+
get(): Value;
|
|
46
|
+
/**
|
|
47
|
+
* Subscribes to exposed value changes.
|
|
48
|
+
*
|
|
49
|
+
* Subscribers are only called when the signal's value changes according to
|
|
50
|
+
* its configured equality function.
|
|
51
|
+
*/
|
|
52
|
+
subscribe(notify: SignalSubscriber<Value>): () => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read-write signal contract.
|
|
56
|
+
*
|
|
57
|
+
* Writable signals expose direct writes in addition to dependency-tracked
|
|
58
|
+
* reads.
|
|
59
|
+
*/
|
|
60
|
+
export interface WritableSignal<Value> extends Signal<Value> {
|
|
61
|
+
/**
|
|
62
|
+
* Replaces the current value.
|
|
63
|
+
*
|
|
64
|
+
* No invalidation happens when the configured equality function reports the
|
|
65
|
+
* new value as equal to the current one.
|
|
66
|
+
*/
|
|
67
|
+
set(nextValue: Value): void;
|
|
68
|
+
/**
|
|
69
|
+
* Replaces the current value by deriving the next one from the current one.
|
|
70
|
+
*/
|
|
71
|
+
update(updater: (value: Value) => Value): void;
|
|
72
|
+
}
|
|
73
|
+
/** Scheduler used to defer effect re-execution. */
|
|
74
|
+
export type EffectScheduler = (run: () => void) => void;
|
|
75
|
+
/** Cleanup function returned from an effect body. */
|
|
76
|
+
export type EffectCleanup = void | (() => void);
|
|
77
|
+
/** Callback executed by an effect. */
|
|
78
|
+
export type EffectCallback = () => EffectCleanup;
|
|
79
|
+
/**
|
|
80
|
+
* Configuration for an effect.
|
|
81
|
+
*
|
|
82
|
+
* Effects default to microtask scheduling so multiple synchronous writes can
|
|
83
|
+
* collapse into a single rerun.
|
|
84
|
+
*/
|
|
85
|
+
export interface EffectOptions {
|
|
86
|
+
/** Scheduler used after a dependency changes. Defaults to a microtask queue. */
|
|
87
|
+
scheduler?: EffectScheduler;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Configuration for `watch(...)`.
|
|
91
|
+
*
|
|
92
|
+
* Watches reuse computed-style equality and effect-style scheduling.
|
|
93
|
+
*/
|
|
94
|
+
export interface WatchOptions<Value> extends SignalOptions<Value> {
|
|
95
|
+
/**
|
|
96
|
+
* When `true`, invokes the callback during the initial run with an undefined
|
|
97
|
+
* previous value.
|
|
98
|
+
*/
|
|
99
|
+
immediate?: boolean;
|
|
100
|
+
/** Scheduler used after the watched value changes. */
|
|
101
|
+
scheduler?: EffectScheduler;
|
|
102
|
+
}
|
|
103
|
+
/** Marker interface returned from `createStore(...)`. */
|
|
104
|
+
export type SignalStore<Value extends object> = Value;
|
|
105
|
+
export interface DependencyNode extends Signal<unknown> {
|
|
106
|
+
addWatcher(notify: () => void): () => void;
|
|
107
|
+
getVersion(): number;
|
|
108
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WatchOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Watches a derived value and invokes `notify` when the exposed value changes
|
|
4
|
+
* under the configured equality function.
|
|
5
|
+
*
|
|
6
|
+
* This helper combines a computed signal with an effect so callers can observe
|
|
7
|
+
* derived state without manually managing previous values.
|
|
8
|
+
*/
|
|
9
|
+
export declare function watch<Value>(read: () => Value, notify: (nextValue: Value, previousValue: Value | undefined) => void, options?: WatchOptions<Value>): () => void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Signal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Low-level watcher used to schedule work when watched signals may have become
|
|
4
|
+
* stale.
|
|
5
|
+
*
|
|
6
|
+
* This follows the proposal-shaped watcher model: every call to `watch(...)`
|
|
7
|
+
* re-arms the watcher by clearing the pending set and resetting its single
|
|
8
|
+
* notification latch for the next invalidation cycle.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Watcher {
|
|
11
|
+
private readonly notifyCallback;
|
|
12
|
+
private notified;
|
|
13
|
+
private readonly pendingSignals;
|
|
14
|
+
private readonly signals;
|
|
15
|
+
private readonly unsubscribers;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a watcher whose callback runs once per invalidation cycle until the
|
|
18
|
+
* watcher is re-armed with `watch(...)`.
|
|
19
|
+
*/
|
|
20
|
+
constructor(notifyCallback: (this: Watcher) => void);
|
|
21
|
+
/**
|
|
22
|
+
* Returns the watched signals invalidated since the last `watch(...)` reset.
|
|
23
|
+
*
|
|
24
|
+
* The returned array preserves insertion order for the current pending set.
|
|
25
|
+
*/
|
|
26
|
+
getPending(): Signal<unknown>[];
|
|
27
|
+
/**
|
|
28
|
+
* Stops watching the provided signals.
|
|
29
|
+
*
|
|
30
|
+
* Removing the last watched signal also clears the pending set and resets the
|
|
31
|
+
* notification latch.
|
|
32
|
+
*/
|
|
33
|
+
unwatch(...signals: Signal<unknown>[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Starts watching the provided signals.
|
|
36
|
+
*
|
|
37
|
+
* Re-watching is also a reset point, so `getPending()` only reports
|
|
38
|
+
* invalidations that happen after this call.
|
|
39
|
+
*/
|
|
40
|
+
watch(...signals: Signal<unknown>[]): void;
|
|
41
|
+
private handleSignalChange;
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ecopages/signals",
|
|
3
|
+
"version": "0.3.0-alpha.0",
|
|
4
|
+
"description": "Renderer-agnostic signal primitives based on the TC39 Signals proposal",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/ecopages/radiant.git",
|
|
8
|
+
"directory": "packages/signals"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/ecopages/radiant/tree/main/packages/signals#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ecopages/radiant/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "Andrea Zanenghi",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"module": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/*",
|
|
26
|
+
"README.md",
|
|
27
|
+
"size-budget.json",
|
|
28
|
+
"package.json"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"signals",
|
|
32
|
+
"reactivity",
|
|
33
|
+
"tc39",
|
|
34
|
+
"state-management",
|
|
35
|
+
"radiant"
|
|
36
|
+
],
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build:lib": "bun run clean && bun run build:files && bun run build:types",
|
|
46
|
+
"build:files": "bun run ./build.ts",
|
|
47
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
48
|
+
"clean": "rm -rf ./dist",
|
|
49
|
+
"size:check": "bun run ./check-size.ts",
|
|
50
|
+
"size:report": "bun run ./check-size.ts --report-only",
|
|
51
|
+
"test:lib": "vitest run"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
55
|
+
"vitest": "^3.2.4"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"typescript": "^5"
|
|
59
|
+
}
|
|
60
|
+
}
|