@gjermundgaraba/effect-actions 0.18.2 → 0.19.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 (144) hide show
  1. package/README.md +20 -47
  2. package/dist/Action.d.mts +96 -47
  3. package/dist/Action.mjs +11 -14
  4. package/dist/ActionCli.d.mts +81 -42
  5. package/dist/ActionCli.mjs +52 -33
  6. package/dist/ActionHttp.d.mts +84 -43
  7. package/dist/ActionHttp.mjs +2 -2
  8. package/dist/ActionMcp.d.mts +54 -27
  9. package/dist/ActionMcp.mjs +5 -5
  10. package/dist/ActionRpc.d.mts +48 -35
  11. package/dist/ActionRpc.mjs +1 -1
  12. package/dist/ActionToolkit.d.mts +32 -13
  13. package/dist/ActionToolkit.mjs +10 -7
  14. package/dist/Authentication.d.mts +92 -40
  15. package/dist/Authentication.mjs +37 -35
  16. package/dist/Testing.d.mts +22 -24
  17. package/dist/authentication/provider.d.mts +1 -4
  18. package/dist/authentication/provider.mjs +1 -1
  19. package/dist/authentication/refusal.mjs +2 -1
  20. package/dist/cli/commands.d.mts +34 -11
  21. package/dist/cli/commands.mjs +47 -22
  22. package/dist/contract/caller.mjs +1 -1
  23. package/dist/contract/declared.mjs +27 -14
  24. package/dist/contract/errors.d.mts +42 -17
  25. package/dist/contract/errors.mjs +42 -17
  26. package/dist/contract/hints.mjs +3 -2
  27. package/dist/contract/implementation.d.mts +5 -8
  28. package/dist/contract/implementation.mjs +29 -17
  29. package/dist/contract/media.d.mts +6 -2
  30. package/dist/contract/media.mjs +6 -2
  31. package/dist/contract/names.mjs +14 -12
  32. package/dist/contract/status.mjs +3 -10
  33. package/dist/http/client.d.mts +15 -4
  34. package/dist/http/client.mjs +1 -1
  35. package/dist/mcp/client.d.mts +21 -8
  36. package/dist/mcp/client.mjs +6 -8
  37. package/docs/Action-failures.md +6 -15
  38. package/docs/Action.md +159 -191
  39. package/docs/ActionCli-failures.md +12 -92
  40. package/docs/ActionCli.md +102 -156
  41. package/docs/ActionHttp-failures.md +14 -76
  42. package/docs/ActionHttp.md +150 -176
  43. package/docs/ActionMcp-failures.md +2 -22
  44. package/docs/ActionMcp.md +79 -170
  45. package/docs/ActionRpc-failures.md +3 -12
  46. package/docs/ActionRpc.md +183 -120
  47. package/docs/ActionToolkit-failures.md +3 -10
  48. package/docs/ActionToolkit.md +112 -109
  49. package/docs/Authentication-advanced.md +167 -108
  50. package/docs/Authentication-failures.md +11 -91
  51. package/docs/Authentication.md +114 -135
  52. package/docs/README.md +45 -30
  53. package/docs/Testing-failures.md +2 -6
  54. package/docs/Testing.md +126 -159
  55. package/docs/setup-failures.md +8 -32
  56. package/docs/setup.md +42 -37
  57. package/examples/README.md +6 -10
  58. package/examples/assistant/README.md +20 -38
  59. package/examples/assistant/src/agent.ts +2 -3
  60. package/examples/assistant/src/cli.ts +4 -9
  61. package/examples/assistant/src/commands.ts +6 -4
  62. package/examples/assistant/src/identity/authorize.ts +3 -3
  63. package/examples/assistant/src/tasks/Tasks.ts +1 -2
  64. package/examples/assistant/src/tasks/handlers.ts +3 -3
  65. package/examples/assistant/src/toolkit.ts +6 -8
  66. package/examples/assistant/test/commands.test.ts +16 -11
  67. package/examples/assistant/test/tasks/tasks.test.ts +0 -2
  68. package/examples/assistant/test/toolkit.test.ts +0 -1
  69. package/examples/kitchen-sink/README.md +30 -52
  70. package/examples/kitchen-sink/src/Actions.ts +2 -3
  71. package/examples/kitchen-sink/src/Api.ts +3 -4
  72. package/examples/kitchen-sink/src/admin/Admin.ts +1 -1
  73. package/examples/kitchen-sink/src/admin/authenticate.ts +3 -3
  74. package/examples/kitchen-sink/src/admin/authorize.ts +1 -2
  75. package/examples/kitchen-sink/src/agent.ts +1 -1
  76. package/examples/kitchen-sink/src/assistant/handlers.ts +1 -6
  77. package/examples/kitchen-sink/src/attachments/Attachments.ts +4 -8
  78. package/examples/kitchen-sink/src/attachments/download.ts +5 -8
  79. package/examples/kitchen-sink/src/attachments/handlers.ts +2 -3
  80. package/examples/kitchen-sink/src/browser.ts +3 -4
  81. package/examples/kitchen-sink/src/cli.ts +6 -7
  82. package/examples/kitchen-sink/src/config.ts +2 -0
  83. package/examples/kitchen-sink/src/identity/Identity.ts +7 -7
  84. package/examples/kitchen-sink/src/identity/authenticate.ts +3 -5
  85. package/examples/kitchen-sink/src/identity/authorize.ts +4 -3
  86. package/examples/kitchen-sink/src/identity/quota.ts +1 -1
  87. package/examples/kitchen-sink/src/issues/Issues.ts +2 -2
  88. package/examples/kitchen-sink/src/issues/assistant.ts +3 -3
  89. package/examples/kitchen-sink/src/issues/guide.ts +2 -3
  90. package/examples/kitchen-sink/src/ops.ts +3 -6
  91. package/examples/kitchen-sink/src/request-policy.ts +3 -3
  92. package/examples/kitchen-sink/src/routes.ts +4 -6
  93. package/examples/kitchen-sink/src/seed.ts +0 -1
  94. package/examples/kitchen-sink/src/server.ts +11 -12
  95. package/examples/kitchen-sink/src/stdio.ts +3 -2
  96. package/examples/kitchen-sink/src/workspaces/tenant.ts +3 -1
  97. package/examples/kitchen-sink/test/admin/admin.test.ts +0 -1
  98. package/examples/kitchen-sink/test/assistant/assistant.test.ts +1 -1
  99. package/examples/kitchen-sink/test/attachments/attachments.test.ts +1 -2
  100. package/examples/kitchen-sink/test/identity/identity.test.ts +0 -1
  101. package/examples/kitchen-sink/test/issues/issues.test.ts +0 -2
  102. package/examples/kitchen-sink/test/request-policy.test.ts +1 -1
  103. package/examples/kitchen-sink/test/routes.test.ts +3 -3
  104. package/examples/live-rpc/README.md +8 -49
  105. package/examples/live-rpc/src/browser.ts +8 -41
  106. package/examples/live-rpc/src/chat/Chat.ts +9 -3
  107. package/examples/live-rpc/src/chat/room.ts +1 -1
  108. package/examples/live-rpc/src/client.ts +2 -3
  109. package/examples/live-rpc/src/page.ts +34 -0
  110. package/examples/live-rpc/src/routes.ts +6 -7
  111. package/examples/live-rpc/src/server.ts +6 -4
  112. package/examples/live-rpc/test/browser.test.ts +6 -5
  113. package/examples/live-rpc/test/routes.test.ts +16 -15
  114. package/examples/live-rpc/test/server.test.ts +1 -1
  115. package/examples/mcp-tools/README.md +22 -45
  116. package/examples/mcp-tools/src/printers/Printers.ts +3 -7
  117. package/examples/mcp-tools/src/routes.ts +0 -7
  118. package/examples/mcp-tools/src/server.ts +1 -1
  119. package/examples/mcp-tools/test/printers/printers.test.ts +2 -3
  120. package/examples/mcp-tools/test/routes.test.ts +2 -3
  121. package/examples/notes-service/README.md +9 -43
  122. package/examples/notes-service/src/cli.ts +0 -3
  123. package/examples/notes-service/src/commands.ts +7 -17
  124. package/examples/notes-service/src/identity/authorize.ts +2 -5
  125. package/examples/notes-service/src/identity/verifier.ts +3 -4
  126. package/examples/notes-service/src/notes/Notes.ts +2 -6
  127. package/examples/notes-service/src/notes/store.ts +5 -1
  128. package/examples/notes-service/src/routes.ts +6 -8
  129. package/examples/notes-service/src/server.ts +4 -3
  130. package/examples/notes-service/src/stdio.ts +4 -3
  131. package/examples/notes-service/test/commands.test.ts +1 -1
  132. package/examples/notes-service/test/notes/as.ts +6 -0
  133. package/examples/notes-service/test/notes/idempotency.test.ts +1 -4
  134. package/examples/notes-service/test/notes/in-process.test.ts +50 -0
  135. package/examples/notes-service/test/notes/notes.test.ts +3 -42
  136. package/examples/notes-service/test/routes.test.ts +2 -2
  137. package/examples/quickstart/README.md +1 -1
  138. package/examples/quickstart/src/client.ts +1 -0
  139. package/examples/quickstart/src/server.ts +2 -4
  140. package/examples/quickstart/test/{greeting/greeting.test.ts → routes.test.ts} +3 -3
  141. package/package.json +1 -1
  142. package/docs/Action-in-process.md +0 -93
  143. package/docs/CONTEXT.md +0 -46
  144. package/docs/guarantees.md +0 -44
