@gjermundgaraba/effect-actions 0.10.0 → 0.12.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 (214) hide show
  1. package/README.md +103 -43
  2. package/dist/Action.d.mts +31 -118
  3. package/dist/Action.mjs +15 -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 +25 -31
  9. package/dist/ActionMcp.mjs +140 -74
  10. package/dist/ActionRpc.d.mts +147 -0
  11. package/dist/ActionRpc.mjs +113 -0
  12. package/dist/ActionToolkit.d.mts +5 -8
  13. package/dist/ActionToolkit.mjs +3 -3
  14. package/dist/Authentication.d.mts +15 -15
  15. package/dist/Authentication.mjs +42 -96
  16. package/dist/Testing.d.mts +32 -25
  17. package/dist/Testing.mjs +45 -30
  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 +17 -0
  38. package/dist/mcp/protocol.mjs +149 -0
  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 +206 -424
  46. package/docs/ActionCli-failures.md +102 -0
  47. package/docs/ActionCli.md +206 -292
  48. package/docs/ActionHttp-failures.md +83 -0
  49. package/docs/ActionHttp.md +202 -281
  50. package/docs/ActionMcp-failures.md +31 -0
  51. package/docs/ActionMcp.md +229 -193
  52. package/docs/ActionRpc-failures.md +21 -0
  53. package/docs/ActionRpc.md +178 -0
  54. package/docs/ActionToolkit-failures.md +15 -0
  55. package/docs/ActionToolkit.md +100 -117
  56. package/docs/Authentication-advanced.md +139 -0
  57. package/docs/Authentication-failures.md +99 -0
  58. package/docs/Authentication.md +142 -392
  59. package/docs/CONTEXT.md +39 -23
  60. package/docs/README.md +71 -35
  61. package/docs/Testing-failures.md +20 -0
  62. package/docs/Testing.md +186 -151
  63. package/docs/guarantees.md +35 -95
  64. package/docs/setup-failures.md +34 -0
  65. package/docs/setup.md +88 -87
  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 +317 -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 +146 -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 +109 -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 +41 -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 +75 -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 +136 -0
  162. package/examples/mcp-tools/src/instructions.ts +12 -0
  163. package/examples/mcp-tools/src/printers/Printers.ts +119 -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 +30 -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 +108 -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 +89 -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 +91 -0
  183. package/examples/notes-service/src/notes/handlers.ts +33 -0
  184. package/examples/notes-service/src/notes/store.ts +73 -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 +134 -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 +32 -14
  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 -11
  211. package/dist/internal/mcp.mjs +0 -52
  212. package/dist/internal/refusal.mjs +0 -67
  213. package/dist/internal/schema-errors.mjs +0 -20
  214. package/dist/internal/tools.mjs +0 -44
@@ -1,25 +1,28 @@
1
- import { Anyone } from "./actions.mjs";
1
+ import { Anyone } from "./rules.mjs";
2
2
  import { Refusal } from "./errors.mjs";
3
3
  import { Action_d_exports, Any } from "../Action.mjs";
4
4
  import { Context, Effect, Layer, Scope } from "effect";
5
- //#region src/internal/implementation.d.ts
5
+ //#region src/contract/implementation.d.ts
6
6
  /** Decoded values at the dispatch boundary every surface shares. */
7
7
  export type ErasedValue = Any["input"]["Type"];
8
+ /**
9
+ * A handler, erased. Method extraction makes the argument bivariant: a surface selects the
10
+ * action before it invokes a handler, so its decoded input is the matching handler input, and
11
+ * handlers are not widened at their public API.
12
+ */
8
13
  export type ErasedHandler<R> = {
9
14
  handle(input: ErasedValue): Effect.Effect<ErasedValue, ErasedValue, R>;
10
15
  }["handle"];
11
- /** Authorization runs only for protected actions, after authentication and before the handler. */
16
+ /**
17
+ * Authorization for protected contracts: it refuses, and fails with nothing else. It runs only for
18
+ * protected actions, after authentication and before the handler.
19
+ */
12
20
  export type Authorize<A extends Any, R = never> = (action: A) => Effect.Effect<void, Refusal, R>;
13
21
  /** An implementation's authorizer, erased. */
14
22
  export type ErasedAuthorize = (action: Any) => Effect.Effect<void, unknown, unknown>;
15
23
  /** Each action of an implementation with its handler, as its builder made them. */
16
24
  export type Bound = ReadonlyArray<readonly [Any, ErasedHandler<unknown>]>;
