signalium 0.2.7 → 0.3.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.
Files changed (152) hide show
  1. package/.turbo/turbo-build.log +12 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/cjs/config.d.ts +14 -5
  4. package/dist/cjs/config.d.ts.map +1 -1
  5. package/dist/cjs/config.js +23 -14
  6. package/dist/cjs/config.js.map +1 -1
  7. package/dist/cjs/debug.d.ts +3 -0
  8. package/dist/cjs/debug.d.ts.map +1 -0
  9. package/dist/cjs/debug.js +16 -0
  10. package/dist/cjs/debug.js.map +1 -0
  11. package/dist/cjs/hooks.d.ts +45 -0
  12. package/dist/cjs/hooks.d.ts.map +1 -0
  13. package/dist/cjs/hooks.js +260 -0
  14. package/dist/cjs/hooks.js.map +1 -0
  15. package/dist/cjs/index.d.ts +5 -3
  16. package/dist/cjs/index.d.ts.map +1 -1
  17. package/dist/cjs/index.js +21 -8
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/react/context.d.ts +4 -0
  20. package/dist/cjs/react/context.d.ts.map +1 -0
  21. package/dist/cjs/react/context.js +10 -0
  22. package/dist/cjs/react/context.js.map +1 -0
  23. package/dist/cjs/react/index.d.ts +5 -0
  24. package/dist/cjs/react/index.d.ts.map +1 -0
  25. package/dist/cjs/react/index.js +12 -0
  26. package/dist/cjs/react/index.js.map +1 -0
  27. package/dist/cjs/react/provider.d.ts +7 -0
  28. package/dist/cjs/react/provider.d.ts.map +1 -0
  29. package/dist/cjs/react/provider.js +13 -0
  30. package/dist/cjs/react/provider.js.map +1 -0
  31. package/dist/cjs/react/signal-value.d.ts +3 -0
  32. package/dist/cjs/react/signal-value.d.ts.map +1 -0
  33. package/dist/cjs/react/signal-value.js +42 -0
  34. package/dist/cjs/react/signal-value.js.map +1 -0
  35. package/dist/cjs/react/state.d.ts +3 -0
  36. package/dist/cjs/react/state.d.ts.map +1 -0
  37. package/dist/cjs/react/state.js +13 -0
  38. package/dist/cjs/react/state.js.map +1 -0
  39. package/dist/cjs/scheduling.d.ts +5 -0
  40. package/dist/cjs/scheduling.d.ts.map +1 -1
  41. package/dist/cjs/scheduling.js +59 -5
  42. package/dist/cjs/scheduling.js.map +1 -1
  43. package/dist/cjs/signals.d.ts +28 -65
  44. package/dist/cjs/signals.d.ts.map +1 -1
  45. package/dist/cjs/signals.js +223 -65
  46. package/dist/cjs/signals.js.map +1 -1
  47. package/dist/cjs/trace.d.ts +127 -0
  48. package/dist/cjs/trace.d.ts.map +1 -0
  49. package/dist/cjs/trace.js +319 -0
  50. package/dist/cjs/trace.js.map +1 -0
  51. package/dist/cjs/types.d.ts +66 -0
  52. package/dist/cjs/types.d.ts.map +1 -0
  53. package/dist/cjs/types.js +3 -0
  54. package/dist/cjs/types.js.map +1 -0
  55. package/dist/cjs/utils.d.ts +4 -0
  56. package/dist/cjs/utils.d.ts.map +1 -0
  57. package/dist/cjs/utils.js +80 -0
  58. package/dist/cjs/utils.js.map +1 -0
  59. package/dist/esm/config.d.ts +14 -5
  60. package/dist/esm/config.d.ts.map +1 -1
  61. package/dist/esm/config.js +19 -11
  62. package/dist/esm/config.js.map +1 -1
  63. package/dist/esm/debug.d.ts +3 -0
  64. package/dist/esm/debug.d.ts.map +1 -0
  65. package/dist/esm/debug.js +3 -0
  66. package/dist/esm/debug.js.map +1 -0
  67. package/dist/esm/hooks.d.ts +45 -0
  68. package/dist/esm/hooks.d.ts.map +1 -0
  69. package/dist/esm/hooks.js +243 -0
  70. package/dist/esm/hooks.js.map +1 -0
  71. package/dist/esm/index.d.ts +5 -3
  72. package/dist/esm/index.d.ts.map +1 -1
  73. package/dist/esm/index.js +4 -2
  74. package/dist/esm/index.js.map +1 -1
  75. package/dist/esm/react/context.d.ts +4 -0
  76. package/dist/esm/react/context.d.ts.map +1 -0
  77. package/dist/esm/react/context.js +6 -0
  78. package/dist/esm/react/context.js.map +1 -0
  79. package/dist/esm/react/index.d.ts +5 -0
  80. package/dist/esm/react/index.d.ts.map +1 -0
  81. package/dist/esm/react/index.js +5 -0
  82. package/dist/esm/react/index.js.map +1 -0
  83. package/dist/esm/react/provider.d.ts +7 -0
  84. package/dist/esm/react/provider.d.ts.map +1 -0
  85. package/dist/esm/react/provider.js +10 -0
  86. package/dist/esm/react/provider.js.map +1 -0
  87. package/dist/esm/react/signal-value.d.ts +3 -0
  88. package/dist/esm/react/signal-value.d.ts.map +1 -0
  89. package/dist/esm/react/signal-value.js +38 -0
  90. package/dist/esm/react/signal-value.js.map +1 -0
  91. package/dist/esm/react/state.d.ts +3 -0
  92. package/dist/esm/react/state.d.ts.map +1 -0
  93. package/dist/esm/react/state.js +10 -0
  94. package/dist/esm/react/state.js.map +1 -0
  95. package/dist/esm/scheduling.d.ts +5 -0
  96. package/dist/esm/scheduling.d.ts.map +1 -1
  97. package/dist/esm/scheduling.js +51 -1
  98. package/dist/esm/scheduling.js.map +1 -1
  99. package/dist/esm/signals.d.ts +28 -65
  100. package/dist/esm/signals.d.ts.map +1 -1
  101. package/dist/esm/signals.js +215 -61
  102. package/dist/esm/signals.js.map +1 -1
  103. package/dist/esm/trace.d.ts +127 -0
  104. package/dist/esm/trace.d.ts.map +1 -0
  105. package/dist/esm/trace.js +311 -0
  106. package/dist/esm/trace.js.map +1 -0
  107. package/dist/esm/types.d.ts +66 -0
  108. package/dist/esm/types.d.ts.map +1 -0
  109. package/dist/esm/types.js +2 -0
  110. package/dist/esm/types.js.map +1 -0
  111. package/dist/esm/utils.d.ts +4 -0
  112. package/dist/esm/utils.d.ts.map +1 -0
  113. package/dist/esm/utils.js +75 -0
  114. package/dist/esm/utils.js.map +1 -0
  115. package/package.json +43 -2
  116. package/src/__tests__/hooks/async-computed.test.ts +190 -0
  117. package/src/__tests__/hooks/async-task.test.ts +227 -0
  118. package/src/__tests__/hooks/computed.test.ts +126 -0
  119. package/src/__tests__/hooks/context.test.ts +527 -0
  120. package/src/__tests__/hooks/nesting.test.ts +303 -0
  121. package/src/__tests__/hooks/params-and-state.test.ts +168 -0
  122. package/src/__tests__/hooks/subscription.test.ts +97 -0
  123. package/src/__tests__/signals/async.test.ts +416 -0
  124. package/src/__tests__/signals/basic.test.ts +399 -0
  125. package/src/__tests__/signals/subscription.test.ts +632 -0
  126. package/src/__tests__/signals/watcher.test.ts +253 -0
  127. package/src/__tests__/utils/async.ts +6 -0
  128. package/src/__tests__/utils/builders.ts +22 -0
  129. package/src/__tests__/utils/instrumented-hooks.ts +309 -0
  130. package/src/__tests__/utils/instrumented-signals.ts +281 -0
  131. package/src/__tests__/utils/permute.ts +74 -0
  132. package/src/config.ts +32 -17
  133. package/src/debug.ts +14 -0
  134. package/src/hooks.ts +429 -0
  135. package/src/index.ts +28 -3
  136. package/src/react/__tests__/react.test.tsx +135 -0
  137. package/src/react/context.ts +8 -0
  138. package/src/react/index.ts +4 -0
  139. package/src/react/provider.tsx +18 -0
  140. package/src/react/signal-value.ts +56 -0
  141. package/src/react/state.ts +13 -0
  142. package/src/scheduling.ts +69 -1
  143. package/src/signals.ts +331 -157
  144. package/src/trace.ts +449 -0
  145. package/src/types.ts +86 -0
  146. package/src/utils.ts +83 -0
  147. package/tsconfig.json +2 -1
  148. package/vitest.workspace.ts +24 -0
  149. package/src/__tests__/async.test.ts +0 -426
  150. package/src/__tests__/basic.test.ts +0 -378
  151. package/src/__tests__/subscription.test.ts +0 -645
  152. package/src/__tests__/utils/instrumented.ts +0 -326
