@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.
Files changed (45) hide show
  1. package/ai/genkit/multi-region.plugin.js +15 -11
  2. package/ai/genkit/types.d.ts +1 -1
  3. package/ai/genkit/types.js +1 -1
  4. package/authentication/client/authentication.service.d.ts +5 -0
  5. package/authentication/client/authentication.service.js +21 -4
  6. package/authentication/models/authentication-totp-recovery-code.model.js +3 -2
  7. package/authentication/models/totp-results.model.d.ts +1 -0
  8. package/authentication/models/totp-results.model.js +6 -1
  9. package/authentication/server/authentication.api-controller.d.ts +1 -0
  10. package/authentication/server/authentication.api-controller.js +9 -2
  11. package/authentication/server/authentication.service.js +114 -92
  12. package/authentication/server/drizzle/{0000_odd_echo.sql → 0000_dry_stepford_cuckoos.sql} +2 -1
  13. package/authentication/server/drizzle/meta/0000_snapshot.json +24 -2
  14. package/authentication/server/drizzle/meta/_journal.json +2 -2
  15. package/circuit-breaker/circuit-breaker.d.ts +22 -10
  16. package/circuit-breaker/postgres/circuit-breaker.d.ts +5 -4
  17. package/circuit-breaker/postgres/circuit-breaker.js +21 -19
  18. package/circuit-breaker/postgres/drizzle/{0000_same_captain_cross.sql → 0000_dapper_hercules.sql} +3 -2
  19. package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +13 -6
  20. package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
  21. package/circuit-breaker/postgres/model.d.ts +2 -1
  22. package/circuit-breaker/postgres/model.js +9 -4
  23. package/circuit-breaker/postgres/provider.d.ts +1 -1
  24. package/circuit-breaker/postgres/provider.js +2 -2
  25. package/circuit-breaker/provider.d.ts +6 -1
  26. package/cryptography/totp.d.ts +2 -1
  27. package/cryptography/totp.js +15 -7
  28. package/orm/sqls/sqls.d.ts +5 -3
  29. package/orm/sqls/sqls.js +5 -3
  30. package/package.json +4 -4
  31. package/rate-limit/postgres/drizzle/{0000_serious_sauron.sql → 0000_previous_zeigeist.sql} +2 -1
  32. package/rate-limit/postgres/drizzle/meta/0000_snapshot.json +10 -3
  33. package/rate-limit/postgres/drizzle/meta/_journal.json +2 -2
  34. package/rate-limit/postgres/postgres-rate-limiter.d.ts +1 -0
  35. package/rate-limit/postgres/postgres-rate-limiter.js +3 -2
  36. package/rate-limit/postgres/rate-limit.model.d.ts +1 -0
  37. package/rate-limit/postgres/rate-limit.model.js +6 -1
  38. package/rate-limit/postgres/rate-limiter.provider.d.ts +1 -1
  39. package/rate-limit/postgres/rate-limiter.provider.js +2 -2
  40. package/rate-limit/provider.d.ts +3 -3
  41. package/rate-limit/rate-limiter.d.ts +2 -1
  42. package/signals/notifier.js +1 -1
  43. package/signals/operators/derive-async.js +11 -6
  44. package/task-queue/postgres/task-queue.js +8 -8
  45. package/testing/integration-setup.js +4 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "98e3b751-2fd9-4c4a-a432-c397baa359c0",
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",
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1774646419727,
9
- "tag": "0000_odd_echo",
8
+ "when": 1775138644671,
9
+ "tag": "0000_dry_stepford_cuckoos",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -15,20 +15,32 @@ export type CircuitBreakerCheckResult = {
15
15
  state: CircuitBreakerState;
16
16
  isProbe: boolean;
17
17
  };
18
- export type CircuitBreakerArgument = string | (CircuitBreakerConfig & {
19
- key: string;
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
- /** Records a success, resetting the breaker to Closed. */
29
- abstract recordSuccess(): Promise<void>;
30
- /** Records a failure, potentially tripping the breaker to Open. */
31
- abstract recordFailure(): Promise<void>;
32
- /** Records multiple failures at once. */
33
- abstract recordFailures(count: number): Promise<void>;
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
- check(): Promise<CircuitBreakerCheckResult>;
5
- recordSuccess(): Promise<void>;
6
- recordFailure(): Promise<void>;
7
- recordFailures(count: number): Promise<void>;
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, TRANSACTION_TIMESTAMP } from '../../orm/index.js';
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
- async check() {
25
- const [result] = await this.#checkStatement.execute({ key: this.#key });
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({ key: this.#key });
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 `${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}`
51
+ ? sql `${CLOCK_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}`
52
52
  : null;
53
- await this.#repository.upsert(['key'], {
54
- key: this.#key,
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 ${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}
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.key, sql.placeholder('key')), eq(circuitBreaker.state, CircuitBreakerState.Open), lte(circuitBreaker.resetTimestamp, TRANSACTION_TIMESTAMP)))
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
- key: circuitBreaker.key,
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.key),
86
+ isProbe: sqlIsNotNull(attemptUpdate.resource),
85
87
  })
