signalium 0.3.8 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +21 -0
- package/build/react.js +19 -0
- package/build/transform.js +19 -0
- package/dist/cjs/config.d.ts +8 -3
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +14 -8
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/debug.d.ts +2 -2
- package/dist/cjs/debug.d.ts.map +1 -1
- package/dist/cjs/debug.js +3 -3
- package/dist/cjs/debug.js.map +1 -1
- package/dist/cjs/hooks.d.ts +14 -42
- package/dist/cjs/hooks.d.ts.map +1 -1
- package/dist/cjs/hooks.js +19 -240
- package/dist/cjs/hooks.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -18
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals/async.d.ts +52 -0
- package/dist/cjs/internals/async.d.ts.map +1 -0
- package/dist/cjs/internals/async.js +394 -0
- package/dist/cjs/internals/async.js.map +1 -0
- package/dist/cjs/internals/connect.d.ts +4 -0
- package/dist/cjs/internals/connect.d.ts.map +1 -0
- package/dist/cjs/internals/connect.js +37 -0
- package/dist/cjs/internals/connect.js.map +1 -0
- package/dist/cjs/internals/consumer.d.ts +6 -0
- package/dist/cjs/internals/consumer.d.ts.map +1 -0
- package/dist/cjs/internals/consumer.js +13 -0
- package/dist/cjs/internals/consumer.js.map +1 -0
- package/dist/cjs/internals/contexts.d.ts +33 -0
- package/dist/cjs/internals/contexts.d.ts.map +1 -0
- package/dist/cjs/internals/contexts.js +103 -0
- package/dist/cjs/internals/contexts.js.map +1 -0
- package/dist/cjs/internals/derived.d.ts +66 -0
- package/dist/cjs/internals/derived.d.ts.map +1 -0
- package/dist/cjs/internals/derived.js +128 -0
- package/dist/cjs/internals/derived.js.map +1 -0
- package/dist/cjs/internals/dirty.d.ts +5 -0
- package/dist/cjs/internals/dirty.d.ts.map +1 -0
- package/dist/cjs/internals/dirty.js +79 -0
- package/dist/cjs/internals/dirty.js.map +1 -0
- package/dist/cjs/internals/edge.d.ts +32 -0
- package/dist/cjs/internals/edge.d.ts.map +1 -0
- package/dist/cjs/internals/edge.js +59 -0
- package/dist/cjs/internals/edge.js.map +1 -0
- package/dist/cjs/internals/get.d.ts +10 -0
- package/dist/cjs/internals/get.d.ts.map +1 -0
- package/dist/cjs/internals/get.js +255 -0
- package/dist/cjs/internals/get.js.map +1 -0
- package/dist/cjs/internals/scheduling.d.ts +12 -0
- package/dist/cjs/internals/scheduling.d.ts.map +1 -0
- package/dist/cjs/internals/scheduling.js +117 -0
- package/dist/cjs/internals/scheduling.js.map +1 -0
- package/dist/cjs/internals/state.d.ts +18 -0
- package/dist/cjs/internals/state.d.ts.map +1 -0
- package/dist/cjs/internals/state.js +88 -0
- package/dist/cjs/internals/state.js.map +1 -0
- package/dist/cjs/internals/utils/debug-name.d.ts +2 -0
- package/dist/cjs/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/cjs/internals/utils/debug-name.js +14 -0
- package/dist/cjs/internals/utils/debug-name.js.map +1 -0
- package/dist/cjs/internals/utils/equals.d.ts +3 -0
- package/dist/cjs/internals/utils/equals.d.ts.map +1 -0
- package/dist/cjs/internals/utils/equals.js +13 -0
- package/dist/cjs/internals/utils/equals.js.map +1 -0
- package/dist/cjs/internals/utils/hash.d.ts +7 -0
- package/dist/cjs/internals/utils/hash.d.ts.map +1 -0
- package/dist/cjs/internals/utils/hash.js +181 -0
- package/dist/cjs/internals/utils/hash.js.map +1 -0
- package/dist/cjs/internals/utils/stringify.d.ts +3 -0
- package/dist/cjs/internals/utils/stringify.d.ts.map +1 -0
- package/dist/cjs/{utils.js → internals/utils/stringify.js} +5 -27
- package/dist/cjs/internals/utils/stringify.js.map +1 -0
- package/dist/cjs/internals/utils/type-utils.d.ts +6 -0
- package/dist/cjs/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/cjs/internals/utils/type-utils.js +22 -0
- package/dist/cjs/internals/utils/type-utils.js.map +1 -0
- package/dist/cjs/react/context.d.ts +1 -1
- package/dist/cjs/react/context.d.ts.map +1 -1
- package/dist/cjs/react/provider.d.ts +4 -3
- package/dist/cjs/react/provider.d.ts.map +1 -1
- package/dist/cjs/react/provider.js +7 -3
- package/dist/cjs/react/provider.js.map +1 -1
- package/dist/cjs/react/setup.d.ts.map +1 -1
- package/dist/cjs/react/setup.js +2 -1
- package/dist/cjs/react/setup.js.map +1 -1
- package/dist/cjs/react/signal-value.d.ts +5 -1
- package/dist/cjs/react/signal-value.d.ts.map +1 -1
- package/dist/cjs/react/signal-value.js +35 -45
- package/dist/cjs/react/signal-value.js.map +1 -1
- package/dist/cjs/trace.d.ts +32 -28
- package/dist/cjs/trace.d.ts.map +1 -1
- package/dist/cjs/trace.js +14 -16
- package/dist/cjs/trace.js.map +1 -1
- package/dist/cjs/transform.d.ts +6 -0
- package/dist/cjs/transform.d.ts.map +1 -0
- package/dist/cjs/transform.js +92 -0
- package/dist/cjs/transform.js.map +1 -0
- package/dist/cjs/types.d.ts +32 -40
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/config.d.ts +8 -3
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +12 -7
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/debug.d.ts +2 -2
- package/dist/esm/debug.d.ts.map +1 -1
- package/dist/esm/debug.js +2 -2
- package/dist/esm/debug.js.map +1 -1
- package/dist/esm/hooks.d.ts +14 -42
- package/dist/esm/hooks.d.ts.map +1 -1
- package/dist/esm/hooks.js +17 -226
- package/dist/esm/hooks.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internals/async.d.ts +52 -0
- package/dist/esm/internals/async.d.ts.map +1 -0
- package/dist/esm/internals/async.js +387 -0
- package/dist/esm/internals/async.js.map +1 -0
- package/dist/esm/internals/connect.d.ts +4 -0
- package/dist/esm/internals/connect.d.ts.map +1 -0
- package/dist/esm/internals/connect.js +33 -0
- package/dist/esm/internals/connect.js.map +1 -0
- package/dist/esm/internals/consumer.d.ts +6 -0
- package/dist/esm/internals/consumer.d.ts.map +1 -0
- package/dist/esm/internals/consumer.js +9 -0
- package/dist/esm/internals/consumer.js.map +1 -0
- package/dist/esm/internals/contexts.d.ts +33 -0
- package/dist/esm/internals/contexts.d.ts.map +1 -0
- package/dist/esm/internals/contexts.js +92 -0
- package/dist/esm/internals/contexts.js.map +1 -0
- package/dist/esm/internals/derived.d.ts +66 -0
- package/dist/esm/internals/derived.d.ts.map +1 -0
- package/dist/esm/internals/derived.js +118 -0
- package/dist/esm/internals/derived.js.map +1 -0
- package/dist/esm/internals/dirty.d.ts +5 -0
- package/dist/esm/internals/dirty.d.ts.map +1 -0
- package/dist/esm/internals/dirty.js +75 -0
- package/dist/esm/internals/dirty.js.map +1 -0
- package/dist/esm/internals/edge.d.ts +32 -0
- package/dist/esm/internals/edge.d.ts.map +1 -0
- package/dist/esm/internals/edge.js +54 -0
- package/dist/esm/internals/edge.js.map +1 -0
- package/dist/esm/internals/get.d.ts +10 -0
- package/dist/esm/internals/get.d.ts.map +1 -0
- package/dist/esm/internals/get.js +247 -0
- package/dist/esm/internals/get.js.map +1 -0
- package/dist/esm/internals/scheduling.d.ts +12 -0
- package/dist/esm/internals/scheduling.d.ts.map +1 -0
- package/dist/esm/internals/scheduling.js +106 -0
- package/dist/esm/internals/scheduling.js.map +1 -0
- package/dist/esm/internals/state.d.ts +18 -0
- package/dist/esm/internals/state.d.ts.map +1 -0
- package/dist/esm/internals/state.js +82 -0
- package/dist/esm/internals/state.js.map +1 -0
- package/dist/esm/internals/utils/debug-name.d.ts +2 -0
- package/dist/esm/internals/utils/debug-name.d.ts.map +1 -0
- package/dist/esm/internals/utils/debug-name.js +11 -0
- package/dist/esm/internals/utils/debug-name.js.map +1 -0
- package/dist/esm/internals/utils/equals.d.ts +3 -0
- package/dist/esm/internals/utils/equals.d.ts.map +1 -0
- package/dist/esm/internals/utils/equals.js +9 -0
- package/dist/esm/internals/utils/equals.js.map +1 -0
- package/dist/esm/internals/utils/hash.d.ts +7 -0
- package/dist/esm/internals/utils/hash.d.ts.map +1 -0
- package/dist/esm/internals/utils/hash.js +174 -0
- package/dist/esm/internals/utils/hash.js.map +1 -0
- package/dist/esm/internals/utils/stringify.d.ts +3 -0
- package/dist/esm/internals/utils/stringify.d.ts.map +1 -0
- package/dist/esm/{utils.js → internals/utils/stringify.js} +4 -25
- package/dist/esm/internals/utils/stringify.js.map +1 -0
- package/dist/esm/internals/utils/type-utils.d.ts +6 -0
- package/dist/esm/internals/utils/type-utils.d.ts.map +1 -0
- package/dist/esm/internals/utils/type-utils.js +15 -0
- package/dist/esm/internals/utils/type-utils.js.map +1 -0
- package/dist/esm/react/context.d.ts +1 -1
- package/dist/esm/react/context.d.ts.map +1 -1
- package/dist/esm/react/provider.d.ts +4 -3
- package/dist/esm/react/provider.d.ts.map +1 -1
- package/dist/esm/react/provider.js +6 -2
- package/dist/esm/react/provider.js.map +1 -1
- package/dist/esm/react/setup.d.ts.map +1 -1
- package/dist/esm/react/setup.js +3 -2
- package/dist/esm/react/setup.js.map +1 -1
- package/dist/esm/react/signal-value.d.ts +5 -1
- package/dist/esm/react/signal-value.d.ts.map +1 -1
- package/dist/esm/react/signal-value.js +34 -45
- package/dist/esm/react/signal-value.js.map +1 -1
- package/dist/esm/trace.d.ts +32 -28
- package/dist/esm/trace.d.ts.map +1 -1
- package/dist/esm/trace.js +13 -15
- package/dist/esm/trace.js.map +1 -1
- package/dist/esm/transform.d.ts +6 -0
- package/dist/esm/transform.d.ts.map +1 -0
- package/dist/esm/transform.js +89 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +32 -40
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +23 -4
- package/src/__tests__/__snapshots__/context.test.ts.snap +2101 -0
- package/src/__tests__/__snapshots__/nesting.test.ts.snap +16201 -0
- package/src/__tests__/__snapshots__/params-and-state.test.ts.snap +1879 -0
- package/src/__tests__/async-task.test.ts +327 -0
- package/src/__tests__/context.test.ts +517 -0
- package/src/__tests__/nesting.test.ts +298 -0
- package/src/__tests__/params-and-state.test.ts +230 -0
- package/src/__tests__/reactive-async.test.ts +548 -0
- package/src/__tests__/reactive-sync.test.ts +130 -0
- package/src/__tests__/subscription.test.ts +510 -0
- package/src/__tests__/utils/async.ts +1 -1
- package/src/__tests__/utils/instrumented-hooks.ts +229 -124
- package/src/__tests__/utils/permute.ts +25 -14
- package/src/config.ts +19 -9
- package/src/debug.ts +2 -2
- package/src/hooks.ts +46 -380
- package/src/index.ts +7 -24
- package/src/internals/async.ts +558 -0
- package/src/internals/connect.ts +41 -0
- package/src/internals/consumer.ts +13 -0
- package/src/internals/contexts.ts +133 -0
- package/src/internals/derived.ts +208 -0
- package/src/internals/dirty.ts +91 -0
- package/src/internals/edge.ts +109 -0
- package/src/internals/get.ts +298 -0
- package/src/internals/scheduling.ts +140 -0
- package/src/internals/state.ts +111 -0
- package/src/internals/utils/debug-name.ts +14 -0
- package/src/internals/utils/equals.ts +12 -0
- package/src/internals/utils/hash.ts +221 -0
- package/src/{utils.ts → internals/utils/stringify.ts} +3 -29
- package/src/internals/utils/type-utils.ts +19 -0
- package/src/react/__tests__/async.test.tsx +704 -0
- package/src/react/__tests__/basic.test.tsx +95 -0
- package/src/react/__tests__/contexts.test.tsx +99 -0
- package/src/react/__tests__/subscriptions.test.tsx +49 -0
- package/src/react/__tests__/utils.tsx +40 -0
- package/src/react/context.ts +1 -1
- package/src/react/provider.tsx +12 -4
- package/src/react/setup.ts +3 -2
- package/src/react/signal-value.ts +47 -67
- package/src/trace.ts +43 -38
- package/src/transform.ts +113 -0
- package/src/types.ts +56 -46
- package/transform.js +19 -0
- package/vitest.workspace.ts +38 -2
- package/dist/cjs/scheduling.d.ts +0 -11
- package/dist/cjs/scheduling.d.ts.map +0 -1
- package/dist/cjs/scheduling.js +0 -108
- package/dist/cjs/scheduling.js.map +0 -1
- package/dist/cjs/signals.d.ts +0 -73
- package/dist/cjs/signals.d.ts.map +0 -1
- package/dist/cjs/signals.js +0 -632
- package/dist/cjs/signals.js.map +0 -1
- package/dist/cjs/utils.d.ts +0 -4
- package/dist/cjs/utils.d.ts.map +0 -1
- package/dist/cjs/utils.js.map +0 -1
- package/dist/esm/scheduling.d.ts +0 -11
- package/dist/esm/scheduling.d.ts.map +0 -1
- package/dist/esm/scheduling.js +0 -97
- package/dist/esm/scheduling.js.map +0 -1
- package/dist/esm/signals.d.ts +0 -73
- package/dist/esm/signals.d.ts.map +0 -1
- package/dist/esm/signals.js +0 -614
- package/dist/esm/signals.js.map +0 -1
- package/dist/esm/utils.d.ts +0 -4
- package/dist/esm/utils.d.ts.map +0 -1
- package/dist/esm/utils.js.map +0 -1
- package/src/__tests__/hooks/async-computed.test.ts +0 -190
- package/src/__tests__/hooks/async-task.test.ts +0 -334
- package/src/__tests__/hooks/computed.test.ts +0 -126
- package/src/__tests__/hooks/context.test.ts +0 -527
- package/src/__tests__/hooks/nesting.test.ts +0 -303
- package/src/__tests__/hooks/params-and-state.test.ts +0 -168
- package/src/__tests__/hooks/subscription.test.ts +0 -97
- package/src/__tests__/signals/async.test.ts +0 -416
- package/src/__tests__/signals/basic.test.ts +0 -399
- package/src/__tests__/signals/subscription.test.ts +0 -632
- package/src/__tests__/signals/watcher.test.ts +0 -253
- package/src/__tests__/utils/builders.ts +0 -22
- package/src/__tests__/utils/instrumented-signals.ts +0 -291
- package/src/react/__tests__/react.test.tsx +0 -227
- package/src/scheduling.ts +0 -130
- package/src/signals.ts +0 -824
@@ -0,0 +1,298 @@
|
|
1
|
+
import { scheduleListeners, scheduleTracer, scheduleUnwatch, setResolved } from './scheduling.js';
|
2
|
+
import { SignalType, TRACER as TRACER, TracerEventType } from '../trace.js';
|
3
|
+
import { DerivedSignal, SignalFlags, SignalState } from './derived.js';
|
4
|
+
import { createEdge, Edge, EdgeType } from './edge.js';
|
5
|
+
import { watchSignal } from './connect.js';
|
6
|
+
import { ReactivePromise } from './async.js';
|
7
|
+
import { ReactiveValue } from '../types.js';
|
8
|
+
import { isGeneratorResult, isPromise, isReactivePromise } from './utils/type-utils.js';
|
9
|
+
|
10
|
+
export let CURRENT_CONSUMER: DerivedSignal<any, any> | undefined;
|
11
|
+
|
12
|
+
export function getSignal<T, Args extends unknown[]>(signal: DerivedSignal<T, Args>): ReactiveValue<T> {
|
13
|
+
if (CURRENT_CONSUMER !== undefined) {
|
14
|
+
const { ref, computedCount, deps } = CURRENT_CONSUMER;
|
15
|
+
const prevEdge = deps.get(signal);
|
16
|
+
|
17
|
+
const prevConsumedAt = prevEdge?.consumedAt;
|
18
|
+
|
19
|
+
if (prevConsumedAt !== computedCount) {
|
20
|
+
if (prevEdge === undefined) {
|
21
|
+
TRACER?.emit({
|
22
|
+
type: TracerEventType.Connected,
|
23
|
+
id: CURRENT_CONSUMER.tracerMeta!.id,
|
24
|
+
childId: signal.tracerMeta!.id,
|
25
|
+
name: signal.tracerMeta!.desc,
|
26
|
+
params: signal.tracerMeta!.params,
|
27
|
+
nodeType: SignalType.Reactive,
|
28
|
+
});
|
29
|
+
|
30
|
+
if (CURRENT_CONSUMER.watchCount > 0) {
|
31
|
+
watchSignal(signal);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
const updatedAt = checkSignal(signal);
|
36
|
+
const newEdge = createEdge(prevEdge, EdgeType.Signal, signal, updatedAt, computedCount);
|
37
|
+
|
38
|
+
signal.subs.set(ref, newEdge);
|
39
|
+
deps.set(signal, newEdge);
|
40
|
+
}
|
41
|
+
} else {
|
42
|
+
checkSignal(signal);
|
43
|
+
}
|
44
|
+
|
45
|
+
return signal.value as ReactiveValue<T>;
|
46
|
+
}
|
47
|
+
|
48
|
+
export function checkSignal(signal: DerivedSignal<any, any>): number {
|
49
|
+
let { ref, _state: state } = signal;
|
50
|
+
|
51
|
+
if (state < SignalState.Dirty) {
|
52
|
+
return signal.updatedCount;
|
53
|
+
}
|
54
|
+
|
55
|
+
if (state === SignalState.MaybeDirty) {
|
56
|
+
let edge: Edge | undefined = signal.dirtyHead;
|
57
|
+
|
58
|
+
while (edge !== undefined) {
|
59
|
+
if (edge.type === EdgeType.Promise) {
|
60
|
+
const dep = edge.dep;
|
61
|
+
|
62
|
+
// If the dependency is pending, then we need to propagate the pending state to the
|
63
|
+
// parent signal, and we halt the computation here.
|
64
|
+
if (dep.isPending) {
|
65
|
+
const value = signal.value;
|
66
|
+
|
67
|
+
if (value instanceof ReactivePromise) {
|
68
|
+
// Propagate the pending state to the parent signal
|
69
|
+
value._setPending();
|
70
|
+
}
|
71
|
+
|
72
|
+
// Add the signal to the awaitSubs map to be notified when the promise is resolved
|
73
|
+
dep._awaitSubs.set(ref, edge);
|
74
|
+
|
75
|
+
state = SignalState.Pending;
|
76
|
+
signal.dirtyHead = edge;
|
77
|
+
|
78
|
+
// Early return to prevent the signal from being computed and to preserve the dirty state
|
79
|
+
return signal.updatedCount;
|
80
|
+
}
|
81
|
+
|
82
|
+
edge = edge.nextDirty;
|
83
|
+
continue;
|
84
|
+
}
|
85
|
+
|
86
|
+
const dep = edge.dep;
|
87
|
+
const updatedAt = checkSignal(dep);
|
88
|
+
|
89
|
+
dep.subs.set(ref, edge);
|
90
|
+
|
91
|
+
if (edge.updatedAt !== updatedAt) {
|
92
|
+
signal.dirtyHead = edge.nextDirty;
|
93
|
+
state = SignalState.Dirty;
|
94
|
+
break;
|
95
|
+
}
|
96
|
+
|
97
|
+
edge = edge.nextDirty;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
if (state === SignalState.Dirty) {
|
102
|
+
runSignal(signal);
|
103
|
+
}
|
104
|
+
|
105
|
+
signal._state = SignalState.Clean;
|
106
|
+
signal.dirtyHead = undefined;
|
107
|
+
|
108
|
+
if (TRACER !== undefined && signal.tracerMeta?.tracer) {
|
109
|
+
scheduleTracer(signal.tracerMeta.tracer);
|
110
|
+
}
|
111
|
+
|
112
|
+
return signal.updatedCount;
|
113
|
+
}
|
114
|
+
|
115
|
+
export function runSignal(signal: DerivedSignal<any, any[]>) {
|
116
|
+
TRACER?.emit({
|
117
|
+
type: TracerEventType.StartUpdate,
|
118
|
+
id: signal.tracerMeta!.id,
|
119
|
+
});
|
120
|
+
|
121
|
+
const prevConsumer = CURRENT_CONSUMER;
|
122
|
+
|
123
|
+
const updatedCount = signal.updatedCount;
|
124
|
+
const computedCount = ++signal.computedCount;
|
125
|
+
|
126
|
+
try {
|
127
|
+
CURRENT_CONSUMER = signal;
|
128
|
+
|
129
|
+
const initialized = updatedCount !== 0;
|
130
|
+
const prevValue = signal.value;
|
131
|
+
let nextValue = signal.compute(...signal.args);
|
132
|
+
let valueIsPromise = false;
|
133
|
+
|
134
|
+
if (nextValue !== null && typeof nextValue === 'object') {
|
135
|
+
if (isGeneratorResult(nextValue)) {
|
136
|
+
nextValue = generatorResultToPromise(nextValue, signal);
|
137
|
+
valueIsPromise = true;
|
138
|
+
} else if (isPromise(nextValue)) {
|
139
|
+
valueIsPromise = true;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
if (valueIsPromise) {
|
144
|
+
if (TRACER !== undefined) {
|
145
|
+
TRACER.emit({
|
146
|
+
type: TracerEventType.StartLoading,
|
147
|
+
id: signal.tracerMeta!.id,
|
148
|
+
});
|
149
|
+
|
150
|
+
nextValue.finally(() => {
|
151
|
+
TRACER!.emit({
|
152
|
+
type: TracerEventType.EndLoading,
|
153
|
+
id: signal.tracerMeta!.id,
|
154
|
+
value: signal.value,
|
155
|
+
});
|
156
|
+
});
|
157
|
+
}
|
158
|
+
|
159
|
+
TRACER?.emit({
|
160
|
+
type: TracerEventType.StartLoading,
|
161
|
+
id: signal.tracerMeta!.id,
|
162
|
+
});
|
163
|
+
|
164
|
+
if (prevValue !== null && typeof prevValue === 'object' && isReactivePromise(prevValue)) {
|
165
|
+
// Update the ReactivePromise with the new promise. Since the value
|
166
|
+
// returned from the function is the same ReactivePromise instance,
|
167
|
+
// we don't need to increment the updatedCount, because the returned
|
168
|
+
// value is the same. _setPromise will update the nested values on the
|
169
|
+
// ReactivePromise instance, and consumers of those values will be notified
|
170
|
+
// of the change through that.
|
171
|
+
prevValue._setPromise(nextValue);
|
172
|
+
} else {
|
173
|
+
// If the signal has not been computed yet, we then the initValue was assigned
|
174
|
+
// in the constructor. Otherwise, we don't know what the initial value was, so
|
175
|
+
// we don't pass it to the ReactivePromise constructor.
|
176
|
+
const initValue = !initialized ? prevValue : undefined;
|
177
|
+
signal.value = ReactivePromise.createPromise(nextValue, signal, initValue);
|
178
|
+
signal.updatedCount = updatedCount + 1;
|
179
|
+
}
|
180
|
+
} else if (!initialized || !signal.equals(prevValue!, nextValue)) {
|
181
|
+
signal.value = nextValue;
|
182
|
+
signal.updatedCount = updatedCount + 1;
|
183
|
+
}
|
184
|
+
} finally {
|
185
|
+
CURRENT_CONSUMER = prevConsumer;
|
186
|
+
|
187
|
+
TRACER?.emit({
|
188
|
+
type: TracerEventType.EndUpdate,
|
189
|
+
id: signal.tracerMeta!.id,
|
190
|
+
value: signal.value,
|
191
|
+
});
|
192
|
+
|
193
|
+
const { ref, deps } = signal;
|
194
|
+
|
195
|
+
for (const [dep, edge] of deps) {
|
196
|
+
if (edge.consumedAt !== computedCount) {
|
197
|
+
scheduleUnwatch(dep);
|
198
|
+
dep.subs.delete(ref);
|
199
|
+
deps.delete(dep);
|
200
|
+
|
201
|
+
TRACER?.emit({
|
202
|
+
type: TracerEventType.Disconnected,
|
203
|
+
id: signal.tracerMeta!.id,
|
204
|
+
childId: dep.tracerMeta!.id,
|
205
|
+
});
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
export function checkAndRunListeners(signal: DerivedSignal<any, any>, willWatch = false) {
|
212
|
+
const listeners = signal.listeners;
|
213
|
+
|
214
|
+
if (willWatch && (listeners === null || listeners.current.size === 0)) {
|
215
|
+
signal.watchCount++;
|
216
|
+
signal['flags'] |= SignalFlags.isListener;
|
217
|
+
}
|
218
|
+
|
219
|
+
let updatedCount = checkSignal(signal);
|
220
|
+
|
221
|
+
if (listeners !== null && listeners.updatedAt !== updatedCount) {
|
222
|
+
listeners.updatedAt = updatedCount;
|
223
|
+
|
224
|
+
scheduleListeners(signal);
|
225
|
+
}
|
226
|
+
|
227
|
+
return updatedCount;
|
228
|
+
}
|
229
|
+
|
230
|
+
export function callback<T, Args extends unknown[]>(fn: (...args: Args) => T): (...args: Args) => T {
|
231
|
+
const savedConsumer = CURRENT_CONSUMER;
|
232
|
+
|
233
|
+
return (...args) => {
|
234
|
+
const prevConsumer = CURRENT_CONSUMER;
|
235
|
+
CURRENT_CONSUMER = savedConsumer;
|
236
|
+
|
237
|
+
try {
|
238
|
+
const result = fn(...args);
|
239
|
+
|
240
|
+
if (result !== null && typeof result === 'object' && isGeneratorResult(result)) {
|
241
|
+
return generatorResultToPromise(result, savedConsumer) as T;
|
242
|
+
}
|
243
|
+
|
244
|
+
return result;
|
245
|
+
} finally {
|
246
|
+
CURRENT_CONSUMER = prevConsumer;
|
247
|
+
}
|
248
|
+
};
|
249
|
+
}
|
250
|
+
|
251
|
+
export function generatorResultToPromise<T, Args extends unknown[]>(
|
252
|
+
generator: Generator<any, T>,
|
253
|
+
savedConsumer: DerivedSignal<any, any> | undefined,
|
254
|
+
): Promise<T> {
|
255
|
+
function adopt(value: any) {
|
256
|
+
return typeof value === 'object' && value !== null && (isPromise(value) || isReactivePromise(value))
|
257
|
+
? value
|
258
|
+
: Promise.resolve(value);
|
259
|
+
}
|
260
|
+
|
261
|
+
return new Promise((resolve, reject) => {
|
262
|
+
function step(result: any) {
|
263
|
+
if (result.done) {
|
264
|
+
resolve(result.value);
|
265
|
+
} else {
|
266
|
+
adopt(result.value).then(fulfilled, rejected);
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
function fulfilled(value: any) {
|
271
|
+
const prevConsumer = CURRENT_CONSUMER;
|
272
|
+
|
273
|
+
try {
|
274
|
+
CURRENT_CONSUMER = savedConsumer;
|
275
|
+
step(generator.next(value));
|
276
|
+
} catch (e) {
|
277
|
+
reject(e);
|
278
|
+
} finally {
|
279
|
+
CURRENT_CONSUMER = prevConsumer;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
function rejected(value: any) {
|
284
|
+
const prevConsumer = CURRENT_CONSUMER;
|
285
|
+
|
286
|
+
try {
|
287
|
+
CURRENT_CONSUMER = savedConsumer;
|
288
|
+
step(generator['throw'](value));
|
289
|
+
} catch (e) {
|
290
|
+
reject(e);
|
291
|
+
} finally {
|
292
|
+
CURRENT_CONSUMER = prevConsumer;
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
step(generator.next());
|
297
|
+
});
|
298
|
+
}
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import { scheduleFlush as _scheduleFlush, runBatch } from '../config.js';
|
2
|
+
import { DerivedSignal } from './derived.js';
|
3
|
+
import { checkAndRunListeners, checkSignal } from './get.js';
|
4
|
+
import { runListeners as runDerivedListeners } from './derived.js';
|
5
|
+
import { runListeners as runStateListeners } from './state.js';
|
6
|
+
import { Tracer } from '../trace.js';
|
7
|
+
import { unwatchSignal } from './connect.js';
|
8
|
+
import { StateSignal } from './state.js';
|
9
|
+
|
10
|
+
let PROMISE_WAS_RESOLVED = false;
|
11
|
+
|
12
|
+
let PENDING_PULLS: DerivedSignal<any, any>[] = [];
|
13
|
+
let PENDING_ASYNC_PULLS: DerivedSignal<any, any>[] = [];
|
14
|
+
let PENDING_UNWATCH = new Map<DerivedSignal<any, any>, number>();
|
15
|
+
let PENDING_LISTENERS: (DerivedSignal<any, any> | StateSignal<any>)[] = [];
|
16
|
+
let PENDING_TRACERS: Tracer[] = [];
|
17
|
+
|
18
|
+
const microtask = () => Promise.resolve();
|
19
|
+
|
20
|
+
let currentFlush: { promise: Promise<void>; resolve: () => void } | null = null;
|
21
|
+
|
22
|
+
const scheduleFlush = (fn: () => void) => {
|
23
|
+
if (currentFlush) return;
|
24
|
+
|
25
|
+
let resolve: () => void;
|
26
|
+
const promise = new Promise<void>(r => (resolve = r));
|
27
|
+
|
28
|
+
currentFlush = { promise, resolve: resolve! };
|
29
|
+
|
30
|
+
_scheduleFlush(flushWatchers);
|
31
|
+
};
|
32
|
+
|
33
|
+
export const setResolved = () => {
|
34
|
+
PROMISE_WAS_RESOLVED = true;
|
35
|
+
};
|
36
|
+
|
37
|
+
export const schedulePull = (signal: DerivedSignal<any, any>) => {
|
38
|
+
PENDING_PULLS.push(signal);
|
39
|
+
scheduleFlush(flushWatchers);
|
40
|
+
};
|
41
|
+
|
42
|
+
export const scheduleAsyncPull = (signal: DerivedSignal<any, any>) => {
|
43
|
+
PENDING_ASYNC_PULLS.push(signal);
|
44
|
+
scheduleFlush(flushWatchers);
|
45
|
+
};
|
46
|
+
|
47
|
+
export const scheduleUnwatch = (unwatch: DerivedSignal<any, any>) => {
|
48
|
+
const current = PENDING_UNWATCH.get(unwatch) ?? 0;
|
49
|
+
|
50
|
+
PENDING_UNWATCH.set(unwatch, current + 1);
|
51
|
+
|
52
|
+
scheduleFlush(flushWatchers);
|
53
|
+
};
|
54
|
+
|
55
|
+
export const scheduleListeners = (signal: DerivedSignal<any, any> | StateSignal<any>) => {
|
56
|
+
PENDING_LISTENERS.push(signal);
|
57
|
+
scheduleFlush(flushWatchers);
|
58
|
+
};
|
59
|
+
|
60
|
+
export const scheduleTracer = (tracer: Tracer) => {
|
61
|
+
PENDING_TRACERS.push(tracer);
|
62
|
+
scheduleFlush(flushWatchers);
|
63
|
+
};
|
64
|
+
|
65
|
+
const flushWatchers = async () => {
|
66
|
+
const flush = currentFlush!;
|
67
|
+
|
68
|
+
// Flush all auto-pulled signals recursively, clearing
|
69
|
+
// the microtask queue until they are all settled
|
70
|
+
while (PROMISE_WAS_RESOLVED || PENDING_ASYNC_PULLS.length > 0 || PENDING_PULLS.length > 0) {
|
71
|
+
PROMISE_WAS_RESOLVED = false;
|
72
|
+
const asyncPulls = PENDING_ASYNC_PULLS;
|
73
|
+
|
74
|
+
PENDING_ASYNC_PULLS = [];
|
75
|
+
|
76
|
+
for (const pull of asyncPulls) {
|
77
|
+
checkSignal(pull);
|
78
|
+
}
|
79
|
+
|
80
|
+
const pulls = PENDING_PULLS;
|
81
|
+
|
82
|
+
PENDING_PULLS = [];
|
83
|
+
|
84
|
+
for (const pull of pulls) {
|
85
|
+
checkAndRunListeners(pull);
|
86
|
+
}
|
87
|
+
|
88
|
+
// This is used to tell the scheduler to wait if any async values have been resolved
|
89
|
+
// since the last tick. If they have, we wait an extra microtask to ensure that the
|
90
|
+
// async values have recursivey flushed before moving on to pulling watchers.
|
91
|
+
|
92
|
+
await microtask();
|
93
|
+
}
|
94
|
+
|
95
|
+
// Clear the flush so that if any more watchers are scheduled,
|
96
|
+
// they will be flushed in the next tick
|
97
|
+
currentFlush = null;
|
98
|
+
|
99
|
+
runBatch(() => {
|
100
|
+
for (const [signal, count] of PENDING_UNWATCH) {
|
101
|
+
unwatchSignal(signal, count);
|
102
|
+
}
|
103
|
+
|
104
|
+
for (const signal of PENDING_LISTENERS) {
|
105
|
+
if (signal instanceof DerivedSignal) {
|
106
|
+
runDerivedListeners(signal as any);
|
107
|
+
} else {
|
108
|
+
runStateListeners(signal as any);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
for (const tracer of PENDING_TRACERS) {
|
113
|
+
tracer.flush();
|
114
|
+
}
|
115
|
+
|
116
|
+
PENDING_UNWATCH.clear();
|
117
|
+
PENDING_LISTENERS = [];
|
118
|
+
PENDING_TRACERS = [];
|
119
|
+
});
|
120
|
+
|
121
|
+
// resolve the flush promise
|
122
|
+
flush.resolve();
|
123
|
+
};
|
124
|
+
|
125
|
+
export const settled = async () => {
|
126
|
+
while (currentFlush) {
|
127
|
+
await currentFlush.promise;
|
128
|
+
}
|
129
|
+
};
|
130
|
+
|
131
|
+
export const batch = (fn: () => void) => {
|
132
|
+
let resolve: () => void;
|
133
|
+
const promise = new Promise<void>(r => (resolve = r));
|
134
|
+
|
135
|
+
currentFlush = { promise, resolve: resolve! };
|
136
|
+
|
137
|
+
fn();
|
138
|
+
flushWatchers();
|
139
|
+
// flushDisconnects();
|
140
|
+
};
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import { TRACER as TRACER, TracerEventType } from '../trace.js';
|
2
|
+
import { ReactiveValue, SignalEquals, SignalListener, SignalOptions, WriteableSignal } from '../types.js';
|
3
|
+
import { DerivedSignal, SignalState } from './derived.js';
|
4
|
+
import { dirtySignal } from './dirty.js';
|
5
|
+
import { CURRENT_CONSUMER } from './get.js';
|
6
|
+
import { useStateSignal } from '../config.js';
|
7
|
+
import { scheduleListeners } from './scheduling.js';
|
8
|
+
|
9
|
+
let STATE_ID = 0;
|
10
|
+
|
11
|
+
export class StateSignal<T> implements WriteableSignal<T> {
|
12
|
+
private _value: T;
|
13
|
+
private _equals: SignalEquals<T>;
|
14
|
+
private _subs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, number>();
|
15
|
+
_desc: string;
|
16
|
+
_id: number;
|
17
|
+
|
18
|
+
private _listeners: Set<SignalListener> | null = null;
|
19
|
+
|
20
|
+
constructor(value: T, equals: SignalEquals<T> = (a, b) => a === b, desc: string = 'state') {
|
21
|
+
this._value = value;
|
22
|
+
this._equals = equals;
|
23
|
+
this._id = STATE_ID++;
|
24
|
+
this._desc = `${desc}${this._id}`;
|
25
|
+
}
|
26
|
+
|
27
|
+
get(): T {
|
28
|
+
if (CURRENT_CONSUMER !== undefined) {
|
29
|
+
TRACER?.emit({
|
30
|
+
type: TracerEventType.ConsumeState,
|
31
|
+
id: CURRENT_CONSUMER.tracerMeta!.id,
|
32
|
+
childId: this._id,
|
33
|
+
value: this._value,
|
34
|
+
setValue: (value: unknown) => {
|
35
|
+
this.set(value as T);
|
36
|
+
},
|
37
|
+
});
|
38
|
+
this._subs.set(CURRENT_CONSUMER.ref, CURRENT_CONSUMER.computedCount);
|
39
|
+
return this._value!;
|
40
|
+
}
|
41
|
+
|
42
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
43
|
+
return useStateSignal(this);
|
44
|
+
}
|
45
|
+
|
46
|
+
update(fn: (value: T) => T) {
|
47
|
+
this.set(fn(this._value));
|
48
|
+
}
|
49
|
+
|
50
|
+
peek(): T {
|
51
|
+
return this._value;
|
52
|
+
}
|
53
|
+
|
54
|
+
set(value: T) {
|
55
|
+
if (this._equals(value, this._value)) {
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
|
59
|
+
this._value = value;
|
60
|
+
const { _subs: subs, _listeners: listeners } = this;
|
61
|
+
|
62
|
+
for (const [subRef, consumedAt] of subs.entries()) {
|
63
|
+
const sub = subRef.deref();
|
64
|
+
|
65
|
+
if (sub === undefined || consumedAt !== sub.computedCount) {
|
66
|
+
continue;
|
67
|
+
}
|
68
|
+
|
69
|
+
dirtySignal(sub);
|
70
|
+
}
|
71
|
+
|
72
|
+
this._subs = new Map();
|
73
|
+
|
74
|
+
scheduleListeners(this);
|
75
|
+
}
|
76
|
+
|
77
|
+
addListener(listener: SignalListener): () => void {
|
78
|
+
let listeners = this._listeners;
|
79
|
+
|
80
|
+
if (listeners === null) {
|
81
|
+
this._listeners = listeners = new Set();
|
82
|
+
}
|
83
|
+
|
84
|
+
listeners.add(listener);
|
85
|
+
|
86
|
+
return () => listeners.delete(listener);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
export function runListeners(signal: StateSignal<any>) {
|
91
|
+
const listeners = signal['_listeners'];
|
92
|
+
|
93
|
+
if (listeners === null) {
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
|
97
|
+
for (const listener of listeners) {
|
98
|
+
listener();
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
const FALSE_EQUALS: SignalEquals<unknown> = () => false;
|
103
|
+
|
104
|
+
export function createStateSignal<T>(
|
105
|
+
initialValue: T,
|
106
|
+
opts?: Omit<SignalOptions<T, unknown[]>, 'paramKey'>,
|
107
|
+
): StateSignal<T> {
|
108
|
+
const equals = opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b));
|
109
|
+
|
110
|
+
return new StateSignal(initialValue, equals, opts?.desc);
|
111
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
let UNKNOWN_SIGNAL_ID = 0;
|
2
|
+
const UNKNOWN_SIGNAL_NAMES = new Map<object, string>();
|
3
|
+
|
4
|
+
export function getUnknownSignalFnName(fn: object) {
|
5
|
+
let name = UNKNOWN_SIGNAL_NAMES.get(fn);
|
6
|
+
|
7
|
+
if (name === undefined) {
|
8
|
+
name = `unknownSignal${UNKNOWN_SIGNAL_ID++}`;
|
9
|
+
|
10
|
+
UNKNOWN_SIGNAL_NAMES.set(fn, name);
|
11
|
+
}
|
12
|
+
|
13
|
+
return name;
|
14
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { SignalEquals } from '../../types.js';
|
2
|
+
|
3
|
+
const DEFAULT_EQUALS: SignalEquals<unknown> = (a, b) => a === b;
|
4
|
+
const FALSE_EQUALS: SignalEquals<unknown> = () => false;
|
5
|
+
|
6
|
+
export const equalsFrom = <T>(equals: SignalEquals<T> | false | undefined): SignalEquals<T> => {
|
7
|
+
if (equals === false) {
|
8
|
+
return FALSE_EQUALS;
|
9
|
+
}
|
10
|
+
|
11
|
+
return equals ?? DEFAULT_EQUALS;
|
12
|
+
};
|