17
- /**
18
- * What a layer builds once per layer graph, under a key private to it: an implementation's
19
- * handlers, or its authorizer. Effect memoizes a layer by reference within one layer graph, so
20
- * everything in the graph holding this one shares one run.
21
- */
22
- export interface Memoized<S, E, R> {
25
+ interface Memoized<S, E, R> {
23
26
  readonly key: Context.Key<S, S>;
24
27
  readonly layer: Layer.Layer<S, E, R>;
25
28
  }
@@ -81,7 +84,13 @@ export declare class Implementation<A extends Any, RequestServices extends {
81
84
  /** `app`, if this copy of the module made it; another installed copy's cannot be read. */
82
85
  private static own;
83
86
  }
84
- /** Any implementation, with its actions and channels erased. */
87
+ /**
88
+ * Any implementation, with its actions and channels erased: the constraint of a helper generic over
89
+ * implementations, `<App extends Action.AnyImplementation>`, whatever surface it passes them to;
90
+ * `AnyImplementation<A>` is one of the actions `A`. Each parameter is covariant, so `unknown`
91
+ * admits every implementation, while a value of this type owes `unknown`, which no surface can be
92
+ * given.
93
+ */
85
94
  export type AnyImplementation<A extends Any = Any> = Implementation<A, {
86
95
  readonly [name: string]: unknown;
87
96
  }, unknown, unknown, unknown, unknown, unknown>;
@@ -101,6 +110,16 @@ export interface Selection<A extends Any> {
101
110
  export type Selected<O, All extends Any> = O extends {
102
111
  readonly actions: ReadonlyArray<infer A extends Any>;
103
112
  } ? A : All;
113
+ /**
114
+ * The middleware options `O` install, each a member of `L`: its `middleware` when always given,
115
+ * an array of unknown length when it may be absent, which installs none or all of them, and
116
+ * none otherwise.
117
+ */
118
+ export type MiddlewareOf<O, L extends ReadonlyArray<unknown>> = O extends {
119
+ readonly middleware: infer M extends L;
120
+ } ? M : O extends {
121
+ readonly middleware?: infer M extends L | undefined;
122
+ } ? "middleware" extends keyof O ? ReadonlyArray<NonNullable<M>[number]> : [] : [];
104
123
  /** The actions options `O` select of the implementations `Apps` stand for. */
105
124
  export type SelectedOf<O, Apps extends Served> = Selected<O, ActionOf<Member<Apps>>>;
106
125
  /**
@@ -109,7 +128,6 @@ export type SelectedOf<O, Apps extends Served> = Selected<O, ActionOf<Member<App
109
128
  * given or not; every one where no member lists any.
110
129
  */
111
130
  export type Offered<O, All extends Any> = [Listing<O>] extends [never] ? All : Extract<All, Listing<O> extends ReadonlyArray<infer A extends Any> ? A : never>;
112
- /** The lists of actions the members of `O` may give; none where no member gives one. */
113
131
  type Listing<O> = O extends unknown ? NonNullable<O["actions" & keyof O]> : never;
114
132
  /**
115
133
  * No key of any member of `O` beyond those of `Keys`, so a misspelled option is refused
@@ -127,7 +145,6 @@ export type OptionalUnless<O, Given> = {} extends O ? [options?: Given] : [optio
127
145
  export type ActionOf<App> = App extends {
128
146
  readonly actions: ReadonlyArray<infer A>;
129
147
  } ? A : never;
130
- /** Whether `A` may be one of `Listed`: either is assignable to the other. */
131
148
  type Matches<A extends Any, Listed extends Any> = Listed extends Any ? [A] extends [Listed] ? true : [Listed] extends [A] ? true : never : never;
132
149
  /**
133
150
  * The actions of `App` a surface given the actions `Listed` may serve, as `select` selects
@@ -147,12 +164,7 @@ export type Holding<App, Listed extends Any> = App extends unknown ? [Serving<Ap
147
164
  /** The protected members of a contract union. */
148
165
  export type Protected<A extends Any> = A extends unknown ? A["caller"] extends typeof Anyone ? never : A : never;
149
166
  type ServiceOf<K> = K extends Context.Key<infer I, unknown> ? I : never;
150
- /**
151
- * The identity required by the contract, even when its handler never reads it. A `caller`
152
- * narrowed to `Anyone`, such as `Action.Any & { caller: typeof Anyone }`'s, which TypeScript
153
- * keeps as `typeof Anyone | (Key & typeof Anyone)`, requires none.
154
- */
155
- export type AuthenticationOf<A extends Any> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
167
+ type AuthenticationOf<A extends Any> = A["caller"] extends typeof Anyone ? never : ServiceOf<A["caller"]>;
156
168
  /**
157
169
  * Per-request requirements of `App`'s authorization and handler for each `A` it
158
170
  * implements: those of every name `A`'s may be, so a union or an erased name owes each it may
@@ -163,14 +175,18 @@ export type RequestOf<App, A extends Any> = App extends {
163
175
  readonly "~authorizeRequest": infer RA;
164
176
  } ? A extends Any ? [A["name"] & keyof R] extends [never] ? never : R[A["name"] & keyof R] | (A["caller"] extends typeof Anyone ? never : RA) | AuthenticationOf<A> : never : never;
165
177
  type OwnActions<App> = Extract<ActionOf<App>, Any>;
166
- /** Builder failures include authorization only if selected protected actions need it. */
178
+ /**
179
+ * What a surface serving the actions `Listed` of the implementations `App` fails with at startup:
180
+ * their builders' and built authorizers' failures, an authorizer's only if a selected protected
181
+ * action needs it.
182
+ */
167
183
  export type BuildError<App, Listed extends Any = OwnActions<App>> = App extends {
168
184
  readonly "~buildError": infer EX;
169
185
  readonly "~authorizeBuildError": infer EAX;
170
186
  } ? EX | ([Protected<Serving<App, Listed>>] extends [never] ? never : EAX) : never;
171
187
  /**
172
- * What a surface serving the actions `Listed` reads at startup: the services the builders of
173
- * their handlers and authorization read.
188
+ * What a surface serving the actions `Listed` of the implementations `App` reads at startup: their
189
+ * builders' and built authorizers' services.
174
190
  */
175
191
  export type BuildServices<App, Listed extends Any = OwnActions<App>> = App extends {
176
192
  readonly "~buildServices": infer RX;
@@ -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,17 @@
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.
10
+ */
11
+ export interface Params {
12
+ readonly _meta?: {
13
+ readonly [key: string]: Schema.Json;
14
+ };
15
+ readonly [key: string]: Schema.Json | undefined;
16
+ }
17
+ //#endregion
@@ -0,0 +1,149 @@
1
+ import { unsuspended } from "../contract/rules.mjs";
2
+ import { Media, isMarked, kindOf } from "../contract/media.mjs";
3
+ import { Predicate, Schema, SchemaAST } from "effect";
4
+ import { McpProtocol } from "effect/ai";
5
+ //#region src/mcp/protocol.ts
6
+ const httpProtocol = McpProtocol.v2026_07_28;
7
+ const protocols = [
8
+ McpProtocol.v2026_07_28,
9
+ McpProtocol.v2025_11_25,
10
+ McpProtocol.v2025_06_18
11
+ ];
12
+ const defaultPath = "/mcp";
13
+ const mcpNameHeaderOf = (method, params) => method === "resources/read" ? params.uri : params.name;
14
+ /**
15
+ * One stateless MCP request of `method` with `params`, as `ActionMcp.layerHttp` serves it:
16
+ * the headers routing it, which repeat what its body says, and its JSON-RPC body. The client
17
+ * metadata goes in `_meta`, under any given, such as a `progressToken`; the protocol version
18
+ * is always the request's own.
19
+ */
20
+ const statelessRequest = (method, params) => {
21
+ const { protocolVersion } = httpProtocol;
22
+ const name = mcpNameHeaderOf(method, params);
23
+ return {
24
+ headers: {
25
+ accept: "application/json, text/event-stream",
26
+ "mcp-protocol-version": protocolVersion,
27
+ "mcp-method": method,
28
+ ...Predicate.isString(name) ? { "mcp-name": name } : {}
29
+ },
30
+ body: {
31
+ jsonrpc: "2.0",
32
+ id: 1,
33
+ method,
34
+ params: {
35
+ ...params,
36
+ _meta: {
37
+ "io.modelcontextprotocol/clientCapabilities": {},
38
+ "io.modelcontextprotocol/clientInfo": {
39
+ name: "effect-actions",
40
+ version: "0"
41
+ },
42
+ ...params._meta,
43
+ "io.modelcontextprotocol/protocolVersion": protocolVersion
44
+ }
45
+ }
46
+ }
47
+ };
48
+ };
49
+ const fieldKey = "effect-actions/field";
50
+ const isMedia = Schema.is(Media);
51
+ const isMediaArray = Schema.is(Schema.Array(Media));
52
+ const kindOfUnsuspended = (ast) => kindOf(unsuspended(ast));
53
+ const wholeSuccessMediaOf = (suspended) => {
54
+ const ast = unsuspended(suspended);
55
+ const one = kindOf(ast);
56
+ if (one !== void 0) return {
57
+ kind: one,
58
+ many: false
59
+ };
60
+ if (!SchemaAST.isArrays(ast) || ast.encoding !== void 0) return void 0;
61
+ const [kind, ...others] = new Set([...ast.elements, ...ast.rest].map(kindOfUnsuspended));
62
+ return kind === void 0 || others.length > 0 ? void 0 : {
63
+ kind,
64
+ many: true
65
+ };
66
+ };
67
+ const mediaFieldOf = (field) => {
68
+ const optional = field.context?.isOptional === true;
69
+ const ast = unsuspended(field);
70
+ if (optional && SchemaAST.isUnion(ast) && ast.types.length === 2) {
71
+ const [definedMember, ...otherDefinedMembers] = ast.types.filter((member) => !SchemaAST.isUndefined(member));
72
+ const kind = definedMember === void 0 || otherDefinedMembers.length > 0 ? void 0 : kindOfUnsuspended(definedMember);
73
+ return kind === void 0 ? void 0 : {
74
+ kind,
75
+ many: false
76
+ };
77
+ }
78
+ const whole = wholeSuccessMediaOf(ast);
79
+ return whole === void 0 || optional && whole.many ? void 0 : whole;
80
+ };
81
+ const liftOf = (success) => {
82
+ const ast = unsuspended(success.ast);
83
+ const whole = wholeSuccessMediaOf(ast);
84
+ if (whole !== void 0) return {
85
+ fields: [{
86
+ name: void 0,
87
+ ...whole
88
+ }],
89
+ rest: void 0
90
+ };
91
+ if (!SchemaAST.isObjects(ast) || ast.encoding !== void 0) return {
92
+ fields: [],
93
+ rest: success
94
+ };
95
+ const fields = ast.propertySignatures.flatMap(({ name, type }) => {
96
+ const field = mediaFieldOf(type);
97
+ return field === void 0 ? [] : [{
98
+ name: String(name),
99
+ ...field
100
+ }];
101
+ });
102
+ if (fields.length === 0) return {
103
+ fields,
104
+ rest: success
105
+ };
106
+ const names = new Set(fields.map(({ name }) => name));
107
+ const rest = ast.propertySignatures.filter(({ name }) => !names.has(name));
108
+ const { identifier: _identifierOfWholeSuccess, ...annotationsOfRest } = ast.annotations ?? {};
109
+ return {
110
+ fields,
111
+ rest: rest.length === 0 && ast.indexSignatures.length === 0 ? void 0 : Schema.make(new SchemaAST.Objects(rest, ast.indexSignatures, annotationsOfRest))
112
+ };
113
+ };
114
+ const partsAndEncodedSideOf = (ast) => [
115
+ ...SchemaAST.isDeclaration(ast) ? ast.typeParameters : [],
116
+ ...SchemaAST.isArrays(ast) ? [...ast.elements, ...ast.rest] : [],
117
+ ...SchemaAST.isObjects(ast) ? [...ast.propertySignatures, ...ast.indexSignatures].map(({ type }) => type) : [],
118
+ ...SchemaAST.isUnion(ast) ? ast.types : [],
119
+ ...SchemaAST.isSuspend(ast) ? [ast.thunk()] : [],
120
+ ...(ast.encoding ?? []).map(({ to }) => to)
121
+ ];
122
+ const holdsMediaAnywhere = (ast) => {
123
+ const seen = /* @__PURE__ */ new Set();
124
+ const pending = [ast];
125
+ for (let next = pending.pop(); next !== void 0; next = pending.pop()) {
126
+ if (seen.has(next)) continue;
127
+ seen.add(next);
128
+ if (isMarked(next)) return true;
129
+ pending.push(...partsAndEncodedSideOf(next));
130
+ }
131
+ return false;
132
+ };
133
+ const isMisplaced = (action, { rest }) => {
134
+ return [
135
+ action.input,
136
+ ...action.error,
137
+ ...rest === void 0 ? [] : [rest]
138
+ ].some(({ ast }) => holdsMediaAnywhere(ast));
139
+ };
140
+ const liftedFrom = (lift, success) => lift.fields.flatMap(({ name, kind, many }) => {
141
+ const value = name === void 0 ? success : Predicate.hasProperty(success, name) ? success[name] : void 0;
142
+ return (many ? isMediaArray(value) ? value : [] : isMedia(value) ? [value] : []).map((media) => ({
143
+ kind,
144
+ field: name,
145
+ value: media
146
+ }));
147
+ });
148
+ //#endregion
149
+ export { defaultPath, fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols, statelessRequest };