@qlever-llc/trellis 0.10.22 → 0.11.0-rc.1

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 (406) hide show
  1. package/esm/auth/mod.d.ts +1 -1
  2. package/esm/auth/mod.d.ts.map +1 -1
  3. package/esm/auth/mod.js +1 -1
  4. package/esm/auth/protocol.d.ts +55 -0
  5. package/esm/auth/protocol.d.ts.map +1 -1
  6. package/esm/browser.d.ts +3 -3
  7. package/esm/browser.d.ts.map +1 -1
  8. package/esm/browser.js +1 -1
  9. package/esm/client_connect.d.ts +12 -3
  10. package/esm/client_connect.d.ts.map +1 -1
  11. package/esm/client_connect.js +112 -7
  12. package/esm/codec.d.ts +7 -6
  13. package/esm/codec.d.ts.map +1 -1
  14. package/esm/codec.js +125 -6
  15. package/esm/contract_support/canonical.d.ts +3 -0
  16. package/esm/contract_support/canonical.d.ts.map +1 -1
  17. package/esm/contract_support/canonical.js +3 -0
  18. package/esm/contract_support/mod.d.ts +125 -10
  19. package/esm/contract_support/mod.d.ts.map +1 -1
  20. package/esm/contract_support/mod.js +159 -5
  21. package/esm/contract_support/protocol.d.ts +92 -7
  22. package/esm/contract_support/protocol.d.ts.map +1 -1
  23. package/esm/contract_support/protocol.js +41 -7
  24. package/esm/contract_support/runtime.d.ts +4 -1
  25. package/esm/contract_support/runtime.d.ts.map +1 -1
  26. package/esm/errors/SchemaValidationError.d.ts +49 -0
  27. package/esm/errors/SchemaValidationError.d.ts.map +1 -0
  28. package/esm/errors/SchemaValidationError.js +67 -0
  29. package/esm/errors/index.d.ts +38 -9
  30. package/esm/errors/index.d.ts.map +1 -1
  31. package/esm/errors/index.js +16 -0
  32. package/esm/generated-sdk/auth/api.d.ts +33 -605
  33. package/esm/generated-sdk/auth/api.d.ts.map +1 -1
  34. package/esm/generated-sdk/auth/api.js +6 -1
  35. package/esm/generated-sdk/auth/client.d.ts +129 -134
  36. package/esm/generated-sdk/auth/client.d.ts.map +1 -1
  37. package/esm/generated-sdk/auth/contract.d.ts +2 -601
  38. package/esm/generated-sdk/auth/contract.d.ts.map +1 -1
  39. package/esm/generated-sdk/auth/contract.js +310 -93
  40. package/esm/generated-sdk/auth/mod.d.ts +1 -1
  41. package/esm/generated-sdk/auth/mod.d.ts.map +1 -1
  42. package/esm/generated-sdk/auth/owned_api.d.ts +88 -86
  43. package/esm/generated-sdk/auth/owned_api.d.ts.map +1 -1
  44. package/esm/generated-sdk/auth/owned_api.js +6 -0
  45. package/esm/generated-sdk/auth/schemas.d.ts +284 -136
  46. package/esm/generated-sdk/auth/schemas.d.ts.map +1 -1
  47. package/esm/generated-sdk/auth/schemas.js +231 -92
  48. package/esm/generated-sdk/auth/types.d.ts +565 -89
  49. package/esm/generated-sdk/auth/types.d.ts.map +1 -1
  50. package/esm/generated-sdk/auth/types.js +1 -1
  51. package/esm/generated-sdk/health/api.d.ts +29 -18
  52. package/esm/generated-sdk/health/api.d.ts.map +1 -1
  53. package/esm/generated-sdk/health/client.d.ts +7 -21
  54. package/esm/generated-sdk/health/client.d.ts.map +1 -1
  55. package/esm/generated-sdk/health/contract.d.ts +2 -15
  56. package/esm/generated-sdk/health/contract.d.ts.map +1 -1
  57. package/esm/generated-sdk/health/contract.js +2 -10
  58. package/esm/generated-sdk/health/mod.d.ts +1 -1
  59. package/esm/generated-sdk/health/mod.d.ts.map +1 -1
  60. package/esm/generated-sdk/health/owned_api.d.ts +2 -2
  61. package/esm/generated-sdk/health/schemas.d.ts +1 -14
  62. package/esm/generated-sdk/health/schemas.d.ts.map +1 -1
  63. package/esm/generated-sdk/health/schemas.js +1 -9
  64. package/esm/generated-sdk/health/types.d.ts +10 -5
  65. package/esm/generated-sdk/health/types.d.ts.map +1 -1
  66. package/esm/generated-sdk/health/types.js +1 -2
  67. package/esm/generated-sdk/jobs/api.d.ts +29 -109
  68. package/esm/generated-sdk/jobs/api.d.ts.map +1 -1
  69. package/esm/generated-sdk/jobs/client.d.ts +21 -33
  70. package/esm/generated-sdk/jobs/client.d.ts.map +1 -1
  71. package/esm/generated-sdk/jobs/contract.d.ts +2 -106
  72. package/esm/generated-sdk/jobs/contract.d.ts.map +1 -1
  73. package/esm/generated-sdk/jobs/contract.js +268 -1
  74. package/esm/generated-sdk/jobs/mod.d.ts +1 -1
  75. package/esm/generated-sdk/jobs/mod.d.ts.map +1 -1
  76. package/esm/generated-sdk/jobs/owned_api.d.ts +22 -9
  77. package/esm/generated-sdk/jobs/owned_api.d.ts.map +1 -1
  78. package/esm/generated-sdk/jobs/owned_api.js +20 -1
  79. package/esm/generated-sdk/jobs/schemas.d.ts +437 -0
  80. package/esm/generated-sdk/jobs/schemas.d.ts.map +1 -1
  81. package/esm/generated-sdk/jobs/schemas.js +211 -0
  82. package/esm/generated-sdk/jobs/types.d.ts +195 -12
  83. package/esm/generated-sdk/jobs/types.d.ts.map +1 -1
  84. package/esm/generated-sdk/jobs/types.js +1 -1
  85. package/esm/generated-sdk/state/api.d.ts +29 -68
  86. package/esm/generated-sdk/state/api.d.ts.map +1 -1
  87. package/esm/generated-sdk/state/client.d.ts +17 -31
  88. package/esm/generated-sdk/state/client.d.ts.map +1 -1
  89. package/esm/generated-sdk/state/contract.d.ts +1 -64
  90. package/esm/generated-sdk/state/contract.d.ts.map +1 -1
  91. package/esm/generated-sdk/state/mod.d.ts +1 -1
  92. package/esm/generated-sdk/state/mod.d.ts.map +1 -1
  93. package/esm/generated-sdk/state/owned_api.d.ts +7 -7
  94. package/esm/generated-sdk/state/types.d.ts +52 -9
  95. package/esm/generated-sdk/state/types.d.ts.map +1 -1
  96. package/esm/generated-sdk/trellis-core/api.d.ts +33 -45
  97. package/esm/generated-sdk/trellis-core/api.d.ts.map +1 -1
  98. package/esm/generated-sdk/trellis-core/api.js +6 -1
  99. package/esm/generated-sdk/trellis-core/client.d.ts +31 -20
  100. package/esm/generated-sdk/trellis-core/client.d.ts.map +1 -1
  101. package/esm/generated-sdk/trellis-core/contract.d.ts +2 -41
  102. package/esm/generated-sdk/trellis-core/contract.d.ts.map +1 -1
  103. package/esm/generated-sdk/trellis-core/contract.js +91 -1
  104. package/esm/generated-sdk/trellis-core/mod.d.ts +1 -1
  105. package/esm/generated-sdk/trellis-core/mod.d.ts.map +1 -1
  106. package/esm/generated-sdk/trellis-core/owned_api.d.ts +4 -4
  107. package/esm/generated-sdk/trellis-core/schemas.d.ts +113 -0
  108. package/esm/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
  109. package/esm/generated-sdk/trellis-core/schemas.js +81 -0
  110. package/esm/generated-sdk/trellis-core/types.d.ts +54 -7
  111. package/esm/generated-sdk/trellis-core/types.d.ts.map +1 -1
  112. package/esm/generated-sdk/trellis-core/types.js +1 -1
  113. package/esm/health.d.ts +1 -1
  114. package/esm/health.d.ts.map +1 -1
  115. package/esm/health.js +1 -1
  116. package/esm/index.d.ts +4 -4
  117. package/esm/index.d.ts.map +1 -1
  118. package/esm/index.js +2 -2
  119. package/esm/jobs.d.ts +69 -3
  120. package/esm/jobs.d.ts.map +1 -1
  121. package/esm/jobs.js +92 -3
  122. package/esm/server/health.d.ts +2 -6
  123. package/esm/server/health.d.ts.map +1 -1
  124. package/esm/server/health_schemas.d.ts +0 -8
  125. package/esm/server/health_schemas.d.ts.map +1 -1
  126. package/esm/server/health_schemas.js +0 -5
  127. package/esm/server/internal_jobs/bindings.d.ts +11 -0
  128. package/esm/server/internal_jobs/bindings.d.ts.map +1 -1
  129. package/esm/server/internal_jobs/job-manager.d.ts +58 -2
  130. package/esm/server/internal_jobs/job-manager.d.ts.map +1 -1
  131. package/esm/server/internal_jobs/job-manager.js +429 -55
  132. package/esm/server/internal_jobs/key-coordinator.d.ts +269 -0
  133. package/esm/server/internal_jobs/key-coordinator.d.ts.map +1 -0
  134. package/esm/server/internal_jobs/key-coordinator.js +677 -0
  135. package/esm/server/internal_jobs/projection.d.ts.map +1 -1
  136. package/esm/server/internal_jobs/projection.js +2 -0
  137. package/esm/server/internal_jobs/runtime-worker.d.ts +5 -1
  138. package/esm/server/internal_jobs/runtime-worker.d.ts.map +1 -1
  139. package/esm/server/internal_jobs/runtime-worker.js +14 -2
  140. package/esm/server/internal_jobs/types.d.ts +5 -5
  141. package/esm/server/internal_jobs/types.d.ts.map +1 -1
  142. package/esm/server/internal_jobs/types.js +6 -0
  143. package/esm/server/runtime.d.ts.map +1 -1
  144. package/esm/server/service.d.ts +81 -112
  145. package/esm/server/service.d.ts.map +1 -1
  146. package/esm/server/service.js +372 -130
  147. package/esm/server.d.ts +3 -3
  148. package/esm/server.d.ts.map +1 -1
  149. package/esm/service/deno.d.ts +1 -1
  150. package/esm/service/deno.d.ts.map +1 -1
  151. package/esm/service/drizzle.d.ts +52 -0
  152. package/esm/service/drizzle.d.ts.map +1 -0
  153. package/esm/service/drizzle.js +108 -0
  154. package/esm/service/mod.d.ts +3 -3
  155. package/esm/service/mod.d.ts.map +1 -1
  156. package/esm/service/mod.js +2 -2
  157. package/esm/service/node.d.ts +1 -1
  158. package/esm/service/node.d.ts.map +1 -1
  159. package/esm/service/outbox_inbox.d.ts +76 -22
  160. package/esm/service/outbox_inbox.d.ts.map +1 -1
  161. package/esm/service/outbox_inbox.js +231 -70
  162. package/esm/telemetry/metrics.d.ts +55 -0
  163. package/esm/telemetry/metrics.d.ts.map +1 -1
  164. package/esm/telemetry/metrics.js +86 -0
  165. package/esm/telemetry/mod.d.ts +2 -1
  166. package/esm/telemetry/mod.d.ts.map +1 -1
  167. package/esm/telemetry/mod.js +1 -1
  168. package/esm/trellis.d.ts +62 -30
  169. package/esm/trellis.d.ts.map +1 -1
  170. package/esm/trellis.js +125 -42
  171. package/package.json +14 -2
  172. package/script/auth/mod.d.ts +1 -1
  173. package/script/auth/mod.d.ts.map +1 -1
  174. package/script/auth/mod.js +11 -4
  175. package/script/auth/protocol.d.ts +55 -0
  176. package/script/auth/protocol.d.ts.map +1 -1
  177. package/script/browser.d.ts +3 -3
  178. package/script/browser.d.ts.map +1 -1
  179. package/script/browser.js +3 -2
  180. package/script/client_connect.d.ts +12 -3
  181. package/script/client_connect.d.ts.map +1 -1
  182. package/script/client_connect.js +111 -6
  183. package/script/codec.d.ts +7 -6
  184. package/script/codec.d.ts.map +1 -1
  185. package/script/codec.js +124 -5
  186. package/script/contract_support/canonical.d.ts +3 -0
  187. package/script/contract_support/canonical.d.ts.map +1 -1
  188. package/script/contract_support/canonical.js +3 -0
  189. package/script/contract_support/mod.d.ts +125 -10
  190. package/script/contract_support/mod.d.ts.map +1 -1
  191. package/script/contract_support/mod.js +165 -5
  192. package/script/contract_support/protocol.d.ts +92 -7
  193. package/script/contract_support/protocol.d.ts.map +1 -1
  194. package/script/contract_support/protocol.js +42 -8
  195. package/script/contract_support/runtime.d.ts +4 -1
  196. package/script/contract_support/runtime.d.ts.map +1 -1
  197. package/script/errors/SchemaValidationError.d.ts +49 -0
  198. package/script/errors/SchemaValidationError.d.ts.map +1 -0
  199. package/script/errors/SchemaValidationError.js +74 -0
  200. package/script/errors/index.d.ts +38 -9
  201. package/script/errors/index.d.ts.map +1 -1
  202. package/script/errors/index.js +20 -1
  203. package/script/generated-sdk/auth/api.d.ts +33 -605
  204. package/script/generated-sdk/auth/api.d.ts.map +1 -1
  205. package/script/generated-sdk/auth/api.js +6 -1
  206. package/script/generated-sdk/auth/client.d.ts +129 -134
  207. package/script/generated-sdk/auth/client.d.ts.map +1 -1
  208. package/script/generated-sdk/auth/contract.d.ts +2 -601
  209. package/script/generated-sdk/auth/contract.d.ts.map +1 -1
  210. package/script/generated-sdk/auth/contract.js +310 -93
  211. package/script/generated-sdk/auth/mod.d.ts +1 -1
  212. package/script/generated-sdk/auth/mod.d.ts.map +1 -1
  213. package/script/generated-sdk/auth/owned_api.d.ts +88 -86
  214. package/script/generated-sdk/auth/owned_api.d.ts.map +1 -1
  215. package/script/generated-sdk/auth/owned_api.js +6 -0
  216. package/script/generated-sdk/auth/schemas.d.ts +284 -136
  217. package/script/generated-sdk/auth/schemas.d.ts.map +1 -1
  218. package/script/generated-sdk/auth/schemas.js +231 -92
  219. package/script/generated-sdk/auth/types.d.ts +565 -89
  220. package/script/generated-sdk/auth/types.d.ts.map +1 -1
  221. package/script/generated-sdk/auth/types.js +1 -1
  222. package/script/generated-sdk/health/api.d.ts +29 -18
  223. package/script/generated-sdk/health/api.d.ts.map +1 -1
  224. package/script/generated-sdk/health/client.d.ts +7 -21
  225. package/script/generated-sdk/health/client.d.ts.map +1 -1
  226. package/script/generated-sdk/health/contract.d.ts +2 -15
  227. package/script/generated-sdk/health/contract.d.ts.map +1 -1
  228. package/script/generated-sdk/health/contract.js +2 -10
  229. package/script/generated-sdk/health/mod.d.ts +1 -1
  230. package/script/generated-sdk/health/mod.d.ts.map +1 -1
  231. package/script/generated-sdk/health/owned_api.d.ts +2 -2
  232. package/script/generated-sdk/health/schemas.d.ts +1 -14
  233. package/script/generated-sdk/health/schemas.d.ts.map +1 -1
  234. package/script/generated-sdk/health/schemas.js +1 -9
  235. package/script/generated-sdk/health/types.d.ts +10 -5
  236. package/script/generated-sdk/health/types.d.ts.map +1 -1
  237. package/script/generated-sdk/health/types.js +1 -2
  238. package/script/generated-sdk/jobs/api.d.ts +29 -109
  239. package/script/generated-sdk/jobs/api.d.ts.map +1 -1
  240. package/script/generated-sdk/jobs/client.d.ts +21 -33
  241. package/script/generated-sdk/jobs/client.d.ts.map +1 -1
  242. package/script/generated-sdk/jobs/contract.d.ts +2 -106
  243. package/script/generated-sdk/jobs/contract.d.ts.map +1 -1
  244. package/script/generated-sdk/jobs/contract.js +268 -1
  245. package/script/generated-sdk/jobs/mod.d.ts +1 -1
  246. package/script/generated-sdk/jobs/mod.d.ts.map +1 -1
  247. package/script/generated-sdk/jobs/owned_api.d.ts +22 -9
  248. package/script/generated-sdk/jobs/owned_api.d.ts.map +1 -1
  249. package/script/generated-sdk/jobs/owned_api.js +19 -0
  250. package/script/generated-sdk/jobs/schemas.d.ts +437 -0
  251. package/script/generated-sdk/jobs/schemas.d.ts.map +1 -1
  252. package/script/generated-sdk/jobs/schemas.js +212 -1
  253. package/script/generated-sdk/jobs/types.d.ts +195 -12
  254. package/script/generated-sdk/jobs/types.d.ts.map +1 -1
  255. package/script/generated-sdk/jobs/types.js +1 -1
  256. package/script/generated-sdk/state/api.d.ts +29 -68
  257. package/script/generated-sdk/state/api.d.ts.map +1 -1
  258. package/script/generated-sdk/state/client.d.ts +17 -31
  259. package/script/generated-sdk/state/client.d.ts.map +1 -1
  260. package/script/generated-sdk/state/contract.d.ts +1 -64
  261. package/script/generated-sdk/state/contract.d.ts.map +1 -1
  262. package/script/generated-sdk/state/mod.d.ts +1 -1
  263. package/script/generated-sdk/state/mod.d.ts.map +1 -1
  264. package/script/generated-sdk/state/owned_api.d.ts +7 -7
  265. package/script/generated-sdk/state/types.d.ts +52 -9
  266. package/script/generated-sdk/state/types.d.ts.map +1 -1
  267. package/script/generated-sdk/trellis-core/api.d.ts +33 -45
  268. package/script/generated-sdk/trellis-core/api.d.ts.map +1 -1
  269. package/script/generated-sdk/trellis-core/api.js +6 -1
  270. package/script/generated-sdk/trellis-core/client.d.ts +31 -20
  271. package/script/generated-sdk/trellis-core/client.d.ts.map +1 -1
  272. package/script/generated-sdk/trellis-core/contract.d.ts +2 -41
  273. package/script/generated-sdk/trellis-core/contract.d.ts.map +1 -1
  274. package/script/generated-sdk/trellis-core/contract.js +91 -1
  275. package/script/generated-sdk/trellis-core/mod.d.ts +1 -1
  276. package/script/generated-sdk/trellis-core/mod.d.ts.map +1 -1
  277. package/script/generated-sdk/trellis-core/owned_api.d.ts +4 -4
  278. package/script/generated-sdk/trellis-core/schemas.d.ts +113 -0
  279. package/script/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
  280. package/script/generated-sdk/trellis-core/schemas.js +81 -0
  281. package/script/generated-sdk/trellis-core/types.d.ts +54 -7
  282. package/script/generated-sdk/trellis-core/types.d.ts.map +1 -1
  283. package/script/generated-sdk/trellis-core/types.js +1 -1
  284. package/script/health.d.ts +1 -1
  285. package/script/health.d.ts.map +1 -1
  286. package/script/health.js +1 -2
  287. package/script/index.d.ts +4 -4
  288. package/script/index.d.ts.map +1 -1
  289. package/script/index.js +2 -2
  290. package/script/jobs.d.ts +69 -3
  291. package/script/jobs.d.ts.map +1 -1
  292. package/script/jobs.js +93 -3
  293. package/script/server/health.d.ts +2 -6
  294. package/script/server/health.d.ts.map +1 -1
  295. package/script/server/health_schemas.d.ts +0 -8
  296. package/script/server/health_schemas.d.ts.map +1 -1
  297. package/script/server/health_schemas.js +1 -6
  298. package/script/server/internal_jobs/bindings.d.ts +11 -0
  299. package/script/server/internal_jobs/bindings.d.ts.map +1 -1
  300. package/script/server/internal_jobs/job-manager.d.ts +58 -2
  301. package/script/server/internal_jobs/job-manager.d.ts.map +1 -1
  302. package/script/server/internal_jobs/job-manager.js +429 -54
  303. package/script/server/internal_jobs/key-coordinator.d.ts +269 -0
  304. package/script/server/internal_jobs/key-coordinator.d.ts.map +1 -0
  305. package/script/server/internal_jobs/key-coordinator.js +690 -0
  306. package/script/server/internal_jobs/projection.d.ts.map +1 -1
  307. package/script/server/internal_jobs/projection.js +2 -0
  308. package/script/server/internal_jobs/runtime-worker.d.ts +5 -1
  309. package/script/server/internal_jobs/runtime-worker.d.ts.map +1 -1
  310. package/script/server/internal_jobs/runtime-worker.js +14 -2
  311. package/script/server/internal_jobs/types.d.ts +5 -5
  312. package/script/server/internal_jobs/types.d.ts.map +1 -1
  313. package/script/server/internal_jobs/types.js +6 -0
  314. package/script/server/runtime.d.ts.map +1 -1
  315. package/script/server/service.d.ts +81 -112
  316. package/script/server/service.d.ts.map +1 -1
  317. package/script/server/service.js +369 -127
  318. package/script/server.d.ts +3 -3
  319. package/script/server.d.ts.map +1 -1
  320. package/script/service/deno.d.ts +1 -1
  321. package/script/service/deno.d.ts.map +1 -1
  322. package/script/service/drizzle.d.ts +52 -0
  323. package/script/service/drizzle.d.ts.map +1 -0
  324. package/script/service/drizzle.js +113 -0
  325. package/script/service/mod.d.ts +3 -3
  326. package/script/service/mod.d.ts.map +1 -1
  327. package/script/service/mod.js +4 -3
  328. package/script/service/node.d.ts +1 -1
  329. package/script/service/node.d.ts.map +1 -1
  330. package/script/service/outbox_inbox.d.ts +76 -22
  331. package/script/service/outbox_inbox.d.ts.map +1 -1
  332. package/script/service/outbox_inbox.js +233 -70
  333. package/script/telemetry/metrics.d.ts +55 -0
  334. package/script/telemetry/metrics.d.ts.map +1 -1
  335. package/script/telemetry/metrics.js +88 -0
  336. package/script/telemetry/mod.d.ts +2 -1
  337. package/script/telemetry/mod.d.ts.map +1 -1
  338. package/script/telemetry/mod.js +3 -1
  339. package/script/trellis.d.ts +62 -30
  340. package/script/trellis.d.ts.map +1 -1
  341. package/script/trellis.js +124 -41
  342. package/src/auth/mod.ts +7 -0
  343. package/src/browser.ts +5 -7
  344. package/src/client_connect.ts +200 -12
  345. package/src/codec.ts +186 -11
  346. package/src/contract_support/canonical.ts +3 -0
  347. package/src/contract_support/mod.ts +380 -9
  348. package/src/contract_support/protocol.ts +57 -10
  349. package/src/contract_support/runtime.ts +7 -0
  350. package/src/errors/SchemaValidationError.ts +67 -0
  351. package/src/errors/index.ts +28 -0
  352. package/src/health.ts +0 -1
  353. package/src/index.ts +7 -8
  354. package/src/jobs.ts +138 -1
  355. package/src/sdk/_generated/auth/api.ts +38 -7
  356. package/src/sdk/_generated/auth/client.ts +168 -530
  357. package/src/sdk/_generated/auth/contract.ts +310 -93
  358. package/src/sdk/_generated/auth/mod.ts +0 -1
  359. package/src/sdk/_generated/auth/owned_api.ts +92 -86
  360. package/src/sdk/_generated/auth/schemas.ts +231 -92
  361. package/src/sdk/_generated/auth/types.ts +1069 -273
  362. package/src/sdk/_generated/core/api.ts +38 -7
  363. package/src/sdk/_generated/core/client.ts +47 -73
  364. package/src/sdk/_generated/core/contract.ts +91 -1
  365. package/src/sdk/_generated/core/mod.ts +0 -1
  366. package/src/sdk/_generated/core/owned_api.ts +4 -4
  367. package/src/sdk/_generated/core/schemas.ts +81 -0
  368. package/src/sdk/_generated/core/types.ts +79 -15
  369. package/src/sdk/_generated/health/api.ts +29 -5
  370. package/src/sdk/_generated/health/client.ts +11 -58
  371. package/src/sdk/_generated/health/contract.ts +2 -10
  372. package/src/sdk/_generated/health/mod.ts +0 -1
  373. package/src/sdk/_generated/health/owned_api.ts +2 -2
  374. package/src/sdk/_generated/health/schemas.ts +1 -9
  375. package/src/sdk/_generated/health/types.ts +23 -2
  376. package/src/sdk/_generated/jobs/api.ts +29 -5
  377. package/src/sdk/_generated/jobs/client.ts +25 -109
  378. package/src/sdk/_generated/jobs/contract.ts +269 -1
  379. package/src/sdk/_generated/jobs/mod.ts +0 -1
  380. package/src/sdk/_generated/jobs/owned_api.ts +30 -9
  381. package/src/sdk/_generated/jobs/schemas.ts +213 -0
  382. package/src/sdk/_generated/jobs/types.ts +252 -18
  383. package/src/sdk/_generated/state/api.ts +29 -5
  384. package/src/sdk/_generated/state/client.ts +18 -95
  385. package/src/sdk/_generated/state/mod.ts +0 -1
  386. package/src/sdk/_generated/state/owned_api.ts +7 -7
  387. package/src/sdk/_generated/state/types.ts +91 -15
  388. package/src/server/health.ts +2 -6
  389. package/src/server/health_schemas.ts +0 -6
  390. package/src/server/internal_jobs/bindings.ts +11 -0
  391. package/src/server/internal_jobs/job-manager.ts +553 -24
  392. package/src/server/internal_jobs/key-coordinator.ts +1080 -0
  393. package/src/server/internal_jobs/projection.ts +2 -0
  394. package/src/server/internal_jobs/runtime-worker.ts +17 -1
  395. package/src/server/internal_jobs/types.ts +6 -0
  396. package/src/server/runtime.ts +4 -0
  397. package/src/server/service.ts +645 -625
  398. package/src/server.ts +14 -7
  399. package/src/service/deno.ts +0 -5
  400. package/src/service/drizzle.ts +184 -0
  401. package/src/service/mod.ts +17 -7
  402. package/src/service/node.ts +0 -5
  403. package/src/service/outbox_inbox.ts +354 -88
  404. package/src/telemetry/metrics.ts +198 -1
  405. package/src/telemetry/mod.ts +6 -0
  406. package/src/trellis.ts +286 -105
