@qlever-llc/trellis 0.8.4 → 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 +0 -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 +39 -26
  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 -3
  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 +39 -59
  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
package/src/trellis.ts ADDED
@@ -0,0 +1,3650 @@
1
+ import {
2
+ type ConsumerMessages,
3
+ jetstream,
4
+ type JetStreamClient,
5
+ jetstreamManager,
6
+ } from "@nats-io/jetstream";
7
+ import {
8
+ createInbox,
9
+ headers as natsHeaders,
10
+ type Msg,
11
+ type MsgHdrs,
12
+ type NatsConnection,
13
+ } from "@nats-io/nats-core";
14
+ import type {
15
+ EventDesc,
16
+ FeedDesc,
17
+ InferSchemaType,
18
+ RPCDesc,
19
+ TrellisAPI,
20
+ } from "./contracts.js";
21
+ import {
22
+ CONTRACT_JOBS_METADATA,
23
+ CONTRACT_KV_METADATA,
24
+ CONTRACT_STATE_METADATA,
25
+ type ContractJobsMetadata,
26
+ type ContractKvMetadata,
27
+ } from "./contract_support/mod.js";
28
+ import type { StaticDecode } from "typebox";
29
+ import {
30
+ AuthRequestsValidateResponseSchema,
31
+ AuthRequestsValidateSchema,
32
+ } from "./auth/protocol.js";
33
+ import {
34
+ AsyncResult,
35
+ BaseError,
36
+ err,
37
+ type InferErr,
38
+ isErr,
39
+ type MaybeAsync,
40
+ ok,
41
+ Result,
42
+ } from "@qlever-llc/result";
43
+ import {
44
+ context,
45
+ createNatsHeaderCarrier,
46
+ extractTraceContext,
47
+ injectTraceContext,
48
+ SpanStatusCode,
49
+ startClientSpan,
50
+ startServerSpan,
51
+ trace,
52
+ withSpanAsync,
53
+ } from "./tracing.js";
54
+ import { Type } from "typebox";
55
+ import { AssertError, Pointer } from "typebox/value";
56
+ import { ulid } from "ulid";
57
+ import {
58
+ encodeSchema,
59
+ type JsonValue,
60
+ parse,
61
+ parseSchema,
62
+ parseUnknownSchema,
63
+ } from "./codec.js";
64
+ import {
65
+ AuthError,
66
+ BUILTIN_RPC_ERRORS,
67
+ getBuiltinRpcError,
68
+ type StoreError,
69
+ TransferError,
70
+ TransportError,
71
+ type TrellisErrorInstance,
72
+ type TrellisErrorMap,
73
+ type TrellisErrorName,
74
+ UnexpectedError,
75
+ ValidationError,
76
+ } from "./errors/index.js";
77
+ import { RemoteError } from "./errors/RemoteError.js";
78
+ import { logger, type LoggerLike } from "./globals.js";
79
+ import { TypedKV } from "./kv.js";
80
+ import { TrellisErrorDataSchema } from "./models/trellis/TrellisError.js";
81
+ import type { ActiveJob, JobRef, JobTypeMetadata } from "./jobs.js";
82
+ import type { StoreWaitOptions, TypedStore, TypedStoreEntry } from "./store.js";
83
+ import {
84
+ OperationInvoker,
85
+ type OperationRefData,
86
+ type OperationTransport,
87
+ } from "./operations.js";
88
+ import type { Span } from "./telemetry/mod.js";
89
+ import type { StateDeleteResponse } from "./models/trellis/rpc/StateDelete.js";
90
+ import {
91
+ StateDeleteResponseSchema,
92
+ StateDeleteSchema,
93
+ } from "./models/trellis/rpc/StateDelete.js";
94
+ import type { StateGetResponse } from "./models/trellis/rpc/StateGet.js";
95
+ import {
96
+ StateGetResponseSchema,
97
+ StateGetSchema,
98
+ } from "./models/trellis/rpc/StateGet.js";
99
+ import type { StateListResponse } from "./models/trellis/rpc/StateList.js";
100
+ import {
101
+ StateListResponseSchema,
102
+ StateListSchema,
103
+ } from "./models/trellis/rpc/StateList.js";
104
+ import type { StatePutResponse } from "./models/trellis/rpc/StatePut.js";
105
+ import {
106
+ StatePutResponseSchema,
107
+ StatePutSchema,
108
+ } from "./models/trellis/rpc/StatePut.js";
109
+ import {
110
+ createTransferHandle,
111
+ type FileInfo,
112
+ type ReceiveTransferGrant,
113
+ type ReceiveTransferHandle,
114
+ type SendTransferGrant,
115
+ type SendTransferHandle,
116
+ type TransferBody,
117
+ type TransferGrant,
118
+ } from "./transfer.js";
119
+ import { TrellisTasks } from "./tasks.js";
120
+ import { TrellisConnection } from "./connection.js";
121
+
122
+ export type { NatsConnection } from "@nats-io/nats-core";
123
+
124
+ type RuntimeRpcErrorDesc = {
125
+ type: string;
126
+ schema?: unknown;
127
+ fromSerializable(data: unknown): Error;
128
+ };
129
+
130
+ type InferRuntimeRpcError<T> = T extends {
131
+ fromSerializable(data: unknown): infer TError;
132
+ } ? TError
133
+ : never;
134
+
135
+ export type AuthRequestsValidateResponse = StaticDecode<
136
+ typeof AuthRequestsValidateResponseSchema
137
+ >;
138
+ export type AuthRequestsValidateInput = StaticDecode<
139
+ typeof AuthRequestsValidateSchema
140
+ >;
141
+
142
+ export type SessionCaller = AuthRequestsValidateResponse["caller"];
143
+
144
+ /**
145
+ * Safely extract JSON from a NATS message.
146
+ * The .json() method can throw if the message data is not valid JSON.
147
+ */
148
+ export function safeJson(msg: Msg): Result<JsonValue, UnexpectedError> {
149
+ return Result.try(() => msg.json() as JsonValue);
150
+ }
151
+
152
+ function transportCauseContext(cause: unknown): Record<string, unknown> {
153
+ if (cause instanceof Error) {
154
+ return {
155
+ causeName: cause.name,
156
+ causeMessage: cause.message,
157
+ };
158
+ }
159
+
160
+ return { cause: String(cause) };
161
+ }
162
+
163
+ function createTransportError(args: {
164
+ code: string;
165
+ message: string;
166
+ hint: string;
167
+ context?: Record<string, unknown>;
168
+ cause?: unknown;
169
+ }): TransportError {
170
+ return new TransportError({
171
+ code: args.code,
172
+ message: args.message,
173
+ hint: args.hint,
174
+ cause: args.cause,
175
+ context: {
176
+ ...(args.context ?? {}),
177
+ ...(args.cause === undefined ? {} : transportCauseContext(args.cause)),
178
+ },
179
+ });
180
+ }
181
+
182
+ function requestFailedTransportError(args: {
183
+ code: string;
184
+ method?: string;
185
+ subject: string;
186
+ hint: string;
187
+ message: string;
188
+ cause?: unknown;
189
+ context?: Record<string, unknown>;
190
+ }): TransportError {
191
+ return createTransportError({
192
+ code: args.code,
193
+ message: args.message,
194
+ hint: args.hint,
195
+ cause: args.cause,
196
+ context: {
197
+ subject: args.subject,
198
+ ...(args.method === undefined ? {} : { method: args.method }),
199
+ ...(args.context ?? {}),
200
+ },
201
+ });
202
+ }
203
+
204
+ function classifyRequestTransportFailure(args: {
205
+ method?: string;
206
+ subject: string;
207
+ callerCapabilities?: readonly string[];
208
+ cause: unknown;
209
+ }): TransportError {
210
+ const message = args.cause instanceof Error
211
+ ? args.cause.message
212
+ : String(args.cause);
213
+ const isNoResponders = message.includes("no responders");
214
+ const isNatsPermission = message.includes("Permissions Violation");
215
+
216
+ return requestFailedTransportError({
217
+ code: isNoResponders
218
+ ? "trellis.request.unavailable"
219
+ : isNatsPermission
220
+ ? "trellis.request.denied"
221
+ : "trellis.request.failed",
222
+ message: isNoResponders
223
+ ? "Trellis could not reach the requested capability."
224
+ : isNatsPermission
225
+ ? "Trellis denied this request."
226
+ : "Trellis could not complete the request.",
227
+ hint: isNoResponders
228
+ ? "Check that the target service is installed and reachable, then try again."
229
+ : isNatsPermission
230
+ ? "Sign in with a profile that has the required capability, then try again."
231
+ : "Retry the request. If it keeps failing, check Trellis runtime health.",
232
+ cause: args.cause,
233
+ method: args.method,
234
+ subject: args.subject,
235
+ context: {
236
+ ...(args.callerCapabilities === undefined
237
+ ? {}
238
+ : { requiredCapabilities: args.callerCapabilities }),
239
+ noResponders: isNoResponders,
240
+ lowLevelMessage: message,
241
+ },
242
+ });
243
+ }
244
+
245
+ function encodeRuntimeSchema(
246
+ schema: unknown,
247
+ data: unknown,
248
+ ): Result<string, ValidationError | UnexpectedError> {
249
+ return encodeSchema(schema as never, data);
250
+ }
251
+
252
+ function parseRuntimeSchema(
253
+ schema: unknown,
254
+ data: JsonValue,
255
+ ): Result<unknown, ValidationError | UnexpectedError> {
256
+ return parseUnknownSchema(
257
+ schema as Parameters<typeof parseUnknownSchema>[0],
258
+ data,
259
+ );
260
+ }
261
+
262
+ export function base64urlEncode(data: Uint8Array): string {
263
+ const b64 = btoa(String.fromCharCode(...data));
264
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
265
+ }
266
+
267
+ export function base64urlDecode(s: string): Uint8Array {
268
+ const normalized = s.replace(/-/g, "+").replace(/_/g, "/");
269
+ const padLen = (4 - (normalized.length % 4)) % 4;
270
+ const padded = normalized + "=".repeat(padLen);
271
+ const bin = atob(padded);
272
+ const out = new Uint8Array(bin.length);
273
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
274
+ return out;
275
+ }
276
+
277
+ export function toArrayBuffer(data: Uint8Array): ArrayBuffer {
278
+ const buf = data.buffer;
279
+ if (buf instanceof ArrayBuffer) {
280
+ return buf.slice(data.byteOffset, data.byteOffset + data.byteLength);
281
+ }
282
+ const copy = new Uint8Array(data.byteLength);
283
+ copy.set(data);
284
+ return copy.buffer;
285
+ }
286
+
287
+ export async function sha256(data: Uint8Array): Promise<Uint8Array> {
288
+ const digest = await crypto.subtle.digest("SHA-256", toArrayBuffer(data));
289
+ return new Uint8Array(digest);
290
+ }
291
+
292
+ export function buildProofInput(
293
+ sessionKey: string,
294
+ subject: string,
295
+ payloadHash: Uint8Array,
296
+ iat: number,
297
+ requestId: string,
298
+ ): Uint8Array {
299
+ const enc = new TextEncoder();
300
+ const sessionKeyBytes = enc.encode(sessionKey);
301
+ const subjectBytes = enc.encode(subject);
302
+ const iatBytes = enc.encode(String(iat));
303
+ const requestIdBytes = enc.encode(requestId);
304
+
305
+ const buf = new Uint8Array(
306
+ 4 +
307
+ sessionKeyBytes.length +
308
+ 4 +
309
+ subjectBytes.length +
310
+ 4 +
311
+ payloadHash.length +
312
+ 4 +
313
+ iatBytes.length +
314
+ 4 +
315
+ requestIdBytes.length,
316
+ );
317
+ const view = new DataView(buf.buffer);
318
+
319
+ let offset = 0;
320
+ view.setUint32(offset, sessionKeyBytes.length);
321
+ offset += 4;
322
+ buf.set(sessionKeyBytes, offset);
323
+ offset += sessionKeyBytes.length;
324
+
325
+ view.setUint32(offset, subjectBytes.length);
326
+ offset += 4;
327
+ buf.set(subjectBytes, offset);
328
+ offset += subjectBytes.length;
329
+
330
+ view.setUint32(offset, payloadHash.length);
331
+ offset += 4;
332
+ buf.set(payloadHash, offset);
333
+ offset += payloadHash.length;
334
+
335
+ view.setUint32(offset, iatBytes.length);
336
+ offset += 4;
337
+ buf.set(iatBytes, offset);
338
+ offset += iatBytes.length;
339
+
340
+ view.setUint32(offset, requestIdBytes.length);
341
+ offset += 4;
342
+ buf.set(requestIdBytes, offset);
343
+
344
+ return buf;
345
+ }
346
+
347
+ export type TrellisSigner = (
348
+ data: Uint8Array,
349
+ ) => Promise<Uint8Array> | Uint8Array;
350
+
351
+ export type TrellisAuth = {
352
+ sessionKey: string;
353
+ sign: TrellisSigner;
354
+ currentIat?: () => number;
355
+ };
356
+
357
+ export type AnyTrellisAPI = TrellisAPI;
358
+ export type TrellisMode = "client" | "server";
359
+ type Simplify<T> = { [K in keyof T]: T[K] } & {};
360
+ type OwnedApiFor<TContract> = TContract extends
361
+ { API: { owned: infer TOwnedApi } }
362
+ ? TOwnedApi extends AnyTrellisAPI ? TOwnedApi
363
+ : never
364
+ : never;
365
+ type ContractKvFor<TContract> = TContract extends {
366
+ readonly [CONTRACT_KV_METADATA]?: infer TKv;
367
+ } ? NonNullable<TKv> extends ContractKvMetadata ? NonNullable<TKv>
368
+ : {}
369
+ : {};
370
+ type ContractJobsFor<TContract> = TContract extends {
371
+ readonly [CONTRACT_JOBS_METADATA]?: infer TJobs;
372
+ } ? NonNullable<TJobs> extends ContractJobsMetadata ? NonNullable<TJobs>
373
+ : {}
374
+ : {};
375
+ export type RuntimeStateStoreShape = {
376
+ kind: "value" | "map";
377
+ value: unknown;
378
+ schema?: unknown;
379
+ stateVersion?: string;
380
+ acceptedVersions?: Record<string, unknown>;
381
+ };
382
+ export type RuntimeStateStores = Record<string, RuntimeStateStoreShape>;
383
+ export type RuntimeStateStoresForContract<TContract> = TContract extends {
384
+ readonly [CONTRACT_STATE_METADATA]?: infer TState;
385
+ } ? NonNullable<TState> extends RuntimeStateStores ? NonNullable<TState>
386
+ : {}
387
+ : {};
388
+ type TrellisApiFor<TContract> = TContract extends
389
+ { API: { trellis: infer TTrellisApi } }
390
+ ? TTrellisApi extends AnyTrellisAPI ? TTrellisApi
391
+ : OwnedApiFor<TContract>
392
+ : OwnedApiFor<TContract>;
393
+ type RpcMethodsOf<TA extends AnyTrellisAPI> = TA["rpc"];
394
+ export type MethodsOf<TA extends AnyTrellisAPI> =
395
+ & keyof RpcMethodsOf<TA>
396
+ & string;
397
+ export type RpcMethodNameOf<TA extends AnyTrellisAPI> = MethodsOf<TA>;
398
+ export type OperationsOf<TA extends AnyTrellisAPI> =
399
+ & keyof TA["operations"]
400
+ & string;
401
+ type EventsOf<TA extends AnyTrellisAPI> = keyof TA["events"] & string;
402
+ export type FeedsOf<TA extends AnyTrellisAPI> =
403
+ & keyof NonNullable<TA["feeds"]>
404
+ & string;
405
+ type RpcMethodOf<TA extends AnyTrellisAPI, M extends keyof TA["rpc"] & string> =
406
+ RpcMethodsOf<TA>[M];
407
+ type MethodInputOf<
408
+ TA extends AnyTrellisAPI,
409
+ M extends keyof TA["rpc"] & string,
410
+ > = RpcMethodOf<TA, M> extends { input: infer TInput } ? InferSchemaType<TInput>
411
+ : never;
412
+ export type RpcInputOf<
413
+ TA extends AnyTrellisAPI,
414
+ M extends RpcMethodNameOf<TA>,
415
+ > = MethodInputOf<TA, M>;
416
+ type MethodOutputOf<
417
+ TA extends AnyTrellisAPI,
418
+ M extends keyof TA["rpc"] & string,
419
+ > = RpcMethodOf<TA, M> extends { output: infer TOutput }
420
+ ? InferSchemaType<TOutput>
421
+ : never;
422
+ export type RpcOutputOf<
423
+ TA extends AnyTrellisAPI,
424
+ M extends RpcMethodNameOf<TA>,
425
+ > = MethodOutputOf<TA, M>;
426
+ type RpcRequestShapes<TA extends AnyTrellisAPI> = {
427
+ [M in keyof TA["rpc"] & string]: {
428
+ input: MethodInputOf<TA, M>;
429
+ output: MethodOutputOf<TA, M>;
430
+ };
431
+ };
432
+ type RequestMethodOf<TRequests> = keyof TRequests & string;
433
+ type RequestInputOf<TRequests, M extends RequestMethodOf<TRequests>> =
434
+ TRequests[M] extends { input: infer TInput } ? TInput : never;
435
+ type RequestOutputOf<TRequests, M extends RequestMethodOf<TRequests>> =
436
+ TRequests[M] extends { output: infer TOutput } ? TOutput : never;
437
+ type RpcDescriptorOf<
438
+ TA extends AnyTrellisAPI,
439
+ M extends keyof TA["rpc"] & string,
440
+ > = RpcMethodOf<TA, M> extends {
441
+ input: infer TInput;
442
+ output: infer TOutput;
443
+ errors?: infer TErrors;
444
+ runtimeErrors?: infer TRuntimeErrors;
445
+ declaredErrorTypes?: infer TDeclaredErrorTypes;
446
+ } ? {
447
+ input: TInput;
448
+ output: TOutput;
449
+ errors?: TErrors;
450
+ runtimeErrors?: TRuntimeErrors;
451
+ declaredErrorTypes?: TDeclaredErrorTypes;
452
+ } & RpcMethodOf<TA, M>
453
+ : never;
454
+ type DeclaredBuiltinErrorOf<TNames> = TNames extends readonly (infer TName)[]
455
+ ? TName extends TrellisErrorName ? TrellisErrorMap[TName]
456
+ : never
457
+ : never;
458
+ type DeclaredRuntimeErrorOf<TRuntimeErrors> = TRuntimeErrors extends readonly (
459
+ infer TRuntimeError
460
+ )[] ? InferRuntimeRpcError<TRuntimeError>
461
+ : never;
462
+ type MethodDeclaredErrorOf<
463
+ TA extends AnyTrellisAPI,
464
+ M extends keyof TA["rpc"] & string,
465
+ > = RpcDescriptorOf<TA, M> extends {
466
+ errors?: infer TErrors;
467
+ runtimeErrors?: infer TRuntimeErrors;
468
+ } ? DeclaredBuiltinErrorOf<TErrors> | DeclaredRuntimeErrorOf<TRuntimeErrors>
469
+ : never;
470
+ type RequestErrorOf<TA extends AnyTrellisAPI, M extends MethodsOf<TA>> =
471
+ | MethodDeclaredErrorOf<TA, M>
472
+ | RemoteError
473
+ | TransportError
474
+ | ValidationError
475
+ | UnexpectedError;
476
+ type HandlerErrorOf<TA extends AnyTrellisAPI, M extends MethodsOf<TA>> =
477
+ | MethodDeclaredErrorOf<TA, M>
478
+ | TrellisErrorInstance;
479
+ type EventOf<TA extends AnyTrellisAPI, E extends EventsOf<TA>> =
480
+ TA["events"][E] extends EventDesc<infer TEvent> ? InferSchemaType<TEvent>
481
+ : never;
482
+ type EventDescriptorOf<TA extends AnyTrellisAPI, E extends EventsOf<TA>> =
483
+ TA["events"][E] extends EventDesc<infer TEvent>
484
+ ? EventDesc<TEvent> & TA["events"][E]
485
+ : never;
486
+ type EventPayloadOf<TA extends AnyTrellisAPI, E extends EventsOf<TA>> = Omit<
487
+ EventOf<TA, E>,
488
+ "header"
489
+ >;
490
+ export type FeedInputOf<TA extends AnyTrellisAPI, F extends FeedsOf<TA>> =
491
+ NonNullable<TA["feeds"]>[F] extends FeedDesc<infer TInput, infer _TEvent>
492
+ ? InferSchemaType<TInput>
493
+ : never;
494
+ export type FeedEventOf<TA extends AnyTrellisAPI, F extends FeedsOf<TA>> =
495
+ NonNullable<TA["feeds"]>[F] extends FeedDesc<infer _TInput, infer TEvent>
496
+ ? InferSchemaType<TEvent>
497
+ : never;
498
+ type FeedDescriptorOf<TA extends AnyTrellisAPI, F extends FeedsOf<TA>> =
499
+ NonNullable<TA["feeds"]>[F] extends FeedDesc<infer TInput, infer TEvent>
500
+ ? FeedDesc<TInput, TEvent> & NonNullable<TA["feeds"]>[F]
501
+ : never;
502
+ export type OperationInputOf<
503
+ TA extends AnyTrellisAPI,
504
+ O extends OperationsOf<TA>,
505
+ > = TA["operations"][O] extends { input: infer TInput }
506
+ ? InferSchemaType<TInput>
507
+ : never;
508
+ export type OperationProgressOf<
509
+ TA extends AnyTrellisAPI,
510
+ O extends OperationsOf<TA>,
511
+ > = TA["operations"][O] extends { progress: infer TProgress }
512
+ ? TProgress extends undefined ? unknown
513
+ : InferSchemaType<NonNullable<TProgress>>
514
+ : unknown;
515
+ export type OperationOutputOf<
516
+ TA extends AnyTrellisAPI,
517
+ O extends OperationsOf<TA>,
518
+ > = TA["operations"][O] extends { output: infer TOutput }
519
+ ? TOutput extends undefined ? unknown : InferSchemaType<NonNullable<TOutput>>
520
+ : unknown;
521
+ export type OperationRuntimeHandle<TProgress = unknown, TOutput = unknown> = {
522
+ id: string;
523
+ started(): AsyncResult<RuntimeOperationSnapshot, BaseError>;
524
+ progress(
525
+ value: TProgress,
526
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
527
+ complete(
528
+ value: TOutput,
529
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
530
+ fail(
531
+ error: BaseError,
532
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
533
+ cancel(): AsyncResult<RuntimeOperationSnapshot, BaseError>;
534
+ attach(
535
+ job: { wait(): AsyncResult<unknown, BaseError> },
536
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
537
+ signals(): AsyncIterable<RuntimeOperationSignal>;
538
+ nextSignal(
539
+ name?: string,
540
+ ): AsyncResult<RuntimeOperationSignal, BaseError>;
541
+ defer(): OperationDeferred;
542
+ };
543
+ export type OperationDeferred = {
544
+ kind: "deferred";
545
+ };
546
+
547
+ /**
548
+ * Returns true when a handler result explicitly leaves operation completion to
549
+ * an external control path.
550
+ */
551
+ export function isOperationDeferred(
552
+ value: unknown,
553
+ ): value is OperationDeferred {
554
+ return !!value && typeof value === "object" &&
555
+ "kind" in value && value.kind === "deferred";
556
+ }
557
+ export type AcceptedOperation<TProgress = unknown, TOutput = unknown> =
558
+ & OperationRuntimeHandle<TProgress, TOutput>
559
+ & {
560
+ ref: OperationRefData;
561
+ snapshot: RuntimeOperationSnapshot & {
562
+ progress?: TProgress;
563
+ output?: TOutput;
564
+ };
565
+ };
566
+ export type OperationTransferHandle = {
567
+ updates(): AsyncIterable<RuntimeOperationTransferProgress>;
568
+ completed(): AsyncResult<FileInfo, TransferError>;
569
+ };
570
+ type StateEntryBase<TValue> = {
571
+ value: TValue;
572
+ revision: string;
573
+ updatedAt: string;
574
+ expiresAt?: string;
575
+ };
576
+ type ValueStateEntry<TValue> = StateEntryBase<TValue>;
577
+ type MapStateEntry<TValue> = StateEntryBase<TValue> & { key: string };
578
+ type StateMigrationRequiredEntry<TEntry> = {
579
+ migrationRequired: true;
580
+ entry: TEntry;
581
+ stateVersion: string;
582
+ currentStateVersion: string;
583
+ writerContractDigest: string;
584
+ };
585
+ type StateGetResult<TStore extends RuntimeStateStoreShape> =
586
+ | { found: false }
587
+ | {
588
+ found: true;
589
+ entry: TStore["kind"] extends "map" ? MapStateEntry<TStore["value"]>
590
+ : ValueStateEntry<TStore["value"]>;
591
+ }
592
+ | StateMigrationRequiredEntry<
593
+ TStore["kind"] extends "map" ? MapStateEntry<unknown>
594
+ : ValueStateEntry<unknown>
595
+ >;
596
+ type StatePutResult<TStore extends RuntimeStateStoreShape> =
597
+ | {
598
+ applied: true;
599
+ entry: TStore["kind"] extends "map" ? MapStateEntry<TStore["value"]>
600
+ : ValueStateEntry<TStore["value"]>;
601
+ }
602
+ | {
603
+ applied: false;
604
+ found: boolean;
605
+ entry?:
606
+ | (TStore["kind"] extends "map" ? MapStateEntry<TStore["value"]>
607
+ : ValueStateEntry<TStore["value"]>)
608
+ | StateMigrationRequiredEntry<
609
+ TStore["kind"] extends "map" ? MapStateEntry<unknown>
610
+ : ValueStateEntry<unknown>
611
+ >;
612
+ };
613
+ type StateDeleteOptions = {
614
+ expectedRevision?: string;
615
+ };
616
+ type StatePutOptions = {
617
+ expectedRevision?: string | null;
618
+ ttlMs?: number;
619
+ };
620
+ type StateListOptions = {
621
+ offset?: number;
622
+ limit?: number;
623
+ };
624
+ export type ValueStateStoreClient<TValue> = {
625
+ get(): AsyncResult<
626
+ StateGetResult<{ kind: "value"; value: TValue }>,
627
+ BaseError
628
+ >;
629
+ put(
630
+ value: TValue,
631
+ opts?: StatePutOptions,
632
+ ): AsyncResult<StatePutResult<{ kind: "value"; value: TValue }>, BaseError>;
633
+ delete(
634
+ opts?: StateDeleteOptions,
635
+ ): AsyncResult<{ deleted: boolean }, BaseError>;
636
+ };
637
+ export type MapStateStoreClient<TValue> = {
638
+ get(
639
+ key: string,
640
+ ): AsyncResult<StateGetResult<{ kind: "map"; value: TValue }>, BaseError>;
641
+ put(
642
+ key: string,
643
+ value: TValue,
644
+ opts?: StatePutOptions,
645
+ ): AsyncResult<StatePutResult<{ kind: "map"; value: TValue }>, BaseError>;
646
+ delete(
647
+ key: string,
648
+ opts?: StateDeleteOptions,
649
+ ): AsyncResult<{ deleted: boolean }, BaseError>;
650
+ list(opts?: StateListOptions): AsyncResult<{
651
+ entries: Array<
652
+ | MapStateEntry<TValue>
653
+ | StateMigrationRequiredEntry<MapStateEntry<unknown>>
654
+ >;
655
+ count: number;
656
+ offset: number;
657
+ limit: number;
658
+ nextOffset?: number;
659
+ }, BaseError>;
660
+ prefix(path: string): MapStateStoreClient<TValue>;
661
+ };
662
+ export type StateFacade<TState extends RuntimeStateStores> = {
663
+ [K in keyof TState]: TState[K]["kind"] extends "map"
664
+ ? MapStateStoreClient<TState[K]["value"]>
665
+ : ValueStateStoreClient<TState[K]["value"]>;
666
+ };
667
+ export type OperationHandlerContext<
668
+ TInput,
669
+ TProgress = unknown,
670
+ TOutput = unknown,
671
+ TTransfer = undefined,
672
+ > = {
673
+ input: TInput;
674
+ op: OperationRuntimeHandle<TProgress, TOutput>;
675
+ caller: SessionCaller;
676
+ } & (TTransfer extends undefined ? {} : { transfer: TTransfer });
677
+ export type OperationRegistration<
678
+ TInput,
679
+ TProgress = unknown,
680
+ TOutput = unknown,
681
+ TTransfer = undefined,
682
+ > = {
683
+ accept(args: {
684
+ sessionKey: string;
685
+ }): AsyncResult<AcceptedOperation<TProgress, TOutput>, UnexpectedError>;
686
+ /**
687
+ * Loads an existing operation by id and returns a service-side control handle.
688
+ * The operation must belong to this service and registration name.
689
+ */
690
+ control(
691
+ operationId: string,
692
+ ): AsyncResult<OperationRuntimeHandle<TProgress, TOutput>, BaseError>;
693
+ handle(
694
+ handler: (
695
+ context: OperationHandlerContext<TInput, TProgress, TOutput, TTransfer>,
696
+ ) => unknown | Promise<unknown>,
697
+ ): Promise<void>;
698
+ };
699
+ export type OperationTransferContextOf<
700
+ TA extends AnyTrellisAPI,
701
+ O extends OperationsOf<TA>,
702
+ > = TA["operations"][O] extends { transfer: infer TTransfer }
703
+ ? TTransfer extends undefined ? undefined
704
+ : OperationTransferHandle
705
+ : undefined;
706
+ export type OperationSurface<
707
+ TA extends AnyTrellisAPI,
708
+ TMode extends TrellisMode,
709
+ O extends OperationsOf<TA>,
710
+ > = TMode extends "server" ? OperationRegistration<
711
+ OperationInputOf<TA, O>,
712
+ OperationProgressOf<TA, O>,
713
+ OperationOutputOf<TA, O>,
714
+ OperationTransferContextOf<TA, O>
715
+ >
716
+ : OperationInvoker<TA["operations"][O] & RuntimeOperationDesc>;
717
+
718
+ export function isResultLike(
719
+ value: unknown,
720
+ ): value is Result<unknown, BaseError> {
721
+ return value instanceof Result;
722
+ }
723
+ export type RuntimeOperationDesc = {
724
+ subject: string;
725
+ input: unknown;
726
+ progress?: unknown;
727
+ output?: unknown;
728
+ signals?: Record<string, { input: unknown }>;
729
+ cancelCapabilities?: readonly string[];
730
+ controlCapabilities?: readonly string[];
731
+ transfer?: {
732
+ store: string;
733
+ key: `/${string}`;
734
+ contentType?: `/${string}`;
735
+ metadata?: `/${string}`;
736
+ expiresInMs?: number;
737
+ maxBytes?: number;
738
+ };
739
+ cancel?: boolean;
740
+ };
741
+
742
+ export type RuntimeOperationSignal = {
743
+ operationId: string;
744
+ sequence: number;
745
+ signal: string;
746
+ input?: JsonValue;
747
+ acceptedAt: string;
748
+ };
749
+
750
+ export type RuntimeOperationSignalWaiter = (
751
+ result: Result<RuntimeOperationSignal, BaseError>,
752
+ ) => void;
753
+
754
+ export type RuntimeOperationTransferProgress = {
755
+ chunkIndex: number;
756
+ chunkBytes: number;
757
+ transferredBytes: number;
758
+ };
759
+
760
+ export type RuntimeOperationState =
761
+ | "pending"
762
+ | "running"
763
+ | "completed"
764
+ | "failed"
765
+ | "cancelled";
766
+
767
+ export type RuntimeOperationSnapshot = {
768
+ id: string;
769
+ service: string;
770
+ operation: string;
771
+ revision: number;
772
+ state: RuntimeOperationState;
773
+ createdAt: string;
774
+ updatedAt: string;
775
+ completedAt?: string;
776
+ progress?: unknown;
777
+ transfer?: RuntimeOperationTransferProgress;
778
+ output?: unknown;
779
+ error?: {
780
+ type: string;
781
+ message: string;
782
+ };
783
+ };
784
+
785
+ export type RuntimeOperationRecord = {
786
+ id: string;
787
+ service: string;
788
+ operation: string;
789
+ ownerSessionKey: string;
790
+ snapshot: RuntimeOperationSnapshot;
791
+ sequence: number;
792
+ signalSequence: number;
793
+ signals: RuntimeOperationSignal[];
794
+ terminal: boolean;
795
+ watchers: Set<string>;
796
+ waiters: Set<string>;
797
+ signalWaiters: Set<RuntimeOperationSignalWaiter>;
798
+ };
799
+
800
+ export type DurableOperationRecord = {
801
+ ownerSessionKey: string;
802
+ sequence: number;
803
+ signalSequence?: number;
804
+ signals?: RuntimeOperationSignal[];
805
+ snapshot: RuntimeOperationSnapshot;
806
+ };
807
+
808
+ const DurableOperationSignalSchema = Type.Object({
809
+ operationId: Type.String(),
810
+ sequence: Type.Number(),
811
+ signal: Type.String(),
812
+ input: Type.Optional(Type.Any()),
813
+ acceptedAt: Type.String(),
814
+ });
815
+
816
+ const DurableOperationSnapshotSchema = Type.Object({
817
+ id: Type.String(),
818
+ service: Type.String(),
819
+ operation: Type.String(),
820
+ revision: Type.Number(),
821
+ state: Type.Union([
822
+ Type.Literal("pending"),
823
+ Type.Literal("running"),
824
+ Type.Literal("completed"),
825
+ Type.Literal("failed"),
826
+ Type.Literal("cancelled"),
827
+ ]),
828
+ createdAt: Type.String(),
829
+ updatedAt: Type.String(),
830
+ completedAt: Type.Optional(Type.String()),
831
+ progress: Type.Optional(Type.Any()),
832
+ transfer: Type.Optional(Type.Object({
833
+ chunkIndex: Type.Number(),
834
+ chunkBytes: Type.Number(),
835
+ transferredBytes: Type.Number(),
836
+ })),
837
+ output: Type.Optional(Type.Any()),
838
+ error: Type.Optional(Type.Object({
839
+ type: Type.String(),
840
+ message: Type.String(),
841
+ })),
842
+ });
843
+
844
+ export const DurableOperationRecordSchema = Type.Object({
845
+ ownerSessionKey: Type.String(),
846
+ sequence: Type.Number(),
847
+ signalSequence: Type.Optional(Type.Number()),
848
+ signals: Type.Optional(Type.Array(DurableOperationSignalSchema)),
849
+ snapshot: DurableOperationSnapshotSchema,
850
+ });
851
+
852
+ export type RuntimeOperationAcceptedEnvelope = {
853
+ kind: "accepted";
854
+ ref: OperationRefData;
855
+ snapshot: RuntimeOperationSnapshot;
856
+ transfer?: SendTransferGrant;
857
+ };
858
+
859
+ export type RuntimeOperationControlRequest =
860
+ | {
861
+ action: "get" | "wait" | "watch" | "cancel";
862
+ operationId: string;
863
+ }
864
+ | {
865
+ action: "signal";
866
+ operationId: string;
867
+ signal: string;
868
+ input?: JsonValue;
869
+ };
870
+
871
+ export type RuntimeOperationController = {
872
+ get(
873
+ operationId: string,
874
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
875
+ started(
876
+ operationId: string,
877
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
878
+ progress(
879
+ operationId: string,
880
+ progress: unknown,
881
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
882
+ complete(
883
+ operationId: string,
884
+ output: unknown,
885
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
886
+ fail(
887
+ operationId: string,
888
+ error: BaseError,
889
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
890
+ cancel(
891
+ operationId: string,
892
+ ): AsyncResult<RuntimeOperationSnapshot, BaseError>;
893
+ signals(operationId: string): AsyncIterable<RuntimeOperationSignal>;
894
+ nextSignal(
895
+ operationId: string,
896
+ name?: string,
897
+ ): AsyncResult<RuntimeOperationSignal, BaseError>;
898
+ };
899
+
900
+ export function buildRuntimeOperationSnapshot(
901
+ runtime: Pick<
902
+ RuntimeOperationRecord,
903
+ "id" | "service" | "operation" | "snapshot"
904
+ >,
905
+ state: RuntimeOperationState,
906
+ patch?: Partial<RuntimeOperationSnapshot>,
907
+ ): RuntimeOperationSnapshot {
908
+ const updatedAt = new Date().toISOString();
909
+ const completedAt =
910
+ state === "completed" || state === "failed" || state === "cancelled"
911
+ ? (patch?.completedAt ?? updatedAt)
912
+ : patch?.completedAt;
913
+ return {
914
+ id: runtime.id,
915
+ service: runtime.service,
916
+ operation: runtime.operation,
917
+ revision: patch?.revision ?? runtime.snapshot.revision + 1,
918
+ state,
919
+ createdAt: patch?.createdAt ?? runtime.snapshot.createdAt,
920
+ updatedAt,
921
+ ...(completedAt ? { completedAt } : {}),
922
+ ...(patch?.progress !== undefined
923
+ ? { progress: patch.progress }
924
+ : runtime.snapshot.progress !== undefined
925
+ ? { progress: runtime.snapshot.progress }
926
+ : {}),
927
+ ...(patch?.transfer !== undefined
928
+ ? { transfer: patch.transfer }
929
+ : runtime.snapshot.transfer !== undefined
930
+ ? { transfer: runtime.snapshot.transfer }
931
+ : {}),
932
+ ...(patch?.output !== undefined
933
+ ? { output: patch.output }
934
+ : runtime.snapshot.output !== undefined
935
+ ? { output: runtime.snapshot.output }
936
+ : {}),
937
+ ...(patch?.error
938
+ ? { error: patch.error }
939
+ : runtime.snapshot.error
940
+ ? { error: runtime.snapshot.error }
941
+ : {}),
942
+ };
943
+ }
944
+
945
+ function isRuntimeOperationSnapshot(
946
+ value: unknown,
947
+ ): value is RuntimeOperationSnapshot {
948
+ return !!value && typeof value === "object" &&
949
+ typeof (value as RuntimeOperationSnapshot).id === "string" &&
950
+ typeof (value as RuntimeOperationSnapshot).service === "string" &&
951
+ typeof (value as RuntimeOperationSnapshot).operation === "string" &&
952
+ typeof (value as RuntimeOperationSnapshot).revision === "number" &&
953
+ typeof (value as RuntimeOperationSnapshot).state === "string" &&
954
+ typeof (value as RuntimeOperationSnapshot).createdAt === "string" &&
955
+ typeof (value as RuntimeOperationSnapshot).updatedAt === "string";
956
+ }
957
+
958
+ export function isTerminalRuntimeOperationSnapshot(
959
+ value: unknown,
960
+ ): value is RuntimeOperationSnapshot {
961
+ return isRuntimeOperationSnapshot(value) && (
962
+ value.state === "completed" || value.state === "failed" ||
963
+ value.state === "cancelled"
964
+ );
965
+ }
966
+
967
+ type NoResponderRetryOpts = {
968
+ maxAttempts?: number;
969
+ baseDelayMs?: number;
970
+ };
971
+
972
+ export type TrellisOpts<TA extends AnyTrellisAPI> = {
973
+ log?: LoggerLike;
974
+ timeout?: number;
975
+ stream?: string;
976
+ noResponderRetry?: NoResponderRetryOpts;
977
+ api?: TA;
978
+ state?: RuntimeStateStores;
979
+ connection?: TrellisConnection;
980
+ onSessionNotFound?: () => MaybePromise<void>;
981
+ };
982
+
983
+ export type RequestOpts = {
984
+ timeout?: number;
985
+ };
986
+
987
+ export type EventOpts = {
988
+ mode?: "durable" | "ephemeral";
989
+ replay?: "all" | "new";
990
+ durableName?: string;
991
+ signal?: AbortSignal;
992
+ };
993
+
994
+ export type FeedSubscribeOpts = {
995
+ signal?: AbortSignal;
996
+ };
997
+
998
+ export type FeedSubscription<TEvent> = AsyncIterable<TEvent>;
999
+
1000
+ export type FeedInputBuilder<TInput, TEvent> = {
1001
+ input(input: TInput): {
1002
+ subscribe(
1003
+ opts?: FeedSubscribeOpts,
1004
+ ): AsyncResult<FeedSubscription<TEvent>, BaseError>;
1005
+ };
1006
+ };
1007
+
1008
+ export type FeedHandlerContext<TInput, TEvent> = {
1009
+ input: TInput;
1010
+ caller: SessionCaller;
1011
+ signal: AbortSignal;
1012
+ emit(event: TEvent): AsyncResult<void, ValidationError | UnexpectedError>;
1013
+ };
1014
+
1015
+ export type FeedRegistration<TInput, TEvent> = {
1016
+ handle(
1017
+ handler: (
1018
+ context: FeedHandlerContext<TInput, TEvent>,
1019
+ ) => unknown | Promise<unknown>,
1020
+ ): Promise<void>;
1021
+ };
1022
+
1023
+ export type FeedSurface<
1024
+ TA extends AnyTrellisAPI,
1025
+ TMode extends TrellisMode,
1026
+ F extends FeedsOf<TA>,
1027
+ > = TMode extends "server"
1028
+ ? FeedRegistration<FeedInputOf<TA, F>, FeedEventOf<TA, F>>
1029
+ : FeedInputBuilder<FeedInputOf<TA, F>, FeedEventOf<TA, F>>;
1030
+
1031
+ type MaybePromise<T> = T | Promise<T>;
1032
+
1033
+ type EventCallback<TMessage> = {
1034
+ bivarianceHack(message: TMessage): MaybeAsync<void, BaseError>;
1035
+ }["bivarianceHack"];
1036
+
1037
+ export type RpcHandlerContext = {
1038
+ caller: SessionCaller;
1039
+ sessionKey: string;
1040
+ };
1041
+
1042
+ export type HandlerTrellis<
1043
+ TA extends AnyTrellisAPI,
1044
+ TRequests = RpcRequestShapes<TA>,
1045
+ > = {
1046
+ request<const M extends RequestMethodOf<TRequests>>(
1047
+ method: M,
1048
+ input: RequestInputOf<TRequests, M>,
1049
+ opts?: RequestOpts,
1050
+ ): AsyncResult<RequestOutputOf<TRequests, M>, BaseError>;
1051
+ publish(
1052
+ event: string,
1053
+ data: Record<string, unknown>,
1054
+ ): AsyncResult<void, ValidationError | UnexpectedError>;
1055
+ event<E extends EventsOf<TA>>(
1056
+ event: E,
1057
+ subjectData: Record<string, unknown>,
1058
+ fn: EventCallback<EventOf<TA, E>>,
1059
+ opts?: EventOpts,
1060
+ ): AsyncResult<void, ValidationError | UnexpectedError>;
1061
+ operation<O extends OperationsOf<TA>>(
1062
+ operation: O,
1063
+ ): OperationSurface<TA, TrellisMode, O>;
1064
+ };
1065
+
1066
+ export type HandlerKvFacade<TKv extends ContractKvMetadata> = {
1067
+ [K in keyof TKv]: TKv[K]["required"] extends false
1068
+ ? TypedKV<TKv[K]["schema"]> | undefined
1069
+ : TypedKV<TKv[K]["schema"]>;
1070
+ };
1071
+
1072
+ export type HandlerStoreHandle = {
1073
+ open(): AsyncResult<TypedStore, StoreError>;
1074
+ waitFor(
1075
+ key: string,
1076
+ options?: StoreWaitOptions,
1077
+ ): AsyncResult<TypedStoreEntry, StoreError>;
1078
+ };
1079
+
1080
+ export type HandlerJobQueue<
1081
+ TPayload,
1082
+ TResult,
1083
+ TTrellis,
1084
+ > = {
1085
+ create(payload: TPayload): AsyncResult<JobRef<TPayload, TResult>, BaseError>;
1086
+ handle(
1087
+ handler: (args: {
1088
+ job: ActiveJob<TPayload, TResult>;
1089
+ trellis: TTrellis;
1090
+ }) => Promise<Result<TResult, BaseError>>,
1091
+ ): void;
1092
+ };
1093
+
1094
+ export type HandlerJobsFacade<
1095
+ TJobs extends Record<string, JobTypeMetadata>,
1096
+ TTrellis,
1097
+ > = {
1098
+ [K in keyof TJobs]: HandlerJobQueue<
1099
+ TJobs[K]["payload"],
1100
+ TJobs[K]["result"],
1101
+ TTrellis
1102
+ >;
1103
+ };
1104
+
1105
+ export type HandlerTrellisForContract<TContract> =
1106
+ & HandlerTrellis<TrellisApiFor<TContract>>
1107
+ & {
1108
+ kv: HandlerKvFacade<ContractKvFor<TContract>>;
1109
+ store: Record<string, HandlerStoreHandle>;
1110
+ jobs: HandlerJobsFacade<
1111
+ ContractJobsFor<TContract>,
1112
+ HandlerTrellisForContract<TContract>
1113
+ >;
1114
+ };
1115
+
1116
+ /** Public client-side surface returned by `TrellisClient.connect`. */
1117
+ export type ClientTrellis<
1118
+ TA extends AnyTrellisAPI = TrellisAPI,
1119
+ TState extends RuntimeStateStores = {},
1120
+ TRequests = RpcRequestShapes<TA>,
1121
+ > = {
1122
+ readonly name: string;
1123
+ readonly timeout: number;
1124
+ readonly stream: string;
1125
+ readonly api: TA;
1126
+ readonly state: StateFacade<TState>;
1127
+ readonly connection: TrellisConnection;
1128
+ readonly natsConnection: NatsConnection;
1129
+ request<const M extends RequestMethodOf<TRequests>>(
1130
+ method: M,
1131
+ input: RequestInputOf<TRequests, M>,
1132
+ opts?: RequestOpts,
1133
+ ): AsyncResult<RequestOutputOf<TRequests, M>, BaseError>;
1134
+ publish<E extends EventsOf<TA>>(
1135
+ event: E,
1136
+ data: EventPayloadOf<TA, E>,
1137
+ ): AsyncResult<void, ValidationError | UnexpectedError>;
1138
+ event(
1139
+ event: string,
1140
+ subjectData: Record<string, unknown>,
1141
+ fn: EventCallback<unknown>,
1142
+ opts?: EventOpts,
1143
+ ): AsyncResult<void, ValidationError | UnexpectedError>;
1144
+ feed<F extends FeedsOf<TA>>(
1145
+ feed: F,
1146
+ ): FeedInputBuilder<FeedInputOf<TA, F>, FeedEventOf<TA, F>>;
1147
+ operation<O extends OperationsOf<TA>>(
1148
+ operation: O,
1149
+ ): OperationSurface<TA, "client", O>;
1150
+ transfer(grant: SendTransferGrant): SendTransferHandle;
1151
+ transfer(grant: ReceiveTransferGrant): ReceiveTransferHandle;
1152
+ wait(): AsyncResult<void, BaseError>;
1153
+ };
1154
+
1155
+ /** Connected client type for a generated Trellis contract. */
1156
+ export type ConnectedTrellisClient<TContract> = Simplify<
1157
+ ClientTrellis<
1158
+ TContract extends { API: { trellis: infer TApi } }
1159
+ ? TApi extends AnyTrellisAPI ? TApi : TrellisAPI
1160
+ : TrellisAPI,
1161
+ RuntimeStateStoresForContract<TContract>
1162
+ >
1163
+ >;
1164
+
1165
+ export type HandlerArgs<
1166
+ TMountApi extends AnyTrellisAPI,
1167
+ M extends MethodsOf<TMountApi>,
1168
+ TOutboundApi extends AnyTrellisAPI = TMountApi,
1169
+ TTrellis = HandlerTrellis<TOutboundApi>,
1170
+ > = {
1171
+ input: MethodInputOf<TMountApi, M>;
1172
+ context: RpcHandlerContext;
1173
+ trellis: TTrellis;
1174
+ };
1175
+
1176
+ export type HandlerFn<
1177
+ TMountApi extends AnyTrellisAPI,
1178
+ M extends MethodsOf<TMountApi>,
1179
+ TOutboundApi extends AnyTrellisAPI = TMountApi,
1180
+ TTrellis = HandlerTrellis<TOutboundApi>,
1181
+ > = (args: HandlerArgs<TMountApi, M, TOutboundApi, TTrellis>) => MaybePromise<
1182
+ Result<MethodOutputOf<TMountApi, M>, HandlerErrorOf<TMountApi, M>>
1183
+ >;
1184
+ export type RpcHandlerFn<
1185
+ TA extends AnyTrellisAPI,
1186
+ M extends RpcMethodNameOf<TA>,
1187
+ > = HandlerFn<TA, M, TA>;
1188
+ export type TrellisFor<TContract> = HandlerTrellisForContract<TContract>;
1189
+
1190
+ const DEFAULT_STATE_LIST_LIMIT = 100;
1191
+
1192
+ const STATE_RUNTIME_RPC = {
1193
+ get: {
1194
+ subject: "rpc.v1.State.Get",
1195
+ input: StateGetSchema,
1196
+ output: StateGetResponseSchema,
1197
+ callerCapabilities: [],
1198
+ errors: ["AuthError", "ValidationError", "UnexpectedError"] as const,
1199
+ declaredErrorTypes: [
1200
+ "AuthError",
1201
+ "ValidationError",
1202
+ "UnexpectedError",
1203
+ ] as const,
1204
+ },
1205
+ put: {
1206
+ subject: "rpc.v1.State.Put",
1207
+ input: StatePutSchema,
1208
+ output: StatePutResponseSchema,
1209
+ callerCapabilities: [],
1210
+ errors: ["AuthError", "ValidationError", "UnexpectedError"] as const,
1211
+ declaredErrorTypes: [
1212
+ "AuthError",
1213
+ "ValidationError",
1214
+ "UnexpectedError",
1215
+ ] as const,
1216
+ },
1217
+ delete: {
1218
+ subject: "rpc.v1.State.Delete",
1219
+ input: StateDeleteSchema,
1220
+ output: StateDeleteResponseSchema,
1221
+ callerCapabilities: [],
1222
+ errors: ["AuthError", "ValidationError", "UnexpectedError"] as const,
1223
+ declaredErrorTypes: [
1224
+ "AuthError",
1225
+ "ValidationError",
1226
+ "UnexpectedError",
1227
+ ] as const,
1228
+ },
1229
+ list: {
1230
+ subject: "rpc.v1.State.List",
1231
+ input: StateListSchema,
1232
+ output: StateListResponseSchema,
1233
+ callerCapabilities: [],
1234
+ errors: ["AuthError", "ValidationError", "UnexpectedError"] as const,
1235
+ declaredErrorTypes: [
1236
+ "AuthError",
1237
+ "ValidationError",
1238
+ "UnexpectedError",
1239
+ ] as const,
1240
+ },
1241
+ } satisfies Record<string, {
1242
+ subject: string;
1243
+ input: unknown;
1244
+ output: unknown;
1245
+ callerCapabilities: readonly string[];
1246
+ errors: readonly string[];
1247
+ declaredErrorTypes: readonly string[];
1248
+ }>;
1249
+
1250
+ function joinStatePath(prefix: string | undefined, key: string): string {
1251
+ return [prefix, key]
1252
+ .flatMap((value) => value?.split("/") ?? [])
1253
+ .filter((segment) => segment.length > 0)
1254
+ .join("/");
1255
+ }
1256
+
1257
+ function validateStateValue(
1258
+ schema: unknown,
1259
+ value: JsonValue,
1260
+ ): Result<unknown, ValidationError | UnexpectedError> {
1261
+ return parseRuntimeSchema(schema, value);
1262
+ }
1263
+
1264
+ function validateStateGetResult<TStore extends RuntimeStateStoreShape>(
1265
+ descriptor: RuntimeStateStoreShape,
1266
+ result: StateGetResult<TStore>,
1267
+ ): Result<StateGetResult<TStore>, ValidationError | UnexpectedError> {
1268
+ if ("migrationRequired" in result) {
1269
+ const schema = descriptor.acceptedVersions?.[result.stateVersion];
1270
+ if (!schema) {
1271
+ return Result.err(
1272
+ new ValidationError({
1273
+ errors: [{
1274
+ path: "/stateVersion",
1275
+ message:
1276
+ `state version '${result.stateVersion}' is not accepted by the runtime store`,
1277
+ }],
1278
+ }),
1279
+ );
1280
+ }
1281
+ const parsed = validateStateValue(schema, result.entry.value as JsonValue);
1282
+ if (parsed.isErr()) return Result.err(parsed.error);
1283
+ return Result.ok({
1284
+ ...result,
1285
+ entry: {
1286
+ ...result.entry,
1287
+ value: parsed.unwrapOrElse(() => {
1288
+ throw new Error("state value validation unexpectedly failed");
1289
+ }),
1290
+ },
1291
+ });
1292
+ }
1293
+
1294
+ if (!result.found) {
1295
+ return Result.ok(result);
1296
+ }
1297
+
1298
+ const parsed = validateStateValue(
1299
+ descriptor.schema,
1300
+ result.entry.value as JsonValue,
1301
+ );
1302
+ if (parsed.isErr()) {
1303
+ return Result.err(parsed.error);
1304
+ }
1305
+
1306
+ return Result.ok({
1307
+ ...result,
1308
+ entry: {
1309
+ ...result.entry,
1310
+ value: parsed.unwrapOrElse(() => {
1311
+ throw new Error("state value validation unexpectedly failed");
1312
+ }),
1313
+ },
1314
+ });
1315
+ }
1316
+
1317
+ function validateStatePutResult<TStore extends RuntimeStateStoreShape>(
1318
+ descriptor: RuntimeStateStoreShape,
1319
+ result: StatePutResult<TStore>,
1320
+ ): Result<StatePutResult<TStore>, ValidationError | UnexpectedError> {
1321
+ if (result.applied) {
1322
+ const parsed = validateStateValue(
1323
+ descriptor.schema,
1324
+ result.entry.value as JsonValue,
1325
+ );
1326
+ if (parsed.isErr()) return Result.err(parsed.error);
1327
+ return Result.ok({
1328
+ ...result,
1329
+ entry: {
1330
+ ...result.entry,
1331
+ value: parsed.unwrapOrElse(() => {
1332
+ throw new Error("state value validation unexpectedly failed");
1333
+ }),
1334
+ },
1335
+ });
1336
+ }
1337
+
1338
+ if (!result.entry) {
1339
+ return Result.ok(result);
1340
+ }
1341
+
1342
+ if ("migrationRequired" in result.entry) {
1343
+ const schema = descriptor.acceptedVersions?.[result.entry.stateVersion];
1344
+ if (!schema) {
1345
+ return Result.err(
1346
+ new ValidationError({
1347
+ errors: [{
1348
+ path: "/stateVersion",
1349
+ message:
1350
+ `state version '${result.entry.stateVersion}' is not accepted by the runtime store`,
1351
+ }],
1352
+ }),
1353
+ );
1354
+ }
1355
+ const parsed = validateStateValue(
1356
+ schema,
1357
+ result.entry.entry.value as JsonValue,
1358
+ );
1359
+ if (parsed.isErr()) return Result.err(parsed.error);
1360
+ return Result.ok({
1361
+ ...result,
1362
+ entry: {
1363
+ ...result.entry,
1364
+ entry: {
1365
+ ...result.entry.entry,
1366
+ value: parsed.unwrapOrElse(() => {
1367
+ throw new Error("state value validation unexpectedly failed");
1368
+ }),
1369
+ },
1370
+ },
1371
+ });
1372
+ }
1373
+
1374
+ const parsed = validateStateValue(
1375
+ descriptor.schema,
1376
+ result.entry.value as JsonValue,
1377
+ );
1378
+ if (parsed.isErr()) {
1379
+ return Result.err(parsed.error);
1380
+ }
1381
+
1382
+ return Result.ok({
1383
+ ...result,
1384
+ entry: {
1385
+ ...result.entry,
1386
+ value: parsed.unwrapOrElse(() => {
1387
+ throw new Error("state value validation unexpectedly failed");
1388
+ }),
1389
+ },
1390
+ });
1391
+ }
1392
+
1393
+ function validateStateListResult(
1394
+ descriptor: RuntimeStateStoreShape,
1395
+ result: {
1396
+ entries: Array<
1397
+ MapStateEntry<unknown> | {
1398
+ migrationRequired: true;
1399
+ entry: MapStateEntry<unknown>;
1400
+ stateVersion: string;
1401
+ currentStateVersion: string;
1402
+ writerContractDigest: string;
1403
+ }
1404
+ >;
1405
+ count: number;
1406
+ offset: number;
1407
+ limit: number;
1408
+ nextOffset?: number;
1409
+ },
1410
+ ): Result<typeof result, ValidationError | UnexpectedError> {
1411
+ const entries: typeof result.entries = [];
1412
+ for (const entry of result.entries) {
1413
+ if ("migrationRequired" in entry) {
1414
+ const schema = descriptor.acceptedVersions?.[entry.stateVersion];
1415
+ if (!schema) {
1416
+ return Result.err(
1417
+ new ValidationError({
1418
+ errors: [{
1419
+ path: "/stateVersion",
1420
+ message:
1421
+ `state version '${entry.stateVersion}' is not accepted by the runtime store`,
1422
+ }],
1423
+ }),
1424
+ );
1425
+ }
1426
+ const parsed = validateStateValue(schema, entry.entry.value as JsonValue);
1427
+ if (parsed.isErr()) return Result.err(parsed.error);
1428
+ entries.push({
1429
+ ...entry,
1430
+ entry: {
1431
+ ...entry.entry,
1432
+ value: parsed.unwrapOrElse(() => {
1433
+ throw new Error("state value validation unexpectedly failed");
1434
+ }),
1435
+ },
1436
+ });
1437
+ continue;
1438
+ }
1439
+
1440
+ const parsed = validateStateValue(
1441
+ descriptor.schema,
1442
+ entry.value as JsonValue,
1443
+ );
1444
+ if (parsed.isErr()) {
1445
+ return Result.err(parsed.error);
1446
+ }
1447
+ entries.push({
1448
+ ...entry,
1449
+ value: parsed.unwrapOrElse(() => {
1450
+ throw new Error("state value validation unexpectedly failed");
1451
+ }),
1452
+ });
1453
+ }
1454
+
1455
+ return Result.ok({ ...result, entries });
1456
+ }
1457
+
1458
+ export type RpcArgs<
1459
+ TContract,
1460
+ M extends RpcMethodNameOf<OwnedApiFor<TContract>>,
1461
+ > = HandlerArgs<
1462
+ OwnedApiFor<TContract>,
1463
+ M,
1464
+ TrellisApiFor<TContract>,
1465
+ HandlerTrellisForContract<TContract>
1466
+ >;
1467
+ export type RpcResult<
1468
+ TContract,
1469
+ M extends RpcMethodNameOf<OwnedApiFor<TContract>>,
1470
+ > = Result<
1471
+ RpcOutputOf<OwnedApiFor<TContract>, M>,
1472
+ RpcHandlerErrorOf<OwnedApiFor<TContract>, M>
1473
+ >;
1474
+ export type RpcRequestErrorOf<
1475
+ TA extends AnyTrellisAPI,
1476
+ M extends RpcMethodNameOf<TA>,
1477
+ > = RequestErrorOf<TA, M>;
1478
+ export type RpcHandlerErrorOf<
1479
+ TA extends AnyTrellisAPI,
1480
+ M extends RpcMethodNameOf<TA>,
1481
+ > = HandlerErrorOf<TA, M>;
1482
+ export type EventName<TContract> = EventsOf<OwnedApiFor<TContract>>;
1483
+ export type EventType<
1484
+ TContract,
1485
+ E extends EventName<TContract>,
1486
+ > = EventOf<OwnedApiFor<TContract>, E>;
1487
+ export type EventPayload<
1488
+ TContract,
1489
+ E extends EventName<TContract>,
1490
+ > = EventPayloadOf<OwnedApiFor<TContract>, E>;
1491
+ export type EventHandler<
1492
+ TContract,
1493
+ E extends EventName<TContract>,
1494
+ > = (event: EventType<TContract, E>) => MaybeAsync<void, BaseError>;
1495
+
1496
+ type DeepRecord<T> = {
1497
+ [k: string]: T | DeepRecord<T>;
1498
+ };
1499
+
1500
+ const NATS_SUBJECT_TOKEN_FORBIDDEN = /[\u0000\s.*>~]/gu;
1501
+
1502
+ const DEFAULT_NO_RESPONDER_MAX_RETRIES = 2;
1503
+ const DEFAULT_NO_RESPONDER_RETRY_MS = 200;
1504
+ const DEFAULT_AUTH_VALIDATE_SESSION_RETRY_ATTEMPTS = 3;
1505
+ const DEFAULT_AUTH_VALIDATE_SESSION_RETRY_MS = 25;
1506
+
1507
+ function activeTraceId(span: Span): string | undefined {
1508
+ const traceId = span.spanContext().traceId;
1509
+ return traceId === "00000000000000000000000000000000" ? undefined : traceId;
1510
+ }
1511
+
1512
+ function traceIdFromTraceparent(
1513
+ traceparent: string | undefined,
1514
+ ): string | undefined {
1515
+ const [version, traceId, parentId, flags, extra] = traceparent?.split("-") ??
1516
+ [];
1517
+ if (
1518
+ extra !== undefined ||
1519
+ !/^[0-9a-f]{2}$/u.test(version ?? "") ||
1520
+ version === "ff" ||
1521
+ !/^[0-9a-f]{32}$/u.test(traceId ?? "") ||
1522
+ traceId === "00000000000000000000000000000000" ||
1523
+ !/^[0-9a-f]{16}$/u.test(parentId ?? "") ||
1524
+ parentId === "0000000000000000" ||
1525
+ !/^[0-9a-f]{2}$/u.test(flags ?? "")
1526
+ ) {
1527
+ return undefined;
1528
+ }
1529
+ return traceId;
1530
+ }
1531
+
1532
+ const EMPTY_TRELLIS_API: TrellisAPI = {
1533
+ rpc: {},
1534
+ operations: {},
1535
+ events: {},
1536
+ feeds: {},
1537
+ subjects: {},
1538
+ };
1539
+
1540
+ type AuthCacheEntry = {
1541
+ caller: SessionCaller;
1542
+ expires: number;
1543
+ };
1544
+
1545
+ function isBrowserAuthRequiredError(error: unknown): boolean {
1546
+ const isAuthRequiredReason = (reason: unknown): boolean =>
1547
+ reason === "session_not_found" || reason === "reauth_required";
1548
+
1549
+ if (error instanceof AuthError) {
1550
+ return isAuthRequiredReason(error.reason);
1551
+ }
1552
+
1553
+ if (
1554
+ error instanceof RemoteError &&
1555
+ error.remoteError.type === "AuthError"
1556
+ ) {
1557
+ const reason = Reflect.get(error.remoteError, "reason");
1558
+ return isAuthRequiredReason(reason);
1559
+ }
1560
+
1561
+ return false;
1562
+ }
1563
+
1564
+ function isTransientAuthValidateSessionError(error: unknown): boolean {
1565
+ if (error instanceof AuthError) {
1566
+ return error.reason === "session_not_found";
1567
+ }
1568
+
1569
+ if (
1570
+ error instanceof RemoteError &&
1571
+ error.remoteError.type === "AuthError"
1572
+ ) {
1573
+ const reason = Reflect.get(error.remoteError, "reason");
1574
+ return reason === "session_not_found";
1575
+ }
1576
+
1577
+ return false;
1578
+ }
1579
+
1580
+ function isDeclaredRpcError(
1581
+ errorNames: readonly string[] | undefined,
1582
+ type: string,
1583
+ ): boolean {
1584
+ return !!errorNames?.includes(type);
1585
+ }
1586
+
1587
+ function isRuntimeRpcErrorDesc(value: unknown): value is RuntimeRpcErrorDesc {
1588
+ return !!value && typeof value === "object" &&
1589
+ typeof Reflect.get(value, "type") === "string" &&
1590
+ typeof Reflect.get(value, "fromSerializable") === "function";
1591
+ }
1592
+
1593
+ const payloadSizeEncoder = new TextEncoder();
1594
+
1595
+ function payloadByteLength(payload: string | Uint8Array): number {
1596
+ return typeof payload === "string"
1597
+ ? payloadSizeEncoder.encode(payload).byteLength
1598
+ : payload.byteLength;
1599
+ }
1600
+
1601
+ function causeMessage(cause: unknown): string {
1602
+ return cause instanceof Error ? cause.message : String(cause);
1603
+ }
1604
+
1605
+ function causeLogData(cause: unknown): unknown {
1606
+ return cause instanceof Error
1607
+ ? { message: cause.message, stack: cause.stack, name: cause.name }
1608
+ : cause;
1609
+ }
1610
+
1611
+ function reconstructDeclaredRpcError(
1612
+ errorNames: readonly string[] | undefined,
1613
+ runtimeErrors: readonly RuntimeRpcErrorDesc[] | undefined,
1614
+ data: StaticDecode<typeof TrellisErrorDataSchema>,
1615
+ json: JsonValue,
1616
+ ): BaseError | ValidationError | UnexpectedError | null {
1617
+ if (!isDeclaredRpcError(errorNames, data.type)) {
1618
+ return null;
1619
+ }
1620
+
1621
+ const runtimeError = getBuiltinRpcError(data.type) ??
1622
+ runtimeErrors?.find((candidate) => candidate.type === data.type);
1623
+ if (!runtimeError) {
1624
+ return null;
1625
+ }
1626
+
1627
+ const parsed = runtimeError.schema
1628
+ ? parseRuntimeSchema(runtimeError.schema, json).take()
1629
+ : data;
1630
+ if (isErr(parsed)) {
1631
+ return parsed.error instanceof ValidationError ||
1632
+ parsed.error instanceof UnexpectedError
1633
+ ? parsed.error
1634
+ : new UnexpectedError({ cause: parsed.error });
1635
+ }
1636
+
1637
+ try {
1638
+ const reconstructed = runtimeError.fromSerializable(parsed);
1639
+ if (reconstructed instanceof BaseError) {
1640
+ return reconstructed;
1641
+ }
1642
+ return new UnexpectedError({
1643
+ cause: new Error(
1644
+ `RPC error '${data.type}' reconstructed to a non-Trellis error instance`,
1645
+ ),
1646
+ });
1647
+ } catch (cause) {
1648
+ return new UnexpectedError({ cause });
1649
+ }
1650
+ }
1651
+
1652
+ async function sleep(ms: number): Promise<void> {
1653
+ await new Promise((resolve) => setTimeout(resolve, ms));
1654
+ }
1655
+
1656
+ export class Trellis<
1657
+ TA extends AnyTrellisAPI = TrellisAPI,
1658
+ TMode extends TrellisMode = "client",
1659
+ TState extends RuntimeStateStores = {},
1660
+ TRequests = RpcRequestShapes<TA>,
1661
+ > {
1662
+ readonly name: string;
1663
+ readonly timeout: number;
1664
+ readonly stream: string;
1665
+ readonly state: StateFacade<TState>;
1666
+ /** Framework-neutral lifecycle handle for this Trellis runtime connection. */
1667
+ readonly connection: TrellisConnection;
1668
+
1669
+ protected nats: NatsConnection;
1670
+ protected js: JetStreamClient;
1671
+ protected auth: TrellisAuth;
1672
+ readonly api: TA;
1673
+ #log: LoggerLike;
1674
+ #tasks: TrellisTasks;
1675
+ #hasExplicitApi: boolean;
1676
+ #noResponderMaxRetries: number;
1677
+ #noResponderRetryMs: number;
1678
+ #onSessionNotFound?: () => MaybePromise<void>;
1679
+ #operationStore?: Promise<TypedKV<typeof DurableOperationRecordSchema>>;
1680
+
1681
+ constructor(
1682
+ name: string, // Must be unique for a service
1683
+ nats: NatsConnection,
1684
+ auth: TrellisAuth,
1685
+ opts?: TrellisOpts<TA>,
1686
+ ) {
1687
+ const api = opts?.api;
1688
+
1689
+ this.name = name;
1690
+ this.nats = nats;
1691
+ this.js = jetstream(this.nats);
1692
+ this.auth = auth as TrellisAuth;
1693
+ this.api = (api ?? EMPTY_TRELLIS_API) as TA;
1694
+ this.#log = (opts?.log ?? logger).child({ lib: "trellis" });
1695
+ this.timeout = opts?.timeout ?? 3000;
1696
+ this.stream = opts?.stream ?? "trellis";
1697
+ this.#hasExplicitApi = api !== undefined;
1698
+ this.#noResponderMaxRetries = opts?.noResponderRetry?.maxAttempts ??
1699
+ DEFAULT_NO_RESPONDER_MAX_RETRIES;
1700
+ this.#noResponderRetryMs = opts?.noResponderRetry?.baseDelayMs ??
1701
+ DEFAULT_NO_RESPONDER_RETRY_MS;
1702
+ this.#onSessionNotFound = opts?.onSessionNotFound;
1703
+ this.connection = opts?.connection ??
1704
+ new TrellisConnection({ kind: "client" });
1705
+
1706
+ this.#tasks = new TrellisTasks({ log: this.#log });
1707
+ this.state = this.#createStateFacade(opts?.state as TState | undefined);
1708
+ }
1709
+
1710
+ /**
1711
+ * Returns the underlying NATS connection.
1712
+ */
1713
+ get natsConnection(): NatsConnection {
1714
+ return this.nats;
1715
+ }
1716
+
1717
+ #createStateFacade(state: TState | undefined): StateFacade<TState> {
1718
+ const stores = (state ?? {}) as RuntimeStateStores;
1719
+ const facade = Object.fromEntries(
1720
+ Object.entries(stores).map(([store, descriptor]) => {
1721
+ if (descriptor.kind === "value") {
1722
+ const client: ValueStateStoreClient<unknown> = {
1723
+ get: () =>
1724
+ AsyncResult.from((async () => {
1725
+ const result = await this.#requestBuiltRpc<
1726
+ StateGetResult<{ kind: "value"; value: unknown }>
1727
+ >(
1728
+ "State.Get",
1729
+ { store },
1730
+ STATE_RUNTIME_RPC.get,
1731
+ );
1732
+ if (result.isErr()) return result;
1733
+ return validateStateGetResult(
1734
+ descriptor,
1735
+ result.unwrapOrElse(() => {
1736
+ throw new Error("state get unexpectedly failed");
1737
+ }),
1738
+ );
1739
+ })()),
1740
+ put: (value, opts) =>
1741
+ AsyncResult.from((async () => {
1742
+ const encoded = encodeRuntimeSchema(descriptor.schema, value)
1743
+ .take();
1744
+ if (isErr(encoded)) {
1745
+ return Result.err(encoded.error);
1746
+ }
1747
+ const result = await this.#requestBuiltRpc<
1748
+ StatePutResult<{ kind: "value"; value: unknown }>
1749
+ >(
1750
+ "State.Put",
1751
+ { store, value, ...opts },
1752
+ STATE_RUNTIME_RPC.put,
1753
+ );
1754
+ if (result.isErr()) return result;
1755
+ return validateStatePutResult(
1756
+ descriptor,
1757
+ result.unwrapOrElse(() => {
1758
+ throw new Error("state put unexpectedly failed");
1759
+ }),
1760
+ );
1761
+ })()),
1762
+ delete: (opts) =>
1763
+ this.#requestBuiltRpc<{ deleted: boolean }>(
1764
+ "State.Delete",
1765
+ { store, ...opts },
1766
+ STATE_RUNTIME_RPC.delete,
1767
+ ),
1768
+ };
1769
+ return [store, client];
1770
+ }
1771
+
1772
+ const mapClient = (prefix?: string): MapStateStoreClient<unknown> => ({
1773
+ get: (key) =>
1774
+ AsyncResult.from((async () => {
1775
+ const result = await this.#requestBuiltRpc<
1776
+ StateGetResult<{ kind: "map"; value: unknown }>
1777
+ >(
1778
+ "State.Get",
1779
+ { store, key: joinStatePath(prefix, key) },
1780
+ STATE_RUNTIME_RPC.get,
1781
+ );
1782
+ if (result.isErr()) return result;
1783
+ return validateStateGetResult(
1784
+ descriptor,
1785
+ result.unwrapOrElse(() => {
1786
+ throw new Error("state get unexpectedly failed");
1787
+ }),
1788
+ );
1789
+ })()),
1790
+ put: (key, value, opts) =>
1791
+ AsyncResult.from((async () => {
1792
+ const encoded = encodeRuntimeSchema(descriptor.schema, value)
1793
+ .take();
1794
+ if (isErr(encoded)) {
1795
+ return Result.err(encoded.error);
1796
+ }
1797
+ const result = await this.#requestBuiltRpc<
1798
+ StatePutResult<{ kind: "map"; value: unknown }>
1799
+ >(
1800
+ "State.Put",
1801
+ { store, key: joinStatePath(prefix, key), value, ...opts },
1802
+ STATE_RUNTIME_RPC.put,
1803
+ );
1804
+ if (result.isErr()) return result;
1805
+ return validateStatePutResult(
1806
+ descriptor,
1807
+ result.unwrapOrElse(() => {
1808
+ throw new Error("state put unexpectedly failed");
1809
+ }),
1810
+ );
1811
+ })()),
1812
+ delete: (key, opts) =>
1813
+ this.#requestBuiltRpc<{ deleted: boolean }>(
1814
+ "State.Delete",
1815
+ { store, key: joinStatePath(prefix, key), ...opts },
1816
+ STATE_RUNTIME_RPC.delete,
1817
+ ),
1818
+ list: (opts) =>
1819
+ AsyncResult.from((async () => {
1820
+ const result = await this.#requestBuiltRpc<{
1821
+ entries: Array<
1822
+ | MapStateEntry<unknown>
1823
+ | StateMigrationRequiredEntry<MapStateEntry<unknown>>
1824
+ >;
1825
+ count: number;
1826
+ offset: number;
1827
+ limit: number;
1828
+ nextOffset?: number;
1829
+ }>(
1830
+ "State.List",
1831
+ {
1832
+ store,
1833
+ ...(prefix ? { prefix } : {}),
1834
+ offset: opts?.offset ?? 0,
1835
+ limit: opts?.limit ?? DEFAULT_STATE_LIST_LIMIT,
1836
+ },
1837
+ STATE_RUNTIME_RPC.list,
1838
+ );
1839
+ if (result.isErr()) return result;
1840
+ return validateStateListResult(
1841
+ descriptor,
1842
+ result.unwrapOrElse(() => {
1843
+ throw new Error("state list unexpectedly failed");
1844
+ }),
1845
+ );
1846
+ })()),
1847
+ prefix: (path) => mapClient(joinStatePath(prefix, path)),
1848
+ });
1849
+
1850
+ return [store, mapClient()];
1851
+ }),
1852
+ );
1853
+
1854
+ return facade as StateFacade<TState>;
1855
+ }
1856
+
1857
+ #unknownApiError(
1858
+ kind: "RPC method" | "operation" | "event" | "feed",
1859
+ name: string,
1860
+ ): Error {
1861
+ const base = `Unknown ${kind} '${name}'.`;
1862
+ if (this.#hasExplicitApi) {
1863
+ return new Error(`${base} Did you forget to include its API module?`);
1864
+ }
1865
+ return new Error(
1866
+ `${base} No API surface was provided. Pass opts.api, use createClient(contract, ...), or await createCoreClient(...) instead.`,
1867
+ );
1868
+ }
1869
+
1870
+ async operationStoreHandle(): Promise<
1871
+ TypedKV<typeof DurableOperationRecordSchema>
1872
+ > {
1873
+ if (!this.#operationStore) {
1874
+ const bucket = `trellis_operations_${this.auth.sessionKey.slice(0, 16)}`;
1875
+ this.#operationStore = (async () => {
1876
+ const result = await TypedKV.open(
1877
+ this.nats,
1878
+ bucket,
1879
+ DurableOperationRecordSchema,
1880
+ {
1881
+ history: 5,
1882
+ ttl: 0,
1883
+ },
1884
+ );
1885
+ const value = result.take();
1886
+ if (isErr(value)) {
1887
+ throw value.error;
1888
+ }
1889
+ return value;
1890
+ })();
1891
+ }
1892
+ return this.#operationStore;
1893
+ }
1894
+
1895
+ async loadOperationRecord(
1896
+ operationId: string,
1897
+ ): Promise<DurableOperationRecord | null> {
1898
+ const store = await this.operationStoreHandle();
1899
+ const entry = await store.get(operationId);
1900
+ const value = entry.take();
1901
+ if (isErr(value)) {
1902
+ return null;
1903
+ }
1904
+ return value.value as DurableOperationRecord;
1905
+ }
1906
+
1907
+ async saveOperationRecord(runtime: RuntimeOperationRecord): Promise<void> {
1908
+ const store = await this.operationStoreHandle();
1909
+ const record: DurableOperationRecord = {
1910
+ ownerSessionKey: runtime.ownerSessionKey,
1911
+ sequence: runtime.sequence,
1912
+ signalSequence: runtime.signalSequence,
1913
+ signals: runtime.signals,
1914
+ snapshot: runtime.snapshot,
1915
+ };
1916
+ await store.put(runtime.id, record);
1917
+ }
1918
+
1919
+ /**
1920
+ * Makes an authenticated request to a Trellis RPC method.
1921
+ *
1922
+ * @template M The specific RPC method being called.
1923
+ * @param method The name of the RPC method to call.
1924
+ * @param input The input data for the method, conforming to its schema.
1925
+ * @param opts Optional request-specific options.
1926
+ * @returns An `AsyncResult` containing either the method's output or an error.
1927
+ * @returns A `Result` object after awaiting:
1928
+ * ok: A validated response for method M
1929
+ * err: declared RPC errors | RemoteError | ValidationError | UnexpectedError
1930
+ */
1931
+ request<const M extends RequestMethodOf<TRequests>>(
1932
+ method: M,
1933
+ input: RequestInputOf<TRequests, M>,
1934
+ opts?: RequestOpts,
1935
+ ): AsyncResult<RequestOutputOf<TRequests, M>, BaseError>;
1936
+ request(
1937
+ method: string,
1938
+ input: unknown,
1939
+ opts?: RequestOpts,
1940
+ ): AsyncResult<unknown, BaseError> {
1941
+ const rpcApi = this.api["rpc"] as Record<string, unknown>;
1942
+ const ctx = rpcApi[method] as {
1943
+ subject: string;
1944
+ input: unknown;
1945
+ output: unknown;
1946
+ callerCapabilities: readonly string[];
1947
+ errors?: readonly string[];
1948
+ declaredErrorTypes?: readonly string[];
1949
+ runtimeErrors?: readonly RuntimeRpcErrorDesc[];
1950
+ } | undefined;
1951
+ if (!ctx) {
1952
+ return AsyncResult.from(Promise.resolve(err(
1953
+ new UnexpectedError({
1954
+ cause: this.#unknownApiError("RPC method", method.toString()),
1955
+ context: { method: method.toString() },
1956
+ }),
1957
+ )));
1958
+ }
1959
+
1960
+ return this.#requestBuiltRpcUnknown(method, input, ctx, opts);
1961
+ }
1962
+
1963
+ #requestBuiltRpcUnknown(
1964
+ method: string,
1965
+ input: unknown,
1966
+ ctx: {
1967
+ subject: string;
1968
+ input: unknown;
1969
+ output: unknown;
1970
+ callerCapabilities: readonly string[];
1971
+ errors?: readonly string[];
1972
+ declaredErrorTypes?: readonly string[];
1973
+ runtimeErrors?: readonly RuntimeRpcErrorDesc[];
1974
+ },
1975
+ opts?: RequestOpts,
1976
+ ): AsyncResult<unknown, BaseError> {
1977
+ return this.#requestBuiltRpc(method, input, ctx, opts);
1978
+ }
1979
+
1980
+ #requestBuiltRpc<TOutput>(
1981
+ method: string,
1982
+ input: unknown,
1983
+ ctx: {
1984
+ subject: string;
1985
+ input: unknown;
1986
+ output: unknown;
1987
+ callerCapabilities: readonly string[];
1988
+ errors?: readonly string[];
1989
+ declaredErrorTypes?: readonly string[];
1990
+ runtimeErrors?: readonly RuntimeRpcErrorDesc[];
1991
+ },
1992
+ opts?: RequestOpts,
1993
+ ): AsyncResult<TOutput, BaseError> {
1994
+ return AsyncResult.from((async () => {
1995
+ this.#log.trace(
1996
+ { method: String(method) },
1997
+ `Calling ${method.toString()}.`,
1998
+ );
1999
+
2000
+ const msg = encodeRuntimeSchema(ctx.input, input).take();
2001
+ if (isErr(msg)) {
2002
+ return msg;
2003
+ }
2004
+
2005
+ const subject = this.template(ctx.subject, input).take();
2006
+ if (isErr(subject)) {
2007
+ return subject;
2008
+ }
2009
+
2010
+ const span = startClientSpan(method, subject);
2011
+ const attempt = async (): Promise<Result<TOutput, BaseError>> => {
2012
+ const authHeaders = await this.#createProof(subject, msg);
2013
+
2014
+ const headers = natsHeaders();
2015
+ headers.set("session-key", this.auth.sessionKey);
2016
+ headers.set("proof", authHeaders.proof);
2017
+ headers.set("iat", String(authHeaders.iat));
2018
+ headers.set("request-id", authHeaders.requestId);
2019
+ injectTraceContext(createNatsHeaderCarrier(headers), span);
2020
+
2021
+ const msgResult = await this.#requestMessageWithRetry({
2022
+ method,
2023
+ subject,
2024
+ payload: msg,
2025
+ headers,
2026
+ timeout: opts?.timeout ?? this.timeout,
2027
+ callerCapabilities: ctx.callerCapabilities,
2028
+ });
2029
+ const response = msgResult.take();
2030
+ if (isErr(response)) {
2031
+ return response;
2032
+ }
2033
+
2034
+ if (response.headers?.get("status") === "error") {
2035
+ const json = safeJson(response).take();
2036
+ if (isErr(json)) {
2037
+ return err(requestFailedTransportError({
2038
+ code: "trellis.request.invalid_response",
2039
+ message: "Trellis returned an invalid response.",
2040
+ hint:
2041
+ "Retry the request. If it keeps happening, check the Trellis capability handling this request.",
2042
+ method,
2043
+ subject,
2044
+ cause: json.error.cause,
2045
+ }));
2046
+ }
2047
+
2048
+ const errorData = parse(TrellisErrorDataSchema, json).take();
2049
+ if (isErr(errorData)) {
2050
+ return err(requestFailedTransportError({
2051
+ code: "trellis.request.invalid_response",
2052
+ message: "Trellis returned an invalid response.",
2053
+ hint:
2054
+ "Retry the request. If it keeps happening, check the Trellis capability handling this request.",
2055
+ method,
2056
+ subject,
2057
+ cause: errorData.error,
2058
+ }));
2059
+ }
2060
+
2061
+ const declaredErrorTypes = Array.isArray(ctx.declaredErrorTypes)
2062
+ ? ctx.declaredErrorTypes.filter((value): value is string =>
2063
+ typeof value === "string"
2064
+ )
2065
+ : ctx.errors;
2066
+ const runtimeErrors = Array.isArray(ctx.runtimeErrors)
2067
+ ? ctx.runtimeErrors.filter(isRuntimeRpcErrorDesc)
2068
+ : undefined;
2069
+ const reconstructed = reconstructDeclaredRpcError(
2070
+ declaredErrorTypes,
2071
+ runtimeErrors,
2072
+ errorData,
2073
+ json,
2074
+ );
2075
+ if (reconstructed) {
2076
+ await this.#handleBrowserAuthRequired(reconstructed);
2077
+ return err(reconstructed);
2078
+ }
2079
+
2080
+ const remoteError = new RemoteError({ error: errorData });
2081
+ await this.#handleBrowserAuthRequired(remoteError);
2082
+ return err(remoteError);
2083
+ }
2084
+
2085
+ const json = safeJson(response).take();
2086
+ if (isErr(json)) {
2087
+ return err(requestFailedTransportError({
2088
+ code: "trellis.request.invalid_response",
2089
+ message: "Trellis returned an invalid response.",
2090
+ hint:
2091
+ "Retry the request. If it keeps happening, check the Trellis capability handling this request.",
2092
+ method,
2093
+ subject,
2094
+ cause: json.error.cause,
2095
+ }));
2096
+ }
2097
+
2098
+ const outputResult = parseRuntimeSchema(ctx.output, json).take();
2099
+ if (isErr(outputResult)) {
2100
+ return err(outputResult.error);
2101
+ }
2102
+
2103
+ return ok(outputResult as TOutput);
2104
+ };
2105
+
2106
+ return await withSpanAsync(span, async () => {
2107
+ try {
2108
+ const result = await attempt();
2109
+ const value = result.take();
2110
+ if (isErr(value)) {
2111
+ span.setStatus({
2112
+ code: SpanStatusCode.ERROR,
2113
+ message: value.error.message,
2114
+ });
2115
+ } else {
2116
+ span.setStatus({ code: SpanStatusCode.OK });
2117
+ }
2118
+ return result;
2119
+ } catch (cause) {
2120
+ const unexpected = cause instanceof TransportError
2121
+ ? cause
2122
+ : new UnexpectedError({ cause });
2123
+ span.setStatus({
2124
+ code: SpanStatusCode.ERROR,
2125
+ message: unexpected.message,
2126
+ });
2127
+ span.recordException(unexpected);
2128
+ return err(unexpected);
2129
+ } finally {
2130
+ span.end();
2131
+ }
2132
+ });
2133
+ })());
2134
+ }
2135
+
2136
+ async #handleBrowserAuthRequired(error: unknown): Promise<void> {
2137
+ if (
2138
+ !this.#onSessionNotFound || !isBrowserAuthRequiredError(error)
2139
+ ) {
2140
+ return;
2141
+ }
2142
+
2143
+ await this.#onSessionNotFound();
2144
+ }
2145
+
2146
+ async #authenticateFeedRequest(args: {
2147
+ feed: string;
2148
+ subject: string;
2149
+ msg: Msg;
2150
+ payloadHash: Uint8Array;
2151
+ requiredCapabilities: readonly string[];
2152
+ }): Promise<Result<SessionCaller, BaseError>> {
2153
+ const sessionKey = args.msg.headers?.get("session-key");
2154
+ const proof = args.msg.headers?.get("proof");
2155
+ const iatHeader = args.msg.headers?.get("iat");
2156
+ const requestId = args.msg.headers?.get("request-id");
2157
+ if (!sessionKey) {
2158
+ return err(new AuthError({ reason: "missing_session_key" }));
2159
+ }
2160
+ if (!proof) return err(new AuthError({ reason: "missing_proof" }));
2161
+ const iat = Number(iatHeader);
2162
+ if (!Number.isSafeInteger(iat) || !requestId) {
2163
+ return err(new AuthError({ reason: "invalid_signature" }));
2164
+ }
2165
+
2166
+ const proofInput = buildProofInput(
2167
+ sessionKey,
2168
+ args.subject,
2169
+ args.payloadHash,
2170
+ iat,
2171
+ requestId,
2172
+ );
2173
+ const digest = await sha256(proofInput);
2174
+ const verifyResult = await AsyncResult.try(async () => {
2175
+ const publicKeyRaw = base64urlDecode(sessionKey);
2176
+ const pub = await crypto.subtle.importKey(
2177
+ "raw",
2178
+ toArrayBuffer(publicKeyRaw),
2179
+ { name: "Ed25519" },
2180
+ true,
2181
+ ["verify"],
2182
+ );
2183
+ return crypto.subtle.verify(
2184
+ { name: "Ed25519" },
2185
+ pub,
2186
+ toArrayBuffer(base64urlDecode(proof)),
2187
+ toArrayBuffer(digest),
2188
+ );
2189
+ });
2190
+ if (!verifyResult.isOk() || verifyResult.take() !== true) {
2191
+ return err(
2192
+ new AuthError({ reason: "invalid_signature", context: { sessionKey } }),
2193
+ );
2194
+ }
2195
+
2196
+ const auth = await this.requestAuthValidate({
2197
+ sessionKey,
2198
+ proof,
2199
+ subject: args.subject,
2200
+ payloadHash: base64urlEncode(args.payloadHash),
2201
+ iat,
2202
+ requestId,
2203
+ capabilities: [...args.requiredCapabilities],
2204
+ }).take();
2205
+ if (isErr(auth)) return err(auth.error);
2206
+
2207
+ if (!auth.allowed) {
2208
+ return err(
2209
+ new AuthError({
2210
+ reason: "insufficient_permissions",
2211
+ context: {
2212
+ feed: args.feed,
2213
+ requiredCapabilities: args.requiredCapabilities,
2214
+ userCapabilities: auth.caller.capabilities,
2215
+ },
2216
+ }),
2217
+ );
2218
+ }
2219
+
2220
+ if (
2221
+ typeof args.msg.reply !== "string" ||
2222
+ !args.msg.reply.startsWith(`${auth.inboxPrefix}.`)
2223
+ ) {
2224
+ return err(
2225
+ new AuthError({
2226
+ reason: "reply_subject_mismatch",
2227
+ context: { expected: auth.inboxPrefix, actual: args.msg.reply },
2228
+ }),
2229
+ );
2230
+ }
2231
+
2232
+ return ok(auth.caller);
2233
+ }
2234
+
2235
+ feed<F extends FeedsOf<TA>>(
2236
+ feed: F,
2237
+ ):
2238
+ & FeedInputBuilder<FeedInputOf<TA, F>, FeedEventOf<TA, F>>
2239
+ & FeedRegistration<FeedInputOf<TA, F>, FeedEventOf<TA, F>> {
2240
+ const descriptor = this.api.feeds?.[feed] as
2241
+ | FeedDescriptorOf<TA, F>
2242
+ | undefined;
2243
+ if (!descriptor) {
2244
+ throw this.#unknownApiError("feed", feed.toString());
2245
+ }
2246
+
2247
+ return {
2248
+ input: (input: FeedInputOf<TA, F>) => ({
2249
+ subscribe: (opts?: FeedSubscribeOpts) =>
2250
+ this.#subscribeFeed(
2251
+ feed.toString(),
2252
+ descriptor,
2253
+ input,
2254
+ opts,
2255
+ ) as AsyncResult<
2256
+ FeedSubscription<FeedEventOf<TA, F>>,
2257
+ BaseError
2258
+ >,
2259
+ }),
2260
+ handle: (
2261
+ handler: (
2262
+ context: FeedHandlerContext<FeedInputOf<TA, F>, FeedEventOf<TA, F>>,
2263
+ ) => unknown | Promise<unknown>,
2264
+ ) => this.#handleFeed(feed.toString(), descriptor, handler),
2265
+ };
2266
+ }
2267
+
2268
+ #subscribeFeed<TInput, TEvent>(
2269
+ feed: string,
2270
+ descriptor: FeedDesc,
2271
+ input: TInput,
2272
+ opts?: FeedSubscribeOpts,
2273
+ ): AsyncResult<FeedSubscription<TEvent>, BaseError> {
2274
+ return AsyncResult.from((async () => {
2275
+ const payload = encodeRuntimeSchema(descriptor.input, input).take();
2276
+ if (isErr(payload)) return payload;
2277
+
2278
+ const subject = this.template(
2279
+ descriptor.subject,
2280
+ input as Record<string, unknown>,
2281
+ ).take();
2282
+ if (isErr(subject)) return subject;
2283
+
2284
+ const authHeaders = await this.#createProof(subject, payload);
2285
+ const headers = natsHeaders();
2286
+ headers.set("session-key", this.auth.sessionKey);
2287
+ headers.set("proof", authHeaders.proof);
2288
+ headers.set("iat", String(authHeaders.iat));
2289
+ headers.set("request-id", authHeaders.requestId);
2290
+ injectTraceContext(createNatsHeaderCarrier(headers));
2291
+
2292
+ const inbox = createInbox(`_INBOX.${this.auth.sessionKey.slice(0, 16)}`);
2293
+ const sub = this.nats.subscribe(inbox);
2294
+ const iterator = sub[Symbol.asyncIterator]();
2295
+ const abort = () => sub.unsubscribe();
2296
+ opts?.signal?.addEventListener("abort", abort, { once: true });
2297
+
2298
+ try {
2299
+ this.nats.publish(subject, payload, { headers, reply: inbox });
2300
+ await this.nats.flush();
2301
+ } catch (cause) {
2302
+ opts?.signal?.removeEventListener("abort", abort);
2303
+ sub.unsubscribe();
2304
+ return err(createTransportError({
2305
+ code: "trellis.feed.subscribe_failed",
2306
+ message: "Trellis could not subscribe to the feed.",
2307
+ hint:
2308
+ "Retry the subscription. If it keeps failing, check Trellis runtime health.",
2309
+ cause,
2310
+ context: { feed, subject },
2311
+ }));
2312
+ }
2313
+
2314
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
2315
+ let abortHandler: (() => void) | undefined;
2316
+ const handshakePromises: Array<
2317
+ Promise<IteratorResult<Msg> | "aborted" | "timeout">
2318
+ > = [
2319
+ iterator.next(),
2320
+ new Promise<"timeout">((resolve) => {
2321
+ timeoutId = setTimeout(() => resolve("timeout"), this.timeout);
2322
+ }),
2323
+ ];
2324
+ const signal = opts?.signal;
2325
+ if (signal) {
2326
+ handshakePromises.push(
2327
+ new Promise<"aborted">((resolve) => {
2328
+ abortHandler = () => resolve("aborted");
2329
+ signal.addEventListener("abort", abortHandler, { once: true });
2330
+ }),
2331
+ );
2332
+ }
2333
+
2334
+ const firstFrame = await Promise.race(handshakePromises);
2335
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
2336
+ if (signal && abortHandler) {
2337
+ signal.removeEventListener("abort", abortHandler);
2338
+ }
2339
+ if (firstFrame === "timeout" || firstFrame === "aborted") {
2340
+ opts?.signal?.removeEventListener("abort", abort);
2341
+ sub.unsubscribe();
2342
+ return err(createTransportError({
2343
+ code: firstFrame === "timeout"
2344
+ ? "trellis.feed.subscribe_timeout"
2345
+ : "trellis.feed.subscribe_aborted",
2346
+ message: firstFrame === "timeout"
2347
+ ? "Trellis did not receive a feed acknowledgement."
2348
+ : "The feed subscription was aborted before Trellis acknowledged it.",
2349
+ hint: firstFrame === "timeout"
2350
+ ? "Check that the target service is running and has the current deployment digest, then retry."
2351
+ : "Retry the subscription if the feed is still needed.",
2352
+ context: { feed, subject },
2353
+ }));
2354
+ }
2355
+ if (firstFrame.done) {
2356
+ opts?.signal?.removeEventListener("abort", abort);
2357
+ sub.unsubscribe();
2358
+ return err(createTransportError({
2359
+ code: "trellis.feed.subscribe_closed",
2360
+ message: "Trellis closed the feed before acknowledging it.",
2361
+ hint:
2362
+ "Retry the subscription. If it keeps failing, check Trellis runtime health.",
2363
+ context: { feed, subject },
2364
+ }));
2365
+ }
2366
+ const firstMessage = firstFrame.value;
2367
+ if (firstMessage.headers?.get("status") === "error") {
2368
+ opts?.signal?.removeEventListener("abort", abort);
2369
+ sub.unsubscribe();
2370
+ return err(createTransportError({
2371
+ code: "trellis.feed.failed",
2372
+ message: "Trellis rejected the feed subscription.",
2373
+ hint:
2374
+ "Retry the subscription. If it keeps failing, check Trellis runtime health and permissions.",
2375
+ context: { feed, subject, frame: firstMessage.string() },
2376
+ }));
2377
+ }
2378
+ const firstEvent = firstMessage.headers?.get("feed-status") === "ready"
2379
+ ? undefined
2380
+ : firstMessage;
2381
+
2382
+ const eventSchema = descriptor.event;
2383
+ return ok((async function* () {
2384
+ try {
2385
+ const parseFeedFrame = (msg: Msg): TEvent => {
2386
+ if (msg.headers?.get("status") === "error") {
2387
+ throw createTransportError({
2388
+ code: "trellis.feed.failed",
2389
+ message: "Trellis stopped the feed.",
2390
+ hint:
2391
+ "Retry the subscription. If it keeps failing, check Trellis runtime health.",
2392
+ context: { feed, subject, frame: msg.string() },
2393
+ });
2394
+ }
2395
+ const json = safeJson(msg).take();
2396
+ if (isErr(json)) throw json.error;
2397
+ const parsed = parseRuntimeSchema(eventSchema, json).take();
2398
+ if (isErr(parsed)) throw parsed.error;
2399
+ return parsed as TEvent;
2400
+ };
2401
+ if (firstEvent) yield parseFeedFrame(firstEvent);
2402
+ while (true) {
2403
+ const next = await iterator.next();
2404
+ if (next.done) break;
2405
+ yield parseFeedFrame(next.value);
2406
+ }
2407
+ } finally {
2408
+ opts?.signal?.removeEventListener("abort", abort);
2409
+ sub.unsubscribe();
2410
+ }
2411
+ })());
2412
+ })());
2413
+ }
2414
+
2415
+ async #handleFeed<TInput, TEvent>(
2416
+ feed: string,
2417
+ descriptor: FeedDesc,
2418
+ handler: (
2419
+ context: FeedHandlerContext<TInput, TEvent>,
2420
+ ) => unknown | Promise<unknown>,
2421
+ ): Promise<void> {
2422
+ const subject = this.template(descriptor.subject, {}, true).take();
2423
+ if (isErr(subject)) throw subject.error;
2424
+ let sub: ReturnType<NatsConnection["subscribe"]>;
2425
+ try {
2426
+ sub = this.nats.subscribe(subject);
2427
+ await this.nats.flush();
2428
+ } catch (cause) {
2429
+ throw createTransportError({
2430
+ code: "trellis.feed.listen_failed",
2431
+ message: "Trellis could not listen for feed requests.",
2432
+ hint:
2433
+ "Check the service deployment digest and runtime permissions, then restart the service.",
2434
+ cause,
2435
+ context: { feed, subject },
2436
+ });
2437
+ }
2438
+ const task = AsyncResult.try(async () => {
2439
+ for await (const msg of sub) {
2440
+ void (async () => {
2441
+ try {
2442
+ const result = await this.#processFeedMessage(
2443
+ feed,
2444
+ descriptor,
2445
+ msg,
2446
+ handler,
2447
+ );
2448
+ const value = result.take();
2449
+ if (isErr(value)) {
2450
+ this.#respondWithError(msg, value.error);
2451
+ }
2452
+ } catch (cause) {
2453
+ const error = cause instanceof BaseError
2454
+ ? cause
2455
+ : new UnexpectedError({ cause });
2456
+ this.#respondWithError(msg, error);
2457
+ }
2458
+ })();
2459
+ }
2460
+ });
2461
+ this.#tasks.add(`feed:${feed}`, task);
2462
+ }
2463
+
2464
+ async #processFeedMessage<TInput, TEvent>(
2465
+ feed: string,
2466
+ descriptor: FeedDesc,
2467
+ msg: Msg,
2468
+ handler: (
2469
+ context: FeedHandlerContext<TInput, TEvent>,
2470
+ ) => unknown | Promise<unknown>,
2471
+ ): Promise<Result<void, BaseError>> {
2472
+ const json = safeJson(msg).take();
2473
+ if (isErr(json)) return json;
2474
+ const parsed = parseRuntimeSchema(descriptor.input, json).take();
2475
+ if (isErr(parsed)) return parsed;
2476
+
2477
+ const caller = await this.#authenticateFeedRequest({
2478
+ feed,
2479
+ subject: msg.subject,
2480
+ msg,
2481
+ payloadHash: await sha256(msg.data ?? new Uint8Array()),
2482
+ requiredCapabilities: descriptor.subscribeCapabilities,
2483
+ });
2484
+ const callerValue = caller.take();
2485
+ if (isErr(callerValue)) return callerValue;
2486
+ if (!msg.reply) {
2487
+ return err(
2488
+ new UnexpectedError({
2489
+ context: { feed, reason: "missing_reply" },
2490
+ }),
2491
+ );
2492
+ }
2493
+ const readyHeaders = natsHeaders();
2494
+ readyHeaders.set("feed-status", "ready");
2495
+ this.nats.publish(msg.reply, new Uint8Array(), { headers: readyHeaders });
2496
+ await this.nats.flush();
2497
+
2498
+ const controller = new AbortController();
2499
+ try {
2500
+ await handler({
2501
+ input: parsed as TInput,
2502
+ caller: callerValue,
2503
+ signal: controller.signal,
2504
+ emit: (event: TEvent) =>
2505
+ AsyncResult.from((async () => {
2506
+ const payload = encodeRuntimeSchema(descriptor.event, event).take();
2507
+ if (isErr(payload)) return payload;
2508
+ if (!msg.reply) {
2509
+ return err(
2510
+ new UnexpectedError({
2511
+ context: { feed, reason: "missing_reply" },
2512
+ }),
2513
+ );
2514
+ }
2515
+ this.nats.publish(msg.reply, payload);
2516
+ await this.nats.flush();
2517
+ return ok(undefined);
2518
+ })()),
2519
+ });
2520
+ return ok(undefined);
2521
+ } finally {
2522
+ controller.abort();
2523
+ }
2524
+ }
2525
+
2526
+ operation<O extends OperationsOf<TA>>(
2527
+ operation: O,
2528
+ ): OperationSurface<TA, TMode, O> {
2529
+ const descriptor = this.api["operations"]?.[operation];
2530
+ if (!descriptor) {
2531
+ throw this.#unknownApiError("operation", operation.toString());
2532
+ }
2533
+
2534
+ const transport: OperationTransport = {
2535
+ requestJson: (subject, body) =>
2536
+ this.#requestJson(subject, body as JsonValue),
2537
+ watchJson: (subject, body) => this.#watchJson(subject, body as JsonValue),
2538
+ putTransfer: (
2539
+ grant: SendTransferGrant,
2540
+ body: TransferBody,
2541
+ ): AsyncResult<FileInfo, TransferError> =>
2542
+ AsyncResult.from((async () => {
2543
+ const handle = createTransferHandle(
2544
+ this.nats,
2545
+ this.auth,
2546
+ this.timeout,
2547
+ grant,
2548
+ );
2549
+ if (!(handle instanceof Object) || !("send" in handle)) {
2550
+ return err(
2551
+ new TransferError({
2552
+ operation: "transfer",
2553
+ context: { reason: "invalid_operation_transfer_grant" },
2554
+ }),
2555
+ );
2556
+ }
2557
+ return await handle.send(body);
2558
+ })()),
2559
+ };
2560
+
2561
+ return new OperationInvoker(
2562
+ transport,
2563
+ descriptor as TA["operations"][O] & RuntimeOperationDesc,
2564
+ ) as OperationSurface<TA, TMode, O>;
2565
+ }
2566
+
2567
+ /**
2568
+ * Creates a helper for a short-lived Trellis transfer grant.
2569
+ */
2570
+ transfer(grant: SendTransferGrant): SendTransferHandle;
2571
+ transfer(grant: ReceiveTransferGrant): ReceiveTransferHandle;
2572
+ transfer(grant: TransferGrant): ReturnType<typeof createTransferHandle> {
2573
+ return createTransferHandle(this.nats, this.auth, this.timeout, grant);
2574
+ }
2575
+
2576
+ /*
2577
+ * Mount a handler to process requests made to a specific Trellis API
2578
+ */
2579
+ async mount(
2580
+ method: string,
2581
+ fn: (args: {
2582
+ input: unknown;
2583
+ context: RpcHandlerContext;
2584
+ trellis: HandlerTrellis<TA, TRequests>;
2585
+ }) => MaybePromise<Result<unknown, BaseError>>,
2586
+ ) {
2587
+ const methodName = method as MethodsOf<TA>;
2588
+ const ctx = this.api["rpc"][methodName];
2589
+ if (!ctx) {
2590
+ throw this.#unknownApiError("RPC method", method.toString());
2591
+ }
2592
+ const task = this.#handleRPC(
2593
+ methodName,
2594
+ fn as HandlerFn<TA, MethodsOf<TA>, TA, HandlerTrellis<TA, TRequests>>,
2595
+ );
2596
+ this.#tasks.add(methodName, task);
2597
+ }
2598
+
2599
+ #handleRPC(
2600
+ method: MethodsOf<TA>,
2601
+ fn: HandlerFn<TA, MethodsOf<TA>, TA, HandlerTrellis<TA, TRequests>>,
2602
+ subjectData: Record<string, unknown> = {},
2603
+ ): AsyncResult<void, ValidationError | UnexpectedError> {
2604
+ // Get API details
2605
+ const ctx = this.api["rpc"][method] as RpcDescriptorOf<TA, MethodsOf<TA>>;
2606
+
2607
+ const subject = this.template(ctx.subject, subjectData, true).take();
2608
+ if (isErr(subject)) {
2609
+ return AsyncResult.lift(subject);
2610
+ }
2611
+
2612
+ const handlerTrellis: HandlerTrellis<TA, TRequests> = this;
2613
+
2614
+ this.#log.info(
2615
+ { method: String(method) },
2616
+ `Mounting ${method.toString()} RPC handler`,
2617
+ );
2618
+ const sub = this.nats.subscribe(subject);
2619
+
2620
+ return AsyncResult.try(async () => {
2621
+ for await (const msg of sub) {
2622
+ const resultPromise = await this.#processRPCMessage(
2623
+ method,
2624
+ ctx,
2625
+ msg,
2626
+ fn,
2627
+ handlerTrellis,
2628
+ );
2629
+ const result = resultPromise.take();
2630
+
2631
+ if (isErr(result)) {
2632
+ this.#respondWithError(msg, result.error, { method: String(method) });
2633
+ continue;
2634
+ }
2635
+
2636
+ const sent = this.#respondWithPayload(msg, result, undefined, {
2637
+ method: String(method),
2638
+ responseKind: "success",
2639
+ });
2640
+ if (sent.isErr()) {
2641
+ const responseBytes = payloadByteLength(result);
2642
+ const message = causeMessage(sent.error.cause);
2643
+ this.#respondWithError(
2644
+ msg,
2645
+ new TransportError({
2646
+ code: "trellis.rpc.response_send_failed",
2647
+ message: message.includes("max_payload")
2648
+ ? "Trellis RPC response exceeded NATS max_payload."
2649
+ : "Trellis could not send the RPC response.",
2650
+ hint:
2651
+ "Reduce the requested page size or use a narrower RPC that does not include large detail payloads.",
2652
+ cause: sent.error.cause,
2653
+ context: {
2654
+ method: String(method),
2655
+ subject: msg.subject,
2656
+ responseBytes,
2657
+ causeMessage: message,
2658
+ },
2659
+ }),
2660
+ { method: String(method), responseBytes },
2661
+ );
2662
+ }
2663
+ }
2664
+ });
2665
+ }
2666
+
2667
+ async #processRPCMessage(
2668
+ method: MethodsOf<TA>,
2669
+ ctx: RpcDescriptorOf<TA, MethodsOf<TA>>,
2670
+ msg: Msg,
2671
+ fn: HandlerFn<TA, MethodsOf<TA>, TA, HandlerTrellis<TA, TRequests>>,
2672
+ handlerTrellis: HandlerTrellis<TA, TRequests>,
2673
+ ): Promise<Result<string, BaseError>> {
2674
+ this.#log.debug(
2675
+ { method: String(method), subject: msg.subject },
2676
+ "Processing RPC message",
2677
+ );
2678
+
2679
+ // Extract trace context from incoming NATS headers
2680
+ const parentContext = extractTraceContext(
2681
+ createNatsHeaderCarrier({
2682
+ get: (k: string) => msg.headers?.get(k) ?? undefined,
2683
+ set: () => {}, // Server doesn't need to set headers on incoming messages
2684
+ }),
2685
+ );
2686
+
2687
+ // Start a server span for this RPC handler
2688
+ const span = startServerSpan(method, msg.subject, parentContext);
2689
+ const incomingTraceId = traceIdFromTraceparent(
2690
+ msg.headers?.get("traceparent"),
2691
+ );
2692
+
2693
+ // Execute the handler within the span's context
2694
+ return withSpanAsync(span, async () => {
2695
+ const execute = async (): Promise<Result<string, BaseError>> => {
2696
+ const jsonData = safeJson(msg).take();
2697
+ if (isErr(jsonData)) {
2698
+ this.#log.warn(
2699
+ { method, error: jsonData.error.message },
2700
+ "Failed to parse JSON",
2701
+ );
2702
+ span.setStatus({
2703
+ code: SpanStatusCode.ERROR,
2704
+ message: "Failed to parse JSON",
2705
+ });
2706
+ return jsonData;
2707
+ }
2708
+
2709
+ const parsedInput = parseRuntimeSchema(ctx.input, jsonData).take();
2710
+ if (isErr(parsedInput)) {
2711
+ span.setStatus({
2712
+ code: SpanStatusCode.ERROR,
2713
+ message: "Input validation failed",
2714
+ });
2715
+ return parsedInput;
2716
+ }
2717
+
2718
+ let caller: SessionCaller;
2719
+ const callerSessionKey = msg.headers?.get("session-key") ?? "";
2720
+
2721
+ const authRequired = ctx.authRequired ?? true;
2722
+ if (!authRequired) {
2723
+ caller = {
2724
+ type: "service",
2725
+ id: "system",
2726
+ active: true,
2727
+ name: "System",
2728
+ capabilities: ["service"],
2729
+ };
2730
+ } else {
2731
+ const sessionKey = msg.headers?.get("session-key");
2732
+ const proof = msg.headers?.get("proof");
2733
+ const iatHeader = msg.headers?.get("iat");
2734
+ const requestId = msg.headers?.get("request-id");
2735
+ if (!sessionKey) {
2736
+ this.#log.warn({ method }, "Missing session-key header");
2737
+ span.setStatus({
2738
+ code: SpanStatusCode.ERROR,
2739
+ message: "Missing session-key",
2740
+ });
2741
+ return err(new AuthError({ reason: "missing_session_key" }));
2742
+ }
2743
+ if (!proof) {
2744
+ this.#log.warn({ method }, "Missing proof in request");
2745
+ span.setStatus({
2746
+ code: SpanStatusCode.ERROR,
2747
+ message: "Missing proof",
2748
+ });
2749
+ return err(new AuthError({ reason: "missing_proof" }));
2750
+ }
2751
+ const iat = Number(iatHeader);
2752
+ if (!Number.isSafeInteger(iat) || !requestId) {
2753
+ return err(new AuthError({ reason: "invalid_signature" }));
2754
+ }
2755
+
2756
+ // Verify proof signature locally using the raw request bytes we received.
2757
+ const payloadBytes = msg.data ?? new Uint8Array();
2758
+ const payloadHash = await sha256(payloadBytes);
2759
+ const proofInput = buildProofInput(
2760
+ sessionKey,
2761
+ msg.subject,
2762
+ payloadHash,
2763
+ iat,
2764
+ requestId,
2765
+ );
2766
+ const digest = await sha256(proofInput);
2767
+
2768
+ const verifyResult = await AsyncResult.try(async () => {
2769
+ const publicKeyRaw = base64urlDecode(sessionKey);
2770
+ const pub = await crypto.subtle.importKey(
2771
+ "raw",
2772
+ toArrayBuffer(publicKeyRaw),
2773
+ { name: "Ed25519" },
2774
+ true,
2775
+ ["verify"],
2776
+ );
2777
+ return crypto.subtle.verify(
2778
+ { name: "Ed25519" },
2779
+ pub,
2780
+ toArrayBuffer(base64urlDecode(proof)),
2781
+ toArrayBuffer(digest),
2782
+ );
2783
+ });
2784
+ const signatureOk = verifyResult.isOk() &&
2785
+ verifyResult.take() === true;
2786
+
2787
+ if (!signatureOk) {
2788
+ span.setStatus({
2789
+ code: SpanStatusCode.ERROR,
2790
+ message: "Invalid signature",
2791
+ });
2792
+ return err(
2793
+ new AuthError({
2794
+ reason: "invalid_signature",
2795
+ context: { sessionKey },
2796
+ }),
2797
+ );
2798
+ }
2799
+
2800
+ let auth:
2801
+ | AuthRequestsValidateResponse
2802
+ | AuthError
2803
+ | RemoteError
2804
+ | TransportError
2805
+ | ValidationError
2806
+ | UnexpectedError
2807
+ | undefined;
2808
+ for (
2809
+ let attempt = 0;
2810
+ attempt < DEFAULT_AUTH_VALIDATE_SESSION_RETRY_ATTEMPTS;
2811
+ attempt++
2812
+ ) {
2813
+ const authValue = await this.requestAuthValidate({
2814
+ sessionKey,
2815
+ proof,
2816
+ subject: msg.subject,
2817
+ payloadHash: base64urlEncode(payloadHash),
2818
+ iat,
2819
+ requestId,
2820
+ capabilities: [...ctx.callerCapabilities],
2821
+ }).take();
2822
+ if (!isErr(authValue)) {
2823
+ auth = authValue;
2824
+ break;
2825
+ }
2826
+
2827
+ const authError = authValue.error;
2828
+
2829
+ if (
2830
+ !isTransientAuthValidateSessionError(authError) ||
2831
+ attempt === DEFAULT_AUTH_VALIDATE_SESSION_RETRY_ATTEMPTS - 1
2832
+ ) {
2833
+ auth = authError;
2834
+ break;
2835
+ }
2836
+
2837
+ await sleep(
2838
+ DEFAULT_AUTH_VALIDATE_SESSION_RETRY_MS * (attempt + 1),
2839
+ );
2840
+ }
2841
+
2842
+ if (!auth) {
2843
+ return err(
2844
+ new UnexpectedError({
2845
+ context: { reason: "missing_auth_validate_result" },
2846
+ }),
2847
+ );
2848
+ }
2849
+
2850
+ if (auth instanceof Error) {
2851
+ this.#log.warn(
2852
+ {
2853
+ method,
2854
+ error: auth.message,
2855
+ errorType: auth.name,
2856
+ remoteError: auth instanceof RemoteError
2857
+ ? auth.toSerializable()
2858
+ : undefined,
2859
+ },
2860
+ "Auth.Requests.Validate failed",
2861
+ );
2862
+ span.setStatus({
2863
+ code: SpanStatusCode.ERROR,
2864
+ message: "Auth.Requests.Validate failed",
2865
+ });
2866
+ if (auth instanceof BaseError) {
2867
+ return err(auth);
2868
+ }
2869
+ return err(new UnexpectedError({ cause: auth }));
2870
+ }
2871
+
2872
+ if (!auth.allowed) {
2873
+ span.setStatus({
2874
+ code: SpanStatusCode.ERROR,
2875
+ message: "Insufficient permissions",
2876
+ });
2877
+ return err(
2878
+ new AuthError({
2879
+ reason: "insufficient_permissions",
2880
+ context: {
2881
+ requiredCapabilities: ctx.callerCapabilities,
2882
+ userCapabilities: auth.caller.capabilities,
2883
+ },
2884
+ }),
2885
+ );
2886
+ }
2887
+
2888
+ if (
2889
+ typeof msg.reply !== "string" ||
2890
+ !msg.reply.startsWith(`${auth.inboxPrefix}.`)
2891
+ ) {
2892
+ span.setStatus({
2893
+ code: SpanStatusCode.ERROR,
2894
+ message: "Reply subject mismatch",
2895
+ });
2896
+ return err(
2897
+ new AuthError({
2898
+ reason: "reply_subject_mismatch",
2899
+ context: { expected: auth.inboxPrefix, actual: msg.reply },
2900
+ }),
2901
+ );
2902
+ }
2903
+
2904
+ caller = auth.caller;
2905
+ }
2906
+
2907
+ span.setAttribute("auth.caller.type", caller.type);
2908
+ if (caller.type === "user") {
2909
+ span.setAttribute("user.id", caller.userId);
2910
+ span.setAttribute("user.identity.provider", caller.identity.provider);
2911
+ span.setAttribute("user.identity.subject", caller.identity.subject);
2912
+ }
2913
+ if (caller.type === "service") {
2914
+ const { id } = caller;
2915
+ span.setAttribute("service.id", id);
2916
+ }
2917
+ if (caller.type === "device") {
2918
+ span.setAttribute("device.id", caller.deviceId);
2919
+ span.setAttribute("device.deployment_id", caller.deploymentId);
2920
+ }
2921
+
2922
+ const invokeHandler = fn as (
2923
+ args: {
2924
+ input: unknown;
2925
+ context: RpcHandlerContext;
2926
+ trellis: HandlerTrellis<TA, TRequests>;
2927
+ },
2928
+ ) => MaybeAsync<unknown, BaseError>;
2929
+ const handlerResultWrapped = await AsyncResult.try(async () =>
2930
+ await Promise.resolve(
2931
+ invokeHandler({
2932
+ input: parsedInput,
2933
+ context: {
2934
+ caller,
2935
+ sessionKey: callerSessionKey,
2936
+ },
2937
+ trellis: handlerTrellis,
2938
+ }),
2939
+ )
2940
+ );
2941
+
2942
+ if (handlerResultWrapped.isErr()) {
2943
+ const error = handlerResultWrapped.error.withContext({ method });
2944
+ this.#log.error(
2945
+ {
2946
+ method,
2947
+ error: error.message,
2948
+ cause: error.cause instanceof Error
2949
+ ? { message: error.cause.message, stack: error.cause.stack }
2950
+ : error.cause,
2951
+ },
2952
+ "Handler threw unexpectedly.",
2953
+ );
2954
+ span.setStatus({
2955
+ code: SpanStatusCode.ERROR,
2956
+ message: error.message,
2957
+ });
2958
+ span.recordException(error);
2959
+ return err(error);
2960
+ }
2961
+
2962
+ const handlerResult = handlerResultWrapped.take() as {
2963
+ take: () => unknown;
2964
+ };
2965
+ const handlerOutcome = handlerResult.take();
2966
+ if (isErr(handlerOutcome)) {
2967
+ const handlerError = handlerOutcome.error;
2968
+
2969
+ const error = handlerError instanceof BaseError &&
2970
+ !(handlerError instanceof RemoteError)
2971
+ ? handlerError
2972
+ : new UnexpectedError({ cause: handlerError });
2973
+
2974
+ this.#log.error(
2975
+ {
2976
+ method,
2977
+ error: error.message,
2978
+ errorType: error.name,
2979
+ cause: error.cause instanceof Error
2980
+ ? { message: error.cause.message, stack: error.cause.stack }
2981
+ : error.cause,
2982
+ },
2983
+ "Handler returned error.",
2984
+ );
2985
+ span.setStatus({
2986
+ code: SpanStatusCode.ERROR,
2987
+ message: error.message,
2988
+ });
2989
+ return err(error);
2990
+ }
2991
+
2992
+ const encoded = encodeSchema(ctx.output, handlerOutcome).take();
2993
+ if (isErr(encoded)) {
2994
+ span.setStatus({
2995
+ code: SpanStatusCode.ERROR,
2996
+ message: "Output encoding failed",
2997
+ });
2998
+ return encoded;
2999
+ }
3000
+
3001
+ span.setStatus({ code: SpanStatusCode.OK });
3002
+ return ok(encoded);
3003
+ };
3004
+
3005
+ const result = await execute();
3006
+ if (isErr(result)) {
3007
+ result.error.withTraceId(activeTraceId(span) ?? incomingTraceId);
3008
+ }
3009
+ span.end();
3010
+ return result;
3011
+ });
3012
+ }
3013
+
3014
+ #respondWithPayload(
3015
+ msg: Msg,
3016
+ payload: string,
3017
+ options: { headers?: MsgHdrs } | undefined,
3018
+ context: {
3019
+ method?: string;
3020
+ responseKind: "success" | "error";
3021
+ },
3022
+ ): Result<void, UnexpectedError> {
3023
+ const responseBytes = payloadByteLength(payload);
3024
+ try {
3025
+ msg.respond(payload, options);
3026
+ return ok(undefined);
3027
+ } catch (cause) {
3028
+ const error = new UnexpectedError({
3029
+ cause,
3030
+ context: {
3031
+ method: context.method,
3032
+ responseKind: context.responseKind,
3033
+ subject: msg.subject,
3034
+ reply: msg.reply,
3035
+ responseBytes,
3036
+ causeMessage: causeMessage(cause),
3037
+ },
3038
+ });
3039
+ this.#log.error(
3040
+ {
3041
+ method: context.method,
3042
+ responseKind: context.responseKind,
3043
+ subject: msg.subject,
3044
+ reply: msg.reply,
3045
+ responseBytes,
3046
+ cause: causeLogData(cause),
3047
+ },
3048
+ "Failed to send RPC response",
3049
+ );
3050
+ return err(error);
3051
+ }
3052
+ }
3053
+
3054
+ #respondWithError(
3055
+ msg: Msg,
3056
+ error: Error | BaseError,
3057
+ context: { method?: string; responseBytes?: number } = {},
3058
+ ): void {
3059
+ const trellisError = error instanceof BaseError &&
3060
+ !(error instanceof RemoteError)
3061
+ ? error
3062
+ : new UnexpectedError({ cause: error });
3063
+
3064
+ this.#log.error(
3065
+ {
3066
+ method: context.method,
3067
+ subject: msg.subject,
3068
+ responseBytes: context.responseBytes,
3069
+ error: trellisError.toSerializable(),
3070
+ },
3071
+ "RPC error",
3072
+ );
3073
+
3074
+ const errorData = trellisError.toSerializable();
3075
+ const hdrs = natsHeaders();
3076
+ hdrs.set("status", "error");
3077
+
3078
+ const serialized = Result.try(() => JSON.stringify(errorData));
3079
+ if (serialized.isErr()) {
3080
+ this.#log.error(
3081
+ { error: serialized.error },
3082
+ "Failed to serialize error response",
3083
+ );
3084
+ this.#respondWithPayload(
3085
+ msg,
3086
+ '{"type":"UnexpectedError","message":"Failed to serialize error"}',
3087
+ { headers: hdrs },
3088
+ { method: context.method, responseKind: "error" },
3089
+ );
3090
+ return;
3091
+ }
3092
+ this.#respondWithPayload(
3093
+ msg,
3094
+ serialized.take() as string,
3095
+ { headers: hdrs },
3096
+ { method: context.method, responseKind: "error" },
3097
+ );
3098
+ }
3099
+
3100
+ respondWithError(msg: Msg, error: Error | BaseError): void {
3101
+ this.#respondWithError(msg, error);
3102
+ }
3103
+
3104
+ publish(
3105
+ event: string,
3106
+ data: Record<string, unknown>,
3107
+ ): AsyncResult<void, ValidationError | UnexpectedError> {
3108
+ return AsyncResult.from((async () => {
3109
+ try {
3110
+ const eventName = event as EventsOf<TA>;
3111
+ const ctx = this.api["events"][eventName] as EventDescriptorOf<
3112
+ TA,
3113
+ typeof eventName
3114
+ >;
3115
+ if (!ctx) {
3116
+ return err(
3117
+ new UnexpectedError({
3118
+ cause: this.#unknownApiError("event", event.toString()),
3119
+ context: { event: event.toString() },
3120
+ }),
3121
+ );
3122
+ }
3123
+
3124
+ const subject = this.template(ctx.subject, data).take();
3125
+ if (isErr(subject)) {
3126
+ logger.error({ err: subject.error }, "Failed to template event.");
3127
+ return subject;
3128
+ }
3129
+
3130
+ const header = {
3131
+ id: ulid(),
3132
+ time: new Date().toISOString(),
3133
+ };
3134
+ const payload: Record<string, unknown> = {
3135
+ ...data,
3136
+ header,
3137
+ };
3138
+ const msg = encodeSchema(ctx.event, payload).take();
3139
+ if (isErr(msg)) {
3140
+ logger.error({ err: msg.error }, "Failed to encode event.");
3141
+ return err(new UnexpectedError({ cause: msg.error }));
3142
+ }
3143
+
3144
+ const headers = natsHeaders();
3145
+ headers.set("Nats-Msg-Id", header.id);
3146
+ injectTraceContext(createNatsHeaderCarrier(headers));
3147
+
3148
+ logger.trace({ subject }, `Publishing ${event.toString()} event.`);
3149
+ await this.js.publish(subject, msg, { headers });
3150
+ return ok(undefined);
3151
+ } catch (cause) {
3152
+ return err(
3153
+ new UnexpectedError({ cause, context: { event: event.toString() } }),
3154
+ );
3155
+ }
3156
+ })());
3157
+ }
3158
+
3159
+ event<E extends EventsOf<TA>>(
3160
+ event: E,
3161
+ subjectData: Record<string, unknown>,
3162
+ fn: EventCallback<EventOf<TA, E>>,
3163
+ opts?: EventOpts,
3164
+ ): AsyncResult<void, ValidationError | UnexpectedError> {
3165
+ return AsyncResult.from((async () => {
3166
+ try {
3167
+ const eventName = event as EventsOf<TA>;
3168
+ const ctx = this.api["events"][eventName] as EventDescriptorOf<
3169
+ TA,
3170
+ typeof eventName
3171
+ >;
3172
+ if (!ctx) {
3173
+ return err(
3174
+ new UnexpectedError({
3175
+ cause: this.#unknownApiError("event", event.toString()),
3176
+ context: { event: event.toString() },
3177
+ }),
3178
+ );
3179
+ }
3180
+ const subject = this.template(ctx.subject, subjectData, true).take();
3181
+ if (isErr(subject)) return subject;
3182
+
3183
+ if (opts?.mode === "ephemeral") {
3184
+ return this.#startEphemeralEvent(
3185
+ eventName,
3186
+ ctx,
3187
+ subject,
3188
+ fn,
3189
+ opts.signal,
3190
+ );
3191
+ }
3192
+
3193
+ const jsm = await jetstreamManager(this.nats);
3194
+
3195
+ const consumerName = opts?.durableName ??
3196
+ `${this.name}-${String(event).replaceAll(".", "_")}`;
3197
+ const addResult = await AsyncResult.try(() =>
3198
+ jsm.consumers.add(this.stream, {
3199
+ durable_name: consumerName,
3200
+ ack_policy: "explicit",
3201
+ deliver_policy: opts?.replay === "new" ? "new" : "all",
3202
+ filter_subjects: [subject],
3203
+ })
3204
+ );
3205
+
3206
+ const consumerInfoResult = addResult.isOk()
3207
+ ? addResult
3208
+ : await AsyncResult.try(() =>
3209
+ jsm.consumers.info(this.stream, consumerName)
3210
+ );
3211
+
3212
+ const info = consumerInfoResult.take();
3213
+ if (isErr(info)) return info;
3214
+
3215
+ const consumer = this.js.consumers.getConsumerFromInfo(info);
3216
+ const messages = await consumer.consume();
3217
+ if (opts?.signal) {
3218
+ if (opts.signal.aborted) {
3219
+ messages.stop();
3220
+ } else {
3221
+ opts.signal.addEventListener("abort", () => messages.stop(), {
3222
+ once: true,
3223
+ });
3224
+ }
3225
+ }
3226
+
3227
+ this.#tasks.add(
3228
+ `event:${eventName}:${ulid()}`,
3229
+ this.#handleDurableEvent(eventName, ctx, messages, fn),
3230
+ );
3231
+ return ok(undefined);
3232
+ } catch (cause) {
3233
+ return err(
3234
+ new UnexpectedError({ cause, context: { event: event.toString() } }),
3235
+ );
3236
+ }
3237
+ })());
3238
+ }
3239
+
3240
+ #startEphemeralEvent(
3241
+ event: EventsOf<TA>,
3242
+ ctx: EventDescriptorOf<TA, EventsOf<TA>>,
3243
+ subject: string,
3244
+ fn: EventCallback<EventOf<TA, EventsOf<TA>>>,
3245
+ signal?: AbortSignal,
3246
+ ): Result<void, ValidationError | UnexpectedError> {
3247
+ const sub = this.nats.subscribe(subject);
3248
+ if (signal) {
3249
+ if (signal.aborted) {
3250
+ sub.unsubscribe();
3251
+ return ok(undefined);
3252
+ }
3253
+ signal.addEventListener("abort", () => sub.unsubscribe(), { once: true });
3254
+ }
3255
+
3256
+ const task = AsyncResult.try(async () => {
3257
+ for await (const msg of sub) {
3258
+ const parsedEvent = this.#parseEventMessage(event, ctx, msg);
3259
+ const m = parsedEvent.take();
3260
+ if (isErr(m)) {
3261
+ this.#log.error({ error: m.error }, "Event validation failed");
3262
+ continue;
3263
+ }
3264
+
3265
+ const handlerResult = await AsyncResult.lift(
3266
+ fn(m as EventOf<TA, EventsOf<TA>>),
3267
+ );
3268
+ if (handlerResult.isErr()) {
3269
+ this.#log.error(
3270
+ {
3271
+ error: handlerResult.error.toSerializable(),
3272
+ event,
3273
+ subject: msg.subject,
3274
+ },
3275
+ "Event handler failed",
3276
+ );
3277
+ }
3278
+ }
3279
+ });
3280
+
3281
+ this.#tasks.add(`event:${event}:${ulid()}`, task);
3282
+ return ok(undefined);
3283
+ }
3284
+
3285
+ #handleDurableEvent(
3286
+ event: EventsOf<TA>,
3287
+ ctx: EventDescriptorOf<TA, EventsOf<TA>>,
3288
+ messages: ConsumerMessages,
3289
+ fn: EventCallback<EventOf<TA, EventsOf<TA>>>,
3290
+ ): AsyncResult<void, ValidationError | UnexpectedError> {
3291
+ return AsyncResult.try(async () => {
3292
+ for await (const msg of messages) {
3293
+ const parsedEvent = this.#parseEventMessage(event, ctx, msg);
3294
+ const m = parsedEvent.take();
3295
+ if (isErr(m)) {
3296
+ this.#log.error({ error: m.error }, "Event validation failed");
3297
+ msg.term();
3298
+ continue;
3299
+ }
3300
+
3301
+ const handlerResult = await AsyncResult.lift(
3302
+ fn(m as EventOf<TA, EventsOf<TA>>),
3303
+ );
3304
+ if (handlerResult.isErr()) {
3305
+ this.#log.error(
3306
+ {
3307
+ error: handlerResult.error.toSerializable(),
3308
+ event,
3309
+ subject: msg.subject,
3310
+ },
3311
+ "Event handler failed",
3312
+ );
3313
+ msg.nak();
3314
+ continue;
3315
+ }
3316
+
3317
+ msg.ack();
3318
+ }
3319
+ });
3320
+ }
3321
+
3322
+ #parseEventMessage(
3323
+ event: EventsOf<TA>,
3324
+ ctx: EventDescriptorOf<TA, EventsOf<TA>>,
3325
+ msg: Pick<Msg, "json" | "subject">,
3326
+ ): Result<unknown, ValidationError | UnexpectedError> {
3327
+ const jsonData = Result.try<JsonValue>(() => msg.json());
3328
+ const json = jsonData.take();
3329
+ if (isErr(json)) {
3330
+ this.#log.error(
3331
+ { error: json.error, event, subject: msg.subject },
3332
+ "Event parse failed",
3333
+ );
3334
+ return json;
3335
+ }
3336
+
3337
+ return parseRuntimeSchema(ctx.event, json);
3338
+ }
3339
+
3340
+ wait(): AsyncResult<void, BaseError> {
3341
+ return this.#tasks.wait();
3342
+ }
3343
+
3344
+ // FIXME: If are validating things twice in most cases...
3345
+ template(
3346
+ subject: string,
3347
+ data: unknown,
3348
+ allowWildcards = false,
3349
+ ): Result<string, ValidationError> {
3350
+ // Find all template placeholders and check if values exist
3351
+ const placeholders = subject.match(/\{([^}]+)\}/g) || [];
3352
+ for (const placeholder of placeholders) {
3353
+ const key = placeholder.slice(1, -1); // Remove { and }
3354
+ const value = Pointer.Get(data, key);
3355
+
3356
+ if ((value === undefined || value === null) && !allowWildcards) {
3357
+ return err(
3358
+ new ValidationError({
3359
+ errors: [
3360
+ {
3361
+ path: key,
3362
+ message: "Missing required data for subject template",
3363
+ },
3364
+ ],
3365
+ context: { key },
3366
+ }),
3367
+ );
3368
+ }
3369
+ }
3370
+
3371
+ const result = subject.replace(/\{([^}]+)\}/g, (_, key) => {
3372
+ const value = Pointer.Get(data, key);
3373
+ if (allowWildcards && value === "*") {
3374
+ return "*";
3375
+ }
3376
+ if (allowWildcards && (value === undefined || value === null)) {
3377
+ return "*";
3378
+ }
3379
+ return this.#escapeSubjectToken(`${value}`);
3380
+ });
3381
+
3382
+ return ok(result);
3383
+ }
3384
+
3385
+ #escapeSubjectToken(token: string): string {
3386
+ const out = token.replace(
3387
+ NATS_SUBJECT_TOKEN_FORBIDDEN,
3388
+ (ch) => `~${ch.codePointAt(0)!.toString(16).toUpperCase()}~`,
3389
+ );
3390
+
3391
+ // Protect stapRet with $ due to NATS internal use of it
3392
+ if (out.length === 0 || out.startsWith("$")) {
3393
+ return `_${out}`;
3394
+ }
3395
+
3396
+ return out;
3397
+ }
3398
+
3399
+ #currentIat(): number {
3400
+ return this.auth.currentIat?.() ?? Math.floor(Date.now() / 1000);
3401
+ }
3402
+
3403
+ async #createProof(
3404
+ subject: string,
3405
+ payload: string,
3406
+ ): Promise<{ proof: string; iat: number; requestId: string }> {
3407
+ const payloadBytes = new TextEncoder().encode(payload);
3408
+ const payloadHash = await sha256(payloadBytes);
3409
+ const iat = this.#currentIat();
3410
+ const requestId = ulid();
3411
+ const input = buildProofInput(
3412
+ this.auth.sessionKey,
3413
+ subject,
3414
+ payloadHash,
3415
+ iat,
3416
+ requestId,
3417
+ );
3418
+ const digest = await sha256(input);
3419
+ const sigBytes = await this.auth.sign(digest);
3420
+ return { proof: base64urlEncode(sigBytes), iat, requestId };
3421
+ }
3422
+
3423
+ async #requestMessageWithRetry(args: {
3424
+ method?: string;
3425
+ subject: string;
3426
+ payload: string;
3427
+ headers: MsgHdrs;
3428
+ timeout: number;
3429
+ callerCapabilities?: readonly string[];
3430
+ }): Promise<Result<Msg, TransportError>> {
3431
+ for (let retry = 0; retry <= this.#noResponderMaxRetries; retry++) {
3432
+ const result = await AsyncResult.try(() =>
3433
+ this.nats.request(args.subject, args.payload, {
3434
+ headers: args.headers,
3435
+ timeout: args.timeout,
3436
+ })
3437
+ );
3438
+
3439
+ if (result.isOk()) {
3440
+ return ok(result.take() as Msg);
3441
+ }
3442
+
3443
+ const cause = result.error.cause;
3444
+ const message = cause instanceof Error ? cause.message : String(cause);
3445
+ const isNoResponders = message.includes("no responders");
3446
+
3447
+ if (isNoResponders && retry < this.#noResponderMaxRetries) {
3448
+ this.#log.debug(
3449
+ { method: args.method, subject: args.subject, retry },
3450
+ "No responders, retrying...",
3451
+ );
3452
+ await new Promise((resolve) =>
3453
+ setTimeout(resolve, this.#noResponderRetryMs * (retry + 1))
3454
+ );
3455
+ continue;
3456
+ }
3457
+
3458
+ this.#log.warn(
3459
+ { method: args.method, subject: args.subject, error: message },
3460
+ "NATS request failed",
3461
+ );
3462
+ return err(classifyRequestTransportFailure({
3463
+ method: args.method,
3464
+ subject: args.subject,
3465
+ callerCapabilities: args.callerCapabilities,
3466
+ cause,
3467
+ }));
3468
+ }
3469
+
3470
+ return err(
3471
+ requestFailedTransportError({
3472
+ code: "trellis.request.retry_exhausted",
3473
+ message: "Trellis could not complete the request after retrying.",
3474
+ hint:
3475
+ "Retry the request. If it keeps failing, check that the target service is available.",
3476
+ method: args.method,
3477
+ subject: args.subject,
3478
+ context: { retries: this.#noResponderMaxRetries + 1 },
3479
+ }),
3480
+ );
3481
+ }
3482
+
3483
+ #requestJson(
3484
+ subject: string,
3485
+ body: JsonValue,
3486
+ ): AsyncResult<JsonValue, TransportError | UnexpectedError> {
3487
+ return AsyncResult.from((async () => {
3488
+ const span = startClientSpan(subject, subject);
3489
+ return await withSpanAsync(span, async () => {
3490
+ try {
3491
+ const payload = JSON.stringify(body);
3492
+ const authHeaders = await this.#createProof(subject, payload);
3493
+
3494
+ const headers = natsHeaders();
3495
+ headers.set("session-key", this.auth.sessionKey);
3496
+ headers.set("proof", authHeaders.proof);
3497
+ headers.set("iat", String(authHeaders.iat));
3498
+ headers.set("request-id", authHeaders.requestId);
3499
+ injectTraceContext(createNatsHeaderCarrier(headers), span);
3500
+
3501
+ const response = (await this.#requestMessageWithRetry({
3502
+ subject,
3503
+ payload,
3504
+ headers,
3505
+ timeout: this.timeout,
3506
+ })).take();
3507
+ if (isErr(response)) {
3508
+ span.setStatus({
3509
+ code: SpanStatusCode.ERROR,
3510
+ message: response.error.message,
3511
+ });
3512
+ return response;
3513
+ }
3514
+
3515
+ const json = safeJson(response).take();
3516
+ if (isErr(json)) {
3517
+ const error = createTransportError({
3518
+ code: "trellis.request.invalid_response",
3519
+ message: "Trellis returned an invalid response.",
3520
+ hint:
3521
+ "Retry the request. If it keeps happening, reconnect to Trellis and try again.",
3522
+ cause: json.error.cause,
3523
+ context: { subject },
3524
+ });
3525
+ span.setStatus({
3526
+ code: SpanStatusCode.ERROR,
3527
+ message: error.message,
3528
+ });
3529
+ return err(error);
3530
+ }
3531
+
3532
+ span.setStatus({ code: SpanStatusCode.OK });
3533
+ return ok(json);
3534
+ } catch (cause) {
3535
+ const error = new UnexpectedError({ cause });
3536
+ span.setStatus({
3537
+ code: SpanStatusCode.ERROR,
3538
+ message: error.message,
3539
+ });
3540
+ span.recordException(error);
3541
+ return err(error);
3542
+ } finally {
3543
+ span.end();
3544
+ }
3545
+ });
3546
+ })());
3547
+ }
3548
+
3549
+ #watchJson(
3550
+ subject: string,
3551
+ body: JsonValue,
3552
+ ): AsyncResult<
3553
+ AsyncIterable<Result<JsonValue, TransportError | UnexpectedError>>,
3554
+ TransportError | UnexpectedError
3555
+ > {
3556
+ return AsyncResult.from((async () => {
3557
+ const payload = JSON.stringify(body);
3558
+ const authHeaders = await this.#createProof(subject, payload);
3559
+
3560
+ const headers = natsHeaders();
3561
+ headers.set("session-key", this.auth.sessionKey);
3562
+ headers.set("proof", authHeaders.proof);
3563
+ headers.set("iat", String(authHeaders.iat));
3564
+ headers.set("request-id", authHeaders.requestId);
3565
+
3566
+ const inbox = createInbox(`_INBOX.${this.auth.sessionKey.slice(0, 16)}`);
3567
+ const sub = this.nats.subscribe(inbox);
3568
+
3569
+ try {
3570
+ this.nats.publish(subject, payload, {
3571
+ headers,
3572
+ reply: inbox,
3573
+ });
3574
+ await this.nats.flush();
3575
+ } catch (cause) {
3576
+ sub.unsubscribe();
3577
+ return err(createTransportError({
3578
+ code: "trellis.watch.failed",
3579
+ message: "Trellis could not start the operation watch.",
3580
+ hint:
3581
+ "Retry watching the operation. If it keeps failing, reconnect to Trellis and try again.",
3582
+ cause,
3583
+ context: { subject },
3584
+ }));
3585
+ }
3586
+
3587
+ return ok((async function* () {
3588
+ try {
3589
+ for await (const msg of sub) {
3590
+ if (msg.headers?.get("status") === "error") {
3591
+ yield err(createTransportError({
3592
+ code: "trellis.watch.failed",
3593
+ message: "Trellis stopped the operation watch.",
3594
+ hint:
3595
+ "Retry watching the operation. If it keeps happening, reconnect to Trellis and try again.",
3596
+ context: { subject, frame: msg.string() },
3597
+ }));
3598
+ continue;
3599
+ }
3600
+
3601
+ const json = safeJson(msg).take();
3602
+ if (isErr(json)) {
3603
+ yield err(createTransportError({
3604
+ code: "trellis.watch.invalid_response",
3605
+ message: "Trellis returned an invalid watch update.",
3606
+ hint:
3607
+ "Retry watching the operation. If it keeps happening, reconnect to Trellis and try again.",
3608
+ cause: json.error.cause,
3609
+ context: { subject },
3610
+ }));
3611
+ continue;
3612
+ }
3613
+
3614
+ yield ok(json);
3615
+ }
3616
+ } finally {
3617
+ sub.unsubscribe();
3618
+ }
3619
+ })());
3620
+ })());
3621
+ }
3622
+
3623
+ protected requestAuthValidate(
3624
+ input: AuthRequestsValidateInput,
3625
+ ): AsyncResult<
3626
+ AuthRequestsValidateResponse,
3627
+ AuthError | RemoteError | TransportError | ValidationError | UnexpectedError
3628
+ > {
3629
+ const request = this.request.bind(this) as (
3630
+ method: string,
3631
+ input: unknown,
3632
+ opts?: RequestOpts,
3633
+ ) => AsyncResult<
3634
+ unknown,
3635
+ | AuthError
3636
+ | RemoteError
3637
+ | TransportError
3638
+ | ValidationError
3639
+ | UnexpectedError
3640
+ >;
3641
+ return request("Auth.Requests.Validate", input) as AsyncResult<
3642
+ AuthRequestsValidateResponse,
3643
+ | AuthError
3644
+ | RemoteError
3645
+ | TransportError
3646
+ | ValidationError
3647
+ | UnexpectedError
3648
+ >;
3649
+ }
3650
+ }