@elie-laloum/outpost 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +3 -1
  3. package/SECURITY.md +3 -1
  4. package/dist/adapters/agents/gemini-adapter.d.ts +3 -0
  5. package/dist/adapters/agents/gemini-adapter.js +15 -0
  6. package/dist/adapters/agents/gemini-adapter.js.map +1 -0
  7. package/dist/adapters/agents/gemini-diagnostics.d.ts +2 -0
  8. package/dist/adapters/agents/gemini-diagnostics.js +15 -0
  9. package/dist/adapters/agents/gemini-diagnostics.js.map +1 -0
  10. package/dist/adapters/agents/gemini-events.d.ts +2 -0
  11. package/dist/adapters/agents/gemini-events.js +50 -0
  12. package/dist/adapters/agents/gemini-events.js.map +1 -0
  13. package/dist/adapters/agents/gemini-request.d.ts +4 -0
  14. package/dist/adapters/agents/gemini-request.js +21 -0
  15. package/dist/adapters/agents/gemini-request.js.map +1 -0
  16. package/dist/adapters/agents/gemini.types.d.ts +6 -0
  17. package/dist/adapters/agents/gemini.types.js +2 -0
  18. package/dist/adapters/agents/gemini.types.js.map +1 -0
  19. package/dist/adapters/agents/protocol-fixtures.constants.d.ts +1 -1
  20. package/dist/adapters/agents/protocol-fixtures.constants.js +37 -0
  21. package/dist/adapters/agents/protocol-fixtures.constants.js.map +1 -1
  22. package/dist/application/agent-bootstrap.constants.d.ts +1 -0
  23. package/dist/application/agent-bootstrap.constants.js +1 -0
  24. package/dist/application/agent-bootstrap.constants.js.map +1 -1
  25. package/dist/application/agent-bootstrap.js +6 -2
  26. package/dist/application/agent-bootstrap.js.map +1 -1
  27. package/dist/application/agent-output.js +9 -2
  28. package/dist/application/agent-output.js.map +1 -1
  29. package/dist/application/artifact-tasks.d.ts +5 -0
  30. package/dist/application/artifact-tasks.js +31 -0
  31. package/dist/application/artifact-tasks.js.map +1 -0
  32. package/dist/application/artifact-tasks.types.d.ts +8 -0
  33. package/dist/application/artifact-tasks.types.js +2 -0
  34. package/dist/application/artifact-tasks.types.js.map +1 -0
  35. package/dist/application/dispatch-validation.js +2 -0
  36. package/dist/application/dispatch-validation.js.map +1 -1
  37. package/dist/application/doctor-agent.constants.d.ts +2 -0
  38. package/dist/application/doctor-agent.constants.js +2 -0
  39. package/dist/application/doctor-agent.constants.js.map +1 -1
  40. package/dist/application/doctor-agent.js +1 -1
  41. package/dist/application/doctor-agent.js.map +1 -1
  42. package/dist/application/doctor-protocol.js +2 -1
  43. package/dist/application/doctor-protocol.js.map +1 -1
  44. package/dist/application/doctor.constants.js +1 -1
  45. package/dist/application/doctor.constants.js.map +1 -1
  46. package/dist/application/doctor.types.d.ts +1 -1
  47. package/dist/application/doctor.types.js.map +1 -1
  48. package/dist/application/outpost.types.d.ts +2 -0
  49. package/dist/application/outpost.types.js.map +1 -1
  50. package/dist/application/queue-worker.d.ts +2 -0
  51. package/dist/application/queue-worker.js +75 -0
  52. package/dist/application/queue-worker.js.map +1 -0
  53. package/dist/application/queue-worker.types.d.ts +15 -0
  54. package/dist/application/queue-worker.types.js +2 -0
  55. package/dist/application/queue-worker.types.js.map +1 -0
  56. package/dist/application/queued-task.d.ts +3 -0
  57. package/dist/application/queued-task.js +56 -0
  58. package/dist/application/queued-task.js.map +1 -0
  59. package/dist/application/queued-task.types.d.ts +11 -0
  60. package/dist/application/queued-task.types.js +2 -0
  61. package/dist/application/queued-task.types.js.map +1 -0
  62. package/dist/application/recovery-inspection.js +5 -0
  63. package/dist/application/recovery-inspection.js.map +1 -1
  64. package/dist/application/recovery-inspection.types.d.ts +3 -0
  65. package/dist/application/recovery-inspection.types.js.map +1 -1
  66. package/dist/application/recovery-restore-snapshot.d.ts +2 -0
  67. package/dist/application/recovery-restore-snapshot.js +57 -0
  68. package/dist/application/recovery-restore-snapshot.js.map +1 -0
  69. package/dist/application/recovery-restore.d.ts +3 -0
  70. package/dist/application/recovery-restore.js +161 -0
  71. package/dist/application/recovery-restore.js.map +1 -0
  72. package/dist/application/recovery-restore.types.d.ts +28 -0
  73. package/dist/application/recovery-restore.types.js +2 -0
  74. package/dist/application/recovery-restore.types.js.map +1 -0
  75. package/dist/application/recovery-retention.js +5 -0
  76. package/dist/application/recovery-retention.js.map +1 -1
  77. package/dist/application/recovery-verification.d.ts +2 -1
  78. package/dist/application/recovery-verification.js +1 -1
  79. package/dist/application/recovery-verification.js.map +1 -1
  80. package/dist/application/remote-apply.js +6 -0
  81. package/dist/application/remote-apply.js.map +1 -1
  82. package/dist/application/remote-download.js +2 -2
  83. package/dist/application/remote-download.js.map +1 -1
  84. package/dist/application/remote-history.d.ts +6 -0
  85. package/dist/application/remote-history.js +114 -0
  86. package/dist/application/remote-history.js.map +1 -0
  87. package/dist/application/remote-upload.d.ts +2 -0
  88. package/dist/application/remote-upload.js +17 -0
  89. package/dist/application/remote-upload.js.map +1 -0
  90. package/dist/application/remote-validation.js +20 -28
  91. package/dist/application/remote-validation.js.map +1 -1
  92. package/dist/application/remote-workspace.js +4 -10
  93. package/dist/application/remote-workspace.js.map +1 -1
  94. package/dist/application/sandbox-activity.d.ts +3 -0
  95. package/dist/application/sandbox-activity.js +25 -0
  96. package/dist/application/sandbox-activity.js.map +1 -0
  97. package/dist/application/sandbox-provision.js +55 -10
  98. package/dist/application/sandbox-provision.js.map +1 -1
  99. package/dist/application/sandbox-session.types.d.ts +2 -0
  100. package/dist/application/sandbox-session.types.js.map +1 -1
  101. package/dist/application/sandbox.js +23 -11
  102. package/dist/application/sandbox.js.map +1 -1
  103. package/dist/application/speculation-host.d.ts +2 -0
  104. package/dist/application/speculation-host.js +30 -0
  105. package/dist/application/speculation-host.js.map +1 -0
  106. package/dist/application/speculation.constants.d.ts +4 -0
  107. package/dist/application/speculation.constants.js +2 -0
  108. package/dist/application/speculation.constants.js.map +1 -0
  109. package/dist/application/speculation.d.ts +2 -0
  110. package/dist/application/speculation.js +166 -0
  111. package/dist/application/speculation.js.map +1 -0
  112. package/dist/application/speculation.types.d.ts +56 -0
  113. package/dist/application/speculation.types.js +2 -0
  114. package/dist/application/speculation.types.js.map +1 -0
  115. package/dist/application/storage-reservation.d.ts +3 -0
  116. package/dist/application/storage-reservation.js +10 -0
  117. package/dist/application/storage-reservation.js.map +1 -0
  118. package/dist/application/storage-reservation.types.d.ts +4 -0
  119. package/dist/application/storage-reservation.types.js +2 -0
  120. package/dist/application/storage-reservation.types.js.map +1 -0
  121. package/dist/application/task-usage.d.ts +1 -1
  122. package/dist/application/task-usage.js.map +1 -1
  123. package/dist/application/tasks.js +1 -1
  124. package/dist/application/tasks.js.map +1 -1
  125. package/dist/application/tasks.types.d.ts +4 -3
  126. package/dist/application/tasks.types.js.map +1 -1
  127. package/dist/application/workspace-allocation.d.ts +3 -0
  128. package/dist/application/workspace-allocation.js +34 -0
  129. package/dist/application/workspace-allocation.js.map +1 -0
  130. package/dist/application/workspace.js +2 -2
  131. package/dist/application/workspace.js.map +1 -1
  132. package/dist/cli/doctor-command.js +1 -1
  133. package/dist/cli/doctor-command.js.map +1 -1
  134. package/dist/cli/main.constants.d.ts +11 -2
  135. package/dist/cli/main.constants.js +8 -4
  136. package/dist/cli/main.constants.js.map +1 -1
  137. package/dist/cli/recovery-command.js +28 -4
  138. package/dist/cli/recovery-command.js.map +1 -1
  139. package/dist/cli/recovery-restore-command.d.ts +2 -0
  140. package/dist/cli/recovery-restore-command.js +34 -0
  141. package/dist/cli/recovery-restore-command.js.map +1 -0
  142. package/dist/cli/scaffold-files.js +2 -2
  143. package/dist/cli/scaffold-files.js.map +1 -1
  144. package/dist/cli/scaffold-validation.js +1 -1
  145. package/dist/cli/scaffold-validation.js.map +1 -1
  146. package/dist/cli/scaffold.constants.d.ts +5 -0
  147. package/dist/cli/scaffold.constants.js +7 -2
  148. package/dist/cli/scaffold.constants.js.map +1 -1
  149. package/dist/cli/scaffold.types.d.ts +1 -1
  150. package/dist/cli/scaffold.types.js.map +1 -1
  151. package/dist/domain/agent.types.d.ts +2 -0
  152. package/dist/domain/agent.types.js.map +1 -1
  153. package/dist/domain/artifact-reference.d.ts +4 -0
  154. package/dist/domain/artifact-reference.js +90 -0
  155. package/dist/domain/artifact-reference.js.map +1 -0
  156. package/dist/domain/artifact.constants.d.ts +2 -0
  157. package/dist/domain/artifact.constants.js +3 -0
  158. package/dist/domain/artifact.constants.js.map +1 -0
  159. package/dist/domain/artifact.d.ts +7 -0
  160. package/dist/domain/artifact.js +104 -0
  161. package/dist/domain/artifact.js.map +1 -0
  162. package/dist/domain/artifact.types.d.ts +44 -0
  163. package/dist/domain/artifact.types.js +2 -0
  164. package/dist/domain/artifact.types.js.map +1 -0
  165. package/dist/domain/egress.constants.d.ts +1 -0
  166. package/dist/domain/egress.constants.js +7 -0
  167. package/dist/domain/egress.constants.js.map +1 -0
  168. package/dist/domain/egress.d.ts +2 -0
  169. package/dist/domain/egress.js +48 -0
  170. package/dist/domain/egress.js.map +1 -0
  171. package/dist/domain/egress.types.d.ts +8 -0
  172. package/dist/domain/egress.types.js +2 -0
  173. package/dist/domain/egress.types.js.map +1 -0
  174. package/dist/domain/sandbox.types.d.ts +1 -0
  175. package/dist/domain/sandbox.types.js.map +1 -1
  176. package/dist/domain/task-queue.constants.d.ts +8 -0
  177. package/dist/domain/task-queue.constants.js +9 -0
  178. package/dist/domain/task-queue.constants.js.map +1 -0
  179. package/dist/domain/task-queue.d.ts +11 -0
  180. package/dist/domain/task-queue.js +105 -0
  181. package/dist/domain/task-queue.js.map +1 -0
  182. package/dist/domain/task-queue.types.d.ts +41 -0
  183. package/dist/domain/task-queue.types.js +2 -0
  184. package/dist/domain/task-queue.types.js.map +1 -0
  185. package/dist/domain/workflow/budget.d.ts +2 -2
  186. package/dist/domain/workflow/budget.js +5 -3
  187. package/dist/domain/workflow/budget.js.map +1 -1
  188. package/dist/domain/workflow/checkpoint-validation.d.ts +3 -0
  189. package/dist/domain/workflow/checkpoint-validation.js +81 -0
  190. package/dist/domain/workflow/checkpoint-validation.js.map +1 -0
  191. package/dist/domain/workflow/checkpoint-value.d.ts +2 -0
  192. package/dist/domain/workflow/checkpoint-value.js +49 -0
  193. package/dist/domain/workflow/checkpoint-value.js.map +1 -0
  194. package/dist/domain/workflow/checkpoint.d.ts +3 -0
  195. package/dist/domain/workflow/checkpoint.js +70 -0
  196. package/dist/domain/workflow/checkpoint.js.map +1 -0
  197. package/dist/domain/workflow/checkpoint.types.d.ts +40 -0
  198. package/dist/domain/workflow/checkpoint.types.js +2 -0
  199. package/dist/domain/workflow/checkpoint.types.js.map +1 -0
  200. package/dist/domain/workflow/gate-validation.d.ts +4 -0
  201. package/dist/domain/workflow/gate-validation.js +71 -0
  202. package/dist/domain/workflow/gate-validation.js.map +1 -0
  203. package/dist/domain/workflow/gates.constants.d.ts +4 -0
  204. package/dist/domain/workflow/gates.constants.js +5 -0
  205. package/dist/domain/workflow/gates.constants.js.map +1 -0
  206. package/dist/domain/workflow/gates.d.ts +7 -0
  207. package/dist/domain/workflow/gates.js +85 -0
  208. package/dist/domain/workflow/gates.js.map +1 -0
  209. package/dist/domain/workflow/gates.types.d.ts +27 -0
  210. package/dist/domain/workflow/gates.types.js +2 -0
  211. package/dist/domain/workflow/gates.types.js.map +1 -0
  212. package/dist/domain/workflow/task-runner.js +14 -2
  213. package/dist/domain/workflow/task-runner.js.map +1 -1
  214. package/dist/domain/workflow/usage-receipt.constants.d.ts +2 -0
  215. package/dist/domain/workflow/usage-receipt.constants.js +3 -0
  216. package/dist/domain/workflow/usage-receipt.constants.js.map +1 -0
  217. package/dist/domain/workflow/usage-receipt.d.ts +2 -0
  218. package/dist/domain/workflow/usage-receipt.js +18 -0
  219. package/dist/domain/workflow/usage-receipt.js.map +1 -0
  220. package/dist/domain/workflow/validation.js +6 -0
  221. package/dist/domain/workflow/validation.js.map +1 -1
  222. package/dist/domain/workflow/workflow-scheduler.js +65 -32
  223. package/dist/domain/workflow/workflow-scheduler.js.map +1 -1
  224. package/dist/domain/workflow/workflow-state.d.ts +2 -1
  225. package/dist/domain/workflow/workflow-state.js +91 -16
  226. package/dist/domain/workflow/workflow-state.js.map +1 -1
  227. package/dist/domain/workflow.types.d.ts +13 -2
  228. package/dist/domain/workflow.types.js.map +1 -1
  229. package/dist/index.d.ts +32 -2
  230. package/dist/index.js +14 -1
  231. package/dist/index.js.map +1 -1
  232. package/dist/infrastructure/artifact-store.constants.d.ts +1 -0
  233. package/dist/infrastructure/artifact-store.constants.js +2 -0
  234. package/dist/infrastructure/artifact-store.constants.js.map +1 -0
  235. package/dist/infrastructure/artifact-store.d.ts +3 -0
  236. package/dist/infrastructure/artifact-store.js +100 -0
  237. package/dist/infrastructure/artifact-store.js.map +1 -0
  238. package/dist/infrastructure/artifact-store.types.d.ts +4 -0
  239. package/dist/infrastructure/artifact-store.types.js +2 -0
  240. package/dist/infrastructure/artifact-store.types.js.map +1 -0
  241. package/dist/infrastructure/file-manifest.d.ts +1 -1
  242. package/dist/infrastructure/file-manifest.js +3 -2
  243. package/dist/infrastructure/file-manifest.js.map +1 -1
  244. package/dist/infrastructure/git/verification-command.d.ts +1 -1
  245. package/dist/infrastructure/git/verification-command.js +3 -2
  246. package/dist/infrastructure/git/verification-command.js.map +1 -1
  247. package/dist/infrastructure/opentelemetry.js +1 -1
  248. package/dist/infrastructure/opentelemetry.js.map +1 -1
  249. package/dist/infrastructure/resource-activity-inspection.d.ts +2 -0
  250. package/dist/infrastructure/resource-activity-inspection.js +152 -0
  251. package/dist/infrastructure/resource-activity-inspection.js.map +1 -0
  252. package/dist/infrastructure/resource-activity.constants.d.ts +8 -0
  253. package/dist/infrastructure/resource-activity.constants.js +26 -0
  254. package/dist/infrastructure/resource-activity.constants.js.map +1 -0
  255. package/dist/infrastructure/resource-activity.d.ts +2 -0
  256. package/dist/infrastructure/resource-activity.js +166 -0
  257. package/dist/infrastructure/resource-activity.js.map +1 -0
  258. package/dist/infrastructure/resource-activity.types.d.ts +53 -0
  259. package/dist/infrastructure/resource-activity.types.js +2 -0
  260. package/dist/infrastructure/resource-activity.types.js.map +1 -0
  261. package/dist/infrastructure/storage-mutation-lock.constants.d.ts +5 -0
  262. package/dist/infrastructure/storage-mutation-lock.constants.js +6 -0
  263. package/dist/infrastructure/storage-mutation-lock.constants.js.map +1 -0
  264. package/dist/infrastructure/storage-mutation-lock.d.ts +1 -0
  265. package/dist/infrastructure/storage-mutation-lock.js +21 -0
  266. package/dist/infrastructure/storage-mutation-lock.js.map +1 -0
  267. package/dist/infrastructure/storage-reservations.constants.d.ts +4 -0
  268. package/dist/infrastructure/storage-reservations.constants.js +5 -0
  269. package/dist/infrastructure/storage-reservations.constants.js.map +1 -0
  270. package/dist/infrastructure/storage-reservations.d.ts +2 -0
  271. package/dist/infrastructure/storage-reservations.js +150 -0
  272. package/dist/infrastructure/storage-reservations.js.map +1 -0
  273. package/dist/infrastructure/storage-reservations.types.d.ts +21 -0
  274. package/dist/infrastructure/storage-reservations.types.js +2 -0
  275. package/dist/infrastructure/storage-reservations.types.js.map +1 -0
  276. package/dist/infrastructure/task-queue-http.constants.d.ts +2 -0
  277. package/dist/infrastructure/task-queue-http.constants.js +3 -0
  278. package/dist/infrastructure/task-queue-http.constants.js.map +1 -0
  279. package/dist/infrastructure/task-queue-http.d.ts +4 -0
  280. package/dist/infrastructure/task-queue-http.js +158 -0
  281. package/dist/infrastructure/task-queue-http.js.map +1 -0
  282. package/dist/infrastructure/task-queue-http.types.d.ts +16 -0
  283. package/dist/infrastructure/task-queue-http.types.js +2 -0
  284. package/dist/infrastructure/task-queue-http.types.js.map +1 -0
  285. package/dist/infrastructure/task-queue.d.ts +2 -0
  286. package/dist/infrastructure/task-queue.js +134 -0
  287. package/dist/infrastructure/task-queue.js.map +1 -0
  288. package/dist/infrastructure/transfer.js +5 -0
  289. package/dist/infrastructure/transfer.js.map +1 -1
  290. package/dist/infrastructure/workflow-checkpoint.constants.d.ts +1 -0
  291. package/dist/infrastructure/workflow-checkpoint.constants.js +2 -0
  292. package/dist/infrastructure/workflow-checkpoint.constants.js.map +1 -0
  293. package/dist/infrastructure/workflow-checkpoint.d.ts +3 -0
  294. package/dist/infrastructure/workflow-checkpoint.js +77 -0
  295. package/dist/infrastructure/workflow-checkpoint.js.map +1 -0
  296. package/dist/infrastructure/workflow-checkpoint.types.d.ts +4 -0
  297. package/dist/infrastructure/workflow-checkpoint.types.js +2 -0
  298. package/dist/infrastructure/workflow-checkpoint.types.js.map +1 -0
  299. package/dist/providers/agents.d.ts +2 -0
  300. package/dist/providers/agents.js +1 -0
  301. package/dist/providers/agents.js.map +1 -1
  302. package/dist/providers/container-isolation.d.ts +2 -0
  303. package/dist/providers/container-isolation.js +29 -0
  304. package/dist/providers/container-isolation.js.map +1 -0
  305. package/dist/providers/container-mounts.js +7 -2
  306. package/dist/providers/container-mounts.js.map +1 -1
  307. package/dist/providers/container-network.d.ts +2 -0
  308. package/dist/providers/container-network.js +14 -0
  309. package/dist/providers/container-network.js.map +1 -0
  310. package/dist/providers/container-plan.js +4 -2
  311. package/dist/providers/container-plan.js.map +1 -1
  312. package/dist/providers/container.constants.d.ts +1 -0
  313. package/dist/providers/container.constants.js +1 -0
  314. package/dist/providers/container.constants.js.map +1 -1
  315. package/dist/providers/container.js +19 -2
  316. package/dist/providers/container.js.map +1 -1
  317. package/dist/providers/container.types.d.ts +3 -0
  318. package/dist/providers/container.types.js.map +1 -1
  319. package/dist/providers/daytona-command.js +3 -2
  320. package/dist/providers/daytona-command.js.map +1 -1
  321. package/dist/providers/daytona-terminal.d.ts +3 -0
  322. package/dist/providers/daytona-terminal.js +128 -0
  323. package/dist/providers/daytona-terminal.js.map +1 -0
  324. package/dist/providers/daytona.js +2 -0
  325. package/dist/providers/daytona.js.map +1 -1
  326. package/dist/providers/docker.d.ts +1 -0
  327. package/dist/providers/docker.js.map +1 -1
  328. package/dist/providers/file-batches.d.ts +1 -1
  329. package/dist/providers/file-batches.js +9 -0
  330. package/dist/providers/file-batches.js.map +1 -1
  331. package/dist/providers/file-upload.constants.d.ts +1 -0
  332. package/dist/providers/file-upload.constants.js +68 -0
  333. package/dist/providers/file-upload.constants.js.map +1 -0
  334. package/dist/providers/file-upload.d.ts +2 -0
  335. package/dist/providers/file-upload.js +146 -0
  336. package/dist/providers/file-upload.js.map +1 -0
  337. package/dist/providers/firecracker-command.d.ts +5 -0
  338. package/dist/providers/firecracker-command.js +114 -0
  339. package/dist/providers/firecracker-command.js.map +1 -0
  340. package/dist/providers/firecracker-files.constants.d.ts +1 -0
  341. package/dist/providers/firecracker-files.constants.js +21 -0
  342. package/dist/providers/firecracker-files.constants.js.map +1 -0
  343. package/dist/providers/firecracker-files.d.ts +3 -0
  344. package/dist/providers/firecracker-files.js +110 -0
  345. package/dist/providers/firecracker-files.js.map +1 -0
  346. package/dist/providers/firecracker-machine.d.ts +3 -0
  347. package/dist/providers/firecracker-machine.js +155 -0
  348. package/dist/providers/firecracker-machine.js.map +1 -0
  349. package/dist/providers/firecracker.constants.d.ts +10 -0
  350. package/dist/providers/firecracker.constants.js +11 -0
  351. package/dist/providers/firecracker.constants.js.map +1 -0
  352. package/dist/providers/firecracker.d.ts +4 -0
  353. package/dist/providers/firecracker.js +103 -0
  354. package/dist/providers/firecracker.js.map +1 -0
  355. package/dist/providers/firecracker.types.d.ts +36 -0
  356. package/dist/providers/firecracker.types.js +2 -0
  357. package/dist/providers/firecracker.types.js.map +1 -0
  358. package/dist/providers/local.js +2 -1
  359. package/dist/providers/local.js.map +1 -1
  360. package/dist/providers/podman.d.ts +1 -0
  361. package/dist/providers/podman.js.map +1 -1
  362. package/dist/providers/terminal-bridge.d.ts +14 -0
  363. package/dist/providers/terminal-bridge.js +100 -0
  364. package/dist/providers/terminal-bridge.js.map +1 -0
  365. package/dist/providers/vercel-directory.d.ts +2 -0
  366. package/dist/providers/vercel-directory.js +7 -0
  367. package/dist/providers/vercel-directory.js.map +1 -0
  368. package/dist/providers/vercel-files.js +3 -2
  369. package/dist/providers/vercel-files.js.map +1 -1
  370. package/dist/providers/vercel-network.d.ts +2 -0
  371. package/dist/providers/vercel-network.js +18 -0
  372. package/dist/providers/vercel-network.js.map +1 -0
  373. package/dist/providers/vercel.d.ts +1 -0
  374. package/dist/providers/vercel.js +5 -1
  375. package/dist/providers/vercel.js.map +1 -1
  376. package/dist/providers/vercel.types.d.ts +2 -0
  377. package/dist/providers/vercel.types.js.map +1 -1
  378. package/dist/providers/versions.constants.d.ts +1 -0
  379. package/dist/providers/versions.constants.js +1 -0
  380. package/dist/providers/versions.constants.js.map +1 -1
  381. package/package.json +5 -1
