@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
package/dist/Action.mjs CHANGED
@@ -1,20 +1,28 @@
1
- import { Anyone } from "./contract/caller.mjs";
1
+ import { Anyone, assertCaller } from "./contract/caller.mjs";
2
2
  import { BuiltIn, Forbidden, InvalidInput, Refusal, Unauthenticated } from "./contract/errors.mjs";
3
- import { assertKnown, assertName, assertOnce, heldOnce } from "./contract/names.mjs";
4
3
  import { assertDistinctTags, errorList, projectedErrors } from "./contract/declared.mjs";
5
- import { Implementation, builders, built, memoized, select, toList } from "./contract/implementation.mjs";
6
- import { inputOf } from "./contract/call.mjs";
4
+ import { InputCheck } from "./contract/input-check.mjs";
5
+ import { assertKnown, assertName, assertOnce, heldOnce } from "./contract/names.mjs";
6
+ import { progressSink } from "./contract/progress.mjs";
7
+ import { codecOf } from "./contract/codec.mjs";
8
+ import { Stream, elementOf } from "./contract/stream.mjs";
9
+ import { Implementation, acquire, builtWith, elementsOf, memoized, provideBuilders, toList, uniqueKey } from "./contract/implementation.mjs";
10
+ import { authorizerOf } from "./contract/authorizer.mjs";
11
+ import { inputOf, jsonRoundTrip, onProgressOf, watchInput } from "./contract/call.mjs";
12
+ import { acquireFeeds, eventsOf, feedBuilders } from "./contract/feed.mjs";
13
+ import { NotServed, SubscriptionError } from "./Event.mjs";
14
+ import { polledOn, queuedPush, readOn } from "./contract/push.mjs";
15
+ import { selectFrom } from "./contract/serving.mjs";
16
+ import { watching } from "./contract/watch.mjs";
7
17
  import { Image } from "./contract/media.mjs";
8
- import { Array as Array$1, Cause, Context, Effect, Predicate, Schema } from "effect";
18
+ import { Idempotency, keyed, layerIdempotency } from "./contract/idempotency.mjs";
19
+ import { Array as Array$1, Cause, Effect, Function, Layer, Predicate, Schema, Stream as Stream$1 } from "effect";
9
20
  // src/contract/Action.ts
