@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
@@ -5,6 +5,7 @@ import {
5
5
  injectTraceContext,
6
6
  } from "../../telemetry/carrier.js";
7
7
  import { recordTrellisError } from "../../telemetry/mod.js";
8
+ import { JobNotEnqueuedError } from "../../jobs.js";
8
9
 
9
10
  import {
10
11
  ActiveJob,
@@ -12,6 +13,15 @@ import {
12
13
  JobCancellationToken,
13
14
  } from "./active-job.js";
14
15
  import type { JobsBinding, JobsQueueBinding } from "./bindings.js";
16
+ import type {
17
+ ActiveSlotLease,
18
+ JobAdmissionOutcome,
19
+ JobKeyActiveSlot,
20
+ JobKeyCoordinator,
21
+ NormalizedJobKeyPolicy,
22
+ ReplacedQueuedJob,
23
+ } from "./key-coordinator.js";
24
+ import { normalizeJobKeyPolicy } from "./key-coordinator.js";
15
25
  import type {
16
26
  Job,
17
27
  JobContext,
@@ -36,11 +46,14 @@ type JobMetaSource = {
36
46
  type JobManagerContext = {
37
47
  nc: Publisher;
38
48
  jobs?: JobsBinding;
49
+ keyCoordinator?: JobKeyCoordinator;
39
50
  meta?: JobMetaSource;
40
51
  };
41
52
 
42
53
  type ActiveJobRuntimeMetadata = {
43
54
  redeliveryCount?: number;
55
+ instanceId?: string;
56
+ latestState?: Job["state"];
44
57
  };
45
58
 
46
59
  type JobProcessValidation<TPayload, TResult> = {
@@ -73,8 +86,44 @@ export type JobProcessOutcome<TResult> =
73
86
  | { outcome: "retry"; tries: number; error: string }
74
87
  | { outcome: "failed"; tries: number; error: string }
75
88
  | { outcome: "cancelled"; tries: number }
89
+ | { outcome: "deferred"; tries: number; reason: string }
90
+ | { outcome: "stale_completion_ignored"; tries: number }
76
91
  | { outcome: "interrupted"; tries: number };
77
92
 
93
+ export type JobManagerSubmitOutcome<TPayload, TResult> =
94
+ | { kind: "accepted"; job: Job<TPayload, TResult>; key?: string }
95
+ | {
96
+ kind: "rejected";
97
+ key: string;
98
+ reason: "active-limit" | "queue-depth" | "stale-blocked";
99
+ active: number;
100
+ queued: number;
101
+ limit: number;
102
+ }
103
+ | {
104
+ kind: "coalesced";
105
+ key: string;
106
+ existing: { service: string; jobType: string; id: string };
107
+ reason: string;
108
+ }
109
+ | {
110
+ kind: "replaced";
111
+ key: string;
112
+ replaced: { service: string; jobType: string; id: string };
113
+ job: Job<TPayload, TResult>;
114
+ };
115
+
116
+ export type PreparedJobSubmission<TPayload = unknown> = {
117
+ readonly submissionId: string;
118
+ readonly mode: "create" | "submit";
119
+ readonly service: string;
120
+ readonly queue: string;
121
+ readonly jobId: string;
122
+ readonly payload: TPayload;
123
+ readonly createdAt: string;
124
+ readonly context: JobContext;
125
+ };
126
+
78
127
  export class JobManager<TPayload = unknown, TResult = unknown> {
79
128
  readonly #context: JobManagerContext;
80
129
 
@@ -101,9 +150,10 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
101
150
  type: string,
102
151
  jobId: string,
103
152
  event: JobEvent<TPayload, TResult>,
153
+ stableMessageId?: string,
104
154
  ): Promise<void> {
105
155
  const binding = this.#getQueueBinding(type);
106
- const headers = headersFromJobContext(event.context);
156
+ const headers = headersFromJobContext(event.context, stableMessageId);
107
157
  try {
108
158
  await this.#context.nc.publish(
109
159
  `${binding.publishPrefix}.${jobId}.${event.eventType}`,
@@ -126,17 +176,131 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
126
176
  type: string,
127
177
  payload: TPayload,
128
178
  ): Promise<Job<TPayload, TResult>> {
129
- const binding = this.#getQueueBinding(type);
130
- const meta = this.#meta();
179
+ const outcome = await this.#submit(type, payload, true);
180
+ if (outcome.kind === "accepted") {
181
+ return outcome.job;
182
+ }
183
+ if (outcome.kind === "coalesced") {
184
+ throw new JobNotEnqueuedError({
185
+ reason: "coalesced",
186
+ key: outcome.key,
187
+ active: 0,
188
+ queued: 1,
189
+ limit: 0,
190
+ existingJobId: outcome.existing.id,
191
+ });
192
+ }
193
+ if (outcome.kind === "replaced") {
194
+ throw new JobNotEnqueuedError({
195
+ reason: "queue-depth",
196
+ key: outcome.key,
197
+ active: 0,
198
+ queued: 1,
199
+ limit: 0,
200
+ existingJobId: outcome.replaced.id,
201
+ });
202
+ }
203
+ throw new JobNotEnqueuedError({
204
+ reason: outcome.reason,
205
+ key: outcome.key,
206
+ active: outcome.active,
207
+ queued: outcome.queued,
208
+ limit: outcome.limit,
209
+ });
210
+ }
211
+
212
+ async submit(
213
+ type: string,
214
+ payload: TPayload,
215
+ ): Promise<JobManagerSubmitOutcome<TPayload, TResult>> {
216
+ return await this.#submit(type, payload, false);
217
+ }
131
218
 
219
+ async createPrepared(
220
+ submission: PreparedJobSubmission<TPayload>,
221
+ ): Promise<Job<TPayload, TResult>> {
222
+ const outcome = await this.#submitPrepared(submission, true, true);
223
+ if (outcome.kind === "accepted") {
224
+ return outcome.job;
225
+ }
226
+ if (outcome.kind === "coalesced") {
227
+ throw new JobNotEnqueuedError({
228
+ reason: "coalesced",
229
+ key: outcome.key,
230
+ active: 0,
231
+ queued: 1,
232
+ limit: 0,
233
+ existingJobId: outcome.existing.id,
234
+ });
235
+ }
236
+ if (outcome.kind === "replaced") {
237
+ throw new JobNotEnqueuedError({
238
+ reason: "queue-depth",
239
+ key: outcome.key,
240
+ active: 0,
241
+ queued: 1,
242
+ limit: 0,
243
+ existingJobId: outcome.replaced.id,
244
+ });
245
+ }
246
+ throw new JobNotEnqueuedError({
247
+ reason: outcome.reason,
248
+ key: outcome.key,
249
+ active: outcome.active,
250
+ queued: outcome.queued,
251
+ limit: outcome.limit,
252
+ });
253
+ }
254
+
255
+ async submitPrepared(
256
+ submission: PreparedJobSubmission<TPayload>,
257
+ ): Promise<JobManagerSubmitOutcome<TPayload, TResult>> {
258
+ return await this.#submitPrepared(submission, false, true);
259
+ }
260
+
261
+ async #submit(
262
+ type: string,
263
+ payload: TPayload,
264
+ strictCreate: boolean,
265
+ ): Promise<JobManagerSubmitOutcome<TPayload, TResult>> {
266
+ const meta = this.#meta();
132
267
  const now = meta.nowIso();
133
268
  const id = meta.nextJobId();
134
269
  const context = createJobContext();
135
270
  const namespace = this.#context.jobs!.namespace;
271
+
272
+ const submission: PreparedJobSubmission<TPayload> = {
273
+ submissionId: id,
274
+ mode: strictCreate ? "create" : "submit",
275
+ service: namespace,
276
+ queue: type,
277
+ jobId: id,
278
+ payload,
279
+ createdAt: now,
280
+ context,
281
+ };
282
+
283
+ return await this.#submitPrepared(submission, strictCreate);
284
+ }
285
+
286
+ async #submitPrepared(
287
+ submission: PreparedJobSubmission<TPayload>,
288
+ strictCreate: boolean,
289
+ stableMessageIds?: boolean,
290
+ ): Promise<JobManagerSubmitOutcome<TPayload, TResult>> {
291
+ const {
292
+ queue: type,
293
+ jobId: id,
294
+ createdAt: now,
295
+ context,
296
+ service,
297
+ payload,
298
+ } = submission;
299
+ const binding = this.#getQueueBinding(type);
136
300
  const deadline = computeDeadline(now, binding.defaultDeadlineMs);
137
301
  const job: Job<TPayload, TResult> = {
138
302
  id,
139
- service: namespace,
303
+ service,
140
304
  type,
141
305
  state: "pending",
142
306
  context,
@@ -149,7 +313,7 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
149
313
  };
150
314
  const event: JobEvent<TPayload, TResult> = {
151
315
  jobId: id,
152
- service: job.service,
316
+ service,
153
317
  jobType: type,
154
318
  eventType: "created",
155
319
  state: "pending",
@@ -161,9 +325,176 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
161
325
  timestamp: now,
162
326
  };
163
327
 
164
- await this.#publishJobEvent(type, id, event);
328
+ const keyedPolicy = getKeyPolicy(binding);
329
+ if (keyedPolicy) {
330
+ const admission = await this.#admitKeyedCreate({
331
+ type,
332
+ job,
333
+ event,
334
+ policy: keyedPolicy,
335
+ strictCreate,
336
+ submissionId: submission.submissionId,
337
+ });
338
+ if (admission.kind !== "accepted" && admission.kind !== "replaced") {
339
+ return admission;
340
+ }
341
+ if (strictCreate && admission.kind === "replaced") {
342
+ await this.#restoreReplacedKeyedReservation(
343
+ job,
344
+ admission.replaced,
345
+ keyedPolicy,
346
+ submission.submissionId,
347
+ );
348
+ return {
349
+ kind: "rejected",
350
+ key: admission.key,
351
+ reason: "queue-depth",
352
+ active: admission.state.active.length,
353
+ queued: admission.state.queued.length,
354
+ limit: keyedPolicy.queue.maxQueuedPerKey,
355
+ };
356
+ }
357
+ if (admission.kind === "replaced") {
358
+ try {
359
+ const skippedMsgId = stableMessageIds
360
+ ? `trellis-job-skipped:${submission.submissionId}:${admission.replaced.id}`
361
+ : undefined;
362
+ await this.#publishSkipped(
363
+ type,
364
+ admission.replaced,
365
+ now,
366
+ skippedMsgId,
367
+ );
368
+ } catch (error) {
369
+ await this.#restoreReplacedKeyedReservation(
370
+ job,
371
+ admission.replaced,
372
+ keyedPolicy,
373
+ submission.submissionId,
374
+ );
375
+ throw error;
376
+ }
377
+ }
378
+ try {
379
+ const createdMsgId = stableMessageIds
380
+ ? `trellis-job-created:${submission.submissionId}`
381
+ : undefined;
382
+ await this.#publishJobEvent(type, id, event, createdMsgId);
383
+ } catch (error) {
384
+ await this.#removeQueuedKeyedReservation(job, keyedPolicy);
385
+ throw error;
386
+ }
387
+ if (admission.kind === "replaced") {
388
+ return {
389
+ kind: "replaced",
390
+ key: admission.key,
391
+ replaced: {
392
+ service: admission.replaced.service,
393
+ jobType: admission.replaced.jobType,
394
+ id: admission.replaced.id,
395
+ },
396
+ job,
397
+ };
398
+ }
399
+ return { kind: "accepted", job, key: admission.key };
400
+ }
165
401
 
