@qlever-llc/trellis 0.8.3 → 0.9.0-rc.10

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 (693) hide show
  1. package/README.md +1 -1
  2. package/bin/trellis-generate.js +132 -0
  3. package/esm/auth/browser/login.d.ts.map +1 -1
  4. package/esm/auth/browser/login.js +46 -3
  5. package/esm/auth/browser/portal.d.ts.map +1 -1
  6. package/esm/auth/browser/portal.js +5 -1
  7. package/esm/auth/browser/session.d.ts +18 -7
  8. package/esm/auth/browser/session.d.ts.map +1 -1
  9. package/esm/auth/browser/session.js +47 -11
  10. package/esm/auth/browser/storage.d.ts +6 -1
  11. package/esm/auth/browser/storage.d.ts.map +1 -1
  12. package/esm/auth/browser/storage.js +15 -3
  13. package/esm/auth/browser.d.ts +2 -2
  14. package/esm/auth/browser.d.ts.map +1 -1
  15. package/esm/auth/browser.js +1 -1
  16. package/esm/auth/device_activation.d.ts +36 -33
  17. package/esm/auth/device_activation.d.ts.map +1 -1
  18. package/esm/auth/device_activation.js +26 -22
  19. package/esm/auth/mod.d.ts +4 -4
  20. package/esm/auth/mod.d.ts.map +1 -1
  21. package/esm/auth/mod.js +2 -2
  22. package/esm/auth/proof.d.ts +3 -1
  23. package/esm/auth/proof.d.ts.map +1 -1
  24. package/esm/auth/proof.js +21 -15
  25. package/esm/auth/protocol.d.ts +2457 -941
  26. package/esm/auth/protocol.d.ts.map +1 -1
  27. package/esm/auth/protocol.js +747 -375
  28. package/esm/auth/schemas.d.ts +25 -4
  29. package/esm/auth/schemas.d.ts.map +1 -1
  30. package/esm/auth/schemas.js +14 -4
  31. package/esm/auth/session_auth.d.ts +1 -1
  32. package/esm/auth/session_auth.d.ts.map +1 -1
  33. package/esm/auth/session_auth.js +7 -1
  34. package/esm/client_connect.d.ts +2 -0
  35. package/esm/client_connect.d.ts.map +1 -1
  36. package/esm/client_connect.js +76 -15
  37. package/esm/contract.d.ts +3 -0
  38. package/esm/contract.d.ts.map +1 -1
  39. package/esm/contract_support/mod.d.ts +422 -43
  40. package/esm/contract_support/mod.d.ts.map +1 -1
  41. package/esm/contract_support/mod.js +734 -33
  42. package/esm/contract_support/protocol.d.ts +20 -5
  43. package/esm/contract_support/protocol.d.ts.map +1 -1
  44. package/esm/contract_support/protocol.js +18 -10
  45. package/esm/contract_support/runtime.d.ts +11 -0
  46. package/esm/contract_support/runtime.d.ts.map +1 -1
  47. package/esm/contract_support/schema_pointers.d.ts.map +1 -1
  48. package/esm/contract_support/schema_pointers.js +32 -14
  49. package/esm/device.d.ts +2 -0
  50. package/esm/device.d.ts.map +1 -1
  51. package/esm/device.js +3 -0
  52. package/esm/errors/AuthError.d.ts +2 -1
  53. package/esm/errors/AuthError.d.ts.map +1 -1
  54. package/esm/errors/AuthError.js +8 -3
  55. package/esm/errors/index.d.ts +4 -4
  56. package/esm/errors/index.d.ts.map +1 -1
  57. package/esm/errors/index.js +1 -0
  58. package/esm/generated-sdk/auth/api.d.ts +27 -9
  59. package/esm/generated-sdk/auth/api.d.ts.map +1 -1
  60. package/esm/generated-sdk/auth/api.js +16 -590
  61. package/esm/generated-sdk/auth/client.d.ts +91 -85
  62. package/esm/generated-sdk/auth/client.d.ts.map +1 -1
  63. package/esm/generated-sdk/auth/contract.d.ts +1 -1
  64. package/esm/generated-sdk/auth/contract.d.ts.map +1 -1
  65. package/esm/generated-sdk/auth/contract.js +4 -2
  66. package/esm/generated-sdk/auth/mod.d.ts +1 -0
  67. package/esm/generated-sdk/auth/mod.d.ts.map +1 -1
  68. package/esm/generated-sdk/auth/owned_api.d.ts +3 -0
  69. package/esm/generated-sdk/auth/owned_api.d.ts.map +1 -0
  70. package/esm/generated-sdk/auth/owned_api.js +594 -0
  71. package/esm/generated-sdk/auth/schemas.d.ts +9959 -5160
  72. package/esm/generated-sdk/auth/schemas.d.ts.map +1 -1
  73. package/esm/generated-sdk/auth/schemas.js +136 -137
  74. package/esm/generated-sdk/auth/types.d.ts +2418 -1557
  75. package/esm/generated-sdk/auth/types.d.ts.map +1 -1
  76. package/esm/generated-sdk/auth/types.js +1 -1
  77. package/esm/generated-sdk/health/api.d.ts +24 -9
  78. package/esm/generated-sdk/health/api.d.ts.map +1 -1
  79. package/esm/generated-sdk/health/api.js +12 -20
  80. package/esm/generated-sdk/health/client.d.ts +2 -1
  81. package/esm/generated-sdk/health/client.d.ts.map +1 -1
  82. package/esm/generated-sdk/health/contract.d.ts.map +1 -1
  83. package/esm/generated-sdk/health/contract.js +2 -0
  84. package/esm/generated-sdk/health/owned_api.d.ts +3 -0
  85. package/esm/generated-sdk/health/owned_api.d.ts.map +1 -0
  86. package/esm/generated-sdk/health/owned_api.js +16 -0
  87. package/esm/generated-sdk/health/types.d.ts +2 -0
  88. package/esm/generated-sdk/health/types.d.ts.map +1 -1
  89. package/esm/generated-sdk/jobs/api.d.ts +33 -9
  90. package/esm/generated-sdk/jobs/api.d.ts.map +1 -1
  91. package/esm/generated-sdk/jobs/api.js +22 -87
  92. package/esm/generated-sdk/jobs/client.d.ts +9 -2
  93. package/esm/generated-sdk/jobs/client.d.ts.map +1 -1
  94. package/esm/generated-sdk/jobs/contract.d.ts +1 -1
  95. package/esm/generated-sdk/jobs/contract.d.ts.map +1 -1
  96. package/esm/generated-sdk/jobs/contract.js +4 -2
  97. package/esm/generated-sdk/jobs/owned_api.d.ts +3 -0
  98. package/esm/generated-sdk/jobs/owned_api.d.ts.map +1 -0
  99. package/esm/generated-sdk/jobs/owned_api.js +118 -0
  100. package/esm/generated-sdk/jobs/schemas.d.ts +336 -123
  101. package/esm/generated-sdk/jobs/schemas.d.ts.map +1 -1
  102. package/esm/generated-sdk/jobs/schemas.js +17 -15
  103. package/esm/generated-sdk/jobs/types.d.ts +144 -34
  104. package/esm/generated-sdk/jobs/types.d.ts.map +1 -1
  105. package/esm/generated-sdk/jobs/types.js +36 -1
  106. package/esm/generated-sdk/state/api.d.ts +27 -9
  107. package/esm/generated-sdk/state/api.d.ts.map +1 -1
  108. package/esm/generated-sdk/state/api.js +16 -71
  109. package/esm/generated-sdk/state/client.d.ts +4 -2
  110. package/esm/generated-sdk/state/client.d.ts.map +1 -1
  111. package/esm/generated-sdk/state/contract.d.ts +1 -1
  112. package/esm/generated-sdk/state/contract.d.ts.map +1 -1
  113. package/esm/generated-sdk/state/contract.js +4 -2
  114. package/esm/generated-sdk/state/owned_api.d.ts +3 -0
  115. package/esm/generated-sdk/state/owned_api.d.ts.map +1 -0
  116. package/esm/generated-sdk/state/owned_api.js +66 -0
  117. package/esm/generated-sdk/state/schemas.d.ts +264 -284
  118. package/esm/generated-sdk/state/schemas.d.ts.map +1 -1
  119. package/esm/generated-sdk/state/schemas.js +6 -6
  120. package/esm/generated-sdk/state/types.d.ts +24 -23
  121. package/esm/generated-sdk/state/types.d.ts.map +1 -1
  122. package/esm/generated-sdk/state/types.js +1 -1
  123. package/esm/generated-sdk/trellis-core/api.d.ts +27 -9
  124. package/esm/generated-sdk/trellis-core/api.d.ts.map +1 -1
  125. package/esm/generated-sdk/trellis-core/api.js +16 -39
  126. package/esm/generated-sdk/trellis-core/client.d.ts +5 -2
  127. package/esm/generated-sdk/trellis-core/client.d.ts.map +1 -1
  128. package/esm/generated-sdk/trellis-core/contract.d.ts +1 -1
  129. package/esm/generated-sdk/trellis-core/contract.d.ts.map +1 -1
  130. package/esm/generated-sdk/trellis-core/contract.js +4 -2
  131. package/esm/generated-sdk/trellis-core/owned_api.d.ts +3 -0
  132. package/esm/generated-sdk/trellis-core/owned_api.d.ts.map +1 -0
  133. package/esm/generated-sdk/trellis-core/owned_api.js +42 -0
  134. package/esm/generated-sdk/trellis-core/schemas.d.ts +259 -11
  135. package/esm/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
  136. package/esm/generated-sdk/trellis-core/schemas.js +5 -3
  137. package/esm/generated-sdk/trellis-core/types.d.ts +56 -1
  138. package/esm/generated-sdk/trellis-core/types.d.ts.map +1 -1
  139. package/esm/generated-sdk/trellis-core/types.js +1 -1
  140. package/esm/helpers.d.ts.map +1 -1
  141. package/esm/index.d.ts +4 -3
  142. package/esm/index.d.ts.map +1 -1
  143. package/esm/index.js +1 -0
  144. package/esm/jobs.d.ts +10 -1
  145. package/esm/jobs.d.ts.map +1 -1
  146. package/esm/jobs.js +16 -1
  147. package/esm/kv.d.ts.map +1 -1
  148. package/esm/kv.js +10 -4
  149. package/esm/models/auth/rpc/Logout.d.ts +4 -4
  150. package/esm/models/auth/rpc/Logout.d.ts.map +1 -1
  151. package/esm/models/auth/rpc/Logout.js +2 -2
  152. package/esm/models/trellis/Page.d.ts +2 -0
  153. package/esm/models/trellis/Page.d.ts.map +1 -0
  154. package/esm/models/trellis/Page.js +1 -0
  155. package/esm/models/trellis/State.d.ts +1 -0
  156. package/esm/models/trellis/State.d.ts.map +1 -1
  157. package/esm/models/trellis/State.js +1 -0
  158. package/esm/models/trellis/rpc/StateList.d.ts +9 -12
  159. package/esm/models/trellis/rpc/StateList.d.ts.map +1 -1
  160. package/esm/models/trellis/rpc/StateList.js +16 -18
  161. package/esm/npm/src/auth/browser/login.d.ts.map +1 -1
  162. package/esm/npm/src/auth/browser/login.js +46 -3
  163. package/esm/npm/src/auth/browser/portal.d.ts.map +1 -1
  164. package/esm/npm/src/auth/browser/portal.js +5 -1
  165. package/esm/npm/src/auth/browser/session.d.ts +18 -7
  166. package/esm/npm/src/auth/browser/session.d.ts.map +1 -1
  167. package/esm/npm/src/auth/browser/session.js +47 -11
  168. package/esm/npm/src/auth/browser/storage.d.ts +6 -1
  169. package/esm/npm/src/auth/browser/storage.d.ts.map +1 -1
  170. package/esm/npm/src/auth/browser/storage.js +15 -3
  171. package/esm/npm/src/auth/browser.d.ts +2 -2
  172. package/esm/npm/src/auth/browser.d.ts.map +1 -1
  173. package/esm/npm/src/auth/browser.js +1 -1
  174. package/esm/npm/src/auth/device_activation.d.ts +36 -33
  175. package/esm/npm/src/auth/device_activation.d.ts.map +1 -1
  176. package/esm/npm/src/auth/device_activation.js +26 -22
  177. package/esm/npm/src/auth/mod.d.ts +4 -4
  178. package/esm/npm/src/auth/mod.d.ts.map +1 -1
  179. package/esm/npm/src/auth/mod.js +2 -2
  180. package/esm/npm/src/auth/proof.d.ts +3 -1
  181. package/esm/npm/src/auth/proof.d.ts.map +1 -1
  182. package/esm/npm/src/auth/proof.js +21 -15
  183. package/esm/npm/src/auth/protocol.d.ts +2457 -941
  184. package/esm/npm/src/auth/protocol.d.ts.map +1 -1
  185. package/esm/npm/src/auth/protocol.js +747 -375
  186. package/esm/npm/src/auth/schemas.d.ts +25 -4
  187. package/esm/npm/src/auth/schemas.d.ts.map +1 -1
  188. package/esm/npm/src/auth/schemas.js +14 -4
  189. package/esm/npm/src/auth/session_auth.d.ts +1 -1
  190. package/esm/npm/src/auth/session_auth.d.ts.map +1 -1
  191. package/esm/npm/src/auth/session_auth.js +7 -1
  192. package/esm/npm/src/client_connect.d.ts +2 -0
  193. package/esm/npm/src/client_connect.d.ts.map +1 -1
  194. package/esm/npm/src/client_connect.js +76 -15
  195. package/esm/npm/src/contract.d.ts +3 -0
  196. package/esm/npm/src/contract.d.ts.map +1 -1
  197. package/esm/npm/src/contract_support/mod.d.ts +422 -43
  198. package/esm/npm/src/contract_support/mod.d.ts.map +1 -1
  199. package/esm/npm/src/contract_support/mod.js +734 -33
  200. package/esm/npm/src/contract_support/protocol.d.ts +20 -5
  201. package/esm/npm/src/contract_support/protocol.d.ts.map +1 -1
  202. package/esm/npm/src/contract_support/protocol.js +18 -10
  203. package/esm/npm/src/contract_support/runtime.d.ts +11 -0
  204. package/esm/npm/src/contract_support/runtime.d.ts.map +1 -1
  205. package/esm/npm/src/contract_support/schema_pointers.d.ts.map +1 -1
  206. package/esm/npm/src/contract_support/schema_pointers.js +32 -14
  207. package/esm/npm/src/device/deno.d.ts.map +1 -1
  208. package/esm/npm/src/device/deno.js +6 -0
  209. package/esm/npm/src/device.d.ts +2 -0
  210. package/esm/npm/src/device.d.ts.map +1 -1
  211. package/esm/npm/src/device.js +3 -0
  212. package/esm/npm/src/errors/AuthError.d.ts +2 -1
  213. package/esm/npm/src/errors/AuthError.d.ts.map +1 -1
  214. package/esm/npm/src/errors/AuthError.js +8 -3
  215. package/esm/npm/src/errors/index.d.ts +4 -4
  216. package/esm/npm/src/errors/index.d.ts.map +1 -1
  217. package/esm/npm/src/errors/index.js +1 -0
  218. package/esm/npm/src/generate.js +54 -24
  219. package/esm/npm/src/helpers.d.ts.map +1 -1
  220. package/esm/npm/src/index.d.ts +4 -3
  221. package/esm/npm/src/index.d.ts.map +1 -1
  222. package/esm/npm/src/index.js +1 -0
  223. package/esm/npm/src/jobs.d.ts +10 -1
  224. package/esm/npm/src/jobs.d.ts.map +1 -1
  225. package/esm/npm/src/jobs.js +16 -1
  226. package/esm/npm/src/kv.d.ts.map +1 -1
  227. package/esm/npm/src/kv.js +10 -4
  228. package/esm/npm/src/models/auth/rpc/Logout.d.ts +4 -4
  229. package/esm/npm/src/models/auth/rpc/Logout.d.ts.map +1 -1
  230. package/esm/npm/src/models/auth/rpc/Logout.js +2 -2
  231. package/esm/npm/src/models/trellis/Page.d.ts +2 -0
  232. package/esm/npm/src/models/trellis/Page.d.ts.map +1 -0
  233. package/esm/npm/src/models/trellis/Page.js +1 -0
  234. package/esm/npm/src/models/trellis/State.d.ts +1 -0
  235. package/esm/npm/src/models/trellis/State.d.ts.map +1 -1
  236. package/esm/npm/src/models/trellis/State.js +1 -0
  237. package/esm/npm/src/models/trellis/rpc/StateList.d.ts +9 -12
  238. package/esm/npm/src/models/trellis/rpc/StateList.d.ts.map +1 -1
  239. package/esm/npm/src/models/trellis/rpc/StateList.js +16 -18
  240. package/esm/npm/src/operations.d.ts +16 -7
  241. package/esm/npm/src/operations.d.ts.map +1 -1
  242. package/esm/npm/src/operations.js +84 -19
  243. package/esm/npm/src/runtime_transport.d.ts +2 -0
  244. package/esm/npm/src/runtime_transport.d.ts.map +1 -1
  245. package/esm/npm/src/runtime_transport.js +1 -0
  246. package/esm/npm/src/server/internal_jobs/active-job.d.ts +2 -1
  247. package/esm/npm/src/server/internal_jobs/active-job.d.ts.map +1 -1
  248. package/esm/npm/src/server/internal_jobs/active-job.js +3 -0
  249. package/esm/npm/src/server/internal_jobs/job-manager.d.ts +4 -1
  250. package/esm/npm/src/server/internal_jobs/job-manager.d.ts.map +1 -1
  251. package/esm/npm/src/server/internal_jobs/job-manager.js +61 -1
  252. package/esm/npm/src/server/internal_jobs/projection.js +1 -0
  253. package/esm/npm/src/server/internal_jobs/runtime-worker.d.ts +13 -1
  254. package/esm/npm/src/server/internal_jobs/runtime-worker.d.ts.map +1 -1
  255. package/esm/npm/src/server/internal_jobs/runtime-worker.js +73 -13
  256. package/esm/npm/src/server/internal_jobs/types.d.ts +19 -0
  257. package/esm/npm/src/server/internal_jobs/types.d.ts.map +1 -1
  258. package/esm/npm/src/server/internal_jobs/types.js +10 -0
  259. package/esm/npm/src/server/runtime.d.ts +1 -0
  260. package/esm/npm/src/server/runtime.d.ts.map +1 -1
  261. package/esm/npm/src/server/service.d.ts +10 -1
  262. package/esm/npm/src/server/service.d.ts.map +1 -1
  263. package/esm/npm/src/server/service.js +210 -64
  264. package/esm/npm/src/server/transfer.d.ts.map +1 -1
  265. package/esm/npm/src/server/transfer.js +4 -0
  266. package/esm/npm/src/server.d.ts.map +1 -1
  267. package/esm/npm/src/server.js +337 -34
  268. package/esm/npm/src/store.d.ts +8 -1
  269. package/esm/npm/src/store.d.ts.map +1 -1
  270. package/esm/npm/src/store.js +46 -8
  271. package/esm/npm/src/transfer.d.ts +3 -0
  272. package/esm/npm/src/transfer.d.ts.map +1 -1
  273. package/esm/npm/src/transfer.js +20 -30
  274. package/esm/npm/src/trellis.d.ts +85 -22
  275. package/esm/npm/src/trellis.d.ts.map +1 -1
  276. package/esm/npm/src/trellis.js +525 -61
  277. package/esm/operations.d.ts +16 -7
  278. package/esm/operations.d.ts.map +1 -1
  279. package/esm/operations.js +84 -19
  280. package/esm/runtime_transport.d.ts +2 -0
  281. package/esm/runtime_transport.d.ts.map +1 -1
  282. package/esm/runtime_transport.js +1 -0
  283. package/esm/store.d.ts +8 -1
  284. package/esm/store.d.ts.map +1 -1
  285. package/esm/store.js +46 -8
  286. package/esm/transfer.d.ts +3 -0
  287. package/esm/transfer.d.ts.map +1 -1
  288. package/esm/transfer.js +20 -30
  289. package/esm/trellis.d.ts +85 -22
  290. package/esm/trellis.d.ts.map +1 -1
  291. package/esm/trellis.js +525 -61
  292. package/package.json +6 -4
  293. package/script/auth/browser/login.d.ts.map +1 -1
  294. package/script/auth/browser/login.js +46 -3
  295. package/script/auth/browser/portal.d.ts.map +1 -1
  296. package/script/auth/browser/portal.js +5 -1
  297. package/script/auth/browser/session.d.ts +18 -7
  298. package/script/auth/browser/session.d.ts.map +1 -1
  299. package/script/auth/browser/session.js +47 -11
  300. package/script/auth/browser/storage.d.ts +6 -1
  301. package/script/auth/browser/storage.d.ts.map +1 -1
  302. package/script/auth/browser/storage.js +15 -3
  303. package/script/auth/browser.d.ts +2 -2
  304. package/script/auth/browser.d.ts.map +1 -1
  305. package/script/auth/browser.js +2 -1
  306. package/script/auth/device_activation.d.ts +36 -33
  307. package/script/auth/device_activation.d.ts.map +1 -1
  308. package/script/auth/device_activation.js +25 -21
  309. package/script/auth/mod.d.ts +4 -4
  310. package/script/auth/mod.d.ts.map +1 -1
  311. package/script/auth/mod.js +132 -137
  312. package/script/auth/proof.d.ts +3 -1
  313. package/script/auth/proof.d.ts.map +1 -1
  314. package/script/auth/proof.js +21 -15
  315. package/script/auth/protocol.d.ts +2457 -941
  316. package/script/auth/protocol.d.ts.map +1 -1
  317. package/script/auth/protocol.js +749 -377
  318. package/script/auth/schemas.d.ts +25 -4
  319. package/script/auth/schemas.d.ts.map +1 -1
  320. package/script/auth/schemas.js +16 -5
  321. package/script/auth/session_auth.d.ts +1 -1
  322. package/script/auth/session_auth.d.ts.map +1 -1
  323. package/script/auth/session_auth.js +7 -1
  324. package/script/client_connect.d.ts +2 -0
  325. package/script/client_connect.d.ts.map +1 -1
  326. package/script/client_connect.js +76 -15
  327. package/script/contract.d.ts +3 -0
  328. package/script/contract.d.ts.map +1 -1
  329. package/script/contract_support/mod.d.ts +422 -43
  330. package/script/contract_support/mod.d.ts.map +1 -1
  331. package/script/contract_support/mod.js +757 -51
  332. package/script/contract_support/protocol.d.ts +20 -5
  333. package/script/contract_support/protocol.d.ts.map +1 -1
  334. package/script/contract_support/protocol.js +20 -11
  335. package/script/contract_support/runtime.d.ts +11 -0
  336. package/script/contract_support/runtime.d.ts.map +1 -1
  337. package/script/contract_support/schema_pointers.d.ts.map +1 -1
  338. package/script/contract_support/schema_pointers.js +32 -14
  339. package/script/device.d.ts +2 -0
  340. package/script/device.d.ts.map +1 -1
  341. package/script/device.js +3 -0
  342. package/script/errors/AuthError.d.ts +2 -1
  343. package/script/errors/AuthError.d.ts.map +1 -1
  344. package/script/errors/AuthError.js +8 -3
  345. package/script/errors/index.d.ts +4 -4
  346. package/script/errors/index.d.ts.map +1 -1
  347. package/script/errors/index.js +1 -0
  348. package/script/generated-sdk/auth/api.d.ts +27 -9
  349. package/script/generated-sdk/auth/api.d.ts.map +1 -1
  350. package/script/generated-sdk/auth/api.js +17 -591
  351. package/script/generated-sdk/auth/client.d.ts +91 -85
  352. package/script/generated-sdk/auth/client.d.ts.map +1 -1
  353. package/script/generated-sdk/auth/contract.d.ts +1 -1
  354. package/script/generated-sdk/auth/contract.d.ts.map +1 -1
  355. package/script/generated-sdk/auth/contract.js +4 -2
  356. package/script/generated-sdk/auth/mod.d.ts +1 -0
  357. package/script/generated-sdk/auth/mod.d.ts.map +1 -1
  358. package/script/generated-sdk/auth/owned_api.d.ts +3 -0
  359. package/script/generated-sdk/auth/owned_api.d.ts.map +1 -0
  360. package/script/generated-sdk/auth/owned_api.js +597 -0
  361. package/script/generated-sdk/auth/schemas.d.ts +9959 -5160
  362. package/script/generated-sdk/auth/schemas.d.ts.map +1 -1
  363. package/script/generated-sdk/auth/schemas.js +139 -140
  364. package/script/generated-sdk/auth/types.d.ts +2418 -1557
  365. package/script/generated-sdk/auth/types.d.ts.map +1 -1
  366. package/script/generated-sdk/auth/types.js +1 -1
  367. package/script/generated-sdk/health/api.d.ts +24 -9
  368. package/script/generated-sdk/health/api.d.ts.map +1 -1
  369. package/script/generated-sdk/health/api.js +13 -21
  370. package/script/generated-sdk/health/client.d.ts +2 -1
  371. package/script/generated-sdk/health/client.d.ts.map +1 -1
  372. package/script/generated-sdk/health/contract.d.ts.map +1 -1
  373. package/script/generated-sdk/health/contract.js +2 -0
  374. package/script/generated-sdk/health/owned_api.d.ts +3 -0
  375. package/script/generated-sdk/health/owned_api.d.ts.map +1 -0
  376. package/script/generated-sdk/health/owned_api.js +19 -0
  377. package/script/generated-sdk/health/types.d.ts +2 -0
  378. package/script/generated-sdk/health/types.d.ts.map +1 -1
  379. package/script/generated-sdk/jobs/api.d.ts +33 -9
  380. package/script/generated-sdk/jobs/api.d.ts.map +1 -1
  381. package/script/generated-sdk/jobs/api.js +23 -88
  382. package/script/generated-sdk/jobs/client.d.ts +9 -2
  383. package/script/generated-sdk/jobs/client.d.ts.map +1 -1
  384. package/script/generated-sdk/jobs/contract.d.ts +1 -1
  385. package/script/generated-sdk/jobs/contract.d.ts.map +1 -1
  386. package/script/generated-sdk/jobs/contract.js +4 -2
  387. package/script/generated-sdk/jobs/owned_api.d.ts +3 -0
  388. package/script/generated-sdk/jobs/owned_api.d.ts.map +1 -0
  389. package/script/generated-sdk/jobs/owned_api.js +154 -0
  390. package/script/generated-sdk/jobs/schemas.d.ts +336 -123
  391. package/script/generated-sdk/jobs/schemas.d.ts.map +1 -1
  392. package/script/generated-sdk/jobs/schemas.js +18 -16
  393. package/script/generated-sdk/jobs/types.d.ts +144 -34
  394. package/script/generated-sdk/jobs/types.d.ts.map +1 -1
  395. package/script/generated-sdk/jobs/types.js +38 -2
  396. package/script/generated-sdk/state/api.d.ts +27 -9
  397. package/script/generated-sdk/state/api.d.ts.map +1 -1
  398. package/script/generated-sdk/state/api.js +17 -72
  399. package/script/generated-sdk/state/client.d.ts +4 -2
  400. package/script/generated-sdk/state/client.d.ts.map +1 -1
  401. package/script/generated-sdk/state/contract.d.ts +1 -1
  402. package/script/generated-sdk/state/contract.d.ts.map +1 -1
  403. package/script/generated-sdk/state/contract.js +4 -2
  404. package/script/generated-sdk/state/owned_api.d.ts +3 -0
  405. package/script/generated-sdk/state/owned_api.d.ts.map +1 -0
  406. package/script/generated-sdk/state/owned_api.js +69 -0
  407. package/script/generated-sdk/state/schemas.d.ts +264 -284
  408. package/script/generated-sdk/state/schemas.d.ts.map +1 -1
  409. package/script/generated-sdk/state/schemas.js +6 -6
  410. package/script/generated-sdk/state/types.d.ts +24 -23
  411. package/script/generated-sdk/state/types.d.ts.map +1 -1
  412. package/script/generated-sdk/state/types.js +1 -1
  413. package/script/generated-sdk/trellis-core/api.d.ts +27 -9
  414. package/script/generated-sdk/trellis-core/api.d.ts.map +1 -1
  415. package/script/generated-sdk/trellis-core/api.js +17 -40
  416. package/script/generated-sdk/trellis-core/client.d.ts +5 -2
  417. package/script/generated-sdk/trellis-core/client.d.ts.map +1 -1
  418. package/script/generated-sdk/trellis-core/contract.d.ts +1 -1
  419. package/script/generated-sdk/trellis-core/contract.d.ts.map +1 -1
  420. package/script/generated-sdk/trellis-core/contract.js +4 -2
  421. package/script/generated-sdk/trellis-core/owned_api.d.ts +3 -0
  422. package/script/generated-sdk/trellis-core/owned_api.d.ts.map +1 -0
  423. package/script/generated-sdk/trellis-core/owned_api.js +45 -0
  424. package/script/generated-sdk/trellis-core/schemas.d.ts +259 -11
  425. package/script/generated-sdk/trellis-core/schemas.d.ts.map +1 -1
  426. package/script/generated-sdk/trellis-core/schemas.js +6 -4
  427. package/script/generated-sdk/trellis-core/types.d.ts +56 -1
  428. package/script/generated-sdk/trellis-core/types.d.ts.map +1 -1
  429. package/script/generated-sdk/trellis-core/types.js +1 -1
  430. package/script/helpers.d.ts.map +1 -1
  431. package/script/index.d.ts +4 -3
  432. package/script/index.d.ts.map +1 -1
  433. package/script/index.js +5 -2
  434. package/script/jobs.d.ts +10 -1
  435. package/script/jobs.d.ts.map +1 -1
  436. package/script/jobs.js +17 -2
  437. package/script/kv.d.ts.map +1 -1
  438. package/script/kv.js +10 -4
  439. package/script/models/auth/rpc/Logout.d.ts +4 -4
  440. package/script/models/auth/rpc/Logout.d.ts.map +1 -1
  441. package/script/models/auth/rpc/Logout.js +3 -3
  442. package/script/models/trellis/Page.d.ts +2 -0
  443. package/script/models/trellis/Page.d.ts.map +1 -0
  444. package/script/models/trellis/Page.js +6 -0
  445. package/script/models/trellis/State.d.ts +1 -0
  446. package/script/models/trellis/State.d.ts.map +1 -1
  447. package/script/models/trellis/State.js +1 -0
  448. package/script/models/trellis/rpc/StateList.d.ts +9 -12
  449. package/script/models/trellis/rpc/StateList.d.ts.map +1 -1
  450. package/script/models/trellis/rpc/StateList.js +16 -18
  451. package/script/npm/src/auth/browser/login.d.ts.map +1 -1
  452. package/script/npm/src/auth/browser/login.js +46 -3
  453. package/script/npm/src/auth/browser/portal.d.ts.map +1 -1
  454. package/script/npm/src/auth/browser/portal.js +5 -1
  455. package/script/npm/src/auth/browser/session.d.ts +18 -7
  456. package/script/npm/src/auth/browser/session.d.ts.map +1 -1
  457. package/script/npm/src/auth/browser/session.js +47 -11
  458. package/script/npm/src/auth/browser/storage.d.ts +6 -1
  459. package/script/npm/src/auth/browser/storage.d.ts.map +1 -1
  460. package/script/npm/src/auth/browser/storage.js +15 -3
  461. package/script/npm/src/auth/browser.d.ts +2 -2
  462. package/script/npm/src/auth/browser.d.ts.map +1 -1
  463. package/script/npm/src/auth/browser.js +2 -1
  464. package/script/npm/src/auth/device_activation.d.ts +36 -33
  465. package/script/npm/src/auth/device_activation.d.ts.map +1 -1
  466. package/script/npm/src/auth/device_activation.js +25 -21
  467. package/script/npm/src/auth/mod.d.ts +4 -4
  468. package/script/npm/src/auth/mod.d.ts.map +1 -1
  469. package/script/npm/src/auth/mod.js +132 -137
  470. package/script/npm/src/auth/proof.d.ts +3 -1
  471. package/script/npm/src/auth/proof.d.ts.map +1 -1
  472. package/script/npm/src/auth/proof.js +21 -15
  473. package/script/npm/src/auth/protocol.d.ts +2457 -941
  474. package/script/npm/src/auth/protocol.d.ts.map +1 -1
  475. package/script/npm/src/auth/protocol.js +749 -377
  476. package/script/npm/src/auth/schemas.d.ts +25 -4
  477. package/script/npm/src/auth/schemas.d.ts.map +1 -1
  478. package/script/npm/src/auth/schemas.js +16 -5
  479. package/script/npm/src/auth/session_auth.d.ts +1 -1
  480. package/script/npm/src/auth/session_auth.d.ts.map +1 -1
  481. package/script/npm/src/auth/session_auth.js +7 -1
  482. package/script/npm/src/client_connect.d.ts +2 -0
  483. package/script/npm/src/client_connect.d.ts.map +1 -1
  484. package/script/npm/src/client_connect.js +76 -15
  485. package/script/npm/src/contract.d.ts +3 -0
  486. package/script/npm/src/contract.d.ts.map +1 -1
  487. package/script/npm/src/contract_support/mod.d.ts +422 -43
  488. package/script/npm/src/contract_support/mod.d.ts.map +1 -1
  489. package/script/npm/src/contract_support/mod.js +757 -51
  490. package/script/npm/src/contract_support/protocol.d.ts +20 -5
  491. package/script/npm/src/contract_support/protocol.d.ts.map +1 -1
  492. package/script/npm/src/contract_support/protocol.js +20 -11
  493. package/script/npm/src/contract_support/runtime.d.ts +11 -0
  494. package/script/npm/src/contract_support/runtime.d.ts.map +1 -1
  495. package/script/npm/src/contract_support/schema_pointers.d.ts.map +1 -1
  496. package/script/npm/src/contract_support/schema_pointers.js +32 -14
  497. package/script/npm/src/device/deno.d.ts.map +1 -1
  498. package/script/npm/src/device/deno.js +6 -0
  499. package/script/npm/src/device.d.ts +2 -0
  500. package/script/npm/src/device.d.ts.map +1 -1
  501. package/script/npm/src/device.js +3 -0
  502. package/script/npm/src/errors/AuthError.d.ts +2 -1
  503. package/script/npm/src/errors/AuthError.d.ts.map +1 -1
  504. package/script/npm/src/errors/AuthError.js +8 -3
  505. package/script/npm/src/errors/index.d.ts +4 -4
  506. package/script/npm/src/errors/index.d.ts.map +1 -1
  507. package/script/npm/src/errors/index.js +1 -0
  508. package/script/npm/src/generate.js +54 -57
  509. package/script/npm/src/helpers.d.ts.map +1 -1
  510. package/script/npm/src/index.d.ts +4 -3
  511. package/script/npm/src/index.d.ts.map +1 -1
  512. package/script/npm/src/index.js +5 -2
  513. package/script/npm/src/jobs.d.ts +10 -1
  514. package/script/npm/src/jobs.d.ts.map +1 -1
  515. package/script/npm/src/jobs.js +17 -2
  516. package/script/npm/src/kv.d.ts.map +1 -1
  517. package/script/npm/src/kv.js +10 -4
  518. package/script/npm/src/models/auth/rpc/Logout.d.ts +4 -4
  519. package/script/npm/src/models/auth/rpc/Logout.d.ts.map +1 -1
  520. package/script/npm/src/models/auth/rpc/Logout.js +3 -3
  521. package/script/npm/src/models/trellis/Page.d.ts +2 -0
  522. package/script/npm/src/models/trellis/Page.d.ts.map +1 -0
  523. package/script/npm/src/models/trellis/Page.js +6 -0
  524. package/script/npm/src/models/trellis/State.d.ts +1 -0
  525. package/script/npm/src/models/trellis/State.d.ts.map +1 -1
  526. package/script/npm/src/models/trellis/State.js +1 -0
  527. package/script/npm/src/models/trellis/rpc/StateList.d.ts +9 -12
  528. package/script/npm/src/models/trellis/rpc/StateList.d.ts.map +1 -1
  529. package/script/npm/src/models/trellis/rpc/StateList.js +16 -18
  530. package/script/npm/src/operations.d.ts +16 -7
  531. package/script/npm/src/operations.d.ts.map +1 -1
  532. package/script/npm/src/operations.js +84 -19
  533. package/script/npm/src/runtime_transport.d.ts +2 -0
  534. package/script/npm/src/runtime_transport.d.ts.map +1 -1
  535. package/script/npm/src/runtime_transport.js +2 -1
  536. package/script/npm/src/server/internal_jobs/active-job.d.ts +2 -1
  537. package/script/npm/src/server/internal_jobs/active-job.d.ts.map +1 -1
  538. package/script/npm/src/server/internal_jobs/active-job.js +3 -0
  539. package/script/npm/src/server/internal_jobs/job-manager.d.ts +4 -1
  540. package/script/npm/src/server/internal_jobs/job-manager.d.ts.map +1 -1
  541. package/script/npm/src/server/internal_jobs/job-manager.js +61 -1
  542. package/script/npm/src/server/internal_jobs/projection.js +1 -0
  543. package/script/npm/src/server/internal_jobs/runtime-worker.d.ts +13 -1
  544. package/script/npm/src/server/internal_jobs/runtime-worker.d.ts.map +1 -1
  545. package/script/npm/src/server/internal_jobs/runtime-worker.js +74 -13
  546. package/script/npm/src/server/internal_jobs/types.d.ts +19 -0
  547. package/script/npm/src/server/internal_jobs/types.d.ts.map +1 -1
  548. package/script/npm/src/server/internal_jobs/types.js +11 -1
  549. package/script/npm/src/server/runtime.d.ts +1 -0
  550. package/script/npm/src/server/runtime.d.ts.map +1 -1
  551. package/script/npm/src/server/service.d.ts +10 -1
  552. package/script/npm/src/server/service.d.ts.map +1 -1
  553. package/script/npm/src/server/service.js +208 -62
  554. package/script/npm/src/server/transfer.d.ts.map +1 -1
  555. package/script/npm/src/server/transfer.js +4 -0
  556. package/script/npm/src/server.d.ts.map +1 -1
  557. package/script/npm/src/server.js +336 -33
  558. package/script/npm/src/store.d.ts +8 -1
  559. package/script/npm/src/store.d.ts.map +1 -1
  560. package/script/npm/src/store.js +46 -8
  561. package/script/npm/src/transfer.d.ts +3 -0
  562. package/script/npm/src/transfer.d.ts.map +1 -1
  563. package/script/npm/src/transfer.js +19 -29
  564. package/script/npm/src/trellis.d.ts +85 -22
  565. package/script/npm/src/trellis.d.ts.map +1 -1
  566. package/script/npm/src/trellis.js +525 -61
  567. package/script/operations.d.ts +16 -7
  568. package/script/operations.d.ts.map +1 -1
  569. package/script/operations.js +84 -19
  570. package/script/runtime_transport.d.ts +2 -0
  571. package/script/runtime_transport.d.ts.map +1 -1
  572. package/script/runtime_transport.js +2 -1
  573. package/script/store.d.ts +8 -1
  574. package/script/store.d.ts.map +1 -1
  575. package/script/store.js +46 -8
  576. package/script/transfer.d.ts +3 -0
  577. package/script/transfer.d.ts.map +1 -1
  578. package/script/transfer.js +19 -29
  579. package/script/trellis.d.ts +85 -22
  580. package/script/trellis.d.ts.map +1 -1
  581. package/script/trellis.js +525 -61
  582. package/src/_dnt.polyfills.ts +274 -0
  583. package/src/_dnt.shims.ts +64 -0
  584. package/src/auth/browser/login.ts +295 -0
  585. package/src/auth/browser/portal.ts +75 -0
  586. package/src/auth/browser/session.ts +197 -0
  587. package/src/auth/browser/storage.ts +105 -0
  588. package/src/auth/browser.ts +82 -0
  589. package/src/auth/device_activation.ts +715 -0
  590. package/src/auth/keys.ts +116 -0
  591. package/src/auth/mod.ts +298 -0
  592. package/src/auth/proof.ts +111 -0
  593. package/src/auth/protocol.ts +1629 -0
  594. package/src/auth/schemas.ts +145 -0
  595. package/src/auth/session_auth.ts +167 -0
  596. package/src/auth/time.ts +15 -0
  597. package/src/auth/trellis_id.ts +9 -0
  598. package/src/auth/types.ts +4 -0
  599. package/src/auth/utils.ts +87 -0
  600. package/src/auth.ts +2 -0
  601. package/src/browser.ts +8 -0
  602. package/src/client.ts +164 -0
  603. package/src/client_connect.ts +1328 -0
  604. package/src/codec.ts +107 -0
  605. package/src/connection.ts +466 -0
  606. package/src/contract.ts +84 -0
  607. package/src/contract_support/canonical.ts +217 -0
  608. package/src/contract_support/mod.ts +5079 -0
  609. package/src/contract_support/protocol.ts +213 -0
  610. package/src/contract_support/runtime.ts +129 -0
  611. package/src/contract_support/schema_pointers.ts +161 -0
  612. package/src/contracts.ts +9 -0
  613. package/src/device/deno.ts +941 -0
  614. package/src/device.ts +989 -0
  615. package/src/env.ts +1 -0
  616. package/src/errors/AuthError.ts +82 -0
  617. package/src/errors/KVError.ts +47 -0
  618. package/src/errors/RemoteError.ts +111 -0
  619. package/src/errors/StoreError.ts +43 -0
  620. package/src/errors/TransferError.ts +43 -0
  621. package/src/errors/TransportError.ts +48 -0
  622. package/src/errors/TrellisError.ts +20 -0
  623. package/src/errors/ValidationError.ts +80 -0
  624. package/src/errors/index.ts +195 -0
  625. package/src/generate.ts +329 -0
  626. package/src/globals.ts +26 -0
  627. package/src/health.ts +28 -0
  628. package/src/helpers.ts +63 -0
  629. package/src/host/mod.ts +9 -0
  630. package/src/host/node.ts +9 -0
  631. package/src/index.ts +233 -0
  632. package/src/jobs.ts +344 -0
  633. package/src/kv.ts +564 -0
  634. package/src/models/auth/rpc/Logout.ts +15 -0
  635. package/src/models/trellis/Page.ts +6 -0
  636. package/src/models/trellis/State.ts +55 -0
  637. package/src/models/trellis/TrellisError.ts +21 -0
  638. package/src/models/trellis/rpc/StateDelete.ts +13 -0
  639. package/src/models/trellis/rpc/StateGet.ts +25 -0
  640. package/src/models/trellis/rpc/StateList.ts +26 -0
  641. package/src/models/trellis/rpc/StatePut.ts +42 -0
  642. package/src/operations.ts +1508 -0
  643. package/src/runtime_transport.ts +132 -0
  644. package/src/sdk/auth.ts +2 -0
  645. package/src/sdk/core.ts +2 -0
  646. package/src/sdk/health.ts +2 -0
  647. package/src/sdk/jobs.ts +2 -0
  648. package/src/sdk/state.ts +2 -0
  649. package/src/server/health.ts +379 -0
  650. package/src/server/health_rpc.ts +51 -0
  651. package/src/server/health_schemas.ts +61 -0
  652. package/src/server/internal_jobs/active-job.ts +115 -0
  653. package/src/server/internal_jobs/bindings.ts +26 -0
  654. package/src/server/internal_jobs/cancellation-registry.ts +71 -0
  655. package/src/server/internal_jobs/heartbeat.ts +120 -0
  656. package/src/server/internal_jobs/job-manager.ts +456 -0
  657. package/src/server/internal_jobs/projection.ts +48 -0
  658. package/src/server/internal_jobs/runtime-worker.ts +741 -0
  659. package/src/server/internal_jobs/types.ts +124 -0
  660. package/src/server/runtime.ts +27 -0
  661. package/src/server/service.ts +2377 -0
  662. package/src/server/subscription.ts +143 -0
  663. package/src/server/transfer.ts +962 -0
  664. package/src/server.ts +1725 -0
  665. package/src/server_logger.ts +10 -0
  666. package/src/service/deno.ts +18 -0
  667. package/src/service/mod.ts +68 -0
  668. package/src/service/node.ts +18 -0
  669. package/src/store.ts +658 -0
  670. package/src/tasks.ts +34 -0
  671. package/src/telemetry/carrier.ts +35 -0
  672. package/src/telemetry/core.ts +31 -0
  673. package/src/telemetry/env.ts +23 -0
  674. package/src/telemetry/mod.ts +26 -0
  675. package/src/telemetry/nats.ts +15 -0
  676. package/src/telemetry/result.ts +20 -0
  677. package/src/telemetry/trace.ts +39 -0
  678. package/src/telemetry/trellis.ts +1 -0
  679. package/src/tracing.ts +28 -0
  680. package/src/transfer.ts +602 -0
  681. package/src/trellis.ts +3650 -0
  682. package/esm/models/trellis/Paginate.d.ts +0 -7
  683. package/esm/models/trellis/Paginate.d.ts.map +0 -1
  684. package/esm/models/trellis/Paginate.js +0 -5
  685. package/esm/npm/src/models/trellis/Paginate.d.ts +0 -7
  686. package/esm/npm/src/models/trellis/Paginate.d.ts.map +0 -1
  687. package/esm/npm/src/models/trellis/Paginate.js +0 -5
  688. package/script/models/trellis/Paginate.d.ts +0 -7
  689. package/script/models/trellis/Paginate.d.ts.map +0 -1
  690. package/script/models/trellis/Paginate.js +0 -11
  691. package/script/npm/src/models/trellis/Paginate.d.ts +0 -7
  692. package/script/npm/src/models/trellis/Paginate.d.ts.map +0 -1
  693. package/script/npm/src/models/trellis/Paginate.js +0 -11
