@shipfox/api-triggers 2.0.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/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +224 -0
- package/LICENSE +21 -0
- package/README.md +300 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/core/compute-next-fire-at.d.ts +9 -0
- package/dist/core/compute-next-fire-at.d.ts.map +1 -0
- package/dist/core/compute-next-fire-at.js +28 -0
- package/dist/core/compute-next-fire-at.js.map +1 -0
- package/dist/core/config.d.ts +26 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +68 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/dispatch-integration-event.d.ts +14 -0
- package/dist/core/dispatch-integration-event.d.ts.map +1 -0
- package/dist/core/dispatch-integration-event.js +136 -0
- package/dist/core/dispatch-integration-event.js.map +1 -0
- package/dist/core/drain-cron-schedules.d.ts +30 -0
- package/dist/core/drain-cron-schedules.d.ts.map +1 -0
- package/dist/core/drain-cron-schedules.js +76 -0
- package/dist/core/drain-cron-schedules.js.map +1 -0
- package/dist/core/entities/cron-schedule.d.ts +11 -0
- package/dist/core/entities/cron-schedule.d.ts.map +1 -0
- package/dist/core/entities/cron-schedule.js +3 -0
- package/dist/core/entities/cron-schedule.js.map +1 -0
- package/dist/core/entities/decision.d.ts +23 -0
- package/dist/core/entities/decision.d.ts.map +1 -0
- package/dist/core/entities/decision.js +7 -0
- package/dist/core/entities/decision.js.map +1 -0
- package/dist/core/entities/index.d.ts +3 -0
- package/dist/core/entities/index.d.ts.map +1 -0
- package/dist/core/entities/index.js +3 -0
- package/dist/core/entities/index.js.map +1 -0
- package/dist/core/entities/job-listener-subscription.d.ts +14 -0
- package/dist/core/entities/job-listener-subscription.d.ts.map +1 -0
- package/dist/core/entities/job-listener-subscription.js +3 -0
- package/dist/core/entities/job-listener-subscription.js.map +1 -0
- package/dist/core/entities/received-event.d.ts +28 -0
- package/dist/core/entities/received-event.d.ts.map +1 -0
- package/dist/core/entities/received-event.js +14 -0
- package/dist/core/entities/received-event.js.map +1 -0
- package/dist/core/entities/subscription.d.ts +13 -0
- package/dist/core/entities/subscription.d.ts.map +1 -0
- package/dist/core/entities/subscription.js +3 -0
- package/dist/core/entities/subscription.js.map +1 -0
- package/dist/core/errors.d.ts +25 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +41 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fire-cron.d.ts +28 -0
- package/dist/core/fire-cron.d.ts.map +1 -0
- package/dist/core/fire-cron.js +85 -0
- package/dist/core/fire-cron.js.map +1 -0
- package/dist/core/fire-manual.d.ts +9 -0
- package/dist/core/fire-manual.d.ts.map +1 -0
- package/dist/core/fire-manual.js +87 -0
- package/dist/core/fire-manual.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/record-trigger-history.d.ts +35 -0
- package/dist/core/record-trigger-history.d.ts.map +1 -0
- package/dist/core/record-trigger-history.js +75 -0
- package/dist/core/record-trigger-history.js.map +1 -0
- package/dist/core/route-event-to-job-listeners.d.ts +31 -0
- package/dist/core/route-event-to-job-listeners.d.ts.map +1 -0
- package/dist/core/route-event-to-job-listeners.js +140 -0
- package/dist/core/route-event-to-job-listeners.js.map +1 -0
- package/dist/db/cron-schedules.d.ts +36 -0
- package/dist/db/cron-schedules.d.ts.map +1 -0
- package/dist/db/cron-schedules.js +104 -0
- package/dist/db/cron-schedules.js.map +1 -0
- package/dist/db/db.d.ts +2515 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +28 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/event-history.d.ts +50 -0
- package/dist/db/event-history.d.ts.map +1 -0
- package/dist/db/event-history.js +207 -0
- package/dist/db/event-history.js.map +1 -0
- package/dist/db/event-queries.d.ts +38 -0
- package/dist/db/event-queries.d.ts.map +1 -0
- package/dist/db/event-queries.js +78 -0
- package/dist/db/event-queries.js.map +1 -0
- package/dist/db/index.d.ts +11 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +14 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/job-listener-subscriptions.d.ts +24 -0
- package/dist/db/job-listener-subscriptions.d.ts.map +1 -0
- package/dist/db/job-listener-subscriptions.js +69 -0
- package/dist/db/job-listener-subscriptions.js.map +1 -0
- package/dist/db/received-events.d.ts +7 -0
- package/dist/db/received-events.d.ts.map +1 -0
- package/dist/db/received-events.js +11 -0
- package/dist/db/received-events.js.map +1 -0
- package/dist/db/schema/common.d.ts +2 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +4 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/cron-schedules.d.ts +148 -0
- package/dist/db/schema/cron-schedules.d.ts.map +1 -0
- package/dist/db/schema/cron-schedules.js +39 -0
- package/dist/db/schema/cron-schedules.js.map +1 -0
- package/dist/db/schema/decisions.d.ts +284 -0
- package/dist/db/schema/decisions.d.ts.map +1 -0
- package/dist/db/schema/decisions.js +70 -0
- package/dist/db/schema/decisions.js.map +1 -0
- package/dist/db/schema/job-listener-subscriptions.d.ts +185 -0
- package/dist/db/schema/job-listener-subscriptions.d.ts.map +1 -0
- package/dist/db/schema/job-listener-subscriptions.js +41 -0
- package/dist/db/schema/job-listener-subscriptions.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/schema/received-events.d.ts +544 -0
- package/dist/db/schema/received-events.d.ts.map +1 -0
- package/dist/db/schema/received-events.js +86 -0
- package/dist/db/schema/received-events.js.map +1 -0
- package/dist/db/schema/subscriptions.d.ts +182 -0
- package/dist/db/schema/subscriptions.d.ts.map +1 -0
- package/dist/db/schema/subscriptions.js +39 -0
- package/dist/db/schema/subscriptions.js.map +1 -0
- package/dist/db/subscriptions.d.ts +26 -0
- package/dist/db/subscriptions.d.ts.map +1 -0
- package/dist/db/subscriptions.js +103 -0
- package/dist/db/subscriptions.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +15 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +32 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +15 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/trigger-events.d.ts +7 -0
- package/dist/presentation/dto/trigger-events.d.ts.map +1 -0
- package/dist/presentation/dto/trigger-events.js +47 -0
- package/dist/presentation/dto/trigger-events.js.map +1 -0
- package/dist/presentation/index.d.ts +3 -0
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +4 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/presentation/routes/fire-manual.d.ts +2 -0
- package/dist/presentation/routes/fire-manual.d.ts.map +1 -0
- package/dist/presentation/routes/fire-manual.js +71 -0
- package/dist/presentation/routes/fire-manual.js.map +1 -0
- package/dist/presentation/routes/get-trigger-event.d.ts +2 -0
- package/dist/presentation/routes/get-trigger-event.d.ts.map +1 -0
- package/dist/presentation/routes/get-trigger-event.js +37 -0
- package/dist/presentation/routes/get-trigger-event.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +27 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/list-trigger-event-facets.d.ts +2 -0
- package/dist/presentation/routes/list-trigger-event-facets.d.ts.map +1 -0
- package/dist/presentation/routes/list-trigger-event-facets.js +29 -0
- package/dist/presentation/routes/list-trigger-event-facets.js.map +1 -0
- package/dist/presentation/routes/list-trigger-events.d.ts +2 -0
- package/dist/presentation/routes/list-trigger-events.d.ts.map +1 -0
- package/dist/presentation/routes/list-trigger-events.js +56 -0
- package/dist/presentation/routes/list-trigger-events.js.map +1 -0
- package/dist/presentation/subscribers/index.d.ts +6 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -0
- package/dist/presentation/subscribers/index.js +7 -0
- package/dist/presentation/subscribers/index.js.map +1 -0
- package/dist/presentation/subscribers/on-definition-deleted.d.ts +3 -0
- package/dist/presentation/subscribers/on-definition-deleted.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-definition-deleted.js +8 -0
- package/dist/presentation/subscribers/on-definition-deleted.js.map +1 -0
- package/dist/presentation/subscribers/on-definition-resolved.d.ts +3 -0
- package/dist/presentation/subscribers/on-definition-resolved.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-definition-resolved.js +11 -0
- package/dist/presentation/subscribers/on-definition-resolved.js.map +1 -0
- package/dist/presentation/subscribers/on-integration-event-received.d.ts +4 -0
- package/dist/presentation/subscribers/on-integration-event-received.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-integration-event-received.js +17 -0
- package/dist/presentation/subscribers/on-integration-event-received.js.map +1 -0
- package/dist/presentation/subscribers/on-job-activated.d.ts +3 -0
- package/dist/presentation/subscribers/on-job-activated.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-job-activated.js +13 -0
- package/dist/presentation/subscribers/on-job-activated.js.map +1 -0
- package/dist/presentation/subscribers/on-job-terminated.d.ts +3 -0
- package/dist/presentation/subscribers/on-job-terminated.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-job-terminated.js +6 -0
- package/dist/presentation/subscribers/on-job-terminated.js.map +1 -0
- package/dist/temporal/activities/drain-cron-batch.d.ts +4 -0
- package/dist/temporal/activities/drain-cron-batch.d.ts.map +1 -0
- package/dist/temporal/activities/drain-cron-batch.js +20 -0
- package/dist/temporal/activities/drain-cron-batch.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +10 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +15 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/prune-trigger-events.d.ts +4 -0
- package/dist/temporal/activities/prune-trigger-events.d.ts.map +1 -0
- package/dist/temporal/activities/prune-trigger-events.js +11 -0
- package/dist/temporal/activities/prune-trigger-events.js.map +1 -0
- package/dist/temporal/constants.d.ts +7 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +7 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/workflows/cron-tick-cron.d.ts +9 -0
- package/dist/temporal/workflows/cron-tick-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/cron-tick-cron.js +54 -0
- package/dist/temporal/workflows/cron-tick-cron.js.map +1 -0
- package/dist/temporal/workflows/index.d.ts +3 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +4 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/prune-trigger-events-cron.d.ts +2 -0
- package/dist/temporal/workflows/prune-trigger-events-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/prune-trigger-events-cron.js +18 -0
- package/dist/temporal/workflows/prune-trigger-events-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +109 -0
- package/drizzle/meta/0000_snapshot.json +905 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +14 -0
- package/package.json +83 -0
- package/src/config.test.ts +33 -0
- package/src/config.ts +53 -0
- package/src/core/compute-next-fire-at.test.ts +69 -0
- package/src/core/compute-next-fire-at.ts +37 -0
- package/src/core/config.test.ts +213 -0
- package/src/core/config.ts +86 -0
- package/src/core/dispatch-integration-event.history-failure.test.ts +66 -0
- package/src/core/dispatch-integration-event.test.ts +1006 -0
- package/src/core/dispatch-integration-event.ts +150 -0
- package/src/core/drain-cron-schedules.test.ts +265 -0
- package/src/core/drain-cron-schedules.ts +90 -0
- package/src/core/entities/cron-schedule.ts +10 -0
- package/src/core/entities/decision.ts +24 -0
- package/src/core/entities/index.ts +5 -0
- package/src/core/entities/job-listener-subscription.ts +14 -0
- package/src/core/entities/received-event.ts +36 -0
- package/src/core/entities/subscription.ts +12 -0
- package/src/core/errors.ts +63 -0
- package/src/core/fire-cron.test.ts +146 -0
- package/src/core/fire-cron.ts +98 -0
- package/src/core/fire-manual.test.ts +157 -0
- package/src/core/fire-manual.ts +92 -0
- package/src/core/index.ts +34 -0
- package/src/core/record-trigger-history.test.ts +159 -0
- package/src/core/record-trigger-history.ts +145 -0
- package/src/core/route-event-to-job-listeners.test.ts +369 -0
- package/src/core/route-event-to-job-listeners.ts +184 -0
- package/src/db/cron-schedules.test.ts +358 -0
- package/src/db/cron-schedules.ts +163 -0
- package/src/db/db.ts +31 -0
- package/src/db/event-history.test.ts +421 -0
- package/src/db/event-history.ts +279 -0
- package/src/db/event-queries.ts +140 -0
- package/src/db/index.ts +51 -0
- package/src/db/job-listener-subscriptions.test.ts +270 -0
- package/src/db/job-listener-subscriptions.ts +133 -0
- package/src/db/received-events.ts +16 -0
- package/src/db/schema/common.ts +3 -0
- package/src/db/schema/cron-schedules.ts +37 -0
- package/src/db/schema/decisions.test.ts +279 -0
- package/src/db/schema/decisions.ts +77 -0
- package/src/db/schema/job-listener-subscriptions.ts +65 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/schema/received-events.test.ts +135 -0
- package/src/db/schema/received-events.ts +93 -0
- package/src/db/schema/subscriptions.ts +46 -0
- package/src/db/subscriptions.test.ts +340 -0
- package/src/db/subscriptions.ts +179 -0
- package/src/index.ts +108 -0
- package/src/metrics/index.ts +8 -0
- package/src/metrics/instance.ts +33 -0
- package/src/metrics/service.ts +18 -0
- package/src/presentation/dto/trigger-events.test.ts +144 -0
- package/src/presentation/dto/trigger-events.ts +60 -0
- package/src/presentation/index.ts +6 -0
- package/src/presentation/routes/fire-manual.test.ts +118 -0
- package/src/presentation/routes/fire-manual.ts +70 -0
- package/src/presentation/routes/get-trigger-event.test.ts +184 -0
- package/src/presentation/routes/get-trigger-event.ts +37 -0
- package/src/presentation/routes/index.ts +21 -0
- package/src/presentation/routes/list-trigger-event-facets.test.ts +116 -0
- package/src/presentation/routes/list-trigger-event-facets.ts +29 -0
- package/src/presentation/routes/list-trigger-events.test.ts +379 -0
- package/src/presentation/routes/list-trigger-events.ts +68 -0
- package/src/presentation/subscribers/index.ts +5 -0
- package/src/presentation/subscribers/on-definition-deleted.test.ts +42 -0
- package/src/presentation/subscribers/on-definition-deleted.ts +6 -0
- package/src/presentation/subscribers/on-definition-resolved.test.ts +67 -0
- package/src/presentation/subscribers/on-definition-resolved.ts +11 -0
- package/src/presentation/subscribers/on-integration-event-received.test.ts +53 -0
- package/src/presentation/subscribers/on-integration-event-received.ts +21 -0
- package/src/presentation/subscribers/on-job-activated.ts +14 -0
- package/src/presentation/subscribers/on-job-terminated.ts +6 -0
- package/src/temporal/activities/drain-cron-batch.ts +20 -0
- package/src/temporal/activities/index.ts +15 -0
- package/src/temporal/activities/prune-trigger-events.test.ts +78 -0
- package/src/temporal/activities/prune-trigger-events.ts +9 -0
- package/src/temporal/constants.ts +7 -0
- package/src/temporal/workflows/cron-tick-cron.ts +56 -0
- package/src/temporal/workflows/index.ts +2 -0
- package/src/temporal/workflows/prune-trigger-events-cron.ts +17 -0
- package/test/env.ts +8 -0
- package/test/factories/cron-schedule.ts +34 -0
- package/test/factories/decision.ts +49 -0
- package/test/factories/job-listener-subscription.ts +42 -0
- package/test/factories/received-event.ts +49 -0
- package/test/factories/subscription.ts +38 -0
- package/test/globalSetup.ts +20 -0
- package/test/index.ts +5 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import {eq} from 'drizzle-orm';
|
|
2
|
+
import type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';
|
|
3
|
+
import type {TriggerSubscription} from '#core/entities/subscription.js';
|
|
4
|
+
import {db} from './db.js';
|
|
5
|
+
import {
|
|
6
|
+
type InsertReceivedEventParams,
|
|
7
|
+
insertReceivedEvent,
|
|
8
|
+
markReceivedEventDiscarded,
|
|
9
|
+
markReceivedEventErrored,
|
|
10
|
+
markReceivedEventFailed,
|
|
11
|
+
markReceivedEventRouted,
|
|
12
|
+
upsertDispatchErrorDecision,
|
|
13
|
+
upsertFilterErrorDecision,
|
|
14
|
+
upsertListenerDispatchErrorDecision,
|
|
15
|
+
upsertListenerFilterErrorDecision,
|
|
16
|
+
upsertListenerTriggeredDecision,
|
|
17
|
+
upsertTriggeredDecision,
|
|
18
|
+
} from './event-history.js';
|
|
19
|
+
import {triggersDecisions} from './schema/decisions.js';
|
|
20
|
+
import {triggersReceivedEvents} from './schema/received-events.js';
|
|
21
|
+
|
|
22
|
+
function buildEventParams(
|
|
23
|
+
overrides: Partial<InsertReceivedEventParams> = {},
|
|
24
|
+
): InsertReceivedEventParams {
|
|
25
|
+
return {
|
|
26
|
+
eventRef: crypto.randomUUID(),
|
|
27
|
+
origin: 'integration',
|
|
28
|
+
workspaceId: crypto.randomUUID(),
|
|
29
|
+
provider: 'github',
|
|
30
|
+
source: 'github',
|
|
31
|
+
event: 'push',
|
|
32
|
+
deliveryId: null,
|
|
33
|
+
connectionId: null,
|
|
34
|
+
connectionName: null,
|
|
35
|
+
payload: null,
|
|
36
|
+
receivedAt: new Date(),
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildSubscription(overrides: Partial<TriggerSubscription> = {}): TriggerSubscription {
|
|
42
|
+
return {
|
|
43
|
+
id: crypto.randomUUID(),
|
|
44
|
+
workspaceId: crypto.randomUUID(),
|
|
45
|
+
projectId: crypto.randomUUID(),
|
|
46
|
+
workflowDefinitionId: crypto.randomUUID(),
|
|
47
|
+
name: 'trigger',
|
|
48
|
+
source: 'github',
|
|
49
|
+
event: 'push',
|
|
50
|
+
config: {},
|
|
51
|
+
createdAt: new Date(),
|
|
52
|
+
updatedAt: new Date(),
|
|
53
|
+
...overrides,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function buildListenerSubscription(
|
|
58
|
+
overrides: Partial<JobListenerSubscription> = {},
|
|
59
|
+
): JobListenerSubscription {
|
|
60
|
+
return {
|
|
61
|
+
id: crypto.randomUUID(),
|
|
62
|
+
workspaceId: crypto.randomUUID(),
|
|
63
|
+
workflowRunId: crypto.randomUUID(),
|
|
64
|
+
jobId: crypto.randomUUID(),
|
|
65
|
+
kind: 'on',
|
|
66
|
+
matcherOrdinal: 0,
|
|
67
|
+
source: 'github',
|
|
68
|
+
event: 'push',
|
|
69
|
+
config: {},
|
|
70
|
+
createdAt: new Date(),
|
|
71
|
+
...overrides,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function decisionsFor(receivedEventId: string) {
|
|
76
|
+
return db()
|
|
77
|
+
.select()
|
|
78
|
+
.from(triggersDecisions)
|
|
79
|
+
.where(eq(triggersDecisions.receivedEventId, receivedEventId));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe('insertReceivedEvent', () => {
|
|
83
|
+
it('inserts a received event and returns its id', async () => {
|
|
84
|
+
const params = buildEventParams();
|
|
85
|
+
|
|
86
|
+
const id = await insertReceivedEvent(params);
|
|
87
|
+
|
|
88
|
+
const [row] = await db()
|
|
89
|
+
.select()
|
|
90
|
+
.from(triggersReceivedEvents)
|
|
91
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
92
|
+
expect(row?.eventRef).toBe(params.eventRef);
|
|
93
|
+
expect(row?.provider).toBe('github');
|
|
94
|
+
expect(row?.outcome).toBe('received');
|
|
95
|
+
expect(row?.matchedCount).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('returns the existing id on event_ref conflict (replay)', async () => {
|
|
99
|
+
const params = buildEventParams();
|
|
100
|
+
|
|
101
|
+
const first = await insertReceivedEvent(params);
|
|
102
|
+
const second = await insertReceivedEvent(params);
|
|
103
|
+
|
|
104
|
+
expect(second).toBe(first);
|
|
105
|
+
const rows = await db()
|
|
106
|
+
.select()
|
|
107
|
+
.from(triggersReceivedEvents)
|
|
108
|
+
.where(eq(triggersReceivedEvents.eventRef, params.eventRef));
|
|
109
|
+
expect(rows).toHaveLength(1);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('received-event outcome transitions', () => {
|
|
114
|
+
it('marks an event routed with matched_count and processed_at', async () => {
|
|
115
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
116
|
+
|
|
117
|
+
await markReceivedEventRouted(id, 3);
|
|
118
|
+
|
|
119
|
+
const [row] = await db()
|
|
120
|
+
.select()
|
|
121
|
+
.from(triggersReceivedEvents)
|
|
122
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
123
|
+
expect(row?.outcome).toBe('routed');
|
|
124
|
+
expect(row?.matchedCount).toBe(3);
|
|
125
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('marks an event discarded with matched_count 0', async () => {
|
|
129
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
130
|
+
|
|
131
|
+
await markReceivedEventDiscarded(id);
|
|
132
|
+
|
|
133
|
+
const [row] = await db()
|
|
134
|
+
.select()
|
|
135
|
+
.from(triggersReceivedEvents)
|
|
136
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
137
|
+
expect(row?.outcome).toBe('discarded');
|
|
138
|
+
expect(row?.matchedCount).toBe(0);
|
|
139
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('does not downgrade a routed event to discarded', async () => {
|
|
143
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
144
|
+
await markReceivedEventRouted(id, 1);
|
|
145
|
+
|
|
146
|
+
await markReceivedEventDiscarded(id);
|
|
147
|
+
|
|
148
|
+
const [row] = await db()
|
|
149
|
+
.select()
|
|
150
|
+
.from(triggersReceivedEvents)
|
|
151
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
152
|
+
expect(row?.outcome).toBe('routed');
|
|
153
|
+
expect(row?.matchedCount).toBe(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('marks an event failed without setting processed_at (transient, retried)', async () => {
|
|
157
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
158
|
+
|
|
159
|
+
await markReceivedEventFailed(id, 2);
|
|
160
|
+
|
|
161
|
+
const [row] = await db()
|
|
162
|
+
.select()
|
|
163
|
+
.from(triggersReceivedEvents)
|
|
164
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
165
|
+
expect(row?.outcome).toBe('failed');
|
|
166
|
+
expect(row?.matchedCount).toBe(2);
|
|
167
|
+
expect(row?.processedAt).toBeNull();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('does not downgrade a routed event to failed (terminal success wins under a stale failed write)', async () => {
|
|
171
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
172
|
+
await markReceivedEventRouted(id, 2);
|
|
173
|
+
|
|
174
|
+
await markReceivedEventFailed(id, 2);
|
|
175
|
+
|
|
176
|
+
const [row] = await db()
|
|
177
|
+
.select()
|
|
178
|
+
.from(triggersReceivedEvents)
|
|
179
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
180
|
+
expect(row?.outcome).toBe('routed');
|
|
181
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('marks an event errored with processed_at when no run was created', async () => {
|
|
185
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
186
|
+
|
|
187
|
+
await markReceivedEventErrored(id, 2);
|
|
188
|
+
|
|
189
|
+
const [row] = await db()
|
|
190
|
+
.select()
|
|
191
|
+
.from(triggersReceivedEvents)
|
|
192
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
193
|
+
expect(row?.outcome).toBe('errored');
|
|
194
|
+
expect(row?.matchedCount).toBe(2);
|
|
195
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('promotes to routed instead of errored when a triggered decision exists (cross-attempt run)', async () => {
|
|
199
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
200
|
+
await upsertTriggeredDecision({
|
|
201
|
+
receivedEventId: id,
|
|
202
|
+
subscription: buildSubscription(),
|
|
203
|
+
run: {id: crypto.randomUUID(), name: 'Build and test'},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await markReceivedEventErrored(id, 2);
|
|
207
|
+
|
|
208
|
+
const [row] = await db()
|
|
209
|
+
.select()
|
|
210
|
+
.from(triggersReceivedEvents)
|
|
211
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
212
|
+
expect(row?.outcome).toBe('routed');
|
|
213
|
+
expect(row?.matchedCount).toBe(2);
|
|
214
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('does not downgrade a routed event to errored', async () => {
|
|
218
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
219
|
+
await markReceivedEventRouted(id, 1);
|
|
220
|
+
|
|
221
|
+
await markReceivedEventErrored(id, 1);
|
|
222
|
+
|
|
223
|
+
const [row] = await db()
|
|
224
|
+
.select()
|
|
225
|
+
.from(triggersReceivedEvents)
|
|
226
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
227
|
+
expect(row?.outcome).toBe('routed');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('does not downgrade an errored event to failed (a late transient write loses)', async () => {
|
|
231
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
232
|
+
await markReceivedEventErrored(id, 1);
|
|
233
|
+
|
|
234
|
+
await markReceivedEventFailed(id, 1);
|
|
235
|
+
|
|
236
|
+
const [row] = await db()
|
|
237
|
+
.select()
|
|
238
|
+
.from(triggersReceivedEvents)
|
|
239
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
240
|
+
expect(row?.outcome).toBe('errored');
|
|
241
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('promotes a transient failed event to errored on the all-permanent replay', async () => {
|
|
245
|
+
const id = await insertReceivedEvent(buildEventParams());
|
|
246
|
+
await markReceivedEventFailed(id, 2);
|
|
247
|
+
|
|
248
|
+
await markReceivedEventErrored(id, 2);
|
|
249
|
+
|
|
250
|
+
const [row] = await db()
|
|
251
|
+
.select()
|
|
252
|
+
.from(triggersReceivedEvents)
|
|
253
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
254
|
+
expect(row?.outcome).toBe('errored');
|
|
255
|
+
expect(row?.matchedCount).toBe(2);
|
|
256
|
+
expect(row?.processedAt).toBeInstanceOf(Date);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe('decision upserts', () => {
|
|
261
|
+
it('is idempotent on (received_event_id, subscription_id)', async () => {
|
|
262
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
263
|
+
const subscription = buildSubscription();
|
|
264
|
+
const renamedSubscription = buildSubscription({...subscription, name: 'renamed trigger'});
|
|
265
|
+
const run = {id: crypto.randomUUID(), name: 'Build and test'};
|
|
266
|
+
|
|
267
|
+
await upsertTriggeredDecision({receivedEventId, subscription, run});
|
|
268
|
+
await upsertTriggeredDecision({receivedEventId, subscription: renamedSubscription, run});
|
|
269
|
+
|
|
270
|
+
const rows = await decisionsFor(receivedEventId);
|
|
271
|
+
expect(rows).toHaveLength(1);
|
|
272
|
+
expect(rows[0]?.subscriptionKind).toBe('trigger');
|
|
273
|
+
expect(rows[0]?.decision).toBe('triggered');
|
|
274
|
+
expect(rows[0]?.subscriptionName).toBe(subscription.name);
|
|
275
|
+
expect(rows[0]?.runId).toBe(run.id);
|
|
276
|
+
expect(rows[0]?.runName).toBe(run.name);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('flips a dispatch-error decision to triggered on a successful retry', async () => {
|
|
280
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
281
|
+
const subscription = buildSubscription();
|
|
282
|
+
const renamedSubscription = buildSubscription({...subscription, name: 'renamed trigger'});
|
|
283
|
+
const run = {id: crypto.randomUUID(), name: 'Build and test'};
|
|
284
|
+
|
|
285
|
+
await upsertDispatchErrorDecision({receivedEventId, subscription, reason: 'boom'});
|
|
286
|
+
await upsertTriggeredDecision({receivedEventId, subscription: renamedSubscription, run});
|
|
287
|
+
|
|
288
|
+
const rows = await decisionsFor(receivedEventId);
|
|
289
|
+
expect(rows).toHaveLength(1);
|
|
290
|
+
expect(rows[0]?.subscriptionKind).toBe('trigger');
|
|
291
|
+
expect(rows[0]?.decision).toBe('triggered');
|
|
292
|
+
expect(rows[0]?.subscriptionName).toBe(subscription.name);
|
|
293
|
+
expect(rows[0]?.runId).toBe(run.id);
|
|
294
|
+
expect(rows[0]?.reason).toBeNull();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('never downgrades an existing triggered decision to dispatch-error', async () => {
|
|
298
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
299
|
+
const subscription = buildSubscription();
|
|
300
|
+
const renamedSubscription = buildSubscription({...subscription, name: 'renamed trigger'});
|
|
301
|
+
const run = {id: crypto.randomUUID(), name: 'Build and test'};
|
|
302
|
+
|
|
303
|
+
await upsertTriggeredDecision({receivedEventId, subscription, run});
|
|
304
|
+
await upsertDispatchErrorDecision({
|
|
305
|
+
receivedEventId,
|
|
306
|
+
subscription: renamedSubscription,
|
|
307
|
+
reason: 'definition deleted',
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const rows = await decisionsFor(receivedEventId);
|
|
311
|
+
expect(rows).toHaveLength(1);
|
|
312
|
+
expect(rows[0]?.subscriptionKind).toBe('trigger');
|
|
313
|
+
expect(rows[0]?.decision).toBe('triggered');
|
|
314
|
+
expect(rows[0]?.subscriptionName).toBe(subscription.name);
|
|
315
|
+
expect(rows[0]?.runId).toBe(run.id);
|
|
316
|
+
expect(rows[0]?.reason).toBeNull();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('preserves the original subscription name on dispatch-error replay', async () => {
|
|
320
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
321
|
+
const subscription = buildSubscription();
|
|
322
|
+
const renamedSubscription = buildSubscription({...subscription, name: 'renamed trigger'});
|
|
323
|
+
|
|
324
|
+
await upsertDispatchErrorDecision({receivedEventId, subscription, reason: 'boom'});
|
|
325
|
+
await upsertDispatchErrorDecision({
|
|
326
|
+
receivedEventId,
|
|
327
|
+
subscription: renamedSubscription,
|
|
328
|
+
reason: 'still broken',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const rows = await decisionsFor(receivedEventId);
|
|
332
|
+
expect(rows).toHaveLength(1);
|
|
333
|
+
expect(rows[0]?.subscriptionKind).toBe('trigger');
|
|
334
|
+
expect(rows[0]?.decision).toBe('dispatch-error');
|
|
335
|
+
expect(rows[0]?.subscriptionName).toBe(subscription.name);
|
|
336
|
+
expect(rows[0]?.reason).toBe('still broken');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('records a filter-error decision', async () => {
|
|
340
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
341
|
+
const subscription = buildSubscription();
|
|
342
|
+
|
|
343
|
+
await upsertFilterErrorDecision({
|
|
344
|
+
receivedEventId,
|
|
345
|
+
subscription,
|
|
346
|
+
reason: 'Trigger filter evaluation failed',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const rows = await decisionsFor(receivedEventId);
|
|
350
|
+
expect(rows).toHaveLength(1);
|
|
351
|
+
expect(rows[0]?.subscriptionKind).toBe('trigger');
|
|
352
|
+
expect(rows[0]?.decision).toBe('filter-error');
|
|
353
|
+
expect(rows[0]?.reason).toBe('Trigger filter evaluation failed');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('records a listener triggered decision with matcher identity', async () => {
|
|
357
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
358
|
+
const subscription = buildListenerSubscription({kind: 'until', matcherOrdinal: 2});
|
|
359
|
+
|
|
360
|
+
await upsertListenerTriggeredDecision({receivedEventId, subscription});
|
|
361
|
+
|
|
362
|
+
const rows = await decisionsFor(receivedEventId);
|
|
363
|
+
expect(rows).toHaveLength(1);
|
|
364
|
+
expect(rows[0]).toMatchObject({
|
|
365
|
+
subscriptionKind: 'listener',
|
|
366
|
+
subscriptionId: subscription.id,
|
|
367
|
+
subscriptionName: 'listener until[2] github/push',
|
|
368
|
+
workflowDefinitionId: null,
|
|
369
|
+
projectId: null,
|
|
370
|
+
workflowRunId: subscription.workflowRunId,
|
|
371
|
+
jobId: subscription.jobId,
|
|
372
|
+
matcherKind: 'until',
|
|
373
|
+
matcherOrdinal: 2,
|
|
374
|
+
decision: 'triggered',
|
|
375
|
+
reason: null,
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('flips a listener dispatch-error decision to triggered on a successful retry', async () => {
|
|
380
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
381
|
+
const subscription = buildListenerSubscription();
|
|
382
|
+
|
|
383
|
+
await upsertListenerDispatchErrorDecision({receivedEventId, subscription, reason: 'boom'});
|
|
384
|
+
await upsertListenerTriggeredDecision({receivedEventId, subscription});
|
|
385
|
+
|
|
386
|
+
const rows = await decisionsFor(receivedEventId);
|
|
387
|
+
expect(rows).toHaveLength(1);
|
|
388
|
+
expect(rows[0]?.decision).toBe('triggered');
|
|
389
|
+
expect(rows[0]?.reason).toBeNull();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('never downgrades an existing listener triggered decision to dispatch-error', async () => {
|
|
393
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
394
|
+
const subscription = buildListenerSubscription();
|
|
395
|
+
|
|
396
|
+
await upsertListenerTriggeredDecision({receivedEventId, subscription});
|
|
397
|
+
await upsertListenerDispatchErrorDecision({receivedEventId, subscription, reason: 'boom'});
|
|
398
|
+
|
|
399
|
+
const rows = await decisionsFor(receivedEventId);
|
|
400
|
+
expect(rows).toHaveLength(1);
|
|
401
|
+
expect(rows[0]?.decision).toBe('triggered');
|
|
402
|
+
expect(rows[0]?.reason).toBeNull();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('records a listener filter-error decision', async () => {
|
|
406
|
+
const receivedEventId = await insertReceivedEvent(buildEventParams());
|
|
407
|
+
const subscription = buildListenerSubscription();
|
|
408
|
+
|
|
409
|
+
await upsertListenerFilterErrorDecision({
|
|
410
|
+
receivedEventId,
|
|
411
|
+
subscription,
|
|
412
|
+
reason: 'Listener filter evaluation failed',
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const rows = await decisionsFor(receivedEventId);
|
|
416
|
+
expect(rows).toHaveLength(1);
|
|
417
|
+
expect(rows[0]?.subscriptionKind).toBe('listener');
|
|
418
|
+
expect(rows[0]?.decision).toBe('filter-error');
|
|
419
|
+
expect(rows[0]?.reason).toBe('Listener filter evaluation failed');
|
|
420
|
+
});
|
|
421
|
+
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import {and, eq, ne, notInArray, sql} from 'drizzle-orm';
|
|
2
|
+
import type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';
|
|
3
|
+
import type {TriggerEventOrigin} from '#core/entities/received-event.js';
|
|
4
|
+
import type {TriggerSubscription} from '#core/entities/subscription.js';
|
|
5
|
+
import {db} from './db.js';
|
|
6
|
+
import {triggersDecisions} from './schema/decisions.js';
|
|
7
|
+
import {triggersReceivedEvents} from './schema/received-events.js';
|
|
8
|
+
|
|
9
|
+
export interface InsertReceivedEventParams {
|
|
10
|
+
eventRef: string;
|
|
11
|
+
origin: TriggerEventOrigin;
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
provider: string | null;
|
|
14
|
+
source: string;
|
|
15
|
+
event: string;
|
|
16
|
+
deliveryId: string | null;
|
|
17
|
+
connectionId: string | null;
|
|
18
|
+
connectionName: string | null;
|
|
19
|
+
payload: Record<string, unknown> | null;
|
|
20
|
+
receivedAt: Date;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// `event_ref` is unique and delivery is at-least-once, so the same event can
|
|
24
|
+
// arrive twice; either path returns the row id used to attach decisions.
|
|
25
|
+
export async function insertReceivedEvent(params: InsertReceivedEventParams): Promise<string> {
|
|
26
|
+
const [inserted] = await db()
|
|
27
|
+
.insert(triggersReceivedEvents)
|
|
28
|
+
.values({
|
|
29
|
+
eventRef: params.eventRef,
|
|
30
|
+
origin: params.origin,
|
|
31
|
+
workspaceId: params.workspaceId,
|
|
32
|
+
provider: params.provider,
|
|
33
|
+
source: params.source,
|
|
34
|
+
event: params.event,
|
|
35
|
+
deliveryId: params.deliveryId,
|
|
36
|
+
connectionId: params.connectionId,
|
|
37
|
+
connectionName: params.connectionName,
|
|
38
|
+
payload: params.payload,
|
|
39
|
+
receivedAt: params.receivedAt,
|
|
40
|
+
})
|
|
41
|
+
.onConflictDoNothing({target: triggersReceivedEvents.eventRef})
|
|
42
|
+
.returning({id: triggersReceivedEvents.id});
|
|
43
|
+
if (inserted) return inserted.id;
|
|
44
|
+
|
|
45
|
+
const [existing] = await db()
|
|
46
|
+
.select({id: triggersReceivedEvents.id})
|
|
47
|
+
.from(triggersReceivedEvents)
|
|
48
|
+
.where(eq(triggersReceivedEvents.eventRef, params.eventRef));
|
|
49
|
+
if (!existing) {
|
|
50
|
+
throw new Error(`received_events row missing after conflict for event_ref ${params.eventRef}`);
|
|
51
|
+
}
|
|
52
|
+
return existing.id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function markReceivedEventDiscarded(id: string): Promise<void> {
|
|
56
|
+
await db()
|
|
57
|
+
.update(triggersReceivedEvents)
|
|
58
|
+
.set({outcome: 'discarded', matchedCount: 0, processedAt: new Date()})
|
|
59
|
+
.where(
|
|
60
|
+
and(
|
|
61
|
+
eq(triggersReceivedEvents.id, id),
|
|
62
|
+
notInArray(triggersReceivedEvents.outcome, ['routed', 'errored']),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function markReceivedEventRouted(id: string, matchedCount: number): Promise<void> {
|
|
68
|
+
await db()
|
|
69
|
+
.update(triggersReceivedEvents)
|
|
70
|
+
.set({outcome: 'routed', matchedCount, processedAt: new Date()})
|
|
71
|
+
.where(eq(triggersReceivedEvents.id, id));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// No processedAt: `failed` is transient. Under at-least-once dispatch, a late
|
|
75
|
+
// failure must not clobber a sibling invocation's terminal outcome.
|
|
76
|
+
export async function markReceivedEventFailed(id: string, matchedCount: number): Promise<void> {
|
|
77
|
+
await db()
|
|
78
|
+
.update(triggersReceivedEvents)
|
|
79
|
+
.set({outcome: 'failed', matchedCount})
|
|
80
|
+
.where(
|
|
81
|
+
and(
|
|
82
|
+
eq(triggersReceivedEvents.id, id),
|
|
83
|
+
notInArray(triggersReceivedEvents.outcome, ['routed', 'discarded', 'errored']),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Terminal outcome for a fan-out that produced no new run this pass. The CASE is a
|
|
89
|
+
// cross-attempt safety net: under at-least-once delivery a prior attempt may already have
|
|
90
|
+
// created a run (its decision row is `triggered`, which is never downgraded), so promote the
|
|
91
|
+
// event to `routed` rather than falsely record `errored`. Guarded so it never downgrades a
|
|
92
|
+
// terminal success.
|
|
93
|
+
export async function markReceivedEventErrored(id: string, matchedCount: number): Promise<void> {
|
|
94
|
+
await db()
|
|
95
|
+
.update(triggersReceivedEvents)
|
|
96
|
+
.set({
|
|
97
|
+
outcome: sql`CASE WHEN EXISTS (
|
|
98
|
+
SELECT 1
|
|
99
|
+
FROM ${triggersDecisions}
|
|
100
|
+
WHERE ${triggersDecisions.receivedEventId} = ${triggersReceivedEvents.id}
|
|
101
|
+
AND ${triggersDecisions.decision} = 'triggered'
|
|
102
|
+
) THEN 'routed' ELSE 'errored' END`,
|
|
103
|
+
matchedCount,
|
|
104
|
+
processedAt: new Date(),
|
|
105
|
+
})
|
|
106
|
+
.where(
|
|
107
|
+
and(
|
|
108
|
+
eq(triggersReceivedEvents.id, id),
|
|
109
|
+
notInArray(triggersReceivedEvents.outcome, ['routed', 'discarded']),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface UpsertTriggeredDecisionParams {
|
|
115
|
+
receivedEventId: string;
|
|
116
|
+
subscription: TriggerSubscription;
|
|
117
|
+
run: {id: string; name: string};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function upsertTriggeredDecision(
|
|
121
|
+
params: UpsertTriggeredDecisionParams,
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
await db()
|
|
124
|
+
.insert(triggersDecisions)
|
|
125
|
+
.values({
|
|
126
|
+
receivedEventId: params.receivedEventId,
|
|
127
|
+
subscriptionKind: 'trigger',
|
|
128
|
+
subscriptionId: params.subscription.id,
|
|
129
|
+
subscriptionName: params.subscription.name,
|
|
130
|
+
workflowDefinitionId: params.subscription.workflowDefinitionId,
|
|
131
|
+
projectId: params.subscription.projectId,
|
|
132
|
+
decision: 'triggered',
|
|
133
|
+
runId: params.run.id,
|
|
134
|
+
runName: params.run.name,
|
|
135
|
+
reason: null,
|
|
136
|
+
})
|
|
137
|
+
.onConflictDoUpdate({
|
|
138
|
+
target: [
|
|
139
|
+
triggersDecisions.receivedEventId,
|
|
140
|
+
triggersDecisions.subscriptionKind,
|
|
141
|
+
triggersDecisions.subscriptionId,
|
|
142
|
+
],
|
|
143
|
+
set: {decision: 'triggered', runId: params.run.id, runName: params.run.name, reason: null},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface UpsertFailedDecisionParams {
|
|
148
|
+
receivedEventId: string;
|
|
149
|
+
subscription: TriggerSubscription;
|
|
150
|
+
reason: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function upsertFilterErrorDecision(params: UpsertFailedDecisionParams): Promise<void> {
|
|
154
|
+
await upsertFailedDecision(params, 'filter-error');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// A created run is ground truth. A later retry failure must not erase it.
|
|
158
|
+
export async function upsertDispatchErrorDecision(
|
|
159
|
+
params: UpsertFailedDecisionParams,
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
await upsertFailedDecision(params, 'dispatch-error');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function upsertFailedDecision(
|
|
165
|
+
params: UpsertFailedDecisionParams,
|
|
166
|
+
decision: 'filter-error' | 'dispatch-error',
|
|
167
|
+
): Promise<void> {
|
|
168
|
+
await db()
|
|
169
|
+
.insert(triggersDecisions)
|
|
170
|
+
.values({
|
|
171
|
+
receivedEventId: params.receivedEventId,
|
|
172
|
+
subscriptionKind: 'trigger',
|
|
173
|
+
subscriptionId: params.subscription.id,
|
|
174
|
+
subscriptionName: params.subscription.name,
|
|
175
|
+
workflowDefinitionId: params.subscription.workflowDefinitionId,
|
|
176
|
+
projectId: params.subscription.projectId,
|
|
177
|
+
decision,
|
|
178
|
+
reason: params.reason,
|
|
179
|
+
})
|
|
180
|
+
.onConflictDoUpdate({
|
|
181
|
+
target: [
|
|
182
|
+
triggersDecisions.receivedEventId,
|
|
183
|
+
triggersDecisions.subscriptionKind,
|
|
184
|
+
triggersDecisions.subscriptionId,
|
|
185
|
+
],
|
|
186
|
+
set: {decision, reason: params.reason, runId: null, runName: null},
|
|
187
|
+
setWhere: ne(triggersDecisions.decision, 'triggered'),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface UpsertListenerTriggeredDecisionParams {
|
|
192
|
+
receivedEventId: string;
|
|
193
|
+
subscription: JobListenerSubscription;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function upsertListenerTriggeredDecision(
|
|
197
|
+
params: UpsertListenerTriggeredDecisionParams,
|
|
198
|
+
): Promise<void> {
|
|
199
|
+
await db()
|
|
200
|
+
.insert(triggersDecisions)
|
|
201
|
+
.values({
|
|
202
|
+
...listenerDecisionIdentity(params),
|
|
203
|
+
decision: 'triggered',
|
|
204
|
+
runId: null,
|
|
205
|
+
runName: null,
|
|
206
|
+
reason: null,
|
|
207
|
+
})
|
|
208
|
+
.onConflictDoUpdate({
|
|
209
|
+
target: [
|
|
210
|
+
triggersDecisions.receivedEventId,
|
|
211
|
+
triggersDecisions.subscriptionKind,
|
|
212
|
+
triggersDecisions.subscriptionId,
|
|
213
|
+
],
|
|
214
|
+
set: {decision: 'triggered', runId: null, runName: null, reason: null},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface UpsertListenerFailedDecisionParams {
|
|
219
|
+
receivedEventId: string;
|
|
220
|
+
subscription: JobListenerSubscription;
|
|
221
|
+
reason: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function upsertListenerFilterErrorDecision(
|
|
225
|
+
params: UpsertListenerFailedDecisionParams,
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
await upsertListenerFailedDecision(params, 'filter-error');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function upsertListenerDispatchErrorDecision(
|
|
231
|
+
params: UpsertListenerFailedDecisionParams,
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
await upsertListenerFailedDecision(params, 'dispatch-error');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function upsertListenerFailedDecision(
|
|
237
|
+
params: UpsertListenerFailedDecisionParams,
|
|
238
|
+
decision: 'filter-error' | 'dispatch-error',
|
|
239
|
+
): Promise<void> {
|
|
240
|
+
await db()
|
|
241
|
+
.insert(triggersDecisions)
|
|
242
|
+
.values({
|
|
243
|
+
...listenerDecisionIdentity(params),
|
|
244
|
+
decision,
|
|
245
|
+
reason: params.reason,
|
|
246
|
+
})
|
|
247
|
+
.onConflictDoUpdate({
|
|
248
|
+
target: [
|
|
249
|
+
triggersDecisions.receivedEventId,
|
|
250
|
+
triggersDecisions.subscriptionKind,
|
|
251
|
+
triggersDecisions.subscriptionId,
|
|
252
|
+
],
|
|
253
|
+
set: {decision, reason: params.reason, runId: null, runName: null},
|
|
254
|
+
setWhere: ne(triggersDecisions.decision, 'triggered'),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function listenerDecisionIdentity(params: {
|
|
259
|
+
receivedEventId: string;
|
|
260
|
+
subscription: JobListenerSubscription;
|
|
261
|
+
}) {
|
|
262
|
+
const {subscription} = params;
|
|
263
|
+
return {
|
|
264
|
+
receivedEventId: params.receivedEventId,
|
|
265
|
+
subscriptionKind: 'listener' as const,
|
|
266
|
+
subscriptionId: subscription.id,
|
|
267
|
+
subscriptionName: listenerSubscriptionName(subscription),
|
|
268
|
+
workflowDefinitionId: null,
|
|
269
|
+
projectId: null,
|
|
270
|
+
workflowRunId: subscription.workflowRunId,
|
|
271
|
+
jobId: subscription.jobId,
|
|
272
|
+
matcherKind: subscription.kind,
|
|
273
|
+
matcherOrdinal: subscription.matcherOrdinal,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function listenerSubscriptionName(subscription: JobListenerSubscription): string {
|
|
278
|
+
return `listener ${subscription.kind}[${subscription.matcherOrdinal}] ${subscription.source}/${subscription.event}`;
|
|
279
|
+
}
|