166
- return job;
402
+ const createdMsgId = stableMessageIds
403
+ ? `trellis-job-created:${submission.submissionId}`
404
+ : undefined;
405
+ await this.#publishJobEvent(type, id, event, createdMsgId);
406
+
407
+ return { kind: "accepted", job };
408
+ }
409
+
410
+ async #admitKeyedCreate(args: {
411
+ type: string;
412
+ job: Job<TPayload, TResult>;
413
+ event: JobEvent<TPayload, TResult>;
414
+ policy: NormalizedJobKeyPolicy;
415
+ strictCreate: boolean;
416
+ submissionId?: string;
417
+ }): Promise<JobAdmissionOutcome> {
418
+ const coordinator = this.#context.keyCoordinator;
419
+ if (!coordinator) {
420
+ throw new Error(
421
+ `Keyed jobs queue '${args.type}' requires the JOBS_KEYS coordinator`,
422
+ );
423
+ }
424
+ return await coordinator.admitCreate({
425
+ service: args.job.service,
426
+ jobType: args.type,
427
+ jobId: args.job.id,
428
+ payload: args.job.payload,
429
+ context: args.event.context,
430
+ createdAt: args.event.timestamp,
431
+ policy: args.policy,
432
+ strictCreate: args.strictCreate,
433
+ ...(args.submissionId ? { submissionId: args.submissionId } : {}),
434
+ });
435
+ }
436
+
437
+ async cleanupQueuedKeyedJob(job: Job<TPayload, TResult>): Promise<void> {
438
+ const queue = this.#getQueueBinding(job.type);
439
+ const keyedPolicy = getKeyPolicy(queue);
440
+ if (!keyedPolicy) return;
441
+ await this.#removeQueuedKeyedReservation(job, keyedPolicy);
442
+ }
443
+
444
+ async #removeQueuedKeyedReservation(
445
+ job: Job<TPayload, TResult>,
446
+ policy: NormalizedJobKeyPolicy,
447
+ ): Promise<void> {
448
+ const coordinator = this.#context.keyCoordinator;
449
+ if (!coordinator) return;
450
+ await coordinator.removeQueuedJob({
451
+ service: job.service,
452
+ jobType: job.type,
453
+ jobId: job.id,
454
+ payload: job.payload,
455
+ now: this.#meta().nowIso(),
456
+ policy,
457
+ });
458
+ }
459
+
460
+ async #restoreReplacedKeyedReservation(
461
+ job: Job<TPayload, TResult>,
462
+ replaced: ReplacedQueuedJob,
463
+ policy: NormalizedJobKeyPolicy,
464
+ submissionId?: string,
465
+ ): Promise<void> {
466
+ const coordinator = this.#context.keyCoordinator;
467
+ if (!coordinator) return;
468
+ await coordinator.restoreReplacedQueuedJob({
469
+ service: job.service,
470
+ jobType: job.type,
471
+ replacementJobId: job.id,
472
+ replaced,
473
+ payload: job.payload,
474
+ now: this.#meta().nowIso(),
475
+ policy,
476
+ ...(submissionId ? { submissionId } : {}),
477
+ });
478
+ }
479
+
480
+ async #publishSkipped(
481
+ type: string,
482
+ replaced: { id: string; context: JobContext },
483
+ timestamp: string,
484
+ stableMessageId?: string,
485
+ ): Promise<void> {
486
+ await this.#publishJobEvent(type, replaced.id, {
487
+ jobId: replaced.id,
488
+ service: this.#context.jobs!.namespace,
489
+ jobType: type,
490
+ eventType: "skipped",
491
+ state: "skipped",
492
+ previousState: "pending",
493
+ context: replaced.context,
494
+ tries: 0,
495
+ error: "replaced by newer keyed job",
496
+ timestamp,
497
+ }, stableMessageId);
167
498
  }
