@gjermundgaraba/effect-actions 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/Action.d.mts +3 -5
  2. package/dist/Action.mjs +3 -1
  3. package/dist/ActionCli.d.mts +2 -2
  4. package/dist/ActionCli.mjs +2 -1
  5. package/dist/ActionHttp.d.mts +3 -2
  6. package/dist/ActionHttp.mjs +3 -1
  7. package/dist/ActionMcp.d.mts +11 -4
  8. package/dist/ActionMcp.mjs +7 -3
  9. package/dist/ActionRpc.d.mts +5 -3
  10. package/dist/ActionRpc.mjs +2 -1
  11. package/dist/ActionToolkit.d.mts +3 -2
  12. package/dist/ActionToolkit.mjs +3 -1
  13. package/dist/Authentication.d.mts +1 -1
  14. package/dist/Authentication.mjs +1 -1
  15. package/dist/authentication/provider.d.mts +2 -2
  16. package/dist/authentication/refusal.mjs +1 -1
  17. package/dist/cli/commands.d.mts +3 -2
  18. package/dist/cli/commands.mjs +2 -1
  19. package/dist/contract/ast.mjs +10 -0
  20. package/dist/contract/call.d.mts +1 -1
  21. package/dist/contract/caller.d.mts +9 -0
  22. package/dist/contract/caller.mjs +12 -0
  23. package/dist/contract/{rules.d.mts → declared.d.mts} +6 -10
  24. package/dist/contract/declared.mjs +42 -0
  25. package/dist/contract/hints.mjs +14 -0
  26. package/dist/contract/implementation.d.mts +4 -3
  27. package/dist/contract/implementation.mjs +4 -8
  28. package/dist/contract/names.mjs +25 -0
  29. package/dist/contract/status.mjs +18 -0
  30. package/dist/http/client.d.mts +3 -2
  31. package/dist/mcp/client.d.mts +40 -7
  32. package/dist/mcp/client.mjs +60 -17
  33. package/dist/mcp/protocol.mjs +1 -1
  34. package/docs/Action.md +17 -3
  35. package/docs/ActionCli.md +1 -1
  36. package/docs/ActionMcp-failures.md +1 -0
  37. package/docs/ActionMcp.md +16 -16
  38. package/docs/ActionToolkit.md +25 -2
  39. package/docs/Authentication-failures.md +1 -1
  40. package/docs/CONTEXT.md +2 -1
  41. package/docs/README.md +4 -4
  42. package/docs/Testing.md +3 -1
  43. package/examples/README.md +10 -2
  44. package/examples/assistant/README.md +24 -20
  45. package/examples/assistant/src/agent.ts +11 -7
  46. package/examples/assistant/src/cli.ts +2 -2
  47. package/examples/assistant/src/platform/tasks-file.ts +38 -0
  48. package/examples/assistant/src/tasks/store.ts +6 -38
  49. package/examples/assistant/src/toolkit.ts +11 -1
  50. package/examples/assistant/test/{tasks/store.test.ts → platform/tasks-file.test.ts} +2 -1
  51. package/examples/mcp-tools/README.md +14 -11
  52. package/examples/mcp-tools/src/{printers/fake.ts → platform/fake-farm.ts} +2 -2
  53. package/examples/mcp-tools/src/platform/print-farm.ts +118 -0
  54. package/examples/mcp-tools/src/printers/farm.ts +6 -122
  55. package/examples/mcp-tools/src/server.ts +4 -4
  56. package/examples/mcp-tools/src/stdio.ts +4 -4
  57. package/examples/mcp-tools/test/printers/printers.test.ts +3 -3
  58. package/examples/mcp-tools/test/routes.test.ts +4 -4
  59. package/examples/notes-service/README.md +32 -0
  60. package/examples/notes-service/src/identity/authorize.ts +1 -0
  61. package/examples/notes-service/src/notes/Notes.ts +22 -5
  62. package/examples/notes-service/src/notes/handlers.ts +2 -2
  63. package/examples/notes-service/src/notes/store.ts +36 -6
  64. package/examples/notes-service/test/notes/idempotency.test.ts +132 -0
  65. package/examples/notes-service/test/routes.test.ts +40 -1
  66. package/package.json +5 -5
  67. package/dist/_virtual/_rolldown/runtime.mjs +0 -13
  68. package/dist/contract/rules.mjs +0 -100
package/dist/Action.d.mts CHANGED
@@ -1,12 +1,10 @@
1
- import { Anyone } from "./contract/rules.mjs";
1
+ import { Anyone } from "./contract/caller.mjs";
2
2
  import { BuiltIn, BuiltIns, Forbidden, InvalidInput, Issue, Refusal, Unauthenticated } from "./contract/errors.mjs";
3
3
  import { ActionOf, AnyImplementation, Authorize, BuildError, BuildServices, Holding, Implementation, Known, Member, Offered, OptionalUnless, Protected, RequestOf, SelectedOf, Served, Serving } from "./contract/implementation.mjs";