86
88
  .from(circuitBreaker)
87
- .leftJoin(attemptUpdate, eq(circuitBreaker.key, attemptUpdate.key))
88
- .where(eq(circuitBreaker.key, sql.placeholder('key')))
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.key,
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
  ],
@@ -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
- "key" text NOT NULL,
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 "circuit_breaker_key_unique" UNIQUE("key")
9
+ CONSTRAINT "circuit_breaker_namespace_resource_unique" UNIQUE("namespace","resource")
9
10
  );
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "fb39f2b0-2e0e-4ebc-bd1c-dc9f97925626",
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
- "key": {
19
- "name": "key",
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
- "circuit_breaker_key_unique": {
49
- "name": "circuit_breaker_key_unique",
54
+ "circuit_breaker_namespace_resource_unique": {
55
+ "name": "circuit_breaker_namespace_resource_unique",
50
56
  "nullsNotDistinct": false,
51
57
  "columns": [
52
- "key"
58
+ "namespace",
59
+ "resource"
53
60
  ]
54
61
  }
55
62
  },
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1774646420943,
9
- "tag": "0000_same_captain_cross",
8
+ "when": 1775138615813,
9
+ "tag": "0000_dapper_hercules",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -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
- key: string;
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
- key;
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, "key", void 0);
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(key: string, config?: CircuitBreakerConfig): CircuitBreaker;
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(key, config) {
14
- const argument = config ? { key, ...config } : key;
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
- abstract provide(key: string, config?: CircuitBreakerConfig): CircuitBreaker;
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
  }
@@ -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 8.
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.
@@ -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 8.
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 = 8) {
95
- const codes = [];
96
- for (let i = 0; i < count; i++) {
97
- codes.push(getRandomString(length, Alphabet.Base32));
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 codes;
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 keyMaterial = encodeUtf8(code);
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
  }
@@ -55,11 +55,13 @@ export type TsHeadlineOptions = {
55
55
  */
56
56
  fragmentDelimiter?: string;
57
57
  };
58
- /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
58
+ /** Get the current transaction's timestamp. */
59
59
  export declare const TRANSACTION_TIMESTAMP: SQL<Date>;
60
- /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
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
- /** Drizzle SQL helper for generating a random UUID (v7). Returns a Uuid string. */
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
- /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
28
+ /** Get the current transaction's timestamp. */
29
29
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
30
- /** Drizzle SQL helper for generating a random UUID (v4). Returns a Uuid string. */
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
- /** Drizzle SQL helper for generating a random UUID (v7). Returns a Uuid string. */
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.187",
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.1021",
157
- "@aws-sdk/s3-request-presigner": "^3.1021",
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": "^7.24",
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 "rate_limit_resource_unique" UNIQUE("resource")
7
+ CONSTRAINT "rate_limit_namespace_resource_unique" UNIQUE("namespace","resource")
7
8
  );
@@ -1,5 +1,5 @@
1
1
  {
2
- "id": "7e627538-45f2-41c1-991c-f40ad4f76787",
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
- "rate_limit_resource_unique": {
42
- "name": "rate_limit_resource_unique",
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
  }
@@ -5,8 +5,8 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1774646421753,
9
- "tag": "0000_serious_sauron",
8
+ "when": 1775138616730,
9
+ "tag": "0000_previous_zeigeist",
10
10
  "breakpoints": true
11
11
  }
12
12
  ]
@@ -4,6 +4,7 @@ type PostgresRateLimiterContext = {
4
4
  };
5
5
  export declare class PostgresRateLimiter extends RateLimiter {
6
6
  #private;
7
+ readonly namespace: string;
7
8
  readonly burstCapacity: number;
8
9
  readonly refillInterval: number;
9
10
  readonly refillRate: number;
@@ -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(resource: string, config: RateLimiterConfig): RateLimiter;
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(resource, config) {
15
- return this.#injector.resolve(PostgresRateLimiter, { ...config, resource });
14
+ get(namespace, config) {
15
+ return this.#injector.resolve(PostgresRateLimiter, { ...config, namespace });
16
16
  }
17
17
  };
18
18
  PostgresRateLimiterProvider = __decorate([
@@ -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 resource and configuration.
5
- * @param resource The unique name of the rate limiter.
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(resource: string, config: RateLimiterConfig): RateLimiter;
8
+ abstract get(namespace: string, config: RateLimiterConfig): RateLimiter;
9
9
  }