@tstdl/base 0.93.140 → 0.93.142

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 (123) hide show
  1. package/application/application.d.ts +1 -1
  2. package/application/application.js +1 -1
  3. package/application/providers.d.ts +20 -2
  4. package/application/providers.js +34 -7
  5. package/audit/module.d.ts +5 -0
  6. package/audit/module.js +9 -1
  7. package/authentication/client/authentication.service.d.ts +1 -0
  8. package/authentication/client/authentication.service.js +3 -2
  9. package/authentication/server/module.d.ts +5 -0
  10. package/authentication/server/module.js +9 -1
  11. package/authentication/tests/authentication.api-controller.test.js +1 -1
  12. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  13. package/authentication/tests/authentication.client-service.test.js +1 -1
  14. package/circuit-breaker/circuit-breaker.d.ts +6 -4
  15. package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
  16. package/circuit-breaker/postgres/circuit-breaker.js +8 -5
  17. package/circuit-breaker/postgres/module.d.ts +1 -0
  18. package/circuit-breaker/postgres/module.js +5 -1
  19. package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
  20. package/document-management/server/configure.js +5 -1
  21. package/document-management/server/module.d.ts +1 -1
  22. package/document-management/server/module.js +1 -1
  23. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  24. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  25. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  26. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  27. package/examples/document-management/main.d.ts +1 -0
  28. package/examples/document-management/main.js +14 -11
  29. package/key-value-store/postgres/module.d.ts +1 -0
  30. package/key-value-store/postgres/module.js +5 -1
  31. package/lock/postgres/module.d.ts +1 -0
  32. package/lock/postgres/module.js +5 -1
  33. package/mail/module.d.ts +5 -1
  34. package/mail/module.js +11 -6
  35. package/module/modules/web-server.module.js +2 -3
  36. package/notification/server/module.d.ts +1 -0
  37. package/notification/server/module.js +5 -1
  38. package/notification/tests/notification-api.test.js +5 -1
  39. package/notification/tests/notification-flow.test.js +8 -5
  40. package/orm/decorators.d.ts +22 -5
  41. package/orm/decorators.js +10 -1
  42. package/orm/server/bootstrap.d.ts +11 -0
  43. package/orm/server/bootstrap.js +31 -0
  44. package/orm/server/drizzle/schema-converter.d.ts +3 -1
  45. package/orm/server/drizzle/schema-converter.js +85 -56
  46. package/orm/server/encryption.d.ts +0 -1
  47. package/orm/server/encryption.js +1 -4
  48. package/orm/server/extension.d.ts +14 -0
  49. package/orm/server/extension.js +27 -0
  50. package/orm/server/index.d.ts +3 -6
  51. package/orm/server/index.js +3 -6
  52. package/orm/server/migration.d.ts +18 -0
  53. package/orm/server/migration.js +58 -0
  54. package/orm/server/repository.d.ts +2 -1
  55. package/orm/server/repository.js +19 -9
  56. package/orm/server/transaction.d.ts +6 -10
  57. package/orm/server/transaction.js +25 -26
  58. package/orm/server/transactional.js +3 -3
  59. package/orm/tests/database-extension.test.js +63 -0
  60. package/orm/tests/database-migration.test.js +83 -0
  61. package/orm/tests/encryption.test.js +3 -4
  62. package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
  63. package/orm/tests/repository-compound-primary-key.test.js +234 -0
  64. package/orm/tests/schema-generation.test.d.ts +1 -0
  65. package/orm/tests/schema-generation.test.js +52 -5
  66. package/orm/utils.d.ts +17 -2
  67. package/orm/utils.js +49 -1
  68. package/package.json +5 -4
  69. package/rate-limit/postgres/module.d.ts +1 -0
  70. package/rate-limit/postgres/module.js +5 -1
  71. package/reflection/decorator-data.js +11 -12
  72. package/task-queue/README.md +2 -10
  73. package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
  74. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
  75. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  76. package/task-queue/postgres/module.d.ts +1 -0
  77. package/task-queue/postgres/module.js +6 -1
  78. package/task-queue/postgres/schemas.d.ts +15 -6
  79. package/task-queue/postgres/schemas.js +4 -3
  80. package/task-queue/postgres/task-queue.d.ts +18 -15
  81. package/task-queue/postgres/task-queue.js +797 -499
  82. package/task-queue/postgres/task.model.d.ts +20 -9
  83. package/task-queue/postgres/task.model.js +65 -39
  84. package/task-queue/task-context.d.ts +12 -7
  85. package/task-queue/task-context.js +8 -6
  86. package/task-queue/task-queue.d.ts +364 -43
  87. package/task-queue/task-queue.js +153 -41
  88. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  89. package/task-queue/tests/coverage-branch.test.js +395 -0
  90. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  91. package/task-queue/tests/coverage-enhancement.test.js +150 -0
  92. package/task-queue/tests/dag.test.d.ts +1 -0
  93. package/task-queue/tests/dag.test.js +188 -0
  94. package/task-queue/tests/dependencies.test.js +165 -47
  95. package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
  96. package/task-queue/tests/enqueue-batch.test.js +125 -0
  97. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  98. package/task-queue/tests/fan-out-spawning.test.js +94 -0
  99. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  100. package/task-queue/tests/idempotent-replacement.test.js +114 -0
  101. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  102. package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
  103. package/task-queue/tests/queue.test.js +294 -49
  104. package/task-queue/tests/shutdown.test.d.ts +1 -0
  105. package/task-queue/tests/shutdown.test.js +41 -0
  106. package/task-queue/tests/transactions.test.d.ts +1 -0
  107. package/task-queue/tests/transactions.test.js +47 -0
  108. package/task-queue/tests/worker.test.js +63 -15
  109. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  110. package/task-queue/tests/zombie-parent.test.js +45 -0
  111. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  112. package/task-queue/tests/zombie-recovery.test.js +51 -0
  113. package/test5.js +5 -5
  114. package/testing/integration-setup.d.ts +4 -4
  115. package/testing/integration-setup.js +56 -29
  116. package/text/localization.service.js +2 -2
  117. package/utils/file-reader.js +1 -2
  118. package/utils/timing.d.ts +2 -2
  119. package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
  120. package/task-queue/tests/complex.test.js +0 -306
  121. package/task-queue/tests/extensive-dependencies.test.js +0 -234
  122. /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
  123. /package/{task-queue/tests/extensive-dependencies.test.d.ts → orm/tests/database-migration.test.d.ts} +0 -0