4
4
  import { Call } from "./contract/call.mjs";
5
5
  import { Image } from "./contract/media.mjs";
6
6
  import { Context, Effect, Layer, Schema, Scope } from "effect";
7
- declare namespace Action_d_exports {
8
- export { Action, Any, AnyImplementation, Anyone, Authorize, BuildError, BuildServices, BuiltIn, Client, ClientOptions, Errors, Forbidden, Handler, Handlers, Image, Implementation, InvalidInput, Issue, Mcp, Options, Refusal, Unauthenticated, allowAll, byName, client, implement, layer, make };
9
- }
7
+ //#region src/contract/Action.d.ts
10
8
  type Codec = Schema.Codec<unknown, unknown, never, never>;
11
9
  type Fields = {
12
10
  readonly [key: string]: Codec;
@@ -213,4 +211,4 @@ export interface ClientOptions<A extends Any> {
213
211
  */
214
212
  export declare function client<const Apps extends Served, const O extends ClientOptions<ActionOf<Member<Apps>>> = {}>(implementations: Apps, ...options: OptionalUnless<O, ClientOptions<ActionOf<Member<Apps>>> & O & NoInfer<Known<O, ClientOptions<Any>>>>): Effect.Effect<Client<Apps, Offered<O, ActionOf<Member<Apps>>>>, BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>, BuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | Scope.Scope>;
215
213
  //#endregion
216
- export { Action_d_exports, type AnyImplementation, Anyone, type Authorize, type BuildError, type BuildServices, BuiltIn, Forbidden, Image, type Implementation, InvalidInput, type Issue, Refusal, Unauthenticated };
214
+ export { type AnyImplementation, Anyone, type Authorize, type BuildError, type BuildServices, BuiltIn, Forbidden, Image, type Implementation, InvalidInput, type Issue, Refusal, Unauthenticated };
package/dist/Action.mjs CHANGED
@@ -1,5 +1,7 @@
1
+ import { Anyone } from "./contract/caller.mjs";
1
2
  import { BuiltIn, Forbidden, InvalidInput, Refusal, Unauthenticated } from "./contract/errors.mjs";
2
- import { Anyone, assertKnown, assertName, assertOnce, assertOwnTags, errorList, projectedErrors } from "./contract/rules.mjs";
3
+ import { assertKnown, assertName, assertOnce } from "./contract/names.mjs";
4
+ import { assertOwnTags, errorList, projectedErrors } from "./contract/declared.mjs";
3
5
  import { Implementation, builders, built, memoized, select, toList } from "./contract/implementation.mjs";
4
6
  import { inputOf } from "./contract/call.mjs";
5
7
  import { Image } from "./contract/media.mjs";
@@ -1,8 +1,8 @@
1
- import { Declaring } from "./contract/rules.mjs";
2
1
  import { BuiltIn } from "./contract/errors.mjs";
3
2
  import { ActionOf, BuildError, BuildServices, Holding, Known, Member, RequestOf, Selected, SelectedOf, Selection, Served } from "./contract/implementation.mjs";
4
- import { Action_d_exports, Any } from "./Action.mjs";
3
+ import { Any } from "./Action.mjs";
5
4
  import { VerifierError } from "./authentication/provider.mjs";
5
+ import { Declaring } from "./contract/declared.mjs";
6
6
  import { Options, UserError } from "./cli/commands.mjs";
7
7
  import { AnyHttp, Options as Options$2 } from "./http/client.mjs";
8
8
  import "./ActionHttp.mjs";
@@ -1,4 +1,5 @@
1
- import { assertDistinct, assertKnown, bindingErrors, projectedErrors } from "./contract/rules.mjs";
1
+ import { assertDistinct, assertKnown } from "./contract/names.mjs";
2
+ import { bindingErrors, projectedErrors } from "./contract/declared.mjs";
2
3
  import { Implementation, acquire, assertHeld, select, toList } from "./contract/implementation.mjs";
3
4
  import { client } from "./ActionHttp.mjs";
4
5
  import { command as command$1, failingAsDerived } from "./cli/commands.mjs";
@@ -1,8 +1,9 @@
1
- import { Anyone, Certain, ErrorsOf } from "./contract/rules.mjs";
1
+ import { Anyone } from "./contract/caller.mjs";
2
2
  import { BuiltIn, BuiltIns } from "./contract/errors.mjs";
3
3
  import { BuildError, Holding, Known, Member, MiddlewareOf, Protected, Selected, Served, Serving } from "./contract/implementation.mjs";
4
- import { Action_d_exports, Any, Errors } from "./Action.mjs";
4
+ import { Any, Errors } from "./Action.mjs";
5
5
  import { Any as Any$1, DescriptorOf, Identity, Matching, PublicOnly, RemoteRequest, Required, ServedProvider, ServerBuildServices, VerifierError } from "./authentication/provider.mjs";
6
+ import { Certain, ErrorsOf } from "./contract/declared.mjs";
6
7
  import { AnyHttp, Client, MethodError, Options } from "./http/client.mjs";
7
8
  import { Context, Effect, FileSystem, Layer, Path, Schema, Types } from "effect";
8
9
  import { Etag, HttpClient, HttpPlatform, HttpRouter } from "effect/http";
@@ -1,7 +1,9 @@
1
- import { Anyone, bindingErrors, bindingOf, declared, projectedErrors } from "./contract/rules.mjs";
1
+ import { Anyone } from "./contract/caller.mjs";
2
+ import { bindingErrors, bindingOf, projectedErrors } from "./contract/declared.mjs";
2
3
  import { acquire, provideHandlers, servingOf } from "./contract/implementation.mjs";
3
4
  import { providerOf } from "./authentication/provider.mjs";
4
5
  import { inputParseOptions } from "./contract/call.mjs";
6
+ import { declared } from "./contract/status.mjs";
5
7
  import { methods } from "./http/client.mjs";
6
8
  import { SchemaErrors, schemaErrors } from "./http/schema-errors.mjs";
7
9
  import { Context, Effect, Layer, Scope } from "effect";
@@ -1,5 +1,5 @@
1
1
  import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedOf, Selection, Served, ServedRequest } from "./contract/implementation.mjs";
2
- import { Action_d_exports, Any } from "./Action.mjs";
2
+ import { Any } from "./Action.mjs";
3
3
  import { Any as Any$1, Matching, ProviderOf, RemoteRequest, Required, ServerBuildServices } from "./authentication/provider.mjs";
4
4
  import { Client, ClientOptions, McpCallError, Params, RequestOptions, client, request } from "./mcp/client.mjs";
5
5
  import { Cause, Effect, Layer, Stdio } from "effect";
@@ -25,14 +25,21 @@ export interface Options<E = never, R = never, A extends Any = Any> extends Omit
25
25
  readonly features?: Layer.Layer<never, E, R> | undefined;
26
26
  }