@@ -0,0 +1,150 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, open, readdir, realpath, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { invariant, OutpostError } from "../domain/errors.js";
5
+ import { lockStorageMutation } from "./storage-mutation-lock.js";
6
+ import { localProcessIdentity, observeOwnership, } from "./git/process-identity.js";
7
+ import { readInspectionFile } from "./inspection-file.js";
8
+ import { storageInventory } from "./storage-inventory.js";
9
+ import { storageReservationDefaults as defaults } from "./storage-reservations.constants.js";
10
+ async function reservationDirectory(root) {
11
+ const paths = [
12
+ join(root, ".outpost"),
13
+ join(root, ".outpost", "locks"),
14
+ join(root, ".outpost", "locks", "storage-reservations"),
15
+ ];
16
+ for (const path of paths) {
17
+ await mkdir(path, { recursive: true, mode: 0o700 });
18
+ invariant((await realpath(path)) === path, "Storage reservation directory must not use symlinks");
19
+ }
20
+ return paths[2];
21
+ }
22
+ function validRecord(value) {
23
+ if (!value || typeof value !== "object")
24
+ return false;
25
+ return ("version" in value &&
26
+ value.version === 1 &&
27
+ "id" in value &&
28
+ typeof value.id === "string" &&
29
+ "pid" in value &&
30
+ typeof value.pid === "number" &&
31
+ Number.isSafeInteger(value.pid) &&
32
+ value.pid > 0 &&
33
+ value.pid <= 2_147_483_647 &&
34
+ "reserveBytes" in value &&
35
+ typeof value.reserveBytes === "number" &&
36
+ Number.isSafeInteger(value.reserveBytes) &&
37
+ value.reserveBytes >= 0 &&
38
+ "createdAt" in value &&
39
+ typeof value.createdAt === "string" &&
40
+ Number.isFinite(Date.parse(value.createdAt)));
41
+ }
42
+ async function readRecord(path) {
43
+ try {
44
+ const value = JSON.parse((await readInspectionFile(path, defaults.maxRecordBytes)).toString("utf8"));
45
+ if (validRecord(value))
46
+ return value;
47
+ }
48
+ catch (cause) {
49
+ throw new OutpostError("workspace", "Storage reservation record is unreadable; admission refused", { path }, cause);
50
+ }
51
+ throw new OutpostError("workspace", "Storage reservation record is invalid; admission refused", { path });
52
+ }
53
+ async function reservedBytes(directory) {
54
+ const names = await readdir(directory);
55
+ invariant(names.length <= defaults.maxRecords, "Too many storage reservation records");
56
+ let reserved = 0;
57
+ for (const name of names) {
58
+ const path = join(directory, name);
59
+ const record = await readRecord(path);
60
+ invariant(name === `${record.id}.json`, "Storage reservation identity mismatch");
61
+ const owner = await observeOwnership(record.pid, record.identity);
62
+ if (owner.status === "inactive") {
63
+ await rm(path);
64
+ continue;
65
+ }
66
+ reserved += record.reserveBytes;
67
+ invariant(Number.isSafeInteger(reserved), "Storage reservation total exceeds safe integer range");
68
+ }
69
+ return reserved;
70
+ }
71
+ async function releaseRecord(root, path, id) {
72
+ await reservationDirectory(root);
73
+ const unlock = await lockStorageMutation(root);
74
+ try {
75
+ const names = await readdir(join(root, ".outpost", "locks", "storage-reservations"));
76
+ if (!names.includes(`${id}.json`))
77
+ return;
78
+ const record = await readRecord(path);
79
+ invariant(record.id === id, "Storage reservation identity changed; refusing release");
80
+ await rm(path);
81
+ }
82
+ finally {
83
+ await unlock();
84
+ }
85
+ }
86
+ export async function reserveStorage(repository, options) {
87
+ invariant(Number.isSafeInteger(options.maxBytes) && options.maxBytes >= 0, "maxBytes must be a nonnegative integer");
88
+ invariant(Number.isSafeInteger(options.reserveBytes) && options.reserveBytes >= 0, "reserveBytes must be a nonnegative integer");
89
+ options.signal?.throwIfAborted();
90
+ const directory = await reservationDirectory(repository);
91
+ const unlock = await lockStorageMutation(repository, options.signal);
92
+ const id = randomUUID();
93
+ const path = join(directory, `${id}.json`);
94
+ try {
95
+ const reserved = await reservedBytes(directory);
96
+ const inventory = await storageInventory(join(repository, ".outpost"), options.maxEntries);
97
+ const record = {
98
+ version: 1,
99
+ id,
100
+ pid: process.pid,
101
+ identity: await localProcessIdentity(),
102
+ reserveBytes: options.reserveBytes,
103
+ createdAt: new Date().toISOString(),
104
+ };
105
+ const data = JSON.stringify(record);
106
+ if (!inventory.complete ||
107
+ inventory.usage.bytes +
108
+ reserved +
109
+ options.reserveBytes +
110
+ Buffer.byteLength(data) >
111
+ options.maxBytes)
112
+ throw new OutpostError("workspace", "Outpost storage reservation admission refused", {
113
+ usageBytes: inventory.usage.bytes,
114
+ reservedBytes: reserved,
115
+ reserveBytes: options.reserveBytes,
116
+ maxBytes: options.maxBytes,
117
+ complete: inventory.complete,
118
+ });
119
+ options.signal?.throwIfAborted();
120
+ const file = await open(path, "wx", 0o600);
121
+ try {
122
+ await file.writeFile(data);
123
+ await file.sync();
124
+ }
125
+ catch (error) {
126
+ await file.close();
127
+ await rm(path, { force: true });
128
+ throw error;
129
+ }
130
+ await file.close();
131
+ }
132
+ finally {
133
+ await unlock();
134
+ }
135
+ let released = false;
136
+ const release = async () => {
137
+ if (released)
138
+ return;
139
+ await releaseRecord(repository, path, id);
140
+ released = true;
141
+ };
142
+ return {
143
+ id,
144
+ repository,
145
+ reserveBytes: options.reserveBytes,
146
+ release,
147
+ [Symbol.asyncDispose]: release,
148
+ };
149
+ }
150
+ //# sourceMappingURL=storage-reservations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-reservations.js","sourceRoot":"","sources":["../../src/infrastructure/storage-reservations.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, open, readdir, realpath, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { invariant, OutpostError } from \"../domain/errors.ts\";\nimport { lockStorageMutation } from \"./storage-mutation-lock.ts\";\nimport {\n localProcessIdentity,\n observeOwnership,\n} from \"./git/process-identity.ts\";\nimport { readInspectionFile } from \"./inspection-file.ts\";\nimport { storageInventory } from \"./storage-inventory.ts\";\nimport { storageReservationDefaults as defaults } from \"./storage-reservations.constants.ts\";\nimport type {\n StorageReservation,\n StorageReservationOptions,\n StorageReservationRecord,\n} from \"./storage-reservations.types.ts\";\n\nasync function reservationDirectory(root: string): Promise<string> {\n const paths = [\n join(root, \".outpost\"),\n join(root, \".outpost\", \"locks\"),\n join(root, \".outpost\", \"locks\", \"storage-reservations\"),\n ];\n for (const path of paths) {\n await mkdir(path, { recursive: true, mode: 0o700 });\n invariant(\n (await realpath(path)) === path,\n \"Storage reservation directory must not use symlinks\",\n );\n }\n return paths[2]!;\n}\n\nfunction validRecord(value: unknown): value is StorageReservationRecord {\n if (!value || typeof value !== \"object\") return false;\n return (\n \"version\" in value &&\n value.version === 1 &&\n \"id\" in value &&\n typeof value.id === \"string\" &&\n \"pid\" in value &&\n typeof value.pid === \"number\" &&\n Number.isSafeInteger(value.pid) &&\n value.pid > 0 &&\n value.pid <= 2_147_483_647 &&\n \"reserveBytes\" in value &&\n typeof value.reserveBytes === \"number\" &&\n Number.isSafeInteger(value.reserveBytes) &&\n value.reserveBytes >= 0 &&\n \"createdAt\" in value &&\n typeof value.createdAt === \"string\" &&\n Number.isFinite(Date.parse(value.createdAt))\n );\n}\n\nasync function readRecord(path: string): Promise<StorageReservationRecord> {\n try {\n const value: unknown = JSON.parse(\n (await readInspectionFile(path, defaults.maxRecordBytes)).toString(\n \"utf8\",\n ),\n );\n if (validRecord(value)) return value;\n } catch (cause) {\n throw new OutpostError(\n \"workspace\",\n \"Storage reservation record is unreadable; admission refused\",\n { path },\n cause,\n );\n }\n throw new OutpostError(\n \"workspace\",\n \"Storage reservation record is invalid; admission refused\",\n { path },\n );\n}\n\nasync function reservedBytes(directory: string): Promise<number> {\n const names = await readdir(directory);\n invariant(\n names.length <= defaults.maxRecords,\n \"Too many storage reservation records\",\n );\n let reserved = 0;\n for (const name of names) {\n const path = join(directory, name);\n const record = await readRecord(path);\n invariant(\n name === `${record.id}.json`,\n \"Storage reservation identity mismatch\",\n );\n const owner = await observeOwnership(record.pid, record.identity);\n if (owner.status === \"inactive\") {\n await rm(path);\n continue;\n }\n reserved += record.reserveBytes;\n invariant(\n Number.isSafeInteger(reserved),\n \"Storage reservation total exceeds safe integer range\",\n );\n }\n return reserved;\n}\n\nasync function releaseRecord(\n root: string,\n path: string,\n id: string,\n): Promise<void> {\n await reservationDirectory(root);\n const unlock = await lockStorageMutation(root);\n try {\n const names = await readdir(\n join(root, \".outpost\", \"locks\", \"storage-reservations\"),\n );\n if (!names.includes(`${id}.json`)) return;\n const record = await readRecord(path);\n invariant(\n record.id === id,\n \"Storage reservation identity changed; refusing release\",\n );\n await rm(path);\n } finally {\n await unlock();\n }\n}\n\nexport async function reserveStorage(\n repository: string,\n options: StorageReservationOptions,\n): Promise<StorageReservation> {\n invariant(\n Number.isSafeInteger(options.maxBytes) && options.maxBytes >= 0,\n \"maxBytes must be a nonnegative integer\",\n );\n invariant(\n Number.isSafeInteger(options.reserveBytes) && options.reserveBytes >= 0,\n \"reserveBytes must be a nonnegative integer\",\n );\n options.signal?.throwIfAborted();\n const directory = await reservationDirectory(repository);\n const unlock = await lockStorageMutation(repository, options.signal);\n const id = randomUUID();\n const path = join(directory, `${id}.json`);\n try {\n const reserved = await reservedBytes(directory);\n const inventory = await storageInventory(\n join(repository, \".outpost\"),\n options.maxEntries,\n );\n const record: StorageReservationRecord = {\n version: 1,\n id,\n pid: process.pid,\n identity: await localProcessIdentity(),\n reserveBytes: options.reserveBytes,\n createdAt: new Date().toISOString(),\n };\n const data = JSON.stringify(record);\n if (\n !inventory.complete ||\n inventory.usage.bytes +\n reserved +\n options.reserveBytes +\n Buffer.byteLength(data) >\n options.maxBytes\n )\n throw new OutpostError(\n \"workspace\",\n \"Outpost storage reservation admission refused\",\n {\n usageBytes: inventory.usage.bytes,\n reservedBytes: reserved,\n reserveBytes: options.reserveBytes,\n maxBytes: options.maxBytes,\n complete: inventory.complete,\n },\n );\n options.signal?.throwIfAborted();\n const file = await open(path, \"wx\", 0o600);\n try {\n await file.writeFile(data);\n await file.sync();\n } catch (error) {\n await file.close();\n await rm(path, { force: true });\n throw error;\n }\n await file.close();\n } finally {\n await unlock();\n }\n let released = false;\n const release = async () => {\n if (released) return;\n await releaseRecord(repository, path, id);\n released = true;\n };\n return {\n id,\n repository,\n reserveBytes: options.reserveBytes,\n release,\n [Symbol.asyncDispose]: release,\n };\n}\n"]}
@@ -0,0 +1,21 @@
1
+ export interface StorageReservationOptions {
2
+ readonly maxBytes: number;
3
+ readonly reserveBytes: number;
4
+ readonly maxEntries?: number;
5
+ readonly signal?: AbortSignal;
6
+ }
7
+ export interface StorageReservation {
8
+ readonly id: string;
9
+ readonly repository: string;
10
+ readonly reserveBytes: number;
11
+ release(): Promise<void>;
12
+ [Symbol.asyncDispose](): Promise<void>;
13
+ }
14
+ export interface StorageReservationRecord {
15
+ readonly version: 1;
16
+ readonly id: string;
17
+ readonly pid: number;
18
+ readonly identity?: unknown;
19
+ readonly reserveBytes: number;
20
+ readonly createdAt: string;
21
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=storage-reservations.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-reservations.types.js","sourceRoot":"","sources":["../../src/infrastructure/storage-reservations.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface StorageReservationOptions {\n readonly maxBytes: number;\n readonly reserveBytes: number;\n readonly maxEntries?: number;\n readonly signal?: AbortSignal;\n}\n\nexport interface StorageReservation {\n readonly id: string;\n readonly repository: string;\n readonly reserveBytes: number;\n release(): Promise<void>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\nexport interface StorageReservationRecord {\n readonly version: 1;\n readonly id: string;\n readonly pid: number;\n readonly identity?: unknown;\n readonly reserveBytes: number;\n readonly createdAt: string;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const queueHttpTimeoutMs = 10000;
2
+ export declare const queueHttpMaxHeaderBytes = 8192;
@@ -0,0 +1,3 @@
1
+ export const queueHttpTimeoutMs = 10_000;
2
+ export const queueHttpMaxHeaderBytes = 8192;
3
+ //# sourceMappingURL=task-queue-http.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-queue-http.constants.js","sourceRoot":"","sources":["../../src/infrastructure/task-queue-http.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC","sourcesContent":["export const queueHttpTimeoutMs = 10_000;\nexport const queueHttpMaxHeaderBytes = 8192;\n"]}
@@ -0,0 +1,4 @@
1
+ import type { TaskQueue } from "../domain/task-queue.types.ts";
2
+ import type { QueueClientOptions, QueueServer, QueueServerOptions } from "./task-queue-http.types.ts";
3
+ export declare function serveTaskQueue(options: QueueServerOptions): Promise<QueueServer>;
4
+ export declare function httpTaskQueue(options: QueueClientOptions): TaskQueue;
@@ -0,0 +1,158 @@
1
+ import { queueHttpTimeoutMs, queueHttpMaxHeaderBytes, } from "./task-queue-http.constants.js";
2
+ import { createServer } from "node:http";
3
+ import { timingSafeEqual } from "node:crypto";
4
+ import { queueMaxBytes } from "../domain/task-queue.constants.js";
5
+ import { queueClaim, queueJob, queueLease, queueLeaseMs, queueNumber, queueObject, queueRequest, queueResult, queueString, } from "../domain/task-queue.js";
6
+ function tokenValue(token) {
7
+ if (typeof token !== "string" ||
8
+ token.length < 32 ||
9
+ token.length > 512 ||
10
+ /\s/.test(token))
11
+ throw new Error("Queue bearer token must contain 32 to 512 non-whitespace characters");
12
+ return `Bearer ${token}`;
13
+ }
14
+ async function message(stream) {
15
+ const chunks = [];
16
+ let size = 0;
17
+ for await (const chunk of stream) {
18
+ size += chunk.byteLength;
19
+ if (size > queueMaxBytes)
20
+ throw new Error("Queue message exceeds size limit");
21
+ chunks.push(chunk);
22
+ }
23
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
24
+ }
25
+ export async function serveTaskQueue(options) {
26
+ const expected = Buffer.from(tokenValue(options.token));
27
+ const queue = options.queue;
28
+ const server = createServer({
29
+ requestTimeout: queueHttpTimeoutMs,
30
+ headersTimeout: queueHttpTimeoutMs,
31
+ maxHeaderSize: queueHttpMaxHeaderBytes,
32
+ }, async (request, response) => {
33
+ const supplied = Buffer.from(request.headers.authorization ?? "");
34
+ if (supplied.length !== expected.length ||
35
+ !timingSafeEqual(supplied, expected)) {
36
+ response.writeHead(401).end();
37
+ request.resume();
38
+ return;
39
+ }
40
+ if (request.method !== "POST" || request.url !== "/queue") {
41
+ response.writeHead(404).end();
42
+ request.resume();
43
+ return;
44
+ }
45
+ try {
46
+ const data = queueObject(await message(request));
47
+ const operations = new Map([
48
+ ["enqueue", () => queue.enqueue(queueRequest(data.request))],
49
+ ["get", () => queue.get(queueString(data.id))],
50
+ ["claim", () => queue.claim(queueClaim(data.request))],
51
+ [
52
+ "renew",
53
+ () => queue.renew(queueLease(data.lease), queueLeaseMs(data.leaseMs)),
54
+ ],
55
+ [
56
+ "complete",
57
+ () => queue.complete(queueLease(data.lease), queueResult(data.result)),
58
+ ],
59
+ [
60
+ "cancel",
61
+ () => queue.cancel(queueString(data.id), queueNumber(data.fence)),
62
+ ],
63
+ ]);
64
+ const operation = operations.get(queueString(data.operation));
65
+ if (!operation)
66
+ throw new Error("Unknown queue operation");
67
+ const body = JSON.stringify((await operation()) ?? null);
68
+ if (Buffer.byteLength(body) > queueMaxBytes)
69
+ throw new Error("Queue response exceeds size limit");
70
+ response
71
+ .writeHead(200, {
72
+ "content-type": "application/json",
73
+ "cache-control": "no-store",
74
+ })
75
+ .end(body);
76
+ }
77
+ catch {
78
+ response.writeHead(400).end("Queue request rejected");
79
+ }
80
+ });
81
+ server.setTimeout(queueHttpTimeoutMs, (socket) => socket.destroy());
82
+ await new Promise((resolve, reject) => {
83
+ server.once("error", reject);
84
+ server.listen(options.port ?? 0, options.host ?? "127.0.0.1", resolve);
85
+ });
86
+ const address = server.address();
87
+ if (!address || typeof address === "string")
88
+ throw new Error("Queue server address unavailable");
89
+ const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
90
+ return {
91
+ url: `http://${host}:${address.port}`,
92
+ close: () => new Promise((resolve, reject) => {
93
+ server.close((error) => (error ? reject(error) : resolve()));
94
+ server.closeIdleConnections();
95
+ }),
96
+ };
97
+ }
98
+ export function httpTaskQueue(options) {
99
+ const authorization = tokenValue(options.token);
100
+ const url = new URL(options.url);
101
+ if (!["http:", "https:"].includes(url.protocol) ||
102
+ url.username ||
103
+ url.password ||
104
+ url.search ||
105
+ url.hash)
106
+ throw new Error("Invalid queue URL");
107
+ const endpoint = new URL("queue", `${url.href.replace(/\/$/, "")}/`);
108
+ const timeoutMs = options.timeoutMs ?? queueHttpTimeoutMs;
109
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0)
110
+ throw new Error("Invalid queue request timeout");
111
+ async function call(data) {
112
+ const body = JSON.stringify(data);
113
+ if (Buffer.byteLength(body) > queueMaxBytes)
114
+ throw new Error("Queue message exceeds size limit");
115
+ const response = await fetch(endpoint, {
116
+ method: "POST",
117
+ headers: { authorization, "content-type": "application/json" },
118
+ body,
119
+ signal: AbortSignal.timeout(timeoutMs),
120
+ redirect: "error",
121
+ });
122
+ if (!response.ok) {
123
+ await response.body?.cancel();
124
+ throw new Error(`Queue request rejected (${response.status})`);
125
+ }
126
+ if (!response.body)
127
+ throw new Error("Missing queue response");
128
+ const value = await message(response.body);
129
+ return value === null ? undefined : queueJob(value);
130
+ }
131
+ async function required(data) {
132
+ const job = await call(data);
133
+ if (!job)
134
+ throw new Error("Missing queue job");
135
+ return job;
136
+ }
137
+ return {
138
+ enqueue: (request) => required({ operation: "enqueue", request: queueRequest(request) }),
139
+ get: (id) => call({ operation: "get", id: queueString(id) }),
140
+ claim: (request) => call({ operation: "claim", request: queueClaim(request) }),
141
+ renew: (lease, leaseMs) => required({
142
+ operation: "renew",
143
+ lease: queueLease(lease),
144
+ leaseMs: queueLeaseMs(leaseMs),
145
+ }),
146
+ complete: (lease, result) => required({
147
+ operation: "complete",
148
+ lease: queueLease(lease),
149
+ result: queueResult(result),
150
+ }),
151
+ cancel: (id, fence) => required({
152
+ operation: "cancel",
153
+ id: queueString(id),
154
+ fence: queueNumber(fence),
155
+ }),
156
+ };
157
+ }
158
+ //# sourceMappingURL=task-queue-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-queue-http.js","sourceRoot":"","sources":["../../src/infrastructure/task-queue-http.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n queueHttpTimeoutMs,\n queueHttpMaxHeaderBytes,\n} from \"./task-queue-http.constants.ts\";\nimport { createServer } from \"node:http\";\nimport { timingSafeEqual } from \"node:crypto\";\nimport { queueMaxBytes } from \"../domain/task-queue.constants.ts\";\nimport {\n queueClaim,\n queueJob,\n queueLease,\n queueLeaseMs,\n queueNumber,\n queueObject,\n queueRequest,\n queueResult,\n queueString,\n} from \"../domain/task-queue.ts\";\nimport type { TaskQueue } from \"../domain/task-queue.types.ts\";\nimport type {\n QueueClientOptions,\n QueueServer,\n QueueServerOptions,\n} from \"./task-queue-http.types.ts\";\n\nfunction tokenValue(token: string): string {\n if (\n typeof token !== \"string\" ||\n token.length < 32 ||\n token.length > 512 ||\n /\\s/.test(token)\n )\n throw new Error(\n \"Queue bearer token must contain 32 to 512 non-whitespace characters\",\n );\n return `Bearer ${token}`;\n}\nasync function message(stream: AsyncIterable<Uint8Array>): Promise<unknown> {\n const chunks: Uint8Array[] = [];\n let size = 0;\n for await (const chunk of stream) {\n size += chunk.byteLength;\n if (size > queueMaxBytes)\n throw new Error(\"Queue message exceeds size limit\");\n chunks.push(chunk);\n }\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n}\nexport async function serveTaskQueue(\n options: QueueServerOptions,\n): Promise<QueueServer> {\n const expected = Buffer.from(tokenValue(options.token));\n const queue = options.queue;\n const server = createServer(\n {\n requestTimeout: queueHttpTimeoutMs,\n headersTimeout: queueHttpTimeoutMs,\n maxHeaderSize: queueHttpMaxHeaderBytes,\n },\n async (request, response) => {\n const supplied = Buffer.from(request.headers.authorization ?? \"\");\n if (\n supplied.length !== expected.length ||\n !timingSafeEqual(supplied, expected)\n ) {\n response.writeHead(401).end();\n request.resume();\n return;\n }\n if (request.method !== \"POST\" || request.url !== \"/queue\") {\n response.writeHead(404).end();\n request.resume();\n return;\n }\n try {\n const data = queueObject(await message(request));\n const operations = new Map<string, () => Promise<unknown>>([\n [\"enqueue\", () => queue.enqueue(queueRequest(data.request))],\n [\"get\", () => queue.get(queueString(data.id))],\n [\"claim\", () => queue.claim(queueClaim(data.request))],\n [\n \"renew\",\n () =>\n queue.renew(queueLease(data.lease), queueLeaseMs(data.leaseMs)),\n ],\n [\n \"complete\",\n () =>\n queue.complete(queueLease(data.lease), queueResult(data.result)),\n ],\n [\n \"cancel\",\n () => queue.cancel(queueString(data.id), queueNumber(data.fence)),\n ],\n ]);\n const operation = operations.get(queueString(data.operation));\n if (!operation) throw new Error(\"Unknown queue operation\");\n const body = JSON.stringify((await operation()) ?? null);\n if (Buffer.byteLength(body) > queueMaxBytes)\n throw new Error(\"Queue response exceeds size limit\");\n response\n .writeHead(200, {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n })\n .end(body);\n } catch {\n response.writeHead(400).end(\"Queue request rejected\");\n }\n },\n );\n server.setTimeout(queueHttpTimeoutMs, (socket) => socket.destroy());\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.host ?? \"127.0.0.1\", resolve);\n });\n const address = server.address();\n if (!address || typeof address === \"string\")\n throw new Error(\"Queue server address unavailable\");\n const host =\n address.family === \"IPv6\" ? `[${address.address}]` : address.address;\n return {\n url: `http://${host}:${address.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n server.closeIdleConnections();\n }),\n };\n}\nexport function httpTaskQueue(options: QueueClientOptions): TaskQueue {\n const authorization = tokenValue(options.token);\n const url = new URL(options.url);\n if (\n ![\"http:\", \"https:\"].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n )\n throw new Error(\"Invalid queue URL\");\n const endpoint = new URL(\"queue\", `${url.href.replace(/\\/$/, \"\")}/`);\n const timeoutMs = options.timeoutMs ?? queueHttpTimeoutMs;\n if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0)\n throw new Error(\"Invalid queue request timeout\");\n async function call(data: unknown) {\n const body = JSON.stringify(data);\n if (Buffer.byteLength(body) > queueMaxBytes)\n throw new Error(\"Queue message exceeds size limit\");\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { authorization, \"content-type\": \"application/json\" },\n body,\n signal: AbortSignal.timeout(timeoutMs),\n redirect: \"error\",\n });\n if (!response.ok) {\n await response.body?.cancel();\n throw new Error(`Queue request rejected (${response.status})`);\n }\n if (!response.body) throw new Error(\"Missing queue response\");\n const value = await message(response.body);\n return value === null ? undefined : queueJob(value);\n }\n async function required(data: unknown) {\n const job = await call(data);\n if (!job) throw new Error(\"Missing queue job\");\n return job;\n }\n return {\n enqueue: (request) =>\n required({ operation: \"enqueue\", request: queueRequest(request) }),\n get: (id) => call({ operation: \"get\", id: queueString(id) }),\n claim: (request) =>\n call({ operation: \"claim\", request: queueClaim(request) }),\n renew: (lease, leaseMs) =>\n required({\n operation: \"renew\",\n lease: queueLease(lease),\n leaseMs: queueLeaseMs(leaseMs),\n }),\n complete: (lease, result) =>\n required({\n operation: \"complete\",\n lease: queueLease(lease),\n result: queueResult(result),\n }),\n cancel: (id, fence) =>\n required({\n operation: \"cancel\",\n id: queueString(id),\n fence: queueNumber(fence),\n }),\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { TaskQueue } from "../domain/task-queue.types.ts";
2
+ export interface QueueServerOptions {
3
+ readonly queue: TaskQueue;
4
+ readonly token: string;
5
+ readonly host?: string;
6
+ readonly port?: number;
7
+ }
8
+ export interface QueueServer {
9
+ readonly url: string;
10
+ close(): Promise<void>;
11
+ }
12
+ export interface QueueClientOptions {
13
+ readonly url: string;
14
+ readonly token: string;
15
+ readonly timeoutMs?: number;
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=task-queue-http.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-queue-http.types.js","sourceRoot":"","sources":["../../src/infrastructure/task-queue-http.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TaskQueue } from \"../domain/task-queue.types.ts\";\nexport interface QueueServerOptions {\n readonly queue: TaskQueue;\n readonly token: string;\n readonly host?: string;\n readonly port?: number;\n}\nexport interface QueueServer {\n readonly url: string;\n close(): Promise<void>;\n}\nexport interface QueueClientOptions {\n readonly url: string;\n readonly token: string;\n readonly timeoutMs?: number;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { DurableTaskQueue } from "../domain/task-queue.types.ts";
2
+ export declare function sqliteTaskQueue(path: string): Promise<DurableTaskQueue>;
@@ -0,0 +1,134 @@
1
+ import { mkdirSync, chmodSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { queueClaim, queueJob, queueLease, queueLeaseMs, queueNumber, queueRequest, queueResult, queueString, } from "../domain/task-queue.js";
4
+ export async function sqliteTaskQueue(path) {
5
+ const { DatabaseSync } = await import("node:sqlite");
6
+ const file = resolve(path);
7
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
8
+ const db = new DatabaseSync(file);
9
+ chmodSync(file, 0o600);
10
+ db.exec("PRAGMA busy_timeout=5000; PRAGMA synchronous=FULL; CREATE TABLE IF NOT EXISTS queue_jobs (id TEXT PRIMARY KEY, body TEXT NOT NULL, status TEXT NOT NULL, handler TEXT NOT NULL, expires INTEGER, deadline INTEGER); CREATE INDEX IF NOT EXISTS queue_eligible ON queue_jobs(status, handler, expires, deadline, id)");
11
+ function read(id) {
12
+ const row = db.prepare("SELECT body FROM queue_jobs WHERE id = ?").get(id);
13
+ return row ? queueJob(JSON.parse(String(row.body))) : undefined;
14
+ }
15
+ function save(job) {
16
+ db.prepare("INSERT INTO queue_jobs(id, body, status, handler, expires, deadline) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET body=excluded.body, status=excluded.status, handler=excluded.handler, expires=excluded.expires, deadline=excluded.deadline").run(job.id, JSON.stringify(job), job.status, job.handler, job.expires ?? null, job.deadline ?? null);
17
+ return job;
18
+ }
19
+ function transaction(operation) {
20
+ db.exec("BEGIN IMMEDIATE");
21
+ try {
22
+ const result = operation();
23
+ db.exec("COMMIT");
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ db.exec("ROLLBACK");
28
+ throw error;
29
+ }
30
+ }
31
+ function expire(job) {
32
+ if ((job.status === "pending" || job.status === "active") &&
33
+ job.deadline !== undefined &&
34
+ job.deadline <= Date.now())
35
+ return save({ ...job, status: "cancelled", fence: job.fence + 1 });
36
+ return job;
37
+ }
38
+ function required(id) {
39
+ const job = read(queueString(id));
40
+ if (!job)
41
+ throw new Error("Queue job does not exist");
42
+ return expire(job);
43
+ }
44
+ function owned(input) {
45
+ const lease = queueLease(input);
46
+ const job = required(lease.id);
47
+ if (job.status !== "active" ||
48
+ job.worker !== lease.worker ||
49
+ job.fence !== lease.fence ||
50
+ (job.expires ?? 0) <= Date.now())
51
+ throw new Error("Stale queue lease");
52
+ return job;
53
+ }
54
+ return {
55
+ async enqueue(input) {
56
+ return transaction(() => {
57
+ const request = queueRequest(input);
58
+ const existing = read(request.id);
59
+ if (existing) {
60
+ if (JSON.stringify(queueRequest(existing)) !== JSON.stringify(request))
61
+ throw new Error("Queue identity already has a different request");
62
+ return expire(existing);
63
+ }
64
+ return expire(save({ ...request, status: "pending", fence: 0 }));
65
+ });
66
+ },
67
+ async get(id) {
68
+ return transaction(() => {
69
+ const job = read(queueString(id));
70
+ return job ? expire(job) : undefined;
71
+ });
72
+ },
73
+ async claim(input) {
74
+ return transaction(() => {
75
+ const claim = queueClaim(input);
76
+ const now = Date.now();
77
+ const placeholders = claim.handlers.map(() => "?").join(",");
78
+ const selected = db
79
+ .prepare(`SELECT id FROM queue_jobs INDEXED BY queue_eligible
80
+ WHERE status IN ('pending', 'active') AND handler IN (${placeholders})
81
+ AND (status = 'pending' OR expires <= ?)
82
+ AND (deadline IS NULL OR deadline > ?)
83
+ ORDER BY rowid LIMIT 1`)
84
+ .get(...claim.handlers, now, now);
85
+ if (!selected)
86
+ return undefined;
87
+ const job = required(String(selected.id));
88
+ if (job.status === "cancelled")
89
+ return undefined;
90
+ return save({
91
+ ...job,
92
+ status: "active",
93
+ worker: claim.worker,
94
+ fence: job.fence + 1,
95
+ expires: Math.min(now + claim.leaseMs, job.deadline ?? Infinity),
96
+ });
97
+ });
98
+ },
99
+ async renew(lease, ms) {
100
+ return transaction(() => {
101
+ const job = owned(lease);
102
+ return save({
103
+ ...job,
104
+ expires: Math.min(Date.now() + queueLeaseMs(ms), job.deadline ?? Infinity),
105
+ });
106
+ });
107
+ },
108
+ async complete(lease, input) {
109
+ return transaction(() => {
110
+ const job = owned(lease);
111
+ const result = queueResult(input);
112
+ return save({
113
+ ...job,
114
+ status: result.error === undefined ? "done" : "failed",
115
+ result,
116
+ });
117
+ });
118
+ },
119
+ async cancel(id, fence) {
120
+ return transaction(() => {
121
+ const job = required(id);
122
+ if (job.fence !== queueNumber(fence))
123
+ throw new Error("Stale queue fence");
124
+ if (job.status !== "pending" && job.status !== "active")
125
+ return job;
126
+ return save({ ...job, status: "cancelled", fence: job.fence + 1 });
127
+ });
128
+ },
129
+ close() {
130
+ db.close();
131
+ },
132
+ };
133
+ }
134
+ //# sourceMappingURL=task-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-queue.js","sourceRoot":"","sources":["../../src/infrastructure/task-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, chmodSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport {\n queueClaim,\n queueJob,\n queueLease,\n queueLeaseMs,\n queueNumber,\n queueRequest,\n queueResult,\n queueString,\n} from \"../domain/task-queue.ts\";\nimport type {\n DurableTaskQueue,\n QueueJob,\n QueueLease,\n} from \"../domain/task-queue.types.ts\";\n\nexport async function sqliteTaskQueue(path: string): Promise<DurableTaskQueue> {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const file = resolve(path);\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 });\n const db = new DatabaseSync(file);\n chmodSync(file, 0o600);\n db.exec(\n \"PRAGMA busy_timeout=5000; PRAGMA synchronous=FULL; CREATE TABLE IF NOT EXISTS queue_jobs (id TEXT PRIMARY KEY, body TEXT NOT NULL, status TEXT NOT NULL, handler TEXT NOT NULL, expires INTEGER, deadline INTEGER); CREATE INDEX IF NOT EXISTS queue_eligible ON queue_jobs(status, handler, expires, deadline, id)\",\n );\n function read(id: string): QueueJob | undefined {\n const row = db.prepare(\"SELECT body FROM queue_jobs WHERE id = ?\").get(id);\n return row ? queueJob(JSON.parse(String(row.body))) : undefined;\n }\n function save(job: QueueJob): QueueJob {\n db.prepare(\n \"INSERT INTO queue_jobs(id, body, status, handler, expires, deadline) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET body=excluded.body, status=excluded.status, handler=excluded.handler, expires=excluded.expires, deadline=excluded.deadline\",\n ).run(\n job.id,\n JSON.stringify(job),\n job.status,\n job.handler,\n job.expires ?? null,\n job.deadline ?? null,\n );\n return job;\n }\n function transaction<T>(operation: () => T): T {\n db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = operation();\n db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n function expire(job: QueueJob): QueueJob {\n if (\n (job.status === \"pending\" || job.status === \"active\") &&\n job.deadline !== undefined &&\n job.deadline <= Date.now()\n )\n return save({ ...job, status: \"cancelled\", fence: job.fence + 1 });\n return job;\n }\n function required(id: string): QueueJob {\n const job = read(queueString(id));\n if (!job) throw new Error(\"Queue job does not exist\");\n return expire(job);\n }\n function owned(input: QueueLease): QueueJob {\n const lease = queueLease(input);\n const job = required(lease.id);\n if (\n job.status !== \"active\" ||\n job.worker !== lease.worker ||\n job.fence !== lease.fence ||\n (job.expires ?? 0) <= Date.now()\n )\n throw new Error(\"Stale queue lease\");\n return job;\n }\n return {\n async enqueue(input) {\n return transaction(() => {\n const request = queueRequest(input);\n const existing = read(request.id);\n if (existing) {\n if (\n JSON.stringify(queueRequest(existing)) !== JSON.stringify(request)\n )\n throw new Error(\"Queue identity already has a different request\");\n return expire(existing);\n }\n return expire(save({ ...request, status: \"pending\", fence: 0 }));\n });\n },\n async get(id) {\n return transaction(() => {\n const job = read(queueString(id));\n return job ? expire(job) : undefined;\n });\n },\n async claim(input) {\n return transaction(() => {\n const claim = queueClaim(input);\n const now = Date.now();\n const placeholders = claim.handlers.map(() => \"?\").join(\",\");\n const selected = db\n .prepare(\n `SELECT id FROM queue_jobs INDEXED BY queue_eligible\n WHERE status IN ('pending', 'active') AND handler IN (${placeholders})\n AND (status = 'pending' OR expires <= ?)\n AND (deadline IS NULL OR deadline > ?)\n ORDER BY rowid LIMIT 1`,\n )\n .get(...claim.handlers, now, now);\n if (!selected) return undefined;\n const job = required(String(selected.id));\n if (job.status === \"cancelled\") return undefined;\n return save({\n ...job,\n status: \"active\",\n worker: claim.worker,\n fence: job.fence + 1,\n expires: Math.min(now + claim.leaseMs, job.deadline ?? Infinity),\n });\n });\n },\n async renew(lease, ms) {\n return transaction(() => {\n const job = owned(lease);\n return save({\n ...job,\n expires: Math.min(\n Date.now() + queueLeaseMs(ms),\n job.deadline ?? Infinity,\n ),\n });\n });\n },\n async complete(lease, input) {\n return transaction(() => {\n const job = owned(lease);\n const result = queueResult(input);\n return save({\n ...job,\n status: result.error === undefined ? \"done\" : \"failed\",\n result,\n });\n });\n },\n async cancel(id, fence) {\n return transaction(() => {\n const job = required(id);\n if (job.fence !== queueNumber(fence))\n throw new Error(\"Stale queue fence\");\n if (job.status !== \"pending\" && job.status !== \"active\") return job;\n return save({ ...job, status: \"cancelled\", fence: job.fence + 1 });\n });\n },\n close() {\n db.close();\n },\n };\n}\n"]}
@@ -28,6 +28,11 @@ export function boundedTransfers(lease, defaults) {
28
28
  ...(lease.fileTransfers
29
29
  ? {
30
30
  fileTransfers: {
31
+ ...(lease.fileTransfers.uploadBatch
32
+ ? {
33
+ uploadBatch: (source, entries, destination, options = {}) => transfer({ ...defaults, ...options }, (signal) => lease.fileTransfers.uploadBatch(source, entries, destination, { ...defaults, ...options, signal })),
34
+ }
35
+ : {}),
31
36
  manifest: (source, paths, options = {}) => transfer({ ...defaults, ...options }, (signal) => lease.fileTransfers.manifest(source, paths, {
32
37
  ...defaults,
33
38
  ...options,