@rikalabs/akter 0.1.0-alpha.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 (603) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +8 -0
  4. package/README.md +116 -0
  5. package/dist/actor/codecs.d.ts +77 -0
  6. package/dist/actor/codecs.js +87 -0
  7. package/dist/actor/connections.d.ts +34 -0
  8. package/dist/actor/connections.js +145 -0
  9. package/dist/actor/definition.d.ts +893 -0
  10. package/dist/actor/definition.js +147 -0
  11. package/dist/actor/descriptor.d.ts +172 -0
  12. package/dist/actor/descriptor.js +367 -0
  13. package/dist/actor/handles.d.ts +77 -0
  14. package/dist/actor/handles.js +265 -0
  15. package/dist/actor/jobs.d.ts +10 -0
  16. package/dist/actor/jobs.js +137 -0
  17. package/dist/actor/layers.d.ts +41 -0
  18. package/dist/actor/layers.js +177 -0
  19. package/dist/actor/reads.d.ts +47 -0
  20. package/dist/actor/reads.js +224 -0
  21. package/dist/actor/served.d.ts +120 -0
  22. package/dist/actor/served.js +72 -0
  23. package/dist/actor/turns.d.ts +13 -0
  24. package/dist/actor/turns.js +332 -0
  25. package/dist/actor/workflows.d.ts +16 -0
  26. package/dist/actor/workflows.js +37 -0
  27. package/dist/client/calls.d.ts +47 -0
  28. package/dist/client/calls.js +425 -0
  29. package/dist/client/clock.d.ts +56 -0
  30. package/dist/client/clock.js +119 -0
  31. package/dist/client/fleet-page.d.ts +15 -0
  32. package/dist/client/fleet-page.js +13 -0
  33. package/dist/client/fleet.d.ts +29 -0
  34. package/dist/client/fleet.js +51 -0
  35. package/dist/client/index.d.ts +17 -0
  36. package/dist/client/index.js +6 -0
  37. package/dist/client/make.d.ts +157 -0
  38. package/dist/client/make.js +221 -0
  39. package/dist/client/offline/queue.d.ts +118 -0
  40. package/dist/client/offline/queue.js +292 -0
  41. package/dist/client/offline/store.d.ts +74 -0
  42. package/dist/client/offline/store.js +78 -0
  43. package/dist/client/optimistic.d.ts +57 -0
  44. package/dist/client/optimistic.js +136 -0
  45. package/dist/client/report.d.ts +2 -0
  46. package/dist/client/report.js +8 -0
  47. package/dist/client/sessions/connection.d.ts +118 -0
  48. package/dist/client/sessions/connection.js +162 -0
  49. package/dist/client/sessions/feed.d.ts +42 -0
  50. package/dist/client/sessions/feed.js +43 -0
  51. package/dist/client/sessions/sse.d.ts +47 -0
  52. package/dist/client/sessions/sse.js +112 -0
  53. package/dist/client/sessions/stream.d.ts +28 -0
  54. package/dist/client/sessions/stream.js +35 -0
  55. package/dist/client/sessions/watch.d.ts +34 -0
  56. package/dist/client/sessions/watch.js +40 -0
  57. package/dist/client/transport.d.ts +37 -0
  58. package/dist/client/transport.js +66 -0
  59. package/dist/contexts/command.d.ts +132 -0
  60. package/dist/contexts/command.js +20 -0
  61. package/dist/contexts/connection.d.ts +68 -0
  62. package/dist/contexts/connection.js +0 -0
  63. package/dist/contexts/job.d.ts +49 -0
  64. package/dist/contexts/job.js +0 -0
  65. package/dist/contexts/workflow.d.ts +147 -0
  66. package/dist/contexts/workflow.js +22 -0
  67. package/dist/errors/actor.d.ts +200 -0
  68. package/dist/errors/actor.js +271 -0
  69. package/dist/errors/content.d.ts +19 -0
  70. package/dist/errors/content.js +14 -0
  71. package/dist/errors/database.d.ts +22 -0
  72. package/dist/errors/database.js +23 -0
  73. package/dist/errors/events.d.ts +17 -0
  74. package/dist/errors/events.js +14 -0
  75. package/dist/errors/subscription.d.ts +12 -0
  76. package/dist/errors/subscription.js +8 -0
  77. package/dist/errors/workflow.d.ts +28 -0
  78. package/dist/errors/workflow.js +17 -0
  79. package/dist/handles/actors.d.ts +17 -0
  80. package/dist/handles/actors.js +7 -0
  81. package/dist/handles/content.d.ts +59 -0
  82. package/dist/handles/content.js +36 -0
  83. package/dist/handles/intents.d.ts +164 -0
  84. package/dist/handles/intents.js +214 -0
  85. package/dist/handles/run.d.ts +31 -0
  86. package/dist/handles/run.js +21 -0
  87. package/dist/handles/workflow.d.ts +27 -0
  88. package/dist/handles/workflow.js +23 -0
  89. package/dist/identity/caller.d.ts +105 -0
  90. package/dist/identity/caller.js +68 -0
  91. package/dist/identity/child.d.ts +18 -0
  92. package/dist/identity/child.js +39 -0
  93. package/dist/identity/command.d.ts +19 -0
  94. package/dist/identity/command.js +21 -0
  95. package/dist/identity/connection.d.ts +24 -0
  96. package/dist/identity/connection.js +29 -0
  97. package/dist/identity/content.d.ts +18 -0
  98. package/dist/identity/content.js +12 -0
  99. package/dist/identity/digest.d.ts +4 -0
  100. package/dist/identity/digest.js +5 -0
  101. package/dist/identity/execution.d.ts +32 -0
  102. package/dist/identity/execution.js +63 -0
  103. package/dist/identity/mint.d.ts +51 -0
  104. package/dist/identity/mint.js +76 -0
  105. package/dist/identity/version.d.ts +2 -0
  106. package/dist/identity/version.js +3 -0
  107. package/dist/index.d.ts +159 -0
  108. package/dist/index.js +97 -0
  109. package/dist/members/blob.d.ts +43 -0
  110. package/dist/members/blob.js +29 -0
  111. package/dist/members/command.d.ts +109 -0
  112. package/dist/members/command.js +61 -0
  113. package/dist/members/connection.d.ts +55 -0
  114. package/dist/members/connection.js +49 -0
  115. package/dist/members/event.d.ts +36 -0
  116. package/dist/members/event.js +24 -0
  117. package/dist/members/job.d.ts +157 -0
  118. package/dist/members/job.js +67 -0
  119. package/dist/members/payload.d.ts +93 -0
  120. package/dist/members/payload.js +145 -0
  121. package/dist/members/reducer.d.ts +64 -0
  122. package/dist/members/reducer.js +27 -0
  123. package/dist/members/stream.d.ts +36 -0
  124. package/dist/members/stream.js +14 -0
  125. package/dist/members/subscription.d.ts +182 -0
  126. package/dist/members/subscription.js +91 -0
  127. package/dist/members/workflow.d.ts +99 -0
  128. package/dist/members/workflow.js +2 -0
  129. package/dist/policies/access.d.ts +35 -0
  130. package/dist/policies/access.js +6 -0
  131. package/dist/policies/command.d.ts +127 -0
  132. package/dist/policies/command.js +45 -0
  133. package/dist/policies/job.d.ts +28 -0
  134. package/dist/policies/job.js +60 -0
  135. package/dist/policies/schedules.d.ts +33 -0
  136. package/dist/policies/schedules.js +131 -0
  137. package/dist/protocol/frames.d.ts +66 -0
  138. package/dist/protocol/frames.js +55 -0
  139. package/dist/protocol/inspection.d.ts +807 -0
  140. package/dist/protocol/inspection.js +243 -0
  141. package/dist/protocol/wire.d.ts +184 -0
  142. package/dist/protocol/wire.js +150 -0
  143. package/dist/runtime/actors.d.ts +155 -0
  144. package/dist/runtime/actors.js +4 -0
  145. package/dist/runtime/admission.d.ts +34 -0
  146. package/dist/runtime/admission.js +72 -0
  147. package/dist/runtime/adoption/backfill.d.ts +27 -0
  148. package/dist/runtime/adoption/backfill.js +144 -0
  149. package/dist/runtime/adoption/enforce.d.ts +55 -0
  150. package/dist/runtime/adoption/enforce.js +241 -0
  151. package/dist/runtime/adoption/observe.d.ts +43 -0
  152. package/dist/runtime/adoption/observe.js +139 -0
  153. package/dist/runtime/adoption/plan.d.ts +45 -0
  154. package/dist/runtime/adoption/plan.js +192 -0
  155. package/dist/runtime/adoption/startup.d.ts +32 -0
  156. package/dist/runtime/adoption/startup.js +76 -0
  157. package/dist/runtime/adoption/status.d.ts +23 -0
  158. package/dist/runtime/adoption/status.js +47 -0
  159. package/dist/runtime/adoption/target.d.ts +33 -0
  160. package/dist/runtime/adoption/target.js +49 -0
  161. package/dist/runtime/connections/holder.d.ts +221 -0
  162. package/dist/runtime/connections/holder.js +816 -0
  163. package/dist/runtime/connections/owner.d.ts +263 -0
  164. package/dist/runtime/connections/owner.js +896 -0
  165. package/dist/runtime/connections/protocol.d.ts +441 -0
  166. package/dist/runtime/connections/protocol.js +222 -0
  167. package/dist/runtime/connections/reads.d.ts +24 -0
  168. package/dist/runtime/connections/reads.js +19 -0
  169. package/dist/runtime/connections/streams.d.ts +62 -0
  170. package/dist/runtime/connections/streams.js +128 -0
  171. package/dist/runtime/connections/transport.d.ts +40 -0
  172. package/dist/runtime/connections/transport.js +68 -0
  173. package/dist/runtime/connections/watch.d.ts +52 -0
  174. package/dist/runtime/connections/watch.js +159 -0
  175. package/dist/runtime/content/grant.d.ts +34 -0
  176. package/dist/runtime/content/grant.js +58 -0
  177. package/dist/runtime/content/store.d.ts +63 -0
  178. package/dist/runtime/content/store.js +294 -0
  179. package/dist/runtime/cron/key.d.ts +2 -0
  180. package/dist/runtime/cron/key.js +2 -0
  181. package/dist/runtime/cron/schedule.d.ts +69 -0
  182. package/dist/runtime/cron/schedule.js +144 -0
  183. package/dist/runtime/database/bounded.d.ts +43 -0
  184. package/dist/runtime/database/bounded.js +108 -0
  185. package/dist/runtime/database/connection.d.ts +8 -0
  186. package/dist/runtime/database/connection.js +18 -0
  187. package/dist/runtime/database/coordination.d.ts +19 -0
  188. package/dist/runtime/database/coordination.js +72 -0
  189. package/dist/runtime/database/flock.d.ts +10 -0
  190. package/dist/runtime/database/flock.js +60 -0
  191. package/dist/runtime/database/gate.d.ts +26 -0
  192. package/dist/runtime/database/gate.js +54 -0
  193. package/dist/runtime/database/keepalive.d.ts +11 -0
  194. package/dist/runtime/database/keepalive.js +22 -0
  195. package/dist/runtime/database/migrations.d.ts +177 -0
  196. package/dist/runtime/database/migrations.js +1018 -0
  197. package/dist/runtime/database/neki/migrations.d.ts +31 -0
  198. package/dist/runtime/database/neki/migrations.js +276 -0
  199. package/dist/runtime/database/neki/session.d.ts +23 -0
  200. package/dist/runtime/database/neki/session.js +31 -0
  201. package/dist/runtime/database/pglite.d.ts +21 -0
  202. package/dist/runtime/database/pglite.js +74 -0
  203. package/dist/runtime/database/replica.d.ts +41 -0
  204. package/dist/runtime/database/replica.js +51 -0
  205. package/dist/runtime/database/shards.d.ts +21 -0
  206. package/dist/runtime/database/shards.js +37 -0
  207. package/dist/runtime/database/snapshot.d.ts +7 -0
  208. package/dist/runtime/database/snapshot.js +7 -0
  209. package/dist/runtime/database/tenancy.d.ts +69 -0
  210. package/dist/runtime/database/tenancy.js +145 -0
  211. package/dist/runtime/database/transient.d.ts +10 -0
  212. package/dist/runtime/database/transient.js +25 -0
  213. package/dist/runtime/drain.d.ts +132 -0
  214. package/dist/runtime/drain.js +109 -0
  215. package/dist/runtime/entity/mailbox.d.ts +86 -0
  216. package/dist/runtime/entity/mailbox.js +121 -0
  217. package/dist/runtime/entity/register.d.ts +424 -0
  218. package/dist/runtime/entity/register.js +669 -0
  219. package/dist/runtime/events/append.d.ts +192 -0
  220. package/dist/runtime/events/append.js +128 -0
  221. package/dist/runtime/events/cursor.d.ts +2 -0
  222. package/dist/runtime/events/cursor.js +5 -0
  223. package/dist/runtime/events/replay.d.ts +20 -0
  224. package/dist/runtime/events/replay.js +45 -0
  225. package/dist/runtime/feeds.d.ts +26 -0
  226. package/dist/runtime/feeds.js +100 -0
  227. package/dist/runtime/fleet/checks.d.ts +17 -0
  228. package/dist/runtime/fleet/checks.js +106 -0
  229. package/dist/runtime/fleet/maintainer.d.ts +38 -0
  230. package/dist/runtime/fleet/maintainer.js +383 -0
  231. package/dist/runtime/fleet/pgoutput.d.ts +41 -0
  232. package/dist/runtime/fleet/pgoutput.js +102 -0
  233. package/dist/runtime/fleet/setup.d.ts +36 -0
  234. package/dist/runtime/fleet/setup.js +58 -0
  235. package/dist/runtime/fleet/subscribe.d.ts +42 -0
  236. package/dist/runtime/fleet/subscribe.js +147 -0
  237. package/dist/runtime/index.d.ts +66 -0
  238. package/dist/runtime/index.js +37 -0
  239. package/dist/runtime/inspector/http.d.ts +19 -0
  240. package/dist/runtime/inspector/http.js +36 -0
  241. package/dist/runtime/inspector/layer.d.ts +46 -0
  242. package/dist/runtime/inspector/layer.js +180 -0
  243. package/dist/runtime/inspector/queries.d.ts +711 -0
  244. package/dist/runtime/inspector/queries.js +494 -0
  245. package/dist/runtime/jobs/attempt.d.ts +254 -0
  246. package/dist/runtime/jobs/attempt.js +413 -0
  247. package/dist/runtime/jobs/isolation.d.ts +20 -0
  248. package/dist/runtime/jobs/isolation.js +10 -0
  249. package/dist/runtime/jobs/progress.d.ts +95 -0
  250. package/dist/runtime/jobs/progress.js +144 -0
  251. package/dist/runtime/layer.d.ts +260 -0
  252. package/dist/runtime/layer.js +876 -0
  253. package/dist/runtime/members.d.ts +381 -0
  254. package/dist/runtime/members.js +8 -0
  255. package/dist/runtime/operators/audit.d.ts +64 -0
  256. package/dist/runtime/operators/audit.js +56 -0
  257. package/dist/runtime/operators/auth.d.ts +29 -0
  258. package/dist/runtime/operators/auth.js +42 -0
  259. package/dist/runtime/operators/export.d.ts +68 -0
  260. package/dist/runtime/operators/export.js +137 -0
  261. package/dist/runtime/operators/grants.d.ts +45 -0
  262. package/dist/runtime/operators/grants.js +40 -0
  263. package/dist/runtime/operators/repair.d.ts +206 -0
  264. package/dist/runtime/operators/repair.js +226 -0
  265. package/dist/runtime/operators/routes.d.ts +35 -0
  266. package/dist/runtime/operators/routes.js +190 -0
  267. package/dist/runtime/operators/seed.d.ts +123 -0
  268. package/dist/runtime/operators/seed.js +123 -0
  269. package/dist/runtime/payloads/versions.d.ts +65 -0
  270. package/dist/runtime/payloads/versions.js +240 -0
  271. package/dist/runtime/peering/authority.d.ts +42 -0
  272. package/dist/runtime/peering/authority.js +132 -0
  273. package/dist/runtime/peering/credentials.d.ts +21 -0
  274. package/dist/runtime/peering/credentials.js +13 -0
  275. package/dist/runtime/peering/transport.d.ts +51 -0
  276. package/dist/runtime/peering/transport.js +175 -0
  277. package/dist/runtime/queries.d.ts +18 -0
  278. package/dist/runtime/queries.js +169 -0
  279. package/dist/runtime/readiness.d.ts +36 -0
  280. package/dist/runtime/readiness.js +44 -0
  281. package/dist/runtime/registration.d.ts +58 -0
  282. package/dist/runtime/registration.js +186 -0
  283. package/dist/runtime/request.d.ts +158 -0
  284. package/dist/runtime/request.js +102 -0
  285. package/dist/runtime/retry.d.ts +8 -0
  286. package/dist/runtime/retry.js +11 -0
  287. package/dist/runtime/runner.d.ts +93 -0
  288. package/dist/runtime/runner.js +122 -0
  289. package/dist/runtime/storage/codec.d.ts +39 -0
  290. package/dist/runtime/storage/codec.js +41 -0
  291. package/dist/runtime/storage/generation.d.ts +69 -0
  292. package/dist/runtime/storage/generation.js +49 -0
  293. package/dist/runtime/storage/placements.d.ts +15 -0
  294. package/dist/runtime/storage/placements.js +38 -0
  295. package/dist/runtime/storage/platform.d.ts +6 -0
  296. package/dist/runtime/storage/platform.js +9 -0
  297. package/dist/runtime/storage/retention.d.ts +73 -0
  298. package/dist/runtime/storage/retention.js +158 -0
  299. package/dist/runtime/subscriptions/identity.d.ts +31 -0
  300. package/dist/runtime/subscriptions/identity.js +45 -0
  301. package/dist/runtime/subscriptions/relay.d.ts +175 -0
  302. package/dist/runtime/subscriptions/relay.js +763 -0
  303. package/dist/runtime/subscriptions/storage.d.ts +40 -0
  304. package/dist/runtime/subscriptions/storage.js +98 -0
  305. package/dist/runtime/sweeps.d.ts +39 -0
  306. package/dist/runtime/sweeps.js +54 -0
  307. package/dist/runtime/telemetry/defects.d.ts +57 -0
  308. package/dist/runtime/telemetry/defects.js +43 -0
  309. package/dist/runtime/telemetry/metrics.d.ts +52 -0
  310. package/dist/runtime/telemetry/metrics.js +138 -0
  311. package/dist/runtime/telemetry/routes.d.ts +19 -0
  312. package/dist/runtime/telemetry/routes.js +18 -0
  313. package/dist/runtime/telemetry/sampler.d.ts +26 -0
  314. package/dist/runtime/telemetry/sampler.js +101 -0
  315. package/dist/runtime/telemetry/spans.d.ts +38 -0
  316. package/dist/runtime/telemetry/spans.js +44 -0
  317. package/dist/runtime/telemetry/usage.d.ts +52 -0
  318. package/dist/runtime/telemetry/usage.js +30 -0
  319. package/dist/runtime/topology/admission.d.ts +13 -0
  320. package/dist/runtime/topology/admission.js +36 -0
  321. package/dist/runtime/topology/locks.d.ts +56 -0
  322. package/dist/runtime/topology/locks.js +81 -0
  323. package/dist/runtime/topology/messages.d.ts +15 -0
  324. package/dist/runtime/topology/messages.js +32 -0
  325. package/dist/runtime/turn/admission.d.ts +22 -0
  326. package/dist/runtime/turn/admission.js +41 -0
  327. package/dist/runtime/turn/blobs.d.ts +38 -0
  328. package/dist/runtime/turn/blobs.js +215 -0
  329. package/dist/runtime/turn/execute.d.ts +208 -0
  330. package/dist/runtime/turn/execute.js +972 -0
  331. package/dist/runtime/turn/group.d.ts +165 -0
  332. package/dist/runtime/turn/group.js +326 -0
  333. package/dist/runtime/turn/hooks.d.ts +44 -0
  334. package/dist/runtime/turn/hooks.js +28 -0
  335. package/dist/runtime/turn/outbox.d.ts +159 -0
  336. package/dist/runtime/turn/outbox.js +269 -0
  337. package/dist/runtime/turn/pipeline.d.ts +68 -0
  338. package/dist/runtime/turn/pipeline.js +83 -0
  339. package/dist/runtime/turn/receipt.d.ts +81 -0
  340. package/dist/runtime/turn/receipt.js +32 -0
  341. package/dist/runtime/turn/relay.d.ts +179 -0
  342. package/dist/runtime/turn/relay.js +685 -0
  343. package/dist/runtime/turn/rows.d.ts +43 -0
  344. package/dist/runtime/turn/rows.js +592 -0
  345. package/dist/runtime/workflows/compatibility.d.ts +125 -0
  346. package/dist/runtime/workflows/compatibility.js +285 -0
  347. package/dist/runtime/workflows/engine.d.ts +107 -0
  348. package/dist/runtime/workflows/engine.js +826 -0
  349. package/dist/runtime/workflows/manifest.d.ts +34 -0
  350. package/dist/runtime/workflows/manifest.js +39 -0
  351. package/dist/runtime/workflows/steps.d.ts +33 -0
  352. package/dist/runtime/workflows/steps.js +152 -0
  353. package/dist/serve/api.d.ts +99 -0
  354. package/dist/serve/api.js +403 -0
  355. package/dist/serve/assertion/binding.d.ts +41 -0
  356. package/dist/serve/assertion/binding.js +92 -0
  357. package/dist/serve/assertion/verify.d.ts +93 -0
  358. package/dist/serve/assertion/verify.js +189 -0
  359. package/dist/serve/auth.d.ts +200 -0
  360. package/dist/serve/auth.js +92 -0
  361. package/dist/serve/clock.d.ts +10 -0
  362. package/dist/serve/clock.js +25 -0
  363. package/dist/serve/jwt.d.ts +65 -0
  364. package/dist/serve/jwt.js +202 -0
  365. package/dist/serve/layer.d.ts +98 -0
  366. package/dist/serve/layer.js +809 -0
  367. package/dist/serve/mcp/endpoint.d.ts +39 -0
  368. package/dist/serve/mcp/endpoint.js +187 -0
  369. package/dist/serve/mcp/tools.d.ts +37 -0
  370. package/dist/serve/mcp/tools.js +125 -0
  371. package/dist/serve/origin.d.ts +6 -0
  372. package/dist/serve/origin.js +21 -0
  373. package/dist/serve/sessions/feed.d.ts +79 -0
  374. package/dist/serve/sessions/feed.js +115 -0
  375. package/dist/serve/sessions/socket.d.ts +56 -0
  376. package/dist/serve/sessions/socket.js +212 -0
  377. package/dist/serve/sessions/sse.d.ts +27 -0
  378. package/dist/serve/sessions/sse.js +26 -0
  379. package/dist/serve/sessions/stream.d.ts +9 -0
  380. package/dist/serve/sessions/stream.js +9 -0
  381. package/dist/serve/sessions/watch.d.ts +12 -0
  382. package/dist/serve/sessions/watch.js +11 -0
  383. package/dist/serve/wire.d.ts +16 -0
  384. package/dist/serve/wire.js +50 -0
  385. package/dist/state/blob.d.ts +71 -0
  386. package/dist/state/blob.js +0 -0
  387. package/dist/state/migration.d.ts +53 -0
  388. package/dist/state/migration.js +29 -0
  389. package/dist/tables/fleet.d.ts +170 -0
  390. package/dist/tables/fleet.js +214 -0
  391. package/dist/tables/owned.d.ts +250 -0
  392. package/dist/tables/owned.js +233 -0
  393. package/dist/testing/actor-test.d.ts +360 -0
  394. package/dist/testing/actor-test.js +337 -0
  395. package/dist/testing/cluster.d.ts +93 -0
  396. package/dist/testing/cluster.js +296 -0
  397. package/dist/testing/conformance/access.d.ts +5 -0
  398. package/dist/testing/conformance/access.js +531 -0
  399. package/dist/testing/conformance/admission.d.ts +11 -0
  400. package/dist/testing/conformance/admission.js +182 -0
  401. package/dist/testing/conformance/adoption.d.ts +2 -0
  402. package/dist/testing/conformance/adoption.js +1019 -0
  403. package/dist/testing/conformance/assertions.d.ts +202 -0
  404. package/dist/testing/conformance/assertions.js +849 -0
  405. package/dist/testing/conformance/batches.d.ts +41 -0
  406. package/dist/testing/conformance/batches.js +388 -0
  407. package/dist/testing/conformance/blobs.d.ts +14 -0
  408. package/dist/testing/conformance/blobs.js +405 -0
  409. package/dist/testing/conformance/capacity.d.ts +8 -0
  410. package/dist/testing/conformance/capacity.js +433 -0
  411. package/dist/testing/conformance/client.d.ts +5 -0
  412. package/dist/testing/conformance/client.js +719 -0
  413. package/dist/testing/conformance/cold-serve.d.ts +16 -0
  414. package/dist/testing/conformance/cold-serve.js +254 -0
  415. package/dist/testing/conformance/connections/actors.d.ts +1143 -0
  416. package/dist/testing/conformance/connections/actors.js +180 -0
  417. package/dist/testing/conformance/connections/harness.d.ts +1754 -0
  418. package/dist/testing/conformance/connections/harness.js +142 -0
  419. package/dist/testing/conformance/connections/holders.d.ts +4 -0
  420. package/dist/testing/conformance/connections/holders.js +165 -0
  421. package/dist/testing/conformance/connections/lifecycle.d.ts +4 -0
  422. package/dist/testing/conformance/connections/lifecycle.js +113 -0
  423. package/dist/testing/conformance/connections/limits.d.ts +4 -0
  424. package/dist/testing/conformance/connections/limits.js +127 -0
  425. package/dist/testing/conformance/connections/reauthorization.d.ts +4 -0
  426. package/dist/testing/conformance/connections/reauthorization.js +170 -0
  427. package/dist/testing/conformance/connections/resync.d.ts +4 -0
  428. package/dist/testing/conformance/connections/resync.js +360 -0
  429. package/dist/testing/conformance/connections/takeover.d.ts +4 -0
  430. package/dist/testing/conformance/connections/takeover.js +287 -0
  431. package/dist/testing/conformance/connections.d.ts +4 -0
  432. package/dist/testing/conformance/connections.js +15 -0
  433. package/dist/testing/conformance/content-blobs.d.ts +16 -0
  434. package/dist/testing/conformance/content-blobs.js +534 -0
  435. package/dist/testing/conformance/counter.d.ts +16 -0
  436. package/dist/testing/conformance/counter.js +813 -0
  437. package/dist/testing/conformance/cron.d.ts +5 -0
  438. package/dist/testing/conformance/cron.js +665 -0
  439. package/dist/testing/conformance/drain.d.ts +26 -0
  440. package/dist/testing/conformance/drain.js +540 -0
  441. package/dist/testing/conformance/effect-control.d.ts +38 -0
  442. package/dist/testing/conformance/effect-control.js +1059 -0
  443. package/dist/testing/conformance/effects.d.ts +23 -0
  444. package/dist/testing/conformance/effects.js +432 -0
  445. package/dist/testing/conformance/events.d.ts +28 -0
  446. package/dist/testing/conformance/events.js +335 -0
  447. package/dist/testing/conformance/export.d.ts +2 -0
  448. package/dist/testing/conformance/export.js +368 -0
  449. package/dist/testing/conformance/fleet.d.ts +444 -0
  450. package/dist/testing/conformance/fleet.js +1009 -0
  451. package/dist/testing/conformance/groups.d.ts +7 -0
  452. package/dist/testing/conformance/groups.js +353 -0
  453. package/dist/testing/conformance/heap.d.ts +3 -0
  454. package/dist/testing/conformance/heap.js +149 -0
  455. package/dist/testing/conformance/http.d.ts +2809 -0
  456. package/dist/testing/conformance/http.js +1234 -0
  457. package/dist/testing/conformance/inspection-views.d.ts +8 -0
  458. package/dist/testing/conformance/inspection-views.js +272 -0
  459. package/dist/testing/conformance/inspector.d.ts +7 -0
  460. package/dist/testing/conformance/inspector.js +650 -0
  461. package/dist/testing/conformance/mint.d.ts +58 -0
  462. package/dist/testing/conformance/mint.js +692 -0
  463. package/dist/testing/conformance/multi-runner.d.ts +8 -0
  464. package/dist/testing/conformance/multi-runner.js +403 -0
  465. package/dist/testing/conformance/neki/plan.d.ts +19 -0
  466. package/dist/testing/conformance/neki/plan.js +30 -0
  467. package/dist/testing/conformance/observability.d.ts +3 -0
  468. package/dist/testing/conformance/observability.js +343 -0
  469. package/dist/testing/conformance/offline.d.ts +4 -0
  470. package/dist/testing/conformance/offline.js +474 -0
  471. package/dist/testing/conformance/operator-harness.d.ts +57 -0
  472. package/dist/testing/conformance/operator-harness.js +78 -0
  473. package/dist/testing/conformance/operator.d.ts +3 -0
  474. package/dist/testing/conformance/operator.js +379 -0
  475. package/dist/testing/conformance/outbox-cross-shard.d.ts +14 -0
  476. package/dist/testing/conformance/outbox-cross-shard.js +160 -0
  477. package/dist/testing/conformance/outbox.d.ts +36 -0
  478. package/dist/testing/conformance/outbox.js +437 -0
  479. package/dist/testing/conformance/payload-migrations.d.ts +3 -0
  480. package/dist/testing/conformance/payload-migrations.js +612 -0
  481. package/dist/testing/conformance/pipeline.d.ts +3 -0
  482. package/dist/testing/conformance/pipeline.js +1040 -0
  483. package/dist/testing/conformance/placement.d.ts +22 -0
  484. package/dist/testing/conformance/placement.js +539 -0
  485. package/dist/testing/conformance/platform.d.ts +40 -0
  486. package/dist/testing/conformance/platform.js +101 -0
  487. package/dist/testing/conformance/progress.d.ts +39 -0
  488. package/dist/testing/conformance/progress.js +666 -0
  489. package/dist/testing/conformance/properties.d.ts +6 -0
  490. package/dist/testing/conformance/properties.js +295 -0
  491. package/dist/testing/conformance/protocols.d.ts +4 -0
  492. package/dist/testing/conformance/protocols.js +618 -0
  493. package/dist/testing/conformance/provider/advertise.d.ts +21 -0
  494. package/dist/testing/conformance/provider/advertise.js +44 -0
  495. package/dist/testing/conformance/read-your-writes.d.ts +20 -0
  496. package/dist/testing/conformance/read-your-writes.js +192 -0
  497. package/dist/testing/conformance/reducers.d.ts +8 -0
  498. package/dist/testing/conformance/reducers.js +258 -0
  499. package/dist/testing/conformance/relay.d.ts +42 -0
  500. package/dist/testing/conformance/relay.js +1115 -0
  501. package/dist/testing/conformance/restore.d.ts +43 -0
  502. package/dist/testing/conformance/restore.js +341 -0
  503. package/dist/testing/conformance/retention.d.ts +14 -0
  504. package/dist/testing/conformance/retention.js +463 -0
  505. package/dist/testing/conformance/rls.d.ts +24 -0
  506. package/dist/testing/conformance/rls.js +576 -0
  507. package/dist/testing/conformance/simulation.d.ts +31 -0
  508. package/dist/testing/conformance/simulation.js +167 -0
  509. package/dist/testing/conformance/single-shard.d.ts +2 -0
  510. package/dist/testing/conformance/single-shard.js +125 -0
  511. package/dist/testing/conformance/singleton.d.ts +3 -0
  512. package/dist/testing/conformance/singleton.js +293 -0
  513. package/dist/testing/conformance/statements.d.ts +60 -0
  514. package/dist/testing/conformance/statements.js +108 -0
  515. package/dist/testing/conformance/streams.d.ts +7 -0
  516. package/dist/testing/conformance/streams.js +280 -0
  517. package/dist/testing/conformance/subscriptions/access.d.ts +4 -0
  518. package/dist/testing/conformance/subscriptions/access.js +98 -0
  519. package/dist/testing/conformance/subscriptions/actors.d.ts +4910 -0
  520. package/dist/testing/conformance/subscriptions/actors.js +372 -0
  521. package/dist/testing/conformance/subscriptions/cluster.d.ts +4 -0
  522. package/dist/testing/conformance/subscriptions/cluster.js +89 -0
  523. package/dist/testing/conformance/subscriptions/delivery.d.ts +4 -0
  524. package/dist/testing/conformance/subscriptions/delivery.js +253 -0
  525. package/dist/testing/conformance/subscriptions/deploys.d.ts +4 -0
  526. package/dist/testing/conformance/subscriptions/deploys.js +162 -0
  527. package/dist/testing/conformance/subscriptions/epochs.d.ts +4 -0
  528. package/dist/testing/conformance/subscriptions/epochs.js +169 -0
  529. package/dist/testing/conformance/subscriptions/failures.d.ts +4 -0
  530. package/dist/testing/conformance/subscriptions/failures.js +108 -0
  531. package/dist/testing/conformance/subscriptions/feeds.d.ts +4 -0
  532. package/dist/testing/conformance/subscriptions/feeds.js +218 -0
  533. package/dist/testing/conformance/subscriptions/harness.d.ts +124 -0
  534. package/dist/testing/conformance/subscriptions/harness.js +143 -0
  535. package/dist/testing/conformance/subscriptions/operator.d.ts +4 -0
  536. package/dist/testing/conformance/subscriptions/operator.js +128 -0
  537. package/dist/testing/conformance/subscriptions/retention.d.ts +4 -0
  538. package/dist/testing/conformance/subscriptions/retention.js +288 -0
  539. package/dist/testing/conformance/subscriptions/routing.d.ts +4 -0
  540. package/dist/testing/conformance/subscriptions/routing.js +179 -0
  541. package/dist/testing/conformance/subscriptions.d.ts +4 -0
  542. package/dist/testing/conformance/subscriptions.js +19 -0
  543. package/dist/testing/conformance/tables.d.ts +125 -0
  544. package/dist/testing/conformance/tables.js +664 -0
  545. package/dist/testing/conformance/transports/actors.d.ts +1907 -0
  546. package/dist/testing/conformance/transports/actors.js +163 -0
  547. package/dist/testing/conformance/transports/client.d.ts +3 -0
  548. package/dist/testing/conformance/transports/client.js +237 -0
  549. package/dist/testing/conformance/transports/feeds.d.ts +3 -0
  550. package/dist/testing/conformance/transports/feeds.js +232 -0
  551. package/dist/testing/conformance/transports/harness.d.ts +93 -0
  552. package/dist/testing/conformance/transports/harness.js +84 -0
  553. package/dist/testing/conformance/transports/streams.d.ts +3 -0
  554. package/dist/testing/conformance/transports/streams.js +96 -0
  555. package/dist/testing/conformance/transports/websocket.d.ts +3 -0
  556. package/dist/testing/conformance/transports/websocket.js +384 -0
  557. package/dist/testing/conformance/transports/wire.d.ts +141 -0
  558. package/dist/testing/conformance/transports/wire.js +204 -0
  559. package/dist/testing/conformance/transports.d.ts +3 -0
  560. package/dist/testing/conformance/transports.js +11 -0
  561. package/dist/testing/conformance/watch.d.ts +59 -0
  562. package/dist/testing/conformance/watch.js +879 -0
  563. package/dist/testing/conformance/workflow-engine.d.ts +97 -0
  564. package/dist/testing/conformance/workflow-engine.js +253 -0
  565. package/dist/testing/conformance/workflow-versions.d.ts +3 -0
  566. package/dist/testing/conformance/workflow-versions.js +545 -0
  567. package/dist/testing/conformance/workflows/access.d.ts +4 -0
  568. package/dist/testing/conformance/workflows/access.js +147 -0
  569. package/dist/testing/conformance/workflows/actors.d.ts +2317 -0
  570. package/dist/testing/conformance/workflows/actors.js +305 -0
  571. package/dist/testing/conformance/workflows/basics.d.ts +4 -0
  572. package/dist/testing/conformance/workflows/basics.js +144 -0
  573. package/dist/testing/conformance/workflows/engine.d.ts +4 -0
  574. package/dist/testing/conformance/workflows/engine.js +80 -0
  575. package/dist/testing/conformance/workflows/guards.d.ts +4 -0
  576. package/dist/testing/conformance/workflows/guards.js +172 -0
  577. package/dist/testing/conformance/workflows/harness.d.ts +14 -0
  578. package/dist/testing/conformance/workflows/harness.js +44 -0
  579. package/dist/testing/conformance/workflows/recovery.d.ts +4 -0
  580. package/dist/testing/conformance/workflows/recovery.js +181 -0
  581. package/dist/testing/conformance/workflows/starts.d.ts +4 -0
  582. package/dist/testing/conformance/workflows/starts.js +57 -0
  583. package/dist/testing/conformance/workflows/suspension.d.ts +4 -0
  584. package/dist/testing/conformance/workflows/suspension.js +106 -0
  585. package/dist/testing/conformance/workflows/waits.d.ts +4 -0
  586. package/dist/testing/conformance/workflows/waits.js +284 -0
  587. package/dist/testing/conformance/workflows.d.ts +4 -0
  588. package/dist/testing/conformance/workflows.js +19 -0
  589. package/dist/testing/conformance.d.ts +330 -0
  590. package/dist/testing/conformance.js +364 -0
  591. package/dist/testing/database.d.ts +31 -0
  592. package/dist/testing/database.js +70 -0
  593. package/dist/testing/foundation.d.ts +41 -0
  594. package/dist/testing/foundation.js +517 -0
  595. package/dist/testing/index.d.ts +20 -0
  596. package/dist/testing/index.js +12 -0
  597. package/dist/testing/property.d.ts +40 -0
  598. package/dist/testing/property.js +95 -0
  599. package/dist/testing/simulate-cluster.d.ts +112 -0
  600. package/dist/testing/simulate-cluster.js +257 -0
  601. package/dist/testing/simulate.d.ts +90 -0
  602. package/dist/testing/simulate.js +127 -0
  603. package/package.json +1 -0
