signalium 0.3.1 → 0.3.3

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 (41) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/CHANGELOG.md +12 -0
  3. package/dist/cjs/hooks.d.ts +2 -7
  4. package/dist/cjs/hooks.d.ts.map +1 -1
  5. package/dist/cjs/hooks.js +10 -28
  6. package/dist/cjs/hooks.js.map +1 -1
  7. package/dist/cjs/index.d.ts +2 -2
  8. package/dist/cjs/index.d.ts.map +1 -1
  9. package/dist/cjs/index.js +2 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/signals.d.ts +4 -4
  12. package/dist/cjs/signals.d.ts.map +1 -1
  13. package/dist/cjs/signals.js +38 -21
  14. package/dist/cjs/signals.js.map +1 -1
  15. package/dist/cjs/types.d.ts +7 -3
  16. package/dist/cjs/types.d.ts.map +1 -1
  17. package/dist/esm/hooks.d.ts +2 -7
  18. package/dist/esm/hooks.d.ts.map +1 -1
  19. package/dist/esm/hooks.js +10 -28
  20. package/dist/esm/hooks.js.map +1 -1
  21. package/dist/esm/index.d.ts +2 -2
  22. package/dist/esm/index.d.ts.map +1 -1
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/signals.d.ts +4 -4
  26. package/dist/esm/signals.d.ts.map +1 -1
  27. package/dist/esm/signals.js +38 -21
  28. package/dist/esm/signals.js.map +1 -1
  29. package/dist/esm/types.d.ts +7 -3
  30. package/dist/esm/types.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/__tests__/hooks/async-task.test.ts +110 -3
  33. package/src/__tests__/signals/subscription.test.ts +6 -6
  34. package/src/__tests__/signals/watcher.test.ts +2 -2
  35. package/src/__tests__/utils/instrumented-hooks.ts +1 -2
  36. package/src/__tests__/utils/instrumented-signals.ts +23 -13
  37. package/src/__tests__/utils/permute.ts +3 -3
  38. package/src/hooks.ts +27 -64
  39. package/src/index.ts +2 -0
  40. package/src/signals.ts +48 -34
  41. package/src/types.ts +11 -6
@@ -7,8 +7,7 @@ import {
7
7
  watcher,
8
8
  SignalSubscribe,
9
9
  withContext,
10
- clearRootScope,
11
- } from '../../hooks.js';
10
+ } from '../../index.js';
12
11
  import { SignalOptionsWithInit, SignalSubscription, Watcher } from '../../types.js';
13
12
 
14
13
  class SignalHookCounts {
@@ -124,7 +124,7 @@ expect.extend({
124
124
  },
125
125
  });
126
126
 
