@tstdl/base 0.93.188 → 0.93.190
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/ai/genkit/multi-region.plugin.js +3 -2
- package/authentication/client/authentication.service.d.ts +5 -0
- package/authentication/client/authentication.service.js +27 -39
- package/authentication/client/http-client.middleware.js +2 -5
- package/authentication/models/totp-results.model.d.ts +1 -0
- package/authentication/models/totp-results.model.js +6 -1
- package/authentication/server/authentication.service.js +6 -1
- package/cryptography/totp.d.ts +2 -1
- package/cryptography/totp.js +15 -7
- package/package.json +1 -1
- package/signals/notifier.js +10 -7
|
@@ -15,8 +15,9 @@ export function vertexAiMultiLocation(options) {
|
|
|
15
15
|
const tokenLimitThreshold = options.tokenLimitThreshold ?? defaultTokenLimitThreshold;
|
|
16
16
|
const locationConfigs = options.locations.map((location) => {
|
|
17
17
|
const circuitBreakerNamespace = `genkit:vertex-ai:location`;
|
|
18
|
+
const tokenLimitCircuitBreakerNamespace = `genkit:vertex-ai:location-token-limit`;
|
|
18
19
|
const resource = location;
|
|
19
|
-
const tokenLimitResource =
|
|
20
|
+
const tokenLimitResource = location;
|
|
20
21
|
return {
|
|
21
22
|
location,
|
|
22
23
|
resource,
|
|
@@ -26,7 +27,7 @@ export function vertexAiMultiLocation(options) {
|
|
|
26
27
|
resetTimeout: 30 * millisecondsPerSecond,
|
|
27
28
|
...options.circuitBreakerConfig,
|
|
28
29
|
}),
|
|
29
|
-
tokenLimitCircuitBreaker: options.circuitBreakerProvider.provide(
|
|
30
|
+
tokenLimitCircuitBreaker: options.circuitBreakerProvider.provide(tokenLimitCircuitBreakerNamespace, {
|
|
30
31
|
threshold: 1,
|
|
31
32
|
resetTimeout: 15 * millisecondsPerMinute,
|
|
32
33
|
...options.tokenLimitCircuitBreakerConfig,
|
|
@@ -27,6 +27,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
27
27
|
private readonly logger;
|
|
28
28
|
private readonly disposeSignal;
|
|
29
29
|
private readonly forceRefreshRequested;
|
|
30
|
+
private readonly totpStatusReloadNotifier;
|
|
30
31
|
private readonly forceRefreshSubject;
|
|
31
32
|
private clockOffset;
|
|
32
33
|
private initialized;
|
|
@@ -193,6 +194,10 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
193
194
|
* @param newPassword The new password
|
|
194
195
|
*/
|
|
195
196
|
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Reload TOTP status.
|
|
199
|
+
*/
|
|
200
|
+
reloadTotpStatus(): void;
|
|
196
201
|
/**
|
|
197
202
|
* Initiate TOTP enrollment.
|
|
198
203
|
* @returns The secret and URI for enrollment.
|
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Subject, filter, firstValueFrom, from, race, takeUntil,
|
|
10
|
+
import { Subject, filter, firstValueFrom, from, race, takeUntil, timer } from 'rxjs';
|
|
11
11
|
import { CancellationSignal } from '../../cancellation/token.js';
|
|
12
12
|
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
13
13
|
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
@@ -22,7 +22,7 @@ import { Lock } from '../../lock/index.js';
|
|
|
22
22
|
import { Logger } from '../../logger/index.js';
|
|
23
23
|
import { MessageBus } from '../../message-bus/index.js';
|
|
24
24
|
import { computed, signal, toObservable } from '../../signals/api.js';
|
|
25
|
-
import { deriveAsync } from '../../signals/index.js';
|
|
25
|
+
import { createNotifier, deriveAsync } from '../../signals/index.js';
|
|
26
26
|
import { currentTimestampSeconds } from '../../utils/date-time.js';
|
|
27
27
|
import { clamp } from '../../utils/math.js';
|
|
28
28
|
import { timeout } from '../../utils/timing.js';
|
|
@@ -74,6 +74,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
74
74
|
logger = inject(Logger, 'AuthenticationService');
|
|
75
75
|
disposeSignal = inject(CancellationSignal).fork();
|
|
76
76
|
forceRefreshRequested = signal(false);
|
|
77
|
+
totpStatusReloadNotifier = createNotifier();
|
|
77
78
|
forceRefreshSubject = new Subject();
|
|
78
79
|
clockOffset = 0;
|
|
79
80
|
initialized = false;
|
|
@@ -107,6 +108,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
107
108
|
/** Whether the user is impersonated */
|
|
108
109
|
impersonated = computed(() => isDefined(this.impersonator()));
|
|
109
110
|
totpStatus = deriveAsync(async () => {
|
|
111
|
+
this.totpStatusReloadNotifier.listen();
|
|
110
112
|
const token = this.token();
|
|
111
113
|
if (isUndefined(token)) {
|
|
112
114
|
return undefined;
|
|
@@ -403,6 +405,12 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
403
405
|
async resetPassword(token, newPassword) {
|
|
404
406
|
await this.client.resetPassword({ token, newPassword });
|
|
405
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Reload TOTP status.
|
|
410
|
+
*/
|
|
411
|
+
reloadTotpStatus() {
|
|
412
|
+
this.totpStatusReloadNotifier.notify();
|
|
413
|
+
}
|
|
406
414
|
/**
|
|
407
415
|
* Initiate TOTP enrollment.
|
|
408
416
|
* @returns The secret and URI for enrollment.
|
|
@@ -416,7 +424,9 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
416
424
|
* @returns The recovery codes.
|
|
417
425
|
*/
|
|
418
426
|
async completeEnrollTotp(token) {
|
|
419
|
-
|
|
427
|
+
const result = await this.client.completeEnrollTotp({ token });
|
|
428
|
+
this.reloadTotpStatus();
|
|
429
|
+
return result;
|
|
420
430
|
}
|
|
421
431
|
/**
|
|
422
432
|
* Disable TOTP.
|
|
@@ -424,6 +434,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
424
434
|
*/
|
|
425
435
|
async disableTotp(token) {
|
|
426
436
|
await this.client.disableTotp({ token });
|
|
437
|
+
this.reloadTotpStatus();
|
|
427
438
|
}
|
|
428
439
|
/**
|
|
429
440
|
* Disable TOTP using a recovery code.
|
|
@@ -431,6 +442,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
431
442
|
*/
|
|
432
443
|
async disableTotpWithRecoveryCode(recoveryCode) {
|
|
433
444
|
await this.client.disableTotpWithRecoveryCode({ recoveryCode });
|
|
445
|
+
this.reloadTotpStatus();
|
|
434
446
|
}
|
|
435
447
|
/**
|
|
436
448
|
* Regenerate recovery codes.
|
|
@@ -439,7 +451,9 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
439
451
|
* @returns The new recovery codes.
|
|
440
452
|
*/
|
|
441
453
|
async regenerateRecoveryCodes(token, options) {
|
|
442
|
-
|
|
454
|
+
const result = await this.client.regenerateRecoveryCodes({ token, ...options });
|
|
455
|
+
this.reloadTotpStatus();
|
|
456
|
+
return result;
|
|
443
457
|
}
|
|
444
458
|
/**
|
|
445
459
|
* Get TOTP activation status.
|
|
@@ -500,29 +514,23 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
500
514
|
this.tokenUpdateBus.publishAndForget(token);
|
|
501
515
|
}
|
|
502
516
|
async refreshLoop() {
|
|
503
|
-
this.logger.trace('[refreshLoop] Starting refresh loop...');
|
|
504
517
|
if (this.isLoggedIn()) {
|
|
505
|
-
this.logger.trace('[refreshLoop] User is logged in, syncing clock...');
|
|
506
518
|
await this.syncClock();
|
|
507
519
|
}
|
|
508
520
|
// Helper to sleep until the delay passes OR a vital state change occurs
|
|
509
|
-
const waitForNextAction = async (delayMs) =>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
this.forceRefreshSubject.pipe(tap(() => this.logger.trace('[waitForNextAction] Force refresh requested'))),
|
|
516
|
-
]), { defaultValue: undefined });
|
|
517
|
-
};
|
|
521
|
+
const waitForNextAction = async (delayMs) => await firstValueFrom(race([
|
|
522
|
+
timer(Math.max(10, delayMs)),
|
|
523
|
+
from(this.disposeSignal),
|
|
524
|
+
this.tokenUpdateBus.allMessages$,
|
|
525
|
+
this.forceRefreshSubject,
|
|
526
|
+
]), { defaultValue: undefined });
|
|
518
527
|
while (this.disposeSignal.isUnset) {
|
|
519
528
|
const token = this.token();
|
|
520
529
|
// 1. Wait for login/token if none is present
|
|
521
530
|
if (isUndefined(token)) {
|
|
522
|
-
this.logger.trace('[refreshLoop] No token found, waiting for login...');
|
|
523
531
|
await firstValueFrom(race([
|
|
524
|
-
this.
|
|
525
|
-
from(this.disposeSignal)
|
|
532
|
+
this.tokenUpdateBus.allMessages$.pipe(filter(isDefined)),
|
|
533
|
+
from(this.disposeSignal),
|
|
526
534
|
]), { defaultValue: undefined });
|
|
527
535
|
continue;
|
|
528
536
|
}
|
|
@@ -530,16 +538,12 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
530
538
|
const now = this.estimatedServerTimestampSeconds();
|
|
531
539
|
const buffer = calculateRefreshBufferSeconds(token);
|
|
532
540
|
const needsRefresh = this.forceRefreshRequested() || (now >= token.exp - buffer);
|
|
533
|
-
this.logger.trace(`[refreshLoop] Checking token refresh status: now=${now}, exp=${token.exp}, buffer=${buffer}, needsRefresh=${needsRefresh}, force=${this.forceRefreshRequested()}`);
|
|
534
541
|
// 2. Handle token refresh
|
|
535
542
|
if (needsRefresh) {
|
|
536
|
-
this.logger.trace('[refreshLoop] Attempting to acquire refresh lock...');
|
|
537
543
|
const lockResult = await this.lock.tryUse(undefined, async () => {
|
|
538
|
-
this.logger.trace('[refreshLoop] Refresh lock acquired, evaluating if refresh is still needed...');
|
|
539
544
|
this.loadToken();
|
|
540
545
|
const currentToken = this.token();
|
|
541
546
|
if (isUndefined(currentToken)) {
|
|
542
|
-
this.logger.trace('[refreshLoop] Token became undefined while waiting for lock, skipping refresh.');
|
|
543
547
|
return;
|
|
544
548
|
}
|
|
545
549
|
// Passive Sync: Verify if refresh is still needed once lock is acquired
|
|
@@ -547,24 +551,13 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
547
551
|
const currentBuffer = calculateRefreshBufferSeconds(currentToken);
|
|
548
552
|
const stillNeedsRefresh = this.forceRefreshRequested() || (currentNow >= currentToken.exp - currentBuffer);
|
|
549
553
|
if (stillNeedsRefresh) {
|
|
550
|
-
this.logger.trace('[refreshLoop] Refresh is still needed, performing refresh...');
|
|
551
554
|
await this.refresh();
|
|
552
555
|
this.forceRefreshRequested.set(false);
|
|
553
|
-
this.logger.trace('[refreshLoop] Refresh completed successfully.');
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
this.logger.trace('[refreshLoop] Token already refreshed by another instance, skipping.');
|
|
557
556
|
}
|
|
558
557
|
});
|
|
559
558
|
// If lock is held by another instance/tab, wait briefly for it to finish (passive sync)
|
|
560
559
|
if (!lockResult.success) {
|
|
561
|
-
this.logger.trace('[refreshLoop] Refresh lock held by another instance, waiting 5000ms for passive sync...');
|
|
562
560
|
await waitForNextAction(5000);
|
|
563
|
-
// If another tab successfully refreshed, the expiration will have changed
|
|
564
|
-
if (this.token()?.exp !== token.exp) {
|
|
565
|
-
this.logger.trace('[refreshLoop] Token refreshed by another instance (expiration changed), clearing force refresh flag.');
|
|
566
|
-
this.forceRefreshRequested.set(false);
|
|
567
|
-
}
|
|
568
561
|
}
|
|
569
562
|
// Protection against tight loops (e.g. if server clock is ahead and sync failed)
|
|
570
563
|
const newToken = this.token();
|
|
@@ -573,11 +566,10 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
573
566
|
const newBuffer = calculateRefreshBufferSeconds(newToken);
|
|
574
567
|
const stillNeedsRefresh = this.forceRefreshRequested() || (newNow >= newToken.exp - newBuffer);
|
|
575
568
|
if (stillNeedsRefresh) {
|
|
576
|
-
this.logger.warn('
|
|
569
|
+
this.logger.warn('Token still needs refresh after attempt. Waiting briefly to avoid tight loop.');
|
|
577
570
|
await waitForNextAction(refreshLoopTightLoopDelay);
|
|
578
571
|
}
|
|
579
572
|
}
|
|
580
|
-
this.logger.trace('[refreshLoop] Completing refresh evaluation cycle, waiting 100ms...');
|
|
581
573
|
await waitForNextAction(100);
|
|
582
574
|
continue; // Re-evaluate the loop with the newly refreshed (or synced) token
|
|
583
575
|
}
|
|
@@ -585,19 +577,15 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
585
577
|
const timeUntilRefreshMs = (token.exp - this.estimatedServerTimestampSeconds() - buffer) * millisecondsPerSecond;
|
|
586
578
|
let delay = clamp(timeUntilRefreshMs, minRefreshDelay, maxRefreshDelay);
|
|
587
579
|
if (Number.isNaN(delay)) {
|
|
588
|
-
this.logger.trace(`[refreshLoop] Calculated delay is NaN, using minRefreshDelay (${minRefreshDelay}ms)`);
|
|
589
580
|
delay = minRefreshDelay;
|
|
590
581
|
}
|
|
591
|
-
this.logger.trace(`[refreshLoop] Scheduling next refresh in ${delay}ms...`);
|
|
592
582
|
await waitForNextAction(delay);
|
|
593
583
|
}
|
|
594
584
|
catch (error) {
|
|
595
585
|
this.logger.error(error);
|
|
596
|
-
this.logger.trace(`[refreshLoop] Refresh loop encountered error, waiting ${refreshLoopTightLoopDelay}ms before retry...`);
|
|
597
586
|
await waitForNextAction(refreshLoopTightLoopDelay);
|
|
598
587
|
}
|
|
599
588
|
}
|
|
600
|
-
this.logger.trace('[refreshLoop] Refresh loop exited (dispose signal set).');
|
|
601
589
|
}
|
|
602
590
|
async handleRefreshError(error) {
|
|
603
591
|
this.logger.error(error);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { firstValueFrom, race, timeout as rxjsTimeout } from 'rxjs';
|
|
2
2
|
import { HttpError } from '../../http/index.js';
|
|
3
3
|
import { supportsCookies } from '../../supports.js';
|
|
4
|
-
import { timeout } from '../../utils/timing.js';
|
|
5
4
|
import { isDefined } from '../../utils/type-guards.js';
|
|
6
5
|
import { cacheValueOrAsyncProvider } from '../../utils/value-or-provider.js';
|
|
7
6
|
import { dontWaitForValidToken } from '../authentication.api.js';
|
|
@@ -19,15 +18,13 @@ export function waitForAuthenticationMiddleware(authenticationServiceOrProvider)
|
|
|
19
18
|
while (!authenticationService.hasValidToken && authenticationService.isLoggedIn()) {
|
|
20
19
|
const race$ = race([
|
|
21
20
|
authenticationService.validToken$,
|
|
21
|
+
authenticationService.loggedOut$,
|
|
22
22
|
request.cancellationSignal,
|
|
23
23
|
]);
|
|
24
24
|
await firstValueFrom(race$.pipe(rxjsTimeout(30000))).catch(() => undefined);
|
|
25
|
-
if (request.cancellationSignal.isSet) {
|
|
25
|
+
if (request.cancellationSignal.isSet || !authenticationService.isLoggedIn()) {
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
28
|
-
if (!authenticationService.hasValidToken && authenticationService.isLoggedIn()) {
|
|
29
|
-
await timeout(100);
|
|
30
|
-
}
|
|
31
28
|
}
|
|
32
29
|
}
|
|
33
30
|
await next();
|
|
@@ -8,7 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
-
import { Array, BooleanProperty, StringProperty } from '../../schema/index.js';
|
|
11
|
+
import { Array, BooleanProperty, NumberProperty, StringProperty } from '../../schema/index.js';
|
|
12
12
|
export class TotpEnrollmentInitResult {
|
|
13
13
|
secret;
|
|
14
14
|
uri;
|
|
@@ -30,8 +30,13 @@ __decorate([
|
|
|
30
30
|
], TotpRecoveryCodesResult.prototype, "recoveryCodes", void 0);
|
|
31
31
|
export class TotpStatusResult {
|
|
32
32
|
active;
|
|
33
|
+
remainingRecoveryCodes;
|
|
33
34
|
}
|
|
34
35
|
__decorate([
|
|
35
36
|
BooleanProperty(),
|
|
36
37
|
__metadata("design:type", Boolean)
|
|
37
38
|
], TotpStatusResult.prototype, "active", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
NumberProperty({ nullable: true }),
|
|
41
|
+
__metadata("design:type", Object)
|
|
42
|
+
], TotpStatusResult.prototype, "remainingRecoveryCodes", void 0);
|
|
@@ -864,8 +864,13 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
864
864
|
}
|
|
865
865
|
async getTotpStatus(tenantId, subjectId) {
|
|
866
866
|
const totp = await this.tryGetTotp(tenantId, subjectId);
|
|
867
|
+
const active = totp?.status == TotpStatus.Active;
|
|
868
|
+
const remainingRecoveryCodes = active
|
|
869
|
+
? await this.#totpRecoveryCodeRepository.countByQuery({ tenantId, totpId: totp.id, usedTimestamp: null })
|
|
870
|
+
: null;
|
|
867
871
|
return {
|
|
868
|
-
active
|
|
872
|
+
active,
|
|
873
|
+
remainingRecoveryCodes,
|
|
869
874
|
};
|
|
870
875
|
}
|
|
871
876
|
async initEnrollTotp(tenantId, subjectId, auditor) {
|
package/cryptography/totp.d.ts
CHANGED
|
@@ -75,10 +75,11 @@ export declare function generateTotpUri(encodedSecret: string, accountName: stri
|
|
|
75
75
|
/**
|
|
76
76
|
* Generates a set of random recovery codes.
|
|
77
77
|
* @param count The number of codes to generate. Defaults to 10.
|
|
78
|
-
* @param length The length of each code. Defaults to
|
|
78
|
+
* @param length The length of each code. Defaults to 12.
|
|
79
79
|
* @returns An array of generated recovery codes.
|
|
80
80
|
*/
|
|
81
81
|
export declare function generateTotpRecoveryCodes(count?: number, length?: number): string[];
|
|
82
|
+
export declare function generateTotpRecoveryCode(length?: number): string;
|
|
82
83
|
/**
|
|
83
84
|
* Hashes a recovery code.
|
|
84
85
|
* @param code The recovery code to hash.
|
package/cryptography/totp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** biome-ignore-all lint/suspicious/noBitwiseOperators: ok */
|
|
2
2
|
import { Alphabet } from '../utils/alphabet.js';
|
|
3
|
+
import { createArray } from '../utils/array/array.js';
|
|
3
4
|
import { encodeBase32 } from '../utils/base32.js';
|
|
4
5
|
import { currentTimestampSeconds } from '../utils/date-time.js';
|
|
5
6
|
import { encodeUtf8 } from '../utils/encoding.js';
|
|
@@ -88,15 +89,21 @@ export function generateTotpUri(encodedSecret, accountName, issuer, options) {
|
|
|
88
89
|
/**
|
|
89
90
|
* Generates a set of random recovery codes.
|
|
90
91
|
* @param count The number of codes to generate. Defaults to 10.
|
|
91
|
-
* @param length The length of each code. Defaults to
|
|
92
|
+
* @param length The length of each code. Defaults to 12.
|
|
92
93
|
* @returns An array of generated recovery codes.
|
|
93
94
|
*/
|
|
94
|
-
export function generateTotpRecoveryCodes(count = 10, length =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
export function generateTotpRecoveryCodes(count = 10, length = 12) {
|
|
96
|
+
return createArray(count, () => generateTotpRecoveryCode(length));
|
|
97
|
+
}
|
|
98
|
+
export function generateTotpRecoveryCode(length = 12) {
|
|
99
|
+
const code = getRandomString(length, Alphabet.Base32);
|
|
100
|
+
if (length % 4 == 0) {
|
|
101
|
+
return code.match(/.{1,4}/g).join('-');
|
|
102
|
+
}
|
|
103
|
+
if (length % 6 == 0) {
|
|
104
|
+
return code.match(/.{1,6}/g).join('-');
|
|
98
105
|
}
|
|
99
|
-
return
|
|
106
|
+
return code;
|
|
100
107
|
}
|
|
101
108
|
/**
|
|
102
109
|
* Hashes a recovery code.
|
|
@@ -106,7 +113,8 @@ export function generateTotpRecoveryCodes(count = 10, length = 8) {
|
|
|
106
113
|
*/
|
|
107
114
|
export async function hashTotpRecoveryCode(code, options) {
|
|
108
115
|
const { length, algorithm } = options;
|
|
109
|
-
const
|
|
116
|
+
const normalizedCode = code.replace(/[-\s]/g, '').toUpperCase();
|
|
117
|
+
const keyMaterial = encodeUtf8(normalizedCode);
|
|
110
118
|
const key = await importKey('raw-secret', keyMaterial, algorithm, false, ['deriveBits']);
|
|
111
119
|
return await deriveBytes(algorithm, key, length);
|
|
112
120
|
}
|
package/package.json
CHANGED
package/signals/notifier.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { computed, signal } from './api.js';
|
|
2
2
|
export function createNotifier() {
|
|
3
3
|
const sourceSignal = signal(0);
|
|
4
|
-
|
|
4
|
+
return {
|
|
5
5
|
listen: sourceSignal.asReadonly(),
|
|
6
|
-
notify
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
notify() {
|
|
7
|
+
sourceSignal.update((v) => v + 1);
|
|
8
|
+
},
|
|
9
|
+
computed(computation, options) {
|
|
10
|
+
return computed(() => {
|
|
11
|
+
sourceSignal();
|
|
12
|
+
return computation();
|
|
13
|
+
}, options);
|
|
14
|
+
},
|
|
11
15
|
};
|
|
12
|
-
return notifier;
|
|
13
16
|
}
|