@@ -14,14 +14,17 @@ interface AnyBinding extends Declaring {
14
14
  }
15
15
  /**
16
16
  * Make a command of your own fail as a derived command does. Give it what the command calls, the
17
- * binding of `ActionHttp.client` or `ActionRpc.client`, or the actions of `Action.client`, and pipe the command's
18
- * whole Effect through it: a failure that is a declared error, the binding's and its
19
- * authentication's included, or a built-in one becomes a `UserError` whose message is its JSON, and any other, a transport
20
- * failure for one, a `UserError` naming it and the causes beneath it. An Effect CLI error, your
21
- * own `new CliError.UserError({ cause, userMessage })` included, passes through as it is. To
22
- * add context, such as how far a batch got, print it before this in the pipe, with
23
- * `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result would
24
- * replace its JSON.
17
+ * binding of `ActionHttp.client` or `ActionRpc.client`, or the actions of `Action.client`, and
18
+ * pipe the command's whole Effect through it: a failure that is a declared error, the binding's
19
+ * and its authentication's included, or a built-in one becomes a `UserError` whose message is its
20
+ * JSON, and any other, a transport failure for one, a `UserError` naming it and the causes
21
+ * beneath it. An Effect CLI error, your own `new CliError.UserError({ cause, userMessage })`
22
+ * included, passes through as it is.
23
+ *
24
+ * **Gotchas**
25
+ *
26
+ * - A failure left unmapped, one call's included, is reported as a crash, `ERROR (#n)` with a stack trace: pipe the whole Effect through it, once.
27
+ * - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
25
28
  */
26
29
  export declare const toUserError: (calls: AnyBinding | ReadonlyArray<Any>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, UserError<Exclude<E, CliError.CliError>> | Exclude<E, Exclude<E, CliError.CliError>>, R>;
27
30
  /**
@@ -43,11 +46,16 @@ interface Options$1<A extends Any = Any> {
43
46
  readonly commands?: { readonly [K in A as K["name"]]?: Options<K>; };
44
47
  /**
45
48
  * Commands of your own beside the derived ones, as `Command.withSubcommands` takes them:
46
- * `[importLinks]`, each made with Effect CLI's `Command.make`. One fails as you make it fail:
47
- * with a `CliError.UserError` for `Command.run` to print its `userMessage` on stderr. Written
48
- * in the call, or in options held with `satisfies` this type, the list adds its commands'
49
- * failures and services to the aggregate's type; in options annotated with this type, it adds
50
- * none.
49
+ * `[importLinks]`, each made with Effect CLI's `Command.make` and printing its output itself,
50
+ * with `Console.log`. One calls actions through a client in its own handler: `yield*` the
51
+ * Effect `remote` takes as `client`, which needs no scope, or `Action.client`, acquired inside
52
+ * `Effect.scoped`. It fails as you make it fail: through `toUserError` as a derived command
53
+ * does, or with a `CliError.UserError` for `Command.run` to print its `userMessage` on stderr.
54
+ *
55
+ * **Gotchas**
56
+ *
57
+ * - Write the list in the call, or hold the options with `satisfies ActionCli.Options<…>`: in options annotated `: ActionCli.Options<…>` it adds none of its commands' failures and services to the aggregate's type, so the program compiles, then a command's service is not found.
58
+ * - Never pipe `Command.withSubcommands` onto the aggregate instead: it replaces the derived commands, which then answer with the usage text.
51
59
  */
52
60
  readonly subcommands?: Subcommands | undefined;
53
61
  }
