@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
@@ -0,0 +1,47 @@
1
+ import "./implementation.mjs";
2
+ import "../Action.mjs";
3
+ import { Context, Effect, Layer, Schema } from "effect";
4
+ declare const Kept: Schema.Struct<{
5
+ readonly input: Schema.String;
6
+ readonly outcome: Schema.String;
7
+ readonly at: Schema.Finite;
8
+ }>;
9
+ type Kept = typeof Kept.Type;
10
+ declare const Idempotency_base: Context.ServiceClass<Idempotency, "effect-actions/Action/Idempotency", {
11
+ /** What was kept under `id`, or `undefined`; one kept longer than 24 hours may be either. */
12
+ readonly get: (id: string) => Effect.Effect<Kept | undefined>;
13
+ /** Keeps `kept` under `id`, replacing what was. */
14
+ readonly set: (id: string, kept: Kept) => Effect.Effect<void>;
15
+ }>;
16
+ /**
17
+ * Where the answers of calls given an idempotency key are kept, by the action's name and a
18
+ * SHA-256 digest in hex of the key and its caller's principal, `<action>/<digest>`, for 24 hours
19
+ * at least: what an implementation of an action naming `idempotencyKey` requires at startup.
20
+ * `layerIdempotency` keeps them in memory; a Durable Object's `make` and `handlers` provide one
21
+ * over its storage; another store, shared by several servers, implements `get` and `set`, keeping
22
+ * `Idempotency.Kept` at least `Idempotency.keptFor` milliseconds.
23
+ *
24
+ * **Gotchas**
25
+ *
26
+ * - Provide one per server, around the surfaces: an implementation built in another layer graph keeps its own.
27
+ * - A server waits for a repeat's first call itself, in memory: two servers sharing a store run a call each, the later answer kept.
28
+ */
29
+ export declare class Idempotency extends Idempotency_base {
30
+ /**
31
+ * What is kept of a call answered under its key: the digest of its input, its outcome as JSON,
32
+ * and when it was kept, in milliseconds since the epoch.
33
+ */
34
+ static readonly Kept: Schema.Struct<{
35
+ readonly input: Schema.String;
36
+ readonly outcome: Schema.String;
37
+ readonly at: Schema.Finite;
38
+ }>;
39
+ /** How long, in milliseconds, a kept answer is kept at least: 24 hours. */
40
+ static readonly keptFor: number;
41
+ }
42
+ /**
43
+ * `Idempotency` in the server's memory, each answer kept 24 hours after it was kept: lost when
44
+ * the server stops, and kept apart from every other server's.
45
+ */
46
+ export declare const layerIdempotency: Layer.Layer<Idempotency>;
47
+ export {};
@@ -0,0 +1,99 @@
1
+ import { CallerKeyed, principalOf } from "./caller.mjs";
2
+ import { InvalidInput } from "./errors.mjs";
3
+ import { declaredStatus, isTransientStatus } from "./status.mjs";
4
+ import { projectedErrors } from "./declared.mjs";
5
+ import { answered } from "./implementation.mjs";
6
+ import { canonicalInput } from "./canonical.mjs";
7
+ import { sha256Hex } from "./digest.mjs";
8
+ import { Cause, Clock, Context, Deferred, Effect, Exit, Layer, Option, Predicate, Schema } from "effect";
9
+ // src/contract/idempotency.ts
10
+ const Kept = Schema.Struct({
11
+ input: Schema.String,
12
+ outcome: Schema.String,
13
+ at: Schema.Finite
14
+ });
15
+ const keptFor = 864e5;
16
+ /**
17
+ * Where the answers of calls given an idempotency key are kept, by the action's name and a
18
+ * SHA-256 digest in hex of the key and its caller's principal, `<action>/<digest>`, for 24 hours
19
+ * at least: what an implementation of an action naming `idempotencyKey` requires at startup.
20
+ * `layerIdempotency` keeps them in memory; a Durable Object's `make` and `handlers` provide one
21
+ * over its storage; another store, shared by several servers, implements `get` and `set`, keeping
22
+ * `Idempotency.Kept` at least `Idempotency.keptFor` milliseconds.
23
+ *
24
+ * **Gotchas**
25
+ *
26
+ * - Provide one per server, around the surfaces: an implementation built in another layer graph keeps its own.
27
+ * - A server waits for a repeat's first call itself, in memory: two servers sharing a store run a call each, the later answer kept.
28
+ */
29
+ var Idempotency = class extends Context.Service()("effect-actions/Action/Idempotency") {
30
+ /**
31
+ * What is kept of a call answered under its key: the digest of its input, its outcome as JSON,
32
+ * and when it was kept, in milliseconds since the epoch.
33
+ */
34
+ static Kept = Kept;
35
+ /** How long, in milliseconds, a kept answer is kept at least: 24 hours. */
36
+ static keptFor = keptFor;
37
+ };
38
+ /**
39
+ * `Idempotency` in the server's memory, each answer kept 24 hours after it was kept: lost when
40
+ * the server stops, and kept apart from every other server's.
41
+ */
42
+ const layerIdempotency = Layer.sync(Idempotency)(() => {
43
+ const held = /* @__PURE__ */ new Map();
44
+ return Idempotency.of({
45
+ get: (id) => Effect.sync(() => held.get(id)),
46
+ set: (id, kept) => Effect.sync(() => {
47
+ held.delete(id);
48
+ held.set(id, kept);
49
+ for (const [oldest, { at }] of held) {
50
+ if (at > kept.at - keptFor) break;
51
+ held.delete(oldest);
52
+ }
53
+ })
54
+ });
55
+ });
56
+ /**
57
+ * `handle`, answering a call given `action`'s idempotency key as the first call with it and the
58
+ * same input was answered: from `store`, or, while that call runs, once it has.
59
+ */
60
+ const keyed = (action, store, handle) => {
61
+ const field = action.idempotencyKey;
62
+ if (field === void 0) return handle;
63
+ const Outcome = Schema.fromJsonString(Schema.toCodecJson(Schema.Exit(action.success, Schema.Union(projectedErrors(action)), Schema.Never)));
64
+ const encodeOutcome = Schema.encodeUnknownEffect(Outcome);
65
+ const decodeOutcome = Schema.decodeUnknownOption(Outcome);
66
+ const isTransientFailure = Schema.is(Schema.Union(projectedErrors(action).filter((error) => isTransientStatus(declaredStatus(error)))));
67
+ const running = /* @__PURE__ */ new Map();
68
+ const run = (id, key, digest, answer) => Effect.gen(function* () {
69
+ const first = running.get(id);
70
+ if (first !== void 0) {
71
+ yield* Deferred.await(first).pipe(Effect.withSpan("Idempotency.wait", { attributes: { "action.name": action.name } }));
72
+ return yield* run(id, key, digest, answer);
73
+ }
74
+ const claim = Deferred.makeUnsafe();
75
+ running.set(id, claim);
76
+ const release = Effect.andThen(Effect.sync(() => running.delete(id)), Deferred.succeed(claim, void 0));
77
+ return yield* Effect.gen(function* () {
78
+ const kept = yield* store.get(id);
79
+ if (kept !== void 0 && kept.input !== digest) return yield* new InvalidInput({ message: `The idempotency key ${key} of ${action.name} was given with another input (send a new key for another call)` });
80
+ const replayed = kept === void 0 ? Option.none() : decodeOutcome(kept.outcome);
81
+ if (Option.isSome(replayed)) return yield* replayed.value;
82
+ const exit = yield* Effect.exit(answer);
83
+ const json = Exit.isSuccess(exit) || exit.cause.reasons.every((reason) => Cause.isFailReason(reason) && !isTransientFailure(reason.error)) ? yield* Effect.option(encodeOutcome(exit)) : Option.none();
84
+ if (Option.isSome(json)) yield* store.set(id, {
85
+ input: digest,
86
+ outcome: json.value,
87
+ at: yield* Clock.currentTimeMillis
88
+ });
89
+ return yield* exit;
90
+ }).pipe(Effect.ensuring(release));
91
+ });
92
+ return (input, reporting) => {
93
+ const key = Predicate.hasProperty(input, field) ? input[field] : void 0;
94
+ if (!Predicate.isString(key)) return handle(input, reporting);
95
+ const answer = Effect.suspend(() => answered(handle(input, reporting)));
96
+ return Effect.flatMap(Effect.all([Effect.service(CallerKeyed).pipe(Effect.flatMap((named) => principalOf(action, named)), Effect.flatMap((principal) => sha256Hex(JSON.stringify([principal ?? null, key])))), Effect.flatMap(canonicalInput(action.input, input), sha256Hex)]), ([id, digest]) => run(`${action.name}/${id}`, key, digest, answer));
97
+ };
98
+ };
99
+ export { Idempotency, keyed, layerIdempotency };
@@ -1,27 +1,41 @@
1
- import { Anyone } from "./caller.mjs";
1
+ import { Anyone, Keyed } from "./caller.mjs";
2
2
  import { Refusal } from "./errors.mjs";
