@shipfox/api-triggers 10.2.0 → 12.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 +67 -0
- package/README.md +12 -12
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +3 -3
- package/dist/core/config.js.map +1 -1
- package/dist/core/dispatch-integration-event.d.ts.map +1 -1
- package/dist/core/dispatch-integration-event.js +2 -0
- package/dist/core/dispatch-integration-event.js.map +1 -1
- package/dist/core/route-event-to-job-listeners.d.ts +1 -0
- package/dist/core/route-event-to-job-listeners.d.ts.map +1 -1
- package/dist/core/route-event-to-job-listeners.js +21 -4
- package/dist/core/route-event-to-job-listeners.js.map +1 -1
- package/dist/core/workflows-client.d.ts +10 -1
- package/dist/core/workflows-client.d.ts.map +1 -1
- package/dist/presentation/routes/fire-manual.d.ts.map +1 -1
- package/dist/presentation/routes/fire-manual.js +11 -3
- package/dist/presentation/routes/fire-manual.js.map +1 -1
- package/dist/presentation/routes/get-trigger-event.d.ts.map +1 -1
- package/dist/presentation/routes/get-trigger-event.js +11 -4
- package/dist/presentation/routes/get-trigger-event.js.map +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.d.ts.map +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.js +6 -8
- 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 +5 -7
- package/dist/presentation/routes/list-trigger-events.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +13 -13
- package/src/core/config.test.ts +23 -0
- package/src/core/config.ts +3 -2
- package/src/core/dispatch-integration-event.history-failure.test.ts +5 -0
- package/src/core/dispatch-integration-event.test.ts +8 -1
- package/src/core/dispatch-integration-event.ts +2 -0
- package/src/core/route-event-to-job-listeners.test.ts +99 -0
- package/src/core/route-event-to-job-listeners.ts +29 -2
- package/src/core/workflows-client.test.ts +8 -0
- package/src/presentation/routes/fire-manual.test.ts +22 -2
- package/src/presentation/routes/fire-manual.ts +11 -3
- package/src/presentation/routes/get-trigger-event.test.ts +16 -2
- package/src/presentation/routes/get-trigger-event.ts +9 -4
- package/src/presentation/routes/list-trigger-event-facets.test.ts +18 -2
- package/src/presentation/routes/list-trigger-event-facets.ts +3 -6
- package/src/presentation/routes/list-trigger-events.test.ts +18 -2
- package/src/presentation/routes/list-trigger-events.ts +2 -5
- 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: 58 files with swc (
|
|
3
|
-
2026-
|
|
2
|
+
Successfully compiled: 58 files with swc (406.13ms)
|
|
3
|
+
2026-08-04T08:17:12.242Z [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-
|
|
16
|
+
webpack 5.107.2 compiled successfully in 4485 ms
|
|
17
|
+
2026-08-04T08:17:12.280Z [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,72 @@
|
|
|
1
1
|
# @shipfox/api-triggers
|
|
2
2
|
|
|
3
|
+
## 12.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f78740d: Remove Unicode dash punctuation from package prose and source comments.
|
|
8
|
+
- dea1ffd: Expose normalized repository references on listening execution events.
|
|
9
|
+
- 4d246d4: Align predicate validation and runtime evaluation with field-specific context contracts.
|
|
10
|
+
- cb0abfa: Expose the normalized trigger project, repository, ref, and commit in workflow context.
|
|
11
|
+
- Updated dependencies [ee2ce67]
|
|
12
|
+
- Updated dependencies [7c4116e]
|
|
13
|
+
- Updated dependencies [5d2c9cf]
|
|
14
|
+
- Updated dependencies [e95fdf4]
|
|
15
|
+
- Updated dependencies [3d91d1d]
|
|
16
|
+
- Updated dependencies [f7939c7]
|
|
17
|
+
- Updated dependencies [89f2c18]
|
|
18
|
+
- Updated dependencies [045895c]
|
|
19
|
+
- Updated dependencies [f78740d]
|
|
20
|
+
- Updated dependencies [9e1d599]
|
|
21
|
+
- Updated dependencies [dea1ffd]
|
|
22
|
+
- Updated dependencies [adf07e7]
|
|
23
|
+
- Updated dependencies [3f781ee]
|
|
24
|
+
- Updated dependencies [9fdd5e4]
|
|
25
|
+
- Updated dependencies [285fff2]
|
|
26
|
+
- Updated dependencies [e44a279]
|
|
27
|
+
- Updated dependencies [4d246d4]
|
|
28
|
+
- Updated dependencies [f13e8bb]
|
|
29
|
+
- Updated dependencies [4444079]
|
|
30
|
+
- Updated dependencies [869a792]
|
|
31
|
+
- Updated dependencies [54c820e]
|
|
32
|
+
- Updated dependencies [35a42bd]
|
|
33
|
+
- Updated dependencies [d77baaa]
|
|
34
|
+
- Updated dependencies [41d558c]
|
|
35
|
+
- Updated dependencies [032d316]
|
|
36
|
+
- Updated dependencies [c2a8e54]
|
|
37
|
+
- Updated dependencies [54c820e]
|
|
38
|
+
- Updated dependencies [cb0abfa]
|
|
39
|
+
- Updated dependencies [ee2ce67]
|
|
40
|
+
- Updated dependencies [7f90b0c]
|
|
41
|
+
- @shipfox/workflow-document@3.0.0
|
|
42
|
+
- @shipfox/api-definitions-dto@12.0.0
|
|
43
|
+
- @shipfox/api-workflows-dto@12.0.0
|
|
44
|
+
- @shipfox/expression@2.0.0
|
|
45
|
+
- @shipfox/inter-module@0.2.3
|
|
46
|
+
- @shipfox/node-fastify@0.4.1
|
|
47
|
+
- @shipfox/node-module@1.0.5
|
|
48
|
+
- @shipfox/api-integration-core-dto@12.0.0
|
|
49
|
+
- @shipfox/node-postgres@0.5.0
|
|
50
|
+
- @shipfox/api-auth-context@12.0.0
|
|
51
|
+
- @shipfox/node-drizzle@0.3.5
|
|
52
|
+
- @shipfox/node-outbox@0.2.6
|
|
53
|
+
|
|
54
|
+
## 11.0.0
|
|
55
|
+
|
|
56
|
+
### Major Changes
|
|
57
|
+
|
|
58
|
+
- 25158c8: Carry workspace lifecycle status in JWT membership claims and enforce suspended or inactive access at the stateless workspace gate while keeping access-token verification stateless.
|
|
59
|
+
|
|
60
|
+
`getAuthenticatedSessionContext()` now reads refresh-session metadata from verified access-token claims without checking active refresh-session state; revoking a refresh session does not invalidate an already-issued access token.
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- Updated dependencies [71d9ba4]
|
|
65
|
+
- Updated dependencies [25158c8]
|
|
66
|
+
- @shipfox/expression@1.2.1
|
|
67
|
+
- @shipfox/api-auth-context@11.0.0
|
|
68
|
+
- @shipfox/api-definitions-dto@11.0.0
|
|
69
|
+
|
|
3
70
|
## 10.2.0
|
|
4
71
|
|
|
5
72
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -162,14 +162,14 @@ unique per `(workflow_definition_id, name)`.
|
|
|
162
162
|
|
|
163
163
|
### Words we do not use
|
|
164
164
|
|
|
165
|
-
- `provider
|
|
165
|
+
- `provider`: reserved for the integration module's identity
|
|
166
166
|
(`integrations_connections.provider`). Trigger code says `source`.
|
|
167
|
-
- `eventType` / `type
|
|
167
|
+
- `eventType` / `type`: replaced by `event`. The bare word `type` would
|
|
168
168
|
collide with TypeScript discriminators and with the `IntegrationProvider`
|
|
169
169
|
capability `type` field.
|
|
170
|
-
- `triggerContext
|
|
170
|
+
- `triggerContext`: the runtime payload on a run is called
|
|
171
171
|
`triggerPayload`.
|
|
172
|
-
- `kind
|
|
172
|
+
- `kind`: every trigger is identified by `(source, event)`. There is no
|
|
173
173
|
separate axis.
|
|
174
174
|
|
|
175
175
|
## Architecture
|
|
@@ -205,17 +205,17 @@ or POST /workflow-definitions/:definitionId/fire-manual → look up manual subsc
|
|
|
205
205
|
└─────────────────────────────────────────────────────┘
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
### Layer 1
|
|
208
|
+
### Layer 1: workflow definition (source of truth)
|
|
209
209
|
|
|
210
210
|
The YAML `triggers` map lives inside `workflow_definitions.definition`
|
|
211
211
|
(JSONB owned by the definitions module). That is the only place trigger
|
|
212
212
|
declarations live in raw form.
|
|
213
213
|
|
|
214
|
-
### Layer 2
|
|
214
|
+
### Layer 2: projection (queryable)
|
|
215
215
|
|
|
216
216
|
`triggers_subscriptions` is rebuilt from `DEFINITION_RESOLVED` events,
|
|
217
217
|
which carry the parsed `triggers` map. The triggers module never reads the
|
|
218
|
-
definitions table
|
|
218
|
+
definitions table: the event is the contract.
|
|
219
219
|
|
|
220
220
|
Cron triggers also project into `triggers_cron_schedules`, keyed by
|
|
221
221
|
`subscription_id`. That table stores the resolved cron expression, timezone,
|
|
@@ -223,15 +223,15 @@ next fire time, and last fire time used by the cron firing engine.
|
|
|
223
223
|
|
|
224
224
|
Indexes:
|
|
225
225
|
|
|
226
|
-
- `(workflow_definition_id, name)
|
|
227
|
-
- `(workspace_id, source, event)
|
|
226
|
+
- `(workflow_definition_id, name)`: unique. One row per YAML trigger.
|
|
227
|
+
- `(workspace_id, source, event)`: the hot path for matching incoming
|
|
228
228
|
integration events at workspace scope.
|
|
229
|
-
- `(workflow_definition_id)
|
|
229
|
+
- `(workflow_definition_id)`: used to clean up the projection on
|
|
230
230
|
`DEFINITION_DELETED`.
|
|
231
|
-
- `triggers_cron_schedules.next_fire_at
|
|
231
|
+
- `triggers_cron_schedules.next_fire_at`: used to drain due cron schedules
|
|
232
232
|
in next-fire order.
|
|
233
233
|
|
|
234
|
-
### Layer 3
|
|
234
|
+
### Layer 3: run history (immutable)
|
|
235
235
|
|
|
236
236
|
`workflow_runs.trigger_source` and `trigger_event` are indexed text
|
|
237
237
|
columns. `trigger_payload` is a JSONB column typed by `TriggerPayload`:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAKpE,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,mBAAmB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKrC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,UAAU,CAAA;CAAC,GAClB;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,sBAAsB,CAwB/F;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,2BAA2B,GAClC,uBAAuB,CAUzB"}
|
package/dist/core/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createWorkflowExpression, evaluateWorkflowPredicateFailClosed, InvalidWorkflowExpressionError } from '@shipfox/expression';
|
|
1
|
+
import { createWorkflowExpression, evaluateWorkflowPredicateFailClosed, InvalidWorkflowExpressionError, projectWorkflowPredicateContext } from '@shipfox/expression';
|
|
2
2
|
// Narrow the jsonb projection at the read boundary: the parser writes the right shapes,
|
|
3
3
|
// but the column is unconstrained and these values reach run inputs.
|
|
4
4
|
export function readConfigInputs(subscription) {
|
|
@@ -48,13 +48,13 @@ export function evaluateStoredFilter(params) {
|
|
|
48
48
|
export function evaluateTriggerFilter(params) {
|
|
49
49
|
return evaluateStoredFilter({
|
|
50
50
|
value: params.subscription.config.filter,
|
|
51
|
-
context: {
|
|
51
|
+
context: projectWorkflowPredicateContext('trigger.filter', {
|
|
52
52
|
event: params.payload,
|
|
53
53
|
trigger: {
|
|
54
54
|
source: params.source,
|
|
55
55
|
event: params.event
|
|
56
56
|
}
|
|
57
|
-
},
|
|
57
|
+
}),
|
|
58
58
|
invalidReason: 'Trigger subscription filter must be a non-empty string when set',
|
|
59
59
|
evaluationFailedReason: 'Trigger filter evaluation failed'
|
|
60
60
|
});
|
package/dist/core/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import {\n createWorkflowExpression,\n evaluateWorkflowPredicateFailClosed,\n InvalidWorkflowExpressionError,\n type WorkflowExpression,\n} from '@shipfox/expression';\nimport type {TriggerSubscription} from './entities/subscription.js';\n\n// Narrow the jsonb projection at the read boundary: the parser writes the right shapes,\n// but the column is unconstrained and these values reach run inputs.\n\nexport function readConfigInputs(\n subscription: TriggerSubscription,\n): Record<string, unknown> | undefined {\n const value = subscription.config.with;\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\nexport type TriggerFilterEvaluation =\n | {kind: 'matched'}\n | {kind: 'filtered'}\n | {kind: 'filter-error'; reason: string};\n\nexport type StoredFilterEvaluation = TriggerFilterEvaluation;\n\nexport interface EvaluateStoredFilterParams {\n value: unknown;\n context: Record<string, unknown>;\n invalidReason: string;\n evaluationFailedReason: string;\n}\n\nexport function evaluateStoredFilter(params: EvaluateStoredFilterParams): StoredFilterEvaluation {\n const {value} = params;\n if (value === null || value === undefined) return {kind: 'matched'};\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n kind: 'filter-error',\n reason: params.invalidReason,\n };\n }\n\n let expression: WorkflowExpression;\n try {\n expression = createWorkflowExpression({source: value, check: {mode: 'syntax'}});\n } catch (error) {\n return {kind: 'filter-error', reason: reasonFrom(error)};\n }\n\n const result = evaluateWorkflowPredicateFailClosed(expression, params.context);\n\n if (result.evaluationFailed) {\n return {kind: 'filter-error', reason: params.evaluationFailedReason};\n }\n\n return result.value ? {kind: 'matched'} : {kind: 'filtered'};\n}\n\nexport interface EvaluateTriggerFilterParams {\n subscription: TriggerSubscription;\n source: string;\n event: string;\n payload: unknown;\n}\n\nexport function evaluateTriggerFilter(\n params: EvaluateTriggerFilterParams,\n): TriggerFilterEvaluation {\n return evaluateStoredFilter({\n value: params.subscription.config.filter,\n context: {\n event: params.payload,\n trigger: {source: params.source, event: params.event},\n },\n invalidReason: 'Trigger subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Trigger filter evaluation failed',\n });\n}\n\nfunction reasonFrom(error: unknown): string {\n if (error instanceof InvalidWorkflowExpressionError) return error.reason;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n"],"names":["createWorkflowExpression","evaluateWorkflowPredicateFailClosed","InvalidWorkflowExpressionError","readConfigInputs","subscription","value","config","with","undefined","Array","isArray","evaluateStoredFilter","params","kind","trim","reason","invalidReason","expression","source","check","mode","error","reasonFrom","result","context","evaluationFailed","evaluationFailedReason","evaluateTriggerFilter","filter","event","payload","trigger","Error","message","String"],"mappings":"AAAA,SACEA,wBAAwB,EACxBC,mCAAmC,EACnCC,8BAA8B,
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import {\n createWorkflowExpression,\n evaluateWorkflowPredicateFailClosed,\n InvalidWorkflowExpressionError,\n projectWorkflowPredicateContext,\n type WorkflowExpression,\n} from '@shipfox/expression';\nimport type {TriggerSubscription} from './entities/subscription.js';\n\n// Narrow the jsonb projection at the read boundary: the parser writes the right shapes,\n// but the column is unconstrained and these values reach run inputs.\n\nexport function readConfigInputs(\n subscription: TriggerSubscription,\n): Record<string, unknown> | undefined {\n const value = subscription.config.with;\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\nexport type TriggerFilterEvaluation =\n | {kind: 'matched'}\n | {kind: 'filtered'}\n | {kind: 'filter-error'; reason: string};\n\nexport type StoredFilterEvaluation = TriggerFilterEvaluation;\n\nexport interface EvaluateStoredFilterParams {\n value: unknown;\n context: Record<string, unknown>;\n invalidReason: string;\n evaluationFailedReason: string;\n}\n\nexport function evaluateStoredFilter(params: EvaluateStoredFilterParams): StoredFilterEvaluation {\n const {value} = params;\n if (value === null || value === undefined) return {kind: 'matched'};\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n kind: 'filter-error',\n reason: params.invalidReason,\n };\n }\n\n let expression: WorkflowExpression;\n try {\n expression = createWorkflowExpression({source: value, check: {mode: 'syntax'}});\n } catch (error) {\n return {kind: 'filter-error', reason: reasonFrom(error)};\n }\n\n const result = evaluateWorkflowPredicateFailClosed(expression, params.context);\n\n if (result.evaluationFailed) {\n return {kind: 'filter-error', reason: params.evaluationFailedReason};\n }\n\n return result.value ? {kind: 'matched'} : {kind: 'filtered'};\n}\n\nexport interface EvaluateTriggerFilterParams {\n subscription: TriggerSubscription;\n source: string;\n event: string;\n payload: unknown;\n}\n\nexport function evaluateTriggerFilter(\n params: EvaluateTriggerFilterParams,\n): TriggerFilterEvaluation {\n return evaluateStoredFilter({\n value: params.subscription.config.filter,\n context: projectWorkflowPredicateContext('trigger.filter', {\n event: params.payload,\n trigger: {source: params.source, event: params.event},\n }),\n invalidReason: 'Trigger subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Trigger filter evaluation failed',\n });\n}\n\nfunction reasonFrom(error: unknown): string {\n if (error instanceof InvalidWorkflowExpressionError) return error.reason;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n"],"names":["createWorkflowExpression","evaluateWorkflowPredicateFailClosed","InvalidWorkflowExpressionError","projectWorkflowPredicateContext","readConfigInputs","subscription","value","config","with","undefined","Array","isArray","evaluateStoredFilter","params","kind","trim","reason","invalidReason","expression","source","check","mode","error","reasonFrom","result","context","evaluationFailed","evaluationFailedReason","evaluateTriggerFilter","filter","event","payload","trigger","Error","message","String"],"mappings":"AAAA,SACEA,wBAAwB,EACxBC,mCAAmC,EACnCC,8BAA8B,EAC9BC,+BAA+B,QAE1B,sBAAsB;AAG7B,wFAAwF;AACxF,qEAAqE;AAErE,OAAO,SAASC,iBACdC,YAAiC;IAEjC,MAAMC,QAAQD,aAAaE,MAAM,CAACC,IAAI;IACtC,IAAIF,UAAU,QAAQA,UAAUG,WAAW,OAAOA;IAClD,IAAI,OAAOH,UAAU,YAAYI,MAAMC,OAAO,CAACL,QAAQ,OAAOG;IAC9D,OAAOH;AACT;AAgBA,OAAO,SAASM,qBAAqBC,MAAkC;IACrE,MAAM,EAACP,KAAK,EAAC,GAAGO;IAChB,IAAIP,UAAU,QAAQA,UAAUG,WAAW,OAAO;QAACK,MAAM;IAAS;IAClE,IAAI,OAAOR,UAAU,YAAYA,MAAMS,IAAI,OAAO,IAAI;QACpD,OAAO;YACLD,MAAM;YACNE,QAAQH,OAAOI,aAAa;QAC9B;IACF;IAEA,IAAIC;IACJ,IAAI;QACFA,aAAalB,yBAAyB;YAACmB,QAAQb;YAAOc,OAAO;gBAACC,MAAM;YAAQ;QAAC;IAC/E,EAAE,OAAOC,OAAO;QACd,OAAO;YAACR,MAAM;YAAgBE,QAAQO,WAAWD;QAAM;IACzD;IAEA,MAAME,SAASvB,oCAAoCiB,YAAYL,OAAOY,OAAO;IAE7E,IAAID,OAAOE,gBAAgB,EAAE;QAC3B,OAAO;YAACZ,MAAM;YAAgBE,QAAQH,OAAOc,sBAAsB;QAAA;IACrE;IAEA,OAAOH,OAAOlB,KAAK,GAAG;QAACQ,MAAM;IAAS,IAAI;QAACA,MAAM;IAAU;AAC7D;AASA,OAAO,SAASc,sBACdf,MAAmC;IAEnC,OAAOD,qBAAqB;QAC1BN,OAAOO,OAAOR,YAAY,CAACE,MAAM,CAACsB,MAAM;QACxCJ,SAAStB,gCAAgC,kBAAkB;YACzD2B,OAAOjB,OAAOkB,OAAO;YACrBC,SAAS;gBAACb,QAAQN,OAAOM,MAAM;gBAAEW,OAAOjB,OAAOiB,KAAK;YAAA;QACtD;QACAb,eAAe;QACfU,wBAAwB;IAC1B;AACF;AAEA,SAASJ,WAAWD,KAAc;IAChC,IAAIA,iBAAiBpB,gCAAgC,OAAOoB,MAAMN,MAAM;IACxE,IAAIM,iBAAiBW,OAAO,OAAOX,MAAMY,OAAO;IAChD,OAAOC,OAAOb;AAChB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-integration-event.d.ts","sourceRoot":"","sources":["../../src/core/dispatch-integration-event.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAeD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"dispatch-integration-event.d.ts","sourceRoot":"","sources":["../../src/core/dispatch-integration-event.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAeD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,CAmHf"}
|
|
@@ -63,6 +63,7 @@ export async function dispatchIntegrationEvent(params) {
|
|
|
63
63
|
workspaceId: subscription.workspaceId,
|
|
64
64
|
projectId: subscription.projectId,
|
|
65
65
|
definitionId: subscription.workflowDefinitionId,
|
|
66
|
+
triggerConnectionId: params.connectionId,
|
|
66
67
|
triggerPayload: {
|
|
67
68
|
provider: params.provider,
|
|
68
69
|
source: params.source,
|
|
@@ -95,6 +96,7 @@ export async function dispatchIntegrationEvent(params) {
|
|
|
95
96
|
history,
|
|
96
97
|
eventRef: params.eventRef,
|
|
97
98
|
workspaceId: params.workspaceId,
|
|
99
|
+
connectionId: params.connectionId,
|
|
98
100
|
provider: params.provider,
|
|
99
101
|
source: params.source,
|
|
100
102
|
event: params.event,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/dispatch-integration-event.ts"],"sourcesContent":["import {findMatchingSubscriptions} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {evaluateTriggerFilter, readConfigInputs} from './config.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {routeEventToJobListeners} from './route-event-to-job-listeners.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DispatchIntegrationEventParams {\n workflows: WorkflowsModuleClient;\n eventRef: string;\n workspaceId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n connectionId: string;\n connectionName: string | null;\n payload: unknown;\n receivedAt: Date;\n}\n\n// Source-agnostic dispatcher: any inbound integration event fans out to every\n// workspace subscription registered for its (source, event) and to every listening\n// job subscribed to it, passing the raw payload through untouched. The module knows\n// nothing about github, gitlab, etc.\n//\n// Continue-on-error: every matched subscription and listener is attempted so one broken\n// subscription cannot starve its siblings. A permanent admission failure (deleted definition,\n// project mismatch, suspended workspace) is recorded and skipped; a transient one is recorded\n// and re-thrown so the outbox\n// replays the whole event and converges (succeeded siblings dedup on the idempotency key). The\n// event reaches a terminal outcome only when no transient error remains: `routed` if any run\n// was created or any listening job matched, `discarded` if nothing matched, otherwise `errored`.\n// History is best-effort; the thrown transient error, not the recorded outcome, drives the retry.\nexport async function dispatchIntegrationEvent(\n params: DispatchIntegrationEventParams,\n): Promise<void> {\n eventReceivedCount.add(1, {provider: params.provider});\n\n const history = await beginTriggerHistory({\n eventRef: params.eventRef,\n origin: 'integration',\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n connectionId: params.connectionId,\n connectionName: params.connectionName,\n payload: (params.payload ?? null) as Record<string, unknown> | null,\n receivedAt: params.receivedAt,\n });\n\n const subscriptions = await findMatchingSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let triggeredCount = 0;\n let triggerEngagedCount = 0;\n let sawTransientError = false;\n let firstTransientError: unknown;\n\n for (const subscription of subscriptions) {\n const filterResult = evaluateTriggerFilter({\n subscription,\n source: params.source,\n event: params.event,\n payload: params.payload,\n });\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n triggerEngagedCount += 1;\n await history.filterErrored(subscription, filterResult.reason);\n continue;\n }\n triggerEngagedCount += 1;\n\n const inputs = readConfigInputs(subscription);\n try {\n const run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: `${subscription.id}:${params.eventRef}`,\n });\n await history.triggered(subscription, run);\n triggeredCount += 1;\n subscriptionTriggeredCount.add(1, {provider: params.provider});\n } catch (error) {\n await history.dispatchErrored(subscription, toReason(error));\n // Track presence with a flag, not `firstTransientError === undefined`: a thrown\n // value of `undefined` is still a transient failure and must drive the replay.\n if (!isPermanentStartRunError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n const listenerResult = await routeEventToJobListeners({\n workflows: params.workflows,\n history,\n eventRef: params.eventRef,\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n payload: params.payload,\n receivedAt: params.receivedAt,\n });\n\n if (listenerResult.transientErrored && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = listenerResult.transientError;\n }\n\n const totalMatchedCount = triggerEngagedCount + listenerResult.engagedCount;\n\n if (sawTransientError) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'failed'});\n await history.failed(totalMatchedCount);\n throw firstTransientError;\n }\n\n if (triggeredCount > 0 || listenerResult.acceptedJobCount > 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'routed'});\n await history.routed(totalMatchedCount);\n return;\n }\n\n if (totalMatchedCount === 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'discarded'});\n await history.discarded();\n return;\n }\n\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'errored'});\n await history.allErrored(totalMatchedCount);\n}\n"],"names":["findMatchingSubscriptions","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","evaluateTriggerFilter","readConfigInputs","beginTriggerHistory","toReason","routeEventToJobListeners","isPermanentStartRunError","dispatchIntegrationEvent","params","add","provider","history","eventRef","origin","workspaceId","source","event","deliveryId","connectionId","connectionName","payload","receivedAt","subscriptions","triggeredCount","triggerEngagedCount","sawTransientError","firstTransientError","subscription","filterResult","kind","filterErrored","reason","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","data","undefined","idempotencyKey","id","triggered","error","dispatchErrored","listenerResult","transientErrored","transientError","totalMatchedCount","engagedCount","outcome","failed","acceptedJobCount","routed","discarded","allErrored"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,uBAAuB;AAC/D,SACEC,iBAAiB,EACjBC,kBAAkB,EAClBC,0BAA0B,QACrB,uBAAuB;AAC9B,SAAQC,qBAAqB,EAAEC,gBAAgB,QAAO,cAAc;AACpE,SAAQC,mBAAmB,EAAEC,QAAQ,QAAO,8BAA8B;AAC1E,SAAQC,wBAAwB,QAAO,oCAAoC;AAC3E,SAAQC,wBAAwB,QAAmC,wBAAwB;AAgB3F,8EAA8E;AAC9E,mFAAmF;AACnF,oFAAoF;AACpF,qCAAqC;AACrC,EAAE;AACF,wFAAwF;AACxF,8FAA8F;AAC9F,8FAA8F;AAC9F,8BAA8B;AAC9B,+FAA+F;AAC/F,6FAA6F;AAC7F,iGAAiG;AACjG,kGAAkG;AAClG,OAAO,eAAeC,yBACpBC,MAAsC;IAEtCT,mBAAmBU,GAAG,CAAC,GAAG;QAACC,UAAUF,OAAOE,QAAQ;IAAA;IAEpD,MAAMC,UAAU,MAAMR,oBAAoB;QACxCS,UAAUJ,OAAOI,QAAQ;QACzBC,QAAQ;QACRC,aAAaN,OAAOM,WAAW;QAC/BJ,UAAUF,OAAOE,QAAQ;QACzBK,QAAQP,OAAOO,MAAM;QACrBC,OAAOR,OAAOQ,KAAK;QACnBC,YAAYT,OAAOS,UAAU;QAC7BC,cAAcV,OAAOU,YAAY;QACjCC,gBAAgBX,OAAOW,cAAc;QACrCC,SAAUZ,OAAOY,OAAO,IAAI;QAC5BC,YAAYb,OAAOa,UAAU;IAC/B;IAEA,MAAMC,gBAAgB,MAAMzB,0BAA0B;QACpDiB,aAAaN,OAAOM,WAAW;QAC/BC,QAAQP,OAAOO,MAAM;QACrBC,OAAOR,OAAOQ,KAAK;IACrB;IAEA,IAAIO,iBAAiB;IACrB,IAAIC,sBAAsB;IAC1B,IAAIC,oBAAoB;IACxB,IAAIC;IAEJ,KAAK,MAAMC,gBAAgBL,cAAe;QACxC,MAAMM,eAAe3B,sBAAsB;YACzC0B;YACAZ,QAAQP,OAAOO,MAAM;YACrBC,OAAOR,OAAOQ,KAAK;YACnBI,SAASZ,OAAOY,OAAO;QACzB;QACA,IAAIQ,aAAaC,IAAI,KAAK,YAAY;QACtC,IAAID,aAAaC,IAAI,KAAK,gBAAgB;YACxCL,uBAAuB;YACvB,MAAMb,QAAQmB,aAAa,CAACH,cAAcC,aAAaG,MAAM;YAC7D;QACF;QACAP,uBAAuB;QAEvB,MAAMQ,SAAS9B,iBAAiByB;QAChC,IAAI;YACF,MAAMM,MAAM,MAAMzB,OAAO0B,SAAS,CAACC,mBAAmB,CAAC;gBACrDrB,aAAaa,aAAab,WAAW;gBACrCsB,WAAWT,aAAaS,SAAS;gBACjCC,cAAcV,aAAaW,oBAAoB;gBAC/CC,gBAAgB;
|
|
1
|
+
{"version":3,"sources":["../../src/core/dispatch-integration-event.ts"],"sourcesContent":["import {findMatchingSubscriptions} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {evaluateTriggerFilter, readConfigInputs} from './config.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {routeEventToJobListeners} from './route-event-to-job-listeners.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DispatchIntegrationEventParams {\n workflows: WorkflowsModuleClient;\n eventRef: string;\n workspaceId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n connectionId: string;\n connectionName: string | null;\n payload: unknown;\n receivedAt: Date;\n}\n\n// Source-agnostic dispatcher: any inbound integration event fans out to every\n// workspace subscription registered for its (source, event) and to every listening\n// job subscribed to it, passing the raw payload through untouched. The module knows\n// nothing about github, gitlab, etc.\n//\n// Continue-on-error: every matched subscription and listener is attempted so one broken\n// subscription cannot starve its siblings. A permanent admission failure (deleted definition,\n// project mismatch, suspended workspace) is recorded and skipped; a transient one is recorded\n// and re-thrown so the outbox\n// replays the whole event and converges (succeeded siblings dedup on the idempotency key). The\n// event reaches a terminal outcome only when no transient error remains: `routed` if any run\n// was created or any listening job matched, `discarded` if nothing matched, otherwise `errored`.\n// History is best-effort; the thrown transient error, not the recorded outcome, drives the retry.\nexport async function dispatchIntegrationEvent(\n params: DispatchIntegrationEventParams,\n): Promise<void> {\n eventReceivedCount.add(1, {provider: params.provider});\n\n const history = await beginTriggerHistory({\n eventRef: params.eventRef,\n origin: 'integration',\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n connectionId: params.connectionId,\n connectionName: params.connectionName,\n payload: (params.payload ?? null) as Record<string, unknown> | null,\n receivedAt: params.receivedAt,\n });\n\n const subscriptions = await findMatchingSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let triggeredCount = 0;\n let triggerEngagedCount = 0;\n let sawTransientError = false;\n let firstTransientError: unknown;\n\n for (const subscription of subscriptions) {\n const filterResult = evaluateTriggerFilter({\n subscription,\n source: params.source,\n event: params.event,\n payload: params.payload,\n });\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n triggerEngagedCount += 1;\n await history.filterErrored(subscription, filterResult.reason);\n continue;\n }\n triggerEngagedCount += 1;\n\n const inputs = readConfigInputs(subscription);\n try {\n const run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\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 ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: `${subscription.id}:${params.eventRef}`,\n });\n await history.triggered(subscription, run);\n triggeredCount += 1;\n subscriptionTriggeredCount.add(1, {provider: params.provider});\n } catch (error) {\n await history.dispatchErrored(subscription, toReason(error));\n // Track presence with a flag, not `firstTransientError === undefined`: a thrown\n // value of `undefined` is still a transient failure and must drive the replay.\n if (!isPermanentStartRunError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n const listenerResult = await routeEventToJobListeners({\n workflows: params.workflows,\n history,\n eventRef: params.eventRef,\n workspaceId: params.workspaceId,\n connectionId: params.connectionId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n payload: params.payload,\n receivedAt: params.receivedAt,\n });\n\n if (listenerResult.transientErrored && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = listenerResult.transientError;\n }\n\n const totalMatchedCount = triggerEngagedCount + listenerResult.engagedCount;\n\n if (sawTransientError) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'failed'});\n await history.failed(totalMatchedCount);\n throw firstTransientError;\n }\n\n if (triggeredCount > 0 || listenerResult.acceptedJobCount > 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'routed'});\n await history.routed(totalMatchedCount);\n return;\n }\n\n if (totalMatchedCount === 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'discarded'});\n await history.discarded();\n return;\n }\n\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'errored'});\n await history.allErrored(totalMatchedCount);\n}\n"],"names":["findMatchingSubscriptions","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","evaluateTriggerFilter","readConfigInputs","beginTriggerHistory","toReason","routeEventToJobListeners","isPermanentStartRunError","dispatchIntegrationEvent","params","add","provider","history","eventRef","origin","workspaceId","source","event","deliveryId","connectionId","connectionName","payload","receivedAt","subscriptions","triggeredCount","triggerEngagedCount","sawTransientError","firstTransientError","subscription","filterResult","kind","filterErrored","reason","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerConnectionId","triggerPayload","data","undefined","idempotencyKey","id","triggered","error","dispatchErrored","listenerResult","transientErrored","transientError","totalMatchedCount","engagedCount","outcome","failed","acceptedJobCount","routed","discarded","allErrored"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-event-to-job-listeners.d.ts","sourceRoot":"","sources":["../../src/core/route-event-to-job-listeners.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-event-to-job-listeners.d.ts","sourceRoot":"","sources":["../../src/core/route-event-to-job-listeners.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,KAAK,sBAAsB,EAAW,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,8BAA8B,CAAC,CAsFzC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectWorkflowPredicateContext } from '@shipfox/expression';
|
|
1
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
3
|
import { findMatchingJobListenerSubscriptions } from '#db/job-listener-subscriptions.js';
|
|
3
4
|
import { evaluateStoredFilter } from './config.js';
|
|
@@ -27,6 +28,20 @@ export async function routeEventToJobListeners(params) {
|
|
|
27
28
|
effectiveMatchByJobId.set(subscription.jobId, subscription);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
31
|
+
const fireDeliveryNeeded = [
|
|
32
|
+
...effectiveMatchByJobId.values()
|
|
33
|
+
].some((subscription)=>listenerDisposition(subscription) === 'fire');
|
|
34
|
+
const triggerReference = fireDeliveryNeeded ? await params.workflows.resolveWorkflowRunTriggerReference({
|
|
35
|
+
workspaceId: params.workspaceId,
|
|
36
|
+
triggerConnectionId: params.connectionId,
|
|
37
|
+
triggerPayload: {
|
|
38
|
+
provider: params.provider,
|
|
39
|
+
source: params.source,
|
|
40
|
+
event: params.event,
|
|
41
|
+
deliveryId: params.deliveryId,
|
|
42
|
+
data: params.payload
|
|
43
|
+
}
|
|
44
|
+
}) : null;
|
|
30
45
|
let acceptedJobCount = 0;
|
|
31
46
|
let deliveredCount = 0;
|
|
32
47
|
let sawTransientError = false;
|
|
@@ -43,6 +58,8 @@ export async function routeEventToJobListeners(params) {
|
|
|
43
58
|
source: params.source,
|
|
44
59
|
event: params.event,
|
|
45
60
|
provider: params.provider,
|
|
61
|
+
triggerConnectionId: params.connectionId,
|
|
62
|
+
triggerReference,
|
|
46
63
|
payload: params.payload,
|
|
47
64
|
receivedAt: params.receivedAt.toISOString()
|
|
48
65
|
});
|
|
@@ -77,9 +94,9 @@ function evaluateListenerFilter(params) {
|
|
|
77
94
|
if (typeof filter !== 'string' || filter.trim() === '') {
|
|
78
95
|
return evaluateStoredFilter({
|
|
79
96
|
value: filter,
|
|
80
|
-
context: {
|
|
97
|
+
context: projectWorkflowPredicateContext(params.subscription.kind === 'on' ? 'listener.on' : 'listener.until', {
|
|
81
98
|
event: params.payload
|
|
82
|
-
},
|
|
99
|
+
}),
|
|
83
100
|
invalidReason: 'Listener subscription filter must be a non-empty string when set',
|
|
84
101
|
evaluationFailedReason: 'Listener filter evaluation failed'
|
|
85
102
|
});
|
|
@@ -88,10 +105,10 @@ function evaluateListenerFilter(params) {
|
|
|
88
105
|
if (snapshot.kind === 'invalid') return snapshot.result;
|
|
89
106
|
return evaluateStoredFilter({
|
|
90
107
|
value: filter,
|
|
91
|
-
context: {
|
|
108
|
+
context: projectWorkflowPredicateContext(params.subscription.kind === 'on' ? 'listener.on' : 'listener.until', {
|
|
92
109
|
...snapshot.value,
|
|
93
110
|
event: params.payload
|
|
94
|
-
},
|
|
111
|
+
}),
|
|
95
112
|
invalidReason: 'Listener subscription filter must be a non-empty string when set',
|
|
96
113
|
evaluationFailedReason: 'Listener filter evaluation failed'
|
|
97
114
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/route-event-to-job-listeners.ts"],"sourcesContent":["import {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\nexport interface RouteEventToJobListenersParams {\n workflows: WorkflowsModuleClient;\n history: TriggerHistoryRecorder;\n eventRef: string;\n workspaceId: 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 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 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: {event: params.payload},\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: {...snapshot.value, event: params.payload},\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>}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (value === undefined) return {kind: 'valid', value: {}};\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return {kind: 'valid', value: value as Record<string, unknown>};\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\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":["logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","routeEventToJobListeners","params","subscriptions","workspaceId","source","event","filterErrorCount","effectiveMatchByJobId","Map","subscription","filterResult","evaluateListenerFilter","payload","kind","history","listenerFilterErrored","reason","previous","get","jobId","shouldReplaceEffectiveMatcher","set","acceptedJobCount","deliveredCount","sawTransientError","dispatchErrorCount","firstTransientError","values","disposition","listenerDisposition","result","workflows","deliverEventToJobListener","eventRef","deliveryId","provider","receivedAt","toISOString","skipped","listenerTriggered","buffered","error","listenerDispatchErrored","engagedCount","matchedJobCount","size","transientErrored","transientError","undefined","filter","config","trim","value","context","invalidReason","evaluationFailedReason","snapshot","readFilterSnapshot","filter_snapshot","Array","isArray","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 {projectWorkflowPredicateContext} 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\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 {...snapshot.value, 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\nfunction readFilterSnapshot(\n subscription: JobListenerSubscription,\n):\n | {kind: 'valid'; value: Record<string, unknown>}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (value === undefined) return {kind: 'valid', value: {}};\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return {kind: 'valid', value: value as Record<string, unknown>};\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\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":["projectWorkflowPredicateContext","logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","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","filter_snapshot","Array","isArray","listenerMatcherSortKey","kindRank","matcherOrdinal","toString","padStart","candidate","previousKey","candidateKey","warn","keptSubscriptionId","id","candidateSubscriptionId","matcherKind"],"mappings":"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"}
|
|
@@ -26,6 +26,7 @@ declare function startRunKnownError(error: unknown): import("@shipfox/inter-modu
|
|
|
26
26
|
workspaceId: import("zod").ZodString;
|
|
27
27
|
projectId: import("zod").ZodString;
|
|
28
28
|
definitionId: import("zod").ZodString;
|
|
29
|
+
triggerConnectionId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
29
30
|
triggerPayload: import("zod").ZodUnion<readonly [import("zod").ZodObject<{
|
|
30
31
|
provider: import("zod").ZodOptional<import("zod").ZodLiteral<"manual">>;
|
|
31
32
|
source: import("zod").ZodLiteral<"manual">;
|
|
@@ -75,11 +76,19 @@ declare function startRunKnownError(error: unknown): import("@shipfox/inter-modu
|
|
|
75
76
|
"agent.prompt": "agent.prompt";
|
|
76
77
|
"agent.model": "agent.model";
|
|
77
78
|
"agent.provider": "agent.provider";
|
|
79
|
+
"agent.thinking": "agent.thinking";
|
|
78
80
|
"job.runner": "job.runner";
|
|
79
81
|
"job.outputs": "job.outputs";
|
|
80
|
-
"job.
|
|
82
|
+
"job.execution_name": "job.execution_name";
|
|
83
|
+
"workflow.run_name": "workflow.run_name";
|
|
81
84
|
"step.name": "step.name";
|
|
85
|
+
"step.working_directory": "step.working_directory";
|
|
82
86
|
"step.feedback": "step.feedback";
|
|
87
|
+
"checkout.project": "checkout.project";
|
|
88
|
+
"checkout.connection": "checkout.connection";
|
|
89
|
+
"checkout.repository": "checkout.repository";
|
|
90
|
+
"checkout.ref": "checkout.ref";
|
|
91
|
+
"checkout.path": "checkout.path";
|
|
83
92
|
}>;
|
|
84
93
|
source: import("zod").ZodString;
|
|
85
94
|
envKey: import("zod").ZodOptional<import("zod").ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflows-client.d.ts","sourceRoot":"","sources":["../../src/core/workflows-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,yCAAyC,CAAC;AAGjD,YAAY,EAAC,qBAAqB,EAAC,CAAC;AAEpC,KAAK,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEpF;AAED,oFAAoF;AACpF,wBAAgB,yCAAyC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjF;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAEnE;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAAE;IAAC,IAAI,EAAE,4BAA4B,CAAA;CAAC,CAAC,CAK/F;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO
|
|
1
|
+
{"version":3,"file":"workflows-client.d.ts","sourceRoot":"","sources":["../../src/core/workflows-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,yCAAyC,CAAC;AAGjD,YAAY,EAAC,qBAAqB,EAAC,CAAC;AAEpC,KAAK,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEpF;AAED,oFAAoF;AACpF,wBAAgB,yCAAyC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjF;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAEnE;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAAE;IAAC,IAAI,EAAE,4BAA4B,CAAA;CAAC,CAAC,CAK/F;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAKzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fire-manual.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/fire-manual.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AAanF,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"fire-manual.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/fire-manual.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AAanF,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,qBAAqB,mDA0F5E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { requireUserContext } from '@shipfox/api-auth-context';
|
|
1
|
+
import { requireUserContext, requireWorkspaceResourceAccess } from '@shipfox/api-auth-context';
|
|
2
2
|
import { fireManualTriggerBodySchema, fireManualTriggerResponseSchema } from '@shipfox/api-triggers-dto';
|
|
3
3
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -70,10 +70,18 @@ export function createFireManualTriggerRoute(workflows) {
|
|
|
70
70
|
const { definitionId } = request.params;
|
|
71
71
|
const userContext = requireUserContext(request);
|
|
72
72
|
const subscription = await getManualSubscriptionByDefinitionId(definitionId);
|
|
73
|
-
//
|
|
74
|
-
|
|
73
|
+
// Missing triggers and memberships remain 404 to avoid leaking existence; lifecycle claims
|
|
74
|
+
// propagate their stable access errors.
|
|
75
|
+
if (!subscription) {
|
|
75
76
|
throw new ManualTriggerNotFoundError(definitionId);
|
|
76
77
|
}
|
|
78
|
+
requireWorkspaceResourceAccess({
|
|
79
|
+
request,
|
|
80
|
+
workspaceId: subscription.workspaceId,
|
|
81
|
+
notFoundError: new ClientError('Manual trigger not found', 'manual-trigger-not-found', {
|
|
82
|
+
status: 404
|
|
83
|
+
})
|
|
84
|
+
});
|
|
77
85
|
const run = await fireManualSubscription({
|
|
78
86
|
workflows,
|
|
79
87
|
subscriptionId: subscription.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/fire-manual.ts"],"sourcesContent":["import {requireUserContext} from '@shipfox/api-auth-context';\nimport {\n fireManualTriggerBodySchema,\n fireManualTriggerResponseSchema,\n} from '@shipfox/api-triggers-dto';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {ManualTriggerNotFoundError} from '#core/errors.js';\nimport {fireManualSubscription} from '#core/fire-manual.js';\nimport {\n isInterpolationUnresolvableError,\n isWorkspaceDeletedError,\n isWorkspaceNotFoundError,\n isWorkspaceSuspendedError,\n} from '#core/workflows-client.js';\nimport {getManualSubscriptionByDefinitionId} from '#db/subscriptions.js';\n\nexport function createFireManualTriggerRoute(workflows: WorkflowsModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/:definitionId/fire-manual',\n description: 'Fire the manual trigger of a workflow definition, creating a workflow run.',\n schema: {\n params: z.object({\n definitionId: z.string().uuid(),\n }),\n body: fireManualTriggerBodySchema,\n response: {\n 201: fireManualTriggerResponseSchema,\n 422: z.object({\n code: z.string(),\n details: z.object({\n field: z.string(),\n source: z.string(),\n env_key: z.string().optional(),\n }),\n }),\n },\n },\n errorHandler: (error) => {\n if (error instanceof ManualTriggerNotFoundError) {\n throw new ClientError(error.message, 'manual-trigger-not-found', {status: 404});\n }\n if (isWorkspaceSuspendedError(error)) {\n throw new ClientError('Workspace is suspended', 'workspace-suspended', {\n status: 409,\n cause: error,\n });\n }\n if (isWorkspaceDeletedError(error)) {\n throw new ClientError('Workspace is deleted', 'workspace-deleted', {\n status: 404,\n cause: error,\n });\n }\n if (isWorkspaceNotFoundError(error)) {\n throw new ClientError('Workspace not found', 'workspace-not-found', {\n status: 404,\n cause: error,\n });\n }\n if (isInterpolationUnresolvableError(error)) {\n throw new ClientError(\n 'Workflow interpolation cannot be resolved',\n 'workflow-interpolation-unresolvable',\n {\n status: 422,\n details: {\n field: error.details.field,\n source: error.details.source,\n ...(error.details.envKey === undefined ? {} : {env_key: error.details.envKey}),\n },\n },\n );\n }\n throw error;\n },\n handler: async (request, reply) => {\n const {definitionId} = request.params;\n const userContext = requireUserContext(request);\n\n const subscription = await getManualSubscriptionByDefinitionId(definitionId);\n //
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/fire-manual.ts"],"sourcesContent":["import {requireUserContext, requireWorkspaceResourceAccess} from '@shipfox/api-auth-context';\nimport {\n fireManualTriggerBodySchema,\n fireManualTriggerResponseSchema,\n} from '@shipfox/api-triggers-dto';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {ManualTriggerNotFoundError} from '#core/errors.js';\nimport {fireManualSubscription} from '#core/fire-manual.js';\nimport {\n isInterpolationUnresolvableError,\n isWorkspaceDeletedError,\n isWorkspaceNotFoundError,\n isWorkspaceSuspendedError,\n} from '#core/workflows-client.js';\nimport {getManualSubscriptionByDefinitionId} from '#db/subscriptions.js';\n\nexport function createFireManualTriggerRoute(workflows: WorkflowsModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/:definitionId/fire-manual',\n description: 'Fire the manual trigger of a workflow definition, creating a workflow run.',\n schema: {\n params: z.object({\n definitionId: z.string().uuid(),\n }),\n body: fireManualTriggerBodySchema,\n response: {\n 201: fireManualTriggerResponseSchema,\n 422: z.object({\n code: z.string(),\n details: z.object({\n field: z.string(),\n source: z.string(),\n env_key: z.string().optional(),\n }),\n }),\n },\n },\n errorHandler: (error) => {\n if (error instanceof ManualTriggerNotFoundError) {\n throw new ClientError(error.message, 'manual-trigger-not-found', {status: 404});\n }\n if (isWorkspaceSuspendedError(error)) {\n throw new ClientError('Workspace is suspended', 'workspace-suspended', {\n status: 409,\n cause: error,\n });\n }\n if (isWorkspaceDeletedError(error)) {\n throw new ClientError('Workspace is deleted', 'workspace-deleted', {\n status: 404,\n cause: error,\n });\n }\n if (isWorkspaceNotFoundError(error)) {\n throw new ClientError('Workspace not found', 'workspace-not-found', {\n status: 404,\n cause: error,\n });\n }\n if (isInterpolationUnresolvableError(error)) {\n throw new ClientError(\n 'Workflow interpolation cannot be resolved',\n 'workflow-interpolation-unresolvable',\n {\n status: 422,\n details: {\n field: error.details.field,\n source: error.details.source,\n ...(error.details.envKey === undefined ? {} : {env_key: error.details.envKey}),\n },\n },\n );\n }\n throw error;\n },\n handler: async (request, reply) => {\n const {definitionId} = request.params;\n const userContext = requireUserContext(request);\n\n const subscription = await getManualSubscriptionByDefinitionId(definitionId);\n // Missing triggers and memberships remain 404 to avoid leaking existence; lifecycle claims\n // propagate their stable access errors.\n if (!subscription) {\n throw new ManualTriggerNotFoundError(definitionId);\n }\n requireWorkspaceResourceAccess({\n request,\n workspaceId: subscription.workspaceId,\n notFoundError: new ClientError('Manual trigger not found', 'manual-trigger-not-found', {\n status: 404,\n }),\n });\n\n const run = await fireManualSubscription({\n workflows,\n subscriptionId: subscription.id,\n callerWorkspaceId: subscription.workspaceId,\n userId: userContext.userId,\n inputs: request.body.inputs,\n });\n\n reply.status(201);\n return {workflow_run_id: run.id};\n },\n });\n}\n"],"names":["requireUserContext","requireWorkspaceResourceAccess","fireManualTriggerBodySchema","fireManualTriggerResponseSchema","ClientError","defineRoute","z","ManualTriggerNotFoundError","fireManualSubscription","isInterpolationUnresolvableError","isWorkspaceDeletedError","isWorkspaceNotFoundError","isWorkspaceSuspendedError","getManualSubscriptionByDefinitionId","createFireManualTriggerRoute","workflows","method","path","description","schema","params","object","definitionId","string","uuid","body","response","code","details","field","source","env_key","optional","errorHandler","error","message","status","cause","envKey","undefined","handler","request","reply","userContext","subscription","workspaceId","notFoundError","run","subscriptionId","id","callerWorkspaceId","userId","inputs","workflow_run_id"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-trigger-event.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/get-trigger-event.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"get-trigger-event.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/get-trigger-event.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,iDAkC/B,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireWorkspaceResourceAccess } from '@shipfox/api-auth-context';
|
|
2
2
|
import { triggerEventDetailResponseSchema } from '@shipfox/api-triggers-dto';
|
|
3
3
|
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -18,14 +18,21 @@ export const getTriggerEventRoute = defineRoute({
|
|
|
18
18
|
},
|
|
19
19
|
handler: async (request)=>{
|
|
20
20
|
const { id } = request.params;
|
|
21
|
-
const userContext = requireUserContext(request);
|
|
22
21
|
const event = await getTriggerEventById(id);
|
|
23
|
-
//
|
|
24
|
-
|
|
22
|
+
// Missing events and memberships remain 404 to avoid leaking existence; lifecycle claims
|
|
23
|
+
// propagate their stable access errors.
|
|
24
|
+
if (!event) {
|
|
25
25
|
throw new ClientError('Trigger event not found', 'not-found', {
|
|
26
26
|
status: 404
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
+
requireWorkspaceResourceAccess({
|
|
30
|
+
request,
|
|
31
|
+
workspaceId: event.workspaceId,
|
|
32
|
+
notFoundError: new ClientError('Trigger event not found', 'not-found', {
|
|
33
|
+
status: 404
|
|
34
|
+
})
|
|
35
|
+
});
|
|
29
36
|
const decisions = await listDecisionsByReceivedEventId(event.id);
|
|
30
37
|
return {
|
|
31
38
|
...toTriggerEventDto(event),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/get-trigger-event.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/get-trigger-event.ts"],"sourcesContent":["import {requireWorkspaceResourceAccess} from '@shipfox/api-auth-context';\nimport {triggerEventDetailResponseSchema} from '@shipfox/api-triggers-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {getTriggerEventById, listDecisionsByReceivedEventId} from '#db/index.js';\nimport {toTriggerDecisionDto, toTriggerEventDto} from '#presentation/dto/trigger-events.js';\n\nexport const getTriggerEventRoute = defineRoute({\n method: 'GET',\n path: '/:id',\n description: 'Get a trigger event by ID with its routing decisions.',\n schema: {\n params: z.object({\n id: z.string().uuid(),\n }),\n response: {\n 200: triggerEventDetailResponseSchema,\n },\n },\n handler: async (request) => {\n const {id} = request.params;\n\n const event = await getTriggerEventById(id);\n // Missing events and memberships remain 404 to avoid leaking existence; lifecycle claims\n // propagate their stable access errors.\n if (!event) {\n throw new ClientError('Trigger event not found', 'not-found', {status: 404});\n }\n requireWorkspaceResourceAccess({\n request,\n workspaceId: event.workspaceId,\n notFoundError: new ClientError('Trigger event not found', 'not-found', {status: 404}),\n });\n\n const decisions = await listDecisionsByReceivedEventId(event.id);\n\n return {\n ...toTriggerEventDto(event),\n decisions: decisions.map(toTriggerDecisionDto),\n };\n },\n});\n"],"names":["requireWorkspaceResourceAccess","triggerEventDetailResponseSchema","ClientError","defineRoute","z","getTriggerEventById","listDecisionsByReceivedEventId","toTriggerDecisionDto","toTriggerEventDto","getTriggerEventRoute","method","path","description","schema","params","object","id","string","uuid","response","handler","request","event","status","workspaceId","notFoundError","decisions","map"],"mappings":"AAAA,SAAQA,8BAA8B,QAAO,4BAA4B;AACzE,SAAQC,gCAAgC,QAAO,4BAA4B;AAC3E,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,mBAAmB,EAAEC,8BAA8B,QAAO,eAAe;AACjF,SAAQC,oBAAoB,EAAEC,iBAAiB,QAAO,sCAAsC;AAE5F,OAAO,MAAMC,uBAAuBN,YAAY;IAC9CO,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,QAAQV,EAAEW,MAAM,CAAC;YACfC,IAAIZ,EAAEa,MAAM,GAAGC,IAAI;QACrB;QACAC,UAAU;YACR,KAAKlB;QACP;IACF;IACAmB,SAAS,OAAOC;QACd,MAAM,EAACL,EAAE,EAAC,GAAGK,QAAQP,MAAM;QAE3B,MAAMQ,QAAQ,MAAMjB,oBAAoBW;QACxC,yFAAyF;QACzF,wCAAwC;QACxC,IAAI,CAACM,OAAO;YACV,MAAM,IAAIpB,YAAY,2BAA2B,aAAa;gBAACqB,QAAQ;YAAG;QAC5E;QACAvB,+BAA+B;YAC7BqB;YACAG,aAAaF,MAAME,WAAW;YAC9BC,eAAe,IAAIvB,YAAY,2BAA2B,aAAa;gBAACqB,QAAQ;YAAG;QACrF;QAEA,MAAMG,YAAY,MAAMpB,+BAA+BgB,MAAMN,EAAE;QAE/D,OAAO;YACL,GAAGR,kBAAkBc,MAAM;YAC3BI,WAAWA,UAAUC,GAAG,CAACpB;QAC3B;IACF;AACF,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-trigger-event-facets.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/list-trigger-event-facets.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"list-trigger-event-facets.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/list-trigger-event-facets.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,2BAA2B,iDAiBtC,CAAC"}
|