@gjermundgaraba/effect-actions 0.20.0 → 0.21.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 (321) hide show
  1. package/README.md +19 -41
  2. package/dist/Action.d.mts +140 -46
  3. package/dist/Action.mjs +87 -42
  4. package/dist/ActionCli.d.mts +69 -33
  5. package/dist/ActionCli.mjs +51 -32
  6. package/dist/ActionHttp.d.mts +69 -35
  7. package/dist/ActionHttp.mjs +75 -23
  8. package/dist/ActionMcp.d.mts +56 -22
  9. package/dist/ActionMcp.mjs +94 -69
  10. package/dist/ActionRpc.d.mts +222 -55
  11. package/dist/ActionRpc.mjs +299 -46
  12. package/dist/ActionToolkit.d.mts +22 -5
  13. package/dist/ActionToolkit.mjs +36 -12
  14. package/dist/Authentication.d.mts +42 -10
  15. package/dist/Authentication.mjs +37 -30
  16. package/dist/Event.d.mts +370 -0
  17. package/dist/Event.mjs +103 -0
  18. package/dist/Testing.mjs +1 -1
  19. package/dist/Webhook.d.mts +146 -0
  20. package/dist/Webhook.mjs +72 -0
  21. package/dist/authentication/provider.d.mts +35 -10
  22. package/dist/authentication/provider.mjs +1 -1
  23. package/dist/authentication/refusal.mjs +2 -2
  24. package/dist/cli/commands.d.mts +9 -5
  25. package/dist/cli/commands.mjs +84 -33
  26. package/dist/contract/ast.mjs +4 -1
  27. package/dist/contract/authorizer.d.mts +8 -0
  28. package/dist/contract/authorizer.mjs +20 -0
  29. package/dist/contract/call.d.mts +9 -6
  30. package/dist/contract/call.mjs +48 -6
  31. package/dist/contract/callback.d.mts +7 -0
  32. package/dist/contract/callback.mjs +27 -0
  33. package/dist/contract/caller.d.mts +13 -2
  34. package/dist/contract/caller.mjs +22 -3
  35. package/dist/contract/canonical.mjs +10 -0
  36. package/dist/contract/codec.d.mts +9 -0
  37. package/dist/contract/codec.mjs +9 -0
  38. package/dist/contract/codes.mjs +81 -0
  39. package/dist/contract/declared.mjs +25 -9
  40. package/dist/contract/digest.mjs +6 -0
  41. package/dist/contract/errors.d.mts +9 -3
  42. package/dist/contract/errors.mjs +1 -1
  43. package/dist/contract/feed.d.mts +127 -0
  44. package/dist/contract/feed.mjs +249 -0
  45. package/dist/contract/hub.d.mts +94 -0
  46. package/dist/contract/hub.mjs +133 -0
  47. package/dist/contract/idempotency.d.mts +47 -0
  48. package/dist/contract/idempotency.mjs +99 -0
  49. package/dist/contract/implementation.d.mts +73 -47
  50. package/dist/contract/implementation.mjs +92 -58
  51. package/dist/contract/input-check.d.mts +15 -0
  52. package/dist/contract/input-check.mjs +16 -0
  53. package/dist/contract/lease.mjs +28 -0
  54. package/dist/contract/listed.d.mts +4 -0
  55. package/dist/contract/listed.mjs +22 -0
  56. package/dist/contract/log.d.mts +41 -0
  57. package/dist/contract/log.mjs +144 -0
  58. package/dist/contract/names.mjs +14 -4
  59. package/dist/contract/occurrence.d.mts +29 -0
  60. package/dist/contract/occurrence.mjs +45 -0
  61. package/dist/contract/pages.d.mts +13 -0
  62. package/dist/contract/pages.mjs +39 -0
  63. package/dist/contract/progress.d.mts +26 -0
  64. package/dist/contract/progress.mjs +72 -0
  65. package/dist/contract/push.d.mts +20 -0
  66. package/dist/contract/push.mjs +96 -0
  67. package/dist/contract/receiving.d.mts +29 -0
  68. package/dist/contract/receiving.mjs +42 -0
  69. package/dist/contract/recent.mjs +12 -0
  70. package/dist/contract/reported.mjs +16 -0
  71. package/dist/contract/serving.mjs +63 -0
  72. package/dist/contract/status.mjs +14 -5
  73. package/dist/contract/stream.d.mts +34 -0
  74. package/dist/contract/stream.mjs +31 -0
  75. package/dist/contract/subscribe.d.mts +45 -0
  76. package/dist/contract/subscribe.mjs +50 -0
  77. package/dist/contract/transient.mjs +28 -0
  78. package/dist/contract/watch.d.mts +18 -0
  79. package/dist/contract/watch.mjs +105 -0
  80. package/dist/contract/webhooks.d.mts +42 -0
  81. package/dist/contract/webhooks.mjs +53 -0
  82. package/dist/http/client.d.mts +37 -12
  83. package/dist/http/client.mjs +51 -12
  84. package/dist/http/events.d.mts +56 -0
  85. package/dist/http/events.mjs +102 -0
  86. package/dist/http/placed.d.mts +36 -0
  87. package/dist/http/placed.mjs +71 -0
  88. package/dist/http/schema-errors.mjs +1 -1
  89. package/dist/http/streamed.d.mts +21 -0
  90. package/dist/http/streamed.mjs +94 -0
  91. package/dist/http/watch.mjs +50 -0
  92. package/dist/mcp/bridge.mjs +283 -0
  93. package/dist/mcp/client.d.mts +30 -97
  94. package/dist/mcp/client.mjs +59 -132
  95. package/dist/mcp/events.mjs +142 -0
  96. package/dist/mcp/progress.mjs +34 -0
  97. package/dist/mcp/protocol.mjs +39 -13
  98. package/dist/mcp/reply.d.mts +79 -0
  99. package/dist/mcp/reply.mjs +99 -0
  100. package/dist/mcp/request.d.mts +40 -0
  101. package/dist/mcp/request.mjs +51 -0
  102. package/dist/mcp/watch.mjs +115 -0
  103. package/dist/rpc/watch.mjs +39 -0
  104. package/dist/stdio/console.mjs +13 -5
  105. package/dist/testing/memory.d.mts +1 -1
  106. package/dist/testing/memory.mjs +3 -3
  107. package/dist/webhook/address.mjs +47 -0
  108. package/dist/webhook/attempt.mjs +65 -0
  109. package/dist/webhook/receiver.mjs +67 -0
  110. package/dist/webhook/signing.mjs +28 -0
  111. package/dist/webhook/stored.mjs +57 -0
  112. package/dist/webhook/subscriptions.mjs +342 -0
  113. package/docs/Action-failures.md +2 -2
  114. package/docs/Action-streams.md +115 -0
  115. package/docs/Action.md +58 -124
  116. package/docs/ActionCli-failures.md +1 -2
  117. package/docs/ActionCli.md +83 -88
  118. package/docs/ActionHttp-failures.md +3 -3
  119. package/docs/ActionHttp.md +90 -70
  120. package/docs/ActionMcp-failures.md +2 -1
  121. package/docs/ActionMcp.md +120 -92
  122. package/docs/ActionRpc-events.md +126 -0
  123. package/docs/ActionRpc-failures.md +1 -2
  124. package/docs/ActionRpc.md +108 -79
  125. package/docs/ActionToolkit-failures.md +1 -2
  126. package/docs/ActionToolkit.md +33 -90
  127. package/docs/Authentication-advanced.md +52 -60
  128. package/docs/Authentication-failures.md +5 -4
  129. package/docs/Authentication.md +23 -58
  130. package/docs/Event-failures.md +15 -0
  131. package/docs/Event.md +281 -0
  132. package/docs/README.md +51 -82
  133. package/docs/Testing-failures.md +1 -5
  134. package/docs/Testing.md +126 -86
  135. package/docs/Webhook-failures.md +8 -0
  136. package/docs/Webhook.md +319 -0
  137. package/docs/setup-failures.md +2 -3
  138. package/docs/setup.md +32 -52
  139. package/examples/README.md +12 -37
  140. package/examples/assistant/README.md +21 -26
  141. package/examples/assistant/src/agent.ts +66 -24
  142. package/examples/assistant/src/cli.ts +7 -4
  143. package/examples/assistant/src/commands.ts +9 -4
  144. package/examples/assistant/src/identity/Identity.ts +5 -4
  145. package/examples/assistant/src/identity/authorize.ts +13 -5
  146. package/examples/assistant/src/identity/handlers.ts +3 -2
  147. package/examples/assistant/src/identity/permissions.ts +9 -3
  148. package/examples/assistant/src/platform/tasks-file.ts +15 -11
  149. package/examples/assistant/src/scripted.ts +7 -4
  150. package/examples/assistant/src/tasks/Tasks.ts +20 -6
  151. package/examples/assistant/src/tasks/handlers.ts +9 -5
  152. package/examples/assistant/src/tasks/store.ts +38 -11
  153. package/examples/assistant/src/toolkit.ts +11 -7
  154. package/examples/assistant/test/commands.test.ts +53 -27
  155. package/examples/assistant/test/identity/identity.test.ts +27 -11
  156. package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
  157. package/examples/assistant/test/tasks/tasks.test.ts +62 -34
  158. package/examples/assistant/test/toolkit.test.ts +104 -75
  159. package/examples/build-events/README.md +83 -0
  160. package/examples/build-events/src/Api.ts +27 -0
  161. package/examples/build-events/src/Rpc.ts +26 -0
  162. package/examples/build-events/src/builds/Builds.ts +108 -0
  163. package/examples/build-events/src/builds/events.ts +54 -0
  164. package/examples/build-events/src/builds/handlers.ts +58 -0
  165. package/examples/build-events/src/builds/log.ts +30 -0
  166. package/examples/build-events/src/builds/status.ts +28 -0
  167. package/examples/build-events/src/builds/store.ts +130 -0
  168. package/examples/build-events/src/cli.ts +18 -0
  169. package/examples/build-events/src/commands.ts +32 -0
  170. package/examples/build-events/src/deploys/Deploys.ts +44 -0
  171. package/examples/build-events/src/deploys/approvers.ts +55 -0
  172. package/examples/build-events/src/deploys/events.ts +32 -0
  173. package/examples/build-events/src/deploys/handlers.ts +38 -0
  174. package/examples/build-events/src/identity/Identity.ts +29 -0
  175. package/examples/build-events/src/identity/members.ts +7 -0
  176. package/examples/build-events/src/identity/verifier.ts +22 -0
  177. package/examples/build-events/src/notifier/Notices.ts +26 -0
  178. package/examples/build-events/src/notifier/routes.ts +71 -0
  179. package/examples/build-events/src/notifier.ts +48 -0
  180. package/examples/build-events/src/routes.ts +71 -0
  181. package/examples/build-events/src/server.ts +62 -0
  182. package/examples/build-events/test/builds/events.test.ts +68 -0
  183. package/examples/build-events/test/builds/log.test.ts +71 -0
  184. package/examples/build-events/test/builds/progress.test.ts +57 -0
  185. package/examples/build-events/test/builds/status.test.ts +42 -0
  186. package/examples/build-events/test/commands.test.ts +103 -0
  187. package/examples/build-events/test/deploys/events.test.ts +52 -0
  188. package/examples/build-events/test/http.test.ts +95 -0
  189. package/examples/build-events/test/notifier.test.ts +80 -0
  190. package/examples/build-events/test/routes.test.ts +96 -0
  191. package/examples/build-events/test/rpc.test.ts +84 -0
  192. package/examples/build-events/test/support/as.ts +8 -0
  193. package/examples/build-events/test/support/memory.ts +43 -0
  194. package/examples/build-events/test/webhooks.test.ts +143 -0
  195. package/examples/kitchen-sink/README.md +178 -166
  196. package/examples/kitchen-sink/src/Actions.ts +11 -3
  197. package/examples/kitchen-sink/src/Api.ts +12 -6
  198. package/examples/kitchen-sink/src/Rpc.ts +7 -5
  199. package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
  200. package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
  201. package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
  202. package/examples/kitchen-sink/src/admin/grants.ts +5 -3
  203. package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
  204. package/examples/kitchen-sink/src/agent.ts +14 -6
  205. package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
  206. package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
  207. package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
  208. package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
  209. package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
  210. package/examples/kitchen-sink/src/attachments/download.ts +7 -6
  211. package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
  212. package/examples/kitchen-sink/src/attachments/store.ts +25 -18
  213. package/examples/kitchen-sink/src/browser.ts +10 -4
  214. package/examples/kitchen-sink/src/cli.ts +28 -12
  215. package/examples/kitchen-sink/src/config.ts +1 -1
  216. package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
  217. package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
  218. package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
  219. package/examples/kitchen-sink/src/identity/directory.ts +16 -7
  220. package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
  221. package/examples/kitchen-sink/src/identity/members.ts +7 -2
  222. package/examples/kitchen-sink/src/identity/quota.ts +23 -20
  223. package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
  224. package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
  225. package/examples/kitchen-sink/src/issues/events.ts +46 -0
  226. package/examples/kitchen-sink/src/issues/export.ts +35 -0
  227. package/examples/kitchen-sink/src/issues/guide.ts +4 -3
  228. package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
  229. package/examples/kitchen-sink/src/issues/store.ts +156 -67
  230. package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
  231. package/examples/kitchen-sink/src/memory.ts +11 -2
  232. package/examples/kitchen-sink/src/ops.ts +10 -5
  233. package/examples/kitchen-sink/src/request-policy.ts +21 -9
  234. package/examples/kitchen-sink/src/routes.ts +67 -27
  235. package/examples/kitchen-sink/src/sdk.ts +22 -12
  236. package/examples/kitchen-sink/src/seed.ts +15 -9
  237. package/examples/kitchen-sink/src/server.ts +24 -10
  238. package/examples/kitchen-sink/src/stdio.ts +9 -3
  239. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
  240. package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
  241. package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
  242. package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
  243. package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
  244. package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
  245. package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
  246. package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
  247. package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
  248. package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
  249. package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
  250. package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
  251. package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
  252. package/examples/kitchen-sink/test/request-policy.test.ts +108 -47
  253. package/examples/kitchen-sink/test/routes.test.ts +102 -57
  254. package/examples/kitchen-sink/test/support/as.ts +9 -0
  255. package/examples/kitchen-sink/test/support/scripted.ts +6 -5
  256. package/examples/kitchen-sink/test/support/services.ts +17 -0
  257. package/examples/live-rpc/README.md +33 -35
  258. package/examples/live-rpc/src/Rpc.ts +9 -5
  259. package/examples/live-rpc/src/browser.ts +39 -11
  260. package/examples/live-rpc/src/chat/Chat.ts +30 -16
  261. package/examples/live-rpc/src/chat/authorize.ts +2 -1
  262. package/examples/live-rpc/src/chat/events.ts +74 -0
  263. package/examples/live-rpc/src/chat/handlers.ts +13 -5
  264. package/examples/live-rpc/src/chat/room.ts +9 -5
  265. package/examples/live-rpc/src/chat/store.ts +51 -14
  266. package/examples/live-rpc/src/client.ts +22 -5
  267. package/examples/live-rpc/src/identity/Identity.ts +3 -3
  268. package/examples/live-rpc/src/identity/verifier.ts +8 -4
  269. package/examples/live-rpc/src/page.ts +58 -12
  270. package/examples/live-rpc/src/routes.ts +14 -10
  271. package/examples/live-rpc/src/server.ts +15 -5
  272. package/examples/live-rpc/test/browser.test.ts +127 -50
  273. package/examples/live-rpc/test/chat/chat.test.ts +215 -23
  274. package/examples/live-rpc/test/routes.test.ts +130 -46
  275. package/examples/live-rpc/test/server.test.ts +94 -33
  276. package/examples/mcp-tools/README.md +24 -38
  277. package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
  278. package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
  279. package/examples/mcp-tools/src/printers/Printers.ts +18 -7
  280. package/examples/mcp-tools/src/printers/farm.ts +15 -5
  281. package/examples/mcp-tools/src/printers/handlers.ts +12 -6
  282. package/examples/mcp-tools/src/routes.ts +8 -6
  283. package/examples/mcp-tools/src/server.ts +12 -5
  284. package/examples/mcp-tools/src/stdio.ts +3 -2
  285. package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
  286. package/examples/mcp-tools/test/routes.test.ts +140 -81
  287. package/examples/notes-service/README.md +30 -45
  288. package/examples/notes-service/src/Api.ts +1 -0
  289. package/examples/notes-service/src/browser.ts +15 -14
  290. package/examples/notes-service/src/cli.ts +8 -3
  291. package/examples/notes-service/src/commands.ts +46 -20
  292. package/examples/notes-service/src/identity/Identity.ts +2 -2
  293. package/examples/notes-service/src/identity/authorize.ts +6 -2
  294. package/examples/notes-service/src/identity/handlers.ts +3 -2
  295. package/examples/notes-service/src/identity/users.ts +2 -1
  296. package/examples/notes-service/src/identity/verifier.ts +12 -6
  297. package/examples/notes-service/src/notes/Notes.ts +46 -22
  298. package/examples/notes-service/src/notes/handlers.ts +18 -9
  299. package/examples/notes-service/src/notes/store.ts +70 -43
  300. package/examples/notes-service/src/routes.ts +7 -5
  301. package/examples/notes-service/src/server.ts +12 -5
  302. package/examples/notes-service/src/status/Status.ts +1 -1
  303. package/examples/notes-service/src/status/handlers.ts +5 -3
  304. package/examples/notes-service/src/stdio.ts +7 -3
  305. package/examples/notes-service/src/worker.ts +6 -3
  306. package/examples/notes-service/test/commands.test.ts +164 -91
  307. package/examples/notes-service/test/notes/as.ts +3 -1
  308. package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
  309. package/examples/notes-service/test/notes/in-process.test.ts +26 -15
  310. package/examples/notes-service/test/notes/notes.test.ts +49 -31
  311. package/examples/notes-service/test/routes.test.ts +106 -71
  312. package/examples/notes-service/test/worker.test.ts +21 -13
  313. package/examples/quickstart/README.md +2 -6
  314. package/examples/quickstart/src/Api.ts +1 -0
  315. package/examples/quickstart/src/client.ts +6 -3
  316. package/examples/quickstart/src/greeting/Greeting.ts +1 -1
  317. package/examples/quickstart/src/greeting/handlers.ts +5 -2
  318. package/examples/quickstart/src/routes.ts +3 -2
  319. package/examples/quickstart/src/server.ts +6 -2
  320. package/examples/quickstart/test/routes.test.ts +3 -2
  321. package/package.json +10 -4
