@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.
- package/application/application.d.ts +1 -1
- package/application/application.js +1 -1
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/client/authentication.service.d.ts +1 -0
- package/authentication/client/authentication.service.js +3 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/circuit-breaker/circuit-breaker.d.ts +6 -4
- package/circuit-breaker/postgres/circuit-breaker.d.ts +1 -0
- package/circuit-breaker/postgres/circuit-breaker.js +8 -5
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/circuit-breaker/tests/circuit-breaker.test.js +20 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/module/modules/web-server.module.js +2 -3
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-api.test.js +5 -1
- package/notification/tests/notification-flow.test.js +8 -5
- package/orm/decorators.d.ts +22 -5
- package/orm/decorators.js +10 -1
- package/orm/server/bootstrap.d.ts +11 -0
- package/orm/server/bootstrap.js +31 -0
- package/orm/server/drizzle/schema-converter.d.ts +3 -1
- package/orm/server/drizzle/schema-converter.js +85 -56
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/extension.d.ts +14 -0
- package/orm/server/extension.js +27 -0
- package/orm/server/index.d.ts +3 -6
- package/orm/server/index.js +3 -6
- package/orm/server/migration.d.ts +18 -0
- package/orm/server/migration.js +58 -0
- package/orm/server/repository.d.ts +2 -1
- package/orm/server/repository.js +19 -9
- package/orm/server/transaction.d.ts +6 -10
- package/orm/server/transaction.js +25 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-extension.test.js +63 -0
- package/orm/tests/database-migration.test.js +83 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/tests/repository-compound-primary-key.test.d.ts +2 -0
- package/orm/tests/repository-compound-primary-key.test.js +234 -0
- package/orm/tests/schema-generation.test.d.ts +1 -0
- package/orm/tests/schema-generation.test.js +52 -5
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +5 -4
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/decorator-data.js +11 -12
- package/task-queue/README.md +2 -10
- package/task-queue/postgres/drizzle/0000_great_gwen_stacy.sql +84 -0
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +250 -89
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +6 -1
- package/task-queue/postgres/schemas.d.ts +15 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +18 -15
- package/task-queue/postgres/task-queue.js +797 -499
- package/task-queue/postgres/task.model.d.ts +20 -9
- package/task-queue/postgres/task.model.js +65 -39
- package/task-queue/task-context.d.ts +12 -7
- package/task-queue/task-context.js +8 -6
- package/task-queue/task-queue.d.ts +364 -43
- package/task-queue/task-queue.js +153 -41
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +395 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +150 -0
- package/task-queue/tests/dag.test.d.ts +1 -0
- package/task-queue/tests/dag.test.js +188 -0
- package/task-queue/tests/dependencies.test.js +165 -47
- package/task-queue/tests/enqueue-batch.test.d.ts +1 -0
- package/task-queue/tests/enqueue-batch.test.js +125 -0
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +94 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +114 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +39 -0
- package/task-queue/tests/queue.test.js +294 -49
- package/task-queue/tests/shutdown.test.d.ts +1 -0
- package/task-queue/tests/shutdown.test.js +41 -0
- package/task-queue/tests/transactions.test.d.ts +1 -0
- package/task-queue/tests/transactions.test.js +47 -0
- package/task-queue/tests/worker.test.js +63 -15
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/test5.js +5 -5
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +56 -29
- package/text/localization.service.js +2 -2
- package/utils/file-reader.js +1 -2
- package/utils/timing.d.ts +2 -2
- package/task-queue/postgres/drizzle/0000_simple_invisible_woman.sql +0 -74
- package/task-queue/tests/complex.test.js +0 -306
- package/task-queue/tests/extensive-dependencies.test.js +0 -234
- /package/{task-queue/tests/complex.test.d.ts → orm/tests/database-extension.test.d.ts} +0 -0
- /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
|
|
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
|
-
|
|
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;
|
package/application/providers.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
19
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
9
|
+
export type CircuitBreakerConfig = {
|
|
10
10
|
threshold: number;
|
|
11
11
|
resetTimeout: number;
|
|
12
|
-
}
|
|
13
|
-
export
|
|
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
|
}
|
|
@@ -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 =
|
|
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:
|
|
58
|
+
failureCount: count,
|
|
56
59
|
resetTimestamp: initialResetTimestamp,
|
|
57
60
|
}, {
|
|
58
|
-
failureCount: sql `${table.failureCount} +
|
|
61
|
+
failureCount: sql `${table.failureCount} + ${count}`,
|
|
59
62
|
state: sql `CASE
|
|
60
|
-
WHEN ${table.failureCount} +
|
|
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} +
|
|
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 {
|
|
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 {
|
|
21
|
-
import {
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
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;
|
package/lock/postgres/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
|
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;
|