@shipfox/api-triggers 7.0.1 → 7.1.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 +11 -14
- package/CHANGELOG.md +15 -0
- package/dist/core/drain-cron-schedules.d.ts.map +1 -1
- package/dist/core/drain-cron-schedules.js +8 -0
- package/dist/core/drain-cron-schedules.js.map +1 -1
- package/dist/core/record-trigger-history.d.ts.map +1 -1
- package/dist/core/record-trigger-history.js +9 -0
- package/dist/core/record-trigger-history.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +17164 -3306
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +8 -25
- package/src/core/drain-cron-schedules.ts +6 -0
- package/src/core/record-trigger-history.ts +6 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
$ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
|
|
2
|
-
Successfully compiled: 58 files with swc (
|
|
3
|
-
2026-07-
|
|
4
|
-
|
|
5
|
-
modules
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
../../../node_modules/.pnpm/long@5.3.2/node_modules/long/index.js 43.2 KiB [built] [code generated]
|
|
10
|
-
../../../node_modules/.pnpm/ms@3.0.0-canary.1/node_modules/ms/dist/index.mjs 3.26 KiB [built] [code generated]
|
|
11
|
-
modules by path ./dist/temporal/workflows/ 3.72 KiB
|
|
12
|
-
./dist/temporal/workflows/index-autogenerated-entrypoint.cjs 527 bytes [built] [code generated]
|
|
2
|
+
Successfully compiled: 58 files with swc (521.02ms)
|
|
3
|
+
2026-07-22T13:11:53.445Z [INFO] asset workflow-bundle-69915b80080285c7230d.js 3.17 MiB [emitted] [immutable] (name: main)
|
|
4
|
+
orphan modules 33.5 KiB [orphan] 21 modules
|
|
5
|
+
runtime modules 1.13 KiB 5 modules
|
|
6
|
+
modules by path ../../../node_modules/.pnpm/ 970 KiB 196 modules
|
|
7
|
+
modules by path ./dist/temporal/workflows/ 4.11 KiB
|
|
8
|
+
./dist/temporal/workflows/index-autogenerated-entrypoint.cjs 933 bytes [built] [code generated]
|
|
13
9
|
./dist/temporal/workflows/index.js 126 bytes [built] [code generated]
|
|
14
10
|
./dist/temporal/workflows/cron-tick-cron.js 2.56 KiB [built] [code generated]
|
|
15
11
|
./dist/temporal/workflows/prune-trigger-events-cron.js 528 bytes [built] [code generated]
|
|
16
12
|
optional modules 30 bytes [optional]
|
|
17
13
|
__temporal_custom_payload_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
18
14
|
__temporal_custom_failure_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
../../shared/node/temporal/dist/workflow-error-interceptor.js 1.21 KiB [built] [code generated]
|
|
16
|
+
webpack 5.107.2 compiled successfully in 7776 ms
|
|
17
|
+
2026-07-22T13:11:53.465Z [INFO] Workflow bundle created { size: '3.17MB' }
|
|
18
|
+
/home/runner/work/shipfox/shipfox/libs/api/triggers/dist/temporal/workflows/index.js: 3329015 bytes
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @shipfox/api-triggers
|
|
2
2
|
|
|
3
|
+
## 7.1.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ac42c96: Adds boundary-owned reporting for unexpected API runtime failures while preserving expected client and provider outcomes.
|
|
8
|
+
- Updated dependencies [ac42c96]
|
|
9
|
+
- Updated dependencies [6ce08c0]
|
|
10
|
+
- @shipfox/node-error-monitoring@0.2.0
|
|
11
|
+
- @shipfox/node-fastify@0.3.0
|
|
12
|
+
- @shipfox/node-module@0.5.0
|
|
13
|
+
- @shipfox/node-temporal@0.4.0
|
|
14
|
+
- @shipfox/node-opentelemetry@0.6.0
|
|
15
|
+
- @shipfox/api-auth-context@7.1.0
|
|
16
|
+
- @shipfox/api-projects@7.1.0
|
|
17
|
+
|
|
3
18
|
## 7.0.1
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain-cron-schedules.d.ts","sourceRoot":"","sources":["../../src/core/drain-cron-schedules.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drain-cron-schedules.d.ts","sourceRoot":"","sources":["../../src/core/drain-cron-schedules.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAEjE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CACzD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAyD3B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportError } from '@shipfox/node-error-monitoring';
|
|
1
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
3
|
import { advanceCronSchedule, claimDueCronSchedules, selectDbNow } from '#db/cron-schedules.js';
|
|
3
4
|
import { db } from '#db/db.js';
|
|
@@ -62,6 +63,13 @@ import { fireCronSubscription } from './fire-cron.js';
|
|
|
62
63
|
err: error,
|
|
63
64
|
subscriptionId: schedule.subscriptionId
|
|
64
65
|
}, 'cron drain: transient fire failure; schedule left due for retry');
|
|
66
|
+
reportError(error, {
|
|
67
|
+
boundary: 'triggers.maintenance',
|
|
68
|
+
operation: 'drain-cron-schedules',
|
|
69
|
+
extra: {
|
|
70
|
+
subscriptionId: schedule.subscriptionId
|
|
71
|
+
}
|
|
72
|
+
});
|
|
65
73
|
}
|
|
66
74
|
params.onScheduleProcessed?.();
|
|
67
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/drain-cron-schedules.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {advanceCronSchedule, claimDueCronSchedules, selectDbNow} from '#db/cron-schedules.js';\nimport {db} from '#db/db.js';\nimport {computeNextFireAt} from './compute-next-fire-at.js';\nimport {fireCronSubscription} from './fire-cron.js';\nimport type {WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DrainDueCronSchedulesParams {\n readonly workflows: WorkflowsModuleClient;\n readonly batchSize: number;\n readonly jitterWindowSeconds: number;\n /**\n * Liveness signal invoked once per processed schedule (e.g. the Temporal activity\n * heartbeat), so a batch stuck on a slow `runWorkflow` is visible rather than\n * looking hung until the start-to-close timeout fires.\n */\n readonly onScheduleProcessed?: (() => void) | undefined;\n}\n\nexport interface CronDrainSummary {\n readonly claimed: number;\n readonly fired: number;\n readonly errored: number;\n readonly retried: number;\n}\n\n/**\n * Claims one bounded batch of due cron schedules and, per row, advances the schedule\n * and fires the workflow. There is no internal drain loop, so the per-activity ceiling\n * stays a hard `batchSize`.\n *\n * The batch is claimed and processed inside a single transaction, so its `FOR UPDATE\n * SKIP LOCKED` locks are held until commit and concurrent activities/pods claim disjoint\n * rows. Each row runs in a nested savepoint that advances `next_fire_at` before firing:\n * on a transient failure the savepoint rolls back only that row's advance, leaving it due\n * for retry, without blocking the rest of the batch. Firing before the advance commits\n * makes a crash between the two re-fire the (idempotent) occurrence rather than skip it.\n */\nexport async function drainDueCronSchedules(\n params: DrainDueCronSchedulesParams,\n): Promise<CronDrainSummary> {\n let claimed = 0;\n let fired = 0;\n let errored = 0;\n let retried = 0;\n\n await db().transaction(async (tx) => {\n // One database-clock reading shared by the due check and every advance, so the two\n // never disagree under application/database clock skew.\n const now = await selectDbNow(tx);\n const due = await claimDueCronSchedules({tx, limit: params.batchSize, now});\n claimed = due.length;\n\n for (const schedule of due) {\n const scheduledSlot = schedule.nextFireAt;\n try {\n const result = await tx.transaction(async (rowTx) => {\n const nextFireAt = computeNextFireAt({\n cronExpression: schedule.cronExpression,\n timezone: schedule.timezone,\n from: now,\n subscriptionId: schedule.subscriptionId,\n jitterWindowSeconds: params.jitterWindowSeconds,\n });\n await advanceCronSchedule({\n tx: rowTx,\n subscriptionId: schedule.subscriptionId,\n nextFireAt,\n lastFiredAt: scheduledSlot,\n });\n return await fireCronSubscription({\n workflows: params.workflows,\n subscriptionId: schedule.subscriptionId,\n scheduledSlot,\n });\n });\n if (result.outcome === 'fired') fired += 1;\n else errored += 1;\n } catch (error) {\n // Transient failure: the savepoint rolled back the advance, so the schedule\n // stays due and the next tick (or an activity retry) re-claims it.\n retried += 1;\n logger().warn(\n {err: error, subscriptionId: schedule.subscriptionId},\n 'cron drain: transient fire failure; schedule left due for retry',\n );\n }\n params.onScheduleProcessed?.();\n }\n });\n\n return {claimed, fired, errored, retried};\n}\n"],"names":["logger","advanceCronSchedule","claimDueCronSchedules","selectDbNow","db","computeNextFireAt","fireCronSubscription","drainDueCronSchedules","params","claimed","fired","errored","retried","transaction","tx","now","due","limit","batchSize","length","schedule","scheduledSlot","nextFireAt","result","rowTx","cronExpression","timezone","from","subscriptionId","jitterWindowSeconds","lastFiredAt","workflows","outcome","error","warn","err","onScheduleProcessed"],"mappings":"AAAA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,mBAAmB,EAAEC,qBAAqB,EAAEC,WAAW,QAAO,wBAAwB;AAC9F,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,iBAAiB,QAAO,4BAA4B;AAC5D,SAAQC,oBAAoB,QAAO,iBAAiB;AAsBpD;;;;;;;;;;;CAWC,GACD,OAAO,eAAeC,sBACpBC,MAAmC;IAEnC,IAAIC,UAAU;IACd,IAAIC,QAAQ;IACZ,IAAIC,UAAU;IACd,IAAIC,UAAU;IAEd,MAAMR,KAAKS,WAAW,CAAC,OAAOC;QAC5B,mFAAmF;QACnF,wDAAwD;QACxD,MAAMC,MAAM,MAAMZ,YAAYW;QAC9B,MAAME,MAAM,MAAMd,sBAAsB;YAACY;YAAIG,OAAOT,OAAOU,SAAS;YAAEH;QAAG;QACzEN,UAAUO,IAAIG,MAAM;QAEpB,KAAK,MAAMC,YAAYJ,IAAK;YAC1B,MAAMK,gBAAgBD,SAASE,UAAU;YACzC,IAAI;gBACF,MAAMC,SAAS,MAAMT,GAAGD,WAAW,CAAC,OAAOW;oBACzC,MAAMF,aAAajB,kBAAkB;wBACnCoB,gBAAgBL,SAASK,cAAc;wBACvCC,UAAUN,SAASM,QAAQ;wBAC3BC,MAAMZ;wBACNa,gBAAgBR,SAASQ,cAAc;wBACvCC,qBAAqBrB,OAAOqB,mBAAmB;oBACjD;oBACA,MAAM5B,oBAAoB;wBACxBa,IAAIU;wBACJI,gBAAgBR,SAASQ,cAAc;wBACvCN;wBACAQ,aAAaT;oBACf;oBACA,OAAO,MAAMf,qBAAqB;wBAChCyB,WAAWvB,OAAOuB,SAAS;wBAC3BH,gBAAgBR,SAASQ,cAAc;wBACvCP;oBACF;gBACF;gBACA,IAAIE,OAAOS,OAAO,KAAK,SAAStB,SAAS;qBACpCC,WAAW;YAClB,EAAE,OAAOsB,OAAO;gBACd,4EAA4E;gBAC5E,mEAAmE;gBACnErB,WAAW;gBACXZ,SAASkC,IAAI,CACX;oBAACC,KAAKF;oBAAOL,gBAAgBR,SAASQ,cAAc;gBAAA,GACpD;
|
|
1
|
+
{"version":3,"sources":["../../src/core/drain-cron-schedules.ts"],"sourcesContent":["import {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {advanceCronSchedule, claimDueCronSchedules, selectDbNow} from '#db/cron-schedules.js';\nimport {db} from '#db/db.js';\nimport {computeNextFireAt} from './compute-next-fire-at.js';\nimport {fireCronSubscription} from './fire-cron.js';\nimport type {WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DrainDueCronSchedulesParams {\n readonly workflows: WorkflowsModuleClient;\n readonly batchSize: number;\n readonly jitterWindowSeconds: number;\n /**\n * Liveness signal invoked once per processed schedule (e.g. the Temporal activity\n * heartbeat), so a batch stuck on a slow `runWorkflow` is visible rather than\n * looking hung until the start-to-close timeout fires.\n */\n readonly onScheduleProcessed?: (() => void) | undefined;\n}\n\nexport interface CronDrainSummary {\n readonly claimed: number;\n readonly fired: number;\n readonly errored: number;\n readonly retried: number;\n}\n\n/**\n * Claims one bounded batch of due cron schedules and, per row, advances the schedule\n * and fires the workflow. There is no internal drain loop, so the per-activity ceiling\n * stays a hard `batchSize`.\n *\n * The batch is claimed and processed inside a single transaction, so its `FOR UPDATE\n * SKIP LOCKED` locks are held until commit and concurrent activities/pods claim disjoint\n * rows. Each row runs in a nested savepoint that advances `next_fire_at` before firing:\n * on a transient failure the savepoint rolls back only that row's advance, leaving it due\n * for retry, without blocking the rest of the batch. Firing before the advance commits\n * makes a crash between the two re-fire the (idempotent) occurrence rather than skip it.\n */\nexport async function drainDueCronSchedules(\n params: DrainDueCronSchedulesParams,\n): Promise<CronDrainSummary> {\n let claimed = 0;\n let fired = 0;\n let errored = 0;\n let retried = 0;\n\n await db().transaction(async (tx) => {\n // One database-clock reading shared by the due check and every advance, so the two\n // never disagree under application/database clock skew.\n const now = await selectDbNow(tx);\n const due = await claimDueCronSchedules({tx, limit: params.batchSize, now});\n claimed = due.length;\n\n for (const schedule of due) {\n const scheduledSlot = schedule.nextFireAt;\n try {\n const result = await tx.transaction(async (rowTx) => {\n const nextFireAt = computeNextFireAt({\n cronExpression: schedule.cronExpression,\n timezone: schedule.timezone,\n from: now,\n subscriptionId: schedule.subscriptionId,\n jitterWindowSeconds: params.jitterWindowSeconds,\n });\n await advanceCronSchedule({\n tx: rowTx,\n subscriptionId: schedule.subscriptionId,\n nextFireAt,\n lastFiredAt: scheduledSlot,\n });\n return await fireCronSubscription({\n workflows: params.workflows,\n subscriptionId: schedule.subscriptionId,\n scheduledSlot,\n });\n });\n if (result.outcome === 'fired') fired += 1;\n else errored += 1;\n } catch (error) {\n // Transient failure: the savepoint rolled back the advance, so the schedule\n // stays due and the next tick (or an activity retry) re-claims it.\n retried += 1;\n logger().warn(\n {err: error, subscriptionId: schedule.subscriptionId},\n 'cron drain: transient fire failure; schedule left due for retry',\n );\n reportError(error, {\n boundary: 'triggers.maintenance',\n operation: 'drain-cron-schedules',\n extra: {subscriptionId: schedule.subscriptionId},\n });\n }\n params.onScheduleProcessed?.();\n }\n });\n\n return {claimed, fired, errored, retried};\n}\n"],"names":["reportError","logger","advanceCronSchedule","claimDueCronSchedules","selectDbNow","db","computeNextFireAt","fireCronSubscription","drainDueCronSchedules","params","claimed","fired","errored","retried","transaction","tx","now","due","limit","batchSize","length","schedule","scheduledSlot","nextFireAt","result","rowTx","cronExpression","timezone","from","subscriptionId","jitterWindowSeconds","lastFiredAt","workflows","outcome","error","warn","err","boundary","operation","extra","onScheduleProcessed"],"mappings":"AAAA,SAAQA,WAAW,QAAO,iCAAiC;AAC3D,SAAQC,MAAM,QAAO,8BAA8B;AACnD,SAAQC,mBAAmB,EAAEC,qBAAqB,EAAEC,WAAW,QAAO,wBAAwB;AAC9F,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,iBAAiB,QAAO,4BAA4B;AAC5D,SAAQC,oBAAoB,QAAO,iBAAiB;AAsBpD;;;;;;;;;;;CAWC,GACD,OAAO,eAAeC,sBACpBC,MAAmC;IAEnC,IAAIC,UAAU;IACd,IAAIC,QAAQ;IACZ,IAAIC,UAAU;IACd,IAAIC,UAAU;IAEd,MAAMR,KAAKS,WAAW,CAAC,OAAOC;QAC5B,mFAAmF;QACnF,wDAAwD;QACxD,MAAMC,MAAM,MAAMZ,YAAYW;QAC9B,MAAME,MAAM,MAAMd,sBAAsB;YAACY;YAAIG,OAAOT,OAAOU,SAAS;YAAEH;QAAG;QACzEN,UAAUO,IAAIG,MAAM;QAEpB,KAAK,MAAMC,YAAYJ,IAAK;YAC1B,MAAMK,gBAAgBD,SAASE,UAAU;YACzC,IAAI;gBACF,MAAMC,SAAS,MAAMT,GAAGD,WAAW,CAAC,OAAOW;oBACzC,MAAMF,aAAajB,kBAAkB;wBACnCoB,gBAAgBL,SAASK,cAAc;wBACvCC,UAAUN,SAASM,QAAQ;wBAC3BC,MAAMZ;wBACNa,gBAAgBR,SAASQ,cAAc;wBACvCC,qBAAqBrB,OAAOqB,mBAAmB;oBACjD;oBACA,MAAM5B,oBAAoB;wBACxBa,IAAIU;wBACJI,gBAAgBR,SAASQ,cAAc;wBACvCN;wBACAQ,aAAaT;oBACf;oBACA,OAAO,MAAMf,qBAAqB;wBAChCyB,WAAWvB,OAAOuB,SAAS;wBAC3BH,gBAAgBR,SAASQ,cAAc;wBACvCP;oBACF;gBACF;gBACA,IAAIE,OAAOS,OAAO,KAAK,SAAStB,SAAS;qBACpCC,WAAW;YAClB,EAAE,OAAOsB,OAAO;gBACd,4EAA4E;gBAC5E,mEAAmE;gBACnErB,WAAW;gBACXZ,SAASkC,IAAI,CACX;oBAACC,KAAKF;oBAAOL,gBAAgBR,SAASQ,cAAc;gBAAA,GACpD;gBAEF7B,YAAYkC,OAAO;oBACjBG,UAAU;oBACVC,WAAW;oBACXC,OAAO;wBAACV,gBAAgBR,SAASQ,cAAc;oBAAA;gBACjD;YACF;YACApB,OAAO+B,mBAAmB;QAC5B;IACF;IAEA,OAAO;QAAC9B;QAASC;QAAOC;QAASC;IAAO;AAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-trigger-history.d.ts","sourceRoot":"","sources":["../../src/core/record-trigger-history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record-trigger-history.d.ts","sourceRoot":"","sources":["../../src/core/record-trigger-history.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,kCAAkC,CAAC;AACzE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AAmBxE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQ/C;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,YAAY,EAAE,mBAAmB,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,aAAa,CAAC,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,eAAe,CAAC,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,iBAAiB,CAAC,YAAY,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,qBAAqB,CAAC,YAAY,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F,uBAAuB,CAAC,YAAY,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9F,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,UAAU,EAAE,IAAI,CAAC;CAClB;AAID,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,sBAAsB,CAAC,CA2DjC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportError } from '@shipfox/node-error-monitoring';
|
|
1
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
3
|
import { insertReceivedEvent, markReceivedEventDiscarded, markReceivedEventErrored, markReceivedEventFailed, markReceivedEventRouted, upsertDispatchErrorDecision, upsertFilterErrorDecision, upsertListenerDispatchErrorDecision, upsertListenerFilterErrorDecision, upsertListenerTriggeredDecision, upsertTriggeredDecision } from '#db/event-history.js';
|
|
3
4
|
const MAX_REASON_LENGTH = 2000;
|
|
@@ -68,6 +69,14 @@ async function safe(eventRef, label, fn, subscriptionId) {
|
|
|
68
69
|
subscriptionId
|
|
69
70
|
} : {}
|
|
70
71
|
}, 'trigger history write failed; ignored (best-effort)');
|
|
72
|
+
reportError(error, {
|
|
73
|
+
boundary: 'triggers.history',
|
|
74
|
+
operation: label,
|
|
75
|
+
extra: {
|
|
76
|
+
eventRef,
|
|
77
|
+
subscriptionId
|
|
78
|
+
}
|
|
79
|
+
});
|
|
71
80
|
return undefined;
|
|
72
81
|
}
|
|
73
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/record-trigger-history.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';\nimport type {TriggerEventOrigin} from '#core/entities/received-event.js';\nimport type {TriggerSubscription} from '#core/entities/subscription.js';\nimport {\n insertReceivedEvent,\n markReceivedEventDiscarded,\n markReceivedEventErrored,\n markReceivedEventFailed,\n markReceivedEventRouted,\n upsertDispatchErrorDecision,\n upsertFilterErrorDecision,\n upsertListenerDispatchErrorDecision,\n upsertListenerFilterErrorDecision,\n upsertListenerTriggeredDecision,\n upsertTriggeredDecision,\n} from '#db/event-history.js';\n\nconst MAX_REASON_LENGTH = 2000;\n\n// A bounded, deterministic reason string safe to persist: error messages can be\n// long or carry untrusted data, so cap the length and never serialize the object.\nexport function toReason(error: unknown): string {\n let message: string;\n try {\n message = error instanceof Error ? error.message : String(error);\n } catch {\n message = '[unprintable thrown value]';\n }\n return message.slice(0, MAX_REASON_LENGTH);\n}\n\nexport interface TriggerRun {\n id: string;\n name: string;\n}\n\nexport interface TriggerHistoryRecorder {\n triggered(subscription: TriggerSubscription, run: TriggerRun): Promise<void>;\n filterErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n dispatchErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n listenerTriggered(subscription: JobListenerSubscription): Promise<void>;\n listenerFilterErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n listenerDispatchErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n discarded(): Promise<void>;\n routed(matchedCount: number): Promise<void>;\n failed(matchedCount: number): Promise<void>;\n allErrored(matchedCount: number): Promise<void>;\n}\n\nexport interface BeginTriggerHistoryParams {\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n}\n\n// History writes are best-effort and log stable ids only, never payloads or tokens.\n// If the parent insert fails there is no row to attach to, so later calls no-op.\nexport async function beginTriggerHistory(\n params: BeginTriggerHistoryParams,\n): Promise<TriggerHistoryRecorder> {\n const receivedEventId = await safe(params.eventRef, 'insert-received-event', () =>\n insertReceivedEvent(params),\n );\n\n const record = async (\n label: string,\n write: (receivedEventId: string) => Promise<unknown>,\n subscriptionId?: string,\n ): Promise<void> => {\n if (receivedEventId === undefined) return;\n await safe(params.eventRef, label, () => write(receivedEventId), subscriptionId);\n };\n\n return {\n triggered: (subscription, run) =>\n record(\n 'triggered-decision',\n (id) => upsertTriggeredDecision({receivedEventId: id, subscription, run}),\n subscription.id,\n ),\n filterErrored: (subscription, reason) =>\n record(\n 'filter-error-decision',\n (id) => upsertFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n dispatchErrored: (subscription, reason) =>\n record(\n 'dispatch-error-decision',\n (id) => upsertDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerTriggered: (subscription) =>\n record(\n 'listener-triggered-decision',\n (id) => upsertListenerTriggeredDecision({receivedEventId: id, subscription}),\n subscription.id,\n ),\n listenerFilterErrored: (subscription, reason) =>\n record(\n 'listener-filter-error-decision',\n (id) => upsertListenerFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerDispatchErrored: (subscription, reason) =>\n record(\n 'listener-dispatch-error-decision',\n (id) => upsertListenerDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n discarded: () => record('discard-event', (id) => markReceivedEventDiscarded(id)),\n routed: (matchedCount) =>\n record('route-event', (id) => markReceivedEventRouted(id, matchedCount)),\n failed: (matchedCount) =>\n record('fail-event', (id) => markReceivedEventFailed(id, matchedCount)),\n allErrored: (matchedCount) =>\n record('all-errored-event', (id) => markReceivedEventErrored(id, matchedCount)),\n };\n}\n\nasync function safe<T>(\n eventRef: string,\n label: string,\n fn: () => Promise<T>,\n subscriptionId?: string,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n logger().warn(\n {err: error, label, eventRef, ...(subscriptionId ? {subscriptionId} : {})},\n 'trigger history write failed; ignored (best-effort)',\n );\n return undefined;\n }\n}\n"],"names":["logger","insertReceivedEvent","markReceivedEventDiscarded","markReceivedEventErrored","markReceivedEventFailed","markReceivedEventRouted","upsertDispatchErrorDecision","upsertFilterErrorDecision","upsertListenerDispatchErrorDecision","upsertListenerFilterErrorDecision","upsertListenerTriggeredDecision","upsertTriggeredDecision","MAX_REASON_LENGTH","toReason","error","message","Error","String","slice","beginTriggerHistory","params","receivedEventId","safe","eventRef","record","label","write","subscriptionId","undefined","triggered","subscription","run","id","filterErrored","reason","dispatchErrored","listenerTriggered","listenerFilterErrored","listenerDispatchErrored","discarded","routed","matchedCount","failed","allErrored","fn","warn","err"],"mappings":"AAAA,SAAQA,MAAM,QAAO,8BAA8B;AAInD,SACEC,mBAAmB,EACnBC,0BAA0B,EAC1BC,wBAAwB,EACxBC,uBAAuB,EACvBC,uBAAuB,EACvBC,2BAA2B,EAC3BC,yBAAyB,EACzBC,mCAAmC,EACnCC,iCAAiC,EACjCC,+BAA+B,EAC/BC,uBAAuB,QAClB,uBAAuB;AAE9B,MAAMC,oBAAoB;AAE1B,gFAAgF;AAChF,kFAAkF;AAClF,OAAO,SAASC,SAASC,KAAc;IACrC,IAAIC;IACJ,IAAI;QACFA,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAGE,OAAOH;IAC5D,EAAE,OAAM;QACNC,UAAU;IACZ;IACA,OAAOA,QAAQG,KAAK,CAAC,GAAGN;AAC1B;AAkCA,oFAAoF;AACpF,iFAAiF;AACjF,OAAO,eAAeO,oBACpBC,MAAiC;IAEjC,MAAMC,kBAAkB,MAAMC,KAAKF,OAAOG,QAAQ,EAAE,yBAAyB,IAC3EtB,oBAAoBmB;IAGtB,MAAMI,SAAS,OACbC,OACAC,OACAC;QAEA,IAAIN,oBAAoBO,WAAW;QACnC,MAAMN,KAAKF,OAAOG,QAAQ,EAAEE,OAAO,IAAMC,MAAML,kBAAkBM;IACnE;IAEA,OAAO;QACLE,WAAW,CAACC,cAAcC,MACxBP,OACE,sBACA,CAACQ,KAAOrB,wBAAwB;oBAACU,iBAAiBW;oBAAIF;oBAAcC;gBAAG,IACvED,aAAaE,EAAE;QAEnBC,eAAe,CAACH,cAAcI,SAC5BV,OACE,yBACA,CAACQ,KAAOzB,0BAA0B;oBAACc,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IAC5EJ,aAAaE,EAAE;QAEnBG,iBAAiB,CAACL,cAAcI,SAC9BV,OACE,2BACA,CAACQ,KAAO1B,4BAA4B;oBAACe,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IAC9EJ,aAAaE,EAAE;QAEnBI,mBAAmB,CAACN,eAClBN,OACE,+BACA,CAACQ,KAAOtB,gCAAgC;oBAACW,iBAAiBW;oBAAIF;gBAAY,IAC1EA,aAAaE,EAAE;QAEnBK,uBAAuB,CAACP,cAAcI,SACpCV,OACE,kCACA,CAACQ,KAAOvB,kCAAkC;oBAACY,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IACpFJ,aAAaE,EAAE;QAEnBM,yBAAyB,CAACR,cAAcI,SACtCV,OACE,oCACA,CAACQ,KAAOxB,oCAAoC;oBAACa,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IACtFJ,aAAaE,EAAE;QAEnBO,WAAW,IAAMf,OAAO,iBAAiB,CAACQ,KAAO9B,2BAA2B8B;QAC5EQ,QAAQ,CAACC,eACPjB,OAAO,eAAe,CAACQ,KAAO3B,wBAAwB2B,IAAIS;QAC5DC,QAAQ,CAACD,eACPjB,OAAO,cAAc,CAACQ,KAAO5B,wBAAwB4B,IAAIS;QAC3DE,YAAY,CAACF,eACXjB,OAAO,qBAAqB,CAACQ,KAAO7B,yBAAyB6B,IAAIS;IACrE;AACF;AAEA,eAAenB,KACbC,QAAgB,EAChBE,KAAa,EACbmB,EAAoB,EACpBjB,cAAuB;IAEvB,IAAI;QACF,OAAO,MAAMiB;IACf,EAAE,OAAO9B,OAAO;QACdd,SAAS6C,IAAI,CACX;YAACC,KAAKhC;YAAOW;YAAOF;YAAU,GAAII,iBAAiB;gBAACA;YAAc,IAAI,CAAC,CAAC;QAAC,GACzE;
|
|
1
|
+
{"version":3,"sources":["../../src/core/record-trigger-history.ts"],"sourcesContent":["import {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';\nimport type {TriggerEventOrigin} from '#core/entities/received-event.js';\nimport type {TriggerSubscription} from '#core/entities/subscription.js';\nimport {\n insertReceivedEvent,\n markReceivedEventDiscarded,\n markReceivedEventErrored,\n markReceivedEventFailed,\n markReceivedEventRouted,\n upsertDispatchErrorDecision,\n upsertFilterErrorDecision,\n upsertListenerDispatchErrorDecision,\n upsertListenerFilterErrorDecision,\n upsertListenerTriggeredDecision,\n upsertTriggeredDecision,\n} from '#db/event-history.js';\n\nconst MAX_REASON_LENGTH = 2000;\n\n// A bounded, deterministic reason string safe to persist: error messages can be\n// long or carry untrusted data, so cap the length and never serialize the object.\nexport function toReason(error: unknown): string {\n let message: string;\n try {\n message = error instanceof Error ? error.message : String(error);\n } catch {\n message = '[unprintable thrown value]';\n }\n return message.slice(0, MAX_REASON_LENGTH);\n}\n\nexport interface TriggerRun {\n id: string;\n name: string;\n}\n\nexport interface TriggerHistoryRecorder {\n triggered(subscription: TriggerSubscription, run: TriggerRun): Promise<void>;\n filterErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n dispatchErrored(subscription: TriggerSubscription, reason: string): Promise<void>;\n listenerTriggered(subscription: JobListenerSubscription): Promise<void>;\n listenerFilterErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n listenerDispatchErrored(subscription: JobListenerSubscription, reason: string): Promise<void>;\n discarded(): Promise<void>;\n routed(matchedCount: number): Promise<void>;\n failed(matchedCount: number): Promise<void>;\n allErrored(matchedCount: number): Promise<void>;\n}\n\nexport interface BeginTriggerHistoryParams {\n eventRef: string;\n origin: TriggerEventOrigin;\n workspaceId: string;\n provider: string | null;\n source: string;\n event: string;\n deliveryId: string | null;\n connectionId: string | null;\n connectionName: string | null;\n payload: Record<string, unknown> | null;\n receivedAt: Date;\n}\n\n// History writes are best-effort and log stable ids only, never payloads or tokens.\n// If the parent insert fails there is no row to attach to, so later calls no-op.\nexport async function beginTriggerHistory(\n params: BeginTriggerHistoryParams,\n): Promise<TriggerHistoryRecorder> {\n const receivedEventId = await safe(params.eventRef, 'insert-received-event', () =>\n insertReceivedEvent(params),\n );\n\n const record = async (\n label: string,\n write: (receivedEventId: string) => Promise<unknown>,\n subscriptionId?: string,\n ): Promise<void> => {\n if (receivedEventId === undefined) return;\n await safe(params.eventRef, label, () => write(receivedEventId), subscriptionId);\n };\n\n return {\n triggered: (subscription, run) =>\n record(\n 'triggered-decision',\n (id) => upsertTriggeredDecision({receivedEventId: id, subscription, run}),\n subscription.id,\n ),\n filterErrored: (subscription, reason) =>\n record(\n 'filter-error-decision',\n (id) => upsertFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n dispatchErrored: (subscription, reason) =>\n record(\n 'dispatch-error-decision',\n (id) => upsertDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerTriggered: (subscription) =>\n record(\n 'listener-triggered-decision',\n (id) => upsertListenerTriggeredDecision({receivedEventId: id, subscription}),\n subscription.id,\n ),\n listenerFilterErrored: (subscription, reason) =>\n record(\n 'listener-filter-error-decision',\n (id) => upsertListenerFilterErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n listenerDispatchErrored: (subscription, reason) =>\n record(\n 'listener-dispatch-error-decision',\n (id) => upsertListenerDispatchErrorDecision({receivedEventId: id, subscription, reason}),\n subscription.id,\n ),\n discarded: () => record('discard-event', (id) => markReceivedEventDiscarded(id)),\n routed: (matchedCount) =>\n record('route-event', (id) => markReceivedEventRouted(id, matchedCount)),\n failed: (matchedCount) =>\n record('fail-event', (id) => markReceivedEventFailed(id, matchedCount)),\n allErrored: (matchedCount) =>\n record('all-errored-event', (id) => markReceivedEventErrored(id, matchedCount)),\n };\n}\n\nasync function safe<T>(\n eventRef: string,\n label: string,\n fn: () => Promise<T>,\n subscriptionId?: string,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n logger().warn(\n {err: error, label, eventRef, ...(subscriptionId ? {subscriptionId} : {})},\n 'trigger history write failed; ignored (best-effort)',\n );\n reportError(error, {\n boundary: 'triggers.history',\n operation: label,\n extra: {eventRef, subscriptionId},\n });\n return undefined;\n }\n}\n"],"names":["reportError","logger","insertReceivedEvent","markReceivedEventDiscarded","markReceivedEventErrored","markReceivedEventFailed","markReceivedEventRouted","upsertDispatchErrorDecision","upsertFilterErrorDecision","upsertListenerDispatchErrorDecision","upsertListenerFilterErrorDecision","upsertListenerTriggeredDecision","upsertTriggeredDecision","MAX_REASON_LENGTH","toReason","error","message","Error","String","slice","beginTriggerHistory","params","receivedEventId","safe","eventRef","record","label","write","subscriptionId","undefined","triggered","subscription","run","id","filterErrored","reason","dispatchErrored","listenerTriggered","listenerFilterErrored","listenerDispatchErrored","discarded","routed","matchedCount","failed","allErrored","fn","warn","err","boundary","operation","extra"],"mappings":"AAAA,SAAQA,WAAW,QAAO,iCAAiC;AAC3D,SAAQC,MAAM,QAAO,8BAA8B;AAInD,SACEC,mBAAmB,EACnBC,0BAA0B,EAC1BC,wBAAwB,EACxBC,uBAAuB,EACvBC,uBAAuB,EACvBC,2BAA2B,EAC3BC,yBAAyB,EACzBC,mCAAmC,EACnCC,iCAAiC,EACjCC,+BAA+B,EAC/BC,uBAAuB,QAClB,uBAAuB;AAE9B,MAAMC,oBAAoB;AAE1B,gFAAgF;AAChF,kFAAkF;AAClF,OAAO,SAASC,SAASC,KAAc;IACrC,IAAIC;IACJ,IAAI;QACFA,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAGE,OAAOH;IAC5D,EAAE,OAAM;QACNC,UAAU;IACZ;IACA,OAAOA,QAAQG,KAAK,CAAC,GAAGN;AAC1B;AAkCA,oFAAoF;AACpF,iFAAiF;AACjF,OAAO,eAAeO,oBACpBC,MAAiC;IAEjC,MAAMC,kBAAkB,MAAMC,KAAKF,OAAOG,QAAQ,EAAE,yBAAyB,IAC3EtB,oBAAoBmB;IAGtB,MAAMI,SAAS,OACbC,OACAC,OACAC;QAEA,IAAIN,oBAAoBO,WAAW;QACnC,MAAMN,KAAKF,OAAOG,QAAQ,EAAEE,OAAO,IAAMC,MAAML,kBAAkBM;IACnE;IAEA,OAAO;QACLE,WAAW,CAACC,cAAcC,MACxBP,OACE,sBACA,CAACQ,KAAOrB,wBAAwB;oBAACU,iBAAiBW;oBAAIF;oBAAcC;gBAAG,IACvED,aAAaE,EAAE;QAEnBC,eAAe,CAACH,cAAcI,SAC5BV,OACE,yBACA,CAACQ,KAAOzB,0BAA0B;oBAACc,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IAC5EJ,aAAaE,EAAE;QAEnBG,iBAAiB,CAACL,cAAcI,SAC9BV,OACE,2BACA,CAACQ,KAAO1B,4BAA4B;oBAACe,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IAC9EJ,aAAaE,EAAE;QAEnBI,mBAAmB,CAACN,eAClBN,OACE,+BACA,CAACQ,KAAOtB,gCAAgC;oBAACW,iBAAiBW;oBAAIF;gBAAY,IAC1EA,aAAaE,EAAE;QAEnBK,uBAAuB,CAACP,cAAcI,SACpCV,OACE,kCACA,CAACQ,KAAOvB,kCAAkC;oBAACY,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IACpFJ,aAAaE,EAAE;QAEnBM,yBAAyB,CAACR,cAAcI,SACtCV,OACE,oCACA,CAACQ,KAAOxB,oCAAoC;oBAACa,iBAAiBW;oBAAIF;oBAAcI;gBAAM,IACtFJ,aAAaE,EAAE;QAEnBO,WAAW,IAAMf,OAAO,iBAAiB,CAACQ,KAAO9B,2BAA2B8B;QAC5EQ,QAAQ,CAACC,eACPjB,OAAO,eAAe,CAACQ,KAAO3B,wBAAwB2B,IAAIS;QAC5DC,QAAQ,CAACD,eACPjB,OAAO,cAAc,CAACQ,KAAO5B,wBAAwB4B,IAAIS;QAC3DE,YAAY,CAACF,eACXjB,OAAO,qBAAqB,CAACQ,KAAO7B,yBAAyB6B,IAAIS;IACrE;AACF;AAEA,eAAenB,KACbC,QAAgB,EAChBE,KAAa,EACbmB,EAAoB,EACpBjB,cAAuB;IAEvB,IAAI;QACF,OAAO,MAAMiB;IACf,EAAE,OAAO9B,OAAO;QACdd,SAAS6C,IAAI,CACX;YAACC,KAAKhC;YAAOW;YAAOF;YAAU,GAAII,iBAAiB;gBAACA;YAAc,IAAI,CAAC,CAAC;QAAC,GACzE;QAEF5B,YAAYe,OAAO;YACjBiC,UAAU;YACVC,WAAWvB;YACXwB,OAAO;gBAAC1B;gBAAUI;YAAc;QAClC;QACA,OAAOC;IACT;AACF"}
|