@gjermundgaraba/effect-actions 0.11.0 → 0.13.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 (215) hide show
  1. package/README.md +102 -41
  2. package/dist/Action.d.mts +31 -117
  3. package/dist/Action.mjs +14 -53
  4. package/dist/ActionCli.d.mts +60 -28
  5. package/dist/ActionCli.mjs +54 -48
  6. package/dist/ActionHttp.d.mts +18 -67
  7. package/dist/ActionHttp.mjs +20 -70
  8. package/dist/ActionMcp.d.mts +23 -29
  9. package/dist/ActionMcp.mjs +56 -114
  10. package/dist/ActionRpc.d.mts +147 -0
  11. package/dist/ActionRpc.mjs +113 -0
  12. package/dist/ActionToolkit.d.mts +28 -9
  13. package/dist/ActionToolkit.mjs +31 -4
  14. package/dist/Authentication.d.mts +15 -15
  15. package/dist/Authentication.mjs +42 -96
  16. package/dist/Testing.d.mts +30 -23
  17. package/dist/Testing.mjs +27 -44
  18. package/dist/{internal/authentication.d.mts → authentication/provider.d.mts} +41 -9
  19. package/dist/authentication/provider.mjs +12 -0
  20. package/dist/authentication/refusal.mjs +36 -0
  21. package/dist/{internal/cli.d.mts → cli/commands.d.mts} +16 -12
  22. package/dist/cli/commands.mjs +302 -0
  23. package/dist/{internal → contract}/call.d.mts +2 -2
  24. package/dist/contract/call.mjs +10 -0
  25. package/dist/{internal → contract}/errors.d.mts +13 -10
  26. package/dist/{internal → contract}/errors.mjs +9 -14
  27. package/dist/{internal → contract}/implementation.d.mts +37 -21
  28. package/dist/{internal → contract}/implementation.mjs +18 -49
  29. package/dist/contract/media.d.mts +13 -0
  30. package/dist/contract/media.mjs +18 -0
  31. package/dist/contract/rules.d.mts +25 -0
  32. package/dist/{internal/actions.mjs → contract/rules.mjs} +6 -42
  33. package/dist/{internal → http}/client.d.mts +7 -9
  34. package/dist/http/client.mjs +17 -0
  35. package/dist/http/declared.mjs +20 -0
  36. package/dist/http/schema-errors.mjs +9 -0
  37. package/dist/mcp/protocol.d.mts +18 -0
  38. package/dist/{internal/mcp.mjs → mcp/protocol.mjs} +29 -62
  39. package/dist/stdio/console.mjs +64 -0
  40. package/dist/{internal → testing}/memory.d.mts +1 -1
  41. package/dist/{internal → testing}/memory.mjs +5 -17
  42. package/dist/toolkit/tools.mjs +29 -0
  43. package/docs/Action-failures.md +47 -0
  44. package/docs/Action-in-process.md +93 -0
  45. package/docs/Action.md +211 -426
  46. package/docs/ActionCli-failures.md +104 -0
  47. package/docs/ActionCli.md +208 -293
  48. package/docs/ActionHttp-failures.md +83 -0
  49. package/docs/ActionHttp.md +205 -282
  50. package/docs/ActionMcp-failures.md +31 -0
  51. package/docs/ActionMcp.md +222 -271
  52. package/docs/ActionRpc-failures.md +21 -0
  53. package/docs/ActionRpc.md +179 -0
  54. package/docs/ActionToolkit-failures.md +15 -0
  55. package/docs/ActionToolkit.md +103 -119
  56. package/docs/Authentication-advanced.md +139 -0
  57. package/docs/Authentication-failures.md +99 -0
  58. package/docs/Authentication.md +143 -392
  59. package/docs/CONTEXT.md +40 -24
  60. package/docs/README.md +75 -35
  61. package/docs/Testing-failures.md +21 -0
  62. package/docs/Testing.md +193 -151
  63. package/docs/guarantees.md +35 -96
  64. package/docs/setup-failures.md +37 -0
  65. package/docs/setup.md +90 -92
  66. package/examples/README.md +68 -0
  67. package/examples/assistant/README.md +92 -0
  68. package/examples/assistant/src/agent.ts +92 -0
  69. package/examples/assistant/src/cli.ts +34 -0
  70. package/examples/assistant/src/commands.ts +25 -0
  71. package/examples/assistant/src/identity/Identity.ts +33 -0
  72. package/examples/assistant/src/identity/authorize.ts +24 -0
  73. package/examples/assistant/src/identity/handlers.ts +10 -0
  74. package/examples/assistant/src/identity/members.ts +9 -0
  75. package/examples/assistant/src/identity/permissions.ts +23 -0
  76. package/examples/assistant/src/scripted.ts +23 -0
  77. package/examples/assistant/src/tasks/Tasks.ts +63 -0
  78. package/examples/assistant/src/tasks/handlers.ts +25 -0
  79. package/examples/assistant/src/tasks/store.ts +96 -0
  80. package/examples/assistant/src/toolkit.ts +30 -0
  81. package/examples/assistant/test/commands.test.ts +57 -0
  82. package/examples/assistant/test/identity/identity.test.ts +43 -0
  83. package/examples/assistant/test/tasks/store.test.ts +48 -0
  84. package/examples/assistant/test/tasks/tasks.test.ts +75 -0
  85. package/examples/assistant/test/toolkit.test.ts +121 -0
  86. package/examples/kitchen-sink/README.md +318 -0
  87. package/examples/kitchen-sink/src/Actions.ts +19 -0
  88. package/examples/kitchen-sink/src/Api.ts +24 -0
  89. package/examples/kitchen-sink/src/Rpc.ts +12 -0
  90. package/examples/kitchen-sink/src/admin/Admin.ts +59 -0
  91. package/examples/kitchen-sink/src/admin/authenticate.ts +18 -0
  92. package/examples/kitchen-sink/src/admin/authorize.ts +27 -0
  93. package/examples/kitchen-sink/src/admin/grants.ts +22 -0
  94. package/examples/kitchen-sink/src/admin/handlers.ts +21 -0
  95. package/examples/kitchen-sink/src/admin/keys.ts +8 -0
  96. package/examples/kitchen-sink/src/agent.ts +47 -0
  97. package/examples/kitchen-sink/src/assistant/Assistant.ts +82 -0
  98. package/examples/kitchen-sink/src/assistant/conversations.ts +71 -0
  99. package/examples/kitchen-sink/src/assistant/demo-model.ts +33 -0
  100. package/examples/kitchen-sink/src/assistant/handlers.ts +126 -0
  101. package/examples/kitchen-sink/src/attachments/Attachments.ts +48 -0
  102. package/examples/kitchen-sink/src/attachments/download.ts +49 -0
  103. package/examples/kitchen-sink/src/attachments/handlers.ts +38 -0
  104. package/examples/kitchen-sink/src/attachments/store.ts +58 -0
  105. package/examples/kitchen-sink/src/browser.ts +33 -0
  106. package/examples/kitchen-sink/src/cli.ts +85 -0
  107. package/examples/kitchen-sink/src/config.ts +18 -0
  108. package/examples/kitchen-sink/src/identity/Identity.ts +61 -0
  109. package/examples/kitchen-sink/src/identity/authenticate.ts +87 -0
  110. package/examples/kitchen-sink/src/identity/authorize.ts +16 -0
  111. package/examples/kitchen-sink/src/identity/directory.ts +52 -0
  112. package/examples/kitchen-sink/src/identity/handlers.ts +9 -0
  113. package/examples/kitchen-sink/src/identity/members.ts +13 -0
  114. package/examples/kitchen-sink/src/identity/quota.ts +62 -0
  115. package/examples/kitchen-sink/src/issues/Issues.ts +111 -0
  116. package/examples/kitchen-sink/src/issues/assistant.ts +23 -0
  117. package/examples/kitchen-sink/src/issues/guide.ts +37 -0
  118. package/examples/kitchen-sink/src/issues/handlers.ts +37 -0
  119. package/examples/kitchen-sink/src/issues/store.ts +96 -0
  120. package/examples/kitchen-sink/src/issues/write-limit.ts +42 -0
  121. package/examples/kitchen-sink/src/memory.ts +30 -0
  122. package/examples/kitchen-sink/src/ops.ts +42 -0
  123. package/examples/kitchen-sink/src/request-policy.ts +63 -0
  124. package/examples/kitchen-sink/src/routes.ts +120 -0
  125. package/examples/kitchen-sink/src/sdk.ts +46 -0
  126. package/examples/kitchen-sink/src/seed.ts +65 -0
  127. package/examples/kitchen-sink/src/server.ts +62 -0
  128. package/examples/kitchen-sink/src/stdio.ts +34 -0
  129. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +28 -0
  130. package/examples/kitchen-sink/src/workspaces/handlers.ts +23 -0
  131. package/examples/kitchen-sink/src/workspaces/registry.ts +46 -0
  132. package/examples/kitchen-sink/src/workspaces/tenant.ts +17 -0
  133. package/examples/kitchen-sink/test/admin/admin.test.ts +67 -0
  134. package/examples/kitchen-sink/test/assistant/assistant.test.ts +435 -0
  135. package/examples/kitchen-sink/test/attachments/attachments.test.ts +161 -0
  136. package/examples/kitchen-sink/test/identity/identity.test.ts +137 -0
  137. package/examples/kitchen-sink/test/identity/session.test.ts +110 -0
  138. package/examples/kitchen-sink/test/issues/assistant.test.ts +75 -0
  139. package/examples/kitchen-sink/test/issues/issues.test.ts +135 -0
  140. package/examples/kitchen-sink/test/request-policy.test.ts +125 -0
  141. package/examples/kitchen-sink/test/routes.test.ts +123 -0
  142. package/examples/kitchen-sink/test/support/scripted.ts +40 -0
  143. package/examples/live-rpc/README.md +107 -0
  144. package/examples/live-rpc/src/Rpc.ts +8 -0
  145. package/examples/live-rpc/src/browser.ts +98 -0
  146. package/examples/live-rpc/src/chat/Chat.ts +77 -0
  147. package/examples/live-rpc/src/chat/authorize.ts +16 -0
  148. package/examples/live-rpc/src/chat/handlers.ts +50 -0
  149. package/examples/live-rpc/src/chat/room.ts +23 -0
  150. package/examples/live-rpc/src/chat/store.ts +54 -0
  151. package/examples/live-rpc/src/client.ts +37 -0
  152. package/examples/live-rpc/src/identity/Identity.ts +19 -0
  153. package/examples/live-rpc/src/identity/members.ts +8 -0
  154. package/examples/live-rpc/src/identity/verifier.ts +23 -0
  155. package/examples/live-rpc/src/routes.ts +22 -0
  156. package/examples/live-rpc/src/server.ts +43 -0
  157. package/examples/live-rpc/test/browser.test.ts +105 -0
  158. package/examples/live-rpc/test/chat/chat.test.ts +69 -0
  159. package/examples/live-rpc/test/routes.test.ts +99 -0
  160. package/examples/live-rpc/test/server.test.ts +75 -0
  161. package/examples/mcp-tools/README.md +141 -0
  162. package/examples/mcp-tools/src/instructions.ts +12 -0
  163. package/examples/mcp-tools/src/printers/Printers.ts +121 -0
  164. package/examples/mcp-tools/src/printers/fake.ts +115 -0
  165. package/examples/mcp-tools/src/printers/farm.ts +136 -0
  166. package/examples/mcp-tools/src/printers/handlers.ts +29 -0
  167. package/examples/mcp-tools/src/routes.ts +52 -0
  168. package/examples/mcp-tools/src/server.ts +31 -0
  169. package/examples/mcp-tools/src/stdio.ts +26 -0
  170. package/examples/mcp-tools/test/printers/printers.test.ts +189 -0
  171. package/examples/mcp-tools/test/routes.test.ts +202 -0
  172. package/examples/notes-service/README.md +111 -0
  173. package/examples/notes-service/src/Api.ts +13 -0
  174. package/examples/notes-service/src/browser.ts +41 -0
  175. package/examples/notes-service/src/cli.ts +22 -0
  176. package/examples/notes-service/src/commands.ts +137 -0
  177. package/examples/notes-service/src/identity/Identity.ts +31 -0
  178. package/examples/notes-service/src/identity/authorize.ts +18 -0
  179. package/examples/notes-service/src/identity/handlers.ts +10 -0
  180. package/examples/notes-service/src/identity/users.ts +9 -0
  181. package/examples/notes-service/src/identity/verifier.ts +36 -0
  182. package/examples/notes-service/src/notes/Notes.ts +119 -0
  183. package/examples/notes-service/src/notes/handlers.ts +39 -0
  184. package/examples/notes-service/src/notes/store.ts +84 -0
  185. package/examples/notes-service/src/routes.ts +45 -0
  186. package/examples/notes-service/src/server.ts +26 -0
  187. package/examples/notes-service/src/status/Status.ts +10 -0
  188. package/examples/notes-service/src/status/handlers.ts +14 -0
  189. package/examples/notes-service/src/stdio.ts +25 -0
  190. package/examples/notes-service/test/commands.test.ts +288 -0
  191. package/examples/notes-service/test/notes/notes.test.ts +103 -0
  192. package/examples/notes-service/test/routes.test.ts +84 -0
  193. package/examples/quickstart/README.md +31 -0
  194. package/examples/quickstart/src/Api.ts +5 -0
  195. package/examples/quickstart/src/client.ts +13 -0
  196. package/examples/quickstart/src/greeting/Greeting.ts +12 -0
  197. package/examples/quickstart/src/greeting/handlers.ts +6 -0
  198. package/examples/quickstart/src/routes.ts +11 -0
  199. package/examples/quickstart/src/server.ts +16 -0
  200. package/examples/quickstart/test/greeting/greeting.test.ts +18 -0
  201. package/package.json +27 -9
  202. package/dist/internal/actions.d.mts +0 -9
  203. package/dist/internal/authentication.mjs +0 -11
  204. package/dist/internal/call.mjs +0 -12
  205. package/dist/internal/cli.mjs +0 -328
  206. package/dist/internal/client.mjs +0 -25
  207. package/dist/internal/console.mjs +0 -77
  208. package/dist/internal/declared.d.mts +0 -13
  209. package/dist/internal/declared.mjs +0 -34
  210. package/dist/internal/mcp.d.mts +0 -14
  211. package/dist/internal/media.d.mts +0 -8
  212. package/dist/internal/media.mjs +0 -26
  213. package/dist/internal/refusal.mjs +0 -67
  214. package/dist/internal/schema-errors.mjs +0 -20
  215. package/dist/internal/tools.mjs +0 -44
