@shipfox/api-triggers 13.1.0 → 14.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 +4 -4
- package/CHANGELOG.md +27 -0
- package/README.md +14 -10
- package/dist/core/entities/decision.d.ts +3 -2
- package/dist/core/entities/decision.d.ts.map +1 -1
- package/dist/core/entities/decision.js.map +1 -1
- package/dist/core/entities/job-listener-subscription.d.ts +2 -1
- package/dist/core/entities/job-listener-subscription.d.ts.map +1 -1
- package/dist/core/entities/job-listener-subscription.js.map +1 -1
- package/dist/core/entities/received-event.d.ts +14 -1
- package/dist/core/entities/received-event.d.ts.map +1 -1
- package/dist/core/entities/received-event.js +2 -1
- package/dist/core/entities/received-event.js.map +1 -1
- package/dist/core/entities/subscription.d.ts +2 -1
- package/dist/core/entities/subscription.d.ts.map +1 -1
- package/dist/core/entities/subscription.js.map +1 -1
- package/dist/core/fire-cron.d.ts.map +1 -1
- package/dist/core/fire-cron.js +3 -1
- package/dist/core/fire-cron.js.map +1 -1
- package/dist/core/fire-manual.d.ts.map +1 -1
- package/dist/core/fire-manual.js +3 -1
- package/dist/core/fire-manual.js.map +1 -1
- package/dist/core/record-trigger-history.d.ts +2 -0
- package/dist/core/record-trigger-history.d.ts.map +1 -1
- package/dist/core/record-trigger-history.js +13 -1
- package/dist/core/record-trigger-history.js.map +1 -1
- package/dist/core/route-event-to-job-listeners.js +2 -0
- package/dist/core/route-event-to-job-listeners.js.map +1 -1
- package/dist/db/db.d.ts +48 -14
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/event-history.d.ts +17 -0
- package/dist/db/event-history.d.ts.map +1 -1
- package/dist/db/event-history.js +45 -1
- package/dist/db/event-history.js.map +1 -1
- package/dist/db/event-queries.d.ts +5 -1
- package/dist/db/event-queries.d.ts.map +1 -1
- package/dist/db/event-queries.js +30 -4
- package/dist/db/event-queries.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/job-listener-subscriptions.d.ts +1 -1
- package/dist/db/job-listener-subscriptions.d.ts.map +1 -1
- package/dist/db/job-listener-subscriptions.js +10 -3
- package/dist/db/job-listener-subscriptions.js.map +1 -1
- package/dist/db/schema/decisions.d.ts +3 -3
- package/dist/db/schema/decisions.d.ts.map +1 -1
- package/dist/db/schema/decisions.js +8 -3
- package/dist/db/schema/decisions.js.map +1 -1
- package/dist/db/schema/job-listener-subscriptions.d.ts +1 -1
- package/dist/db/schema/job-listener-subscriptions.d.ts.map +1 -1
- package/dist/db/schema/job-listener-subscriptions.js +2 -1
- package/dist/db/schema/job-listener-subscriptions.js.map +1 -1
- package/dist/db/schema/received-events.d.ts +38 -4
- package/dist/db/schema/received-events.d.ts.map +1 -1
- package/dist/db/schema/received-events.js +11 -2
- package/dist/db/schema/received-events.js.map +1 -1
- package/dist/db/schema/subscriptions.d.ts +1 -1
- package/dist/db/schema/subscriptions.d.ts.map +1 -1
- package/dist/db/schema/subscriptions.js +2 -1
- package/dist/db/schema/subscriptions.js.map +1 -1
- package/dist/db/subscription-event.d.ts +7 -0
- package/dist/db/subscription-event.d.ts.map +1 -0
- package/dist/db/subscription-event.js +19 -0
- package/dist/db/subscription-event.js.map +1 -0
- package/dist/db/subscriptions.d.ts +5 -1
- package/dist/db/subscriptions.d.ts.map +1 -1
- package/dist/db/subscriptions.js +10 -3
- package/dist/db/subscriptions.js.map +1 -1
- package/dist/presentation/dto/trigger-events.d.ts +3 -2
- package/dist/presentation/dto/trigger-events.d.ts.map +1 -1
- package/dist/presentation/dto/trigger-events.js +9 -0
- package/dist/presentation/dto/trigger-events.js.map +1 -1
- package/dist/presentation/routes/get-trigger-event.d.ts.map +1 -1
- package/dist/presentation/routes/get-trigger-event.js +8 -4
- package/dist/presentation/routes/get-trigger-event.js.map +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.js +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.js.map +1 -1
- package/dist/presentation/routes/list-trigger-events.d.ts.map +1 -1
- package/dist/presentation/routes/list-trigger-events.js +3 -1
- package/dist/presentation/routes/list-trigger-events.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0001_nullable_subscription_events.sql +2 -0
- package/drizzle/0002_dev_trigger_journal.sql +7 -0
- package/drizzle/0003_add_origin_facet_index.sql +1 -0
- package/drizzle/meta/0001_snapshot.json +905 -0
- package/drizzle/meta/0002_snapshot.json +947 -0
- package/drizzle/meta/0003_snapshot.json +968 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +7 -7
- package/src/core/dispatch-integration-event.test.ts +88 -0
- package/src/core/entities/decision.ts +3 -2
- package/src/core/entities/job-listener-subscription.ts +2 -1
- package/src/core/entities/received-event.ts +15 -1
- package/src/core/entities/subscription.ts +2 -1
- package/src/core/fire-cron.test.ts +20 -0
- package/src/core/fire-cron.ts +3 -1
- package/src/core/fire-manual.test.ts +25 -0
- package/src/core/fire-manual.ts +3 -1
- package/src/core/record-trigger-history.test.ts +78 -0
- package/src/core/record-trigger-history.ts +19 -0
- package/src/core/route-event-to-job-listeners.test.ts +47 -0
- package/src/core/route-event-to-job-listeners.ts +2 -0
- package/src/db/event-history.test.ts +123 -0
- package/src/db/event-history.ts +84 -1
- package/src/db/event-queries.ts +56 -3
- package/src/db/index.ts +1 -0
- package/src/db/job-listener-subscriptions.test.ts +133 -1
- package/src/db/job-listener-subscriptions.ts +5 -4
- package/src/db/schema/decisions.test.ts +93 -4
- package/src/db/schema/decisions.ts +12 -3
- package/src/db/schema/job-listener-subscriptions.ts +2 -1
- package/src/db/schema/received-events.test.ts +45 -0
- package/src/db/schema/received-events.ts +12 -1
- package/src/db/schema/subscriptions.ts +2 -1
- package/src/db/subscription-event.ts +24 -0
- package/src/db/subscriptions.test.ts +137 -0
- package/src/db/subscriptions.ts +9 -4
- package/src/presentation/dto/trigger-events.test.ts +193 -0
- package/src/presentation/dto/trigger-events.ts +12 -0
- package/src/presentation/routes/get-trigger-event.test.ts +179 -0
- package/src/presentation/routes/get-trigger-event.ts +15 -3
- package/src/presentation/routes/list-trigger-event-facets.test.ts +39 -2
- package/src/presentation/routes/list-trigger-event-facets.ts +1 -1
- package/src/presentation/routes/list-trigger-events.test.ts +99 -0
- package/src/presentation/routes/list-trigger-events.ts +4 -0
- package/test/factories/received-event.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
$ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
|
|
2
|
-
Successfully compiled:
|
|
3
|
-
2026-08-
|
|
2
|
+
Successfully compiled: 59 files with swc (453.85ms)
|
|
3
|
+
2026-08-23T20:26:55.572Z [INFO] asset workflow-bundle-69915b80080285c7230d.js 3.17 MiB [emitted] [immutable] (name: main)
|
|
4
4
|
orphan modules 33.5 KiB [orphan] 21 modules
|
|
5
5
|
runtime modules 1.13 KiB 5 modules
|
|
6
6
|
modules by path ../../../node_modules/.pnpm/ 970 KiB 196 modules
|
|
@@ -13,6 +13,6 @@ optional modules 30 bytes [optional]
|
|
|
13
13
|
__temporal_custom_payload_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
14
14
|
__temporal_custom_failure_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
15
15
|
../../shared/node/temporal/dist/workflow-error-interceptor.js 1.21 KiB [built] [code generated]
|
|
16
|
-
webpack 5.107.2 compiled successfully in
|
|
17
|
-
2026-08-
|
|
16
|
+
webpack 5.107.2 compiled successfully in 6873 ms
|
|
17
|
+
2026-08-23T20:26:55.595Z [INFO] Workflow bundle created { size: '3.17MB' }
|
|
18
18
|
/home/runner/work/shipfox/shipfox/libs/api/triggers/dist/temporal/workflows/index.js: 3329015 bytes
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @shipfox/api-triggers
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 05c7c4d: Adds dev-run and replay-link support to trigger journal interfaces and client types.
|
|
8
|
+
- b6c7871: Adds `origin` and `replayable` filters to the trigger events list, an `origins` facet, and `replay_of_event_id` with a `replays` list on the event detail response.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 4f30864: Trigger `event` is now optional end to end. An omitted event subscribes to every event from its source. Explicit events continue to work unchanged. Built-in manual and scheduled triggers use `fire` and `tick`, respectively.
|
|
13
|
+
- Updated dependencies [c0fc35b]
|
|
14
|
+
- Updated dependencies [05c7c4d]
|
|
15
|
+
- Updated dependencies [f7b3db8]
|
|
16
|
+
- Updated dependencies [4f30864]
|
|
17
|
+
- Updated dependencies [18e9bad]
|
|
18
|
+
- Updated dependencies [aeaa0de]
|
|
19
|
+
- Updated dependencies [c44641f]
|
|
20
|
+
- Updated dependencies [b6c7871]
|
|
21
|
+
- Updated dependencies [1b71a66]
|
|
22
|
+
- Updated dependencies [b766ee3]
|
|
23
|
+
- @shipfox/api-definitions-dto@14.0.0
|
|
24
|
+
- @shipfox/api-triggers-dto@14.0.0
|
|
25
|
+
- @shipfox/workflow-document@3.1.0
|
|
26
|
+
- @shipfox/api-workflows-dto@14.0.0
|
|
27
|
+
- @shipfox/api-integration-core-dto@14.0.0
|
|
28
|
+
- @shipfox/expression@2.2.1
|
|
29
|
+
|
|
3
30
|
## 13.1.0
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -64,14 +64,18 @@ triggers:
|
|
|
64
64
|
on_push:
|
|
65
65
|
source: github_acme
|
|
66
66
|
event: push
|
|
67
|
-
|
|
67
|
+
filter: event.ref == "refs/heads/main"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
Each source is an integration connection slug. Each map key is the trigger's `name`. A workflow may declare any number of
|
|
70
|
+
Each source is an integration connection slug or a built-in source (`manual`, `cron`). Each map key is the trigger's `name`. A workflow may declare any number of
|
|
71
71
|
integration triggers and at most one `source: manual` trigger; the manual
|
|
72
72
|
invariant is enforced at parse time so the fire route stays unambiguous.
|
|
73
|
-
The `event` field is optional
|
|
74
|
-
|
|
73
|
+
The `event` field is optional. Omitting it subscribes the trigger to every
|
|
74
|
+
event the source delivers. A source that delivers one event receives that
|
|
75
|
+
one: `manual` receives `fire`, `cron` receives `tick`, and a custom webhook
|
|
76
|
+
integration connection receives `received`. The normalizer materializes `fire` and `tick`
|
|
77
|
+
for the two built-in sources. An omitted event on an integration source
|
|
78
|
+
becomes a source subscription that matches any event from that integration connection.
|
|
75
79
|
|
|
76
80
|
Integration triggers may include a CEL `filter` predicate. Dispatch evaluates
|
|
77
81
|
the predicate for every subscription that matches `(workspace_id, source,
|
|
@@ -136,9 +140,9 @@ The routes are mounted by the host app under the `/workflow-definitions` and
|
|
|
136
140
|
| Method | Path | Auth | Result |
|
|
137
141
|
| --- | --- | --- | --- |
|
|
138
142
|
| `POST` | `/workflow-definitions/:definitionId/fire-manual` | bearer token | Fires the workflow's manual trigger and returns the new `workflow_run_id`. Optional `inputs` in the body are forwarded to the run. |
|
|
139
|
-
| `GET` | `/trigger-events?workspace_id=:workspaceId` | bearer token | Lists received trigger events for a workspace, newest first. Supports source, event, outcome, received-at window, limit, and cursor filters. |
|
|
140
|
-
| `GET` | `/trigger-events/facets?workspace_id=:workspaceId` | bearer token | Returns the workspace's distinct `source` and `
|
|
141
|
-
| `GET` | `/trigger-events/:id` | bearer token | Returns one received trigger event with its full payload and
|
|
143
|
+
| `GET` | `/trigger-events?workspace_id=:workspaceId` | bearer token | Lists received trigger events for a workspace, newest first. Supports source, event, origin, outcome, `replayable=true`, received-at window, limit, and cursor filters. List items include the optional source event ID for replay rows. |
|
|
144
|
+
| `GET` | `/trigger-events/facets?workspace_id=:workspaceId` | bearer token | Returns the workspace's distinct `source`, `event`, and `origin` filter values with counts (top 50 each, by count). Backs the Events page filter dropdowns. |
|
|
145
|
+
| `GET` | `/trigger-events/:id` | bearer token | Returns one received trigger event with its full payload, routing decisions, optional source event ID, and replay links. Cross-workspace ids return `404`. |
|
|
142
146
|
|
|
143
147
|
The manual route is keyed by workflow definition id, not subscription id. The
|
|
144
148
|
server resolves the manual subscription for the workflow internally; the
|
|
@@ -152,9 +156,9 @@ Three words, used the same way at every layer.
|
|
|
152
156
|
|
|
153
157
|
| Word | Meaning | Examples |
|
|
154
158
|
| --- | --- | --- |
|
|
155
|
-
| **source** | Where the trigger came from. | `
|
|
156
|
-
| **event** | The specific thing that happened, scoped to a source. | `push`, `issue_comment`, `alert_triggered`, `fire`, `tick` |
|
|
157
|
-
| **payload** | The data carried by the event, set by the producing integration. Triggers passes it through opaquely. | GitHub's raw webhook JSON for `(
|
|
159
|
+
| **source** | Where the trigger came from: an integration connection slug or a built-in source. | `github_acme`, `gitlab_prod`, `sentry_prod`, `manual`, `cron` |
|
|
160
|
+
| **event** | The specific thing that happened, scoped to a source. Omitted on a subscription means every event the source delivers. | `push`, `issue_comment`, `alert_triggered`, `fire`, `tick` |
|
|
161
|
+
| **payload** | The data carried by the event, set by the producing integration. Triggers passes it through opaquely. | GitHub's raw webhook JSON for `(github_acme, push)` |
|
|
158
162
|
|
|
159
163
|
The `name` field on a subscription is the YAML map key (for example
|
|
160
164
|
`on_push`). It identifies the trigger inside a workflow definition and is
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { JobListenerMatcherKind } from './job-listener-subscription.js';
|
|
2
2
|
export declare const triggerDecisionOutcomes: readonly ['triggered', 'filter-error', 'dispatch-error'];
|
|
3
3
|
export type TriggerDecisionOutcome = (typeof triggerDecisionOutcomes)[number];
|
|
4
|
-
export type TriggerDecisionSubscriptionKind = 'trigger' | 'listener';
|
|
4
|
+
export type TriggerDecisionSubscriptionKind = 'trigger' | 'listener' | 'dev';
|
|
5
5
|
export interface TriggerDecision {
|
|
6
6
|
id: string;
|
|
7
7
|
receivedEventId: string;
|
|
8
8
|
subscriptionKind: TriggerDecisionSubscriptionKind;
|
|
9
|
-
|
|
9
|
+
/** Null for `dev` decisions: a dev journal entry has no subscription row. */
|
|
10
|
+
subscriptionId: string | null;
|
|
10
11
|
subscriptionName: string;
|
|
11
12
|
workflowDefinitionId: string | null;
|
|
12
13
|
projectId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../../src/core/entities/decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,uBAAuB,YAAI,WAAW,EAAE,cAAc,EAAE,gBAAgB,CAAU,CAAC;AAChG,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9E,MAAM,MAAM,+BAA+B,GAAG,SAAS,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../../src/core/entities/decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,uBAAuB,YAAI,WAAW,EAAE,cAAc,EAAE,gBAAgB,CAAU,CAAC;AAChG,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9E,MAAM,MAAM,+BAA+B,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC;AAE7E,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,+BAA+B,CAAC;IAClD,6EAA6E;IAC7E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC3C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/decision.ts"],"sourcesContent":["import type {JobListenerMatcherKind} from './job-listener-subscription.js';\n\nexport const triggerDecisionOutcomes = ['triggered', 'filter-error', 'dispatch-error'] as const;\nexport type TriggerDecisionOutcome = (typeof triggerDecisionOutcomes)[number];\nexport type TriggerDecisionSubscriptionKind = 'trigger' | 'listener';\n\nexport interface TriggerDecision {\n id: string;\n receivedEventId: string;\n subscriptionKind: TriggerDecisionSubscriptionKind;\n subscriptionId: string;\n subscriptionName: string;\n workflowDefinitionId: string | null;\n projectId: string | null;\n workflowRunId: string | null;\n jobId: string | null;\n matcherKind: JobListenerMatcherKind | null;\n matcherOrdinal: number | null;\n decision: TriggerDecisionOutcome;\n runId: string | null;\n runName: string | null;\n reason: string | null;\n createdAt: Date;\n}\n"],"names":["triggerDecisionOutcomes"],"mappings":"AAEA,OAAO,MAAMA,0BAA0B;IAAC;IAAa;IAAgB;CAAiB,CAAU"}
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/decision.ts"],"sourcesContent":["import type {JobListenerMatcherKind} from './job-listener-subscription.js';\n\nexport const triggerDecisionOutcomes = ['triggered', 'filter-error', 'dispatch-error'] as const;\nexport type TriggerDecisionOutcome = (typeof triggerDecisionOutcomes)[number];\nexport type TriggerDecisionSubscriptionKind = 'trigger' | 'listener' | 'dev';\n\nexport interface TriggerDecision {\n id: string;\n receivedEventId: string;\n subscriptionKind: TriggerDecisionSubscriptionKind;\n /** Null for `dev` decisions: a dev journal entry has no subscription row. */\n subscriptionId: string | null;\n subscriptionName: string;\n workflowDefinitionId: string | null;\n projectId: string | null;\n workflowRunId: string | null;\n jobId: string | null;\n matcherKind: JobListenerMatcherKind | null;\n matcherOrdinal: number | null;\n decision: TriggerDecisionOutcome;\n runId: string | null;\n runName: string | null;\n reason: string | null;\n createdAt: Date;\n}\n"],"names":["triggerDecisionOutcomes"],"mappings":"AAEA,OAAO,MAAMA,0BAA0B;IAAC;IAAa;IAAgB;CAAiB,CAAU"}
|
|
@@ -7,7 +7,8 @@ export interface JobListenerSubscription {
|
|
|
7
7
|
kind: JobListenerMatcherKind;
|
|
8
8
|
matcherOrdinal: number;
|
|
9
9
|
source: string;
|
|
10
|
-
|
|
10
|
+
/** NULL is a source subscription: matches every event the source delivers. */
|
|
11
|
+
event: string | null;
|
|
11
12
|
config: Record<string, unknown>;
|
|
12
13
|
createdAt: Date;
|
|
13
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-listener-subscription.d.ts","sourceRoot":"","sources":["../../../src/core/entities/job-listener-subscription.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,sBAAsB,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"job-listener-subscription.d.ts","sourceRoot":"","sources":["../../../src/core/entities/job-listener-subscription.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,IAAI,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,sBAAsB,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/job-listener-subscription.ts"],"sourcesContent":["export type JobListenerMatcherKind = 'on' | 'until';\n\nexport interface JobListenerSubscription {\n id: string;\n workspaceId: string;\n workflowRunId: string;\n jobId: string;\n kind: JobListenerMatcherKind;\n matcherOrdinal: number;\n source: string;\n event: string;\n config: Record<string, unknown>;\n createdAt: Date;\n}\n"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/job-listener-subscription.ts"],"sourcesContent":["export type JobListenerMatcherKind = 'on' | 'until';\n\nexport interface JobListenerSubscription {\n id: string;\n workspaceId: string;\n workflowRunId: string;\n jobId: string;\n kind: JobListenerMatcherKind;\n matcherOrdinal: number;\n source: string;\n /** NULL is a source subscription: matches every event the source delivers. */\n event: string | null;\n config: Record<string, unknown>;\n createdAt: Date;\n}\n"],"names":[],"mappings":"AAEA,WAYC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const triggerEventOrigins: readonly ['integration', 'manual', 'cron'];
|
|
1
|
+
export declare const triggerEventOrigins: readonly ['integration', 'manual', 'cron', 'dev'];
|
|
2
2
|
export type TriggerEventOrigin = (typeof triggerEventOrigins)[number];
|
|
3
3
|
export declare const triggerEventOutcomes: readonly ['received', 'routed', 'discarded', 'failed', 'errored'];
|
|
4
4
|
export type TriggerEventOutcome = (typeof triggerEventOutcomes)[number];
|
|
@@ -10,6 +10,8 @@ export interface TriggerReceivedEvent {
|
|
|
10
10
|
provider: string | null;
|
|
11
11
|
source: string;
|
|
12
12
|
event: string;
|
|
13
|
+
/** The source event this entry replays, when origin is `dev`. */
|
|
14
|
+
replayOfEventId: string | null;
|
|
13
15
|
deliveryId: string | null;
|
|
14
16
|
connectionId: string | null;
|
|
15
17
|
connectionName: string | null;
|
|
@@ -20,6 +22,17 @@ export interface TriggerReceivedEvent {
|
|
|
20
22
|
processedAt: Date | null;
|
|
21
23
|
createdAt: Date;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A dev journal entry that replayed a source event. A missing run ID means
|
|
27
|
+
* that the replay has no recorded workflow run, including refusals and an
|
|
28
|
+
* incomplete or failed dev decision write.
|
|
29
|
+
*/
|
|
30
|
+
export interface TriggerEventReplay {
|
|
31
|
+
id: string;
|
|
32
|
+
receivedAt: Date;
|
|
33
|
+
outcome: TriggerEventOutcome;
|
|
34
|
+
runId: string | null;
|
|
35
|
+
}
|
|
23
36
|
/**
|
|
24
37
|
* Trigger event shape for list read models.
|
|
25
38
|
* It omits payload because webhook bodies can be large/untrusted and only detail views render them.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"received-event.d.ts","sourceRoot":"","sources":["../../../src/core/entities/received-event.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,YAAI,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"received-event.d.ts","sourceRoot":"","sources":["../../../src/core/entities/received-event.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,YAAI,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AACrF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,eAAO,MAAM,oBAAoB,YAC/B,UAAU,EACV,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,CACD,CAAC;AACX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,IAAI,CAAC;IACjB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/received-event.ts"],"sourcesContent":["export const triggerEventOrigins = ['integration', 'manual', 'cron'] as const;\nexport type TriggerEventOrigin = (typeof triggerEventOrigins)[number];\n\nexport const triggerEventOutcomes = [\n 'received',\n 'routed',\n 'discarded',\n 'failed',\n 'errored',\n] as const;\nexport type TriggerEventOutcome = (typeof triggerEventOutcomes)[number];\n\nexport interface TriggerReceivedEvent {\n id: string;\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n outcome: TriggerEventOutcome;\n matchedCount: number;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n processedAt: Date | null;\n createdAt: Date;\n}\n\n/**\n * Trigger event shape for list read models.\n * It omits payload because webhook bodies can be large/untrusted and only detail views render them.\n */\nexport type TriggerReceivedEventSummary = Omit<TriggerReceivedEvent, 'payload'>;\n"],"names":["triggerEventOrigins","triggerEventOutcomes"],"mappings":"AAAA,OAAO,MAAMA,sBAAsB;IAAC;IAAe;IAAU;
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/received-event.ts"],"sourcesContent":["export const triggerEventOrigins = ['integration', 'manual', 'cron', 'dev'] as const;\nexport type TriggerEventOrigin = (typeof triggerEventOrigins)[number];\n\nexport const triggerEventOutcomes = [\n 'received',\n 'routed',\n 'discarded',\n 'failed',\n 'errored',\n] as const;\nexport type TriggerEventOutcome = (typeof triggerEventOutcomes)[number];\n\nexport interface TriggerReceivedEvent {\n id: string;\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n /** The source event this entry replays, when origin is `dev`. */\n replayOfEventId: string | null;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n outcome: TriggerEventOutcome;\n matchedCount: number;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n processedAt: Date | null;\n createdAt: Date;\n}\n\n/**\n * A dev journal entry that replayed a source event. A missing run ID means\n * that the replay has no recorded workflow run, including refusals and an\n * incomplete or failed dev decision write.\n */\nexport interface TriggerEventReplay {\n id: string;\n receivedAt: Date;\n outcome: TriggerEventOutcome;\n runId: string | null;\n}\n\n/**\n * Trigger event shape for list read models.\n * It omits payload because webhook bodies can be large/untrusted and only detail views render them.\n */\nexport type TriggerReceivedEventSummary = Omit<TriggerReceivedEvent, 'payload'>;\n"],"names":["triggerEventOrigins","triggerEventOutcomes"],"mappings":"AAAA,OAAO,MAAMA,sBAAsB;IAAC;IAAe;IAAU;IAAQ;CAAM,CAAU;AAGrF,OAAO,MAAMC,uBAAuB;IAClC;IACA;IACA;IACA;IACA;CACD,CAAU"}
|
|
@@ -5,7 +5,8 @@ export interface TriggerSubscription {
|
|
|
5
5
|
workflowDefinitionId: string;
|
|
6
6
|
name: string;
|
|
7
7
|
source: string;
|
|
8
|
-
|
|
8
|
+
/** NULL is a source subscription: matches every event the source delivers. */
|
|
9
|
+
event: string | null;
|
|
9
10
|
config: Record<string, unknown>;
|
|
10
11
|
createdAt: Date;
|
|
11
12
|
updatedAt: Date;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/core/entities/subscription.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/core/entities/subscription.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/subscription.ts"],"sourcesContent":["export interface TriggerSubscription {\n id: string;\n workspaceId: string;\n projectId: string;\n workflowDefinitionId: string;\n name: string;\n source: string;\n event: string;\n config: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/subscription.ts"],"sourcesContent":["export interface TriggerSubscription {\n id: string;\n workspaceId: string;\n projectId: string;\n workflowDefinitionId: string;\n name: string;\n source: string;\n /** NULL is a source subscription: matches every event the source delivers. */\n event: string | null;\n config: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,WAYC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fire-cron.d.ts","sourceRoot":"","sources":["../../src/core/fire-cron.ts"],"names":[],"mappings":"AAKA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,qBAAqB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GACnD;IAAC,OAAO,EAAE,SAAS,CAAA;CAAC,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,0BAA0B,CAAC,
|
|
1
|
+
{"version":3,"file":"fire-cron.d.ts","sourceRoot":"","sources":["../../src/core/fire-cron.ts"],"names":[],"mappings":"AAKA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,qBAAqB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GACnD;IAAC,OAAO,EAAE,SAAS,CAAA;CAAC,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,0BAA0B,CAAC,CA8DrC"}
|
package/dist/core/fire-cron.js
CHANGED
|
@@ -27,7 +27,9 @@ import { isPermanentStartRunError } from './workflows-client.js';
|
|
|
27
27
|
workspaceId: subscription.workspaceId,
|
|
28
28
|
provider: null,
|
|
29
29
|
source: subscription.source,
|
|
30
|
-
event
|
|
30
|
+
// Cron has no inbound event. Keep the canonical history event for a row
|
|
31
|
+
// that bypassed the projection write-path validation.
|
|
32
|
+
event: subscription.event ?? 'tick',
|
|
31
33
|
deliveryId: null,
|
|
32
34
|
connectionId: null,
|
|
33
35
|
connectionName: null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/fire-cron.ts"],"sourcesContent":["import {getTriggerSubscriptionById} from '#db/subscriptions.js';\nimport {cronFiredCount, cronFireLag} from '#metrics/instance.js';\nimport {readConfigInputs} from './config.js';\nimport {TriggerSubscriptionNotCronError, TriggerSubscriptionNotFoundError} from './errors.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface FireCronSubscriptionParams {\n workflows: WorkflowsModuleClient;\n subscriptionId: string;\n /**\n * The `next_fire_at` value the schedule held when it was claimed. Anchoring the\n * idempotency key and history `event_ref` on this slot, rather than wall-clock\n * time, makes a retry of the same occurrence a deterministic no-op.\n */\n scheduledSlot: Date;\n}\n\nexport type FireCronSubscriptionResult =\n | {outcome: 'fired'; run: {id: string; name: string}}\n | {outcome: 'errored'};\n\n/**\n * Fires one cron schedule occurrence. Mirrors `fireManualSubscription`, but is driven\n * by the drain activity rather than an HTTP route, and reports the outcome instead of\n * throwing on a permanent failure so the caller can advance the schedule past a\n * permanently broken definition:\n *\n * - `{outcome: 'fired'}` on success (or an idempotent replay of an already-created run),\n * - `{outcome: 'errored'}` on a permanent `runWorkflow` failure (recorded, schedule advances),\n * - throws on a transient failure so the caller rolls back the advance and retries.\n */\nexport async function fireCronSubscription(\n params: FireCronSubscriptionParams,\n): Promise<FireCronSubscriptionResult> {\n const subscription = await getTriggerSubscriptionById(params.subscriptionId);\n if (!subscription) throw new TriggerSubscriptionNotFoundError(params.subscriptionId);\n if (subscription.source !== 'cron') {\n throw new TriggerSubscriptionNotCronError(params.subscriptionId, subscription.source);\n }\n\n // Deterministic per-occurrence identity: unique across (subscription, slot), stable\n // across retries. Used for both the run idempotency key and the received-event ref.\n const eventRef = `${subscription.id}:${params.scheduledSlot.toISOString()}`;\n const historyBase = {\n origin: 'cron' as const,\n workspaceId: subscription.workspaceId,\n provider: null,\n source: subscription.source,\n event: subscription.event,\n deliveryId: null,\n connectionId: null,\n connectionName: null,\n payload: null,\n // The scheduled occurrence is the logical arrival time of the tick event.\n receivedAt: params.scheduledSlot,\n eventRef,\n };\n\n const inputs = readConfigInputs(subscription);\n let run: {id: string; name: string};\n try {\n run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: 'cron',\n source: 'cron',\n event: 'tick',\n scheduleId: subscription.id,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: eventRef,\n });\n } catch (error) {\n const failure = await beginTriggerHistory(historyBase);\n await failure.dispatchErrored(subscription, toReason(error));\n if (isPermanentStartRunError(error)) {\n recordFire('errored', params.scheduledSlot);\n await failure.allErrored(1);\n return {outcome: 'errored'};\n }\n // Transient: leave the event non-terminal and re-throw so the caller rolls back\n // the advance and the schedule stays due for the next tick.\n await failure.failed(1);\n throw error;\n }\n\n const history = await beginTriggerHistory(historyBase);\n await history.triggered(subscription, run);\n recordFire('fired', params.scheduledSlot);\n await history.routed(1);\n return {outcome: 'fired', run};\n}\n\nfunction recordFire(outcome: 'fired' | 'errored', scheduledSlot: Date): void {\n cronFiredCount.add(1, {outcome});\n cronFireLag.record(Math.max(0, Date.now() - scheduledSlot.getTime()));\n}\n"],"names":["getTriggerSubscriptionById","cronFiredCount","cronFireLag","readConfigInputs","TriggerSubscriptionNotCronError","TriggerSubscriptionNotFoundError","beginTriggerHistory","toReason","isPermanentStartRunError","fireCronSubscription","params","subscription","subscriptionId","source","eventRef","id","scheduledSlot","toISOString","historyBase","origin","workspaceId","provider","event","deliveryId","connectionId","connectionName","payload","receivedAt","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","scheduleId","undefined","idempotencyKey","error","failure","dispatchErrored","recordFire","allErrored","outcome","failed","history","triggered","routed","add","record","Math","max","Date","now","getTime"],"mappings":"AAAA,SAAQA,0BAA0B,QAAO,uBAAuB;AAChE,SAAQC,cAAc,EAAEC,WAAW,QAAO,uBAAuB;AACjE,SAAQC,gBAAgB,QAAO,cAAc;AAC7C,SAAQC,+BAA+B,EAAEC,gCAAgC,QAAO,cAAc;AAC9F,SAAQC,mBAAmB,EAAEC,QAAQ,QAAO,8BAA8B;AAC1E,SAAQC,wBAAwB,QAAmC,wBAAwB;AAiB3F;;;;;;;;;CASC,GACD,OAAO,eAAeC,qBACpBC,MAAkC;IAElC,MAAMC,eAAe,MAAMX,2BAA2BU,OAAOE,cAAc;IAC3E,IAAI,CAACD,cAAc,MAAM,IAAIN,iCAAiCK,OAAOE,cAAc;IACnF,IAAID,aAAaE,MAAM,KAAK,QAAQ;QAClC,MAAM,IAAIT,gCAAgCM,OAAOE,cAAc,EAAED,aAAaE,MAAM;IACtF;IAEA,oFAAoF;IACpF,oFAAoF;IACpF,MAAMC,WAAW,GAAGH,aAAaI,EAAE,CAAC,CAAC,EAAEL,OAAOM,aAAa,CAACC,WAAW,IAAI;IAC3E,MAAMC,cAAc;QAClBC,QAAQ;QACRC,aAAaT,aAAaS,WAAW;QACrCC,UAAU;QACVR,QAAQF,aAAaE,MAAM;
|
|
1
|
+
{"version":3,"sources":["../../src/core/fire-cron.ts"],"sourcesContent":["import {getTriggerSubscriptionById} from '#db/subscriptions.js';\nimport {cronFiredCount, cronFireLag} from '#metrics/instance.js';\nimport {readConfigInputs} from './config.js';\nimport {TriggerSubscriptionNotCronError, TriggerSubscriptionNotFoundError} from './errors.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface FireCronSubscriptionParams {\n workflows: WorkflowsModuleClient;\n subscriptionId: string;\n /**\n * The `next_fire_at` value the schedule held when it was claimed. Anchoring the\n * idempotency key and history `event_ref` on this slot, rather than wall-clock\n * time, makes a retry of the same occurrence a deterministic no-op.\n */\n scheduledSlot: Date;\n}\n\nexport type FireCronSubscriptionResult =\n | {outcome: 'fired'; run: {id: string; name: string}}\n | {outcome: 'errored'};\n\n/**\n * Fires one cron schedule occurrence. Mirrors `fireManualSubscription`, but is driven\n * by the drain activity rather than an HTTP route, and reports the outcome instead of\n * throwing on a permanent failure so the caller can advance the schedule past a\n * permanently broken definition:\n *\n * - `{outcome: 'fired'}` on success (or an idempotent replay of an already-created run),\n * - `{outcome: 'errored'}` on a permanent `runWorkflow` failure (recorded, schedule advances),\n * - throws on a transient failure so the caller rolls back the advance and retries.\n */\nexport async function fireCronSubscription(\n params: FireCronSubscriptionParams,\n): Promise<FireCronSubscriptionResult> {\n const subscription = await getTriggerSubscriptionById(params.subscriptionId);\n if (!subscription) throw new TriggerSubscriptionNotFoundError(params.subscriptionId);\n if (subscription.source !== 'cron') {\n throw new TriggerSubscriptionNotCronError(params.subscriptionId, subscription.source);\n }\n\n // Deterministic per-occurrence identity: unique across (subscription, slot), stable\n // across retries. Used for both the run idempotency key and the received-event ref.\n const eventRef = `${subscription.id}:${params.scheduledSlot.toISOString()}`;\n const historyBase = {\n origin: 'cron' as const,\n workspaceId: subscription.workspaceId,\n provider: null,\n source: subscription.source,\n // Cron has no inbound event. Keep the canonical history event for a row\n // that bypassed the projection write-path validation.\n event: subscription.event ?? 'tick',\n deliveryId: null,\n connectionId: null,\n connectionName: null,\n payload: null,\n // The scheduled occurrence is the logical arrival time of the tick event.\n receivedAt: params.scheduledSlot,\n eventRef,\n };\n\n const inputs = readConfigInputs(subscription);\n let run: {id: string; name: string};\n try {\n run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: 'cron',\n source: 'cron',\n event: 'tick',\n scheduleId: subscription.id,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: eventRef,\n });\n } catch (error) {\n const failure = await beginTriggerHistory(historyBase);\n await failure.dispatchErrored(subscription, toReason(error));\n if (isPermanentStartRunError(error)) {\n recordFire('errored', params.scheduledSlot);\n await failure.allErrored(1);\n return {outcome: 'errored'};\n }\n // Transient: leave the event non-terminal and re-throw so the caller rolls back\n // the advance and the schedule stays due for the next tick.\n await failure.failed(1);\n throw error;\n }\n\n const history = await beginTriggerHistory(historyBase);\n await history.triggered(subscription, run);\n recordFire('fired', params.scheduledSlot);\n await history.routed(1);\n return {outcome: 'fired', run};\n}\n\nfunction recordFire(outcome: 'fired' | 'errored', scheduledSlot: Date): void {\n cronFiredCount.add(1, {outcome});\n cronFireLag.record(Math.max(0, Date.now() - scheduledSlot.getTime()));\n}\n"],"names":["getTriggerSubscriptionById","cronFiredCount","cronFireLag","readConfigInputs","TriggerSubscriptionNotCronError","TriggerSubscriptionNotFoundError","beginTriggerHistory","toReason","isPermanentStartRunError","fireCronSubscription","params","subscription","subscriptionId","source","eventRef","id","scheduledSlot","toISOString","historyBase","origin","workspaceId","provider","event","deliveryId","connectionId","connectionName","payload","receivedAt","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","scheduleId","undefined","idempotencyKey","error","failure","dispatchErrored","recordFire","allErrored","outcome","failed","history","triggered","routed","add","record","Math","max","Date","now","getTime"],"mappings":"AAAA,SAAQA,0BAA0B,QAAO,uBAAuB;AAChE,SAAQC,cAAc,EAAEC,WAAW,QAAO,uBAAuB;AACjE,SAAQC,gBAAgB,QAAO,cAAc;AAC7C,SAAQC,+BAA+B,EAAEC,gCAAgC,QAAO,cAAc;AAC9F,SAAQC,mBAAmB,EAAEC,QAAQ,QAAO,8BAA8B;AAC1E,SAAQC,wBAAwB,QAAmC,wBAAwB;AAiB3F;;;;;;;;;CASC,GACD,OAAO,eAAeC,qBACpBC,MAAkC;IAElC,MAAMC,eAAe,MAAMX,2BAA2BU,OAAOE,cAAc;IAC3E,IAAI,CAACD,cAAc,MAAM,IAAIN,iCAAiCK,OAAOE,cAAc;IACnF,IAAID,aAAaE,MAAM,KAAK,QAAQ;QAClC,MAAM,IAAIT,gCAAgCM,OAAOE,cAAc,EAAED,aAAaE,MAAM;IACtF;IAEA,oFAAoF;IACpF,oFAAoF;IACpF,MAAMC,WAAW,GAAGH,aAAaI,EAAE,CAAC,CAAC,EAAEL,OAAOM,aAAa,CAACC,WAAW,IAAI;IAC3E,MAAMC,cAAc;QAClBC,QAAQ;QACRC,aAAaT,aAAaS,WAAW;QACrCC,UAAU;QACVR,QAAQF,aAAaE,MAAM;QAC3B,wEAAwE;QACxE,sDAAsD;QACtDS,OAAOX,aAAaW,KAAK,IAAI;QAC7BC,YAAY;QACZC,cAAc;QACdC,gBAAgB;QAChBC,SAAS;QACT,0EAA0E;QAC1EC,YAAYjB,OAAOM,aAAa;QAChCF;IACF;IAEA,MAAMc,SAASzB,iBAAiBQ;IAChC,IAAIkB;IACJ,IAAI;QACFA,MAAM,MAAMnB,OAAOoB,SAAS,CAACC,mBAAmB,CAAC;YAC/CX,aAAaT,aAAaS,WAAW;YACrCY,WAAWrB,aAAaqB,SAAS;YACjCC,cAActB,aAAauB,oBAAoB;YAC/CC,gBAAgB;gBACdd,UAAU;gBACVR,QAAQ;gBACRS,OAAO;gBACPc,YAAYzB,aAAaI,EAAE;YAC7B;YACA,GAAIa,WAAWS,YAAY,CAAC,IAAI;gBAACT;YAAM,CAAC;YACxCU,gBAAgBxB;QAClB;IACF,EAAE,OAAOyB,OAAO;QACd,MAAMC,UAAU,MAAMlC,oBAAoBY;QAC1C,MAAMsB,QAAQC,eAAe,CAAC9B,cAAcJ,SAASgC;QACrD,IAAI/B,yBAAyB+B,QAAQ;YACnCG,WAAW,WAAWhC,OAAOM,aAAa;YAC1C,MAAMwB,QAAQG,UAAU,CAAC;YACzB,OAAO;gBAACC,SAAS;YAAS;QAC5B;QACA,gFAAgF;QAChF,4DAA4D;QAC5D,MAAMJ,QAAQK,MAAM,CAAC;QACrB,MAAMN;IACR;IAEA,MAAMO,UAAU,MAAMxC,oBAAoBY;IAC1C,MAAM4B,QAAQC,SAAS,CAACpC,cAAckB;IACtCa,WAAW,SAAShC,OAAOM,aAAa;IACxC,MAAM8B,QAAQE,MAAM,CAAC;IACrB,OAAO;QAACJ,SAAS;QAASf;IAAG;AAC/B;AAEA,SAASa,WAAWE,OAA4B,EAAE5B,aAAmB;IACnEf,eAAegD,GAAG,CAAC,GAAG;QAACL;IAAO;IAC9B1C,YAAYgD,MAAM,CAACC,KAAKC,GAAG,CAAC,GAAGC,KAAKC,GAAG,KAAKtC,cAAcuC,OAAO;AACnE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fire-manual.d.ts","sourceRoot":"","sources":["../../src/core/fire-manual.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,qBAAqB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"fire-manual.d.ts","sourceRoot":"","sources":["../../src/core/fire-manual.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,qBAAqB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC,CAsErC"}
|
package/dist/core/fire-manual.js
CHANGED
|
@@ -22,7 +22,9 @@ export async function fireManualSubscription(params) {
|
|
|
22
22
|
workspaceId: subscription.workspaceId,
|
|
23
23
|
provider: null,
|
|
24
24
|
source: subscription.source,
|
|
25
|
-
event
|
|
25
|
+
// Manual triggers have no inbound event. Keep the canonical history event
|
|
26
|
+
// for a row that bypassed the projection write-path validation.
|
|
27
|
+
event: subscription.event ?? 'fire',
|
|
26
28
|
deliveryId: null,
|
|
27
29
|
connectionId: null,
|
|
28
30
|
connectionName: null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/fire-manual.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {getTriggerSubscriptionById} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {readConfigInputs} from './config.js';\nimport {\n TriggerSubscriptionNotFoundError,\n TriggerSubscriptionNotManualError,\n TriggerWorkspaceMismatchError,\n} from './errors.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface FireManualSubscriptionParams {\n workflows: WorkflowsModuleClient;\n subscriptionId: string;\n callerWorkspaceId: string;\n userId: string;\n inputs?: Record<string, unknown> | undefined;\n}\n\nexport async function fireManualSubscription(\n params: FireManualSubscriptionParams,\n): Promise<{id: string; name: string}> {\n const subscription = await getTriggerSubscriptionById(params.subscriptionId);\n if (!subscription) throw new TriggerSubscriptionNotFoundError(params.subscriptionId);\n if (subscription.source !== 'manual') {\n throw new TriggerSubscriptionNotManualError(params.subscriptionId, subscription.source);\n }\n // Defence in depth: unreachable from the HTTP route, but required for any direct caller.\n if (subscription.workspaceId !== params.callerWorkspaceId) {\n throw new TriggerWorkspaceMismatchError(\n params.subscriptionId,\n subscription.workspaceId,\n params.callerWorkspaceId,\n );\n }\n\n // Manual fires have no upstream event id. Use the run id after success; failed\n // attempts need a synthesized ref because there is no run to key on.\n const historyBase = {\n origin: 'manual' as const,\n workspaceId: subscription.workspaceId,\n provider: null,\n source: subscription.source,\n event: subscription.event,\n deliveryId: null,\n connectionId: null,\n connectionName: null,\n payload: null,\n receivedAt: new Date(),\n };\n\n eventReceivedCount.add(1, {provider: 'manual'});\n\n const inputs = params.inputs ?? readConfigInputs(subscription);\n let run: {id: string; name: string};\n try {\n run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: 'manual',\n source: 'manual',\n event: 'fire',\n subscriptionId: subscription.id,\n userId: params.userId,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: randomUUID(),\n });\n } catch (error) {\n const failure = await beginTriggerHistory({...historyBase, eventRef: randomUUID()});\n await failure.dispatchErrored(subscription, toReason(error));\n if (isPermanentStartRunError(error)) {\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'errored'});\n await failure.allErrored(1);\n } else {\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'failed'});\n await failure.failed(1);\n }\n throw error;\n }\n\n const history = await beginTriggerHistory({...historyBase, eventRef: run.id});\n await history.triggered(subscription, run);\n subscriptionTriggeredCount.add(1, {provider: 'manual'});\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'routed'});\n await history.routed(1);\n return run;\n}\n"],"names":["randomUUID","getTriggerSubscriptionById","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","readConfigInputs","TriggerSubscriptionNotFoundError","TriggerSubscriptionNotManualError","TriggerWorkspaceMismatchError","beginTriggerHistory","toReason","isPermanentStartRunError","fireManualSubscription","params","subscription","subscriptionId","source","workspaceId","callerWorkspaceId","historyBase","origin","provider","event","deliveryId","connectionId","connectionName","payload","receivedAt","Date","add","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","id","userId","undefined","idempotencyKey","error","failure","eventRef","dispatchErrored","outcome","allErrored","failed","history","triggered","routed"],"mappings":"AAAA,SAAQA,UAAU,QAAO,cAAc;AACvC,SAAQC,0BAA0B,QAAO,uBAAuB;AAChE,SACEC,iBAAiB,EACjBC,kBAAkB,EAClBC,0BAA0B,QACrB,uBAAuB;AAC9B,SAAQC,gBAAgB,QAAO,cAAc;AAC7C,SACEC,gCAAgC,EAChCC,iCAAiC,EACjCC,6BAA6B,QACxB,cAAc;AACrB,SAAQC,mBAAmB,EAAEC,QAAQ,QAAO,8BAA8B;AAC1E,SAAQC,wBAAwB,QAAmC,wBAAwB;AAU3F,OAAO,eAAeC,uBACpBC,MAAoC;IAEpC,MAAMC,eAAe,MAAMb,2BAA2BY,OAAOE,cAAc;IAC3E,IAAI,CAACD,cAAc,MAAM,IAAIR,iCAAiCO,OAAOE,cAAc;IACnF,IAAID,aAAaE,MAAM,KAAK,UAAU;QACpC,MAAM,IAAIT,kCAAkCM,OAAOE,cAAc,EAAED,aAAaE,MAAM;IACxF;IACA,yFAAyF;IACzF,IAAIF,aAAaG,WAAW,KAAKJ,OAAOK,iBAAiB,EAAE;QACzD,MAAM,IAAIV,8BACRK,OAAOE,cAAc,EACrBD,aAAaG,WAAW,EACxBJ,OAAOK,iBAAiB;IAE5B;IAEA,+EAA+E;IAC/E,qEAAqE;IACrE,MAAMC,cAAc;QAClBC,QAAQ;QACRH,aAAaH,aAAaG,WAAW;QACrCI,UAAU;QACVL,QAAQF,aAAaE,MAAM;
|
|
1
|
+
{"version":3,"sources":["../../src/core/fire-manual.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {getTriggerSubscriptionById} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {readConfigInputs} from './config.js';\nimport {\n TriggerSubscriptionNotFoundError,\n TriggerSubscriptionNotManualError,\n TriggerWorkspaceMismatchError,\n} from './errors.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface FireManualSubscriptionParams {\n workflows: WorkflowsModuleClient;\n subscriptionId: string;\n callerWorkspaceId: string;\n userId: string;\n inputs?: Record<string, unknown> | undefined;\n}\n\nexport async function fireManualSubscription(\n params: FireManualSubscriptionParams,\n): Promise<{id: string; name: string}> {\n const subscription = await getTriggerSubscriptionById(params.subscriptionId);\n if (!subscription) throw new TriggerSubscriptionNotFoundError(params.subscriptionId);\n if (subscription.source !== 'manual') {\n throw new TriggerSubscriptionNotManualError(params.subscriptionId, subscription.source);\n }\n // Defence in depth: unreachable from the HTTP route, but required for any direct caller.\n if (subscription.workspaceId !== params.callerWorkspaceId) {\n throw new TriggerWorkspaceMismatchError(\n params.subscriptionId,\n subscription.workspaceId,\n params.callerWorkspaceId,\n );\n }\n\n // Manual fires have no upstream event id. Use the run id after success; failed\n // attempts need a synthesized ref because there is no run to key on.\n const historyBase = {\n origin: 'manual' as const,\n workspaceId: subscription.workspaceId,\n provider: null,\n source: subscription.source,\n // Manual triggers have no inbound event. Keep the canonical history event\n // for a row that bypassed the projection write-path validation.\n event: subscription.event ?? 'fire',\n deliveryId: null,\n connectionId: null,\n connectionName: null,\n payload: null,\n receivedAt: new Date(),\n };\n\n eventReceivedCount.add(1, {provider: 'manual'});\n\n const inputs = params.inputs ?? readConfigInputs(subscription);\n let run: {id: string; name: string};\n try {\n run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: 'manual',\n source: 'manual',\n event: 'fire',\n subscriptionId: subscription.id,\n userId: params.userId,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: randomUUID(),\n });\n } catch (error) {\n const failure = await beginTriggerHistory({...historyBase, eventRef: randomUUID()});\n await failure.dispatchErrored(subscription, toReason(error));\n if (isPermanentStartRunError(error)) {\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'errored'});\n await failure.allErrored(1);\n } else {\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'failed'});\n await failure.failed(1);\n }\n throw error;\n }\n\n const history = await beginTriggerHistory({...historyBase, eventRef: run.id});\n await history.triggered(subscription, run);\n subscriptionTriggeredCount.add(1, {provider: 'manual'});\n eventOutcomeCount.add(1, {provider: 'manual', outcome: 'routed'});\n await history.routed(1);\n return run;\n}\n"],"names":["randomUUID","getTriggerSubscriptionById","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","readConfigInputs","TriggerSubscriptionNotFoundError","TriggerSubscriptionNotManualError","TriggerWorkspaceMismatchError","beginTriggerHistory","toReason","isPermanentStartRunError","fireManualSubscription","params","subscription","subscriptionId","source","workspaceId","callerWorkspaceId","historyBase","origin","provider","event","deliveryId","connectionId","connectionName","payload","receivedAt","Date","add","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","id","userId","undefined","idempotencyKey","error","failure","eventRef","dispatchErrored","outcome","allErrored","failed","history","triggered","routed"],"mappings":"AAAA,SAAQA,UAAU,QAAO,cAAc;AACvC,SAAQC,0BAA0B,QAAO,uBAAuB;AAChE,SACEC,iBAAiB,EACjBC,kBAAkB,EAClBC,0BAA0B,QACrB,uBAAuB;AAC9B,SAAQC,gBAAgB,QAAO,cAAc;AAC7C,SACEC,gCAAgC,EAChCC,iCAAiC,EACjCC,6BAA6B,QACxB,cAAc;AACrB,SAAQC,mBAAmB,EAAEC,QAAQ,QAAO,8BAA8B;AAC1E,SAAQC,wBAAwB,QAAmC,wBAAwB;AAU3F,OAAO,eAAeC,uBACpBC,MAAoC;IAEpC,MAAMC,eAAe,MAAMb,2BAA2BY,OAAOE,cAAc;IAC3E,IAAI,CAACD,cAAc,MAAM,IAAIR,iCAAiCO,OAAOE,cAAc;IACnF,IAAID,aAAaE,MAAM,KAAK,UAAU;QACpC,MAAM,IAAIT,kCAAkCM,OAAOE,cAAc,EAAED,aAAaE,MAAM;IACxF;IACA,yFAAyF;IACzF,IAAIF,aAAaG,WAAW,KAAKJ,OAAOK,iBAAiB,EAAE;QACzD,MAAM,IAAIV,8BACRK,OAAOE,cAAc,EACrBD,aAAaG,WAAW,EACxBJ,OAAOK,iBAAiB;IAE5B;IAEA,+EAA+E;IAC/E,qEAAqE;IACrE,MAAMC,cAAc;QAClBC,QAAQ;QACRH,aAAaH,aAAaG,WAAW;QACrCI,UAAU;QACVL,QAAQF,aAAaE,MAAM;QAC3B,0EAA0E;QAC1E,gEAAgE;QAChEM,OAAOR,aAAaQ,KAAK,IAAI;QAC7BC,YAAY;QACZC,cAAc;QACdC,gBAAgB;QAChBC,SAAS;QACTC,YAAY,IAAIC;IAClB;IAEAzB,mBAAmB0B,GAAG,CAAC,GAAG;QAACR,UAAU;IAAQ;IAE7C,MAAMS,SAASjB,OAAOiB,MAAM,IAAIzB,iBAAiBS;IACjD,IAAIiB;IACJ,IAAI;QACFA,MAAM,MAAMlB,OAAOmB,SAAS,CAACC,mBAAmB,CAAC;YAC/ChB,aAAaH,aAAaG,WAAW;YACrCiB,WAAWpB,aAAaoB,SAAS;YACjCC,cAAcrB,aAAasB,oBAAoB;YAC/CC,gBAAgB;gBACdhB,UAAU;gBACVL,QAAQ;gBACRM,OAAO;gBACPP,gBAAgBD,aAAawB,EAAE;gBAC/BC,QAAQ1B,OAAO0B,MAAM;YACvB;YACA,GAAIT,WAAWU,YAAY,CAAC,IAAI;gBAACV;YAAM,CAAC;YACxCW,gBAAgBzC;QAClB;IACF,EAAE,OAAO0C,OAAO;QACd,MAAMC,UAAU,MAAMlC,oBAAoB;YAAC,GAAGU,WAAW;YAAEyB,UAAU5C;QAAY;QACjF,MAAM2C,QAAQE,eAAe,CAAC/B,cAAcJ,SAASgC;QACrD,IAAI/B,yBAAyB+B,QAAQ;YACnCxC,kBAAkB2B,GAAG,CAAC,GAAG;gBAACR,UAAU;gBAAUyB,SAAS;YAAS;YAChE,MAAMH,QAAQI,UAAU,CAAC;QAC3B,OAAO;YACL7C,kBAAkB2B,GAAG,CAAC,GAAG;gBAACR,UAAU;gBAAUyB,SAAS;YAAQ;YAC/D,MAAMH,QAAQK,MAAM,CAAC;QACvB;QACA,MAAMN;IACR;IAEA,MAAMO,UAAU,MAAMxC,oBAAoB;QAAC,GAAGU,WAAW;QAAEyB,UAAUb,IAAIO,EAAE;IAAA;IAC3E,MAAMW,QAAQC,SAAS,CAACpC,cAAciB;IACtC3B,2BAA2ByB,GAAG,CAAC,GAAG;QAACR,UAAU;IAAQ;IACrDnB,kBAAkB2B,GAAG,CAAC,GAAG;QAACR,UAAU;QAAUyB,SAAS;IAAQ;IAC/D,MAAMG,QAAQE,MAAM,CAAC;IACrB,OAAOpB;AACT"}
|
|
@@ -8,6 +8,8 @@ export interface TriggerRun {
|
|
|
8
8
|
}
|
|
9
9
|
export interface TriggerHistoryRecorder {
|
|
10
10
|
triggered(subscription: TriggerSubscription, run: TriggerRun): Promise<void>;
|
|
11
|
+
devTriggered(triggerKey: string, workflowDefinitionId: string, run: TriggerRun): Promise<void>;
|
|
12
|
+
devFilterErrored(triggerKey: string, workflowDefinitionId: string, reason: string): Promise<void>;
|
|
11
13
|
filterErrored(subscription: TriggerSubscription, reason: string): Promise<void>;
|
|
12
14
|
dispatchErrored(subscription: TriggerSubscription, reason: string): Promise<void>;
|
|
13
15
|
listenerTriggered(subscription: JobListenerSubscription): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-trigger-history.d.ts","sourceRoot":"","sources":["../../src/core/record-trigger-history.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"record-trigger-history.d.ts","sourceRoot":"","sources":["../../src/core/record-trigger-history.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AAqBxE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQ/C;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,YAAY,EAAE,mBAAmB,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7E,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClG,aAAa,CAAC,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,eAAe,CAAC,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,iBAAiB,CAAC,YAAY,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,qBAAqB,CAAC,YAAY,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,uBAAuB,CAAC,YAAY,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9F,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,UAAU,EAAE,IAAI,CAAC;CAClB;AAID,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAwEjC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reportError } from '@shipfox/node-error-monitoring';
|
|
2
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
3
|
-
import { insertReceivedEvent, markReceivedEventDiscarded, markReceivedEventErrored, markReceivedEventFailed, markReceivedEventRouted, upsertDispatchErrorDecision, upsertFilterErrorDecision, upsertListenerDispatchErrorDecision, upsertListenerFilterErrorDecision, upsertListenerTriggeredDecision, upsertTriggeredDecision } from '#db/event-history.js';
|
|
3
|
+
import { insertReceivedEvent, markReceivedEventDiscarded, markReceivedEventErrored, markReceivedEventFailed, markReceivedEventRouted, upsertDevFilterErrorDecision, upsertDevTriggeredDecision, upsertDispatchErrorDecision, upsertFilterErrorDecision, upsertListenerDispatchErrorDecision, upsertListenerFilterErrorDecision, upsertListenerTriggeredDecision, upsertTriggeredDecision } from '#db/event-history.js';
|
|
4
4
|
const MAX_REASON_LENGTH = 2000;
|
|
5
5
|
// A bounded, deterministic reason string safe to persist: error messages can be
|
|
6
6
|
// long or carry untrusted data, so cap the length and never serialize the object.
|
|
@@ -27,6 +27,18 @@ export async function beginTriggerHistory(params) {
|
|
|
27
27
|
subscription,
|
|
28
28
|
run
|
|
29
29
|
}), subscription.id),
|
|
30
|
+
devTriggered: (triggerKey, workflowDefinitionId, run)=>record('dev-triggered-decision', (id)=>upsertDevTriggeredDecision({
|
|
31
|
+
receivedEventId: id,
|
|
32
|
+
triggerKey,
|
|
33
|
+
workflowDefinitionId,
|
|
34
|
+
run
|
|
35
|
+
})),
|
|
36
|
+
devFilterErrored: (triggerKey, workflowDefinitionId, reason)=>record('dev-filter-error-decision', (id)=>upsertDevFilterErrorDecision({
|
|
37
|
+
receivedEventId: id,
|
|
38
|
+
triggerKey,
|
|
39
|
+
workflowDefinitionId,
|
|
40
|
+
reason
|
|
41
|
+
})),
|
|
30
42
|
filterErrored: (subscription, reason)=>record('filter-error-decision', (id)=>upsertFilterErrorDecision({
|
|
31
43
|
receivedEventId: id,
|
|
32
44
|
subscription,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/record-trigger-history.ts"],"sourcesContent":["import {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';\nimport type {TriggerEventOrigin} from '#core/entities/received-event.js';\nimport type {TriggerSubscription} from '#core/entities/subscription.js';\nimport {\n insertReceivedEvent,\n markReceivedEventDiscarded,\n markReceivedEventErrored,\n markReceivedEventFailed,\n markReceivedEventRouted,\n upsertDispatchErrorDecision,\n upsertFilterErrorDecision,\n upsertListenerDispatchErrorDecision,\n upsertListenerFilterErrorDecision,\n upsertListenerTriggeredDecision,\n upsertTriggeredDecision,\n} from '#db/event-history.js';\n\nconst MAX_REASON_LENGTH = 2000;\n\n// A bounded, deterministic reason string safe to persist: error messages can be\n// long or carry untrusted data, so cap the length and never serialize the object.\nexport function toReason(error: unknown): string {\n let message: string;\n try {\n message = error instanceof Error ? error.message : String(error);\n } catch {\n message = '[unprintable thrown value]';\n }\n return message.slice(0, MAX_REASON_LENGTH);\n}\n\nexport interface TriggerRun {\n id: string;\n name: string;\n}\n\nexport interface TriggerHistoryRecorder {\n triggered(subscription: TriggerSubscription, run: TriggerRun): Promise<void>;\n filterErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n dispatchErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n listenerTriggered(subscription: JobListenerSubscription): Promise<void>;\n listenerFilterErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n listenerDispatchErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n discarded(): Promise<void>;\n routed(matchedCount: number): Promise<void>;\n failed(matchedCount: number): Promise<void>;\n allErrored(matchedCount: number): Promise<void>;\n}\n\nexport interface BeginTriggerHistoryParams {\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n}\n\n// History writes are best-effort and log stable ids only, never payloads or tokens.\n// If the parent insert fails there is no row to attach to, so later calls no-op.\nexport async function beginTriggerHistory(\n params: BeginTriggerHistoryParams,\n): Promise<TriggerHistoryRecorder> {\n const receivedEventId = await safe(params.eventRef, 'insert-received-event', () =>\n insertReceivedEvent(params),\n );\n\n const record = async (\n label: string,\n write: (receivedEventId: string) => Promise<unknown>,\n subscriptionId?: string,\n ): Promise<void> => {\n if (receivedEventId === undefined) return;\n await safe(params.eventRef, label, () => write(receivedEventId), subscriptionId);\n };\n\n return {\n triggered: (subscription, run) =>\n record(\n 'triggered-decision',\n (id) => upsertTriggeredDecision({receivedEventId: id, subscription, run}),\n subscription.id,\n ),\n filterErrored: (subscription, reason) =>\n record(\n 'filter-error-decision',\n (id) => upsertFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n dispatchErrored: (subscription, reason) =>\n record(\n 'dispatch-error-decision',\n (id) => upsertDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerTriggered: (subscription) =>\n record(\n 'listener-triggered-decision',\n (id) => upsertListenerTriggeredDecision({receivedEventId: id, subscription}),\n subscription.id,\n ),\n listenerFilterErrored: (subscription, reason) =>\n record(\n 'listener-filter-error-decision',\n (id) => upsertListenerFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerDispatchErrored: (subscription, reason) =>\n record(\n 'listener-dispatch-error-decision',\n (id) => upsertListenerDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n discarded: () => record('discard-event', (id) => markReceivedEventDiscarded(id)),\n routed: (matchedCount) =>\n record('route-event', (id) => markReceivedEventRouted(id, matchedCount)),\n failed: (matchedCount) =>\n record('fail-event', (id) => markReceivedEventFailed(id, matchedCount)),\n allErrored: (matchedCount) =>\n record('all-errored-event', (id) => markReceivedEventErrored(id, matchedCount)),\n };\n}\n\nasync function safe<T>(\n eventRef: string,\n label: string,\n fn: () => Promise<T>,\n subscriptionId?: string,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n logger().warn(\n {err: error, label, eventRef, ...(subscriptionId ? {subscriptionId} : {})},\n 'trigger history write failed; ignored (best-effort)',\n );\n reportError(error, {\n boundary: 'triggers.history',\n operation: label,\n extra: {eventRef, subscriptionId},\n });\n return undefined;\n }\n}\n"],"names":["reportError","logger","insertReceivedEvent","markReceivedEventDiscarded","markReceivedEventErrored","markReceivedEventFailed","markReceivedEventRouted","upsertDispatchErrorDecision","upsertFilterErrorDecision","upsertListenerDispatchErrorDecision","upsertListenerFilterErrorDecision","upsertListenerTriggeredDecision","upsertTriggeredDecision","MAX_REASON_LENGTH","toReason","error","message","Error","String","slice","beginTriggerHistory","params","receivedEventId","safe","eventRef","record","label","write","subscriptionId","undefined","triggered","subscription","run","id","
|
|
1
|
+
{"version":3,"sources":["../../src/core/record-trigger-history.ts"],"sourcesContent":["import {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';\nimport type {TriggerEventOrigin} from '#core/entities/received-event.js';\nimport type {TriggerSubscription} from '#core/entities/subscription.js';\nimport {\n insertReceivedEvent,\n markReceivedEventDiscarded,\n markReceivedEventErrored,\n markReceivedEventFailed,\n markReceivedEventRouted,\n upsertDevFilterErrorDecision,\n upsertDevTriggeredDecision,\n upsertDispatchErrorDecision,\n upsertFilterErrorDecision,\n upsertListenerDispatchErrorDecision,\n upsertListenerFilterErrorDecision,\n upsertListenerTriggeredDecision,\n upsertTriggeredDecision,\n} from '#db/event-history.js';\n\nconst MAX_REASON_LENGTH = 2000;\n\n// A bounded, deterministic reason string safe to persist: error messages can be\n// long or carry untrusted data, so cap the length and never serialize the object.\nexport function toReason(error: unknown): string {\n let message: string;\n try {\n message = error instanceof Error ? error.message : String(error);\n } catch {\n message = '[unprintable thrown value]';\n }\n return message.slice(0, MAX_REASON_LENGTH);\n}\n\nexport interface TriggerRun {\n id: string;\n name: string;\n}\n\nexport interface TriggerHistoryRecorder {\n triggered(subscription: TriggerSubscription, run: TriggerRun): Promise<void>;\n // Dev journal entries carry no subscription row: subscription_name is the\n // trigger key and workflow_definition_id the workflow lineage id.\n devTriggered(triggerKey: string, workflowDefinitionId: string, run: TriggerRun): Promise<void>;\n devFilterErrored(triggerKey: string, workflowDefinitionId: string, reason: string): Promise<void>;\n filterErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n dispatchErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n listenerTriggered(subscription: JobListenerSubscription): Promise<void>;\n listenerFilterErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n listenerDispatchErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n discarded(): Promise<void>;\n routed(matchedCount: number): Promise<void>;\n failed(matchedCount: number): Promise<void>;\n allErrored(matchedCount: number): Promise<void>;\n}\n\nexport interface BeginTriggerHistoryParams {\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n}\n\n// History writes are best-effort and log stable ids only, never payloads or tokens.\n// If the parent insert fails there is no row to attach to, so later calls no-op.\nexport async function beginTriggerHistory(\n params: BeginTriggerHistoryParams,\n): Promise<TriggerHistoryRecorder> {\n const receivedEventId = await safe(params.eventRef, 'insert-received-event', () =>\n insertReceivedEvent(params),\n );\n\n const record = async (\n label: string,\n write: (receivedEventId: string) => Promise<unknown>,\n subscriptionId?: string,\n ): Promise<void> => {\n if (receivedEventId === undefined) return;\n await safe(params.eventRef, label, () => write(receivedEventId), subscriptionId);\n };\n\n return {\n triggered: (subscription, run) =>\n record(\n 'triggered-decision',\n (id) => upsertTriggeredDecision({receivedEventId: id, subscription, run}),\n subscription.id,\n ),\n devTriggered: (triggerKey, workflowDefinitionId, run) =>\n record('dev-triggered-decision', (id) =>\n upsertDevTriggeredDecision({receivedEventId: id, triggerKey, workflowDefinitionId, run}),\n ),\n devFilterErrored: (triggerKey, workflowDefinitionId, reason) =>\n record('dev-filter-error-decision', (id) =>\n upsertDevFilterErrorDecision({\n receivedEventId: id,\n triggerKey,\n workflowDefinitionId,\n reason,\n }),\n ),\n filterErrored: (subscription, reason) =>\n record(\n 'filter-error-decision',\n (id) => upsertFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n dispatchErrored: (subscription, reason) =>\n record(\n 'dispatch-error-decision',\n (id) => upsertDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerTriggered: (subscription) =>\n record(\n 'listener-triggered-decision',\n (id) => upsertListenerTriggeredDecision({receivedEventId: id, subscription}),\n subscription.id,\n ),\n listenerFilterErrored: (subscription, reason) =>\n record(\n 'listener-filter-error-decision',\n (id) => upsertListenerFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerDispatchErrored: (subscription, reason) =>\n record(\n 'listener-dispatch-error-decision',\n (id) => upsertListenerDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n discarded: () => record('discard-event', (id) => markReceivedEventDiscarded(id)),\n routed: (matchedCount) =>\n record('route-event', (id) => markReceivedEventRouted(id, matchedCount)),\n failed: (matchedCount) =>\n record('fail-event', (id) => markReceivedEventFailed(id, matchedCount)),\n allErrored: (matchedCount) =>\n record('all-errored-event', (id) => markReceivedEventErrored(id, matchedCount)),\n };\n}\n\nasync function safe<T>(\n eventRef: string,\n label: string,\n fn: () => Promise<T>,\n subscriptionId?: string,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n logger().warn(\n {err: error, label, eventRef, ...(subscriptionId ? {subscriptionId} : {})},\n 'trigger history write failed; ignored (best-effort)',\n );\n reportError(error, {\n boundary: 'triggers.history',\n operation: label,\n extra: {eventRef, subscriptionId},\n });\n return undefined;\n }\n}\n"],"names":["reportError","logger","insertReceivedEvent","markReceivedEventDiscarded","markReceivedEventErrored","markReceivedEventFailed","markReceivedEventRouted","upsertDevFilterErrorDecision","upsertDevTriggeredDecision","upsertDispatchErrorDecision","upsertFilterErrorDecision","upsertListenerDispatchErrorDecision","upsertListenerFilterErrorDecision","upsertListenerTriggeredDecision","upsertTriggeredDecision","MAX_REASON_LENGTH","toReason","error","message","Error","String","slice","beginTriggerHistory","params","receivedEventId","safe","eventRef","record","label","write","subscriptionId","undefined","triggered","subscription","run","id","devTriggered","triggerKey","workflowDefinitionId","devFilterErrored","reason","filterErrored","dispatchErrored","listenerTriggered","listenerFilterErrored","listenerDispatchErrored","discarded","routed","matchedCount","failed","allErrored","fn","warn","err","boundary","operation","extra"],"mappings":"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"}
|
|
@@ -254,6 +254,8 @@ function listenerDisposition(subscription) {
|
|
|
254
254
|
return subscription.kind === 'until' ? 'resolve' : 'fire';
|
|
255
255
|
}
|
|
256
256
|
function listenerMatcherSortKey(subscription) {
|
|
257
|
+
// An until matcher wins over an on matcher, including when the until matcher
|
|
258
|
+
// is a source wildcard. Resolving the job intentionally suppresses delivery.
|
|
257
259
|
const kindRank = subscription.kind === 'until' ? '0' : '1';
|
|
258
260
|
return `${kindRank}:${subscription.matcherOrdinal.toString().padStart(10, '0')}`;
|
|
259
261
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/route-event-to-job-listeners.ts"],"sourcesContent":["import {\n type ListenerFilterOutputTypes,\n listenerFilterOutputTypesSchema,\n} from '@shipfox/api-workflows-dto';\nimport {projectWorkflowPredicateContext, rehydrateJsonExpressionRecord} from '@shipfox/expression';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {findMatchingJobListenerSubscriptions} from '#db/job-listener-subscriptions.js';\nimport {evaluateStoredFilter, type StoredFilterEvaluation} from './config.js';\nimport type {JobListenerSubscription} from './entities/job-listener-subscription.js';\nimport {type TriggerHistoryRecorder, toReason} from './record-trigger-history.js';\nimport {\n isPermanentDeliverEventToJobListenerError,\n type WorkflowsModuleClient,\n} from './workflows-client.js';\n\nconst LISTENER_INTEGER_PATTERN = /^-?\\d+$/;\n\nexport interface RouteEventToJobListenersParams {\n workflows: WorkflowsModuleClient;\n history: TriggerHistoryRecorder;\n eventRef: string;\n workspaceId: string;\n connectionId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n payload: unknown;\n receivedAt: Date;\n}\n\nexport interface RouteEventToJobListenersResult {\n /**\n * Listener outcomes that should contribute to received_event.matched_count:\n * filter errors, accepted deliveries, and dispatch errors. Stale skipped jobs\n * are intentionally excluded because they did not produce an auditable decision.\n */\n engagedCount: number;\n /**\n * Jobs with an effective matcher after source/event/filter checks, including\n * stale skipped jobs.\n */\n matchedJobCount: number;\n acceptedJobCount: number;\n deliveredCount: number;\n transientErrored: boolean;\n transientError: unknown;\n}\n\nexport async function routeEventToJobListeners(\n params: RouteEventToJobListenersParams,\n): Promise<RouteEventToJobListenersResult> {\n const subscriptions = await findMatchingJobListenerSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let filterErrorCount = 0;\n const effectiveMatchByJobId = new Map<string, JobListenerSubscription>();\n for (const subscription of subscriptions) {\n const filterResult = evaluateListenerFilter({subscription, payload: params.payload});\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n filterErrorCount += 1;\n await params.history.listenerFilterErrored(subscription, filterResult.reason);\n continue;\n }\n\n const previous = effectiveMatchByJobId.get(subscription.jobId);\n if (!previous || shouldReplaceEffectiveMatcher(previous, subscription)) {\n effectiveMatchByJobId.set(subscription.jobId, subscription);\n }\n }\n\n const fireDeliveryNeeded = [...effectiveMatchByJobId.values()].some(\n (subscription) => listenerDisposition(subscription) === 'fire',\n );\n const triggerReference = fireDeliveryNeeded\n ? await params.workflows.resolveWorkflowRunTriggerReference({\n workspaceId: params.workspaceId,\n triggerConnectionId: params.connectionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n })\n : null;\n\n let acceptedJobCount = 0;\n let deliveredCount = 0;\n let sawTransientError = false;\n let dispatchErrorCount = 0;\n let firstTransientError: unknown;\n\n for (const subscription of effectiveMatchByJobId.values()) {\n const disposition = listenerDisposition(subscription);\n try {\n const result = await params.workflows.deliverEventToJobListener({\n jobId: subscription.jobId,\n disposition,\n eventRef: params.eventRef,\n deliveryId: params.deliveryId,\n source: params.source,\n event: params.event,\n provider: params.provider,\n triggerConnectionId: params.connectionId,\n triggerReference,\n payload: params.payload,\n receivedAt: params.receivedAt.toISOString(),\n });\n if (!result.skipped) {\n acceptedJobCount += 1;\n await params.history.listenerTriggered(subscription);\n }\n if (result.buffered) deliveredCount += 1;\n } catch (error) {\n dispatchErrorCount += 1;\n await params.history.listenerDispatchErrored(subscription, toReason(error));\n if (!isPermanentDeliverEventToJobListenerError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n return {\n engagedCount: filterErrorCount + acceptedJobCount + dispatchErrorCount,\n matchedJobCount: effectiveMatchByJobId.size,\n acceptedJobCount,\n deliveredCount,\n transientErrored: sawTransientError,\n transientError: sawTransientError ? firstTransientError : undefined,\n };\n}\n\ninterface EvaluateListenerFilterParams {\n subscription: JobListenerSubscription;\n payload: unknown;\n}\n\nfunction evaluateListenerFilter(params: EvaluateListenerFilterParams): StoredFilterEvaluation {\n const filter = params.subscription.config.filter;\n if (filter === null || filter === undefined) return {kind: 'matched'};\n if (typeof filter !== 'string' || filter.trim() === '') {\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {event: params.payload},\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n }\n\n const snapshot = readFilterSnapshot(params.subscription);\n if (snapshot.kind === 'invalid') return snapshot.result;\n\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {\n ...rehydrateListenerFilterSnapshot(snapshot.value, snapshot.outputTypes),\n event: params.payload,\n },\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n}\n\nfunction readFilterSnapshot(\n subscription: JobListenerSubscription,\n):\n | {kind: 'valid'; value: Record<string, unknown>; outputTypes?: ListenerFilterOutputTypes}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (\n value !== undefined &&\n (value === null || typeof value !== 'object' || Array.isArray(value))\n ) {\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter snapshot must be an object when set',\n },\n };\n }\n\n const outputTypesValue = subscription.config.filter_output_types;\n if (outputTypesValue !== undefined) {\n const parsedOutputTypes = listenerFilterOutputTypesSchema.safeParse(outputTypesValue);\n if (!parsedOutputTypes.success) {\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter output types must be valid when set',\n },\n };\n }\n return {\n kind: 'valid',\n value: (value ?? {}) as Record<string, unknown>,\n outputTypes: parsedOutputTypes.data,\n };\n }\n\n return {kind: 'valid', value: (value ?? {}) as Record<string, unknown>};\n}\n\nfunction rehydrateListenerFilterSnapshot(\n snapshot: Record<string, unknown>,\n outputTypes: ListenerFilterOutputTypes | undefined,\n): Record<string, unknown> {\n let result = snapshot;\n if (outputTypes !== undefined && isRecord(snapshot.jobs)) {\n const jobs = Object.fromEntries(\n Object.entries(snapshot.jobs).map(([jobKey, jobContext]) => {\n const jobOutputTypes = Object.hasOwn(outputTypes, jobKey) ? outputTypes[jobKey] : undefined;\n if (jobOutputTypes === undefined || !isRecord(jobContext)) {\n return [jobKey, jobContext];\n }\n\n const outputs = isRecord(jobContext.outputs)\n ? rehydrateJsonExpressionRecord(jobContext.outputs, jobOutputTypes)\n : jobContext.outputs;\n const executions = Array.isArray(jobContext.executions)\n ? jobContext.executions.map((execution) => {\n if (!isRecord(execution) || !isRecord(execution.outputs)) return execution;\n return {\n ...execution,\n outputs: rehydrateJsonExpressionRecord(execution.outputs, jobOutputTypes),\n };\n })\n : jobContext.executions;\n\n return [\n jobKey,\n {\n ...jobContext,\n outputs,\n executions,\n },\n ];\n }),\n );\n result = {...snapshot, jobs};\n }\n\n const rehydrated = {...result};\n if (isRecord(result.run)) rehydrated.run = rehydrateRunSnapshot(result.run);\n if (isRecord(result.jobs)) rehydrated.jobs = rehydrateJobsSnapshot(result.jobs);\n return rehydrated;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction rehydrateRunSnapshot(run: Record<string, unknown>): Record<string, unknown> {\n const result = {...run};\n if ('number' in run) result.number = rehydrateListenerInt(run.number);\n if ('created_at' in run) result.created_at = rehydrateListenerTimestamp(run.created_at);\n return result;\n}\n\nfunction rehydrateJobsSnapshot(jobs: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(jobs).map(([jobKey, value]) => [\n jobKey,\n isRecord(value) ? rehydrateJobSnapshot(value) : value,\n ]),\n );\n}\n\nfunction rehydrateJobSnapshot(job: Record<string, unknown>): Record<string, unknown> {\n if (!Array.isArray(job.executions)) return job;\n\n return {\n ...job,\n executions: job.executions.map((value) =>\n isRecord(value) ? rehydrateExecutionSnapshot(value) : value,\n ),\n };\n}\n\nfunction rehydrateExecutionSnapshot(execution: Record<string, unknown>): Record<string, unknown> {\n const result = {...execution};\n if ('index' in execution) result.index = rehydrateListenerInt(execution.index);\n if ('started_at' in execution) {\n result.started_at = rehydrateListenerTimestamp(execution.started_at);\n }\n if ('finished_at' in execution) {\n result.finished_at = rehydrateListenerTimestamp(execution.finished_at);\n }\n if (Array.isArray(execution.events)) {\n result.events = execution.events.map((value) =>\n isRecord(value) ? rehydrateExecutionEventSnapshot(value) : value,\n );\n }\n return result;\n}\n\nfunction rehydrateExecutionEventSnapshot(event: Record<string, unknown>): Record<string, unknown> {\n if (!('received_at' in event)) return event;\n return {...event, received_at: rehydrateListenerTimestamp(event.received_at)};\n}\n\nfunction rehydrateListenerInt(value: unknown): unknown {\n if (typeof value === 'number' && Number.isSafeInteger(value)) return BigInt(value);\n if (typeof value !== 'string' || !LISTENER_INTEGER_PATTERN.test(value)) return value;\n\n try {\n return BigInt(value);\n } catch {\n return value;\n }\n}\n\nfunction rehydrateListenerTimestamp(value: unknown): unknown {\n if (typeof value !== 'string') return value;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? value : date;\n}\n\nfunction listenerDisposition(subscription: JobListenerSubscription): 'fire' | 'resolve' {\n return subscription.kind === 'until' ? 'resolve' : 'fire';\n}\n\nfunction listenerMatcherSortKey(subscription: JobListenerSubscription): string {\n const kindRank = subscription.kind === 'until' ? '0' : '1';\n return `${kindRank}:${subscription.matcherOrdinal.toString().padStart(10, '0')}`;\n}\n\nfunction shouldReplaceEffectiveMatcher(\n previous: JobListenerSubscription,\n candidate: JobListenerSubscription,\n): boolean {\n const previousKey = listenerMatcherSortKey(previous);\n const candidateKey = listenerMatcherSortKey(candidate);\n if (candidateKey !== previousKey) return candidateKey < previousKey;\n\n logger().warn(\n {\n jobId: candidate.jobId,\n keptSubscriptionId: previous.id,\n candidateSubscriptionId: candidate.id,\n matcherKind: candidate.kind,\n matcherOrdinal: candidate.matcherOrdinal,\n },\n 'duplicate job listener matcher key encountered; choosing deterministic subscription id order',\n );\n return candidate.id < previous.id;\n}\n"],"names":["listenerFilterOutputTypesSchema","projectWorkflowPredicateContext","rehydrateJsonExpressionRecord","logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","LISTENER_INTEGER_PATTERN","routeEventToJobListeners","params","subscriptions","workspaceId","source","event","filterErrorCount","effectiveMatchByJobId","Map","subscription","filterResult","evaluateListenerFilter","payload","kind","history","listenerFilterErrored","reason","previous","get","jobId","shouldReplaceEffectiveMatcher","set","fireDeliveryNeeded","values","some","listenerDisposition","triggerReference","workflows","resolveWorkflowRunTriggerReference","triggerConnectionId","connectionId","triggerPayload","provider","deliveryId","data","acceptedJobCount","deliveredCount","sawTransientError","dispatchErrorCount","firstTransientError","disposition","result","deliverEventToJobListener","eventRef","receivedAt","toISOString","skipped","listenerTriggered","buffered","error","listenerDispatchErrored","engagedCount","matchedJobCount","size","transientErrored","transientError","undefined","filter","config","trim","value","context","invalidReason","evaluationFailedReason","snapshot","readFilterSnapshot","rehydrateListenerFilterSnapshot","outputTypes","filter_snapshot","Array","isArray","outputTypesValue","filter_output_types","parsedOutputTypes","safeParse","success","isRecord","jobs","Object","fromEntries","entries","map","jobKey","jobContext","jobOutputTypes","hasOwn","outputs","executions","execution","rehydrated","run","rehydrateRunSnapshot","rehydrateJobsSnapshot","number","rehydrateListenerInt","created_at","rehydrateListenerTimestamp","rehydrateJobSnapshot","job","rehydrateExecutionSnapshot","index","started_at","finished_at","events","rehydrateExecutionEventSnapshot","received_at","Number","isSafeInteger","BigInt","test","date","Date","isNaN","getTime","listenerMatcherSortKey","kindRank","matcherOrdinal","toString","padStart","candidate","previousKey","candidateKey","warn","keptSubscriptionId","id","candidateSubscriptionId","matcherKind"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/route-event-to-job-listeners.ts"],"sourcesContent":["import {\n type ListenerFilterOutputTypes,\n listenerFilterOutputTypesSchema,\n} from '@shipfox/api-workflows-dto';\nimport {projectWorkflowPredicateContext, rehydrateJsonExpressionRecord} from '@shipfox/expression';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {findMatchingJobListenerSubscriptions} from '#db/job-listener-subscriptions.js';\nimport {evaluateStoredFilter, type StoredFilterEvaluation} from './config.js';\nimport type {JobListenerSubscription} from './entities/job-listener-subscription.js';\nimport {type TriggerHistoryRecorder, toReason} from './record-trigger-history.js';\nimport {\n isPermanentDeliverEventToJobListenerError,\n type WorkflowsModuleClient,\n} from './workflows-client.js';\n\nconst LISTENER_INTEGER_PATTERN = /^-?\\d+$/;\n\nexport interface RouteEventToJobListenersParams {\n workflows: WorkflowsModuleClient;\n history: TriggerHistoryRecorder;\n eventRef: string;\n workspaceId: string;\n connectionId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n payload: unknown;\n receivedAt: Date;\n}\n\nexport interface RouteEventToJobListenersResult {\n /**\n * Listener outcomes that should contribute to received_event.matched_count:\n * filter errors, accepted deliveries, and dispatch errors. Stale skipped jobs\n * are intentionally excluded because they did not produce an auditable decision.\n */\n engagedCount: number;\n /**\n * Jobs with an effective matcher after source/event/filter checks, including\n * stale skipped jobs.\n */\n matchedJobCount: number;\n acceptedJobCount: number;\n deliveredCount: number;\n transientErrored: boolean;\n transientError: unknown;\n}\n\nexport async function routeEventToJobListeners(\n params: RouteEventToJobListenersParams,\n): Promise<RouteEventToJobListenersResult> {\n const subscriptions = await findMatchingJobListenerSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let filterErrorCount = 0;\n const effectiveMatchByJobId = new Map<string, JobListenerSubscription>();\n for (const subscription of subscriptions) {\n const filterResult = evaluateListenerFilter({subscription, payload: params.payload});\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n filterErrorCount += 1;\n await params.history.listenerFilterErrored(subscription, filterResult.reason);\n continue;\n }\n\n const previous = effectiveMatchByJobId.get(subscription.jobId);\n if (!previous || shouldReplaceEffectiveMatcher(previous, subscription)) {\n effectiveMatchByJobId.set(subscription.jobId, subscription);\n }\n }\n\n const fireDeliveryNeeded = [...effectiveMatchByJobId.values()].some(\n (subscription) => listenerDisposition(subscription) === 'fire',\n );\n const triggerReference = fireDeliveryNeeded\n ? await params.workflows.resolveWorkflowRunTriggerReference({\n workspaceId: params.workspaceId,\n triggerConnectionId: params.connectionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n })\n : null;\n\n let acceptedJobCount = 0;\n let deliveredCount = 0;\n let sawTransientError = false;\n let dispatchErrorCount = 0;\n let firstTransientError: unknown;\n\n for (const subscription of effectiveMatchByJobId.values()) {\n const disposition = listenerDisposition(subscription);\n try {\n const result = await params.workflows.deliverEventToJobListener({\n jobId: subscription.jobId,\n disposition,\n eventRef: params.eventRef,\n deliveryId: params.deliveryId,\n source: params.source,\n event: params.event,\n provider: params.provider,\n triggerConnectionId: params.connectionId,\n triggerReference,\n payload: params.payload,\n receivedAt: params.receivedAt.toISOString(),\n });\n if (!result.skipped) {\n acceptedJobCount += 1;\n await params.history.listenerTriggered(subscription);\n }\n if (result.buffered) deliveredCount += 1;\n } catch (error) {\n dispatchErrorCount += 1;\n await params.history.listenerDispatchErrored(subscription, toReason(error));\n if (!isPermanentDeliverEventToJobListenerError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n return {\n engagedCount: filterErrorCount + acceptedJobCount + dispatchErrorCount,\n matchedJobCount: effectiveMatchByJobId.size,\n acceptedJobCount,\n deliveredCount,\n transientErrored: sawTransientError,\n transientError: sawTransientError ? firstTransientError : undefined,\n };\n}\n\ninterface EvaluateListenerFilterParams {\n subscription: JobListenerSubscription;\n payload: unknown;\n}\n\nfunction evaluateListenerFilter(params: EvaluateListenerFilterParams): StoredFilterEvaluation {\n const filter = params.subscription.config.filter;\n if (filter === null || filter === undefined) return {kind: 'matched'};\n if (typeof filter !== 'string' || filter.trim() === '') {\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {event: params.payload},\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n }\n\n const snapshot = readFilterSnapshot(params.subscription);\n if (snapshot.kind === 'invalid') return snapshot.result;\n\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {\n ...rehydrateListenerFilterSnapshot(snapshot.value, snapshot.outputTypes),\n event: params.payload,\n },\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n}\n\nfunction readFilterSnapshot(\n subscription: JobListenerSubscription,\n):\n | {kind: 'valid'; value: Record<string, unknown>; outputTypes?: ListenerFilterOutputTypes}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (\n value !== undefined &&\n (value === null || typeof value !== 'object' || Array.isArray(value))\n ) {\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter snapshot must be an object when set',\n },\n };\n }\n\n const outputTypesValue = subscription.config.filter_output_types;\n if (outputTypesValue !== undefined) {\n const parsedOutputTypes = listenerFilterOutputTypesSchema.safeParse(outputTypesValue);\n if (!parsedOutputTypes.success) {\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter output types must be valid when set',\n },\n };\n }\n return {\n kind: 'valid',\n value: (value ?? {}) as Record<string, unknown>,\n outputTypes: parsedOutputTypes.data,\n };\n }\n\n return {kind: 'valid', value: (value ?? {}) as Record<string, unknown>};\n}\n\nfunction rehydrateListenerFilterSnapshot(\n snapshot: Record<string, unknown>,\n outputTypes: ListenerFilterOutputTypes | undefined,\n): Record<string, unknown> {\n let result = snapshot;\n if (outputTypes !== undefined && isRecord(snapshot.jobs)) {\n const jobs = Object.fromEntries(\n Object.entries(snapshot.jobs).map(([jobKey, jobContext]) => {\n const jobOutputTypes = Object.hasOwn(outputTypes, jobKey) ? outputTypes[jobKey] : undefined;\n if (jobOutputTypes === undefined || !isRecord(jobContext)) {\n return [jobKey, jobContext];\n }\n\n const outputs = isRecord(jobContext.outputs)\n ? rehydrateJsonExpressionRecord(jobContext.outputs, jobOutputTypes)\n : jobContext.outputs;\n const executions = Array.isArray(jobContext.executions)\n ? jobContext.executions.map((execution) => {\n if (!isRecord(execution) || !isRecord(execution.outputs)) return execution;\n return {\n ...execution,\n outputs: rehydrateJsonExpressionRecord(execution.outputs, jobOutputTypes),\n };\n })\n : jobContext.executions;\n\n return [\n jobKey,\n {\n ...jobContext,\n outputs,\n executions,\n },\n ];\n }),\n );\n result = {...snapshot, jobs};\n }\n\n const rehydrated = {...result};\n if (isRecord(result.run)) rehydrated.run = rehydrateRunSnapshot(result.run);\n if (isRecord(result.jobs)) rehydrated.jobs = rehydrateJobsSnapshot(result.jobs);\n return rehydrated;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction rehydrateRunSnapshot(run: Record<string, unknown>): Record<string, unknown> {\n const result = {...run};\n if ('number' in run) result.number = rehydrateListenerInt(run.number);\n if ('created_at' in run) result.created_at = rehydrateListenerTimestamp(run.created_at);\n return result;\n}\n\nfunction rehydrateJobsSnapshot(jobs: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(jobs).map(([jobKey, value]) => [\n jobKey,\n isRecord(value) ? rehydrateJobSnapshot(value) : value,\n ]),\n );\n}\n\nfunction rehydrateJobSnapshot(job: Record<string, unknown>): Record<string, unknown> {\n if (!Array.isArray(job.executions)) return job;\n\n return {\n ...job,\n executions: job.executions.map((value) =>\n isRecord(value) ? rehydrateExecutionSnapshot(value) : value,\n ),\n };\n}\n\nfunction rehydrateExecutionSnapshot(execution: Record<string, unknown>): Record<string, unknown> {\n const result = {...execution};\n if ('index' in execution) result.index = rehydrateListenerInt(execution.index);\n if ('started_at' in execution) {\n result.started_at = rehydrateListenerTimestamp(execution.started_at);\n }\n if ('finished_at' in execution) {\n result.finished_at = rehydrateListenerTimestamp(execution.finished_at);\n }\n if (Array.isArray(execution.events)) {\n result.events = execution.events.map((value) =>\n isRecord(value) ? rehydrateExecutionEventSnapshot(value) : value,\n );\n }\n return result;\n}\n\nfunction rehydrateExecutionEventSnapshot(event: Record<string, unknown>): Record<string, unknown> {\n if (!('received_at' in event)) return event;\n return {...event, received_at: rehydrateListenerTimestamp(event.received_at)};\n}\n\nfunction rehydrateListenerInt(value: unknown): unknown {\n if (typeof value === 'number' && Number.isSafeInteger(value)) return BigInt(value);\n if (typeof value !== 'string' || !LISTENER_INTEGER_PATTERN.test(value)) return value;\n\n try {\n return BigInt(value);\n } catch {\n return value;\n }\n}\n\nfunction rehydrateListenerTimestamp(value: unknown): unknown {\n if (typeof value !== 'string') return value;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? value : date;\n}\n\nfunction listenerDisposition(subscription: JobListenerSubscription): 'fire' | 'resolve' {\n return subscription.kind === 'until' ? 'resolve' : 'fire';\n}\n\nfunction listenerMatcherSortKey(subscription: JobListenerSubscription): string {\n // An until matcher wins over an on matcher, including when the until matcher\n // is a source wildcard. Resolving the job intentionally suppresses delivery.\n const kindRank = subscription.kind === 'until' ? '0' : '1';\n return `${kindRank}:${subscription.matcherOrdinal.toString().padStart(10, '0')}`;\n}\n\nfunction shouldReplaceEffectiveMatcher(\n previous: JobListenerSubscription,\n candidate: JobListenerSubscription,\n): boolean {\n const previousKey = listenerMatcherSortKey(previous);\n const candidateKey = listenerMatcherSortKey(candidate);\n if (candidateKey !== previousKey) return candidateKey < previousKey;\n\n logger().warn(\n {\n jobId: candidate.jobId,\n keptSubscriptionId: previous.id,\n candidateSubscriptionId: candidate.id,\n matcherKind: candidate.kind,\n matcherOrdinal: candidate.matcherOrdinal,\n },\n 'duplicate job listener matcher key encountered; choosing deterministic subscription id order',\n );\n return candidate.id < previous.id;\n}\n"],"names":["listenerFilterOutputTypesSchema","projectWorkflowPredicateContext","rehydrateJsonExpressionRecord","logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","LISTENER_INTEGER_PATTERN","routeEventToJobListeners","params","subscriptions","workspaceId","source","event","filterErrorCount","effectiveMatchByJobId","Map","subscription","filterResult","evaluateListenerFilter","payload","kind","history","listenerFilterErrored","reason","previous","get","jobId","shouldReplaceEffectiveMatcher","set","fireDeliveryNeeded","values","some","listenerDisposition","triggerReference","workflows","resolveWorkflowRunTriggerReference","triggerConnectionId","connectionId","triggerPayload","provider","deliveryId","data","acceptedJobCount","deliveredCount","sawTransientError","dispatchErrorCount","firstTransientError","disposition","result","deliverEventToJobListener","eventRef","receivedAt","toISOString","skipped","listenerTriggered","buffered","error","listenerDispatchErrored","engagedCount","matchedJobCount","size","transientErrored","transientError","undefined","filter","config","trim","value","context","invalidReason","evaluationFailedReason","snapshot","readFilterSnapshot","rehydrateListenerFilterSnapshot","outputTypes","filter_snapshot","Array","isArray","outputTypesValue","filter_output_types","parsedOutputTypes","safeParse","success","isRecord","jobs","Object","fromEntries","entries","map","jobKey","jobContext","jobOutputTypes","hasOwn","outputs","executions","execution","rehydrated","run","rehydrateRunSnapshot","rehydrateJobsSnapshot","number","rehydrateListenerInt","created_at","rehydrateListenerTimestamp","rehydrateJobSnapshot","job","rehydrateExecutionSnapshot","index","started_at","finished_at","events","rehydrateExecutionEventSnapshot","received_at","Number","isSafeInteger","BigInt","test","date","Date","isNaN","getTime","listenerMatcherSortKey","kindRank","matcherOrdinal","toString","padStart","candidate","previousKey","candidateKey","warn","keptSubscriptionId","id","candidateSubscriptionId","matcherKind"],"mappings":"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"}
|