package/src/signals.ts CHANGED
@@ -1,12 +1,34 @@
1
- import { scheduleDirty, scheduleDisconnect, schedulePull, scheduleWatcher } from './scheduling.js';
1
+ import {
2
+ scheduleConnect,
3
+ scheduleDirty,
4
+ scheduleDisconnect,
5
+ scheduleEffect,
6
+ schedulePull,
7
+ scheduleWatcher,
8
+ } from './scheduling.js';
2
9
  import WeakRef from './weakref.js';
10
+ import { TRACER as TRACER, TracerEventType, VisualizerNodeType } from './trace.js';
11
+ import {
12
+ AsyncResult,
13
+ AsyncSignal,
14
+ AsyncTask,
15
+ Signal,
16
+ SignalAsyncCompute,
17
+ SignalCompute,
18
+ SignalEquals,
19
+ SignalOptions,
20
+ SignalOptionsWithInit,
21
+ SignalSubscribe,
22
+ SignalSubscription,
23
+ Watcher,
24
+ WatcherListenerOptions,
25
+ } from './types.js';
3
26
 
4
27
  let CURRENT_ORD = 0;
5
28
  let CURRENT_CONSUMER: ComputedSignal<any> | undefined;
6
29
  let CURRENT_IS_WAITING: boolean = false;