@@ -1,8 +1,15 @@
1
+ import { Codec } from "../contract/codec.mjs";
1
2
  import { BuiltIns } from "../contract/errors.mjs";
3
+ import { Any, Received, SubscriptionError, WatchOptions } from "../Event.mjs";
4
+ import "../contract/implementation.mjs";
2
5
  import { Call } from "../contract/call.mjs";
3
- import { Any, Errors } from "../Action.mjs";
4
- import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
5
- import { Effect, Schema } from "effect";
6
+ import { Answer, StreamOf } from "../contract/stream.mjs";
7
+ import "../contract/receiving.mjs";
8
+ import { WatchArgs, WatchRequirement } from "../contract/watch.mjs";
9
+ import { Any as Any$1, Errors } from "../Action.mjs";
10
+ import { Any as Any$2, VerifierError } from "../authentication/provider.mjs";
11
+ import { sentOf } from "./events.mjs";
12
+ import { Effect, Schema, Stream } from "effect";
6
13
  import { HttpClient, HttpClientError } from "effect/http";
7
14
  import { HttpApi, HttpApiClient } from "effect/http-api";
8
15
  /**
@@ -31,30 +38,48 @@ export interface Options<E = HttpClientError.HttpClientError, R = never> {
31
38
  readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient.With<E, R>) | undefined;
32
39
  }
33
40
  type BindingError = Errors[number];
41
+ type SseFailure = Extract<Stream.Error<HttpApiClient.SuccessType<ReturnType<typeof sentOf<Any>>>>, {
42
+ readonly _tag: "SseError" | "Retry";
43
+ }>;
44
+ type SseError = Extract<SseFailure, {
45
+ readonly _tag: "SseError";
46
+ }>;
34
47
  /**
35
48
  * What one call of `A` fails with: a declared error value (the action's own, one `E` of the
36
49
  * binding's, or a built-in `InvalidInput`, `Unauthenticated` or `Forbidden`), a native
37
50
  * `HttpClientError` for a failed request or an undeclared answer, or a `SchemaError` when
38
- * the success does not decode.
51
+ * the success does not decode. A call reading an event stream, of an action answering a stream or
52
+ * declaring `progress`, also fails with Effect's `Sse.SseError` or `Sse.Retry`.
39
53
  */
40
- export type MethodError<A extends Any, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | HttpClientError.HttpClientError | Schema.SchemaError;
41
- type Method<A extends Any, E extends BindingError, CE, CR> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E> | CE, CR>>;
54
+ export type MethodError<A extends Any$1, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | HttpClientError.HttpClientError | Schema.SchemaError | (A["progress"] extends undefined ? A["success"] extends StreamOf<Codec> ? SseFailure : never : SseFailure);
55
+ type Method<A extends Any$1, E extends BindingError, CE, CR> = Call<A, Answer<A["success"], MethodError<A, E> | CE, CR>>;
42
56
  /**
43
57
  * Any HTTP binding, with its actions erased: what `layer` and `client` read. The API is a native
44
58
  * constraint here because native groups are invariant in their endpoints; `actions` carries the
45
59
  * types.
46
60
  */
47
61
  export interface AnyHttp {
48
- readonly authentication?: Any$1 | undefined;
49
- readonly actions: ReadonlyArray<Any>;
50
- readonly error: ReadonlyArray<BindingError>;
62
+ readonly authentication?: Any$2 | undefined;
63
+ readonly actions: readonly Any$1[];
64
+ readonly events: readonly Any[];
65
+ readonly error: readonly BindingError[];
51
66
  readonly prefix: `/${string}`;
52
67
  readonly api: HttpApi.Constraint;
53
68
  }
69
+ /**
70
+ * What a watch of `W` over the binding `H` may fail with: what its route's first answer declares,
71
+ * a refusal ending it later, an `Event.SubscriptionError` where it is not served, a native
72
+ * `HttpClientError` that no reconnect outlasts, or a stream or an occurrence that does not decode.
73
+ */
74
+ export type WatchError<W extends Any, H extends AnyHttp> = SubscriptionError | BuiltIns["Type"] | H["error"][number]["Type"] | VerifierError<W, H["authentication"]>["Type"] | HttpClientError.HttpClientError | Schema.SchemaError | SseError;
75
+ type Watching<H extends AnyHttp, E, R> = [H["events"][number]] extends [never] ? unknown : {
76
+ readonly watch: <W extends H["events"][number], SE = never, const O extends WatchOptions<unknown> = {}>(event: W, ...args: WatchArgs<W, SE, O>) => Stream.Stream<Received<W>, WatchError<W, H> | E | SE, R | WatchRequirement<O>>;
77
+ };
54
78
  /**
55
79
  * Every action of the binding `H`, as `client.<action>(input)`: a protected one may also fail
56
- * with what its descriptor's verifier declares. Each call also fails with `E` and requires `R`,
57
- * what its `transformClient` adds.
80
+ * with what its descriptor's verifier declares. Where `H` lists event types, `watch(event, input,
81
+ * options)` subscribes to one. Each call and watch also fails with `E` and requires `R`, what its
82
+ * `transformClient` adds.
58
83
  */
59
- export type Client<H extends AnyHttp, E = never, R = never> = { readonly [A in H["actions"][number] as A["name"]]: Method<A, H["error"][number] | VerifierError<A, H["authentication"]>, E, R>; };
84
+ export type Client<H extends AnyHttp, E = never, R = never> = { readonly [A in H["actions"][number] as A["name"]]: Method<A, H["error"][number] | VerifierError<A, H["authentication"]>, E, R>; } & Watching<H, E, R>;
60
85
  export {};
@@ -1,18 +1,57 @@
1
- import { checked } from "../contract/call.mjs";
2
- import { Effect } from "effect";
3
- import { HttpClient } from "effect/http";
1
+ import { elementOf } from "../contract/stream.mjs";
2
+ import { checked, checkedStream, sendingValue, watchInput } from "../contract/call.mjs";
3
+ import { watching } from "../contract/watch.mjs";
4
+ import { readElements, readWithProgress } from "./streamed.mjs";
5
+ import { transportOf } from "./watch.mjs";
6
+ import { Effect, Stream } from "effect";
7
+ import { HttpClient, HttpClientRequest } from "effect/http";
4
8
  import { HttpApiClient } from "effect/http-api";
5
9
  // src/http/client.ts