168
499
 
169
500
  process(
@@ -193,34 +524,89 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
193
524
  metadata: ActiveJobRuntimeMetadata = {},
194
525
  validation: JobProcessValidation<TPayload, TResult> = {},
195
526
  ): Promise<JobProcessOutcome<TResult>> {
196
- this.#getQueueBinding(job.type);
197
-
198
527
  const tries = job.tries + 1;
199
- await this.#publishJobEvent(job.type, job.id, {
200
- jobId: job.id,
201
- service: job.service,
202
- jobType: job.type,
203
- eventType: "started",
204
- state: "active",
205
- previousState: job.state,
206
- context: job.context,
207
- tries,
208
- timestamp: this.#meta().nowIso(),
209
- });
528
+ const queue = this.#getQueueBinding(job.type);
529
+ const keyedPolicy = getKeyPolicy(queue);
530
+ let lease: ActiveSlotLease | undefined;
531
+ let staleSlots: JobKeyActiveSlot[] = [];
532
+ if (keyedPolicy) {
533
+ const coordinator = this.#context.keyCoordinator;
534
+ if (!coordinator) {
535
+ throw new Error(
536
+ `Keyed jobs queue '${job.type}' requires the JOBS_KEYS coordinator`,
537
+ );
538
+ }
539
+ const acquired = await coordinator.acquireActiveSlot({
540
+ service: job.service,
541
+ jobType: job.type,
542
+ jobId: job.id,
543
+ payload: job.payload,
544
+ context: job.context,
545
+ lifecycleState: metadata.latestState ?? job.state,
546
+ tries,
547
+ instanceId: metadata.instanceId ?? "unknown",
548
+ now: this.#meta().nowIso(),
549
+ policy: keyedPolicy,
550
+ });
551
+ if (acquired.kind === "blocked") {
552
+ return { outcome: "deferred", tries, reason: acquired.reason };
553
+ }
554
+ lease = {
555
+ key: acquired.key,
556
+ keyHash: acquired.keyHash,
557
+ slotToken: acquired.slotToken,
558
+ policy: keyedPolicy,
559
+ };
560
+ staleSlots = acquired.stale;
561
+ }
562
+
563
+ try {
564
+ for (const stale of staleSlots) {
565
+ await this.#publishJobEvent(job.type, stale.jobId, {
566
+ jobId: stale.jobId,
567
+ service: job.service,
568
+ jobType: job.type,
569
+ eventType: "stale",
570
+ state: "stale",
571
+ previousState: "active",
572
+ context: stale.context,
573
+ tries: stale.tries,
574
+ error: "keyed job lease expired",
575
+ timestamp: this.#meta().nowIso(),
576
+ });
577
+ }
578
+ await this.#publishJobEvent(job.type, job.id, {
579
+ jobId: job.id,
580
+ service: job.service,
581
+ jobType: job.type,
582
+ eventType: "started",
583
+ state: "active",
584
+ previousState: job.state,
585
+ context: job.context,
586
+ tries,
587
+ timestamp: this.#meta().nowIso(),
588
+ });
589
+ } catch (error) {
590
+ await this.#releaseKeyedSlotAfterPublishFailure(job, lease, error);
591
+ throw error;
592
+ }
210
593
 