27
27
  /**
28
- * One Streamable HTTP MCP endpoint: `Options`, every native `McpServer.layerHttp` option,
29
- * `allowedOrigins` among them, except `protocols` and `allowSessionTermination`, which are fixed,
30
- * with `path` defaulting to `/mcp`, and `requestScoped`.
28
+ * One Streamable HTTP MCP endpoint: `Options`, `authentication`, `requestScoped`, and every native
29
+ * `McpServer.layerHttp` option, `allowedOrigins` among them, except `protocols` and
30
+ * `allowSessionTermination`, which are fixed.
31
31
  */
32
32
  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"> {
33
33
  /** The endpoint's route; defaults to `/mcp`. */
34
34
  readonly path?: HttpRouter.PathInput;
35
35
  readonly authentication?: Any$1;
36
+ /**
37
+ * Whether MCP 2026-07-28 clients may subscribe, holding a stream open for list changes and
38
+ * resource updates. Defaults to whether `features` are given: an endpoint of actions alone
39
+ * registers its tools once, before any client connects, and serves no resource. Pass `true`
40
+ * where a handler changes the server through `McpServer.McpServer`.
41
+ */
42
+ readonly allowSubscriptions?: boolean | undefined;
36
43
  /**
37
44
  * `true` runs a server per request, as Effect's `McpServer.layerHttpRequestScoped` does, for a
38
45
  * host such as Cloudflare Workers where nothing may outlive a request. It speaks MCP 2026-07-28
@@ -1,4 +1,7 @@
1
- import { Anyone, assertAuthentication, assertOnce, hintsOf, projectedErrors } from "./contract/rules.mjs";
1
+ import { Anyone, assertAuthentication } from "./contract/caller.mjs";
2
+ import { assertOnce } from "./contract/names.mjs";
3
+ import { projectedErrors } from "./contract/declared.mjs";
4
+ import { hintsOf } from "./contract/hints.mjs";
2
5
  import { acquire, provideHandlers, select, toList } from "./contract/implementation.mjs";
3
6
  import { promote, providerOf } from "./authentication/provider.mjs";
4
7
  import { inputParseOptions } from "./contract/call.mjs";
@@ -148,7 +151,7 @@ const namesItsMethod = (request) => Effect.match(request.json, {
148
151
  onFailure: () => true,
149
152
  onSuccess: (body) => !Predicate.hasProperty(body, "method") || body.method === request.headers["mcp-method"]
150
153
  });
151
- function layerHttp(apps, { actions, features, path = defaultPath, authentication, requestScoped, ...options }) {
154
+ function layerHttp(apps, { actions, features, path = defaultPath, authentication, requestScoped, allowSubscriptions = features !== void 0, ...options }) {
152
155
  const selected = select(toList(apps), actions);
153
156
  const served = selected.flatMap((app) => app.actions);
154
157
  assertAuthentication(served, authentication);
@@ -160,7 +163,8 @@ function layerHttp(apps, { actions, features, path = defaultPath, authentication
160
163
  ...options,
161
164
  path,
162
165
  protocols,
163
- allowSessionTermination: true
166
+ allowSessionTermination: true,
167
+ allowSubscriptions
164
168
  }), features, authentication);
165
169
  if (authentication === void 0) return transport;
166
170
  const required = requiresAuthentication(served);
@@ -1,9 +1,10 @@
1
- import { Anyone, Certain, ErrorsOf } from "./contract/rules.mjs";
1
+ import { Anyone } from "./contract/caller.mjs";
2
2
  import { BuiltIn, BuiltIns } from "./contract/errors.mjs";
3
3
  import { BuildError, Holding, Known, Member, MiddlewareOf, Protected, Selected, Served, Serving } from "./contract/implementation.mjs";
4
4
  import { Call } from "./contract/call.mjs";
5
- import { Action_d_exports, Any as Any$1, Errors } from "./Action.mjs";
5
+ import { Any as Any$1, Errors } from "./Action.mjs";
6
6
  import { Any as Any$2, DescriptorOf, Identity, Matching, PublicOnly, RemoteRequest, Required, ServedProvider, ServerBuildServices, VerifierError } from "./authentication/provider.mjs";
7
+ import { Certain, ErrorsOf } from "./contract/declared.mjs";
7
8
  import { Context, Effect, Layer, Schema, Scope, Types } from "effect";
8
9
  import { HttpRouter } from "effect/http";
9
10
  import { Rpc, RpcClient, RpcClientError, RpcGroup, RpcMiddleware, RpcServer } from "effect/rpc";
@@ -144,4 +145,5 @@ export type ClientOptions = Omit<NonNullable<Parameters<typeof RpcClient.make>[1
144
145
  * or with Effect's own `RpcClientError` when the server could not be reached.
145
146
  */