@@ -0,0 +1,1234 @@
1
+ import { Cause, Context, Crypto, Deferred, Effect, Exit, Fiber, Layer, Option, Result, Schedule, Schema, } from "effect";
2
+ import { FetchHttpClient, Headers, HttpClient, HttpClientRequest, HttpMethod as HttpMethodModule, HttpRouter, } from "effect/http";
3
+ import { SqlClient } from "effect/sql";
4
+ import { Actor, ActorUnavailable, Unauthorized, User } from "../../index.js";
5
+ import { ActorError, CommandConflict, CommandExpired, InvalidCommandId, InvalidInput, } from "../../errors/actor.js";
6
+ import { InternalActors } from "../../runtime/actors.js";
7
+ import { RuntimeControl } from "../../runtime/drain.js";
8
+ import { ActorRef, System } from "../../identity/caller.js";
9
+ import { Credential } from "../../serve/auth.js";
10
+ import { serve } from "../../serve/layer.js";
11
+ import { actorErrorBody } from "../../protocol/wire.js";
12
+ import { buildServedApi, openApiDocument } from "../../serve/api.js";
13
+ import { ActorTest } from "../actor-test.js";
14
+ import { Auth } from "../../runtime/index.js";
15
+ import { serveFetch } from "./platform.js";
16
+ export class Full extends Schema.TaggedError()("Full", { capacity: Schema.Int }) {
17
+ }
18
+ export class Closed extends Schema.TaggedError()("Closed", { reason: Schema.String }, { httpApiStatus: 423 }) {
19
+ }
20
+ const Post = Actor.command("Post", {
21
+ payload: Schema.Struct({ text: Schema.String }),
22
+ success: Schema.Int,
23
+ error: Schema.Union([Full, Closed]),
24
+ });
25
+ const Whoami = Actor.command("Whoami", { success: Schema.String });
26
+ const Hold = Actor.command("Hold", { success: Schema.Int });
27
+ const Crash = Actor.command("Crash");
28
+ const Secret = Actor.command("Secret");
29
+ const Count = Actor.query("Count", { success: Schema.Int });
30
+ const Peek = Actor.query("Peek", { success: Schema.UndefinedOr(Schema.Int) });
31
+ const count = Actor.state({
32
+ count: Schema.Int.pipe(Schema.withDecodingDefault(Effect.succeed(0))),
33
+ });
34
+ export const HttpRoom = Actor.make("HttpRoom", {
35
+ key: Schema.String,
36
+ state: count,
37
+ api: { Post, Whoami, Hold, Crash, Count, Peek },
38
+ internal: { Secret },
39
+ });
40
+ const Level = Actor.query("Level", { success: Schema.Int, watch: true });
41
+ /** A room whose `Level` can be watched and whose `Count` cannot. */
42
+ export const HttpWatched = Actor.make("HttpWatched", {
43
+ key: Schema.String,
44
+ state: count,
45
+ api: { Post, Level, Count },
46
+ });
47
+ const Join = Actor.command("Join", { success: Schema.Int });
48
+ const Leave = Actor.command("Leave");
49
+ export const HttpLobby = Actor.make("HttpLobby", {
50
+ key: Actor.singleton,
51
+ state: count,
52
+ api: { Join, Leave },
53
+ });
54
+ export const HttpTicket = Actor.make("HttpTicket", { state: count, api: { Join } });
55
+ export class TooMany extends Schema.TaggedError()("TooMany", { count: Schema.Int }) {
56
+ }
57
+ const Add = Actor.reducer("Add", {
58
+ state: count,
59
+ payload: Schema.Struct({ by: Schema.Int }),
60
+ error: TooMany,
61
+ reduce: (state, { by }) => state.count + by > 10
62
+ ? Result.fail(TooMany.make({ count: state.count }))
63
+ : Result.succeed({ count: state.count + by }),
64
+ });
65
+ const Bump = Actor.reducer("Bump", {
66
+ state: count,
67
+ reduce: (state) => Result.succeed({ count: state.count + 1 }),
68
+ batch: { combine: () => undefined },
69
+ });
70
+ const Snapshot = Actor.query("Snapshot", { success: Schema.Struct({ count: Schema.Int }) });
71
+ /** Reducers only, so a client can run every command optimistically. */
72
+ export const HttpTally = Actor.make("HttpTally", {
73
+ key: Schema.String,
74
+ state: count,
75
+ api: { Add, Bump, Snapshot },
76
+ });
77
+ /** Handler runs, so a replay can be shown not to rerun the handler. */
78
+ export const runs = { count: 0 };
79
+ /** The `text` of every `HttpRoom.Post` the handler ran, in the order it ran them. */
80
+ export const posted = [];
81
+ export const gate = { hold: Effect.void };
82
+ export const httpLayer = Layer.mergeAll(HttpRoom.toLayer(Effect.succeed({
83
+ Post: Effect.fnUntraced(function* ({ text }) {
84
+ const turn = yield* HttpRoom.Turn;
85
+ runs.count += 1;
86
+ posted.push(text);
87
+ yield* turn.state.set({ count: turn.state.count + 1 });
88
+ if (text === "full")
89
+ return yield* Full.make({ capacity: turn.state.count });
90
+ if (text === "closed")
91
+ return yield* Closed.make({ reason: "night" });
92
+ return turn.state.count;
93
+ }),
94
+ Whoami: Effect.fnUntraced(function* () {
95
+ const turn = yield* HttpRoom.Turn;
96
+ runs.count += 1;
97
+ return `${turn.ref.tenant}/${Schema.is(User)(turn.caller) ? turn.caller.subject : turn.caller._tag}`;
98
+ }),
99
+ Hold: Effect.fnUntraced(function* () {
100
+ const turn = yield* HttpRoom.Turn;
101
+ yield* Effect.suspend(() => gate.hold);
102
+ runs.count += 1;
103
+ yield* turn.state.set({ count: turn.state.count + 1 });
104
+ return turn.state.count;
105
+ }),
106
+ Crash: () => Effect.die(new Error("handler crashed with a secret detail")),
107
+ Secret: () => Effect.void,
108
+ })), HttpRoom.toQueryLayer(Effect.succeed({
109
+ Count: Effect.fnUntraced(function* () {
110
+ return (yield* HttpRoom.Read).state.count;
111
+ }),
112
+ Peek: Effect.fnUntraced(function* () {
113
+ const { count } = (yield* HttpRoom.Read).state;
114
+ return count === 0 ? undefined : count;
115
+ }),
116
+ })), HttpWatched.toLayer(Effect.succeed({
117
+ Post: Effect.fnUntraced(function* () {
118
+ const turn = yield* HttpWatched.Turn;
119
+ yield* turn.state.set({ count: turn.state.count + 1 });
120
+ return turn.state.count;
121
+ }),
122
+ })), HttpWatched.toQueryLayer(Effect.succeed({
123
+ Level: Effect.fnUntraced(function* () {
124
+ return (yield* HttpWatched.Read).state.count;
125
+ }),
126
+ Count: Effect.fnUntraced(function* () {
127
+ return (yield* HttpWatched.Read).state.count;
128
+ }),
129
+ })), HttpLobby.toLayer(Effect.succeed({
130
+ Join: Effect.fnUntraced(function* () {
131
+ const turn = yield* HttpLobby.Turn;
132
+ yield* turn.state.set({ count: turn.state.count + 1 });
133
+ return turn.state.count;
134
+ }),
135
+ Leave: Effect.fnUntraced(function* () {
136
+ const turn = yield* HttpLobby.Turn;
137
+ yield* turn.state.set({ count: turn.state.count - 1 });
138
+ }),
139
+ })), HttpTally.toLayer(Effect.succeed({})), HttpTally.toQueryLayer(Effect.succeed({
140
+ Snapshot: Effect.fnUntraced(function* () {
141
+ return { count: (yield* HttpTally.Read).state.count };
142
+ }),
143
+ })), HttpTicket.toLayer(Effect.succeed({
144
+ Join: Effect.fnUntraced(function* () {
145
+ const turn = yield* HttpTicket.Turn;
146
+ yield* turn.state.set({ count: turn.state.count + 1 });
147
+ return turn.state.count;
148
+ }),
149
+ })));
150
+ const principal = (token) => {
151
+ if (token === "expired")
152
+ return Effect.fail(Unauthorized.make({ code: "expired" }));
153
+ const match = /^([^:]+):(.+)$/s.exec(token);
154
+ return match === null
155
+ ? Effect.fail(Unauthorized.make({ code: "invalid_credentials" }))
156
+ : Effect.succeed({ tenant: match[1], caller: User.make({ subject: match[2] }) });
157
+ };
158
+ const bearer = (request) => Option.match(Headers.get(request.headers, "authorization"), {
159
+ onNone: () => Effect.fail(Unauthorized.make({ code: "missing_credentials" })),
160
+ onSome: (header) => principal(header.replace(/^Bearer /, "")),
161
+ });
162
+ export const tokens = Auth.make(bearer);
163
+ const session = (request) => {
164
+ const value = request.cookies["session"];
165
+ return value === undefined
166
+ ? Effect.fail(Unauthorized.make({ code: "missing_credentials" }))
167
+ : principal(value);
168
+ };
169
+ const unavailable = {
170
+ credentials: [Credential.Bearer()],
171
+ authenticate: () => Effect.fail(ActorUnavailable.make({ cause: new Error("identity provider secret detail") })),
172
+ };
173
+ const systemCaller = {
174
+ credentials: [Credential.Bearer()],
175
+ authenticate: () => Effect.succeed({
176
+ tenant: "t",
177
+ caller: Object.assign(User.make({ subject: "smuggled" }), System.make({ source: "actor" })),
178
+ }),
179
+ };
180
+ const served = [HttpRoom, HttpLobby, HttpTicket, HttpTally, HttpWatched];
181
+ const encodeFull = (value) => Schema.encodeEffect(Full)(value).pipe(Effect.orDie);
182
+ const encodeClosed = (value) => Schema.encodeEffect(Closed)(value).pipe(Effect.orDie);
183
+ /** Serves the HTTP actors from a real listening server for the rest of the scope. */
184
+ export const serveHttp = Effect.fnUntraced(function* (options) {
185
+ const actors = yield* InternalActors;
186
+ const crypto = yield* Crypto.Crypto;
187
+ const client = Context.get(yield* Layer.build(FetchHttpClient.layer), HttpClient.HttpClient);
188
+ const context = yield* Effect.context();
189
+ const app = serve({ actors: served, auth: tokens, ...options }).pipe(Layer.provide(Layer.succeedContext(context)));
190
+ const web = HttpRouter.toWebHandler(app, { disableLogger: true });
191
+ yield* Effect.addFinalizer(() => Effect.promise(() => web.dispose()));
192
+ const port = yield* serveFetch((request) => web.handler(request));
193
+ const url = `http://127.0.0.1:${port}`;
194
+ return {
195
+ url,
196
+ handle: (request) => Effect.gen(function* () {
197
+ const response = yield* Effect.promise(() => web.handler(request));
198
+ const text = yield* Effect.promise(() => response.text());
199
+ return {
200
+ status: response.status,
201
+ headers: response.headers,
202
+ text,
203
+ body: text === "" ? undefined : yield* decodeJson(text),
204
+ };
205
+ }).pipe(Effect.orDie),
206
+ send: (path, init) => Effect.gen(function* () {
207
+ const body = init?.raw ?? (init?.body === undefined ? undefined : yield* encodeJson(init.body));
208
+ const base = HttpClientRequest.make(init?.method ?? "POST")(`${url}${path}`, {
209
+ headers: init?.headers ?? {},
210
+ });
211
+ const authed = init?.token === undefined ? base : HttpClientRequest.bearerToken(base, init.token);
212
+ const keyed = init?.key === undefined
213
+ ? authed
214
+ : HttpClientRequest.setHeader(authed, "idempotency-key", init.key);
215
+ const type = init?.headers?.["content-type"] ?? "application/json";
216
+ const request = init?.bytes !== undefined
217
+ ? HttpClientRequest.bodyUint8Array(keyed, init.bytes, type)
218
+ : body === undefined
219
+ ? keyed
220
+ : HttpClientRequest.bodyText(keyed, body, type);
221
+ const response = yield* client.execute(request);
222
+ const text = yield* response.text;
223
+ return {
224
+ status: response.status,
225
+ headers: new globalThis.Headers(response.headers),
226
+ text,
227
+ body: text === "" ? undefined : yield* decodeJson(text),
228
+ };
229
+ }).pipe(Effect.orDie),
230
+ mint: (offsetMs = 0, windowMs = actors.retryWindowMs) => Effect.gen(function* () {
231
+ const now = yield* actors.databaseNow;
232
+ return `v1.${now + offsetMs}.${now + offsetMs + windowMs}.${yield* crypto.randomUUIDv4}`;
233
+ }).pipe(Effect.orDie),
234
+ };
235
+ });
236
+ const isMethod = (value) => HttpMethodModule.isHttpMethod(value);
237
+ const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Json));
238
+ const decodeJson = Schema.decodeEffect(Schema.fromJsonString(Schema.Json));
239
+ const WireReason = Schema.Struct({
240
+ reason: Schema.Struct({ _tag: Schema.String, code: Schema.optionalKey(Schema.String) }),
241
+ });
242
+ export const reasonOf = (body) => Schema.decodeUnknownEffect(WireReason)(body).pipe(Effect.orDie, Effect.map(({ reason }) => reason.code === undefined ? { tag: reason._tag } : { tag: reason._tag, code: reason.code }));
243
+ const SecurityDocument = Schema.Struct({
244
+ paths: Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Struct({
245
+ operationId: Schema.String,
246
+ security: Schema.Array(Schema.Record(Schema.String, Schema.Array(Schema.String))),
247
+ }))),
248
+ components: Schema.Struct({ securitySchemes: Schema.Record(Schema.String, Schema.Json) }),
249
+ });
250
+ /** A served document's security schemes, and the distinct security of its authenticated operations. */
251
+ const securityOf = (server) => server.send("/openapi.json", { method: "GET" }).pipe(Effect.flatMap((reply) => Schema.decodeUnknownEffect(SecurityDocument)(reply.body)), Effect.orDie, Effect.map((spec) => {
252
+ const distinct = new Map(Object.values(spec.paths)
253
+ .flatMap((methods) => Object.values(methods))
254
+ .filter((operation) => operation.operationId !== "durable.protocol" &&
255
+ operation.operationId !== "durable.ready")
256
+ .map((operation) => [JSON.stringify(operation.security), operation.security]));
257
+ return { schemes: spec.components.securitySchemes, security: [...distinct.values()] };
258
+ }));
259
+ export const isDefectBody = Schema.is(Schema.TaggedStruct("Defect", { traceId: Schema.String }));
260
+ export const envelope = (reason) => actorErrorBody(ActorError.make({ reason }));
261
+ export const receipts = Effect.fnUntraced(function* (tenant, actor, id) {
262
+ return (yield* (yield* ActorTest).inspect(ActorRef.make({ tenant, actor, id }))).receipts;
263
+ });
264
+ const rows = Effect.fnUntraced(function* (table) {
265
+ const sql = yield* SqlClient.SqlClient;
266
+ const [row] = yield* sql `SELECT count(*)::int AS n FROM ${sql(table)}`;
267
+ return row.n;
268
+ });
269
+ export const tenantOf = Effect.gen(function* () {
270
+ const uuid = yield* (yield* Crypto.Crypto).randomUUIDv4.pipe(Effect.orDie);
271
+ return `http-${uuid.slice(0, 8)}`;
272
+ });
273
+ export const httpConformance = [
274
+ {
275
+ name: "answers /ready without credentials: 200 while serving, then 503 drained, and refuses commands",
276
+ run: ({ expect, environment }) => Effect.runPromise(Effect.promise(() => environment.run(Effect.gen(function* () {
277
+ const server = yield* serveHttp();
278
+ const token = `${yield* tenantOf}:alice`;
279
+ const ready = yield* server.send("/ready", { method: "GET" });
280
+ expect(ready).toMatchObject({ status: 200, body: { ready: true } });
281
+ expect(ready.headers.get("cache-control")).toBe("no-store");
282
+ expect(ready.headers.get("durable-protocol")).toBe("1");
283
+ const report = yield* RuntimeControl.use((control) => control.drain({ deadline: "5 seconds" }));
284
+ expect(report.outcome).toBe("clean");
285
+ expect(yield* server.send("/ready", { method: "GET" })).toMatchObject({
286
+ status: 503,
287
+ body: { ready: false, reason: "drained" },
288
+ });
289
+ const refused = yield* server.send("/actors/HttpRoom/drained/Post", {
290
+ token,
291
+ key: yield* server.mint(),
292
+ body: { text: "late" },
293
+ });
294
+ expect(refused.status).toBe(503);
295
+ expect(yield* reasonOf(refused.body)).toEqual({ tag: "ActorUnavailable" });
296
+ }))).pipe(Effect.ensuring(environment.restart))),
297
+ },
298
+ {
299
+ name: "stays ready while a command is in flight, then answers /ready with 503 draining while a drain waits for it, and it still commits",
300
+ timeoutMs: 60_000,
301
+ run: ({ expect, environment }) => Effect.runPromise(Effect.promise(() => environment.run(Effect.gen(function* () {
302
+ const server = yield* serveHttp();
303
+ const token = `${yield* tenantOf}:alice`;
304
+ const entered = yield* Deferred.make();
305
+ const release = yield* Deferred.make();
306
+ gate.hold = Deferred.succeed(entered, undefined).pipe(Effect.andThen(Deferred.await(release)));
307
+ const held = yield* server
308
+ .send("/actors/HttpRoom/in-flight/Hold", { token, key: yield* server.mint() })
309
+ .pipe(Effect.forkChild);
310
+ yield* Deferred.await(entered);
311
+ yield* Effect.sleep("1100 millis");
312
+ expect(yield* server.send("/ready", { method: "GET" })).toMatchObject({
313
+ status: 200,
314
+ body: { ready: true },
315
+ });
316
+ const drain = yield* RuntimeControl.use((control) => control.drain({ deadline: "30 seconds" })).pipe(Effect.forkChild);
317
+ const draining = yield* server.send("/ready", { method: "GET" }).pipe(Effect.repeat({
318
+ schedule: Schedule.spaced("10 millis"),
319
+ until: (reply) => reply.status === 503,
320
+ }));
321
+ expect(draining.body).toEqual({ ready: false, reason: "draining" });
322
+ yield* Deferred.succeed(release, undefined);
323
+ expect((yield* Fiber.join(drain)).outcome).toBe("clean");
324
+ expect(yield* Fiber.join(held)).toMatchObject({ status: 200, body: 1 });
325
+ }))).pipe(Effect.ensuring(Effect.sync(() => {
326
+ gate.hold = Effect.void;
327
+ })), Effect.ensuring(environment.restart))),
328
+ },
329
+ {
330
+ name: "gives concurrent requests with different tokens different principals",
331
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
332
+ const server = yield* serveHttp();
333
+ const tenant = yield* tenantOf;
334
+ const subjects = Array.from({ length: 16 }, (_, index) => `user-${index}`);
335
+ const replies = yield* Effect.forEach(subjects, (subject) => Effect.flatMap(server.mint(), (key) => server.send("/actors/HttpRoom/shared/Whoami", {
336
+ token: `${tenant}:${subject}`,
337
+ key,
338
+ })), { concurrency: "unbounded" });
339
+ expect(replies.map((reply) => reply.body)).toEqual(subjects.map((subject) => `${tenant}/${subject}`));
340
+ })),
341
+ },
342
+ {
343
+ name: "fails missing, invalid, and expired credentials with their codes before any turn and never as Anonymous",
344
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
345
+ const server = yield* serveHttp();
346
+ const before = runs.count;
347
+ for (const [token, code] of [
348
+ [undefined, "missing_credentials"],
349
+ ["no-colon", "invalid_credentials"],
350
+ ["expired", "expired"],
351
+ ]) {
352
+ const reply = yield* server.send("/actors/HttpRoom/creds/Whoami", {
353
+ token,
354
+ key: yield* server.mint(),
355
+ });
356
+ expect(reply.status).toBe(401);
357
+ expect(reply.headers.get("www-authenticate")).toBe("Bearer");
358
+ expect(reply.body).toEqual(yield* envelope(Unauthorized.make({ code })));
359
+ const query = yield* server.send("/actors/HttpRoom/creds/Count", { token });
360
+ expect(query.status).toBe(401);
361
+ }
362
+ expect(runs.count).toBe(before);
363
+ expect((yield* server.send("/command-ids")).status).toBe(401);
364
+ })),
365
+ },
366
+ {
367
+ name: "serves every caller as Anonymous in the default tenant under Auth.none, ignoring credentials",
368
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
369
+ const server = yield* serveHttp({ auth: Auth.none });
370
+ const reply = yield* server.send("/actors/HttpRoom/public/Whoami", {
371
+ token: "tenant:alice",
372
+ key: yield* server.mint(),
373
+ headers: { "x-tenant": "other" },
374
+ });
375
+ expect(reply.body).toBe("default/Anonymous");
376
+ })),
377
+ },
378
+ {
379
+ name: "replays a declared failure with the same tag, fields, and status",
380
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
381
+ const server = yield* serveHttp();
382
+ const token = `${yield* tenantOf}:alice`;
383
+ for (const [text, status, body] of [
384
+ ["full", 422, yield* encodeFull(Full.make({ capacity: 1 }))],
385
+ ["closed", 423, yield* encodeClosed(Closed.make({ reason: "night" }))],
386
+ ]) {
387
+ const key = yield* server.mint();
388
+ const first = yield* server.send(`/actors/HttpRoom/declared-${text}/Post`, {
389
+ token,
390
+ key,
391
+ body: { text },
392
+ });
393
+ const before = runs.count;
394
+ const replay = yield* server.send(`/actors/HttpRoom/declared-${text}/Post`, {
395
+ token,
396
+ key,
397
+ body: { text },
398
+ });
399
+ for (const reply of [first, replay]) {
400
+ expect(reply.status).toBe(status);
401
+ expect(reply.body).toEqual(body);
402
+ expect(reply.headers.get("x-request-id")).toBe(key);
403
+ }
404
+ expect(runs.count).toBe(before);
405
+ expect(first.headers.get("durable-replayed")).toBe("false");
406
+ expect(replay.headers.get("durable-replayed")).toBe("true");
407
+ }
408
+ })),
409
+ },
410
+ {
411
+ name: "replays a committed output when a response is dropped and the same Idempotency-Key is retried",
412
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
413
+ const server = yield* serveHttp();
414
+ const tenant = yield* tenantOf;
415
+ const key = yield* server.mint();
416
+ const call = server.send("/actors/HttpRoom/replay/Post", {
417
+ token: `${tenant}:alice`,
418
+ key: `"${key}"`,
419
+ body: { text: "hi" },
420
+ });
421
+ const first = yield* call;
422
+ expect(first.body).toBe(1);
423
+ expect(first.headers.get("durable-replayed")).toBe("false");
424
+ const before = runs.count;
425
+ const retry = yield* server.send("/actors/HttpRoom/replay/Post", {
426
+ token: `${tenant}:alice`,
427
+ key,
428
+ body: { text: "hi" },
429
+ });
430
+ expect(retry).toMatchObject({ status: 200, body: 1 });
431
+ expect(retry.headers.get("x-request-id")).toBe(key);
432
+ expect(retry.headers.get("durable-replayed")).toBe("true");
433
+ expect(runs.count).toBe(before);
434
+ expect(yield* receipts(tenant, "HttpRoom", "replay")).toBe(1);
435
+ })),
436
+ },
437
+ {
438
+ name: "reports one commit and receipt replays for concurrent HTTP retries under one command id",
439
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
440
+ const server = yield* serveHttp();
441
+ const tenant = yield* tenantOf;
442
+ const key = yield* server.mint();
443
+ const replies = yield* Effect.forEach(Array.from({ length: 8 }), () => server.send("/actors/HttpRoom/concurrent-replay/Post", {
444
+ token: `${tenant}:alice`,
445
+ key,
446
+ body: { text: "only once" },
447
+ }), { concurrency: "unbounded" });
448
+ expect(replies.map((reply) => reply.body)).toEqual(Array.from({ length: 8 }, () => 1));
449
+ expect(replies
450
+ .map((reply) => reply.headers.get("durable-replayed"))
451
+ .sort((left, right) => (left ?? "").localeCompare(right ?? ""))).toEqual(["false", "true", "true", "true", "true", "true", "true", "true"]);
452
+ expect(yield* receipts(tenant, "HttpRoom", "concurrent-replay")).toBe(1);
453
+ })),
454
+ },
455
+ {
456
+ name: "returns 409 CommandConflict for a reused id with different input, without running the handler",
457
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
458
+ const server = yield* serveHttp();
459
+ const token = `${yield* tenantOf}:alice`;
460
+ const key = yield* server.mint();
461
+ yield* server.send("/actors/HttpRoom/conflict/Post", { token, key, body: { text: "a" } });
462
+ const before = runs.count;
463
+ const reply = yield* server.send("/actors/HttpRoom/conflict/Post", {
464
+ token,
465
+ key,
466
+ body: { text: "b" },
467
+ });
468
+ expect(reply.status).toBe(409);
469
+ expect(reply.body).toEqual(yield* envelope(CommandConflict.make({ commandId: key })));
470
+ expect(runs.count).toBe(before);
471
+ })),
472
+ },
473
+ {
474
+ name: "returns 410 CommandExpired for an expired id even after its receipt is pruned",
475
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
476
+ const server = yield* serveHttp();
477
+ const actors = yield* InternalActors;
478
+ const tenant = yield* tenantOf;
479
+ const key = yield* server.mint(-actors.retryWindowMs - 1);
480
+ const before = runs.count;
481
+ const reply = yield* server.send("/actors/HttpRoom/expired/Post", {
482
+ token: `${tenant}:alice`,
483
+ key,
484
+ body: { text: "a" },
485
+ });
486
+ expect(reply.status).toBe(410);
487
+ expect(reply.body).toEqual(yield* envelope(CommandExpired.make({ commandId: key })));
488
+ expect(reply.headers.get("x-request-id")).toBe(key);
489
+ expect(runs.count).toBe(before);
490
+ expect(yield* receipts(tenant, "HttpRoom", "expired")).toBe(0);
491
+ })),
492
+ },
493
+ {
494
+ name: "rejects a command without Idempotency-Key, and a malformed, future, or wrong-window id, before any turn",
495
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
496
+ const server = yield* serveHttp();
497
+ const tenant = yield* tenantOf;
498
+ const token = `${tenant}:alice`;
499
+ const before = runs.count;
500
+ const missing = yield* server.send("/actors/HttpRoom/ids/Post", {
501
+ token,
502
+ body: { text: "a" },
503
+ headers: { "x-request-id": yield* server.mint() },
504
+ });
505
+ expect(missing.status).toBe(400);
506
+ expect(yield* reasonOf(missing.body)).toEqual({
507
+ tag: "InvalidInput",
508
+ code: "missing_command_id",
509
+ });
510
+ expect(missing.headers.get("x-request-id")).toBe(null);
511
+ for (const [key, code] of [
512
+ ["v1.not-an-id", "malformed"],
513
+ [`v2.1.2.${yield* (yield* Crypto.Crypto).randomUUIDv4.pipe(Effect.orDie)}`, "version"],
514
+ [yield* server.mint(60 * 60_000), "future"],
515
+ [yield* server.mint(0, 1_000), "window"],
516
+ ]) {
517
+ const reply = yield* server.send("/actors/HttpRoom/ids/Post", {
518
+ token,
519
+ key,
520
+ body: { text: "a" },
521
+ });
522
+ expect(reply.status).toBe(400);
523
+ expect(reply.body).toEqual(yield* envelope(InvalidCommandId.make({ commandId: key, code })));
524
+ expect(reply.headers.get("x-request-id")).toBe(key);
525
+ }
526
+ expect(runs.count).toBe(before);
527
+ expect(yield* receipts(tenant, "HttpRoom", "ids")).toBe(0);
528
+ })),
529
+ },
530
+ {
531
+ name: "mints ids from the database clock at /command-ids without writing, and admits them",
532
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
533
+ const server = yield* serveHttp();
534
+ const actors = yield* InternalActors;
535
+ const token = `${yield* tenantOf}:alice`;
536
+ const protocol = yield* server.send("/protocol", { method: "GET" });
537
+ expect(protocol.status).toBe(200);
538
+ expect(protocol.body).toMatchObject({ protocol: 1, retryWindowMs: actors.retryWindowMs });
539
+ expect(protocol.headers.get("durable-protocol")).toBe("1");
540
+ expect(Number(protocol.headers.get("durable-now")) > 0).toBe(true);
541
+ const receiptsBefore = yield* rows("actor_receipts");
542
+ const minted = yield* server.send("/command-ids", { token });
543
+ expect(minted.status).toBe(200);
544
+ expect(yield* rows("actor_receipts")).toBe(receiptsBefore);
545
+ const { commandId } = minted.body;
546
+ const [, issued, expires] = commandId.split(".");
547
+ expect(Number(expires) - Number(issued)).toBe(actors.retryWindowMs);
548
+ const reply = yield* server.send("/actors/HttpRoom/minted-id/Post", {
549
+ token,
550
+ key: commandId,
551
+ body: { text: "a" },
552
+ });
553
+ expect(reply).toMatchObject({ status: 200, body: 1 });
554
+ })),
555
+ },
556
+ {
557
+ name: "carries no ActorUnavailable cause over HTTP, and computes isRetryable and retryAfter on the wire",
558
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
559
+ const server = yield* serveHttp({ auth: unavailable });
560
+ const reply = yield* server.send("/actors/HttpRoom/unavailable/Whoami", {
561
+ key: yield* server.mint(),
562
+ });
563
+ expect(reply.status).toBe(503);
564
+ const body = reply.body;
565
+ expect(yield* reasonOf(reply.body)).toEqual({ tag: "ActorUnavailable" });
566
+ expect(reply.body).toMatchObject({ isRetryable: true });
567
+ expect(reply.text.includes("secret")).toBe(false);
568
+ expect(Object.keys(reply.body.reason)).toEqual(["_tag"]);
569
+ expect(body.retryAfter >= 125 && body.retryAfter <= 375).toBe(true);
570
+ expect(reply.headers.get("retry-after")).toBe("1");
571
+ })),
572
+ },
573
+ {
574
+ name: "refuses a served command with 503 ActorUnavailable and retry-after before authenticating or reading it while the runtime is overloaded, and still serves queries",
575
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
576
+ const actors = yield* InternalActors;
577
+ let overloaded = false;
578
+ const server = yield* serveHttp().pipe(Effect.provideService(InternalActors, InternalActors.of({ ...actors, overloaded: () => overloaded })));
579
+ const tenant = yield* tenantOf;
580
+ const token = `${tenant}:alice`;
581
+ const first = yield* server.send("/actors/HttpRoom/shed/Post", {
582
+ token,
583
+ key: yield* server.mint(),
584
+ body: { text: "a" },
585
+ });
586
+ expect(first).toMatchObject({ status: 200, body: 1 });
587
+ overloaded = true;
588
+ const key = yield* server.mint();
589
+ const refused = yield* server.send("/actors/HttpRoom/shed/Post", {
590
+ key,
591
+ raw: "{not json",
592
+ });
593
+ expect(refused.status).toBe(503);
594
+ expect(yield* reasonOf(refused.body)).toEqual({ tag: "ActorUnavailable" });
595
+ expect(refused.body).toMatchObject({ isRetryable: true });
596
+ expect(refused.headers.get("retry-after")).toBe("1");
597
+ expect(yield* receipts(tenant, "HttpRoom", "shed")).toBe(1);
598
+ expect(yield* server.send("/actors/HttpRoom/shed/Count", { token })).toMatchObject({
599
+ status: 200,
600
+ body: 1,
601
+ });
602
+ overloaded = false;
603
+ const retried = yield* server.send("/actors/HttpRoom/shed/Post", {
604
+ token,
605
+ key,
606
+ body: { text: "b" },
607
+ });
608
+ expect(retried).toMatchObject({ status: 200, body: 2 });
609
+ expect(yield* receipts(tenant, "HttpRoom", "shed")).toBe(2);
610
+ })),
611
+ },
612
+ {
613
+ name: "bounds served command authentication across serve layers and retries a refused id without a receipt or duplicate transition",
614
+ timeoutMs: 30_000,
615
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
616
+ const release = yield* Deferred.make();
617
+ const reached = yield* Deferred.make();
618
+ let authenticating = 0;
619
+ const auth = {
620
+ ...tokens,
621
+ authenticate: (request) => tokens.authenticate(request).pipe(Effect.tap(() => Effect.gen(function* () {
622
+ authenticating += 1;
623
+ if (authenticating === 64)
624
+ yield* Deferred.succeed(reached, undefined);
625
+ yield* Deferred.await(release);
626
+ }))),
627
+ };
628
+ const a = yield* serveHttp({ auth });
629
+ const b = yield* serveHttp({ auth });
630
+ const tenant = yield* tenantOf;
631
+ const token = `${tenant}:alice`;
632
+ const keys = yield* Effect.forEach(Array.from({ length: 65 }), () => a.mint());
633
+ const held = yield* Effect.forEach(keys.slice(0, 64), (key, index) => (index % 2 === 0 ? a : b).send("/actors/HttpRoom/accept-bound/Post", {
634
+ token,
635
+ key,
636
+ body: { text: "accepted" },
637
+ }), { concurrency: "unbounded" }).pipe(Effect.forkScoped);
638
+ yield* Deferred.await(reached);
639
+ const refused = yield* b.send("/actors/HttpRoom/accept-bound/Post", {
640
+ token,
641
+ key: keys[64],
642
+ body: { text: "refused" },
643
+ });
644
+ expect(refused.status).toBe(503);
645
+ expect(refused.headers.get("retry-after")).toBe("1");
646
+ expect(yield* reasonOf(refused.body)).toEqual({ tag: "ActorUnavailable" });
647
+ expect(authenticating).toBe(64);
648
+ expect(yield* receipts(tenant, "HttpRoom", "accept-bound")).toBe(0);
649
+ yield* Deferred.succeed(release, undefined);
650
+ const replies = yield* Fiber.join(held);
651
+ expect(replies.every((reply) => reply.status === 200)).toBe(true);
652
+ expect(yield* receipts(tenant, "HttpRoom", "accept-bound")).toBe(64);
653
+ for (const server of [a, b]) {
654
+ expect(yield* server.send("/actors/HttpRoom/accept-bound/Post", {
655
+ token,
656
+ key: keys[64],
657
+ body: { text: "refused" },
658
+ })).toMatchObject({ status: 200, body: 65 });
659
+ }
660
+ expect(yield* receipts(tenant, "HttpRoom", "accept-bound")).toBe(65);
661
+ expect(yield* a.send("/actors/HttpRoom/accept-bound/Count", { token })).toMatchObject({
662
+ status: 200,
663
+ body: 65,
664
+ });
665
+ })),
666
+ },
667
+ {
668
+ name: "answers an internal member exactly like an unknown one, and omits it from OpenAPI",
669
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
670
+ const server = yield* serveHttp({ openapi: { path: "/openapi.json" } });
671
+ const token = `${yield* tenantOf}:alice`;
672
+ const internal = yield* server.send("/actors/HttpRoom/r/Secret", {
673
+ token,
674
+ key: yield* server.mint(),
675
+ });
676
+ const unknown = yield* server.send("/actors/HttpRoom/r/Nope", {
677
+ token,
678
+ key: yield* server.mint(),
679
+ });
680
+ expect(internal.status).toBe(404);
681
+ expect(internal.body).toEqual(unknown.body);
682
+ expect(yield* reasonOf(internal.body)).toEqual({
683
+ tag: "InvalidInput",
684
+ code: "unknown_route",
685
+ });
686
+ const spec = yield* server.send("/openapi.json", { method: "GET" });
687
+ expect(spec.text.includes("Secret")).toBe(false);
688
+ })),
689
+ },
690
+ {
691
+ name: "routes keyed, singleton, and minted actors, and treats special characters in ids as one segment",
692
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
693
+ const server = yield* serveHttp();
694
+ const tenant = yield* tenantOf;
695
+ const token = `${tenant}:alice`;
696
+ const odd = "a b/c?d#é";
697
+ const keyed = yield* server.send(`/actors/HttpRoom/${encodeURIComponent(odd)}/Post`, {
698
+ token,
699
+ key: yield* server.mint(),
700
+ body: { text: "a" },
701
+ });
702
+ expect(keyed).toMatchObject({ status: 200, body: 1 });
703
+ expect(yield* receipts(tenant, "HttpRoom", odd)).toBe(1);
704
+ const lobby = yield* server.send("/actors/HttpLobby/Join", {
705
+ token,
706
+ key: yield* server.mint(),
707
+ });
708
+ expect(lobby).toMatchObject({ status: 200, body: 1 });
709
+ expect(yield* receipts(tenant, "HttpLobby", "singleton")).toBe(1);
710
+ const id = yield* (yield* InternalActors).mintActorId;
711
+ const ticket = yield* server.send(`/actors/HttpTicket/${id}/Join`, {
712
+ token,
713
+ key: yield* server.mint(),
714
+ });
715
+ expect(ticket).toMatchObject({ status: 200, body: 1 });
716
+ const notUuid = yield* server.send("/actors/HttpTicket/not-a-uuid/Join", {
717
+ token,
718
+ key: yield* server.mint(),
719
+ });
720
+ expect(notUuid.status).toBe(400);
721
+ expect(yield* reasonOf(notUuid.body)).toEqual({ tag: "InvalidInput", code: "decode" });
722
+ const dots = yield* server.send("/actors/HttpRoom/%2E%2E/Post", {
723
+ token,
724
+ key: yield* server.mint(),
725
+ body: { text: "a" },
726
+ });
727
+ expect(dots.status === 400 || dots.status === 404).toBe(true);
728
+ })),
729
+ },
730
+ {
731
+ name: "keeps running a command whose HTTP client disconnected, and replays it on retry",
732
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
733
+ const server = yield* serveHttp();
734
+ const tenant = yield* tenantOf;
735
+ const token = `${tenant}:alice`;
736
+ const key = yield* server.mint();
737
+ const entered = yield* Deferred.make();
738
+ const release = yield* Deferred.make();
739
+ gate.hold = Deferred.succeed(entered, undefined).pipe(Effect.andThen(Deferred.await(release)));
740
+ const call = yield* server
741
+ .send("/actors/HttpRoom/disconnect/Hold", { token, key })
742
+ .pipe(Effect.forkChild);
743
+ yield* Deferred.await(entered);
744
+ yield* Fiber.interrupt(call);
745
+ yield* Effect.sleep("50 millis");
746
+ gate.hold = Effect.void;
747
+ yield* Deferred.succeed(release, undefined);
748
+ const retry = yield* server
749
+ .send("/actors/HttpRoom/disconnect/Hold", { token, key })
750
+ .pipe(Effect.repeat({
751
+ until: (reply) => reply.status === 200,
752
+ schedule: Schedule.spaced("100 millis"),
753
+ times: 50,
754
+ }));
755
+ expect(retry).toMatchObject({ status: 200, body: 1 });
756
+ expect(yield* receipts(tenant, "HttpRoom", "disconnect")).toBe(1);
757
+ })),
758
+ },
759
+ {
760
+ name: "answers a defect with an opaque 500 and writes no receipt",
761
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
762
+ const server = yield* serveHttp();
763
+ const tenant = yield* tenantOf;
764
+ const key = yield* server.mint();
765
+ const reply = yield* server.send("/actors/HttpRoom/defect/Crash", {
766
+ token: `${tenant}:alice`,
767
+ key,
768
+ });
769
+ expect(reply.status).toBe(500);
770
+ expect(Object.keys(reply.body).sort()).toEqual(["_tag", "traceId"]);
771
+ expect(isDefectBody(reply.body)).toBe(true);
772
+ expect(reply.text.includes("secret")).toBe(false);
773
+ expect(reply.headers.get("x-request-id")).toBe(key);
774
+ expect(yield* receipts(tenant, "HttpRoom", "defect")).toBe(0);
775
+ })),
776
+ },
777
+ {
778
+ name: "takes the tenant only from the provider, and refuses a provider that returns a System caller",
779
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
780
+ const server = yield* serveHttp();
781
+ const tenant = yield* tenantOf;
782
+ const reply = yield* server.send(`/actors/HttpRoom/tenant/Whoami?tenant=other`, {
783
+ token: `${tenant}:alice`,
784
+ key: yield* server.mint(),
785
+ headers: { "x-tenant": "other", "durable-tenant": "other" },
786
+ });
787
+ expect(reply.body).toBe(`${tenant}/alice`);
788
+ const system = yield* serveHttp({ auth: systemCaller });
789
+ const before = runs.count;
790
+ const refused = yield* system.send("/actors/HttpRoom/tenant/Whoami", {
791
+ key: yield* server.mint(),
792
+ });
793
+ expect(refused.status).toBe(500);
794
+ expect(isDefectBody(refused.body)).toBe(true);
795
+ expect(runs.count).toBe(before);
796
+ })),
797
+ },
798
+ {
799
+ name: "serves a 512-byte subject and rejects 513 bytes and an encoded caller over 1 KiB",
800
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
801
+ const server = yield* serveHttp();
802
+ const tenant = yield* tenantOf;
803
+ const fits = "é".repeat(256);
804
+ const ok = yield* server.send("/actors/HttpRoom/subject/Whoami", {
805
+ token: `${tenant}:${fits}`,
806
+ key: yield* server.mint(),
807
+ });
808
+ expect(ok).toMatchObject({ status: 200, body: `${tenant}/${fits}` });
809
+ const before = runs.count;
810
+ for (const subject of [`${fits}x`, `"`.repeat(500)]) {
811
+ const reply = yield* server.send("/actors/HttpRoom/subject/Whoami", {
812
+ token: `${tenant}:${subject}`,
813
+ key: yield* server.mint(),
814
+ });
815
+ expect(reply.status).toBe(401);
816
+ expect(yield* reasonOf(reply.body)).toEqual({
817
+ tag: "Unauthorized",
818
+ code: "invalid_credentials",
819
+ });
820
+ }
821
+ expect(runs.count).toBe(before);
822
+ })),
823
+ },
824
+ {
825
+ name: "documents every served route and serves every documented one; the document is deterministic",
826
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
827
+ const options = { openapi: { path: "/openapi.json" } };
828
+ const server = yield* serveHttp(options);
829
+ const again = yield* serveHttp(options);
830
+ const token = `${yield* tenantOf}:alice`;
831
+ const first = yield* server.send("/openapi.json", { method: "GET" });
832
+ const second = yield* again.send("/openapi.json", { method: "GET" });
833
+ expect(first.status).toBe(200);
834
+ expect(first.text).toBe(second.text);
835
+ const spec = first.body;
836
+ expect(spec.openapi).toBe("3.1.0");
837
+ const operations = Object.entries(spec.paths).flatMap(([path, methods]) => Object.entries(methods).map(([method, operation]) => ({ path, method, operation })));
838
+ expect(operations.map(({ operation }) => operation.operationId).sort()).toEqual([
839
+ "HttpLobby.Join",
840
+ "HttpLobby.Leave",
841
+ "HttpRoom.Count",
842
+ "HttpRoom.Crash",
843
+ "HttpRoom.Hold",
844
+ "HttpRoom.Peek",
845
+ "HttpRoom.Post",
846
+ "HttpRoom.Whoami",
847
+ "HttpTally.Add",
848
+ "HttpTally.Bump",
849
+ "HttpTally.Snapshot",
850
+ "HttpTicket.Join",
851
+ "HttpWatched.Count",
852
+ "HttpWatched.Level",
853
+ "HttpWatched.Level.watch",
854
+ "HttpWatched.Post",
855
+ "durable.commandIds",
856
+ "durable.protocol",
857
+ "durable.ready",
858
+ "durable.uploadContent",
859
+ ].sort());
860
+ for (const { path, method, operation } of operations) {
861
+ const headers = operation.parameters.filter((parameter) => parameter.in === "header");
862
+ const isCommand = path.startsWith("/actors/") &&
863
+ !path.endsWith("/Count") &&
864
+ !path.endsWith("/Peek") &&
865
+ !path.endsWith("/Snapshot") &&
866
+ !path.endsWith("/Level") &&
867
+ !path.endsWith("/watch");
868
+ expect(headers.map((parameter) => parameter.name)).toEqual(isCommand ? ["idempotency-key"] : []);
869
+ expect(operation.security.length === 0).toBe(operation.operationId === "durable.protocol" ||
870
+ operation.operationId === "durable.ready");
871
+ if (path === "/actors/HttpRoom/{id}/Crash" || path === "/actors/HttpRoom/{id}/Hold")
872
+ continue;
873
+ if (path.endsWith("/watch")) {
874
+ expect(operation).toMatchObject({
875
+ "x-durable-transport": "sse",
876
+ "x-durable-element": { $ref: "#/components/schemas/HttpWatched.Level.element" },
877
+ });
878
+ continue;
879
+ }
880
+ const concrete = path.replace("{id}", path.includes("HttpTicket") ? yield* (yield* InternalActors).mintActorId : "doc");
881
+ const reply = yield* server.send(concrete, {
882
+ method: Option.getOrThrow(Option.liftPredicate(method.toUpperCase(), isMethod)),
883
+ token,
884
+ key: isCommand ? yield* server.mint() : undefined,
885
+ body: path.endsWith("/Post")
886
+ ? { text: "doc" }
887
+ : path.endsWith("/Add")
888
+ ? { by: 1 }
889
+ : undefined,
890
+ });
891
+ expect([concrete, reply.status < 300]).toEqual([concrete, true]);
892
+ }
893
+ })),
894
+ },
895
+ {
896
+ name: "documents a cookie provider's cookie as an apiKey scheme and authenticates by it",
897
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
898
+ const openapi = { path: "/openapi.json" };
899
+ const cookieOnly = yield* serveHttp({
900
+ auth: Auth.make({ authenticate: session, cookies: { name: "session" } }),
901
+ openapi,
902
+ });
903
+ const either = yield* serveHttp({
904
+ auth: Auth.make({
905
+ authenticate: (request) => request.cookies["session"] === undefined ? bearer(request) : session(request),
906
+ cookies: { name: "session" },
907
+ bearer: true,
908
+ }),
909
+ openapi,
910
+ });
911
+ const cookie = { type: "apiKey", in: "cookie", name: "session" };
912
+ expect(yield* securityOf(cookieOnly)).toEqual({
913
+ schemes: { cookie },
914
+ security: [[{ cookie: [] }]],
915
+ });
916
+ expect(yield* securityOf(either)).toEqual({
917
+ schemes: { bearer: { type: "http", scheme: "bearer" }, cookie },
918
+ security: [[{ bearer: [] }, { cookie: [] }]],
919
+ });
920
+ expect(yield* securityOf(yield* serveHttp({ openapi }))).toEqual({
921
+ schemes: { bearer: { type: "http", scheme: "bearer" } },
922
+ security: [[{ bearer: [] }]],
923
+ });
924
+ const tenant = yield* tenantOf;
925
+ const whoami = (server, init) => Effect.gen(function* () {
926
+ return yield* server.send("/actors/HttpRoom/cookie/Whoami", {
927
+ key: yield* server.mint(),
928
+ token: init.token,
929
+ headers: init.cookie === undefined ? {} : { cookie: init.cookie },
930
+ });
931
+ });
932
+ expect((yield* whoami(cookieOnly, { cookie: `theme=dark; session=${tenant}:alice` })).body).toBe(`${tenant}/alice`);
933
+ const bearerOnly = yield* whoami(cookieOnly, { token: `${tenant}:alice` });
934
+ expect(bearerOnly.status).toBe(401);
935
+ expect(bearerOnly.body).toEqual(yield* envelope(Unauthorized.make({ code: "missing_credentials" })));
936
+ expect((yield* whoami(either, { cookie: `session=${tenant}:carol` })).body).toBe(`${tenant}/carol`);
937
+ expect((yield* whoami(either, { token: `${tenant}:dave` })).body).toBe(`${tenant}/dave`);
938
+ const ignored = yield* whoami(yield* serveHttp({ auth: Auth.make(session) }), {
939
+ cookie: `session=${tenant}:erin`,
940
+ });
941
+ expect(ignored.status).toBe(401);
942
+ })),
943
+ },
944
+ {
945
+ name: "refuses a request whose Origin is neither the server's nor listed, and serves requests without Origin",
946
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
947
+ const server = yield* serveHttp({ origins: ["https://app.example.com"] });
948
+ const before = runs.count;
949
+ const refused = yield* server.send("/actors/HttpRoom/origin/Whoami", {
950
+ key: yield* server.mint(),
951
+ headers: { origin: "https://evil.example.com" },
952
+ });
953
+ expect(refused.status).toBe(403);
954
+ expect(yield* reasonOf(refused.body)).toEqual({
955
+ tag: "InvalidInput",
956
+ code: "origin_not_allowed",
957
+ });
958
+ expect(runs.count).toBe(before);
959
+ const token = `${yield* tenantOf}:alice`;
960
+ const listed = yield* server.send("/actors/HttpRoom/origin/Whoami", {
961
+ token,
962
+ key: yield* server.mint(),
963
+ headers: { origin: "https://app.example.com" },
964
+ });
965
+ expect(listed.status).toBe(200);
966
+ expect(listed.headers.get("access-control-allow-origin")).toBe("https://app.example.com");
967
+ expect(listed.headers.get("access-control-expose-headers")).toContain("x-request-id");
968
+ const same = yield* server.send("/actors/HttpRoom/origin/Whoami", {
969
+ token,
970
+ key: yield* server.mint(),
971
+ headers: { origin: server.url },
972
+ });
973
+ expect(same.status).toBe(200);
974
+ const otherScheme = yield* server.send("/actors/HttpRoom/origin/Whoami", {
975
+ token,
976
+ key: yield* server.mint(),
977
+ headers: { origin: server.url.replace(/^http:/, "https:") },
978
+ });
979
+ expect(otherScheme.status).toBe(403);
980
+ const forwarded = yield* server.send("/actors/HttpRoom/origin/Whoami", {
981
+ token,
982
+ key: yield* server.mint(),
983
+ headers: {
984
+ origin: server.url.replace(/^http:/, "https:"),
985
+ "x-forwarded-proto": "https",
986
+ },
987
+ });
988
+ expect(forwarded.status).toBe(403);
989
+ const bare = yield* server.send("/actors/HttpRoom/origin/Whoami", {
990
+ token,
991
+ key: yield* server.mint(),
992
+ });
993
+ expect(bare.status).toBe(200);
994
+ const preflight = yield* server.send("/actors/HttpRoom/origin/Whoami", {
995
+ method: "OPTIONS",
996
+ headers: { origin: "https://app.example.com" },
997
+ });
998
+ expect(preflight.status).toBe(204);
999
+ expect(preflight.headers.get("access-control-allow-headers")).toContain("idempotency-key");
1000
+ })),
1001
+ },
1002
+ {
1003
+ name: "rejects non-JSON and oversized bodies and credentials before any turn",
1004
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1005
+ const server = yield* serveHttp({ limits: { requestBytes: 64, credentialBytes: 128 } });
1006
+ const token = `${yield* tenantOf}:alice`;
1007
+ const before = runs.count;
1008
+ const text = yield* server.send("/actors/HttpRoom/body/Post", {
1009
+ token,
1010
+ key: yield* server.mint(),
1011
+ raw: "text=a",
1012
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1013
+ });
1014
+ expect(text.status).toBe(415);
1015
+ expect(yield* reasonOf(text.body)).toEqual({
1016
+ tag: "InvalidInput",
1017
+ code: "unsupported_media_type",
1018
+ });
1019
+ const large = yield* server.send("/actors/HttpRoom/body/Post", {
1020
+ token,
1021
+ key: yield* server.mint(),
1022
+ body: { text: "x".repeat(100) },
1023
+ });
1024
+ expect(large.status).toBe(413);
1025
+ const credential = yield* server.send("/actors/HttpRoom/body/Post", {
1026
+ token: `${token}${"x".repeat(200)}`,
1027
+ key: yield* server.mint(),
1028
+ body: { text: "a" },
1029
+ });
1030
+ expect(credential.status).toBe(413);
1031
+ const invalid = yield* server.send("/actors/HttpRoom/body/Post", {
1032
+ token,
1033
+ key: yield* server.mint(),
1034
+ body: { text: 5 },
1035
+ });
1036
+ expect(invalid.status).toBe(400);
1037
+ expect(invalid.body).toEqual(yield* envelope(InvalidInput.make({
1038
+ code: "decode",
1039
+ issues: [{ path: "text", message: "Invalid type" }],
1040
+ })));
1041
+ const utf8 = yield* server.send("/actors/HttpRoom/body/Post", {
1042
+ token,
1043
+ key: yield* server.mint(),
1044
+ bytes: new Uint8Array([
1045
+ 0x7b, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x3a, 0x22, 0xff, 0x22, 0x7d,
1046
+ ]),
1047
+ });
1048
+ expect(utf8.status).toBe(400);
1049
+ expect(yield* reasonOf(utf8.body)).toEqual({ tag: "InvalidInput", code: "decode" });
1050
+ const protocol = yield* server.send("/actors/HttpRoom/body/Post", {
1051
+ token,
1052
+ key: yield* server.mint(),
1053
+ body: { text: "a" },
1054
+ headers: { "durable-protocol": "2" },
1055
+ });
1056
+ expect(protocol.status).toBe(400);
1057
+ expect(yield* reasonOf(protocol.body)).toEqual({
1058
+ tag: "InvalidInput",
1059
+ code: "unsupported_protocol",
1060
+ });
1061
+ expect(runs.count).toBe(before);
1062
+ })),
1063
+ },
1064
+ {
1065
+ name: "refuses a host-built body that passes its declared content-length, or a content-length that is not a number, before any turn",
1066
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1067
+ const server = yield* serveHttp({ limits: { requestBytes: 64 } });
1068
+ const token = `${yield* tenantOf}:alice`;
1069
+ const before = runs.count;
1070
+ const post = (length, body) => Effect.gen(function* () {
1071
+ return yield* server.handle(new Request(`${server.url}/actors/HttpRoom/body/Post`, {
1072
+ method: "POST",
1073
+ headers: {
1074
+ authorization: `Bearer ${token}`,
1075
+ "idempotency-key": yield* server.mint(),
1076
+ "content-type": "application/json",
1077
+ "content-length": length,
1078
+ },
1079
+ body,
1080
+ }));
1081
+ });
1082
+ const huge = yield* post("5", `{"text":"${"x".repeat(3 * 1024 * 1024)}"}`);
1083
+ expect(huge.status).toBe(413);
1084
+ expect(yield* reasonOf(huge.body)).toEqual({ tag: "InvalidInput", code: "too_large" });
1085
+ const longer = yield* post("12", '{"text":"abcdefgh"}');
1086
+ expect(longer.status).toBe(413);
1087
+ expect(yield* reasonOf(longer.body)).toEqual({ tag: "InvalidInput", code: "too_large" });
1088
+ for (const length of ["abc", "-1", "1e1", ""]) {
1089
+ const malformed = yield* post(length, '{"text":"a"}');
1090
+ expect(malformed.status).toBe(400);
1091
+ expect(yield* reasonOf(malformed.body)).toEqual({ tag: "InvalidInput", code: "decode" });
1092
+ }
1093
+ const exact = yield* post("12", '{"text":"a"}');
1094
+ expect(exact.status).toBe(200);
1095
+ expect(runs.count).toBe(before + 1);
1096
+ })),
1097
+ },
1098
+ {
1099
+ name: "answers a query without Idempotency-Key, x-request-id, or durable-version, reading the primary for any durable-min-version without a replica",
1100
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1101
+ const server = yield* serveHttp();
1102
+ const token = `${yield* tenantOf}:alice`;
1103
+ yield* server.send("/actors/HttpRoom/query/Post", {
1104
+ token,
1105
+ key: yield* server.mint(),
1106
+ body: { text: "a" },
1107
+ });
1108
+ const reply = yield* server.send("/actors/HttpRoom/query/Count", {
1109
+ token,
1110
+ headers: { "durable-min-version": "99" },
1111
+ });
1112
+ expect(reply).toMatchObject({ status: 200, body: 1 });
1113
+ expect(reply.headers.get("x-request-id")).toBe(null);
1114
+ expect(reply.headers.get("durable-version")).toBe(null);
1115
+ expect(reply.headers.get("durable-protocol")).toBe("1");
1116
+ })),
1117
+ },
1118
+ {
1119
+ name: "fails Actor.serve at startup when retryWindowMs is below 60 seconds, and admits ids minted at exactly 60 seconds",
1120
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1121
+ const actors = yield* InternalActors;
1122
+ const short = InternalActors.of({ ...actors, retryWindowMs: 59_999 });
1123
+ const exit = yield* HttpRouter.toHttpEffect(serve({ actors: served, auth: tokens }).pipe(Layer.provide(Layer.succeed(InternalActors, short)))).pipe(Effect.exit);
1124
+ expect(Exit.isFailure(exit) && Cause.pretty(exit.cause)).toContain("at least 60 seconds");
1125
+ expect(actors.retryWindowMs).toBe(60_000);
1126
+ const server = yield* serveHttp();
1127
+ const tenant = yield* tenantOf;
1128
+ const reply = yield* server.send("/actors/HttpRoom/window/Whoami", {
1129
+ token: `${tenant}:alice`,
1130
+ key: yield* server.mint(0, 60_000),
1131
+ });
1132
+ expect(reply).toMatchObject({ status: 200, body: `${tenant}/alice` });
1133
+ })),
1134
+ },
1135
+ {
1136
+ name: "serves routes at the root for basePath /, and answers an undefined query output with 200 null",
1137
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1138
+ const server = yield* serveHttp({ basePath: "/" });
1139
+ const token = `${yield* tenantOf}:alice`;
1140
+ expect((yield* server.send("/protocol", { method: "GET" })).status).toBe(200);
1141
+ expect(yield* server.send("/actors/HttpRoom/peek/Peek", { token })).toMatchObject({
1142
+ status: 200,
1143
+ text: "null",
1144
+ });
1145
+ yield* server.send("/actors/HttpRoom/peek/Post", {
1146
+ token,
1147
+ key: yield* server.mint(),
1148
+ body: { text: "a" },
1149
+ });
1150
+ expect(yield* server.send("/actors/HttpRoom/peek/Peek", { token })).toMatchObject({
1151
+ status: 200,
1152
+ body: 1,
1153
+ });
1154
+ })),
1155
+ },
1156
+ {
1157
+ name: "ignores a trailing slash on basePath",
1158
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1159
+ const server = yield* serveHttp({ basePath: "/api/" });
1160
+ const token = `${yield* tenantOf}:alice`;
1161
+ expect((yield* server.send("/api/protocol", { method: "GET" })).status).toBe(200);
1162
+ expect(yield* server.send("/api/actors/HttpRoom/slash/Peek", { token })).toMatchObject({
1163
+ status: 200,
1164
+ text: "null",
1165
+ });
1166
+ })),
1167
+ },
1168
+ {
1169
+ name: "fails Actor.serve at startup when a member's operation id collides with a protocol route",
1170
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1171
+ const Durable = Actor.make("durable", {
1172
+ key: Actor.singleton,
1173
+ state: count,
1174
+ api: { protocol: Actor.query("protocol", { success: Schema.Int }) },
1175
+ });
1176
+ const exit = yield* HttpRouter.toHttpEffect(serve({ actors: [Durable], auth: tokens }).pipe(Layer.provide(Layer.succeed(InternalActors, yield* InternalActors)))).pipe(Effect.exit);
1177
+ expect(Exit.isFailure(exit) && Cause.pretty(exit.cause)).toContain("collides with a protocol operation id");
1178
+ })),
1179
+ },
1180
+ {
1181
+ name: "fails Actor.serve at startup when openapi.path collides with a protocol route",
1182
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1183
+ const internal = Layer.succeed(InternalActors, yield* InternalActors);
1184
+ for (const path of ["/protocol", "/command-ids", "/ready", "/actors/Room"]) {
1185
+ const exit = yield* HttpRouter.toHttpEffect(serve({ actors: [HttpRoom], auth: tokens, openapi: { path } }).pipe(Layer.provide(internal))).pipe(Effect.exit);
1186
+ expect(Exit.isFailure(exit) && Cause.pretty(exit.cause)).toContain(`openapi.path ${path} collides with a protocol route`);
1187
+ }
1188
+ })),
1189
+ },
1190
+ {
1191
+ name: "fails Actor.serve at startup when a provider declares two credentials of one OpenAPI scheme",
1192
+ run: ({ expect, environment }) => environment.run(Effect.gen(function* () {
1193
+ const internal = Layer.succeed(InternalActors, yield* InternalActors);
1194
+ for (const credentials of [
1195
+ [Credential.Cookie({ name: "a" }), Credential.Cookie({ name: "b" })],
1196
+ [Credential.Bearer(), Credential.Jwt()],
1197
+ ]) {
1198
+ const exit = yield* HttpRouter.toHttpEffect(serve({ actors: [HttpRoom], auth: { ...tokens, credentials } }).pipe(Layer.provide(internal))).pipe(Effect.exit);
1199
+ expect(Exit.isFailure(exit) && Cause.pretty(exit.cause)).toContain("more than one credential documented as the same OpenAPI scheme");
1200
+ }
1201
+ })),
1202
+ },
1203
+ {
1204
+ name: "documents a served fleet view as GET /fleet/{View}, durable.fleet.<View>, with its group-key filters, limit, and x-durable-transport sse",
1205
+ run: ({ expect }) => Effect.runPromise(Effect.sync(() => {
1206
+ const document = openApiDocument({
1207
+ api: buildServedApi({
1208
+ definitions: [],
1209
+ fleet: [{ name: "OrdersByStatus", groupBy: ["status", "region"] }],
1210
+ basePath: "/api",
1211
+ content: false,
1212
+ }),
1213
+ auth: tokens,
1214
+ title: "fleet",
1215
+ version: "1",
1216
+ });
1217
+ const operation = document.paths["/api/fleet/OrdersByStatus"]?.["get"];
1218
+ expect(operation?.operationId).toBe("durable.fleet.OrdersByStatus");
1219
+ expect(operation?.["x-durable-transport"]).toBe("sse");
1220
+ expect(operation?.parameters
1221
+ .filter((parameter) => parameter.in === "query")
1222
+ .map((parameter) => parameter.name)
1223
+ .sort()).toEqual(["limit", "region", "status"]);
1224
+ expect(Object.keys(operation?.responses["200"]?.content ?? {})).toEqual([
1225
+ "text/event-stream",
1226
+ ]);
1227
+ expect(document.paths["/api/fleet/OrdersByStatus"]?.["post"]).toBe(undefined);
1228
+ })),
1229
+ },
1230
+ ];
1231
+ /** The actors `serveHttp` serves. */
1232
+ export const httpSuite = {
1233
+ layer: () => httpLayer,
1234
+ };