211
594
  try {
212
595
  const result = await this.withActiveJobAndHeartbeat(
213
596
  { ...job, state: "active", tries },
214
597
  cancellation,
215
- heartbeat,
598
+ lease ? this.#keyedHeartbeat(job, lease, heartbeat) : heartbeat,
216
599
  handler,
217
600
  metadata,
601
+ lease,
218
602
  );
219
603
 
220
604
  if (cancellation.isHostShutdown()) {
605
+ await this.#releaseKeyedSlot(job, lease);
221
606
  return { outcome: "interrupted", tries };
222
607
  }
223
608
  if (cancellation.isCancelled()) {
609
+ await this.#releaseKeyedSlot(job, lease);
224
610
  return { outcome: "cancelled", tries };
225
611
  }
226
612
  try {
@@ -235,6 +621,11 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
235
621
  );
236
622
  }
237
623
 
624
+ const release = await this.#releaseKeyedSlot(job, lease);
625
+ if (release === "staleCompletion") {
626
+ await this.#publishStaleCompletionIgnored(job, tries);
627
+ return { outcome: "stale_completion_ignored", tries };
628
+ }
238
629
  await this.#publishJobEvent(job.type, job.id, {
239
630
  jobId: job.id,
240
631
  service: job.service,
@@ -250,9 +641,11 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
250
641
  return { outcome: "completed", tries, result };
