@gjermundgaraba/effect-actions 0.19.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.
- package/README.md +19 -41
- package/dist/Action.d.mts +140 -48
- package/dist/Action.mjs +88 -44
- package/dist/ActionCli.d.mts +70 -35
- package/dist/ActionCli.mjs +53 -34
- package/dist/ActionHttp.d.mts +70 -38
- package/dist/ActionHttp.mjs +76 -25
- package/dist/ActionMcp.d.mts +56 -24
- package/dist/ActionMcp.mjs +104 -61
- package/dist/ActionRpc.d.mts +222 -57
- package/dist/ActionRpc.mjs +301 -49
- package/dist/ActionToolkit.d.mts +22 -7
- package/dist/ActionToolkit.mjs +37 -14
- package/dist/Authentication.d.mts +42 -12
- package/dist/Authentication.mjs +38 -32
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.d.mts +1 -3
- package/dist/Testing.mjs +2 -3
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -12
- package/dist/authentication/provider.mjs +2 -3
- package/dist/authentication/refusal.mjs +3 -4
- package/dist/cli/commands.d.mts +9 -7
- package/dist/cli/commands.mjs +85 -35
- package/dist/contract/ast.mjs +5 -3
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -8
- package/dist/contract/call.mjs +49 -8
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +12 -3
- package/dist/contract/caller.mjs +23 -5
- package/dist/contract/canonical.mjs +10 -0
- package/dist/contract/codec.d.mts +9 -0
- package/dist/contract/codec.mjs +9 -0
- package/dist/contract/codes.mjs +81 -0
- package/dist/contract/declared.d.mts +0 -2
- package/dist/contract/declared.mjs +26 -11
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -5
- package/dist/contract/errors.mjs +2 -3
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hints.mjs +1 -2
- package/dist/contract/hub.d.mts +94 -0
- package/dist/contract/hub.mjs +133 -0
- package/dist/contract/idempotency.d.mts +47 -0
- package/dist/contract/idempotency.mjs +99 -0
- package/dist/contract/implementation.d.mts +73 -51
- package/dist/contract/implementation.mjs +94 -60
- package/dist/contract/input-check.d.mts +15 -0
- package/dist/contract/input-check.mjs +16 -0
- package/dist/contract/lease.mjs +28 -0
- package/dist/contract/listed.d.mts +4 -0
- package/dist/contract/listed.mjs +22 -0
- package/dist/contract/log.d.mts +41 -0
- package/dist/contract/log.mjs +144 -0
- package/dist/contract/media.d.mts +1 -3
- package/dist/contract/media.mjs +1 -2
- package/dist/contract/names.mjs +15 -6
- package/dist/contract/occurrence.d.mts +29 -0
- package/dist/contract/occurrence.mjs +45 -0
- package/dist/contract/pages.d.mts +13 -0
- package/dist/contract/pages.mjs +39 -0
- package/dist/contract/progress.d.mts +26 -0
- package/dist/contract/progress.mjs +72 -0
- package/dist/contract/push.d.mts +20 -0
- package/dist/contract/push.mjs +96 -0
- package/dist/contract/receiving.d.mts +29 -0
- package/dist/contract/receiving.mjs +42 -0
- package/dist/contract/recent.mjs +12 -0
- package/dist/contract/reported.mjs +16 -0
- package/dist/contract/serving.mjs +63 -0
- package/dist/contract/status.mjs +15 -7
- package/dist/contract/stream.d.mts +34 -0
- package/dist/contract/stream.mjs +31 -0
- package/dist/contract/subscribe.d.mts +45 -0
- package/dist/contract/subscribe.mjs +50 -0
- package/dist/contract/transient.mjs +28 -0
- package/dist/contract/watch.d.mts +18 -0
- package/dist/contract/watch.mjs +105 -0
- package/dist/contract/webhooks.d.mts +42 -0
- package/dist/contract/webhooks.mjs +53 -0
- package/dist/http/client.d.mts +37 -14
- package/dist/http/client.mjs +52 -14
- package/dist/http/events.d.mts +56 -0
- package/dist/http/events.mjs +102 -0
- package/dist/http/placed.d.mts +36 -0
- package/dist/http/placed.mjs +71 -0
- package/dist/http/schema-errors.mjs +2 -3
- package/dist/http/streamed.d.mts +21 -0
- package/dist/http/streamed.mjs +94 -0
- package/dist/http/watch.mjs +50 -0
- package/dist/mcp/bridge.mjs +283 -0
- package/dist/mcp/client.d.mts +30 -99
- package/dist/mcp/client.mjs +60 -134
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +40 -15
- package/dist/mcp/reply.d.mts +79 -0
- package/dist/mcp/reply.mjs +99 -0
- package/dist/mcp/request.d.mts +40 -0
- package/dist/mcp/request.mjs +51 -0
- package/dist/mcp/watch.mjs +115 -0
- package/dist/rpc/watch.mjs +39 -0
- package/dist/stdio/console.mjs +14 -7
- package/dist/testing/memory.d.mts +1 -3
- package/dist/testing/memory.mjs +4 -5
- package/dist/webhook/address.mjs +47 -0
- package/dist/webhook/attempt.mjs +65 -0
- package/dist/webhook/receiver.mjs +67 -0
- package/dist/webhook/signing.mjs +28 -0
- package/dist/webhook/stored.mjs +57 -0
- package/dist/webhook/subscriptions.mjs +342 -0
- package/docs/Action-failures.md +2 -2
- package/docs/Action-streams.md +115 -0
- package/docs/Action.md +58 -124
- package/docs/ActionCli-failures.md +1 -2
- package/docs/ActionCli.md +83 -88
- package/docs/ActionHttp-failures.md +3 -3
- package/docs/ActionHttp.md +90 -70
- package/docs/ActionMcp-failures.md +2 -1
- package/docs/ActionMcp.md +120 -92
- package/docs/ActionRpc-events.md +126 -0
- package/docs/ActionRpc-failures.md +1 -2
- package/docs/ActionRpc.md +108 -79
- package/docs/ActionToolkit-failures.md +1 -2
- package/docs/ActionToolkit.md +33 -90
- package/docs/Authentication-advanced.md +57 -61
- package/docs/Authentication-failures.md +5 -4
- package/docs/Authentication.md +23 -58
- package/docs/Event-failures.md +15 -0
- package/docs/Event.md +281 -0
- package/docs/README.md +51 -82
- package/docs/Testing-failures.md +1 -5
- package/docs/Testing.md +126 -86
- package/docs/Webhook-failures.md +8 -0
- package/docs/Webhook.md +319 -0
- package/docs/setup-failures.md +2 -3
- package/docs/setup.md +32 -53
- package/examples/README.md +12 -37
- package/examples/assistant/README.md +21 -26
- package/examples/assistant/src/agent.ts +66 -24
- package/examples/assistant/src/cli.ts +7 -4
- package/examples/assistant/src/commands.ts +9 -4
- package/examples/assistant/src/identity/Identity.ts +5 -4
- package/examples/assistant/src/identity/authorize.ts +13 -5
- package/examples/assistant/src/identity/handlers.ts +3 -2
- package/examples/assistant/src/identity/permissions.ts +9 -3
- package/examples/assistant/src/platform/tasks-file.ts +15 -11
- package/examples/assistant/src/scripted.ts +7 -4
- package/examples/assistant/src/tasks/Tasks.ts +20 -6
- package/examples/assistant/src/tasks/handlers.ts +9 -5
- package/examples/assistant/src/tasks/store.ts +38 -11
- package/examples/assistant/src/toolkit.ts +11 -7
- package/examples/assistant/test/commands.test.ts +53 -27
- package/examples/assistant/test/identity/identity.test.ts +27 -11
- package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
- package/examples/assistant/test/tasks/tasks.test.ts +62 -34
- package/examples/assistant/test/toolkit.test.ts +104 -75
- package/examples/build-events/README.md +83 -0
- package/examples/build-events/src/Api.ts +27 -0
- package/examples/build-events/src/Rpc.ts +26 -0
- package/examples/build-events/src/builds/Builds.ts +108 -0
- package/examples/build-events/src/builds/events.ts +54 -0
- package/examples/build-events/src/builds/handlers.ts +58 -0
- package/examples/build-events/src/builds/log.ts +30 -0
- package/examples/build-events/src/builds/status.ts +28 -0
- package/examples/build-events/src/builds/store.ts +130 -0
- package/examples/build-events/src/cli.ts +18 -0
- package/examples/build-events/src/commands.ts +32 -0
- package/examples/build-events/src/deploys/Deploys.ts +44 -0
- package/examples/build-events/src/deploys/approvers.ts +55 -0
- package/examples/build-events/src/deploys/events.ts +32 -0
- package/examples/build-events/src/deploys/handlers.ts +38 -0
- package/examples/build-events/src/identity/Identity.ts +29 -0
- package/examples/build-events/src/identity/members.ts +7 -0
- package/examples/build-events/src/identity/verifier.ts +22 -0
- package/examples/build-events/src/notifier/Notices.ts +26 -0
- package/examples/build-events/src/notifier/routes.ts +71 -0
- package/examples/build-events/src/notifier.ts +48 -0
- package/examples/build-events/src/routes.ts +71 -0
- package/examples/build-events/src/server.ts +62 -0
- package/examples/build-events/test/builds/events.test.ts +68 -0
- package/examples/build-events/test/builds/log.test.ts +71 -0
- package/examples/build-events/test/builds/progress.test.ts +57 -0
- package/examples/build-events/test/builds/status.test.ts +42 -0
- package/examples/build-events/test/commands.test.ts +103 -0
- package/examples/build-events/test/deploys/events.test.ts +52 -0
- package/examples/build-events/test/http.test.ts +95 -0
- package/examples/build-events/test/notifier.test.ts +80 -0
- package/examples/build-events/test/routes.test.ts +96 -0
- package/examples/build-events/test/rpc.test.ts +84 -0
- package/examples/build-events/test/support/as.ts +8 -0
- package/examples/build-events/test/support/memory.ts +43 -0
- package/examples/build-events/test/webhooks.test.ts +143 -0
- package/examples/kitchen-sink/README.md +178 -166
- package/examples/kitchen-sink/src/Actions.ts +11 -3
- package/examples/kitchen-sink/src/Api.ts +12 -6
- package/examples/kitchen-sink/src/Rpc.ts +7 -5
- package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
- package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
- package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
- package/examples/kitchen-sink/src/admin/grants.ts +5 -3
- package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
- package/examples/kitchen-sink/src/agent.ts +14 -6
- package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
- package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
- package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
- package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
- package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
- package/examples/kitchen-sink/src/attachments/download.ts +7 -6
- package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
- package/examples/kitchen-sink/src/attachments/store.ts +25 -18
- package/examples/kitchen-sink/src/browser.ts +10 -4
- package/examples/kitchen-sink/src/cli.ts +28 -12
- package/examples/kitchen-sink/src/config.ts +1 -1
- package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
- package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
- package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
- package/examples/kitchen-sink/src/identity/directory.ts +16 -7
- package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
- package/examples/kitchen-sink/src/identity/members.ts +7 -2
- package/examples/kitchen-sink/src/identity/quota.ts +23 -20
- package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
- package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
- package/examples/kitchen-sink/src/issues/events.ts +46 -0
- package/examples/kitchen-sink/src/issues/export.ts +35 -0
- package/examples/kitchen-sink/src/issues/guide.ts +4 -3
- package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
- package/examples/kitchen-sink/src/issues/store.ts +156 -67
- package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
- package/examples/kitchen-sink/src/memory.ts +11 -2
- package/examples/kitchen-sink/src/ops.ts +10 -5
- package/examples/kitchen-sink/src/request-policy.ts +25 -9
- package/examples/kitchen-sink/src/routes.ts +67 -27
- package/examples/kitchen-sink/src/sdk.ts +22 -12
- package/examples/kitchen-sink/src/seed.ts +15 -9
- package/examples/kitchen-sink/src/server.ts +24 -10
- package/examples/kitchen-sink/src/stdio.ts +9 -3
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
- package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
- package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
- package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
- package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
- package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
- package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
- package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
- package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
- package/examples/kitchen-sink/test/request-policy.test.ts +130 -33
- package/examples/kitchen-sink/test/routes.test.ts +102 -57
- package/examples/kitchen-sink/test/support/as.ts +9 -0
- package/examples/kitchen-sink/test/support/scripted.ts +6 -5
- package/examples/kitchen-sink/test/support/services.ts +17 -0
- package/examples/live-rpc/README.md +33 -35
- package/examples/live-rpc/src/Rpc.ts +9 -5
- package/examples/live-rpc/src/browser.ts +39 -11
- package/examples/live-rpc/src/chat/Chat.ts +30 -16
- package/examples/live-rpc/src/chat/authorize.ts +2 -1
- package/examples/live-rpc/src/chat/events.ts +74 -0
- package/examples/live-rpc/src/chat/handlers.ts +13 -5
- package/examples/live-rpc/src/chat/room.ts +9 -5
- package/examples/live-rpc/src/chat/store.ts +51 -14
- package/examples/live-rpc/src/client.ts +22 -5
- package/examples/live-rpc/src/identity/Identity.ts +3 -3
- package/examples/live-rpc/src/identity/verifier.ts +8 -4
- package/examples/live-rpc/src/page.ts +58 -12
- package/examples/live-rpc/src/routes.ts +14 -10
- package/examples/live-rpc/src/server.ts +15 -5
- package/examples/live-rpc/test/browser.test.ts +127 -50
- package/examples/live-rpc/test/chat/chat.test.ts +215 -23
- package/examples/live-rpc/test/routes.test.ts +130 -46
- package/examples/live-rpc/test/server.test.ts +94 -33
- package/examples/mcp-tools/README.md +24 -38
- package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
- package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
- package/examples/mcp-tools/src/printers/Printers.ts +18 -7
- package/examples/mcp-tools/src/printers/farm.ts +15 -5
- package/examples/mcp-tools/src/printers/handlers.ts +12 -6
- package/examples/mcp-tools/src/routes.ts +8 -6
- package/examples/mcp-tools/src/server.ts +12 -5
- package/examples/mcp-tools/src/stdio.ts +3 -2
- package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
- package/examples/mcp-tools/test/routes.test.ts +140 -81
- package/examples/notes-service/README.md +30 -45
- package/examples/notes-service/src/Api.ts +1 -0
- package/examples/notes-service/src/browser.ts +15 -14
- package/examples/notes-service/src/cli.ts +8 -3
- package/examples/notes-service/src/commands.ts +46 -20
- package/examples/notes-service/src/identity/Identity.ts +2 -2
- package/examples/notes-service/src/identity/authorize.ts +6 -2
- package/examples/notes-service/src/identity/handlers.ts +3 -2
- package/examples/notes-service/src/identity/users.ts +2 -1
- package/examples/notes-service/src/identity/verifier.ts +12 -6
- package/examples/notes-service/src/notes/Notes.ts +46 -22
- package/examples/notes-service/src/notes/handlers.ts +18 -9
- package/examples/notes-service/src/notes/store.ts +70 -43
- package/examples/notes-service/src/routes.ts +7 -5
- package/examples/notes-service/src/server.ts +12 -5
- package/examples/notes-service/src/status/Status.ts +1 -1
- package/examples/notes-service/src/status/handlers.ts +5 -3
- package/examples/notes-service/src/stdio.ts +7 -3
- package/examples/notes-service/src/worker.ts +6 -3
- package/examples/notes-service/test/commands.test.ts +164 -91
- package/examples/notes-service/test/notes/as.ts +3 -1
- package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
- package/examples/notes-service/test/notes/in-process.test.ts +26 -15
- package/examples/notes-service/test/notes/notes.test.ts +49 -31
- package/examples/notes-service/test/routes.test.ts +106 -71
- package/examples/notes-service/test/worker.test.ts +21 -13
- package/examples/quickstart/README.md +2 -6
- package/examples/quickstart/src/Api.ts +1 -0
- package/examples/quickstart/src/client.ts +6 -3
- package/examples/quickstart/src/greeting/Greeting.ts +1 -1
- package/examples/quickstart/src/greeting/handlers.ts +5 -2
- package/examples/quickstart/src/routes.ts +3 -2
- package/examples/quickstart/src/server.ts +6 -2
- package/examples/quickstart/test/routes.test.ts +3 -2
- package/package.json +19 -17
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Implementation, memoized } from "../contract/implementation.mjs";
|
|
2
|
+
import { EventImplementation } from "../contract/feed.mjs";
|
|
3
|
+
import { SubscriptionError, allowAll } from "../Event.mjs";
|
|
4
|
+
import { isNotFound } from "./watch.mjs";
|
|
5
|
+
import { sendersWith } from "./client.mjs";
|
|
6
|
+
import { allowAll as allowAll$1 } from "../Action.mjs";
|
|
7
|
+
import { Context, Effect, Option, Predicate, Schema, Stream } from "effect";
|
|
8
|
+
import { Headers, HttpClient, HttpClientRequest, HttpServerRequest } from "effect/http";
|
|
9
|
+
// src/http/placed.ts
|
|
10
|
+
const ownedByTheRequest = (name) => [
|
|
11
|
+
"host",
|
|
12
|
+
"connection",
|
|
13
|
+
"keep-alive",
|
|
14
|
+
"te",
|
|
15
|
+
"trailer",
|
|
16
|
+
"transfer-encoding",
|
|
17
|
+
"upgrade"
|
|
18
|
+
].includes(name) || [
|
|
19
|
+
"content-",
|
|
20
|
+
"accept",
|
|
21
|
+
"proxy-",
|
|
22
|
+
"sec-websocket-",
|
|
23
|
+
"mcp-"
|
|
24
|
+
].some((family) => name.startsWith(family));
|
|
25
|
+
const forwarded = Effect.map(Effect.serviceOption(HttpServerRequest.HttpServerRequest), Option.match({
|
|
26
|
+
onNone: () => Headers.empty,
|
|
27
|
+
onSome: ({ headers }) => Headers.fromInput(Object.entries(headers).filter(([name]) => !ownedByTheRequest(name)))
|
|
28
|
+
}));
|
|
29
|
+
var Sending = class extends Context.Service()("effect-actions/ActionHttp/placed/Sending") {};
|
|
30
|
+
const dispatching = HttpClient.makeWith((request) => Effect.flatMap(Effect.all([request, Effect.service(Sending)]), ([sent, client]) => client.execute(sent)), (request) => Effect.succeed(request));
|
|
31
|
+
const isSubscriptionError = Schema.is(SubscriptionError);
|
|
32
|
+
const isNotServed = Predicate.compose(isSubscriptionError, (error) => Predicate.isTagged(error.reason, "NotServed"));
|
|
33
|
+
const notServedThere = (name) => /* @__PURE__ */ new Error(`ActionHttp.placed: the server its client reaches does not serve ${name} (serve its implementation in that server's ActionHttp.layer of this binding)`);
|
|
34
|
+
/**
|
|
35
|
+
* The actions and event types of `binding` served where another server serves them, as a
|
|
36
|
+
* surface's implementations: each call and subscription sent on over `binding` with the client
|
|
37
|
+
* `client` answers for its decoded input and its contract, one of the binding's, carrying the
|
|
38
|
+
* caller's headers, and its answer, its errors and its occurrences passed back as they are. The
|
|
39
|
+
* surface serving them authenticates a protected one as its own and authorizes every caller; the
|
|
40
|
+
* server reached authenticates and authorizes again.
|
|
41
|
+
*
|
|
42
|
+
* **Gotchas**
|
|
43
|
+
*
|
|
44
|
+
* - The caller's credential is verified twice, by the surface serving these and by the server reached, so both are given the verifier: a credential expiring between the two is refused by the second.
|
|
45
|
+
* - The server reached builds its own verifier: one caching signing keys in memory fetches them in each of its instances, a Durable Object's again after each eviction.
|
|
46
|
+
* - Every header of the caller's request is sent on but its connection's, its body's, what it accepts and an MCP request's routing headers (`mcp-*`): one that must not reach the server reached is removed per contract by the client `client` returns, `HttpClient.mapRequest(HttpClientRequest.removeHeader("cookie"))`, which runs after them.
|
|
47
|
+
* - A header `client`'s client sets replaces the caller's of that name, as it is set after them: a service's own `authorization` is sent in place of the caller's.
|
|
48
|
+
* - A call with no HTTP request, as through `Action.client` in process or an MCP stdio surface, sends no header on: a protected one reaches the server reached with no credential, which refuses it with `Unauthenticated`.
|
|
49
|
+
* - An error the server reached declares beyond the action's own and the built-in ones, its binding's `error` or its verifier's, is a defect where it is passed back, answered as one.
|
|
50
|
+
* - A server reached that does not serve one of them, its route a 404, is a defect naming it, with the fix: a declared error whose status is 404 is passed back as it is.
|
|
51
|
+
* - A refusal of a subscription by the server reached comes after the surface admitted it, so it ends the subscription's stream where the surface's own would have answered its 401 or 403.
|
|
52
|
+
*/
|
|
53
|
+
const placed = (binding, client) => {
|
|
54
|
+
const route = client;
|
|
55
|
+
const sendingTo = (input, contract) => HttpClient.mapRequestInputEffect(route(input, contract), (request) => Effect.map(forwarded, (headers) => HttpClientRequest.setHeaders(request, headers)));
|
|
56
|
+
const senders = sendersWith(binding, dispatching, void 0);
|
|
57
|
+
const handlerOf = ({ method }, action) => {
|
|
58
|
+
const send = method(action);
|
|
59
|
+
return (input, reporting) => {
|
|
60
|
+
const sent = send(input, ...reporting === void 0 ? [] : [{ onProgress: reporting.progress }]);
|
|
61
|
+
const sending = sendingTo(input, action);
|
|
62
|
+
return Stream.isStream(sent) ? sent.pipe(Stream.provideService(Sending, sending), Stream.catchIf(isNotFound, () => Stream.die(notServedThere(action.name)))) : sent.pipe(Effect.provideService(Sending, sending), Effect.catchIf(isNotFound, () => Effect.die(notServedThere(action.name))));
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const runOf = ({ transport }, event) => ({
|
|
66
|
+
poll: (input, cursor) => transport(event, input).poll(cursor).pipe(Effect.provideService(Sending, sendingTo(input, event)), Effect.catchIf(isNotServed, () => Effect.die(notServedThere(event.name)))),
|
|
67
|
+
stream: (input, cursor) => transport(event, input).stream(cursor).pipe(Stream.provideService(Sending, sendingTo(input, event)), Stream.catchIf(isNotServed, () => Stream.die(notServedThere(event.name))))
|
|
68
|
+
});
|
|
69
|
+
return [...binding.actions.length === 0 ? [] : [new Implementation(binding.actions, memoized(Effect.map(senders, (built) => binding.actions.map((action) => [action, handlerOf(built, action)]))), memoized(Effect.succeed(allowAll$1)))], ...binding.events.length === 0 ? [] : [new EventImplementation(binding.events, binding.events.map((event) => [event, memoized(Effect.map(senders, (built) => runOf(built, event)))]), memoized(Effect.succeed(allowAll)))]];
|
|
70
|
+
};
|
|
71
|
+
export { placed };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { InvalidInput } from "../contract/errors.mjs";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { HttpApiMiddleware } from "effect/http-api";
|
|
4
|
-
|
|
4
|
+
// src/http/schema-errors.ts
|
|
5
5
|
const responseEncodingKinds = /* @__PURE__ */ new Set(["Body", "ResponseHeaders"]);
|
|
6
6
|
var SchemaErrors = class extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: [InvalidInput] }) {};
|
|
7
|
-
const schemaErrors =
|
|
8
|
-
//#endregion
|
|
7
|
+
const schemaErrors = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(responseEncodingKinds.has(failure.kind) ? failure : InvalidInput.fromSchemaError(failure.cause)));
|
|
9
8
|
export { SchemaErrors, schemaErrors };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Codec } from "../contract/codec.mjs";
|
|
2
|
+
import "../contract/progress.mjs";
|
|
3
|
+
import { ErasedValue } from "../contract/implementation.mjs";
|
|
4
|
+
import { StreamOf } from "../contract/stream.mjs";
|
|
5
|
+
import { Any } from "../Action.mjs";
|
|
6
|
+
import { Effect, Schema, Stream } from "effect";
|
|
7
|
+
import { HttpApiSchema } from "effect/http-api";
|
|
8
|
+
/** One event a route streams: a progress value, an element, or the answer it ends with. */
|
|
9
|
+
interface Sent {
|
|
10
|
+
readonly event: "progress" | "element" | "final";
|
|
11
|
+
readonly data: ErasedValue;
|
|
12
|
+
}
|
|
13
|
+
declare const eventOf: <const N extends Sent["event"], D extends Codec>(event: N, data: D) => Schema.Struct<{
|
|
14
|
+
readonly id: Schema.optional<Schema.String>;
|
|
15
|
+
readonly event: Schema.Literal<N>;
|
|
16
|
+
readonly data: Schema.fromJsonString<Schema.toCodecJson<D>>;
|
|
17
|
+
}>;
|
|
18
|
+
type EventOf<N extends Sent["event"], D extends Codec> = ReturnType<typeof eventOf<N, D>>;
|
|
19
|
+
/** The success of the endpoint of action `A` declaring errors `E`, as `ActionHttp` builds it. */
|
|
20
|
+
export type SuccessOf<A extends Any, E extends Schema.Top> = A["success"] extends StreamOf<infer Element> ? HttpApiSchema.StreamSse<Schema.Union<A["progress"] extends Codec ? readonly [EventOf<"element", Element>, EventOf<"progress", A["progress"]>] : readonly [EventOf<"element", Element>]>, E> : A["progress"] extends Codec ? Schema.toCodecJson<A["success"]> | HttpApiSchema.StreamSse<Schema.Union<readonly [EventOf<"progress", A["progress"]>, EventOf<"final", A["success"]>]>, E> : Schema.toCodecJson<A["success"]>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Progressed, answerFrom, elementsFrom } from "../contract/progress.mjs";
|
|
2
|
+
import { elementsOf } from "../contract/implementation.mjs";
|
|
3
|
+
import { concealed } from "../contract/reported.mjs";
|
|
4
|
+
import { Cause, Effect, ErrorReporter, Queue, Schema, Stream } from "effect";
|
|
5
|
+
import { HttpServerRequest } from "effect/http";
|
|
6
|
+
import { HttpApiSchema } from "effect/http-api";
|
|
7
|
+
// src/http/streamed.ts
|
|
8
|
+
const eventOf = (event, data) => Schema.Struct({
|
|
9
|
+
id: Schema.optional(Schema.String),
|
|
10
|
+
event: Schema.Literal(event),
|
|
11
|
+
data: Schema.fromJsonString(Schema.toCodecJson(data))
|
|
12
|
+
});
|
|
13
|
+
/** What a route of an action declaring `progress` answers without its stream: `success`. */
|
|
14
|
+
const answeredWithProgress = (success) => HttpApiSchema.isNoContent(success.ast) ? HttpApiSchema.NoContent : success;
|
|
15
|
+
/**
|
|
16
|
+
* What a route of an action declaring `progress` answers a request accepting
|
|
17
|
+
* `text/event-stream`: a `progress` event for each value its handler reports, then a `final`
|
|
18
|
+
* event with its answer. A declared failure after the first event ends the stream as Effect's
|
|
19
|
+
* stream failure event, and a defect as one saying `Internal server error`.
|
|
20
|
+
*/
|
|
21
|
+
const progressStreamOf = (progress, success, errors) => HttpApiSchema.StreamSse({
|
|
22
|
+
events: Schema.Union([eventOf("progress", progress), eventOf("final", success)]),
|
|
23
|
+
error: Schema.Union(errors)
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* What a route of an action answering a stream answers: an `element` event for each element,
|
|
27
|
+
* beside a `progress` event for each value its handler reports where it declares `progress`. A
|
|
28
|
+
* declared failure after the first event ends the stream as Effect's stream failure event, and
|
|
29
|
+
* a defect as one saying `Internal server error`.
|
|
30
|
+
*/
|
|
31
|
+
const elementStreamOf = (element, progress, errors) => HttpApiSchema.StreamSse({
|
|
32
|
+
events: Schema.Union(progress === void 0 ? [eventOf("element", element)] : [eventOf("element", element), eventOf("progress", progress)]),
|
|
33
|
+
error: Schema.Union(errors)
|
|
34
|
+
});
|
|
35
|
+
const streamed = (answer) => Effect.gen(function* () {
|
|
36
|
+
const sent = yield* Queue.bounded(1);
|
|
37
|
+
let streaming = false;
|
|
38
|
+
const send = (event) => Effect.andThen(Queue.offer(sent, event), Effect.sync(() => {
|
|
39
|
+
streaming = true;
|
|
40
|
+
}));
|
|
41
|
+
const reporting = { progress: (data) => send({
|
|
42
|
+
event: "progress",
|
|
43
|
+
data
|
|
44
|
+
}) };
|
|
45
|
+
const ended = (cause) => {
|
|
46
|
+
if (Cause.hasDies(cause)) return concealed(cause);
|
|
47
|
+
return streaming ? Effect.andThen(ErrorReporter.report(cause), Effect.failCause(cause)) : Effect.failCause(cause);
|
|
48
|
+
};
|
|
49
|
+
yield* answer(reporting, send).pipe(Effect.catchCause(ended), Queue.into(sent), Effect.forkScoped({ startImmediately: true }));
|
|
50
|
+
const first = yield* Effect.catchIf(Queue.take(sent), Cause.isDone, () => Effect.undefined);
|
|
51
|
+
return first === void 0 ? Stream.empty : Stream.concat(Stream.make(first), Stream.fromQueue(sent));
|
|
52
|
+
});
|
|
53
|
+
/** A check of `schema` failing as a defect, inside the call's boundary. */
|
|
54
|
+
const checkedBy = (schema) => {
|
|
55
|
+
const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(schema));
|
|
56
|
+
return (data) => Effect.orDie(encode(data));
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The route handler of an action declaring `progress`: its answer streamed with its progress
|
|
60
|
+
* where the request's `Accept` names `text/event-stream`, its answer alone otherwise.
|
|
61
|
+
*/
|
|
62
|
+
const answeringWithProgress = (success, run) => {
|
|
63
|
+
const check = checkedBy(success);
|
|
64
|
+
return (payload) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => (request.headers["accept"] ?? "").includes("text/event-stream") ? streamed((reporting, send) => run(payload, reporting).pipe(Effect.tap(check), Effect.flatMap((data) => send({
|
|
65
|
+
event: "final",
|
|
66
|
+
data
|
|
67
|
+
})))) : run(payload));
|
|
68
|
+
};
|
|
69
|
+
/** The route handler of an action answering a stream: its elements and its progress, streamed. */
|
|
70
|
+
const answeringElements = (element, run) => {
|
|
71
|
+
const check = checkedBy(element);
|
|
72
|
+
return (payload) => streamed((reporting, send) => Stream.runForEach(elementsOf(run(payload, reporting)), (data) => Effect.andThen(check(data), send({
|
|
73
|
+
event: "element",
|
|
74
|
+
data
|
|
75
|
+
}))));
|
|
76
|
+
};
|
|
77
|
+
const progressedOf = ({ event, data }) => {
|
|
78
|
+
if (event === "progress") return Progressed.Progress({ value: data });
|
|
79
|
+
return event === "final" ? Progressed.Final({ value: data }) : Progressed.Element({ value: data });
|
|
80
|
+
};
|
|
81
|
+
const answeredWithout = /* @__PURE__ */ new Error("A route of an action declaring progress or answering a stream answered without its event stream");
|
|
82
|
+
/** The events of a route's answer, as one stream of `Progressed`. */
|
|
83
|
+
const sentFrom = (answer) => Stream.isStream(answer) ? Stream.map(answer, progressedOf) : Stream.die(answeredWithout);
|
|
84
|
+
/**
|
|
85
|
+
* A client's answer from a route of an action declaring `progress`: each `progress` event handed
|
|
86
|
+
* to `onProgress`, where the call gave one, then the `final` event's answer.
|
|
87
|
+
*/
|
|
88
|
+
const readWithProgress = (answer, onProgress) => answerFrom(sentFrom(answer), onProgress);
|
|
89
|
+
/**
|
|
90
|
+
* A client's elements from a route of an action answering a stream: each `element` event's
|
|
91
|
+
* data, each `progress` event's handed to `onProgress`.
|
|
92
|
+
*/
|
|
93
|
+
const readElements = (answer, onProgress) => elementsFrom(sentFrom(answer), onProgress);
|
|
94
|
+
export { answeredWithProgress, answeringElements, answeringWithProgress, elementStreamOf, progressStreamOf, readElements, readWithProgress };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { fedOf } from "../contract/pages.mjs";
|
|
2
|
+
import { NotServed, SubscriptionError } from "../Event.mjs";
|
|
3
|
+
import { Receiving, webhookFed } from "../contract/receiving.mjs";
|
|
4
|
+
import { answeredStatus, failingWhenUnanswered, isTransient, receivedFrom } from "../contract/transient.mjs";
|
|
5
|
+
import { Effect, Predicate, Stream } from "effect";
|
|
6
|
+
import { HttpClientError } from "effect/http";
|
|
7
|
+
// src/http/watch.ts
|
|
8
|
+
/** Whether `error` is an HTTP client's failure on a 404 answer. */
|
|
9
|
+
const isNotFound = Predicate.compose(HttpClientError.isHttpClientError, (error) => answeredStatus(error) === 404);
|
|
10
|
+
/**
|
|
11
|
+
* The transport of one subscription to `event` over its route, `input` its decoded input: each
|
|
12
|
+
* page from `poll`, the stream from `stream`, and a webhook subscription from `webhook`'s routes
|
|
13
|
+
* where the binding has them, a 404 failing as `NotServed`.
|
|
14
|
+
*/
|
|
15
|
+
const transportOf = (event, poll, stream, webhook, input) => {
|
|
16
|
+
const notServed = Effect.fail(SubscriptionError.of(event.name, new NotServed()));
|
|
17
|
+
const answered = (request) => request.pipe(failingWhenUnanswered, Effect.catchIf(isNotFound, () => notServed));
|
|
18
|
+
const webhooks = webhook === void 0 ? void 0 : {
|
|
19
|
+
receiver: Effect.service(Receiving),
|
|
20
|
+
subscribe: (request) => answered(webhook.subscribe({ payload: {
|
|
21
|
+
input,
|
|
22
|
+
...request
|
|
23
|
+
} })),
|
|
24
|
+
unsubscribe: (request) => answered(webhook.unsubscribe({ payload: {
|
|
25
|
+
input,
|
|
26
|
+
...request
|
|
27
|
+
} })),
|
|
28
|
+
read: webhookFed(event)
|
|
29
|
+
};
|
|
30
|
+
const read = (sent) => sent.pipe(Stream.catchIf(Predicate.isTagged("Retry"), () => Stream.empty), receivedFrom("HTTP", event.name));
|
|
31
|
+
return {
|
|
32
|
+
name: event.name,
|
|
33
|
+
poll: (cursor) => answered(poll({ payload: {
|
|
34
|
+
input,
|
|
35
|
+
cursor
|
|
36
|
+
} })),
|
|
37
|
+
stream: (cursor) => Stream.unwrap(answered(stream({ payload: {
|
|
38
|
+
input,
|
|
39
|
+
cursor
|
|
40
|
+
} })).pipe(Effect.map((answer) => Stream.isStream(answer) ? read(answer) : Stream.fromIterable(fedOf({
|
|
41
|
+
items: answer.events,
|
|
42
|
+
truncated: answer.truncated,
|
|
43
|
+
from: cursor,
|
|
44
|
+
to: answer.cursor ?? cursor
|
|
45
|
+
}))))),
|
|
46
|
+
webhook: webhooks,
|
|
47
|
+
transient: isTransient
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export { isNotFound, transportOf };
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Anyone, principalOf } from "../contract/caller.mjs";
|
|
2
|
+
import { inputParseOptions } from "../contract/call.mjs";
|
|
3
|
+
import { reported } from "../contract/reported.mjs";
|
|
4
|
+
import { encodeUntaggedOf } from "../contract/occurrence.mjs";
|
|
5
|
+
import { acquireFeeds, unkeyedHook } from "../contract/feed.mjs";
|
|
6
|
+
import { NotServed, SubscriptionError, Unsupported } from "../Event.mjs";
|
|
7
|
+
import { sentError } from "../contract/codes.mjs";
|
|
8
|
+
import { Webhooks, offersWebhook, servedWebhooks, webhookSubscriber } from "../contract/webhooks.mjs";
|
|
9
|
+
import { pushed, refusalIn } from "../contract/push.mjs";
|
|
10
|
+
import { promote } from "../authentication/provider.mjs";
|
|
11
|
+
import { recordStepUp } from "../authentication/refusal.mjs";
|
|
12
|
+
import { Active, EventsError, Failed, Heartbeat, Notifications, Occurred, Requests, Terminated, codes, extension, subscriptionIdKey } from "./events.mjs";
|
|
13
|
+
import { isObjectSchema, routingFormsOf, toolJsonSchema } from "./protocol.mjs";
|
|
14
|
+
import { Cause, Context, Duration, Effect, Exit, Match, Option, Predicate, Result, Schema } from "effect";
|
|
15
|
+
import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
16
|
+
import { McpSchema } from "effect/ai";
|
|
17
|
+
// src/mcp/bridge.ts
|
|
18
|
+
const isGroup = (u) => Predicate.isObjectKeyword(u) && "merge" in u && Predicate.isFunction(u.merge) && "requests" in u && u.requests instanceof Map;
|
|
19
|
+
const groupOf = (adapter, key) => {
|
|
20
|
+
const group = Predicate.hasProperty(adapter, key) ? adapter[key] : void 0;
|
|
21
|
+
if (!isGroup(group)) throw new Error(`Effect's MCP ${adapter.protocolVersion} adapter holds no ${key} group: effect-actions supports MCP events on effect 4.0.x (install the effect version its peer range names)`);
|
|
22
|
+
return group;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The JSON Schemas `events/list` lists for `event`, refusing an input or a payload that is no
|
|
26
|
+
* object.
|
|
27
|
+
*/
|
|
28
|
+
const eventSchemasOf = (event) => {
|
|
29
|
+
const inputSchema = toolJsonSchema(Schema.toCodecJson(event.input), true);
|
|
30
|
+
if (!isObjectSchema(inputSchema)) throw new Error(`MCP event input must be one object with keys, such as a struct: ${event.name} (wrap a union, an array or a scalar in a field, or omit input for no arguments)`);
|
|
31
|
+
const payloadSchema = toolJsonSchema(Schema.toCodecJson(event.payload), false);
|
|
32
|
+
if (!isObjectSchema(payloadSchema)) throw new Error(`MCP event payload must be one object, such as a struct: ${event.name} (wrap an array or a scalar in a field)`);
|
|
33
|
+
return {
|
|
34
|
+
inputSchema,
|
|
35
|
+
payloadSchema
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const encodeServerInfo = Schema.encodeEffect(McpSchema.Implementation);
|
|
39
|
+
const internalError = new EventsError({
|
|
40
|
+
code: codes.internal,
|
|
41
|
+
message: "Internal error"
|
|
42
|
+
});
|
|
43
|
+
const isSubscriptionError = Schema.is(SubscriptionError);
|
|
44
|
+
const wireErrorOf = (cause) => {
|
|
45
|
+
const refusal = refusalIn(cause);
|
|
46
|
+
if (refusal !== void 0) return Effect.succeed(new EventsError(sentError(refusal)));
|
|
47
|
+
const failure = Cause.hasDies(cause) ? void 0 : Result.getOrUndefined(Cause.findFail(cause));
|
|
48
|
+
return isSubscriptionError(failure?.error) ? Effect.succeed(new EventsError(sentError(failure.error))) : Effect.as(reported(cause), internalError);
|
|
49
|
+
};
|
|
50
|
+
const headerMismatch = (request) => Effect.andThen(HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setStatus(response, 400))).pipe(Effect.provideService(HttpServerRequest.HttpServerRequest, request)), Effect.fail(new EventsError({
|
|
51
|
+
code: codes.headerMismatch,
|
|
52
|
+
message: "Mcp-Name header does not match request parameters"
|
|
53
|
+
})));
|
|
54
|
+
const routedTo = (name) => Effect.flatMap(Effect.serviceOption(HttpServerRequest.HttpServerRequest), (request) => Option.isNone(request) || routingFormsOf(name).includes(request.value.headers["mcp-name"] ?? "") ? Effect.void : headerMismatch(request.value));
|
|
55
|
+
const servedOf = (feed, delivery) => {
|
|
56
|
+
const { event } = feed;
|
|
57
|
+
const decode = Schema.decodeUnknownEffect(Schema.toCodecJson(event.input), inputParseOptions);
|
|
58
|
+
return {
|
|
59
|
+
feed,
|
|
60
|
+
decode: (args) => Effect.mapError(decode(args ?? {}), ({ message }) => new EventsError({
|
|
61
|
+
code: codes.invalidParams,
|
|
62
|
+
message: `Invalid arguments for event '${event.name}': ${message}`
|
|
63
|
+
})),
|
|
64
|
+
wire: encodeUntaggedOf(event),
|
|
65
|
+
listing: {
|
|
66
|
+
name: event.name,
|
|
67
|
+
description: event.description,
|
|
68
|
+
delivery,
|
|
69
|
+
...eventSchemasOf(event)
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Dies with the reason the endpoint of `serving` cannot serve one of its events, run where the
|
|
75
|
+
* endpoint's layer is built: a protected one with a `subscription` hook, under an authentication
|
|
76
|
+
* without a principal to key its polls by.
|
|
77
|
+
*/
|
|
78
|
+
const refuseUnservable = ({ events, authentication }) => authentication === void 0 ? Effect.void : Effect.flatMap(acquireFeeds(events), (feeds) => {
|
|
79
|
+
const refusal = unkeyedHook("MCP", feeds, authentication.principal !== void 0);
|
|
80
|
+
return refusal === void 0 ? Effect.void : Effect.die(new Error(refusal));
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Serves the feeds the endpoint of `serving` offers by webhook to `Webhooks`, where it is
|
|
84
|
+
* provided, run where the endpoint's layer is built: it restores their kept subscriptions, and
|
|
85
|
+
* dies where they cannot be kept.
|
|
86
|
+
*/
|
|
87
|
+
const serveWebhooks = (serving) => Effect.asVoid(Effect.flatMap(acquireFeeds(serving.events), (feeds) => servedWebhooks(feeds, serving.authentication)));
|
|
88
|
+
/**
|
|
89
|
+
* One push subscription, as `events/stream` holds it: what the feed's `pushed` sends, as
|
|
90
|
+
* SEP-3415's notifications. Its confirmation is `active`, a later gap a fresh `active`, a
|
|
91
|
+
* position a heartbeat, and a failure it reads on through `error`. A failure before `active` is
|
|
92
|
+
* the request's error; a refusal after it ends the subscription with `terminated`.
|
|
93
|
+
*/
|
|
94
|
+
const delivered = ({ send, clientId, requestId, wire, _meta: resultMeta }, run) => {
|
|
95
|
+
const notify = (rpc, payload) => send("2026-07-28", clientId, {
|
|
96
|
+
tag: rpc._tag,
|
|
97
|
+
payload
|
|
98
|
+
});
|
|
99
|
+
return Effect.gen(function* () {
|
|
100
|
+
const _meta = { [subscriptionIdKey]: requestId };
|
|
101
|
+
let active = false;
|
|
102
|
+
const activate = (cursor, truncated) => {
|
|
103
|
+
active = true;
|
|
104
|
+
return notify(Active, {
|
|
105
|
+
_meta,
|
|
106
|
+
cursor,
|
|
107
|
+
truncated
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const deliver = (sent) => Match.valueTags(sent, {
|
|
111
|
+
Gap: ({ cursor }) => activate(cursor, true),
|
|
112
|
+
Checked: ({ cursor }) => active ? notify(Heartbeat, {
|
|
113
|
+
_meta,
|
|
114
|
+
cursor
|
|
115
|
+
}) : activate(cursor, false),
|
|
116
|
+
Occurrence: (occurrence) => Effect.flatMap(wire(occurrence), (event) => notify(Occurred, {
|
|
117
|
+
...event,
|
|
118
|
+
_meta
|
|
119
|
+
})),
|
|
120
|
+
Failed: () => notify(Failed, {
|
|
121
|
+
_meta,
|
|
122
|
+
error: {
|
|
123
|
+
code: internalError.code,
|
|
124
|
+
message: internalError.message
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
const exit = yield* Effect.exit(run(deliver));
|
|
129
|
+
if (Exit.isFailure(exit)) {
|
|
130
|
+
if (Cause.hasInterruptsOnly(exit.cause)) return yield* Effect.interrupt;
|
|
131
|
+
const error = yield* wireErrorOf(exit.cause);
|
|
132
|
+
if (!active) return yield* error;
|
|
133
|
+
const { code, message, data } = error;
|
|
134
|
+
yield* notify(Terminated, {
|
|
135
|
+
_meta,
|
|
136
|
+
error: {
|
|
137
|
+
code,
|
|
138
|
+
message,
|
|
139
|
+
...data === void 0 ? {} : { data }
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
resultType: "complete",
|
|
145
|
+
_meta: {
|
|
146
|
+
...resultMeta,
|
|
147
|
+
..._meta
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* `base`, the 2026-07-28 adapter, also serving SEP-3415's `events/list`, `events/poll` and
|
|
154
|
+
* `events/stream` for the event implementations of `serving`, and advertising the extension in
|
|
155
|
+
* its discovery.
|
|
156
|
+
*/
|
|
157
|
+
const withEvents = (base, serving) => {
|
|
158
|
+
const { authentication } = serving;
|
|
159
|
+
const asSubscriber = (event, run) => authentication === void 0 || event.caller === Anyone ? run : recordStepUp(promote(authentication, run));
|
|
160
|
+
const installHandlers = (core, lifecycle, target) => Effect.gen(function* () {
|
|
161
|
+
const feeds = yield* acquireFeeds(serving.events);
|
|
162
|
+
const built = yield* Effect.context();
|
|
163
|
+
const send = target.context.sendNotification;
|
|
164
|
+
const meta = Effect.map(Effect.orDie(encodeServerInfo(target.context.serverInfo)), (serverInfo) => ({ "io.modelcontextprotocol/serverInfo": serverInfo }));
|
|
165
|
+
const webhooks = Context.getOption(built, Webhooks);
|
|
166
|
+
const webhookOffered = (event) => Option.isSome(webhooks) && offersWebhook(event, authentication);
|
|
167
|
+
const deliveryOf = (event) => [
|
|
168
|
+
"poll",
|
|
169
|
+
...send === void 0 ? [] : ["push"],
|
|
170
|
+
...webhookOffered(event) ? ["webhook"] : []
|
|
171
|
+
];
|
|
172
|
+
const served = new Map(feeds.map((feed) => [feed.event.name, servedOf(feed, deliveryOf(feed.event))]));
|
|
173
|
+
const servedNamed = (name) => {
|
|
174
|
+
const found = served.get(name);
|
|
175
|
+
return found === void 0 ? Effect.fail(new EventsError(sentError(SubscriptionError.of(name, new NotServed())))) : Effect.succeed(found);
|
|
176
|
+
};
|
|
177
|
+
const asWebhook = (feed, run) => Effect.catchCause(asSubscriber(feed.event, Effect.flatMap(webhookSubscriber(feed, authentication, webhooks), run)), (cause) => Effect.flatMap(wireErrorOf(cause), Effect.fail));
|
|
178
|
+
const fromBuilt = (handled) => Effect.updateContext(handled, (request) => Context.merge(built, request));
|
|
179
|
+
const handlers = Requests.of({
|
|
180
|
+
"events/list": () => Effect.map(meta, (_meta) => ({
|
|
181
|
+
resultType: "complete",
|
|
182
|
+
events: [...served.values()].map(({ listing }) => listing),
|
|
183
|
+
_meta
|
|
184
|
+
})),
|
|
185
|
+
"events/poll": ({ name, arguments: args, cursor, maxEvents }) => Effect.gen(function* () {
|
|
186
|
+
yield* routedTo(name);
|
|
187
|
+
const { feed, decode, wire } = yield* servedNamed(name);
|
|
188
|
+
const input = yield* decode(args);
|
|
189
|
+
const { page, events } = yield* Effect.gen(function* () {
|
|
190
|
+
const polled = yield* asSubscriber(feed.event, Effect.flatMap(principalOf(feed.event, authentication), (subscriber) => feed.poll(input, cursor ?? null, {
|
|
191
|
+
subscriber,
|
|
192
|
+
maxEvents
|
|
193
|
+
})));
|
|
194
|
+
return {
|
|
195
|
+
page: polled,
|
|
196
|
+
events: yield* Effect.forEach(polled.events, wire)
|
|
197
|
+
};
|
|
198
|
+
}).pipe(Effect.catchCause((cause) => Effect.flatMap(wireErrorOf(cause), Effect.fail)));
|
|
199
|
+
return {
|
|
200
|
+
resultType: "complete",
|
|
201
|
+
events,
|
|
202
|
+
cursor: page.cursor,
|
|
203
|
+
truncated: page.truncated,
|
|
204
|
+
hasMore: page.hasMore,
|
|
205
|
+
nextPollMs: Duration.toMillis(page.nextPoll),
|
|
206
|
+
_meta: yield* meta
|
|
207
|
+
};
|
|
208
|
+
}).pipe(fromBuilt),
|
|
209
|
+
"events/stream": ({ name, arguments: args, cursor }, { client, requestId }) => Effect.gen(function* () {
|
|
210
|
+
yield* routedTo(name);
|
|
211
|
+
const { feed, decode, wire } = yield* servedNamed(name);
|
|
212
|
+
const input = yield* decode(args);
|
|
213
|
+
if (send === void 0) return yield* new EventsError(sentError(SubscriptionError.of(name, new Unsupported({
|
|
214
|
+
feature: "deliveryMode",
|
|
215
|
+
value: "push"
|
|
216
|
+
}))));
|
|
217
|
+
return yield* delivered({
|
|
218
|
+
send,
|
|
219
|
+
clientId: client.id,
|
|
220
|
+
requestId,
|
|
221
|
+
wire,
|
|
222
|
+
_meta: yield* meta
|
|
223
|
+
}, (deliver) => asSubscriber(feed.event, Effect.flatMap(feed.stream(input), (open) => pushed(open, cursor ?? null, deliver))));
|
|
224
|
+
}).pipe(fromBuilt),
|
|
225
|
+
"events/subscribe": ({ name, arguments: args, delivery: { url, secret }, cursor, ttlMs }) => Effect.gen(function* () {
|
|
226
|
+
yield* routedTo(name);
|
|
227
|
+
const { feed, decode } = yield* servedNamed(name);
|
|
228
|
+
const input = yield* decode(args);
|
|
229
|
+
return {
|
|
230
|
+
resultType: "complete",
|
|
231
|
+
...yield* asWebhook(feed, ({ webhooks: service, ...subscriber }) => service.subscribe({
|
|
232
|
+
...subscriber,
|
|
233
|
+
input,
|
|
234
|
+
delivery: {
|
|
235
|
+
url,
|
|
236
|
+
secret
|
|
237
|
+
},
|
|
238
|
+
cursor: cursor ?? null,
|
|
239
|
+
ttlMs
|
|
240
|
+
})),
|
|
241
|
+
_meta: yield* meta
|
|
242
|
+
};
|
|
243
|
+
}).pipe(fromBuilt),
|
|
244
|
+
"events/unsubscribe": ({ name, arguments: args, delivery }) => Effect.gen(function* () {
|
|
245
|
+
yield* routedTo(name);
|
|
246
|
+
const { feed, decode } = yield* servedNamed(name);
|
|
247
|
+
const input = yield* decode(args);
|
|
248
|
+
yield* asWebhook(feed, ({ webhooks: service, feed: subscribed, principal }) => service.unsubscribe({
|
|
249
|
+
feed: subscribed,
|
|
250
|
+
principal,
|
|
251
|
+
input,
|
|
252
|
+
delivery
|
|
253
|
+
}));
|
|
254
|
+
return {
|
|
255
|
+
resultType: "complete",
|
|
256
|
+
_meta: yield* meta
|
|
257
|
+
};
|
|
258
|
+
}).pipe(fromBuilt)
|
|
259
|
+
});
|
|
260
|
+
yield* Effect.asVoid(base.installHandlers(core, lifecycle, {
|
|
261
|
+
...target,
|
|
262
|
+
context: {
|
|
263
|
+
...target.context,
|
|
264
|
+
serverInfo: {
|
|
265
|
+
...target.context.serverInfo,
|
|
266
|
+
extensions: {
|
|
267
|
+
...target.context.serverInfo.extensions,
|
|
268
|
+
[extension]: { listChanged: false }
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
yield* target.install(base, Requests, handlers);
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
...base,
|
|
277
|
+
clientRpcs: groupOf(base, "clientRpcs").merge(Requests),
|
|
278
|
+
handlerRpcs: groupOf(base, "handlerRpcs").merge(Requests),
|
|
279
|
+
serverNotificationRpcs: groupOf(base, "serverNotificationRpcs").merge(Notifications),
|
|
280
|
+
installHandlers
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
export { eventSchemasOf, refuseUnservable, serveWebhooks, withEvents };
|