@gjermundgaraba/effect-actions 0.1.0-rc.0 → 0.1.0-rc.2

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 (45) hide show
  1. package/README.md +249 -75
  2. package/dist/Action.d.mts +35 -27
  3. package/dist/Action.mjs +9 -10
  4. package/dist/ActionCatalog.d.mts +31 -0
  5. package/dist/ActionCatalog.mjs +36 -0
  6. package/dist/ActionCli.d.mts +27 -0
  7. package/dist/ActionCli.mjs +26 -0
  8. package/dist/ActionCliClient.d.mts +45 -0
  9. package/dist/ActionCliClient.mjs +52 -0
  10. package/dist/ActionGroup.d.mts +25 -16
  11. package/dist/ActionGroup.mjs +18 -19
  12. package/dist/ActionHttp.d.mts +39 -0
  13. package/dist/ActionHttp.mjs +67 -0
  14. package/dist/ActionMcp.d.mts +41 -0
  15. package/dist/ActionMcp.mjs +25 -0
  16. package/dist/ActionToolkit.d.mts +37 -0
  17. package/dist/ActionToolkit.mjs +10 -0
  18. package/dist/Authentication.d.mts +46 -0
  19. package/dist/Authentication.mjs +55 -0
  20. package/dist/Testing.d.mts +36 -0
  21. package/dist/Testing.mjs +42 -0
  22. package/dist/TestingClient.d.mts +16 -0
  23. package/dist/{testing/client.mjs → TestingClient.mjs} +5 -6
  24. package/dist/{rolldown-runtime-D7D4PA-g.mjs → _virtual/_rolldown/runtime.mjs} +1 -1
  25. package/dist/internal/actions.d.mts +18 -0
  26. package/dist/internal/actions.mjs +16 -0
  27. package/dist/internal/cli.d.mts +29 -0
  28. package/dist/internal/cli.mjs +36 -0
  29. package/dist/internal/implementation.d.mts +39 -0
  30. package/dist/internal/implementation.mjs +37 -0
  31. package/dist/internal/tools.mjs +84 -0
  32. package/package.json +16 -12
  33. package/dist/authentication.d.mts +0 -27
  34. package/dist/authentication.mjs +0 -31
  35. package/dist/http.d.mts +0 -53
  36. package/dist/http.mjs +0 -74
  37. package/dist/implementation-BkLqMWtv.mjs +0 -32
  38. package/dist/implementation-D75QAcD3.d.mts +0 -21
  39. package/dist/index.d.mts +0 -6
  40. package/dist/index.mjs +0 -6
  41. package/dist/mcp.d.mts +0 -46
  42. package/dist/mcp.mjs +0 -189
  43. package/dist/testing/client.d.mts +0 -11
  44. package/dist/testing.d.mts +0 -10
  45. package/dist/testing.mjs +0 -34
@@ -0,0 +1,42 @@
1
+ import { Effect, Layer, Predicate } from "effect";
2
+ import { HttpApiClient } from "effect/unstable/httpapi";
3
+ import { FetchHttpClient } from "effect/unstable/http";
4
+ //#region src/Testing.ts
5
+ /**
6
+ * The native grouped HTTP client, calling `handler` in memory instead of the network.
7
+ * `baseUrl` defaults to `http://localhost`.
8
+ */
9
+ const httpClient = (api, handler, options) => HttpApiClient.make(api, {
10
+ baseUrl: "http://localhost",
11
+ ...options
12
+ }).pipe(Effect.provide(FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch, (input, init) => handler(new Request(input, init)))))));
13
+ /** Build one stateless 2026-07-28 JSON-RPC request, with client metadata defaulted. */
14
+ const mcpRequest = ({ url, method, params = {}, headers: init }) => {
15
+ const headers = new Headers(init);
16
+ headers.set("content-type", "application/json");
17
+ headers.set("accept", "application/json, text/event-stream");
18
+ headers.set("mcp-protocol-version", "2026-07-28");
19
+ headers.set("mcp-method", method);
20
+ if (Predicate.isString(params.name)) headers.set("mcp-name", params.name);
21
+ return new Request(url, {
22
+ method: "POST",
23
+ headers,
24
+ body: JSON.stringify({
25
+ jsonrpc: "2.0",
26
+ id: 1,
27
+ method,
28
+ params: {
29
+ ...params,
30
+ _meta: Object.assign({
31
+ "io.modelcontextprotocol/clientCapabilities": {},
32
+ "io.modelcontextprotocol/clientInfo": {
33
+ name: "test",
34
+ version: "0"
35
+ }
36
+ }, params._meta, { "io.modelcontextprotocol/protocolVersion": "2026-07-28" })
37
+ }
38
+ })
39
+ });
40
+ };
41
+ //#endregion
42
+ export { httpClient, mcpRequest };
@@ -0,0 +1,16 @@
1
+ import { Client, ClientOptions } from "@modelcontextprotocol/client";
2
+ //#region src/TestingClient.d.ts
3
+ /** How `withMcpClient` connects the official client. */
4
+ export interface McpClientOptions {
5
+ /** A web handler, such as `HttpRouter.toWebHandler(routes).handler`. */
6
+ readonly fetch: (request: Request) => Promise<Response>;
7
+ readonly path: string;
8
+ /** Passed directly to the official client; omitted options use its native default. */
9
+ readonly versionNegotiation?: ClientOptions["versionNegotiation"];
10
+ /** Defaults to `http://localhost`. */
11
+ readonly baseUrl?: string | URL;
12
+ readonly headers?: ConstructorParameters<typeof Headers>[0];
13
+ }
14
+ /** Connect an official client and always close its transport after the callback. */
15
+ export declare const withMcpClient: <A>({ fetch, path, baseUrl, headers, versionNegotiation }: McpClientOptions, run: (client: Client) => Promise<A>) => Promise<A>;
16
+ //#endregion
@@ -1,16 +1,15 @@
1
1
  import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