251
642
  } catch (error) {
252
643
  if (cancellation.isHostShutdown()) {
644
+ await this.#releaseKeyedSlot(job, lease);
253
645
  return { outcome: "interrupted", tries };
254
646
  }
255
647
  if (cancellation.isCancelled()) {
648
+ await this.#releaseKeyedSlot(job, lease);
256
649
  return { outcome: "cancelled", tries };
257
650
  }
258
651
 
@@ -265,6 +658,11 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
265
658
  operation: job.type,
266
659
  phase: "handler_result",
267
660
  });
661
+ const release = await this.#releaseKeyedSlot(job, lease);
662
+ if (release === "staleCompletion") {
663
+ await this.#publishStaleCompletionIgnored(job, tries);
664
+ return { outcome: "stale_completion_ignored", tries };
665
+ }
268
666
  await this.#publishJobEvent(job.type, job.id, {
269
667
  jobId: job.id,
270
668
  service: job.service,
@@ -286,6 +684,11 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
286
684
  operation: job.type,
287
685
  phase: "handler_result",
288
686
  });
687
+ const release = await this.#releaseKeyedSlot(job, lease);
688
+ if (release === "staleCompletion") {
689
+ await this.#publishStaleCompletionIgnored(job, tries);
690
+ return { outcome: "stale_completion_ignored", tries };
691
+ }
289
692
  await this.#publishJobEvent(job.type, job.id, {