@@ -64,25 +72,48 @@ type Local<App, A extends Any> = Effect.Effect<A["success"]["Type"], A["error"][
64
72
  /**
65
73
  * Project one implemented action into a native Effect CLI command that runs its handler in
66
74
  * process, behind its implementation's authorization. It is named after the action in kebab
67
- * case with one flag per field of its input (`--user-id`), or `--input` taking the whole input
68
- * as JSON when it is not a struct. The flag of an array of strings or numbers is repeated for each value, a boolean
69
- * field is a switch, `--no-<name>` sending `false` as a required one left out does, and a field's `description` is its flag's help. It needs what the authorization, its handler and its builder
70
- * need; the host provides the identity. It prints the result on stdout; a failure is a
71
- * `UserError`, or an Effect CLI error as it is, such as a builder's `CliError.UserError`, which
72
- * `Command.run` prints on stderr. `remoteCommand` calls a server instead.
75
+ * case, with one flag per top-level field of its input (`--user-id`), or `--input` taking the
76
+ * whole input as JSON when it is not a struct of named fields. The host provides the caller on
77
+ * the command, `Command.provideSync`, beside what the authorization, the handler and the builder
78
+ * need. `remoteCommand` calls a server instead.
79
+ *
80
+ * **Gotchas**
81
+ *
82
+ * - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
83
+ * - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
84
+ * - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
85
+ * - Provide services with `Command.provide` on the command, built when an action runs: provided around `Command.run`, they are built before parsing, so `--help` and a mistyped flag open the database.
86
+ * - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
87
+ * - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
73
88
  */
74
89
  export declare function command<const Apps extends Served, A extends ActionOf<Member<Apps>>, const O = {}>(implementations: Apps, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, Options<Any>>>): Command.Command<string, never, {}, Failing<Effect.Error<Local<Owning<Member<Apps>, A>, A>>>, Effect.Services<Local<Owning<Member<Apps>, A>, A>>>;
75
90
  /**
76
91
  * Project one action of an HTTP binding into a native Effect CLI command, derived as `command`
77
92
  * derives it, that calls the action through `client`'s method and fails as the method does, or
78
- * as the Effect building the client does. `client` is an Effect of the binding's client, such as
79
- * `ActionHttp.client(Http, { baseUrl })`, run each time the command runs; omitted, it is
80
- * `ActionHttp.client(binding)` on the host's `HttpClient`. It runs nothing locally.
93
+ * as the Effect building the client does. It runs nothing locally and names no caller: the
94
+ * server verifies the credentials its client sends.
81
95
  */
82
- export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any>>>> & {
96
+ export declare function remoteCommand<const H extends AnyHttp, A extends H["actions"][number], const O = {}, C extends ClientOf<H, A> | undefined = undefined>(binding: H, action: A, options?: O & NoInfer<Options<A>> & NoInfer<Known<O, RemoteKeys<Options<Any>>>> & Calling<C>): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
97
+ export interface Calling<C> {
98
+ /**
99
+ * The connection: an Effect of the binding's client, such as
100
+ * `ActionHttp.client(Http, { baseUrl, transformClient })`, run each time a command runs, so one
101
+ * reading `Config` is an `Effect.gen` returning `yield* ActionHttp.client(…)` and `--help`
102
+ * reads nothing. What it requires, the host provides. Omitted, it is
103
+ * `ActionHttp.client(binding)` on the host's `HttpClient`, with no `baseUrl`. It needs only the
104
+ * methods of the actions it derives commands for: those `remote`'s `actions` lists, or
105
+ * `remoteCommand`'s one.
106
+ *
107
+ * **Gotchas**
108
+ *
109
+ * - A token read with `Config` and not set fails every command, a public one's too, as `ConfigError: …`, which names no fix: map it to a tagged error whose `message` says what to do, or read an optional one with `Config.option`.
110
+ * - Credentials go in `transformClient`: a command adds no headers of its own, so without them a protected action answers `Unauthenticated`.
111
+ * - Never transform the `HttpClient` around the program instead: it rewrites every request the program makes, an absolute URL too, and sends each one the token.
112
+ */
83
113
  readonly client?: C;
84
- }): Command.Command<string, never, {}, Failing<CallFailure<H, C, A>>, CallServices<C, A>>;
85
- type ClientOf<H extends AnyHttp> = Effect.Effect<Client<H, unknown, unknown>, unknown, unknown>;
114
+ }
115
+ type ClientOf<H extends AnyHttp, A extends Any> = Effect.Effect<Pick<Client<H, unknown, unknown>, A["name"]>, unknown, unknown>;
116
+ type Needed<O, L extends ReadonlyArray<Any>, All extends Any> = "actions" extends (O extends unknown ? keyof O : never) ? Selected<O, All> : L[number];
86
117
  type RemoteKeys<Keys> = Keys & {
87
118
  readonly client?: unknown;
88
119
  };
@@ -91,35 +122,43 @@ type Called<M, A extends Any> = A extends unknown ? A["name"] extends keyof M ?
91
122
  type CallFailure<H extends AnyHttp, C, A extends Any> = C extends Effect.Effect<infer M, infer CE, unknown> ? CE | Effect.Error<Called<M, A>> : Effect.Error<Called<Client<H>, A>>;
92
123
  type CallServices<C, A extends Any> = C extends Effect.Effect<infer M, unknown, infer CR> ? CR | Effect.Services<Called<M, A>> : HttpClient.HttpClient;
93
124
  /**
94
- * Project every implemented action as a subcommand of one aggregate command, each named after
95
- * its action in kebab case and run in process. `commands` gives a subcommand the options
96
- * `command` takes, by action name; `subcommands` adds commands of your own, which
97
- * `Command.withSubcommands` on the result would put in the derived ones' place. `remote` calls a
98
- * server instead. A test runs it in memory, with no process:
99
- * `Command.runWith(command, { version })(args)`.
125
+ * Project the implemented actions, every one or those `actions` selects, as subcommands of one
126
+ * aggregate command, each derived as `command` derives it and run in process. `commands` gives a
127
+ * subcommand the options `command` takes, by action name; `subcommands` adds commands of your
128
+ * own. `remote` calls a server instead. A test runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
100
129
  * Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
130
+ *
131
+ * **Gotchas**
132
+ *
133
+ * - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
101
134
  */