@@ -4,7 +4,7 @@ import { Module } from '../module/index.js';
4
4
  import type { OneOrMany, Type } from '../types/index.js';
5
5
  export type InitializerFn = () => void | Promise<void>;
6
6
  export type DestructorFn = () => void | Promise<void>;
7
- export declare const APPLICATION_MODULE: import("../injector/token.js").InjectionToken<Module, never>;
7
+ export declare const APPLICATION_MODULE: import("../injector/token.js").InjectionToken<Module | Type<Module>, never>;
8
8
  export declare const APPLICATION_INITIALIZER: import("../injector/token.js").InjectionToken<InitializerFn, never>;
9
9
  export declare const APPLICATION_DESTRUCTOR: import("../injector/token.js").InjectionToken<DestructorFn, never>;
10
10
  export declare const APPLICATION_INSTANCE_ID: import("../injector/token.js").InjectionToken<string, never>;
@@ -79,7 +79,7 @@ let Application = Application_1 = class Application {
79
79
  try {
80
80
  const initializers = await this.#injector.resolveAllAsync(APPLICATION_INITIALIZER, undefined, { optional: true });
81
81
  for (const initializer of initializers) {
82
- await runInInjectionContext(this.#injector, initializer);
82
+ await runInInjectionContext(this.#injector, async () => await initializer());
83
83
  }
84
84
  modules = await toArrayAsync(mapAsync(this.#moduleTypesAndInstances, async (instanceOrType) => (isFunction(instanceOrType) ? await this.#injector.resolveAsync(instanceOrType) : instanceOrType)));
85
85
  await Promise.race([
@@ -1,9 +1,27 @@
1
1
  import { CancellationSignal } from '../cancellation/index.js';
2
- import { type ProvidersItem } from '../injector/index.js';
2
+ import { Injector, type ProvidersItem } from '../injector/index.js';
3
3
  import { Module, type FunctionModuleFunction } from '../module/index.js';
4
4
  import type { OneOrMany, Type } from '../types/index.js';
5
5
  import { type DestructorFn, type InitializerFn } from './application.js';
6
- export declare function provideModule(...functionsAndModules: OneOrMany<FunctionModuleFunction | Type<Module>>[]): ProvidersItem[];
6
+ /**
7
+ * Registers an application initializer function that will be executed during application startup.
8
+ */
9
+ export declare function registerInitializer(fn: InitializerFn, options?: {
10
+ injector?: Injector;
11
+ }): void;
12
+ /**
13
+ * Registers an application destructor function that will be executed during application shutdown.
14
+ */
15
+ export declare function registerDestructor(fn: DestructorFn, options?: {
16
+ injector?: Injector;
17
+ }): void;
18
+ /**
19
+ * Registers a module or a function-based module in the application. If a class extending Module is provided, it will be registered directly. If a function is provided, it will be wrapped in a FunctionModule.
20
+ */
21
+ export declare function registerModule(fnOrModule: FunctionModuleFunction | Type<Module>, options?: {
22
+ injector?: Injector;
23
+ }): void;
24
+ export declare function provideModule(...functionsAndModules: OneOrMany<FunctionModuleFunction | Type<Module>>[]): ProvidersItem<Module | Type<Module>>[];
7
25
  export declare function provideInitializer(fn: InitializerFn): ProvidersItem;
8
26
  export declare function provideDestructor(fn: DestructorFn): ProvidersItem;
9
27
  export declare function provideShutdownSignal(signal: CancellationSignal): ProvidersItem;
@@ -1,5 +1,5 @@
1
1
  import { CancellationSignal } from '../cancellation/index.js';
2
- import { inject } from '../injector/index.js';
2
+ import { inject, Injector, provide } from '../injector/index.js';
3
3
  import { Logger } from '../logger/index.js';
4
4
  import { FunctionModule, Module } from '../module/index.js';
5
5
  import { isFunction } from '../utils/type-guards.js';
@@ -7,17 +7,44 @@ import { typeExtends } from '../utils/type/index.js';
7
7
  import { Application, APPLICATION_DESTRUCTOR, APPLICATION_INITIALIZER, APPLICATION_MODULE } from './application.js';
8
8
  const quitSignals = ['SIGTERM', 'SIGINT', 'SIGHUP', 'SIGBREAK'];
9
9
  const quitEvents = ['uncaughtException', 'unhandledRejection', 'rejectionHandled'];
10
+ /**
11
+ * Registers an application initializer function that will be executed during application startup.
12
+ */
13
+ export function registerInitializer(fn, options) {
14
+ const targetInjector = options?.injector ?? Injector;
15
+ targetInjector.register(APPLICATION_INITIALIZER, { useValue: fn }, { multi: true });
16
+ }
17
+ /**
18
+ * Registers an application destructor function that will be executed during application shutdown.
19
+ */
20
+ export function registerDestructor(fn, options) {
21
+ const targetInjector = options?.injector ?? Injector;
22
+ targetInjector.register(APPLICATION_DESTRUCTOR, { useValue: fn }, { multi: true });
23
+ }
24
+ /**
25
+ * Registers a module or a function-based module in the application. If a class extending Module is provided, it will be registered directly. If a function is provided, it will be wrapped in a FunctionModule.
26
+ */
27
+ export function registerModule(fnOrModule, options) {
28
+ const targetInjector = options?.injector ?? Injector;
29
+ if (typeExtends(fnOrModule, Module)) {
30
+ targetInjector.register(APPLICATION_MODULE, { useValue: fnOrModule }, { multi: true });
31
+ }
32
+ else if (isFunction(fnOrModule)) {
33
+ targetInjector.register(APPLICATION_MODULE, { useToken: FunctionModule, defaultArgument: fnOrModule }, { multi: true });
34
+ }
35
+ else {
36
+ throw new TypeError('Invalid module or function provided to registerModule');
37
+ }
38
+ }
10
39
  export function provideModule(...functionsAndModules) {
11
40
  return functionsAndModules.flat().map((fnOrModule) => {
12
41
  if (typeExtends(fnOrModule, Module)) {
13
- return { provide: APPLICATION_MODULE, useValue: fnOrModule, multi: true };
14
- }
15
- else if (isFunction(fnOrModule)) {
16
- return { provide: APPLICATION_MODULE, useToken: FunctionModule, defaultArgument: fnOrModule, multi: true };
42
+ return provide(APPLICATION_MODULE, { useValue: fnOrModule, multi: true });
17
43
  }
18
- else {
19
- throw new TypeError('Invalid module or function provided to provideModules');
44
+ if (isFunction(fnOrModule)) {
45
+ return provide(APPLICATION_MODULE, { useToken: FunctionModule, defaultArgument: fnOrModule, multi: true });
20
46
  }
47
+ throw new TypeError('Invalid module or function provided to provideModule');
21
48
  });
22
49
  }
23
50
  export function provideInitializer(fn) {
package/audit/module.d.ts CHANGED
@@ -9,6 +9,11 @@ export declare class AuditModuleConfig {
9
9
  * If not provided, the global database configuration is used.
10
10
  */
11
11
  database?: DatabaseConfig;
12
+ /**
13
+ * Whether to automatically run database migrations when the application starts.
14
+ * Defaults to `true`.
15
+ */
16
+ autoMigrate?: boolean;
12
17
  }
13
18
  /**
14
19
  * Configures audit server services.
package/audit/module.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { inject, Injector } from '../injector/index.js';
2
- import { Database, migrate } from '../orm/server/index.js';
2
+ import { Database, migrate, registerDatabaseMigration } from '../orm/server/index.js';
3
3
  /**
4
4
  * Configuration for {@link configureAudit}.
5
5
  */
@@ -9,6 +9,11 @@ export class AuditModuleConfig {
9
9
  * If not provided, the global database configuration is used.
10
10
  */
11
11
  database;
12
+ /**
13
+ * Whether to automatically run database migrations when the application starts.
14
+ * Defaults to `true`.
15
+ */
16
+ autoMigrate;
12
17
  }
13
18
  /**
14
19
  * Configures audit server services.
@@ -17,6 +22,9 @@ export class AuditModuleConfig {
17
22
  export function configureAudit({ injector, ...config } = {}) {
18
23
  const targetInjector = injector ?? Injector;
19
24
  targetInjector.register(AuditModuleConfig, { useValue: config });
25
+ if (config.autoMigrate != false) {
26
+ registerDatabaseMigration('Audit', migrateAuditSchema, { injector });
27
+ }
20
28
  }
21
29
  /**
22
30
  * Migrates the audit database schema to the latest version.
@@ -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 forceRefreshRequested$;
30
31
  private clockOffset;
31
32
  private initialized;
32
33
  private refreshLoopPromise;
@@ -70,6 +70,7 @@ let AuthenticationClientService = class AuthenticationClientService {
70
70
  logger = inject(Logger, 'AuthenticationService');
71
71
  disposeSignal = inject(CancellationSignal).fork();
72
72
  forceRefreshRequested = signal(false);
73
+ forceRefreshRequested$ = toObservable(this.forceRefreshRequested);
73
74
  clockOffset = 0;
74
75
  initialized = false;
75
76
  refreshLoopPromise;
@@ -435,7 +436,7 @@ let AuthenticationClientService = class AuthenticationClientService {
435
436
  this.token$.pipe(filter((t) => t != currentToken)),
436
437
  ];
437
438
  if (!forceRefresh) {
438
- wakeUpSignals.push(toObservable(this.forceRefreshRequested).pipe(filter((v) => v)));
439
+ wakeUpSignals.push(this.forceRefreshRequested$.pipe(filter((requested) => requested)));
439
440
  }
440
441
  if (delay > 0) {
441
442
  await firstValueFrom(race([timer(delay), ...wakeUpSignals]), { defaultValue: undefined });
@@ -451,7 +452,7 @@ let AuthenticationClientService = class AuthenticationClientService {
451
452
  timer(2500),
452
453
  from(this.disposeSignal),
453
454
  this.token$.pipe(filter((t) => t != currentToken)),
454
- toObservable(this.forceRefreshRequested).pipe(filter((requested) => requested), skip(this.forceRefreshRequested() ? 1 : 0)),
455
+ this.forceRefreshRequested$.pipe(filter((requested) => requested), skip(this.forceRefreshRequested() ? 1 : 0)),
455
456
  ]), { defaultValue: undefined });
456
457
  }
457
458
  }
@@ -25,6 +25,11 @@ export declare class AuthenticationModuleConfig {
25
25
  * Override default {@link AuthenticationAncillaryService}.
26
26
  */
27
27
  authenticationAncillaryService?: InjectionToken<AuthenticationAncillaryService<any, any, any>>;
28
+ /**
29
+ * Whether to automatically run database migrations when the application starts.
30
+ * Defaults to `true`.
31
+ */
32
+ autoMigrate?: boolean;
28
33
  }
29
34
  /**
30
35
  * Configures authentication server services.
@@ -2,7 +2,7 @@ import { ApiRequestTokenProvider } from '../../api/server/api-request-token.prov
2
2
  import { inject } from '../../injector/index.js';
3
3
  import { Injector } from '../../injector/injector.js';
4
4
  import { isProvider } from '../../injector/provider.js';
5
- import { Database, migrate } from '../../orm/server/index.js';
5
+ import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
6
6
  import { isDefined } from '../../utils/type-guards.js';
7
7
  import { AuthenticationAncillaryService } from './authentication-ancillary.service.js';
8
8
  import { AuthenticationApiRequestTokenProvider } from './authentication-api-request-token.provider.js';
@@ -28,6 +28,11 @@ export class AuthenticationModuleConfig {
28
28
  * Override default {@link AuthenticationAncillaryService}.
29
29
  */
30
30
  authenticationAncillaryService;
31
+ /**
32
+ * Whether to automatically run database migrations when the application starts.
33
+ * Defaults to `true`.
34
+ */
35
+ autoMigrate;
31
36
  }
32
37
  /**
33
38
  * Configures authentication server services.
@@ -44,6 +49,9 @@ export function configureAuthenticationServer({ injector, ...config }) {
44
49
  if (isDefined(config.authenticationAncillaryService)) {
45
50
  targetInjector.registerSingleton(AuthenticationAncillaryService, { useToken: config.authenticationAncillaryService });
46
51
  }
52
+ if (config.autoMigrate != false) {
53
+ registerDatabaseMigration('Authentication', migrateAuthenticationSchema, { injector });
54
+ }
47
55
  }
48
56
  /**
49
57
  * Migrates the authentication schema.
@@ -25,7 +25,7 @@ describe('AuthenticationApiController Integration', () => {
25
25
  clear: vi.fn(() => storage.clear()),
26
26
  };
27
27
  ({ injector, database } = await setupIntegrationTest({
28
- modules: { authentication: true, audit: true, keyValueStore: true, test: true, api: true, webServer: true },
28
+ modules: { authentication: true, audit: true, keyValueStore: true, signals: true, api: true, webServer: true },
29
29
  authenticationAncillaryService: DefaultAuthenticationAncillaryService,
30
30
  }));
31
31
  await runInInjectionContext(injector, async () => {
@@ -11,7 +11,7 @@ describe('AuthenticationApiRequestTokenProvider', () => {
11
11
  let tokenProvider;
12
12
  const tenantId = crypto.randomUUID();
13
13
  beforeAll(async () => {
14
- ({ injector } = await setupIntegrationTest({ modules: { authentication: true, test: true } }));
14
+ ({ injector } = await setupIntegrationTest({ modules: { authentication: true, signals: true } }));
15
15
  authenticationService = await injector.resolveAsync(AuthenticationService);
16
16
  subjectService = await injector.resolveAsync(SubjectService);
17
17
  tokenProvider = injector.resolve(AuthenticationApiRequestTokenProvider);
@@ -25,7 +25,7 @@ describe('AuthenticationClientService Integration', () => {
25
25
  clear: vi.fn(() => storage.clear()),
26
26
  };
27
27
  ({ injector, database } = await setupIntegrationTest({
28
- modules: { authentication: true, audit: true, keyValueStore: true, test: true, api: true, webServer: true },
28
+ modules: { authentication: true, audit: true, keyValueStore: true, signals: true, api: true, webServer: true },
29
29
  authenticationAncillaryService: DefaultAuthenticationAncillaryService,
30
30
  }));
31
31
  await runInInjectionContext(injector, async () => {
@@ -6,15 +6,15 @@ export declare const CircuitBreakerState: {
6
6
  readonly HalfOpen: "half-open";
7
7
  };
8
8
  export type CircuitBreakerState = EnumType<typeof CircuitBreakerState>;
9
- export interface CircuitBreakerConfig {
9
+ export type CircuitBreakerConfig = {
10
10
  threshold: number;
11
11
  resetTimeout: number;
12
- }
13
- export interface CircuitBreakerCheckResult {
12
+ };
13
+ export type CircuitBreakerCheckResult = {
14
14
  allowed: boolean;
15
15
  state: CircuitBreakerState;
16
16
  isProbe?: boolean;
17
- }
17
+ };
18
18
  export type CircuitBreakerArgument = string | (CircuitBreakerConfig & {
19
19
  key: string;
20
20
  });
@@ -29,4 +29,6 @@ export declare abstract class CircuitBreaker implements Resolvable<CircuitBreake
29
29
  abstract recordSuccess(): Promise<void>;
30
30
  /** Records a failure, potentially tripping the breaker to Open. */
31
31
  abstract recordFailure(): Promise<void>;
32
+ /** Records multiple failures at once. */
33
+ abstract recordFailures(count: number): Promise<void>;
32
34
  }
@@ -4,4 +4,5 @@ export declare class PostgresCircuitBreakerService extends CircuitBreaker {
4
4
  check(): Promise<CircuitBreakerCheckResult>;
5
5
  recordSuccess(): Promise<void>;
6
6
  recordFailure(): Promise<void>;
7
+ recordFailures(count: number): Promise<void>;
7
8
  }
@@ -43,8 +43,11 @@ let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends
43
43
  await this.#repository.tryDeleteByQuery({ key: this.#key });
44
44
  }
45
45
  async recordFailure() {
46
+ await this.recordFailures(1);
47
+ }
48
+ async recordFailures(count) {
46
49
  const table = this.#repository.table;
47
- const initialTrip = 1 >= this.#threshold;
50
+ const initialTrip = count >= this.#threshold;
48
51
  const initialState = initialTrip ? CircuitBreakerState.Open : CircuitBreakerState.Closed;
49
52
  const initialResetTimestamp = initialTrip
50
53
  ? sql `${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}`
@@ -52,16 +55,16 @@ let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends
52
55
  await this.#repository.upsert(['key'], {
53
56
  key: this.#key,
54
57
  state: initialState,
55
- failureCount: 1,
58
+ failureCount: count,
56
59
  resetTimestamp: initialResetTimestamp,
57
60
  }, {
58
- failureCount: sql `${table.failureCount} + 1`,
61
+ failureCount: sql `${table.failureCount} + ${count}`,
59
62
  state: sql `CASE
60
- WHEN ${table.failureCount} + 1 >= ${this.#threshold} THEN ${CircuitBreakerState.Open}
63
+ WHEN ${table.failureCount} + ${count} >= ${this.#threshold} THEN ${CircuitBreakerState.Open}
61
64
  ELSE ${table.state}
62
65
  END`,
63
66
  resetTimestamp: sql `CASE
64
- WHEN ${table.failureCount} + 1 >= ${this.#threshold} THEN ${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}
67
+ WHEN ${table.failureCount} + ${count} >= ${this.#threshold} THEN ${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}
65
68
  ELSE ${table.resetTimestamp}
66
69
  END`,
67
70
  });
@@ -2,6 +2,7 @@ import { Injector } from '../../injector/index.js';
2
2
  import { type DatabaseConfig } from '../../orm/server/index.js';
3
3
  export declare class PostgresCircuitBreakerModuleConfig {
4
4
  database?: DatabaseConfig;
5
+ autoMigrate?: boolean;
5
6
  }
6
7
  /**
7
8
  * configure circuit breaker module
@@ -1,11 +1,12 @@
1
1
  import { inject, Injector } from '../../injector/index.js';
2
- import { Database, migrate } from '../../orm/server/index.js';
2
+ import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
3
3
  import { CircuitBreaker } from '../circuit-breaker.js';
4
4
  import { CircuitBreakerProvider } from '../provider.js';
5
5
  import { PostgresCircuitBreakerService } from './circuit-breaker.js';
6
6
  import { PostgresCircuitBreakerProvider } from './provider.js';
7
7
  export class PostgresCircuitBreakerModuleConfig {
8
8
  database;
9
+ autoMigrate;
9
10
  }
10
11
  /**
11
12
  * configure circuit breaker module
@@ -15,6 +16,9 @@ export function configurePostgresCircuitBreaker({ injector, ...config } = {}) {
15
16
  targetInjector.register(PostgresCircuitBreakerModuleConfig, { useValue: config });
16
17
  targetInjector.registerSingleton(CircuitBreakerProvider, { useToken: PostgresCircuitBreakerProvider });
17
18
  targetInjector.registerSingleton(CircuitBreaker, { useToken: PostgresCircuitBreakerService });
19
+ if (config.autoMigrate != false) {
20
+ registerDatabaseMigration('PostgresCircuitBreaker', migratePostgresCircuitBreaker, { injector });
21
+ }
18
22
  }
19
23
  export async function migratePostgresCircuitBreaker() {
20
24
  const connection = inject(PostgresCircuitBreakerModuleConfig, undefined, { optional: true })?.database?.connection;
@@ -90,4 +90,24 @@ describe('Circuit Breaker (Standalone) Tests', () => {
90
90
  expect(result.allowed).toBe(false);
91
91
  expect(result.state).toBe(CircuitBreakerState.Open);
92
92
  });
93
+ it('should trip to Open after threshold failures via recordFailures', async () => {
94
+ const breaker = provider.provide(`cb-bulk-trip-${Date.now()}`, { threshold: 5, resetTimeout: 1000 });
95
+ // 1. Bulk Failure (Threshold Not Reached)
96
+ await breaker.recordFailures(3);
97
+ let result = await breaker.check();
98
+ expect(result.state).toBe(CircuitBreakerState.Closed);
99
+ // 2. Bulk Failure (Threshold Reached)
100
+ await breaker.recordFailures(2);
101
+ result = await breaker.check();
102
+ expect(result.allowed).toBe(false);
103
+ expect(result.state).toBe(CircuitBreakerState.Open);
104
+ });
105
+ it('should trip to Open after threshold failures via single large recordFailures', async () => {
106
+ const breaker = provider.provide(`cb-single-bulk-trip-${Date.now()}`, { threshold: 5, resetTimeout: 1000 });
107
+ // Single large bulk failure exceeding threshold
108
+ await breaker.recordFailures(10);
109
+ const result = await breaker.check();
110
+ expect(result.allowed).toBe(false);
111
+ expect(result.state).toBe(CircuitBreakerState.Open);
112
+ });
93
113
  });
@@ -1,7 +1,8 @@
1
1
  import { Injector } from '../../injector/injector.js';
2
+ import { registerDatabaseMigration } from '../../orm/server/index.js';
2
3
  import { isDefined } from '../../utils/type-guards.js';
3
4
  import { DocumentManagementAuthorizationService } from '../authorization/document-management-authorization.service.js';
4
- import { DocumentManagementConfiguration } from './module.js';
5
+ import { DocumentManagementConfiguration, migrateDocumentManagementSchema } from './module.js';
5
6
  import { DocumentManagementAiProviderService, DocumentManagementAncillaryService } from './services/index.js';
6
7
  export function configureDocumentManagement(configuration) {
7
8
  const targetInjector = configuration.injector ?? Injector;
@@ -11,4 +12,7 @@ export function configureDocumentManagement(configuration) {
11
12
  if (isDefined(configuration.aiProvider)) {
12
13
  targetInjector.register(DocumentManagementAiProviderService, { useToken: configuration.aiProvider });
13
14
  }
15
+ if (configuration.autoMigrate != false) {
16
+ registerDatabaseMigration('DocumentManagement', migrateDocumentManagementSchema, { injector: configuration.injector });
17
+ }
14
18
  }
@@ -1,4 +1,3 @@
1
- import './schemas.js';
2
1
  import { type InjectionToken } from '../../injector/index.js';
3
2
  import { type DatabaseConfig } from '../../orm/server/index.js';
4
3
  import type { DocumentManagementAuthorizationService } from '../authorization/document-management-authorization.service.js';
@@ -14,5 +13,6 @@ export declare class DocumentManagementConfiguration {
14
13
  database?: DatabaseConfig;
15
14
  maxFileSize?: number;
16
15
  skipAi?: boolean;
16
+ autoMigrate?: boolean;
17
17
  }
18
18
  export declare function migrateDocumentManagementSchema(): Promise<void>;
@@ -1,4 +1,3 @@
1
- import './schemas.js';
2
1
  import { inject } from '../../injector/index.js';
3
2
  import { Database, migrate } from '../../orm/server/index.js';
4
3
  export class DocumentManagementConfiguration {
@@ -11,6 +10,7 @@ export class DocumentManagementConfiguration {
11
10
  database;
12
11
  maxFileSize;
13
12
  skipAi;
13
+ autoMigrate;
14
14
  }
15
15
  ;
16
16
  export async function migrateDocumentManagementSchema() {
@@ -6,6 +6,6 @@ export class DocumentManagementAncillaryService {
6
6
  * @returns The resolved file name for the document, without the file extension.
7
7
  */
8
8
  getDocumentFilename(document) {
9
- return document.title ?? document.originalFileName ?? document.id;
9
+ return document.title ?? document.originalFileName?.slice(0, document.originalFileName.lastIndexOf('.')) ?? document.id;
10
10
  }
11
11
  }
@@ -8,7 +8,6 @@ import { TestDocumentManagementAncillaryService } from './helper.js';
8
8
  describe('DocumentManagementAiService Hierarchy', () => {
9
9
  let injector;
10
10
  let aiService;
11
- let flashModel;
12
11
  const tenantId = crypto.randomUUID();
13
12
  const mockAiProvider = {
14
13
  getGlobalConfiguration: vi.fn(),
@@ -20,12 +19,8 @@ describe('DocumentManagementAiService Hierarchy', () => {
20
19
  };
21
20
  beforeAll(async () => {
22
21
  ({ injector } = await setupIntegrationTest({
23
- orm: { schema: 'document_management' },
24
22
  modules: { messageBus: true, signals: true, objectStorage: true, documentManagement: true },
25
23
  }));
26
- runInInjectionContext(injector, () => {
27
- flashModel = injectModel('gemini-2.5-flash');
28
- });
29
24
  injector.register(DocumentManagementAiProviderService, { useValue: mockAiProvider });
30
25
  // Ancillary and configuration are already registered by setupIntegrationTest, but we can override if needed
31
26
  injector.register(DocumentManagementAncillaryService, { useToken: TestDocumentManagementAncillaryService });
@@ -22,7 +22,6 @@ describe('DocumentManagementAiService Overrides', () => {
22
22
  };
23
23
  beforeAll(async () => {
24
24
  ({ injector } = await setupIntegrationTest({
25
- orm: { schema: 'document_management' },
26
25
  modules: { messageBus: true, signals: true, objectStorage: true },
27
26
  }));
28
27
  runInInjectionContext(injector, () => {
@@ -32,7 +32,6 @@ describe('AiValidationExecutor Overrides', () => {
32
32
  };
33
33
  beforeAll(async () => {
34
34
  ({ injector } = await setupIntegrationTest({
35
- orm: { schema: 'document_management' },
36
35
  modules: { messageBus: true, signals: true, objectStorage: true },
37
36
  }));
38
37
  runInInjectionContext(injector, () => {
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  import '../../polyfills.js';
2
3
  import { DocumentManagementAuthorizationService, type DocumentCollection, type DocumentCollectionMetadata } from '../../document-management/index.js';
3
4
  import { DocumentManagementAncillaryService } from '../../document-management/server/index.js';
@@ -1,3 +1,4 @@
1
+ /** biome-ignore-all lint/nursery/noExcessiveClassesPerFile: <explanation> */
1
2
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -10,22 +11,23 @@ import { MockApiRequestTokenProvider } from '../../api/server/api-request-token.
10
11
  import { configureApiServer } from '../../api/server/module.js';
11
12
  import { Application } from '../../application/application.js';
12
13
  import { provideInitializer, provideModule, provideSignalHandler } from '../../application/index.js';
13
- import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../../circuit-breaker/postgres/module.js';
14
+ import { configureAudit } from '../../audit/module.js';
15
+ import { configurePostgresCircuitBreaker } from '../../circuit-breaker/postgres/module.js';
14
16
  import { DocumentManagementAuthorizationService } from '../../document-management/index.js';
15
17
  import { configureDocumentManagement } from '../../document-management/server/configure.js';
16
18
  import { DocumentCategoryTypeService, DocumentCollectionService, DocumentManagementAncillaryService, DocumentManagementApiController, DocumentRequestService } from '../../document-management/server/index.js';
17
- import { migrateDocumentManagementSchema } from '../../document-management/server/module.js';
18
19
  import { DocumentManagementService } from '../../document-management/server/services/document-management.service.js';
19
20
  import { configureNodeHttpServer } from '../../http/server/node/module.js';
20
- import { Injector, Singleton } from '../../injector/index.js';
21
- import { inject, injectManyAsync, runInInjectionContext } from '../../injector/inject.js';
21
+ import { Singleton } from '../../injector/index.js';
22
+ import { injectManyAsync } from '../../injector/inject.js';
22
23
  import { PrettyPrintLogFormatter, provideConsoleLogTransport } from '../../logger/index.js';
23
24
  import { configureLocalMessageBus } from '../../message-bus/index.js';
24
25
  import { WebServerModule } from '../../module/index.js';
25
26
  import { configureS3ObjectStorage } from '../../object-storage/s3/index.js';
26
- import { configureOrm } from '../../orm/server/index.js';
27
+ import { configureOrm, provideOrm } from '../../orm/server/index.js';
28
+ import { configurePostgresRateLimiter } from '../../rate-limit/postgres/module.js';
27
29
  import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
28
- import { configurePostgresTaskQueue, migratePostgresTaskQueueSchema } from '../../task-queue/postgres/index.js';
30
+ import { configurePostgresTaskQueue } from '../../task-queue/postgres/index.js';
29
31
  import { boolean, positiveInteger, string } from '../../utils/config-parser.js';
30
32
  import { ExampleAiProviderService } from './ai-provider.js';
31
33
  import { TstdlCategoryParents, TstdlDocumentCategoryLabels, TstdlDocumentPropertyConfiguration, TstdlDocumentTypeCategories, TstdlDocumentTypeLabels, TstdlDocumentTypeProperties } from './categories-and-types.js';
@@ -84,11 +86,13 @@ AllowAllDocumentManagementAuthorizationService = __decorate([
84
86
  Singleton()
85
87
  ], AllowAllDocumentManagementAuthorizationService);
86
88
  export { AllowAllDocumentManagementAuthorizationService };
87
- async function bootstrap() {
88
- const injector = inject(Injector);
89
+ function bootstrap() {
90
+ configureAudit();
89
91
  configureNodeHttpServer();
90
92
  configurePostgresTaskQueue();
91
93
  configurePostgresCircuitBreaker();
94
+ configurePostgresTaskQueue();
95
+ configurePostgresRateLimiter();
92
96
  configureLocalMessageBus();
93
97
  configureDefaultSignalsImplementation();
94
98
  configureGenkit({
@@ -119,6 +123,7 @@ async function bootstrap() {
119
123
  bucketPerModule: config.s3.bucketPerModule,
120
124
  accessKey: config.s3.accessKey,
121
125
  secretKey: config.s3.secretKey,
126
+ forcePathStyle: true,
122
127
  });
123
128
  configureApiServer({
124
129
  controllers: [DocumentManagementApiController],
@@ -139,9 +144,6 @@ async function bootstrap() {
139
144
  location: config.ai.vertex.location,
140
145
  },
141
146
  });
142
- await runInInjectionContext(injector, migrateDocumentManagementSchema);
143
- await runInInjectionContext(injector, migratePostgresCircuitBreaker);
144
- await runInInjectionContext(injector, migratePostgresTaskQueueSchema);
145
147
  }
146
148
  async function main() {
147
149
  const tenantId = '00000000-0000-0000-0000-000000000000';
@@ -171,4 +173,5 @@ Application.run('DocumentManagementTest', [
171
173
  provideModule(main, WebServerModule),
172
174
  provideSignalHandler(),
173
175
  provideConsoleLogTransport(PrettyPrintLogFormatter),
176
+ provideOrm(),
174
177
  ]);
@@ -2,6 +2,7 @@ import { Injector } from '../../injector/index.js';
2
2
  import { type DatabaseConfig } from '../../orm/server/index.js';
3
3
  export declare class PostgresKeyValueStoreModuleConfig {
4
4
  database?: DatabaseConfig;
5
+ autoMigrate?: boolean;
5
6
  }
6
7
  /**
7
8
  * configure key-value store module
@@ -1,10 +1,11 @@
1
1
  import { inject, Injector } from '../../injector/index.js';
2
- import { Database, migrate } from '../../orm/server/index.js';
2
+ import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
3
3
  import { isDefined } from '../../utils/type-guards.js';
4
4
  import { KeyValueStore } from '../key-value.store.js';
5
5
  import { PostgresKeyValueStore } from './key-value-store.service.js';
6
6
  export class PostgresKeyValueStoreModuleConfig {
7
7
  database;
8
+ autoMigrate;
8
9
  }
9
10
  /**
10
11
  * configure key-value store module
@@ -15,6 +16,9 @@ export function configurePostgresKeyValueStore({ injector, ...config } = {}) {
15
16
  targetInjector.register(PostgresKeyValueStoreModuleConfig, { useValue: config });
16
17
  }
17
18
  targetInjector.registerSingleton(KeyValueStore, { useToken: PostgresKeyValueStore });
19
+ if (config.autoMigrate != false) {
20
+ registerDatabaseMigration('PostgresKeyValueStore', migratePostgresKeyValueStoreSchema, { injector });
21
+ }
18
22
  }
19
23
  export async function migratePostgresKeyValueStoreSchema() {
20
24
  const connection = inject(PostgresKeyValueStoreModuleConfig, undefined, { optional: true })?.database?.connection;
@@ -2,6 +2,7 @@ import { Injector } from '../../injector/index.js';
2
2
  import { type DatabaseConfig } from '../../orm/server/index.js';
3
3
  export declare class PostgresLockModuleConfig {
4
4
  database?: DatabaseConfig;
5
+ autoMigrate?: boolean;
5
6
  }
6
7
  /**
7
8
  * configure lock module
@@ -1,5 +1,5 @@
1
1
  import { inject, Injector } from '../../injector/index.js';
2
- import { Database, migrate } from '../../orm/server/index.js';
2
+ import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
3
3
  import { isDefined } from '../../utils/type-guards.js';
4
4
  import { Lock } from '../lock.js';
5
5
  import { LockProvider } from '../provider.js';
@@ -7,6 +7,7 @@ import { PostgresLockAdapter } from './lock.js';
7
7
  import { PostgresLockProvider } from './provider.js';
8
8
  export class PostgresLockModuleConfig {
9
9
  database;
10
+ autoMigrate;
10
11
  }
11
12
  /**
12
13
  * configure lock module
@@ -18,6 +19,9 @@ export function configurePostgresLock({ injector, ...config } = {}) {
18
19
  }
19
20
  targetInjector.registerSingleton(LockProvider, { useToken: PostgresLockProvider });
20
21
  targetInjector.registerSingleton(Lock, { useToken: PostgresLockAdapter });
22
+ if (config.autoMigrate != false) {
23
+ registerDatabaseMigration('PostgresLock', migratePostgresLockSchema, { injector });
24
+ }
21
25
  }
22
26
  export async function migratePostgresLockSchema() {
23
27
  const connection = inject(PostgresLockModuleConfig, undefined, { optional: true })?.database?.connection;