290
693
  jobId: job.id,
291
694
  service: job.service,
@@ -385,13 +788,101 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
385
788
  );
386
789
  }
387
790
 
791
+ #keyedHeartbeat(
792
+ job: Job<TPayload, TResult>,
793
+ lease: ActiveSlotLease,
794
+ heartbeat: () => Promise<void>,
795
+ ): () => Promise<void> {
796
+ return async () => {
797
+ await heartbeat();
798
+ const renewed = await this.#context.keyCoordinator!.renewHeartbeat({
799
+ service: job.service,
800
+ jobType: job.type,
801
+ jobId: job.id,
802
+ lease,
803
+ now: this.#meta().nowIso(),
804
+ });
805
+ if (renewed.kind === "lost") {
806
+ recordTrellisError(
807
+ new ActiveJobRuntimeError("keyed job slot lease was lost"),
808
+ {
809
+ surface: "job",
810
+ direction: "worker",
811
+ operation: job.type,
812
+ phase: "heartbeat",
813
+ },
814
+ );
815
+ }
816
+ };
817
+ }
818
+
819
+ async #releaseKeyedSlot(
820
+ job: Job<TPayload, TResult>,
821
+ lease: ActiveSlotLease | undefined,
822
+ ): Promise<"released" | "staleCompletion" | undefined> {
823
+ if (!lease) return undefined;
824
+ const released = await this.#context.keyCoordinator!.releaseActiveSlot({
825
+ service: job.service,
826
+ jobType: job.type,
827
+ jobId: job.id,
828
+ lease,
829
+ now: this.#meta().nowIso(),
830
+ });
831
+ return released.kind === "released" ? "released" : "staleCompletion";
832
+ }
833
+
834
+ async #releaseKeyedSlotAfterPublishFailure(
835
+ job: Job<TPayload, TResult>,
836
+ lease: ActiveSlotLease | undefined,
837
+ publishError: unknown,
838
+ ): Promise<void> {
839
+ try {
840
+ await this.#releaseKeyedSlot(job, lease);
841
+ } catch (cleanupError) {
842
+ recordTrellisError(cleanupError, {
843
+ surface: "job",
844
+ direction: "worker",
845
+ operation: job.type,
846
+ phase: "keyed_slot_cleanup",
847
+ });
848
+ recordTrellisError(publishError, {
849
+ surface: "job",
850
+ direction: "worker",
851
+ operation: job.type,
852
+ phase: "publish",
853
+ });
854
+ }
855
+ }
856
+
857
+ async #publishStaleCompletionIgnored(
858
+ job: Job<TPayload, TResult>,
859
+ tries: number,
860
+ ): Promise<void> {
861
+ await this.#publishJobEvent(job.type, job.id, {
862
+ jobId: job.id,
863
+ service: job.service,
864
+ jobType: job.type,
865
+ eventType: "staleCompletionIgnored",
866
+ state: "stale",
867
+ previousState: "active",
868
+ context: job.context,
869
+ tries,
870
+ error: "keyed job slot lease was lost before completion",
871
+ timestamp: this.#meta().nowIso(),
872
+ });
873
+ }
874
+
388
875
  async withActiveJobAndHeartbeat<T>(
389
876
  job: Job<TPayload, TResult>,
390
877
  cancellation: JobCancellationToken,
391
878
  heartbeat: () => Promise<void>,
392
879
  f: (job: ActiveJob<TPayload, TResult>) => Promise<T>,
393
880
  metadata: ActiveJobRuntimeMetadata = {},
881
+ lease?: ActiveSlotLease,
394
882
  ): Promise<T> {
883
+ const stopAutoHeartbeat = lease
884
+ ? startAutoHeartbeat(heartbeat, lease.policy.heartbeatIntervalMs)
885
+ : () => {};
395
886
  const activeJob = new ActiveJob(job, cancellation, {
396
887
  updateProgress: (progress) => this.emitProgress(job, progress),
397
888
  log: (entry) => this.emitLog(job, entry),
@@ -411,10 +902,42 @@ export class JobManager<TPayload = unknown, TResult = unknown> {
411
902
  redeliveryCount: metadata.redeliveryCount ?? 0,
412
903
  });
413
904
 
414
- return await f(activeJob);
905
+ try {
906
+ return await f(activeJob);
907
+ } finally {
908
+ stopAutoHeartbeat();
909
+ }
415
910
  }
416
911
  }