7
30
 
8
- let ID = 0;
9
-
31
+ // Should not leave the file so it doesn't become an interop issue
10
32
  const enum SignalType {
11
33
  Computed,
12
34
  Subscription,
@@ -14,39 +36,6 @@ const enum SignalType {
14
36
  Watcher,
15
37
  }
16
38
 
17
- export interface Signal<T = unknown> {
18
- get(): T;
19
- }
20
-
21
- export interface WriteableSignal<T> extends Signal<T> {
22
- set(value: T): void;
23
- }
24
-
25
- export type AsyncSignal<T> = Signal<AsyncResult<T>>;
26
-
27
- export type SignalCompute<T> = (prev: T | undefined) => T;
28
-
29
- export type SignalAsyncCompute<T> = (prev: T | undefined) => T | Promise<T>;
30
-
31
- export type SignalWatcherEffect = () => void;
32
-
33
- export type SignalEquals<T> = (prev: T, next: T) => boolean;
34
-
35
- export type SignalSubscription = {
36
- update?(): void;
37
- unsubscribe?(): void;
38
- };
39
-
40
- export type SignalSubscribe<T> = (get: () => T, set: (value: T) => void) => SignalSubscription | undefined | void;
41
-
42
- export interface SignalOptions<T> {
43
- equals?: SignalEquals<T>;
44
- }
45
-
46
- export interface SignalOptionsWithInit<T> extends SignalOptions<T> {
47
- initValue: T;
48
- }
49
-
50
39
  const SUBSCRIPTIONS = new WeakMap<ComputedSignal<any>, SignalSubscription | undefined | void>();
51
40
  const ACTIVE_ASYNCS = new WeakMap<ComputedSignal<any>, Promise<unknown>>();
52
41
 
@@ -68,8 +57,28 @@ interface Link {
68
57
  nextDirty: Link | undefined;
69
58
  }
70
59
 
60
+ const FALSE_EQUALS: SignalEquals<unknown> = () => false;
61
+
62
+ export function signalTypeToVisualizerType(type: SignalType): VisualizerNodeType {
63
+ switch (type) {
64
+ case SignalType.Computed:
65
+ return VisualizerNodeType.Computed;
66
+ case SignalType.Subscription:
67
+ return VisualizerNodeType.Subscription;
68
+ case SignalType.Async:
69
+ return VisualizerNodeType.AsyncComputed;
70
+ case SignalType.Watcher:
71
+ return VisualizerNodeType.Watcher;
72
+ }
73
+ }
74
+
75
+ interface InternalSignalOptions<T> extends SignalOptions<T, unknown[]> {
76
+ equals: SignalEquals<T>;
77
+ id: string;
78
+ subscribers?: ((value: T) => void)[];
79
+ }
80
+
71
81
  export class ComputedSignal<T> {
72
- _id = ID++;
73
82
  _type: SignalType;
74
83
 
75
84
  _deps = new Map<ComputedSignal<any>, Link>();
@@ -81,21 +90,20 @@ export class ComputedSignal<T> {
81
90
  _computedCount: number = 0;
82
91
  _connectedCount: number = 0;
83
92
  _currentValue: T | AsyncResult<T> | undefined;
84
- _compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined;
93
+ _compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>;
85
94
 
86
- _equals: SignalEquals<T>;
95
+ _opts: InternalSignalOptions<T>;
87
96
  _ref: WeakRef<ComputedSignal<T>> = new WeakRef(this);
88
97
 
89
98
  constructor(
90
99
  type: SignalType,
91
- compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T> | undefined,
92
- equals?: SignalEquals<T>,
100
+ compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>,
101
+ opts: InternalSignalOptions<T>,
93
102
  initValue?: T,
94
103
  ) {
95
104
  this._type = type;
96
105
  this._compute = compute;
97
- this._equals = equals ?? ((a, b) => a === b);
98
- this._connectedCount = type === SignalType.Watcher ? 1 : 0;
106
+ this._opts = opts;
99
107
 
100
108
  this._currentValue =
101
109
  type !== SignalType.Async
@@ -122,6 +130,11 @@ export class ComputedSignal<T> {
122
130
  );
123
131
  }
124
132
 
133
+ TRACER?.emit({
134
+ type: TracerEventType.StartLoading,
135
+ id: CURRENT_CONSUMER._opts.id,
136
+ });
137
+
125
138
  const value = this._currentValue as AsyncResult<T>;
126
139
 
127
140
  if (value.isPending) {
@@ -144,6 +157,17 @@ export class ComputedSignal<T> {
144
157
  const { _deps: deps, _computedCount: computedCount, _connectedCount: connectedCount } = CURRENT_CONSUMER;
145
158
  const prevLink = deps.get(this);
146
159
 
160
+ if (prevLink === undefined) {
161
+ TRACER?.emit({
162
+ type: TracerEventType.Connected,
163
+ id: CURRENT_CONSUMER._opts.id,
164
+ childId: this._opts.id,
165
+ name: this._opts.desc,
166
+ params: this._opts.params,
167
+ nodeType: signalTypeToVisualizerType(this._type),
168
+ });
169
+ }
170
+
147
171
  const ord = CURRENT_ORD++;
148
172
 
149
173
  this._check(!prevLink && connectedCount > 0);
@@ -164,7 +188,6 @@ export class ComputedSignal<T> {
164
188
  prevLink.ord = ord;
165
189
  prevLink.version = this._version;
166
190
  prevLink.consumedAt = computedCount;
167
- // prevLink.nextDirty = undefined;
168
191
  this._subs.add(prevLink);
169
192
  }
170
193
  } else {
@@ -174,8 +197,7 @@ export class ComputedSignal<T> {
174
197
  return this._currentValue!;
175
198
  }
176
199
 
177
- _check(shouldWatch = false): number {
178
- // COUNTS.checks++;
200
+ _check(shouldWatch = false, connectCount = 1, immediate = false): number {
179
201
  let state = this._state;
180
202
  let connectedCount = this._connectedCount;
181
203
 
@@ -183,7 +205,7 @@ export class ComputedSignal<T> {
183
205
  const shouldConnect = shouldWatch && !wasConnected;
184
206
 
185
207
  if (shouldWatch) {
186
- this._connectedCount = connectedCount = connectedCount + 1;
208
+ this._connectedCount = connectedCount = connectedCount + connectCount;
187
209
  }
188
210
 
189
211
  if (shouldConnect) {
@@ -219,7 +241,7 @@ export class ComputedSignal<T> {
219
241
  }
220
242
 
221
243
  if (state === SignalState.Dirty) {
222
- this._run(wasConnected, shouldConnect);
244
+ this._run(wasConnected, shouldConnect, immediate);
223
245
  } else {
224
246
  this._resetDirty();
225
247
  }
@@ -230,7 +252,12 @@ export class ComputedSignal<T> {
230
252
  return this._version;
231
253
  }
232
254
 
233
- _run(wasConnected: boolean, shouldConnect: boolean) {
255
+ _run(wasConnected: boolean, shouldConnect: boolean, immediate = false) {
256
+ TRACER?.emit({
257
+ type: TracerEventType.StartUpdate,
258
+ id: this._opts.id,
259
+ });
260
+
234
261
  const { _type: type } = this;
235
262
 
236
263
  const prevConsumer = CURRENT_CONSUMER;
@@ -243,13 +270,15 @@ export class ComputedSignal<T> {
243
270
 
244
271
  switch (type) {
245
272
  case SignalType.Computed: {
246
- const prevValue = this._currentValue as T;
273
+ const version = this._version;
274
+ const prevValue = this._currentValue as T | undefined;
247
275
  const nextValue = (this._compute as SignalCompute<T>)(prevValue);
248
276
 
249
- if (!this._equals(prevValue!, nextValue)) {
277
+ if (version === 0 || !this._opts.equals(prevValue!, nextValue)) {
250
278
  this._currentValue = nextValue;
251
- this._version++;
279
+ this._version = version + 1;
252
280
  }
281
+
253
282
  break;
254
283
  }
255
284
 
@@ -292,34 +321,47 @@ export class ComputedSignal<T> {
292
321
  } else if (nextValue instanceof Promise) {
293
322
  const currentVersion = ++this._version;
294
323
 
295
- nextValue = nextValue.then(
296
- result => {
297
- if (currentVersion !== this._version) {
298
- return;
299
- }
324
+ TRACER?.emit({
325
+ type: TracerEventType.StartLoading,
326
+ id: this._opts.id,
327
+ });
300
328
 
301
- value.result = result;
302
- value.isReady = true;
303
- value.didResolve = true;
329
+ nextValue = nextValue
330
+ .then(
331
+ result => {
332
+ if (currentVersion !== this._version) {
333
+ return;
334
+ }
304
335
 
305
- value.isPending = false;
306
- value.isSuccess = true;
336
+ value.result = result;
337
+ value.isReady = true;
338
+ value.didResolve = true;
307
339
 
308
- this._version++;
309
- scheduleDirty(this);
310
- },
311
- error => {
312
- if (currentVersion !== this._version || error === WAITING) {
313
- return;
314
- }
340
+ value.isPending = false;
341
+ value.isSuccess = true;
315
342
 
316
- value.error = error;
317
- value.isPending = false;
318
- value.isError = true;
319
- this._version++;
320
- scheduleDirty(this);
321
- },
322
- );
343
+ this._version++;
344
+ scheduleDirty(this);
345
+ },
346
+ error => {
347
+ if (currentVersion !== this._version || error === WAITING) {
348
+ return;
349
+ }
350
+
351
+ value.error = error;
352
+ value.isPending = false;
353
+ value.isError = true;
354
+ this._version++;
355
+ scheduleDirty(this);
356
+ },
357
+ )
358
+ .finally(() => {
359
+ TRACER?.emit({
360
+ type: TracerEventType.EndLoading,
361
+ id: this._opts.id,
362
+ value: value,
363
+ });
364
+ });
323
365
 
324
366
  ACTIVE_ASYNCS.set(this, nextValue);
325
367
 
@@ -334,6 +376,12 @@ export class ComputedSignal<T> {
334
376
  value.isError = false;
335
377
 
336
378
  this._version++;
379
+
380
+ TRACER?.emit({
381
+ type: TracerEventType.EndLoading,
382
+ id: this._opts.id,
383
+ value: value,
384
+ });
337
385
  }
338
386
 
339
387
  break;
@@ -344,12 +392,27 @@ export class ComputedSignal<T> {
344
392
  const subscription = (this._compute as SignalSubscribe<T>)(
345
393
  () => this._currentValue as T,
346
394
  value => {
347
- if (this._equals(value, this._currentValue as T)) {
395
+ const version = this._version;
396
+
397
+ if (version !== 0 && this._opts.equals(value, this._currentValue as T)) {
348
398
  return;
349
399
  }
400
+
401
+ TRACER?.emit({
402
+ type: TracerEventType.StartUpdate,
403
+ id: this._opts.id,
404
+ });
405
+
350
406
  this._currentValue = value;
351
- this._version++;
407
+ this._version = version + 1;
352
408
  this._dirtyConsumers();
409
+
410
+ TRACER?.emit({
411
+ type: TracerEventType.EndUpdate,
412
+ id: this._opts.id,
413
+ value: this._currentValue,
414
+ preserveChildren: true,
415
+ });
353
416
  },
354
417
  );
355
418
  SUBSCRIPTIONS.set(this, subscription);
@@ -363,23 +426,52 @@ export class ComputedSignal<T> {
363
426
  }
364
427
 
365
428
  default: {
366
- (this._compute as SignalWatcherEffect)!();
429
+ const version = this._version;
430
+ const prevValue = this._currentValue as T | undefined;
431
+ const nextValue = (this._compute as SignalCompute<T>)(prevValue);
432
+
433
+ if (version === 0 || !this._opts.equals(prevValue!, nextValue)) {
434
+ this._currentValue = nextValue;
435
+ this._version = version + 1;
436
+
437
+ if (immediate) {
438
+ this._runEffects();
439
+ } else {
440
+ scheduleEffect(this);
441
+ }
442
+ }
443
+
444
+ break;
367
445
  }
368
446
  }
369
447
  } finally {
370
- const deps = this._deps;
448
+ TRACER?.emit({
449
+ type: TracerEventType.EndUpdate,
450
+ id: this._opts.id,
451
+ value: this._currentValue,
452
+ });
371
453
 
372
- for (const link of deps.values()) {
373
- if (link.consumedAt === this._computedCount) continue;
454
+ if (this._type !== SignalType.Watcher) {
455
+ const deps = this._deps;
374
456
 
375
- const dep = link.dep;
457
+ for (const link of deps.values()) {
458
+ if (link.consumedAt === this._computedCount) continue;
376
459
 
377
- if (wasConnected) {
378
- scheduleDisconnect(dep);
379
- }
460
+ const dep = link.dep;
380
461
 
381
- deps.delete(dep);
382
- dep._subs.delete(link);
462
+ if (wasConnected) {
463
+ scheduleDisconnect(dep);
464
+ }
465
+
466
+ TRACER?.emit({
467
+ type: TracerEventType.Disconnected,
468
+ id: this._opts.id,
469
+ childId: dep._opts.id,
470
+ });
471
+
472
+ deps.delete(dep);
473
+ dep._subs.delete(link);
474
+ }
383
475
  }
384
476
 
385
477
  CURRENT_CONSUMER = prevConsumer;
@@ -388,10 +480,8 @@ export class ComputedSignal<T> {
388
480
 
389
481
  _resetDirty() {
390
482
  let dirty = this._dirtyDep;
391
- // COUNTS.dirtyResetIterations++;
392
483
 
393
484
  while (dirty !== undefined) {
394
- // COUNTS.dirtyResetIterations++;
395
485
  dirty.dep._subs.add(dirty);
396
486
 
397
487
  let nextDirty = dirty.nextDirty;
@@ -430,7 +520,6 @@ export class ComputedSignal<T> {
430
520
  } else {
431
521
  let nextDirty = dirty!.nextDirty;
432
522
  while (nextDirty !== undefined && nextDirty!.ord < ord) {
433
- // COUNTS.dirtyInsertIterations++;
434
523
  dirty = nextDirty;
435
524
  nextDirty = dirty.nextDirty;
436
525
  }
@@ -475,45 +564,63 @@ export class ComputedSignal<T> {
475
564
  dep._disconnect();
476
565
  }
477
566
  }
478
- }
479
567
 
480
- export interface AsyncBaseResult<T> {
481
- invalidate(): void;
482
- await(): T;
483
- }
568
+ _runEffects() {
569
+ for (const subscriber of this._opts.subscribers!) {
570
+ subscriber(this._currentValue as T);
571
+ }
572
+ }
484
573
 
485
- export interface AsyncPending<T> extends AsyncBaseResult<T> {
486
- result: undefined;
487
- error: unknown;
488
- isPending: boolean;
489
- isReady: false;
490
- isError: boolean;
491
- isSuccess: boolean;
492
- didResolve: boolean;
493
- }
574
+ addListener(subscriber: (value: T) => void, opts?: WatcherListenerOptions) {
575
+ const subscribers = this._opts.subscribers!;
576
+ const index = subscribers.indexOf(subscriber);
577
+
578
+ if (index === -1) {
579
+ subscribers.push(subscriber);
494
580
 
495
- export interface AsyncReady<T> extends AsyncBaseResult<T> {
496
- result: T;
497
- error: unknown;
498
- isPending: boolean;
499
- isReady: true;
500
- isError: boolean;
501
- isSuccess: boolean;
502
- didResolve: boolean;
581
+ if (opts?.immediate) {
582
+ this._check(true, 1, true);
583
+ } else {
584
+ scheduleConnect(this);
585
+ }
586
+ }
587
+
588
+ return () => {
589
+ const index = subscribers.indexOf(subscriber);
590
+
591
+ if (index !== -1) {
592
+ subscribers.splice(index, 1);
593
+ scheduleDisconnect(this);
594
+ }
595
+ };
596
+ }
503
597
  }
504
598
 
505
- export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
599
+ let STATE_ID = 0;
506
600
 
507
- class StateSignal<T> implements StateSignal<T> {
508
- private _subs: WeakRef<ComputedSignal<unknown>>[] = [];
601
+ export class StateSignal<T> implements StateSignal<T> {
602
+ _subs: WeakRef<ComputedSignal<unknown>>[] = [];
603
+ _desc: string;
509
604
 
510
605
  constructor(
511
606
  private _value: T,
512
607
  private _equals: SignalEquals<T> = (a, b) => a === b,
513
- ) {}
608
+ desc: string = 'state',
609
+ ) {
610
+ this._desc = `${desc}${STATE_ID++}`;
611
+ }
514
612
 
515
613
  get(): T {
516
614
  if (CURRENT_CONSUMER !== undefined) {
615
+ TRACER?.emit({
616
+ type: TracerEventType.ConsumeState,
617
+ id: CURRENT_CONSUMER._opts.id,
618
+ childId: this._desc,
619
+ value: this._value,
620
+ setValue: (value: unknown) => {
621
+ this.set(value as T);
622
+ },
623
+ });
517
624
  this._subs.push(CURRENT_CONSUMER._ref);
518
625
  }
519
626
 
@@ -551,64 +658,132 @@ class StateSignal<T> implements StateSignal<T> {
551
658
  }
552
659
  }
553
660
 
554
- export function state<T>(initialValue: T, opts?: SignalOptions<T>): StateSignal<T> {
555
- return new StateSignal(initialValue, opts?.equals);
661
+ let UNKNOWN_SIGNAL_ID = 0;
662
+
663
+ const normalizeOpts = <T>(
664
+ opts?: SignalOptions<T, unknown[]> & { subscribers?: ((value: T) => void)[] },
665
+ ): InternalSignalOptions<T> => {
666
+ return {
667
+ equals: opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b)),
668
+ id: opts?.id ?? `unknownSignal${UNKNOWN_SIGNAL_ID++}`,
669
+ desc: opts?.desc,
670
+ params: opts?.params,
671
+ };
672
+ };
673
+
674
+ export function createStateSignal<T>(
675
+ initialValue: T,
676
+ opts?: Omit<SignalOptions<T, unknown[]>, 'paramKey'>,
677
+ ): StateSignal<T> {
678
+ const equals = opts?.equals === false ? FALSE_EQUALS : (opts?.equals ?? ((a, b) => a === b));
679
+
680
+ return new StateSignal(initialValue, equals, opts?.desc);
556
681
  }
557
682
 
558
- export function computed<T>(compute: (prev: T | undefined) => T, opts?: SignalOptions<T>): Signal<T> {
559
- return new ComputedSignal(SignalType.Computed, compute, opts?.equals) as Signal<T>;
683
+ export function createComputedSignal<T>(
684
+ compute: (prev: T | undefined) => T,
685
+ opts?: SignalOptions<T, unknown[]>,
686
+ ): Signal<T> {
687
+ return new ComputedSignal(SignalType.Computed, compute, normalizeOpts(opts)) as Signal<T>;
560
688
  }
561
689
 
562
- export function asyncComputed<T>(compute: (prev: T | undefined) => Promise<T>, opts?: SignalOptions<T>): AsyncSignal<T>;
563
- export function asyncComputed<T>(
690
+ export function createAsyncComputedSignal<T>(
691
+ compute: (prev: T | undefined) => Promise<T>,
692
+ opts?: SignalOptions<T, unknown[]>,
693
+ ): AsyncSignal<T>;
694
+ export function createAsyncComputedSignal<T>(
564
695
  compute: (prev: T | undefined) => Promise<T>,
565
- opts: SignalOptionsWithInit<T>,
696
+ opts: SignalOptionsWithInit<T, unknown[]>,
566
697
  ): AsyncSignal<T>;
567
- export function asyncComputed<T>(
698
+ export function createAsyncComputedSignal<T>(
568
699
  compute: (prev: T | undefined) => Promise<T>,
569
- opts?: Partial<SignalOptionsWithInit<T>>,
700
+ opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
570
701
  ): AsyncSignal<T> {
571
- return new ComputedSignal(SignalType.Async, compute, opts?.equals, opts?.initValue) as AsyncSignal<T>;
702
+ return new ComputedSignal(SignalType.Async, compute, normalizeOpts(opts), opts?.initValue) as AsyncSignal<T>;
572
703
  }
573
704
 
574
- export function subscription<T>(subscribe: SignalSubscribe<T>, opts?: SignalOptions<T>): Signal<T | undefined>;
575
- export function subscription<T>(subscribe: SignalSubscribe<T>, opts: SignalOptionsWithInit<T>): Signal<T>;
576
- export function subscription<T>(subscribe: SignalSubscribe<T>, opts?: Partial<SignalOptionsWithInit<T>>): Signal<T> {
577
- return new ComputedSignal(SignalType.Subscription, subscribe, opts?.equals, opts?.initValue) as Signal<T>;
705
+ export function createSubscriptionSignal<T>(
706
+ subscribe: SignalSubscribe<T>,
707
+ opts?: SignalOptions<T, unknown[]>,
708
+ ): Signal<T | undefined>;
709
+ export function createSubscriptionSignal<T>(
710
+ subscribe: SignalSubscribe<T>,
711
+ opts: SignalOptionsWithInit<T, unknown[]>,
712
+ ): Signal<T>;
713
+ export function createSubscriptionSignal<T>(
714
+ subscribe: SignalSubscribe<T>,
715
+ opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
716
+ ): Signal<T> {
717
+ return new ComputedSignal(SignalType.Subscription, subscribe, normalizeOpts(opts), opts?.initValue) as Signal<T>;
578
718
  }
579
719
 
580
- export interface Watcher {
581
- disconnect(): void;
582
- subscribe(subscriber: () => void): () => void;
583
- }
720
+ export function createAsyncTaskSignal<T, Args extends unknown[]>(
721
+ fn: (...args: Args) => Promise<T>,
722
+ ): Signal<AsyncTask<T, Args>> {
723
+ let currentPromise: Promise<T> | undefined;
724
+
725
+ const task: AsyncTask<T, Args> = {
726
+ result: undefined,
727
+ error: undefined,
728
+ isPending: false,
729
+ isSuccess: false,
730
+ isError: false,
731
+ isReady: false,
732
+
733
+ async run(...params) {
734
+ if (!task.isPending) {
735
+ currentPromise = run(...params);
736
+ }
584
737
 
585
- export function watcher(fn: () => void): Watcher {
586
- const subscribers: (() => void)[] = [];
587
- const watcher = new ComputedSignal(SignalType.Watcher, () => {
588
- fn();
738
+ return currentPromise!;
739
+ },
740
+ };
589
741
 
590
- untrack(() => {
591
- for (const subscriber of subscribers) {
592
- subscriber();
593
- }
594
- });
595
- });
742
+ const run = async (...params: Args) => {
743
+ try {
744
+ task.isPending = true;
745
+ task.isError = false;
746
+ task.isSuccess = false;
596
747
 
597
- scheduleWatcher(watcher);
748
+ signal.set(task);
598
749
 
599
- return {
600
- disconnect() {
601
- scheduleDisconnect(watcher);
602
- },
750
+ const result = await fn(...params);
603
751
 
604
- subscribe(subscriber: () => void) {
605
- subscribers.push(subscriber);
752
+ task.result = result;
753
+ task.isSuccess = true;
754
+ task.isReady = true;
606
755
 
607
- return () => {
608
- subscribers.splice(subscribers.indexOf(subscriber), 1);
609
- };
610
- },
756
+ return result;
757
+ } catch (error) {
758
+ task.error = error;
759
+ task.isError = true;
760
+
761
+ throw error;
762
+ } finally {
763
+ task.isPending = false;
764
+ signal.set(task);
765
+ }
611
766
  };
767
+
768
+ const signal = createStateSignal<AsyncTask<T, Args>>(task, { equals: false });
769
+
770
+ return signal;
771
+ }
772
+
773
+ export function createWatcherSignal<T>(fn: (prev: T | undefined) => T, opts?: SignalOptions<T, unknown[]>): Watcher<T> {
774
+ const normalizedOpts = normalizeOpts({
775
+ equals: FALSE_EQUALS,
776
+ subscribers: [],
777
+ ...opts,
778
+ });
779
+
780
+ normalizedOpts.subscribers = [];
781
+
782
+ return new ComputedSignal(SignalType.Watcher, fn, normalizedOpts);
783
+ }
784
+
785
+ export function getCurrentConsumer(): ComputedSignal<any> | undefined {
786
+ return CURRENT_CONSUMER;
612
787
  }
613
788
 
614
789
  export function isTracking(): boolean {
@@ -620,7 +795,6 @@ export function untrack<T = void>(fn: () => T): T {
620
795
 
621
796
  try {
622
797
  CURRENT_CONSUMER = undefined;
623
- // LAST_CONSUMED = undefined;
624
798
 
625
799
  return fn();
626
800
  } finally {