@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
package/dist/ActionHttp.d.mts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { Anyone } from "./contract/caller.mjs";
|
|
2
2
|
import { BuiltIn, BuiltIns } from "./contract/errors.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { Any
|
|
5
|
-
import {
|
|
3
|
+
import { EventBuildError, EventBuildServices, EventServedRequest, HoldingEvents, ServingEvents } from "./contract/feed.mjs";
|
|
4
|
+
import { Any } from "./Event.mjs";
|
|
5
|
+
import { BuildError, Holding, Known, Member, MiddlewareOf, Selected, SelectedEvents, Served } from "./contract/implementation.mjs";
|
|
6
|
+
import { Any as Any$1, Errors } from "./Action.mjs";
|
|
6
7
|
import { Certain, ErrorsOf } from "./contract/declared.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { Any as Any$2, DescriptorOf, LayerIdentity, Matching, PublicOnly, RemoteRequest, Required, ServedProvider, ServerBuildServices, VerifierError } from "./authentication/provider.mjs";
|
|
9
|
+
import { Only } from "./contract/listed.mjs";
|
|
10
|
+
import { SubscriptionOf } from "./contract/push.mjs";
|
|
11
|
+
import { pageOf, sentOf } from "./http/events.mjs";
|
|
12
|
+
import { AnyHttp, Client, MethodError, Options, WatchError } from "./http/client.mjs";
|
|
13
|
+
import { SuccessOf } from "./http/streamed.mjs";
|
|
14
|
+
import { Placed, placed } from "./http/placed.mjs";
|
|
8
15
|
import { Context, Effect, FileSystem, Layer, Path, Schema, Types } from "effect";
|
|
9
16
|
import { Etag, HttpClient, HttpClientError, HttpPlatform, HttpRouter } from "effect/http";
|
|
10
17
|
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware } from "effect/http-api";
|
|
11
|
-
//#region src/http/ActionHttp.d.ts
|
|
12
18
|
/** What `fetchClient` takes: `client`'s options, and the `fetch` its calls send with. */
|
|
13
19
|
export interface FetchClientOptions<E = HttpClientError.HttpClientError, R = never> extends Options<E, R> {
|
|
14
20
|
/**
|
|
@@ -28,29 +34,32 @@ type Through<M extends Middleware, R> = number extends M["length"] ? R | HttpApi
|
|
|
28
34
|
type ServerOnly<M extends Middleware, E extends Errors> = [Exclude<HttpApiMiddleware.Error<IdOf<M[number]>>, Extract<Certain<E>, Errors[number]>["Type"] | BuiltIn> | HttpApiMiddleware.MiddlewareClient<IdOf<M[number]>>] extends [never] ? unknown : {
|
|
29
35
|
readonly "Layer middleware fails only with the binding's errors and needs no client": never;
|
|
30
36
|
};
|
|
31
|
-
type HttpLayer<H extends AnyHttp, Apps extends Served, M extends Middleware, A extends Any = H["actions"][number]> = Layer.Layer<never, BuildError<Holding<Member<Apps>, A>, A>, ServerBuildServices<Holding<Member<Apps>, A>, A> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", LayerRequest<Member<Apps>, A, M>> | ServedProvider<Member<Apps>, A, H extends {
|
|
37
|
+
type HttpLayer<H extends AnyHttp, Apps extends Served, M extends Middleware, A extends Any$1 = H["actions"][number], E extends Any = H["events"][number]> = Layer.Layer<never, BuildError<Holding<Member<Apps>, A>, A> | EventBuildError<HoldingEvents<Member<Apps>, E>, E>, ServerBuildServices<Holding<Member<Apps>, A>, A> | EventBuildServices<HoldingEvents<Member<Apps>, E>, E> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", LayerRequest<Member<Apps>, A, E, M>> | ServedProvider<Member<Apps>, A, H extends {
|
|
32
38
|
readonly authentication: infer D;
|
|
33
|
-
} ? D : never
|
|
39
|
+
} ? D : never, ServingEvents<Member<Apps>, E>> | IdOf<M[number]> | Etag.Generator | FileSystem.FileSystem | HttpPlatform.HttpPlatform | Path.Path>;
|
|
34
40
|
/** `ActionHttp.layer`'s options. */
|
|
35
|
-
export interface LayerOptions<M extends Middleware = [],
|
|
41
|
+
export interface LayerOptions<M extends Middleware = [], C extends Any$1 | Any = never> {
|
|
36
42
|
/**
|
|
37
|
-
* The actions it serves, among the binding's
|
|
38
|
-
* Defaults to every one of them.
|
|
43
|
+
* The actions and event types it serves, among the binding's the implementations hold:
|
|
44
|
+
* `[GetUser, UserRenamed]`, exactly those. Defaults to every one of them.
|
|
45
|
+
*
|
|
46
|
+
* **Gotchas**
|
|
47
|
+
*
|
|
48
|
+
* - A list naming actions alone serves no event type: each event route answers 404 until the event types are listed beside them; two layers of one binding given lists that overlap fail at startup with Effect's duplicate-route message.
|
|
39
49
|
*/
|
|
40
|
-
readonly actions?:
|
|
50
|
+
readonly actions?: readonly C[] | undefined;
|
|
41
51
|
/**
|
|
42
52
|
* Native endpoint middleware every served route runs, the first innermost: inside the
|
|
43
53
|
* authentication of a protected route, outside its decoding, so it also sees invalid input.
|
|
44
54
|
*
|
|
45
55
|
* **Gotchas**
|
|
46
56
|
*
|
|
47
|
-
* - A middleware requiring the identity needs a layer serving only protected actions: serve the public ones from a second layer without it.
|
|
57
|
+
* - A middleware requiring the identity needs a layer serving only protected actions and event types: serve the public ones from a second layer without it.
|
|
58
|
+
* - On a stream answering an action declaring `progress`, or one answering a stream, a middleware sees the call end at its first event, while its handler runs on, and never sees a later failure; what it provides lasts the handler.
|
|
48
59
|
*/
|
|
49
60
|
readonly middleware?: M;
|
|
50
61
|
}
|
|
51
|
-
type LayerRequest<App, A extends Any, M extends Middleware> = Exclude<Through<M, RemoteRequest<App, A>>, HttpRouter.Provided |
|
|
52
|
-
readonly caller: typeof Anyone;
|
|
53
|
-
}>] extends [never] ? Identity<Protected<Serving<App, A>>> : never)>;
|
|
62
|
+
type LayerRequest<App, A extends Any$1, E extends Any, M extends Middleware> = Exclude<Through<M, RemoteRequest<App, A> | EventServedRequest<App, E>>, HttpRouter.Provided | LayerIdentity<App, A, E>>;
|
|
54
63
|
/** Contract-level configuration shared by servers and clients. */
|
|
55
64
|
interface Options$1<E extends Errors = Errors> {
|
|
56
65
|
/**
|
|
@@ -78,20 +87,24 @@ interface Options$1<E extends Errors = Errors> {
|
|
|
78
87
|
*
|
|
79
88
|
* - One descriptor per binding: actions of a second identity take a second binding with its own `prefix`, and a public action may be in both.
|
|
80
89
|
*/
|
|
81
|
-
readonly authentication?: Any$
|
|
90
|
+
readonly authentication?: Any$2;
|
|
82
91
|
}
|
|
83
|
-
type
|
|
84
|
-
type
|
|
92
|
+
type EndpointError<A extends Any$1, E extends Errors, D> = Schema.toCodecJson<A["error"][number] | E[number] | VerifierError<A, D> | BuiltIns>;
|
|
93
|
+
type Endpoint<A extends Any$1, E extends Errors, D> = A extends Any$1 ? HttpApiEndpoint.HttpApiEndpoint<A["name"], "POST", `/${string}`, never, never, Schema.toCodecJson<A["input"]>, never, SuccessOf<A, EndpointError<A, E, D>>, EndpointError<A, E, D>> : never;
|
|
94
|
+
type EventEndpoint<V extends Any, E extends Errors, D> = V extends Any ? HttpApiEndpoint.HttpApiEndpoint<V["name"], "POST", `/${string}`, never, never, Schema.toCodecJson<ReturnType<typeof SubscriptionOf<V>>>, never, Schema.toCodecJson<ReturnType<typeof pageOf<V>>> | ReturnType<typeof sentOf<V>>, Schema.toCodecJson<E[number] | VerifierError<V, D> | BuiltIns>> : never;
|
|
95
|
+
type Api<Actions extends readonly Any$1[], E extends Errors, D, Events extends readonly Any[]> = HttpApi.HttpApi<"actions", HttpApiGroup.HttpApiGroup<string, Endpoint<Actions[number], E, D> | EventEndpoint<Events[number], E, D>, true>>;
|
|
85
96
|
/**
|
|
86
|
-
* An HTTP binding: actions, the errors every endpoint declares, and where they
|
|
87
|
-
* Plain data, so a copy of it, or one made by another installed copy of this
|
|
88
|
-
* serves the same; `layer` serves it and `client` calls it.
|
|
97
|
+
* An HTTP binding: actions and event types, the errors every endpoint declares, and where they
|
|
98
|
+
* are mounted. Plain data, so a copy of it, or one made by another installed copy of this
|
|
99
|
+
* package, serves the same; `layer` serves it and `client` calls it.
|
|
89
100
|
*/
|
|
90
|
-
export interface Binding<Actions extends
|
|
101
|
+
export interface Binding<Actions extends readonly Any$1[], E extends Errors = [], D extends Any$2 | undefined = undefined, Events extends readonly Any[] = readonly []> {
|
|
91
102
|
/** Its protected actions' authentication descriptor, or `undefined` without one. */
|
|
92
103
|
readonly authentication: D;
|
|
93
104
|
/** The exact actions bound to this binding. */
|
|
94
105
|
readonly actions: Actions;
|
|
106
|
+
/** The exact event types bound to this binding, each at `POST <prefix>/events/<name>`. */
|
|
107
|
+
readonly events: Events;
|
|
95
108
|
/** The errors every endpoint declares besides its action's own, as a list. */
|
|
96
109
|
readonly error: E;
|
|
97
110
|
/** Where its routes mount: `/api` by default, `/` at the root, without a trailing slash. */
|
|
@@ -108,24 +121,30 @@ export interface Binding<Actions extends ReadonlyArray<Any>, E extends Errors =
|
|
|
108
121
|
* - Combined into one document, `HttpApi.make("app").addHttpApi(A.api).addHttpApi(B.api)`, a shared action name makes `OpenApi.fromApi` throw `Duplicate OpenAPI operationId`, and a shared prefix silently drops one binding's actions from the document and a native client: otherwise document each alone.
|
|
109
122
|
* - A binding holding no protected action documents no security scheme, so Swagger shows no Authorize button.
|
|
110
123
|
*/
|
|
111
|
-
readonly api: Api<Actions, E, D>;
|
|
124
|
+
readonly api: Api<Actions, E, D, Events>;
|
|
112
125
|
}
|
|
113
126
|
/**
|
|
114
127
|
* Bind actions once, for servers and clients alike, each at `POST <prefix>/<action>`, a
|
|
115
|
-
* `readOnly` one too
|
|
116
|
-
*
|
|
117
|
-
*
|
|
128
|
+
* `readOnly` one too, and event types, each at `POST <prefix>/events/<name>`. Every endpoint
|
|
129
|
+
* declares its action's errors, the binding's `error`, and the built-in `InvalidInput`,
|
|
130
|
+
* `Unauthenticated` and `Forbidden`, so clients decode each as a typed failure; a protected one
|
|
131
|
+
* also declares what its descriptor's verifier fails with.
|
|
118
132
|
* Guide: `docs/ActionHttp.md` in this package; in an application: `examples/notes-service/src/Api.ts`.
|
|
133
|
+
*
|
|
134
|
+
* **Gotchas**
|
|
135
|
+
*
|
|
136
|
+
* - An action and an event type of one name, or an event type listed twice, throw when `make` is called: they would share a client's method.
|
|
137
|
+
* - A protected event type, where `authentication` has `principal`, is bound at `POST <prefix>/events/<name>/subscribe` and `/unsubscribe` too, for `Webhook.layer`'s delivery, and OpenAPI lists both: a binding cannot know whether its server provides that layer, and without it they answer a 409 `Event.SubscriptionError`, reason `Unsupported`.
|
|
119
138
|
*/
|
|
120
|
-
export declare function make<const
|
|
139
|
+
export declare function make<const Contracts extends ReadonlyArray<(Any$1 | Any) & {
|
|
121
140
|
readonly caller: typeof Anyone;
|
|
122
|
-
}>>(
|
|
123
|
-
export declare function make<const
|
|
124
|
-
export declare function make<const
|
|
141
|
+
}>>(contracts: Contracts): Binding<Only<Contracts, Any$1>, [], undefined, Only<Contracts, Any>>;
|
|
142
|
+
export declare function make<const Contracts extends ReadonlyArray<Any$1 | Any>, const O extends Options$1 = {}>(contracts: Contracts, options: O & Required<Contracts[number]> & Matching<Contracts[number], NoInfer<DescriptorOf<O>>> & NoInfer<Known<O, Options$1>>): Binding<Only<Contracts, Any$1>, ErrorsOf<O>, DescriptorOf<O>, Only<Contracts, Any>>;
|
|
143
|
+
export declare function make<const Contracts extends ReadonlyArray<Any$1 | Any>>(contracts: Contracts & PublicOnly<Contracts>): Binding<Only<Contracts, Any$1>, [], undefined, Only<Contracts, Any>>;
|
|
125
144
|
/**
|
|
126
|
-
* Serve the binding's actions among `implementations` in one layer, or those its
|
|
127
|
-
* authenticating a protected action's request before decoding it, then running
|
|
128
|
-
* implementation's `authorize` before each handler. An implementation's actions the binding
|
|
145
|
+
* Serve the binding's actions and event types among `implementations` in one layer, or those its
|
|
146
|
+
* `actions` lists, authenticating a protected action's request before decoding it, then running
|
|
147
|
+
* its implementation's `authorize` before each handler. An implementation's actions the binding
|
|
129
148
|
* leaves out have no route here, one holding none of those served is not built, and a non-empty
|
|
130
149
|
* list holding none of the binding's actions is refused when `layer` is called. Several layers
|
|
131
150
|
* may serve one binding, each with its own `actions` and `middleware` covering its routes only.
|
|
@@ -139,18 +158,27 @@ export declare function make<const Actions extends ReadonlyArray<Any>>(actions:
|
|
|
139
158
|
*
|
|
140
159
|
* **Gotchas**
|
|
141
160
|
*
|
|
142
|
-
* - A success is its JSON as the body; an action without `success` answers an empty 200, no body and no content type, so a hand-written client must not parse it
|
|
161
|
+
* - A success is its JSON as the body; an action without `success` answers an empty 200, no body and no content type, so a hand-written client must not parse it, or an empty 204 where it declares `progress`. One without `input` still takes the body `{}` typed as JSON: no body is a 400.
|
|
162
|
+
* - An action declaring `progress` answers a request whose `Accept` names `text/event-stream`, whatever else it names, with an SSE stream, listed in OpenAPI: a `progress` event for each value, its data the value's JSON, then one `final` event holding the answer's JSON, `null` for none; without that `Accept` it answers as any action, its progress dropped.
|
|
163
|
+
* - An action answering a stream, `Action.Stream`, answers an SSE stream alone, whatever `Accept` says: an `element` event for each element, its data the element's JSON, and a `progress` event for each value where it declares `progress`; a stream of none answers a 200 with no event. A failure before the first event answers its status; one after ends the stream as Effect's stream failure event, a defect as `Internal server error`.
|
|
164
|
+
* - The stream starts with the call's first event, so a failure before it, a refusal included, answers as it would without the stream; a declared failure after it ends the stream with Effect's `effect/http-api/stream/failure` event, reported as `Info` as without the stream, and a defect or an answer that does not encode with one saying `Internal server error`, its cause logged and reported.
|
|
143
165
|
* - A body that is not JSON is a 400 `InvalidInput`; a request with no `Content-Type` a 415 `Unsupported content-type: none`, one with another type Effect's own 415; each after authentication on a protected route.
|
|
144
166
|
* - A defect, an undeclared failure, or a success or an error that does not encode answers an empty 500: the cause goes to the log and to the call's `ErrorReporter`s, never to the body.
|
|
145
167
|
* - A path no layer serves, or a method other than `POST` on a route, is a 404.
|
|
146
168
|
* - The 415 keeps from every handler what another site's page can send without a CORS preflight, such as a form post carrying the user's cookie; the server's CORS policy decides the rest.
|
|
147
169
|
* - Router middleware providing a request service `X` is declared `HttpRouter.middleware<{ provides: X }>()`: without the type argument it runs and the layer still owes `X`.
|
|
148
170
|
* - `HttpRouter.provideRequest(layer)` builds its layer once, at startup, so it never provides an identity or a tenant.
|
|
149
|
-
* - A request body is read whole, however large, unless the host provides `HttpServerRequest.MaxBodySize` to `HttpRouter.serve`, limiting every route that reads its body whole, MCP endpoints included; a body a route reads as a stream is not limited. On Node a request over it gets no response, its connection closed
|
|
171
|
+
* - A request body is read whole, however large, unless the host provides `HttpServerRequest.MaxBodySize` to `HttpRouter.serve`, limiting every route that reads its body whole, MCP endpoints included; a body a route reads as a stream is not limited. On Node a request over it is an empty 413 from Effect 4.0.6, and before it gets no response, its connection closed; `HttpRouter.toWebHandler` ignores it, so limit bodies in the platform serving the handler.
|
|
150
172
|
* - An `ErrorReporter` the server runs with is given, as `Info`, a handler's declared failure and an input that does not decode, never a refusal or a refused credential: give an error class `override readonly [ErrorReporter.ignore] = true` to keep it out, and drop `Info` for a built-in error, which takes no mark.
|
|
173
|
+
* - An event's route takes `{ input, cursor }`, a `cursor` omitted or `null` reading from now, and answers a page, `{ events, cursor, truncated, hasMore, nextPollMs }`, or, to a request accepting `text/event-stream`, an SSE stream of `Checked`, `Occurrence`, `Gap` and `Failed` data, a `Checked` every 30 seconds; the stream carries no `id:`, so `Last-Event-ID` resumes nothing: resume from a cursor in the body.
|
|
174
|
+
* - A subscription is refused as an action's call is, before its stream starts, with the route's 401 or 403; a refusal later ends the stream with Effect's `effect/http-api/stream/failure` event.
|
|
175
|
+
* - A source's failure no refusal explains is logged and reported once, sent as a bare `Failed`, and read again from where it reached after 5 seconds, before the stream's first `Checked` too; a poll's is a 500, which `client`'s `watch` reconnects after.
|
|
176
|
+
* - A stream is read only as fast as its client reads it: a slow reader of a log source falls behind its log, and receives a `Gap`.
|
|
177
|
+
* - A protected event with a `subscription` hook needs `principal` on its descriptor to key poll subscribers: without it the layer fails to build with a message naming the event.
|
|
178
|
+
* - On a host with no lasting process, such as `HttpRouter.toWebHandler` on a serverless platform, serve check sources alone, without a `subscription` hook or `Webhook.layer`: nothing refuses the others, and under `Event.layerMemory` each instance keeps its own log of a log source, whose cursors another reads as a `Gap`, and its own leases and webhook subscriptions, lost as it ends.
|
|
151
179
|
*/
|
|
152
180
|
export declare function layer<const H extends AnyHttp, const Apps extends Served>(binding: H, implementations: Apps): HttpLayer<H, Apps, []>;
|
|
153
|
-
export declare function layer<const H extends AnyHttp, const Apps extends Served, const O extends LayerOptions<Middleware, H["actions"][number]>>(binding: H, implementations: Apps, options: O & ServerOnly<MiddlewareOf<O, Middleware>, H["error"]> & NoInfer<Known<O, LayerOptions<Middleware>>>): HttpLayer<H, Apps, MiddlewareOf<O, Middleware>, Selected<O, H["actions"][number]>>;
|
|
181
|
+
export declare function layer<const H extends AnyHttp, const Apps extends Served, const O extends LayerOptions<Middleware, H["actions"][number] | H["events"][number]>>(binding: H, implementations: Apps, options: O & ServerOnly<MiddlewareOf<O, Middleware>, H["error"]> & NoInfer<Known<O, LayerOptions<Middleware>>>): HttpLayer<H, Apps, MiddlewareOf<O, Middleware>, Selected<O, H["actions"][number]>, SelectedEvents<O, H["events"][number]>>;
|
|
154
182
|
/**
|
|
155
183
|
* Effect's native `HttpApiClient` for a binding, one method per action taking the
|
|
156
184
|
* action's input directly: `client.greet({ name })`, the argument omitted when `{}` is a valid
|
|
@@ -163,6 +191,11 @@ export declare function layer<const H extends AnyHttp, const Apps extends Served
|
|
|
163
191
|
*
|
|
164
192
|
* - An input that does not encode fails with `InvalidInput`, and nothing is sent; undeclared input fields, which TypeScript lets through on a wider value, are dropped as it encodes, since the server would refuse them.
|
|
165
193
|
* - It interprets no status and retries nothing: a failed write may have happened, and only a declared error says what the server did.
|
|
194
|
+
* - `watch(event, input, options)` holds the event's SSE stream open, or polls its pages with `mode: "poll"`, as `Action.client`'s; a watch of an event the server does not serve, its route a 404, fails with `Event.SubscriptionError`, reason `NotServed`.
|
|
195
|
+
* - A watch reconnects after the transport fails, a status such as 503, or 60 seconds without an answer or a frame, a second later and doubling to 30 seconds, each reconnect logged as a warning; a declared error, a refusal included, ends it.
|
|
196
|
+
* - A watch's stream ends at an SSE `retry:` field, which `ActionHttp.layer` never sends, as Effect's client decodes it, and the watch connects again: a server or proxy sending one as each stream starts delivers nothing.
|
|
197
|
+
* - A call of an action declaring `progress` reads the route's event stream, as every request accepts it, and its answer from the `final` event; the server sends the values whether or not the call gave `onProgress`, and without one they are dropped.
|
|
198
|
+
* - A call of an action answering a stream returns a `Stream` of its elements, failing as the route's stream fails, with `Sse.SseError` or `Sse.Retry` too; its `onProgress` gets each `progress` event, which the server sends whether or not the call gave one.
|
|
166
199
|
*/
|
|
167
200
|
export declare function client<const H extends AnyHttp>(binding: H, options?: Options): Effect.Effect<Client<H>, never, HttpClient.HttpClient>;
|
|
168
201
|
export declare function client<const H extends AnyHttp, E, R>(binding: H, options: Options<E, R>): Effect.Effect<Client<H, E, R>, never, HttpClient.HttpClient>;
|
|
@@ -174,5 +207,4 @@ export declare function client<const H extends AnyHttp, E, R>(binding: H, option
|
|
|
174
207
|
*/
|
|
175
208
|
export declare function fetchClient<const H extends AnyHttp>(binding: H, options?: FetchClientOptions): Client<H>;
|
|
176
209
|
export declare function fetchClient<const H extends AnyHttp, E, R>(binding: H, options: FetchClientOptions<E, R>): Client<H, E, R>;
|
|
177
|
-
|
|
178
|
-
export { type AnyHttp as Any, type Client, type Options as ClientOptions, type MethodError, Options$1 as Options };
|
|
210
|
+
export { type AnyHttp as Any, type Client, type Options as ClientOptions, type MethodError, Options$1 as Options, type Placed, type WatchError, placed };
|
package/dist/ActionHttp.mjs
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { Anyone } from "./contract/caller.mjs";
|
|
2
2
|
import { declared } from "./contract/status.mjs";
|
|
3
|
-
import { bindingErrors, bindingOf, projectedErrors } from "./contract/declared.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { bindingErrors, bindingOf, eventErrors, projectedErrors } from "./contract/declared.mjs";
|
|
4
|
+
import { elementOf } from "./contract/stream.mjs";
|
|
5
|
+
import { acquire } from "./contract/implementation.mjs";
|
|
6
6
|
import { inputParseOptions } from "./contract/call.mjs";
|
|
7
|
+
import { acquireFeeds, unkeyedHook } from "./contract/feed.mjs";
|
|
8
|
+
import { offersWebhook, servedWebhooks } from "./contract/webhooks.mjs";
|
|
9
|
+
import { providerOf } from "./authentication/provider.mjs";
|
|
10
|
+
import { listed } from "./contract/listed.mjs";
|
|
11
|
+
import { provideServed, servingOf } from "./contract/serving.mjs";
|
|
12
|
+
import { answeredWithProgress, answeringElements, answeringWithProgress, elementStreamOf, progressStreamOf } from "./http/streamed.mjs";
|
|
7
13
|
import { methods } from "./http/client.mjs";
|
|
14
|
+
import { answering, eventEndpointOf, servedSent, webhookAnswers, webhookEndpointsOf } from "./http/events.mjs";
|
|
8
15
|
import { SchemaErrors, schemaErrors } from "./http/schema-errors.mjs";
|
|
16
|
+
import { placed } from "./http/placed.mjs";
|
|
9
17
|
import { Context, Effect, Layer, Scope } from "effect";
|
|
10
18
|
import { FetchHttpClient, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
11
19
|
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/http-api";
|
|
12
|
-
|
|
20
|
+
// src/http/ActionHttp.ts
|
|
13
21
|
const mountSegments = (prefix) => (prefix ?? "/api").split("/").filter((segment) => segment !== "");
|
|
14
22
|
const absolutePath = (segments) => `/${segments.join("/")}`;
|
|
15
23
|
const groupName = (mount) => mount.join("/") || "/";
|
|
@@ -18,24 +26,45 @@ const apiOf = (group, endpoints) => {
|
|
|
18
26
|
const [first, ...rest] = endpoints;
|
|
19
27
|
return HttpApi.make("actions").annotate(HttpApi.ParseOptions, { errors: "all" }).add(first === void 0 ? empty : empty.add(first, ...rest));
|
|
20
28
|
};
|
|
29
|
+
const successOf = (action, errors) => {
|
|
30
|
+
const element = elementOf(action.success);
|
|
31
|
+
if (element !== void 0) return elementStreamOf(element, action.progress, errors);
|
|
32
|
+
return action.progress === void 0 ? action.success : [answeredWithProgress(action.success), progressStreamOf(action.progress, action.success, errors)];
|
|
33
|
+
};
|
|
21
34
|
const endpointOf = (action, path, errors) => HttpApiEndpoint.post(action.name, path, {
|
|
22
35
|
payload: action.input.pipe(HttpApiSchema.asJson()),
|
|
23
|
-
success: action
|
|
36
|
+
success: successOf(action, errors),
|
|
24
37
|
error: errors.map(declared)
|
|
25
38
|
});
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
const answerOf = (action, run) => {
|
|
40
|
+
const element = elementOf(action.success);
|
|
41
|
+
if (element !== void 0) return answeringElements(element, run);
|
|
42
|
+
return action.progress === void 0 ? run : answeringWithProgress(action.success, run);
|
|
43
|
+
};
|
|
44
|
+
const withinLayerMiddleware = (endpoint, middleware = []) => {
|
|
45
|
+
let within = endpoint;
|
|
46
|
+
for (const key of middleware) within = within.middleware(key);
|
|
47
|
+
return within;
|
|
48
|
+
};
|
|
49
|
+
function make(contracts, options = {}) {
|
|
50
|
+
const { actions, events } = listed(contracts);
|
|
51
|
+
const bound = bindingOf("ActionHttp binding", actions, events, options);
|
|
29
52
|
const mount = mountSegments(options.prefix);
|
|
30
53
|
const security = bound.authentication?.["~middleware"];
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
const secured = (endpoint, { caller }) => security === void 0 || caller === Anyone ? endpoint : endpoint.middleware(security);
|
|
55
|
+
const endpoints = [...actions.map((action) => secured(endpointOf(action, absolutePath([...mount, action.name]), bindingErrors(action, bound)).annotate(OpenApi.Description, action.description), action)), ...events.flatMap((event) => {
|
|
56
|
+
const path = absolutePath([
|
|
57
|
+
...mount,
|
|
58
|
+
"events",
|
|
59
|
+
event.name
|
|
60
|
+
]);
|
|
61
|
+
const errors = eventErrors(event, bound);
|
|
62
|
+
return [eventEndpointOf(event, path, errors), ...offersWebhook(event, bound.authentication) ? webhookEndpointsOf(event, path, errors) : []].map((endpoint) => secured(endpoint, event));
|
|
63
|
+
})];
|
|
36
64
|
const api = apiOf(groupName(mount), endpoints);
|
|
37
65
|
return {
|
|
38
66
|
actions,
|
|
67
|
+
events,
|
|
39
68
|
...bound,
|
|
40
69
|
prefix: absolutePath(mount),
|
|
41
70
|
api
|
|
@@ -50,32 +79,54 @@ var StepUp = class extends HttpApiMiddleware.Service()("effect-actions/ActionHtt
|
|
|
50
79
|
var JsonContentType = class extends HttpApiMiddleware.Service()("effect-actions/ActionHttp/JsonContentType") {};
|
|
51
80
|
const jsonContentType = () => Layer.succeed(JsonContentType, (route) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => request.headers["content-type"] === void 0 ? Effect.succeed(HttpServerResponse.text("Unsupported content-type: none", { status: 415 })) : route));
|
|
52
81
|
function layer(binding, served, options = {}) {
|
|
53
|
-
const
|
|
82
|
+
const selected = servingOf("HTTP binding", binding, served, options);
|
|
83
|
+
const { apps, actions, events, eventTypes, authentication } = selected;
|
|
54
84
|
const mount = mountSegments(binding.prefix);
|
|
55
85
|
const name = groupName(mount);
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
return
|
|
59
|
-
}
|
|
86
|
+
const routed = (endpoint, { caller }) => {
|
|
87
|
+
const within = withinLayerMiddleware(endpoint.middleware(JsonContentType).middleware(SchemaErrors), options.middleware);
|
|
88
|
+
return caller === Anyone || authentication === void 0 ? within : within.middleware(StepUp).middleware(authentication["~middleware"]);
|
|
89
|
+
};
|
|
90
|
+
const api = apiOf(name, [...actions.map((action) => routed(endpointOf(action, absolutePath([...mount, action.name]), projectedErrors(action, binding.error)), action)), ...eventTypes.flatMap((event) => {
|
|
91
|
+
const path = absolutePath([
|
|
92
|
+
...mount,
|
|
93
|
+
"events",
|
|
94
|
+
event.name
|
|
95
|
+
]);
|
|
96
|
+
const errors = eventErrors(event, { error: binding.error });
|
|
97
|
+
return [eventEndpointOf(event, path, errors, servedSent), ...offersWebhook(event, authentication) ? webhookEndpointsOf(event, path, errors) : []].map((endpoint) => routed(endpoint, event));
|
|
98
|
+
})]).annotate(HttpApi.PayloadParseOptions, inputParseOptions);
|
|
60
99
|
const handlers = Layer.unwrap(Effect.gen(function* () {
|
|
61
|
-
const bound = yield* acquire(apps);
|
|
100
|
+
const bound = yield* acquire(apps, authentication);
|
|
101
|
+
const feeds = yield* acquireFeeds(events);
|
|
102
|
+
const refusal = unkeyedHook("ActionHttp", feeds, authentication?.principal !== void 0);
|
|
103
|
+
if (refusal !== void 0) return yield* Effect.die(new Error(refusal));
|
|
104
|
+
const { webhooks, offered } = yield* servedWebhooks(feeds, authentication);
|
|
62
105
|
const security = authentication === void 0 ? Layer.empty : yield* Effect.map(providerOf(authentication), (provider) => Layer.mergeAll(Layer.succeed(authentication["~middleware"], provider.middleware), Layer.succeed(StepUp, provider.stepUp)));
|
|
63
106
|
const hostProvidedMiddleware = yield* Effect.forEach(options.middleware ?? [], (key) => Effect.map(Effect.service(key), (service) => Layer.succeed(key, service)));
|
|
64
|
-
const
|
|
107
|
+
const webhookAnswered = offered.map((feed) => [feed.event.name, webhookAnswers(feed, authentication, webhooks)]);
|
|
108
|
+
const byName = {
|
|
109
|
+
...Object.fromEntries(bound.map(([action, run]) => {
|
|
110
|
+
const answer = answerOf(action, run);
|
|
111
|
+
return [action.name, (request) => answer(request.payload)];
|
|
112
|
+
})),
|
|
113
|
+
...Object.fromEntries(feeds.map((feed) => [feed.event.name, answering(feed, authentication)])),
|
|
114
|
+
...Object.fromEntries(webhookAnswered.map(([eventName, { subscribe }]) => [`${eventName}/subscribe`, subscribe])),
|
|
115
|
+
...Object.fromEntries(webhookAnswered.map(([eventName, { unsubscribe }]) => [`${eventName}/unsubscribe`, unsubscribe]))
|
|
116
|
+
};
|
|
65
117
|
return HttpApiBuilder.group(api, name, (builder) => builder.handleAll(byName)).pipe(Layer.provide([
|
|
66
|
-
schemaErrors
|
|
118
|
+
schemaErrors,
|
|
67
119
|
jsonContentType(),
|
|
68
120
|
security,
|
|
69
121
|
...hostProvidedMiddleware
|
|
70
122
|
]), buildInEmptyContext);
|
|
71
|
-
})).pipe(
|
|
123
|
+
})).pipe(provideServed(selected));
|
|
72
124
|
return HttpApiBuilder.layer(api).pipe(Layer.provide(handlers), Layer.provide(startupBeneathRequest()));
|
|
73
125
|
}
|
|
74
126
|
function client(binding, options) {
|
|
75
127
|
return methods(binding, options);
|
|
76
128
|
}
|
|
77
129
|
function fetchClient(binding, { fetch, ...options } = {}) {
|
|
78
|
-
return methods(binding, options).pipe(Effect.provide(FetchHttpClient.layer), Effect.provideService(FetchHttpClient.Fetch, fetch ?? ((input, init) => globalThis.fetch(input, init))), Effect.runSync);
|
|
130
|
+
return methods(binding, options).pipe(Effect.provide(FetchHttpClient.layer), Effect.provideService(FetchHttpClient.Fetch, fetch ?? (async (input, init) => await globalThis.fetch(input, init))), Effect.runSync);
|
|
79
131
|
}
|
|
80
|
-
|
|
81
|
-
export { client, fetchClient, layer, make };
|
|
132
|
+
export { client, fetchClient, layer, make, placed };
|
package/dist/ActionMcp.d.mts
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Any } from "./
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { EventBuildError, EventBuildServices, EventOf, EventRequestOf, EventServedRequest, HoldingEvents } from "./contract/feed.mjs";
|
|
2
|
+
import { Any } from "./Event.mjs";
|
|
3
|
+
import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedEventsOf, SelectedOf, Selection, Served, ServedRequest } from "./contract/implementation.mjs";
|
|
4
|
+
import { Any as Any$1 } from "./Action.mjs";
|
|
5
|
+
import { Any as Any$2, Matching, ProviderOf, RemoteRequest, Required, ServerBuildServices } from "./authentication/provider.mjs";
|
|
6
|
+
import { McpCallError } from "./mcp/reply.mjs";
|
|
7
|
+
import { Client, ClientOptions, client } from "./mcp/client.mjs";
|
|
8
|
+
import { Params, RequestOptions, request } from "./mcp/request.mjs";
|
|
5
9
|
import { Cause, Effect, Layer, Stdio } from "effect";
|
|
6
|
-
import { McpSchema, McpServer } from "effect/ai";
|
|
7
10
|
import { HttpRouter } from "effect/http";
|
|
8
|
-
|
|
11
|
+
import { McpSchema, McpServer } from "effect/ai";
|
|
9
12
|
/**
|
|
10
13
|
* An MCP server, over HTTP or stdio: every native `McpServer.layerStdio` option but `protocols`,
|
|
11
14
|
* the server information, `instructions` and `extensions` among them, the actions `A` that are
|
|
12
|
-
* its tools, and the native features it serves beside them.
|
|
15
|
+
* its tools and the event types `A` it serves, and the native features it serves beside them.
|
|
13
16
|
*/
|
|
14
|
-
export interface Options<E = never, R = never, A extends Any = Any> extends Omit<Parameters<typeof McpServer.layerStdio>[0], "protocols"> {
|
|
17
|
+
export interface Options<E = never, R = never, A extends Any$1 | Any = Any$1 | Any> extends Omit<Parameters<typeof McpServer.layerStdio>[0], "protocols"> {
|
|
15
18
|
/**
|
|
16
|
-
* The actions that are tools, among the implementations'
|
|
17
|
-
* Each keeps its implementation's
|
|
19
|
+
* The actions that are tools and the event types it serves, among the implementations':
|
|
20
|
+
* `[GetUser, RenameUser, UserRenamed]`, exactly those. Each keeps its implementation's
|
|
21
|
+
* authorization and builder. Defaults to every action and event type of them.
|
|
22
|
+
*
|
|
23
|
+
* **Gotchas**
|
|
24
|
+
*
|
|
25
|
+
* - A list naming actions alone serves no event type: the events extension is not declared, and `events/*` answer `-32601`, until the event types are listed beside them.
|
|
18
26
|
*/
|
|
19
|
-
readonly actions?:
|
|
27
|
+
readonly actions?: readonly A[] | undefined;
|
|
20
28
|
/**
|
|
21
29
|
* Native MCP features served beside the actions' tools, such as `McpServer.resource`,
|
|
22
30
|
* `McpServer.prompt` and `McpServer.toolkit` layers merged into one. They register on this
|
|
@@ -36,7 +44,7 @@ export interface Options<E = never, R = never, A extends Any = Any> extends Omit
|
|
|
36
44
|
* `McpServer.layerHttp` option, `allowedOrigins` among them, except `protocols` and
|
|
37
45
|
* `allowSessionTermination`, which are fixed.
|
|
38
46
|
*/
|
|
39
|
-
export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path" | "allowSessionTermination"> {
|
|
47
|
+
export interface LayerHttpOptions<E = never, R = never, A extends Any$1 | Any = Any$1 | Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path" | "allowSessionTermination"> {
|
|
40
48
|
/** The endpoint's route; defaults to `/mcp`. */
|
|
41
49
|
readonly path?: HttpRouter.PathInput;
|
|
42
50
|
/**
|
|
@@ -54,12 +62,16 @@ export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> ext
|
|
|
54
62
|
* - Every prompt and resource request needs a credential, listings included: to offer them signed out, serve them on an endpoint without protected tools.
|
|
55
63
|
* - A handler that sends a notification starts the response, so a refusal after it is an `isError: true` tool result, not the 401 or 403 a client re-authorizes on: refuse in `authorize`.
|
|
56
64
|
*/
|
|
57
|
-
readonly authentication?: Any$
|
|
65
|
+
readonly authentication?: Any$2;
|
|
58
66
|
/**
|
|
59
67
|
* Whether MCP 2026-07-28 clients may subscribe, holding a stream open for list changes and
|
|
60
|
-
* resource updates. Defaults to whether `features` are given: an
|
|
61
|
-
* registers its tools once, before any client connects, and serves no
|
|
62
|
-
* where a handler changes the server through `McpServer.McpServer`.
|
|
68
|
+
* resource updates. Defaults to whether `features` or event implementations are given: an
|
|
69
|
+
* endpoint of actions alone registers its tools once, before any client connects, and serves no
|
|
70
|
+
* resource. Pass `true` where a handler changes the server through `McpServer.McpServer`.
|
|
71
|
+
*
|
|
72
|
+
* **Gotchas**
|
|
73
|
+
*
|
|
74
|
+
* - `false` also serves events by poll alone: Effect's server pushes no notification without subscriptions, so `events/list` lists `["poll"]` and `events/stream` answers `-32026`.
|
|
63
75
|
*/
|
|
64
76
|
readonly allowSubscriptions?: boolean | undefined;
|
|
65
77
|
/**
|
|
@@ -75,7 +87,13 @@ export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> ext
|
|
|
75
87
|
type ToolRequestContext<R> = Exclude<R, McpSchema.McpRequestContext | McpServer.McpServer>;
|
|
76
88
|
type HttpToolRequestContext<R> = Exclude<ToolRequestContext<R>, HttpRouter.Provided>;
|
|
77
89
|
type Features<R> = Exclude<R, McpServer.McpServer>;
|
|
78
|
-
|
|
90
|
+
/** The actions and event types an endpoint of `Apps` given the options `O` serves. */
|
|
91
|
+
type Contracts<Apps extends Served, O> = SelectedOf<O, Apps> | SelectedEventsOf<O, Apps>;
|
|
92
|
+
/** What building an endpoint of `Apps` given the options `O` may fail with. */
|
|
93
|
+
type ServerError<Apps extends Served, O> = BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | EventBuildError<HoldingEvents<Member<Apps>, SelectedEventsOf<O, Apps>>, SelectedEventsOf<O, Apps>> | Cause.IllegalArgumentError;
|
|
94
|
+
/** What building the event sources of an endpoint of `Apps` given the options `O` needs. */
|
|
95
|
+
type EventServices<Apps extends Served, O> = EventBuildServices<HoldingEvents<Member<Apps>, SelectedEventsOf<O, Apps>>, SelectedEventsOf<O, Apps>>;
|
|
96
|
+
type Inferring<D, A extends Pick<Any$1, "caller">> = [D] extends [undefined] ? [Protected<A>] extends [never] ? {
|
|
79
97
|
readonly authentication?: undefined;
|
|
80
98
|
} : unknown : {
|
|
81
99
|
readonly authentication?: D;
|
|
@@ -86,21 +104,36 @@ type Inferring<D, A extends Any> = [D] extends [undefined] ? [Protected<A>] exte
|
|
|
86
104
|
* revision opens a session with `initialize`, which holds what it sent there and no identity,
|
|
87
105
|
* and lasts until that client ends it with a DELETE, answered 204, or the process stops. Protected
|
|
88
106
|
* tools need `authentication`; for a serverless host, see `requestScoped`. In a tool call, what
|
|
89
|
-
* middleware provides per request wins over what the endpoint was built with.
|
|
90
|
-
*
|
|
107
|
+
* middleware provides per request wins over what the endpoint was built with. The event
|
|
108
|
+
* types among `implementations`, or those `actions` lists, are served as SEP-3415 events,
|
|
109
|
+
* `events/list`, `events/poll` and `events/stream`, and, where `Webhook.layer` is provided, `events/subscribe`
|
|
110
|
+
* and `events/unsubscribe`: experimental, as that extension is a draft, pinned to its PR head
|
|
111
|
+
* 2ec39b9c49c583411b73cb460dea5f52b5c541a6.
|
|
112
|
+
* Guide: `docs/ActionMcp.md` in this package; in an application: `examples/mcp-tools/`, and
|
|
113
|
+
* `examples/build-events/` for events.
|
|
91
114
|
*
|
|
92
115
|
* **Gotchas**
|
|
93
116
|
*
|
|
94
117
|
* - A success is `isError: false` with `structuredContent` its JSON and the same JSON as one text block, both `null` for an action without `success`; an `Action.Image` field is left out of `structuredContent` and its text, its block following them, and a success of images alone has no `structuredContent`.
|
|
95
118
|
* - A declared error is an `isError: true` tool result in a 200 holding its JSON as one text block, with no `structuredContent`; a refusal is answered as `Action.Unauthenticated` and `Action.Forbidden` say.
|
|
119
|
+
* - An action answering a stream, `Action.Stream`, answers its elements collected into one array once the stream ends, a success of that array.
|
|
120
|
+
* - An action declaring `progress` sends each value as `notifications/progress` on the `progressToken` its call's `_meta` carries, before the result: `progress` counts from 1, `message` is the value's JSON and `_meta["effect-actions/progress"]` the value; a call without a token gets none.
|
|
96
121
|
* - Arguments that do not decode never reach the handler: an `isError: true` result in a 200, text `Invalid parameters for tool '<name>': …`, or for a 2025-06-18 client a JSON-RPC error.
|
|
97
122
|
* - A defect, an undeclared failure, or a success or an error that does not encode is `isError: true`, text `Tool execution failed due to an internal server error.`: the cause goes to the log and to the call's `ErrorReporter`s, never to the client.
|
|
98
123
|
* - An unknown tool is a JSON-RPC error `-32602` in a 200; a body that is not JSON a JSON-RPC error `-32700` in a 200; a `Content-Type` missing or not JSON an empty 415; another HTTP method an empty 405 with `Allow: POST, DELETE`, `Allow: POST` where `requestScoped`, or the 401 of an endpoint with authentication when signed out.
|
|
99
124
|
* - A 2025-11-25 or 2025-06-18 client gets `structuredContent` only for a success that is an object: any other success arrives as its text block alone, a string unquoted.
|
|
100
125
|
* - A client asking for an earlier revision, such as 2025-03-26, is offered 2025-11-25, so a client speaking only that earlier one cannot connect.
|
|
101
126
|
* - A request body is read whole, however large, and a 2025 session holds what its `initialize` sent until it ends, so an oversized `initialize` costs memory for that long: limit bodies as `ActionHttp.layer` says.
|
|
127
|
+
* - An event type named starting with `mcp.`, a prefix MCP keeps for its own events, is refused as the endpoint is made, with `MCP event name starts with "mcp."`.
|
|
128
|
+
* - Events are 2026-07-28's alone: a 2025 session gets `-32601` for `events/*`, and its `initialize` advertises no events extension.
|
|
129
|
+
* - With `requestScoped`, `events/stream` answers `-32026`, no server outliving a request to push, so events are served by poll, and by webhook where `Webhook.layer` is provided; their sources, logs, `subscription` hooks' leases and webhook subscriptions are built once with the endpoint, as without it.
|
|
130
|
+
* - On a host with no lasting process, such as a serverless platform, serve check sources alone, without a `subscription` hook: nothing refuses the others, and under `Event.layerMemory` each instance keeps its own log of a log source, whose cursors another reads as a gap, and its own leases, lost as it ends.
|
|
131
|
+
* - A protected event with a `subscription` hook needs `principal` on `authentication`, or the endpoint is refused at startup: a poll's hook is held per principal between requests.
|
|
132
|
+
* - A push stream holds its request open while it is read, a heartbeat every 30 seconds: a host's request timeout ends it, and `client`'s `watch` reconnects from its cursor.
|
|
133
|
+
* - A poll's `maxEvents` caps a log source's page, at most 100, and is not honored for a check source, whose page is answered whole since only its last cursor resumes; `maxAgeMs` is accepted and not honored, a log source keeping what its `log.maxAge` and `log.maxEvents` say.
|
|
134
|
+
* - A push stream's failure that no refusal explains, such as an upstream's outage, is `notifications/events/error`, its cause logged and reported, and the stream resumes from where it reached after 5 seconds; a refusal ends it with `notifications/events/terminated`. One before `active`, or a poll's, is a JSON-RPC `-32603`, which `client`'s `watch` reconnects after, as after a 5xx.
|
|
102
135
|
*/
|
|
103
|
-
export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$
|
|
136
|
+
export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>> | EventOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$2 | undefined = undefined>(implementations: Apps, options: LayerHttpOptions<E, R, ActionOf<Member<Apps>> | EventOf<Member<Apps>>> & O & NoInfer<Known<O, LayerHttpOptions<E, R>>> & NoInfer<Required<Contracts<Apps, O>>> & Matching<NoInfer<Contracts<Apps, O>>, NoInfer<D>> & Inferring<D, NoInfer<Contracts<Apps, O>>>): Layer.Layer<never, ServerError<Apps, O> | E, ServerBuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | EventServices<Apps, O> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", HttpToolRequestContext<RemoteRequest<Member<Apps>, SelectedOf<O, Apps>> | EventServedRequest<Member<Apps>, SelectedEventsOf<O, Apps>>>> | ProviderOf<D> | Features<R>>;
|
|
104
137
|
/**
|
|
105
138
|
* Serve MCP tools through newline-delimited JSON-RPC on standard I/O, speaking MCP
|
|
106
139
|
* 2026-07-28, 2025-11-25 or 2025-06-18, as the host negotiates: the whole
|
|
@@ -109,14 +142,13 @@ export declare function layerHttp<const Apps extends Served, const O extends Sel
|
|
|
109
142
|
* go to stderr, since stdout carries the protocol. The host supplies the `Stdio` service, and
|
|
110
143
|
* provides the identity around `runStdio`; arguments are tool input only and never establish
|
|
111
144
|
* request identity or authority, and each implementation's `authorize` runs for its protected
|
|
112
|
-
* actions. Its tool results, and how it answers each revision and an
|
|
113
|
-
* `layerHttp`'s.
|
|
145
|
+
* actions and events. Its tool results, its events, and how it answers each revision and an
|
|
146
|
+
* earlier one asked for, are `layerHttp`'s.
|
|
114
147
|
*
|
|
115
148
|
* **Gotchas**
|
|
116
149
|
*
|
|
117
150
|
* - A line that is not JSON goes unanswered, with no `-32700` as over HTTP, and the lines after it are still answered.
|
|
118
151
|
* - The layers provided around it log to stdout, among the protocol's lines: apply `ActionCli.logToStderr` to the program, last, as its JSDoc says.
|
|
119
152
|
*/
|
|
120
|
-
export declare function runStdio<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never>(implementations: Apps, options: Options<E, R, ActionOf<Member<Apps>>> & O & NoInfer<Known<O, Options<E, R
|
|
121
|
-
//#endregion
|
|
153
|
+
export declare function runStdio<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>> | EventOf<Member<Apps>>> = {}, E = never, R = never>(implementations: Apps, options: Options<E, R, ActionOf<Member<Apps>> | EventOf<Member<Apps>>> & O & NoInfer<Known<O, Options<E, R>>>): Effect.Effect<void, ServerError<Apps, O> | E, BuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | EventServices<Apps, O> | Stdio.Stdio | ToolRequestContext<ServedRequest<Member<Apps>, SelectedOf<O, Apps>> | EventRequestOf<Member<Apps>, SelectedEventsOf<O, Apps>>> | Features<R>>;
|
|
122
154
|
export { type Client, type ClientOptions, McpCallError, type Params, type RequestOptions, client, request };
|