146
147
  export declare function client<const B extends Any>(binding: B, options?: ClientOptions): Effect.Effect<Client<B>, never, RpcClient.Protocol | Scope.Scope>;
147
- //#endregion
148
+ //#endregion
149
+ export {};
@@ -1,4 +1,5 @@
1
- import { Anyone, bindingErrors, bindingOf } from "./contract/rules.mjs";
1
+ import { Anyone } from "./contract/caller.mjs";
2
+ import { bindingErrors, bindingOf } from "./contract/declared.mjs";
2
3
  import { acquire, provideHandlers, servingOf } from "./contract/implementation.mjs";
3
4
  import { providerOf } from "./authentication/provider.mjs";
4
5
  import { checked, decodeInput } from "./contract/call.mjs";
@@ -1,6 +1,6 @@
1
1
  import { BuiltIns } from "./contract/errors.mjs";
2
2
  import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Offered, OptionalUnless, RequestOf, SelectedOf, Served, Serving } from "./contract/implementation.mjs";
3
- import { Action_d_exports, Any } from "./Action.mjs";
3
+ import { Any } from "./Action.mjs";
4
4
  import { Effect, Layer, Schema } from "effect";
5
5
  import { Prompt, Tool, Toolkit } from "effect/ai";
6
6
  //#region src/toolkit/ActionToolkit.d.ts
