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.
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +12 -0
- package/dist/cjs/hooks.d.ts +2 -7
- package/dist/cjs/hooks.d.ts.map +1 -1
- package/dist/cjs/hooks.js +10 -28
- package/dist/cjs/hooks.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/signals.d.ts +4 -4
- package/dist/cjs/signals.d.ts.map +1 -1
- package/dist/cjs/signals.js +38 -21
- package/dist/cjs/signals.js.map +1 -1
- package/dist/cjs/types.d.ts +7 -3
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/hooks.d.ts +2 -7
- package/dist/esm/hooks.d.ts.map +1 -1
- package/dist/esm/hooks.js +10 -28
- package/dist/esm/hooks.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/signals.d.ts +4 -4
- package/dist/esm/signals.d.ts.map +1 -1
- package/dist/esm/signals.js +38 -21
- package/dist/esm/signals.js.map +1 -1
- package/dist/esm/types.d.ts +7 -3
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/hooks/async-task.test.ts +110 -3
- package/src/__tests__/signals/subscription.test.ts +6 -6
- package/src/__tests__/signals/watcher.test.ts +2 -2
- package/src/__tests__/utils/instrumented-hooks.ts +1 -2
- package/src/__tests__/utils/instrumented-signals.ts +23 -13
- package/src/__tests__/utils/permute.ts +3 -3
- package/src/hooks.ts +27 -64
- package/src/index.ts +2 -0
- package/src/signals.ts +48 -34
- package/src/types.ts +11 -6
@@ -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
|
-
|
233
|
-
|
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((
|
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
|
-
(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
-
|
200
|
+
initialized = true;
|
204
201
|
|
205
|
-
|
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
|
-
|
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
|
-
): (
|
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
|
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
|
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
|
25
|
-
get: () => T
|
26
|
-
set: (value: T) => void
|
27
|
-
|
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,
|
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:
|
82
|
+
run(...args: RunArgs): Promise<T>;
|
78
83
|
}
|
79
84
|
|
80
85
|
export interface WatcherListenerOptions {
|