417
912
 
913
+ function getKeyPolicy(
914
+ binding: JobsQueueBinding,
915
+ ): NormalizedJobKeyPolicy | undefined {
916
+ if (!binding.keyConcurrency) {
917
+ return undefined;
918
+ }
919
+ return normalizeJobKeyPolicy({
920
+ keyConcurrency: binding.keyConcurrency,
921
+ queue: binding.queue,
922
+ });
923
+ }
924
+
925
+ function startAutoHeartbeat(
926
+ heartbeat: () => Promise<void>,
927
+ intervalMs: number,
928
+ ): () => void {
929
+ const timer = setInterval(() => {
930
+ void heartbeat().catch((error) => {
931
+ recordTrellisError(error, {
932
+ surface: "job",
933
+ direction: "worker",
934
+ phase: "heartbeat",
935
+ });
936
+ });
937
+ }, intervalMs);
938
+ return () => clearInterval(timer);
939
+ }
940
+
418
941
  function computeDeadline(now: string, deadlineMs?: number): string | undefined {
419
942
  if (deadlineMs === undefined) {
420
943
  return undefined;
@@ -425,7 +948,7 @@ function computeDeadline(now: string, deadlineMs?: number): string | undefined {
425
948
  return timestamp.toISOString();
426
949
  }
427
950
 
428
- function createJobContext(): JobContext {
951
+ export function createJobContext(): JobContext {
429
952
  const carrier = createMapCarrier();
430
953
  injectTraceContext(carrier);
431
954
  const inheritedTraceparent = carrier.get("traceparent");
@@ -442,13 +965,19 @@ function createJobContext(): JobContext {
442
965
  };
443
966
  }
444
967
 
445
- function headersFromJobContext(context: JobContext): MsgHdrs {
968
+ function headersFromJobContext(
969
+ context: JobContext,
970
+ stableMessageId?: string,
971
+ ): MsgHdrs {
446
972
  const headers = natsHeaders();
447
973
  headers.set("request-id", context.requestId);
448
974
  headers.set("traceparent", context.traceparent);
449
975
  if (context.tracestate) {
450
976
  headers.set("tracestate", context.tracestate);
451
977
  }
978
+ if (stableMessageId) {
979
+ headers.set("Nats-Msg-Id", stableMessageId);
980
+ }
452
981
  return headers;
453
982
  }
454
983