10
- const NoInput = Schema.Record(Schema.String, Schema.Never);
11
- const codecOf = (schema) => Schema.isSchema(schema) ? schema : Reflect.ownKeys(schema).length === 0 ? NoInput : Schema.Struct(schema);
12
21
  function make(name, options) {
13
22
  assertName(name);
14
23
  const { caller, readOnly } = options;
15
- if (caller !== Anyone && !Context.isKey(caller)) throw new Error("Missing caller: declare Action.Anyone or an identity service key");
16
- if (caller !== Anyone && Context.isReference(caller)) throw new Error("Invalid caller: an identity is a Context.Service, not a Context.Reference");
17
- if (readOnly !== true && readOnly !== false) throw new Error(`Invalid readOnly: ${String(readOnly)} (set true or false)`);
24
+ assertCaller(caller);
25
+ if (!Predicate.isBoolean(readOnly)) throw new Error(`Invalid readOnly: ${String(readOnly)} (set true or false)`);
18
26
  const action = {
19
27
  caller,
20
28
  name,
@@ -22,72 +30,109 @@ function make(name, options) {
22
30
  input: codecOf(options.input ?? {}),
23
31
  success: options.success === void 0 ? Schema.Void : codecOf(options.success),
24
32
  error: errorList(options.error),
33
+ progress: options.progress === void 0 ? void 0 : codecOf(options.progress),
25
34
  readOnly,
26
- mcp: { ...options.mcp }
35
+ mcp: { ...options.mcp },
36
+ idempotencyKey: options.idempotencyKey
27
37
  };
38
+ if (action.idempotencyKey !== void 0 && elementOf(action.success) !== void 0) throw new Error(`Action "${name}" answers a stream, so it takes no idempotencyKey (a kept answer is whole: answer a value, or remove the key)`);
28
39
  assertDistinctTags(`Action "${action.name}"`, projectedErrors(action));
29
40
  return action;
30
41
  }
42
+ const appendErrors = (action, more) => {
43
+ const error = [...action.error, ...more];
44
+ const appended = {
45
+ ...action,
46
+ error
47
+ };
48
+ assertDistinctTags(`Action "${action.name}"`, projectedErrors(appended));
49
+ return appended;
50
+ };
51
+ /**
52
+ * `action` with the errors `more` declared after its own, as if `make` had been given them in
53
+ * `error`: its handler may fail with them, and every surface answers and every client decodes
54
+ * them. For errors a module adds to the actions it serves, such as a proxy's or a store's,
55
+ * whose contracts are written elsewhere: `pipe(action, Action.withErrors([Unavailable]))`, or
56
+ * `Action.withErrors(action, [Unavailable])`.
57
+ *
58
+ * **Gotchas**
59
+ *
60
+ * - It returns a new contract with the same name: implement, bind and list the value it returns, since surfaces match a contract by identity; the original and the copy in one surface are one name held twice.
61
+ * - A `_tag` that `more` shares with the action's own errors or a built-in one throws as `make` throws for one `error` list: reuse the one schema value, which is declared once.
62
+ */
63
+ const withErrors = Function.dual(2, appendErrors);
31
64
  function byName(actions) {
32
65
  assertOnce("action", actions);
33
66
  return Object.fromEntries(actions.map((action) => [action.name, action]));
34
67
  }
35
68
  /** Every authenticated caller may call a protected action. */
36
69
  const allowAll = () => Effect.void;
37
- const assertAuthorizer = (before) => {
38
- if (!Predicate.isFunction(before)) throw new Error("Missing authorize: not a function (pass your rule, Action.allowAll, or an Effect returning one)");
39
- return before;
40
- };
41
- const assertAuthorization = (authorize) => {
42
- if (authorize === void 0) throw new Error("Protected actions require authorize, or Action.allowAll");
43
- return authorize;
44
- };
45
- const assertNoAuthorization = (authorize) => {
46
- if (authorize !== void 0) throw new Error("A public-only target takes no authorize (remove it, or list a protected action)");
47
- return allowAll;
48
- };
49
- const authorizerOf = (before) => memoized(Effect.isEffect(before) ? Effect.map(before, assertAuthorizer) : Effect.succeed(assertAuthorizer(before)));
50
70
  function implement(actions, handlers, options) {
51
71
  const listed = Array$1.ensure(actions);
52
72
  const names = listed.map((action) => action.name);
53
73
  assertOnce("action", listed);
54
- const authorizer = authorizerOf(listed.some((action) => action.caller !== Anyone) ? assertAuthorization(options?.authorize) : assertNoAuthorization(options?.authorize));
74
+ const authorizer = authorizerOf({
75
+ singular: "action",
76
+ module: "Action"
77
+ }, listed.some((action) => action.caller !== Anyone), options?.authorize);
55
78
  const pairWithActions = (built) => {
56
- const keyed = Predicate.isFunction(built) ? Object.fromEntries(Array.isArray(actions) ? [] : listed.map(({ name }) => [name, built])) : built;
57
- assertKnown("handlers", Object.keys(keyed), names, "remove it, or list its action");
79
+ const handlersByName = Predicate.isFunction(built) ? Object.fromEntries(Array.isArray(actions) ? [] : listed.map(({ name }) => [name, built])) : built;
80
+ assertKnown("handlers", Object.keys(handlersByName), names, "remove it, or list its action");
58
81
  const bound = listed.flatMap((action) => {
59
- const handle = Object.hasOwn(keyed, action.name) ? keyed[action.name] : void 0;
82
+ const handle = Object.hasOwn(handlersByName, action.name) ? handlersByName[action.name] : void 0;
60
83
  return Predicate.isFunction(handle) ? [[action, handle]] : [];
61
84
  });
62
85
  const missing = names.filter((name) => !bound.some(([action]) => action.name === name));
63
86
  if (missing.length > 0) throw new Error(`Missing handlers: ${missing.join(", ")} (return the handler for one action, or a record with an own function per listed action)`);
64
87
  return bound;
65
88
  };
66
- const recorded = Effect.isEffect(handlers) ? Effect.map(handlers, pairWithActions) : Effect.succeed(pairWithActions(handlers));
89
+ const paired = Effect.isEffect(handlers) ? Effect.map(handlers, pairWithActions) : Effect.succeed(pairWithActions(handlers));
90
+ const recorded = listed.some((action) => action.idempotencyKey !== void 0) ? Effect.flatMap(paired, (written) => Effect.map(Effect.service(Idempotency), (store) => written.map(([action, handle]) => [action, keyed(action, store, handle)]))) : paired;
67
91
  return new Implementation(listed, memoized(recorded), authorizer);
68
92
  }
69
93
  function layer(implementations) {
70
- return builders(toList(implementations));
94
+ return Layer.empty.pipe(provideBuilders([...toList(implementations).map((app) => Implementation.layerOf(app)), ...feedBuilders(eventsOf(implementations))]));
71
95
  }
72
- const jsonRoundTrip = (schema) => {
73
- const codec = Schema.fromJsonString(Schema.toCodecJson(schema));
74
- const encode = Schema.encodeUnknownEffect(codec, { errors: "all" });
75
- const decode = Schema.decodeUnknownEffect(codec, { errors: "all" });
76
- return (value) => Effect.flatMap(encode(value), (encoded) => decode(encoded));
96
+ const received = (schema) => {
97
+ const sent = jsonRoundTrip(schema);
98
+ return (value) => Effect.map(sent(value), (roundTripped) => roundTripped.value);
77
99
  };
78
100
  const mapFailuresInPlace = (cause, failure) => Effect.map(Effect.forEach(cause.reasons, (reason) => Cause.isFailReason(reason) ? Effect.match(failure(reason.error), {
79
101
  onFailure: (refused) => Cause.combine(Cause.die(refused), Cause.die(reason.error)),
80
102
  onSuccess: (decoded) => Cause.fail(Predicate.isError(reason.error) && Predicate.isError(decoded) ? Object.assign(decoded, { stack: reason.error.stack }) : decoded)
81
103
  }).pipe(Effect.map((mapped) => Cause.annotate(mapped, Cause.reasonAnnotations(reason)))) : Effect.succeed(Cause.fromReasons([reason]))), (causes) => Cause.fromReasons(causes.flatMap(({ reasons }) => reasons)));
82
104
  const methodOf = (action, run) => {
83
- const input = jsonRoundTrip(action.input);
84
- const success = jsonRoundTrip(action.success);
85
- const failure = jsonRoundTrip(Schema.Union(projectedErrors(action)));
86
- return (...args) => inputOf(action, args).pipe(Effect.flatMap(input), Effect.mapError(InvalidInput.fromSchemaError), Effect.flatMap((value) => Effect.catchCause(run(value), (cause) => Effect.flatMap(mapFailuresInPlace(cause, failure), Effect.failCause))), Effect.flatMap((value) => Effect.orDie(success(value))));
105
+ const input = received(action.input);
106
+ const element = elementOf(action.success);
107
+ const success = received(element ?? action.success);
108
+ const failure = received(Schema.Union(projectedErrors(action)));
109
+ const declared = (cause) => Effect.flatMap(mapFailuresInPlace(cause, failure), Effect.failCause);
110
+ const called = (args) => inputOf(action, args).pipe(Effect.flatMap(input), Effect.mapError(InvalidInput.fromSchemaError), Effect.map((value) => run(value, progressSink(onProgressOf(args)))));
111
+ if (element !== void 0) return (...args) => Stream$1.unwrap(Effect.map(called(args), elementsOf)).pipe(Stream$1.catchCause((cause) => Stream$1.fromEffect(declared(cause))), Stream$1.mapEffect((value) => Effect.orDie(success(value))));
112
+ return (...args) => called(args).pipe(Effect.flatMap((answer) => Effect.catchCause(answer, declared)), Effect.flatMap((value) => Effect.orDie(success(value))));
113
+ };
114
+ const watcherOf = (feeds) => (event, input, options) => {
115
+ const feed = feeds.find((held) => held.event === event);
116
+ if (feed === void 0) return Stream$1.fail(SubscriptionError.of(event.name, new NotServed()));
117
+ return Stream$1.unwrap(Effect.map(watchInput(event, input), ({ value }) => {
118
+ const subscriber = uniqueKey();
119
+ return watching({
120
+ name: event.name,
121
+ poll: polledOn("In process", event.name)((cursor) => feed.poll(value, cursor, { subscriber })),
122
+ stream: (cursor) => Stream$1.unwrap(Effect.flatMap(feed.stream(value), (open) => Effect.map(queuedPush(open, cursor, Effect.succeed, {
123
+ inOrder: true,
124
+ capacity: 1
125
+ }), (queue) => Stream$1.fromQueue(queue)))).pipe(readOn("In process", event.name))
126
+ }, options).pipe(Stream$1.ensuring(feed.release?.(value, subscriber) ?? Effect.void));
127
+ }));
87
128
  };
88
129
  function client(served, options) {
89
- const apps = select(toList(served), options?.actions);
130
+ const { apps, events } = selectFrom(served, options?.actions);
90
131
  assertOnce("action", apps.flatMap((app) => app.actions), heldOnce);
91
- return Effect.map(built(apps), (bound) => Object.fromEntries(bound.map(([action, run]) => [action.name, methodOf(action, run)])));
132
+ assertOnce("event", events.flatMap((app) => app.events), heldOnce);
133
+ return Effect.map(builtWith([...apps.map((app) => Implementation.layerOf(app)), ...feedBuilders(events)], Effect.all([acquire(apps, void 0), acquireFeeds(events)])), ([bound, feeds]) => ({
134
+ ...Object.fromEntries(bound.map(([action, run]) => [action.name, methodOf(action, run)])),
135
+ ...feeds.length === 0 ? {} : { watch: watcherOf(feeds) }
136
+ }));
92
137
  }
93
- export { Anyone, BuiltIn, Forbidden, Image, InvalidInput, Refusal, Unauthenticated, allowAll, byName, client, implement, layer, make };
138
+ export { Anyone, BuiltIn, Forbidden, Idempotency, Image, InputCheck, InvalidInput, Refusal, Stream, Unauthenticated, allowAll, byName, client, implement, layer, layerIdempotency, make, withErrors };
@@ -1,15 +1,19 @@
1
1
  import { BuiltIn } from "./contract/errors.mjs";
2
- import { ActionOf, BuildError, BuildServices, Holding, Known, Member, RequestOf, Selected, SelectedOf, Selection, Served } from "./contract/implementation.mjs";
3
- import { Any } from "./Action.mjs";
2
+ import { EventBuildError, EventBuildServices, EventOf, EventRequestOf, ServingEvents } from "./contract/feed.mjs";
3
+ import { Any, SubscriptionError } from "./Event.mjs";
4
+ import { ActionOf, BuildError, BuildServices, Holding, Known, Member, RequestOf, Selected, SelectedEvents, SelectedEventsOf, SelectedOf, Selection, Served, ServedActions } from "./contract/implementation.mjs";
5
+ import { Receiving } from "./contract/receiving.mjs";
6
+ import { WatchArgs } from "./contract/watch.mjs";
7
+ import { Any as Any$1 } from "./Action.mjs";
4
8
  import { Declaring } from "./contract/declared.mjs";
5
- import { Options, UserError } from "./cli/commands.mjs";
6
- import { AnyHttp, Client } from "./http/client.mjs";
9
+ import { AnyHttp, Client, WatchError } from "./http/client.mjs";
7
10
  import "./ActionHttp.mjs";
8
- import { Effect } from "effect";
11
+ import { Options, UserError } from "./cli/commands.mjs";
12
+ import { Effect, Stream } from "effect";
9
13
  import { HttpClient } from "effect/http";
10
14
  import { CliError, Command } from "effect/cli";
11
15
  interface AnyBinding extends Declaring {
12
- readonly actions: ReadonlyArray<Any>;
16
+ readonly actions: readonly Any$1[];
13
17
  }
14
18
  /**
15
19
  * Make a command of your own fail as a derived command does. Give it what the command calls, the
@@ -25,24 +29,29 @@ interface AnyBinding extends Declaring {
25
29
  * - A failure left unmapped, one call's included, is reported as a crash, `ERROR (#n)` with a stack trace: pipe the whole Effect through it, once.
26
30
  * - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
27
31
  */
28
- export declare const toUserError: (calls: AnyBinding | ReadonlyArray<Any>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, UserError<Exclude<E, CliError.CliError>> | Exclude<E, Exclude<E, CliError.CliError>>, R>;
32
+ export declare const toUserError: (calls: AnyBinding | readonly Any$1[]) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, UserError<Exclude<E, CliError.CliError>> | Exclude<E, Exclude<E, CliError.CliError>>, R>;
29
33
  /**
30
34
  * An aggregate command of the actions `A`: its name, the actions that are its subcommands,
31
35
  * and their options.
32
36
  */
33
- interface Options$1<A extends Any = Any> {
37
+ interface Options$1<A extends Any$1 | Any = Any$1 | Any> {
34
38
  /** The aggregate command's name. */
35
39
  readonly name: string;
36
40
  /**
37
- * The actions that are subcommands, among the binding's or the implementations' actions:
38
- * `[GetUser, RenameUser]`. Defaults to every action of them.
41
+ * The actions that are subcommands and the event types that have a `watch`, among the
42
+ * binding's or the implementations': `[GetUser, RenameUser, UserRenamed]`, exactly those.
43
+ * Defaults to every one of them.
44
+ *
45
+ * **Gotchas**
46
+ *
47
+ * - A list naming actions alone adds no `watch`: list the event types beside them.
39
48
  */
40
- readonly actions?: ReadonlyArray<A> | undefined;
49
+ readonly actions?: readonly A[] | undefined;
41
50
  /**
42
51
  * The options of each subcommand, keyed by its action's name, as `command` takes them:
43
52
  * `{ readFile: { positional: ["path"], render } }`.
44
53
  */
45
- readonly commands?: { readonly [K in A as K["name"]]?: Options<K>; };
54
+ readonly commands?: { readonly [K in Extract<A, Any$1> as K["name"]]?: Options<K>; };
46
55
  /**
47
56
  * Commands of your own beside the derived ones, as `Command.withSubcommands` takes them:
48
57
  * `[importLinks]`, each made with Effect CLI's `Command.make` and printing its output itself,
@@ -58,16 +67,29 @@ interface Options$1<A extends Any = Any> {
58
67
  */
59
68
  readonly subcommands?: Subcommands | undefined;
60
69
  }
61
- type Subcommands = ReadonlyArray<Command.Command.SubcommandEntry>;
70
+ type Subcommands = readonly Command.Command.SubcommandEntry[];
62
71
  type OwnCommand<Own extends Subcommands> = Subcommands extends Own ? never : Own[number] extends (infer Entry) ? Entry extends Command.Command<infer _N, infer _I, infer _C, infer _E, infer _R> ? Entry : Entry extends Command.Command.SubcommandGroup<infer Commands> ? Commands[number] : never : never;
63
72
  type CommandsOf<O> = O extends unknown ? "commands" extends keyof O ? NonNullable<O["commands" & keyof O]> : never : never;
64
73
  type KeysOf<C> = C extends unknown ? keyof C : never;
65
- type CommandOf<C, K> = C extends unknown ? (K extends keyof C ? C[K] : never) : never;
66
- type KnownOptions<O, Keys, A extends Any> = Known<O, Keys> & ([CommandsOf<O>] extends [never] ? unknown : {
67
- readonly commands?: Known<CommandsOf<O>, { readonly [K in A["name"]]: Options<A>; }> & { readonly [K in KeysOf<CommandsOf<O>>]?: Known<CommandOf<CommandsOf<O>, K>, Options<Any>>; };
74
+ type CommandOf<C, K> = C extends unknown ? K extends keyof C ? C[K] : never : never;
75
+ type KnownOptions<O, Keys, A extends Any$1> = Known<O, Keys> & ([CommandsOf<O>] extends [never] ? unknown : {
76
+ readonly commands?: Known<CommandsOf<O>, { readonly [K in A["name"]]: Options<A>; }> & { readonly [K in KeysOf<CommandsOf<O>>]?: Known<CommandOf<CommandsOf<O>, K>, Options<Any$1>>; };
68
77
  });
69
- type Owning<App, A extends Any> = App extends unknown ? A extends ActionOf<App> ? App : never : never;
70
- type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][number]["Type"] | BuildError<App, A> | BuiltIn, RequestOf<App, A> | BuildServices<App, A>>;
78
+ type Owning<App, A extends Any$1> = App extends unknown ? A extends ActionOf<App> ? App : never : never;
79
+ type Local<App, A extends Any$1> = Effect.Effect<A["success"]["Type"], A["error"][number]["Type"] | BuildError<App, A> | BuiltIn, RequestOf<App, A> | BuildServices<App, A>>;
80
+ type LocalCall<Apps extends Served, O> = [SelectedOf<O, Apps>] extends [never] ? never : Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>;
81
+ type LocalWatch<App, Listed extends Any> = [ServingEvents<App, Listed>] extends [never] ? never : Effect.Effect<void, SubscriptionError | BuiltIn | EventBuildError<App, Listed>, EventRequestOf<App, ServingEvents<App, Listed>> | EventBuildServices<App, Listed>>;
82
+ type Watched<M, E extends Any> = M extends {
83
+ readonly watch: (event: E, ...args: WatchArgs<E, never>) => Stream.Stream<unknown, infer WE, infer WR>;
84
+ } ? {
85
+ readonly error: WE;
86
+ readonly services: Exclude<WR, Receiving>;
87
+ } : {
88
+ readonly error: never;
89
+ readonly services: never;
90
+ };
91
+ type WatchFailure<H extends AnyHttp, C, E extends Any> = [E] extends [never] ? never : C extends Effect.Effect<infer M, infer CE, unknown> ? CE | Watched<M, E>["error"] : WatchError<E, H>;
92
+ type WatchServices<C, E extends Any> = [E] extends [never] ? never : C extends Effect.Effect<infer M, unknown, infer CR> ? CR | Watched<M, E>["services"] : HttpClient.HttpClient;
71
93
  /**
72
94
  * Project one implemented action into a native Effect CLI command that runs its handler in
73
95
  * process, behind its implementation's authorization. It is named after the action in kebab
@@ -79,6 +101,8 @@ type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][
79
101
  * **Gotchas**
80
102
  *
81
103
  * - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
104
+ * - Each value an action declaring `progress` reports is one line of compact JSON on stderr, as it arrives, so stdout holds the answer alone; `remoteCommand` asks the server for them too.
105
+ * - An action answering a stream prints each element on stdout as it arrives, one line of compact JSON, or `render`'s text of it; a declared failure ends the command after the elements before it.
82
106
  * - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
83
107
  * - Effect CLI styles what it prints, a failure's JSON included, on a terminal and, from Effect 4.0.6, wherever `FORCE_COLOR` forces colour: for plain text there, set `NO_COLOR` or provide `TerminalCapabilities.TerminalCapabilities` with `canColor: false`.
84
108
  * - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
@@ -86,14 +110,15 @@ type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][
86
110
  * - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
87
111
  * - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
88
112
  */
89
- export declare function command<const Apps extends Served, A extends ActionOf<Member<Apps>>, const O = {}>(implementations: Apps, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, Options<Any>>>): Command.Command<string, never, {}, Failing<Effect.Error<Local<Owning<Member<Apps>, A>, A>>>, Effect.Services<Local<Owning<Member<Apps>, A>, A>>>;
113
+ export declare const command: <const Apps extends ServedActions, A extends ActionOf<Member<Apps>>, const O = {}>(implementations: Apps, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, Options<Any$1>>>) => Command.Command<string, never, {}, Failing<Effect.Error<Local<Owning<Member<Apps>, A>, A>>>, Effect.Services<Local<Owning<Member<Apps>, A>, A>>>;
90
114
  /**
91
115
  * Project one action of an HTTP binding into a native Effect CLI command, derived as `command`
92
116
  * derives it, that calls the action through `client`'s method and fails as the method does, or
93
117
  * as the Effect building the client does. It runs nothing locally and names no caller: the
94
118
  * server verifies the credentials its client sends.
95
119
  */
96
- export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H, A> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any>>>> & Calling<C>): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
120
+ export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H, A> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any$1>>>> & Calling<C>): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
121
+ /** The `client` option of `remote` and `remoteCommand`, declared once for both. */
97
122
  export interface Calling<C> {
98
123
  /**
99
124
  * The connection: an Effect of the binding's client, such as
@@ -112,41 +137,52 @@ export interface Calling<C> {
112
137
  */
113
138
  readonly client?: C;
114
139
  }
115
- type ClientOf<H extends AnyHttp, A extends Any> = Effect.Effect<Pick<Client<H, unknown, unknown>, A["name"]>, unknown, unknown>;
116
- type Needed<O, L extends ReadonlyArray<Any>, All extends Any> = "actions" extends (O extends unknown ? keyof O : never) ? Selected<O, All> : L[number];
140
+ type ClientOf<H extends AnyHttp, A extends Any$1, E extends Any = never> = Effect.Effect<Pick<Client<H, unknown, unknown>, A["name"] | ([E] extends [never] ? never : "watch")>, unknown, unknown>;
141
+ type Needed<O, L extends readonly unknown[], All extends Any$1> = "actions" extends (O extends unknown ? keyof O : never) ? Selected<O, All> : Extract<L[number], All>;
142
+ type NeededEvents<O, L extends readonly unknown[], All extends Any> = "actions" extends (O extends unknown ? keyof O : never) ? SelectedEvents<O, All> : Extract<L[number], All>;
117
143
  type RemoteKeys<Keys> = Keys & {
118
144
  readonly client?: unknown;
119
145
  };
120
- type Failing<F> = UserError<Exclude<F, CliError.CliError>> | Extract<F, CliError.CliError>;
121
- type Called<M, A extends Any> = A extends unknown ? A["name"] extends keyof M ? M[A["name"]] extends ((...input: never) => infer Call) ? Call : never : never : never;
122
- type CallFailure<H extends AnyHttp, C, A extends Any> = C extends Effect.Effect<infer M, infer CE, unknown> ? CE | Effect.Error<Called<M, A>> : Effect.Error<Called<Client<H>, A>>;
123
- type CallServices<C, A extends Any> = C extends Effect.Effect<infer M, unknown, infer CR> ? CR | Effect.Services<Called<M, A>> : HttpClient.HttpClient;
146
+ type Failing<F> = [F] extends [never] ? never : UserError<Exclude<F, CliError.CliError>> | Extract<F, CliError.CliError>;
147
+ type Called<M, A extends Any$1> = A extends unknown ? A["name"] extends keyof M ? M[A["name"]] extends ((...input: never) => infer Call) ? Call : never : never : never;
148
+ type CallError<Call> = Effect.Error<Call> | Stream.Error<Call>;
149
+ type CallRequirements<Call> = Effect.Services<Call> | Stream.Services<Call>;
150
+ type CallFailure<H extends AnyHttp, C, A extends Any$1> = C extends Effect.Effect<infer M, infer CE, unknown> ? CE | CallError<Called<M, A>> : CallError<Called<Client<H>, A>>;
151
+ type CallServices<C, A extends Any$1> = C extends Effect.Effect<infer M, unknown, infer CR> ? CR | CallRequirements<Called<M, A>> : HttpClient.HttpClient;
124
152
  /**
125
153
  * Project the implemented actions, every one or those `actions` selects, as subcommands of one
126
154
  * aggregate command, each derived as `command` derives it and run in process. `commands` gives a
127
155
  * subcommand the options `command` takes, by action name; `subcommands` adds commands of your
128
- * own. `remote` calls a server instead. A test runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
156
+ * own. `remote` calls a server instead. Each event type among the implementations, or each
157
+ * `actions` lists, adds `watch <event>`, a flag per input field and `--cursor`, printing each
158
+ * occurrence, or a `Gap`, as one line of JSON, its `cursor` included, until it is stopped. A test
159
+ * runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
129
160
  * Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
130
161
  *
131
162
  * **Gotchas**
132
163
  *
133
164
  * - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
165
+ * - An event input field named `cursor` throws when `make` or `remote` is called, `--cursor` claiming its flag: rename the field.
166
+ * - A watch's refusal, when it starts or later, or `Event.SubscriptionError`, ends the command as its `UserError`, exit 1, after the lines it printed.
134
167
  */
135
- export declare function make<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, const Own extends Subcommands = []>(implementations: Apps, options: O & NoInfer<Options$1<ActionOf<Member<Apps>>>> & NoInfer<KnownOptions<O, Options$1, ActionOf<Member<Apps>>>> & {
168
+ export declare const make: <const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>> | EventOf<Member<Apps>>> = {}, const Own extends Subcommands = []>(implementations: Apps, options: O & NoInfer<Options$1<ActionOf<Member<Apps>> | EventOf<Member<Apps>>>> & NoInfer<KnownOptions<O, Options$1, ActionOf<Member<Apps>>>> & {
136
169
  readonly subcommands?: Own | undefined;
137
- }): Command.Command<string, {}, {}, Failing<Effect.Error<Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>>> | Command.Error<OwnCommand<Own>>, Effect.Services<Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>> | Command.Services<OwnCommand<Own>>>;
170
+ }) => Command.Command<string, {}, {}, Failing<Effect.Error<LocalCall<Apps, O> | LocalWatch<Member<Apps>, SelectedEventsOf<O, Apps>>>> | Command.Error<OwnCommand<Own>>, Effect.Services<LocalCall<Apps, O> | LocalWatch<Member<Apps>, SelectedEventsOf<O, Apps>>> | Command.Services<OwnCommand<Own>>>;
138
171
  /**
139
172
  * Project the actions of an HTTP binding, every one or those `actions` selects, as subcommands of
140
173
  * one aggregate command, derived as `make` derives them, each calling its action through
141
174
  * `client`'s method, as `remoteCommand` does. `commands` gives one the options `command` takes;
142
175
  * `subcommands` adds commands of your own, which call through the same client by yielding the
143
- * Effect given as `client`.
144
- * In an application: `examples/notes-service/src/commands.ts`.
176
+ * Effect given as `client`. Each event type of the binding, or each `actions` lists, adds
177
+ * `watch <event>`, as `make` adds it, watching through `client`'s `watch`, which reconnects as
178
+ * `ActionHttp.client`'s does.
179
+ * In an application: `examples/notes-service/src/commands.ts`, and for events,
180
+ * `examples/build-events/src/commands.ts`.
145
181
  */
146
- export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number]> = {}, const Own extends Subcommands = [], const L extends ReadonlyArray<H["actions"][number]> = H["actions"], C extends ClientOf<H, Needed<O, L, H["actions"][number]>> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1<Any>>, H["actions"][number]>> & Calling<C> & {
182
+ export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number] | H["events"][number]> = {}, const Own extends Subcommands = [], const L extends ReadonlyArray<H["actions"][number] | H["events"][number]> = [...H["actions"], ...H["events"]], C extends ClientOf<H, Needed<O, L, H["actions"][number]>, NeededEvents<O, L, H["events"][number]>> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number] | H["events"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1>, H["actions"][number]>> & Calling<C> & {
147
183
  readonly actions?: L | undefined;
148
184
  readonly subcommands?: Own | undefined;
149
- }): Command.Command<string, {}, {}, Failing<CallFailure<H, C, Selected<O, H["actions"][number]>>> | Command.Error<OwnCommand<Own>>, CallServices<C, Selected<O, H["actions"][number]>> | Command.Services<OwnCommand<Own>>>;
185
+ }): Command.Command<string, {}, {}, Failing<CallFailure<H, C, Selected<O, H["actions"][number]>> | WatchFailure<H, C, SelectedEvents<O, H["events"][number]>>> | Command.Error<OwnCommand<Own>>, CallServices<C, Selected<O, H["actions"][number]>> | WatchServices<C, SelectedEvents<O, H["events"][number]>> | Command.Services<OwnCommand<Own>>>;
150
186
  /**
151
187
  * A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP
152
188
  * subprocess, applied last, before `NodeRuntime.runMain`: the default logger of every layer
@@ -1,9 +1,14 @@
1
+ import { bindingErrors, eventErrors, projectedErrors } from "./contract/declared.mjs";
1
2
  import { assertDistinct, assertKnown } from "./contract/names.mjs";
2
- import { bindingErrors, projectedErrors } from "./contract/declared.mjs";
3
- import { Implementation, acquire, assertHeld, notHeld, select, toList } from "./contract/implementation.mjs";
4
- import { client } from "./ActionHttp.mjs";
5
- import { command as command$1, failingAsDerived } from "./cli/commands.mjs";
6
- import { Cause, Effect, Logger, Runtime } from "effect";
3
+ import { progressSink } from "./contract/progress.mjs";
4
+ import { elementOf } from "./contract/stream.mjs";
5
+ import { Implementation, acquire, assertHeld, elementsOf, notHeld, toList } from "./contract/implementation.mjs";
6
+ import { selection } from "./contract/listed.mjs";
7
+ import { selectFrom } from "./contract/serving.mjs";
8
+ import { client } from "./Action.mjs";
9
+ import { client as client$1 } from "./ActionHttp.mjs";
10
+ import { command as command$1, failingAsDerived, watchCommand } from "./cli/commands.mjs";
11
+ import { Cause, Effect, Logger, Runtime, Stream } from "effect";
7
12
  import { Command } from "effect/cli";
8
13
  // src/cli/ActionCli.ts
9
14
  /**
@@ -21,12 +26,14 @@ import { Command } from "effect/cli";
21
26
  * - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
22
27
  */