6
- const methodsWith = (http, httpClient, baseUrl) => Effect.map(HttpApiClient.makeWith(http.api, {
10
+ const methodOf = (native, name, what) => {
11
+ const method = native[name];
12
+ if (method === void 0) throw new Error(`No client method for ${name} (use the binding \`ActionHttp.make\` returned, whose group holds this ${what})`);
13
+ return method;
14
+ };
15
+ const acceptingStreams = HttpClient.mapRequest(HttpClientRequest.setHeader("accept", "application/json, text/event-stream"));
16
+ const nativeOf = (http, httpClient, baseUrl) => HttpApiClient.makeWith(http.api, {
7
17
  baseUrl,
8
18
  httpClient
9
- }), (client) => {
10
- const native = client;
11
- return Object.fromEntries(http.actions.map((action) => {
12
- const method = native[action.name];
13
- if (method === void 0) throw new Error(`No client method for ${action.name} (use the binding \`ActionHttp.make\` returned, whose group holds this action)`);
14
- return [action.name, checked(action, (payload) => method({ payload }))];
15
- }));
19
+ });
20
+ const actionMethodOf = (action, native) => {
21
+ const method = methodOf(native, action.name, "action");
22
+ if (elementOf(action.success) !== void 0) return checkedStream(action, sendingValue, (payload, onProgress) => Stream.unwrap(Effect.map(method({ payload }), (answer) => readElements(answer, onProgress))));
23
+ if (action.progress === void 0) return checked(action, sendingValue, (payload) => method({ payload }));
24
+ return checked(action, sendingValue, (payload, onProgress) => Effect.flatMap(method({ payload }), (answer) => readWithProgress(answer, onProgress)));
25
+ };
26
+ /** The `Senders` of `http` over `httpClient`: its native clients, built once. */
27
+ const sendersWith = (http, httpClient, baseUrl) => Effect.gen(function* () {
28
+ const native = yield* nativeOf(http, acceptingStreams(httpClient), baseUrl);
29
+ const plain = yield* nativeOf(http, httpClient, baseUrl);
30
+ const polls = plain;
31
+ const streams = native;
32
+ const subscribes = plain;
33
+ const unsubscribes = plain;
34
+ const webhookOf = (name) => {
35
+ const subscribe = subscribes[`${name}/subscribe`];
36
+ const unsubscribe = unsubscribes[`${name}/unsubscribe`];
37
+ return subscribe === void 0 || unsubscribe === void 0 ? void 0 : {
38
+ subscribe,
39
+ unsubscribe
40
+ };
41
+ };
42
+ return {
43
+ method: (action) => actionMethodOf(action, native),
44
+ transport: (event, input) => transportOf(event, methodOf(polls, event.name, "event"), methodOf(streams, event.name, "event"), webhookOf(event.name), input)
45
+ };
46
+ });
47
+ const methodsWith = (http, httpClient, baseUrl) => Effect.map(sendersWith(http, httpClient, baseUrl), ({ method, transport }) => {
48
+ const actions = Object.fromEntries(http.actions.map((action) => [action.name, method(action)]));
49
+ if (http.events.length === 0) return actions;
50
+ const watch = (event, input, options) => Stream.unwrap(Effect.map(watchInput(event, input), ({ value }) => watching(transport(event, value), options)));
51
+ return {
52
+ ...actions,
53
+ watch
54
+ };
16
55
  });
17
56
  const methods = (http, { baseUrl, transformClient } = {}) => Effect.flatMap(HttpClient.HttpClient, (native) => transformClient === void 0 ? methodsWith(http, native, baseUrl) : methodsWith(http, transformClient(native), baseUrl));
18
- export { methods };
57
+ export { methods, sendersWith };
@@ -0,0 +1,56 @@
1
+ import "../contract/codec.mjs";
2
+ import { Forbidden, Unauthenticated } from "../contract/errors.mjs";
3
+ import { OccurrenceSchema } from "../contract/occurrence.mjs";
4
+ import "../contract/pages.mjs";
5
+ import "../contract/feed.mjs";
6
+ import { Any } from "../Event.mjs";
7
+ import "../contract/subscribe.mjs";
8
+ import "../Action.mjs";
9
+ import "../authentication/provider.mjs";
10
+ import "../contract/push.mjs";
11
+ import "../contract/webhooks.mjs";
12
+ import { Effect, Option, Schema, Stream } from "effect";
13
+ import { HttpApiEndpoint, HttpApiSchema } from "effect/http-api";
14
+ /**
15
+ * One poll's answer: the occurrences since the cursor, and when to ask again, sent as
16
+ * `nextPollMs`, its milliseconds.
17
+ */
18
+ export declare const pageOf: <E extends Any>(event: E) => Schema.encodeKeys<Schema.Struct<{
19
+ readonly events: Schema.$Array<OccurrenceSchema<E>>;
20
+ readonly cursor: Schema.NullOr<Schema.String>;
21
+ readonly truncated: Schema.Boolean;
22
+ readonly hasMore: Schema.Boolean;
23
+ readonly nextPoll: Schema.DurationFromMillis;
24
+ }>, {
25
+ readonly nextPoll: "nextPollMs";
26
+ }>;
27
+ /**
28
+ * What a subscription's event stream sends, each as one SSE `message`: a `Checked` with the
29
+ * position it starts from, or a `Gap` where that was no longer served, then each occurrence, a
30
+ * `Gap` where some were missed, a `Checked` every 30 seconds, and a `Failed` where reading
31
+ * failed and resumes. A later refusal ends it, as Effect's stream failure event.
32
+ */
33
+ export declare const sentOf: <E extends Any>(event: E) => HttpApiSchema.StreamSse<HttpApiSchema.SseEventFromData<Schema.toCodecJson<Schema.Union<readonly [OccurrenceSchema<E>, Schema.TaggedStruct<"Gap", {
34
+ readonly cursor: Schema.NullOr<Schema.String>;
35
+ }>, Schema.TaggedStruct<"Checked", {
36
+ readonly cursor: Schema.NullOr<Schema.String>;
37
+ }>, Schema.TaggedStruct<"Failed", {}>]>>>, Schema.Union<readonly [typeof Unauthenticated, typeof Forbidden]>, {
38
+ readonly _tag: "Gap";
39
+ readonly cursor: string | null;
40
+ } | {
41
+ readonly _tag: "Checked";
42
+ readonly cursor: string | null;
43
+ } | {
44
+ readonly _tag: "Failed";
45
+ } | Schema.Struct.View<{
46
+ readonly _tag: Schema.tag<"Occurrence">;
47
+ eventId: Schema.String;
48
+ name: Schema.Literal<E["name"]>;
49
+ timestamp: Schema.DateTimeUtc;
50
+ data: E["payload"];
51
+ cursor: Schema.NullOr<Schema.String>;
52
+ }, "Type", E["payload"] extends {
53
+ readonly "~type.optionality": "optional";
54
+ } ? "data" : never, E["payload"] extends {
55
+ readonly "~type.mutability": "mutable";
56
+ } ? "data" : never>>;
@@ -0,0 +1,102 @@
1
+ import { principalOf } from "../contract/caller.mjs";
2
+ import { Refusal } from "../contract/errors.mjs";
3
+ import { declared } from "../contract/status.mjs";
4
+ import { assertDistinctTags } from "../contract/declared.mjs";
5
+ import { OccurrenceOf } from "../contract/occurrence.mjs";
6
+ import { SubscriptionError } from "../Event.mjs";
7
+ import { webhookSubscriber } from "../contract/webhooks.mjs";
8
+ import { PushedOf, SubscriptionOf, queuedPush } from "../contract/push.mjs";
9
+ import { Subscribed, WebhookCancellationOf, WebhookSubscriptionOf } from "../contract/subscribe.mjs";
10
+ import { Effect, Schema, Stream } from "effect";
11
+ import { HttpServerRequest } from "effect/http";
12
+ import { HttpApiEndpoint, HttpApiSchema, OpenApi } from "effect/http-api";
13
+ // src/http/events.ts
14
+ /**
15
+ * One poll's answer: the occurrences since the cursor, and when to ask again, sent as
16
+ * `nextPollMs`, its milliseconds.
17
+ */
18
+ const pageOf = (event) => Schema.Struct({
19
+ events: Schema.Array(OccurrenceOf(event)),
20
+ cursor: Schema.NullOr(Schema.String),
21
+ truncated: Schema.Boolean,
22
+ hasMore: Schema.Boolean,
23
+ nextPoll: Schema.DurationFromMillis
24
+ }).pipe(Schema.encodeKeys({ nextPoll: "nextPollMs" }));
25
+ /**
26
+ * What a subscription's event stream sends, each as one SSE `message`: a `Checked` with the
27
+ * position it starts from, or a `Gap` where that was no longer served, then each occurrence, a
28
+ * `Gap` where some were missed, a `Checked` every 30 seconds, and a `Failed` where reading
29
+ * failed and resumes. A later refusal ends it, as Effect's stream failure event.
30
+ */
31
+ const sentOf = (event) => HttpApiSchema.StreamSse({
32
+ data: Schema.toCodecJson(PushedOf(event)),
33
+ error: Refusal
34
+ });
35
+ /** What a served subscription's event stream sends: each element's JSON, as `sentOf` encodes it. */
36
+ const servedSent = HttpApiSchema.StreamSse({
37
+ data: Schema.Json,
38
+ error: Refusal
39
+ });
40
+ /**
41
+ * The route of one event type at `path`, its poll answered as JSON and its stream as SSE, as
42
+ * `sent` declares it.
43
+ */
44
+ const eventEndpointOf = (event, path, errors, sent = sentOf(event)) => HttpApiEndpoint.post(event.name, path, {
45
+ payload: SubscriptionOf(event).pipe(HttpApiSchema.asJson()),
46
+ success: [pageOf(event), sent],
47
+ error: errors.map(declared)
48
+ }).annotate(OpenApi.Description, event.description);
49
+ /**
50
+ * The webhook routes of one protected event type at `path`: `/subscribe`, which starts or
51
+ * refreshes a subscription and answers its grant, and `/unsubscribe`, which ends it.
52
+ */
53
+ const webhookEndpointsOf = (event, path, errors) => {
54
+ const withWebhooks = [...errors, SubscriptionError];
55
+ assertDistinctTags(`ActionHttp binding of event "${event.name}"'s webhook routes`, withWebhooks);
56
+ const error = withWebhooks.map(declared);
57
+ return [HttpApiEndpoint.post(`${event.name}/subscribe`, `${path}/subscribe`, {
58
+ payload: WebhookSubscriptionOf(event).pipe(HttpApiSchema.asJson()),
59
+ success: Subscribed,
60
+ error
61
+ }).annotate(OpenApi.Description, `Deliver ${event.name} to a webhook, or refresh its grant.`), HttpApiEndpoint.post(`${event.name}/unsubscribe`, `${path}/unsubscribe`, {
62
+ payload: WebhookCancellationOf(event).pipe(HttpApiSchema.asJson()),
63
+ success: HttpApiSchema.NoContent,
64
+ error
65
+ }).annotate(OpenApi.Description, `Stop delivering ${event.name} to a webhook.`)];
66
+ };
67
+ /**
68
+ * The handlers of `feed`'s webhook routes, through `webhooks` where the host provided
69
+ * `Webhook.layer`; without it, both fail with `Unsupported`.
70
+ */
71
+ const webhookAnswers = (feed, authentication, webhooks) => {
72
+ const served = webhookSubscriber(feed, authentication, webhooks);
73
+ return {
74
+ subscribe: ({ payload }) => Effect.flatMap(served, ({ webhooks: service, ...subscriber }) => service.subscribe({
75
+ ...payload,
76
+ ...subscriber
77
+ })),
78
+ unsubscribe: ({ payload }) => Effect.flatMap(served, ({ webhooks: service, feed: subscribed, principal }) => service.unsubscribe({
79
+ ...payload,
80
+ feed: subscribed,
81
+ principal
82
+ }))
83
+ };
84
+ };
85
+ /**
86
+ * Answers a subscription's request to `feed`: a page, or, where it accepts `text/event-stream`,
87
+ * the subscription's stream, each element its JSON. Both admit the subscriber first, so a refusal
88
+ * is the route's error; `authentication`'s principal names a poll's subscriber, for its
89
+ * `subscription` hook.
90
+ */
91
+ const answering = (feed, authentication) => {
92
+ const encode = Schema.encodeEffect(Schema.toCodecJson(PushedOf(feed.event)));
93
+ return ({ payload: { input, cursor } }) => Effect.gen(function* () {
94
+ if (((yield* HttpServerRequest.HttpServerRequest).headers["accept"] ?? "").includes("text/event-stream")) {
95
+ const open = yield* feed.stream(input);
96
+ return Stream.fromQueue(yield* queuedPush(open, cursor, encode));
97
+ }
98
+ const subscriber = yield* principalOf(feed.event, authentication);
99
+ return yield* feed.poll(input, cursor, { subscriber });
100
+ });
101
+ };
102
+ export { answering, eventEndpointOf, pageOf, sentOf, servedSent, webhookAnswers, webhookEndpointsOf };
@@ -0,0 +1,36 @@
1
+ import { EventImplementation } from "../contract/feed.mjs";
2
+ import { Any } from "../Event.mjs";
3
+ import { Implementation } from "../contract/implementation.mjs";
4
+ import { Any as Any$1 } from "../Action.mjs";
5
+ import { AnyHttp } from "./client.mjs";
6
+ import { HttpClient } from "effect/http";
7
+ type Contract<H extends AnyHttp> = H["actions"][number] | H["events"][number];
8
+ /** A call's or a subscription's decoded input, beside its contract, one of `C`. */
9
+ /** What the contracts `C` require per request, by name: nothing. */
10
+ type Nothing<C extends Any$1 | Any> = { readonly [K in C["name"]]: never; };
11
+ /**
12
+ * What `placed` returns: an implementation of the binding's actions where it has some, and one of
13
+ * its event types where it has some, which require nothing.
14
+ */
15
+ export type Placed<H extends AnyHttp> = ReadonlyArray<Implementation<H["actions"][number], Nothing<H["actions"][number]>, never, never> | EventImplementation<H["events"][number], Nothing<H["events"][number]>, never, never>>;
16
+ /**
17
+ * The actions and event types of `binding` served where another server serves them, as a
18
+ * surface's implementations: each call and subscription sent on over `binding` with the client
19
+ * `client` answers for its decoded input and its contract, one of the binding's, carrying the
20
+ * caller's headers, and its answer, its errors and its occurrences passed back as they are. The
21
+ * surface serving them authenticates a protected one as its own and authorizes every caller; the
22
+ * server reached authenticates and authorizes again.
23
+ *
24
+ * **Gotchas**
25
+ *
26
+ * - The caller's credential is verified twice, by the surface serving these and by the server reached, so both are given the verifier: a credential expiring between the two is refused by the second.
27
+ * - The server reached builds its own verifier: one caching signing keys in memory fetches them in each of its instances, a Durable Object's again after each eviction.
28
+ * - Every header of the caller's request is sent on but its connection's, its body's, what it accepts and an MCP request's routing headers (`mcp-*`): one that must not reach the server reached is removed per contract by the client `client` returns, `HttpClient.mapRequest(HttpClientRequest.removeHeader("cookie"))`, which runs after them.
29
+ * - A header `client`'s client sets replaces the caller's of that name, as it is set after them: a service's own `authorization` is sent in place of the caller's.
30
+ * - A call with no HTTP request, as through `Action.client` in process or an MCP stdio surface, sends no header on: a protected one reaches the server reached with no credential, which refuses it with `Unauthenticated`.
31
+ * - An error the server reached declares beyond the action's own and the built-in ones, its binding's `error` or its verifier's, is a defect where it is passed back, answered as one.
32
+ * - A server reached that does not serve one of them, its route a 404, is a defect naming it, with the fix: a declared error whose status is 404 is passed back as it is.
33
+ * - A refusal of a subscription by the server reached comes after the surface admitted it, so it ends the subscription's stream where the surface's own would have answered its 401 or 403.
34
+ */
35
+ export declare const placed: <const H extends AnyHttp>(binding: H, client: (input: Contract<H>["input"]["Type"], contract: Contract<H>) => HttpClient.HttpClient) => Placed<H>;
36
+ export {};
@@ -0,0 +1,71 @@
1
+ import { Implementation, memoized } from "../contract/implementation.mjs";
2
+ import { EventImplementation } from "../contract/feed.mjs";
3
+ import { SubscriptionError, allowAll } from "../Event.mjs";
4
+ import { isNotFound } from "./watch.mjs";
5
+ import { sendersWith } from "./client.mjs";
6
+ import { allowAll as allowAll$1 } from "../Action.mjs";
7
+ import { Context, Effect, Option, Predicate, Schema, Stream } from "effect";
8
+ import { Headers, HttpClient, HttpClientRequest, HttpServerRequest } from "effect/http";
9
+ // src/http/placed.ts
10
+ const ownedByTheRequest = (name) => [
11
+ "host",
12
+ "connection",
13
+ "keep-alive",
14
+ "te",
15
+ "trailer",
16
+ "transfer-encoding",
17
+ "upgrade"
18
+ ].includes(name) || [
19
+ "content-",
20
+ "accept",
21
+ "proxy-",
22
+ "sec-websocket-",
23
+ "mcp-"
24
+ ].some((family) => name.startsWith(family));
25
+ const forwarded = Effect.map(Effect.serviceOption(HttpServerRequest.HttpServerRequest), Option.match({
26
+ onNone: () => Headers.empty,
27
+ onSome: ({ headers }) => Headers.fromInput(Object.entries(headers).filter(([name]) => !ownedByTheRequest(name)))
28
+ }));
29
+ var Sending = class extends Context.Service()("effect-actions/ActionHttp/placed/Sending") {};
30
+ const dispatching = HttpClient.makeWith((request) => Effect.flatMap(Effect.all([request, Effect.service(Sending)]), ([sent, client]) => client.execute(sent)), (request) => Effect.succeed(request));
31
+ const isSubscriptionError = Schema.is(SubscriptionError);
32
+ const isNotServed = Predicate.compose(isSubscriptionError, (error) => Predicate.isTagged(error.reason, "NotServed"));
33
+ const notServedThere = (name) => /* @__PURE__ */ new Error(`ActionHttp.placed: the server its client reaches does not serve ${name} (serve its implementation in that server's ActionHttp.layer of this binding)`);
34
+ /**
35
+ * The actions and event types of `binding` served where another server serves them, as a
36
+ * surface's implementations: each call and subscription sent on over `binding` with the client
37
+ * `client` answers for its decoded input and its contract, one of the binding's, carrying the
38
+ * caller's headers, and its answer, its errors and its occurrences passed back as they are. The
39
+ * surface serving them authenticates a protected one as its own and authorizes every caller; the
40
+ * server reached authenticates and authorizes again.
41
+ *
42
+ * **Gotchas**
43
+ *
44
+ * - The caller's credential is verified twice, by the surface serving these and by the server reached, so both are given the verifier: a credential expiring between the two is refused by the second.
45
+ * - The server reached builds its own verifier: one caching signing keys in memory fetches them in each of its instances, a Durable Object's again after each eviction.
46
+ * - Every header of the caller's request is sent on but its connection's, its body's, what it accepts and an MCP request's routing headers (`mcp-*`): one that must not reach the server reached is removed per contract by the client `client` returns, `HttpClient.mapRequest(HttpClientRequest.removeHeader("cookie"))`, which runs after them.
47
+ * - A header `client`'s client sets replaces the caller's of that name, as it is set after them: a service's own `authorization` is sent in place of the caller's.
48
+ * - A call with no HTTP request, as through `Action.client` in process or an MCP stdio surface, sends no header on: a protected one reaches the server reached with no credential, which refuses it with `Unauthenticated`.
49
+ * - An error the server reached declares beyond the action's own and the built-in ones, its binding's `error` or its verifier's, is a defect where it is passed back, answered as one.
50
+ * - A server reached that does not serve one of them, its route a 404, is a defect naming it, with the fix: a declared error whose status is 404 is passed back as it is.
51
+ * - A refusal of a subscription by the server reached comes after the surface admitted it, so it ends the subscription's stream where the surface's own would have answered its 401 or 403.
52
+ */
53
+ const placed = (binding, client) => {
54
+ const route = client;
55
+ const sendingTo = (input, contract) => HttpClient.mapRequestInputEffect(route(input, contract), (request) => Effect.map(forwarded, (headers) => HttpClientRequest.setHeaders(request, headers)));
56
+ const senders = sendersWith(binding, dispatching, void 0);
57
+ const handlerOf = ({ method }, action) => {
58
+ const send = method(action);
59
+ return (input, reporting) => {
60
+ const sent = send(input, ...reporting === void 0 ? [] : [{ onProgress: reporting.progress }]);
61
+ const sending = sendingTo(input, action);
62
+ return Stream.isStream(sent) ? sent.pipe(Stream.provideService(Sending, sending), Stream.catchIf(isNotFound, () => Stream.die(notServedThere(action.name)))) : sent.pipe(Effect.provideService(Sending, sending), Effect.catchIf(isNotFound, () => Effect.die(notServedThere(action.name))));
63
+ };
64
+ };
65
+ const runOf = ({ transport }, event) => ({
66
+ poll: (input, cursor) => transport(event, input).poll(cursor).pipe(Effect.provideService(Sending, sendingTo(input, event)), Effect.catchIf(isNotServed, () => Effect.die(notServedThere(event.name)))),
67
+ stream: (input, cursor) => transport(event, input).stream(cursor).pipe(Stream.provideService(Sending, sendingTo(input, event)), Stream.catchIf(isNotServed, () => Stream.die(notServedThere(event.name))))
68
+ });
69
+ return [...binding.actions.length === 0 ? [] : [new Implementation(binding.actions, memoized(Effect.map(senders, (built) => binding.actions.map((action) => [action, handlerOf(built, action)]))), memoized(Effect.succeed(allowAll$1)))], ...binding.events.length === 0 ? [] : [new EventImplementation(binding.events, binding.events.map((event) => [event, memoized(Effect.map(senders, (built) => runOf(built, event)))]), memoized(Effect.succeed(allowAll)))]];
70
+ };
71
+ export { placed };
@@ -4,5 +4,5 @@ import { HttpApiMiddleware } from "effect/http-api";
4
4
  // src/http/schema-errors.ts
5
5
  const responseEncodingKinds = /* @__PURE__ */ new Set(["Body", "ResponseHeaders"]);
6
6
  var SchemaErrors = class extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: [InvalidInput] }) {};