102
135
  export declare function make<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, const Own extends Subcommands = []>(implementations: Apps, options: O & NoInfer<Options$1<ActionOf<Member<Apps>>>> & NoInfer<KnownOptions<O, Options$1, ActionOf<Member<Apps>>>> & {
103
136
  readonly subcommands?: Own | undefined;
104
137
  }): Command.Command<string, {}, {}, Failing<Effect.Error<Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>>> | Command.Error<OwnCommand<Own>>, Effect.Services<Local<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>>> | Command.Services<OwnCommand<Own>>>;
105
138
  /**
106
- * Project every action of an HTTP binding as a subcommand of one aggregate command, derived as
107
- * `make` derives it, each calling its action through `client`'s method, as `remoteCommand`
108
- * does. `commands` gives one the options `command` takes; `subcommands` adds commands of your
109
- * own, which call through the same client by yielding the Effect given as `client`.
139
+ * Project the actions of an HTTP binding, every one or those `actions` selects, as subcommands of
140
+ * one aggregate command, derived as `make` derives them, each calling its action through
141
+ * `client`'s method, as `remoteCommand` does. `commands` gives one the options `command` takes;
142
+ * `subcommands` adds commands of your own, which call through the same client by yielding the
143
+ * Effect given as `client`.
144
+ * In an application: `examples/notes-service/src/commands.ts`.
110
145
  */
111
- export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number]> = {}, const Own extends Subcommands = [], C extends ClientOf<H> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1<Any>>, H["actions"][number]>> & {
112
- readonly client?: C;
146
+ export declare function remote<const H extends AnyHttp, const O extends Selection<H["actions"][number]> = {}, const Own extends Subcommands = [], const L extends ReadonlyArray<H["actions"][number]> = H["actions"], C extends ClientOf<H, Needed<O, L, H["actions"][number]>> | undefined = undefined>(binding: H, options: O & NoInfer<Options$1<H["actions"][number]>> & NoInfer<KnownOptions<O, RemoteKeys<Options$1<Any>>, H["actions"][number]>> & Calling<C> & {
147
+ readonly actions?: L | undefined;
113
148
  readonly subcommands?: Own | undefined;
114
149
  }): Command.Command<string, {}, {}, Failing<CallFailure<H, C, Selected<O, H["actions"][number]>>> | Command.Error<OwnCommand<Own>>, CallServices<C, Selected<O, H["actions"][number]>> | Command.Services<OwnCommand<Own>>>;
115
150
  /**
116
151
  * A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP
117
152
  * subprocess, applied last, before `NodeRuntime.runMain`: the default logger of every layer
118
- * provided within writes to stderr, and a failure `runMain` would report, such as a defect or
119
- * a failure of a layer the host provides, is reported on stderr instead, once. A command's
120
- * own failure, which `Command.run` has printed, is not reported again. The exit code is the
121
- * one `runMain` gives: the failure's `Runtime.errorExitCode`, 1 by default, or 130 for an
122
- * interruption.
153
+ * provided within writes to stderr, and a failure `runMain` would report on stdout, such as a
154
+ * defect, a success that does not encode or a failure of a layer the host provides, is reported
155
+ * on stderr instead, once. A command's own failure, which `Command.run` has printed, is not
156
+ * reported again. The exit code is the one `runMain` gives: the failure's
157
+ * `Runtime.errorExitCode`, 1 by default, or 130 for an interruption.
158
+ *
159
+ * **Gotchas**
160
+ *
161
+ * - It moves the default logger only: a host layer's `Console` output, the global `console.log`, and a logger writing through `Console.log` such as `Logger.consoleJson`, still reach stdout; log JSON with `Logger.withConsoleError(Logger.formatJson)`.
123
162
  */
124
163
  export declare const logToStderr: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, never, R>;
125
164
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { assertDistinct, assertKnown } from "./contract/names.mjs";
2
2
  import { bindingErrors, projectedErrors } from "./contract/declared.mjs";
3
- import { Implementation, acquire, assertHeld, select, toList } from "./contract/implementation.mjs";
3
+ import { Implementation, acquire, assertHeld, notHeld, select, toList } from "./contract/implementation.mjs";
4
4
  import { client } from "./ActionHttp.mjs";
5
5
  import { command as command$1, failingAsDerived } from "./cli/commands.mjs";
6
6
  import { Cause, Effect, Logger, Runtime } from "effect";
@@ -8,14 +8,17 @@ import { Command } from "effect/cli";
8
8
  //#region src/cli/ActionCli.ts
9
9
  /**
10
10
  * Make a command of your own fail as a derived command does. Give it what the command calls, the
11
- * binding of `ActionHttp.client` or `ActionRpc.client`, or the actions of `Action.client`, and pipe the command's
12
- * whole Effect through it: a failure that is a declared error, the binding's and its
13
- * authentication's included, or a built-in one becomes a `UserError` whose message is its JSON, and any other, a transport
14
- * failure for one, a `UserError` naming it and the causes beneath it. An Effect CLI error, your
15
- * own `new CliError.UserError({ cause, userMessage })` included, passes through as it is. To
16
- * add context, such as how far a batch got, print it before this in the pipe, with
17
- * `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result would
18
- * replace its JSON.
11
+ * binding of `ActionHttp.client` or `ActionRpc.client`, or the actions of `Action.client`, and
12
+ * pipe the command's whole Effect through it: a failure that is a declared error, the binding's
13
+ * and its authentication's included, or a built-in one becomes a `UserError` whose message is its
14
+ * JSON, and any other, a transport failure for one, a `UserError` naming it and the causes
15
+ * beneath it. An Effect CLI error, your own `new CliError.UserError({ cause, userMessage })`
16
+ * included, passes through as it is.
17
+ *
18
+ * **Gotchas**
19
+ *
20
+ * - A failure left unmapped, one call's included, is reported as a crash, `ERROR (#n)` with a stack trace: pipe the whole Effect through it, once.
21
+ * - To add context, such as how far a batch got, print it before this in the pipe, `Effect.tapError(() => Console.error(…))`: a `UserError` wrapped around the result puts its JSON mid-sentence and hides the action's failure from `catchReason`.
19
22
  */
20
23
  const toUserError = (calls) => "actions" in calls ? failingAsDerived(calls.actions.flatMap((action) => bindingErrors(action, calls))) : failingAsDerived(calls.flatMap((action) => projectedErrors(action)));
21
24
  const runWithBuildOfItsOwn = (app, action, input) => {
@@ -27,8 +30,8 @@ const runWithBuildOfItsOwn = (app, action, input) => {
27
30
  };
28
31
  const theImplementationOf = (apps, action) => {
29
32
  const [app, ...others] = apps.filter((candidate) => candidate.actions.includes(action));
30
- if (app === void 0) throw new Error(`Action "${action.name}" has no implementation here`);
31
- if (others.length > 0) throw new Error(`Action "${action.name}" is implemented twice here`);
33
+ if (app === void 0) throw new Error(`Action "${action.name}" has no implementation here (pass its implementation, and select with the contract value it implements: an equal-looking action does not match)`);
34
+ if (others.length > 0) throw new Error(`Action "${action.name}" is implemented twice here (pass one of them)`);
32
35
  return app;
33
36
  };
34
37
  const inProcess = (apps, action, options) => {
@@ -36,10 +39,10 @@ const inProcess = (apps, action, options) => {
36
39
  return command$1(action, (input) => runWithBuildOfItsOwn(app, action, input), options, projectedErrors(action));
37
40
  };
38
41
  const overHttp = (binding, action, options, client$1) => {
39
- if (!binding.actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding`);
42
+ if (!binding.actions.includes(action)) throw new Error(`Action "${action.name}" is not in this HTTP binding (pass it to the binding's ActionHttp.make, and select with the contract value it received)`);
40
43
  return command$1(action, (input) => Effect.flatMap(client$1 ?? client(binding), (methods) => {
41
44
  const method = methods[action.name];
42
- if (method === void 0) throw new Error(`No client method for ${action.name}`);
45
+ if (method === void 0) throw new Error(`No client method for ${action.name} (give \`client\` a method for this action)`);
43
46
  return method(input);
44
47
  }), options, bindingErrors(action, binding));
45
48
  };
@@ -54,23 +57,33 @@ const aggregateCommand = (name, actions, commands, own, project) => {
54
57
  })), ...own.flatMap((entry) => Command.isCommand(entry) ? [entry] : entry.commands).map((command) => ({
55
58
  claimant: "subcommands",
56
59
  name: command.name
57
- }))], ({ name: claimed }) => claimed, ({ claimant }) => claimant);
60
+ }))], ({ name: claimed }) => claimed, {
61
+ claimantOf: ({ claimant }) => claimant,
62
+ fix: "rename one of your subcommands, or give an action's command another name in commands"
63
+ });
58
64
  return Command.make(name).pipe(Command.withSubcommands([...derived.map(({ command }) => command), ...own]));