@@ -0,0 +1,2377 @@
1
+ import {
2
+ headers as natsHeaders,
3
+ jwtAuthenticator,
4
+ type MsgHdrs,
5
+ type NatsConnection,
6
+ type Subscription,
7
+ } from "@nats-io/nats-core";
8
+ import {
9
+ type KVError,
10
+ type StoreError,
11
+ type StoreWaitOptions,
12
+ Trellis as RootTrellis,
13
+ TypedKV,
14
+ TypedStore,
15
+ TypedStoreEntry,
16
+ } from "@qlever-llc/trellis";
17
+ import { sdk as trellisAuth } from "../sdk/auth.js";
18
+ import {
19
+ TrellisServiceRuntime,
20
+ type TrellisServiceRuntimeFor,
21
+ } from "../server.js";
22
+ import {
23
+ createAuth,
24
+ estimateMidpointClockOffsetMs,
25
+ type SentinelCreds,
26
+ SentinelCredsSchema,
27
+ type TrellisAuth as SessionAuth,
28
+ } from "@qlever-llc/trellis/auth";
29
+ import {
30
+ ContractResourceBindingsSchema,
31
+ type InferSchemaType,
32
+ } from "@qlever-llc/trellis/contracts";
33
+ import type { TrellisAPI } from "@qlever-llc/trellis/contracts";
34
+ import type { TrellisContractV1 } from "../contract_support/mod.js";
35
+ import type {
36
+ ContractJobsMetadata,
37
+ ContractKvMetadata,
38
+ } from "../contract_support/mod.js";
39
+ import {
40
+ CONTRACT_JOBS_METADATA,
41
+ CONTRACT_KV_METADATA,
42
+ } from "../contract_support/mod.js";
43
+ import { AsyncResult, type BaseError, isErr, Result } from "@qlever-llc/result";
44
+ import { Type } from "typebox";
45
+ import { Value } from "typebox/value";
46
+ import { type HealthCheckFn, ServiceHealth } from "./health.js";
47
+ import { mountStandardHealthRpc } from "./health_rpc.js";
48
+ import type { RPCDesc } from "@qlever-llc/trellis/contracts";
49
+ import type {
50
+ AcceptedOperation,
51
+ FeedEventOf,
52
+ FeedInputOf,
53
+ FeedRegistration as RootFeedRegistration,
54
+ HandlerTrellis,
55
+ OperationHandlerContext,
56
+ OperationOutputOf,
57
+ OperationProgressOf,
58
+ OperationRegistration as RootOperationRegistration,
59
+ OperationRuntimeHandle,
60
+ OperationTransferContextOf,
61
+ RpcHandlerContext,
62
+ RpcHandlerErrorOf,
63
+ RpcRequestErrorOf,
64
+ } from "../trellis.js";
65
+ import type {
66
+ NatsConnectFn,
67
+ NatsConnectOpts,
68
+ TrellisServiceRuntimeDeps,
69
+ } from "./runtime.js";
70
+ import { ServiceTransfer } from "./transfer.js";
71
+ import { logger as noopLogger, type LoggerLike } from "../globals.js";
72
+ import {
73
+ DEFAULT_RUNTIME_MAX_RECONNECT_ATTEMPTS,
74
+ DEFAULT_SERVICE_RUNTIME_WAIT_ON_FIRST_CONNECT,
75
+ loadDefaultRuntimeTransport,
76
+ selectRuntimeTransportServers,
77
+ } from "../runtime_transport.js";
78
+ import { serverLogger } from "../server_logger.js";
79
+ import {
80
+ TransferError,
81
+ TransportError,
82
+ UnexpectedError,
83
+ } from "../errors/index.js";
84
+ import type { ReceiveTransferGrant } from "../transfer.js";
85
+ import {
86
+ ActiveJob as PublicActiveJob,
87
+ type JobIdentity,
88
+ type JobLogEntry,
89
+ type JobProgress,
90
+ JobRef,
91
+ type JobSnapshot,
92
+ JobWorkerHostAdapter,
93
+ type TerminalJob,
94
+ } from "../jobs.js";
95
+ import {
96
+ JobManager as InternalJobManager,
97
+ JobProcessError as InternalJobProcessError,
98
+ } from "./internal_jobs/job-manager.js";
99
+ import { startNatsWorkerHostFromBinding } from "./internal_jobs/runtime-worker.js";
100
+ import type { ActiveJob as InternalActiveJob } from "./internal_jobs/active-job.js";
101
+ import {
102
+ type Job as InternalJob,
103
+ type JobContext as InternalJobContext,
104
+ type JobEvent as InternalJobEvent,
105
+ JobEventSchema,
106
+ } from "./internal_jobs/types.js";
107
+ import {
108
+ observeNatsTrellisConnection,
109
+ type TrellisConnection,
110
+ } from "../connection.js";
111
+
112
+ type ExtraNatsConnectOpts = Omit<
113
+ NatsConnectOpts,
114
+ "servers" | "token" | "inboxPrefix" | "authenticator"
115
+ >;
116
+
117
+ type ResourceBindingJobsQueue = {
118
+ queueType: string;
119
+ publishPrefix: string;
120
+ workSubject: string;
121
+ consumerName: string;
122
+ payload: { schema: string };
123
+ result?: { schema: string };
124
+ maxDeliver: number;
125
+ backoffMs: number[];
126
+ ackWaitMs: number;
127
+ defaultDeadlineMs?: number;
128
+ progress: boolean;
129
+ logs: boolean;
130
+ dlq: boolean;
131
+ concurrency: number;
132
+ };
133
+
134
+ type ResourceBindingJobs = {
135
+ namespace: string;
136
+ workStream?: string;
137
+ queues: Record<string, ResourceBindingJobsQueue>;
138
+ };
139
+
140
+ const ClientTransportEndpointsSchema = Type.Object({
141
+ natsServers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
142
+ });
143
+
144
+ const ClientTransportsSchema = Type.Object({
145
+ native: Type.Optional(ClientTransportEndpointsSchema),
146
+ websocket: Type.Optional(ClientTransportEndpointsSchema),
147
+ });
148
+
149
+ type ServiceBootstrapConnectInfo = {
150
+ sessionKey: string;
151
+ contractId: string;
152
+ contractDigest: string;
153
+ transports: {
154
+ native?: { natsServers: string[] };
155
+ websocket?: { natsServers: string[] };
156
+ };
157
+ transport: {
158
+ sentinel: SentinelCreds;
159
+ };
160
+ auth: {
161
+ mode: "service_identity";
162
+ iatSkewSeconds: number;
163
+ };
164
+ };
165
+
166
+ type ServiceBootstrapResponse = {
167
+ status: "ready";
168
+ serverNow: number;
169
+ connectInfo: ServiceBootstrapConnectInfo;
170
+ binding: {
171
+ contractId: string;
172
+ digest: string;
173
+ resources: ResourceBindings;
174
+ };
175
+ };
176
+
177
+ type ServiceBootstrapFailure = {
178
+ reason: string;
179
+ message?: string;
180
+ serverNow?: number;
181
+ requestId?: string;
182
+ deploymentId?: string;
183
+ issueId?: string;
184
+ activeContractDigest?: string;
185
+ };
186
+
187
+ const DEFAULT_BOOTSTRAP_PENDING_RETRY_MS = 5_000;
188
+ const MAX_BOOTSTRAP_PENDING_RETRY_MS = 60_000;
189
+ const DEFAULT_BOOTSTRAP_UNAVAILABLE_INITIAL_RETRY_MS = 1_000;
190
+ const MAX_BOOTSTRAP_UNAVAILABLE_RETRY_MS = 30_000;
191
+
192
+ type RpcMethodName<TA extends TrellisAPI> = keyof TA["rpc"] & string;
193
+ type RpcMethodInput<TA extends TrellisAPI, M extends RpcMethodName<TA>> =
194
+ InferSchemaType<TA["rpc"][M]["input"]>;
195
+ type RpcMethodOutput<TA extends TrellisAPI, M extends RpcMethodName<TA>> =
196
+ InferSchemaType<TA["rpc"][M]["output"]>;
197
+
198
+ type TrellisServiceRuntimeCreateOpts<
199
+ TOwnedApi extends TrellisAPI,
200
+ TTrellisApi extends TrellisAPI = TOwnedApi,
201
+ > = {
202
+ log?: LoggerLike | false;
203
+ timeout?: number;
204
+ stream?: string;
205
+ noResponderRetry?: { maxAttempts?: number; baseDelayMs?: number };
206
+ api: TOwnedApi;
207
+ trellisApi?: TTrellisApi;
208
+ version?: string;
209
+ health?: TrellisServiceHealthOpts;
210
+ healthChecks?: Record<string, HealthCheckFn>;
211
+ };
212
+
213
+ export type TrellisServiceHealthOpts = {
214
+ publishIntervalMs?: number;
215
+ };
216
+
217
+ export type TrellisServiceServerOpts = {
218
+ log?: LoggerLike | false;
219
+ timeout?: number;
220
+ stream?: string;
221
+ noResponderRetry?: { maxAttempts?: number; baseDelayMs?: number };
222
+ health?: TrellisServiceHealthOpts;
223
+ healthChecks?: Record<string, HealthCheckFn>;
224
+ };
225
+
226
+ function resolveServiceLogger(log?: LoggerLike | false): LoggerLike {
227
+ if (log === false) {
228
+ return noopLogger;
229
+ }
230
+
231
+ return log ?? serverLogger;
232
+ }
233
+
234
+ export type ResourceBindingKV = {
235
+ bucket: string;
236
+ history: number;
237
+ ttlMs: number;
238
+ maxValueBytes?: number;
239
+ };
240
+
241
+ export type ResourceBindingStore = {
242
+ name: string;
243
+ ttlMs: number;
244
+ maxObjectBytes?: number;
245
+ maxTotalBytes?: number;
246
+ };
247
+
248
+ export type ResourceBindings = {
249
+ kv: Record<string, ResourceBindingKV>;
250
+ store: Record<string, ResourceBindingStore>;
251
+ jobs?: ResourceBindingJobs;
252
+ };
253
+
254
+ function getErrorCauseMessage(error: unknown): string {
255
+ if (error && typeof error === "object") {
256
+ const context = (error as { context?: Record<string, unknown> }).context;
257
+ if (
258
+ typeof context?.causeMessage === "string" &&
259
+ context.causeMessage.length > 0
260
+ ) {
261
+ return context.causeMessage;
262
+ }
263
+ }
264
+
265
+ return error instanceof Error ? error.message : String(error);
266
+ }
267
+
268
+ function bootstrapContractStateError(args: {
269
+ serviceName: string;
270
+ contractId: string;
271
+ contractDigest: string;
272
+ step: "catalog lookup" | "bindings lookup";
273
+ cause?: unknown;
274
+ }): Error {
275
+ const base =
276
+ `Service '${args.serviceName}' could not bootstrap contract '${args.contractId}' (${args.contractDigest}) during ${args.step}. ` +
277
+ "This usually means Trellis has stale or incomplete state for this service session. " +
278
+ "Expand the service deployment envelope or re-run instance provisioning so Trellis records contract evidence, permissions, and resource bindings for this instance key.";
279
+ const cause = args.cause
280
+ ? ` Underlying error: ${getErrorCauseMessage(args.cause)}`
281
+ : "";
282
+ return new Error(base + cause);
283
+ }
284
+
285
+ function runtimeImport<TModule>(specifier: string): Promise<TModule> {
286
+ const load = new Function("specifier", "return import(specifier);") as (
287
+ specifier: string,
288
+ ) => Promise<TModule>;
289
+ return load(specifier);
290
+ }
291
+
292
+ function delay(ms: number): Promise<void> {
293
+ return new Promise((resolve) => setTimeout(resolve, ms));
294
+ }
295
+
296
+ function bootstrapRetryDelayMs(response: Response): number {
297
+ const retryAfter = response.headers.get("retry-after");
298
+ if (retryAfter === null) return DEFAULT_BOOTSTRAP_PENDING_RETRY_MS;
299
+
300
+ const seconds = Number(retryAfter);
301
+ if (Number.isFinite(seconds) && seconds >= 0) {
302
+ return Math.min(seconds * 1_000, MAX_BOOTSTRAP_PENDING_RETRY_MS);
303
+ }
304
+
305
+ const retryAt = Date.parse(retryAfter);
306
+ if (Number.isNaN(retryAt)) return DEFAULT_BOOTSTRAP_PENDING_RETRY_MS;
307
+ return Math.min(
308
+ Math.max(0, retryAt - Date.now()),
309
+ MAX_BOOTSTRAP_PENDING_RETRY_MS,
310
+ );
311
+ }
312
+
313
+ function bootstrapUnavailableRetryDelayMs(attempt: number): number {
314
+ const exponent = Math.min(attempt, 10);
315
+ return Math.min(
316
+ DEFAULT_BOOTSTRAP_UNAVAILABLE_INITIAL_RETRY_MS * 2 ** exponent,
317
+ MAX_BOOTSTRAP_UNAVAILABLE_RETRY_MS,
318
+ );
319
+ }
320
+
321
+ class ServiceBootstrapEndpointUnavailableError extends Error {
322
+ constructor(cause: unknown) {
323
+ super("Service bootstrap endpoint is unavailable.", { cause });
324
+ this.name = "ServiceBootstrapEndpointUnavailableError";
325
+ }
326
+ }
327
+
328
+ async function loadDefaultServiceRuntimeDeps(): Promise<
329
+ TrellisServiceRuntimeDeps
330
+ > {
331
+ const transport = await loadDefaultRuntimeTransport();
332
+ return {
333
+ connect: (
334
+ { servers, token, authenticator, inboxPrefix, ...extraOptions },
335
+ ) =>
336
+ transport.connect({
337
+ servers,
338
+ ...extraOptions,
339
+ ...(token ? { token } : {}),
340
+ ...(authenticator ? { authenticator: authenticator as never } : {}),
341
+ ...(inboxPrefix ? { inboxPrefix } : {}),
342
+ }),
343
+ };
344
+ }
345
+
346
+ const ServiceBootstrapReadySchema = Type.Object({
347
+ status: Type.Literal("ready"),
348
+ serverNow: Type.Integer(),
349
+ connectInfo: Type.Object({
350
+ sessionKey: Type.String({ minLength: 1 }),
351
+ contractId: Type.String({ minLength: 1 }),
352
+ contractDigest: Type.String({ minLength: 1 }),
353
+ transports: ClientTransportsSchema,
354
+ transport: Type.Object({
355
+ sentinel: SentinelCredsSchema,
356
+ }),
357
+ auth: Type.Object({
358
+ mode: Type.Literal("service_identity"),
359
+ iatSkewSeconds: Type.Integer({ minimum: 1 }),
360
+ }),
361
+ }),
362
+ binding: Type.Object({
363
+ contractId: Type.String({ minLength: 1 }),
364
+ digest: Type.String({ minLength: 1 }),
365
+ resources: ContractResourceBindingsSchema,
366
+ }),
367
+ }, { additionalProperties: true });
368
+
369
+ const ServiceBootstrapFailureSchema = Type.Object({
370
+ reason: Type.String({ minLength: 1 }),
371
+ message: Type.Optional(Type.String({ minLength: 1 })),
372
+ serverNow: Type.Optional(Type.Integer()),
373
+ }, { additionalProperties: true });
374
+
375
+ async function fetchServiceBootstrapInfoOnce(args: {
376
+ bootstrapUrl: URL;
377
+ contractId: string;
378
+ contractDigest: string;
379
+ contract?: TrellisContractV1;
380
+ auth: SessionAuth;
381
+ }): Promise<{
382
+ response: Response;
383
+ responseText: string;
384
+ payload: unknown;
385
+ requestStartedAtMs: number;
386
+ responseReceivedAtMs: number;
387
+ }> {
388
+ const requestStartedAtMs = Date.now();
389
+ const iat = args.auth.currentIat();
390
+ const body = JSON.stringify({
391
+ sessionKey: args.auth.sessionKey,
392
+ contractId: args.contractId,
393
+ contractDigest: args.contractDigest,
394
+ ...(args.contract ? { contract: args.contract } : {}),
395
+ iat,
396
+ sig: await args.auth.natsConnectSigForIat(iat, args.contractDigest),
397
+ });
398
+ let response: Response;
399
+ try {
400
+ response = await fetch(args.bootstrapUrl, {
401
+ method: "POST",
402
+ headers: { "Content-Type": "application/json" },
403
+ body,
404
+ });
405
+ } catch (cause) {
406
+ throw new ServiceBootstrapEndpointUnavailableError(cause);
407
+ }
408
+ const responseReceivedAtMs = Date.now();
409
+
410
+ const responseText = await response.text();
411
+ let payload: unknown;
412
+ try {
413
+ payload = JSON.parse(responseText);
414
+ } catch {
415
+ payload = undefined;
416
+ }
417
+ return {
418
+ response,
419
+ responseText,
420
+ payload,
421
+ requestStartedAtMs,
422
+ responseReceivedAtMs,
423
+ };
424
+ }
425
+
426
+ async function fetchServiceBootstrapInfo(args: {
427
+ trellisUrl: string;
428
+ serviceName: string;
429
+ contractId: string;
430
+ contractDigest: string;
431
+ contract?: TrellisContractV1;
432
+ auth: SessionAuth;
433
+ log: LoggerLike;
434
+ }): Promise<ServiceBootstrapResponse> {
435
+ const bootstrapUrl = new URL("/bootstrap/service", args.trellisUrl);
436
+ let includeContract = false;
437
+ let unavailableAttempt = 0;
438
+ const loggedPendingRequests = new Set<string>();
439
+ while (true) {
440
+ let settled: Awaited<ReturnType<typeof fetchServiceBootstrapInfoOnce>>;
441
+ try {
442
+ settled = await fetchServiceBootstrapInfoOnce({
443
+ ...args,
444
+ bootstrapUrl,
445
+ contract: includeContract ? args.contract : undefined,
446
+ });
447
+ unavailableAttempt = 0;
448
+ } catch (cause) {
449
+ if (!(cause instanceof ServiceBootstrapEndpointUnavailableError)) {
450
+ throw cause;
451
+ }
452
+
453
+ const retryDelayMs = bootstrapUnavailableRetryDelayMs(unavailableAttempt);
454
+ unavailableAttempt += 1;
455
+ args.log.warn(
456
+ {
457
+ service: args.serviceName,
458
+ trellisUrl: args.trellisUrl,
459
+ contractId: args.contractId,
460
+ contractDigest: args.contractDigest,
461
+ attempt: unavailableAttempt,
462
+ retryDelayMs,
463
+ causeMessage: getErrorCauseMessage(cause.cause),
464
+ },
465
+ "Service bootstrap endpoint unavailable; retrying",
466
+ );
467
+ await delay(retryDelayMs);
468
+ continue;
469
+ }
470
+
471
+ if (
472
+ settled.payload !== undefined &&
473
+ Value.Check(ServiceBootstrapFailureSchema, settled.payload)
474
+ ) {
475
+ const failure = settled.payload as ServiceBootstrapFailure;
476
+ if (
477
+ failure.reason === "iat_out_of_range" &&
478
+ typeof failure.serverNow === "number"
479
+ ) {
480
+ args.auth.setServerClockOffsetMs(
481
+ estimateMidpointClockOffsetMs({
482
+ requestStartedAtMs: settled.requestStartedAtMs,
483
+ responseReceivedAtMs: settled.responseReceivedAtMs,
484
+ serverNowSeconds: failure.serverNow,
485
+ }),
486
+ );
487
+ continue;
488
+ }
489
+ if (
490
+ failure.reason === "manifest_required" && args.contract !== undefined
491
+ ) {
492
+ includeContract = true;
493
+ continue;
494
+ }
495
+ if (failure.reason === "envelope_expansion_required") {
496
+ const retryDelayMs = bootstrapRetryDelayMs(settled.response);
497
+ const pendingKey = failure.requestId ??
498
+ `${failure.deploymentId ?? "unknown"}:${args.contractDigest}`;
499
+ if (!loggedPendingRequests.has(pendingKey)) {
500
+ loggedPendingRequests.add(pendingKey);
501
+ args.log.info(
502
+ {
503
+ service: args.serviceName,
504
+ deploymentId: failure.deploymentId,
505
+ requestId: failure.requestId,
506
+ contractId: args.contractId,
507
+ contractDigest: args.contractDigest,
508
+ retryDelayMs,
509
+ },
510
+ "Service deployment envelope expansion pending; waiting for approval",
511
+ );
512
+ }
513
+ await delay(retryDelayMs);
514
+ includeContract = true;
515
+ continue;
516
+ }
517
+ if (failure.reason === "contract_activation_pending") {
518
+ const retryDelayMs = bootstrapRetryDelayMs(settled.response);
519
+ const pendingKey = failure.requestId ??
520
+ `${failure.deploymentId ?? "unknown"}:${args.contractDigest}`;
521
+ if (!loggedPendingRequests.has(pendingKey)) {
522
+ loggedPendingRequests.add(pendingKey);
523
+ args.log.info(
524
+ {
525
+ service: args.serviceName,
526
+ deploymentId: failure.deploymentId,
527
+ requestId: failure.requestId,
528
+ contractId: args.contractId,
529
+ contractDigest: args.contractDigest,
530
+ retryDelayMs,
531
+ },
532
+ "Service contract activation pending; waiting for dependency closure",
533
+ );
534
+ }
535
+ await delay(retryDelayMs);
536
+ includeContract = true;
537
+ continue;
538
+ }
539
+ if (failure.reason === "contract_catalog_issue") {
540
+ const retryDelayMs = bootstrapRetryDelayMs(settled.response);
541
+ const pendingKey = failure.issueId ??
542
+ `${failure.activeContractDigest ?? "unknown"}:${args.contractDigest}`;
543
+ if (!loggedPendingRequests.has(pendingKey)) {
544
+ loggedPendingRequests.add(pendingKey);
545
+ args.log.info(
546
+ {
547
+ service: args.serviceName,
548
+ deploymentId: failure.deploymentId,
549
+ issueId: failure.issueId,
550
+ activeContractDigest: failure.activeContractDigest,
551
+ contractId: args.contractId,
552
+ contractDigest: args.contractDigest,
553
+ retryDelayMs,
554
+ },
555
+ "Service contract catalog issue pending; waiting for forced update resolution",
556
+ );
557
+ }
558
+ await delay(retryDelayMs);
559
+ includeContract = true;
560
+ continue;
561
+ }
562
+ throw new TransportError({
563
+ code: "trellis.bootstrap.failed",
564
+ message: `Service bootstrap failed: ${
565
+ failure.message ?? failure.reason
566
+ }`,
567
+ hint:
568
+ "Retry the connection. If it keeps failing, check Trellis bootstrap availability and contract activation.",
569
+ context: {
570
+ trellisUrl: args.trellisUrl,
571
+ contractId: args.contractId,
572
+ contractDigest: args.contractDigest,
573
+ status: settled.response.status,
574
+ reason: failure.reason,
575
+ },
576
+ });
577
+ }
578
+
579
+ if (!settled.response.ok) {
580
+ const detail = settled.responseText.trim();
581
+ throw new TransportError({
582
+ code: "trellis.bootstrap.failed",
583
+ message: detail.length > 0
584
+ ? `Service bootstrap failed with HTTP ${settled.response.status}: ${detail}`
585
+ : `Service bootstrap failed with HTTP ${settled.response.status}`,
586
+ hint:
587
+ "Retry the connection. If it keeps failing, check Trellis bootstrap availability.",
588
+ context: {
589
+ trellisUrl: args.trellisUrl,
590
+ contractId: args.contractId,
591
+ contractDigest: args.contractDigest,
592
+ status: settled.response.status,
593
+ },
594
+ });
595
+ }
596
+
597
+ if (settled.payload === undefined) {
598
+ throw new TransportError({
599
+ code: "trellis.bootstrap.invalid_response",
600
+ message: `Service bootstrap returned invalid JSON: ${
601
+ settled.responseText.trim() || "<empty body>"
602
+ }`,
603
+ hint:
604
+ "Retry the connection. If it keeps happening, check the Trellis deployment.",
605
+ context: {
606
+ trellisUrl: args.trellisUrl,
607
+ contractId: args.contractId,
608
+ contractDigest: args.contractDigest,
609
+ },
610
+ });
611
+ }
612
+
613
+ const ready = Value.Parse(
614
+ ServiceBootstrapReadySchema,
615
+ settled.payload,
616
+ ) as ServiceBootstrapResponse;
617
+ args.auth.setServerClockOffsetMs(
618
+ estimateMidpointClockOffsetMs({
619
+ requestStartedAtMs: settled.requestStartedAtMs,
620
+ responseReceivedAtMs: settled.responseReceivedAtMs,
621
+ serverNowSeconds: ready.serverNow,
622
+ }),
623
+ );
624
+
625
+ return ready;
626
+ }
627
+ }
628
+
629
+ export class StoreHandle {
630
+ readonly binding: ResourceBindingStore;
631
+ readonly #nc: NatsConnection;
632
+
633
+ constructor(nc: NatsConnection, binding: ResourceBindingStore) {
634
+ this.#nc = nc;
635
+ this.binding = binding;
636
+ }
637
+
638
+ open(): AsyncResult<TypedStore, StoreError> {
639
+ return TypedStore.open(this.#nc, this.binding.name, {
640
+ ttlMs: this.binding.ttlMs,
641
+ maxObjectBytes: this.binding.maxObjectBytes,
642
+ maxTotalBytes: this.binding.maxTotalBytes,
643
+ bindOnly: true,
644
+ });
645
+ }
646
+
647
+ /**
648
+ * Waits for a staged object to appear in the bound store and returns its entry.
649
+ */
650
+ waitFor(
651
+ key: string,
652
+ options: StoreWaitOptions = {},
653
+ ): AsyncResult<TypedStoreEntry, StoreError> {
654
+ return this.open().andThen((store) => store.waitFor(key, options));
655
+ }
656
+ }
657
+
658
+ async function openServiceKvBindings<TKv extends ContractKvMetadata>(args: {
659
+ nc: NatsConnection;
660
+ bindings: Record<string, ResourceBindingKV>;
661
+ contractKv: TKv;
662
+ }): Promise<ServiceKvFacade<TKv>> {
663
+ for (const alias of Object.keys(args.bindings)) {
664
+ if (!args.contractKv[alias]) {
665
+ throw new Error(
666
+ `KV binding '${alias}' is missing contract schema metadata`,
667
+ );
668
+ }
669
+ }
670
+
671
+ const entries = await Promise.all(
672
+ Object.entries(args.contractKv).map(async ([alias, metadata]) => {
673
+ const binding = args.bindings[alias];
674
+ if (!binding) {
675
+ if (!metadata.required) {
676
+ return [alias, undefined] as const;
677
+ }
678
+ throw new Error(`Required KV binding '${alias}' is unavailable`);
679
+ }
680
+
681
+ const store = await TypedKV.open(
682
+ args.nc,
683
+ binding.bucket,
684
+ metadata.schema,
685
+ {
686
+ history: binding.history,
687
+ ttl: binding.ttlMs,
688
+ maxValueBytes: binding.maxValueBytes,
689
+ bindOnly: true,
690
+ },
691
+ ).orThrow();
692
+
693
+ return [alias, store] as const;
694
+ }),
695
+ );
696
+
697
+ return Object.fromEntries(entries) as ServiceKvFacade<TKv>;
698
+ }
699
+
700
+ type TrellisServiceRuntimeConnectOpts<
701
+ TOwnedApi extends TrellisAPI = TrellisAPI,
702
+ TTrellisApi extends TrellisAPI = TOwnedApi,
703
+ > = {
704
+ /**
705
+ * Session key seed (base64url Ed25519 private key seed) used to derive the service session key.
706
+ * If you already have a `TrellisAuth` object, pass it via `auth` instead.
707
+ */
708
+ sessionKeySeed?: string;
709
+
710
+ /**
711
+ * Pre-created session-key auth (typically from `@qlever-llc/trellis/auth.createAuth`).
712
+ * If omitted, `sessionKeySeed` is required.
713
+ */
714
+ auth?: SessionAuth;
715
+
716
+ nats: {
717
+ servers: string | string[];
718
+
719
+ /**
720
+ * Sentinel creds content (NATS creds file bytes).
721
+ * Provide this OR `sentinelCredsPath` OR `authenticator`.
722
+ */
723
+ sentinelCreds?: Uint8Array;
724
+
725
+ /**
726
+ * Path to a sentinel creds file on disk.
727
+ * Provide this OR `sentinelCreds` OR `authenticator`.
728
+ */
729
+ sentinelCredsPath?: string;
730
+
731
+ /**
732
+ * Custom NATS authenticator. If provided, sentinel creds are not used.
733
+ */
734
+ authenticator?: NatsConnectOpts["authenticator"];
735
+
736
+ /**
737
+ * Additional NATS connection options (reconnect, timeouts, etc).
738
+ * `servers`, `token`, `inboxPrefix`, and `authenticator` are controlled by this helper.
739
+ */
740
+ options?: ExtraNatsConnectOpts;
741
+ };
742
+
743
+ server: TrellisServiceRuntimeCreateOpts<TOwnedApi, TTrellisApi>;
744
+ };
745
+
746
+ export type TrellisServiceConnectOpts<
747
+ TOwnedApi extends TrellisAPI = TrellisAPI,
748
+ TTrellisApi extends TrellisAPI = TOwnedApi,
749
+ > = {
750
+ trellisUrl: string;
751
+ contract: ServiceContract<TOwnedApi, TTrellisApi>;
752
+ name: string;
753
+ sessionKeySeed: string;
754
+ server?: TrellisServiceServerOpts;
755
+ };
756
+
757
+ export type ServiceTrellis<
758
+ TOwnedApi extends TrellisAPI,
759
+ TTrellisApi extends TrellisAPI,
760
+ TKv extends ContractKvMetadata = ContractKvMetadata,
761
+ TJobs extends ContractJobsMetadata = {},
762
+ > =
763
+ & Omit<RootTrellis<TTrellisApi>, "mount">
764
+ & {
765
+ mount<M extends RpcMethodName<TOwnedApi>>(
766
+ method: M,
767
+ fn: ({
768
+ input,
769
+ context,
770
+ trellis,
771
+ }: {
772
+ input: RpcMethodInput<TOwnedApi, M>;
773
+ context: RpcHandlerContext;
774
+ trellis: Trellis<TTrellisApi, TKv, TJobs>;
775
+ }) =>
776
+ | Promise<
777
+ Result<RpcMethodOutput<TOwnedApi, M>, RpcHandlerErrorOf<TOwnedApi, M>>
778
+ >
779
+ | Result<
780
+ RpcMethodOutput<TOwnedApi, M>,
781
+ RpcHandlerErrorOf<TOwnedApi, M>
782
+ >,
783
+ ): Promise<void>;
784
+ };
785
+
786
+ type ServiceKvFacade<TKv extends ContractKvMetadata> = {
787
+ [K in keyof TKv]: TKv[K]["required"] extends false
788
+ ? TypedKV<TKv[K]["schema"]> | undefined
789
+ : TypedKV<TKv[K]["schema"]>;
790
+ };
791
+
792
+ type ServiceHandlerResources<
793
+ TKv extends ContractKvMetadata,
794
+ TJobs extends ContractJobsMetadata,
795
+ TTrellisApi extends TrellisAPI,
796
+ > = {
797
+ kv: ServiceKvFacade<TKv>;
798
+ store: Record<string, StoreHandle>;
799
+ jobs: JobsFacadeOf<TJobs, TTrellisApi, TKv>;
800
+ };
801
+
802
+ export type Trellis<
803
+ TTrellisApi extends TrellisAPI,
804
+ TKv extends ContractKvMetadata = ContractKvMetadata,
805
+ TJobs extends ContractJobsMetadata = {},
806
+ > =
807
+ & HandlerTrellis<TTrellisApi>
808
+ & ServiceHandlerResources<TKv, TJobs, TTrellisApi>;
809
+
810
+ type RequestOpts = {
811
+ timeout?: number;
812
+ };
813
+
814
+ export type ServiceContract<
815
+ TOwnedApi extends TrellisAPI,
816
+ TTrellisApi extends TrellisAPI,
817
+ TJobs extends ContractJobsMetadata = {},
818
+ TKv extends ContractKvMetadata = ContractKvMetadata,
819
+ > = {
820
+ CONTRACT_ID: string;
821
+ CONTRACT_DIGEST: string;
822
+ CONTRACT: TrellisContractV1;
823
+ API: {
824
+ owned: TOwnedApi;
825
+ trellis: TTrellisApi;
826
+ };
827
+ readonly [CONTRACT_JOBS_METADATA]?: TJobs;
828
+ readonly [CONTRACT_KV_METADATA]?: TKv;
829
+ };
830
+
831
+ type ContractOwnedApi<
832
+ TContract extends ServiceContract<
833
+ TrellisAPI,
834
+ TrellisAPI,
835
+ ContractJobsMetadata
836
+ >,
837
+ > = TContract["API"]["owned"];
838
+
839
+ type ContractTrellisApi<
840
+ TContract extends ServiceContract<
841
+ TrellisAPI,
842
+ TrellisAPI,
843
+ ContractJobsMetadata
844
+ >,
845
+ > = TContract["API"]["trellis"];
846
+
847
+ type ContractJobsOf<
848
+ TContract extends ServiceContract<
849
+ TrellisAPI,
850
+ TrellisAPI,
851
+ ContractJobsMetadata,
852
+ ContractKvMetadata
853
+ >,
854
+ > = NonNullable<TContract[typeof CONTRACT_JOBS_METADATA]>;
855
+
856
+ type ContractKvOf<
857
+ TContract extends ServiceContract<
858
+ TrellisAPI,
859
+ TrellisAPI,
860
+ ContractJobsMetadata,
861
+ ContractKvMetadata
862
+ >,
863
+ > = NonNullable<TContract[typeof CONTRACT_KV_METADATA]>;
864
+
865
+ type ContractJobName<
866
+ TContract extends ServiceContract<
867
+ TrellisAPI,
868
+ TrellisAPI,
869
+ ContractJobsMetadata
870
+ >,
871
+ > = keyof ContractJobsOf<TContract> & string;
872
+
873
+ type ContractOperationName<
874
+ TContract extends ServiceContract<
875
+ TrellisAPI,
876
+ TrellisAPI,
877
+ ContractJobsMetadata
878
+ >,
879
+ > = keyof ContractOwnedApi<TContract>["operations"] & string;
880
+
881
+ type ContractJobPayload<
882
+ TContract extends ServiceContract<
883
+ TrellisAPI,
884
+ TrellisAPI,
885
+ ContractJobsMetadata
886
+ >,
887
+ TJob extends ContractJobName<TContract>,
888
+ > = ContractJobsOf<TContract>[TJob]["payload"];
889
+
890
+ type ContractJobResult<
891
+ TContract extends ServiceContract<
892
+ TrellisAPI,
893
+ TrellisAPI,
894
+ ContractJobsMetadata
895
+ >,
896
+ TJob extends ContractJobName<TContract>,
897
+ > = ContractJobsOf<TContract>[TJob]["result"];
898
+
899
+ /** Arguments passed to a typed Trellis service job handler. */
900
+ export type JobArgs<
901
+ TContract extends ServiceContract<
902
+ TrellisAPI,
903
+ TrellisAPI,
904
+ ContractJobsMetadata,
905
+ ContractKvMetadata
906
+ >,
907
+ TJob extends ContractJobName<TContract>,
908
+ > = {
909
+ job: PublicActiveJob<
910
+ ContractJobPayload<TContract, TJob>,
911
+ ContractJobResult<TContract, TJob>
912
+ >;
913
+ trellis: Trellis<
914
+ ContractTrellisApi<TContract>,
915
+ ContractKvOf<TContract>,
916
+ ContractJobsOf<TContract>
917
+ >;
918
+ };
919
+
920
+ /** Result returned by a typed Trellis service job handler. */
921
+ export type JobResult<
922
+ TContract extends ServiceContract<
923
+ TrellisAPI,
924
+ TrellisAPI,
925
+ ContractJobsMetadata,
926
+ ContractKvMetadata
927
+ >,
928
+ TJob extends ContractJobName<TContract>,
929
+ > = Result<ContractJobResult<TContract, TJob>, BaseError>;
930
+
931
+ export type RpcHandler<
932
+ TContract extends ServiceContract<
933
+ TrellisAPI,
934
+ TrellisAPI,
935
+ ContractJobsMetadata,
936
+ ContractKvMetadata
937
+ >,
938
+ M extends RpcMethodName<ContractOwnedApi<TContract>>,
939
+ > = ({
940
+ input,
941
+ context,
942
+ trellis,
943
+ }: {
944
+ input: RpcMethodInput<ContractOwnedApi<TContract>, M>;
945
+ context: RpcHandlerContext;
946
+ trellis: Trellis<
947
+ ContractTrellisApi<TContract>,
948
+ ContractKvOf<TContract>,
949
+ ContractJobsOf<TContract>
950
+ >;
951
+ }) =>
952
+ | Promise<
953
+ Result<
954
+ RpcMethodOutput<ContractOwnedApi<TContract>, M>,
955
+ RpcHandlerErrorOf<ContractOwnedApi<TContract>, M>
956
+ >
957
+ >
958
+ | Result<
959
+ RpcMethodOutput<ContractOwnedApi<TContract>, M>,
960
+ RpcHandlerErrorOf<ContractOwnedApi<TContract>, M>
961
+ >;
962
+
963
+ export type JobHandler<
964
+ TContract extends ServiceContract<
965
+ TrellisAPI,
966
+ TrellisAPI,
967
+ ContractJobsMetadata,
968
+ ContractKvMetadata
969
+ >,
970
+ TJob extends ContractJobName<TContract>,
971
+ > = (args: JobArgs<TContract, TJob>) => Promise<JobResult<TContract, TJob>>;
972
+
973
+ export type OperationHandler<
974
+ TContract extends ServiceContract<
975
+ TrellisAPI,
976
+ TrellisAPI,
977
+ ContractJobsMetadata,
978
+ ContractKvMetadata
979
+ >,
980
+ O extends ContractOperationName<TContract>,
981
+ > = (
982
+ args:
983
+ & OperationHandlerContext<
984
+ InferSchemaType<ContractOwnedApi<TContract>["operations"][O]["input"]>,
985
+ OperationProgressOf<ContractOwnedApi<TContract>, O>,
986
+ OperationOutputOf<ContractOwnedApi<TContract>, O>,
987
+ OperationTransferContextOf<ContractOwnedApi<TContract>, O>
988
+ >
989
+ & {
990
+ trellis: Trellis<
991
+ ContractTrellisApi<TContract>,
992
+ ContractKvOf<TContract>,
993
+ ContractJobsOf<TContract>
994
+ >;
995
+ },
996
+ ) => unknown | Promise<unknown>;
997
+
998
+ export type JobQueue<
999
+ TPayload,
1000
+ TResult,
1001
+ TTrellisApi extends TrellisAPI,
1002
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1003
+ TJobs extends ContractJobsMetadata = {},
1004
+ > = {
1005
+ create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
1006
+ handle(
1007
+ handler: (args: {
1008
+ job: PublicActiveJob<TPayload, TResult>;
1009
+ trellis: Trellis<TTrellisApi, TKv, TJobs>;
1010
+ }) => Promise<Result<TResult, BaseError>>,
1011
+ ): void;
1012
+ };
1013
+
1014
+ export type JobsFacadeOf<
1015
+ TJobs extends ContractJobsMetadata,
1016
+ TTrellisApi extends TrellisAPI,
1017
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1018
+ > = {
1019
+ [K in keyof TJobs]: JobQueue<
1020
+ TJobs[K]["payload"],
1021
+ TJobs[K]["result"],
1022
+ TTrellisApi,
1023
+ TKv,
1024
+ TJobs
1025
+ >;
1026
+ };
1027
+
1028
+ const MANAGED_JOB_WORKERS = Symbol("trellis.managedJobWorkers");
1029
+
1030
+ type ManagedJobWorkers = {
1031
+ start(): AsyncResult<JobWorkerHostAdapter, BaseError>;
1032
+ stop(): AsyncResult<void, BaseError>;
1033
+ };
1034
+
1035
+ type ManagedJobsFacade<
1036
+ TJobs extends ContractJobsMetadata,
1037
+ TTrellisApi extends TrellisAPI,
1038
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1039
+ > = JobsFacadeOf<TJobs, TTrellisApi, TKv> & {
1040
+ [MANAGED_JOB_WORKERS]: ManagedJobWorkers;
1041
+ };
1042
+
1043
+ export type OperationRegistration<
1044
+ TOwnedApi extends TrellisAPI,
1045
+ TTrellisApi extends TrellisAPI,
1046
+ O extends keyof TOwnedApi["operations"] & string,
1047
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1048
+ TJobs extends ContractJobsMetadata = {},
1049
+ > = {
1050
+ accept(args: {
1051
+ sessionKey: string;
1052
+ }): AsyncResult<
1053
+ AcceptedOperation<
1054
+ OperationProgressOf<TOwnedApi, O>,
1055
+ OperationOutputOf<TOwnedApi, O>
1056
+ >,
1057
+ UnexpectedError
1058
+ >;
1059
+ /**
1060
+ * Loads an existing operation by id and returns a service-side control handle.
1061
+ * The operation must belong to this service and registration name.
1062
+ */
1063
+ control(
1064
+ operationId: string,
1065
+ ): AsyncResult<
1066
+ OperationRuntimeHandle<
1067
+ OperationProgressOf<TOwnedApi, O>,
1068
+ OperationOutputOf<TOwnedApi, O>
1069
+ >,
1070
+ BaseError
1071
+ >;
1072
+ handle(
1073
+ handler: (
1074
+ args:
1075
+ & OperationHandlerContext<
1076
+ InferSchemaType<TOwnedApi["operations"][O]["input"]>,
1077
+ OperationProgressOf<TOwnedApi, O>,
1078
+ OperationOutputOf<TOwnedApi, O>,
1079
+ OperationTransferContextOf<TOwnedApi, O>
1080
+ >
1081
+ & { trellis: Trellis<TTrellisApi, TKv, TJobs> },
1082
+ ) => unknown | Promise<unknown>,
1083
+ ): Promise<void>;
1084
+ };
1085
+
1086
+ export type FeedRegistration<
1087
+ TOwnedApi extends TrellisAPI,
1088
+ F extends keyof TOwnedApi["feeds"] & string,
1089
+ > = RootFeedRegistration<FeedInputOf<TOwnedApi, F>, FeedEventOf<TOwnedApi, F>>;
1090
+
1091
+ export type TrellisServiceConnectArgs<
1092
+ TContract extends ServiceContract<
1093
+ TrellisAPI,
1094
+ TrellisAPI,
1095
+ ContractJobsMetadata,
1096
+ ContractKvMetadata
1097
+ >,
1098
+ > = {
1099
+ trellisUrl: string;
1100
+ contract: TContract;
1101
+ name: string;
1102
+ sessionKeySeed: string;
1103
+ server?: TrellisServiceServerOpts;
1104
+ };
1105
+
1106
+ export type TrellisServiceInternalConnectArgs<
1107
+ TOwnedApi extends TrellisAPI = TrellisAPI,
1108
+ TTrellisApi extends TrellisAPI = TOwnedApi,
1109
+ TKv extends ContractKvMetadata = {},
1110
+ > = TrellisServiceRuntimeConnectOpts<TOwnedApi, TTrellisApi> & {
1111
+ contractId?: string;
1112
+ contractDigest: string;
1113
+ contractKv?: TKv;
1114
+ };
1115
+
1116
+ export async function createConnectedService<
1117
+ TOwnedApi extends TrellisAPI,
1118
+ TTrellisApi extends TrellisAPI,
1119
+ TJobs extends ContractJobsMetadata = {},
1120
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1121
+ >(args: {
1122
+ name: string;
1123
+ auth: SessionAuth;
1124
+ nc: NatsConnection;
1125
+ contractId?: string;
1126
+ contractDigest?: string;
1127
+ contractJobs: TJobs;
1128
+ contractKv: TKv;
1129
+ server: TrellisServiceRuntimeCreateOpts<TOwnedApi, TTrellisApi>;
1130
+ bindings: ResourceBindings;
1131
+ }): Promise<TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>> {
1132
+ const resolvedLog = resolveServiceLogger(args.server.log);
1133
+ const connection = observeNatsTrellisConnection({
1134
+ kind: "service",
1135
+ nc: args.nc,
1136
+ log: false,
1137
+ lifecycleLog: {
1138
+ log: resolvedLog,
1139
+ context: { service: args.name },
1140
+ },
1141
+ });
1142
+ const currentApi = (args.server.trellisApi ?? args.server.api) as
1143
+ & TOwnedApi
1144
+ & TTrellisApi;
1145
+ const runtimeApi = {
1146
+ ...currentApi,
1147
+ rpc: {
1148
+ ...trellisAuth.API.owned.rpc,
1149
+ ...currentApi.rpc,
1150
+ },
1151
+ } as TOwnedApi & TTrellisApi;
1152
+
1153
+ const server = TrellisServiceRuntime.create(
1154
+ args.name,
1155
+ args.nc,
1156
+ { sessionKey: args.auth.sessionKey, sign: args.auth.sign },
1157
+ {
1158
+ log: resolvedLog,
1159
+ timeout: args.server.timeout,
1160
+ stream: args.server.stream,
1161
+ noResponderRetry: args.server.noResponderRetry,
1162
+ api: runtimeApi,
1163
+ connection,
1164
+ transferSupport: {
1165
+ openOperationTransfer: (transferArgs) =>
1166
+ getTransfer().createOperationUpload(transferArgs),
1167
+ },
1168
+ version: args.server.version,
1169
+ },
1170
+ );
1171
+
1172
+ const outbound = new RootTrellis<TTrellisApi>(
1173
+ args.name,
1174
+ args.nc,
1175
+ { sessionKey: args.auth.sessionKey, sign: args.auth.sign },
1176
+ {
1177
+ log: resolvedLog,
1178
+ timeout: args.server.timeout,
1179
+ stream: args.server.stream,
1180
+ noResponderRetry: args.server.noResponderRetry,
1181
+ api: runtimeApi,
1182
+ connection,
1183
+ },
1184
+ );
1185
+
1186
+ let transfer: ServiceTransfer | undefined;
1187
+ const getTransfer = (): ServiceTransfer => {
1188
+ if (!transfer) {
1189
+ throw new Error("service transfer helper accessed before initialization");
1190
+ }
1191
+ return transfer;
1192
+ };
1193
+ let handlerResources:
1194
+ | ServiceHandlerResources<TKv, TJobs, TTrellisApi>
1195
+ | undefined;
1196
+ const getHandlerResources = (): ServiceHandlerResources<
1197
+ TKv,
1198
+ TJobs,
1199
+ TTrellisApi
1200
+ > => {
1201
+ if (!handlerResources) {
1202
+ throw new Error(
1203
+ "service resource handles accessed before initialization",
1204
+ );
1205
+ }
1206
+ return handlerResources;
1207
+ };
1208
+
1209
+ const handlerTrellis: Trellis<TTrellisApi, TKv, TJobs> = {
1210
+ request: outbound.request.bind(outbound),
1211
+ publish: (event, data) => outbound.publish(event, data),
1212
+ event: (event, subjectData, fn, opts) =>
1213
+ outbound.event(
1214
+ event,
1215
+ subjectData,
1216
+ fn as (message: unknown) => ReturnType<typeof fn>,
1217
+ opts,
1218
+ ),
1219
+ operation: (operation) => outbound.operation(operation),
1220
+ get kv() {
1221
+ return getHandlerResources().kv;
1222
+ },
1223
+ get store() {
1224
+ return getHandlerResources().store;
1225
+ },
1226
+ get jobs() {
1227
+ return getHandlerResources().jobs;
1228
+ },
1229
+ };
1230
+ const trellis = Object.assign(
1231
+ outbound,
1232
+ {
1233
+ mount: <M extends RpcMethodName<TOwnedApi>>(
1234
+ method: M,
1235
+ fn: ({
1236
+ input,
1237
+ context,
1238
+ trellis,
1239
+ }: {
1240
+ input: RpcMethodInput<TOwnedApi, M>;
1241
+ context: RpcHandlerContext;
1242
+ trellis: Trellis<TTrellisApi, TKv, TJobs>;
1243
+ }) =>
1244
+ | Promise<
1245
+ Result<
1246
+ RpcMethodOutput<TOwnedApi, M>,
1247
+ RpcHandlerErrorOf<TOwnedApi, M>
1248
+ >
1249
+ >
1250
+ | Result<
1251
+ RpcMethodOutput<TOwnedApi, M>,
1252
+ RpcHandlerErrorOf<TOwnedApi, M>
1253
+ >,
1254
+ ) =>
1255
+ server.mountRuntime(
1256
+ method as string,
1257
+ async ({ input, context }) =>
1258
+ await Promise.resolve(
1259
+ fn({
1260
+ input: input as RpcMethodInput<TOwnedApi, M>,
1261
+ context,
1262
+ trellis: handlerTrellis,
1263
+ }),
1264
+ ) as Result<unknown, BaseError>,
1265
+ ),
1266
+ },
1267
+ ) as ServiceTrellis<TOwnedApi, TTrellisApi, TKv, TJobs>;
1268
+
1269
+ const health = new ServiceHealth({
1270
+ serviceName: args.name,
1271
+ contractId: args.contractId ?? "unknown",
1272
+ contractDigest: args.contractDigest ?? "unknown",
1273
+ publishIntervalMs: args.server.health?.publishIntervalMs ?? 30_000,
1274
+ checks: args.server.healthChecks,
1275
+ });
1276
+ health.add("nats", () => ({
1277
+ status: args.nc.isClosed() ? "failed" : "ok",
1278
+ ...(args.nc.isClosed() ? { summary: "NATS connection closed" } : {}),
1279
+ }));
1280
+
1281
+ await mountStandardHealthRpc(server, {
1282
+ response: () => health.response(),
1283
+ });
1284
+
1285
+ const heartbeatEventEnabled = Boolean(
1286
+ (currentApi.events as Record<string, unknown> | undefined)
1287
+ ?.["Health.Heartbeat"],
1288
+ );
1289
+ let healthPublishTimer: ReturnType<typeof setInterval> | undefined;
1290
+ let publishingHeartbeat = false;
1291
+ const publishHealthHeartbeat = async (): Promise<void> => {
1292
+ if (!heartbeatEventEnabled || publishingHeartbeat) {
1293
+ return;
1294
+ }
1295
+
1296
+ publishingHeartbeat = true;
1297
+ try {
1298
+ const heartbeat = await health.heartbeat();
1299
+ const published = await (
1300
+ outbound.publish as (
1301
+ event: string,
1302
+ data: Record<string, unknown>,
1303
+ ) => AsyncResult<void, BaseError>
1304
+ )("Health.Heartbeat", heartbeat as Record<string, unknown>);
1305
+ const value = published.take();
1306
+ if (isErr(value)) {
1307
+ resolvedLog.warn(
1308
+ { error: value.error },
1309
+ "Failed to publish health heartbeat",
1310
+ );
1311
+ }
1312
+ } catch (error) {
1313
+ resolvedLog.warn(
1314
+ { error },
1315
+ "Failed to build or publish health heartbeat",
1316
+ );
1317
+ } finally {
1318
+ publishingHeartbeat = false;
1319
+ }
1320
+ };
1321
+ const stopHealthPublishing = async (): Promise<void> => {
1322
+ if (healthPublishTimer !== undefined) {
1323
+ clearInterval(healthPublishTimer);
1324
+ healthPublishTimer = undefined;
1325
+ }
1326
+ };
1327
+
1328
+ const kv = await openServiceKvBindings({
1329
+ nc: args.nc,
1330
+ bindings: args.bindings.kv ?? {},
1331
+ contractKv: args.contractKv,
1332
+ });
1333
+
1334
+ const operationTransfer = new ServiceTransfer({
1335
+ name: args.name,
1336
+ nc: args.nc,
1337
+ auth: args.auth,
1338
+ stores: Object.fromEntries(
1339
+ Object.entries(args.bindings.store ?? {}).map(([alias, binding]) => [
1340
+ alias,
1341
+ new StoreHandle(args.nc, binding),
1342
+ ]),
1343
+ ),
1344
+ });
1345
+
1346
+ const service = new TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>(
1347
+ args.name,
1348
+ args.auth,
1349
+ args.nc,
1350
+ server,
1351
+ trellis,
1352
+ handlerTrellis,
1353
+ kv,
1354
+ args.contractJobs,
1355
+ args.bindings,
1356
+ operationTransfer,
1357
+ health,
1358
+ stopHealthPublishing,
1359
+ connection,
1360
+ );
1361
+ handlerResources = {
1362
+ kv: service.kv,
1363
+ store: service.store,
1364
+ jobs: service.jobs,
1365
+ };
1366
+ transfer = operationTransfer;
1367
+
1368
+ if (heartbeatEventEnabled) {
1369
+ await publishHealthHeartbeat();
1370
+ healthPublishTimer = setInterval(() => {
1371
+ void publishHealthHeartbeat();
1372
+ }, health.publishIntervalMs);
1373
+ void args.nc.closed().finally(stopHealthPublishing);
1374
+ }
1375
+
1376
+ return service;
1377
+ }
1378
+
1379
+ type RegisteredJobHandler<TPayload, TResult> = (
1380
+ job: PublicActiveJob<TPayload, TResult>,
1381
+ ) => Promise<Result<TResult, BaseError>>;
1382
+
1383
+ function toUnexpectedError(cause: unknown): UnexpectedError {
1384
+ return cause instanceof UnexpectedError
1385
+ ? cause
1386
+ : new UnexpectedError({ cause });
1387
+ }
1388
+
1389
+ function okVoid(): Result<void, never> {
1390
+ return Result.ok(undefined);
1391
+ }
1392
+
1393
+ function wrapVoidTask(task: () => Promise<void>): AsyncResult<void, BaseError> {
1394
+ return AsyncResult.from((async () => {
1395
+ try {
1396
+ await task();
1397
+ return okVoid();
1398
+ } catch (cause) {
1399
+ return Result.err(toUnexpectedError(cause));
1400
+ }
1401
+ })());
1402
+ }
1403
+
1404
+ function isTerminalJobState(
1405
+ state: string,
1406
+ ): state is TerminalJob<unknown, unknown>["state"] {
1407
+ return state === "completed" || state === "failed" || state === "cancelled" ||
1408
+ state === "expired" || state === "dead" || state === "dismissed";
1409
+ }
1410
+
1411
+ function isTerminalJobSnapshot<TPayload, TResult>(
1412
+ snapshot: JobSnapshot<TPayload, TResult>,
1413
+ ): snapshot is TerminalJob<TPayload, TResult> {
1414
+ return isTerminalJobState(snapshot.state);
1415
+ }
1416
+
1417
+ function operationOutputsEqual(left: unknown, right: unknown): boolean {
1418
+ if (Object.is(left, right)) return true;
1419
+ if (typeof left !== typeof right || left === null || right === null) {
1420
+ return false;
1421
+ }
1422
+ if (Array.isArray(left) || Array.isArray(right)) {
1423
+ return Array.isArray(left) && Array.isArray(right) &&
1424
+ left.length === right.length &&
1425
+ left.every((value, index) => operationOutputsEqual(value, right[index]));
1426
+ }
1427
+ if (typeof left === "object") {
1428
+ if (Object.getPrototypeOf(left) !== Object.prototype) return false;
1429
+ if (Object.getPrototypeOf(right) !== Object.prototype) return false;
1430
+ const leftRecord = left as Record<string, unknown>;
1431
+ const rightRecord = right as Record<string, unknown>;
1432
+ const leftKeys = Object.keys(leftRecord);
1433
+ const rightKeys = Object.keys(rightRecord);
1434
+ return leftKeys.length === rightKeys.length &&
1435
+ leftKeys.every((key) =>
1436
+ Object.hasOwn(rightRecord, key) &&
1437
+ operationOutputsEqual(leftRecord[key], rightRecord[key])
1438
+ );
1439
+ }
1440
+ return false;
1441
+ }
1442
+
1443
+ function parseJobLifecycleEvent<TPayload, TResult>(
1444
+ data: Uint8Array,
1445
+ ): InternalJobEvent<TPayload, TResult> | undefined {
1446
+ try {
1447
+ const decoded = JSON.parse(new TextDecoder().decode(data));
1448
+ if (!Value.Check(JobEventSchema, decoded)) {
1449
+ return undefined;
1450
+ }
1451
+ return decoded as InternalJobEvent<TPayload, TResult>;
1452
+ } catch {
1453
+ return undefined;
1454
+ }
1455
+ }
1456
+
1457
+ function jobLifecycleKey(
1458
+ service: string,
1459
+ jobType: string,
1460
+ jobId: string,
1461
+ ): string {
1462
+ return `${service}.${jobType}.${jobId}`;
1463
+ }
1464
+
1465
+ function subjectMatchesLifecycleEvent(
1466
+ subject: string,
1467
+ queueBinding: ResourceBindingJobsQueue,
1468
+ event: InternalJobEvent,
1469
+ ): boolean {
1470
+ const prefix = `${queueBinding.publishPrefix}.`;
1471
+ if (!subject.startsWith(prefix)) return false;
1472
+
1473
+ const suffix = subject.slice(prefix.length).split(".");
1474
+ return suffix.length === 2 && suffix[0] === event.jobId &&
1475
+ suffix[1] === event.eventType;
1476
+ }
1477
+
1478
+ function snapshotFromLifecycleEvent<TPayload, TResult>(
1479
+ current: JobSnapshot<TPayload, TResult>,
1480
+ event: InternalJobEvent<TPayload, TResult>,
1481
+ ): JobSnapshot<TPayload, TResult> {
1482
+ if (
1483
+ event.service !== current.service || event.jobType !== current.type ||
1484
+ event.jobId !== current.id
1485
+ ) {
1486
+ return current;
1487
+ }
1488
+ if (isTerminalJobState(current.state)) {
1489
+ return current;
1490
+ }
1491
+
1492
+ const base: JobSnapshot<TPayload, TResult> = {
1493
+ ...current,
1494
+ state: event.state,
1495
+ updatedAt: event.timestamp,
1496
+ tries: event.tries,
1497
+ ...(event.maxTries !== undefined ? { maxTries: event.maxTries } : {}),
1498
+ ...(event.deadline !== undefined ? { deadline: event.deadline } : {}),
1499
+ };
1500
+
1501
+ switch (event.eventType) {
1502
+ case "created":
1503
+ case "retried":
1504
+ return event.payload === undefined ? base : {
1505
+ ...base,
1506
+ payload: event.payload,
1507
+ };
1508
+ case "started":
1509
+ return { ...base, startedAt: event.timestamp };
1510
+ case "progress":
1511
+ return event.progress === undefined ? base : {
1512
+ ...base,
1513
+ progress: event.progress,
1514
+ };
1515
+ case "logged":
1516
+ return event.logs === undefined ? base : {
1517
+ ...base,
1518
+ logs: [...(current.logs ?? []), ...event.logs],
1519
+ };
1520
+ case "completed":
1521
+ return {
1522
+ ...base,
1523
+ completedAt: event.timestamp,
1524
+ ...(event.result !== undefined ? { result: event.result } : {}),
1525
+ };
1526
+ case "failed":
1527
+ case "cancelled":
1528
+ case "expired":
1529
+ case "dead":
1530
+ case "dismissed":
1531
+ return event.error === undefined ? base : {
1532
+ ...base,
1533
+ lastError: event.error,
1534
+ };
1535
+ }
1536
+
1537
+ return base;
1538
+ }
1539
+
1540
+ function headersFromJobContext(context: InternalJobContext): MsgHdrs {
1541
+ const headers = natsHeaders();
1542
+ headers.set("request-id", context.requestId);
1543
+ headers.set("traceparent", context.traceparent);
1544
+ if (context.tracestate) {
1545
+ headers.set("tracestate", context.tracestate);
1546
+ }
1547
+ return headers;
1548
+ }
1549
+
1550
+ type JobLifecycleWaiter<TPayload, TResult> = {
1551
+ resolve(snapshot: TerminalJob<TPayload, TResult>): void;
1552
+ reject(cause: BaseError): void;
1553
+ };
1554
+
1555
+ type JobLifecycleTracker = {
1556
+ watch(queueBinding: ResourceBindingJobsQueue): void;
1557
+ seed<TPayload, TResult>(snapshot: JobSnapshot<TPayload, TResult>): void;
1558
+ get<TPayload, TResult>(args: {
1559
+ service: string;
1560
+ jobType: string;
1561
+ id: string;
1562
+ }): JobSnapshot<TPayload, TResult> | undefined;
1563
+ apply<TPayload, TResult>(
1564
+ event: InternalJobEvent<TPayload, TResult>,
1565
+ ): JobSnapshot<TPayload, TResult> | undefined;
1566
+ wait<TPayload, TResult>(
1567
+ snapshot: JobSnapshot<TPayload, TResult>,
1568
+ ): Promise<TerminalJob<TPayload, TResult>>;
1569
+ stop(): void;
1570
+ };
1571
+
1572
+ function createJobLifecycleTracker(nc: NatsConnection): JobLifecycleTracker {
1573
+ const snapshots = new Map<string, JobSnapshot<unknown, unknown>>();
1574
+ const waiters = new Map<string, JobLifecycleWaiter<unknown, unknown>[]>();
1575
+ const subscriptions = new Map<string, Subscription>();
1576
+ let stopped = false;
1577
+
1578
+ const notify = (key: string, snapshot: JobSnapshot<unknown, unknown>) => {
1579
+ if (!isTerminalJobSnapshot(snapshot)) return;
1580
+ const pending = waiters.get(key) ?? [];
1581
+ waiters.delete(key);
1582
+ for (const waiter of pending) waiter.resolve(snapshot);
1583
+ };
1584
+
1585
+ const apply = <TPayload, TResult>(
1586
+ event: InternalJobEvent<TPayload, TResult>,
1587
+ ): JobSnapshot<TPayload, TResult> | undefined => {
1588
+ const key = jobLifecycleKey(event.service, event.jobType, event.jobId);
1589
+ const current = snapshots.get(key) as
1590
+ | JobSnapshot<TPayload, TResult>
1591
+ | undefined;
1592
+ if (!current) return undefined;
1593
+
1594
+ const next = snapshotFromLifecycleEvent(current, event);
1595
+ snapshots.set(key, next as JobSnapshot<unknown, unknown>);
1596
+ notify(key, next as JobSnapshot<unknown, unknown>);
1597
+ return next;
1598
+ };
1599
+
1600
+ return {
1601
+ watch(queueBinding) {
1602
+ if (subscriptions.has(queueBinding.publishPrefix)) return;
1603
+
1604
+ const subscription = nc.subscribe(`${queueBinding.publishPrefix}.*.*`);
1605
+ subscriptions.set(queueBinding.publishPrefix, subscription);
1606
+ void (async () => {
1607
+ for await (const msg of subscription) {
1608
+ const event = parseJobLifecycleEvent(msg.data);
1609
+ if (
1610
+ !event || !subjectMatchesLifecycleEvent(
1611
+ msg.subject,
1612
+ queueBinding,
1613
+ event,
1614
+ )
1615
+ ) {
1616
+ continue;
1617
+ }
1618
+ apply(event);
1619
+ }
1620
+ })();
1621
+ },
1622
+ seed<TPayload, TResult>(snapshot: JobSnapshot<TPayload, TResult>) {
1623
+ const key = jobLifecycleKey(snapshot.service, snapshot.type, snapshot.id);
1624
+ const current = snapshots.get(key) as
1625
+ | JobSnapshot<TPayload, TResult>
1626
+ | undefined;
1627
+ if (current && isTerminalJobState(current.state)) return;
1628
+ snapshots.set(key, snapshot as JobSnapshot<unknown, unknown>);
1629
+ notify(key, snapshot as JobSnapshot<unknown, unknown>);
1630
+ },
1631
+ get<TPayload, TResult>(args: {
1632
+ service: string;
1633
+ jobType: string;
1634
+ id: string;
1635
+ }) {
1636
+ const current = snapshots.get(
1637
+ jobLifecycleKey(args.service, args.jobType, args.id),
1638
+ );
1639
+ return current as JobSnapshot<TPayload, TResult> | undefined;
1640
+ },
1641
+ apply,
1642
+ wait<TPayload, TResult>(snapshot: JobSnapshot<TPayload, TResult>) {
1643
+ this.seed(snapshot);
1644
+ const key = jobLifecycleKey(snapshot.service, snapshot.type, snapshot.id);
1645
+ const current = snapshots.get(key) as
1646
+ | JobSnapshot<TPayload, TResult>
1647
+ | undefined;
1648
+ if (current && isTerminalJobSnapshot(current)) {
1649
+ return Promise.resolve(current);
1650
+ }
1651
+ if (stopped) {
1652
+ return Promise.reject(toUnexpectedError(
1653
+ new Error("job lifecycle tracker stopped"),
1654
+ ));
1655
+ }
1656
+
1657
+ return new Promise((resolve, reject) => {
1658
+ const pending = waiters.get(key) ?? [];
1659
+ pending.push({
1660
+ resolve: resolve as (snapshot: TerminalJob<unknown, unknown>) => void,
1661
+ reject,
1662
+ });
1663
+ waiters.set(key, pending);
1664
+ });
1665
+ },
1666
+ stop() {
1667
+ stopped = true;
1668
+ for (const subscription of subscriptions.values()) {
1669
+ subscription.unsubscribe();
1670
+ }
1671
+ subscriptions.clear();
1672
+ const error = toUnexpectedError(
1673
+ new Error("job lifecycle tracker stopped"),
1674
+ );
1675
+ for (const pending of waiters.values()) {
1676
+ for (const waiter of pending) waiter.reject(error);
1677
+ }
1678
+ waiters.clear();
1679
+ },
1680
+ };
1681
+ }
1682
+
1683
+ function createJobRef<TPayload, TResult>(args: {
1684
+ nc: NatsConnection;
1685
+ queueType: string;
1686
+ jobsBinding: ResourceBindingJobs;
1687
+ queueBinding: ResourceBindingJobsQueue;
1688
+ seed: JobSnapshot<TPayload, TResult>;
1689
+ lifecycle: JobLifecycleTracker;
1690
+ }): JobRef<TPayload, TResult> {
1691
+ args.lifecycle.seed(args.seed);
1692
+
1693
+ return new JobRef<TPayload, TResult>(
1694
+ {
1695
+ id: args.seed.id,
1696
+ service: args.seed.service,
1697
+ jobType: args.queueType,
1698
+ },
1699
+ {
1700
+ get: () =>
1701
+ AsyncResult.ok(
1702
+ args.lifecycle.get<TPayload, TResult>({
1703
+ service: args.seed.service,
1704
+ jobType: args.queueType,
1705
+ id: args.seed.id,
1706
+ }) ?? args.seed,
1707
+ ),
1708
+ wait: () =>
1709
+ AsyncResult.from((async () => {
1710
+ try {
1711
+ return Result.ok(await args.lifecycle.wait(args.seed));
1712
+ } catch (cause) {
1713
+ return Result.err(toUnexpectedError(cause));
1714
+ }
1715
+ })()),
1716
+ cancel: () =>
1717
+ AsyncResult.from((async () => {
1718
+ const current = args.lifecycle.get<TPayload, TResult>({
1719
+ service: args.seed.service,
1720
+ jobType: args.queueType,
1721
+ id: args.seed.id,
1722
+ }) ?? args.seed;
1723
+ if (isTerminalJobState(current.state)) {
1724
+ return Result.ok(current);
1725
+ }
1726
+
1727
+ const event: InternalJobEvent<TPayload, TResult> = {
1728
+ jobId: args.seed.id,
1729
+ service: current.service,
1730
+ jobType: args.queueType,
1731
+ eventType: "cancelled",
1732
+ state: "cancelled",
1733
+ previousState: current.state,
1734
+ context: current.context,
1735
+ tries: current.tries,
1736
+ error: "cancelled",
1737
+ timestamp: new Date().toISOString(),
1738
+ };
1739
+
1740
+ try {
1741
+ args.nc.publish(
1742
+ `${args.queueBinding.publishPrefix}.${args.seed.id}.cancelled`,
1743
+ new TextEncoder().encode(JSON.stringify(event)),
1744
+ { headers: headersFromJobContext(event.context) },
1745
+ );
1746
+ } catch (cause) {
1747
+ return Result.err(toUnexpectedError(cause));
1748
+ }
1749
+
1750
+ return Result.ok(args.lifecycle.apply(event) ?? current);
1751
+ })()),
1752
+ },
1753
+ );
1754
+ }
1755
+
1756
+ function createNoopJobWorkerHost(): JobWorkerHostAdapter {
1757
+ return new JobWorkerHostAdapter({
1758
+ stop: () => AsyncResult.ok(undefined),
1759
+ join: () => AsyncResult.ok(undefined),
1760
+ });
1761
+ }
1762
+
1763
+ async function closeFailedServiceBootstrapConnection(
1764
+ nc: NatsConnection,
1765
+ ): Promise<void> {
1766
+ if (nc.isClosed()) {
1767
+ return;
1768
+ }
1769
+
1770
+ try {
1771
+ await nc.drain();
1772
+ } catch {
1773
+ await nc.closed().catch(() => undefined);
1774
+ }
1775
+ }
1776
+
1777
+ function createJobsFacade<
1778
+ TJobs extends ContractJobsMetadata,
1779
+ TTrellisApi extends TrellisAPI,
1780
+ TKv extends ContractKvMetadata = ContractKvMetadata,
1781
+ >(args: {
1782
+ serviceName: string;
1783
+ nc: NatsConnection;
1784
+ contractJobs: TJobs;
1785
+ trellis: Trellis<TTrellisApi, TKv, TJobs>;
1786
+ jobsBinding?: ResourceBindingJobs;
1787
+ workStream?: string;
1788
+ }): ManagedJobsFacade<TJobs, TTrellisApi, TKv> {
1789
+ const handlers = new Map<string, RegisteredJobHandler<unknown, unknown>>();
1790
+ const jobsFacade: Record<string, unknown> = {};
1791
+ const lifecycle = createJobLifecycleTracker(args.nc);
1792
+ let activeHost: JobWorkerHostAdapter | undefined;
1793
+ let startupPromise:
1794
+ | Promise<Result<JobWorkerHostAdapter, BaseError>>
1795
+ | undefined;
1796
+ let stopPromise: Promise<Result<void, BaseError>> | undefined;
1797
+
1798
+ for (const queueType of Object.keys(args.contractJobs ?? {})) {
1799
+ const queueBinding = args.jobsBinding?.queues[queueType];
1800
+ if (queueBinding) lifecycle.watch(queueBinding);
1801
+
1802
+ jobsFacade[queueType] = {
1803
+ create: (payload) =>
1804
+ AsyncResult.from((async () => {
1805
+ try {
1806
+ const jobsBinding = args.jobsBinding;
1807
+ if (!jobsBinding) {
1808
+ return Result.err(
1809
+ toUnexpectedError(new Error("Jobs bindings are unavailable")),
1810
+ );
1811
+ }
1812
+ const queueBinding = jobsBinding.queues[queueType];
1813
+ if (!queueBinding) {
1814
+ return Result.err(toUnexpectedError(
1815
+ new Error(
1816
+ `Jobs binding for queue '${queueType}' is unavailable`,
1817
+ ),
1818
+ ));
1819
+ }
1820
+
1821
+ const manager = new InternalJobManager<unknown, unknown>({
1822
+ nc: args.nc,
1823
+ jobs: jobsBinding,
1824
+ });
1825
+ await args.nc.flush();
1826
+ const created = await manager.create(queueType, payload);
1827
+ return Result.ok(createJobRef({
1828
+ nc: args.nc,
1829
+ queueType,
1830
+ jobsBinding,
1831
+ queueBinding,
1832
+ seed: created as JobSnapshot<unknown, unknown>,
1833
+ lifecycle,
1834
+ }));
1835
+ } catch (cause) {
1836
+ return Result.err(toUnexpectedError(cause));
1837
+ }
1838
+ })()),
1839
+ handle: (handler) => {
1840
+ if (handlers.has(queueType)) {
1841
+ throw new Error(
1842
+ `Job handler for queue '${queueType}' is already registered`,
1843
+ );
1844
+ }
1845
+ if (activeHost || startupPromise) {
1846
+ throw new Error(
1847
+ `Job handler for queue '${queueType}' cannot be registered after worker startup has begun`,
1848
+ );
1849
+ }
1850
+ handlers.set(
1851
+ queueType,
1852
+ async (job) =>
1853
+ await handler({
1854
+ job,
1855
+ trellis: args.trellis,
1856
+ }),
1857
+ );
1858
+ },
1859
+ } satisfies JobQueue<unknown, unknown, TTrellisApi, TKv>;
1860
+ }
1861
+
1862
+ const managedWorkers: ManagedJobWorkers = {
1863
+ start: () => {
1864
+ if (activeHost) {
1865
+ return AsyncResult.ok(activeHost);
1866
+ }
1867
+ if (startupPromise) {
1868
+ return AsyncResult.from(startupPromise);
1869
+ }
1870
+
1871
+ startupPromise = (async () => {
1872
+ const selectedQueues = [...handlers.keys()];
1873
+ if (selectedQueues.length === 0) {
1874
+ const host = createNoopJobWorkerHost();
1875
+ activeHost = host;
1876
+ return Result.ok(host);
1877
+ }
1878
+
1879
+ if (!args.jobsBinding || !args.workStream) {
1880
+ return Result.err(toUnexpectedError(
1881
+ new Error(
1882
+ "Jobs infrastructure bindings are unavailable for this service",
1883
+ ),
1884
+ ));
1885
+ }
1886
+
1887
+ const jobsBinding = args.jobsBinding;
1888
+ const workStream = args.workStream;
1889
+
1890
+ const hosts = [] as Array<{ stop(): Promise<void> }>;
1891
+ try {
1892
+ for (const queueType of selectedQueues) {
1893
+ const queueBinding = jobsBinding.queues[queueType];
1894
+ if (!queueBinding) {
1895
+ throw new Error(`Unknown jobs queue '${queueType}'`);
1896
+ }
1897
+ const handler = handlers.get(queueType);
1898
+ if (!handler) {
1899
+ throw new Error(
1900
+ `No job handler registered for queue '${queueType}'`,
1901
+ );
1902
+ }
1903
+
1904
+ const manager = new InternalJobManager<unknown, unknown>({
1905
+ nc: args.nc,
1906
+ jobs: jobsBinding,
1907
+ });
1908
+ const host = await startNatsWorkerHostFromBinding<unknown>({
1909
+ jobs: jobsBinding,
1910
+ workStream,
1911
+ }, {
1912
+ nats: args.nc,
1913
+ instanceId: `${args.serviceName}-worker`,
1914
+ queueTypes: [queueType],
1915
+ manager,
1916
+ getProjectedJob: async (job) => {
1917
+ return lifecycle.get({
1918
+ service: job.service,
1919
+ jobType: job.type,
1920
+ id: job.id,
1921
+ });
1922
+ },
1923
+ handler: async (job: InternalActiveJob<unknown, unknown>) => {
1924
+ const publicJob = new PublicActiveJob(
1925
+ createJobRef({
1926
+ nc: args.nc,
1927
+ queueType,
1928
+ jobsBinding,
1929
+ queueBinding,
1930
+ seed: job.job() as JobSnapshot<unknown, unknown>,
1931
+ lifecycle,
1932
+ }),
1933
+ job.job().payload,
1934
+ job.context(),
1935
+ () => job.isCancelled(),
1936
+ {
1937
+ heartbeat: () => wrapVoidTask(() => job.heartbeat()),
1938
+ progress: (value: JobProgress) =>
1939
+ wrapVoidTask(() => job.updateProgress(value)),
1940
+ log: (entry: JobLogEntry) =>
1941
+ wrapVoidTask(() => job.log(entry.level, entry.message)),
1942
+ redeliveryCount: job.redeliveryCount(),
1943
+ },
1944
+ );
1945
+
1946
+ const handled = (await handler(publicJob)).take();
1947
+ if (isErr(handled)) {
1948
+ throw InternalJobProcessError.failed(handled.error.message);
1949
+ }
1950
+ return handled;
1951
+ },
1952
+ });
1953
+ hosts.push(host);
1954
+ }
1955
+ } catch (cause) {
1956
+ const stopResults = await Promise.allSettled(
1957
+ hosts.map((host) => host.stop()),
1958
+ );
1959
+ const stopErrors = stopResults
1960
+ .filter((result): result is PromiseRejectedResult =>
1961
+ result.status === "rejected"
1962
+ )
1963
+ .map((result) => result.reason);
1964
+ if (stopErrors.length > 0) {
1965
+ return Result.err(
1966
+ toUnexpectedError(new AggregateError([cause, ...stopErrors])),
1967
+ );
1968
+ }
1969
+ return Result.err(toUnexpectedError(cause));
1970
+ }
1971
+
1972
+ activeHost = new JobWorkerHostAdapter({
1973
+ stop: () =>
1974
+ wrapVoidTask(async () => {
1975
+ for (const host of hosts) {
1976
+ await host.stop();
1977
+ }
1978
+ }),
1979
+ join: () => AsyncResult.ok(undefined),
1980
+ });
1981
+ return Result.ok(activeHost);
1982
+ })().finally(() => {
1983
+ startupPromise = undefined;
1984
+ });
1985
+
1986
+ return AsyncResult.from(startupPromise);
1987
+ },
1988
+ stop: () => {
1989
+ if (stopPromise) {
1990
+ return AsyncResult.from(stopPromise);
1991
+ }
1992
+ stopPromise = (async () => {
1993
+ const startup = startupPromise;
1994
+ if (startup) {
1995
+ const started = await startup;
1996
+ if (isErr(started)) {
1997
+ return Result.ok(undefined);
1998
+ }
1999
+ }
2000
+ if (!activeHost) {
2001
+ lifecycle.stop();
2002
+ return Result.ok(undefined);
2003
+ }
2004
+
2005
+ const host = activeHost;
2006
+ try {
2007
+ return await host.stop();
2008
+ } finally {
2009
+ lifecycle.stop();
2010
+ if (activeHost === host) {
2011
+ activeHost = undefined;
2012
+ }
2013
+ stopPromise = undefined;
2014
+ }
2015
+ })();
2016
+
2017
+ return AsyncResult.from(stopPromise);
2018
+ },
2019
+ };
2020
+
2021
+ Object.defineProperty(jobsFacade, MANAGED_JOB_WORKERS, {
2022
+ value: managedWorkers,
2023
+ enumerable: false,
2024
+ });
2025
+
2026
+ return jobsFacade as ManagedJobsFacade<TJobs, TTrellisApi, TKv>;
2027
+ }
2028
+
2029
+ export class TrellisService<
2030
+ TOwnedApi extends TrellisAPI = TrellisAPI,
2031
+ TTrellisApi extends TrellisAPI = TOwnedApi,
2032
+ TJobs extends ContractJobsMetadata = {},
2033
+ TKv extends ContractKvMetadata = ContractKvMetadata,
2034
+ > {
2035
+ readonly name: string;
2036
+ readonly auth: SessionAuth;
2037
+ readonly nc: NatsConnection;
2038
+ readonly #server: TrellisServiceRuntimeFor<TOwnedApi & TTrellisApi>;
2039
+ readonly trellis: ServiceTrellis<TOwnedApi, TTrellisApi, TKv, TJobs>;
2040
+ readonly #handlerTrellis: Trellis<TTrellisApi, TKv, TJobs>;
2041
+ readonly kv: ServiceKvFacade<TKv>;
2042
+ readonly store: Record<string, StoreHandle>;
2043
+ readonly jobs: JobsFacadeOf<TJobs, TTrellisApi, TKv>;
2044
+ readonly health: ServiceHealth;
2045
+ /** Framework-neutral lifecycle handle for the service runtime connection. */
2046
+ readonly connection: TrellisConnection;
2047
+ readonly #operationTransfer: ServiceTransfer;
2048
+ readonly #stopHealthPublishing: () => Promise<void>;
2049
+ readonly #managedJobWorkers: ManagedJobWorkers;
2050
+ #waitPromise?: Promise<void>;
2051
+ #stopPromise?: Promise<void>;
2052
+
2053
+ constructor(
2054
+ name: string,
2055
+ auth: SessionAuth,
2056
+ nc: NatsConnection,
2057
+ server: TrellisServiceRuntimeFor<TOwnedApi & TTrellisApi>,
2058
+ trellis: ServiceTrellis<TOwnedApi, TTrellisApi, TKv, TJobs>,
2059
+ handlerTrellis: Trellis<TTrellisApi, TKv, TJobs>,
2060
+ kv: ServiceKvFacade<TKv>,
2061
+ contractJobs: TJobs,
2062
+ bindings: ResourceBindings,
2063
+ operationTransfer: ServiceTransfer,
2064
+ health: ServiceHealth,
2065
+ stopHealthPublishing: () => Promise<void>,
2066
+ connection: TrellisConnection,
2067
+ ) {
2068
+ const storeBindings = bindings.store ?? {};
2069
+
2070
+ this.name = name;
2071
+ this.auth = auth;
2072
+ this.nc = nc;
2073
+ this.#server = server;
2074
+ Object.defineProperty(this, "server", {
2075
+ value: server,
2076
+ enumerable: false,
2077
+ });
2078
+ this.trellis = trellis;
2079
+ this.#handlerTrellis = handlerTrellis;
2080
+ this.kv = kv;
2081
+ this.store = Object.fromEntries(
2082
+ Object.entries(storeBindings).map((
2083
+ [alias, binding],
2084
+ ) => [alias, new StoreHandle(nc, binding)]),
2085
+ );
2086
+ this.#operationTransfer = operationTransfer;
2087
+ const jobs = createJobsFacade<TJobs, TTrellisApi, TKv>({
2088
+ serviceName: name,
2089
+ nc,
2090
+ contractJobs,
2091
+ trellis: handlerTrellis,
2092
+ jobsBinding: bindings.jobs,
2093
+ workStream: bindings.jobs?.workStream,
2094
+ });
2095
+ this.jobs = jobs;
2096
+ this.#managedJobWorkers = jobs[MANAGED_JOB_WORKERS];
2097
+ this.health = health;
2098
+ this.connection = connection;
2099
+ this.#stopHealthPublishing = stopHealthPublishing;
2100
+ }
2101
+
2102
+ /**
2103
+ * Creates a short-lived receive transfer grant for a caller session.
2104
+ */
2105
+ createTransfer(args: {
2106
+ direction: "receive";
2107
+ store: string;
2108
+ key: string;
2109
+ sessionKey: string;
2110
+ expiresInMs?: number;
2111
+ }): AsyncResult<ReceiveTransferGrant, TransferError> {
2112
+ return AsyncResult.from(
2113
+ this.#operationTransfer.initiateDownload({
2114
+ store: args.store,
2115
+ key: args.key,
2116
+ sessionKey: args.sessionKey,
2117
+ expiresInMs: args.expiresInMs ?? 60_000,
2118
+ }),
2119
+ );
2120
+ }
2121
+
2122
+ /**
2123
+ * Completes an operation from Trellis-owned control-plane code that resolves
2124
+ * an operation from a separate RPC handler.
2125
+ *
2126
+ * @internal
2127
+ */
2128
+ completeOperation(
2129
+ operationId: string,
2130
+ output: unknown,
2131
+ ): AsyncResult<unknown, UnexpectedError> {
2132
+ return AsyncResult.from((async () => {
2133
+ const completed = await this.#server.operations.complete(
2134
+ operationId,
2135
+ output,
2136
+ ).take();
2137
+ if (!isErr(completed)) return Result.ok(completed);
2138
+
2139
+ const current = await this.#server.operations.get(operationId).take();
2140
+ if (!isErr(current) && current.state === "completed") {
2141
+ if (!operationOutputsEqual(current.output, output)) {
2142
+ return Result.err(
2143
+ new UnexpectedError({
2144
+ cause: new Error(
2145
+ "operation already completed with different output",
2146
+ ),
2147
+ }),
2148
+ );
2149
+ }
2150
+ return Result.ok(current);
2151
+ }
2152
+
2153
+ return Result.err(toUnexpectedError(completed.error));
2154
+ })());
2155
+ }
2156
+
2157
+ static connect<
2158
+ const TContract extends ServiceContract<
2159
+ TrellisAPI,
2160
+ TrellisAPI,
2161
+ ContractJobsMetadata,
2162
+ ContractKvMetadata
2163
+ >,
2164
+ >(
2165
+ args: TrellisServiceConnectArgs<TContract>,
2166
+ deps?: Partial<TrellisServiceRuntimeDeps>,
2167
+ ): AsyncResult<
2168
+ TrellisService<
2169
+ ContractOwnedApi<TContract>,
2170
+ ContractTrellisApi<TContract>,
2171
+ ContractJobsOf<TContract>,
2172
+ ContractKvOf<TContract>
2173
+ >,
2174
+ TransportError | UnexpectedError
2175
+ > {
2176
+ return AsyncResult.from((async () => {
2177
+ try {
2178
+ type TOwnedApi = ContractOwnedApi<TContract>;
2179
+ type TTrellisApi = ContractTrellisApi<TContract>;
2180
+
2181
+ const runtimeDeps = {
2182
+ ...(await loadDefaultServiceRuntimeDeps()),
2183
+ ...deps,
2184
+ } satisfies TrellisServiceRuntimeDeps;
2185
+ const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
2186
+ const bootstrapLog = resolveServiceLogger(args.server?.log);
2187
+ const bootstrap = await fetchServiceBootstrapInfo({
2188
+ trellisUrl: args.trellisUrl,
2189
+ serviceName: args.name,
2190
+ contractId: args.contract.CONTRACT_ID,
2191
+ contractDigest: args.contract.CONTRACT_DIGEST,
2192
+ contract: args.contract.CONTRACT,
2193
+ auth,
2194
+ log: bootstrapLog,
2195
+ });
2196
+ const { authenticator: authTokenAuthenticator, inboxPrefix } =
2197
+ await auth
2198
+ .natsConnectOptions({
2199
+ contractDigest: args.contract.CONTRACT_DIGEST,
2200
+ });
2201
+
2202
+ let nc: NatsConnection;
2203
+ try {
2204
+ nc = await runtimeDeps.connect({
2205
+ servers: selectRuntimeTransportServers(
2206
+ bootstrap.connectInfo.transports,
2207
+ ),
2208
+ maxReconnectAttempts: DEFAULT_RUNTIME_MAX_RECONNECT_ATTEMPTS,
2209
+ waitOnFirstConnect: DEFAULT_SERVICE_RUNTIME_WAIT_ON_FIRST_CONNECT,
2210
+ inboxPrefix,
2211
+ authenticator: [
2212
+ authTokenAuthenticator,
2213
+ jwtAuthenticator(
2214
+ bootstrap.connectInfo.transport.sentinel.jwt,
2215
+ new TextEncoder().encode(
2216
+ bootstrap.connectInfo.transport.sentinel.seed,
2217
+ ),
2218
+ ),
2219
+ ],
2220
+ });
2221
+ } catch (cause) {
2222
+ throw new TransportError({
2223
+ code: "trellis.runtime.connect_failed",
2224
+ message: "Trellis could not open the service runtime connection.",
2225
+ hint:
2226
+ "Retry the connection. If it keeps failing, check Trellis transport availability.",
2227
+ cause,
2228
+ context: {
2229
+ trellisUrl: args.trellisUrl,
2230
+ contractId: args.contract.CONTRACT_ID,
2231
+ contractDigest: args.contract.CONTRACT_DIGEST,
2232
+ },
2233
+ });
2234
+ }
2235
+
2236
+ try {
2237
+ return Result.ok(
2238
+ await createConnectedService<
2239
+ TOwnedApi,
2240
+ TTrellisApi,
2241
+ ContractJobsOf<TContract>,
2242
+ ContractKvOf<TContract>
2243
+ >({
2244
+ name: args.name,
2245
+ auth,
2246
+ nc,
2247
+ contractId: args.contract.CONTRACT_ID,
2248
+ contractDigest: args.contract.CONTRACT_DIGEST,
2249
+ contractJobs:
2250
+ (args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
2251
+ TContract
2252
+ >,
2253
+ contractKv:
2254
+ (args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
2255
+ TContract
2256
+ >,
2257
+ server: {
2258
+ ...(args.server ?? {}),
2259
+ api: args.contract.API.owned,
2260
+ trellisApi: args.contract.API.trellis,
2261
+ },
2262
+ bindings: bootstrap.binding.resources,
2263
+ }),
2264
+ );
2265
+ } catch (cause) {
2266
+ await closeFailedServiceBootstrapConnection(nc);
2267
+ throw cause;
2268
+ }
2269
+ } catch (cause) {
2270
+ return Result.err(
2271
+ cause instanceof TransportError ? cause : toUnexpectedError(cause),
2272
+ );
2273
+ }
2274
+ })());
2275
+ }
2276
+
2277
+ /**
2278
+ * Starts managed job workers for registered handlers and waits for shutdown.
2279
+ */
2280
+ async wait(): Promise<void> {
2281
+ this.#waitPromise ??= (async () => {
2282
+ try {
2283
+ await this.#managedJobWorkers.start().orThrow();
2284
+ const closed = await this.nc.closed();
2285
+ if (closed instanceof Error) {
2286
+ throw closed;
2287
+ }
2288
+ } finally {
2289
+ await this.stop();
2290
+ }
2291
+ })();
2292
+
2293
+ await this.#waitPromise;
2294
+ }
2295
+
2296
+ async stop(): Promise<void> {
2297
+ this.#stopPromise ??= (async () => {
2298
+ this.connection.stopObserving();
2299
+
2300
+ try {
2301
+ await this.#stopHealthPublishing();
2302
+ } finally {
2303
+ try {
2304
+ await this.#managedJobWorkers.stop().orThrow();
2305
+ } finally {
2306
+ try {
2307
+ await this.#operationTransfer.stop();
2308
+ } finally {
2309
+ await this.#server.stop();
2310
+ }
2311
+ }
2312
+ }
2313
+ })();
2314
+
2315
+ await this.#stopPromise;
2316
+ }
2317
+
2318
+ request<M extends RpcMethodName<TTrellisApi>>(
2319
+ method: M,
2320
+ input: RpcMethodInput<TTrellisApi, M>,
2321
+ opts?: RequestOpts,
2322
+ ): AsyncResult<
2323
+ RpcMethodOutput<TTrellisApi, M>,
2324
+ RpcRequestErrorOf<TTrellisApi, M>
2325
+ > {
2326
+ return this.trellis.request(
2327
+ method as never,
2328
+ input as never,
2329
+ opts,
2330
+ ) as AsyncResult<
2331
+ RpcMethodOutput<TTrellisApi, M>,
2332
+ RpcRequestErrorOf<TTrellisApi, M>
2333
+ >;
2334
+ }
2335
+
2336
+ feed<F extends keyof TOwnedApi["feeds"] & string>(
2337
+ feed: F,
2338
+ ): FeedRegistration<TOwnedApi, F> {
2339
+ return this.#server.feed(feed) as FeedRegistration<TOwnedApi, F>;
2340
+ }
2341
+
2342
+ operation<O extends keyof TOwnedApi["operations"] & string>(
2343
+ operation: O,
2344
+ ): OperationRegistration<TOwnedApi, TTrellisApi, O, TKv, TJobs> {
2345
+ const registration = this.#server.operation(
2346
+ operation,
2347
+ ) as RootOperationRegistration<
2348
+ InferSchemaType<TOwnedApi["operations"][O]["input"]>,
2349
+ OperationProgressOf<TOwnedApi, O>,
2350
+ OperationOutputOf<TOwnedApi, O>,
2351
+ OperationTransferContextOf<TOwnedApi, O>
2352
+ >;
2353
+
2354
+ return {
2355
+ accept: (args) => registration.accept(args),
2356
+ control: (operationId) => registration.control(operationId),
2357
+ handle: (
2358
+ handler: (
2359
+ args:
2360
+ & OperationHandlerContext<
2361
+ InferSchemaType<TOwnedApi["operations"][O]["input"]>,
2362
+ OperationProgressOf<TOwnedApi, O>,
2363
+ OperationOutputOf<TOwnedApi, O>,
2364
+ OperationTransferContextOf<TOwnedApi, O>
2365
+ >
2366
+ & { trellis: Trellis<TTrellisApi, TKv, TJobs> },
2367
+ ) => unknown | Promise<unknown>,
2368
+ ) =>
2369
+ registration.handle((context) =>
2370
+ handler({
2371
+ ...context,
2372
+ trellis: this.#handlerTrellis,
2373
+ })
2374
+ ),
2375
+ };
2376
+ }
2377
+ }