signalium 0.3.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +50 -0
- package/dist/cjs/internals/async.d.ts.map +1 -0
- package/dist/cjs/internals/async.js +390 -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 +50 -0
- package/dist/esm/internals/async.d.ts.map +1 -0
- package/dist/esm/internals/async.js +383 -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 +556 -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,556 @@
|
|
1
|
+
import {
|
2
|
+
BaseReactivePromise,
|
3
|
+
ReactiveSubscription,
|
4
|
+
ReactiveTask,
|
5
|
+
ReactivePromise as IReactivePromise,
|
6
|
+
SignalEquals,
|
7
|
+
SignalOptionsWithInit,
|
8
|
+
SignalSubscribe,
|
9
|
+
SignalSubscription,
|
10
|
+
} from '../types.js';
|
11
|
+
import { createDerivedSignal, DerivedSignal, SignalState } from './derived.js';
|
12
|
+
import { CURRENT_CONSUMER, generatorResultToPromise, getSignal } from './get.js';
|
13
|
+
import { dirtySignal, dirtySignalConsumers } from './dirty.js';
|
14
|
+
import { scheduleAsyncPull, schedulePull, setResolved } from './scheduling.js';
|
15
|
+
import { createEdge, Edge, EdgeType, findAndRemoveDirty, PromiseEdge } from './edge.js';
|
16
|
+
import { getCurrentScope, ROOT_SCOPE, SignalScope, withScope } from './contexts.js';
|
17
|
+
import { createStateSignal } from './state.js';
|
18
|
+
import { useStateSignal } from '../config.js';
|
19
|
+
import { isGeneratorResult, isPromise } from './utils/type-utils.js';
|
20
|
+
import { equalsFrom } from './utils/equals.js';
|
21
|
+
|
22
|
+
const enum AsyncFlags {
|
23
|
+
// ======= Notifiers ========
|
24
|
+
|
25
|
+
Pending = 1,
|
26
|
+
Rejected = 1 << 1,
|
27
|
+
Resolved = 1 << 2,
|
28
|
+
Ready = 1 << 3,
|
29
|
+
|
30
|
+
Value = 1 << 4,
|
31
|
+
Error = 1 << 5,
|
32
|
+
|
33
|
+
// ======= Properties ========
|
34
|
+
|
35
|
+
isRunnable = 1 << 6,
|
36
|
+
isSubscription = 1 << 7,
|
37
|
+
|
38
|
+
// ======= Helpers ========
|
39
|
+
|
40
|
+
Settled = Resolved | Rejected,
|
41
|
+
}
|
42
|
+
|
43
|
+
interface PendingResolve<T> {
|
44
|
+
ref: WeakRef<DerivedSignal<unknown, unknown[]>> | undefined;
|
45
|
+
edge: PromiseEdge | undefined;
|
46
|
+
resolve: ((value: T) => void) | undefined | null;
|
47
|
+
reject: ((error: unknown) => void) | undefined | null;
|
48
|
+
}
|
49
|
+
|
50
|
+
type TaskFn<T, Args extends unknown[]> = (...args: Args) => Promise<T>;
|
51
|
+
|
52
|
+
export class ReactivePromise<T, Args extends unknown[] = unknown[]> implements BaseReactivePromise<T> {
|
53
|
+
private _value: T | undefined = undefined;
|
54
|
+
|
55
|
+
private _error: unknown | undefined = undefined;
|
56
|
+
private _flags = 0;
|
57
|
+
|
58
|
+
private _signal: DerivedSignal<any, any> | TaskFn<T, Args> | undefined = undefined;
|
59
|
+
private _equals!: SignalEquals<T>;
|
60
|
+
private _promise: Promise<T> | undefined;
|
61
|
+
|
62
|
+
private _pending: PendingResolve<T>[] = [];
|
63
|
+
|
64
|
+
private _stateSubs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, number>();
|
65
|
+
_awaitSubs = new Map<WeakRef<DerivedSignal<unknown, unknown[]>>, PromiseEdge>();
|
66
|
+
|
67
|
+
// Version is not really needed in a pure signal world, but when integrating
|
68
|
+
// with non-signal code, it's sometimes needed to entangle changes to the promise.
|
69
|
+
// For example, in React we need to entangle each promise immediately after it
|
70
|
+
// was used because we can't dynamically call hooks.
|
71
|
+
private _version = createStateSignal(0);
|
72
|
+
|
73
|
+
static createPromise<T>(promise: Promise<T>, signal?: DerivedSignal<T, unknown[]>, initValue?: T | undefined) {
|
74
|
+
const p = new ReactivePromise();
|
75
|
+
|
76
|
+
p._signal = signal;
|
77
|
+
p._equals = signal?.equals ?? ((a, b) => a === b);
|
78
|
+
|
79
|
+
p._initFlags(AsyncFlags.Pending, initValue);
|
80
|
+
|
81
|
+
if (promise) {
|
82
|
+
p._setPromise(promise);
|
83
|
+
}
|
84
|
+
|
85
|
+
return p;
|
86
|
+
}
|
87
|
+
|
88
|
+
static createSubscription<T>(
|
89
|
+
subscribe: SignalSubscribe<T>,
|
90
|
+
scope: SignalScope,
|
91
|
+
opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
|
92
|
+
) {
|
93
|
+
const p = new ReactivePromise<T>();
|
94
|
+
const initValue = opts?.initValue;
|
95
|
+
|
96
|
+
let active = false;
|
97
|
+
let currentSub: SignalSubscription | (() => void) | undefined | void;
|
98
|
+
|
99
|
+
const unsubscribe = () => {
|
100
|
+
if (typeof currentSub === 'function') {
|
101
|
+
currentSub();
|
102
|
+
} else if (currentSub !== undefined) {
|
103
|
+
currentSub.unsubscribe?.();
|
104
|
+
}
|
105
|
+
|
106
|
+
const signal = p._signal as DerivedSignal<any, any>;
|
107
|
+
|
108
|
+
// Reset the signal state, preparing it for next activation
|
109
|
+
signal.subs = new Map();
|
110
|
+
signal._state = SignalState.Dirty;
|
111
|
+
signal.watchCount = 0;
|
112
|
+
active = false;
|
113
|
+
currentSub = undefined;
|
114
|
+
};
|
115
|
+
|
116
|
+
const state = {
|
117
|
+
get: () => p._value as T,
|
118
|
+
|
119
|
+
set: (value: T | Promise<T>) => {
|
120
|
+
if (value !== null && typeof value === 'object' && isPromise(value)) {
|
121
|
+
p._setPromise(value);
|
122
|
+
} else {
|
123
|
+
p._setValue(value as T);
|
124
|
+
}
|
125
|
+
},
|
126
|
+
|
127
|
+
setError: (error: unknown) => {
|
128
|
+
p._setError(error);
|
129
|
+
},
|
130
|
+
};
|
131
|
+
|
132
|
+
p._signal = createDerivedSignal(
|
133
|
+
() => {
|
134
|
+
if (active === false) {
|
135
|
+
currentSub = subscribe(state);
|
136
|
+
active = true;
|
137
|
+
} else if (typeof currentSub === 'function' || currentSub === undefined) {
|
138
|
+
currentSub?.();
|
139
|
+
currentSub = subscribe(state);
|
140
|
+
} else {
|
141
|
+
currentSub.update?.();
|
142
|
+
}
|
143
|
+
|
144
|
+
return unsubscribe;
|
145
|
+
},
|
146
|
+
[],
|
147
|
+
undefined,
|
148
|
+
scope,
|
149
|
+
opts as Omit<SignalOptionsWithInit<T, unknown[]>, 'equals' | 'initValue' | 'paramKey'>,
|
150
|
+
true,
|
151
|
+
);
|
152
|
+
|
153
|
+
p._equals = equalsFrom(opts?.equals);
|
154
|
+
p._initFlags(AsyncFlags.isSubscription | AsyncFlags.Pending, initValue as T);
|
155
|
+
|
156
|
+
return p;
|
157
|
+
}
|
158
|
+
|
159
|
+
static createTask<T, Args extends unknown[]>(
|
160
|
+
task: (...args: Args) => Promise<T>,
|
161
|
+
scope: SignalScope,
|
162
|
+
opts?: Partial<SignalOptionsWithInit<T, Args>>,
|
163
|
+
): ReactiveTask<T, Args> {
|
164
|
+
const p = new ReactivePromise<T, Args>();
|
165
|
+
const initValue = opts?.initValue;
|
166
|
+
|
167
|
+
p._signal = (...args) => {
|
168
|
+
return withScope(scope, () => {
|
169
|
+
const result = task(...args);
|
170
|
+
|
171
|
+
return typeof result === 'object' && result !== null && isGeneratorResult(result)
|
172
|
+
? generatorResultToPromise(result, undefined)
|
173
|
+
: result;
|
174
|
+
});
|
175
|
+
};
|
176
|
+
|
177
|
+
p._equals = equalsFrom(opts?.equals);
|
178
|
+
p._initFlags(AsyncFlags.isRunnable, initValue as T);
|
179
|
+
|
180
|
+
return p as ReactiveTask<T, Args>;
|
181
|
+
}
|
182
|
+
|
183
|
+
private _initFlags(baseFlags: number, initValue?: T) {
|
184
|
+
let flags = baseFlags;
|
185
|
+
|
186
|
+
if (initValue !== undefined) {
|
187
|
+
flags |= AsyncFlags.Ready;
|
188
|
+
}
|
189
|
+
|
190
|
+
this._flags = flags;
|
191
|
+
this._value = initValue as T;
|
192
|
+
}
|
193
|
+
|
194
|
+
private _consumeFlags(flags: number) {
|
195
|
+
if (CURRENT_CONSUMER === undefined) return;
|
196
|
+
|
197
|
+
if ((this._flags & AsyncFlags.isSubscription) !== 0) {
|
198
|
+
this._connect();
|
199
|
+
}
|
200
|
+
|
201
|
+
const ref = CURRENT_CONSUMER.ref;
|
202
|
+
|
203
|
+
const subs = this._stateSubs;
|
204
|
+
|
205
|
+
const subbedFlags = subs.get(ref) ?? 0;
|
206
|
+
subs.set(ref, subbedFlags | flags);
|
207
|
+
}
|
208
|
+
|
209
|
+
private _connect() {
|
210
|
+
const signal = this._signal as DerivedSignal<any, any>;
|
211
|
+
|
212
|
+
if (CURRENT_CONSUMER?.watchCount === 0) {
|
213
|
+
const { ref, computedCount, deps } = CURRENT_CONSUMER!;
|
214
|
+
const prevEdge = deps.get(signal);
|
215
|
+
|
216
|
+
if (prevEdge?.consumedAt !== computedCount) {
|
217
|
+
const newEdge = createEdge(prevEdge, EdgeType.Signal, signal, signal.updatedCount, computedCount);
|
218
|
+
|
219
|
+
signal.subs.set(ref, newEdge);
|
220
|
+
deps.set(signal, newEdge);
|
221
|
+
}
|
222
|
+
} else {
|
223
|
+
getSignal(signal);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
private _setFlags(setTrue: number, setFalse = 0, notify = 0) {
|
228
|
+
const prevFlags = this._flags;
|
229
|
+
|
230
|
+
const nextFlags = (prevFlags & ~setFalse) | setTrue;
|
231
|
+
const allChanged = (prevFlags ^ nextFlags) | notify;
|
232
|
+
|
233
|
+
this._flags = nextFlags;
|
234
|
+
|
235
|
+
if (allChanged === 0) {
|
236
|
+
return;
|
237
|
+
}
|
238
|
+
|
239
|
+
const subs = this._stateSubs;
|
240
|
+
|
241
|
+
for (const [signalRef, subbedFlags] of subs) {
|
242
|
+
if ((subbedFlags & allChanged) !== 0) {
|
243
|
+
const signal = signalRef.deref();
|
244
|
+
|
245
|
+
if (signal) {
|
246
|
+
dirtySignal(signal);
|
247
|
+
}
|
248
|
+
|
249
|
+
subs.delete(signalRef);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
this._version.update(v => v + 1);
|
254
|
+
}
|
255
|
+
|
256
|
+
_setPending() {
|
257
|
+
this._setFlags(AsyncFlags.Pending);
|
258
|
+
}
|
259
|
+
|
260
|
+
async _setPromise(promise: Promise<T>) {
|
261
|
+
// Store the current promise so we can check if it's the same promise in the
|
262
|
+
// then handlers. If it's not the same promise, it means that the promise has
|
263
|
+
// been recomputed and replaced, so we should not update state.
|
264
|
+
this._promise = promise;
|
265
|
+
|
266
|
+
const flags = this._flags;
|
267
|
+
let awaitSubs = this._awaitSubs;
|
268
|
+
|
269
|
+
// If we were not already pending, we need to propagate the dirty state to any
|
270
|
+
// consumers that were added since the promise was resolved last.
|
271
|
+
if ((flags & AsyncFlags.Pending) === 0) {
|
272
|
+
this._setPending();
|
273
|
+
dirtySignalConsumers(awaitSubs);
|
274
|
+
this._awaitSubs = awaitSubs = new Map();
|
275
|
+
}
|
276
|
+
|
277
|
+
try {
|
278
|
+
const nextValue = await promise;
|
279
|
+
|
280
|
+
if (promise !== this._promise) {
|
281
|
+
return;
|
282
|
+
}
|
283
|
+
|
284
|
+
this._setValue(nextValue, awaitSubs);
|
285
|
+
} catch (nextError) {
|
286
|
+
if (promise !== this._promise) {
|
287
|
+
return;
|
288
|
+
}
|
289
|
+
|
290
|
+
this._setError(nextError, awaitSubs);
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
private _setValue(nextValue: T, awaitSubs = this._awaitSubs) {
|
295
|
+
let flags = this._flags;
|
296
|
+
let value = this._value;
|
297
|
+
|
298
|
+
let notifyFlags = 0;
|
299
|
+
|
300
|
+
if ((flags & AsyncFlags.Ready) === 0 || this._equals(value!, nextValue) === false) {
|
301
|
+
this._value = value = nextValue;
|
302
|
+
notifyFlags = AsyncFlags.Value;
|
303
|
+
}
|
304
|
+
|
305
|
+
if (flags & AsyncFlags.Rejected) {
|
306
|
+
notifyFlags = AsyncFlags.Error;
|
307
|
+
this._error = undefined;
|
308
|
+
}
|
309
|
+
|
310
|
+
this._scheduleSubs(awaitSubs, notifyFlags !== 0);
|
311
|
+
|
312
|
+
this._setFlags(AsyncFlags.Resolved | AsyncFlags.Ready, AsyncFlags.Pending | AsyncFlags.Rejected, notifyFlags);
|
313
|
+
|
314
|
+
for (const { ref, edge, resolve } of this._pending) {
|
315
|
+
resolve?.(value!);
|
316
|
+
|
317
|
+
if (ref !== undefined) {
|
318
|
+
awaitSubs.set(ref, edge!);
|
319
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
this._pending = [];
|
323
|
+
}
|
324
|
+
|
325
|
+
private _setError(nextError: unknown, awaitSubs = this._awaitSubs) {
|
326
|
+
let error = this._error;
|
327
|
+
|
328
|
+
let notifyFlags = 0;
|
329
|
+
|
330
|
+
if (error !== nextError) {
|
331
|
+
this._error = error = nextError;
|
332
|
+
notifyFlags = AsyncFlags.Error;
|
333
|
+
}
|
334
|
+
|
335
|
+
this._scheduleSubs(awaitSubs, notifyFlags !== 0);
|
336
|
+
|
337
|
+
this._setFlags(AsyncFlags.Rejected, AsyncFlags.Pending | AsyncFlags.Resolved, notifyFlags);
|
338
|
+
|
339
|
+
for (const { ref, edge, reject } of this._pending) {
|
340
|
+
reject?.(error);
|
341
|
+
|
342
|
+
if (ref !== undefined) {
|
343
|
+
awaitSubs.set(ref, edge!);
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
this._pending = [];
|
348
|
+
}
|
349
|
+
|
350
|
+
private _scheduleSubs(awaitSubs: Map<WeakRef<DerivedSignal<unknown, unknown[]>>, PromiseEdge>, dirty: boolean) {
|
351
|
+
// Await subscribers that have been added since the promise was set are specifically
|
352
|
+
// subscribers that were previously notified and MaybeDirty, were removed from the
|
353
|
+
// signal, and then were checked (e.g. checkSignal was called on them) and they
|
354
|
+
// halted and added themselves back as dependencies.
|
355
|
+
//
|
356
|
+
// If the value actually changed, then these consumers are Dirty and will notify and
|
357
|
+
// schedule themselves the standard way here. If the value did not change, then the
|
358
|
+
// consumers are not notified and end up back in the same state as before the promise
|
359
|
+
// was set (because nothing changed), and instead they will be scheduled to continue
|
360
|
+
// the computation from where they left off.
|
361
|
+
const newState = dirty ? SignalState.Dirty : SignalState.MaybeDirty;
|
362
|
+
|
363
|
+
for (const ref of awaitSubs.keys()) {
|
364
|
+
const signal = ref.deref();
|
365
|
+
|
366
|
+
if (signal === undefined) {
|
367
|
+
continue;
|
368
|
+
}
|
369
|
+
|
370
|
+
signal._state = newState;
|
371
|
+
|
372
|
+
scheduleAsyncPull(signal);
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
376
|
+
get value() {
|
377
|
+
this._consumeFlags(AsyncFlags.Value);
|
378
|
+
|
379
|
+
return this._value;
|
380
|
+
}
|
381
|
+
|
382
|
+
get error() {
|
383
|
+
this._consumeFlags(AsyncFlags.Error);
|
384
|
+
|
385
|
+
return this._error;
|
386
|
+
}
|
387
|
+
|
388
|
+
get isPending() {
|
389
|
+
this._consumeFlags(AsyncFlags.Pending);
|
390
|
+
|
391
|
+
return (this._flags & AsyncFlags.Pending) !== 0;
|
392
|
+
}
|
393
|
+
|
394
|
+
get isRejected() {
|
395
|
+
this._consumeFlags(AsyncFlags.Rejected);
|
396
|
+
|
397
|
+
return (this._flags & AsyncFlags.Rejected) !== 0;
|
398
|
+
}
|
399
|
+
|
400
|
+
get isResolved() {
|
401
|
+
this._consumeFlags(AsyncFlags.Resolved);
|
402
|
+
|
403
|
+
return (this._flags & AsyncFlags.Resolved) !== 0;
|
404
|
+
}
|
405
|
+
|
406
|
+
get isReady() {
|
407
|
+
this._consumeFlags(AsyncFlags.Ready);
|
408
|
+
|
409
|
+
return (this._flags & AsyncFlags.Ready) !== 0;
|
410
|
+
}
|
411
|
+
|
412
|
+
get isSettled() {
|
413
|
+
this._consumeFlags(AsyncFlags.Settled);
|
414
|
+
|
415
|
+
return (this._flags & AsyncFlags.Settled) !== 0;
|
416
|
+
}
|
417
|
+
|
418
|
+
// Aliases for backwards compatibility (TODO: Figure out how to do this better)
|
419
|
+
get data() {
|
420
|
+
return this.value;
|
421
|
+
}
|
422
|
+
|
423
|
+
get isFetching() {
|
424
|
+
return this.isPending;
|
425
|
+
}
|
426
|
+
|
427
|
+
get isSuccess() {
|
428
|
+
return this.isResolved;
|
429
|
+
}
|
430
|
+
|
431
|
+
get isError() {
|
432
|
+
return this.isRejected;
|
433
|
+
}
|
434
|
+
|
435
|
+
get isFetched() {
|
436
|
+
return this.isSettled;
|
437
|
+
}
|
438
|
+
|
439
|
+
run(...args: Args) {
|
440
|
+
if ((this._flags & AsyncFlags.isRunnable) === 0) {
|
441
|
+
throw new Error('ReactivePromise is not runnable, make sure you used `task` to create this promise');
|
442
|
+
}
|
443
|
+
|
444
|
+
const signal = this._signal as TaskFn<T, Args>;
|
445
|
+
|
446
|
+
this._setPromise(signal(...args));
|
447
|
+
|
448
|
+
return this;
|
449
|
+
}
|
450
|
+
|
451
|
+
rerun() {
|
452
|
+
const { _signal: signal } = this;
|
453
|
+
|
454
|
+
if (!signal) {
|
455
|
+
throw new Error('ReactivePromise is not associated with a signal');
|
456
|
+
}
|
457
|
+
|
458
|
+
dirtySignal(signal as DerivedSignal<any, any>);
|
459
|
+
}
|
460
|
+
|
461
|
+
then<TResult1 = T, TResult2 = never>(
|
462
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
463
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
464
|
+
): Promise<TResult1 | TResult2> {
|
465
|
+
const flags = this._flags;
|
466
|
+
|
467
|
+
// Create a new Promise that will be returned
|
468
|
+
return new Promise<TResult1 | TResult2>((resolve, reject) => {
|
469
|
+
let ref, edge;
|
470
|
+
|
471
|
+
if (CURRENT_CONSUMER !== undefined) {
|
472
|
+
if ((flags & AsyncFlags.isSubscription) !== 0) {
|
473
|
+
this._connect();
|
474
|
+
}
|
475
|
+
|
476
|
+
ref = CURRENT_CONSUMER.ref;
|
477
|
+
|
478
|
+
const prevEdge =
|
479
|
+
this._awaitSubs.get(ref!) ?? findAndRemoveDirty(CURRENT_CONSUMER, this as ReactivePromise<any>);
|
480
|
+
|
481
|
+
edge = createEdge(prevEdge, EdgeType.Promise, this as ReactivePromise<any>, -1, CURRENT_CONSUMER.computedCount);
|
482
|
+
}
|
483
|
+
// Create wrapper functions that will call the original callbacks and then resolve/reject the new Promise
|
484
|
+
const wrappedFulfilled = onfulfilled
|
485
|
+
? (value: T) => {
|
486
|
+
try {
|
487
|
+
const result = onfulfilled(value);
|
488
|
+
resolve(result);
|
489
|
+
} catch (error) {
|
490
|
+
reject(error);
|
491
|
+
}
|
492
|
+
}
|
493
|
+
: (resolve as unknown as (value: T) => void);
|
494
|
+
|
495
|
+
const wrappedRejected = onrejected
|
496
|
+
? (reason: unknown) => {
|
497
|
+
try {
|
498
|
+
const result = onrejected(reason);
|
499
|
+
resolve(result);
|
500
|
+
} catch (error) {
|
501
|
+
reject(error);
|
502
|
+
}
|
503
|
+
}
|
504
|
+
: reject;
|
505
|
+
|
506
|
+
if (flags & AsyncFlags.Pending) {
|
507
|
+
this._pending.push({ ref, edge, resolve: wrappedFulfilled, reject: wrappedRejected });
|
508
|
+
} else {
|
509
|
+
if (flags & AsyncFlags.Resolved) {
|
510
|
+
wrappedFulfilled(this._value!);
|
511
|
+
} else if (flags & AsyncFlags.Rejected) {
|
512
|
+
wrappedRejected(this._error);
|
513
|
+
}
|
514
|
+
|
515
|
+
if (ref) {
|
516
|
+
this._awaitSubs.set(ref, edge!);
|
517
|
+
}
|
518
|
+
}
|
519
|
+
});
|
520
|
+
}
|
521
|
+
|
522
|
+
catch<TResult = never>(
|
523
|
+
onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
524
|
+
): Promise<T | TResult> {
|
525
|
+
return this.then(null, onrejected);
|
526
|
+
}
|
527
|
+
|
528
|
+
finally(onfinally?: (() => void) | null): Promise<T> {
|
529
|
+
return this.then(
|
530
|
+
value => {
|
531
|
+
onfinally?.();
|
532
|
+
return value;
|
533
|
+
},
|
534
|
+
reason => {
|
535
|
+
onfinally?.();
|
536
|
+
throw reason;
|
537
|
+
},
|
538
|
+
);
|
539
|
+
}
|
540
|
+
|
541
|
+
get [Symbol.toStringTag](): string {
|
542
|
+
return 'ReactivePromise';
|
543
|
+
}
|
544
|
+
}
|
545
|
+
|
546
|
+
export function isReactivePromise(obj: unknown): obj is IReactivePromise<unknown> {
|
547
|
+
return obj instanceof ReactivePromise && (obj['_flags'] & (AsyncFlags.isSubscription & AsyncFlags.isRunnable)) === 0;
|
548
|
+
}
|
549
|
+
|
550
|
+
export function isReactiveTask(obj: unknown): obj is ReactiveTask<unknown, unknown[]> {
|
551
|
+
return obj instanceof ReactivePromise && (obj['_flags'] & AsyncFlags.isRunnable) !== 0;
|
552
|
+
}
|
553
|
+
|
554
|
+
export function isReactiveSubscription<T, Args extends unknown[]>(obj: unknown): obj is ReactiveSubscription<T> {
|
555
|
+
return obj instanceof ReactivePromise && (obj['_flags'] & AsyncFlags.isSubscription) !== 0;
|
556
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { DerivedSignal, isSubscription } from './derived.js';
|
2
|
+
import { checkSignal } from './get.js';
|
3
|
+
|
4
|
+
export function watchSignal(signal: DerivedSignal<any, any>): void {
|
5
|
+
const { watchCount } = signal;
|
6
|
+
const newWatchCount = watchCount + 1;
|
7
|
+
|
8
|
+
signal.watchCount = newWatchCount;
|
9
|
+
|
10
|
+
// If > 0, already watching, return
|
11
|
+
if (watchCount > 0) return;
|
12
|
+
|
13
|
+
for (const dep of signal.deps.keys()) {
|
14
|
+
watchSignal(dep);
|
15
|
+
}
|
16
|
+
|
17
|
+
if (isSubscription(signal)) {
|
18
|
+
// Bootstrap the subscription
|
19
|
+
checkSignal(signal);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
export function unwatchSignal(signal: DerivedSignal<any, any>, count = 1) {
|
24
|
+
const { watchCount } = signal;
|
25
|
+
const newWatchCount = Math.max(watchCount - count, 0);
|
26
|
+
|
27
|
+
signal.watchCount = newWatchCount;
|
28
|
+
|
29
|
+
if (newWatchCount > 0) {
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
|
33
|
+
for (const dep of signal.deps.keys()) {
|
34
|
+
unwatchSignal(dep);
|
35
|
+
}
|
36
|
+
|
37
|
+
if (isSubscription(signal)) {
|
38
|
+
// teardown the subscription
|
39
|
+
signal.value?.();
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { DerivedSignal } from './derived.js';
|
2
|
+
|
3
|
+
export let CURRENT_CONSUMER: DerivedSignal<any, any> | undefined;
|
4
|
+
|
5
|
+
export let IS_WATCHING = false;
|
6
|
+
|
7
|
+
export const setIsWatching = (isWatching: boolean) => {
|
8
|
+
IS_WATCHING = isWatching;
|
9
|
+
};
|
10
|
+
|
11
|
+
export const setCurrentConsumer = (consumer: DerivedSignal<any, any> | undefined) => {
|
12
|
+
CURRENT_CONSUMER = consumer;
|
13
|
+
};
|