@@ -51,8 +51,8 @@ import { Value } from "typebox/value";
51
51
  import {
52
52
  type HealthCheckFn,
53
53
  ServiceHealth,
54
- type ServiceHealthCheck,
55
- type ServiceHealthInfo,
54
+ type ServiceHealthCheckFn,
55
+ type ServiceHealthInfoFn,
56
56
  } from "./health.js";
57
57
  import { mountStandardHealthRpc } from "./health_rpc.js";
58
58
  import type { EventDesc, RPCDesc } from "../contracts.js";
@@ -67,6 +67,7 @@ import type {
67
67
  FeedRegistration as RootFeedRegistration,
68
68
  HandlerTrellis,
69
69
  OperationHandlerContext,
70
+ OperationHandlerErrorOf,
70
71
  OperationOutputOf,
71
72
  OperationProgressOf,
72
73
  OperationRegistration as RootOperationRegistration,
@@ -105,17 +106,33 @@ import {
105
106
  ActiveJob as PublicActiveJob,
106
107
  type JobIdentity,
107
108
  type JobLogEntry,
109
+ JobNotEnqueuedError,
108
110
  type JobProgress,
109
111
  JobRef,
110
112
  type JobSnapshot,
113
+ type JobSubmitOutcome,
111
114
  JobWorkerHostAdapter,
112
115
  type TerminalJob,
113
116
  } from "../jobs.js";
117
+ import { ulid } from "ulid";
114
118
  import {
119
+ createJobContext,
115
120
  JobManager as InternalJobManager,
116
121
  JobProcessError as InternalJobProcessError,
117
122
  } from "./internal_jobs/job-manager.js";
118
123
  import { startNatsWorkerHostFromBinding } from "./internal_jobs/runtime-worker.js";
124
+ import {
125
+ createNatsJobKeyCoordinator,
126
+ normalizeJobKeyPolicy,
127
+ } from "./internal_jobs/key-coordinator.js";
128
+ import type {
129
+ JobKeyConcurrencyBinding,
130
+ JobQueuePolicyBinding,
131
+ } from "./internal_jobs/key-coordinator.js";
132
+ import type {
133
+ JobsBinding,
134
+ JobsQueueBinding,
135
+ } from "./internal_jobs/bindings.js";
119
136
  import type { ActiveJob as InternalActiveJob } from "./internal_jobs/active-job.js";
120
137
  import {
121
138
  type Job as InternalJob,
@@ -128,6 +145,20 @@ import {
128
145
  type TrellisConnection,
129
146
  } from "../connection.js";
130
147
  import { initTelemetry } from "../telemetry/init.js";
148
+ import { recordTrellisDuration } from "../telemetry/mod.js";
149
+ import {
150
+ defaultSqlOutboxTables,
151
+ OutboxDispatcher,
152
+ type OutboxDispatcherOptions,
153
+ type OutboxDispatchRuntime,
154
+ type OutboxMessage,
155
+ type PreparedOutboxRecord,
156
+ preparedTrellisEventToOutboxRecord,
157
+ type SqlDialect,
158
+ type SqlExecutor,
159
+ SqlOutboxRepository,
160
+ type SqlOutboxTables,
161
+ } from "../service/outbox_inbox.js";
131
162
 
132
163
  type ExtraNatsConnectOpts = Omit<
133
164
  NatsConnectOpts,
@@ -149,6 +180,8 @@ type ResourceBindingJobsQueue = {
149
180
  logs: boolean;
150
181
  dlq: boolean;
151
182
  concurrency: number;
183
+ keyConcurrency?: JobKeyConcurrencyBinding;
184
+ queue?: JobQueuePolicyBinding;
152
185
  };
153
186
 
154
187
  type ResourceBindingJobs = {
@@ -157,6 +190,71 @@ type ResourceBindingJobs = {
157
190
  queues: Record<string, ResourceBindingJobsQueue>;
158
191
  };
159
192
 
193
+ function normalizeResourceJobsBinding(
194
+ binding: ResourceBindingJobs,
195
+ ): JobsBinding {
196
+ const queues: Record<string, JobsQueueBinding> = {};
197
+ for (const [name, queue] of Object.entries(binding.queues)) {
198
+ const baseQueue = baseJobsQueueBinding(queue);
199
+ if (!queue.keyConcurrency) {
200
+ queues[name] = {
201
+ ...baseQueue,
202
+ ...(queue.queue ? { queue: normalizeQueuePolicy(queue.queue) } : {}),
203
+ };
204
+ continue;
205
+ }
206
+
207
+ const policy = normalizeJobKeyPolicy({
208
+ keyConcurrency: queue.keyConcurrency,
209
+ queue: queue.queue,
210
+ });
211
+ queues[name] = {
212
+ ...baseQueue,
213
+ keyConcurrency: {
214
+ key: policy.key,
215
+ maxActive: policy.maxActive,
216
+ heartbeatIntervalMs: policy.heartbeatIntervalMs,
217
+ heartbeatTtlMs: policy.heartbeatTtlMs,
218
+ stalePolicy: policy.stalePolicy,
219
+ },
220
+ queue: policy.queue,
221
+ };
222
+ }
223
+ return { namespace: binding.namespace, queues };
224
+ }
225
+
226
+ function baseJobsQueueBinding(
227
+ queue: ResourceBindingJobsQueue,
228
+ ): Omit<JobsQueueBinding, "keyConcurrency" | "queue"> {
229
+ return {
230
+ queueType: queue.queueType,
231
+ publishPrefix: queue.publishPrefix,
232
+ workSubject: queue.workSubject,
233
+ consumerName: queue.consumerName,
234
+ payload: queue.payload,
235
+ ...(queue.result ? { result: queue.result } : {}),
236
+ maxDeliver: queue.maxDeliver,
237
+ backoffMs: queue.backoffMs,
238
+ ackWaitMs: queue.ackWaitMs,
239
+ ...(queue.defaultDeadlineMs !== undefined
240
+ ? { defaultDeadlineMs: queue.defaultDeadlineMs }
241
+ : {}),
242
+ progress: queue.progress,
243
+ logs: queue.logs,
244
+ dlq: queue.dlq,
245
+ concurrency: queue.concurrency,
246
+ };
247
+ }
248
+
249
+ function normalizeQueuePolicy(
250
+ queue: JobQueuePolicyBinding,
251
+ ): JobsQueueBinding["queue"] {
252
+ return {
253
+ maxQueuedPerKey: queue.maxQueuedPerKey ?? 0,
254
+ whenFull: queue.whenFull ?? "reject",
255
+ };
256
+ }
257
+
160
258
  type ResourceBindingEventConsumer = {
161
259
  stream: string;
162
260
  consumerName: string;
@@ -981,14 +1079,6 @@ type ContractKvOf<
981
1079
  >,
982
1080
  > = NonNullable<TContract[typeof CONTRACT_KV_METADATA]>;
983
1081
 
984
- type ContractJobName<
985
- TContract extends ServiceContract<
986
- TrellisAPI,
987
- TrellisAPI | undefined,
988
- ContractJobsMetadata
989
- >,
990
- > = keyof ContractJobsOf<TContract> & string;
991
-
992
1082
  type ContractEventName<
993
1083
  TContract extends ServiceContract<
994
1084
  TrellisAPI,
@@ -997,14 +1087,6 @@ type ContractEventName<
997
1087
  >,
998
1088
  > = ServiceEventName<ContractTrellisApi<TContract>>;
999
1089
 
1000
- type ContractFeedName<
1001
- TContract extends ServiceContract<
1002
- TrellisAPI,
1003
- TrellisAPI | undefined,
1004
- ContractJobsMetadata
1005
- >,
1006
- > = keyof ContractOwnedApi<TContract>["feeds"] & string;
1007
-
1008
1090
  type ContractOperationName<
1009
1091
  TContract extends ServiceContract<
1010
1092
  TrellisAPI,
@@ -1013,59 +1095,6 @@ type ContractOperationName<
1013
1095
  >,
1014
1096
  > = keyof ContractOwnedApi<TContract>["operations"] & string;
1015
1097
 
1016
- type ContractJobPayload<
1017
- TContract extends ServiceContract<
1018
- TrellisAPI,
1019
- TrellisAPI | undefined,
1020
- ContractJobsMetadata
1021
- >,
1022
- TJob extends ContractJobName<TContract>,
1023
- > = ContractJobsOf<TContract>[TJob]["payload"];
1024
-
1025
- type ContractJobResult<
1026
- TContract extends ServiceContract<
1027
- TrellisAPI,
1028
- TrellisAPI | undefined,
1029
- ContractJobsMetadata
1030
- >,
1031
- TJob extends ContractJobName<TContract>,
1032
- > = ContractJobsOf<TContract>[TJob]["result"];
1033
-
1034
- /** Arguments passed to a typed Trellis service job handler. */
1035
- export type JobArgs<
1036
- TContract extends ServiceContract<
1037
- TrellisAPI,
1038
- TrellisAPI | undefined,
1039
- ContractJobsMetadata,
1040
- ContractKvMetadata
1041
- >,
1042
- TJob extends ContractJobName<TContract>,
1043
- TDeps = undefined,
1044
- > = {
1045
- job: PublicActiveJob<
1046
- ContractJobPayload<TContract, TJob>,
1047
- ContractJobResult<TContract, TJob>
1048
- >;
1049
- client: Trellis<
1050
- ContractTrellisApi<TContract>,
1051
- ContractKvOf<TContract>,
1052
- ContractJobsOf<TContract>
1053
- >;
1054
- } & WithDeps<TDeps>;
1055
-
1056
- /** Result returned by a typed Trellis service job handler. */
1057
- export type JobResult<
1058
- TContract extends ServiceContract<
1059
- TrellisAPI,
1060
- TrellisAPI | undefined,
1061
- ContractJobsMetadata,
1062
- ContractKvMetadata
1063
- >,
1064
- TJob extends ContractJobName<TContract>,
1065
- > = Result<ContractJobResult<TContract, TJob>, BaseError>;
1066
-
1067
- type WithDeps<TDeps> = [TDeps] extends [undefined] ? {} : { deps: TDeps };
1068
-
1069
1098
  /** Typed RPC handler function for an extracted Trellis service handler. */
1070
1099
  export type RpcHandler<
1071
1100
  TContract extends ServiceContract<
@@ -1075,7 +1104,6 @@ export type RpcHandler<
1075
1104
  ContractKvMetadata
1076
1105
  >,
1077
1106
  M extends RpcMethodName<ContractOwnedApi<TContract>>,
1078
- TDeps = undefined,
1079
1107
  > = ({
1080
1108
  input,
1081
1109
  context,
@@ -1088,7 +1116,7 @@ export type RpcHandler<
1088
1116
  ContractKvOf<TContract>,
1089
1117
  ContractJobsOf<TContract>
1090
1118
  >;
1091
- } & WithDeps<TDeps>) =>
1119
+ }) =>
1092
1120
  | Promise<
1093
1121
  Result<
1094
1122
  RpcMethodOutput<ContractOwnedApi<TContract>, M>,
@@ -1109,7 +1137,6 @@ export type ServiceEventHandler<
1109
1137
  ContractKvMetadata
1110
1138
  >,
1111
1139
  E extends ContractEventName<TContract>,
1112
- TDeps = undefined,
1113
1140
  > = (
1114
1141
  args: {
1115
1142
  event: ServiceEventOf<ContractTrellisApi<TContract>, E>;
@@ -1119,49 +1146,9 @@ export type ServiceEventHandler<
1119
1146
  ContractKvOf<TContract>,
1120
1147
  ContractJobsOf<TContract>
1121
1148
  >;
1122
- } & WithDeps<TDeps>,
1149
+ },
1123
1150
  ) => MaybeAsync<void, BaseError>;
1124
1151
 
1125
- /** Typed feed handler function for an extracted Trellis service handler. */
1126
- export type FeedHandler<
1127
- TContract extends ServiceContract<
1128
- TrellisAPI,
1129
- TrellisAPI | undefined,
1130
- ContractJobsMetadata,
1131
- ContractKvMetadata
1132
- >,
1133
- F extends ContractFeedName<TContract>,
1134
- TDeps = undefined,
1135
- > = (
1136
- context: {
1137
- input: FeedInputOf<ContractOwnedApi<TContract>, F>;
1138
- caller: unknown;
1139
- signal: AbortSignal;
1140
- emit(
1141
- event: FeedEventOf<ContractOwnedApi<TContract>, F>,
1142
- ): AsyncResult<void, ValidationError | UnexpectedError>;
1143
- client: Trellis<
1144
- ContractTrellisApi<TContract>,
1145
- ContractKvOf<TContract>,
1146
- ContractJobsOf<TContract>
1147
- >;
1148
- } & WithDeps<TDeps>,
1149
- ) => unknown | Promise<unknown>;
1150
-
1151
- /** Typed job handler function for an extracted Trellis service job handler. */
1152
- export type JobHandler<
1153
- TContract extends ServiceContract<
1154
- TrellisAPI,
1155
- TrellisAPI | undefined,
1156
- ContractJobsMetadata,
1157
- ContractKvMetadata
1158
- >,
1159
- TJob extends ContractJobName<TContract>,
1160
- TDeps = undefined,
1161
- > = (args: JobArgs<TContract, TJob, TDeps>) => Promise<
1162
- JobResult<TContract, TJob>
1163
- >;
1164
-
1165
1152
  /** Typed operation handler function for an extracted Trellis service handler. */
1166
1153
  export type OperationHandler<
1167
1154
  TContract extends ServiceContract<
@@ -1171,14 +1158,14 @@ export type OperationHandler<
1171
1158
  ContractKvMetadata
1172
1159
  >,
1173
1160
  O extends ContractOperationName<TContract>,
1174
- TDeps = undefined,
1175
1161
  > = (
1176
1162
  args:
1177
1163
  & OperationHandlerContext<
1178
1164
  InferSchemaType<ContractOwnedApi<TContract>["operations"][O]["input"]>,
1179
1165
  OperationProgressOf<ContractOwnedApi<TContract>, O>,
1180
1166
  OperationOutputOf<ContractOwnedApi<TContract>, O>,
1181
- OperationTransferContextOf<ContractOwnedApi<TContract>, O>
1167
+ OperationTransferContextOf<ContractOwnedApi<TContract>, O>,
1168
+ OperationHandlerErrorOf<ContractOwnedApi<TContract>, O>
1182
1169
  >
1183
1170
  & {
1184
1171
  client: Trellis<
@@ -1186,19 +1173,14 @@ export type OperationHandler<
1186
1173
  ContractKvOf<TContract>,
1187
1174
  ContractJobsOf<TContract>
1188
1175
  >;
1189
- }
1190
- & WithDeps<TDeps>,
1176
+ },
1191
1177
  ) => unknown | Promise<unknown>;