59
65
  };
60
66
  const checkedCommandsOf = (commands, actions) => {
61
67
  const given = commands ?? {};
62
- assertKnown("commands", Object.keys(given), actions.map((action) => action.name));
68
+ assertKnown("commands", Object.keys(given), actions.map((action) => action.name), "key it by the action's own name, not its command's");
63
69
  return given;
64
70
  };
65
71
  /**
66
72
  * Project one implemented action into a native Effect CLI command that runs its handler in
67
73
  * process, behind its implementation's authorization. It is named after the action in kebab
68
- * case with one flag per field of its input (`--user-id`), or `--input` taking the whole input
69
- * as JSON when it is not a struct. The flag of an array of strings or numbers is repeated for each value, a boolean
70
- * field is a switch, `--no-<name>` sending `false` as a required one left out does, and a field's `description` is its flag's help. It needs what the authorization, its handler and its builder
71
- * need; the host provides the identity. It prints the result on stdout; a failure is a
72
- * `UserError`, or an Effect CLI error as it is, such as a builder's `CliError.UserError`, which
73
- * `Command.run` prints on stderr. `remoteCommand` calls a server instead.
74
+ * case, with one flag per top-level field of its input (`--user-id`), or `--input` taking the
75
+ * whole input as JSON when it is not a struct of named fields. The host provides the caller on
76
+ * the command, `Command.provideSync`, beside what the authorization, the handler and the builder
77
+ * need. `remoteCommand` calls a server instead.
78
+ *
79
+ * **Gotchas**
80
+ *
81
+ * - A success is its JSON on stdout, indented by two spaces, or `render`'s text; nothing for an action without `success`. Its Effect logs and `Console` output go to stderr; other writes as `logToStderr` says.
82
+ * - A declared error is its JSON on stderr, under Effect CLI's `ERROR` heading, the command failing with a `UserError` whose `cause` is the error, exit 1 or its `Runtime.errorExitCode`; a refusal as `Action.Unauthenticated` and `Action.Forbidden` say; input that does not decode, in `--input` or a flag's JSON value, is `InvalidInput`'s JSON there, while a missing flag or a rejected choice is Effect CLI's own parse error.
83
+ * - A failure no schema encodes, a builder's or an accepted error that does not encode, is a `UserError` printed as its tag or name and its message, then its causes, never its other fields; a defect, an undeclared failure, or a success that does not encode is the command's defect, which `runMain` reports as `logToStderr` says.
84
+ * - Provide services with `Command.provide` on the command, built when an action runs: provided around `Command.run`, they are built before parsing, so `--help` and a mistyped flag open the database.
85
+ * - Each run builds the action's implementation, its builder and an authorizer an Effect builds, and releases them after the call, even where the host already built them; implementations passed beside it are not built.
86
+ * - A field named like a global flag (`help`, `version`, `log-level`) is no clash: its flag shadows the global one on this command.
74
87
  */
