@shipfox/api-triggers 9.2.0 → 9.3.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.
@@ -1,6 +1,6 @@
1
1
  $ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
2
- Successfully compiled: 58 files with swc (444.12ms)
3
- 2026-07-26T13:33:23.929Z [INFO] asset workflow-bundle-69915b80080285c7230d.js 3.17 MiB [emitted] [immutable] (name: main)
2
+ Successfully compiled: 58 files with swc (466.2ms)
3
+ 2026-07-26T20:59:53.851Z [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 7019 ms
17
- 2026-07-26T13:33:23.946Z [INFO] Workflow bundle created { size: '3.17MB' }
16
+ webpack 5.107.2 compiled successfully in 8047 ms
17
+ 2026-07-26T20:59:53.876Z [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,22 @@
1
1
  # @shipfox/api-triggers
2
2
 
3
+ ## 9.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6017e56: Reject new workflow work for suspended or deleted workspaces with stable non-retryable results.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [4425c6d]
12
+ - Updated dependencies [6017e56]
13
+ - @shipfox/node-opentelemetry@0.6.3
14
+ - @shipfox/api-workflows-dto@9.3.0
15
+ - @shipfox/api-auth-context@9.3.0
16
+ - @shipfox/node-fastify@0.3.4
17
+ - @shipfox/node-module@1.0.3
18
+ - @shipfox/node-temporal@0.4.4
19
+
3
20
  ## 9.2.0
4
21
 
5
22
  ### Patch Changes
@@ -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;AAcD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiHf"}
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,CAiHf"}
@@ -10,8 +10,9 @@ import { isPermanentStartRunError } from './workflows-client.js';
10
10
  // nothing about github, gitlab, etc.
11
11
  //
12
12
  // Continue-on-error: every matched subscription and listener is attempted so one broken
13
- // subscription cannot starve its siblings. A permanent failure (deleted definition, project
14
- // mismatch) is recorded and skipped; a transient one is recorded and re-thrown so the outbox
13
+ // subscription cannot starve its siblings. A permanent admission failure (deleted definition,
14
+ // project mismatch, suspended workspace) is recorded and skipped; a transient one is recorded
15
+ // and re-thrown so the outbox
15
16
  // replays the whole event and converges (succeeded siblings dedup on the idempotency key). The
16
17
  // event reaches a terminal outcome only when no transient error remains: `routed` if any run
17
18
  // was created or any listening job matched, `discarded` if nothing matched, otherwise `errored`.
@@ -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 failure (deleted definition, project\n// mismatch) is recorded and skipped; a transient one is recorded 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":"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"}
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":"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"}
@@ -1,5 +1,5 @@
1
1
  import { type TriggerHistoryRecorder } from './record-trigger-history.js';
2
- import type { WorkflowsModuleClient } from './workflows-client.js';
2
+ import { type WorkflowsModuleClient } from './workflows-client.js';
3
3
  export interface RouteEventToJobListenersParams {
4
4
  workflows: WorkflowsModuleClient;
5
5
  history: TriggerHistoryRecorder;
@@ -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":"AAIA,OAAO,EAAC,KAAK,sBAAsB,EAAW,MAAM,6BAA6B,CAAC;AAClF,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAEjE,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,OAAO,EAAE,sBAAsB,CAAC;IAChC,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,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,CAmEzC"}
1
+ {"version":3,"file":"route-event-to-job-listeners.d.ts","sourceRoot":"","sources":["../../src/core/route-event-to-job-listeners.ts"],"names":[],"mappings":"AAIA,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,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,CAmEzC"}
@@ -2,6 +2,7 @@ import { logger } from '@shipfox/node-opentelemetry';
2
2
  import { findMatchingJobListenerSubscriptions } from '#db/job-listener-subscriptions.js';
3
3
  import { evaluateStoredFilter } from './config.js';
4
4
  import { toReason } from './record-trigger-history.js';
5
+ import { isPermanentDeliverEventToJobListenerError } from './workflows-client.js';
5
6
  export async function routeEventToJobListeners(params) {
6
7
  const subscriptions = await findMatchingJobListenerSubscriptions({
7
8
  workspaceId: params.workspaceId,
@@ -53,7 +54,7 @@ export async function routeEventToJobListeners(params) {
53
54
  } catch (error) {
54
55
  dispatchErrorCount += 1;
55
56
  await params.history.listenerDispatchErrored(subscription, toReason(error));
56
- if (!sawTransientError) {
57
+ if (!isPermanentDeliverEventToJobListenerError(error) && !sawTransientError) {
57
58
  sawTransientError = true;
58
59
  firstTransientError = error;
59
60
  }
@@ -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 type {WorkflowsModuleClient} 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 (!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","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 {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,11 +1,24 @@
1
1
  import { type WorkflowsModuleClient } from '@shipfox/api-workflows-dto/inter-module';
2
2
  export type { WorkflowsModuleClient };
3
+ type StartRunKnownError = NonNullable<ReturnType<typeof startRunKnownError>>;
3
4
  /**
4
5
  * Workflows declares only failures that can never succeed on retry for trigger
5
- * run creation. Every other outcome is opaque and must remain retryable because
6
- * it may have committed before the caller stopped waiting.
6
+ * run creation, including workspace admission failures. Every other outcome is
7
+ * opaque and must remain retryable because it may have committed before the
8
+ * caller stopped waiting.
7
9
  */
8
- export declare function isPermanentStartRunError(error: unknown): boolean;
10
+ export declare function isPermanentStartRunError(error: unknown): error is StartRunKnownError;
11
+ /** Known listener admission failures cannot succeed by replaying the same event. */
12
+ export declare function isPermanentDeliverEventToJobListenerError(error: unknown): boolean;
13
+ export declare function isWorkspaceSuspendedError(error: unknown): error is Extract<StartRunKnownError, {
14
+ code: 'workspace-suspended';
15
+ }>;
16
+ export declare function isWorkspaceNotFoundError(error: unknown): error is Extract<StartRunKnownError, {
17
+ code: 'workspace-not-found';
18
+ }>;
19
+ export declare function isWorkspaceDeletedError(error: unknown): error is Extract<StartRunKnownError, {
20
+ code: 'workspace-deleted';
21
+ }>;
9
22
  export declare function isInterpolationUnresolvableError(error: unknown): error is Extract<ReturnType<typeof startRunKnownError>, {
10
23
  code: 'interpolation-unresolvable';
11
24
  }>;
@@ -37,6 +50,15 @@ declare function startRunKnownError(error: unknown): import("@shipfox/inter-modu
37
50
  id: import("zod").ZodString;
38
51
  name: import("zod").ZodString;
39
52
  }, import("zod/v4/core").$strip>, {
53
+ readonly 'workspace-not-found': import("zod").ZodObject<{
54
+ workspaceId: import("zod").ZodString;
55
+ }, import("zod/v4/core").$strip>;
56
+ readonly 'workspace-suspended': import("zod").ZodObject<{
57
+ workspaceId: import("zod").ZodString;
58
+ }, import("zod/v4/core").$strip>;
59
+ readonly 'workspace-deleted': import("zod").ZodObject<{
60
+ workspaceId: import("zod").ZodString;
61
+ }, import("zod/v4/core").$strip>;
40
62
  readonly 'definition-not-found': import("zod").ZodObject<{
41
63
  definitionId: import("zod").ZodString;
42
64
  }, import("zod/v4/core").$strip>;
@@ -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;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEhE;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
+ {"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"}
@@ -2,11 +2,24 @@ import { workflowsInterModuleContract } from '@shipfox/api-workflows-dto/inter-m
2
2
  import { isInterModuleKnownError } from '@shipfox/inter-module';
3
3
  /**
4
4
  * Workflows declares only failures that can never succeed on retry for trigger
5
- * run creation. Every other outcome is opaque and must remain retryable because
6
- * it may have committed before the caller stopped waiting.
5
+ * run creation, including workspace admission failures. Every other outcome is
6
+ * opaque and must remain retryable because it may have committed before the
7
+ * caller stopped waiting.
7
8
  */ export function isPermanentStartRunError(error) {
8
9
  return isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error);
9
10
  }
11
+ /** Known listener admission failures cannot succeed by replaying the same event. */ export function isPermanentDeliverEventToJobListenerError(error) {
12
+ return isInterModuleKnownError(workflowsInterModuleContract.methods.deliverEventToJobListener, error);
13
+ }
14
+ export function isWorkspaceSuspendedError(error) {
15
+ return isPermanentStartRunError(error) && error.code === 'workspace-suspended';
16
+ }
17
+ export function isWorkspaceNotFoundError(error) {
18
+ return isPermanentStartRunError(error) && error.code === 'workspace-not-found';
19
+ }
20
+ export function isWorkspaceDeletedError(error) {
21
+ return isPermanentStartRunError(error) && error.code === 'workspace-deleted';
22
+ }
10
23
  export function isInterpolationUnresolvableError(error) {
11
24
  return isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error) && error.code === 'interpolation-unresolvable';
12
25
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/workflows-client.ts"],"sourcesContent":["import {\n type WorkflowsModuleClient,\n workflowsInterModuleContract,\n} from '@shipfox/api-workflows-dto/inter-module';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\n\nexport type {WorkflowsModuleClient};\n\n/**\n * Workflows declares only failures that can never succeed on retry for trigger\n * run creation. Every other outcome is opaque and must remain retryable because\n * it may have committed before the caller stopped waiting.\n */\nexport function isPermanentStartRunError(error: unknown): boolean {\n return isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error);\n}\n\nexport function isInterpolationUnresolvableError(\n error: unknown,\n): error is Extract<ReturnType<typeof startRunKnownError>, {code: 'interpolation-unresolvable'}> {\n return (\n isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error) &&\n error.code === 'interpolation-unresolvable'\n );\n}\n\nfunction startRunKnownError(error: unknown) {\n if (!isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error)) {\n return undefined;\n }\n return error;\n}\n"],"names":["workflowsInterModuleContract","isInterModuleKnownError","isPermanentStartRunError","error","methods","startRunFromTrigger","isInterpolationUnresolvableError","code","startRunKnownError","undefined"],"mappings":"AAAA,SAEEA,4BAA4B,QACvB,0CAA0C;AACjD,SAAQC,uBAAuB,QAAO,wBAAwB;AAI9D;;;;CAIC,GACD,OAAO,SAASC,yBAAyBC,KAAc;IACrD,OAAOF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF;AAC3F;AAEA,OAAO,SAASG,iCACdH,KAAc;IAEd,OACEF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF,UAClFA,MAAMI,IAAI,KAAK;AAEnB;AAEA,SAASC,mBAAmBL,KAAc;IACxC,IAAI,CAACF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF,QAAQ;QAC7F,OAAOM;IACT;IACA,OAAON;AACT"}
1
+ {"version":3,"sources":["../../src/core/workflows-client.ts"],"sourcesContent":["import {\n type WorkflowsModuleClient,\n workflowsInterModuleContract,\n} from '@shipfox/api-workflows-dto/inter-module';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\n\nexport type {WorkflowsModuleClient};\n\ntype StartRunKnownError = NonNullable<ReturnType<typeof startRunKnownError>>;\n\n/**\n * Workflows declares only failures that can never succeed on retry for trigger\n * run creation, including workspace admission failures. Every other outcome is\n * opaque and must remain retryable because it may have committed before the\n * caller stopped waiting.\n */\nexport function isPermanentStartRunError(error: unknown): error is StartRunKnownError {\n return isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error);\n}\n\n/** Known listener admission failures cannot succeed by replaying the same event. */\nexport function isPermanentDeliverEventToJobListenerError(error: unknown): boolean {\n return isInterModuleKnownError(\n workflowsInterModuleContract.methods.deliverEventToJobListener,\n error,\n );\n}\n\nexport function isWorkspaceSuspendedError(\n error: unknown,\n): error is Extract<StartRunKnownError, {code: 'workspace-suspended'}> {\n return isPermanentStartRunError(error) && error.code === 'workspace-suspended';\n}\n\nexport function isWorkspaceNotFoundError(\n error: unknown,\n): error is Extract<StartRunKnownError, {code: 'workspace-not-found'}> {\n return isPermanentStartRunError(error) && error.code === 'workspace-not-found';\n}\n\nexport function isWorkspaceDeletedError(\n error: unknown,\n): error is Extract<StartRunKnownError, {code: 'workspace-deleted'}> {\n return isPermanentStartRunError(error) && error.code === 'workspace-deleted';\n}\n\nexport function isInterpolationUnresolvableError(\n error: unknown,\n): error is Extract<ReturnType<typeof startRunKnownError>, {code: 'interpolation-unresolvable'}> {\n return (\n isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error) &&\n error.code === 'interpolation-unresolvable'\n );\n}\n\nfunction startRunKnownError(error: unknown) {\n if (!isInterModuleKnownError(workflowsInterModuleContract.methods.startRunFromTrigger, error)) {\n return undefined;\n }\n return error;\n}\n"],"names":["workflowsInterModuleContract","isInterModuleKnownError","isPermanentStartRunError","error","methods","startRunFromTrigger","isPermanentDeliverEventToJobListenerError","deliverEventToJobListener","isWorkspaceSuspendedError","code","isWorkspaceNotFoundError","isWorkspaceDeletedError","isInterpolationUnresolvableError","startRunKnownError","undefined"],"mappings":"AAAA,SAEEA,4BAA4B,QACvB,0CAA0C;AACjD,SAAQC,uBAAuB,QAAO,wBAAwB;AAM9D;;;;;CAKC,GACD,OAAO,SAASC,yBAAyBC,KAAc;IACrD,OAAOF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF;AAC3F;AAEA,kFAAkF,GAClF,OAAO,SAASG,0CAA0CH,KAAc;IACtE,OAAOF,wBACLD,6BAA6BI,OAAO,CAACG,yBAAyB,EAC9DJ;AAEJ;AAEA,OAAO,SAASK,0BACdL,KAAc;IAEd,OAAOD,yBAAyBC,UAAUA,MAAMM,IAAI,KAAK;AAC3D;AAEA,OAAO,SAASC,yBACdP,KAAc;IAEd,OAAOD,yBAAyBC,UAAUA,MAAMM,IAAI,KAAK;AAC3D;AAEA,OAAO,SAASE,wBACdR,KAAc;IAEd,OAAOD,yBAAyBC,UAAUA,MAAMM,IAAI,KAAK;AAC3D;AAEA,OAAO,SAASG,iCACdT,KAAc;IAEd,OACEF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF,UAClFA,MAAMM,IAAI,KAAK;AAEnB;AAEA,SAASI,mBAAmBV,KAAc;IACxC,IAAI,CAACF,wBAAwBD,6BAA6BI,OAAO,CAACC,mBAAmB,EAAEF,QAAQ;QAC7F,OAAOW;IACT;IACA,OAAOX;AACT"}
@@ -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;AAQnF,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,qBAAqB,mDAgE5E"}
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,mDAkF5E"}
@@ -4,7 +4,7 @@ import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
4
  import { z } from 'zod';
5
5
  import { ManualTriggerNotFoundError } from '#core/errors.js';
6
6
  import { fireManualSubscription } from '#core/fire-manual.js';
7
- import { isInterpolationUnresolvableError } from '#core/workflows-client.js';
7
+ import { isInterpolationUnresolvableError, isWorkspaceDeletedError, isWorkspaceNotFoundError, isWorkspaceSuspendedError } from '#core/workflows-client.js';
8
8
  import { getManualSubscriptionByDefinitionId } from '#db/subscriptions.js';
9
9
  export function createFireManualTriggerRoute(workflows) {
10
10
  return defineRoute({
@@ -34,6 +34,24 @@ export function createFireManualTriggerRoute(workflows) {
34
34
  status: 404
35
35
  });
36
36
  }
37
+ if (isWorkspaceSuspendedError(error)) {
38
+ throw new ClientError('Workspace is suspended', 'workspace-suspended', {
39
+ status: 409,
40
+ cause: error
41
+ });
42
+ }
43
+ if (isWorkspaceDeletedError(error)) {
44
+ throw new ClientError('Workspace is deleted', 'workspace-deleted', {
45
+ status: 404,
46
+ cause: error
47
+ });
48
+ }
49
+ if (isWorkspaceNotFoundError(error)) {
50
+ throw new ClientError('Workspace not found', 'workspace-not-found', {
51
+ status: 404,
52
+ cause: error
53
+ });
54
+ }
37
55
  if (isInterpolationUnresolvableError(error)) {
38
56
  throw new ClientError('Workflow interpolation cannot be resolved', 'workflow-interpolation-unresolvable', {
39
57
  status: 422,
@@ -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 {isInterpolationUnresolvableError} 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 (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 // 404 covers both \"no such manual trigger\" and \"not your workspace\" to avoid leaking existence.\n if (!subscription || !userContext.canAccess(subscription.workspaceId)) {\n throw new ManualTriggerNotFoundError(definitionId);\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","fireManualTriggerBodySchema","fireManualTriggerResponseSchema","ClientError","defineRoute","z","ManualTriggerNotFoundError","fireManualSubscription","isInterpolationUnresolvableError","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","envKey","undefined","handler","request","reply","userContext","subscription","canAccess","workspaceId","run","subscriptionId","id","callerWorkspaceId","userId","inputs","workflow_run_id"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,4BAA4B;AAC7D,SACEC,2BAA2B,EAC3BC,+BAA+B,QAC1B,4BAA4B;AAEnC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,0BAA0B,QAAO,kBAAkB;AAC3D,SAAQC,sBAAsB,QAAO,uBAAuB;AAC5D,SAAQC,gCAAgC,QAAO,4BAA4B;AAC3E,SAAQC,mCAAmC,QAAO,uBAAuB;AAEzE,OAAO,SAASC,6BAA6BC,SAAgC;IAC3E,OAAOP,YAAY;QACjBQ,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,QAAQX,EAAEY,MAAM,CAAC;gBACfC,cAAcb,EAAEc,MAAM,GAAGC,IAAI;YAC/B;YACAC,MAAMpB;YACNqB,UAAU;gBACR,KAAKpB;gBACL,KAAKG,EAAEY,MAAM,CAAC;oBACZM,MAAMlB,EAAEc,MAAM;oBACdK,SAASnB,EAAEY,MAAM,CAAC;wBAChBQ,OAAOpB,EAAEc,MAAM;wBACfO,QAAQrB,EAAEc,MAAM;wBAChBQ,SAAStB,EAAEc,MAAM,GAAGS,QAAQ;oBAC9B;gBACF;YACF;QACF;QACAC,cAAc,CAACC;YACb,IAAIA,iBAAiBxB,4BAA4B;gBAC/C,MAAM,IAAIH,YAAY2B,MAAMC,OAAO,EAAE,4BAA4B;oBAACC,QAAQ;gBAAG;YAC/E;YACA,IAAIxB,iCAAiCsB,QAAQ;gBAC3C,MAAM,IAAI3B,YACR,6CACA,uCACA;oBACE6B,QAAQ;oBACRR,SAAS;wBACPC,OAAOK,MAAMN,OAAO,CAACC,KAAK;wBAC1BC,QAAQI,MAAMN,OAAO,CAACE,MAAM;wBAC5B,GAAII,MAAMN,OAAO,CAACS,MAAM,KAAKC,YAAY,CAAC,IAAI;4BAACP,SAASG,MAAMN,OAAO,CAACS,MAAM;wBAAA,CAAC;oBAC/E;gBACF;YAEJ;YACA,MAAMH;QACR;QACAK,SAAS,OAAOC,SAASC;YACvB,MAAM,EAACnB,YAAY,EAAC,GAAGkB,QAAQpB,MAAM;YACrC,MAAMsB,cAActC,mBAAmBoC;YAEvC,MAAMG,eAAe,MAAM9B,oCAAoCS;YAC/D,gGAAgG;YAChG,IAAI,CAACqB,gBAAgB,CAACD,YAAYE,SAAS,CAACD,aAAaE,WAAW,GAAG;gBACrE,MAAM,IAAInC,2BAA2BY;YACvC;YAEA,MAAMwB,MAAM,MAAMnC,uBAAuB;gBACvCI;gBACAgC,gBAAgBJ,aAAaK,EAAE;gBAC/BC,mBAAmBN,aAAaE,WAAW;gBAC3CK,QAAQR,YAAYQ,MAAM;gBAC1BC,QAAQX,QAAQf,IAAI,CAAC0B,MAAM;YAC7B;YAEAV,MAAML,MAAM,CAAC;YACb,OAAO;gBAACgB,iBAAiBN,IAAIE,EAAE;YAAA;QACjC;IACF;AACF"}
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 // 404 covers both \"no such manual trigger\" and \"not your workspace\" to avoid leaking existence.\n if (!subscription || !userContext.canAccess(subscription.workspaceId)) {\n throw new ManualTriggerNotFoundError(definitionId);\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","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","canAccess","workspaceId","run","subscriptionId","id","callerWorkspaceId","userId","inputs","workflow_run_id"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,4BAA4B;AAC7D,SACEC,2BAA2B,EAC3BC,+BAA+B,QAC1B,4BAA4B;AAEnC,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,0BAA0B,QAAO,kBAAkB;AAC3D,SAAQC,sBAAsB,QAAO,uBAAuB;AAC5D,SACEC,gCAAgC,EAChCC,uBAAuB,EACvBC,wBAAwB,EACxBC,yBAAyB,QACpB,4BAA4B;AACnC,SAAQC,mCAAmC,QAAO,uBAAuB;AAEzE,OAAO,SAASC,6BAA6BC,SAAgC;IAC3E,OAAOV,YAAY;QACjBW,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,QAAQd,EAAEe,MAAM,CAAC;gBACfC,cAAchB,EAAEiB,MAAM,GAAGC,IAAI;YAC/B;YACAC,MAAMvB;YACNwB,UAAU;gBACR,KAAKvB;gBACL,KAAKG,EAAEe,MAAM,CAAC;oBACZM,MAAMrB,EAAEiB,MAAM;oBACdK,SAAStB,EAAEe,MAAM,CAAC;wBAChBQ,OAAOvB,EAAEiB,MAAM;wBACfO,QAAQxB,EAAEiB,MAAM;wBAChBQ,SAASzB,EAAEiB,MAAM,GAAGS,QAAQ;oBAC9B;gBACF;YACF;QACF;QACAC,cAAc,CAACC;YACb,IAAIA,iBAAiB3B,4BAA4B;gBAC/C,MAAM,IAAIH,YAAY8B,MAAMC,OAAO,EAAE,4BAA4B;oBAACC,QAAQ;gBAAG;YAC/E;YACA,IAAIxB,0BAA0BsB,QAAQ;gBACpC,MAAM,IAAI9B,YAAY,0BAA0B,uBAAuB;oBACrEgC,QAAQ;oBACRC,OAAOH;gBACT;YACF;YACA,IAAIxB,wBAAwBwB,QAAQ;gBAClC,MAAM,IAAI9B,YAAY,wBAAwB,qBAAqB;oBACjEgC,QAAQ;oBACRC,OAAOH;gBACT;YACF;YACA,IAAIvB,yBAAyBuB,QAAQ;gBACnC,MAAM,IAAI9B,YAAY,uBAAuB,uBAAuB;oBAClEgC,QAAQ;oBACRC,OAAOH;gBACT;YACF;YACA,IAAIzB,iCAAiCyB,QAAQ;gBAC3C,MAAM,IAAI9B,YACR,6CACA,uCACA;oBACEgC,QAAQ;oBACRR,SAAS;wBACPC,OAAOK,MAAMN,OAAO,CAACC,KAAK;wBAC1BC,QAAQI,MAAMN,OAAO,CAACE,MAAM;wBAC5B,GAAII,MAAMN,OAAO,CAACU,MAAM,KAAKC,YAAY,CAAC,IAAI;4BAACR,SAASG,MAAMN,OAAO,CAACU,MAAM;wBAAA,CAAC;oBAC/E;gBACF;YAEJ;YACA,MAAMJ;QACR;QACAM,SAAS,OAAOC,SAASC;YACvB,MAAM,EAACpB,YAAY,EAAC,GAAGmB,QAAQrB,MAAM;YACrC,MAAMuB,cAAc1C,mBAAmBwC;YAEvC,MAAMG,eAAe,MAAM/B,oCAAoCS;YAC/D,gGAAgG;YAChG,IAAI,CAACsB,gBAAgB,CAACD,YAAYE,SAAS,CAACD,aAAaE,WAAW,GAAG;gBACrE,MAAM,IAAIvC,2BAA2Be;YACvC;YAEA,MAAMyB,MAAM,MAAMvC,uBAAuB;gBACvCO;gBACAiC,gBAAgBJ,aAAaK,EAAE;gBAC/BC,mBAAmBN,aAAaE,WAAW;gBAC3CK,QAAQR,YAAYQ,MAAM;gBAC1BC,QAAQX,QAAQhB,IAAI,CAAC2B,MAAM;YAC7B;YAEAV,MAAMN,MAAM,CAAC;YACb,OAAO;gBAACiB,iBAAiBN,IAAIE,EAAE;YAAA;QACjC;IACF;AACF"}