@@ -85,4 +85,5 @@ export interface Approval {
85
85
  * `examples/kitchen-sink/src/assistant/handlers.ts`.
86
86
  */
87
87
  export declare const openApprovals: ({ content: messages }: Prompt.Prompt) => ReadonlyArray<Approval>;
88
- //#endregion
88
+ //#endregion
89
+ export {};
@@ -1,4 +1,6 @@
1
- import { assertOnce, hintsOf, projectedErrors } from "./contract/rules.mjs";
1
+ import { assertOnce } from "./contract/names.mjs";
2
+ import { projectedErrors } from "./contract/declared.mjs";
3
+ import { hintsOf } from "./contract/hints.mjs";
2
4
  import { Implementation, acquire, provideHandlers, select, toList } from "./contract/implementation.mjs";
3
5
  import { Effect, Schema } from "effect";
4
6
  import { Tool, Toolkit } from "effect/ai";
@@ -1,8 +1,8 @@
1
- import { ErrorsOf } from "./contract/rules.mjs";
2
1
  import { Refusal } from "./contract/errors.mjs";
3
2
  import { Known, OptionalUnless } from "./contract/implementation.mjs";
4
3
  import { Errors } from "./Action.mjs";
5
4
  import { Any, Descriptor, Provider, Security } from "./authentication/provider.mjs";
5
+ import { ErrorsOf } from "./contract/declared.mjs";
6
6
  import { Array, Context, Effect, Layer, Option, Redacted, Scope } from "effect";
7
7
  import { HttpRouter, HttpServerResponse } from "effect/http";
8
8
  import { HttpApiSecurity } from "effect/http-api";
@@ -1,5 +1,5 @@
1
1
  import { Unauthenticated, scopeToken } from "./contract/errors.mjs";
2
- import { assertOwnTags, errorList } from "./contract/rules.mjs";
2
+ import { assertOwnTags, errorList } from "./contract/declared.mjs";
3
3
  import { providerOf } from "./authentication/provider.mjs";
4
4
  import { answer, answerStepUp, bearer, challenge, declaredResponse, isRefusal, isStepUp, plain } from "./authentication/refusal.mjs";
5
5
  import { Context, Effect, Layer, Match, Option, Predicate, Redacted } from "effect";
@@ -1,7 +1,7 @@
1
- import { Anyone } from "../contract/rules.mjs";
1
+ import { Anyone } from "../contract/caller.mjs";
2
2
  import { Refusal } from "../contract/errors.mjs";
3
3
  import { BuildServices, Protected, ServedRequest, Serving } from "../contract/implementation.mjs";
4
- import { Action_d_exports, Any as Any$1, Errors } from "../Action.mjs";
4
+ import { Any as Any$1, Errors } from "../Action.mjs";
5
5
  import { Context, Effect } from "effect";
6
6
  import { HttpRouter, HttpServerResponse } from "effect/http";
7
7
  import { HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
@@ -1,5 +1,5 @@
1
1
  import { Forbidden, Refusal, Unauthenticated } from "../contract/errors.mjs";
2
- import { declared, httpApiStatusOf } from "../contract/rules.mjs";
2
+ import { declared, httpApiStatusOf } from "../contract/status.mjs";
3
3
  import { Cause, Context, Effect, Exit, Option, Predicate, Ref, Schema } from "effect";
4
4
  import { HttpServerResponse } from "effect/http";
5
5
  //#region src/authentication/refusal.ts
@@ -1,5 +1,5 @@
1
1
  import "../contract/errors.mjs";
2
- import { Action_d_exports, Any } from "../Action.mjs";
2
+ import { Any } from "../Action.mjs";
3
3
  import { Effect, Runtime, Types } from "effect";
4
4
  import { CliError, Command } from "effect/cli";
5
5
  //#region src/cli/commands.d.ts
@@ -44,4 +44,5 @@ export declare class UserError<out E = unknown> extends CliError.UserError {
44
44
  get reason(): E;
45
45
  get [Runtime.errorExitCode](): number;
46
46
  }
47
- //#endregion
47
+ //#endregion
48
+ export {};
@@ -1,5 +1,6 @@
1
+ import { literalValues, members, unsuspended } from "../contract/ast.mjs";
1
2
  import { InvalidInput } from "../contract/errors.mjs";
2
- import { assertDistinct, literalValues, members, unsuspended } from "../contract/rules.mjs";
3
+ import { assertDistinct } from "../contract/names.mjs";
3
4
  import { decodeInput } from "../contract/call.mjs";
4
5
  import { withStderrConsole } from "../stdio/console.mjs";
5
6
  import { Console, Effect, Option, Predicate, Record, Runtime, Schema, SchemaAST, Stdio, Stream } from "effect";
@@ -0,0 +1,10 @@
1
+ import { SchemaAST } from "effect";
2
+ //#region src/contract/ast.ts
3
+ const unsuspended = (ast) => SchemaAST.isSuspend(ast) ? unsuspended(ast.thunk()) : ast;
4
+ const members = (ast) => {
5
+ const resolved = unsuspended(ast);
6
+ return SchemaAST.isUnion(resolved) ? resolved.types.flatMap(members) : [resolved];
7
+ };
8
+ const literalValues = (ast) => SchemaAST.isLiteral(ast) ? [ast.literal] : SchemaAST.isEnum(ast) ? ast.enums.map(([, value]) => value) : [void 0];
9
+ //#endregion
10
+ export { literalValues, members, unsuspended };
@@ -1,6 +1,6 @@
1
1
  import "./errors.mjs";
2
2
  import "./implementation.mjs";
3
- import { Action_d_exports, Any } from "../Action.mjs";
3
+ import { Any } from "../Action.mjs";
4
4
  import { Effect, Schema } from "effect";
5
5
  //#region src/contract/call.d.ts
6
6
  /**
@@ -0,0 +1,9 @@
1
+ import "../Action.mjs";
2
+ //#region src/contract/caller.d.ts
3
+ /**
4
+ * The caller of a public action, `caller: Action.Anyone`: anyone, signed in or not, and no
5
+ * authorizer runs for it. Registered globally, so two copies of the package agree on it, as they
6
+ * agree on a name.
7
+ */
8
+ export declare const Anyone: unique symbol;
9
+ //#endregion
@@ -0,0 +1,12 @@
1
+ //#region src/contract/caller.ts
2
+ /**
3
+ * The caller of a public action, `caller: Action.Anyone`: anyone, signed in or not, and no
4
+ * authorizer runs for it. Registered globally, so two copies of the package agree on it, as they
5
+ * agree on a name.
6
+ */
7
+ const Anyone = Symbol.for("@gjermundgaraba/effect-actions/Anyone");
8
+ const assertAuthentication = (actions, authentication) => {
9
+ for (const action of actions) if (action.caller !== Anyone && (authentication === void 0 || action.caller.key !== authentication.identity.key)) throw new Error(`Protected action '${action.name}' requires its matching authentication descriptor`);
10
+ };
11
+ //#endregion
12
+ export { Anyone, assertAuthentication };
@@ -1,6 +1,7 @@
1
- import { Action_d_exports, Errors } from "../Action.mjs";
2
- import { SchemaAST, Types } from "effect";
3
- //#region src/contract/rules.d.ts
1
+ import "./caller.mjs";
2
+ import { Errors } from "../Action.mjs";
3
+ import { Types } from "effect";
4
+ //#region src/contract/declared.d.ts
4
5
  type ListOf<G> = G extends Errors ? G : readonly [G];
5
6
  /**
6
7
  * The errors options `O` declare, as at run time: those `error` always gives, or, where it may
@@ -23,10 +24,5 @@ export interface Declaring {
23
24
  readonly error: Errors;
24
25
  } | undefined;
25
26
  }
26
- /**
27
- * The caller of a public action, `caller: Action.Anyone`: anyone, signed in or not, and no
28
- * authorizer runs for it. Registered globally, so two copies of the package agree on it, as they
29
- * agree on a name.
30
- */
31
- export declare const Anyone: unique symbol;
32
- //#endregion
27
+ //#endregion
28
+ export {};
@@ -0,0 +1,42 @@
1
+ import { literalValues, members } from "./ast.mjs";
2
+ import { Anyone, assertAuthentication } from "./caller.mjs";
3
+ import { builtIns } from "./errors.mjs";
4
+ import { assertOnce } from "./names.mjs";
5
+ import { Predicate, Schema, SchemaAST } from "effect";
6
+ //#region src/contract/declared.ts
7
+ const errorList = (error) => error === void 0 ? [] : Schema.isSchema(error) ? [error] : error;
8
+ const projectedErrors = (action, surface = []) => [.../* @__PURE__ */ new Set([
9
+ ...builtIns,
10
+ ...action.error,
11
+ ...surface
12
+ ])];
13
+ /**
14
+ * Every error a binding's call of `action` declares: the built-in ones, the action's, the
15
+ * binding's and, where `action` is protected, its authentication's.
16
+ */
17
+ const bindingErrors = (action, binding) => projectedErrors(action, action.caller === Anyone || binding.authentication === void 0 ? binding.error : [...binding.error, ...binding.authentication.error]);
18
+ const encodedTags = (ast) => members(SchemaAST.toEncoded(ast)).flatMap((member) => {
19
+ const tag = SchemaAST.isObjects(member) ? member.propertySignatures.find((property) => property.name === "_tag")?.type : void 0;
20
+ return tag === void 0 ? [] : members(tag).flatMap(literalValues).filter(Predicate.isString);
21
+ });
22
+ const builtInTags = new Set(builtIns.flatMap((error) => encodedTags(error.ast)));
23
+ const assertOwnTags = (what, errors) => {
24
+ const tag = errors.flatMap((error) => encodedTags(error.ast)).find((tag) => builtInTags.has(tag));
25
+ if (tag !== void 0) throw new Error(`${what}: error _tag "${tag}" is built in, and declared on every surface`);
26
+ };
27
+ /**
28
+ * The checks `ActionHttp.make` and `ActionRpc.make` share: each action once, no built-in error in
29
+ * `error`, and the descriptor every protected action names. Returns `error` as a list.
30
+ */
31
+ const bindingOf = (what, actions, options) => {
32
+ assertOnce("action", actions);
33
+ const error = errorList(options.error);
34
+ assertOwnTags(what, error);
35
+ assertAuthentication(actions, options.authentication);
36
+ return {
37
+ error,
38
+ authentication: options.authentication
39
+ };
40
+ };
41
+ //#endregion
42
+ export { assertOwnTags, bindingErrors, bindingOf, errorList, projectedErrors };
@@ -0,0 +1,14 @@
1
+ //#region src/contract/hints.ts
2
+ /**
3
+ * An action's MCP tool hints, MCP's defaults where its `mcp` leaves one out: a write destructive,
4
+ * a read not, no tool idempotent and every tool open-world. A given value passes through, as MCP
5
+ * calls `destructiveHint` and `idempotentHint` meaningless on a read-only tool.
6
+ */
7
+ const hintsOf = ({ readOnly, mcp }) => ({
8
+ readOnlyHint: readOnly,
9
+ destructiveHint: mcp.destructiveHint ?? !readOnly,
10
+ idempotentHint: mcp.idempotentHint ?? false,
11
+ openWorldHint: mcp.openWorldHint ?? true
12
+ });
13
+ //#endregion
14
+ export { hintsOf };
@@ -1,6 +1,6 @@
1
- import { Anyone } from "./rules.mjs";
1
+ import { Anyone } from "./caller.mjs";
2
2
  import { Refusal } from "./errors.mjs";
3
- import { Action_d_exports, Any } from "../Action.mjs";
3
+ import { Any } from "../Action.mjs";
4
4
  import { Context, Effect, Layer, Scope } from "effect";
5
5
  //#region src/contract/implementation.d.ts
6
6
  /** Decoded values at the dispatch boundary every surface shares. */
@@ -192,4 +192,5 @@ export type BuildServices<App, Listed extends Any = OwnActions<App>> = App exten
192
192
  readonly "~buildServices": infer RX;
193
193
  readonly "~authorizeBuildServices": infer RAX;
194
194
  } ? RX | ([Protected<Serving<App, Listed>>] extends [never] ? never : RAX) : never;
195
- //#endregion
195
+ //#endregion
196
+ export {};
@@ -1,5 +1,7 @@
1
+ import { Anyone, assertAuthentication } from "./caller.mjs";
1
2
  import { Unauthenticated } from "./errors.mjs";
2
- import { Anyone, assertAuthentication, assertDistinct, assertOnce, projectedErrors } from "./rules.mjs";
3
+ import { assertDistinct, assertOnce } from "./names.mjs";
4
+ import { projectedErrors } from "./declared.mjs";
3
5
  import { Array, Cause, Context, Effect, Exit, Layer, Option, Schema } from "effect";
4
6
  //#region src/contract/implementation.ts
5
7
  const uniqueKey = () => Math.random().toString(36).slice(2);
@@ -152,13 +154,7 @@ const dispatch = (action, handle, before) => {
152
154
  } else reasons.push(reason);
153
155
  return yield* Effect.failCause(Cause.fromReasons(reasons));
154
156
  });
