@tstdl/base 0.93.76 → 0.93.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/prompts/instructions-formatter.d.ts +68 -5
- package/ai/prompts/instructions-formatter.js +11 -3
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/models/authentication-credentials.model.d.ts +2 -2
- package/authentication/models/authentication-credentials.model.js +5 -3
- package/authentication/models/authentication-session.model.d.ts +2 -2
- package/authentication/models/authentication-session.model.js +5 -3
- package/authentication/models/index.d.ts +4 -0
- package/authentication/models/index.js +4 -0
- package/authentication/models/service-account.model.d.ts +7 -0
- package/authentication/models/service-account.model.js +31 -0
- package/authentication/models/subject.model.d.ts +16 -0
- package/authentication/models/subject.model.js +59 -0
- package/authentication/models/system-account.model.d.ts +5 -0
- package/authentication/models/system-account.model.js +25 -0
- package/authentication/models/user.model.d.ts +15 -0
- package/authentication/models/user.model.js +47 -0
- package/authentication/server/drizzle/0001_condemned_pretty_boy.sql +70 -0
- package/authentication/server/drizzle/meta/0001_snapshot.json +651 -0
- package/authentication/server/drizzle/meta/_journal.json +7 -0
- package/authentication/server/index.d.ts +1 -0
- package/authentication/server/index.js +1 -0
- package/authentication/server/schemas.d.ts +16 -1
- package/authentication/server/schemas.js +7 -1
- package/authentication/server/subject.service.d.ts +6 -0
- package/authentication/server/subject.service.js +44 -0
- package/circuit-breaker/circuit-breaker.d.ts +32 -0
- package/circuit-breaker/circuit-breaker.js +9 -0
- package/circuit-breaker/index.d.ts +2 -0
- package/circuit-breaker/index.js +2 -0
- package/circuit-breaker/postgres/circuit-breaker.d.ts +7 -0
- package/circuit-breaker/postgres/circuit-breaker.js +78 -0
- package/circuit-breaker/postgres/drizzle/0000_hard_shocker.sql +9 -0
- package/circuit-breaker/postgres/drizzle/meta/0000_snapshot.json +82 -0
- package/circuit-breaker/postgres/drizzle/meta/_journal.json +13 -0
- package/circuit-breaker/postgres/drizzle.config.d.ts +2 -0
- package/circuit-breaker/postgres/drizzle.config.js +11 -0
- package/circuit-breaker/postgres/index.d.ts +5 -0
- package/circuit-breaker/postgres/index.js +5 -0
- package/circuit-breaker/postgres/model.d.ts +9 -0
- package/circuit-breaker/postgres/model.js +40 -0
- package/circuit-breaker/postgres/module.d.ts +6 -0
- package/circuit-breaker/postgres/module.js +25 -0
- package/circuit-breaker/postgres/provider.d.ts +6 -0
- package/circuit-breaker/postgres/provider.js +21 -0
- package/circuit-breaker/postgres/schemas.d.ts +8 -0
- package/circuit-breaker/postgres/schemas.js +6 -0
- package/circuit-breaker/provider.d.ts +4 -0
- package/circuit-breaker/provider.js +2 -0
- package/circuit-breaker/tests/circuit-breaker.test.js +113 -0
- package/document-management/models/document.model.d.ts +0 -1
- package/document-management/models/document.model.js +0 -5
- package/document-management/server/api/document-management.api.js +1 -2
- package/document-management/server/drizzle/0002_round_warbird.sql +1 -0
- package/document-management/server/drizzle/meta/0002_snapshot.json +2722 -0
- package/document-management/server/drizzle/meta/_journal.json +7 -0
- package/document-management/server/services/document-collection.service.js +3 -3
- package/document-management/server/services/document-management-ancillary.service.d.ts +1 -1
- package/document-management/server/services/document-management.service.js +1 -1
- package/document-management/server/services/document-workflow.service.js +5 -5
- package/document-management/server/services/document.service.d.ts +0 -2
- package/document-management/server/services/document.service.js +1 -2
- package/document-management/service-models/enriched/enriched-document.view.d.ts +1 -1
- package/examples/document-management/main.d.ts +1 -1
- package/examples/document-management/main.js +1 -1
- package/logger/transports/console.d.ts +1 -1
- package/logger/transports/console.js +4 -1
- package/message-bus/message-bus-base.js +1 -1
- package/package.json +8 -5
- package/queue/enqueue-batch.d.ts +11 -11
- package/queue/enqueue-batch.js +2 -3
- package/queue/index.d.ts +1 -0
- package/queue/index.js +1 -0
- package/queue/postgres/drizzle/0003_tricky_venom.sql +30 -0
- package/queue/postgres/drizzle/meta/0003_snapshot.json +288 -0
- package/queue/postgres/drizzle/meta/_journal.json +7 -0
- package/queue/postgres/drizzle.config.js +2 -2
- package/queue/postgres/index.d.ts +1 -1
- package/queue/postgres/index.js +1 -1
- package/queue/postgres/module.d.ts +1 -1
- package/queue/postgres/module.js +1 -1
- package/queue/postgres/queue.d.ts +52 -23
- package/queue/postgres/queue.js +582 -64
- package/queue/postgres/queue.provider.d.ts +1 -1
- package/queue/postgres/schemas.d.ts +13 -2
- package/queue/postgres/schemas.js +4 -2
- package/queue/postgres/task.model.d.ts +24 -0
- package/queue/postgres/task.model.js +115 -0
- package/queue/provider.d.ts +1 -1
- package/queue/queue.d.ts +158 -37
- package/queue/queue.js +97 -19
- package/queue/task-context.d.ts +38 -0
- package/queue/task-context.js +102 -0
- package/queue/tests/queue.test.d.ts +1 -0
- package/queue/tests/queue.test.js +623 -0
- package/test4.d.ts +1 -1
- package/test4.js +1 -1
- package/utils/format-error.d.ts +17 -20
- package/utils/format-error.js +105 -47
- package/queue/postgres/job.model.d.ts +0 -12
- package/queue/postgres/job.model.js +0 -53
- package/test6.js +0 -33
- /package/{test6.d.ts → circuit-breaker/tests/circuit-breaker.test.d.ts} +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { databaseSchema } from '../../orm/server/index.js';
|
|
2
|
-
import { AuthenticationCredentials, AuthenticationSession } from '../models/index.js';
|
|
2
|
+
import { AuthenticationCredentials, AuthenticationSession, ServiceAccount, Subject, SubjectType, SystemAccount, User, UserStatus } from '../models/index.js';
|
|
3
3
|
export const authenticationSchema = databaseSchema('authentication');
|
|
4
|
+
export const subjectType = authenticationSchema.getEnum(SubjectType);
|
|
5
|
+
export const userStatus = authenticationSchema.getEnum(UserStatus);
|
|
4
6
|
export const authenticationCredentials = authenticationSchema.getTable(AuthenticationCredentials);
|
|
5
7
|
export const authenticationSession = authenticationSchema.getTable(AuthenticationSession);
|
|
8
|
+
export const serviceAccount = authenticationSchema.getTable(ServiceAccount);
|
|
9
|
+
export const subject = authenticationSchema.getTable(Subject);
|
|
10
|
+
export const systemAccount = authenticationSchema.getTable(SystemAccount);
|
|
11
|
+
export const user = authenticationSchema.getTable(User);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Subject, SystemAccount } from '../models/index.js';
|
|
2
|
+
export declare class SubjectService {
|
|
3
|
+
readonly subjectRepository: import("../../orm/server/index.js").EntityRepository<Subject>;
|
|
4
|
+
readonly systemAccountRepository: import("../../orm/server/index.js").EntityRepository<SystemAccount>;
|
|
5
|
+
getSystemAccountSubject(tenantId: string, identifier: string): Promise<Subject>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Singleton } from '../../injector/index.js';
|
|
8
|
+
import { injectRepository } from '../../orm/server/index.js';
|
|
9
|
+
import { isUndefined } from '../../utils/type-guards.js';
|
|
10
|
+
import { Subject, SubjectType, SystemAccount } from '../models/index.js';
|
|
11
|
+
let SubjectService = class SubjectService {
|
|
12
|
+
subjectRepository = injectRepository(Subject);
|
|
13
|
+
systemAccountRepository = injectRepository(SystemAccount);
|
|
14
|
+
async getSystemAccountSubject(tenantId, identifier) {
|
|
15
|
+
return await this.subjectRepository.transaction(async (tx) => {
|
|
16
|
+
let systemAccount = await this.systemAccountRepository.withTransaction(tx).tryLoadByQuery({
|
|
17
|
+
tenantId,
|
|
18
|
+
identifier,
|
|
19
|
+
});
|
|
20
|
+
if (isUndefined(systemAccount)) {
|
|
21
|
+
systemAccount = await this.systemAccountRepository.withTransaction(tx).insert({
|
|
22
|
+
tenantId,
|
|
23
|
+
identifier,
|
|
24
|
+
});
|
|
25
|
+
return await this.subjectRepository.withTransaction(tx).insert({
|
|
26
|
+
type: SubjectType.System,
|
|
27
|
+
tenantId,
|
|
28
|
+
systemAccountId: systemAccount.id,
|
|
29
|
+
displayName: `System: ${identifier}`,
|
|
30
|
+
userId: null,
|
|
31
|
+
serviceAccountId: null,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return await this.subjectRepository.withTransaction(tx).loadByQuery({
|
|
35
|
+
tenantId,
|
|
36
|
+
systemAccountId: systemAccount.id,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
SubjectService = __decorate([
|
|
42
|
+
Singleton()
|
|
43
|
+
], SubjectService);
|
|
44
|
+
export { SubjectService };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type EnumType } from '../enumeration/enumeration.js';
|
|
2
|
+
import { resolveArgumentType, type Resolvable } from '../injector/interfaces.js';
|
|
3
|
+
export declare const CircuitBreakerState: {
|
|
4
|
+
readonly Closed: "closed";
|
|
5
|
+
readonly Open: "open";
|
|
6
|
+
readonly HalfOpen: "half-open";
|
|
7
|
+
};
|
|
8
|
+
export type CircuitBreakerState = EnumType<typeof CircuitBreakerState>;
|
|
9
|
+
export interface CircuitBreakerConfig {
|
|
10
|
+
threshold: number;
|
|
11
|
+
resetTimeout: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CircuitBreakerCheckResult {
|
|
14
|
+
allowed: boolean;
|
|
15
|
+
state: CircuitBreakerState;
|
|
16
|
+
isProbe?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type CircuitBreakerArgument = string | (CircuitBreakerConfig & {
|
|
19
|
+
key: string;
|
|
20
|
+
});
|
|
21
|
+
export declare abstract class CircuitBreaker implements Resolvable<CircuitBreakerArgument> {
|
|
22
|
+
readonly [resolveArgumentType]: CircuitBreakerArgument;
|
|
23
|
+
/**
|
|
24
|
+
* Checks if execution is allowed.
|
|
25
|
+
* Transitions from Open to Half-Open automatically if timeout passed.
|
|
26
|
+
*/
|
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineEnum } from '../enumeration/enumeration.js';
|
|
2
|
+
import { resolveArgumentType } from '../injector/interfaces.js';
|
|
3
|
+
export const CircuitBreakerState = defineEnum('CircuitBreakerState', {
|
|
4
|
+
Closed: 'closed',
|
|
5
|
+
Open: 'open',
|
|
6
|
+
HalfOpen: 'half-open',
|
|
7
|
+
});
|
|
8
|
+
export class CircuitBreaker {
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CircuitBreaker, type CircuitBreakerCheckResult } from '../circuit-breaker.js';
|
|
2
|
+
export declare class PostgresCircuitBreakerService extends CircuitBreaker {
|
|
3
|
+
#private;
|
|
4
|
+
check(): Promise<CircuitBreakerCheckResult>;
|
|
5
|
+
recordSuccess(): Promise<void>;
|
|
6
|
+
recordFailure(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { sql } from 'drizzle-orm';
|
|
8
|
+
import { injectArgument, provide, Singleton } from '../../injector/index.js';
|
|
9
|
+
import { interval, TRANSACTION_TIMESTAMP } from '../../orm/index.js';
|
|
10
|
+
import { DatabaseConfig, injectRepository } from '../../orm/server/index.js';
|
|
11
|
+
import { currentTimestamp } from '../../utils/date-time.js';
|
|
12
|
+
import { isDefined, isString, isUndefined } from '../../utils/type-guards.js';
|
|
13
|
+
import { millisecondsPerSecond } from '../../utils/units.js';
|
|
14
|
+
import { CircuitBreaker, CircuitBreakerState } from '../circuit-breaker.js';
|
|
15
|
+
import { PostgresCircuitBreaker } from './model.js';
|
|
16
|
+
import { PostgresCircuitBreakerModuleConfig } from './module.js';
|
|
17
|
+
let PostgresCircuitBreakerService = class PostgresCircuitBreakerService extends CircuitBreaker {
|
|
18
|
+
#repository = injectRepository(PostgresCircuitBreaker);
|
|
19
|
+
#arg = injectArgument(this);
|
|
20
|
+
#key = isString(this.#arg) ? this.#arg : this.#arg.key;
|
|
21
|
+
#threshold = (isString(this.#arg) ? undefined : this.#arg.threshold) ?? 5;
|
|
22
|
+
#resetTimeout = (isString(this.#arg) ? undefined : this.#arg.resetTimeout) ?? 30 * millisecondsPerSecond;
|
|
23
|
+
async check() {
|
|
24
|
+
return await this.#repository.transaction(async (tx) => {
|
|
25
|
+
const breaker = await this.#repository.withTransaction(tx).tryLoadByQuery({ key: this.#key });
|
|
26
|
+
if (isUndefined(breaker) || breaker.state === CircuitBreakerState.Closed) {
|
|
27
|
+
return { allowed: true, state: CircuitBreakerState.Closed };
|
|
28
|
+
}
|
|
29
|
+
if (breaker.state === CircuitBreakerState.HalfOpen) {
|
|
30
|
+
return { allowed: true, state: CircuitBreakerState.HalfOpen, isProbe: false };
|
|
31
|
+
}
|
|
32
|
+
// State is Open
|
|
33
|
+
if (currentTimestamp() < (breaker.resetTimestamp ?? 0)) {
|
|
34
|
+
return { allowed: false, state: CircuitBreakerState.Open };
|
|
35
|
+
}
|
|
36
|
+
// Atomic transition from Open -> HalfOpen (The Probe)
|
|
37
|
+
const updated = await this.#repository.withTransaction(tx).tryUpdateByQuery({ key: this.#key, state: CircuitBreakerState.Open }, { state: CircuitBreakerState.HalfOpen });
|
|
38
|
+
const isProbe = isDefined(updated);
|
|
39
|
+
return { allowed: isProbe, state: isProbe ? CircuitBreakerState.HalfOpen : CircuitBreakerState.Open, isProbe };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async recordSuccess() {
|
|
43
|
+
await this.#repository.tryDeleteByQuery({ key: this.#key });
|
|
44
|
+
}
|
|
45
|
+
async recordFailure() {
|
|
46
|
+
const table = this.#repository.table;
|
|
47
|
+
const initialTrip = 1 >= this.#threshold;
|
|
48
|
+
const initialState = initialTrip ? CircuitBreakerState.Open : CircuitBreakerState.Closed;
|
|
49
|
+
const initialResetTimestamp = initialTrip
|
|
50
|
+
? sql `${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}`
|
|
51
|
+
: null;
|
|
52
|
+
await this.#repository.upsert(['key'], {
|
|
53
|
+
key: this.#key,
|
|
54
|
+
state: initialState,
|
|
55
|
+
failureCount: 1,
|
|
56
|
+
resetTimestamp: initialResetTimestamp,
|
|
57
|
+
}, {
|
|
58
|
+
failureCount: sql `${table.failureCount} + 1`,
|
|
59
|
+
state: sql `CASE
|
|
60
|
+
WHEN ${table.failureCount} + 1 >= ${this.#threshold} THEN ${CircuitBreakerState.Open}
|
|
61
|
+
ELSE ${table.state}
|
|
62
|
+
END`,
|
|
63
|
+
resetTimestamp: sql `CASE
|
|
64
|
+
WHEN ${table.failureCount} + 1 >= ${this.#threshold} THEN ${TRANSACTION_TIMESTAMP} + ${interval(this.#resetTimeout, 'milliseconds')}
|
|
65
|
+
ELSE ${table.resetTimestamp}
|
|
66
|
+
END`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
PostgresCircuitBreakerService = __decorate([
|
|
71
|
+
Singleton({
|
|
72
|
+
argumentIdentityProvider: (arg) => isString(arg) ? arg : arg.key,
|
|
73
|
+
providers: [
|
|
74
|
+
provide(DatabaseConfig, { useFactory: (_, context) => context.resolve(PostgresCircuitBreakerModuleConfig).database ?? context.resolve(DatabaseConfig, undefined, { skipSelf: 2 }) }),
|
|
75
|
+
],
|
|
76
|
+
})
|
|
77
|
+
], PostgresCircuitBreakerService);
|
|
78
|
+
export { PostgresCircuitBreakerService };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
CREATE TYPE "circuit_breaker"."circuit_breaker_state" AS ENUM('closed', 'open', 'half-open');--> statement-breakpoint
|
|
2
|
+
CREATE TABLE "circuit_breaker"."circuit_breaker" (
|
|
3
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
4
|
+
"key" text NOT NULL,
|
|
5
|
+
"state" "circuit_breaker"."circuit_breaker_state" NOT NULL,
|
|
6
|
+
"failure_count" integer NOT NULL,
|
|
7
|
+
"reset_timestamp" timestamp with time zone,
|
|
8
|
+
CONSTRAINT "circuit_breaker_key_unique" UNIQUE("key")
|
|
9
|
+
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cc140886-299f-4337-b208-50a976c703f3",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"circuit_breaker.circuit_breaker": {
|
|
8
|
+
"name": "circuit_breaker",
|
|
9
|
+
"schema": "circuit_breaker",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "uuid",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true,
|
|
16
|
+
"default": "gen_random_uuid()"
|
|
17
|
+
},
|
|
18
|
+
"key": {
|
|
19
|
+
"name": "key",
|
|
20
|
+
"type": "text",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": true
|
|
23
|
+
},
|
|
24
|
+
"state": {
|
|
25
|
+
"name": "state",
|
|
26
|
+
"type": "circuit_breaker_state",
|
|
27
|
+
"typeSchema": "circuit_breaker",
|
|
28
|
+
"primaryKey": false,
|
|
29
|
+
"notNull": true
|
|
30
|
+
},
|
|
31
|
+
"failure_count": {
|
|
32
|
+
"name": "failure_count",
|
|
33
|
+
"type": "integer",
|
|
34
|
+
"primaryKey": false,
|
|
35
|
+
"notNull": true
|
|
36
|
+
},
|
|
37
|
+
"reset_timestamp": {
|
|
38
|
+
"name": "reset_timestamp",
|
|
39
|
+
"type": "timestamp with time zone",
|
|
40
|
+
"primaryKey": false,
|
|
41
|
+
"notNull": false
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"indexes": {},
|
|
45
|
+
"foreignKeys": {},
|
|
46
|
+
"compositePrimaryKeys": {},
|
|
47
|
+
"uniqueConstraints": {
|
|
48
|
+
"circuit_breaker_key_unique": {
|
|
49
|
+
"name": "circuit_breaker_key_unique",
|
|
50
|
+
"nullsNotDistinct": false,
|
|
51
|
+
"columns": [
|
|
52
|
+
"key"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"policies": {},
|
|
57
|
+
"checkConstraints": {},
|
|
58
|
+
"isRLSEnabled": false
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"enums": {
|
|
62
|
+
"circuit_breaker.circuit_breaker_state": {
|
|
63
|
+
"name": "circuit_breaker_state",
|
|
64
|
+
"schema": "circuit_breaker",
|
|
65
|
+
"values": [
|
|
66
|
+
"closed",
|
|
67
|
+
"open",
|
|
68
|
+
"half-open"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"schemas": {},
|
|
73
|
+
"sequences": {},
|
|
74
|
+
"roles": {},
|
|
75
|
+
"policies": {},
|
|
76
|
+
"views": {},
|
|
77
|
+
"_meta": {
|
|
78
|
+
"columns": {},
|
|
79
|
+
"schemas": {},
|
|
80
|
+
"tables": {}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { relative, resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'drizzle-kit';
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
dialect: 'postgresql',
|
|
5
|
+
out: relative('./', resolve(__dirname, './drizzle/').replace('dist', 'source')),
|
|
6
|
+
schema: resolve(__dirname, './schemas.js'),
|
|
7
|
+
migrations: {
|
|
8
|
+
schema: 'circuit_breaker',
|
|
9
|
+
table: '_migrations',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseEntity, type Timestamp } from '../../orm/index.js';
|
|
2
|
+
import { CircuitBreakerState } from '../circuit-breaker.js';
|
|
3
|
+
export declare class PostgresCircuitBreaker extends BaseEntity {
|
|
4
|
+
static readonly entityName = "CircuitBreaker";
|
|
5
|
+
key: string;
|
|
6
|
+
state: CircuitBreakerState;
|
|
7
|
+
failureCount: number;
|
|
8
|
+
resetTimestamp: Timestamp | null;
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { BaseEntity, Table, TimestampProperty, Unique } from '../../orm/index.js';
|
|
11
|
+
import { Enumeration, Integer, StringProperty } from '../../schema/index.js';
|
|
12
|
+
import { CircuitBreakerState } from '../circuit-breaker.js';
|
|
13
|
+
let PostgresCircuitBreaker = class PostgresCircuitBreaker extends BaseEntity {
|
|
14
|
+
static entityName = 'CircuitBreaker';
|
|
15
|
+
key;
|
|
16
|
+
state;
|
|
17
|
+
failureCount;
|
|
18
|
+
resetTimestamp;
|
|
19
|
+
};
|
|
20
|
+
__decorate([
|
|
21
|
+
StringProperty(),
|
|
22
|
+
Unique(),
|
|
23
|
+
__metadata("design:type", String)
|
|
24
|
+
], PostgresCircuitBreaker.prototype, "key", void 0);
|
|
25
|
+
__decorate([
|
|
26
|
+
Enumeration(CircuitBreakerState),
|
|
27
|
+
__metadata("design:type", String)
|
|
28
|
+
], PostgresCircuitBreaker.prototype, "state", void 0);
|
|
29
|
+
__decorate([
|
|
30
|
+
Integer(),
|
|
31
|
+
__metadata("design:type", Number)
|
|
32
|
+
], PostgresCircuitBreaker.prototype, "failureCount", void 0);
|
|
33
|
+
__decorate([
|
|
34
|
+
TimestampProperty({ nullable: true }),
|
|
35
|
+
__metadata("design:type", Object)
|
|
36
|
+
], PostgresCircuitBreaker.prototype, "resetTimestamp", void 0);
|
|
37
|
+
PostgresCircuitBreaker = __decorate([
|
|
38
|
+
Table('circuit_breaker', { schema: 'circuit_breaker' })
|
|
39
|
+
], PostgresCircuitBreaker);
|
|
40
|
+
export { PostgresCircuitBreaker };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type DatabaseConfig } from '../../orm/server/index.js';
|
|
2
|
+
export declare class PostgresCircuitBreakerModuleConfig {
|
|
3
|
+
database?: DatabaseConfig;
|
|
4
|
+
}
|
|
5
|
+
export declare function configurePostgresCircuitBreaker(config?: PostgresCircuitBreakerModuleConfig, register?: boolean): void;
|
|
6
|
+
export declare function migratePostgresCircuitBreaker(): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { inject, Injector } from '../../injector/index.js';
|
|
2
|
+
import { Database, migrate } from '../../orm/server/index.js';
|
|
3
|
+
import { CircuitBreaker } from '../circuit-breaker.js';
|
|
4
|
+
import { CircuitBreakerProvider } from '../provider.js';
|
|
5
|
+
import { PostgresCircuitBreakerService } from './circuit-breaker.js';
|
|
6
|
+
import { PostgresCircuitBreakerProvider } from './provider.js';
|
|
7
|
+
export class PostgresCircuitBreakerModuleConfig {
|
|
8
|
+
database;
|
|
9
|
+
}
|
|
10
|
+
export function configurePostgresCircuitBreaker(config, register = true) {
|
|
11
|
+
Injector.register(PostgresCircuitBreakerModuleConfig, { useValue: config });
|
|
12
|
+
if (register) {
|
|
13
|
+
Injector.registerSingleton(CircuitBreakerProvider, { useToken: PostgresCircuitBreakerProvider });
|
|
14
|
+
Injector.registerSingleton(CircuitBreaker, { useToken: PostgresCircuitBreakerService });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function migratePostgresCircuitBreaker() {
|
|
18
|
+
const connection = inject(PostgresCircuitBreakerModuleConfig, undefined, { optional: true })?.database?.connection;
|
|
19
|
+
const database = inject(Database, connection);
|
|
20
|
+
await migrate(database, {
|
|
21
|
+
migrationsSchema: 'circuit_breaker',
|
|
22
|
+
migrationsTable: '_migrations',
|
|
23
|
+
migrationsFolder: import.meta.resolve('./drizzle'),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CircuitBreaker, CircuitBreakerConfig } from '../circuit-breaker.js';
|
|
2
|
+
import { CircuitBreakerProvider } from '../provider.js';
|
|
3
|
+
export declare class PostgresCircuitBreakerProvider extends CircuitBreakerProvider {
|
|
4
|
+
#private;
|
|
5
|
+
provide(key: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
6
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { inject, Singleton } from '../../injector/index.js';
|
|
8
|
+
import { Injector } from '../../injector/injector.js';
|
|
9
|
+
import { CircuitBreakerProvider } from '../provider.js';
|
|
10
|
+
import { PostgresCircuitBreakerService } from './circuit-breaker.js';
|
|
11
|
+
let PostgresCircuitBreakerProvider = class PostgresCircuitBreakerProvider extends CircuitBreakerProvider {
|
|
12
|
+
#injector = inject(Injector);
|
|
13
|
+
provide(key, config) {
|
|
14
|
+
const argument = config ? { key, ...config } : key;
|
|
15
|
+
return this.#injector.resolve(PostgresCircuitBreakerService, argument);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
PostgresCircuitBreakerProvider = __decorate([
|
|
19
|
+
Singleton()
|
|
20
|
+
], PostgresCircuitBreakerProvider);
|
|
21
|
+
export { PostgresCircuitBreakerProvider };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PostgresCircuitBreaker } from './model.js';
|
|
2
|
+
export declare const circuitBreakerSchema: import("../../orm/server/index.js").DatabaseSchema<"circuit_breaker">;
|
|
3
|
+
export declare const circuitBreakerState: import("../../orm/enums.js").PgEnumFromEnumeration<{
|
|
4
|
+
readonly Closed: "closed";
|
|
5
|
+
readonly Open: "open";
|
|
6
|
+
readonly HalfOpen: "half-open";
|
|
7
|
+
}>;
|
|
8
|
+
export declare const circuitBreaker: import("../../orm/server/types.js").PgTableFromType<typeof PostgresCircuitBreaker, "circuit_breaker">;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { databaseSchema } from '../../orm/server/index.js';
|
|
2
|
+
import { CircuitBreakerState } from '../circuit-breaker.js';
|
|
3
|
+
import { PostgresCircuitBreaker } from './model.js';
|
|
4
|
+
export const circuitBreakerSchema = databaseSchema('circuit_breaker');
|
|
5
|
+
export const circuitBreakerState = circuitBreakerSchema.getEnum(CircuitBreakerState);
|
|
6
|
+
export const circuitBreaker = circuitBreakerSchema.getTable(PostgresCircuitBreaker);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { CircuitBreakerState } from '../../circuit-breaker/index.js';
|
|
3
|
+
import { CircuitBreakerProvider } from '../../circuit-breaker/provider.js';
|
|
4
|
+
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
5
|
+
import { configureOrm } from '../../orm/server/index.js';
|
|
6
|
+
import * as configParser from '../../utils/config-parser.js';
|
|
7
|
+
import { timeout } from '../../utils/timing.js';
|
|
8
|
+
import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from '../postgres/module.js';
|
|
9
|
+
async function setupIntegrationTest() {
|
|
10
|
+
const injector = new Injector('TestInjector');
|
|
11
|
+
const dbConfig = {
|
|
12
|
+
host: configParser.string('DATABASE_HOST', '127.0.0.1'),
|
|
13
|
+
port: configParser.positiveInteger('DATABASE_PORT', 5432),
|
|
14
|
+
user: configParser.string('DATABASE_USER', 'tstdl'),
|
|
15
|
+
password: configParser.string('DATABASE_PASS', 'wf7rq6glrk5jykne'),
|
|
16
|
+
database: configParser.string('DATABASE_NAME', 'tstdl'),
|
|
17
|
+
};
|
|
18
|
+
configureOrm({
|
|
19
|
+
repositoryConfig: { schema: 'test' },
|
|
20
|
+
connection: dbConfig,
|
|
21
|
+
});
|
|
22
|
+
configurePostgresCircuitBreaker({ database: { connection: dbConfig } });
|
|
23
|
+
await runInInjectionContext(injector, migratePostgresCircuitBreaker);
|
|
24
|
+
return injector;
|
|
25
|
+
}
|
|
26
|
+
describe('Circuit Breaker (Standalone) Tests', () => {
|
|
27
|
+
let injector;
|
|
28
|
+
let provider;
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
injector = await setupIntegrationTest();
|
|
31
|
+
provider = injector.resolve(CircuitBreakerProvider);
|
|
32
|
+
});
|
|
33
|
+
afterAll(async () => {
|
|
34
|
+
await injector.dispose();
|
|
35
|
+
});
|
|
36
|
+
it('should start in Closed state', async () => {
|
|
37
|
+
const breaker = provider.provide(`cb-init-${Date.now()}`, { threshold: 2, resetTimeout: 1000 });
|
|
38
|
+
const result = await breaker.check();
|
|
39
|
+
expect(result.allowed).toBe(true);
|
|
40
|
+
expect(result.state).toBe(CircuitBreakerState.Closed);
|
|
41
|
+
});
|
|
42
|
+
it('should trip to Open after threshold failures', async () => {
|
|
43
|
+
const breaker = provider.provide(`cb-trip-${Date.now()}`, { threshold: 2, resetTimeout: 1000 });
|
|
44
|
+
// 1. First Failure
|
|
45
|
+
await breaker.recordFailure();
|
|
46
|
+
let result = await breaker.check();
|
|
47
|
+
expect(result.state).toBe(CircuitBreakerState.Closed);
|
|
48
|
+
// 2. Second Failure (Threshold Reached)
|
|
49
|
+
await breaker.recordFailure();
|
|
50
|
+
result = await breaker.check();
|
|
51
|
+
expect(result.allowed).toBe(false);
|
|
52
|
+
expect(result.state).toBe(CircuitBreakerState.Open);
|
|
53
|
+
});
|
|
54
|
+
it('should reset to Closed on success', async () => {
|
|
55
|
+
const breaker = provider.provide(`cb-reset-${Date.now()}`, { threshold: 2, resetTimeout: 1000 });
|
|
56
|
+
// Trip it
|
|
57
|
+
await breaker.recordFailure();
|
|
58
|
+
await breaker.recordFailure();
|
|
59
|
+
expect((await breaker.check()).state).toBe(CircuitBreakerState.Open);
|
|
60
|
+
// Record Success
|
|
61
|
+
await breaker.recordSuccess();
|
|
62
|
+
const result = await breaker.check();
|
|
63
|
+
expect(result.allowed).toBe(true);
|
|
64
|
+
expect(result.state).toBe(CircuitBreakerState.Closed);
|
|
65
|
+
});
|
|
66
|
+
it('should transition to Half-Open (Probe) after timeout', async () => {
|
|
67
|
+
const timeoutMs = 200;
|
|
68
|
+
const breaker = provider.provide(`cb-probe-${Date.now()}`, { threshold: 1, resetTimeout: timeoutMs });
|
|
69
|
+
// Trip it
|
|
70
|
+
await breaker.recordFailure();
|
|
71
|
+
expect((await breaker.check()).state).toBe(CircuitBreakerState.Open);
|
|
72
|
+
// Wait for timeout
|
|
73
|
+
await timeout(timeoutMs + 50);
|
|
74
|
+
// First check should be the Probe
|
|
75
|
+
const probe = await breaker.check();
|
|
76
|
+
expect(probe.allowed).toBe(true);
|
|
77
|
+
expect(probe.state).toBe(CircuitBreakerState.HalfOpen);
|
|
78
|
+
expect(probe.isProbe).toBe(true);
|
|
79
|
+
// Subsequent check should be denied (Half-Open wait)
|
|
80
|
+
const subsequent = await breaker.check();
|
|
81
|
+
expect(subsequent.allowed).toBe(true); // check() allows HalfOpen, but queue logic restricts it.
|
|
82
|
+
// Wait, let's verify PostgresCircuitBreakerService logic:
|
|
83
|
+
// if (breaker.state === CircuitBreakerState.HalfOpen) return { allowed: true, state: CircuitBreakerState.HalfOpen, isProbe: false };
|
|
84
|
+
expect(subsequent.state).toBe(CircuitBreakerState.HalfOpen);
|
|
85
|
+
expect(subsequent.isProbe).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it('should close if Probe succeeds', async () => {
|
|
88
|
+
const timeoutMs = 100;
|
|
89
|
+
const breaker = provider.provide(`cb-probe-success-${Date.now()}`, { threshold: 1, resetTimeout: timeoutMs });
|
|
90
|
+
// Trip and Probe
|
|
91
|
+
await breaker.recordFailure();
|
|
92
|
+
await timeout(timeoutMs + 50);
|
|
93
|
+
await breaker.check(); // Probe triggered
|
|
94
|
+
// Probe succeeds
|
|
95
|
+
await breaker.recordSuccess();
|
|
96
|
+
const result = await breaker.check();
|
|
97
|
+
expect(result.state).toBe(CircuitBreakerState.Closed);
|
|
98
|
+
});
|
|
99
|
+
it('should reopen if Probe fails', async () => {
|
|
100
|
+
const timeoutMs = 100;
|
|
101
|
+
const breaker = provider.provide(`cb-probe-fail-${Date.now()}`, { threshold: 1, resetTimeout: timeoutMs });
|
|
102
|
+
// Trip and Probe
|
|
103
|
+
await breaker.recordFailure();
|
|
104
|
+
await timeout(timeoutMs + 50);
|
|
105
|
+
await breaker.check(); // Probe triggered (State: HalfOpen)
|
|
106
|
+
// Probe fails
|
|
107
|
+
await breaker.recordFailure();
|
|
108
|
+
// Should be Open again
|
|
109
|
+
const result = await breaker.check();
|
|
110
|
+
expect(result.allowed).toBe(false);
|
|
111
|
+
expect(result.state).toBe(CircuitBreakerState.Open);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -31,7 +31,6 @@ let Document = class Document extends TenantEntity {
|
|
|
31
31
|
mimeType;
|
|
32
32
|
hash;
|
|
33
33
|
size;
|
|
34
|
-
createUserId;
|
|
35
34
|
};
|
|
36
35
|
__decorate([
|
|
37
36
|
UuidProperty({ nullable: true }),
|
|
@@ -82,10 +81,6 @@ __decorate([
|
|
|
82
81
|
Integer(),
|
|
83
82
|
__metadata("design:type", Number)
|
|
84
83
|
], Document.prototype, "size", void 0);
|
|
85
|
-
__decorate([
|
|
86
|
-
UuidProperty({ nullable: true }),
|
|
87
|
-
__metadata("design:type", Object)
|
|
88
|
-
], Document.prototype, "createUserId", void 0);
|
|
89
84
|
Document = __decorate([
|
|
90
85
|
DocumentManagementTable(),
|
|
91
86
|
Unique(['tenantId', 'id'])
|
|
@@ -155,8 +155,7 @@ let DocumentManagementApiController = DocumentManagementApiController_1 = class
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
return await this.#documentService.create(tenantId, createParameters, { uploadId, uploadKey: subject }, { createUserId: actionUserId });
|
|
158
|
+
return await this.#documentService.create(tenantId, createParameters, { uploadId, uploadKey: subject });
|
|
160
159
|
}
|
|
161
160
|
async createDocumentRequestsTemplate(context) {
|
|
162
161
|
const token = await context.getToken();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "document_management"."document" DROP COLUMN "create_user_id";
|