7
- const schemaErrors = () => HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(responseEncodingKinds.has(failure.kind) ? failure : InvalidInput.fromSchemaError(failure.cause)));
7
+ const schemaErrors = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(responseEncodingKinds.has(failure.kind) ? failure : InvalidInput.fromSchemaError(failure.cause)));
8
8
  export { SchemaErrors, schemaErrors };
@@ -0,0 +1,21 @@
1
+ import { Codec } from "../contract/codec.mjs";
2
+ import "../contract/progress.mjs";
3
+ import { ErasedValue } from "../contract/implementation.mjs";
4
+ import { StreamOf } from "../contract/stream.mjs";
5
+ import { Any } from "../Action.mjs";
6
+ import { Effect, Schema, Stream } from "effect";
7
+ import { HttpApiSchema } from "effect/http-api";
8
+ /** One event a route streams: a progress value, an element, or the answer it ends with. */
9
+ interface Sent {
10
+ readonly event: "progress" | "element" | "final";
11
+ readonly data: ErasedValue;
12
+ }
13
+ declare const eventOf: <const N extends Sent["event"], D extends Codec>(event: N, data: D) => Schema.Struct<{
14
+ readonly id: Schema.optional<Schema.String>;
15
+ readonly event: Schema.Literal<N>;
16
+ readonly data: Schema.fromJsonString<Schema.toCodecJson<D>>;
17
+ }>;
18
+ type EventOf<N extends Sent["event"], D extends Codec> = ReturnType<typeof eventOf<N, D>>;
19
+ /** The success of the endpoint of action `A` declaring errors `E`, as `ActionHttp` builds it. */
20
+ export type SuccessOf<A extends Any, E extends Schema.Top> = A["success"] extends StreamOf<infer Element> ? HttpApiSchema.StreamSse<Schema.Union<A["progress"] extends Codec ? readonly [EventOf<"element", Element>, EventOf<"progress", A["progress"]>] : readonly [EventOf<"element", Element>]>, E> : A["progress"] extends Codec ? Schema.toCodecJson<A["success"]> | HttpApiSchema.StreamSse<Schema.Union<readonly [EventOf<"progress", A["progress"]>, EventOf<"final", A["success"]>]>, E> : Schema.toCodecJson<A["success"]>;
21
+ export {};
@@ -0,0 +1,94 @@
1
+ import { Progressed, answerFrom, elementsFrom } from "../contract/progress.mjs";
2
+ import { elementsOf } from "../contract/implementation.mjs";
3
+ import { concealed } from "../contract/reported.mjs";
4
+ import { Cause, Effect, ErrorReporter, Queue, Schema, Stream } from "effect";
5
+ import { HttpServerRequest } from "effect/http";
6
+ import { HttpApiSchema } from "effect/http-api";
7
+ // src/http/streamed.ts
8
+ const eventOf = (event, data) => Schema.Struct({
9
+ id: Schema.optional(Schema.String),
10
+ event: Schema.Literal(event),
11
+ data: Schema.fromJsonString(Schema.toCodecJson(data))
12
+ });
13
+ /** What a route of an action declaring `progress` answers without its stream: `success`. */
14
+ const answeredWithProgress = (success) => HttpApiSchema.isNoContent(success.ast) ? HttpApiSchema.NoContent : success;
15
+ /**
16
+ * What a route of an action declaring `progress` answers a request accepting
17
+ * `text/event-stream`: a `progress` event for each value its handler reports, then a `final`
18
+ * event with its answer. A declared failure after the first event ends the stream as Effect's
19
+ * stream failure event, and a defect as one saying `Internal server error`.
20
+ */
21
+ const progressStreamOf = (progress, success, errors) => HttpApiSchema.StreamSse({
22
+ events: Schema.Union([eventOf("progress", progress), eventOf("final", success)]),
23
+ error: Schema.Union(errors)
24
+ });
25
+ /**
26
+ * What a route of an action answering a stream answers: an `element` event for each element,
27
+ * beside a `progress` event for each value its handler reports where it declares `progress`. A
28
+ * declared failure after the first event ends the stream as Effect's stream failure event, and
29
+ * a defect as one saying `Internal server error`.
30
+ */
31
+ const elementStreamOf = (element, progress, errors) => HttpApiSchema.StreamSse({
32
+ events: Schema.Union(progress === void 0 ? [eventOf("element", element)] : [eventOf("element", element), eventOf("progress", progress)]),
33
+ error: Schema.Union(errors)
34
+ });
35
+ const streamed = (answer) => Effect.gen(function* () {
36
+ const sent = yield* Queue.bounded(1);
37
+ let streaming = false;
38
+ const send = (event) => Effect.andThen(Queue.offer(sent, event), Effect.sync(() => {
39
+ streaming = true;
40
+ }));
41
+ const reporting = { progress: (data) => send({
42
+ event: "progress",
43
+ data
44
+ }) };
45
+ const ended = (cause) => {
46
+ if (Cause.hasDies(cause)) return concealed(cause);
47
+ return streaming ? Effect.andThen(ErrorReporter.report(cause), Effect.failCause(cause)) : Effect.failCause(cause);
48
+ };
49
+ yield* answer(reporting, send).pipe(Effect.catchCause(ended), Queue.into(sent), Effect.forkScoped({ startImmediately: true }));
50
+ const first = yield* Effect.catchIf(Queue.take(sent), Cause.isDone, () => Effect.undefined);
51
+ return first === void 0 ? Stream.empty : Stream.concat(Stream.make(first), Stream.fromQueue(sent));
52
+ });
53
+ /** A check of `schema` failing as a defect, inside the call's boundary. */
54
+ const checkedBy = (schema) => {
55
+ const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(schema));
56
+ return (data) => Effect.orDie(encode(data));
57
+ };
58
+ /**
59
+ * The route handler of an action declaring `progress`: its answer streamed with its progress
60
+ * where the request's `Accept` names `text/event-stream`, its answer alone otherwise.
61
+ */
62
+ const answeringWithProgress = (success, run) => {
63
+ const check = checkedBy(success);
64
+ return (payload) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => (request.headers["accept"] ?? "").includes("text/event-stream") ? streamed((reporting, send) => run(payload, reporting).pipe(Effect.tap(check), Effect.flatMap((data) => send({
65
+ event: "final",
66
+ data
67
+ })))) : run(payload));
68
+ };
69
+ /** The route handler of an action answering a stream: its elements and its progress, streamed. */
70
+ const answeringElements = (element, run) => {
71
+ const check = checkedBy(element);
72
+ return (payload) => streamed((reporting, send) => Stream.runForEach(elementsOf(run(payload, reporting)), (data) => Effect.andThen(check(data), send({
73
+ event: "element",
74
+ data
75
+ }))));
76
+ };
77
+ const progressedOf = ({ event, data }) => {
78
+ if (event === "progress") return Progressed.Progress({ value: data });
79
+ return event === "final" ? Progressed.Final({ value: data }) : Progressed.Element({ value: data });
80
+ };
81
+ const answeredWithout = /* @__PURE__ */ new Error("A route of an action declaring progress or answering a stream answered without its event stream");
82
+ /** The events of a route's answer, as one stream of `Progressed`. */
83
+ const sentFrom = (answer) => Stream.isStream(answer) ? Stream.map(answer, progressedOf) : Stream.die(answeredWithout);
84
+ /**
85
+ * A client's answer from a route of an action declaring `progress`: each `progress` event handed
86
+ * to `onProgress`, where the call gave one, then the `final` event's answer.
87
+ */
88
+ const readWithProgress = (answer, onProgress) => answerFrom(sentFrom(answer), onProgress);
89
+ /**
90
+ * A client's elements from a route of an action answering a stream: each `element` event's
91
+ * data, each `progress` event's handed to `onProgress`.
92
+ */
93
+ const readElements = (answer, onProgress) => elementsFrom(sentFrom(answer), onProgress);
94
+ export { answeredWithProgress, answeringElements, answeringWithProgress, elementStreamOf, progressStreamOf, readElements, readWithProgress };
@@ -0,0 +1,50 @@
1
+ import { fedOf } from "../contract/pages.mjs";
2
+ import { NotServed, SubscriptionError } from "../Event.mjs";
3
+ import { Receiving, webhookFed } from "../contract/receiving.mjs";
4
+ import { answeredStatus, failingWhenUnanswered, isTransient, receivedFrom } from "../contract/transient.mjs";
5
+ import { Effect, Predicate, Stream } from "effect";
6
+ import { HttpClientError } from "effect/http";
7
+ // src/http/watch.ts
8
+ /** Whether `error` is an HTTP client's failure on a 404 answer. */
9
+ const isNotFound = Predicate.compose(HttpClientError.isHttpClientError, (error) => answeredStatus(error) === 404);
10
+ /**
11
+ * The transport of one subscription to `event` over its route, `input` its decoded input: each
12
+ * page from `poll`, the stream from `stream`, and a webhook subscription from `webhook`'s routes
13
+ * where the binding has them, a 404 failing as `NotServed`.
14
+ */
15
+ const transportOf = (event, poll, stream, webhook, input) => {
16
+ const notServed = Effect.fail(SubscriptionError.of(event.name, new NotServed()));
17
+ const answered = (request) => request.pipe(failingWhenUnanswered, Effect.catchIf(isNotFound, () => notServed));
18
+ const webhooks = webhook === void 0 ? void 0 : {
19
+ receiver: Effect.service(Receiving),
20
+ subscribe: (request) => answered(webhook.subscribe({ payload: {
21
+ input,
22
+ ...request
23
+ } })),
24
+ unsubscribe: (request) => answered(webhook.unsubscribe({ payload: {
25
+ input,
26
+ ...request
27
+ } })),
28
+ read: webhookFed(event)
29
+ };
30
+ const read = (sent) => sent.pipe(Stream.catchIf(Predicate.isTagged("Retry"), () => Stream.empty), receivedFrom("HTTP", event.name));
31
+ return {
32
+ name: event.name,
33
+ poll: (cursor) => answered(poll({ payload: {
34
+ input,
35
+ cursor
36
+ } })),
37
+ stream: (cursor) => Stream.unwrap(answered(stream({ payload: {
38
+ input,
39
+ cursor
40
+ } })).pipe(Effect.map((answer) => Stream.isStream(answer) ? read(answer) : Stream.fromIterable(fedOf({
41
+ items: answer.events,
42
+ truncated: answer.truncated,
43
+ from: cursor,
44
+ to: answer.cursor ?? cursor
45
+ }))))),
46
+ webhook: webhooks,
47
+ transient: isTransient
48
+ };
49
+ };
50
+ export { isNotFound, transportOf };