3
- import { Any } from "../Action.mjs";
4
- import { Context, Effect, Layer, Scope } from "effect";
3
+ import { AnyEventImplementation, EventOf } from "./feed.mjs";
4
+ import { Any } from "../Event.mjs";
5
+ import { Reporting } from "./progress.mjs";
6
+ import { Any as Any$1 } from "../Action.mjs";
7
+ import { Context, Effect, Layer, Scope, Stream } from "effect";
5
8
  /** Decoded values at the dispatch boundary every surface shares. */
6
- export type ErasedValue = Any["input"]["Type"];
9
+ export type ErasedValue = Any$1["input"]["Type"];
7
10
  /**
8
- * A handler, erased. Method extraction makes the argument bivariant: a surface selects the
9
- * action before it invokes a handler, so its decoded input is the matching handler input, and
10
- * handlers are not widened at their public API.
11
+ * A handler as written, erased: its answer, or the stream of an action answering one. Method
12
+ * extraction makes the argument bivariant: a surface selects the action before it invokes a
13
+ * handler, so its decoded input is the matching handler input, and handlers are not widened at
14
+ * their public API.
11
15
  */
12
- export type ErasedHandler<R> = {
13
- handle(input: ErasedValue): Effect.Effect<ErasedValue, ErasedValue, R>;
16
+ export type WrittenHandler<R> = {
17
+ handle(input: ErasedValue, reporting?: Reporting): Effect.Effect<ErasedValue, ErasedValue, R> | Stream.Stream<ErasedValue, ErasedValue, R>;
14
18
  }["handle"];
19
+ /**
20
+ * A handler behind its action's authentication, authorization and declared errors, as a surface
21
+ * calls it. For an action answering a stream it succeeds at once with the call's stream, which
22
+ * `elementsOf` reads, the refusals included. `reporting` is where its progress goes, ignored for
23
+ * an action declaring no `progress`, so a surface may hand one to every call.
24
+ */
25
+ export type ErasedHandler<R> = (input: ErasedValue, reporting?: Reporting) => Effect.Effect<ErasedValue, ErasedValue, R>;
15
26
  /**
16
27
  * Authorization for protected contracts: it refuses, and fails with nothing else. It runs only for
17
28
  * protected actions, after authentication and before the handler.
18
29
  */
19
- export type Authorize<A extends Any, R = never> = (action: A) => Effect.Effect<void, Refusal, R>;
20
- /** An implementation's authorizer, erased. */
21
- export type ErasedAuthorize = (action: Any) => Effect.Effect<void, unknown, unknown>;
22
- /** Each action of an implementation with its handler, as its builder made them. */
23
- export type Bound = ReadonlyArray<readonly [Any, ErasedHandler<unknown>]>;
24
- interface Memoized<S, E, R> {
30
+ export type Authorize<A extends Any$1, R = never> = (action: A) => Effect.Effect<void, Refusal, R>;
31
+ /** An authorization rule over contracts `C`, erased: it refuses, or lets the call run. */
32
+ export type ErasedAuthorize<C = Any$1> = (contract: C) => Effect.Effect<void, unknown, unknown>;
33
+ /** Each action of an implementation with its handler, as `dispatch` makes it. */
34
+ export type Bound = ReadonlyArray<readonly [Any$1, ErasedHandler<unknown>]>;
35
+ /** Each action of an implementation with its handler as written, as its builder made them. */
36
+ export type Written = ReadonlyArray<readonly [Any$1, WrittenHandler<unknown>]>;
37
+ /** A build shared by every layer an implementation reaches, through the one key it is provided under. */
38
+ export interface Memoized<S, E, R> {
25
39
  readonly key: Context.Key<S, S>;
26
40
  readonly layer: Layer.Layer<S, E, R>;
27
41
  }
@@ -34,7 +48,7 @@ interface Memoized<S, E, R> {
34
48
  * its handlers' builder, `RA` what its authorizer reads per request, and `EAX` and `RAX` the
35
49
  * failures and services of the authorizer's builder.
36
50
  */
37
- export declare class Implementation<A extends Any, RequestServices extends {
51
+ export declare class Implementation<A extends Any$1, RequestServices extends {
38
52
  readonly [name: string]: unknown;
39
53
  }, EX, RX, RA = never, EAX = never, RAX = never> {
40
54
  #private;
@@ -51,14 +65,14 @@ export declare class Implementation<A extends Any, RequestServices extends {
51
65
  /** Type-only: what building its authorizer needs. */
52
66
  readonly "~authorizeBuildServices": RAX;
53
67
  /** The contracts this implementation answers. */
54
- readonly actions: ReadonlyArray<A>;
55
- constructor(actions: ReadonlyArray<A>,
68
+ readonly actions: readonly A[];
69
+ constructor(actions: readonly A[],
56
70
  /** Builds each action paired with its handler: its own builder, or the one it shares. */
57
- handlers: Memoized<Bound, EX, RX>,
71
+ handlers: Memoized<Written, EX, RX>,
58
72
  /** Builds its authorizer. */
59
73
  authorizer: Memoized<ErasedAuthorize, EAX, RAX>);
60
74
  /** Select contracts while retaining their handlers, authorization and shared builder. */
61
- static share(actions: ReadonlyArray<Any>, app: AnyImplementation): AnyImplementation;
75
+ static share(actions: readonly Any$1[], app: AnyImplementation): AnyImplementation;
62
76
  /**
63
77
  * The key of what runs a call of `app`'s actions: its handlers' memoized layer's, which each
64
78
  * `implement` makes, so it is unique to the implementation. Every selection retains it.
@@ -74,7 +88,7 @@ export declare class Implementation<A extends Any, RequestServices extends {
74
88
  * The actions of `app` with their handlers, behind its authorizer, from the context
75
89
  * `layerOf(app)` provides.
76
90
  */
77
- static boundOf(app: AnyImplementation): Effect.Effect<Bound, never, unknown>;
91
+ static boundOf(app: AnyImplementation, keyed: Keyed | undefined): Effect.Effect<Bound, never, unknown>;
78
92
  /** `app`, if this copy of the module made it; another installed copy's cannot be read. */
79
93
  private static own;
80
94
  }
@@ -85,43 +99,55 @@ export declare class Implementation<A extends Any, RequestServices extends {
85
99
  * admits every implementation, while a value of this type owes `unknown`, which no surface can be
86
100
  * given.
87
101
  */
88
- export type AnyImplementation<A extends Any = Any> = Implementation<A, {
102
+ export type AnyImplementation<A extends Any$1 = Any$1> = Implementation<A, {
89
103
  readonly [name: string]: unknown;
90
104
  }, unknown, unknown, unknown, unknown, unknown>;
91
- /** What a surface serves: one implementation, or a list of them. */
92
- export type Served = AnyImplementation | ReadonlyArray<AnyImplementation>;
105
+ /** What a surface serves: one implementation, of actions or of events, or a list of them. */
106
+ export type Served = AnyImplementation | AnyEventImplementation | ReadonlyArray<AnyImplementation | AnyEventImplementation>;
107
+ /** What a surface serving no events takes: one action implementation, or a list of them. */
108
+ export type ServedActions = AnyImplementation | readonly AnyImplementation[];
93
109
  /** The implementations `S` stands for. */
94
110
  export type Member<S> = S extends ReadonlyArray<infer App> ? App : S;
95
- /** Options that may list actions of `A`, the shape every selecting surface's options share. */
96
- export interface Selection<A extends Any> {
97
- readonly actions?: ReadonlyArray<A> | undefined;
111
+ /**
112
+ * Options that may list actions and event types of `C`, the shape every selecting surface's
113
+ * options share.
114
+ */
115
+ export interface Selection<C extends Any$1 | Any> {
116
+ readonly actions?: readonly C[] | undefined;
98
117
  }
99
118
  /**
100
119
  * The actions options `O` select, of `All`: those its `actions` lists when it always lists
101
- * them, and every one otherwise. Options whose `actions` may be absent, being optional or
102
- * a union with options lacking it, select every action at run time, so they narrow nothing.
120
+ * them, none where it lists event types alone, and every one otherwise. Options whose `actions`
121
+ * may be absent, being optional or a union with options lacking it, select every action at run
122
+ * time, so they narrow nothing.
103
123
  */
104
- export type Selected<O, All extends Any> = O extends {
105
- readonly actions: ReadonlyArray<infer A extends Any>;
106
- } ? A : All;
124
+ export type Selected<O, All extends Any$1> = O extends {
125
+ readonly actions: ReadonlyArray<infer C>;
126
+ } ? Extract<C, Any$1> : All;
127
+ /** The event types options `O` select, of `All`, as `Selected` selects actions. */
128
+ export type SelectedEvents<O, All extends Any> = O extends {
129
+ readonly actions: ReadonlyArray<infer C>;
130
+ } ? Extract<C, Any> : All;
107
131
  /**
108
132
  * The middleware options `O` install, each a member of `L`: its `middleware` when always given,
109
133
  * an array of unknown length when it may be absent, which installs none or all of them, and
110
134
  * none otherwise.
111
135
  */
112
- export type MiddlewareOf<O, L extends ReadonlyArray<unknown>> = O extends {
136
+ export type MiddlewareOf<O, L extends readonly unknown[]> = O extends {
113
137
  readonly middleware: infer M extends L;
114
138
  } ? M : O extends {
115
139
  readonly middleware?: infer M extends L | undefined;
116
140
  } ? "middleware" extends keyof O ? ReadonlyArray<NonNullable<M>[number]> : [] : [];
117
141
  /** The actions options `O` select of the implementations `Apps` stand for. */
118
142
  export type SelectedOf<O, Apps extends Served> = Selected<O, ActionOf<Member<Apps>>>;
143
+ /** The event types options `O` select of the implementations `Apps` stand for. */
144
+ export type SelectedEventsOf<O, Apps extends Served> = SelectedEvents<O, EventOf<Member<Apps>>>;
119
145
  /**
120
- * The actions a client of options `O` offers methods for, of `All`: those `actions` may
146
+ * The actions or event types a client of options `O` offers, of `All`: those `actions` may
121
147
  * list, wherever some member of `O` may list them, as each is present whether the list is
122
148
  * given or not; every one where no member lists any.
123
149
  */
124
- export type Offered<O, All extends Any> = [Listing<O>] extends [never] ? All : Extract<All, Listing<O> extends ReadonlyArray<infer A extends Any> ? A : never>;
150
+ export type Offered<O, All extends Any$1 | Any> = [Listing<O>] extends [never] ? All : Extract<All, Listing<O> extends ReadonlyArray<infer C> ? C : never>;
125
151
  type Listing<O> = O extends unknown ? NonNullable<O["actions" & keyof O]> : never;
126
152
  /**
127
153
  * No key of any member of `O` beyond those of `Keys`, so a misspelled option is refused
@@ -139,7 +165,7 @@ export type OptionalUnless<O, Given> = {} extends O ? [options?: Given] : [optio
139
165
  export type ActionOf<App> = App extends {
140
166
  readonly actions: ReadonlyArray<infer A>;
141
167
  } ? A : never;
142
- type Matches<A extends Any, Listed extends Any> = Listed extends Any ? [A] extends [Listed] ? true : [Listed] extends [A] ? true : never : never;
168
+ type Matches<A extends Any$1, Listed extends Any$1> = Listed extends Any$1 ? [A] extends [Listed] ? true : [Listed] extends [A] ? true : never : never;
143
169
  /**
144
170
  * The actions of `App` a surface given the actions `Listed` may serve, as `select` selects
145
171
  * them by identity: an action whose type either is assignable to, or is assigned by, one
@@ -147,34 +173,34 @@ type Matches<A extends Any, Listed extends Any> = Listed extends Any ? [A] exten
147
173
  * actions, an erased one included, still counts, and another contract of a listed name, which
148
174
  * the surface leaves out beside an action it serves, does not.
149
175
  */
150
- export type Serving<App, Listed extends Any> = ActionOf<App> extends (infer A extends Any) ? A extends unknown ? [A] extends [Listed] ? A : string extends A["name"] ? A : [Extract<A["name"], Listed["name"]>] extends [never] ? never : [Matches<A, Listed>] extends [never] ? never : A : never : never;
176
+ export type Serving<App, Listed extends Any$1> = ActionOf<App> extends (infer A extends Any$1) ? A extends unknown ? [A] extends [Listed] ? A : string extends A["name"] ? A : [Extract<A["name"], Listed["name"]>] extends [never] ? never : [Matches<A, Listed>] extends [never] ? never : A : never : never;
151
177
  /**
152
178
  * What each implementation among `App` owes per request where a surface serving the actions
153
179
  * `Listed` serves it: its authorizer's services, and the handlers' of the actions it serves.
154
180
  */
155
- export type ServedRequest<App, Listed extends Any> = App extends unknown ? RequestOf<App, Serving<App, Listed>> : never;
181
+ export type ServedRequest<App, Listed extends Any$1> = App extends unknown ? RequestOf<App, Serving<App, Listed>> : never;
156
182
  /** The implementations among `App` holding an action of `Listed`: those a surface builds. */
157
- export type Holding<App, Listed extends Any> = App extends unknown ? [Serving<App, Listed>] extends [never] ? never : App : never;
158
- /** The protected members of a contract union. */
159
- export type Protected<A extends Any> = A extends unknown ? A["caller"] extends typeof Anyone ? never : A : never;
183
+ export type Holding<App, Listed extends Any$1> = App extends unknown ? [Serving<App, Listed>] extends [never] ? never : App : never;
184
+ /** The protected members of a contract union, of actions or of events. */
185
+ export type Protected<A extends Pick<Any$1, "caller">> = A extends unknown ? A["caller"] extends typeof Anyone ? never : A : never;
160
186
  type ServiceOf<K> = K extends Context.Key<infer I, unknown> ? I : never;
161
- type AuthenticationOf<A extends Any> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
187
+ type AuthenticationOf<A extends Any$1> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
162
188
  /**
163
189
  * Per-request requirements of `App`'s authorization and handler for each `A` it
164
190
  * implements: those of every name `A`'s may be, so a union or an erased name owes each it may
165
191
  * stand for.
166
192
  */
167
- export type RequestOf<App, A extends Any> = App extends {
193
+ export type RequestOf<App, A extends Any$1> = App extends {
168
194
  readonly "~request": infer R;
169
195
  readonly "~authorizeRequest": infer RA;
170
- } ? A extends Any ? [A["name"] & keyof R] extends [never] ? never : R[A["name"] & keyof R] | (A["caller"] extends typeof Anyone ? never : RA) | AuthenticationOf<A> : never : never;
171
- type OwnActions<App> = Extract<ActionOf<App>, Any>;
196
+ } ? A extends Any$1 ? [A["name"] & keyof R] extends [never] ? never : R[A["name"] & keyof R] | (A["caller"] extends typeof Anyone ? never : RA) | AuthenticationOf<A> : never : never;
197
+ type OwnActions<App> = Extract<ActionOf<App>, Any$1>;
172
198
  /**
173
199
  * What a surface serving the actions `Listed` of the implementations `App` fails with at startup:
174
200
  * their builders' and built authorizers' failures, an authorizer's only if a selected protected
175
201
  * action needs it.
176
202
  */
177
- export type BuildError<App, Listed extends Any = OwnActions<App>> = App extends {
203
+ export type BuildError<App, Listed extends Any$1 = OwnActions<App>> = App extends {
178
204
  readonly "~buildError": infer EX;
179
205
  readonly "~authorizeBuildError": infer EAX;
180
206
  } ? EX | ([Protected<Serving<App, Listed>>] extends [never] ? never : EAX) : never;
@@ -182,7 +208,7 @@ export type BuildError<App, Listed extends Any = OwnActions<App>> = App extends
182
208
  * What a surface serving the actions `Listed` of the implementations `App` reads at startup: their
183
209
  * builders' and built authorizers' services.
184
210
  */
185
- export type BuildServices<App, Listed extends Any = OwnActions<App>> = App extends {
211
+ export type BuildServices<App, Listed extends Any$1 = OwnActions<App>> = App extends {
186
212
  readonly "~buildServices": infer RX;
187
213
  readonly "~authorizeBuildServices": infer RAX;
188
214
  } ? RX | ([Protected<Serving<App, Listed>>] extends [never] ? never : RAX) : never;
@@ -1,10 +1,23 @@
1
- import { Anyone, assertAuthentication } from "./caller.mjs";
1
+ import { Anyone, CallerKeyed } from "./caller.mjs";
2
2
  import { Unauthenticated } from "./errors.mjs";
3
- import { assertDistinct, assertOnce, heldOnce } from "./names.mjs";
4
3
  import { projectedErrors } from "./declared.mjs";
5
- import { Array, Cause, Context, Effect, Exit, Layer, Option, Schema } from "effect";
4
+ import { checkInput } from "./input-check.mjs";
5
+ import { assertOnce, heldOnce } from "./names.mjs";
6
+ import { reportedTo, streamReportedTo } from "./progress.mjs";
7
+ import { elementOf } from "./stream.mjs";
8
+ import { Array, Cause, Context, Effect, Exit, Layer, Option, Predicate, References, Schema, Stream } from "effect";
6
9
  // src/contract/implementation.ts
7
10
  const uniqueKey = () => Math.random().toString(36).slice(2);
11
+ const notAStream = /* @__PURE__ */ new Error("A handler answered otherwise than its action's success declares");
12
+ /** The stream of a call of an action answering one, from its `ErasedHandler`'s answer. */
13
+ const elementsOf = (answer) => Stream.unwrap(Effect.filterOrElse(answer, Stream.isStream, () => Effect.die(notAStream)));
14
+ /** The answer of a handler of an action answering one value, a stream refused as a defect. */
15
+ const answered = (answer) => Stream.isStream(answer) ? Effect.die(notAStream) : answer;
16
+ /**
17
+ * `run` as a tool calls it, a tool answering one result: the elements of an action answering a
18
+ * stream collected, in order, into one array once the stream ends.
19
+ */
20
+ const answeringWhole = (action, run) => elementOf(action.success) === void 0 ? run : (input, reporting) => Stream.runCollect(elementsOf(run(input, reporting)));
8
21
  const memoized = (build) => {
9
22
  const key = Context.Service(`effect-actions/Implementation/${uniqueKey()}`);
10
23
  return {
@@ -13,6 +26,48 @@ const memoized = (build) => {
13
26
  };
14
27
  };
15
28
  /**
29
+ * The builders of an implementation of `contracts` as one layer: what its builder made, and its
30
+ * authorizer where a contract is protected, as only those need one.
31
+ */
32
+ const builderLayer = (contracts, built, authorizer) => contracts.some((contract) => contract.caller !== Anyone) ? Layer.merge(built.layer, authorizer.layer) : built.layer;
33
+ /** What a surface throws for an implementation some other copy of this module made. */
34
+ const notMadeHere = (module) => /* @__PURE__ */ new Error(`Not an implementation made by this ${module}.implement: pass the value implement returned, not a copy such as a spread; or effect-actions is installed twice`);
35
+ const asDefects = (reason, rejected) => {
36
+ const trace = Context.makeUnsafe(reason.annotations);
37
+ return [Cause.makeDieReason(reason.error).annotate(trace), Cause.makeDieReason(Cause.squash(rejected)).annotate(trace)];
38
+ };
39
+ const dispatch = (action, handle, before, keyed) => {
40
+ const actionAttributes = {
41
+ "action.name": action.name,
42
+ "action.read_only": action.readOnly
43
+ };
44
+ const authentication = action.caller === Anyone ? Effect.void : Effect.flatMap(Effect.serviceOption(action.caller), (actor) => Option.isSome(actor) ? Effect.void : Effect.fail(new Unauthenticated()));
45
+ const authorization = action.caller === Anyone ? Effect.void : Effect.suspend(() => before(action));
46
+ const validate = Schema.decodeUnknownEffect(Schema.toType(Schema.Union(projectedErrors(action))));
47
+ const declaredOrDefect = Effect.fnUntraced(function* (cause) {
48
+ const reasons = [];
49
+ for (const reason of cause.reasons) if (Cause.isFailReason(reason)) {
50
+ const validated = yield* Effect.exit(validate(reason.error));
51
+ reasons.push(...Exit.isSuccess(validated) ? [reason] : asDefects(reason, validated.cause));
52
+ } else reasons.push(reason);
53
+ return yield* Effect.failCause(Cause.fromReasons(reasons));
54
+ });
55
+ const admitted = (input) => Effect.andThen(authentication, Effect.andThen(checkInput(action, input), authorization));
56
+ const span = { attributes: actionAttributes };
57
+ const trace = { captureStackTrace: false };
58
+ if (elementOf(action.success) !== void 0) {
59
+ const streamed = (answer) => Stream.isStream(answer) ? Stream.scoped(answer) : Stream.die(notAStream);
60
+ return (input, sink) => Effect.succeed(Stream.unwrap(Effect.map(admitted(input), () => streamReportedTo(action.progress, sink, (reporting) => streamed(handle(input, reporting))))).pipe(Stream.catchCause((cause) => Stream.fromEffect(declaredOrDefect(cause))), Stream.updateService(References.CurrentLogAnnotations, (annotations) => ({
61
+ ...annotations,
62
+ ...actionAttributes
63
+ })), Stream.withSpan(action.name, {
64
+ ...span,
65
+ ...trace
66
+ })));
67
+ }
68
+ return (input, sink) => Effect.scoped(admitted(input).pipe(Effect.andThen(Effect.suspend(() => reportedTo(action.progress, sink, (reporting) => answered(handle(input, reporting))))), Effect.provideService(CallerKeyed, keyed), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, span, trace));
69
+ };
70
+ /**
16
71
  * Actions bound to their handlers and their authorizer: everything one `Action.implement` call
17
72
  * produced, one builder serving every surface. The private fields make this class nominal: a structurally similar object, including one made by
18
73
  * spreading an implementation, is not an implementation. Its type parameters may be written
@@ -50,28 +105,32 @@ var Implementation = class Implementation {
50
105
  */
51
106
  static layerOf(app) {
52
107
  const source = Implementation.own(app);
53
- return app.actions.some((action) => action.caller !== Anyone) ? Layer.merge(source.#handlers.layer, source.#authorizer.layer) : source.#handlers.layer;
108
+ return builderLayer(app.actions, source.#handlers, source.#authorizer);
54
109
  }
55
110
  /**
56
111
  * The actions of `app` with their handlers, behind its authorizer, from the context
57
112
  * `layerOf(app)` provides.
58
113
  */
59
- static boundOf(app) {
114
+ static boundOf(app, keyed) {
60
115
  const { actions } = app;
61
116
  const source = Implementation.own(app);
62
117
  return Effect.gen(function* () {
63
118
  const bound = yield* source.#handlers.key;
64
119
  const before = actions.some((action) => action.caller !== Anyone) ? yield* source.#authorizer.key : () => Effect.void;
65
- return actions.flatMap((action) => bound.flatMap(([held, handle]) => held === action ? [[action, dispatch(action, handle, before)]] : []));
120
+ return actions.flatMap((action) => bound.flatMap(([held, handle]) => held === action ? [[action, dispatch(action, handle, before, keyed)]] : []));
66
121
  });
67
122
  }
68
123
  /** `app`, if this copy of the module made it; another installed copy's cannot be read. */
69
124
  static own(app) {
70
- if (!(#handlers in app)) throw new Error("Not an implementation made by this Action.implement: pass the value implement returned, not a copy such as a spread; or effect-actions is installed twice");
125
+ if (!(#handlers in app)) throw notMadeHere("Action");
71
126
  return app;
72
127
  }
73
128
  };
74
- const toList = (served) => Array.ensure(served);
129
+ /**
130
+ * The action implementations among what a surface is given; `eventsOf` reads the event
131
+ * implementations, by their tag.
132
+ */
133
+ const toList = (served) => Array.ensure(served).filter((app) => !Predicate.isTagged(app, "EventImplementation"));
75
134
  const notHeld = {
76
135
  byImplementation: {
77
136
  why: "no implementation holds it",
@@ -94,15 +153,22 @@ const assertHeld = ({ why, fix, holder }, listed, held) => {
94
153
  throw new Error(`Listed in actions, but ${why}: ${markingOthers(missing, names)} (${fix}${sameName ? `; one marked (another contract) is a contract of that name ${holder}` : ""})`);
95
154
  }
96
155
  };
97
- const select = (apps, listed) => {
156
+ /**
157
+ * The implementations among `apps` holding what `listed` lists, each sharing its builder to hold
158
+ * those alone, or every one where it lists none: action and event implementations alike, by the
159
+ * contracts each `holds` and how it `share`s them.
160
+ */
161
+ const selectHeld = (apps, listed, holds, share) => {
98
162
  if (listed === void 0) return apps;
99
- assertHeld(notHeld.byImplementation, listed, apps.flatMap((app) => app.actions));
163
+ assertHeld(notHeld.byImplementation, listed, apps.flatMap(holds));
100
164
  return apps.flatMap((app) => {
101
- const own = app.actions.filter((action) => listed.includes(action));
165
+ const held = holds(app);
166
+ const own = held.filter((contract) => listed.includes(contract));
102
167
  if (own.length === 0) return [];
103
- return own.length === app.actions.length ? [app] : [Implementation.share(own, app)];
168
+ return own.length === held.length ? [app] : [share(own, app)];
104
169
  });
105
170
  };
171
+ const select = (apps, listed) => selectHeld(apps, listed, (app) => app.actions, (own, app) => Implementation.share(own, app));
106
172
  const servedBy = (what, bound, apps, listed) => {
107
173
  if (listed !== void 0) assertHeld(notHeld.byBinding, listed, bound);
108
174
  const held = apps.flatMap((app) => app.actions);
@@ -115,58 +181,26 @@ const servedBy = (what, bound, apps, listed) => {
115
181
  assertOnce("served action", actions, heldOnce);
116
182
  return served;
117
183
  };
118
- /**
119
- * What `ActionHttp.layer` and `ActionRpc.layer` serve, with the checks they share;
120
- * `authentication` is the binding's where a served action is protected.
121
- */
122
- const servingOf = (what, binding, served, options) => {
123
- const apps = servedBy(what, binding.actions, toList(served), options.actions);
124
- const actions = apps.flatMap((app) => app.actions);
125
- assertAuthentication(actions, binding.authentication);
126
- assertDistinct("middleware", options.middleware ?? [], (key) => key.key, { fix: "list each middleware once" });
127
- return {
128
- apps,
129
- actions,
130
- authentication: actions.some((action) => action.caller !== Anyone) ? binding.authentication : void 0
131
- };
132
- };
133
- const provideHandlers = (apps) => (layer) => {
134
- const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
184
+ /** Provide `builders`, implementations' builder layers, to a surface's layer. */
185
+ const provideBuilders = (builders) => (layer) => {
186
+ const [first, ...rest] = builders;
135
187
  return first === void 0 ? layer : Layer.provide(layer, Layer.mergeAll(first, ...rest));
136
188
  };
137
- const builders = (apps) => Layer.empty.pipe(provideHandlers(apps));
138
- const acquire = (apps) => Effect.map(Effect.forEach(apps, (app) => Implementation.boundOf(app)), (bound) => bound.flat());
139
- const built = (apps) => {
140
- const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
189
+ const provideHandlers = (apps) => provideBuilders(apps.map((app) => Implementation.layerOf(app)));
190
+ const acquire = (apps, keyed) => Effect.map(Effect.forEach(apps, (app) => Implementation.boundOf(app, keyed)), (bound) => bound.flat());
191
+ /**
192
+ * What `acquisition` reads, once `builders` are built into the layer graph around the caller, and
193
+ * held by its scope.
194
+ */
195
+ const builtWith = (builders, acquisition) => {
196
+ const [first, ...rest] = builders;
141
197
  return Effect.gen(function* () {
142
- if (first === void 0) return [];
198
+ if (first === void 0) return yield* acquisition;
143
199
  const current = yield* Effect.serviceOption(Layer.CurrentMemoMap);
144
200
  const memoMap = Option.getOrElse(current, Layer.makeMemoMapUnsafe);
145
201
  const scope = yield* Effect.scope;
146
202
  const context = yield* Layer.buildWithMemoMap(Layer.mergeAll(first, ...rest), memoMap, scope);
147
- return yield* Effect.provideContext(acquire(apps), context);
148
- });
149
- };
150
- const dispatch = (action, handle, before) => {
151
- const actionAttributes = {
152
- "action.name": action.name,
153
- "action.read_only": action.readOnly
154
- };
155
- const authentication = action.caller === Anyone ? Effect.void : Effect.flatMap(Effect.serviceOption(action.caller), (actor) => Option.isSome(actor) ? Effect.void : Effect.fail(new Unauthenticated()));
156
- const authorization = action.caller === Anyone ? Effect.void : Effect.suspend(() => before(action));
157
- const validate = Schema.decodeUnknownEffect(Schema.toType(Schema.Union(projectedErrors(action))));
158
- const asDefects = (reason, rejected) => {
159
- const trace = Context.makeUnsafe(reason.annotations);
160
- return [Cause.makeDieReason(reason.error).annotate(trace), Cause.makeDieReason(Cause.squash(rejected)).annotate(trace)];
161
- };
162
- const declaredOrDefect = Effect.fnUntraced(function* (cause) {
163
- const reasons = [];
164
- for (const reason of cause.reasons) if (Cause.isFailReason(reason)) {
165
- const validated = yield* Effect.exit(validate(reason.error));
166
- reasons.push(...Exit.isSuccess(validated) ? [reason] : asDefects(reason, validated.cause));
167
- } else reasons.push(reason);
168
- return yield* Effect.failCause(Cause.fromReasons(reasons));
203
+ return yield* Effect.provideContext(acquisition, context);
169
204
  });
170
- return (input) => Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(Effect.suspend(() => handle(input))), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, { attributes: actionAttributes }, { captureStackTrace: false }));
171
205
  };
172
- export { Implementation, acquire, assertHeld, builders, built, memoized, notHeld, provideHandlers, select, servingOf, toList };
206
+ export { Implementation, acquire, answered, answeringWhole, assertHeld, builderLayer, builtWith, elementsOf, markingOthers, memoized, notHeld, notMadeHere, provideBuilders, provideHandlers, select, selectHeld, servedBy, toList, uniqueKey };
@@ -0,0 +1,15 @@
1
+ import { Forbidden } from "./errors.mjs";
2
+ import { Any } from "../Event.mjs";
3
+ import { Any as Any$1 } from "../Action.mjs";
4
+ import { Context, Effect } from "effect";
5
+ /**
6
+ * A check every call and every subscription runs on its decoded input, on every surface, once the
7
+ * surface has verified its caller and before its authorizer: what a host provides, as a Durable
8
+ * Object refuses an input naming another object, to refuse an input it does not serve. Defaults
9
+ * to none.
10
+ *
11
+ * **Gotchas**
12
+ *
13
+ * - Provide it to the layer graph the surfaces are built in, as `Layer.succeed(Action.InputCheck)(check)`: provided per request, it misses a socket's later calls.
14
+ */
15
+ export declare const InputCheck: Context.Reference<(contract: Any$1 | Any, input: Any$1["input"]["Type"]) => Effect.Effect<void, Forbidden>>;
@@ -0,0 +1,16 @@
1
+ import { Context, Effect } from "effect";
2
+ // src/contract/input-check.ts
3
+ /**
4
+ * A check every call and every subscription runs on its decoded input, on every surface, once the
5
+ * surface has verified its caller and before its authorizer: what a host provides, as a Durable
6
+ * Object refuses an input naming another object, to refuse an input it does not serve. Defaults
7
+ * to none.
8
+ *
9
+ * **Gotchas**
10
+ *
11
+ * - Provide it to the layer graph the surfaces are built in, as `Layer.succeed(Action.InputCheck)(check)`: provided per request, it misses a socket's later calls.
12
+ */
13
+ const InputCheck = Context.Reference("effect-actions/Action/InputCheck", { defaultValue: () => () => Effect.void });
14
+ /** `contract`'s `input`, as the provided `InputCheck` judges it. */
15
+ const checkInput = (contract, input) => Effect.flatMap(Effect.service(InputCheck), (check) => check(contract, input));
16
+ export { InputCheck, checkInput };