@tstdl/base 0.93.187 → 0.93.189
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 +15 -11
- package/ai/genkit/types.d.ts +1 -1
- package/ai/genkit/types.js +1 -1
- package/authentication/client/authentication.service.d.ts +5 -0
- package/authentication/client/authentication.service.js +21 -4
- package/authentication/models/authentication-totp-recovery-code.model.js +3 -2
- package/authentication/models/totp-results.model.d.ts +1 -0
- package/authentication/models/totp-results.model.js +6 -1
- package/authentication/server/authentication.api-controller.d.ts +1 -0
- package/authentication/server/authentication.api-controller.js +9 -2
- package/authentication/server/authentication.service.js +114 -92
- package/authentication/server/drizzle/{0000_odd_echo.sql → 0000_dry_stepford_cuckoos.sql} +2 -1
- package/authentication/server/drizzle/meta/0000_snapshot.json +24 -2
- package/authentication/server/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/circuit-breaker.d.ts +22 -10
- package/circuit-breaker/postgres/circuit-breaker.d.ts +5 -4
- package/circuit-breaker/postgres/circuit-breaker.js +21 -19
- package/circuit-breaker/postgres/drizzle/{0000_same_captain_cross.sql → 0000_dapper_hercules.sql} +3 -2
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +13 -6
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
- package/circuit-breaker/postgres/model.d.ts +2 -1
- package/circuit-breaker/postgres/model.js +9 -4
- package/circuit-breaker/postgres/provider.d.ts +1 -1
- package/circuit-breaker/postgres/provider.js +2 -2
- package/circuit-breaker/provider.d.ts +6 -1
- package/cryptography/totp.d.ts +2 -1
- package/cryptography/totp.js +15 -7
- package/orm/sqls/sqls.d.ts +5 -3
- package/orm/sqls/sqls.js +5 -3
- package/package.json +4 -4
- package/rate-limit/postgres/drizzle/{0000_serious_sauron.sql → 0000_previous_zeigeist.sql} +2 -1
- package/rate-limit/postgres/drizzle/meta/0000_snapshot.json +10 -3
- package/rate-limit/postgres/drizzle/meta/_journal.json +2 -2
- package/rate-limit/postgres/postgres-rate-limiter.d.ts +1 -0
- package/rate-limit/postgres/postgres-rate-limiter.js +3 -2
- package/rate-limit/postgres/rate-limit.model.d.ts +1 -0
- package/rate-limit/postgres/rate-limit.model.js +6 -1
- package/rate-limit/postgres/rate-limiter.provider.d.ts +1 -1
- package/rate-limit/postgres/rate-limiter.provider.js +2 -2
- package/rate-limit/provider.d.ts +3 -3
- package/rate-limit/rate-limiter.d.ts +2 -1
- package/signals/notifier.js +1 -1
- package/signals/operators/derive-async.js +11 -6
- package/task-queue/postgres/task-queue.js +8 -8
- package/testing/integration-setup.js +4 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "06619eff-dfa3-4a91-b5f4-c3cfe7a78951",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -408,7 +408,29 @@
|
|
|
408
408
|
"default": "'{}'::jsonb"
|
|
409
409
|
}
|
|
410
410
|
},
|
|
411
|
-
"indexes": {
|
|
411
|
+
"indexes": {
|
|
412
|
+
"totp_recovery_code_tenant_id_totp_id_idx": {
|
|
413
|
+
"name": "totp_recovery_code_tenant_id_totp_id_idx",
|
|
414
|
+
"columns": [
|
|
415
|
+
{
|
|
416
|
+
"expression": "tenant_id",
|
|
417
|
+
"isExpression": false,
|
|
418
|
+
"asc": true,
|
|
419
|
+
"nulls": "last"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"expression": "totp_id",
|
|
423
|
+
"isExpression": false,
|
|
424
|
+
"asc": true,
|
|
425
|
+
"nulls": "last"
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
"isUnique": false,
|
|
429
|
+
"concurrently": false,
|
|
430
|
+
"method": "btree",
|
|
431
|
+
"with": {}
|
|
432
|
+
}
|
|
433
|
+
},
|
|
412
434
|
"foreignKeys": {
|
|
413
435
|
"totp_recovery_code_id_totp_fkey": {
|
|
414
436
|
"name": "totp_recovery_code_id_totp_fkey",
|
|
@@ -15,20 +15,32 @@ export type CircuitBreakerCheckResult = {
|
|
|
15
15
|
state: CircuitBreakerState;
|
|
16
16
|
isProbe: boolean;
|
|
17
17
|
};
|
|
18
|
-
export type CircuitBreakerArgument = string | (CircuitBreakerConfig & {
|
|
19
|
-
|
|
18
|
+
export type CircuitBreakerArgument = string | (Partial<CircuitBreakerConfig> & {
|
|
19
|
+
namespace: string;
|
|
20
20
|
});
|
|
21
21
|
export declare abstract class CircuitBreaker implements Resolvable<CircuitBreakerArgument> {
|
|
22
22
|
readonly [resolveArgumentType]: CircuitBreakerArgument;
|
|
23
|
+
abstract readonly namespace: string;
|
|
23
24
|
/**
|
|
24
|
-
* Checks if execution is allowed.
|
|
25
|
+
* Checks if execution is allowed for a resource.
|
|
25
26
|
* Transitions from Open to Half-Open automatically if timeout passed.
|
|
27
|
+
* @param resource The resource to check.
|
|
26
28
|
*/
|
|
27
|
-
abstract check(): Promise<CircuitBreakerCheckResult>;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
abstract check(resource: string): Promise<CircuitBreakerCheckResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Records a success for a resource, resetting its breaker to Closed.
|
|
32
|
+
* @param resource The resource to record success for.
|
|
33
|
+
*/
|
|
34
|
+
abstract recordSuccess(resource: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Records a failure for a resource, potentially tripping its breaker to Open.
|
|
37
|
+
* @param resource The resource to record failure for.
|
|
38
|
+
*/
|
|
39
|
+
abstract recordFailure(resource: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Records multiple failures for a resource at once.
|
|
42
|
+
* @param resource The resource to record failures for.
|
|
43
|
+
* @param count The number of failures to record.
|
|
44
|
+
*/
|
|
45
|
+
abstract recordFailures(resource: string, count: number): Promise<void>;
|
|
34
46
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CircuitBreaker, type CircuitBreakerCheckResult } from '../circuit-breaker.js';
|
|
2
2
|
export declare class PostgresCircuitBreakerService extends CircuitBreaker {
|
|
3
3
|
#private;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
readonly namespace: string;
|
|
5
|
+
check(resource: string): Promise<CircuitBreakerCheckResult>;
|
|
6
|
+
recordSuccess(resource: string): Promise<void>;
|
|
7
|
+
recordFailure(resource: string): Promise<void>;
|
|
8
|
+
recordFailures(resource: string, count: number): Promise<void>;
|
|
8
9
|
private getPreparedCheckStatement;
|
|
9
10
|
}
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { and, eq, lte, sql, isNotNull as sqlIsNotNull } from 'drizzle-orm';
|
|
8
8
|
import { injectArgument, provide, Singleton } from '../../injector/index.js';
|
|
9
|
-
import { coalesce, interval
|
|
9
|
+
import { CLOCK_TIMESTAMP, coalesce, interval } from '../../orm/index.js';
|
|
10
10
|
import { DatabaseConfig, injectRepository } from '../../orm/server/index.js';
|
|
11
11
|
import { isString, isUndefined } from '../../utils/type-guards.js';
|
|
12
12
|
import { millisecondsPerSecond } from '../../utils/units.js';
|
|
@@ -17,12 +17,12 @@ import { circuitBreaker } from './schemas.js';
|
|
|
17
17
|
let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends CircuitBreaker {
|
|
18
18
|
#repository = injectRepository(PostgresCircuitBreaker);
|
|
19
19
|
#arg = injectArgument(this);
|
|
20
|
-
#key = isString(this.#arg) ? this.#arg : this.#arg.key;
|
|
21
20
|
#threshold = (isString(this.#arg) ? undefined : this.#arg.threshold) ?? 5;
|
|
22
21
|
#resetTimeout = (isString(this.#arg) ? undefined : this.#arg.resetTimeout) ?? 30 * millisecondsPerSecond;
|
|
23
22
|
#checkStatement = this.getPreparedCheckStatement();
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
namespace = isString(this.#arg) ? this.#arg : this.#arg.namespace;
|
|
24
|
+
async check(resource) {
|
|
25
|
+
const [result] = await this.#checkStatement.execute({ namespace: this.namespace, resource });
|
|
26
26
|
// 1. Breaker doesn't exist or is Closed
|
|
27
27
|
if (isUndefined(result) || (result.state === CircuitBreakerState.Closed)) {
|
|
28
28
|
return { allowed: true, state: CircuitBreakerState.Closed, isProbe: false };
|
|
@@ -38,20 +38,21 @@ let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends
|
|
|
38
38
|
// 4. Fallback: Catch-all for Open state where timeout hasn't expired. Reject
|
|
39
39
|
return { allowed: false, state: result.state, isProbe: false };
|
|
40
40
|
}
|
|
41
|
-
async recordSuccess() {
|
|
42
|
-
await this.#repository.tryDeleteByQuery({
|
|
41
|
+
async recordSuccess(resource) {
|
|
42
|
+
await this.#repository.tryDeleteByQuery({ namespace: this.namespace, resource });
|
|
43
43
|
}
|
|
44
|
-
async recordFailure() {
|
|
45
|
-
await this.recordFailures(1);
|
|
44
|
+
async recordFailure(resource) {
|
|
45
|
+
await this.recordFailures(resource, 1);
|
|
46
46
|
}
|
|
47
|
-
async recordFailures(count) {
|
|
47
|
+
async recordFailures(resource, count) {
|
|
48
48
|
const initialTrip = count >= this.#threshold;
|
|
49
49
|
const initialState = initialTrip ? CircuitBreakerState.Open : CircuitBreakerState.Closed;
|
|
50
50
|
const initialResetTimestamp = initialTrip
|
|
51
|
-
? sql `${
|
|
51
|
+
? sql `${CLOCK_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}`
|
|
52
52
|
: null;
|
|
53
|
-
await this.#repository.upsert(['
|
|
54
|
-
|
|
53
|
+
await this.#repository.upsert(['namespace', 'resource'], {
|
|
54
|
+
namespace: this.namespace,
|
|
55
|
+
resource,
|
|
55
56
|
state: initialState,
|
|
56
57
|
failureCount: count,
|
|
57
58
|
resetTimestamp: initialResetTimestamp,
|
|
@@ -62,7 +63,7 @@ let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends
|
|
|
62
63
|
ELSE ${circuitBreaker.state}
|
|
63
64
|
END`,
|
|
64
65
|
resetTimestamp: sql `CASE
|
|
65
|
-
WHEN ${circuitBreaker.failureCount} + ${count} >= ${this.#threshold} THEN ${
|
|
66
|
+
WHEN ${circuitBreaker.failureCount} + ${count} >= ${this.#threshold} THEN ${CLOCK_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}
|
|
66
67
|
ELSE ${circuitBreaker.resetTimestamp}
|
|
67
68
|
END`,
|
|
68
69
|
});
|
|
@@ -72,26 +73,27 @@ let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends
|
|
|
72
73
|
const attemptUpdate = session.$with('attempt_update').as(() => session
|
|
73
74
|
.update(circuitBreaker)
|
|
74
75
|
.set({ state: CircuitBreakerState.HalfOpen })
|
|
75
|
-
.where(and(eq(circuitBreaker.
|
|
76
|
+
.where(and(eq(circuitBreaker.namespace, sql.placeholder('namespace')), eq(circuitBreaker.resource, sql.placeholder('resource')), eq(circuitBreaker.state, CircuitBreakerState.Open), lte(circuitBreaker.resetTimestamp, CLOCK_TIMESTAMP)))
|
|
76
77
|
.returning({
|
|
77
|
-
|
|
78
|
+
namespace: circuitBreaker.namespace,
|
|
79
|
+
resource: circuitBreaker.resource,
|
|
78
80
|
state: circuitBreaker.state,
|
|
79
81
|
}));
|
|
80
82
|
return session
|
|
81
83
|
.with(attemptUpdate)
|
|
82
84
|
.select({
|
|
83
85
|
state: coalesce(attemptUpdate.state, circuitBreaker.state),
|
|
84
|
-
isProbe: sqlIsNotNull(attemptUpdate.
|
|
86
|
+
isProbe: sqlIsNotNull(attemptUpdate.resource),
|
|
85
87
|
})
|
|
86
88
|
.from(circuitBreaker)
|
|
87
|
-
.leftJoin(attemptUpdate, eq(circuitBreaker.
|
|
88
|
-
.where(eq(circuitBreaker.
|
|
89
|
+
.leftJoin(attemptUpdate, and(eq(circuitBreaker.namespace, attemptUpdate.namespace), eq(circuitBreaker.resource, attemptUpdate.resource)))
|
|
90
|
+
.where(and(eq(circuitBreaker.namespace, sql.placeholder('namespace')), eq(circuitBreaker.resource, sql.placeholder('resource'))))
|
|
89
91
|
.prepare('circuit_breaker_check');
|
|
90
92
|
}
|
|
91
93
|
};
|
|
92
94
|
PostgresCircuitBreakerService = __decorate([
|
|
93
95
|
Singleton({
|
|
94
|
-
argumentIdentityProvider: (arg) => isString(arg) ? arg : arg.
|
|
96
|
+
argumentIdentityProvider: (arg) => isString(arg) ? arg : arg.namespace,
|
|
95
97
|
providers: [
|
|
96
98
|
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresCircuitBreakerModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: true }) }),
|
|
97
99
|
],
|
package/circuit-breaker/postgres/drizzle/{0000_same_captain_cross.sql → 0000_dapper_hercules.sql}
RENAMED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
CREATE TYPE "circuit_breaker"."circuit_breaker_state" AS ENUM('closed', 'open', 'half-open');--> statement-breakpoint
|
|
2
2
|
CREATE TABLE "circuit_breaker"."circuit_breaker" (
|
|
3
3
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
4
|
-
"
|
|
4
|
+
"namespace" text NOT NULL,
|
|
5
|
+
"resource" text NOT NULL,
|
|
5
6
|
"state" "circuit_breaker"."circuit_breaker_state" NOT NULL,
|
|
6
7
|
"failure_count" integer NOT NULL,
|
|
7
8
|
"reset_timestamp" timestamp with time zone,
|
|
8
|
-
CONSTRAINT "
|
|
9
|
+
CONSTRAINT "circuit_breaker_namespace_resource_unique" UNIQUE("namespace","resource")
|
|
9
10
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "d6defc88-8e78-4652-bce7-95d26f93e753",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -15,8 +15,14 @@
|
|
|
15
15
|
"notNull": true,
|
|
16
16
|
"default": "uuidv7()"
|
|
17
17
|
},
|
|
18
|
-
"
|
|
19
|
-
"name": "
|
|
18
|
+
"namespace": {
|
|
19
|
+
"name": "namespace",
|
|
20
|
+
"type": "text",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
24
|
+
"resource": {
|
|
25
|
+
"name": "resource",
|
|
20
26
|
"type": "text",
|
|
21
27
|
"primaryKey": false,
|
|
22
28
|
"notNull": true
|
|
@@ -45,11 +51,12 @@
|
|
|
45
51
|
"foreignKeys": {},
|
|
46
52
|
"compositePrimaryKeys": {},
|
|
47
53
|
"uniqueConstraints": {
|
|
48
|
-
"
|
|
49
|
-
"name": "
|
|
54
|
+
"circuit_breaker_namespace_resource_unique": {
|
|
55
|
+
"name": "circuit_breaker_namespace_resource_unique",
|
|
50
56
|
"nullsNotDistinct": false,
|
|
51
57
|
"columns": [
|
|
52
|
-
"
|
|
58
|
+
"namespace",
|
|
59
|
+
"resource"
|
|
53
60
|
]
|
|
54
61
|
}
|
|
55
62
|
},
|
|
@@ -2,7 +2,8 @@ import { BaseEntity, type Timestamp } from '../../orm/index.js';
|
|
|
2
2
|
import { CircuitBreakerState } from '../circuit-breaker.js';
|
|
3
3
|
export declare class PostgresCircuitBreaker extends BaseEntity {
|
|
4
4
|
static readonly entityName = "CircuitBreaker";
|
|
5
|
-
|
|
5
|
+
namespace: string;
|
|
6
|
+
resource: string;
|
|
6
7
|
state: CircuitBreakerState;
|
|
7
8
|
failureCount: number;
|
|
8
9
|
resetTimestamp: Timestamp | null;
|
|
@@ -12,16 +12,20 @@ import { Enumeration, Integer, StringProperty } from '../../schema/index.js';
|
|
|
12
12
|
import { CircuitBreakerState } from '../circuit-breaker.js';
|
|
13
13
|
let PostgresCircuitBreaker = class PostgresCircuitBreaker extends BaseEntity {
|
|
14
14
|
static entityName = 'CircuitBreaker';
|
|
15
|
-
|
|
15
|
+
namespace;
|
|
16
|
+
resource;
|
|
16
17
|
state;
|
|
17
18
|
failureCount;
|
|
18
19
|
resetTimestamp;
|
|
19
20
|
};
|
|
20
21
|
__decorate([
|
|
21
22
|
StringProperty(),
|
|
22
|
-
Unique(),
|
|
23
23
|
__metadata("design:type", String)
|
|
24
|
-
], PostgresCircuitBreaker.prototype, "
|
|
24
|
+
], PostgresCircuitBreaker.prototype, "namespace", void 0);
|
|
25
|
+
__decorate([
|
|
26
|
+
StringProperty(),
|
|
27
|
+
__metadata("design:type", String)
|
|
28
|
+
], PostgresCircuitBreaker.prototype, "resource", void 0);
|
|
25
29
|
__decorate([
|
|
26
30
|
Enumeration(CircuitBreakerState),
|
|
27
31
|
__metadata("design:type", String)
|
|
@@ -35,6 +39,7 @@ __decorate([
|
|
|
35
39
|
__metadata("design:type", Object)
|
|
36
40
|
], PostgresCircuitBreaker.prototype, "resetTimestamp", void 0);
|
|
37
41
|
PostgresCircuitBreaker = __decorate([
|
|
38
|
-
Table('circuit_breaker', { schema: 'circuit_breaker' })
|
|
42
|
+
Table('circuit_breaker', { schema: 'circuit_breaker' }),
|
|
43
|
+
Unique(['namespace', 'resource'])
|
|
39
44
|
], PostgresCircuitBreaker);
|
|
40
45
|
export { PostgresCircuitBreaker };
|
|
@@ -2,5 +2,5 @@ import type { CircuitBreaker, CircuitBreakerConfig } from '../circuit-breaker.js
|
|
|
2
2
|
import { CircuitBreakerProvider } from '../provider.js';
|
|
3
3
|
export declare class PostgresCircuitBreakerProvider extends CircuitBreakerProvider {
|
|
4
4
|
#private;
|
|
5
|
-
provide(
|
|
5
|
+
provide(namespace: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
6
6
|
}
|
|
@@ -10,8 +10,8 @@ import { CircuitBreakerProvider } from '../provider.js';
|
|
|
10
10
|
import { PostgresCircuitBreakerService } from './circuit-breaker.js';
|
|
11
11
|
let PostgresCircuitBreakerProvider = class PostgresCircuitBreakerProvider extends CircuitBreakerProvider {
|
|
12
12
|
#injector = inject(Injector);
|
|
13
|
-
provide(
|
|
14
|
-
const argument = config ? {
|
|
13
|
+
provide(namespace, config) {
|
|
14
|
+
const argument = config ? { namespace, ...config } : namespace;
|
|
15
15
|
return this.#injector.resolve(PostgresCircuitBreakerService, argument);
|
|
16
16
|
}
|
|
17
17
|
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { CircuitBreaker, CircuitBreakerConfig } from './circuit-breaker.js';
|
|
2
2
|
export declare abstract class CircuitBreakerProvider {
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Provides a CircuitBreaker for a namespace.
|
|
5
|
+
* @param namespace The namespace for the circuit breaker.
|
|
6
|
+
* @param config The configuration for the circuit breaker.
|
|
7
|
+
*/
|
|
8
|
+
abstract provide(namespace: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
4
9
|
}
|
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/orm/sqls/sqls.d.ts
CHANGED
|
@@ -55,11 +55,13 @@ export type TsHeadlineOptions = {
|
|
|
55
55
|
*/
|
|
56
56
|
fragmentDelimiter?: string;
|
|
57
57
|
};
|
|
58
|
-
/**
|
|
58
|
+
/** Get the current transaction's timestamp. */
|
|
59
59
|
export declare const TRANSACTION_TIMESTAMP: SQL<Date>;
|
|
60
|
-
/**
|
|
60
|
+
/** Get the current clock timestamp (real time, even within transaction). */
|
|
61
|
+
export declare const CLOCK_TIMESTAMP: SQL<Date>;
|
|
62
|
+
/** Generate a random UUID (v4). */
|
|
61
63
|
export declare const RANDOM_UUID_V4: SQL<Uuid>;
|
|
62
|
-
/**
|
|
64
|
+
/** Generate a random UUID (v7). */
|
|
63
65
|
export declare const RANDOM_UUID_V7: SQL<Uuid>;
|
|
64
66
|
export declare const SQL_TRUE: SQL<boolean>;
|
|
65
67
|
export declare const SQL_FALSE: SQL<boolean>;
|
package/orm/sqls/sqls.js
CHANGED
|
@@ -25,11 +25,13 @@ function isJsonb(value) {
|
|
|
25
25
|
}
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
28
|
+
/** Get the current transaction's timestamp. */
|
|
29
29
|
export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
|
|
30
|
-
/**
|
|
30
|
+
/** Get the current clock timestamp (real time, even within transaction). */
|
|
31
|
+
export const CLOCK_TIMESTAMP = sql `clock_timestamp()`;
|
|
32
|
+
/** Generate a random UUID (v4). */
|
|
31
33
|
export const RANDOM_UUID_V4 = sql `gen_random_uuid()`;
|
|
32
|
-
/**
|
|
34
|
+
/** Generate a random UUID (v7). */
|
|
33
35
|
export const RANDOM_UUID_V7 = sql `uuidv7()`;
|
|
34
36
|
export const SQL_TRUE = sql `TRUE`;
|
|
35
37
|
export const SQL_FALSE = sql `FALSE`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.189",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -153,8 +153,8 @@
|
|
|
153
153
|
"type-fest": "^5.5"
|
|
154
154
|
},
|
|
155
155
|
"peerDependencies": {
|
|
156
|
-
"@aws-sdk/client-s3": "^3.
|
|
157
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
156
|
+
"@aws-sdk/client-s3": "^3.1022",
|
|
157
|
+
"@aws-sdk/s3-request-presigner": "^3.1022",
|
|
158
158
|
"@genkit-ai/google-genai": "^1.31",
|
|
159
159
|
"@google-cloud/storage": "^7.19",
|
|
160
160
|
"@toon-format/toon": "^2.1.0",
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
"preact": "^10.29",
|
|
175
175
|
"preact-render-to-string": "^6.6",
|
|
176
176
|
"sharp": "^0.34",
|
|
177
|
-
"undici": "^
|
|
177
|
+
"undici": "^8.0",
|
|
178
178
|
"urlpattern-polyfill": "^10.1",
|
|
179
179
|
"zod": "^3.25"
|
|
180
180
|
},
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
CREATE TABLE "rate_limit"."rate_limit" (
|
|
2
2
|
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
3
|
+
"namespace" text NOT NULL,
|
|
3
4
|
"resource" text NOT NULL,
|
|
4
5
|
"tokens" double precision NOT NULL,
|
|
5
6
|
"last_refill_timestamp" timestamp with time zone NOT NULL,
|
|
6
|
-
CONSTRAINT "
|
|
7
|
+
CONSTRAINT "rate_limit_namespace_resource_unique" UNIQUE("namespace","resource")
|
|
7
8
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
2
|
+
"id": "e46716f4-2778-4a99-b7ac-731d9efead2c",
|
|
3
3
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
4
|
"version": "7",
|
|
5
5
|
"dialect": "postgresql",
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
"notNull": true,
|
|
16
16
|
"default": "uuidv7()"
|
|
17
17
|
},
|
|
18
|
+
"namespace": {
|
|
19
|
+
"name": "namespace",
|
|
20
|
+
"type": "text",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
18
24
|
"resource": {
|
|
19
25
|
"name": "resource",
|
|
20
26
|
"type": "text",
|
|
@@ -38,10 +44,11 @@
|
|
|
38
44
|
"foreignKeys": {},
|
|
39
45
|
"compositePrimaryKeys": {},
|
|
40
46
|
"uniqueConstraints": {
|
|
41
|
-
"
|
|
42
|
-
"name": "
|
|
47
|
+
"rate_limit_namespace_resource_unique": {
|
|
48
|
+
"name": "rate_limit_namespace_resource_unique",
|
|
43
49
|
"nullsNotDistinct": false,
|
|
44
50
|
"columns": [
|
|
51
|
+
"namespace",
|
|
45
52
|
"resource"
|
|
46
53
|
]
|
|
47
54
|
}
|
|
@@ -16,6 +16,7 @@ import { rateLimit } from './schemas.js';
|
|
|
16
16
|
let PostgresRateLimiter = class PostgresRateLimiter extends RateLimiter {
|
|
17
17
|
#repository = injectRepository(PostgresRateLimit);
|
|
18
18
|
#config = this.transactionalContextData?.config ?? injectArgument(this);
|
|
19
|
+
namespace = isString(this.#config) ? this.#config : this.#config.namespace;
|
|
19
20
|
burstCapacity = isString(this.#config) ? 100 : this.#config.burstCapacity;
|
|
20
21
|
refillInterval = isString(this.#config) ? 1000 : this.#config.refillInterval;
|
|
21
22
|
refillRate = this.burstCapacity / this.refillInterval;
|
|
@@ -38,7 +39,7 @@ let PostgresRateLimiter = class PostgresRateLimiter extends RateLimiter {
|
|
|
38
39
|
const lastRefillMs = sql `(EXTRACT(EPOCH FROM ${rateLimit.lastRefillTimestamp}) * 1000)`;
|
|
39
40
|
const tokensToAdd = sql `(${nowMs} - ${lastRefillMs}) * ${refillRate}`;
|
|
40
41
|
const newTokensExpression = least(burstCapacity, sql `${rateLimit.tokens} + ${tokensToAdd}`);
|
|
41
|
-
const result = await this.#repository.tryUpsert('resource', { resource, tokens: burstCapacity - cost, lastRefillTimestamp: TRANSACTION_TIMESTAMP }, { tokens: sql `${newTokensExpression} - ${cost}`, lastRefillTimestamp: TRANSACTION_TIMESTAMP }, { set: gte(newTokensExpression, cost) });
|
|
42
|
+
const result = await this.#repository.tryUpsert(['namespace', 'resource'], { namespace: this.namespace, resource, tokens: burstCapacity - cost, lastRefillTimestamp: TRANSACTION_TIMESTAMP }, { tokens: sql `${newTokensExpression} - ${cost}`, lastRefillTimestamp: TRANSACTION_TIMESTAMP }, { set: gte(newTokensExpression, cost) });
|
|
42
43
|
return isDefined(result);
|
|
43
44
|
}
|
|
44
45
|
async refund(resource, amount = 1, options) {
|
|
@@ -46,7 +47,7 @@ let PostgresRateLimiter = class PostgresRateLimiter extends RateLimiter {
|
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
const burstCapacity = options?.burstCapacity ?? this.burstCapacity;
|
|
49
|
-
await this.#repository.updateByQuery({ resource }, { tokens: least(burstCapacity, sql `${rateLimit.tokens} + ${amount}`) });
|
|
50
|
+
await this.#repository.updateByQuery({ namespace: this.namespace, resource }, { tokens: least(burstCapacity, sql `${rateLimit.tokens} + ${amount}`) });
|
|
50
51
|
}
|
|
51
52
|
getTransactionalContextData() {
|
|
52
53
|
return { config: this.#config };
|
|
@@ -2,6 +2,7 @@ import { BaseEntity } from '../../orm/index.js';
|
|
|
2
2
|
import type { Timestamp } from '../../orm/types.js';
|
|
3
3
|
export declare class PostgresRateLimit extends BaseEntity {
|
|
4
4
|
static readonly entityName = "RateLimit";
|
|
5
|
+
namespace: string;
|
|
5
6
|
resource: string;
|
|
6
7
|
tokens: number;
|
|
7
8
|
lastRefillTimestamp: Timestamp;
|
|
@@ -12,10 +12,15 @@ import { TimestampProperty } from '../../orm/schemas/timestamp.js';
|
|
|
12
12
|
import { NumberProperty, StringProperty } from '../../schema/index.js';
|
|
13
13
|
let PostgresRateLimit = class PostgresRateLimit extends BaseEntity {
|
|
14
14
|
static entityName = 'RateLimit';
|
|
15
|
+
namespace;
|
|
15
16
|
resource;
|
|
16
17
|
tokens;
|
|
17
18
|
lastRefillTimestamp;
|
|
18
19
|
};
|
|
20
|
+
__decorate([
|
|
21
|
+
StringProperty(),
|
|
22
|
+
__metadata("design:type", String)
|
|
23
|
+
], PostgresRateLimit.prototype, "namespace", void 0);
|
|
19
24
|
__decorate([
|
|
20
25
|
StringProperty(),
|
|
21
26
|
__metadata("design:type", String)
|
|
@@ -30,6 +35,6 @@ __decorate([
|
|
|
30
35
|
], PostgresRateLimit.prototype, "lastRefillTimestamp", void 0);
|
|
31
36
|
PostgresRateLimit = __decorate([
|
|
32
37
|
Table('rate_limit', { schema: 'rate_limit' }),
|
|
33
|
-
Unique(['resource'])
|
|
38
|
+
Unique(['namespace', 'resource'])
|
|
34
39
|
], PostgresRateLimit);
|
|
35
40
|
export { PostgresRateLimit };
|
|
@@ -2,5 +2,5 @@ import { RateLimiterProvider } from '../provider.js';
|
|
|
2
2
|
import { RateLimiter, type RateLimiterConfig } from '../rate-limiter.js';
|
|
3
3
|
export declare class PostgresRateLimiterProvider extends RateLimiterProvider {
|
|
4
4
|
#private;
|
|
5
|
-
get(
|
|
5
|
+
get(namespace: string, config: RateLimiterConfig): RateLimiter;
|
|
6
6
|
}
|
|
@@ -11,8 +11,8 @@ import { RateLimiter } from '../rate-limiter.js';
|
|
|
11
11
|
import { PostgresRateLimiter } from './postgres-rate-limiter.js';
|
|
12
12
|
let PostgresRateLimiterProvider = class PostgresRateLimiterProvider extends RateLimiterProvider {
|
|
13
13
|
#injector = inject(Injector);
|
|
14
|
-
get(
|
|
15
|
-
return this.#injector.resolve(PostgresRateLimiter, { ...config,
|
|
14
|
+
get(namespace, config) {
|
|
15
|
+
return this.#injector.resolve(PostgresRateLimiter, { ...config, namespace });
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
PostgresRateLimiterProvider = __decorate([
|
package/rate-limit/provider.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { RateLimiter, RateLimiterConfig } from './rate-limiter.js';
|
|
2
2
|
export declare abstract class RateLimiterProvider {
|
|
3
3
|
/**
|
|
4
|
-
* Gets a RateLimiter instance for the specified
|
|
5
|
-
* @param
|
|
4
|
+
* Gets a RateLimiter instance for the specified namespace and configuration.
|
|
5
|
+
* @param namespace The namespace of the rate limiter.
|
|
6
6
|
* @param config The configuration for the rate limiter.
|
|
7
7
|
*/
|
|
8
|
-
abstract get(
|
|
8
|
+
abstract get(namespace: string, config: RateLimiterConfig): RateLimiter;
|
|
9
9
|
}
|