23
28
  const toUserError = (calls) => "actions" in calls ? failingAsDerived(calls.actions.flatMap((action) => bindingErrors(action, calls))) : failingAsDerived(calls.flatMap((action) => projectedErrors(action)));
24
- const runWithBuildOfItsOwn = (app, action, input) => {
29
+ const runWithBuildOfItsOwn = (app, action, input, onProgress) => {
25
30
  const thisActionOnly = Implementation.share([action], app);
26
- return Effect.flatMap(acquire([thisActionOnly]), (bound) => {
31
+ const call = Effect.flatMap(acquire([thisActionOnly], void 0), (bound) => {
27
32
  const [, run] = bound.find(([candidate]) => candidate === action) ?? [];
28
- return run === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No handler for ${action.name}`)) : run(input);
29
- }).pipe(Effect.provide(Implementation.layerOf(thisActionOnly), { local: true }));
33
+ return run === void 0 ? Effect.die(/* @__PURE__ */ new Error(`No handler for ${action.name}`)) : run(input, progressSink(onProgress));
34
+ });
35
+ const builder = Implementation.layerOf(thisActionOnly);
36
+ return elementOf(action.success) === void 0 ? call.pipe(Effect.provide(builder, { local: true })) : Effect.succeed(elementsOf(call).pipe(Stream.provide(builder, { local: true })));
30
37
  };
31
38
  const theImplementationOf = (apps, action) => {
32
39
  const [app, ...others] = apps.filter((candidate) => candidate.actions.includes(action));
@@ -36,32 +43,38 @@ const theImplementationOf = (apps, action) => {
36
43
  };
37
44
  const inProcess = (apps, action, options) => {
38
45
  const app = theImplementationOf(apps, action);
39
- return command$1(action, (input) => runWithBuildOfItsOwn(app, action, input), options, projectedErrors(action));
46
+ return command$1(action, (input, onProgress) => runWithBuildOfItsOwn(app, action, input, onProgress), options, projectedErrors(action));
40
47
  };
41
- const overHttp = (binding, action, options, client$1) => {
48
+ const overHttp = (binding, action, options, client) => {
42
49
  if (!binding.actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding (pass it to the binding's ActionHttp.make, and select with the contract value it received)`);
43
- return command$1(action, (input) => Effect.flatMap(client$1 ?? client(binding), (methods) => {
50
+ return command$1(action, (input, onProgress) => Effect.flatMap(client ?? client$1(binding), (methods) => {
44
51
  const method = methods[action.name];
45
52
  if (method === void 0) throw new Error(`No client method for ${action.name} (give \`client\` a method for this action)`);
46
- return method(input);
53
+ const answer = method(input, { onProgress });
54
+ return Stream.isStream(answer) ? Effect.succeed(answer) : answer;
47
55
  }), options, bindingErrors(action, binding));
48
56
  };
49
- const aggregateCommand = (name, actions, commands, own, project) => {
50
- const derived = actions.map((action) => ({
57
+ /** The command watching `event` through the `watch` of each client `client` makes as it runs. */
58
+ const watched = (event, client, errors) => watchCommand(event, (input, cursor) => Stream.unwrap(Effect.map(client, (methods) => methods.watch(event, input, { cursor }))), errors);
59
+ const aggregateCommand = (name, actions, commands, own, project, watches) => {
60
+ const derived = [...actions.map((action) => ({
51
61
  claimant: `action ${action.name}`,
52
62
  command: project(action, Object.hasOwn(commands, action.name) ? commands[action.name] : void 0)
53
- }));
54
- assertDistinct("command", [...derived.map(({ claimant, command }) => ({
63
+ })), ...watches.length === 0 ? [] : [{
64
+ claimant: "the event types",
65
+ command: Command.make("watch").pipe(Command.withDescription("Watch an event type, each occurrence a line of JSON"), Command.withSubcommands(watches))
66
+ }]];
67
+ assertDistinct("command", [...derived.map(({ claimant, command: derivedCommand }) => ({
55
68
  claimant,
56
- name: command.name
57
- })), ...own.flatMap((entry) => Command.isCommand(entry) ? [entry] : entry.commands).map((command) => ({
69
+ name: derivedCommand.name
70
+ })), ...own.flatMap((entry) => Command.isCommand(entry) ? [entry] : entry.commands).map((subcommand) => ({
58
71
  claimant: "subcommands",
59
- name: command.name
72
+ name: subcommand.name
60
73
  }))], ({ name: claimed }) => claimed, {
61
74
  claimantOf: ({ claimant }) => claimant,
62
75
  fix: "rename one of your subcommands, or give an action's command another name in commands"
63
76
  });
64
- return Command.make(name).pipe(Command.withSubcommands([...derived.map(({ command }) => command), ...own]));
77
+ return Command.make(name).pipe(Command.withSubcommands([...derived.map(({ command: derivedCommand }) => derivedCommand), ...own]));
65
78
  };
66
79
  const checkedCommandsOf = (commands, actions) => {
67
80
  const given = commands ?? {};
@@ -79,6 +92,8 @@ const checkedCommandsOf = (commands, actions) => {
79
92
  * **Gotchas**
80
93
  *
81
94
  * - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
95
+ * - Each value an action declaring `progress` reports is one line of compact JSON on stderr, as it arrives, so stdout holds the answer alone; `remoteCommand` asks the server for them too.
96
+ * - An action answering a stream prints each element on stdout as it arrives, one line of compact JSON, or `render`'s text of it; a declared failure ends the command after the elements before it.
82
97
  * - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
83
98
  * - Effect CLI styles what it prints, a failure's JSON included, on a terminal and, from Effect 4.0.6, wherever `FORCE_COLOR` forces colour: for plain text there, set `NO_COLOR` or provide `TerminalCapabilities.TerminalCapabilities` with `canColor: false`.
84
99
  * - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
@@ -86,9 +101,7 @@ const checkedCommandsOf = (commands, actions) => {
86
101
  * - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
87
102
  * - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
88
103
  */
89
- function command(implementations, action, options) {
90
- return inProcess(toList(implementations), action, options);
91
- }
104
+ const command = (implementations, action, options) => inProcess(toList(implementations), action, options);
92
105
  function remoteCommand(binding, action, options) {
93
106
  const { client, ...syntax } = options ?? {};
94
107
  return overHttp(binding, action, options === void 0 ? void 0 : syntax, client);
@@ -97,24 +110,30 @@ function remoteCommand(binding, action, options) {
97
110
  * Project the implemented actions, every one or those `actions` selects, as subcommands of one
98
111
  * aggregate command, each derived as `command` derives it and run in process. `commands` gives a
99
112
  * subcommand the options `command` takes, by action name; `subcommands` adds commands of your
100
- * own. `remote` calls a server instead. A test runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
113
+ * own. `remote` calls a server instead. Each event type among the implementations, or each
114
+ * `actions` lists, adds `watch <event>`, a flag per input field and `--cursor`, printing each
115
+ * occurrence, or a `Gap`, as one line of JSON, its `cursor` included, until it is stopped. A test
116
+ * runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
101
117
  * Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
102
118
  *
103
119
  * **Gotchas**
104
120
  *
105
121
  * - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
122
+ * - An event input field named `cursor` throws when `make` or `remote` is called, `--cursor` claiming its flag: rename the field.
123
+ * - A watch's refusal, when it starts or later, or `Event.SubscriptionError`, ends the command as its `UserError`, exit 1, after the lines it printed.
106
124
  */
107
- function make(implementations, options) {
125
+ const make = (implementations, options) => {
108
126
  const apps = toList(implementations);
127
+ const { apps: served, events } = selectFrom(implementations, options.actions);
109
128
  const commands = checkedCommandsOf(options.commands, apps.flatMap((app) => app.actions));
110
- const served = select(apps, options.actions);
111
- return aggregateCommand(options.name, served.flatMap((app) => app.actions), commands, options.subcommands ?? [], (action, syntax) => inProcess(served, action, syntax));
112
- }
129
+ return aggregateCommand(options.name, served.flatMap((app) => app.actions), commands, options.subcommands ?? [], (action, syntax) => inProcess(served, action, syntax), events.flatMap((app) => app.events.map((event) => watched(event, client(app), eventErrors(event, { error: [] })))));
130
+ };
113
131
  function remote(binding, options) {
114
- const listed = options.actions;
115
- if (listed !== void 0) assertHeld(notHeld.byBinding, listed, binding.actions);
132
+ const listed = selection(options.actions);
133
+ if (listed.actions !== void 0) assertHeld(notHeld.byBinding, listed.actions, binding.actions);
134
+ if (listed.events !== void 0) assertHeld(notHeld.byBinding, listed.events, binding.events);
116
135
  const commands = checkedCommandsOf(options.commands, binding.actions);
117
- return aggregateCommand(options.name, binding.actions.filter((action) => listed?.includes(action) ?? true), commands, options.subcommands ?? [], (action, syntax) => overHttp(binding, action, syntax, options.client));
136
+ return aggregateCommand(options.name, binding.actions.filter((action) => listed.actions?.includes(action) ?? true), commands, options.subcommands ?? [], (action, syntax) => overHttp(binding, action, syntax, options.client), binding.events.filter((event) => listed.events?.includes(event) ?? true).map((event) => watched(event, options.client ?? client$1(binding), eventErrors(event, binding))));
118
137
  }
119
138
  /**
120
139
  * A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP