@shipfox/api-triggers 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +224 -0
  5. package/LICENSE +21 -0
  6. package/README.md +300 -0
  7. package/dist/config.d.ts +10 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +42 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/compute-next-fire-at.d.ts +9 -0
  12. package/dist/core/compute-next-fire-at.d.ts.map +1 -0
  13. package/dist/core/compute-next-fire-at.js +28 -0
  14. package/dist/core/compute-next-fire-at.js.map +1 -0
  15. package/dist/core/config.d.ts +26 -0
  16. package/dist/core/config.d.ts.map +1 -0
  17. package/dist/core/config.js +68 -0
  18. package/dist/core/config.js.map +1 -0
  19. package/dist/core/dispatch-integration-event.d.ts +14 -0
  20. package/dist/core/dispatch-integration-event.d.ts.map +1 -0
  21. package/dist/core/dispatch-integration-event.js +136 -0
  22. package/dist/core/dispatch-integration-event.js.map +1 -0
  23. package/dist/core/drain-cron-schedules.d.ts +30 -0
  24. package/dist/core/drain-cron-schedules.d.ts.map +1 -0
  25. package/dist/core/drain-cron-schedules.js +76 -0
  26. package/dist/core/drain-cron-schedules.js.map +1 -0
  27. package/dist/core/entities/cron-schedule.d.ts +11 -0
  28. package/dist/core/entities/cron-schedule.d.ts.map +1 -0
  29. package/dist/core/entities/cron-schedule.js +3 -0
  30. package/dist/core/entities/cron-schedule.js.map +1 -0
  31. package/dist/core/entities/decision.d.ts +23 -0
  32. package/dist/core/entities/decision.d.ts.map +1 -0
  33. package/dist/core/entities/decision.js +7 -0
  34. package/dist/core/entities/decision.js.map +1 -0
  35. package/dist/core/entities/index.d.ts +3 -0
  36. package/dist/core/entities/index.d.ts.map +1 -0
  37. package/dist/core/entities/index.js +3 -0
  38. package/dist/core/entities/index.js.map +1 -0
  39. package/dist/core/entities/job-listener-subscription.d.ts +14 -0
  40. package/dist/core/entities/job-listener-subscription.d.ts.map +1 -0
  41. package/dist/core/entities/job-listener-subscription.js +3 -0
  42. package/dist/core/entities/job-listener-subscription.js.map +1 -0
  43. package/dist/core/entities/received-event.d.ts +28 -0
  44. package/dist/core/entities/received-event.d.ts.map +1 -0
  45. package/dist/core/entities/received-event.js +14 -0
  46. package/dist/core/entities/received-event.js.map +1 -0
  47. package/dist/core/entities/subscription.d.ts +13 -0
  48. package/dist/core/entities/subscription.d.ts.map +1 -0
  49. package/dist/core/entities/subscription.js +3 -0
  50. package/dist/core/entities/subscription.js.map +1 -0
  51. package/dist/core/errors.d.ts +25 -0
  52. package/dist/core/errors.d.ts.map +1 -0
  53. package/dist/core/errors.js +41 -0
  54. package/dist/core/errors.js.map +1 -0
  55. package/dist/core/fire-cron.d.ts +28 -0
  56. package/dist/core/fire-cron.d.ts.map +1 -0
  57. package/dist/core/fire-cron.js +85 -0
  58. package/dist/core/fire-cron.js.map +1 -0
  59. package/dist/core/fire-manual.d.ts +9 -0
  60. package/dist/core/fire-manual.d.ts.map +1 -0
  61. package/dist/core/fire-manual.js +87 -0
  62. package/dist/core/fire-manual.js.map +1 -0
  63. package/dist/core/index.d.ts +11 -0
  64. package/dist/core/index.d.ts.map +1 -0
  65. package/dist/core/index.js +10 -0
  66. package/dist/core/index.js.map +1 -0
  67. package/dist/core/record-trigger-history.d.ts +35 -0
  68. package/dist/core/record-trigger-history.d.ts.map +1 -0
  69. package/dist/core/record-trigger-history.js +75 -0
  70. package/dist/core/record-trigger-history.js.map +1 -0
  71. package/dist/core/route-event-to-job-listeners.d.ts +31 -0
  72. package/dist/core/route-event-to-job-listeners.d.ts.map +1 -0
  73. package/dist/core/route-event-to-job-listeners.js +140 -0
  74. package/dist/core/route-event-to-job-listeners.js.map +1 -0
  75. package/dist/db/cron-schedules.d.ts +36 -0
  76. package/dist/db/cron-schedules.d.ts.map +1 -0
  77. package/dist/db/cron-schedules.js +104 -0
  78. package/dist/db/cron-schedules.js.map +1 -0
  79. package/dist/db/db.d.ts +2515 -0
  80. package/dist/db/db.d.ts.map +1 -0
  81. package/dist/db/db.js +28 -0
  82. package/dist/db/db.js.map +1 -0
  83. package/dist/db/event-history.d.ts +50 -0
  84. package/dist/db/event-history.d.ts.map +1 -0
  85. package/dist/db/event-history.js +207 -0
  86. package/dist/db/event-history.js.map +1 -0
  87. package/dist/db/event-queries.d.ts +38 -0
  88. package/dist/db/event-queries.d.ts.map +1 -0
  89. package/dist/db/event-queries.js +78 -0
  90. package/dist/db/event-queries.js.map +1 -0
  91. package/dist/db/index.d.ts +11 -0
  92. package/dist/db/index.d.ts.map +1 -0
  93. package/dist/db/index.js +14 -0
  94. package/dist/db/index.js.map +1 -0
  95. package/dist/db/job-listener-subscriptions.d.ts +24 -0
  96. package/dist/db/job-listener-subscriptions.d.ts.map +1 -0
  97. package/dist/db/job-listener-subscriptions.js +69 -0
  98. package/dist/db/job-listener-subscriptions.js.map +1 -0
  99. package/dist/db/received-events.d.ts +7 -0
  100. package/dist/db/received-events.d.ts.map +1 -0
  101. package/dist/db/received-events.js +11 -0
  102. package/dist/db/received-events.js.map +1 -0
  103. package/dist/db/schema/common.d.ts +2 -0
  104. package/dist/db/schema/common.d.ts.map +1 -0
  105. package/dist/db/schema/common.js +4 -0
  106. package/dist/db/schema/common.js.map +1 -0
  107. package/dist/db/schema/cron-schedules.d.ts +148 -0
  108. package/dist/db/schema/cron-schedules.d.ts.map +1 -0
  109. package/dist/db/schema/cron-schedules.js +39 -0
  110. package/dist/db/schema/cron-schedules.js.map +1 -0
  111. package/dist/db/schema/decisions.d.ts +284 -0
  112. package/dist/db/schema/decisions.d.ts.map +1 -0
  113. package/dist/db/schema/decisions.js +70 -0
  114. package/dist/db/schema/decisions.js.map +1 -0
  115. package/dist/db/schema/job-listener-subscriptions.d.ts +185 -0
  116. package/dist/db/schema/job-listener-subscriptions.d.ts.map +1 -0
  117. package/dist/db/schema/job-listener-subscriptions.js +41 -0
  118. package/dist/db/schema/job-listener-subscriptions.js.map +1 -0
  119. package/dist/db/schema/outbox.d.ts +195 -0
  120. package/dist/db/schema/outbox.d.ts.map +1 -0
  121. package/dist/db/schema/outbox.js +5 -0
  122. package/dist/db/schema/outbox.js.map +1 -0
  123. package/dist/db/schema/received-events.d.ts +544 -0
  124. package/dist/db/schema/received-events.d.ts.map +1 -0
  125. package/dist/db/schema/received-events.js +86 -0
  126. package/dist/db/schema/received-events.js.map +1 -0
  127. package/dist/db/schema/subscriptions.d.ts +182 -0
  128. package/dist/db/schema/subscriptions.d.ts.map +1 -0
  129. package/dist/db/schema/subscriptions.js +39 -0
  130. package/dist/db/schema/subscriptions.js.map +1 -0
  131. package/dist/db/subscriptions.d.ts +26 -0
  132. package/dist/db/subscriptions.d.ts.map +1 -0
  133. package/dist/db/subscriptions.js +103 -0
  134. package/dist/db/subscriptions.js.map +1 -0
  135. package/dist/index.d.ts +7 -0
  136. package/dist/index.d.ts.map +1 -0
  137. package/dist/index.js +68 -0
  138. package/dist/index.js.map +1 -0
  139. package/dist/metrics/index.d.ts +3 -0
  140. package/dist/metrics/index.d.ts.map +1 -0
  141. package/dist/metrics/index.js +4 -0
  142. package/dist/metrics/index.js.map +1 -0
  143. package/dist/metrics/instance.d.ts +15 -0
  144. package/dist/metrics/instance.d.ts.map +1 -0
  145. package/dist/metrics/instance.js +32 -0
  146. package/dist/metrics/instance.js.map +1 -0
  147. package/dist/metrics/service.d.ts +2 -0
  148. package/dist/metrics/service.d.ts.map +1 -0
  149. package/dist/metrics/service.js +15 -0
  150. package/dist/metrics/service.js.map +1 -0
  151. package/dist/presentation/dto/trigger-events.d.ts +7 -0
  152. package/dist/presentation/dto/trigger-events.d.ts.map +1 -0
  153. package/dist/presentation/dto/trigger-events.js +47 -0
  154. package/dist/presentation/dto/trigger-events.js.map +1 -0
  155. package/dist/presentation/index.d.ts +3 -0
  156. package/dist/presentation/index.d.ts.map +1 -0
  157. package/dist/presentation/index.js +4 -0
  158. package/dist/presentation/index.js.map +1 -0
  159. package/dist/presentation/routes/fire-manual.d.ts +2 -0
  160. package/dist/presentation/routes/fire-manual.d.ts.map +1 -0
  161. package/dist/presentation/routes/fire-manual.js +71 -0
  162. package/dist/presentation/routes/fire-manual.js.map +1 -0
  163. package/dist/presentation/routes/get-trigger-event.d.ts +2 -0
  164. package/dist/presentation/routes/get-trigger-event.d.ts.map +1 -0
  165. package/dist/presentation/routes/get-trigger-event.js +37 -0
  166. package/dist/presentation/routes/get-trigger-event.js.map +1 -0
  167. package/dist/presentation/routes/index.d.ts +3 -0
  168. package/dist/presentation/routes/index.d.ts.map +1 -0
  169. package/dist/presentation/routes/index.js +27 -0
  170. package/dist/presentation/routes/index.js.map +1 -0
  171. package/dist/presentation/routes/list-trigger-event-facets.d.ts +2 -0
  172. package/dist/presentation/routes/list-trigger-event-facets.d.ts.map +1 -0
  173. package/dist/presentation/routes/list-trigger-event-facets.js +29 -0
  174. package/dist/presentation/routes/list-trigger-event-facets.js.map +1 -0
  175. package/dist/presentation/routes/list-trigger-events.d.ts +2 -0
  176. package/dist/presentation/routes/list-trigger-events.d.ts.map +1 -0
  177. package/dist/presentation/routes/list-trigger-events.js +56 -0
  178. package/dist/presentation/routes/list-trigger-events.js.map +1 -0
  179. package/dist/presentation/subscribers/index.d.ts +6 -0
  180. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  181. package/dist/presentation/subscribers/index.js +7 -0
  182. package/dist/presentation/subscribers/index.js.map +1 -0
  183. package/dist/presentation/subscribers/on-definition-deleted.d.ts +3 -0
  184. package/dist/presentation/subscribers/on-definition-deleted.d.ts.map +1 -0
  185. package/dist/presentation/subscribers/on-definition-deleted.js +8 -0
  186. package/dist/presentation/subscribers/on-definition-deleted.js.map +1 -0
  187. package/dist/presentation/subscribers/on-definition-resolved.d.ts +3 -0
  188. package/dist/presentation/subscribers/on-definition-resolved.d.ts.map +1 -0
  189. package/dist/presentation/subscribers/on-definition-resolved.js +11 -0
  190. package/dist/presentation/subscribers/on-definition-resolved.js.map +1 -0
  191. package/dist/presentation/subscribers/on-integration-event-received.d.ts +4 -0
  192. package/dist/presentation/subscribers/on-integration-event-received.d.ts.map +1 -0
  193. package/dist/presentation/subscribers/on-integration-event-received.js +17 -0
  194. package/dist/presentation/subscribers/on-integration-event-received.js.map +1 -0
  195. package/dist/presentation/subscribers/on-job-activated.d.ts +3 -0
  196. package/dist/presentation/subscribers/on-job-activated.d.ts.map +1 -0
  197. package/dist/presentation/subscribers/on-job-activated.js +13 -0
  198. package/dist/presentation/subscribers/on-job-activated.js.map +1 -0
  199. package/dist/presentation/subscribers/on-job-terminated.d.ts +3 -0
  200. package/dist/presentation/subscribers/on-job-terminated.d.ts.map +1 -0
  201. package/dist/presentation/subscribers/on-job-terminated.js +6 -0
  202. package/dist/presentation/subscribers/on-job-terminated.js.map +1 -0
  203. package/dist/temporal/activities/drain-cron-batch.d.ts +4 -0
  204. package/dist/temporal/activities/drain-cron-batch.d.ts.map +1 -0
  205. package/dist/temporal/activities/drain-cron-batch.js +20 -0
  206. package/dist/temporal/activities/drain-cron-batch.js.map +1 -0
  207. package/dist/temporal/activities/index.d.ts +10 -0
  208. package/dist/temporal/activities/index.d.ts.map +1 -0
  209. package/dist/temporal/activities/index.js +15 -0
  210. package/dist/temporal/activities/index.js.map +1 -0
  211. package/dist/temporal/activities/prune-trigger-events.d.ts +4 -0
  212. package/dist/temporal/activities/prune-trigger-events.d.ts.map +1 -0
  213. package/dist/temporal/activities/prune-trigger-events.js +11 -0
  214. package/dist/temporal/activities/prune-trigger-events.js.map +1 -0
  215. package/dist/temporal/constants.d.ts +7 -0
  216. package/dist/temporal/constants.d.ts.map +1 -0
  217. package/dist/temporal/constants.js +7 -0
  218. package/dist/temporal/constants.js.map +1 -0
  219. package/dist/temporal/workflows/cron-tick-cron.d.ts +9 -0
  220. package/dist/temporal/workflows/cron-tick-cron.d.ts.map +1 -0
  221. package/dist/temporal/workflows/cron-tick-cron.js +54 -0
  222. package/dist/temporal/workflows/cron-tick-cron.js.map +1 -0
  223. package/dist/temporal/workflows/index.d.ts +3 -0
  224. package/dist/temporal/workflows/index.d.ts.map +1 -0
  225. package/dist/temporal/workflows/index.js +4 -0
  226. package/dist/temporal/workflows/index.js.map +1 -0
  227. package/dist/temporal/workflows/prune-trigger-events-cron.d.ts +2 -0
  228. package/dist/temporal/workflows/prune-trigger-events-cron.d.ts.map +1 -0
  229. package/dist/temporal/workflows/prune-trigger-events-cron.js +18 -0
  230. package/dist/temporal/workflows/prune-trigger-events-cron.js.map +1 -0
  231. package/dist/tsconfig.test.tsbuildinfo +1 -0
  232. package/drizzle/0000_initial.sql +109 -0
  233. package/drizzle/meta/0000_snapshot.json +905 -0
  234. package/drizzle/meta/_journal.json +13 -0
  235. package/drizzle.config.ts +14 -0
  236. package/package.json +83 -0
  237. package/src/config.test.ts +33 -0
  238. package/src/config.ts +53 -0
  239. package/src/core/compute-next-fire-at.test.ts +69 -0
  240. package/src/core/compute-next-fire-at.ts +37 -0
  241. package/src/core/config.test.ts +213 -0
  242. package/src/core/config.ts +86 -0
  243. package/src/core/dispatch-integration-event.history-failure.test.ts +66 -0
  244. package/src/core/dispatch-integration-event.test.ts +1006 -0
  245. package/src/core/dispatch-integration-event.ts +150 -0
  246. package/src/core/drain-cron-schedules.test.ts +265 -0
  247. package/src/core/drain-cron-schedules.ts +90 -0
  248. package/src/core/entities/cron-schedule.ts +10 -0
  249. package/src/core/entities/decision.ts +24 -0
  250. package/src/core/entities/index.ts +5 -0
  251. package/src/core/entities/job-listener-subscription.ts +14 -0
  252. package/src/core/entities/received-event.ts +36 -0
  253. package/src/core/entities/subscription.ts +12 -0
  254. package/src/core/errors.ts +63 -0
  255. package/src/core/fire-cron.test.ts +146 -0
  256. package/src/core/fire-cron.ts +98 -0
  257. package/src/core/fire-manual.test.ts +157 -0
  258. package/src/core/fire-manual.ts +92 -0
  259. package/src/core/index.ts +34 -0
  260. package/src/core/record-trigger-history.test.ts +159 -0
  261. package/src/core/record-trigger-history.ts +145 -0
  262. package/src/core/route-event-to-job-listeners.test.ts +369 -0
  263. package/src/core/route-event-to-job-listeners.ts +184 -0
  264. package/src/db/cron-schedules.test.ts +358 -0
  265. package/src/db/cron-schedules.ts +163 -0
  266. package/src/db/db.ts +31 -0
  267. package/src/db/event-history.test.ts +421 -0
  268. package/src/db/event-history.ts +279 -0
  269. package/src/db/event-queries.ts +140 -0
  270. package/src/db/index.ts +51 -0
  271. package/src/db/job-listener-subscriptions.test.ts +270 -0
  272. package/src/db/job-listener-subscriptions.ts +133 -0
  273. package/src/db/received-events.ts +16 -0
  274. package/src/db/schema/common.ts +3 -0
  275. package/src/db/schema/cron-schedules.ts +37 -0
  276. package/src/db/schema/decisions.test.ts +279 -0
  277. package/src/db/schema/decisions.ts +77 -0
  278. package/src/db/schema/job-listener-subscriptions.ts +65 -0
  279. package/src/db/schema/outbox.ts +4 -0
  280. package/src/db/schema/received-events.test.ts +135 -0
  281. package/src/db/schema/received-events.ts +93 -0
  282. package/src/db/schema/subscriptions.ts +46 -0
  283. package/src/db/subscriptions.test.ts +340 -0
  284. package/src/db/subscriptions.ts +179 -0
  285. package/src/index.ts +108 -0
  286. package/src/metrics/index.ts +8 -0
  287. package/src/metrics/instance.ts +33 -0
  288. package/src/metrics/service.ts +18 -0
  289. package/src/presentation/dto/trigger-events.test.ts +144 -0
  290. package/src/presentation/dto/trigger-events.ts +60 -0
  291. package/src/presentation/index.ts +6 -0
  292. package/src/presentation/routes/fire-manual.test.ts +118 -0
  293. package/src/presentation/routes/fire-manual.ts +70 -0
  294. package/src/presentation/routes/get-trigger-event.test.ts +184 -0
  295. package/src/presentation/routes/get-trigger-event.ts +37 -0
  296. package/src/presentation/routes/index.ts +21 -0
  297. package/src/presentation/routes/list-trigger-event-facets.test.ts +116 -0
  298. package/src/presentation/routes/list-trigger-event-facets.ts +29 -0
  299. package/src/presentation/routes/list-trigger-events.test.ts +379 -0
  300. package/src/presentation/routes/list-trigger-events.ts +68 -0
  301. package/src/presentation/subscribers/index.ts +5 -0
  302. package/src/presentation/subscribers/on-definition-deleted.test.ts +42 -0
  303. package/src/presentation/subscribers/on-definition-deleted.ts +6 -0
  304. package/src/presentation/subscribers/on-definition-resolved.test.ts +67 -0
  305. package/src/presentation/subscribers/on-definition-resolved.ts +11 -0
  306. package/src/presentation/subscribers/on-integration-event-received.test.ts +53 -0
  307. package/src/presentation/subscribers/on-integration-event-received.ts +21 -0
  308. package/src/presentation/subscribers/on-job-activated.ts +14 -0
  309. package/src/presentation/subscribers/on-job-terminated.ts +6 -0
  310. package/src/temporal/activities/drain-cron-batch.ts +20 -0
  311. package/src/temporal/activities/index.ts +15 -0
  312. package/src/temporal/activities/prune-trigger-events.test.ts +78 -0
  313. package/src/temporal/activities/prune-trigger-events.ts +9 -0
  314. package/src/temporal/constants.ts +7 -0
  315. package/src/temporal/workflows/cron-tick-cron.ts +56 -0
  316. package/src/temporal/workflows/index.ts +2 -0
  317. package/src/temporal/workflows/prune-trigger-events-cron.ts +17 -0
  318. package/test/env.ts +8 -0
  319. package/test/factories/cron-schedule.ts +34 -0
  320. package/test/factories/decision.ts +49 -0
  321. package/test/factories/job-listener-subscription.ts +42 -0
  322. package/test/factories/received-event.ts +49 -0
  323. package/test/factories/subscription.ts +38 -0
  324. package/test/globalSetup.ts +20 -0
  325. package/test/index.ts +5 -0
  326. package/test/setup.ts +17 -0
  327. package/tsconfig.build.json +9 -0
  328. package/tsconfig.build.tsbuildinfo +1 -0
  329. package/tsconfig.json +3 -0
  330. package/tsconfig.test.json +8 -0
  331. package/vitest.config.ts +11 -0
@@ -0,0 +1,67 @@
1
+ import type {DefinitionResolvedEvent} from '@shipfox/api-definitions-dto';
2
+ import {eq} from 'drizzle-orm';
3
+ import {db} from '#db/db.js';
4
+ import {triggerSubscriptions} from '#db/schema/subscriptions.js';
5
+ import {triggerSubscriptionFactory} from '#test/index.js';
6
+ import {onDefinitionResolved} from './on-definition-resolved.js';
7
+
8
+ function buildPayload(overrides: Partial<DefinitionResolvedEvent> = {}): DefinitionResolvedEvent {
9
+ return {
10
+ definitionId: crypto.randomUUID(),
11
+ projectId: crypto.randomUUID(),
12
+ workspaceId: crypto.randomUUID(),
13
+ configPath: '.shipfox/workflow.yml',
14
+ triggers: {onPush: {source: 'github', event: 'push'}},
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ function listSubscriptions(workflowDefinitionId: string) {
20
+ return db()
21
+ .select()
22
+ .from(triggerSubscriptions)
23
+ .where(eq(triggerSubscriptions.workflowDefinitionId, workflowDefinitionId));
24
+ }
25
+
26
+ describe('onDefinitionResolved', () => {
27
+ it('persists a subscription for each declared trigger', async () => {
28
+ const payload = buildPayload({
29
+ triggers: {
30
+ onPush: {source: 'github', event: 'push', with: {env: 'staging'}},
31
+ onManual: {source: 'manual', event: 'fire'},
32
+ },
33
+ });
34
+
35
+ await onDefinitionResolved(payload);
36
+
37
+ const rows = await listSubscriptions(payload.definitionId);
38
+ expect(rows.map((r) => r.name).sort()).toEqual(['onManual', 'onPush']);
39
+ expect(rows.find((r) => r.name === 'onPush')?.config).toEqual({with: {env: 'staging'}});
40
+ });
41
+
42
+ it('removes subscriptions whose trigger is no longer declared', async () => {
43
+ const workflowDefinitionId = crypto.randomUUID();
44
+ await triggerSubscriptionFactory.create({workflowDefinitionId, name: 'onPush'});
45
+ await triggerSubscriptionFactory.create({workflowDefinitionId, name: 'stale'});
46
+
47
+ await onDefinitionResolved(
48
+ buildPayload({
49
+ definitionId: workflowDefinitionId,
50
+ triggers: {onPush: {source: 'github', event: 'push'}},
51
+ }),
52
+ );
53
+
54
+ const rows = await listSubscriptions(workflowDefinitionId);
55
+ expect(rows.map((r) => r.name)).toEqual(['onPush']);
56
+ });
57
+
58
+ it('clears every subscription when the definition declares no triggers', async () => {
59
+ const workflowDefinitionId = crypto.randomUUID();
60
+ await triggerSubscriptionFactory.create({workflowDefinitionId, name: 'onPush'});
61
+
62
+ await onDefinitionResolved(buildPayload({definitionId: workflowDefinitionId, triggers: {}}));
63
+
64
+ const rows = await listSubscriptions(workflowDefinitionId);
65
+ expect(rows).toHaveLength(0);
66
+ });
67
+ });
@@ -0,0 +1,11 @@
1
+ import type {DefinitionResolvedEvent} from '@shipfox/api-definitions-dto';
2
+ import {projectDefinitionTriggers} from '#db/subscriptions.js';
3
+
4
+ export async function onDefinitionResolved(payload: DefinitionResolvedEvent): Promise<void> {
5
+ await projectDefinitionTriggers({
6
+ workspaceId: payload.workspaceId,
7
+ projectId: payload.projectId,
8
+ workflowDefinitionId: payload.definitionId,
9
+ triggers: payload.triggers,
10
+ });
11
+ }
@@ -0,0 +1,53 @@
1
+ import type {IntegrationEventReceivedEvent} from '@shipfox/api-integration-core-dto';
2
+ import type {DomainEvent} from '@shipfox/node-outbox';
3
+
4
+ const dispatchIntegrationEvent = vi.fn();
5
+
6
+ vi.mock('#core/dispatch-integration-event.js', () => ({
7
+ dispatchIntegrationEvent: (...args: unknown[]) => dispatchIntegrationEvent(...args),
8
+ }));
9
+
10
+ // Import after mocks so the subscriber sees the spy.
11
+ const {onIntegrationEventReceived} = await import('./on-integration-event-received.js');
12
+
13
+ describe('onIntegrationEventReceived', () => {
14
+ beforeEach(() => {
15
+ dispatchIntegrationEvent.mockReset();
16
+ });
17
+
18
+ test('passes the integration envelope to the core dispatcher', async () => {
19
+ const receivedAt = '2026-06-21T03:20:00.000Z';
20
+ const envelope: IntegrationEventReceivedEvent = {
21
+ provider: 'github',
22
+ source: 'github',
23
+ event: 'push',
24
+ workspaceId: crypto.randomUUID(),
25
+ connectionId: crypto.randomUUID(),
26
+ connectionName: 'Acme Production',
27
+ deliveryId: crypto.randomUUID(),
28
+ receivedAt,
29
+ payload: {ref: 'main', headCommitSha: 'abc123'},
30
+ };
31
+ const event: DomainEvent<IntegrationEventReceivedEvent> = {
32
+ id: crypto.randomUUID(),
33
+ type: 'integrations.event.received',
34
+ createdAt: new Date(),
35
+ payload: envelope,
36
+ };
37
+
38
+ await onIntegrationEventReceived(envelope, event);
39
+
40
+ expect(dispatchIntegrationEvent).toHaveBeenCalledWith({
41
+ eventRef: event.id,
42
+ workspaceId: envelope.workspaceId,
43
+ provider: envelope.provider,
44
+ source: envelope.source,
45
+ event: envelope.event,
46
+ deliveryId: envelope.deliveryId,
47
+ connectionId: envelope.connectionId,
48
+ connectionName: envelope.connectionName,
49
+ payload: envelope.payload,
50
+ receivedAt: new Date(receivedAt),
51
+ });
52
+ });
53
+ });
@@ -0,0 +1,21 @@
1
+ import type {IntegrationEventReceivedEvent} from '@shipfox/api-integration-core-dto';
2
+ import type {DomainEvent} from '@shipfox/node-outbox';
3
+ import {dispatchIntegrationEvent} from '#core/dispatch-integration-event.js';
4
+
5
+ export async function onIntegrationEventReceived(
6
+ envelope: IntegrationEventReceivedEvent,
7
+ event: DomainEvent<IntegrationEventReceivedEvent>,
8
+ ): Promise<void> {
9
+ await dispatchIntegrationEvent({
10
+ eventRef: event.id,
11
+ workspaceId: envelope.workspaceId,
12
+ provider: envelope.provider,
13
+ source: envelope.source,
14
+ event: envelope.event,
15
+ deliveryId: envelope.deliveryId,
16
+ connectionId: envelope.connectionId,
17
+ connectionName: envelope.connectionName ?? null,
18
+ payload: envelope.payload,
19
+ receivedAt: new Date(envelope.receivedAt),
20
+ });
21
+ }
@@ -0,0 +1,14 @@
1
+ import type {WorkflowsJobActivatedEventDto} from '@shipfox/api-workflows-dto';
2
+ import {projectJobListenerSubscriptions} from '#db/job-listener-subscriptions.js';
3
+
4
+ export async function onJobActivated(payload: WorkflowsJobActivatedEventDto): Promise<void> {
5
+ if (payload.mode !== 'listening') return;
6
+
7
+ await projectJobListenerSubscriptions({
8
+ workspaceId: payload.workspaceId,
9
+ workflowRunId: payload.workflowRunId,
10
+ jobId: payload.jobId,
11
+ on: payload.on,
12
+ until: payload.until,
13
+ });
14
+ }
@@ -0,0 +1,6 @@
1
+ import type {WorkflowsJobTerminatedEventDto} from '@shipfox/api-workflows-dto';
2
+ import {removeJobListenerSubscriptionsForJob} from '#db/job-listener-subscriptions.js';
3
+
4
+ export async function onJobTerminated(payload: WorkflowsJobTerminatedEventDto): Promise<void> {
5
+ await removeJobListenerSubscriptionsForJob(payload.jobId);
6
+ }
@@ -0,0 +1,20 @@
1
+ import {Context} from '@temporalio/activity';
2
+ import {config} from '#config.js';
3
+ import {type CronDrainSummary, drainDueCronSchedules} from '#core/drain-cron-schedules.js';
4
+
5
+ export async function drainCronBatchActivity(): Promise<CronDrainSummary> {
6
+ const ctx = Context.current();
7
+ return await drainDueCronSchedules({
8
+ batchSize: config.TRIGGER_CRON_CLAIM_BATCH,
9
+ jitterWindowSeconds: config.TRIGGER_CRON_JITTER_WINDOW_SECONDS,
10
+ onScheduleProcessed: () => ctx.heartbeat(),
11
+ });
12
+ }
13
+
14
+ // Read at runtime rather than baked into the cron workflow's start args: a start arg
15
+ // would freeze fanout at first launch (a restart re-skips the already-running workflow),
16
+ // so an activity read lets a config change take effect on the next tick after a worker
17
+ // restart, consistent with how TRIGGER_CRON_CLAIM_BATCH is read in the drain activity.
18
+ export function readCronFanoutActivity(): Promise<number> {
19
+ return Promise.resolve(config.TRIGGER_CRON_FANOUT);
20
+ }
@@ -0,0 +1,15 @@
1
+ import {drainCronBatchActivity, readCronFanoutActivity} from './drain-cron-batch.js';
2
+ import {pruneTriggerEventsActivity} from './prune-trigger-events.js';
3
+
4
+ export function createTriggersMaintenanceActivities() {
5
+ return {
6
+ pruneTriggerEventsActivity,
7
+ };
8
+ }
9
+
10
+ export function createTriggersCronActivities() {
11
+ return {
12
+ drainCronBatchActivity,
13
+ readCronFanoutActivity,
14
+ };
15
+ }
@@ -0,0 +1,78 @@
1
+ import {randomUUID} from 'node:crypto';
2
+ import {eq} from 'drizzle-orm';
3
+ import {db} from '#db/db.js';
4
+ import {triggersDecisions} from '#db/schema/decisions.js';
5
+ import {triggersReceivedEvents} from '#db/schema/received-events.js';
6
+ import {pruneTriggerEventsActivity} from './prune-trigger-events.js';
7
+
8
+ const DAY_MS = 24 * 60 * 60 * 1000;
9
+
10
+ // The prune is global in a shared test DB, so assert by id instead of truncating.
11
+ async function insertEvent(createdAt: Date): Promise<string> {
12
+ const id = randomUUID();
13
+ await db().insert(triggersReceivedEvents).values({
14
+ id,
15
+ eventRef: randomUUID(),
16
+ origin: 'integration',
17
+ workspaceId: randomUUID(),
18
+ source: 'github',
19
+ event: 'push',
20
+ receivedAt: createdAt,
21
+ createdAt,
22
+ });
23
+ return id;
24
+ }
25
+
26
+ async function insertDecision(receivedEventId: string): Promise<string> {
27
+ const id = randomUUID();
28
+ await db().insert(triggersDecisions).values({
29
+ id,
30
+ receivedEventId,
31
+ subscriptionKind: 'trigger',
32
+ subscriptionId: randomUUID(),
33
+ subscriptionName: 'Deploy production',
34
+ workflowDefinitionId: randomUUID(),
35
+ projectId: randomUUID(),
36
+ decision: 'triggered',
37
+ });
38
+ return id;
39
+ }
40
+
41
+ async function eventExists(id: string): Promise<boolean> {
42
+ const rows = await db()
43
+ .select({id: triggersReceivedEvents.id})
44
+ .from(triggersReceivedEvents)
45
+ .where(eq(triggersReceivedEvents.id, id));
46
+ return rows.length > 0;
47
+ }
48
+
49
+ async function decisionExists(id: string): Promise<boolean> {
50
+ const rows = await db()
51
+ .select({id: triggersDecisions.id})
52
+ .from(triggersDecisions)
53
+ .where(eq(triggersDecisions.id, id));
54
+ return rows.length > 0;
55
+ }
56
+
57
+ describe('pruneTriggerEventsActivity', () => {
58
+ it('deletes events older than the retention window and cascades their decisions', async () => {
59
+ const oldId = await insertEvent(new Date(Date.now() - 60 * DAY_MS));
60
+ const decisionId = await insertDecision(oldId);
61
+ const recentId = await insertEvent(new Date());
62
+
63
+ const result = await pruneTriggerEventsActivity();
64
+
65
+ expect(result.deleted).toBeGreaterThanOrEqual(1);
66
+ expect(await eventExists(oldId)).toBe(false);
67
+ expect(await decisionExists(decisionId)).toBe(false);
68
+ expect(await eventExists(recentId)).toBe(true);
69
+ });
70
+
71
+ it('keeps events within the retention window', async () => {
72
+ const recentId = await insertEvent(new Date());
73
+
74
+ await pruneTriggerEventsActivity();
75
+
76
+ expect(await eventExists(recentId)).toBe(true);
77
+ });
78
+ });
@@ -0,0 +1,9 @@
1
+ import {config} from '#config.js';
2
+ import {pruneTriggerEvents} from '#db/received-events.js';
3
+
4
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
5
+
6
+ export async function pruneTriggerEventsActivity(): Promise<{deleted: number}> {
7
+ const olderThan = new Date(Date.now() - config.TRIGGER_EVENT_RETENTION_DAYS * MS_PER_DAY);
8
+ return await pruneTriggerEvents({olderThan});
9
+ }
@@ -0,0 +1,7 @@
1
+ export const TRIGGERS_MAINTENANCE_TASK_QUEUE = 'triggers-maintenance';
2
+
3
+ /**
4
+ * Dedicated queue for the minute cron tick, so bursty cron-fire load and hourly
5
+ * maintenance load scale independently.
6
+ */
7
+ export const TRIGGERS_CRON_TASK_QUEUE = 'triggers-cron';
@@ -0,0 +1,56 @@
1
+ import {log, proxyActivities} from '@temporalio/workflow';
2
+ import type {createTriggersCronActivities} from '../activities/index.js';
3
+
4
+ // Upper bound on per-tick parallelism. A misconfigured TRIGGER_CRON_FANOUT (e.g. a
5
+ // pasted large number) is clamped to this rather than spawning an unbounded fan-out
6
+ // that could stall the tick, so one config mistake cannot stop cron firing.
7
+ const MAX_CRON_FANOUT = 32;
8
+
9
+ const {drainCronBatchActivity, readCronFanoutActivity} = proxyActivities<
10
+ ReturnType<typeof createTriggersCronActivities>
11
+ >({
12
+ // Throughput is governed by fanout and claim batch, not a per-tick time budget, so
13
+ // give the batch drain plenty of room.
14
+ startToCloseTimeout: '5 minutes',
15
+ // The drain heartbeats once per processed schedule, so a hung run stops heartbeating
16
+ // well before the start-to-close timeout. Fail it within a minute (rather than after
17
+ // five) so its FOR UPDATE SKIP LOCKED locks release and the next tick re-claims those
18
+ // rows.
19
+ heartbeatTimeout: '1 minute',
20
+ // No activity retry: the drain is not batch-idempotent across retries. A retry after a
21
+ // committed advance would claim a fresh due batch and fire beyond the
22
+ // fanout * TRIGGER_CRON_CLAIM_BATCH per-tick ceiling. Whole-activity failures roll the
23
+ // batch back (rows stay due) and the once-per-minute cron schedule is the retry.
24
+ retry: {maximumAttempts: 1},
25
+ });
26
+
27
+ /**
28
+ * Fires once a minute (Temporal never overlaps a cron workflow with itself, so a slow
29
+ * tick delays the next rather than doubling it). Reads the current fanout and runs that
30
+ * many drain activities; Temporal spreads them across the worker fleet, and `SKIP LOCKED`
31
+ * keeps their claims disjoint. The per-tick fire ceiling is
32
+ * `fanout * TRIGGER_CRON_CLAIM_BATCH`.
33
+ */
34
+ export async function cronTickCron(): Promise<void> {
35
+ const fanout = await readCronFanoutActivity();
36
+ const requested = Number.isInteger(fanout) && fanout > 0 ? fanout : 1;
37
+ const parallelism = Math.min(requested, MAX_CRON_FANOUT);
38
+ if (parallelism < requested) {
39
+ log.warn('Cron tick fanout exceeds the maximum; clamping', {
40
+ requested,
41
+ max: MAX_CRON_FANOUT,
42
+ });
43
+ }
44
+
45
+ const summaries = await Promise.all(
46
+ Array.from({length: parallelism}, () => drainCronBatchActivity()),
47
+ );
48
+
49
+ const fired = summaries.reduce((total, summary) => total + summary.fired, 0);
50
+ const errored = summaries.reduce((total, summary) => total + summary.errored, 0);
51
+ const retried = summaries.reduce((total, summary) => total + summary.retried, 0);
52
+
53
+ if (fired > 0 || errored > 0 || retried > 0) {
54
+ log.info('Cron tick drained schedules', {fired, errored, retried});
55
+ }
56
+ }
@@ -0,0 +1,2 @@
1
+ export {cronTickCron} from './cron-tick-cron.js';
2
+ export {pruneTriggerEventsCron} from './prune-trigger-events-cron.js';
@@ -0,0 +1,17 @@
1
+ import {log, proxyActivities} from '@temporalio/workflow';
2
+ import type {createTriggersMaintenanceActivities} from '../activities/index.js';
3
+
4
+ const {pruneTriggerEventsActivity} = proxyActivities<
5
+ ReturnType<typeof createTriggersMaintenanceActivities>
6
+ >({
7
+ startToCloseTimeout: '5 minutes',
8
+ // Let persistent failures fall through so the next hourly run retries with a fresh cutoff.
9
+ retry: {maximumAttempts: 5},
10
+ });
11
+
12
+ export async function pruneTriggerEventsCron(): Promise<void> {
13
+ const {deleted} = await pruneTriggerEventsActivity();
14
+ if (deleted > 0) {
15
+ log.info('Pruned trigger received events', {deleted});
16
+ }
17
+ }
package/test/env.ts ADDED
@@ -0,0 +1,8 @@
1
+ process.env.POSTGRES_HOST ??= 'localhost';
2
+ process.env.POSTGRES_PORT ??= '5432';
3
+ process.env.POSTGRES_USERNAME ??= 'shipfox';
4
+ process.env.POSTGRES_PASSWORD ??= 'password';
5
+ process.env.POSTGRES_DATABASE = 'api_test';
6
+ process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
7
+ process.env.SECRETS_ENCRYPTION_KEK = 'ZmVkY2JhOTg3NjU0MzIxMGZlZGNiYTk4NzY1NDMyMTA=';
8
+ process.env.TZ = 'UTC';
@@ -0,0 +1,34 @@
1
+ import {Factory} from 'fishery';
2
+ import type {CronSchedule} from '#core/entities/cron-schedule.js';
3
+ import {db} from '#db/db.js';
4
+ import {toCronSchedule, triggersCronSchedules} from '#db/schema/cron-schedules.js';
5
+
6
+ export const cronScheduleFactory = Factory.define<CronSchedule>(({onCreate}) => {
7
+ onCreate(async (schedule) => {
8
+ const [row] = await db()
9
+ .insert(triggersCronSchedules)
10
+ .values({
11
+ subscriptionId: schedule.subscriptionId,
12
+ workspaceId: schedule.workspaceId,
13
+ cronExpression: schedule.cronExpression,
14
+ timezone: schedule.timezone,
15
+ nextFireAt: schedule.nextFireAt,
16
+ lastFiredAt: schedule.lastFiredAt,
17
+ })
18
+ .returning();
19
+ if (!row) throw new Error('Insert returned no rows');
20
+ return toCronSchedule(row);
21
+ });
22
+
23
+ return {
24
+ subscriptionId: crypto.randomUUID(),
25
+ workspaceId: crypto.randomUUID(),
26
+ cronExpression: '0 2 * * *',
27
+ timezone: 'UTC',
28
+ // Default well into the future so a schedule is only "due" when a test says so.
29
+ nextFireAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
30
+ lastFiredAt: null,
31
+ createdAt: new Date(),
32
+ updatedAt: new Date(),
33
+ };
34
+ });
@@ -0,0 +1,49 @@
1
+ import {Factory} from 'fishery';
2
+ import type {TriggerDecision} from '#core/entities/decision.js';
3
+ import {db} from '#db/db.js';
4
+ import {toTriggerDecision, triggersDecisions} from '#db/schema/decisions.js';
5
+
6
+ export const decisionFactory = Factory.define<TriggerDecision>(({onCreate}) => {
7
+ onCreate(async (decision) => {
8
+ const [row] = await db()
9
+ .insert(triggersDecisions)
10
+ .values({
11
+ receivedEventId: decision.receivedEventId,
12
+ subscriptionKind: decision.subscriptionKind,
13
+ subscriptionId: decision.subscriptionId,
14
+ subscriptionName: decision.subscriptionName,
15
+ workflowDefinitionId: decision.workflowDefinitionId,
16
+ projectId: decision.projectId,
17
+ workflowRunId: decision.workflowRunId,
18
+ jobId: decision.jobId,
19
+ matcherKind: decision.matcherKind,
20
+ matcherOrdinal: decision.matcherOrdinal,
21
+ decision: decision.decision,
22
+ runId: decision.runId,
23
+ runName: decision.runName,
24
+ reason: decision.reason,
25
+ })
26
+ .returning();
27
+ if (!row) throw new Error('Insert returned no rows');
28
+ return toTriggerDecision(row);
29
+ });
30
+
31
+ return {
32
+ id: crypto.randomUUID(),
33
+ receivedEventId: crypto.randomUUID(),
34
+ subscriptionKind: 'trigger',
35
+ subscriptionId: crypto.randomUUID(),
36
+ subscriptionName: 'Deploy production',
37
+ workflowDefinitionId: crypto.randomUUID(),
38
+ projectId: crypto.randomUUID(),
39
+ workflowRunId: null,
40
+ jobId: null,
41
+ matcherKind: null,
42
+ matcherOrdinal: null,
43
+ decision: 'triggered',
44
+ runId: crypto.randomUUID(),
45
+ runName: 'deploy',
46
+ reason: null,
47
+ createdAt: new Date(),
48
+ };
49
+ });
@@ -0,0 +1,42 @@
1
+ import {Factory} from 'fishery';
2
+ import type {JobListenerSubscription} from '#core/entities/job-listener-subscription.js';
3
+ import {db} from '#db/db.js';
4
+ import {
5
+ jobListenerSubscriptions,
6
+ toJobListenerSubscription,
7
+ } from '#db/schema/job-listener-subscriptions.js';
8
+
9
+ export const jobListenerSubscriptionFactory = Factory.define<JobListenerSubscription>(
10
+ ({sequence, onCreate}) => {
11
+ onCreate(async (subscription) => {
12
+ const [row] = await db()
13
+ .insert(jobListenerSubscriptions)
14
+ .values({
15
+ workspaceId: subscription.workspaceId,
16
+ workflowRunId: subscription.workflowRunId,
17
+ jobId: subscription.jobId,
18
+ kind: subscription.kind,
19
+ matcherOrdinal: subscription.matcherOrdinal,
20
+ source: subscription.source,
21
+ event: subscription.event,
22
+ config: subscription.config,
23
+ })
24
+ .returning();
25
+ if (!row) throw new Error('Insert returned no rows');
26
+ return toJobListenerSubscription(row);
27
+ });
28
+
29
+ return {
30
+ id: crypto.randomUUID(),
31
+ workspaceId: crypto.randomUUID(),
32
+ workflowRunId: crypto.randomUUID(),
33
+ jobId: crypto.randomUUID(),
34
+ kind: 'on',
35
+ matcherOrdinal: sequence,
36
+ source: 'github',
37
+ event: 'push',
38
+ config: {},
39
+ createdAt: new Date(),
40
+ };
41
+ },
42
+ );
@@ -0,0 +1,49 @@
1
+ import {Factory} from 'fishery';
2
+ import type {TriggerReceivedEvent} from '#core/entities/received-event.js';
3
+ import {db} from '#db/db.js';
4
+ import {toTriggerReceivedEvent, triggersReceivedEvents} from '#db/schema/received-events.js';
5
+
6
+ export const receivedEventFactory = Factory.define<TriggerReceivedEvent>(({sequence, onCreate}) => {
7
+ onCreate(async (event) => {
8
+ const [row] = await db()
9
+ .insert(triggersReceivedEvents)
10
+ .values({
11
+ eventRef: event.eventRef,
12
+ origin: event.origin,
13
+ workspaceId: event.workspaceId,
14
+ provider: event.provider,
15
+ source: event.source,
16
+ event: event.event,
17
+ deliveryId: event.deliveryId,
18
+ connectionId: event.connectionId,
19
+ connectionName: event.connectionName,
20
+ outcome: event.outcome,
21
+ matchedCount: event.matchedCount,
22
+ payload: event.payload,
23
+ receivedAt: event.receivedAt,
24
+ processedAt: event.processedAt,
25
+ })
26
+ .returning();
27
+ if (!row) throw new Error('Insert returned no rows');
28
+ return toTriggerReceivedEvent(row);
29
+ });
30
+
31
+ return {
32
+ id: crypto.randomUUID(),
33
+ eventRef: crypto.randomUUID(),
34
+ origin: 'integration',
35
+ workspaceId: crypto.randomUUID(),
36
+ provider: 'github',
37
+ source: 'github',
38
+ event: 'push',
39
+ deliveryId: crypto.randomUUID(),
40
+ connectionId: crypto.randomUUID(),
41
+ connectionName: 'Acme Production',
42
+ outcome: 'routed',
43
+ matchedCount: 1,
44
+ payload: {ref: `refs/heads/main-${sequence}`},
45
+ receivedAt: new Date(),
46
+ processedAt: new Date(),
47
+ createdAt: new Date(),
48
+ };
49
+ });
@@ -0,0 +1,38 @@
1
+ import {Factory} from 'fishery';
2
+ import type {TriggerSubscription} from '#core/entities/subscription.js';
3
+ import {db} from '#db/db.js';
4
+ import {toTriggerSubscription, triggerSubscriptions} from '#db/schema/subscriptions.js';
5
+
6
+ export const triggerSubscriptionFactory = Factory.define<TriggerSubscription>(
7
+ ({sequence, onCreate}) => {
8
+ onCreate(async (subscription) => {
9
+ const [row] = await db()
10
+ .insert(triggerSubscriptions)
11
+ .values({
12
+ workspaceId: subscription.workspaceId,
13
+ projectId: subscription.projectId,
14
+ workflowDefinitionId: subscription.workflowDefinitionId,
15
+ name: subscription.name,
16
+ source: subscription.source,
17
+ event: subscription.event,
18
+ config: subscription.config,
19
+ })
20
+ .returning();
21
+ if (!row) throw new Error('Insert returned no rows');
22
+ return toTriggerSubscription(row);
23
+ });
24
+
25
+ return {
26
+ id: crypto.randomUUID(),
27
+ workspaceId: crypto.randomUUID(),
28
+ projectId: crypto.randomUUID(),
29
+ workflowDefinitionId: crypto.randomUUID(),
30
+ name: `trigger_${sequence}`,
31
+ source: 'manual',
32
+ event: 'fire',
33
+ config: {},
34
+ createdAt: new Date(),
35
+ updatedAt: new Date(),
36
+ };
37
+ },
38
+ );
@@ -0,0 +1,20 @@
1
+ import './env.js';
2
+ import {runMigrations} from '@shipfox/node-drizzle';
3
+ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
4
+ import {sql} from 'drizzle-orm';
5
+ import {closeDb, db, migrationsPath} from '#db/index.js';
6
+
7
+ export async function setup() {
8
+ createPostgresClient();
9
+
10
+ await runMigrations(db(), migrationsPath, '__drizzle_migrations_triggers');
11
+ await db().execute(sql`TRUNCATE triggers_job_listener_subscriptions CASCADE`);
12
+ await db().execute(sql`TRUNCATE triggers_cron_schedules CASCADE`);
13
+ await db().execute(sql`TRUNCATE triggers_subscriptions CASCADE`);
14
+ await db().execute(sql`TRUNCATE triggers_outbox CASCADE`);
15
+ await db().execute(sql`TRUNCATE triggers_received_events CASCADE`);
16
+ await db().execute(sql`TRUNCATE triggers_decisions CASCADE`);
17
+
18
+ closeDb();
19
+ await closePostgresClient();
20
+ }
package/test/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export {cronScheduleFactory} from './factories/cron-schedule.js';
2
+ export {decisionFactory} from './factories/decision.js';
3
+ export {jobListenerSubscriptionFactory} from './factories/job-listener-subscription.js';
4
+ export {receivedEventFactory} from './factories/received-event.js';
5
+ export {triggerSubscriptionFactory} from './factories/subscription.js';
package/test/setup.ts ADDED
@@ -0,0 +1,17 @@
1
+ import './env.js';
2
+ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
3
+ import {afterAll, afterEach, beforeAll, vi} from '@shipfox/vitest/vi';
4
+ import {closeDb} from '#db/index.js';
5
+
6
+ beforeAll(() => {
7
+ createPostgresClient();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ afterAll(async () => {
15
+ closeDb();
16
+ await closePostgresClient();
17
+ });