155
- return (input) => {
156
- const handled = Effect.withSpan(Effect.annotateLogs(Effect.suspend(() => handle(input)), actionAttributes), action.name, {
157
- captureStackTrace: false,
158
- attributes: actionAttributes
159
- });
160
- return Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(handled), Effect.catchCause(declaredOrDefect)));
161
- };
157
+ return (input) => Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(Effect.suspend(() => handle(input))), Effect.catchCause(declaredOrDefect))).pipe(Effect.annotateLogs(actionAttributes), Effect.withSpan(action.name, { attributes: actionAttributes }, { captureStackTrace: false }));
162
158
  };
163
159
  //#endregion
164
160
  export { Implementation, acquire, assertHeld, builders, built, memoized, provideHandlers, select, servingOf, toList };
@@ -0,0 +1,25 @@
1
+ //#region src/contract/names.ts
2
+ const validName = /^[A-Za-z0-9_-]{1,128}$/;
3
+ const assertName = (what, name) => {
4
+ if (!validName.test(name) || name === "then") throw new Error(`Invalid ${what}: ${name}`);
5
+ };
6
+ const assertDistinct = (what, items, nameOf, claimantOf = nameOf) => {
7
+ const seen = /* @__PURE__ */ new Map();
8
+ for (const item of items) {
9
+ const name = nameOf(item);
10
+ const claimant = claimantOf(item);
11
+ const other = seen.get(name);
12
+ if (other !== void 0) {
13
+ const by = other === claimant ? "" : `, claimed by ${other} and ${claimant}`;
14
+ throw new Error(`Duplicate ${what}: ${name}${by}`);
15
+ }
16
+ seen.set(name, claimant);
17
+ }
18
+ };
19
+ const assertOnce = (what, actions) => assertDistinct(what, actions, (action) => action.name);
20
+ const assertKnown = (what, keys, names) => {
21
+ const unknown = keys.filter((key) => !names.includes(key));
22
+ if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")}`);
23
+ };
24
+ //#endregion
25
+ export { assertDistinct, assertKnown, assertName, assertOnce };
@@ -0,0 +1,18 @@
1
+ import { unsuspended } from "./ast.mjs";
2
+ import { Schema, SchemaAST } from "effect";
3
+ //#region src/contract/status.ts
4
+ const httpApiStatusOf = SchemaAST.resolveAt("httpApiStatus");
5
+ const statusThroughSuspensions = (ast) => httpApiStatusOf(ast) ?? (SchemaAST.isSuspend(ast) ? statusThroughSuspensions(ast.thunk()) : void 0);
6
+ /**
7
+ * `error` as an endpoint declares it: each member of a union without a status of its own apart,
8
+ * each with its status, 422 without one.
9
+ */
10
+ const declared = (error) => {
11
+ const status = statusThroughSuspensions(error.ast);
12
+ if (status !== void 0) return [httpApiStatusOf(error.ast) === void 0 ? error.annotate({ httpApiStatus: status }) : error];
13
+ const resolved = unsuspended(error.ast);
14
+ if (SchemaAST.isUnion(resolved) && resolved.checks === void 0 && resolved.encoding === void 0) return resolved.types.flatMap((member) => declared(Schema.make(member)));
15
+ return [error.annotate({ httpApiStatus: 422 })];
16
+ };
17
+ //#endregion
18
+ export { declared, httpApiStatusOf };
@@ -1,6 +1,6 @@
1
1
  import { BuiltIns } from "../contract/errors.mjs";