2
2
  //#region src/TestingClient.ts
3
3
  /** Connect an official client and always close its transport after the callback. */
4
- const withMcpClient = async (fetch, run, { mode = "modern", path, baseUrl = "http://localhost", headers }) => {
4
+ const withMcpClient = async ({ fetch, path, baseUrl = "http://localhost", headers, versionNegotiation }, run) => {
5
5
  const client = new Client({
6
6
  name: "test",
7
7
  version: "0"
8
- }, { versionNegotiation: { mode: mode === "modern" ? { pin: "2026-07-28" } : "legacy" } });
8
+ }, versionNegotiation === void 0 ? {} : { versionNegotiation });
9
9
  try {
10
- await client.connect(new StreamableHTTPClientTransport(new URL(path, baseUrl), {
11
- ...headers === void 0 ? {} : { requestInit: { headers } },
12
- fetch: (input, init) => fetch(new Request(input, init))
13
- }));
10
+ const transportOptions = { fetch: (input, init) => fetch(new Request(input, init)) };
11
+ if (headers !== void 0) transportOptions.requestInit = { headers };
12
+ await client.connect(new StreamableHTTPClientTransport(new URL(path, baseUrl), transportOptions));
14
13
  return await run(client);
15
14
  } finally {
16
15
  await client.close();
@@ -10,4 +10,4 @@ var __exportAll = (all, no_symbols) => {
10
10
  return target;
11
11
  };
12
12
  //#endregion
13
- export { __exportAll as t };
13
+ export { __exportAll };
@@ -0,0 +1,18 @@
1
+ import { Action_d_exports, Any, Codec } from "../Action.mjs";
2
+ import { HttpApiError } from "effect/unstable/httpapi";
3
+ //#region src/internal/actions.d.ts
4
+ /** Pure HTTP policy for native schema decoding and encoding failures. */
5
+ export interface SchemaErrorPolicy<Errors extends ReadonlyArray<Codec>> {
6
+ readonly errors: Errors;
7
+ readonly map: (failure: HttpApiError.HttpApiSchemaError) => NoInfer<Errors[number]["Type"]>;
8
+ }
9
+ /** The contract half of a group: what adapters and clients need, without `implement`. */
10
+ export interface Actions<Name extends string = string, A extends ReadonlyArray<Any> = ReadonlyArray<Any>, PolicyErrors extends ReadonlyArray<Codec> = ReadonlyArray<Codec>> {
11
+ readonly name: Name;
12
+ readonly actions: A;
13
+ /** How HTTP answers failed decoding or encoding; native behavior without one. MCP is unaffected. */
14
+ readonly schemaError: SchemaErrorPolicy<PolicyErrors> | undefined;
15
+ }
16
+ /** The errors a group's policy may answer with, by lookup rather than a conditional type. */
17
+ export type PolicyError<G extends Actions> = NonNullable<G["schemaError"]>["errors"][number];
18
+ //#endregion
@@ -0,0 +1,16 @@
1
+ //#region src/internal/actions.ts
2
+ const validName = /^[A-Za-z0-9_-]+$/;
3
+ /** Names become path segments, OpenAPI identifiers and client method keys. */
4
+ const assertName = (what, name) => {
5
+ if (!validName.test(name) || name === "then") throw new Error(`Invalid ${what}: ${name}`);
6
+ };
7
+ /** Each namespace is checked by whoever owns it: a group, the routes, or the MCP tools. */
8
+ const assertDistinct = (what, names) => {
9
+ const seen = /* @__PURE__ */ new Set();
10
+ for (const name of names) {
11
+ if (seen.has(name)) throw new Error(`Duplicate ${what}: ${name}`);
12
+ seen.add(name);
13
+ }
14
+ };
15
+ //#endregion
16
+ export { assertDistinct, assertName };
@@ -0,0 +1,29 @@
1
+ import "../Action.mjs";
2
+ import { Effect, Schema } from "effect";
3
+ import { Command } from "effect/unstable/cli";
4
+ //#region src/internal/cli.d.ts
5
+ /** Options shared by the local and HTTP command projections. */
6
+ interface CommonOptions<Output> {
7
+ /** Override the command name. The action name is used by default. */
8
+ readonly name?: string;
9
+ /** Human output. JSON remains available with `--json`. */
10
+ readonly render?: (output: Output) => string;
11
+ }
12
+ /** The default projection accepts a whole canonical JSON input with `--input`. */
13
+ export interface JsonOptions<Output> extends CommonOptions<Output> {
14
+ readonly parameters?: never;
15
+ readonly input?: never;
16
+ }
17
+ /** Supply native CLI parameters and map their parsed values to canonical action JSON. */
18
+ export interface ParametersOptions<Output, Parameters extends Command.Command.Config> extends CommonOptions<Output> {
19
+ /** Native Effect CLI flags and arguments. */
20
+ readonly parameters: Parameters;
21
+ /** Maps native parsed parameters to the action's canonical JSON input. */
22
+ readonly input: (parsed: Command.Command.Config.Infer<Parameters>) => Schema.Json;
23
+ }
24
+ /**
25
+ * A command either accepts whole JSON input or has explicit native CLI parameters.
26
+ * The latter intentionally has no implicit `--input` mode.
27
+ */
28
+ export type Options<Output, Parameters extends Command.Command.Config = never> = JsonOptions<Output> | ParametersOptions<Output, Parameters>;
29
+ //#endregion
@@ -0,0 +1,36 @@
1
+ import { Console, Effect, Option, Schema } from "effect";
2
+ import { Command, Flag } from "effect/unstable/cli";
3
+ //#region src/internal/cli.ts
4
+ const isParametersOptions = (options) => options?.parameters !== void 0;
5
+ const jsonOutput = Flag.Boolean("json").pipe(Flag.optional, Flag.withDescription("Print machine-readable JSON"));
6
+ const output = (action, execute, input, render, useJson) => Effect.gen(function* () {
7
+ const value = yield* execute(input);
8
+ const encoded = yield* Schema.encodeEffect(Schema.toCodecJson(action.success))(value);
9
+ const text = render !== void 0 && !useJson ? render(value) : JSON.stringify(encoded, null, 2);
10
+ yield* Console.log(text);
11
+ });
12
+ const defaultCommand = (action, execute, options) => {
13
+ const input = Flag.String("input").pipe(Flag.withDefault("{}"), Flag.withSchema(Schema.fromJsonString(Schema.toCodecJson(action.input))), Flag.withDescription("Whole canonical action input as JSON"));
14
+ const config = options?.render === void 0 ? { input } : {
15
+ input,
16
+ output: { json: jsonOutput }
17
+ };
18
+ return Command.make(options?.name ?? action.name, config, (parsed) => Effect.gen(function* () {
19
+ const useJson = "output" in parsed && Option.isSome(parsed.output.json) && parsed.output.json.value === true;
20
+ return yield* output(action, execute, parsed.input, options?.render, useJson);
21
+ })).pipe(Command.withDescription(action.description));
22
+ };
23
+ const parametersCommand = (action, execute, options) => {
24
+ if (options.render === void 0) return Command.make(options.name ?? action.name, options.parameters, (parsed) => Effect.flatMap(Schema.decodeUnknownEffect(Schema.toCodecJson(action.input))(options.input(parsed)), (input) => output(action, execute, input, void 0, true))).pipe(Command.withDescription(action.description));
25
+ return Command.make(options.name ?? action.name, {
26
+ parameters: options.parameters,
27
+ output: { json: jsonOutput }
28
+ }, (parsed) => Effect.flatMap(Schema.decodeUnknownEffect(Schema.toCodecJson(action.input))(options.input(parsed.parameters)), (input) => output(action, execute, input, options.render, Option.isSome(parsed.output.json) && parsed.output.json.value === true))).pipe(Command.withDescription(action.description));
29
+ };
30
+ /** Build one native command around an action-bound operation. */
31
+ const command = (action, execute, options) => {
32
+ if (isParametersOptions(options)) return parametersCommand(action, execute, options);
33
+ return defaultCommand(action, execute, options);
34
+ };
35
+ //#endregion
36
+ export { command };
@@ -0,0 +1,39 @@
1
+ import { Action_d_exports, Any } from "../Action.mjs";
2
+ import { Actions } from "./actions.mjs";
3
+ import { Effect, Scope } from "effect";
4
+ //#region src/internal/implementation.d.ts
5
+ /** Decoded values at the adapter dispatch boundary. */
6
+ export type ErasedValue = Any["input"]["Type"];
7
+ type ErasedHandler<R> = {
8
+ handle(input: ErasedValue): Effect.Effect<ErasedValue, ErasedValue, R>;
9
+ }["handle"];
10
+ /** An adapter's erased view of a record of handlers with request requirements `R`. */
11
+ export type Handlers<R> = Readonly<Record<string, ErasedHandler<R>>>;
12
+ /** Per-request requirements of the handler selected by an action name. */
13
+ export type HandlerContext<H, Name extends keyof H> = H[Name] extends ((input: never) => Effect.Effect<infer _A, infer _E, infer R>) ? R : never;
14
+ /** The union of per-request requirements declared by a handler record. */
15
+ export type HandlersContext<H> = { readonly [K in keyof H]: HandlerContext<H, K>; }[keyof H];
16
+ /**
17
+ * An acquired handler record for a group.
18
+ *
19
+ * The private field makes this class nominal: a structurally similar object,
20
+ * including one made by spreading an implementation, is not an implementation.
21
+ */
22
+ export declare class Implementation<G extends Actions, H, EX, RX> {
23
+ #private;
24
+ /** The group whose exact handler record was built. */
25
+ readonly group: G;
26
+ private constructor();
27
+ /** Acquire the exact handler record in the adapter layer's scope. */
28
+ get build(): Effect.Effect<H, EX, RX | Scope.Scope>;
29
+ static make<G extends Actions, H, EX, RX>(group: G, build: Effect.Effect<H, EX, RX | Scope.Scope>): Implementation<G, H, EX, RX>;
30
+ }
31
+ /** Any nominal implementation of `G`, with its record and channels erased. */
32
+ export type AnyImplementation<G extends Actions = Actions> = Implementation<G, any, any, any>;
33
+ /** Per-request requirements of one implementation, or of a union of them. */
34
+ export type RequestContext<App> = App extends Implementation<any, infer H, any, any> ? HandlersContext<H> : never;
35
+ /** Handler-acquisition failures of one implementation, or of a union of them. */
36
+ export type BuildError<App> = App extends Implementation<any, any, infer EX, any> ? EX : never;
37
+ /** Handler-acquisition requirements of one implementation, or of a union of them. */
38
+ export type BuildContext<App> = App extends Implementation<any, any, any, infer RX> ? RX : never;
39
+ //#endregion
@@ -0,0 +1,37 @@
1
+ import { Effect } from "effect";
2
+ //#region src/internal/implementation.ts
3
+ /**
4
+ * An acquired handler record for a group.
5
+ *
6
+ * The private field makes this class nominal: a structurally similar object,
7
+ * including one made by spreading an implementation, is not an implementation.
8
+ */
9
+ var Implementation = class Implementation {
10
+ group;
11
+ #build;
12
+ constructor(group, build) {
13
+ this.group = group;
14
+ this.#build = build;
15
+ }
16
+ /** Acquire the exact handler record in the adapter layer's scope. */
17
+ get build() {
18
+ return this.#build;
19
+ }
20
+ static make(group, build) {
21
+ return new Implementation(group, build);
22
+ }
23
+ };
24
+ /**
25
+ * Select and invoke a handler after an adapter has selected a concrete action.
26
+ * `R` remains in the returned effect so transport layers cannot erase required
27
+ * per-request services while assembling routes.
28
+ */
29
+ const dispatch = (group, action, handlers) => {
30
+ const handle = handlers[action.name];
31
+ if (handle === void 0) throw new Error(`Missing handler: ${action.name}`);
32
+ return (input) => {
33
+ return Effect.withSpan(Effect.suspend(() => handle(input)), `${group.name}.${action.name}`, { captureStackTrace: false });
34
+ };
35
+ };
36
+ //#endregion
37
+ export { Implementation, dispatch };
@@ -0,0 +1,84 @@
1
+ import { dispatch } from "./implementation.mjs";
2
+ import { Effect, Schema } from "effect";
3
+ import { Tool, Toolkit } from "effect/unstable/ai";
4
+ //#region src/internal/tools.ts
5
+ const isToolAction = (action) => action.mcp !== false;
6
+ /** Only tool-enabled groups are acquired; each implementation appears once. */
7
+ const toolApps = (apps) => apps.flatMap((app) => {
8
+ const actions = app.group.actions.filter(isToolAction);
9
+ return actions.length === 0 ? [] : [{
10
+ app,
11
+ actions
12
+ }];
13
+ });
14
+ /** Flatten the projected contracts only after deciding which groups are served. */
15
+ const entries = (apps) => apps.flatMap(({ app, actions }) => actions.map((action) => ({
16
+ app,
17
+ action
18
+ })));
19
+ /** Fail before acquiring handlers when two exposed tools share a name. */
20
+ const assertDistinctToolNames = (tools) => {
21
+ const names = /* @__PURE__ */ new Set();
22
+ for (const { action } of tools) {
23
+ const name = action.mcp.name;
24
+ if (names.has(name)) throw new Error(`Duplicate MCP tool: ${name}`);
25
+ names.add(name);
26
+ }
27
+ };
28
+ const annotate = (tool, mcp) => tool.annotate(Tool.Readonly, mcp.readOnly).annotate(Tool.Destructive, mcp.destructive).annotate(Tool.Idempotent, mcp.idempotent).annotate(Tool.OpenWorld, mcp.openWorld);
29
+ /**
30
+ * A native Effect AI tool. Its schemas retain action transforms and its result
31
+ * is the action result itself, rather than an MCP response envelope.
32
+ */
33
+ const nativeTool = (action) => annotate(Tool.make(action.mcp.name, {
34
+ description: action.description,
35
+ parameters: action.input,
36
+ success: action.success,
37
+ failure: Schema.Union(action.errors),
38
+ failureMode: "return"
39
+ }), action.mcp);
40
+ /**
41
+ * MCP has a JSON-only wire contract. Success uses its documented `{ value }`
42
+ * structured-content envelope; declared failures are returned as JSON text.
43
+ */
44
+ const mcpTool = (action) => annotate(Tool.make(action.mcp.name, {
45
+ description: action.description,
46
+ parameters: Schema.toCodecJson(action.input),
47
+ success: Schema.toCodecJson(Schema.Struct({ value: action.success })),
48
+ failure: Schema.toCodecJson(Schema.Union(action.errors)),
49
+ failureMode: "return"
50
+ }), action.mcp);
51
+ /** MCP's input schema must describe a JSON object, including no-argument tools. */
52
+ const assertMcpObjectInput = (action, tool) => {
53
+ const root = Tool.getJsonSchemaFromSchema(tool.parametersSchema);
54
+ if (root.$ref === void 0 && root.type !== "object") throw new Error(`${action.name}: MCP input must have an object root; omit input for no arguments`);
55
+ };
56
+ const project = (projection, entry) => {
57
+ const tool = projection === "native" ? nativeTool(entry.action) : mcpTool(entry.action);
58
+ if (projection === "mcp") assertMcpObjectInput(entry.action, tool);
59
+ return tool;
60
+ };
61
+ const handler = (projection, app, action, handlers) => {
62
+ const run = (input) => dispatch(app.group, action, handlers)(input);
63
+ return projection === "native" ? run : (input) => Effect.map(run(input), (value) => ({ value }));
64
+ };
65
+ /**
66
+ * Project MCP-enabled actions and acquire their handlers exactly once per adapter layer.
67
+ * Native and MCP differ only in tool codecs and the MCP success envelope; selection,
68
+ * scoped acquisition, dispatch and native Toolkit binding stay identical.
69
+ */
70
+ const bindTools = (apps, projection) => {
71
+ const served = toolApps(apps);
72
+ const selected = entries(served);
73
+ assertDistinctToolNames(selected);
74
+ const toolkit = Toolkit.make(...selected.map((entry) => project(projection, entry)));
75
+ return {
76
+ toolkit,
77
+ layer: toolkit.toLayer(Effect.map(Effect.forEach(served, ({ app, actions }) => Effect.map(app.build, (handlers) => {
78
+ const record = handlers;
79
+ return actions.map((action) => [action.mcp.name, handler(projection, app, action, record)]);
80
+ })), (built) => Object.fromEntries(built.flat())))
81
+ };
82
+ };
83
+ //#endregion
84
+ export { bindTools };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/effect-actions",
3
- "version": "0.1.0-rc.0",
4
- "description": "Define Effect action contracts once and expose them over HTTP and MCP.",
3
+ "version": "0.1.0-rc.2",
4
+ "description": "Define Effect action contracts once and project them onto HTTP, MCP, native Toolkits, catalogs, and CLIs.",
5
5
  "homepage": "https://github.com/gjermundgaraba/effect-actions#readme",
6
6
  "bugs": {
7
7
  "url": "https://github.com/gjermundgaraba/effect-actions/issues"
@@ -16,24 +16,28 @@
16
16
  ],
17
17
  "type": "module",
18
18
  "exports": {
19
- ".": "./dist/index.mjs",
20
19
  "./Action": "./dist/Action.mjs",
20
+ "./ActionCatalog": "./dist/ActionCatalog.mjs",
21
+ "./ActionCli": "./dist/ActionCli.mjs",
22
+ "./ActionCliClient": "./dist/ActionCliClient.mjs",
21
23
  "./ActionGroup": "./dist/ActionGroup.mjs",
22
- "./authentication": "./dist/authentication.mjs",
23
- "./http": "./dist/http.mjs",
24
- "./mcp": "./dist/mcp.mjs",
25
- "./testing": "./dist/testing.mjs",
26
- "./testing/client": "./dist/testing/client.mjs",
24
+ "./ActionHttp": "./dist/ActionHttp.mjs",
25
+ "./ActionMcp": "./dist/ActionMcp.mjs",
26
+ "./ActionToolkit": "./dist/ActionToolkit.mjs",
27
+ "./Authentication": "./dist/Authentication.mjs",
28
+ "./Testing": "./dist/Testing.mjs",
29
+ "./TestingClient": "./dist/TestingClient.mjs",
27
30
  "./package.json": "./package.json"
28
31
  },
29
32
  "publishConfig": {
30
- "access": "public"
33
+ "access": "public",
34
+ "tag": "rc"
31
35
  },
32
36
  "devDependencies": {
33
- "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect/@effect/platform-node@4e4a3a6",
37
+ "@effect/platform-node": "4.0.0-rc.116",
34
38
  "@modelcontextprotocol/client": "2.0.0",
35
39
  "@types/node": "26.5.1",
36
- "effect": "https://pkg.pr.new/Effect-TS/effect/effect@4e4a3a6",
40
+ "effect": "4.0.0-rc.116",
37
41
  "tsx": "4.23.13",
38
42
  "typescript": "7.0.2",
39
43
  "vite": "npm:@voidzero-dev/vite-plus-core@0.3.2",
@@ -41,7 +45,7 @@
41
45
  },
42
46
  "peerDependencies": {
43
47
  "@modelcontextprotocol/client": "^2.0.0",
44
- "effect": "4.0.0-rc.115"
48
+ "effect": "4.0.0-rc.116"
45
49
  },
46
50
  "peerDependenciesMeta": {
47
51
  "@modelcontextprotocol/client": {
@@ -1,27 +0,0 @@
1
- import { Codec, t as Action_d_exports } from "./Action.mjs";
2
- import { Context, Effect } from "effect";
3
- import { Headers, HttpRouter } from "effect/unstable/http";
4
- declare namespace Authentication_d_exports {
5
- export { Options, middleware };
6
- }
7
- export interface Options<A, Errors extends ReadonlyArray<Codec>, R> {
8
- readonly authenticate: Effect.Effect<A, Errors[number]["Type"], R>;
9
- readonly errors: Errors;
10
- readonly headers?: (error: Errors[number]["Type"]) => Headers.Input;
11
- }
12
- /**
13
- * Authenticate each request and provide its identity to the downstream handler.
14
- * Only authentication failures are encoded, using each schema's httpApiStatus.
15
- * Dependencies remain native router request requirements. Acquired resources live
16
- * until the request scope closes, including while the handler is running.
17
- */
18
- export declare const middleware: <I, A, const Errors extends ReadonlyArray<Codec>, R>(service: Context.Key<I, A>, options: Options<NoInfer<A>, Errors, R>) => HttpRouter.Middleware<{
19
- provides: I;
20
- handles: never;
21
- error: never;
22
- requires: Exclude<R, HttpRouter.Provided> | Exclude<Exclude<import("effect/Types").NoInfer<I>, I>, HttpRouter.Provided>;
23
- layerError: never;
24
- layerRequires: never;
25
- }>;
26
- //#endregion
27
- export { Authentication_d_exports as t };
@@ -1,31 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { Effect, Schema, SchemaAST } from "effect";
3
- import { HttpEffect, HttpRouter, HttpServerResponse } from "effect/unstable/http";
4
- //#region src/Authentication.ts
5
- var Authentication_exports = /* @__PURE__ */ __exportAll({ middleware: () => middleware });
6
- /**
7
- * Authenticate each request and provide its identity to the downstream handler.
8
- * Only authentication failures are encoded, using each schema's httpApiStatus.
9
- * Dependencies remain native router request requirements. Acquired resources live
10
- * until the request scope closes, including while the handler is running.
11
- */
12
- const middleware = (service, options) => {
13
- const errors = options.errors.map((schema) => ({
14
- matches: Schema.is(schema),
15
- encode: HttpServerResponse.schemaJson(schema),
16
- status: SchemaAST.resolveAt("httpApiStatus")(schema.ast) ?? 500
17
- }));
18
- return HttpRouter.middleware()((httpEffect) => options.authenticate.pipe(Effect.matchEffect({
19
- onFailure: (error) => {
20
- const selected = errors.find((candidate) => candidate.matches(error));
21
- if (selected === void 0) return Effect.die(/* @__PURE__ */ new Error("Undeclared authentication error"));
22
- return selected.encode(error, {
23
- status: selected.status,
24
- ...options.headers === void 0 ? {} : { headers: options.headers(error) }
25
- }).pipe(Effect.orDie);
26
- },
27
- onSuccess: (identity) => Effect.provideService(httpEffect, service, identity)
28
- }), HttpEffect.withPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "cache-control", "no-store")))));
29
- };
30
- //#endregion
31
- export { middleware, Authentication_exports as t };
package/dist/http.d.mts DELETED
@@ -1,53 +0,0 @@
1
- import { Any, Codec, SchemaErrorPolicy, t as Action_d_exports } from "./Action.mjs";
2
- import { t as Implementation } from "./implementation-D75QAcD3.mjs";
3
- import { Effect, Layer, Schema } from "effect";
4
- import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi";
5
- import { Etag, HttpClient, HttpPlatform, HttpRouter } from "effect/unstable/http";
6
- import { FileSystem } from "effect/FileSystem";
7
- import { Path } from "effect/Path";
8
- declare namespace ActionHttp_d_exports {
9
- export { Actions, Client, ClientOptions, LayerOptions, Options, api, client, configure, layer, openapi };
10
- }
11
- export interface Options<Errors extends ReadonlyArray<Codec> = []> {
12
- readonly apiPath: `/${string}`;
13
- readonly schemaError?: SchemaErrorPolicy<Errors>;
14
- }
15
- /** Layer/configure options: mount path plus OpenAPI document route. */
16
- export interface LayerOptions<Errors extends ReadonlyArray<Codec> = []> extends Options<Errors> {
17
- /** Set false when the host serves a combined document for multiple groups. */
18
- readonly openapiPath: `/${string}` | false;
19
- }
20
- /** A group or its implementation. */
21
- export interface Actions<A extends ReadonlyArray<Any> = ReadonlyArray<Any>> {
22
- readonly actions: A;
23
- }
24
- /** Distribute over the tuple so each name retains its own codecs. */
25
- type Endpoint<A extends Any, E extends Codec> = A extends {
26
- readonly http: false;
27
- } ? never : A extends Any ? HttpApiEndpoint.HttpApiEndpoint<A["name"], "POST", `/${string}`, never, never, Schema.toCodecJson<A["input"]>, never, Schema.toCodecJson<A["success"]>, Schema.toCodecJson<A["errors"][number] | E>> : never;
28
- /** The native `HttpApi` for every HTTP-enabled action: `POST <apiPath>/<name>`. */
29
- export declare function api<A extends ReadonlyArray<Any>, Errors extends ReadonlyArray<Codec> = []>(group: Actions<A>, options: Options<Errors>): HttpApi.HttpApi<"actions", HttpApiGroup.HttpApiGroup<"actions", Endpoint<A[number], Errors[number]>>>;
30
- /** The OpenAPI 3.1 document Effect derives from `api`. */
31
- export declare const openapi: <Errors extends ReadonlyArray<Codec> = []>(group: Actions, options: Options<Errors>) => OpenApi.OpenAPISpec;
32
- /**
33
- * Register the endpoints and optional OpenAPI document route on the router.
34
- * Handler requirements are request-level requirements, exactly as native
35
- * `HttpApiBuilder` handlers' are. Groups with no HTTP action register nothing.
36
- */
37
- export declare const layer: <Actions extends ReadonlyArray<Any>, R, EX, RX, Errors extends ReadonlyArray<Codec> = []>(app: Implementation<Actions, R, EX, RX>, options: LayerOptions<Errors>) => Layer.Layer<never, EX, RX | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", R> | Etag.Generator | FileSystem | HttpPlatform.HttpPlatform | Path>;
38
- type NativeClientOptions = NonNullable<Parameters<typeof HttpApiClient.make>[1]>;
39
- /** Connection options plus the transport configuration used by HTTP clients. */
40
- export type ClientOptions<Errors extends ReadonlyArray<Codec> = []> = NativeClientOptions & Options<Errors>;
41
- /** Direct decoded-input methods, excluding MCP-only actions. */
42
- export type Client<A extends ReadonlyArray<Any>, E extends Codec = never> = { readonly [Item in A[number] as Item["http"] extends false ? never : Item["name"]]: (...args: {} extends Item["input"]["Type"] ? [input?: Item["input"]["Type"]] : [input: Item["input"]["Type"]]) => HttpApiClient.Client.MethodReturn<Endpoint<Item, E>, never, never, "decoded-only">; };
43
- /** Bind transport configuration once for contracts, routes, documents and clients. */
44
- export declare const configure: <Errors extends ReadonlyArray<Codec> = []>(options: LayerOptions<Errors>) => {
45
- api: <A extends ReadonlyArray<Any>>(group: Actions<A>) => HttpApi.HttpApi<"actions", HttpApiGroup.HttpApiGroup<"actions", Endpoint<A[number], Errors[number]>, false>>;
46
- openapi: (group: Actions) => OpenApi.OpenAPISpec;
47
- layer: <A extends ReadonlyArray<Any>, R, EX, RX>(app: Implementation<A, R, EX, RX>) => Layer.Layer<never, EX, RX | FileSystem | Etag.Generator | HttpPlatform.HttpPlatform | HttpRouter.HttpRouter | Path | HttpRouter.Request.From<"Requires", R>>;
48
- client: <A extends ReadonlyArray<Any>>(group: Actions<A>, { baseUrl, transformClient, transformResponse }?: NativeClientOptions) => Effect.Effect<Client<A, Errors[number]>, never, HttpClient.HttpClient>;
49
- };
50
- /** Direct action methods backed by the native HTTP client and its codecs. */
51
- export declare function client<A extends ReadonlyArray<Any>, Errors extends ReadonlyArray<Codec> = []>(group: Actions<A>, options: ClientOptions<Errors>): Effect.Effect<Client<A, Errors[number]>, never, HttpClient.HttpClient>;
52
- //#endregion
53
- export { ActionHttp_d_exports as t };
package/dist/http.mjs DELETED
@@ -1,74 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { n as handlerFor, t as Implementation } from "./implementation-BkLqMWtv.mjs";
3
- import { Context, Effect, Layer, Schema } from "effect";
4
- import { HttpApi, HttpApiBuilder, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, OpenApi } from "effect/unstable/httpapi";
5
- //#region src/ActionHttp.ts
6
- var ActionHttp_exports = /* @__PURE__ */ __exportAll({
7
- api: () => api,
8
- client: () => client,
9
- configure: () => configure,
10
- layer: () => layer,
11
- openapi: () => openapi
12
- });
13
- const endpoint = (apiPath, action, errors) => HttpApiEndpoint.post(action.name, `${apiPath}/${action.name}`, {
14
- payload: action.input,
15
- success: action.success,
16
- error: [...action.errors, ...errors]
17
- }).annotate(OpenApi.Description, action.description);
18
- function api(group, options) {
19
- const [first, ...rest] = group.actions.filter((action) => action.http).map((action) => endpoint(options.apiPath, action, options.schemaError?.errors ?? []));
20
- if (first === void 0) throw new Error("No HTTP-enabled actions");
21
- return HttpApi.make("actions").add(HttpApiGroup.make("actions").add(first, ...rest));
22
- }
23
- /** The OpenAPI 3.1 document Effect derives from `api`. */
24
- const openapi = (group, options) => OpenApi.fromApi(api(group, options));
25
- /**
26
- * Register the endpoints and optional OpenAPI document route on the router.
27
- * Handler requirements are request-level requirements, exactly as native
28
- * `HttpApiBuilder` handlers' are. Groups with no HTTP action register nothing.
29
- */
30
- const layer = (app, options) => {
31
- const exposed = app.actions.filter((action) => action.http);
32
- if (exposed.length === 0) return Layer.empty;
33
- const policy = options.schemaError;
34
- class SchemaErrors extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: policy?.errors ?? [] }) {}
35
- const schemaErrors = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(policy === void 0 ? failure : policy.map({
36
- phase: failure.kind === "Body" || failure.kind === "ResponseHeaders" ? "output" : "input",
37
- cause: failure.cause
38
- })));
39
- const httpApi = api(app, options).middleware(SchemaErrors);
40
- return Implementation.register(app, (table) => {
41
- const group = HttpApiBuilder.group(httpApi, "actions", (handlers) => handlers.handleAll(Object.fromEntries(exposed.map((action) => {
42
- const handle = handlerFor(table, action);
43
- return [action.name, (request) => handle(request.payload)];
44
- }))));
45
- const isolated = Layer.fromBuildMemo((memoMap, scope) => Layer.buildWithMemoMap(group.pipe(Layer.provide(schemaErrors)), memoMap, scope).pipe(Effect.setContext(Context.empty())));
46
- return HttpApiBuilder.layer(httpApi, { openapiPath: options.openapiPath === false ? void 0 : options.openapiPath }).pipe(Layer.provide(isolated));
47
- });
48
- };
49
- /** Bind transport configuration once for contracts, routes, documents and clients. */
50
- const configure = (options) => ({
51
- api: (group) => api(group, options),
52
- openapi: (group) => openapi(group, options),
53
- layer: (app) => layer(app, options),
54
- client: (group, { baseUrl, transformClient, transformResponse } = {}) => client(group, {
55
- ...options,
56
- baseUrl,
57
- transformClient,
58
- transformResponse
59
- })
60
- });
61
- function client(group, options) {
62
- return Effect.map(HttpApiClient.make(api(group, options), options), (native) => {
63
- const acceptsUndefined = new Set(group.actions.filter((action) => Schema.is(action.input)(void 0)).map((action) => action.name));
64
- return Object.fromEntries(Object.entries(native.actions).map(([name, method]) => {
65
- if (name === "then") throw new Error("Action \"then\" requires the native grouped HttpApiClient");
66
- return [name, (input) => method({
67
- payload: input === void 0 && !acceptsUndefined.has(name) ? {} : input,
68
- responseMode: "decoded-only"
69
- })];
70
- }));
71
- });
72
- }
73
- //#endregion
74
- export { api, client, configure, layer, openapi, ActionHttp_exports as t };
@@ -1,32 +0,0 @@
1
- import { Context, Effect, Layer } from "effect";
2
- //#region src/internal/implementation.ts
3
- const handlerFor = (table, action) => {
4
- const handle = table[action.name];
5
- if (handle === void 0) throw new Error(`Missing handler: ${action.name}`);
6
- return handle;
7
- };
8
- let implementations = 0;
9
- /**
10
- * Opaque implementation value; only its type is public. `R` is required per
11
- * request, `EX`/`RX` describe handler acquisition at Layer build. Layer
12
- * memoization lets both adapters share one handler build per runtime.
13
- */
14
- var Implementation = class Implementation {
15
- actions;
16
- #handlers;
17
- #layer;
18
- constructor(actions, handlers, layer) {
19
- this.actions = actions;
20
- this.#handlers = handlers;
21
- this.#layer = layer;
22
- }
23
- static make(actions, build) {
24
- const handlers = Context.Service(`effect-actions/Handlers#${++implementations}`);
25
- return new Implementation(actions, handlers, Layer.effect(handlers, build));
26
- }
27
- static register(app, make) {
28
- return Layer.unwrap(Effect.map(app.#handlers, make)).pipe(Layer.provide(app.#layer));
29
- }
30
- };
31
- //#endregion
32
- export { handlerFor as n, Implementation as t };