@@ -1,13 +1,8 @@
1
1
  import { Unauthenticated } from "./errors.mjs";
2
- import { Anyone } from "./actions.mjs";
2
+ import { Anyone, assertOnce } from "./rules.mjs";
3
3
  import { Array, Context, Effect, Layer, Option } from "effect";
4
- //#region src/internal/implementation.ts
5
- /**
6
- * A random key segment, unique to its caller even across installed copies of this module,
7
- * where a counter of each copy would repeat another's.
8
- */
4
+ //#region src/contract/implementation.ts
9
5
  const uniqueKey = () => Math.random().toString(36).slice(2);
10
- /** What `build` makes, as a layer of its own. */
11
6
  const memoized = (build) => {
12
7
  const key = Context.Service(`effect-actions/Implementation/${uniqueKey()}`);
13
8
  return {
@@ -76,12 +71,7 @@ var Implementation = class Implementation {
76
71
  return app;
77
72
  }
78
73
  };
79
- /** A surface's implementations as a list. */
80
74
  const toList = (served) => Array.ensure(served);
81
- /**
82
- * Refuse an action of `listed` that `held` lacks, by identity, as a stale list, marking one
83
- * of a held name as another contract's.
84
- */
85
75
  const assertHeld = (why, listed, held) => {
86
76
  const missing = listed.filter((action) => !held.includes(action));
87
77
  if (missing.length > 0) {
@@ -89,12 +79,6 @@ const assertHeld = (why, listed, held) => {
89
79
  throw new Error(`Listed in actions, but ${why}: ${missing.map(({ name }) => names.has(name) ? `${name} (another contract)` : name).join(", ")}`);
90
80
  }
91
81
  };
92
- /**
93
- * Each of `apps` narrowed to the actions `listed` holds, matched by identity, behind its own
94
- * authorization and sharing its builder; one holding none of them is
95
- * left out, and not built. All of `apps` as they are when nothing is listed. A listed action
96
- * none of them holds is refused, as a stale list or another contract of its name.
97
- */
98
82
  const select = (apps, listed) => {
99
83
  if (listed === void 0) return apps;
100
84
  assertHeld("no implementation holds it", listed, apps.flatMap((app) => app.actions));
@@ -104,32 +88,24 @@ const select = (apps, listed) => {
104
88
  return own.length === app.actions.length ? [app] : [Implementation.share(own, app)];
105
89
  });
106
90
  };
107
- /**
108
- * Provide `layer` the handlers of `apps`. Each implementation's layer is memoized, so its
109
- * builder runs once per layer graph however many surfaces serve it.
110
- */
91
+ const servedBy = (what, bound, apps, listed) => {
92
+ if (listed !== void 0) assertHeld("the binding does not hold it", listed, bound);
93
+ const held = apps.flatMap((app) => app.actions);
94
+ const served = select(apps, listed ?? bound.filter((action) => held.includes(action)));
95
+ const actions = served.flatMap((app) => app.actions);
96
+ if (actions.length === 0 && listed === void 0 && apps.length > 0) {
97
+ const names = new Set(bound.map(({ name }) => name));
98
+ throw new Error(`No action of these implementations is in this ${what}: ${held.map(({ name }) => names.has(name) ? `${name} (another contract)` : name).join(", ") || "none"}`);
99
+ }
100
+ assertOnce("served action", actions);
101
+ return served;
102
+ };
111
103
  const provideHandlers = (apps) => (layer) => {
112
104
  const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
113
105
  return first === void 0 ? layer : Layer.provide(layer, Layer.mergeAll(first, ...rest));
114
106
  };
115
- /**
116
- * The builders of `apps` as one layer providing nothing: built above the surfaces, it runs
117
- * each builder once for all of them, since every surface builds the same memoized layers.
118
- */
119
107
  const builders = (apps) => Layer.empty.pipe(provideHandlers(apps));
120
- /**
121
- * Every action `apps` serve with its handler, behind its authorizer, as `provideHandlers` built
122
- * them.
123
- */
124
108
  const acquire = (apps) => Effect.map(Effect.forEach(apps, (app) => Implementation.boundOf(app)), (bound) => bound.flat());
125
- /**
126
- * Every action `apps` serve with its handler, behind its authorizer, their builders built as layers
127
- * of the graph being built around the caller, into its memo map, as `HttpRouter` builds a
128
- * middleware's dependencies: a builder acquiring them shares each builder's one run with the
129
- * surfaces of its graph, whichever builds first. Outside any graph, a program under
130
- * `Effect.provide` shares that layer's, and one under none builds into a map of its own. The
131
- * caller's scope holds them; a surface serving them too keeps them until it is released.
132
- */
133
109
  const built = (apps) => {
134
110
  const [first, ...rest] = apps.map((app) => Implementation.layerOf(app));
135
111
  return Effect.gen(function* () {
@@ -141,27 +117,20 @@ const built = (apps) => {
141
117
  return yield* Effect.provideContext(acquire(apps), context);
142
118
  });
143
119
  };
144
- /**
145
- * One action's handler behind its admission: a protected action's identity, then its
146
- * `authorize`. These run outside the action's span, so what they fail with is attributed to
147
- * the surface rather than to a handler that never ran. Each call has a scope of its own, on
148
- * every surface: what `authorize` and the handler acquire is released when the call ends, the
149
- * handler's first, so no call needs a `Scope` of its caller.
150
- */
151
120
  const dispatch = (action, handle, before) => {
152
- const attributes = {
121
+ const actionAttributes = {
153
122
  "action.name": action.name,
154
123
  "action.read_only": action.readOnly
155
124
  };
156
125
  const authentication = action.caller === Anyone ? Effect.void : Effect.flatMap(Effect.serviceOption(action.caller), (actor) => Option.isSome(actor) ? Effect.void : Effect.fail(new Unauthenticated()));
157
126
  const authorization = action.caller === Anyone ? Effect.void : Effect.suspend(() => before(action));
158
127
  return (input) => {
159
- const handled = Effect.withSpan(Effect.annotateLogs(Effect.suspend(() => handle(input)), attributes), action.name, {
128
+ const handled = Effect.withSpan(Effect.annotateLogs(Effect.suspend(() => handle(input)), actionAttributes), action.name, {
160
129
  captureStackTrace: false,
161
- attributes
130
+ attributes: actionAttributes
162
131
  });
163
132
  return Effect.scoped(authentication.pipe(Effect.andThen(authorization), Effect.andThen(handled)));
164
133
  };
165
134
  };
166
135
  //#endregion
167
- export { Implementation, acquire, assertHeld, builders, built, memoized, provideHandlers, select, toList, uniqueKey };
136
+ export { Implementation, acquire, assertHeld, builders, built, memoized, provideHandlers, select, servedBy, toList };
@@ -0,0 +1,13 @@
1
+ import { Schema, SchemaAST } from "effect";
2
+ //#region src/contract/media.d.ts
3
+ /**
4
+ * An image, a media field of a success: `{ data: Uint8Array, mimeType: string }`, `mimeType` such
5
+ * as `image/png`. A top-level field of a struct success, optional or an array, or the whole
6
+ * success, one or an array: an MCP tool lifts it into an image block of its own, and every other
7
+ * surface sends it as JSON, the bytes in base64. `ActionMcp` refuses it anywhere else.
8
+ */
9
+ export declare const Image: Schema.Struct<{
10
+ readonly data: Schema.Uint8Array;
11
+ readonly mimeType: Schema.String;
12
+ }>;
13
+ //#endregion
@@ -0,0 +1,18 @@
1
+ import { Schema } from "effect";
2
+ //#region src/contract/media.ts
3
+ const mediaKindAnnotation = "effect-actions/media";
4
+ const Media = Schema.Struct({
5
+ data: Schema.Uint8Array,
6
+ mimeType: Schema.String
7
+ });
8
+ /**
9
+ * An image, a media field of a success: `{ data: Uint8Array, mimeType: string }`, `mimeType` such
10
+ * as `image/png`. A top-level field of a struct success, optional or an array, or the whole
11
+ * success, one or an array: an MCP tool lifts it into an image block of its own, and every other
12
+ * surface sends it as JSON, the bytes in base64. `ActionMcp` refuses it anywhere else.
13
+ */
14
+ const Image = Media.annotate({ [mediaKindAnnotation]: "image" });
15
+ const isMarked = (ast) => ast.annotations?.[mediaKindAnnotation] === "image";
16
+ const kindOf = (ast) => isMarked(ast) && ast.encoding === void 0 ? "image" : void 0;
17
+ //#endregion
18
+ export { Image, Media, isMarked, kindOf };
@@ -0,0 +1,25 @@
1
+ import { Action_d_exports, Errors } from "../Action.mjs";
2
+ import { SchemaAST, Types } from "effect";
3
+ //#region src/contract/rules.d.ts
4
+ type ListOf<G> = G extends Errors ? G : readonly [G];
5
+ /**
6
+ * The errors options `O` declare, as at run time: those `error` always gives, or, where it may
7
+ * be absent, those it gives or none, so clients decode each that may arrive.
8
+ */
9
+ export type ErrorsOf<O> = O extends unknown ? "error" extends keyof O ? O extends {
10
+ readonly error: infer G extends Errors | Errors[number];
11
+ } ? ListOf<G> : ListOf<Extract<O["error" & keyof O], Errors | Errors[number]>> | [] : [] : never;
12
+ type Single<T> = true extends Types.IsUnion<T> ? never : T;
13
+ /**
14
+ * The errors a binding of errors `E` surely declares: each slot of one error in a list of
15
+ * fixed length, never one of an array of unknown length, which may be empty, nor of a list
16
+ * `E` may be one of several.
17
+ */
18
+ export type Certain<E extends Errors> = true extends Types.IsUnion<E> ? never : number extends E["length"] ? never : { readonly [K in keyof E]: Single<E[K]>; }[number];
19
+ /**
20
+ * The caller of a public action, `caller: Action.Anyone`: anyone, signed in or not, and no
21
+ * authorizer runs for it. Registered globally, so two copies of the package agree on it, as they
22
+ * agree on a name.
23
+ */
24
+ export declare const Anyone: unique symbol;
25
+ //#endregion
@@ -1,37 +1,22 @@
1
1
  import { builtIns, statuses } from "./errors.mjs";
2
2
  import { Predicate, Schema, SchemaAST } from "effect";
3
- //#region src/internal/actions.ts
4
- /**
5
- * The list an `error` option stands for: one schema, a list as given, or none, as
6
- * `HttpApiEndpoint` takes it.
7
- */
3
+ //#region src/contract/rules.ts
8
4
  const errorList = (error) => error === void 0 ? [] : Schema.isSchema(error) ? [error] : error;
9
- /**
10
- * What a projection of an action declares: the built-in errors, the action's own, and any
11
- * its surface adds, such as a binding's. The built-ins come first, since a failure encodes
12
- * and decodes with the first schema that accepts it, and they are classes, which accept
13
- * only their own instances: a loose schema of the action's never captures one. A schema
14
- * listed twice is not repeated.
15
- */
16
5
  const projectedErrors = (action, surface = []) => [.../* @__PURE__ */ new Set([
17
6
  ...builtIns,
18
7
  ...action.error,
19
8
  ...surface
20
9
  ])];
21
10
  /**
22
- * The caller of a public action: anyone, signed in or not. Registered globally, so two copies
23
- * of the package agree on it, as they agree on a name.
11
+ * The caller of a public action, `caller: Action.Anyone`: anyone, signed in or not, and no
12
+ * authorizer runs for it. Registered globally, so two copies of the package agree on it, as they
13
+ * agree on a name.
24
14
  */
25
15
  const Anyone = Symbol.for("@gjermundgaraba/effect-actions/Anyone");
26
16
  const validName = /^[A-Za-z0-9_-]{1,128}$/;
27
- /** Names become path segments, OpenAPI identifiers and client method keys. */
28
17
  const assertName = (what, name) => {
29
18
  if (!validName.test(name) || name === "then") throw new Error(`Invalid ${what}: ${name}`);
30
19
  };
31
- /**
32
- * Names are checked by whoever owns them: an implementation, a binding, a CLI, or the MCP
33
- * tools. `claimantOf` says who claims a name, so a clash between two claimants names both.
34
- */
35
20
  const assertDistinct = (what, items, nameOf, claimantOf = nameOf) => {
36
21
  const seen = /* @__PURE__ */ new Map();
37
22
  for (const item of items) {
@@ -45,44 +30,23 @@ const assertDistinct = (what, items, nameOf, claimantOf = nameOf) => {
45
30
  seen.set(name, claimant);
46
31
  }
47
32
  };
48
- /** Refuse an action name `actions` hold twice. */
49
33
  const assertOnce = (what, actions) => assertDistinct(what, actions, (action) => action.name);
50
- /**
51
- * Refuse a key of options keyed by action name, such as handlers, tools or commands, that no
52
- * action of `names` has, so a stale option cannot outlive its action.
53
- */
54
34
  const assertKnown = (what, keys, names) => {
55
35
  const unknown = keys.filter((key) => !names.includes(key));
56
36
  if (unknown.length > 0) throw new Error(`Unknown ${what}: ${unknown.join(", ")}`);
57
37
  };
58
- /** `ast` with any suspension at its top resolved, as a recursive schema's is. */
59
38
  const unsuspended = (ast) => SchemaAST.isSuspend(ast) ? unsuspended(ast.thunk()) : ast;
60
- /** The members of a union, nested and suspended ones included, or the one type otherwise. */
61
39
  const members = (ast) => {
62
40
  const resolved = unsuspended(ast);
63
41
  return SchemaAST.isUnion(resolved) ? resolved.types.flatMap(members) : [resolved];
64
42
  };
65
- /**
66
- * The values a literal or an enum accepts, or `undefined` for any other type, which has no
67
- * fixed set.
68
- */
69
43
  const literalValues = (ast) => SchemaAST.isLiteral(ast) ? [ast.literal] : SchemaAST.isEnum(ast) ? ast.enums.map(([, value]) => value) : [void 0];
70
- /**
71
- * The `_tag`s a schema's encoding carries: each member's of a union, every value of a union of
72
- * literals or of an enum, and a suspended schema's, as a recursive error is written.
73
- */
74
- const tagsOf = (ast) => members(SchemaAST.toEncoded(ast)).flatMap((member) => {
44
+ const encodedTags = (ast) => members(SchemaAST.toEncoded(ast)).flatMap((member) => {
75
45
  const tag = SchemaAST.isObjects(member) ? member.propertySignatures.find((property) => property.name === "_tag")?.type : void 0;
76
46
  return tag === void 0 ? [] : members(tag).flatMap(literalValues).filter(Predicate.isString);
77
47
  });
78
- /**
79
- * Refuse an error that encodes with a built-in error's `_tag`, the built-in itself included:
80
- * every endpoint and tool declares the built-in errors already, a client decoding the answer
81
- * could not tell a look-alike from them, and a look-alike of a refusal would step up. Checked
82
- * where the action or the binding is made, so every surface and client of it may rely on it.
83
- */
84
48
  const assertOwnTags = (what, errors) => {
85
- const tag = errors.flatMap((error) => tagsOf(error.ast)).find((tag) => Object.hasOwn(statuses, tag));
49
+ const tag = errors.flatMap((error) => encodedTags(error.ast)).find((tag) => Object.hasOwn(statuses, tag));
86
50
  if (tag !== void 0) throw new Error(`${what}: error _tag "${tag}" is built in, and declared on every surface`);
87
51
  };
88
52
  //#endregion
@@ -1,19 +1,17 @@
1
- import { BuiltIns } from "./errors.mjs";
2
- import "./implementation.mjs";
3
- import { Call } from "./call.mjs";
1
+ import { BuiltIns } from "../contract/errors.mjs";
2
+ import { Call } from "../contract/call.mjs";
4
3
  import { Action_d_exports, Any, Errors } from "../Action.mjs";
5
- import { Any as Any$1, VerifierError } from "./authentication.mjs";
4
+ import { Any as Any$1, VerifierError } from "../authentication/provider.mjs";
6
5
  import { Effect, Schema } from "effect";
7
6
  import { HttpClient, HttpClientError } from "effect/http";
8
7
  import { HttpApi, HttpApiClient } from "effect/http-api";
9
- //#region src/internal/client.d.ts
8
+ //#region src/http/client.d.ts
10
9
  /**
11
10
  * The native `HttpApiClient.make` options except `transformResponse`: `baseUrl` and
12
11
  * `transformClient`. `transformResponse` may change a call's success, failure or
13
12
  * services, which the method types cannot follow.
14
13
  */
15
14
  export type Options = Omit<NonNullable<Parameters<typeof HttpApiClient.make>[1]>, "transformResponse">;
16
- /** An error schema the binding declares on every endpoint. */
17
15
  type BindingError = Errors[number];
18
16
  /**
19
17
  * What one call of `A` fails with: a declared error value (the action's own, one `E` of the
@@ -22,11 +20,11 @@ type BindingError = Errors[number];
22
20
  * the success does not decode. Input that does not encode is `InvalidInput`, and nothing is sent.
23
21
  */
24
22
  export type MethodError<A extends Any, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | HttpClientError.HttpClientError | Schema.SchemaError;
25
- /** One action as an Effect of its decoded success, failing with `MethodError`. */
26
23
  type Method<A extends Any, E extends BindingError> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E>>>;
27
24
  /**
28
- * Any HTTP binding. The API is a native constraint here because native groups are
29
- * invariant in their endpoints; `actions` carries the types.
25
+ * Any HTTP binding, with its actions erased: what `layer` and `client` read. The API is a native
26
+ * constraint here because native groups are invariant in their endpoints; `actions` carries the
27
+ * types.
30
28
  */
31
29
  export interface AnyHttp {
32
30
  readonly authentication?: Any$1 | undefined;
@@ -0,0 +1,17 @@
1
+ import { checked } from "../contract/call.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpApiClient } from "effect/http-api";
4
+ //#region src/http/client.ts
5
+ const assertInBinding = (actions, action) => {
6
+ if (!actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding`);
7
+ };
8
+ const methods = (http, options = {}) => Effect.map(HttpApiClient.make(http.api, options), (client) => {
9
+ const native = client;
10
+ return (action) => {
11
+ const method = native[action.name];
12
+ if (method === void 0) throw new Error(`No client method for ${action.name}`);
13
+ return checked(action, (payload) => method({ payload }));
14
+ };
15
+ });
16
+ //#endregion
17
+ export { assertInBinding, methods };
@@ -0,0 +1,20 @@
1
+ import { unsuspended } from "../contract/rules.mjs";
2
+ import { Schema, SchemaAST } from "effect";
3
+ import { HttpServerResponse } from "effect/http";
4
+ import { HttpApiSchema } from "effect/http-api";
5
+ //#region src/http/declared.ts
6
+ const httpApiStatusOf = SchemaAST.resolveAt("httpApiStatus");
7
+ const statusThroughSuspensions = (ast) => httpApiStatusOf(ast) ?? (SchemaAST.isSuspend(ast) ? statusThroughSuspensions(ast.thunk()) : void 0);
8
+ const declared = (error) => {
9
+ const status = statusThroughSuspensions(error.ast);
10
+ if (status !== void 0) return [httpApiStatusOf(error.ast) === void 0 ? HttpApiSchema.status(status)(error) : error];
11
+ const resolved = unsuspended(error.ast);
12
+ if (SchemaAST.isUnion(resolved) && resolved.checks === void 0 && resolved.encoding === void 0) return resolved.types.flatMap((member) => declared(Schema.make(member)));
13
+ return [HttpApiSchema.status(422)(error)];
14
+ };
15
+ const declaredResponse = (errors, error) => {
16
+ const schema = errors.flatMap(declared).find((each) => Schema.is(each)(error));
17
+ return schema === void 0 ? void 0 : HttpServerResponse.jsonUnsafe(Schema.encodeUnknownSync(Schema.toCodecJson(schema))(error), { status: httpApiStatusOf(schema.ast) ?? 422 });
18
+ };
19
+ //#endregion
20
+ export { declared, declaredResponse };
@@ -0,0 +1,9 @@
1
+ import { InvalidInput } from "../contract/errors.mjs";
2
+ import { Effect } from "effect";
3
+ import { HttpApiMiddleware } from "effect/http-api";
4
+ //#region src/http/schema-errors.ts
5
+ const responseEncodingKinds = /* @__PURE__ */ new Set(["Body", "ResponseHeaders"]);
6
+ var SchemaErrors = class extends HttpApiMiddleware.Service()("effect-actions/http/SchemaErrors", { error: [InvalidInput] }) {};
7
+ const schemaErrors = () => HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrors, (failure) => Effect.fail(responseEncodingKinds.has(failure.kind) ? failure : InvalidInput.fromSchemaError(failure.cause)));
8
+ //#endregion
9
+ export { SchemaErrors, schemaErrors };
@@ -0,0 +1,18 @@
1
+ import "../contract/implementation.mjs";
2
+ import "../contract/media.mjs";
3
+ import "../Action.mjs";
4
+ import { Schema } from "effect";
5
+ import { McpProtocol } from "effect/ai";
6
+ //#region src/mcp/protocol.d.ts
7
+ /**
8
+ * An MCP request's parameters, as `Testing.mcpRequest` takes them: JSON, with any `_meta`, leaving
9
+ * out an `undefined` one, as a caller may. A helper passing on whole params types them as this,
10
+ * and a tool's `arguments` as `Schema.Json`, never `unknown`.
11
+ */
12
+ export interface Params {
13
+ readonly _meta?: {
14
+ readonly [key: string]: Schema.Json;
15
+ };
16
+ readonly [key: string]: Schema.Json | undefined;
17
+ }
18
+ //#endregion
@@ -1,19 +1,16 @@
1
- import { Media, isMarked, kindOf } from "./media.mjs";
1
+ import { unsuspended } from "../contract/rules.mjs";
2
+ import { Media, isMarked, kindOf } from "../contract/media.mjs";
2
3
  import { Predicate, Schema, SchemaAST } from "effect";
3
4
  import { McpProtocol } from "effect/ai";
4
- //#region src/internal/mcp.ts
5
- /**
6
- * The one protocol revision served over HTTP. 2026-07-28 is stateless: every request
7
- * stands alone. The stateful revisions keep a session per `initialize`, which the native
8
- * HTTP runtime never expires and which no identity owns. A mixed endpoint's gate relies on it
9
- * too: it decides from the routing headers, which the native runtime checks against the body
10
- * of a stateless request alone, so serving a stateful revision over HTTP would open the gate.
11
- */
5
+ //#region src/mcp/protocol.ts
12
6
  const httpProtocol = McpProtocol.v2026_07_28;
13
- /** Where `ActionMcp.layerHttp` serves, and `Testing.mcpClient` calls, by default. */
7
+ const protocols = [
8
+ McpProtocol.v2026_07_28,
9
+ McpProtocol.v2025_11_25,
10
+ McpProtocol.v2025_06_18
11
+ ];
14
12
  const defaultPath = "/mcp";
15
- /** The parameter a method's `mcp-name` header repeats, as the native runtime routes it. */
16
- const routed = (method, params) => method === "resources/read" ? params.uri : params.name;
13
+ const mcpNameHeaderOf = (method, params) => method === "resources/read" ? params.uri : params.name;
17
14
  /**
18
15
  * One stateless MCP request of `method` with `params`, as `ActionMcp.layerHttp` serves it:
19
16
  * the headers routing it, which repeat what its body says, and its JSON-RPC body. The client
@@ -22,7 +19,7 @@ const routed = (method, params) => method === "resources/read" ? params.uri : pa
22
19
  */
23
20
  const statelessRequest = (method, params) => {
24
21
  const { protocolVersion } = httpProtocol;
25
- const name = routed(method, params);
22
+ const name = mcpNameHeaderOf(method, params);
26
23
  return {
27
24
  headers: {
28
25
  accept: "application/json, text/event-stream",
@@ -49,60 +46,41 @@ const statelessRequest = (method, params) => {
49
46
  }
50
47
  };
51
48
  };
52
- /** The `_meta` key of a lifted block naming its field. */
53
49
  const fieldKey = "effect-actions/field";
54
50
  const isMedia = Schema.is(Media);
55
51
  const isMediaArray = Schema.is(Schema.Array(Media));
56
- /** The schema `ast` stands for, a `Schema.suspend` resolved, which only delays its definition. */
57
- const resolved = (ast) => SchemaAST.isSuspend(ast) ? resolved(ast.thunk()) : ast;
58
- /** The kind `ast` is, suspended or not. */
59
- const kindIn = (ast) => kindOf(resolved(ast));
60
- /**
61
- * The media `ast` is, one value or an array of them, as a whole success may be: an array without
62
- * an encoding of its own whose every element is media of one kind, such as `Schema.Array` or
63
- * `Schema.NonEmptyArray` of it.
64
- */
65
- const wholeOf = (suspended) => {
66
- const ast = resolved(suspended);
52
+ const kindOfUnsuspended = (ast) => kindOf(unsuspended(ast));
53
+ const wholeSuccessMediaOf = (suspended) => {
54
+ const ast = unsuspended(suspended);
67
55
  const one = kindOf(ast);
68
56
  if (one !== void 0) return {
69
57
  kind: one,
70
58
  many: false
71
59
  };
72
60
  if (!SchemaAST.isArrays(ast) || ast.encoding !== void 0) return void 0;
73
- const [kind, ...others] = new Set([...ast.elements, ...ast.rest].map(kindIn));
61
+ const [kind, ...others] = new Set([...ast.elements, ...ast.rest].map(kindOfUnsuspended));
74
62
  return kind === void 0 || others.length > 0 ? void 0 : {
75
63
  kind,
76
64
  many: true
77
65
  };
78
66
  };
79
- /**
80
- * The media a field's `ast` is: one value, which `Schema.optional` or `Schema.optionalKey` may
81
- * make absent, or an array, which they may not, as no block would tell an absent one from an
82
- * empty one.
83
- */
84
- const fieldOf = (field) => {
67
+ const mediaFieldOf = (field) => {
85
68
  const optional = field.context?.isOptional === true;
86
- const ast = resolved(field);
69
+ const ast = unsuspended(field);
87
70
  if (optional && SchemaAST.isUnion(ast) && ast.types.length === 2) {
88
- const [value, ...more] = ast.types.filter((member) => !SchemaAST.isUndefined(member));
89
- const kind = value === void 0 || more.length > 0 ? void 0 : kindIn(value);
71
+ const [definedMember, ...otherDefinedMembers] = ast.types.filter((member) => !SchemaAST.isUndefined(member));
72
+ const kind = definedMember === void 0 || otherDefinedMembers.length > 0 ? void 0 : kindOfUnsuspended(definedMember);
90
73
  return kind === void 0 ? void 0 : {
91
74
  kind,
92
75
  many: false
93
76
  };
94
77
  }
95
- const whole = wholeOf(ast);
78
+ const whole = wholeSuccessMediaOf(ast);
96
79
  return whole === void 0 || optional && whole.many ? void 0 : whole;
97
80
  };
98
- /**
99
- * How an MCP tool sends `success`: it lifts the success itself, one value or an array, or the
100
- * top-level media fields of a struct without an encoding of its own, keyed by strings, and
101
- * sends the rest of the struct, if any is left.
102
- */
103
81
  const liftOf = (success) => {
104
- const ast = resolved(success.ast);
105
- const whole = wholeOf(ast);
82
+ const ast = unsuspended(success.ast);
83
+ const whole = wholeSuccessMediaOf(ast);
106
84
  if (whole !== void 0) return {
107
85
  fields: [{
108
86
  name: void 0,
@@ -115,7 +93,7 @@ const liftOf = (success) => {
115
93
  rest: success
116
94
  };
117
95
  const fields = ast.propertySignatures.flatMap(({ name, type }) => {
118
- const field = fieldOf(type);
96
+ const field = mediaFieldOf(type);
119
97
  return field === void 0 ? [] : [{
120
98
  name: String(name),
121
99
  ...field
@@ -127,14 +105,13 @@ const liftOf = (success) => {
127
105
  };
128
106
  const names = new Set(fields.map(({ name }) => name));
129
107
  const rest = ast.propertySignatures.filter(({ name }) => !names.has(name));
130
- const { identifier: _identifier, ...annotations } = ast.annotations ?? {};
108
+ const { identifier: _identifierOfWholeSuccess, ...annotationsOfRest } = ast.annotations ?? {};
131
109
  return {
132
110
  fields,
133
- rest: rest.length === 0 && ast.indexSignatures.length === 0 ? void 0 : Schema.make(new SchemaAST.Objects(rest, ast.indexSignatures, annotations))
111
+ rest: rest.length === 0 && ast.indexSignatures.length === 0 ? void 0 : Schema.make(new SchemaAST.Objects(rest, ast.indexSignatures, annotationsOfRest))
134
112
  };
135
113
  };
136
- /** The schemas `ast` is made of, its encoded side included. */
137
- const partsOf = (ast) => [
114
+ const partsAndEncodedSideOf = (ast) => [
138
115
  ...SchemaAST.isDeclaration(ast) ? ast.typeParameters : [],
139
116
  ...SchemaAST.isArrays(ast) ? [...ast.elements, ...ast.rest] : [],
140
117
  ...SchemaAST.isObjects(ast) ? [...ast.propertySignatures, ...ast.indexSignatures].map(({ type }) => type) : [],
@@ -142,34 +119,24 @@ const partsOf = (ast) => [
142
119
  ...SchemaAST.isSuspend(ast) ? [ast.thunk()] : [],
143
120
  ...(ast.encoding ?? []).map(({ to }) => to)
144
121
  ];
145
- /** Whether `ast` holds media anywhere, each schema visited once, so a recursive one ends. */
146
- const holdsMedia = (ast) => {
122
+ const holdsMediaAnywhere = (ast) => {
147
123
  const seen = /* @__PURE__ */ new Set();
148
124
  const pending = [ast];
149
125
  for (let next = pending.pop(); next !== void 0; next = pending.pop()) {
150
126
  if (seen.has(next)) continue;
151
127
  seen.add(next);
152
128
  if (isMarked(next)) return true;
153
- pending.push(...partsOf(next));
129
+ pending.push(...partsAndEncodedSideOf(next));
154
130
  }
155
131
  return false;
156
132
  };
157
- /**
158
- * Whether `action`, whose success an MCP tool lifts as `lift` says, holds media where no tool
159
- * lifts it: in its input, in a declared error, or in the rest of its success, such as nested in
160
- * a field or in a union.
161
- */
162
133
  const isMisplaced = (action, { rest }) => {
163
134
  return [
164
135
  action.input,
165
136
  ...action.error,
166
137
  ...rest === void 0 ? [] : [rest]
167
- ].some(({ ast }) => holdsMedia(ast));
138
+ ].some(({ ast }) => holdsMediaAnywhere(ast));
168
139
  };
169
- /**
170
- * The media `lift` finds in `success`, a decoded value its schema has validated: the whole
171
- * success's, or each field's, in field order, an array's in its order, none for an absent field.
172
- */
173
140
  const liftedFrom = (lift, success) => lift.fields.flatMap(({ name, kind, many }) => {
174
141
  const value = name === void 0 ? success : Predicate.hasProperty(success, name) ? success[name] : void 0;
175
142
  return (many ? isMediaArray(value) ? value : [] : isMedia(value) ? [value] : []).map((media) => ({
@@ -179,4 +146,4 @@ const liftedFrom = (lift, success) => lift.fields.flatMap(({ name, kind, many })
179
146
  }));
180
147
  });
181
148
  //#endregion
182
- export { defaultPath, fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, statelessRequest };
149
+ export { defaultPath, fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols, statelessRequest };
@@ -0,0 +1,64 @@
1
+ import { Clock, Console, Effect, Function, Predicate } from "effect";
2
+ //#region src/stdio/console.ts
3
+ const stderrConsole = (console, clock) => {
4
+ const counts = /* @__PURE__ */ new Map();
5
+ const timers = /* @__PURE__ */ new Map();
6
+ const write = (...args) => console.error(...args);
7
+ const elapsed = (label, data) => {
8
+ const start = timers.get(label);
9
+ if (start !== void 0) {
10
+ const millis = Number(clock.monotonicTimeNanosUnsafe() - start) / 1e6;
11
+ write("%s: %sms", label, Number(millis.toFixed(3)), ...data);
12
+ }
13
+ };
14
+ const writeLabelKeepingFormat = (label, args) => {
15
+ const [formatOrFirst, ...rest] = args;
16
+ write(...Predicate.isString(formatOrFirst) ? [`${label}: ${formatOrFirst}`, ...rest] : [label, ...args]);
17
+ };
18
+ const group = (...labelOrUndefined) => {
19
+ if (labelOrUndefined.some((part) => part !== void 0)) write(...labelOrUndefined);
20
+ };
21
+ return {
22
+ assert: (condition, ...args) => {
23
+ if (!condition) writeLabelKeepingFormat("Assertion failed", args);
24
+ },
25
+ clear: Function.constVoid,
26
+ count: (label = "default") => {
27
+ const count = (counts.get(label) ?? 0) + 1;
28
+ counts.set(label, count);
29
+ write("%s: %d", label, count);
30
+ },
31
+ countReset: (label = "default") => {
32
+ counts.delete(label);
33
+ },
34
+ debug: write,
35
+ dir: (item) => write("%O", item),
36
+ dirxml: write,
37
+ error: write,
38
+ group,
39
+ groupCollapsed: group,
40
+ groupEnd: Function.constVoid,
41
+ info: write,
42
+ log: write,
43
+ table: (data) => write(data),
44
+ time: (label = "default") => {
45
+ if (!timers.has(label)) timers.set(label, clock.monotonicTimeNanosUnsafe());
46
+ },
47
+ timeEnd: (label = "default") => {
48
+ elapsed(label, []);
49
+ timers.delete(label);
50
+ },
51
+ timeLog: (label = "default", ...data) => elapsed(label, data),
52
+ trace: (...args) => {
53
+ const { stack = "" } = /* @__PURE__ */ new Error();
54
+ const [_header, _traceFrame, ...callerFrames] = stack.split("\n");
55
+ const callerStack = callerFrames.join("\n");
56
+ writeLabelKeepingFormat("Trace", args);
57
+ if (callerStack !== "") write(callerStack);
58
+ },
59
+ warn: write
60
+ };
61
+ };
62
+ const withStderrConsole = (effect) => Effect.provideServiceEffect(effect, Console.Console, Effect.zipWith(Effect.service(Console.Console), Effect.service(Clock.Clock), stderrConsole));
63
+ //#endregion
64
+ export { withStderrConsole };
@@ -1,6 +1,6 @@
1
1
  import { Layer } from "effect";
2
2
  import { HttpClient, HttpRouter, HttpServer } from "effect/http";
3
- //#region src/internal/memory.d.ts
3
+ //#region src/testing/memory.d.ts
4
4
  /** What in-memory routes may leave to the host: the router, the platform, nothing per request. */
5
5
  export type Served = HttpRouter.HttpRouter | HttpRouter.Request<"Requires", never> | HttpRouter.Request<"GlobalRequires", never> | HttpRouter.Request<"Error", any> | HttpRouter.Request<"GlobalError", any> | Layer.Success<typeof HttpServer.layerServices>;
6
6
  //#endregion