75
88
  function command(implementations, action, options) {
76
89
  return inProcess(toList(implementations), action, options);
@@ -80,13 +93,15 @@ function remoteCommand(binding, action, options) {
80
93
  return overHttp(binding, action, options === void 0 ? void 0 : syntax, client);
81
94
  }
82
95
  /**
83
- * Project every implemented action as a subcommand of one aggregate command, each named after
84
- * its action in kebab case and run in process. `commands` gives a subcommand the options
85
- * `command` takes, by action name; `subcommands` adds commands of your own, which
86
- * `Command.withSubcommands` on the result would put in the derived ones' place. `remote` calls a
87
- * server instead. A test runs it in memory, with no process:
88
- * `Command.runWith(command, { version })(args)`.
96
+ * Project the implemented actions, every one or those `actions` selects, as subcommands of one
97
+ * aggregate command, each derived as `command` derives it and run in process. `commands` gives a
98
+ * subcommand the options `command` takes, by action name; `subcommands` adds commands of your
99
+ * own. `remote` calls a server instead. A test runs it in memory, with no process: `Command.runWith(command, { version })(args)`.
89
100
  * Guide: `docs/ActionCli.md` in this package; in an application: `examples/assistant/src/commands.ts`.
101
+ *
102
+ * **Gotchas**
103
+ *
104
+ * - Services provided on it with `Command.provide` are also built when it runs with no subcommand, before it shows its help.
90
105
  */
91
106
  function make(implementations, options) {
92
107
  const apps = toList(implementations);
@@ -96,18 +111,22 @@ function make(implementations, options) {
96
111
  }
97
112
  function remote(binding, options) {
98
113
  const listed = options.actions;
99
- if (listed !== void 0) assertHeld("the binding does not hold it", listed, binding.actions);
114
+ if (listed !== void 0) assertHeld(notHeld.byBinding, listed, binding.actions);
100
115
  const commands = checkedCommandsOf(options.commands, binding.actions);
101
116
  return aggregateCommand(options.name, binding.actions.filter((action) => listed?.includes(action) ?? true), commands, options.subcommands ?? [], (action, syntax) => overHttp(binding, action, syntax, options.client));
102
117
  }
103
118
  /**
104
119
  * A program whose stdout carries only its results, such as a CLI feeding scripts or an MCP
105
120
  * subprocess, applied last, before `NodeRuntime.runMain`: the default logger of every layer
106
- * provided within writes to stderr, and a failure `runMain` would report, such as a defect or
107
- * a failure of a layer the host provides, is reported on stderr instead, once. A command's
108
- * own failure, which `Command.run` has printed, is not reported again. The exit code is the
109
- * one `runMain` gives: the failure's `Runtime.errorExitCode`, 1 by default, or 130 for an
110
- * interruption.
121
+ * provided within writes to stderr, and a failure `runMain` would report on stdout, such as a
122
+ * defect, a success that does not encode or a failure of a layer the host provides, is reported
123
+ * on stderr instead, once. A command's own failure, which `Command.run` has printed, is not
124
+ * reported again. The exit code is the one `runMain` gives: the failure's
125
+ * `Runtime.errorExitCode`, 1 by default, or 130 for an interruption.
126
+ *
127
+ * **Gotchas**
128
+ *
129
+ * - It moves the default logger only: a host layer's `Console` output, the global `console.log`, and a logger writing through `Console.log` such as `Logger.consoleJson`, still reach stdout; log JSON with `Logger.withConsoleError(Logger.formatJson)`.
111
130
  */
112
131
  const logToStderr = (self) => self.pipe(Effect.catchCause((cause) => {
113
132
  if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt;
@@ -14,6 +14,11 @@ export interface FetchClientOptions<E = HttpClientError.HttpClientError, R = nev
14
14
  /**
15
15
  * What each call sends with. Omitted: the global `fetch`, looked up on every call, so one
16
16
  * installed after the client is built, such as a test's stub, is used.
17
+ *
18
+ * **Gotchas**
19
+ *
20
+ * - A page on the API's own origin sends its cookies already; from another origin pass `(input, init) => fetch(input, { ...init, credentials: "include" })`, and the server's CORS must allow credentials, `HttpRouter.cors({ allowedOrigins, credentials: true })`.
21
+ * - A `fetch` of your own passes `init.signal` on, or interrupting a call no longer aborts its request: a deadline is `Effect.timeout` on the call.
17
22
  */
18
23
  readonly fetch?: typeof globalThis.fetch | undefined;
19
24
  }
@@ -30,14 +35,16 @@ type HttpLayer<H extends AnyHttp, Apps extends Served, M extends Middleware, A e
30
35
  export interface LayerOptions<M extends Middleware = [], A extends Any = never> {
31
36
  /**
32
37
  * The actions it serves, among the binding's actions the implementations hold: `[GetUser]`.
33
- * Defaults to every one of them. A layer whose middleware requires the identity lists
34
- * protected actions only.
38
+ * Defaults to every one of them.
35
39
  */
36
40
  readonly actions?: ReadonlyArray<A> | undefined;
37
41
  /**
38
42
  * Native endpoint middleware every served route runs, the first innermost: inside the
39
- * authentication of a protected route, outside its decoding. A middleware requiring the
40
- * identity needs a layer serving only protected actions.
43
+ * authentication of a protected route, outside its decoding, so it also sees invalid input.
44
+ *
45
+ * **Gotchas**
46
+ *
47
+ * - A middleware requiring the identity needs a layer serving only protected actions: serve the public ones from a second layer without it.
41
48
  */
42
49
  readonly middleware?: M;
43
50
  }
@@ -46,9 +53,31 @@ type LayerRequest<App, A extends Any, M extends Middleware> = Exclude<Through<M,
46
53
  }>] extends [never] ? Identity<Protected<Serving<App, A>>> : never)>;
47
54
  /** Contract-level configuration shared by servers and clients. */
48
55
  interface Options$1<E extends Errors = Errors> {
56
+ /**
57
+ * The mount path of every route: `/api` by default, `/` at the root. Split a large API, or
58
+ * actions sharing a name, into one binding per area, each with its own `prefix` and client;
59
+ * one host serves them all.
60
+ */
49
61
  readonly prefix?: `/${string}`;
50
- /** Router middleware's errors, which every endpoint declares: one schema, or a list. */
62
+ /**
63
+ * What router middleware around the routes, or the layer's `middleware`, answers with, such as
64
+ * a quota's 429, which every endpoint declares: one schema, or a list.
65
+ *
66
+ * **Gotchas**
67
+ *
68
+ * - An error a handler fails with goes in its action's `error`, not here.
69
+ * - Router middleware answering with one encodes and sends it itself, `HttpServerResponse.schemaJson(QuotaExceeded)(error, { status: 429 })`, at the status `Action.Options.error` gives it: at any other a client fails with an `HttpClientError`.
70
+ */
51
71
  readonly error?: E | E[number];
72
+ /**
73
+ * The descriptor of the binding's protected actions, `Authentication.make(...)`: required
74
+ * when it holds one, and it documents and enforces their credentials as native endpoint
75
+ * security middleware on each of them.
76
+ *
77
+ * **Gotchas**
78
+ *
79
+ * - One descriptor per binding: actions of a second identity take a second binding with its own `prefix`, and a public action may be in both.
80
+ */
52
81
  readonly authentication?: Any$1;
53
82
  }
54
83
  type Endpoint<A extends Any, E extends Errors, D> = A extends Any ? HttpApiEndpoint.HttpApiEndpoint<A["name"], "POST", `/${string}`, never, never, Schema.toCodecJson<A["input"]>, never, Schema.toCodecJson<A["success"]>, Schema.toCodecJson<A["error"][number] | E[number] | VerifierError<A, D> | BuiltIns>, never> : never;
@@ -59,6 +88,7 @@ type Api<Actions extends ReadonlyArray<Any>, E extends Errors, D> = HttpApi.Http
59
88
  * serves the same; `layer` serves it and `client` calls it.
60
89
  */
61
90
  export interface Binding<Actions extends ReadonlyArray<Any>, E extends Errors = [], D extends Any$1 | undefined = undefined> {
91
+ /** Its protected actions' authentication descriptor, or `undefined` without one. */
62
92
  readonly authentication: D;
63
93
  /** The exact actions bound to this binding. */
64
94
  readonly actions: Actions;
@@ -66,16 +96,26 @@ export interface Binding<Actions extends ReadonlyArray<Any>, E extends Errors =
66
96
  readonly error: E;
67
97
  /** Where its routes mount: `/api` by default, `/` at the root, without a trailing slash. */
68
98
  readonly prefix: `/${string}`;
99
+ /**
100
+ * The native `HttpApi`, for `OpenApi.fromApi`, `HttpApiSwagger.layer`, `HttpApiScalar.layer`
101
+ * and Effect's own tools: it lists every action of the binding, served or not.
102
+ *
103
+ * **Gotchas**
104
+ *
105
+ * - Serve it with `layer`, never the native `HttpApiBuilder`: its group name is typed `string`, so `HttpApiBuilder.group(api, "api", …)` is refused as `Endpoint not handled: ${string}`, and given a `string`-typed name a protected action makes the host fail to build with `Service not found: effect-actions/Authentication/Security/…`.
106
+ * - A header that middleware reads is not in its document, whose operations list no `parameters`: rewrite it, `OpenApi.fromApi(Http.api.annotate(OpenApi.Transform, (document) => …))`, or make the value an input.
107
+ * - An OAuth protected resource is documented as a bearer scheme: Effect has no OAuth scheme.
108
+ * - Combined into one document, `HttpApi.make("app").addHttpApi(A.api).addHttpApi(B.api)`, a shared action name makes `OpenApi.fromApi` throw `Duplicate OpenAPI operationId`, and a shared prefix silently drops one binding's actions from the document and a native client: otherwise document each alone.
109
+ * - A binding holding no protected action documents no security scheme, so Swagger shows no Authorize button.
110
+ */
69
111
  readonly api: Api<Actions, E, D>;
70
112
  }
71
113
  /**
72
- * Bind actions once, for servers and clients alike, each at `POST <prefix>/<action>`.
73
- * Every endpoint declares its action's errors, the binding's `error`, and the built-in
74
- * `InvalidInput`, `Unauthenticated` and `Forbidden`, so clients decode each as a typed
75
- * failure. A union's own `httpApiStatus` applies to every member; otherwise, each member
76
- * uses its own status, or 422 without one. Protected contracts receive native endpoint
77
- * security middleware: the same descriptor documents their credentials and enforces them.
78
- * Guide: `docs/ActionHttp.md` in this package; in an application: `examples/notes-service/src/routes.ts`.
114
+ * Bind actions once, for servers and clients alike, each at `POST <prefix>/<action>`, a
115
+ * `readOnly` one too. Every endpoint declares its action's errors, the binding's `error`, and
116
+ * the built-in `InvalidInput`, `Unauthenticated` and `Forbidden`, so clients decode each as a
117
+ * typed failure; a protected one also declares what its descriptor's verifier fails with.
118
+ * Guide: `docs/ActionHttp.md` in this package; in an application: `examples/notes-service/src/Api.ts`.
79
119
  */
80
120
  export declare function make<const Actions extends ReadonlyArray<Any & {
81
121
  readonly caller: typeof Anyone;
@@ -84,41 +124,45 @@ export declare function make<const Actions extends ReadonlyArray<Any>, const O e
84
124
  export declare function make<const Actions extends ReadonlyArray<Any>>(actions: Actions & PublicOnly<Actions>): Binding<Actions, [], undefined>;
85
125
  /**
86
126
  * Serve the binding's actions among `implementations` in one layer, or those its `actions` lists,
87
- * authenticating a protected action's request before decoding it, then running its implementation's
88
- * `authorize` before each handler. The binding decides what may be served:
89
- * an implementation's actions the binding leaves out have no route here, and one holding none of
90
- * those served is not built; implementations holding none of the binding's actions at all are
91
- * refused when `layer` is called. It mounts only the routes of the actions it serves, so one
92
- * binding may be served by several layers, each with its own `actions` and `middleware`, which
93
- * covers its routes only. Each implementation's builder runs once per layer graph however many
94
- * layers serve it.
127
+ * authenticating a protected action's request before decoding it, then running its
128
+ * implementation's `authorize` before each handler. An implementation's actions the binding
129
+ * leaves out have no route here, one holding none of those served is not built, and a non-empty
130
+ * list holding none of the binding's actions is refused when `layer` is called. Several layers
131
+ * may serve one binding, each with its own `actions` and `middleware` covering its routes only.
132
+ * The layer fails as the builders do, needs at startup what they need, and per request what the
133
+ * authorization and handlers of the actions it serves need, until its `middleware` or middleware
134
+ * around it provides them, the identity of a protected action excepted, which its authentication
135
+ * provides; what is provided per request wins over what the layer was built with. A declared
136
+ * error answers with its JSON at the status `Action.Options.error` gives it, input that does not
137
+ * decode a 400 `InvalidInput`, and a refusal as `Action.Unauthenticated` and `Action.Forbidden`
138
+ * say.
139
+ *
140
+ * **Gotchas**
95
141
  *
96
- * The layer fails as the builders do, needs at startup what they need, and per request what
97
- * each implementation's authorization and the handlers of the actions it serves
98
- * need, until its `middleware` or middleware provided around it provides them, the identity
99
- * of a protected action excepted, which its authentication provides; the router provides
100
- * its own, such as the request, to every route. Its routes take only requests typed as JSON,
101
- * and what middleware provides per request wins over what the layer was built with. A request
102
- * body is read whole, however large, unless the host limits it where the routes are served:
103
- * `Layer.provide(Layer.succeed(HttpServerRequest.MaxBodySize, ByteSize.mebibytes(1)))`.
142
+ * - A success is its JSON as the body; an action without `success` answers an empty 200, no body and no content type, so a hand-written client must not parse it. One without `input` still takes the body `{}` typed as JSON: no body is a 400.
143
+ * - A body that is not JSON is a 400 `InvalidInput`; a request with no `Content-Type` a 415 `Unsupported content-type: none`, one with another type Effect's own 415; each after authentication on a protected route.
144
+ * - A defect, an undeclared failure, or a success or an error that does not encode answers an empty 500: the cause goes to the log and to the call's `ErrorReporter`s, never to the body.
145
+ * - A path no layer serves, or a method other than `POST` on a route, is a 404.
146
+ * - The 415 keeps from every handler what another site's page can send without a CORS preflight, such as a form post carrying the user's cookie; the server's CORS policy decides the rest.
147
+ * - Router middleware providing a request service `X` is declared `HttpRouter.middleware<{ provides: X }>()`: without the type argument it runs and the layer still owes `X`.
148
+ * - `HttpRouter.provideRequest(layer)` builds its layer once, at startup, so it never provides an identity or a tenant.
149
+ * - A request body is read whole, however large, unless the host provides `HttpServerRequest.MaxBodySize` to `HttpRouter.serve`, limiting every route that reads its body whole, MCP endpoints included; a body a route reads as a stream is not limited. On Node a request over it gets no response, its connection closed, never a 413; `HttpRouter.toWebHandler` ignores it, so limit bodies in the platform serving the handler.
150
+ * - An `ErrorReporter` the server runs with is given, as `Info`, a handler's declared failure and an input that does not decode, never a refusal or a refused credential: give an error class `override readonly [ErrorReporter.ignore] = true` to keep it out, and drop `Info` for a built-in error, which takes no mark.
104
151
  */
105
152
  export declare function layer<const H extends AnyHttp, const Apps extends Served>(binding: H, implementations: Apps): HttpLayer<H, Apps, []>;
106
153
  export declare function layer<const H extends AnyHttp, const Apps extends Served, const O extends LayerOptions<Middleware, H["actions"][number]>>(binding: H, implementations: Apps, options: O & ServerOnly<MiddlewareOf<O, Middleware>, H["error"]> & NoInfer<Known<O, LayerOptions<Middleware>>>): HttpLayer<H, Apps, MiddlewareOf<O, Middleware>, Selected<O, H["actions"][number]>>;
107
154
  /**
108
155
  * Effect's native `HttpApiClient` for a binding, one method per action taking the
109
- * action's input directly: `client.greet({ name })`. The argument may be omitted when `{}`
110
- * is a valid input. Requires the native `HttpClient`, as `HttpApiClient.make` does.
111
- * The options are the native ones, `baseUrl` and `transformClient`, a bearer token being
112
- * `transformClient: HttpClient.mapRequest(HttpClientRequest.bearerToken(token))`. The native
113
- * client itself stays available: `HttpApiClient.make(Http.api)`.
156
+ * action's input directly: `client.greet({ name })`, the argument omitted when `{}` is a valid
157
+ * input. Requires the native `HttpClient`, as `HttpApiClient.make` does; the native client
158
+ * itself stays available: `HttpApiClient.make(Http.api)`. Each call fails with its
159
+ * `MethodError`, an undeclared answer such as a 502 from a proxy or a defect's empty 500 as an
160
+ * `HttpClientError`, and with what `transformClient` adds.
114
161
  *
115
- * A call fails with a declared error value (the action's own, the binding's, a built-in
116
- * `InvalidInput`, `Unauthenticated` or `Forbidden`, or on a protected action its descriptor's),
117
- * a native `HttpClientError` when the server could not be reached or answered with a status or
118
- * body the contract does not declare, or a `SchemaError` when the success does not decode. An
119
- * input that does not encode fails with `InvalidInput`, and nothing is sent. A call also fails
120
- * with what the client `transformClient` returns fails with, as it is, and requires what that
121
- * client requires.
162
+ * **Gotchas**
163
+ *
164
+ * - An input that does not encode fails with `InvalidInput`, and nothing is sent; undeclared input fields, which TypeScript lets through on a wider value, are dropped as it encodes, since the server would refuse them.
165
+ * - It interprets no status and retries nothing: a failed write may have happened, and only a declared error says what the server did.
122
166
  */
123
167
  export declare function client<const H extends AnyHttp>(binding: H, options?: Options): Effect.Effect<Client<H>, never, HttpClient.HttpClient>;
124
168
  export declare function client<const H extends AnyHttp, E, R>(binding: H, options: Options<E, R>): Effect.Effect<Client<H, E, R>, never, HttpClient.HttpClient>;
@@ -127,9 +171,6 @@ export declare function client<const H extends AnyHttp, E, R>(binding: H, option
127
171
  * a browser app: its methods are the same Effects, which require only what `transformClient`
128
172
  * requires, so a call without one runs alone, as `Effect.runPromise(api.greet({ name }))`. The
129
173
  * options are `client`'s, and `fetch`.
130
- *
131
- * Each call sends with `options.fetch`, or with the global `fetch` as it is when the call
132
- * runs.
133
174
  */
134
175
  export declare function fetchClient<const H extends AnyHttp>(binding: H, options?: FetchClientOptions): Client<H>;
135
176
  export declare function fetchClient<const H extends AnyHttp, E, R>(binding: H, options: FetchClientOptions<E, R>): Client<H, E, R>;
@@ -1,9 +1,9 @@
1
1
  import { Anyone } from "./contract/caller.mjs";
2
+ import { declared } from "./contract/status.mjs";
2
3
  import { bindingErrors, bindingOf, projectedErrors } from "./contract/declared.mjs";
3
4
  import { acquire, provideHandlers, servingOf } from "./contract/implementation.mjs";
4
5
  import { providerOf } from "./authentication/provider.mjs";
5
6
  import { inputParseOptions } from "./contract/call.mjs";
6
- import { declared } from "./contract/status.mjs";
7
7
  import { methods } from "./http/client.mjs";
8
8
  import { SchemaErrors, schemaErrors } from "./http/schema-errors.mjs";
9
9
  import { Context, Effect, Layer, Scope } from "effect";
@@ -21,7 +21,7 @@ const apiOf = (group, endpoints) => {
21
21
  const endpointOf = (action, path, errors) => HttpApiEndpoint.post(action.name, path, {
22
22
  payload: action.input.pipe(HttpApiSchema.asJson()),
23
23
  success: action.success,
24
- error: errors.flatMap(declared)
24
+ error: errors.map(declared)
25
25
  });
26
26
  const withinLayerMiddleware = (endpoint, middleware = []) => middleware.reduce((inner, key) => inner.middleware(key), endpoint);
27
27
  function make(actions, options = {}) {