@rytass/bpm-core-nestjs-module 0.21.4 → 0.23.0
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/CHANGELOG.md +87 -0
- package/README.md +9 -1
- package/package.json +2 -2
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/bpm/bpm-root-options.d.ts +57 -0
- package/src/lib/bpm/bpm-root-options.js.map +1 -1
- package/src/lib/bpm/bpm-root.module.d.ts +21 -0
- package/src/lib/bpm/bpm-root.module.js +6 -0
- package/src/lib/bpm/bpm-root.module.js.map +1 -1
- package/src/lib/form/dto/form-definition.input.d.ts +5 -0
- package/src/lib/form/dto/form-definition.input.js.map +1 -1
- package/src/lib/form/form-definition.entity.d.ts +5 -0
- package/src/lib/form/form-definition.entity.js +7 -0
- package/src/lib/form/form-definition.entity.js.map +1 -1
- package/src/lib/form/form-schema.validator.js +17 -5
- package/src/lib/form/form-schema.validator.js.map +1 -1
- package/src/lib/form/form.queries.d.ts +2 -2
- package/src/lib/form/form.queries.js +17 -5
- package/src/lib/form/form.queries.js.map +1 -1
- package/src/lib/form/form.service.d.ts +2 -1
- package/src/lib/form/form.service.js +10 -5
- package/src/lib/form/form.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js +22 -2
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.errors.d.ts +11 -0
- package/src/lib/form-data-source/form-data-source.errors.js +14 -1
- package/src/lib/form-data-source/form-data-source.errors.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.service.d.ts +6 -0
- package/src/lib/form-data-source/form-data-source.service.js +41 -15
- package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
- package/src/lib/identity/identity-options.d.ts +1 -1
- package/src/lib/identity/identity.queries.d.ts +0 -2
- package/src/lib/identity/identity.queries.js +0 -11
- package/src/lib/identity/identity.queries.js.map +1 -1
- package/src/lib/instance-event/approval-instance-event.entity.d.ts +27 -0
- package/src/lib/instance-event/approval-instance-event.entity.js +93 -0
- package/src/lib/instance-event/approval-instance-event.entity.js.map +1 -0
- package/src/lib/instance-event/index.d.ts +13 -0
- package/src/lib/instance-event/index.js +17 -0
- package/src/lib/instance-event/index.js.map +1 -0
- package/src/lib/instance-event/instance-event.delivery-scheduler.service.d.ts +21 -0
- package/src/lib/instance-event/instance-event.delivery-scheduler.service.js +81 -0
- package/src/lib/instance-event/instance-event.delivery-scheduler.service.js.map +1 -0
- package/src/lib/instance-event/instance-event.delivery.service.d.ts +69 -0
- package/src/lib/instance-event/instance-event.delivery.service.js +259 -0
- package/src/lib/instance-event/instance-event.delivery.service.js.map +1 -0
- package/src/lib/instance-event/instance-event.delivery.subscriber.d.ts +24 -0
- package/src/lib/instance-event/instance-event.delivery.subscriber.js +85 -0
- package/src/lib/instance-event/instance-event.delivery.subscriber.js.map +1 -0
- package/src/lib/instance-event/instance-event.enums.d.ts +12 -0
- package/src/lib/instance-event/instance-event.enums.js +28 -0
- package/src/lib/instance-event/instance-event.enums.js.map +1 -0
- package/src/lib/instance-event/instance-event.module.d.ts +13 -0
- package/src/lib/instance-event/instance-event.module.js +52 -0
- package/src/lib/instance-event/instance-event.module.js.map +1 -0
- package/src/lib/instance-event/instance-event.object.d.ts +24 -0
- package/src/lib/instance-event/instance-event.object.js +73 -0
- package/src/lib/instance-event/instance-event.object.js.map +1 -0
- package/src/lib/instance-event/instance-event.options.d.ts +26 -0
- package/src/lib/instance-event/instance-event.options.js +35 -0
- package/src/lib/instance-event/instance-event.options.js.map +1 -0
- package/src/lib/instance-event/instance-event.resolver.d.ts +10 -0
- package/src/lib/instance-event/instance-event.resolver.js +75 -0
- package/src/lib/instance-event/instance-event.resolver.js.map +1 -0
- package/src/lib/instance-event/instance-event.service.d.ts +18 -0
- package/src/lib/instance-event/instance-event.service.js +37 -0
- package/src/lib/instance-event/instance-event.service.js.map +1 -0
- package/src/lib/instance-event/instance-event.token.d.ts +19 -0
- package/src/lib/instance-event/instance-event.token.js +9 -0
- package/src/lib/instance-event/instance-event.token.js.map +1 -0
- package/src/lib/instance-event/instance-event.types.d.ts +67 -0
- package/src/lib/instance-event/instance-event.types.js +9 -0
- package/src/lib/instance-event/instance-event.types.js.map +1 -0
- package/src/lib/instance-event/instance-event.writer.d.ts +21 -0
- package/src/lib/instance-event/instance-event.writer.js +45 -0
- package/src/lib/instance-event/instance-event.writer.js.map +1 -0
- package/src/lib/migrations/0000000032000-host-integration-round.d.ts +25 -0
- package/src/lib/migrations/0000000032000-host-integration-round.js +104 -0
- package/src/lib/migrations/0000000032000-host-integration-round.js.map +1 -0
- package/src/lib/migrations/index.d.ts +1 -0
- package/src/lib/migrations/index.js +3 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/notification.service.js +50 -9
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/template/approval-template-version.entity.d.ts +9 -1
- package/src/lib/template/approval-template-version.entity.js +11 -1
- package/src/lib/template/approval-template-version.entity.js.map +1 -1
- package/src/lib/template/approval-template.entity.d.ts +18 -0
- package/src/lib/template/approval-template.entity.js +23 -0
- package/src/lib/template/approval-template.entity.js.map +1 -1
- package/src/lib/template/dto/approval-template.input.d.ts +21 -0
- package/src/lib/template/dto/approval-template.input.js +38 -0
- package/src/lib/template/dto/approval-template.input.js.map +1 -1
- package/src/lib/template/dto/compose-approval-template.input.d.ts +23 -0
- package/src/lib/template/dto/compose-approval-template.input.js +37 -0
- package/src/lib/template/dto/compose-approval-template.input.js.map +1 -1
- package/src/lib/template/template-key.d.ts +26 -0
- package/src/lib/template/template-key.js +67 -0
- package/src/lib/template/template-key.js.map +1 -0
- package/src/lib/template/template.enums.d.ts +11 -0
- package/src/lib/template/template.enums.js +17 -1
- package/src/lib/template/template.enums.js.map +1 -1
- package/src/lib/template/template.queries.d.ts +3 -2
- package/src/lib/template/template.queries.js +29 -4
- package/src/lib/template/template.queries.js.map +1 -1
- package/src/lib/template/template.service.d.ts +8 -1
- package/src/lib/template/template.service.js +94 -17
- package/src/lib/template/template.service.js.map +1 -1
- package/src/lib/workflow-engine/approver-filled-table-columns.d.ts +91 -0
- package/src/lib/workflow-engine/approver-filled-table-columns.js +301 -0
- package/src/lib/workflow-engine/approver-filled-table-columns.js.map +1 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.d.ts +7 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js +9 -0
- package/src/lib/workflow-engine/dto/dry-run-approval-workflow.input.js.map +1 -1
- package/src/lib/workflow-engine/index.d.ts +1 -0
- package/src/lib/workflow-engine/index.js +1 -0
- package/src/lib/workflow-engine/index.js.map +1 -1
- package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.d.ts +31 -11
- package/src/lib/workflow-engine/initiator-custom-fields-resolver.token.js.map +1 -1
- package/src/lib/workflow-engine/instance-guard.runner.d.ts +19 -0
- package/src/lib/workflow-engine/instance-guard.runner.js +102 -0
- package/src/lib/workflow-engine/instance-guard.runner.js.map +1 -0
- package/src/lib/workflow-engine/instance-guard.token.d.ts +101 -0
- package/src/lib/workflow-engine/instance-guard.token.js +9 -0
- package/src/lib/workflow-engine/instance-guard.token.js.map +1 -0
- package/src/lib/workflow-engine/missing-form-fields.d.ts +12 -0
- package/src/lib/workflow-engine/missing-form-fields.js +27 -0
- package/src/lib/workflow-engine/missing-form-fields.js.map +1 -0
- package/src/lib/workflow-engine/workflow-engine.module.d.ts +2 -0
- package/src/lib/workflow-engine/workflow-engine.module.js +3 -0
- package/src/lib/workflow-engine/workflow-engine.module.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.mutations.d.ts +1 -1
- package/src/lib/workflow-engine/workflow-engine.mutations.js +8 -3
- package/src/lib/workflow-engine/workflow-engine.mutations.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.queries.d.ts +7 -5
- package/src/lib/workflow-engine/workflow-engine.queries.js +136 -24
- package/src/lib/workflow-engine/workflow-engine.queries.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.service.d.ts +216 -7
- package/src/lib/workflow-engine/workflow-engine.service.js +864 -131
- package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
- package/src/lib/workflow-engine/workflow-engine.tokens.d.ts +5 -0
|
@@ -5,7 +5,6 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const graphql_1 = require("@nestjs/graphql");
|
|
6
6
|
const bpm_auth_1 = require("../bpm-auth");
|
|
7
7
|
const identity_service_1 = require("./identity.service");
|
|
8
|
-
const member_metadata_cache_entity_1 = require("./member-metadata-cache.entity");
|
|
9
8
|
const member_profile_object_1 = require("./member-profile.object");
|
|
10
9
|
let IdentityQueries = class IdentityQueries {
|
|
11
10
|
constructor(identityService) {
|
|
@@ -19,9 +18,6 @@ let IdentityQueries = class IdentityQueries {
|
|
|
19
18
|
const metadataList = await this.identityService.resolveMembers(memberIds);
|
|
20
19
|
return metadataList.map(member_profile_object_1.toMemberProfileObject);
|
|
21
20
|
}
|
|
22
|
-
async cachedMembers() {
|
|
23
|
-
return this.identityService.listCachedMembers();
|
|
24
|
-
}
|
|
25
21
|
async searchMembers(searchText, page, pageSize) {
|
|
26
22
|
const metadataList = await this.identityService.searchMembers(searchText, {
|
|
27
23
|
page: page ?? undefined,
|
|
@@ -48,13 +44,6 @@ tslib_1.__decorate([
|
|
|
48
44
|
tslib_1.__metadata("design:paramtypes", [Array]),
|
|
49
45
|
tslib_1.__metadata("design:returntype", Promise)
|
|
50
46
|
], IdentityQueries.prototype, "members", null);
|
|
51
|
-
tslib_1.__decorate([
|
|
52
|
-
(0, graphql_1.Query)(() => [member_metadata_cache_entity_1.MemberMetadataCacheEntity]),
|
|
53
|
-
(0, bpm_auth_1.BPMAdminOnly)(),
|
|
54
|
-
tslib_1.__metadata("design:type", Function),
|
|
55
|
-
tslib_1.__metadata("design:paramtypes", []),
|
|
56
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
57
|
-
], IdentityQueries.prototype, "cachedMembers", null);
|
|
58
47
|
tslib_1.__decorate([
|
|
59
48
|
(0, graphql_1.Query)(() => [member_profile_object_1.MemberProfileObject]),
|
|
60
49
|
tslib_1.__param(0, (0, graphql_1.Args)('searchText', { type: () => String })),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.queries.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/identity/identity.queries.ts"],"names":[],"mappings":";;;;AAAA,6CAA6D;AAC7D,
|
|
1
|
+
{"version":3,"file":"identity.queries.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/identity/identity.queries.ts"],"names":[],"mappings":";;;;AAAA,6CAA6D;AAC7D,0CAA+C;AAC/C,yDAAqD;AACrD,mEAGiC;AAI1B,IAAM,eAAe,GAArB,MAAM,eAAe;IAC1B,YAA6B,eAAgC;QAAhC,oBAAe,GAAf,eAAe,CAAiB;IAAG,CAAC;IAG3D,AAAN,KAAK,CAAC,MAAM,CACgC,QAAgB;QAE1D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAEpE,OAAO,IAAA,6CAAqB,EAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAGK,AAAN,KAAK,CAAC,OAAO,CACkC,SAA4B;QAEzE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAE1E,OAAO,YAAY,CAAC,GAAG,CAAC,6CAAqB,CAAC,CAAC;IACjD,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CAC2B,UAAkB,EACX,IAAoB,EAEvE,QAAwB;QAExB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,UAAU,EAAE;YACxE,IAAI,EAAE,IAAI,IAAI,SAAS;YACvB,QAAQ,EAAE,QAAQ,IAAI,SAAS;SAChC,CAAC,CAAC;QAEH,OAAO,YAAY,CAAC,GAAG,CAAC,6CAAqB,CAAC,CAAC;IACjD,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAEf,UAA0B;QAE1B,OAAO,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;CACF,CAAA;AA3CY,0CAAe;AAIpB;IADL,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,2CAAmB,CAAC;IAE9B,mBAAA,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;;;;6CAK1C;AAGK;IADL,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,CAAC,2CAAmB,CAAC,CAAC;IAEhC,mBAAA,IAAA,cAAI,EAAC,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;;;;8CAK7C;AAGK;IADL,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,CAAC,2CAAmB,CAAC,CAAC;IAEhC,mBAAA,IAAA,cAAI,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;IAC1C,mBAAA,IAAA,cAAI,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,aAAG,EAAE,CAAC,CAAA;IACjD,mBAAA,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,aAAG,EAAE,CAAC,CAAA;;;;oDASvD;AAGK;IADL,IAAA,eAAK,EAAC,GAAG,EAAE,CAAC,aAAG,CAAC;IAEd,mBAAA,IAAA,cAAI,EAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAA;;;;kDAI5D;0BA1CU,eAAe;IAF3B,IAAA,kBAAQ,GAAE;IACV,IAAA,2BAAgB,GAAE;6CAE6B,kCAAe;GADlD,eAAe,CA2C3B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ApprovalInstanceEntity } from '../workflow-engine/approval-instance.entity';
|
|
2
|
+
import { BPMInstanceEventPayload, BPMInstanceEventStatus, BPMInstanceEventType } from './instance-event.types';
|
|
3
|
+
/**
|
|
4
|
+
* One outbox row per instance lifecycle event (ADR 27 §1.2). Written in the
|
|
5
|
+
* same transaction as the state change, delivered after commit. Mirrors
|
|
6
|
+
* migration `0000000032000-host-integration-round`; `synchronize` stays off.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ApprovalInstanceEventEntity {
|
|
9
|
+
id: string;
|
|
10
|
+
/**
|
|
11
|
+
* Write order, assigned by the database (migration
|
|
12
|
+
* `0000000032000-host-integration-round`). Sort by this, not by `id` or
|
|
13
|
+
* `occurredAt`. `bigint`, so the driver returns a string.
|
|
14
|
+
*/
|
|
15
|
+
seq: string;
|
|
16
|
+
instanceId: string;
|
|
17
|
+
instance?: ApprovalInstanceEntity;
|
|
18
|
+
type: BPMInstanceEventType;
|
|
19
|
+
payload: BPMInstanceEventPayload;
|
|
20
|
+
status: BPMInstanceEventStatus;
|
|
21
|
+
attemptCount: number;
|
|
22
|
+
nextAttemptAt: Date;
|
|
23
|
+
claimedAt: Date | null;
|
|
24
|
+
deliveredAt: Date | null;
|
|
25
|
+
lastError: string | null;
|
|
26
|
+
occurredAt: Date;
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApprovalInstanceEventEntity = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
const approval_instance_entity_1 = require("../workflow-engine/approval-instance.entity");
|
|
7
|
+
const instance_event_types_1 = require("./instance-event.types");
|
|
8
|
+
/**
|
|
9
|
+
* One outbox row per instance lifecycle event (ADR 27 §1.2). Written in the
|
|
10
|
+
* same transaction as the state change, delivered after commit. Mirrors
|
|
11
|
+
* migration `0000000032000-host-integration-round`; `synchronize` stays off.
|
|
12
|
+
*/
|
|
13
|
+
let ApprovalInstanceEventEntity = class ApprovalInstanceEventEntity {
|
|
14
|
+
};
|
|
15
|
+
exports.ApprovalInstanceEventEntity = ApprovalInstanceEventEntity;
|
|
16
|
+
tslib_1.__decorate([
|
|
17
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
18
|
+
tslib_1.__metadata("design:type", String)
|
|
19
|
+
], ApprovalInstanceEventEntity.prototype, "id", void 0);
|
|
20
|
+
tslib_1.__decorate([
|
|
21
|
+
(0, typeorm_1.Column)({
|
|
22
|
+
generated: 'identity',
|
|
23
|
+
generatedIdentity: 'ALWAYS',
|
|
24
|
+
name: 'seq',
|
|
25
|
+
type: 'bigint',
|
|
26
|
+
update: false,
|
|
27
|
+
}),
|
|
28
|
+
tslib_1.__metadata("design:type", String)
|
|
29
|
+
], ApprovalInstanceEventEntity.prototype, "seq", void 0);
|
|
30
|
+
tslib_1.__decorate([
|
|
31
|
+
(0, typeorm_1.Column)('uuid', { name: 'instance_id' }),
|
|
32
|
+
tslib_1.__metadata("design:type", String)
|
|
33
|
+
], ApprovalInstanceEventEntity.prototype, "instanceId", void 0);
|
|
34
|
+
tslib_1.__decorate([
|
|
35
|
+
(0, typeorm_1.ManyToOne)(() => approval_instance_entity_1.ApprovalInstanceEntity, {
|
|
36
|
+
createForeignKeyConstraints: true,
|
|
37
|
+
onDelete: 'CASCADE',
|
|
38
|
+
}),
|
|
39
|
+
(0, typeorm_1.JoinColumn)({
|
|
40
|
+
foreignKeyConstraintName: 'FK_approval_instance_events_instance',
|
|
41
|
+
name: 'instance_id',
|
|
42
|
+
}),
|
|
43
|
+
tslib_1.__metadata("design:type", approval_instance_entity_1.ApprovalInstanceEntity)
|
|
44
|
+
], ApprovalInstanceEventEntity.prototype, "instance", void 0);
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, typeorm_1.Column)('text'),
|
|
47
|
+
tslib_1.__metadata("design:type", String)
|
|
48
|
+
], ApprovalInstanceEventEntity.prototype, "type", void 0);
|
|
49
|
+
tslib_1.__decorate([
|
|
50
|
+
(0, typeorm_1.Column)('jsonb'),
|
|
51
|
+
tslib_1.__metadata("design:type", Object)
|
|
52
|
+
], ApprovalInstanceEventEntity.prototype, "payload", void 0);
|
|
53
|
+
tslib_1.__decorate([
|
|
54
|
+
(0, typeorm_1.Column)('text', { default: 'PENDING' }),
|
|
55
|
+
tslib_1.__metadata("design:type", String)
|
|
56
|
+
], ApprovalInstanceEventEntity.prototype, "status", void 0);
|
|
57
|
+
tslib_1.__decorate([
|
|
58
|
+
(0, typeorm_1.Column)('integer', { default: 0, name: 'attempt_count' }),
|
|
59
|
+
tslib_1.__metadata("design:type", Number)
|
|
60
|
+
], ApprovalInstanceEventEntity.prototype, "attemptCount", void 0);
|
|
61
|
+
tslib_1.__decorate([
|
|
62
|
+
(0, typeorm_1.Column)('timestamptz', {
|
|
63
|
+
default: () => 'now()',
|
|
64
|
+
name: 'next_attempt_at',
|
|
65
|
+
}),
|
|
66
|
+
tslib_1.__metadata("design:type", Date)
|
|
67
|
+
], ApprovalInstanceEventEntity.prototype, "nextAttemptAt", void 0);
|
|
68
|
+
tslib_1.__decorate([
|
|
69
|
+
(0, typeorm_1.Column)('timestamptz', { name: 'claimed_at', nullable: true }),
|
|
70
|
+
tslib_1.__metadata("design:type", Object)
|
|
71
|
+
], ApprovalInstanceEventEntity.prototype, "claimedAt", void 0);
|
|
72
|
+
tslib_1.__decorate([
|
|
73
|
+
(0, typeorm_1.Column)('timestamptz', { name: 'delivered_at', nullable: true }),
|
|
74
|
+
tslib_1.__metadata("design:type", Object)
|
|
75
|
+
], ApprovalInstanceEventEntity.prototype, "deliveredAt", void 0);
|
|
76
|
+
tslib_1.__decorate([
|
|
77
|
+
(0, typeorm_1.Column)('text', { name: 'last_error', nullable: true }),
|
|
78
|
+
tslib_1.__metadata("design:type", Object)
|
|
79
|
+
], ApprovalInstanceEventEntity.prototype, "lastError", void 0);
|
|
80
|
+
tslib_1.__decorate([
|
|
81
|
+
(0, typeorm_1.Column)('timestamptz', {
|
|
82
|
+
default: () => 'now()',
|
|
83
|
+
name: 'occurred_at',
|
|
84
|
+
}),
|
|
85
|
+
tslib_1.__metadata("design:type", Date)
|
|
86
|
+
], ApprovalInstanceEventEntity.prototype, "occurredAt", void 0);
|
|
87
|
+
exports.ApprovalInstanceEventEntity = ApprovalInstanceEventEntity = tslib_1.__decorate([
|
|
88
|
+
(0, typeorm_1.Entity)('approval_instance_events'),
|
|
89
|
+
(0, typeorm_1.Check)('CHK_approval_instance_events_status', `"status" IN (${instance_event_types_1.BPM_INSTANCE_EVENT_STATUSES.map((status) => `'${status}'`).join(', ')})`),
|
|
90
|
+
(0, typeorm_1.Index)('IDX_approval_instance_events_due', ['status', 'nextAttemptAt']),
|
|
91
|
+
(0, typeorm_1.Index)('IDX_approval_instance_events_instance_seq', ['instanceId', 'seq'])
|
|
92
|
+
], ApprovalInstanceEventEntity);
|
|
93
|
+
//# sourceMappingURL=approval-instance-event.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-instance-event.entity.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/instance-event/approval-instance-event.entity.ts"],"names":[],"mappings":";;;;AAAA,qCAQiB;AACjB,0FAAqF;AACrF,iEAKgC;AAEhC;;;;GAIG;AAQI,IAAM,2BAA2B,GAAjC,MAAM,2BAA2B;CA+DvC,CAAA;AA/DY,kEAA2B;AAEtC;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;uDACnB;AAcZ;IAPC,IAAA,gBAAM,EAAC;QACN,SAAS,EAAE,UAAU;QACrB,iBAAiB,EAAE,QAAQ;QAC3B,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,KAAK;KACd,CAAC;;wDACW;AAGb;IADC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;;+DACpB;AAUpB;IARC,IAAA,mBAAS,EAAC,GAAG,EAAE,CAAC,iDAAsB,EAAE;QACvC,2BAA2B,EAAE,IAAI;QACjC,QAAQ,EAAE,SAAS;KACpB,CAAC;IACD,IAAA,oBAAU,EAAC;QACV,wBAAwB,EAAE,sCAAsC;QAChE,IAAI,EAAE,aAAa;KACpB,CAAC;sCACS,iDAAsB;6DAAC;AAGlC;IADC,IAAA,gBAAM,EAAC,MAAM,CAAC;;yDACa;AAG5B;IADC,IAAA,gBAAM,EAAC,OAAO,CAAC;;4DACkB;AAGlC;IADC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;2DACP;AAGhC;IADC,IAAA,gBAAM,EAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;;iEACnC;AAMtB;IAJC,IAAA,gBAAM,EAAC,aAAa,EAAE;QACrB,OAAO,EAAE,GAAW,EAAE,CAAC,OAAO;QAC9B,IAAI,EAAE,iBAAiB;KACxB,CAAC;sCACc,IAAI;kEAAC;AAGrB;IADC,IAAA,gBAAM,EAAC,aAAa,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8DACtC;AAGxB;IADC,IAAA,gBAAM,EAAC,aAAa,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gEACtC;AAG1B;IADC,IAAA,gBAAM,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8DAC7B;AAM1B;IAJC,IAAA,gBAAM,EAAC,aAAa,EAAE;QACrB,OAAO,EAAE,GAAW,EAAE,CAAC,OAAO;QAC9B,IAAI,EAAE,aAAa;KACpB,CAAC;sCACW,IAAI;+DAAC;sCA9DP,2BAA2B;IAPvC,IAAA,gBAAM,EAAC,0BAA0B,CAAC;IAClC,IAAA,eAAK,EACJ,qCAAqC,EACrC,gBAAgB,kDAA2B,CAAC,GAAG,CAAC,CAAC,MAAc,EAAU,EAAE,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACzG;IACA,IAAA,eAAK,EAAC,kCAAkC,EAAE,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IACtE,IAAA,eAAK,EAAC,2CAA2C,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;GAC7D,2BAA2B,CA+DvC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './approval-instance-event.entity';
|
|
2
|
+
export * from './instance-event.delivery-scheduler.service';
|
|
3
|
+
export * from './instance-event.delivery.service';
|
|
4
|
+
export * from './instance-event.delivery.subscriber';
|
|
5
|
+
export * from './instance-event.enums';
|
|
6
|
+
export * from './instance-event.module';
|
|
7
|
+
export * from './instance-event.object';
|
|
8
|
+
export * from './instance-event.options';
|
|
9
|
+
export * from './instance-event.resolver';
|
|
10
|
+
export * from './instance-event.service';
|
|
11
|
+
export * from './instance-event.token';
|
|
12
|
+
export * from './instance-event.types';
|
|
13
|
+
export * from './instance-event.writer';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./approval-instance-event.entity"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./instance-event.delivery-scheduler.service"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./instance-event.delivery.service"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./instance-event.delivery.subscriber"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./instance-event.enums"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./instance-event.module"), exports);
|
|
10
|
+
tslib_1.__exportStar(require("./instance-event.object"), exports);
|
|
11
|
+
tslib_1.__exportStar(require("./instance-event.options"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./instance-event.resolver"), exports);
|
|
13
|
+
tslib_1.__exportStar(require("./instance-event.service"), exports);
|
|
14
|
+
tslib_1.__exportStar(require("./instance-event.token"), exports);
|
|
15
|
+
tslib_1.__exportStar(require("./instance-event.types"), exports);
|
|
16
|
+
tslib_1.__exportStar(require("./instance-event.writer"), exports);
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/instance-event/index.ts"],"names":[],"mappings":";;;AAAA,2EAAiD;AACjD,sFAA4D;AAC5D,4EAAkD;AAClD,+EAAqD;AACrD,iEAAuC;AACvC,kEAAwC;AACxC,kEAAwC;AACxC,mEAAyC;AACzC,oEAA0C;AAC1C,mEAAyC;AACzC,iEAAuC;AACvC,iEAAuC;AACvC,kEAAwC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
|
|
2
|
+
import { InstanceEventDeliveryService } from './instance-event.delivery.service';
|
|
3
|
+
import { InstanceEventService } from './instance-event.service';
|
|
4
|
+
/**
|
|
5
|
+
* The fallback that retries failed attempts, releases rows the commit kick
|
|
6
|
+
* missed (ADR 27 §1.3), and sweeps delivered rows past retention. `SKIP LOCKED`
|
|
7
|
+
* in the claim lets several API instances run it at once.
|
|
8
|
+
*/
|
|
9
|
+
export declare class InstanceEventDeliverySchedulerService implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
10
|
+
private readonly deliveryService;
|
|
11
|
+
private readonly events;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
private timer;
|
|
14
|
+
private scanning;
|
|
15
|
+
private lastPurgeAt;
|
|
16
|
+
constructor(deliveryService: InstanceEventDeliveryService, events: InstanceEventService);
|
|
17
|
+
onApplicationBootstrap(): void;
|
|
18
|
+
onApplicationShutdown(): void;
|
|
19
|
+
scan(): Promise<void>;
|
|
20
|
+
private purgeIfDue;
|
|
21
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var InstanceEventDeliverySchedulerService_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InstanceEventDeliverySchedulerService = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const instance_event_delivery_service_1 = require("./instance-event.delivery.service");
|
|
8
|
+
const instance_event_service_1 = require("./instance-event.service");
|
|
9
|
+
/** Retention sweeps are cheap but not urgent; once an hour is plenty. */
|
|
10
|
+
const PURGE_INTERVAL_MS = 3_600_000;
|
|
11
|
+
/**
|
|
12
|
+
* The fallback that retries failed attempts, releases rows the commit kick
|
|
13
|
+
* missed (ADR 27 §1.3), and sweeps delivered rows past retention. `SKIP LOCKED`
|
|
14
|
+
* in the claim lets several API instances run it at once.
|
|
15
|
+
*/
|
|
16
|
+
let InstanceEventDeliverySchedulerService = InstanceEventDeliverySchedulerService_1 = class InstanceEventDeliverySchedulerService {
|
|
17
|
+
constructor(deliveryService, events) {
|
|
18
|
+
this.deliveryService = deliveryService;
|
|
19
|
+
this.events = events;
|
|
20
|
+
this.logger = new common_1.Logger(InstanceEventDeliverySchedulerService_1.name);
|
|
21
|
+
this.timer = null;
|
|
22
|
+
this.scanning = false;
|
|
23
|
+
this.lastPurgeAt = 0;
|
|
24
|
+
}
|
|
25
|
+
onApplicationBootstrap() {
|
|
26
|
+
// Without an observer there is nothing to deliver to, so no idle timer.
|
|
27
|
+
if (process.env.NODE_ENV === 'test' || !this.events.enabled) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.timer = setInterval(() => {
|
|
31
|
+
void this.scan();
|
|
32
|
+
}, this.events.delivery.scanIntervalMs);
|
|
33
|
+
this.timer.unref();
|
|
34
|
+
}
|
|
35
|
+
onApplicationShutdown() {
|
|
36
|
+
if (this.timer) {
|
|
37
|
+
clearInterval(this.timer);
|
|
38
|
+
this.timer = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async scan() {
|
|
42
|
+
// A slow observer must not stack scans on top of each other.
|
|
43
|
+
if (this.scanning) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.scanning = true;
|
|
47
|
+
try {
|
|
48
|
+
const count = await this.deliveryService.deliverDue();
|
|
49
|
+
if (count) {
|
|
50
|
+
this.logger.log(`Attempted ${count} instance event deliveries`);
|
|
51
|
+
}
|
|
52
|
+
await this.purgeIfDue(Date.now());
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
this.logger.error(error instanceof Error
|
|
56
|
+
? error.message
|
|
57
|
+
: 'Instance event delivery scan failed');
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.scanning = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async purgeIfDue(now) {
|
|
64
|
+
if (now - this.lastPurgeAt < PURGE_INTERVAL_MS) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Stamped before the call, so a failing sweep is not retried every scan.
|
|
68
|
+
this.lastPurgeAt = now;
|
|
69
|
+
const purged = await this.deliveryService.purgeDelivered(new Date(now));
|
|
70
|
+
if (purged) {
|
|
71
|
+
this.logger.log(`Purged ${purged} delivered instance events`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
exports.InstanceEventDeliverySchedulerService = InstanceEventDeliverySchedulerService;
|
|
76
|
+
exports.InstanceEventDeliverySchedulerService = InstanceEventDeliverySchedulerService = InstanceEventDeliverySchedulerService_1 = tslib_1.__decorate([
|
|
77
|
+
(0, common_1.Injectable)(),
|
|
78
|
+
tslib_1.__metadata("design:paramtypes", [instance_event_delivery_service_1.InstanceEventDeliveryService,
|
|
79
|
+
instance_event_service_1.InstanceEventService])
|
|
80
|
+
], InstanceEventDeliverySchedulerService);
|
|
81
|
+
//# sourceMappingURL=instance-event.delivery-scheduler.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-event.delivery-scheduler.service.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/instance-event/instance-event.delivery-scheduler.service.ts"],"names":[],"mappings":";;;;;AAAA,2CAKwB;AACxB,uFAAiF;AACjF,qEAAgE;AAEhE,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAEpC;;;;GAIG;AAEI,IAAM,qCAAqC,6CAA3C,MAAM,qCAAqC;IAUhD,YACmB,eAA6C,EAC7C,MAA4B;QAD5B,oBAAe,GAAf,eAAe,CAA8B;QAC7C,WAAM,GAAN,MAAM,CAAsB;QAT9B,WAAM,GAAG,IAAI,eAAM,CAClC,uCAAqC,CAAC,IAAI,CAC3C,CAAC;QACM,UAAK,GAA0B,IAAI,CAAC;QACpC,aAAQ,GAAG,KAAK,CAAC;QACjB,gBAAW,GAAG,CAAC,CAAC;IAKrB,CAAC;IAEJ,sBAAsB;QACpB,wEAAwE;QACxE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAS,EAAE;YAClC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,qBAAqB;QACnB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,6DAA6D;QAC7D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QAErB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;YAEtD,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,KAAK,4BAA4B,CAAC,CAAC;YAClE,CAAC;YAED,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,KAAK,YAAY,KAAK;gBACpB,CAAC,CAAC,KAAK,CAAC,OAAO;gBACf,CAAC,CAAC,qCAAqC,CAC1C,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,GAAW;QAClC,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,GAAG,iBAAiB,EAAE,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,yEAAyE;QACzE,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAEvB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAExE,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,MAAM,4BAA4B,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;CACF,CAAA;AA3EY,sFAAqC;gDAArC,qCAAqC;IADjD,IAAA,mBAAU,GAAE;6CAYyB,8DAA4B;QACrC,6CAAoB;GAZpC,qCAAqC,CA2EjD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Repository } from 'typeorm';
|
|
2
|
+
import { ApprovalInstanceEventEntity } from './approval-instance-event.entity';
|
|
3
|
+
import { InstanceEventService } from './instance-event.service';
|
|
4
|
+
import type { BPMInstanceEventStatus } from './instance-event.types';
|
|
5
|
+
export interface ListInstanceEventsInput {
|
|
6
|
+
readonly instanceId: string | null;
|
|
7
|
+
readonly page: number;
|
|
8
|
+
readonly pageSize: number;
|
|
9
|
+
readonly status: BPMInstanceEventStatus | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Delivers outbox rows to the host observer (ADR 27 §1.3).
|
|
13
|
+
*
|
|
14
|
+
* Guarantees, and how they are kept:
|
|
15
|
+
* - At least once. A row is `DELIVERED` only after the observer resolved; a
|
|
16
|
+
* crash between dispatch and write-back redelivers. Hosts dedupe on `id`.
|
|
17
|
+
* - Per-instance order by `seq`. The claim skips a row while an earlier `PENDING`
|
|
18
|
+
* row of the same instance exists. `FAILED` rows do not block.
|
|
19
|
+
* - Claims never change `status`. A claim is a `claimed_at` stamp, taken with
|
|
20
|
+
* `FOR UPDATE SKIP LOCKED`, so several replicas can scan at once.
|
|
21
|
+
* - Write-back is conditional on `claimed_at`. A worker that lost its claim
|
|
22
|
+
* records nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare class InstanceEventDeliveryService {
|
|
25
|
+
private readonly repository;
|
|
26
|
+
private readonly events;
|
|
27
|
+
private readonly logger;
|
|
28
|
+
constructor(repository: Repository<ApprovalInstanceEventEntity>, events: InstanceEventService);
|
|
29
|
+
/**
|
|
30
|
+
* Claims and delivers due rows across all instances, up to `batchSize`.
|
|
31
|
+
* Returns the number of rows attempted. No-op without a bound observer.
|
|
32
|
+
*/
|
|
33
|
+
deliverDue(now?: Date): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Delivers the named rows. Used by the commit subscriber. A row whose earlier
|
|
36
|
+
* sibling is still pending is skipped here and picked up by the scheduler.
|
|
37
|
+
*/
|
|
38
|
+
deliverByIds(ids: readonly string[], now?: Date): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* Deletes `DELIVERED` rows older than `retentionDays`. `FAILED` rows are kept
|
|
41
|
+
* until an operator retries or removes them. Returns the number deleted.
|
|
42
|
+
*/
|
|
43
|
+
purgeDelivered(now?: Date): Promise<number>;
|
|
44
|
+
/**
|
|
45
|
+
* Admin read: newest first, optionally filtered. Not gated by `enabled`, so
|
|
46
|
+
* rows left behind by an earlier deployment stay visible.
|
|
47
|
+
*/
|
|
48
|
+
listEvents(input: ListInstanceEventsInput): Promise<{
|
|
49
|
+
readonly items: readonly ApprovalInstanceEventEntity[];
|
|
50
|
+
readonly total: number;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Admin retry of a `FAILED` row. Resets the attempt budget and makes the row
|
|
54
|
+
* due now; the next scan or commit kick delivers it. Only `FAILED` rows match,
|
|
55
|
+
* so a row that is already pending or delivered is rejected, not reset.
|
|
56
|
+
*/
|
|
57
|
+
retryFailedEvent(id: string, now?: Date): Promise<ApprovalInstanceEventEntity>;
|
|
58
|
+
/**
|
|
59
|
+
* `base * 2^(attempt - 1)` with ±20 % jitter, capped at `maxRetryDelayMs`.
|
|
60
|
+
* Same policy as the webhook outbox.
|
|
61
|
+
*/
|
|
62
|
+
readRetryDelay(attempt: number): number;
|
|
63
|
+
private claim;
|
|
64
|
+
private attemptSafely;
|
|
65
|
+
private attempt;
|
|
66
|
+
private dispatch;
|
|
67
|
+
private writeOutcome;
|
|
68
|
+
private readStaleClaimMs;
|
|
69
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var InstanceEventDeliveryService_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InstanceEventDeliveryService = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const typeorm_1 = require("@nestjs/typeorm");
|
|
8
|
+
const typeorm_2 = require("typeorm");
|
|
9
|
+
const outbox_1 = require("../common/outbox");
|
|
10
|
+
const approval_instance_event_entity_1 = require("./approval-instance-event.entity");
|
|
11
|
+
const instance_event_service_1 = require("./instance-event.service");
|
|
12
|
+
/** Dispatches in flight per claim, so one scan cannot saturate the host observer. */
|
|
13
|
+
const MAX_CONCURRENT_DISPATCHES = 5;
|
|
14
|
+
/** Admin list page size bounds; a larger request is clamped, not rejected. */
|
|
15
|
+
const MAX_PAGE_SIZE = 200;
|
|
16
|
+
/** Stored error text is capped; host error messages are not trusted to be short. */
|
|
17
|
+
const MAX_ERROR_LENGTH = 500;
|
|
18
|
+
/** A claim older than this is treated as abandoned and may be taken over. */
|
|
19
|
+
const MIN_STALE_CLAIM_MS = 60_000;
|
|
20
|
+
const DAY_MS = 86_400_000;
|
|
21
|
+
/**
|
|
22
|
+
* Delivers outbox rows to the host observer (ADR 27 §1.3).
|
|
23
|
+
*
|
|
24
|
+
* Guarantees, and how they are kept:
|
|
25
|
+
* - At least once. A row is `DELIVERED` only after the observer resolved; a
|
|
26
|
+
* crash between dispatch and write-back redelivers. Hosts dedupe on `id`.
|
|
27
|
+
* - Per-instance order by `seq`. The claim skips a row while an earlier `PENDING`
|
|
28
|
+
* row of the same instance exists. `FAILED` rows do not block.
|
|
29
|
+
* - Claims never change `status`. A claim is a `claimed_at` stamp, taken with
|
|
30
|
+
* `FOR UPDATE SKIP LOCKED`, so several replicas can scan at once.
|
|
31
|
+
* - Write-back is conditional on `claimed_at`. A worker that lost its claim
|
|
32
|
+
* records nothing.
|
|
33
|
+
*/
|
|
34
|
+
let InstanceEventDeliveryService = InstanceEventDeliveryService_1 = class InstanceEventDeliveryService {
|
|
35
|
+
constructor(repository, events) {
|
|
36
|
+
this.repository = repository;
|
|
37
|
+
this.events = events;
|
|
38
|
+
this.logger = new common_1.Logger(InstanceEventDeliveryService_1.name);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Claims and delivers due rows across all instances, up to `batchSize`.
|
|
42
|
+
* Returns the number of rows attempted. No-op without a bound observer.
|
|
43
|
+
*/
|
|
44
|
+
async deliverDue(now = new Date()) {
|
|
45
|
+
if (!this.events.enabled) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const { batchSize } = this.events.delivery;
|
|
49
|
+
let attempted = 0;
|
|
50
|
+
while (attempted < batchSize) {
|
|
51
|
+
const rows = await this.claim({
|
|
52
|
+
ids: null,
|
|
53
|
+
limit: Math.min(MAX_CONCURRENT_DISPATCHES, batchSize - attempted),
|
|
54
|
+
now,
|
|
55
|
+
});
|
|
56
|
+
if (rows.length === 0) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
attempted += rows.length;
|
|
60
|
+
await Promise.all(rows.map((row) => this.attemptSafely(row, now)));
|
|
61
|
+
}
|
|
62
|
+
return attempted;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Delivers the named rows. Used by the commit subscriber. A row whose earlier
|
|
66
|
+
* sibling is still pending is skipped here and picked up by the scheduler.
|
|
67
|
+
*/
|
|
68
|
+
async deliverByIds(ids, now = new Date()) {
|
|
69
|
+
if (!this.events.enabled || ids.length === 0) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
const rows = await this.claim({
|
|
73
|
+
ids: [...new Set(ids)],
|
|
74
|
+
limit: ids.length,
|
|
75
|
+
now,
|
|
76
|
+
});
|
|
77
|
+
await Promise.all(rows.map((row) => this.attemptSafely(row, now)));
|
|
78
|
+
return rows.length;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Deletes `DELIVERED` rows older than `retentionDays`. `FAILED` rows are kept
|
|
82
|
+
* until an operator retries or removes them. Returns the number deleted.
|
|
83
|
+
*/
|
|
84
|
+
async purgeDelivered(now = new Date()) {
|
|
85
|
+
const cutoff = new Date(now.getTime() - this.events.delivery.retentionDays * DAY_MS);
|
|
86
|
+
const result = await this.repository.delete({
|
|
87
|
+
deliveredAt: (0, typeorm_2.LessThan)(cutoff),
|
|
88
|
+
status: 'DELIVERED',
|
|
89
|
+
});
|
|
90
|
+
return result.affected ?? 0;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Admin read: newest first, optionally filtered. Not gated by `enabled`, so
|
|
94
|
+
* rows left behind by an earlier deployment stay visible.
|
|
95
|
+
*/
|
|
96
|
+
async listEvents(input) {
|
|
97
|
+
const pageSize = clampPageSize(input.pageSize);
|
|
98
|
+
const page = Math.max(1, Math.floor(input.page));
|
|
99
|
+
const [items, total] = await this.repository.findAndCount({
|
|
100
|
+
order: { seq: 'DESC' },
|
|
101
|
+
skip: (page - 1) * pageSize,
|
|
102
|
+
take: pageSize,
|
|
103
|
+
where: {
|
|
104
|
+
...(input.instanceId ? { instanceId: input.instanceId } : {}),
|
|
105
|
+
...(input.status ? { status: input.status } : {}),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return { items, total };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Admin retry of a `FAILED` row. Resets the attempt budget and makes the row
|
|
112
|
+
* due now; the next scan or commit kick delivers it. Only `FAILED` rows match,
|
|
113
|
+
* so a row that is already pending or delivered is rejected, not reset.
|
|
114
|
+
*/
|
|
115
|
+
async retryFailedEvent(id, now = new Date()) {
|
|
116
|
+
const existing = await this.repository.findOne({ where: { id } });
|
|
117
|
+
if (!existing) {
|
|
118
|
+
throw new common_1.NotFoundException(`Instance event ${id} not found`);
|
|
119
|
+
}
|
|
120
|
+
const result = await this.repository.update({ id, status: 'FAILED' }, {
|
|
121
|
+
attemptCount: 0,
|
|
122
|
+
lastError: null,
|
|
123
|
+
nextAttemptAt: now,
|
|
124
|
+
status: 'PENDING',
|
|
125
|
+
});
|
|
126
|
+
if (!result.affected) {
|
|
127
|
+
throw new common_1.BadRequestException(`Instance event ${id} is not FAILED and cannot be retried`);
|
|
128
|
+
}
|
|
129
|
+
const refreshed = await this.repository.findOne({ where: { id } });
|
|
130
|
+
if (!refreshed) {
|
|
131
|
+
throw new common_1.NotFoundException(`Instance event ${id} not found`);
|
|
132
|
+
}
|
|
133
|
+
return refreshed;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* `base * 2^(attempt - 1)` with ±20 % jitter, capped at `maxRetryDelayMs`.
|
|
137
|
+
* Same policy as the webhook outbox.
|
|
138
|
+
*/
|
|
139
|
+
readRetryDelay(attempt) {
|
|
140
|
+
const { maxRetryDelayMs, retryBaseDelayMs } = this.events.delivery;
|
|
141
|
+
const exponential = retryBaseDelayMs * 2 ** Math.min(Math.max(attempt - 1, 0), 30);
|
|
142
|
+
return Math.min(Math.round(exponential * (0.8 + Math.random() * 0.4)), maxRetryDelayMs);
|
|
143
|
+
}
|
|
144
|
+
async claim({ ids, limit, now, }) {
|
|
145
|
+
const staleBefore = new Date(now.getTime() - this.readStaleClaimMs());
|
|
146
|
+
return this.repository.manager.transaction(async (manager) => {
|
|
147
|
+
const claimedRows = (await manager.query(`
|
|
148
|
+
UPDATE approval_instance_events
|
|
149
|
+
SET claimed_at = $1
|
|
150
|
+
WHERE id IN (
|
|
151
|
+
SELECT e.id
|
|
152
|
+
FROM approval_instance_events e
|
|
153
|
+
WHERE e.status = $2
|
|
154
|
+
AND e.next_attempt_at <= $1
|
|
155
|
+
AND (e.claimed_at IS NULL OR e.claimed_at <= $3)
|
|
156
|
+
AND ($5::uuid[] IS NULL OR e.id = ANY($5::uuid[]))
|
|
157
|
+
AND NOT EXISTS (
|
|
158
|
+
SELECT 1
|
|
159
|
+
FROM approval_instance_events earlier
|
|
160
|
+
WHERE earlier.instance_id = e.instance_id
|
|
161
|
+
AND earlier.seq < e.seq
|
|
162
|
+
AND earlier.status = $2
|
|
163
|
+
)
|
|
164
|
+
ORDER BY e.seq ASC
|
|
165
|
+
FOR UPDATE SKIP LOCKED
|
|
166
|
+
LIMIT $4
|
|
167
|
+
)
|
|
168
|
+
RETURNING id
|
|
169
|
+
`, [now, 'PENDING', staleBefore, limit, ids ? [...ids] : null]));
|
|
170
|
+
const claimedIds = (0, outbox_1.readClaimedIds)(claimedRows);
|
|
171
|
+
if (claimedIds.length === 0) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const found = await manager
|
|
175
|
+
.getRepository(approval_instance_event_entity_1.ApprovalInstanceEventEntity)
|
|
176
|
+
.find({ where: { id: (0, typeorm_2.In)([...claimedIds]) } });
|
|
177
|
+
return [...found].sort((left, right) => Number(left.seq) - Number(right.seq));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async attemptSafely(row, claimedAt) {
|
|
181
|
+
try {
|
|
182
|
+
await this.attempt(row, claimedAt);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.logger.warn(`Instance event ${row.id} could not record its outcome (${readErrorName(error)})`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async attempt(row, claimedAt) {
|
|
189
|
+
const observer = this.events.observer;
|
|
190
|
+
if (observer === null) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const { dispatchTimeoutMs, maxAttempts } = this.events.delivery;
|
|
194
|
+
const attemptCount = row.attemptCount + 1;
|
|
195
|
+
const outcome = await (0, outbox_1.withDispatchTimeout)(this.dispatch(observer, row), dispatchTimeoutMs).then(() => ({ kind: 'DELIVERED' }), (error) => ({ error, kind: 'FAILED' }));
|
|
196
|
+
if (outcome.kind === 'DELIVERED') {
|
|
197
|
+
await this.writeOutcome(row, claimedAt, {
|
|
198
|
+
attemptCount,
|
|
199
|
+
claimedAt: null,
|
|
200
|
+
deliveredAt: new Date(),
|
|
201
|
+
lastError: null,
|
|
202
|
+
status: 'DELIVERED',
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const terminal = attemptCount >= maxAttempts;
|
|
207
|
+
await this.writeOutcome(row, claimedAt, {
|
|
208
|
+
attemptCount,
|
|
209
|
+
claimedAt: null,
|
|
210
|
+
lastError: readErrorText(outcome.error),
|
|
211
|
+
nextAttemptAt: terminal
|
|
212
|
+
? row.nextAttemptAt
|
|
213
|
+
: new Date(Date.now() + this.readRetryDelay(attemptCount)),
|
|
214
|
+
status: terminal ? 'FAILED' : 'PENDING',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async dispatch(observer, row) {
|
|
218
|
+
await observer.onInstanceEvent(toObserverEvent(row));
|
|
219
|
+
}
|
|
220
|
+
async writeOutcome(row, claimedAt, changes) {
|
|
221
|
+
const result = await this.repository.update({ claimedAt, id: row.id }, changes);
|
|
222
|
+
if (!result.affected) {
|
|
223
|
+
this.logger.warn(`Instance event ${row.id} lost its claim before the outcome was recorded`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
readStaleClaimMs() {
|
|
227
|
+
return Math.max(this.events.delivery.dispatchTimeoutMs * 3, MIN_STALE_CLAIM_MS);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
exports.InstanceEventDeliveryService = InstanceEventDeliveryService;
|
|
231
|
+
exports.InstanceEventDeliveryService = InstanceEventDeliveryService = InstanceEventDeliveryService_1 = tslib_1.__decorate([
|
|
232
|
+
(0, common_1.Injectable)(),
|
|
233
|
+
tslib_1.__param(0, (0, typeorm_1.InjectRepository)(approval_instance_event_entity_1.ApprovalInstanceEventEntity)),
|
|
234
|
+
tslib_1.__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
235
|
+
instance_event_service_1.InstanceEventService])
|
|
236
|
+
], InstanceEventDeliveryService);
|
|
237
|
+
/** Reads a stored row as the observer sees it. Payloads never hold `formData`. */
|
|
238
|
+
function toObserverEvent(row) {
|
|
239
|
+
return {
|
|
240
|
+
...row.payload,
|
|
241
|
+
id: row.id,
|
|
242
|
+
instanceId: row.instanceId,
|
|
243
|
+
occurredAt: row.occurredAt.toISOString(),
|
|
244
|
+
seq: row.seq,
|
|
245
|
+
type: row.type,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function clampPageSize(pageSize) {
|
|
249
|
+
return Math.min(Math.max(Math.floor(pageSize), 1), MAX_PAGE_SIZE);
|
|
250
|
+
}
|
|
251
|
+
function readErrorName(error) {
|
|
252
|
+
return error instanceof Error ? error.name : 'UnknownError';
|
|
253
|
+
}
|
|
254
|
+
function readErrorText(error) {
|
|
255
|
+
const name = readErrorName(error);
|
|
256
|
+
const message = error instanceof Error ? error.message : '';
|
|
257
|
+
return `${name}: ${message}`.slice(0, MAX_ERROR_LENGTH);
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=instance-event.delivery.service.js.map
|