127
- export const createStateSignal = <T>(initialValue: T, opts?: SignalOptions<T>): WriteableSignal<T> => {
127
+ export const createStateSignal = <T>(initialValue: T, opts?: SignalOptions<T, []>): WriteableSignal<T> => {
128
128
  const desc = opts?.desc || 'unlabeled';
129
129
  const s = _createStateSignal(initialValue, opts);
130
130
 
@@ -211,35 +211,45 @@ export const createSubscriptionSignal: typeof _createSubscriptionSignal = (subsc
211
211
  const desc = opts?.desc || 'unlabeled';
212
212
  const counts = new SignalCounts(desc);
213
213
 
214
- const s = _createSubscriptionSignal((get, set) => {
214
+ const s = _createSubscriptionSignal(({ get, set }) => {
215
215
  counts.subscribe++;
216
216
 
217
217
  if (desc) {
218
218
  currentOrder?.push(desc);
219
219
  }
220
220
 
221
- const result = subscribe(
222
- () => {
221
+ const result = subscribe({
222
+ get: () => {
223
223
  counts.internalGet++;
224
224
  return get();
225
225
  },
226
- v => {
226
+ set: v => {
227
227
  counts.internalSet++;
228
228
  set(v);
229
229
  },
230
- );
230
+ });
231
231
 
232
- const subscriptionWrapper: SignalSubscription = {
233
- unsubscribe() {
232
+ let subscriptionWrapper: SignalSubscription | (() => unknown) | undefined;
233
+
234
+ if (typeof result === 'function') {
235
+ subscriptionWrapper = () => {
236
+ counts.unsubscribe++;
237
+ result();
238
+ };
239
+ } else {
240
+ subscriptionWrapper = {};
241
+
242
+ subscriptionWrapper.unsubscribe = () => {
234
243
  counts.unsubscribe++;
235
244
  result?.unsubscribe?.();
236
- },
245
+ };
237
246
 
238
- update() {
247
+ subscriptionWrapper.update = () => {
248
+ counts.compute++;
239
249
  counts.update++;
240
250
  result?.update?.();
241
- },
242
- };
251
+ };
252
+ }
243
253
 
244
254
  return subscriptionWrapper;
245
255
  }, opts);
@@ -256,7 +266,7 @@ export const createSubscriptionSignal: typeof _createSubscriptionSignal = (subsc
256
266
  return wrapper;
257
267
  };
258
268
 
259
- export function createWatcherSignal<T>(fn: () => T, opts?: SignalOptions<T>): Watcher<T> {
269
+ export function createWatcherSignal<T>(fn: () => T, opts?: SignalOptions<T, []>): Watcher<T> {
260
270
  const desc = opts?.desc || 'unlabeled';
261
271
  const counts = new SignalCounts(desc);
262
272
 
@@ -26,12 +26,12 @@ const createMethods = [
26
26
  fn: (...args: Args) => T,
27
27
  opts?: Partial<SignalOptionsWithInit<T, Args>>,
28
28
  ): (...args: Args) => T {
29
- return subscription(({ set }, ...args) => {
30
- set(fn(...args));
29
+ return subscription((state, ...args) => {
30
+ state.set(fn(...args));
31
31
 
32
32
  return {
33
33
  update: () => {
34
- set(fn(...args));
34
+ state.set(fn(...args));
35
35
  },
36
36
  };
37
37
  }, opts);
package/src/hooks.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  SignalSubscription,
20
20
  Watcher,
21
21
  WriteableSignal,
22
+ SignalSubscribe,
23
+ SubscriptionState,
22
24
  } from './types.js';
23
25
  import { getObjectId, getUnknownSignalFnName, hashValue } from './utils.js';
24
26
  import { getFrameworkScope, useSignalValue } from './config.js';
@@ -172,40 +174,33 @@ export class SignalScope {
172
174
  let signal = this.getSignal(key, computedMask);
173
175
 
174
176
  if (signal === undefined) {
177
+ const optsWithMeta = { ...opts, id: key, desc: fnName, params };
175
178
  let initialized = false;
176
179
 
177
180
  if (makeSignal === createSubscriptionSignal) {
178
- signal = makeSignal(
179
- (get, set) => {
180
- const sub = this.run(fn, [{ get, set }, ...args], key, signal!, initialized) as
181
- | SignalSubscription
182
- | undefined;
183
-
184
- if (sub?.update) {
185
- const originalUpdate = sub.update;
186
-
187
- sub.update = (...args) => {
188
- return this.run(originalUpdate, [], key, signal!, initialized);
189
- };
190
- }
191
-
192
- initialized = true;
193
-
194
- return sub;
195
- },
196
- { ...opts, id: key, desc: fnName, params },
197
- );
181
+ signal = makeSignal(state => {
182
+ const sub = this.run(fn, [state, ...args], key, signal!, initialized);
183
+
184
+ if (typeof sub === 'object' && sub !== null && sub?.update) {
185
+ const originalUpdate = sub.update;
186
+
187
+ sub.update = () => {
188
+ return this.run(originalUpdate, [], key, signal!, initialized);
189
+ };
190
+ }
191
+
192
+ initialized = true;
193
+
194
+ return sub;
195
+ }, optsWithMeta);
198
196
  } else {
199
- signal = makeSignal(
200
- () => {
201
- const result = this.run(fn, args, key, signal!, initialized);
197
+ signal = makeSignal((...runArgs) => {
198
+ const result = this.run(fn, [...args, ...runArgs], key, signal!, initialized);
202
199
 
203
- initialized = true;
200
+ initialized = true;
204
201
 
205
- return result;
206
- },
207
- { ...opts, id: key, desc: fnName, params },
208
- );
202
+ return result;
203
+ }, optsWithMeta);
209
204
  }
210
205
  }
211
206
 
@@ -341,44 +336,10 @@ export function asyncComputed<T, Args extends unknown[]>(
341
336
  };
342
337
  }
343
338
 
344
- export interface SubscriptionState<T> {
345
- get: () => T;
346
- set: (value: T) => void;
347
- }
348
-
349
- export type SignalSubscribe<T, Args extends unknown[]> = (
350
- state: SubscriptionState<T>,
351
- ...args: Args
352
- ) => SignalSubscription | (() => unknown) | undefined | void;
353
-
354
339
  export function subscription<T, Args extends unknown[]>(
355
340
  fn: SignalSubscribe<T, Args>,
356
341
  opts?: Partial<SignalOptionsWithInit<T, Args>>,
357
342
  ): (...args: Args) => T {
358
- const wrapper = (state: SubscriptionState<T>, ...args: Args) => {
359
- let result = fn(state, ...args);
360
-
361
- if (typeof result === 'function') {
362
- return {
363
- update() {
364
- (result as () => void)();
365
- result = fn(state, ...args);
366
- },
367
-
368
- unsubscribe() {
369
- (result as () => void)();
370
- },
371
- };
372
- }
373
-
374
- return result;
375
- };
376
-
377
- Object.defineProperty(wrapper, 'name', {
378
- value: fn.name,
379
- writable: false,
380
- });
381
-
382
343
  return (...args) => {
383
344
  const params = getParamsKey(args, opts);
384
345
  const key = getComputedKey(fn, params);
@@ -387,7 +348,7 @@ export function subscription<T, Args extends unknown[]>(
387
348
  const scope = getCurrentScope();
388
349
  return scope.get(
389
350
  createSubscriptionSignal,
390
- wrapper,
351
+ fn,
391
352
  key,
392
353
  params,
393
354
  args,
@@ -400,7 +361,9 @@ export function subscription<T, Args extends unknown[]>(
400
361
  export const asyncTask = <T, Args extends unknown[]>(
401
362
  fn: (...args: Args) => Promise<T>,
402
363
  opts?: Partial<SignalOptionsWithInit<T, Args>>,
403
- ): ((...args: Args) => AsyncTask<T>) => {
364
+ ): (<BuildArgs extends unknown[], RunArgs extends Args extends [...BuildArgs, ...infer _Rest] ? _Rest : Args>(
365
+ ...args: Args extends [...BuildArgs, ...infer _Rest] ? BuildArgs : Args
366
+ ) => AsyncTask<T, RunArgs>) => {
404
367
  return (...args) => {
405
368
  const params = getParamsKey(args, opts);
406
369
  const key = getComputedKey(fn, params);
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ export type {
13
13
  AsyncReady,
14
14
  AsyncResult,
15
15
  Watcher,
16
+ SubscriptionState,
16
17
  } from './types.js';
17
18
 
18
19
  export {
@@ -32,6 +33,7 @@ export {
32
33
  withContext,
33
34
  computed,
34
35
  asyncComputed,
36
+ asyncTask,
35
37
  subscription,
36
38
  watcher,
37
39
  SignalScope,
package/src/signals.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  SignalOptionsWithInit,
21
21
  SignalSubscribe,
22
22
  SignalSubscription,
23
+ SubscriptionState,
23
24
  Watcher,
24
25
  WatcherListenerOptions,
25
26
  } from './types.js';
@@ -36,7 +37,7 @@ const enum SignalType {
36
37
  Watcher,
37
38
  }
38
39
 
39
- const SUBSCRIPTIONS = new WeakMap<ComputedSignal<any>, SignalSubscription | undefined | void>();
40
+ const SUBSCRIPTIONS = new WeakMap<ComputedSignal<any>, SignalSubscription | (() => unknown) | undefined | void>();
40
41
  const ACTIVE_ASYNCS = new WeakMap<ComputedSignal<any>, Promise<unknown>>();
41
42
 
42
43
  const enum SignalState {
@@ -90,14 +91,14 @@ export class ComputedSignal<T> {
90
91
  _computedCount: number = 0;
91
92
  _connectedCount: number = 0;
92
93
  _currentValue: T | AsyncResult<T> | undefined;
93
- _compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>;
94
+ _compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T, []>;
94
95
 
95
96
  _opts: InternalSignalOptions<T>;
96
97
  _ref: WeakRef<ComputedSignal<T>> = new WeakRef(this);
97
98
 
98
99
  constructor(
99
100
  type: SignalType,
100
- compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T>,
101
+ compute: SignalCompute<T> | SignalAsyncCompute<T> | SignalSubscribe<T, []>,
101
102
  opts: InternalSignalOptions<T>,
102
103
  initValue?: T,
103
104
  ) {
@@ -389,37 +390,18 @@ export class ComputedSignal<T> {
389
390
 
390
391
  case SignalType.Subscription: {
391
392
  if (shouldConnect) {
392
- const subscription = (this._compute as SignalSubscribe<T>)(
393
- () => this._currentValue as T,
394
- value => {
395
- const version = this._version;
396
-
397
- if (version !== 0 && this._opts.equals(value, this._currentValue as T)) {
398
- return;
399
- }
400
-
401
- TRACER?.emit({
402
- type: TracerEventType.StartUpdate,
403
- id: this._opts.id,
404
- });
405
-
406
- this._currentValue = value;
407
- this._version = version + 1;
408
- this._dirtyConsumers();
409
-
410
- TRACER?.emit({
411
- type: TracerEventType.EndUpdate,
412
- id: this._opts.id,
413
- value: this._currentValue,
414
- preserveChildren: true,
415
- });
416
- },
417
- );
393
+ const subscription = (this._compute as SignalSubscribe<T, []>)(createSubscriptionState(this));
418
394
  SUBSCRIPTIONS.set(this, subscription);
419
395
  } else {
420
396
  const subscription = SUBSCRIPTIONS.get(this);
421
397
 
422
- subscription?.update?.();
398
+ if (typeof subscription === 'function') {
399
+ subscription();
400
+ const nextSubscription = (this._compute as SignalSubscribe<T, []>)(createSubscriptionState(this));
401
+ SUBSCRIPTIONS.set(this, nextSubscription);
402
+ } else if (subscription !== undefined) {
403
+ subscription.update?.();
404
+ }
423
405
  }
424
406
 
425
407
  break;
@@ -552,7 +534,10 @@ export class ComputedSignal<T> {
552
534
  if (this._type === SignalType.Subscription) {
553
535
  const subscription = SUBSCRIPTIONS.get(this);
554
536
 
555
- if (subscription !== undefined) {
537
+ if (typeof subscription === 'function') {
538
+ subscription();
539
+ SUBSCRIPTIONS.delete(this);
540
+ } else if (subscription !== undefined) {
556
541
  subscription.unsubscribe?.();
557
542
  SUBSCRIPTIONS.delete(this);
558
543
  }
@@ -596,6 +581,35 @@ export class ComputedSignal<T> {
596
581
  }
597
582
  }
598
583
 
584
+ function createSubscriptionState<T>(signal: ComputedSignal<T>): SubscriptionState<T> {
585
+ return {
586
+ get: () => signal._currentValue as T,
587
+ set: value => {
588
+ const version = signal._version;
589
+
590
+ if (version !== 0 && signal._opts.equals(value, signal._currentValue as T)) {
591
+ return;
592
+ }
593
+
594
+ TRACER?.emit({
595
+ type: TracerEventType.StartUpdate,
596
+ id: signal._opts.id,
597
+ });
598
+
599
+ signal._currentValue = value;
600
+ signal._version = version + 1;
601
+ signal._dirtyConsumers();
602
+
603
+ TRACER?.emit({
604
+ type: TracerEventType.EndUpdate,
605
+ id: signal._opts.id,
606
+ value: signal._currentValue,
607
+ preserveChildren: true,
608
+ });
609
+ },
610
+ };
611
+ }
612
+
599
613
  let STATE_ID = 0;
600
614
 
601
615
  export class StateSignal<T> implements StateSignal<T> {
@@ -703,15 +717,15 @@ export function createAsyncComputedSignal<T>(
703
717
  }
704
718
 
705
719
  export function createSubscriptionSignal<T>(
706
- subscribe: SignalSubscribe<T>,
720
+ subscribe: SignalSubscribe<T, []>,
707
721
  opts?: SignalOptions<T, unknown[]>,
708
722
  ): Signal<T | undefined>;
709
723
  export function createSubscriptionSignal<T>(
710
- subscribe: SignalSubscribe<T>,
724
+ subscribe: SignalSubscribe<T, []>,
711
725
  opts: SignalOptionsWithInit<T, unknown[]>,
712
726
  ): Signal<T>;
713
727
  export function createSubscriptionSignal<T>(
714
- subscribe: SignalSubscribe<T>,
728
+ subscribe: SignalSubscribe<T, []>,
715
729
  opts?: Partial<SignalOptionsWithInit<T, unknown[]>>,
716
730
  ): Signal<T> {
717
731
  return new ComputedSignal(SignalType.Subscription, subscribe, normalizeOpts(opts), opts?.initValue) as Signal<T>;
package/src/types.ts CHANGED
@@ -21,10 +21,15 @@ export type SignalSubscription = {
21
21
  unsubscribe?(): void;
22
22
  };
23
23
 
24
- export type SignalSubscribe<T> = (
25
- get: () => T | undefined,
26
- set: (value: T) => void,
27
- ) => SignalSubscription | undefined | void;
24
+ export interface SubscriptionState<T> {
25
+ get: () => T;
26
+ set: (value: T) => void;
27
+ }
28
+
29
+ export type SignalSubscribe<T, Args extends unknown[]> = (
30
+ state: SubscriptionState<T>,
31
+ ...args: Args
32
+ ) => SignalSubscription | (() => unknown) | undefined | void;
28
33
 
29
34
  export interface SignalOptions<T, Args extends unknown[]> {
30
35
  equals?: SignalEquals<T> | false;
@@ -66,7 +71,7 @@ export interface AsyncReady<T> extends AsyncBaseResult<T> {
66
71
 
67
72
  export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
68
73
 
69
- export interface AsyncTask<T, Args extends unknown[] = unknown[]> {
74
+ export interface AsyncTask<T, RunArgs extends unknown[] = unknown[]> {
70
75
  result: T | undefined;
71
76
  error: unknown;
72
77
  isPending: boolean;
@@ -74,7 +79,7 @@ export interface AsyncTask<T, Args extends unknown[] = unknown[]> {
74
79
  isError: boolean;
75
80
  isReady: boolean;
76
81
 
77
- run(...args: Args): Promise<T>;
82
+ run(...args: RunArgs): Promise<T>;
78
83
  }
79
84
 
80
85
  export interface WatcherListenerOptions {