1192
1178
 
1193
- /** Typed health info function for an extracted bound service health handler. */
1194
- export type HealthInfoHandler<TDeps = undefined> = (
1195
- args: WithDeps<TDeps>,
1196
- ) => ServiceHealthInfo | undefined | Promise<ServiceHealthInfo | undefined>;
1179
+ /** Typed health info function for an extracted service health handler. */
1180
+ export type HealthInfoHandler = ServiceHealthInfoFn;
1197
1181
 
1198
- /** Typed health check function for an extracted bound service health handler. */
1199
- export type HealthCheckHandler<TDeps = undefined> = (
1200
- args: WithDeps<TDeps>,
1201
- ) => ServiceHealthCheck | Promise<ServiceHealthCheck>;
1182
+ /** Typed health check function for an extracted service health handler. */
1183
+ export type HealthCheckHandler = ServiceHealthCheckFn;
1202
1184
 
1203
1185
  export type JobQueue<
1204
1186
  TPayload,
@@ -1208,6 +1190,9 @@ export type JobQueue<
1208
1190
  TJobs extends ContractJobsMetadata = {},
1209
1191
  > = {
1210
1192
  create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
1193
+ submit(
1194
+ payload: TPayload,
1195
+ ): AsyncResult<JobSubmitOutcome<TPayload, TResult>, BaseError>;
1211
1196
  handle(
1212
1197
  handler: (args: {
1213
1198
  job: PublicActiveJob<TPayload, TResult>;
@@ -1239,28 +1224,42 @@ type ServiceEventOf<
1239
1224
  type ServiceEventPayloadOf<
1240
1225
  TA extends TrellisAPI,
1241
1226
  E extends ServiceEventName<TA>,
1242
- > = Omit<ServiceEventOf<TA, E>, "header">;
1227
+ > = ServiceEventOf<TA, E> & Record<string, unknown>;
1228
+
1229
+ /** Runs SQL outbox work inside a caller-owned service database transaction. */
1230
+ export type SqlOutboxTransactionRunner<TTx> = <TResult>(
1231
+ work: (context: { tx: TTx; executor: SqlExecutor }) =>
1232
+ | Promise<TResult>
1233
+ | TResult,
1234
+ ) => Promise<TResult>;
1235
+
1236
+ /** Options shared by all Trellis SQL outbox service bindings. */
1237
+ export type TrellisServiceSqlOutboxCommonOptions = {
1238
+ /** SQL dialect used by the Trellis helper tables. */
1239
+ readonly dialect: SqlDialect;
1240
+ /** Optional Trellis helper-table names; omitted names use Trellis defaults. */
1241
+ readonly tables?: Partial<SqlOutboxTables>;
1242
+ /** Optional process-local dispatcher tuning. */
1243
+ readonly dispatcher?: OutboxDispatcherOptions;
1244
+ };
1243
1245
 
1244
- type BoundEventHandleFn<
1245
- TEventApi extends TrellisAPI,
1246
- TTrellisApi extends TrellisAPI,
1247
- E extends ServiceEventName<TEventApi>,
1248
- TKv extends ContractKvMetadata,
1249
- TJobs extends ContractJobsMetadata,
1250
- TDeps,
1251
- > = (args: {
1252
- event: ServiceEventOf<TEventApi, E>;
1253
- context: EventListenerContext;
1254
- client: Trellis<TTrellisApi, TKv, TJobs>;
1255
- deps: TDeps;
1256
- }) => MaybeAsync<void, BaseError>;
1246
+ /** Options for binding a Trellis service to generic caller-owned SQL storage. */
1247
+ export type TrellisServiceSqlOutboxExecutorOptions<TTx> =
1248
+ & TrellisServiceSqlOutboxCommonOptions
1249
+ & {
1250
+ /** Non-transactional executor used by the process-local dispatcher. */
1251
+ readonly executor: SqlExecutor;
1252
+ /** Service-owned transaction runner for handler-scoped work. */
1253
+ readonly transaction: SqlOutboxTransactionRunner<TTx>;
1254
+ };
1257
1255
 
1258
- type BoundActiveEventFacade<
1259
- TEventApi extends TrellisAPI,
1260
- TTrellisApi extends TrellisAPI,
1261
- TKv extends ContractKvMetadata,
1262
- TJobs extends ContractJobsMetadata,
1263
- TDeps,
1256
+ /** Options for binding a Trellis service to caller-owned SQL outbox storage. */
1257
+ export type TrellisServiceSqlOutboxOptions<TTx> =
1258
+ TrellisServiceSqlOutboxExecutorOptions<TTx>;
1259
+
1260
+ /** Typed transaction-scoped event facade that enqueues prepared events. */
1261
+ export type SqlOutboxEventEnqueueFacade<
1262
+ TEventApi extends TrellisAPI = TrellisAPI,
1264
1263
  > = {
1265
1264
  readonly [TGroup in SurfaceGroupName<ServiceEventName<TEventApi>>]: {
1266
1265
  readonly [
@@ -1269,237 +1268,64 @@ type BoundActiveEventFacade<
1269
1268
  TGroup
1270
1269
  > as SurfaceLeafName<E>
1271
1270
  ]: {
1272
- prepare(
1273
- event: ServiceEventPayloadOf<TEventApi, E>,
1274
- ): Result<
1275
- PreparedTrellisEvent<ServiceEventPayloadOf<TEventApi, E>>,
1276
- ValidationError | UnexpectedError
1277
- >;
1278
- publish(
1271
+ enqueue(
1279
1272
  event: ServiceEventPayloadOf<TEventApi, E>,
1280
- ): AsyncResult<void, ValidationError | UnexpectedError>;
1281
- listen(
1282
- handler: BoundEventHandleFn<
1283
- TEventApi,
1284
- TTrellisApi,
1285
- E,
1286
- TKv,
1287
- TJobs,
1288
- TDeps
1289
- >,
1290
- subjectData?: Record<string, unknown>,
1291
- opts?: EventOpts,
1292
- ): AsyncResult<void, ValidationError | UnexpectedError>;
1273
+ ): AsyncResult<OutboxMessage, ValidationError | UnexpectedError>;
1293
1274
  };
1294
1275
  };
1295
1276
  };
1296
1277
 
1297
- type BoundRpcHandleFn<
1298
- TOwnedApi extends TrellisAPI,
1299
- TTrellisApi extends TrellisAPI,
1300
- M extends RpcMethodName<TOwnedApi>,
1301
- TKv extends ContractKvMetadata,
1302
- TJobs extends ContractJobsMetadata,
1303
- TDeps,
1304
- > = (args: {
1305
- input: RpcMethodInput<TOwnedApi, M>;
1306
- context: RpcHandlerContext;
1307
- client: Trellis<TTrellisApi, TKv, TJobs>;
1308
- deps: TDeps;
1309
- }) =>
1310
- | Promise<
1311
- Result<RpcMethodOutput<TOwnedApi, M>, RpcHandlerErrorOf<TOwnedApi, M>>
1312
- >
1313
- | Result<RpcMethodOutput<TOwnedApi, M>, RpcHandlerErrorOf<TOwnedApi, M>>;
1314
-
1315
- type BoundFeedHandleFn<
1316
- TOwnedApi extends TrellisAPI,
1317
- TTrellisApi extends TrellisAPI,
1318
- F extends keyof TOwnedApi["feeds"] & string,
1319
- TKv extends ContractKvMetadata,
1320
- TJobs extends ContractJobsMetadata,
1321
- TDeps,
1322
- > = (context: {
1323
- input: FeedInputOf<TOwnedApi, F>;
1324
- caller: unknown;
1325
- signal: AbortSignal;
1326
- emit(
1327
- event: FeedEventOf<TOwnedApi, F>,
1328
- ): AsyncResult<void, ValidationError | UnexpectedError>;
1329
- client: Trellis<TTrellisApi, TKv, TJobs>;
1330
- deps: TDeps;
1331
- }) => unknown | Promise<unknown>;
1332
-
1333
- type BoundOperationHandleFn<
1334
- TOwnedApi extends TrellisAPI,
1335
- TTrellisApi extends TrellisAPI,
1336
- O extends keyof TOwnedApi["operations"] & string,
1337
- TKv extends ContractKvMetadata,
1338
- TJobs extends ContractJobsMetadata,
1339
- TDeps,
1340
- > =
1341
- & ((
1342
- handler: (
1343
- context:
1344
- & OperationHandlerContext<
1345
- InferSchemaType<TOwnedApi["operations"][O]["input"]>,
1346
- OperationProgressOf<TOwnedApi, O>,
1347
- OperationOutputOf<TOwnedApi, O>,
1348
- OperationTransferContextOf<TOwnedApi, O>
1349
- >
1350
- & {
1351
- client: Trellis<TTrellisApi, TKv, TJobs>;
1352
- deps: TDeps;
1353
- },
1354
- ) => unknown | Promise<unknown>,
1355
- ) => Promise<void>)
1356
- & Pick<
1357
- OperationHandleFn<TOwnedApi, TTrellisApi, O, TKv, TJobs>,
1358
- "accept" | "control"
1359
- >;
1278
+ /** Returned by outbox.job.<queue>.create/submit inside a transaction. */
1279
+ export type SqlOutboxJobSubmission = {
1280
+ readonly submissionId: string;
1281
+ readonly jobId: string;
1282
+ readonly queue: string;
1283
+ readonly mode: "create" | "submit";
1284
+ };
1360
1285
 
1361
- type BoundTypedServiceHandleFacade<
1362
- TOwnedApi extends TrellisAPI,
1363
- TTrellisApi extends TrellisAPI,
1364
- TKv extends ContractKvMetadata,
1365
- TJobs extends ContractJobsMetadata,
1366
- TDeps,
1367
- > = {
1368
- readonly rpc: {
1369
- readonly [TGroup in SurfaceGroupName<RpcMethodName<TOwnedApi>>]: {
1370
- readonly [
1371
- M in SurfaceKeysForGroup<
1372
- RpcMethodName<TOwnedApi>,
1373
- TGroup
1374
- > as SurfaceLeafName<M>
1375
- ]: (
1376
- handler: BoundRpcHandleFn<TOwnedApi, TTrellisApi, M, TKv, TJobs, TDeps>,
1377
- ) => Promise<void>;
1378
- };
1379
- };
1380
- readonly feed: {
1381
- readonly [TGroup in SurfaceGroupName<keyof TOwnedApi["feeds"] & string>]: {
1382
- readonly [
1383
- F in SurfaceKeysForGroup<
1384
- keyof TOwnedApi["feeds"] & string,
1385
- TGroup
1386
- > as SurfaceLeafName<F>
1387
- ]: (
1388
- handler: BoundFeedHandleFn<
1389
- TOwnedApi,
1390
- TTrellisApi,
1391
- F,
1392
- TKv,
1393
- TJobs,
1394
- TDeps
1395
- >,
1396
- ) => Promise<void>;
1397
- };
1398
- };
1399
- readonly operation: {
1400
- readonly [
1401
- TGroup in SurfaceGroupName<keyof TOwnedApi["operations"] & string>
1402
- ]: {
1403
- readonly [
1404
- O in SurfaceKeysForGroup<
1405
- keyof TOwnedApi["operations"] & string,
1406
- TGroup
1407
- > as SurfaceLeafName<O>
1408
- ]: BoundOperationHandleFn<TOwnedApi, TTrellisApi, O, TKv, TJobs, TDeps>;
1409
- };
1286
+ /** Transaction-scoped facade for enqueuing job creation/submission intents. */
1287
+ export type SqlOutboxJobEnqueueFacade<TJobs extends ContractJobsMetadata> = {
1288
+ readonly [K in keyof TJobs]: {
1289
+ create(
1290
+ payload: TJobs[K]["payload"],
1291
+ ): AsyncResult<SqlOutboxJobSubmission, ValidationError | UnexpectedError>;
1292
+ submit(
1293
+ payload: TJobs[K]["payload"],
1294
+ ): AsyncResult<SqlOutboxJobSubmission, ValidationError | UnexpectedError>;
1410
1295
  };
1411
1296
  };
1412
1297
 
1413
- type BoundJobQueue<
1414
- TPayload,
1415
- TResult,
1416
- TTrellisApi extends TrellisAPI,
1417
- TKv extends ContractKvMetadata,
1418
- TJobs extends ContractJobsMetadata,
1419
- TDeps,
1298
+ /** Context supplied to `outbox.transaction(...)` work callbacks. */
1299
+ export type SqlOutboxTransactionContext<
1300
+ TTx,
1301
+ TEventApi extends TrellisAPI = TrellisAPI,
1302
+ TJobs extends ContractJobsMetadata = ContractJobsMetadata,
1420
1303
  > = {
1421
- create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
1422
- handle(
1423
- handler: (args: {
1424
- job: PublicActiveJob<TPayload, TResult>;
1425
- client: Trellis<TTrellisApi, TKv, TJobs>;
1426
- deps: TDeps;
1427
- }) => Promise<Result<TResult, BaseError>>,
1428
- ): void;
1304
+ /** Service-owned transaction object supplied by the configured runner. */
1305
+ readonly tx: TTx;
1306
+ /** Transaction-scoped typed event enqueue facade. */
1307
+ readonly event: SqlOutboxEventEnqueueFacade<TEventApi>;
1308
+ /** Transaction-scoped typed job enqueue facade. */
1309
+ readonly job: SqlOutboxJobEnqueueFacade<TJobs>;
1429
1310
  };
1430
1311
 
1431
- type BoundJobsFacadeOf<
1432
- TJobs extends ContractJobsMetadata,
1433
- TTrellisApi extends TrellisAPI,
1434
- TKv extends ContractKvMetadata,
1435
- TDeps,
1312
+ /** Startup-created SQL outbox dependency for transactional event/job enqueue. */
1313
+ export type SqlOutbox<
1314
+ TTx,
1315
+ TEventApi extends TrellisAPI = TrellisAPI,
1316
+ TJobs extends ContractJobsMetadata = ContractJobsMetadata,
1436
1317
  > = {
1437
- [K in keyof TJobs]: BoundJobQueue<
1438
- TJobs[K]["payload"],
1439
- TJobs[K]["result"],
1440
- TTrellisApi,
1441
- TKv,
1442
- TJobs,
1443
- TDeps
1444
- >;
1445
- };
1446
-
1447
- type BoundServiceHealth<TDeps> = Omit<ServiceHealth, "add" | "setInfo"> & {
1448
- setInfo(info: ServiceHealthInfo | HealthInfoHandler<TDeps>): void;
1449
- add(
1450
- name: string,
1451
- check: HealthCheckHandler<TDeps>,
1452
- ): () => void;
1318
+ /**
1319
+ * Runs service DB work and typed event/job enqueue operations in one SQL
1320
+ * transaction, notifying the dispatcher once after a successful commit.
1321
+ */
1322
+ transaction<TResult>(
1323
+ work: (context: SqlOutboxTransactionContext<TTx, TEventApi, TJobs>) =>
1324
+ | Promise<TResult>
1325
+ | TResult,
1326
+ ): AsyncResult<TResult, ValidationError | UnexpectedError>;
1453
1327
  };
1454
1328
 
1455
- /** Service wrapper returned by `TrellisService.with(deps)`. */
1456
- export type BoundTrellisService<
1457
- TOwnedApi extends TrellisAPI = TrellisAPI,
1458
- TTrellisApi extends TrellisAPI = TOwnedApi,
1459
- TJobs extends ContractJobsMetadata = {},
1460
- TKv extends ContractKvMetadata = ContractKvMetadata,
1461
- TDeps = unknown,
1462
- > =
1463
- & Pick<
1464
- TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>,
1465
- | "name"
1466
- | "auth"
1467
- | "kv"
1468
- | "store"
1469
- | "connection"
1470
- | "createTransfer"
1471
- | "completeOperation"
1472
- | "publishPrepared"
1473
- | "wait"
1474
- | "stop"
1475
- >
1476
- & {
1477
- readonly event: BoundActiveEventFacade<
1478
- TTrellisApi,
1479
- TTrellisApi,
1480
- TKv,
1481
- TJobs,
1482
- TDeps
1483
- >;
1484
- readonly health: BoundServiceHealth<TDeps>;
1485
- readonly jobs: BoundJobsFacadeOf<TJobs, TTrellisApi, TKv, TDeps>;
1486
- readonly handle: BoundTypedServiceHandleFacade<
1487
- TOwnedApi,
1488
- TTrellisApi,
1489
- TKv,
1490
- TJobs,
1491
- TDeps
1492
- >;
1493
- /** Returns a new bound wrapper that injects the provided dependencies. */
1494
- with<TNextDeps>(deps: TNextDeps): BoundTrellisService<
1495
- TOwnedApi,
1496
- TTrellisApi,
1497
- TJobs,
1498
- TKv,
1499
- TNextDeps
1500
- >;
1501
- };
1502
-
1503
1329
  const MANAGED_JOB_WORKERS = Symbol("trellis.managedJobWorkers");
1504
1330
 
1505
1331
  type ManagedJobWorkers = {
@@ -1520,10 +1346,16 @@ type ServiceHandleOperationLeaf =
1520
1346
  & {
1521
1347
  accept(
1522
1348
  args: { sessionKey: string },
1523
- ): AsyncResult<AcceptedOperation, UnexpectedError>;
1349
+ ): AsyncResult<
1350
+ AcceptedOperation<unknown, unknown, BaseError>,
1351
+ UnexpectedError
1352
+ >;
1524
1353
  control(
1525
1354
  operationId: string,
1526
- ): AsyncResult<OperationRuntimeHandle, BaseError>;
1355
+ ): AsyncResult<
1356
+ OperationRuntimeHandle<unknown, unknown, BaseError>,
1357
+ BaseError
1358
+ >;
1527
1359
  };
1528
1360
 
1529
1361
  type ServiceHandleFacade = {
@@ -1561,19 +1393,6 @@ type ServiceEventLeaf = ServiceEventPublishLeaf & {
1561
1393
  ): AsyncResult<void, ValidationError | UnexpectedError>;
1562
1394
  };
1563
1395
 
1564
- type BoundServiceEventLeaf<TDeps> = ServiceEventPublishLeaf & {
1565
- listen(
1566
- handler: (args: {
1567
- event: unknown;
1568
- context: EventListenerContext;
1569
- client: unknown;
1570
- deps: TDeps;
1571
- }) => MaybeAsync<void, BaseError>,
1572
- subjectData?: Record<string, unknown>,
1573
- opts?: EventOpts,
1574
- ): AsyncResult<void, ValidationError | UnexpectedError>;
1575
- };
1576
-
1577
1396
  function createServiceEventPublishFacade<TA extends TrellisAPI>(outbound: {
1578
1397
  readonly api: TA;
1579
1398
  prepare(
@@ -1708,7 +1527,8 @@ type OperationHandleFn<
1708
1527
  InferSchemaType<TOwnedApi["operations"][O]["input"]>,
1709
1528
  OperationProgressOf<TOwnedApi, O>,
1710
1529
  OperationOutputOf<TOwnedApi, O>,
1711
- OperationTransferContextOf<TOwnedApi, O>
1530
+ OperationTransferContextOf<TOwnedApi, O>,
1531
+ OperationHandlerErrorOf<TOwnedApi, O>
1712
1532
  >
1713
1533
  & { client: Trellis<TTrellisApi, TKv, TJobs> },
1714
1534
  ) => unknown | Promise<unknown>,
@@ -1717,14 +1537,16 @@ type OperationHandleFn<
1717
1537
  accept(args: { sessionKey: string }): AsyncResult<
1718
1538
  AcceptedOperation<
1719
1539
  OperationProgressOf<TOwnedApi, O>,
1720
- OperationOutputOf<TOwnedApi, O>
1540
+ OperationOutputOf<TOwnedApi, O>,
1541
+ OperationHandlerErrorOf<TOwnedApi, O>
1721
1542
  >,
1722
1543
  UnexpectedError
1723
1544
  >;
1724
1545
  control(operationId: string): AsyncResult<
1725
1546
  OperationRuntimeHandle<
1726
1547
  OperationProgressOf<TOwnedApi, O>,
1727
- OperationOutputOf<TOwnedApi, O>
1548
+ OperationOutputOf<TOwnedApi, O>,
1549
+ OperationHandlerErrorOf<TOwnedApi, O>
1728
1550
  >,
1729
1551
  BaseError
1730
1552
  >;
@@ -1742,7 +1564,8 @@ export type OperationRegistration<
1742
1564
  }): AsyncResult<
1743
1565
  AcceptedOperation<
1744
1566
  OperationProgressOf<TOwnedApi, O>,
1745
- OperationOutputOf<TOwnedApi, O>
1567
+ OperationOutputOf<TOwnedApi, O>,
1568
+ OperationHandlerErrorOf<TOwnedApi, O>
1746
1569
  >,
1747
1570
  UnexpectedError
1748
1571
  >;
@@ -1755,7 +1578,8 @@ export type OperationRegistration<
1755
1578
  ): AsyncResult<
1756
1579
  OperationRuntimeHandle<
1757
1580
  OperationProgressOf<TOwnedApi, O>,
1758
- OperationOutputOf<TOwnedApi, O>
1581
+ OperationOutputOf<TOwnedApi, O>,
1582
+ OperationHandlerErrorOf<TOwnedApi, O>
1759
1583
  >,
1760
1584
  BaseError
1761
1585
  >;
@@ -1766,7 +1590,8 @@ export type OperationRegistration<
1766
1590
  InferSchemaType<TOwnedApi["operations"][O]["input"]>,
1767
1591
  OperationProgressOf<TOwnedApi, O>,
1768
1592
  OperationOutputOf<TOwnedApi, O>,
1769
- OperationTransferContextOf<TOwnedApi, O>
1593
+ OperationTransferContextOf<TOwnedApi, O>,
1594
+ OperationHandlerErrorOf<TOwnedApi, O>
1770
1595
  >
1771
1596
  & { client: Trellis<TTrellisApi, TKv, TJobs> },
1772
1597
  ) => unknown | Promise<unknown>,
@@ -1827,6 +1652,9 @@ export async function createConnectedService<
1827
1652
  contractEventConsumers?: ContractEventConsumers;
1828
1653
  server: TrellisServiceRuntimeCreateOpts<TOwnedApi, TTrellisApi>;
1829
1654
  bindings: ResourceBindings;
1655
+ durableEventConsumerBeforeReadinessCheck?: TrellisServiceRuntimeDeps[
1656
+ "durableEventConsumerBeforeReadinessCheck"
1657
+ ];
1830
1658
  }): Promise<TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>> {
1831
1659
  const resolvedLog = resolveServiceLogger(args.server.log);
1832
1660
  const connection = observeNatsTrellisConnection({
@@ -1886,6 +1714,8 @@ export async function createConnectedService<
1886
1714
  metadata: args.contractEventConsumers,
1887
1715
  bindings: args.bindings.eventConsumers,
1888
1716
  },
1717
+ durableEventConsumerBeforeReadinessCheck:
1718
+ args.durableEventConsumerBeforeReadinessCheck,
1889
1719
  connection,
1890
1720
  },
1891
1721
  );
@@ -1922,6 +1752,7 @@ export async function createConnectedService<
1922
1752
  prepare: (event, data) => outbound.prepare(event, data),
1923
1753
  publish: (event, data) => outbound.publish(event, data),
1924
1754
  publishPrepared: (event) => outbound.publishPrepared(event),
1755
+ stopEventListeners: () => outbound.stopEventListeners(),
1925
1756
  get kv() {
1926
1757
  return getHandlerResources().kv;
1927
1758
  },
@@ -2054,6 +1885,190 @@ function toUnexpectedError(cause: unknown): UnexpectedError {
2054
1885
  : new UnexpectedError({ cause });
2055
1886
  }
2056
1887
 
1888
+ function resolveSqlOutboxTables(
1889
+ tables: Partial<SqlOutboxTables> | undefined,
1890
+ ): SqlOutboxTables {
1891
+ return {
1892
+ outbox: tables?.outbox ?? defaultSqlOutboxTables.outbox,
1893
+ inbox: tables?.inbox ?? defaultSqlOutboxTables.inbox,
1894
+ };
1895
+ }
1896
+
1897
+ function createSqlOutboxBaseExecutor<TTx>(
1898
+ options: TrellisServiceSqlOutboxOptions<TTx>,
1899
+ ): SqlExecutor {
1900
+ return options.executor;
1901
+ }
1902
+
1903
+ function createSqlOutboxTransactionRunner<TTx>(
1904
+ options: TrellisServiceSqlOutboxOptions<TTx>,
1905
+ ): SqlOutboxTransactionRunner<TTx> {
1906
+ return options.transaction;
1907
+ }
1908
+
1909
+ type SqlOutboxEventEnqueueLeaf = {
1910
+ enqueue(
1911
+ event: Record<string, unknown>,
1912
+ ): AsyncResult<OutboxMessage, ValidationError | UnexpectedError>;
1913
+ };
1914
+
1915
+ function createSqlOutboxEventEnqueueFacade<TEventApi extends TrellisAPI>(args: {
1916
+ event: ActiveEventFacade<TEventApi>;
1917
+ repository: SqlOutboxRepository;
1918
+ onEnqueued(): void;
1919
+ }): SqlOutboxEventEnqueueFacade<TEventApi> {
1920
+ const facade: Record<string, Record<string, SqlOutboxEventEnqueueLeaf>> = {};
1921
+ const source = args.event as Record<
1922
+ string,
1923
+ Record<string, ServiceEventLeaf>
1924
+ >;
1925
+
1926
+ for (const [groupName, leaves] of Object.entries(source)) {
1927
+ const group: Record<string, SqlOutboxEventEnqueueLeaf> = {};
1928
+ for (const [leafName, leaf] of Object.entries(leaves)) {
1929
+ group[leafName] = {
1930
+ enqueue: (payload) =>
1931
+ AsyncResult.from((async () => {
1932
+ const prepared = leaf.prepare(payload).take();
1933
+ if (isErr(prepared)) return Result.err(prepared.error);
1934
+ const record = preparedTrellisEventToOutboxRecord(prepared);
1935
+ try {
1936
+ const message = await args.repository.enqueue(record);
1937
+ args.onEnqueued();
1938
+ return Result.ok(message);
1939
+ } catch (cause) {
1940
+ return Result.err(toUnexpectedError(cause));
1941
+ }
1942
+ })()),
1943
+ };
1944
+ }
1945
+ Object.defineProperty(facade, groupName, {
1946
+ value: group,
1947
+ enumerable: true,
1948
+ configurable: true,
1949
+ });
1950
+ }
1951
+
1952
+ return facade as SqlOutboxEventEnqueueFacade<TEventApi>;
1953
+ }
1954
+
1955
+ function createSqlOutboxJobEnqueueFacade<TJobs extends ContractJobsMetadata>(
1956
+ args: {
1957
+ serviceName: string;
1958
+ contractJobs: TJobs;
1959
+ jobsBinding?: ResourceBindingJobs;
1960
+ repository: SqlOutboxRepository;
1961
+ onEnqueued(): void;
1962
+ },
1963
+ ): SqlOutboxJobEnqueueFacade<TJobs> {
1964
+ const facade: Record<string, Record<string, unknown>> = {};
1965
+
1966
+ for (
1967
+ const queueType of Object.keys(args.contractJobs as Record<string, unknown>)
1968
+ ) {
1969
+ const queueBinding = args.jobsBinding?.queues[queueType];
1970
+ const queue = queueType;
1971
+ facade[queue] = {
1972
+ create: (payload: unknown) => {
1973
+ if (!queueBinding) {
1974
+ return AsyncResult.err(
1975
+ new ValidationError({
1976
+ errors: [{
1977
+ path: "/",
1978
+ message: `Jobs binding unavailable for queue '${queue}'`,
1979
+ }],
1980
+ }),
1981
+ );
1982
+ }
1983
+ const submissionId = ulid();
1984
+ const jobId = ulid();
1985
+ const context = createJobContext();
1986
+
1987
+ const record: PreparedOutboxRecord = {
1988
+ id: submissionId,
1989
+ kind: "job.create",
1990
+ name: queue,
1991
+ subject: `${queueBinding.publishPrefix}.${jobId}.created`,
1992
+ payload: JSON.stringify({
1993
+ ...(payload as Record<string, unknown>),
1994
+ jobId,
1995
+ submissionId,
1996
+ }),
1997
+ headers: {
1998
+ "request-id": context.requestId,
1999
+ "traceparent": context.traceparent,
2000
+ ...(context.tracestate ? { "tracestate": context.tracestate } : {}),
2001
+ },
2002
+ };
2003
+
2004
+ return AsyncResult.from((async () => {
2005
+ try {
2006
+ await args.repository.enqueue(record);
2007
+ args.onEnqueued();
2008
+ return Result.ok({
2009
+ submissionId,
2010
+ jobId,
2011
+ queue,
2012
+ mode: "create",
2013
+ });
2014
+ } catch (cause) {
2015
+ return Result.err(toUnexpectedError(cause));
2016
+ }
2017
+ })());
2018
+ },
2019
+ submit: (payload: unknown) => {
2020
+ if (!queueBinding) {
2021
+ return AsyncResult.err(
2022
+ new ValidationError({
2023
+ errors: [{
2024
+ path: "/",
2025
+ message: `Jobs binding unavailable for queue '${queue}'`,
2026
+ }],
2027
+ }),
2028
+ );
2029
+ }
2030
+ const submissionId = ulid();
2031
+ const jobId = ulid();
2032
+ const context = createJobContext();
2033
+
2034
+ const record: PreparedOutboxRecord = {
2035
+ id: submissionId,
2036
+ kind: "job.submit",
2037
+ name: queue,
2038
+ subject: `${queueBinding.publishPrefix}.${jobId}.created`,
2039
+ payload: JSON.stringify({
2040
+ ...(payload as Record<string, unknown>),
2041
+ jobId,
2042
+ submissionId,
2043
+ }),
2044
+ headers: {
2045
+ "request-id": context.requestId,
2046
+ "traceparent": context.traceparent,
2047
+ ...(context.tracestate ? { "tracestate": context.tracestate } : {}),
2048
+ },
2049
+ };
2050
+
2051
+ return AsyncResult.from((async () => {
2052
+ try {
2053
+ await args.repository.enqueue(record);
2054
+ args.onEnqueued();
2055
+ return Result.ok({
2056
+ submissionId,
2057
+ jobId,
2058
+ queue,
2059
+ mode: "submit",
2060
+ });
2061
+ } catch (cause) {
2062
+ return Result.err(toUnexpectedError(cause));
2063
+ }
2064
+ })());
2065
+ },
2066
+ };
2067
+ }
2068
+
2069
+ return facade as SqlOutboxJobEnqueueFacade<TJobs>;
2070
+ }
2071
+
2057
2072
  function serializeJobHandlerError(error: BaseError): string {
2058
2073
  try {
2059
2074
  return JSON.stringify(error.toSerializable());
@@ -2081,7 +2096,8 @@ function isTerminalJobState(
2081
2096
  state: string,
2082
2097
  ): state is TerminalJob<unknown, unknown>["state"] {
2083
2098
  return state === "completed" || state === "failed" || state === "cancelled" ||
2084
- state === "expired" || state === "dead" || state === "dismissed";
2099
+ state === "expired" || state === "skipped" || state === "stale" ||
2100
+ state === "dead" || state === "dismissed";
2085
2101
  }
2086
2102
 
2087
2103
  function isTerminalJobSnapshot<TPayload, TResult>(
@@ -2202,6 +2218,9 @@ function snapshotFromLifecycleEvent<TPayload, TResult>(
2202
2218
  case "failed":
2203
2219
  case "cancelled":
2204
2220
  case "expired":
2221
+ case "skipped":
2222
+ case "stale":
2223
+ case "staleCompletionIgnored":
2205
2224
  case "dead":
2206
2225
  case "dismissed":
2207
2226
  return event.error === undefined ? base : {
@@ -2359,8 +2378,8 @@ function createJobLifecycleTracker(nc: NatsConnection): JobLifecycleTracker {
2359
2378
  function createJobRef<TPayload, TResult>(args: {
2360
2379
  nc: NatsConnection;
2361
2380
  queueType: string;
2362
- jobsBinding: ResourceBindingJobs;
2363
- queueBinding: ResourceBindingJobsQueue;
2381
+ jobsBinding: JobsBinding;
2382
+ queueBinding: JobsQueueBinding;
2364
2383
  seed: JobSnapshot<TPayload, TResult>;
2365
2384
  lifecycle: JobLifecycleTracker;
2366
2385
  }): JobRef<TPayload, TResult> {
@@ -2467,6 +2486,10 @@ function createJobsFacade<
2467
2486
  const handlers = new Map<string, RegisteredJobHandler<unknown, unknown>>();
2468
2487
  const jobsFacade: Record<string, unknown> = {};
2469
2488
  const lifecycle = createJobLifecycleTracker(args.nc);
2489
+ const keyCoordinator = createNatsJobKeyCoordinator(args.nc);
2490
+ const jobsBinding = args.jobsBinding
2491
+ ? normalizeResourceJobsBinding(args.jobsBinding)
2492
+ : undefined;
2470
2493
  let activeHost: JobWorkerHostAdapter | undefined;
2471
2494
  let startupPromise:
2472
2495
  | Promise<Result<JobWorkerHostAdapter, BaseError>>
@@ -2474,14 +2497,13 @@ function createJobsFacade<
2474
2497
  let stopPromise: Promise<Result<void, BaseError>> | undefined;
2475
2498
 
2476
2499
  for (const queueType of Object.keys(args.contractJobs ?? {})) {
2477
- const queueBinding = args.jobsBinding?.queues[queueType];
2500
+ const queueBinding = jobsBinding?.queues[queueType];
2478
2501
  if (queueBinding) lifecycle.watch(queueBinding);
2479
2502
 
2480
2503
  jobsFacade[queueType] = {
2481
2504
  create: (payload) =>
2482
2505
  AsyncResult.from((async () => {
2483
2506
  try {
2484
- const jobsBinding = args.jobsBinding;
2485
2507
  if (!jobsBinding) {
2486
2508
  return Result.err(
2487
2509
  toUnexpectedError(new Error("Jobs bindings are unavailable")),
@@ -2499,6 +2521,7 @@ function createJobsFacade<
2499
2521
  const manager = new InternalJobManager<unknown, unknown>({
2500
2522
  nc: args.nc,
2501
2523
  jobs: jobsBinding,
2524
+ keyCoordinator,
2502
2525
  });
2503
2526
  await args.nc.flush();
2504
2527
  const created = await manager.create(queueType, payload);
@@ -2510,6 +2533,67 @@ function createJobsFacade<
2510
2533
  seed: created as JobSnapshot<unknown, unknown>,
2511
2534
  lifecycle,
2512
2535
  }));
2536
+ } catch (cause) {
2537
+ if (cause instanceof JobNotEnqueuedError) {
2538
+ return Result.err(cause);
2539
+ }
2540
+ return Result.err(toUnexpectedError(cause));
2541
+ }
2542
+ })()),
2543
+ submit: (payload) =>
2544
+ AsyncResult.from((async () => {
2545
+ try {
2546
+ if (!jobsBinding) {
2547
+ return Result.err(
2548
+ toUnexpectedError(new Error("Jobs bindings are unavailable")),
2549
+ );
2550
+ }
2551
+ const queueBinding = jobsBinding.queues[queueType];
2552
+ if (!queueBinding) {
2553
+ return Result.err(toUnexpectedError(
2554
+ new Error(
2555
+ `Jobs binding for queue '${queueType}' is unavailable`,
2556
+ ),
2557
+ ));
2558
+ }
2559
+
2560
+ const manager = new InternalJobManager<unknown, unknown>({
2561
+ nc: args.nc,
2562
+ jobs: jobsBinding,
2563
+ keyCoordinator,
2564
+ });
2565
+ await args.nc.flush();
2566
+ const outcome = await manager.submit(queueType, payload);
2567
+ if (outcome.kind === "accepted") {
2568
+ return Result.ok({
2569
+ kind: "accepted",
2570
+ key: outcome.key,
2571
+ ref: createJobRef({
2572
+ nc: args.nc,
2573
+ queueType,
2574
+ jobsBinding,
2575
+ queueBinding,
2576
+ seed: outcome.job as JobSnapshot<unknown, unknown>,
2577
+ lifecycle,
2578
+ }),
2579
+ });
2580
+ }
2581
+ if (outcome.kind === "replaced") {
2582
+ return Result.ok({
2583
+ kind: "replaced",
2584
+ key: outcome.key,
2585
+ replaced: outcome.replaced,
2586
+ ref: createJobRef({
2587
+ nc: args.nc,
2588
+ queueType,
2589
+ jobsBinding,
2590
+ queueBinding,
2591
+ seed: outcome.job as JobSnapshot<unknown, unknown>,
2592
+ lifecycle,
2593
+ }),
2594
+ });
2595
+ }
2596
+ return Result.ok(outcome);
2513
2597
  } catch (cause) {
2514
2598
  return Result.err(toUnexpectedError(cause));
2515
2599
  }
@@ -2554,7 +2638,7 @@ function createJobsFacade<
2554
2638
  return Result.ok(host);
2555
2639
  }
2556
2640
 
2557
- if (!args.jobsBinding || !args.workStream) {
2641
+ if (!jobsBinding || !args.workStream) {
2558
2642
  return Result.err(toUnexpectedError(
2559
2643
  new Error(
2560
2644
  "Jobs infrastructure bindings are unavailable for this service",
@@ -2562,7 +2646,6 @@ function createJobsFacade<
2562
2646
  ));
2563
2647
  }
2564
2648
 
2565
- const jobsBinding = args.jobsBinding;
2566
2649
  const workStream = args.workStream;
2567
2650
 
2568
2651
  const hosts = [] as Array<{ stop(): Promise<void> }>;
@@ -2582,6 +2665,7 @@ function createJobsFacade<
2582
2665
  const manager = new InternalJobManager<unknown, unknown>({
2583
2666
  nc: args.nc,
2584
2667
  jobs: jobsBinding,
2668
+ keyCoordinator,
2585
2669
  });
2586
2670
  const host = await startNatsWorkerHostFromBinding<unknown>({
2587
2671
  jobs: jobsBinding,
@@ -2591,6 +2675,7 @@ function createJobsFacade<
2591
2675
  instanceId: `${args.serviceName}-worker`,
2592
2676
  queueTypes: [queueType],
2593
2677
  manager,
2678
+ heartbeatPublisher: args.nc,
2594
2679
  getProjectedJob: async (job) => {
2595
2680
  return lifecycle.get({
2596
2681
  service: job.service,
@@ -2730,47 +2815,6 @@ function createJobsFacade<
2730
2815
  return jobsFacade as ManagedJobsFacade<TJobs, TTrellisApi, TKv>;
2731
2816
  }
2732
2817
 
2733
- function createBoundJobsFacade<
2734
- TJobs extends ContractJobsMetadata,
2735
- TTrellisApi extends TrellisAPI,
2736
- TKv extends ContractKvMetadata,
2737
- TDeps,
2738
- >(args: {
2739
- jobs: JobsFacadeOf<TJobs, TTrellisApi, TKv>;
2740
- deps: TDeps;
2741
- }): BoundJobsFacadeOf<TJobs, TTrellisApi, TKv, TDeps> {
2742
- const boundJobs: Record<string, unknown> = {};
2743
- const jobs = args.jobs as Record<
2744
- string,
2745
- JobQueue<unknown, unknown, TTrellisApi, TKv, TJobs>
2746
- >;
2747
-
2748
- for (const queueType of Object.keys(jobs)) {
2749
- const queue = jobs[queueType];
2750
- if (!queue) continue;
2751
- boundJobs[queueType] = {
2752
- create: (payload) => queue.create(payload),
2753
- handle: (handler) =>
2754
- queue.handle(({ job, client }) =>
2755
- handler({
2756
- job,
2757
- client,
2758
- deps: args.deps,
2759
- })
2760
- ),
2761
- } satisfies BoundJobQueue<
2762
- unknown,
2763
- unknown,
2764
- TTrellisApi,
2765
- TKv,
2766
- TJobs,
2767
- TDeps
2768
- >;
2769
- }
2770
-
2771
- return boundJobs as BoundJobsFacadeOf<TJobs, TTrellisApi, TKv, TDeps>;
2772
- }
2773
-
2774
2818
  /**
2775
2819
  * Connects a service with caller-supplied runtime dependencies for tests and
2776
2820
  * Trellis-owned internals. This helper is intentionally not re-exported from
@@ -2798,6 +2842,7 @@ export function connectTrellisServiceWithRuntimeDeps<
2798
2842
  TransportError | UnexpectedError
2799
2843
  > {
2800
2844
  return AsyncResult.from((async () => {
2845
+ const totalStartedAt = performance.now();
2801
2846
  try {
2802
2847
  type TOwnedApi = ContractOwnedApi<TContract>;
2803
2848
  type TTrellisApi = ContractTrellisApi<TContract>;
@@ -2811,6 +2856,7 @@ export function connectTrellisServiceWithRuntimeDeps<
2811
2856
  }
2812
2857
  const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
2813
2858
  const bootstrapLog = resolveServiceLogger(args.server?.log);
2859
+ const bootstrapStartedAt = performance.now();
2814
2860
  const bootstrap = await fetchServiceBootstrapInfo({
2815
2861
  trellisUrl: args.trellisUrl,
2816
2862
  serviceName: args.name,
@@ -2820,6 +2866,15 @@ export function connectTrellisServiceWithRuntimeDeps<
2820
2866
  auth,
2821
2867
  log: bootstrapLog,
2822
2868
  });
2869
+ recordTrellisDuration(
2870
+ "trellis.connect.duration",
2871
+ performance.now() - bootstrapStartedAt,
2872
+ {
2873
+ phase: "bootstrap",
2874
+ participantKind: "service",
2875
+ outcome: "ok",
2876
+ },
2877
+ );
2823
2878
  const { authenticator: authTokenAuthenticator, inboxPrefix } = await auth
2824
2879
  .natsConnectOptions({
2825
2880
  contractDigest: args.contract.CONTRACT_DIGEST,
@@ -2827,6 +2882,7 @@ export function connectTrellisServiceWithRuntimeDeps<
2827
2882
 
2828
2883
  let nc: NatsConnection;
2829
2884
  try {
2885
+ const natsStartedAt = performance.now();
2830
2886
  nc = await runtimeDeps.connect({
2831
2887
  servers: selectRuntimeTransportServers(
2832
2888
  bootstrap.connectInfo.transports,
@@ -2844,6 +2900,15 @@ export function connectTrellisServiceWithRuntimeDeps<
2844
2900
  ),
2845
2901
  ],
2846
2902
  });
2903
+ recordTrellisDuration(
2904
+ "trellis.connect.duration",
2905
+ performance.now() - natsStartedAt,
2906
+ {
2907
+ phase: "nats_connect",
2908
+ participantKind: "service",
2909
+ outcome: "ok",
2910
+ },
2911
+ );
2847
2912
  } catch (cause) {
2848
2913
  throw new TransportError({
2849
2914
  code: "trellis.runtime.connect_failed",
@@ -2871,31 +2936,41 @@ export function connectTrellisServiceWithRuntimeDeps<
2871
2936
  api: args.contract.API.owned,
2872
2937
  };
2873
2938
 
2874
- return Result.ok(
2875
- await createConnectedService<
2876
- TOwnedApi,
2877
- TTrellisApi,
2878
- ContractJobsOf<TContract>,
2879
- ContractKvOf<TContract>
2880
- >({
2881
- name: args.name,
2882
- auth,
2883
- nc,
2884
- contractId: args.contract.CONTRACT_ID,
2885
- contractDigest: args.contract.CONTRACT_DIGEST,
2886
- contractJobs:
2887
- (args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
2888
- TContract
2889
- >,
2890
- contractKv:
2891
- (args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
2892
- TContract
2893
- >,
2894
- contractEventConsumers: args.contract.CONTRACT.eventConsumers,
2895
- server,
2896
- bindings: bootstrap.binding.resources,
2897
- }),
2939
+ const service = await createConnectedService<
2940
+ TOwnedApi,
2941
+ TTrellisApi,
2942
+ ContractJobsOf<TContract>,
2943
+ ContractKvOf<TContract>
2944
+ >({
2945
+ name: args.name,
2946
+ auth,
2947
+ nc,
2948
+ contractId: args.contract.CONTRACT_ID,
2949
+ contractDigest: args.contract.CONTRACT_DIGEST,
2950
+ contractJobs:
2951
+ (args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
2952
+ TContract
2953
+ >,
2954
+ contractKv:
2955
+ (args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
2956
+ TContract
2957
+ >,
2958
+ contractEventConsumers: args.contract.CONTRACT.eventConsumers,
2959
+ server,
2960
+ bindings: bootstrap.binding.resources,
2961
+ durableEventConsumerBeforeReadinessCheck:
2962
+ runtimeDeps.durableEventConsumerBeforeReadinessCheck,
2963
+ });
2964
+ recordTrellisDuration(
2965
+ "trellis.connect.duration",
2966
+ performance.now() - totalStartedAt,
2967
+ {
2968
+ phase: "total",
2969
+ participantKind: "service",
2970
+ outcome: "ok",
2971
+ },
2898
2972
  );
2973
+ return Result.ok(service);
2899
2974
  } catch (cause) {
2900
2975
  await closeFailedServiceBootstrapConnection(nc);
2901
2976
  throw cause;
@@ -2931,6 +3006,9 @@ export class TrellisService<
2931
3006
  readonly #operationTransfer: ServiceTransfer;
2932
3007
  readonly #stopHealthPublishing: () => Promise<void>;
2933
3008
  readonly #managedJobWorkers: ManagedJobWorkers;
3009
+ readonly #contractJobs: TJobs;
3010
+ readonly #jobsBinding?: ResourceBindingJobs;
3011
+ readonly #ownedOutboxDispatchers = new Set<OutboxDispatcher>();
2934
3012
  #waitPromise?: Promise<void>;
2935
3013
  #stopPromise?: Promise<void>;
2936
3014
 
@@ -2987,6 +3065,8 @@ export class TrellisService<
2987
3065
  });
2988
3066
  this.jobs = jobs;
2989
3067
  this.#managedJobWorkers = jobs[MANAGED_JOB_WORKERS];
3068
+ this.#contractJobs = contractJobs;
3069
+ this.#jobsBinding = bindings.jobs;
2990
3070
  this.health = health;
2991
3071
  this.handle = this.#createHandleFacade() as TypedServiceHandleFacade<
2992
3072
  TOwnedApi,
@@ -2999,30 +3079,14 @@ export class TrellisService<
2999
3079
  }
3000
3080
 
3001
3081
  /**
3002
- * Returns a service wrapper that injects application dependencies into
3003
- * service-owned handler argument objects as `args.deps`.
3082
+ * Creates an explicit SQL outbox helper for service-owned transactions.
3083
+ * Services should create this at startup and close over it in handlers.
3004
3084
  */
3005
- with<TDeps>(
3006
- deps: TDeps,
3007
- ): BoundTrellisService<TOwnedApi, TTrellisApi, TJobs, TKv, TDeps> {
3008
- return {
3009
- name: this.name,
3010
- auth: this.auth,
3011
- event: this.#createBoundEventFacade(deps),
3012
- kv: this.kv,
3013
- store: this.store,
3014
- jobs: createBoundJobsFacade({ jobs: this.jobs, deps }),
3015
- health: this.#createBoundHealth(deps),
3016
- handle: this.#createBoundHandleFacade(deps),
3017
- connection: this.connection,
3018
- createTransfer: (args) => this.createTransfer(args),
3019
- completeOperation: (operationId, output) =>
3020
- this.completeOperation(operationId, output),
3021
- publishPrepared: (event) => this.publishPrepared(event),
3022
- wait: () => this.wait(),
3023
- stop: () => this.stop(),
3024
- with: (nextDeps) => this.with(nextDeps),
3025
- };
3085
+ createSqlOutbox<TTx>(
3086
+ options: TrellisServiceSqlOutboxExecutorOptions<TTx>,
3087
+ ): SqlOutbox<TTx, TTrellisApi, TJobs> {
3088
+ const binding = this.#createSqlOutboxBinding(options);
3089
+ return this.#createSqlOutbox(binding);
3026
3090
  }
3027
3091
 
3028
3092
  /** Publishes a prepared event through the service runtime connection. */
@@ -3032,77 +3096,6 @@ export class TrellisService<
3032
3096
  return this.#handlerTrellis.publishPrepared(event);
3033
3097
  }
3034
3098
 
3035
- #createBoundHealth<TDeps>(deps: TDeps): BoundServiceHealth<TDeps> {
3036
- const health = this.health;
3037
- return {
3038
- serviceName: health.serviceName,
3039
- kind: health.kind,
3040
- instanceId: health.instanceId,
3041
- contractId: health.contractId,
3042
- contractDigest: health.contractDigest,
3043
- startedAt: health.startedAt,
3044
- publishIntervalMs: health.publishIntervalMs,
3045
- setInfo(info) {
3046
- if (typeof info !== "function") {
3047
- health.setInfo(info);
3048
- return;
3049
- }
3050
-
3051
- health.setInfo(() => info({ deps }));
3052
- },
3053
- add(name, check) {
3054
- return health.add(name, () => check({ deps }));
3055
- },
3056
- checks: () => health.checks(),
3057
- response: () => health.response(),
3058
- heartbeat: () => health.heartbeat(),
3059
- };
3060
- }
3061
-
3062
- #createBoundEventFacade<TDeps>(
3063
- deps: TDeps,
3064
- ): BoundActiveEventFacade<TTrellisApi, TTrellisApi, TKv, TJobs, TDeps> {
3065
- const event = {} as BoundActiveEventFacade<
3066
- TTrellisApi,
3067
- TTrellisApi,
3068
- TKv,
3069
- TJobs,
3070
- TDeps
3071
- >;
3072
- const source = this.event as Record<
3073
- string,
3074
- Record<string, ServiceEventLeaf>
3075
- >;
3076
- for (const [groupName, leaves] of Object.entries(source)) {
3077
- const group: Record<string, BoundServiceEventLeaf<TDeps>> = {};
3078
- for (const [leafName, leaf] of Object.entries(leaves)) {
3079
- group[leafName] = {
3080
- prepare: (payload) => leaf.prepare(payload),
3081
- publish: (payload) => leaf.publish(payload),
3082
- listen: (handler, subjectData, opts) =>
3083
- leaf.listen(
3084
- (payload, context) =>
3085
- handler({
3086
- event: payload,
3087
- context,
3088
- client: this.#handlerTrellis,
3089
- deps,
3090
- }),
3091
- subjectData,
3092
- opts,
3093
- ),
3094
- };
3095
- }
3096
- Object.defineProperty(event, groupName, {
3097
- value: group,
3098
- enumerable: true,
3099
- configurable: true,
3100
- });
3101
- }
3102
-
3103
- return event;
3104
- }
3105
-
3106
3099
  #createHandleFacade(): ServiceHandleFacade {
3107
3100
  const rpc: ServiceHandleFacade["rpc"] = {};
3108
3101
  for (const method of Object.keys(this.#server.api.rpc ?? {})) {
@@ -3168,80 +3161,100 @@ export class TrellisService<
3168
3161
  return { rpc, feed, operation };
3169
3162
  }
3170
3163
 
3171
- #createBoundHandleFacade<TDeps>(
3172
- deps: TDeps,
3173
- ): BoundTypedServiceHandleFacade<TOwnedApi, TTrellisApi, TKv, TJobs, TDeps> {
3174
- const rpc: ServiceHandleFacade["rpc"] = {};
3175
- for (const method of Object.keys(this.#server.api.rpc ?? {})) {
3176
- addSurfaceLeaf(rpc, method, (handler) =>
3177
- this.#server.mountRuntime(
3178
- method,
3179
- async ({ input, context }) =>
3180
- await Promise.resolve(
3181
- (handler as (
3182
- args: unknown,
3183
- ) =>
3184
- | Promise<Result<unknown, BaseError>>
3185
- | Result<unknown, BaseError>)({
3186
- input,
3187
- context,
3188
- client: this.#handlerTrellis,
3189
- deps,
3190
- }),
3191
- ),
3192
- ));
3193
- }
3194
-
3195
- const feed: ServiceHandleFacade["feed"] = {};
3196
- for (const feedName of Object.keys(this.#server.api.feeds ?? {})) {
3197
- addSurfaceLeaf(
3198
- feed,
3199
- feedName,
3200
- (handler) =>
3201
- this.#server.feedHandle(feedName).handle((context) =>
3202
- (handler as (args: unknown) => unknown | Promise<unknown>)({
3203
- ...context,
3204
- client: this.#handlerTrellis,
3205
- deps,
3206
- })
3207
- ),
3208
- );
3209
- }
3210
-
3211
- const operation: Record<
3212
- string,
3213
- Record<string, ServiceHandleOperationLeaf>
3214
- > = {};
3215
- for (
3216
- const operationName of Object.keys(this.#server.api.operations ?? {})
3217
- ) {
3218
- const registration = this.#operation(
3219
- operationName as keyof TOwnedApi["operations"] & string,
3220
- );
3221
- const leaf = Object.assign(
3222
- (handler: (context: unknown) => unknown) =>
3223
- registration.handle((context) =>
3224
- handler({
3225
- ...context,
3226
- client: this.#handlerTrellis,
3227
- deps,
3228
- })
3229
- ),
3230
- {
3231
- accept: (args: { sessionKey: string }) => registration.accept(args),
3232
- control: (operationId: string) => registration.control(operationId),
3233
- },
3234
- ) as ServiceHandleOperationLeaf;
3235
- addSurfaceLeaf(operation, operationName, leaf);
3236
- }
3164
+ #createSqlOutboxBinding<TTx>(
3165
+ options: TrellisServiceSqlOutboxOptions<TTx>,
3166
+ ): {
3167
+ readonly dialect: SqlDialect;
3168
+ readonly tables: SqlOutboxTables;
3169
+ readonly transaction: SqlOutboxTransactionRunner<TTx>;
3170
+ readonly dispatcher: OutboxDispatcher;
3171
+ } {
3172
+ const tables = resolveSqlOutboxTables(options.tables);
3173
+ const executor = createSqlOutboxBaseExecutor(options);
3174
+ const repository = new SqlOutboxRepository(
3175
+ executor,
3176
+ options.dialect,
3177
+ tables,
3178
+ );
3179
+ const dispatchRuntime: OutboxDispatchRuntime = {
3180
+ publishPreparedEvent: (event) =>
3181
+ this.#handlerTrellis.publishPrepared(event),
3182
+ // dispatchJobSubmission: stub — filled in by Phase 3
3183
+ };
3184
+ const dispatcher = new OutboxDispatcher(
3185
+ repository,
3186
+ dispatchRuntime,
3187
+ options.dispatcher,
3188
+ );
3189
+ this.#ownedOutboxDispatchers.add(dispatcher);
3190
+ return {
3191
+ dialect: options.dialect,
3192
+ tables,
3193
+ transaction: createSqlOutboxTransactionRunner(options),
3194
+ dispatcher,
3195
+ };
3196
+ }
3237
3197
 
3238
- return { rpc, feed, operation } as BoundTypedServiceHandleFacade<
3239
- TOwnedApi,
3240
- TTrellisApi,
3241
- TKv,
3242
- TJobs,
3243
- TDeps
3244
- >;
3198
+ #createSqlOutbox<TTx>(binding: {
3199
+ readonly dialect: SqlDialect;
3200
+ readonly tables: SqlOutboxTables;
3201
+ readonly transaction: SqlOutboxTransactionRunner<TTx>;
3202
+ readonly dispatcher: OutboxDispatcher;
3203
+ }): SqlOutbox<TTx, TTrellisApi, TJobs> {
3204
+ return {
3205
+ transaction: <TResult>(
3206
+ work: (
3207
+ context: SqlOutboxTransactionContext<TTx, TTrellisApi, TJobs>,
3208
+ ) => Promise<TResult> | TResult,
3209
+ ) =>
3210
+ AsyncResult.from((() => {
3211
+ let enqueued = 0;
3212
+ const toResultError = (
3213
+ cause: unknown,
3214
+ ): Result<TResult, ValidationError | UnexpectedError> => {
3215
+ if (
3216
+ cause instanceof ValidationError ||
3217
+ cause instanceof UnexpectedError
3218
+ ) {
3219
+ return Result.err(cause);
3220
+ }
3221
+ return Result.err(toUnexpectedError(cause));
3222
+ };
3223
+ let transaction: Promise<TResult>;
3224
+ try {
3225
+ transaction = binding.transaction<TResult>(({ tx, executor }) => {
3226
+ const repository = new SqlOutboxRepository(
3227
+ executor,
3228
+ binding.dialect,
3229
+ binding.tables,
3230
+ );
3231
+ const event = createSqlOutboxEventEnqueueFacade({
3232
+ event: this.event,
3233
+ repository,
3234
+ onEnqueued: () => {
3235
+ enqueued += 1;
3236
+ },
3237
+ });
3238
+ const job = createSqlOutboxJobEnqueueFacade({
3239
+ serviceName: this.name,
3240
+ contractJobs: this.#contractJobs,
3241
+ jobsBinding: this.#jobsBinding,
3242
+ repository,
3243
+ onEnqueued: () => {
3244
+ enqueued += 1;
3245
+ },
3246
+ });
3247
+ return work({ tx, event, job });
3248
+ });
3249
+ } catch (cause) {
3250
+ return Promise.resolve(toResultError(cause));
3251
+ }
3252
+ return transaction.then((result) => {
3253
+ if (enqueued > 0) binding.dispatcher.notify();
3254
+ return Result.ok(result);
3255
+ }, toResultError);
3256
+ })()),
3257
+ };
3245
3258
  }
3246
3259
 
3247
3260
  /**
@@ -3339,6 +3352,11 @@ export class TrellisService<
3339
3352
  async stop(): Promise<void> {
3340
3353
  this.#stopPromise ??= (async () => {
3341
3354
  this.connection.stopObserving();
3355
+ this.#handlerTrellis.stopEventListeners();
3356
+ for (const dispatcher of this.#ownedOutboxDispatchers) {
3357
+ dispatcher.stop();
3358
+ }
3359
+ this.#ownedOutboxDispatchers.clear();
3342
3360
 
3343
3361
  try {
3344
3362
  await this.#stopHealthPublishing();
@@ -3373,7 +3391,8 @@ export class TrellisService<
3373
3391
  InferSchemaType<TOwnedApi["operations"][O]["input"]>,
3374
3392
  OperationProgressOf<TOwnedApi, O>,
3375
3393
  OperationOutputOf<TOwnedApi, O>,
3376
- OperationTransferContextOf<TOwnedApi, O>
3394
+ OperationTransferContextOf<TOwnedApi, O>,
3395
+ BaseError
3377
3396
  >;
3378
3397
 
3379
3398
  return {
@@ -3386,7 +3405,8 @@ export class TrellisService<
3386
3405
  InferSchemaType<TOwnedApi["operations"][O]["input"]>,
3387
3406
  OperationProgressOf<TOwnedApi, O>,
3388
3407
  OperationOutputOf<TOwnedApi, O>,
3389
- OperationTransferContextOf<TOwnedApi, O>
3408
+ OperationTransferContextOf<TOwnedApi, O>,
3409
+ BaseError
3390
3410
  >
3391
3411
  & { client: Trellis<TTrellisApi, TKv, TJobs> },
3392
3412
  ) => unknown | Promise<unknown>,