signalium 0.3.8 → 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 +15 -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,133 @@
|
|
1
|
+
import { getFrameworkScope } from '../config.js';
|
2
|
+
import { SignalOptionsWithInit } from '../types.js';
|
3
|
+
import { DerivedSignal, createDerivedSignal } from './derived.js';
|
4
|
+
import { CURRENT_CONSUMER } from './get.js';
|
5
|
+
import { hashReactiveFn, hashValue } from './utils/hash.js';
|
6
|
+
|
7
|
+
export const CONTEXT_KEY = Symbol('signalium:context');
|
8
|
+
|
9
|
+
export type Context<T> = {
|
10
|
+
defaultValue: T;
|
11
|
+
};
|
12
|
+
|
13
|
+
export type ContextPair<T extends unknown[]> = {
|
14
|
+
[K in keyof T]: [Context<T[K]>, NoInfer<T[K]>];
|
15
|
+
};
|
16
|
+
|
17
|
+
let CONTEXT_ID = 0;
|
18
|
+
|
19
|
+
export class ContextImpl<T> {
|
20
|
+
_key: symbol;
|
21
|
+
_description: string;
|
22
|
+
|
23
|
+
constructor(
|
24
|
+
public readonly defaultValue: T,
|
25
|
+
desc?: string,
|
26
|
+
) {
|
27
|
+
this._description = desc ?? `context:${CONTEXT_ID++}`;
|
28
|
+
this._key = Symbol(this._description);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
export const createContext = <T>(initialValue: T, description?: string): Context<T> => {
|
33
|
+
return new ContextImpl(initialValue, description);
|
34
|
+
};
|
35
|
+
|
36
|
+
export class SignalScope {
|
37
|
+
constructor(contexts: [ContextImpl<unknown>, unknown][], parent?: SignalScope) {
|
38
|
+
this.parentScope = parent;
|
39
|
+
this.contexts = Object.create(parent?.contexts || null);
|
40
|
+
|
41
|
+
for (const [context, value] of contexts) {
|
42
|
+
this.contexts[context._key] = value;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
private parentScope?: SignalScope = undefined;
|
47
|
+
private contexts: Record<symbol, unknown>;
|
48
|
+
private children = new Map<number, SignalScope>();
|
49
|
+
private signals = new Map<number, DerivedSignal<any, any[]>>();
|
50
|
+
|
51
|
+
getChild(contexts: [ContextImpl<unknown>, unknown][]) {
|
52
|
+
const key = hashValue(contexts);
|
53
|
+
|
54
|
+
let child = this.children.get(key);
|
55
|
+
|
56
|
+
if (child === undefined) {
|
57
|
+
child = new SignalScope(contexts, this);
|
58
|
+
this.children.set(key, child);
|
59
|
+
}
|
60
|
+
|
61
|
+
return child;
|
62
|
+
}
|
63
|
+
|
64
|
+
getContext<T>(_context: Context<T>): T | undefined {
|
65
|
+
const context = _context as unknown as ContextImpl<T>;
|
66
|
+
|
67
|
+
return this.contexts[context._key] as T | undefined;
|
68
|
+
}
|
69
|
+
|
70
|
+
get<T, Args extends unknown[]>(
|
71
|
+
fn: (...args: Args) => T,
|
72
|
+
args: Args,
|
73
|
+
opts?: Partial<SignalOptionsWithInit<T, Args>>,
|
74
|
+
): DerivedSignal<T, Args> {
|
75
|
+
const paramKey = opts?.paramKey?.(...args);
|
76
|
+
const key = hashReactiveFn(fn, paramKey ? [paramKey] : args);
|
77
|
+
let signal = this.signals.get(key) as DerivedSignal<T, Args> | undefined;
|
78
|
+
|
79
|
+
if (signal === undefined) {
|
80
|
+
signal = createDerivedSignal(fn, args, key, this, opts);
|
81
|
+
this.signals.set(key, signal);
|
82
|
+
}
|
83
|
+
|
84
|
+
return signal;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
export let ROOT_SCOPE = new SignalScope([]);
|
89
|
+
|
90
|
+
export const clearRootScope = () => {
|
91
|
+
ROOT_SCOPE = new SignalScope([]);
|
92
|
+
};
|
93
|
+
|
94
|
+
let OVERRIDE_SCOPE: SignalScope | undefined;
|
95
|
+
|
96
|
+
export const getCurrentScope = (): SignalScope => {
|
97
|
+
return OVERRIDE_SCOPE ?? CURRENT_CONSUMER?.scope ?? getFrameworkScope() ?? ROOT_SCOPE;
|
98
|
+
};
|
99
|
+
|
100
|
+
export function withContexts<C extends unknown[], U>(contexts: [...ContextPair<C>], fn: () => U): U {
|
101
|
+
const prevScope = OVERRIDE_SCOPE;
|
102
|
+
const currentScope = getCurrentScope();
|
103
|
+
|
104
|
+
try {
|
105
|
+
OVERRIDE_SCOPE = currentScope.getChild(contexts as [ContextImpl<unknown>, unknown][]);
|
106
|
+
return fn();
|
107
|
+
} finally {
|
108
|
+
OVERRIDE_SCOPE = prevScope;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
export const withScope = <T>(scope: SignalScope, fn: () => T) => {
|
113
|
+
const prevScope = OVERRIDE_SCOPE;
|
114
|
+
|
115
|
+
try {
|
116
|
+
OVERRIDE_SCOPE = scope;
|
117
|
+
return fn();
|
118
|
+
} finally {
|
119
|
+
OVERRIDE_SCOPE = prevScope;
|
120
|
+
}
|
121
|
+
};
|
122
|
+
|
123
|
+
export const useContext = <T>(context: Context<T>): T => {
|
124
|
+
const scope = OVERRIDE_SCOPE ?? CURRENT_CONSUMER?.scope ?? getFrameworkScope();
|
125
|
+
|
126
|
+
if (scope === undefined) {
|
127
|
+
throw new Error(
|
128
|
+
'useContext must be used within a signal hook, a withContext, or within a framework-specific context provider.',
|
129
|
+
);
|
130
|
+
}
|
131
|
+
|
132
|
+
return scope.getContext(context) ?? (context as unknown as ContextImpl<T>).defaultValue;
|
133
|
+
};
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import WeakRef from '../weakref.js';
|
2
|
+
import { Tracer, TRACER, TracerMeta } from '../trace.js';
|
3
|
+
import { ReactiveValue, Signal, SignalEquals, SignalListener, SignalOptionsWithInit } from '../types.js';
|
4
|
+
import { getUnknownSignalFnName } from './utils/debug-name.js';
|
5
|
+
import { SignalScope } from './contexts.js';
|
6
|
+
import { checkAndRunListeners, getSignal } from './get.js';
|
7
|
+
import { Edge, EdgeType, SignalEdge } from './edge.js';
|
8
|
+
import { schedulePull, scheduleUnwatch } from './scheduling.js';
|
9
|
+
import { hashValue } from './utils/hash.js';
|
10
|
+
import { stringifyValue } from './utils/stringify.js';
|
11
|
+
import { equalsFrom } from './utils/equals.js';
|
12
|
+
|
13
|
+
/**
|
14
|
+
* This file contains computed signal base types and struct definitions.
|
15
|
+
*
|
16
|
+
* Computed signals are monomorphic to make them more efficient, but this also
|
17
|
+
* means that multiple fields differ based on the type of the signal. Defining
|
18
|
+
* them using this pattern rather than a class allows us to switch on the `type`
|
19
|
+
* field to get strong typing in branches everywhere else.
|
20
|
+
*
|
21
|
+
* "Methods" for this struct are defined in other files for better organization.
|
22
|
+
*/
|
23
|
+
|
24
|
+
export type SignalId = number;
|
25
|
+
|
26
|
+
export const enum SignalState {
|
27
|
+
Clean = 0,
|
28
|
+
Pending = 1,
|
29
|
+
Dirty = 2,
|
30
|
+
MaybeDirty = 3,
|
31
|
+
}
|
32
|
+
|
33
|
+
export const enum SignalFlags {
|
34
|
+
// State
|
35
|
+
State = 0b11,
|
36
|
+
|
37
|
+
// Properties
|
38
|
+
isSubscription = 0b100,
|
39
|
+
isListener = 0b1000,
|
40
|
+
}
|
41
|
+
|
42
|
+
let ID = 0;
|
43
|
+
|
44
|
+
interface ListenerMeta {
|
45
|
+
updatedAt: number;
|
46
|
+
current: Set<SignalListener>;
|
47
|
+
|
48
|
+
// Cached bound add method to avoid creating a new one on each call, this is
|
49
|
+
// specifically for React hooks where useSyncExternalStore will resubscribe each
|
50
|
+
// time if the method is not cached. This prevents us from having to add a
|
51
|
+
// useCallback for the listener.
|
52
|
+
cachedBoundAdd: (listener: SignalListener) => () => void;
|
53
|
+
}
|
54
|
+
|
55
|
+
export class DerivedSignal<T, Args extends unknown[]> implements Signal<ReactiveValue<T>> {
|
56
|
+
// Bitmask containing state in the first 2 bits and boolean properties in the remaining bits
|
57
|
+
private flags: number;
|
58
|
+
scope: SignalScope | undefined = undefined;
|
59
|
+
|
60
|
+
subs = new Map<WeakRef<DerivedSignal<any, any>>, Edge>();
|
61
|
+
deps = new Map<DerivedSignal<any, any>, Edge>();
|
62
|
+
|
63
|
+
ref: WeakRef<DerivedSignal<T, Args>> = new WeakRef(this);
|
64
|
+
|
65
|
+
equals: SignalEquals<any>;
|
66
|
+
dirtyHead: Edge | undefined = undefined;
|
67
|
+
|
68
|
+
updatedCount: number = 0;
|
69
|
+
computedCount: number = 0;
|
70
|
+
|
71
|
+
watchCount: number = 0;
|
72
|
+
|
73
|
+
_listeners: ListenerMeta | null = null;
|
74
|
+
|
75
|
+
compute: (...args: Args) => T;
|
76
|
+
args: Args;
|
77
|
+
value: ReactiveValue<T> | undefined;
|
78
|
+
|
79
|
+
tracerMeta?: TracerMeta;
|
80
|
+
|
81
|
+
constructor(
|
82
|
+
isSubscription: boolean,
|
83
|
+
compute: (...args: Args) => T,
|
84
|
+
args: Args,
|
85
|
+
key?: SignalId,
|
86
|
+
scope?: SignalScope,
|
87
|
+
opts?: Partial<SignalOptionsWithInit<T, Args>> & { tracer?: Tracer },
|
88
|
+
) {
|
89
|
+
this.flags = (isSubscription ? SignalFlags.isSubscription : 0) | SignalState.Dirty;
|
90
|
+
this.scope = scope;
|
91
|
+
this.compute = compute;
|
92
|
+
this.args = args;
|
93
|
+
|
94
|
+
this.equals = equalsFrom(opts?.equals);
|
95
|
+
this.value = opts?.initValue as ReactiveValue<T>;
|
96
|
+
|
97
|
+
if (TRACER) {
|
98
|
+
this.tracerMeta = {
|
99
|
+
id: opts?.id ?? key ?? hashValue([compute, ID++]),
|
100
|
+
desc: opts?.desc ?? compute.name ?? getUnknownSignalFnName(compute),
|
101
|
+
params: args.map(arg => stringifyValue(arg)).join(', '),
|
102
|
+
tracer: opts?.tracer,
|
103
|
+
};
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
get _state() {
|
108
|
+
return this.flags & SignalFlags.State;
|
109
|
+
}
|
110
|
+
|
111
|
+
set _state(state: SignalState) {
|
112
|
+
this.flags = (this.flags & ~SignalFlags.State) | state;
|
113
|
+
}
|
114
|
+
|
115
|
+
get _isListener() {
|
116
|
+
return (this.flags & SignalFlags.isListener) !== 0;
|
117
|
+
}
|
118
|
+
|
119
|
+
set _isListener(isListener: boolean) {
|
120
|
+
if (isListener) {
|
121
|
+
this.flags |= SignalFlags.isListener;
|
122
|
+
} else {
|
123
|
+
this.flags &= ~SignalFlags.isListener;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
get listeners() {
|
128
|
+
return (
|
129
|
+
this._listeners ??
|
130
|
+
(this._listeners = {
|
131
|
+
updatedAt: 0,
|
132
|
+
current: new Set(),
|
133
|
+
cachedBoundAdd: this.addListener.bind(this),
|
134
|
+
})
|
135
|
+
);
|
136
|
+
}
|
137
|
+
|
138
|
+
get(): ReactiveValue<T> {
|
139
|
+
return getSignal(this);
|
140
|
+
}
|
141
|
+
|
142
|
+
addListener(listener: SignalListener) {
|
143
|
+
const { current } = this.listeners;
|
144
|
+
|
145
|
+
if (!current.has(listener)) {
|
146
|
+
if (!this._isListener) {
|
147
|
+
this.watchCount++;
|
148
|
+
this.flags |= SignalFlags.isListener;
|
149
|
+
}
|
150
|
+
|
151
|
+
schedulePull(this);
|
152
|
+
|
153
|
+
current.add(listener);
|
154
|
+
}
|
155
|
+
|
156
|
+
return () => {
|
157
|
+
if (current.has(listener)) {
|
158
|
+
current.delete(listener);
|
159
|
+
|
160
|
+
if (current.size === 0) {
|
161
|
+
scheduleUnwatch(this);
|
162
|
+
this.flags &= ~SignalFlags.isListener;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
};
|
166
|
+
}
|
167
|
+
|
168
|
+
// This method is used in React hooks specifically. It returns a bound add method
|
169
|
+
// that is cached to avoid creating a new one on each call, and it eagerly sets
|
170
|
+
// the listener as watched so that subscriptions that are accessed will be activated.
|
171
|
+
addListenerLazy() {
|
172
|
+
if (!this._isListener) {
|
173
|
+
this.watchCount++;
|
174
|
+
this.flags |= SignalFlags.isListener;
|
175
|
+
}
|
176
|
+
|
177
|
+
return this.listeners.cachedBoundAdd;
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
export const runListeners = (signal: DerivedSignal<any, any>) => {
|
182
|
+
const { listeners } = signal;
|
183
|
+
|
184
|
+
if (listeners === null) {
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
const { current } = listeners;
|
189
|
+
|
190
|
+
for (const listener of current) {
|
191
|
+
listener();
|
192
|
+
}
|
193
|
+
};
|
194
|
+
|
195
|
+
export const isSubscription = (signal: unknown): boolean => {
|
196
|
+
return ((signal as any).flags & SignalFlags.isSubscription) !== 0;
|
197
|
+
};
|
198
|
+
|
199
|
+
export function createDerivedSignal<T, Args extends unknown[]>(
|
200
|
+
compute: (...args: Args) => T,
|
201
|
+
args: Args = [] as any,
|
202
|
+
key?: SignalId,
|
203
|
+
scope?: SignalScope,
|
204
|
+
opts?: Partial<SignalOptionsWithInit<T, Args>> & { tracer?: Tracer },
|
205
|
+
isSubscription: boolean = false,
|
206
|
+
): DerivedSignal<T, Args> {
|
207
|
+
return new DerivedSignal(isSubscription, compute, args, key, scope, opts);
|
208
|
+
}
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import { scheduleAsyncPull, schedulePull } from './scheduling.js';
|
2
|
+
import { DerivedSignal, isSubscription, SignalState } from './derived.js';
|
3
|
+
import { CURRENT_CONSUMER } from './get.js';
|
4
|
+
import { Edge } from './edge.js';
|
5
|
+
|
6
|
+
export function dirtySignal(signal: DerivedSignal<any, any>) {
|
7
|
+
const prevState = signal._state;
|
8
|
+
|
9
|
+
if (prevState === SignalState.Dirty) {
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
|
13
|
+
signal._state = SignalState.Dirty;
|
14
|
+
|
15
|
+
if (prevState !== SignalState.MaybeDirty) {
|
16
|
+
propagateDirty(signal);
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
function propagateDirty(signal: DerivedSignal<any, any>) {
|
21
|
+
if (CURRENT_CONSUMER === signal) {
|
22
|
+
throw new Error(
|
23
|
+
'A signal was dirtied after it was consumed by the current function. This can cause race conditions and infinite rerenders and is not allowed.',
|
24
|
+
);
|
25
|
+
}
|
26
|
+
|
27
|
+
if (isSubscription(signal)) {
|
28
|
+
if (signal.watchCount > 0) {
|
29
|
+
scheduleAsyncPull(signal);
|
30
|
+
}
|
31
|
+
|
32
|
+
// else do nothing, only schedule if connected
|
33
|
+
} else {
|
34
|
+
if (signal._isListener) {
|
35
|
+
schedulePull(signal);
|
36
|
+
}
|
37
|
+
|
38
|
+
dirtySignalConsumers(signal.subs);
|
39
|
+
signal.subs = new Map();
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
export function dirtySignalConsumers(map: Map<WeakRef<DerivedSignal<any, any>>, Edge>) {
|
44
|
+
for (const [subRef, edge] of map) {
|
45
|
+
const sub = subRef.deref();
|
46
|
+
|
47
|
+
if (sub === undefined || sub.computedCount !== edge.consumedAt) continue;
|
48
|
+
|
49
|
+
const dirtyState = sub._state;
|
50
|
+
|
51
|
+
switch (dirtyState) {
|
52
|
+
case SignalState.Clean:
|
53
|
+
sub._state = SignalState.MaybeDirty;
|
54
|
+
sub.dirtyHead = edge;
|
55
|
+
edge.nextDirty = undefined;
|
56
|
+
propagateDirty(sub);
|
57
|
+
break;
|
58
|
+
|
59
|
+
case SignalState.Pending:
|
60
|
+
case SignalState.MaybeDirty: {
|
61
|
+
let subEdge = sub.dirtyHead!;
|
62
|
+
const ord = edge.ord;
|
63
|
+
|
64
|
+
if (subEdge.ord > ord) {
|
65
|
+
sub.dirtyHead = edge;
|
66
|
+
edge.nextDirty = subEdge;
|
67
|
+
|
68
|
+
if (dirtyState === SignalState.Pending) {
|
69
|
+
// If the signal is pending, the first edge is the halt edge. If the
|
70
|
+
// new dirty edge is BEFORE the halt edge, then it means that something
|
71
|
+
// changed before the current halt, so we need to cancel the current computation
|
72
|
+
// and recompute.
|
73
|
+
sub._state = SignalState.MaybeDirty;
|
74
|
+
propagateDirty(sub);
|
75
|
+
}
|
76
|
+
} else {
|
77
|
+
let nextDirty = subEdge.nextDirty;
|
78
|
+
|
79
|
+
while (nextDirty !== undefined && nextDirty.ord < ord) {
|
80
|
+
subEdge = nextDirty;
|
81
|
+
nextDirty = subEdge.nextDirty;
|
82
|
+
}
|
83
|
+
|
84
|
+
edge.nextDirty = nextDirty;
|
85
|
+
subEdge!.nextDirty = edge;
|
86
|
+
}
|
87
|
+
break;
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { ReactivePromise } from './async.js';
|
2
|
+
import type { DerivedSignal } from './derived.js';
|
3
|
+
|
4
|
+
let CURRENT_ORD = 0;
|
5
|
+
|
6
|
+
export const enum EdgeType {
|
7
|
+
Signal = 0,
|
8
|
+
Promise = 1,
|
9
|
+
}
|
10
|
+
|
11
|
+
export interface EdgeTypeDep {
|
12
|
+
[EdgeType.Signal]: DerivedSignal<any, any>;
|
13
|
+
[EdgeType.Promise]: ReactivePromise<any>;
|
14
|
+
}
|
15
|
+
|
16
|
+
interface BaseEdge {
|
17
|
+
type: EdgeType;
|
18
|
+
dep: EdgeTypeDep[EdgeType];
|
19
|
+
ord: number;
|
20
|
+
updatedAt: number;
|
21
|
+
consumedAt: number;
|
22
|
+
|
23
|
+
nextDirty: Edge | undefined;
|
24
|
+
}
|
25
|
+
|
26
|
+
export interface SignalEdge extends BaseEdge {
|
27
|
+
type: EdgeType.Signal;
|
28
|
+
dep: DerivedSignal<any, any>;
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface PromiseEdge extends BaseEdge {
|
32
|
+
type: EdgeType.Promise;
|
33
|
+
dep: ReactivePromise<any>;
|
34
|
+
}
|
35
|
+
|
36
|
+
export type Edge = SignalEdge | PromiseEdge;
|
37
|
+
|
38
|
+
export function createEdge<T extends EdgeType, R extends T extends EdgeType.Signal ? SignalEdge : PromiseEdge>(
|
39
|
+
prevEdge: Edge | undefined,
|
40
|
+
type: T,
|
41
|
+
dep: EdgeTypeDep[T],
|
42
|
+
updatedAt: number,
|
43
|
+
consumedAt: number,
|
44
|
+
): R {
|
45
|
+
if (prevEdge === undefined) {
|
46
|
+
return {
|
47
|
+
type,
|
48
|
+
dep,
|
49
|
+
ord: CURRENT_ORD++,
|
50
|
+
updatedAt,
|
51
|
+
consumedAt,
|
52
|
+
nextDirty: undefined,
|
53
|
+
} as R;
|
54
|
+
}
|
55
|
+
|
56
|
+
prevEdge.ord = CURRENT_ORD++;
|
57
|
+
prevEdge.updatedAt = updatedAt;
|
58
|
+
prevEdge.consumedAt = consumedAt;
|
59
|
+
prevEdge.nextDirty = undefined;
|
60
|
+
return prevEdge as R;
|
61
|
+
}
|
62
|
+
|
63
|
+
export function insertDirty(node: DerivedSignal<any, any>, edge: Edge) {
|
64
|
+
const ord = edge.ord;
|
65
|
+
let currentEdge = node.dirtyHead;
|
66
|
+
|
67
|
+
if (currentEdge === undefined || currentEdge.ord > ord) {
|
68
|
+
node.dirtyHead = edge;
|
69
|
+
} else {
|
70
|
+
let nextEdge = currentEdge.nextDirty;
|
71
|
+
|
72
|
+
while (nextEdge !== undefined && nextEdge.ord < ord) {
|
73
|
+
currentEdge = nextEdge;
|
74
|
+
nextEdge = currentEdge.nextDirty;
|
75
|
+
}
|
76
|
+
|
77
|
+
currentEdge.nextDirty = edge;
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
export function findAndRemoveDirty(
|
82
|
+
sub: DerivedSignal<any, any>,
|
83
|
+
dep: DerivedSignal<any, any> | ReactivePromise<any>,
|
84
|
+
): Edge | undefined {
|
85
|
+
let edge = sub.dirtyHead;
|
86
|
+
|
87
|
+
if (edge === undefined) {
|
88
|
+
return undefined;
|
89
|
+
}
|
90
|
+
|
91
|
+
if (edge.dep === dep) {
|
92
|
+
sub.dirtyHead = edge.nextDirty;
|
93
|
+
return edge;
|
94
|
+
}
|
95
|
+
|
96
|
+
let nextLink = edge.nextDirty;
|
97
|
+
|
98
|
+
while (nextLink !== undefined) {
|
99
|
+
if (nextLink.dep === dep) {
|
100
|
+
edge.nextDirty = nextLink.nextDirty;
|
101
|
+
return nextLink;
|
102
|
+
}
|
103
|
+
|
104
|
+
edge = nextLink;
|
105
|
+
nextLink = edge.nextDirty;
|
106
|
+
}
|
107
|
+
|
108
|
+
return undefined;
|
109
|
+
}
|