2
2
  import { Call } from "../contract/call.mjs";
3
- import { Action_d_exports, Any, Errors } from "../Action.mjs";
3
+ import { Any, Errors } from "../Action.mjs";
4
4
  import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
5
5
  import { Effect, Schema } from "effect";
6
6
  import { HttpClient, HttpClientError } from "effect/http";
@@ -38,4 +38,5 @@ export interface AnyHttp {
38
38
  * with what its descriptor's verifier declares.
39
39
  */
40
40
  export type Client<H extends AnyHttp> = { readonly [A in H["actions"][number] as A["name"]]: Method<A, H["error"][number] | VerifierError<A, H["authentication"]>>; };
41
- //#endregion
41
+ //#endregion
42
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { BuiltIn } from "../contract/errors.mjs";
2
2
  import { Call } from "../contract/call.mjs";
3
- import { Action_d_exports, Any } from "../Action.mjs";
3
+ import { Any } from "../Action.mjs";
4
4
  import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
5
5
  import { Effect, Schema } from "effect";
6
6
  import { Headers, HttpClient, HttpClientError, HttpClientRequest } from "effect/http";
@@ -45,15 +45,47 @@ export interface RequestOptions {
45
45
  readonly url?: string;
46
46
  readonly headers?: Headers.Input;
47
47
  }
48
+ declare const StatusError_base: Schema.Class<StatusError, Schema.TaggedStruct<"StatusError", {
49
+ readonly status: Schema.Int;
50
+ readonly body: Schema.String;
51
+ }>, import("effect/Cause").YieldableError>;
52
+ /** The endpoint answered with an HTTP status other than 200, and a body no declared error decodes. */
53
+ declare class StatusError extends StatusError_base {}
54
+ declare const ProtocolError_base: Schema.Class<ProtocolError, Schema.TaggedStruct<"ProtocolError", {
55
+ readonly code: Schema.Finite;
56
+ readonly message: Schema.String;
57
+ }>, import("effect/Cause").YieldableError>;
58
+ /**
59
+ * The endpoint answered with a JSON-RPC error, such as the native server's for invalid arguments
60
+ * or an unknown tool.
61
+ */
62
+ declare class ProtocolError extends ProtocolError_base {}
63
+ declare const NoReply_base: Schema.Class<NoReply, Schema.TaggedStruct<"NoReply", {
64
+ readonly body: Schema.String;
65
+ }>, import("effect/Cause").YieldableError>;
66
+ /** The endpoint answered 200 without a JSON-RPC reply to read, as JSON or as an event stream. */
67
+ declare class NoReply extends NoReply_base {}
68
+ declare const UndeclaredError_base: Schema.Class<UndeclaredError, Schema.TaggedStruct<"UndeclaredError", {
69
+ readonly text: Schema.String;
70
+ }>, import("effect/Cause").YieldableError>;
71
+ /** The tool returned an error whose text decodes as none of the action's declared errors. */
72
+ declare class UndeclaredError extends UndeclaredError_base {}
73
+ declare const NoStructuredContent_base: Schema.Class<NoStructuredContent, Schema.TaggedStruct<"NoStructuredContent", {
74
+ readonly body: Schema.String;
75
+ }>, import("effect/Cause").YieldableError>;
76
+ /** The tool returned a result without the structured content its action's success needs. */
77
+ declare class NoStructuredContent extends NoStructuredContent_base {}
48
78
  declare const McpCallError_base: Schema.Class<McpCallError, Schema.TaggedStruct<"McpCallError", {
79
+ readonly tool: Schema.String;
80
+ readonly reason: Schema.Union<readonly [typeof StatusError, typeof ProtocolError, typeof NoReply, typeof UndeclaredError, typeof NoStructuredContent]>;
49
81
  readonly message: Schema.String;
50
82
  }>, import("effect/Cause").YieldableError>;
51
83
  /**
52
- * An answer a client method cannot read as the action's success or a declared error, such as
53
- * the native server's message for invalid arguments or an unknown tool, or a result without
54
- * its structured content. Its message holds the answer. Structured content that does not
55
- * decode as the success is a `SchemaError` instead, and a request that fails an
56
- * `HttpClientError`.
84
+ * An answer a client method cannot read as the action's success or a declared error: `reason`
85
+ * says which, with what the endpoint sent, so a caller branches on it with
86
+ * `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names the tool and the
87
+ * reason. Structured content that does not decode as the success is a `SchemaError` instead,
88
+ * and a request that fails an `HttpClientError`.
57
89
  */
58
90
  export declare class McpCallError extends McpCallError_base {}
59
91
  type CallError<A extends Any, D> = A["error"][number]["Type"] | VerifierError<A, D>["Type"] | BuiltIn | Schema.SchemaError | HttpClientError.HttpClientError | McpCallError;
@@ -84,4 +116,5 @@ export declare function client<const Actions extends ReadonlyArray<Any>, D exten
84
116
  * a call its types would not send.
85
117
  */
86
118
  export declare const request: (method: string, params?: Params, { headers, url }?: RequestOptions) => HttpClientRequest.HttpClientRequest;
87
- //#endregion
119
+ //#endregion
120
+ export {};