@tstdl/base 0.93.187 → 0.93.188

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 (39) hide show
  1. package/ai/genkit/multi-region.plugin.js +14 -11
  2. package/ai/genkit/types.d.ts +1 -1
  3. package/ai/genkit/types.js +1 -1
  4. package/authentication/client/authentication.service.js +34 -9
  5. package/authentication/models/authentication-totp-recovery-code.model.js +3 -2
  6. package/authentication/server/authentication.api-controller.d.ts +1 -0
  7. package/authentication/server/authentication.api-controller.js +9 -2
  8. package/authentication/server/authentication.service.js +108 -91
  9. package/authentication/server/drizzle/{0000_odd_echo.sql → 0000_dry_stepford_cuckoos.sql} +2 -1
  10. package/authentication/server/drizzle/meta/0000_snapshot.json +24 -2
  11. package/authentication/server/drizzle/meta/_journal.json +2 -2
  12. package/circuit-breaker/circuit-breaker.d.ts +22 -10
  13. package/circuit-breaker/postgres/circuit-breaker.d.ts +5 -4
  14. package/circuit-breaker/postgres/circuit-breaker.js +21 -19
  15. package/circuit-breaker/postgres/drizzle/{0000_same_captain_cross.sql → 0000_dapper_hercules.sql} +3 -2
  16. package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +13 -6
  17. package/circuit-breaker/postgres/drizzle/meta/_journal.json +2 -2
  18. package/circuit-breaker/postgres/model.d.ts +2 -1
  19. package/circuit-breaker/postgres/model.js +9 -4
  20. package/circuit-breaker/postgres/provider.d.ts +1 -1
  21. package/circuit-breaker/postgres/provider.js +2 -2
  22. package/circuit-breaker/provider.d.ts +6 -1
  23. package/orm/sqls/sqls.d.ts +5 -3
  24. package/orm/sqls/sqls.js +5 -3
  25. package/package.json +4 -4
  26. package/rate-limit/postgres/drizzle/{0000_serious_sauron.sql → 0000_previous_zeigeist.sql} +2 -1
  27. package/rate-limit/postgres/drizzle/meta/0000_snapshot.json +10 -3
  28. package/rate-limit/postgres/drizzle/meta/_journal.json +2 -2
  29. package/rate-limit/postgres/postgres-rate-limiter.d.ts +1 -0
  30. package/rate-limit/postgres/postgres-rate-limiter.js +3 -2
  31. package/rate-limit/postgres/rate-limit.model.d.ts +1 -0
  32. package/rate-limit/postgres/rate-limit.model.js +6 -1
  33. package/rate-limit/postgres/rate-limiter.provider.d.ts +1 -1
  34. package/rate-limit/postgres/rate-limiter.provider.js +2 -2
  35. package/rate-limit/provider.d.ts +3 -3
  36. package/rate-limit/rate-limiter.d.ts +2 -1
  37. package/signals/operators/derive-async.js +11 -6
  38. package/task-queue/postgres/task-queue.js +8 -8
  39. package/testing/integration-setup.js +4 -0
@@ -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
  }
@@ -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.188",
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
  }
@@ -11,10 +11,11 @@ export type RateLimiterConfig = {
11
11
  refillInterval: number;
12
12
  };
13
13
  export type RateLimiterArgument = string | (RateLimiterConfig & {
14
- resource: string;
14
+ namespace: string;
15
15
  });
16
16
  export declare abstract class RateLimiter extends Transactional implements Resolvable<RateLimiterArgument> {
17
17
  readonly [resolveArgumentType]: RateLimiterArgument;
18
+ abstract readonly namespace: string;
18
19
  abstract readonly burstCapacity: number;
19
20
  abstract readonly refillInterval: number;
20
21
  /**
@@ -21,7 +21,8 @@ const operatorMap = {
21
21
  * @returns A Signal that represents the latest value from the async source.
22
22
  */
23
23
  export function deriveAsync(source, options) {
24
- const initialRaw = untracked(source);
24
+ const sourceSignal = computed(source);
25
+ const initialRaw = untracked(sourceSignal);
25
26
  const initialSource = isPromise(initialRaw) ? from(initialRaw) : isObservable(initialRaw) ? initialRaw : of(initialRaw);
26
27
  const source$ = new BehaviorSubject(initialSource);
27
28
  const destroy$ = new Subject();
@@ -44,14 +45,18 @@ export function deriveAsync(source, options) {
44
45
  }
45
46
  return notification.value;
46
47
  }, { equal: options?.equal });
47
- let firstRun = true;
48
+ let lastRaw = initialRaw;
48
49
  const effectRef = effect(() => {
49
- const rawSource = source();
50
- if (firstRun) {
51
- firstRun = false;
50
+ const rawSource = sourceSignal();
51
+ if (rawSource === lastRaw) {
52
52
  return;
53
53
  }
54
- const observableInput = isPromise(rawSource) ? from(rawSource) : isObservable(rawSource) ? rawSource : of(rawSource);
54
+ lastRaw = rawSource;
55
+ const observableInput = isPromise(rawSource)
56
+ ? from(rawSource)
57
+ : isObservable(rawSource)
58
+ ? rawSource
59
+ : of(rawSource);
55
60
  untracked(() => source$.next(observableInput));
56
61
  });
57
62
  registerFinalization(result, () => {