@gjermundgaraba/effect-actions 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +102 -41
  2. package/dist/Action.d.mts +31 -117
  3. package/dist/Action.mjs +14 -53
  4. package/dist/ActionCli.d.mts +60 -28
  5. package/dist/ActionCli.mjs +54 -48
  6. package/dist/ActionHttp.d.mts +18 -67
  7. package/dist/ActionHttp.mjs +20 -70
  8. package/dist/ActionMcp.d.mts +23 -29
  9. package/dist/ActionMcp.mjs +56 -114
  10. package/dist/ActionRpc.d.mts +147 -0
  11. package/dist/ActionRpc.mjs +113 -0
  12. package/dist/ActionToolkit.d.mts +28 -9
  13. package/dist/ActionToolkit.mjs +31 -4
  14. package/dist/Authentication.d.mts +15 -15
  15. package/dist/Authentication.mjs +42 -96
  16. package/dist/Testing.d.mts +30 -23
  17. package/dist/Testing.mjs +27 -44
  18. package/dist/{internal/authentication.d.mts → authentication/provider.d.mts} +41 -9
  19. package/dist/authentication/provider.mjs +12 -0
  20. package/dist/authentication/refusal.mjs +36 -0
  21. package/dist/{internal/cli.d.mts → cli/commands.d.mts} +16 -12
  22. package/dist/cli/commands.mjs +302 -0
  23. package/dist/{internal → contract}/call.d.mts +2 -2
  24. package/dist/contract/call.mjs +10 -0
  25. package/dist/{internal → contract}/errors.d.mts +13 -10
  26. package/dist/{internal → contract}/errors.mjs +9 -14
  27. package/dist/{internal → contract}/implementation.d.mts +37 -21
  28. package/dist/{internal → contract}/implementation.mjs +18 -49
  29. package/dist/contract/media.d.mts +13 -0
  30. package/dist/contract/media.mjs +18 -0
  31. package/dist/contract/rules.d.mts +25 -0
  32. package/dist/{internal/actions.mjs → contract/rules.mjs} +6 -42
  33. package/dist/{internal → http}/client.d.mts +7 -9
  34. package/dist/http/client.mjs +17 -0
  35. package/dist/http/declared.mjs +20 -0
  36. package/dist/http/schema-errors.mjs +9 -0
  37. package/dist/mcp/protocol.d.mts +18 -0
  38. package/dist/{internal/mcp.mjs → mcp/protocol.mjs} +29 -62
  39. package/dist/stdio/console.mjs +64 -0
  40. package/dist/{internal → testing}/memory.d.mts +1 -1
  41. package/dist/{internal → testing}/memory.mjs +5 -17
  42. package/dist/toolkit/tools.mjs +29 -0
  43. package/docs/Action-failures.md +47 -0
  44. package/docs/Action-in-process.md +93 -0
  45. package/docs/Action.md +211 -426
  46. package/docs/ActionCli-failures.md +104 -0
  47. package/docs/ActionCli.md +208 -293
  48. package/docs/ActionHttp-failures.md +83 -0
  49. package/docs/ActionHttp.md +205 -282
  50. package/docs/ActionMcp-failures.md +31 -0
  51. package/docs/ActionMcp.md +222 -271
  52. package/docs/ActionRpc-failures.md +21 -0
  53. package/docs/ActionRpc.md +179 -0
  54. package/docs/ActionToolkit-failures.md +15 -0
  55. package/docs/ActionToolkit.md +103 -119
  56. package/docs/Authentication-advanced.md +139 -0
  57. package/docs/Authentication-failures.md +99 -0
  58. package/docs/Authentication.md +143 -392
  59. package/docs/CONTEXT.md +40 -24
  60. package/docs/README.md +75 -35
  61. package/docs/Testing-failures.md +21 -0
  62. package/docs/Testing.md +193 -151
  63. package/docs/guarantees.md +35 -96
  64. package/docs/setup-failures.md +37 -0
  65. package/docs/setup.md +90 -92
  66. package/examples/README.md +68 -0
  67. package/examples/assistant/README.md +92 -0
  68. package/examples/assistant/src/agent.ts +92 -0
  69. package/examples/assistant/src/cli.ts +34 -0
  70. package/examples/assistant/src/commands.ts +25 -0
  71. package/examples/assistant/src/identity/Identity.ts +33 -0
  72. package/examples/assistant/src/identity/authorize.ts +24 -0
  73. package/examples/assistant/src/identity/handlers.ts +10 -0
  74. package/examples/assistant/src/identity/members.ts +9 -0
  75. package/examples/assistant/src/identity/permissions.ts +23 -0
  76. package/examples/assistant/src/scripted.ts +23 -0
  77. package/examples/assistant/src/tasks/Tasks.ts +63 -0
  78. package/examples/assistant/src/tasks/handlers.ts +25 -0
  79. package/examples/assistant/src/tasks/store.ts +96 -0
  80. package/examples/assistant/src/toolkit.ts +30 -0
  81. package/examples/assistant/test/commands.test.ts +57 -0
  82. package/examples/assistant/test/identity/identity.test.ts +43 -0
  83. package/examples/assistant/test/tasks/store.test.ts +48 -0
  84. package/examples/assistant/test/tasks/tasks.test.ts +75 -0
  85. package/examples/assistant/test/toolkit.test.ts +121 -0
  86. package/examples/kitchen-sink/README.md +318 -0
  87. package/examples/kitchen-sink/src/Actions.ts +19 -0
  88. package/examples/kitchen-sink/src/Api.ts +24 -0
  89. package/examples/kitchen-sink/src/Rpc.ts +12 -0
  90. package/examples/kitchen-sink/src/admin/Admin.ts +59 -0
  91. package/examples/kitchen-sink/src/admin/authenticate.ts +18 -0
  92. package/examples/kitchen-sink/src/admin/authorize.ts +27 -0
  93. package/examples/kitchen-sink/src/admin/grants.ts +22 -0
  94. package/examples/kitchen-sink/src/admin/handlers.ts +21 -0
  95. package/examples/kitchen-sink/src/admin/keys.ts +8 -0
  96. package/examples/kitchen-sink/src/agent.ts +47 -0
  97. package/examples/kitchen-sink/src/assistant/Assistant.ts +82 -0
  98. package/examples/kitchen-sink/src/assistant/conversations.ts +71 -0
  99. package/examples/kitchen-sink/src/assistant/demo-model.ts +33 -0
  100. package/examples/kitchen-sink/src/assistant/handlers.ts +126 -0
  101. package/examples/kitchen-sink/src/attachments/Attachments.ts +48 -0
  102. package/examples/kitchen-sink/src/attachments/download.ts +49 -0
  103. package/examples/kitchen-sink/src/attachments/handlers.ts +38 -0
  104. package/examples/kitchen-sink/src/attachments/store.ts +58 -0
  105. package/examples/kitchen-sink/src/browser.ts +33 -0
  106. package/examples/kitchen-sink/src/cli.ts +85 -0
  107. package/examples/kitchen-sink/src/config.ts +18 -0
  108. package/examples/kitchen-sink/src/identity/Identity.ts +61 -0
  109. package/examples/kitchen-sink/src/identity/authenticate.ts +87 -0
  110. package/examples/kitchen-sink/src/identity/authorize.ts +16 -0
  111. package/examples/kitchen-sink/src/identity/directory.ts +52 -0
  112. package/examples/kitchen-sink/src/identity/handlers.ts +9 -0
  113. package/examples/kitchen-sink/src/identity/members.ts +13 -0
  114. package/examples/kitchen-sink/src/identity/quota.ts +62 -0
  115. package/examples/kitchen-sink/src/issues/Issues.ts +111 -0
  116. package/examples/kitchen-sink/src/issues/assistant.ts +23 -0
  117. package/examples/kitchen-sink/src/issues/guide.ts +37 -0
  118. package/examples/kitchen-sink/src/issues/handlers.ts +37 -0
  119. package/examples/kitchen-sink/src/issues/store.ts +96 -0
  120. package/examples/kitchen-sink/src/issues/write-limit.ts +42 -0
  121. package/examples/kitchen-sink/src/memory.ts +30 -0
  122. package/examples/kitchen-sink/src/ops.ts +42 -0
  123. package/examples/kitchen-sink/src/request-policy.ts +63 -0
  124. package/examples/kitchen-sink/src/routes.ts +120 -0
  125. package/examples/kitchen-sink/src/sdk.ts +46 -0
  126. package/examples/kitchen-sink/src/seed.ts +65 -0
  127. package/examples/kitchen-sink/src/server.ts +62 -0
  128. package/examples/kitchen-sink/src/stdio.ts +34 -0
  129. package/examples/kitchen-sink/src/workspaces/Workspaces.ts +28 -0
  130. package/examples/kitchen-sink/src/workspaces/handlers.ts +23 -0
  131. package/examples/kitchen-sink/src/workspaces/registry.ts +46 -0
  132. package/examples/kitchen-sink/src/workspaces/tenant.ts +17 -0
  133. package/examples/kitchen-sink/test/admin/admin.test.ts +67 -0
  134. package/examples/kitchen-sink/test/assistant/assistant.test.ts +435 -0
  135. package/examples/kitchen-sink/test/attachments/attachments.test.ts +161 -0
  136. package/examples/kitchen-sink/test/identity/identity.test.ts +137 -0
  137. package/examples/kitchen-sink/test/identity/session.test.ts +110 -0
  138. package/examples/kitchen-sink/test/issues/assistant.test.ts +75 -0
  139. package/examples/kitchen-sink/test/issues/issues.test.ts +135 -0
  140. package/examples/kitchen-sink/test/request-policy.test.ts +125 -0
  141. package/examples/kitchen-sink/test/routes.test.ts +123 -0
  142. package/examples/kitchen-sink/test/support/scripted.ts +40 -0
  143. package/examples/live-rpc/README.md +107 -0
  144. package/examples/live-rpc/src/Rpc.ts +8 -0
  145. package/examples/live-rpc/src/browser.ts +98 -0
  146. package/examples/live-rpc/src/chat/Chat.ts +77 -0
  147. package/examples/live-rpc/src/chat/authorize.ts +16 -0
  148. package/examples/live-rpc/src/chat/handlers.ts +50 -0
  149. package/examples/live-rpc/src/chat/room.ts +23 -0
  150. package/examples/live-rpc/src/chat/store.ts +54 -0
  151. package/examples/live-rpc/src/client.ts +37 -0
  152. package/examples/live-rpc/src/identity/Identity.ts +19 -0
  153. package/examples/live-rpc/src/identity/members.ts +8 -0
  154. package/examples/live-rpc/src/identity/verifier.ts +23 -0
  155. package/examples/live-rpc/src/routes.ts +22 -0
  156. package/examples/live-rpc/src/server.ts +43 -0
  157. package/examples/live-rpc/test/browser.test.ts +105 -0
  158. package/examples/live-rpc/test/chat/chat.test.ts +69 -0
  159. package/examples/live-rpc/test/routes.test.ts +99 -0
  160. package/examples/live-rpc/test/server.test.ts +75 -0
  161. package/examples/mcp-tools/README.md +141 -0
  162. package/examples/mcp-tools/src/instructions.ts +12 -0
  163. package/examples/mcp-tools/src/printers/Printers.ts +121 -0
  164. package/examples/mcp-tools/src/printers/fake.ts +115 -0
  165. package/examples/mcp-tools/src/printers/farm.ts +136 -0
  166. package/examples/mcp-tools/src/printers/handlers.ts +29 -0
  167. package/examples/mcp-tools/src/routes.ts +52 -0
  168. package/examples/mcp-tools/src/server.ts +31 -0
  169. package/examples/mcp-tools/src/stdio.ts +26 -0
  170. package/examples/mcp-tools/test/printers/printers.test.ts +189 -0
  171. package/examples/mcp-tools/test/routes.test.ts +202 -0
  172. package/examples/notes-service/README.md +111 -0
  173. package/examples/notes-service/src/Api.ts +13 -0
  174. package/examples/notes-service/src/browser.ts +41 -0
  175. package/examples/notes-service/src/cli.ts +22 -0
  176. package/examples/notes-service/src/commands.ts +137 -0
  177. package/examples/notes-service/src/identity/Identity.ts +31 -0
  178. package/examples/notes-service/src/identity/authorize.ts +18 -0
  179. package/examples/notes-service/src/identity/handlers.ts +10 -0
  180. package/examples/notes-service/src/identity/users.ts +9 -0
  181. package/examples/notes-service/src/identity/verifier.ts +36 -0
  182. package/examples/notes-service/src/notes/Notes.ts +119 -0
  183. package/examples/notes-service/src/notes/handlers.ts +39 -0
  184. package/examples/notes-service/src/notes/store.ts +84 -0
  185. package/examples/notes-service/src/routes.ts +45 -0
  186. package/examples/notes-service/src/server.ts +26 -0
  187. package/examples/notes-service/src/status/Status.ts +10 -0
  188. package/examples/notes-service/src/status/handlers.ts +14 -0
  189. package/examples/notes-service/src/stdio.ts +25 -0
  190. package/examples/notes-service/test/commands.test.ts +288 -0
  191. package/examples/notes-service/test/notes/notes.test.ts +103 -0
  192. package/examples/notes-service/test/routes.test.ts +84 -0
  193. package/examples/quickstart/README.md +31 -0
  194. package/examples/quickstart/src/Api.ts +5 -0
  195. package/examples/quickstart/src/client.ts +13 -0
  196. package/examples/quickstart/src/greeting/Greeting.ts +12 -0
  197. package/examples/quickstart/src/greeting/handlers.ts +6 -0
  198. package/examples/quickstart/src/routes.ts +11 -0
  199. package/examples/quickstart/src/server.ts +16 -0
  200. package/examples/quickstart/test/greeting/greeting.test.ts +18 -0
  201. package/package.json +27 -9
  202. package/dist/internal/actions.d.mts +0 -9
  203. package/dist/internal/authentication.mjs +0 -11
  204. package/dist/internal/call.mjs +0 -12
  205. package/dist/internal/cli.mjs +0 -328
  206. package/dist/internal/client.mjs +0 -25
  207. package/dist/internal/console.mjs +0 -77
  208. package/dist/internal/declared.d.mts +0 -13
  209. package/dist/internal/declared.mjs +0 -34
  210. package/dist/internal/mcp.d.mts +0 -14
  211. package/dist/internal/media.d.mts +0 -8
  212. package/dist/internal/media.mjs +0 -26
  213. package/dist/internal/refusal.mjs +0 -67
  214. package/dist/internal/schema-errors.mjs +0 -20
  215. package/dist/internal/tools.mjs +0 -44
@@ -0,0 +1,302 @@
1
+ import { InvalidInput } from "../contract/errors.mjs";
2
+ import { assertDistinct, literalValues, members, projectedErrors, unsuspended } from "../contract/rules.mjs";
3
+ import { withStderrConsole } from "../stdio/console.mjs";
4
+ import { Console, Effect, Option, Predicate, Record, Runtime, Schema, SchemaAST, Stdio, Stream, flow } from "effect";
5
+ import { CliError, Command, Flag, Param } from "effect/cli";
6
+ //#region src/cli/commands.ts
7
+ const kebab = (name) => {
8
+ const kebabbed = name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
9
+ return kebabbed === "" ? name : kebabbed;
10
+ };
11
+ const renderedCommandJsonFlag = Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print machine-readable JSON"));
12
+ const jsonKinds = [
13
+ "null",
14
+ "boolean",
15
+ "number",
16
+ "string",
17
+ "array",
18
+ "object"
19
+ ];
20
+ const jsonKindOf = (value) => value === null ? "null" : Predicate.isBoolean(value) ? "boolean" : Predicate.isNumber(value) ? "number" : Predicate.isString(value) ? "string" : Array.isArray(value) ? "array" : "object";
21
+ const acceptedJsonKindsOf = (ast) => members(ast).flatMap((member) => {
22
+ if (SchemaAST.isLiteral(member)) return [jsonKindOf(member.literal)];
23
+ if (SchemaAST.isEnum(member)) return member.enums.map(([, value]) => jsonKindOf(value));
24
+ if (SchemaAST.isNull(member)) return ["null"];
25
+ if (SchemaAST.isBoolean(member)) return ["boolean"];
26
+ if (SchemaAST.isNumber(member)) return ["number"];
27
+ if (SchemaAST.isString(member) || SchemaAST.isTemplateLiteral(member)) return ["string"];
28
+ if (SchemaAST.isArrays(member)) return ["array"];
29
+ if (SchemaAST.isObjects(member)) return ["object"];
30
+ return jsonKinds;
31
+ });
32
+ const jsonOfAcceptedKindOrText = (encoded) => {
33
+ const accepted = new Set(acceptedJsonKindsOf(encoded));
34
+ return Schema.Union([Schema.fromJsonString(Schema.Json).check(Schema.makeFilter((json) => accepted.has(jsonKindOf(json)))), Schema.String]);
35
+ };
36
+ const stringChoicesOf = (ast) => {
37
+ const accepted = members(ast).flatMap(literalValues);
38
+ return accepted.every(Predicate.isString) ? accepted : void 0;
39
+ };
40
+ const takesTextAsIs = (encoded) => stringChoicesOf(encoded) !== void 0 || SchemaAST.isString(encoded) || SchemaAST.isTemplateLiteral(encoded);
41
+ const valueParam = (kind, name, field) => {
42
+ const encoded = unsuspended(field);
43
+ if (takesTextAsIs(encoded)) {
44
+ const literals = stringChoicesOf(encoded);
45
+ return literals === void 0 ? Param.String(kind, name) : Param.Literals(kind, name, literals);
46
+ }
47
+ if (SchemaAST.isBoolean(encoded)) return Param.Boolean(kind, name);
48
+ return Param.String(kind, name).pipe(Param.withSchema(jsonOfAcceptedKindOrText(encoded)), Param.withMetavar("value"));
49
+ };
50
+ const withoutOptionalAbsence = (ast) => {
51
+ const members = SchemaAST.isUnion(ast) ? ast.types.filter((member) => !SchemaAST.isNull(member) && !SchemaAST.isUndefined(member)) : [];
52
+ return members.length === 1 && members[0] !== void 0 ? members[0] : ast;
53
+ };
54
+ const optionalAddedNull = (plain) => {
55
+ const types = members(plain);
56
+ return types.some(SchemaAST.isUndefined) && !types.some(SchemaAST.isNull);
57
+ };
58
+ const repeatedElementOf = (ast) => {
59
+ const encoded = unsuspended(ast);
60
+ if (!SchemaAST.isArrays(encoded)) return void 0;
61
+ const [element, ...after] = encoded.rest;
62
+ if (element === void 0 || after.length > 0 || !encoded.elements.every((first) => first === element) || !acceptedJsonKindsOf(element).every((kind) => kind === "string" || kind === "number")) return;
63
+ return {
64
+ element,
65
+ min: encoded.elements.length
66
+ };
67
+ };
68
+ const noElementOccurrence = "[]";
69
+ const jsonHintOf = (encoded) => {
70
+ const value = unsuspended(encoded);
71
+ const kinds = new Set(acceptedJsonKindsOf(value));
72
+ if (kinds.size !== 1) return void 0;
73
+ if (kinds.has("object")) return "A JSON object.";
74
+ if (!kinds.has("array")) return void 0;
75
+ const fields = elementFieldsOf(value);
76
+ return fields === void 0 ? "A JSON array." : `A JSON array of {${fields.join(", ")}}.`;
77
+ };
78
+ const elementFieldsOf = (array) => {
79
+ if (!SchemaAST.isArrays(array)) return void 0;
80
+ const [element, ...others] = [...array.elements, ...array.rest];
81
+ if (element === void 0 || others.some((other) => other !== element)) return void 0;
82
+ const fields = namedFieldsOf(unsuspended(element));
83
+ return fields.size === 0 ? void 0 : Array.from(fields, ([name, field]) => SchemaAST.isOptional(field) ? `${String(name)}?` : String(name));
84
+ };
85
+ const withHint = (description, hint) => {
86
+ if (hint === void 0 || description === void 0) return description ?? hint;
87
+ const stated = description.trimEnd();
88
+ return /[.!?:]$/.test(stated) ? `${stated} ${hint}` : `${stated}. ${hint}`;
89
+ };
90
+ const fieldParam = (kind, name, encoded, plain) => {
91
+ const optional = SchemaAST.isOptional(encoded);
92
+ const value = optional && plain !== void 0 && optionalAddedNull(plain) ? withoutOptionalAbsence(encoded) : encoded;
93
+ const repeats = repeatedElementOf(value);
94
+ if (repeats !== void 0) {
95
+ const literals = stringChoicesOf(unsuspended(repeats.element));
96
+ const elements = literals === void 0 ? valueParam(kind, name, repeats.element) : Param.Literals(kind, name, [...literals, noElementOccurrence]);
97
+ const listed = (values) => Option.some(values.filter((value) => value !== noElementOccurrence));
98
+ return {
99
+ param: optional ? Param.variadic(elements).pipe(Param.map((values) => values.length === 0 ? Option.none() : listed(values))) : Param.variadic(elements, { min: repeats.min }).pipe(Param.map(listed)),
100
+ repeats: true,
101
+ hint: kind === Param.flagKind ? "Repeat for each value." : void 0
102
+ };
103
+ }
104
+ if (optional) return {
105
+ param: Param.optional(valueParam(kind, name, value)),
106
+ repeats: false,
107
+ hint: kind === Param.flagKind && SchemaAST.isBoolean(unsuspended(value)) ? `--no-${name} sends false.` : jsonHintOf(value)
108
+ };
109
+ const isSwitch = kind === Param.flagKind && SchemaAST.isBoolean(unsuspended(encoded));
110
+ return {
111
+ param: isSwitch ? Flag.Boolean(name).pipe(Flag.withDefault(false), Flag.map(Option.some)) : valueParam(kind, name, encoded).pipe(Param.map(Option.some)),
112
+ repeats: false,
113
+ hint: isSwitch ? "Left out, it sends false." : jsonHintOf(encoded)
114
+ };
115
+ };
116
+ const namedFieldsOf = (ast) => {
117
+ const fields = SchemaAST.isDeclaration(ast) ? ast.typeParameters[0] : ast;
118
+ return new Map(fields !== void 0 && SchemaAST.isObjects(fields) ? fields.propertySignatures.map((property) => [property.name, property.type]) : []);
119
+ };
120
+ const isTransformedAsWhole = (ast) => (SchemaAST.isDeclaration(ast) ? ast.typeParameters[0] : ast)?.encoding !== void 0;
121
+ const ownOrSuspendedDescriptionOf = (ast) => ast.context?.annotations?.description ?? SchemaAST.resolveDescription(ast) ?? describedBeforeLastCheck(ast) ?? (SchemaAST.isSuspend(ast) ? ownOrSuspendedDescriptionOf(ast.thunk()) : void 0);
122
+ const describedBeforeLastCheck = (ast) => [ast.annotations, ...(ast.checks ?? []).map((check) => check.annotations)].map((annotations) => annotations?.["description"]).findLast(Predicate.isString);
123
+ const describedThroughAbsence = (ast) => ownOrSuspendedDescriptionOf(ast) ?? ownOrSuspendedDescriptionOf(withoutOptionalAbsence(ast));
124
+ const describedByElement = (ast) => {
125
+ const array = unsuspended(withoutOptionalAbsence(ast));
126
+ const element = SchemaAST.isArrays(array) ? array.rest[0] : void 0;
127
+ return element === void 0 ? void 0 : describedThroughAbsence(element);
128
+ };
129
+ const fieldParams = (encoded, input, positional, aliases) => {
130
+ const plainEncodedFields = namedFieldsOf(SchemaAST.toEncoded(input));
131
+ const declaredFieldsUnlessRenamed = namedFieldsOf(isTransformedAsWhole(input) ? encoded : SchemaAST.toType(input));
132
+ return encoded.propertySignatures.map((property) => {
133
+ const field = String(property.name);
134
+ const declaredField = declaredFieldsUnlessRenamed.get(property.name);
135
+ const kind = positional.includes(field) ? Param.argumentKind : Param.flagKind;
136
+ const { param, repeats, hint } = fieldParam(kind, kebab(field), property.type, plainEncodedFields.get(property.name));
137
+ const alias = kind === Param.flagKind ? aliases[field] : void 0;
138
+ const aliased = alias === void 0 ? param : Param.withAlias(param, alias);
139
+ const documented = declaredField ?? property.type;
140
+ const description = describedThroughAbsence(documented) ?? describedThroughAbsence(property.type) ?? (repeats ? describedByElement(documented) ?? describedByElement(property.type) : void 0);
141
+ const help = withHint(description, hint);
142
+ return {
143
+ field,
144
+ param: help === void 0 ? aliased : Param.withDescription(aliased, help),
145
+ optional: SchemaAST.isOptional(property.type),
146
+ repeats,
147
+ hint,
148
+ description
149
+ };
150
+ });
151
+ };
152
+ const positionalOrder = (params, positional) => {
153
+ assertDistinct("positional argument", positional, (field) => field);
154
+ const ordered = positional.map((field) => {
155
+ const found = params.find((param) => param.field === field);
156
+ if (found === void 0) throw new Error(`Not an input field: ${field}`);
157
+ return found;
158
+ });
159
+ const firstOptional = ordered.findIndex((param) => param.optional);
160
+ const misplaced = ordered.slice(firstOptional === -1 ? ordered.length : firstOptional).find((param) => !param.optional);
161
+ if (misplaced !== void 0) throw new Error(`Required positional argument after an optional one: ${misplaced.field}`);
162
+ const repeatedBeforeLast = ordered.slice(0, -1).find((param) => param.repeats);
163
+ if (repeatedBeforeLast !== void 0) throw new Error(`Repeated positional argument before another one: ${repeatedBeforeLast.field}`);
164
+ return ordered;
165
+ };
166
+ const wholeInputFlag = (encoded) => Flag.String("input").pipe(Flag.withSchema(jsonOfAcceptedKindOrText(encoded)), Flag.withDescription("Whole action input as JSON; omitted, {}"), Flag.optional);
167
+ const printedOutput = (action, value, render) => Effect.map(Effect.orDie(Schema.encodeEffect(Schema.toCodecJson(action.success))(value)), (encoded) => render === void 0 && SchemaAST.isVoid(action.success.ast) ? Option.none() : Option.some(render === void 0 ? JSON.stringify(encoded, null, 2) : render(value)));
168
+ /**
169
+ * What a command fails with when its action fails: Effect CLI's `UserError`, typed with the
170
+ * action's failure as its `cause` and `reason`, and whose message is its JSON, which `Command.run`
171
+ * prints on stderr and marks reported, so `runMain` does not print it again. The process exits with
172
+ * the cause's `Runtime.errorExitCode`, 1 by default. A type only: after `Command.run`, match the
173
+ * reason by its tag, `Effect.catchReason("UserError", "UserNotFound", f)`. A command of your own
174
+ * fails with one through `toUserError`; any other failure is reported as a crash, `ERROR (#n)`.
175
+ */
176
+ var UserError = class extends CliError.UserError {
177
+ /**
178
+ * The action's failure, as Effect names an error's nested one, so the native
179
+ * `Effect.catchReason("UserError", "UserNotFound", f)` and `catchReasons` match it by tag.
180
+ */
181
+ get reason() {
182
+ return this.cause;
183
+ }
184
+ get [Runtime.errorExitCode]() {
185
+ return Runtime.getErrorExitCode(this.cause);
186
+ }
187
+ };
188
+ const ownStringProperty = (error, key) => {
189
+ if (!Predicate.hasProperty(error, key)) return void 0;
190
+ const value = error[key];
191
+ return Predicate.isString(value) ? value : void 0;
192
+ };
193
+ const describedWithoutFields = (error, seen = /* @__PURE__ */ new Set()) => {
194
+ const name = ownStringProperty(error, "_tag") ?? (error instanceof Error ? error.name : "Error");
195
+ const message = Predicate.isString(error) ? error : ownStringProperty(error, "message") ?? "";
196
+ const named = message === "" ? name : `${name}: ${message}`;
197
+ seen.add(error);
198
+ return Predicate.hasProperty(error, "cause") && error.cause !== void 0 && !seen.has(error.cause) ? `${named}: ${describedWithoutFields(error.cause, seen)}` : named;
199
+ };
200
+ const userErrorOf = (errors) => {
201
+ const encode = Schema.encodeUnknownEffect(Schema.fromJsonString(Schema.toCodecJson(Schema.Union(errors))));
202
+ return (error) => encode(error, { onExcessProperty: "error" }).pipe(Effect.catch(() => encode(error)), Effect.orElseSucceed(() => describedWithoutFields(error)), Effect.flatMap((userMessage) => Effect.fail(new UserError({
203
+ cause: error,
204
+ userMessage
205
+ }))));
206
+ };
207
+ const failingAsDerived = (actions, surfaceErrorsOf) => {
208
+ const failure = userErrorOf([...new Set(actions.flatMap((action) => projectedErrors(action, surfaceErrorsOf(action))))]);
209
+ return (self) => Effect.catchIf(self, (error) => !CliError.isCliError(error), failure);
210
+ };
211
+ const unreadableStdinField = (field, message) => new InvalidInput({
212
+ message,
213
+ issues: [{
214
+ path: [field],
215
+ message
216
+ }]
217
+ });
218
+ const fieldValueFromText = (field) => {
219
+ const encoded = unsuspended(field);
220
+ return takesTextAsIs(encoded) ? (text) => text : Schema.decodeSync(jsonOfAcceptedKindOrText(encoded));
221
+ };
222
+ const stdinTextOf = (field) => Effect.gen(function* () {
223
+ const stdio = yield* Effect.serviceOption(Stdio.Stdio);
224
+ if (Option.isNone(stdio)) return yield* Effect.die(/* @__PURE__ */ new Error("Stdio is not provided"));
225
+ if (yield* stdio.value.stdinIsTerminal) return yield* unreadableStdinField(field, `${field} is read from stdin, which is a terminal: pipe it in`);
226
+ return (yield* stdio.value.stdin.pipe(Stream.decodeText(), Stream.mkString, Effect.mapError((error) => unreadableStdinField(field, `stdin cannot be read: ${error.message}`)))).replace(/[\r\n]+$/, "");
227
+ });
228
+ const inputConfig = (action, positional, aliases, stdin) => {
229
+ const codec = Schema.toCodecJson(action.input);
230
+ const encoded = unsuspended(SchemaAST.toEncoded(codec.ast));
231
+ const decodeRefusingUndeclared = Schema.decodeUnknownEffect(codec, { onExcessProperty: "error" });
232
+ const decode = flow(decodeRefusingUndeclared, Effect.mapError(InvalidInput.fromSchemaError));
233
+ if (SchemaAST.isObjects(encoded) && encoded.indexSignatures.every((signature) => SchemaAST.isNever(signature.type))) {
234
+ const params = fieldParams(encoded, unsuspended(action.input.ast), positional, aliases);
235
+ const fromStdin = Option.map(Option.fromUndefinedOr(stdin), (field) => {
236
+ const property = encoded.propertySignatures.find(({ name }) => name === field);
237
+ if (property === void 0 || SchemaAST.isOptional(property.type)) throw new Error(`Not a required input field: ${field}`);
238
+ if (positional.includes(field)) throw new Error(`Both positional and stdin: ${field}`);
239
+ const description = params.find((param) => param.field === field)?.description;
240
+ return {
241
+ field,
242
+ value: fieldValueFromText(property.type),
243
+ help: `Reads ${kebab(field)} from stdin${description === void 0 ? "." : `: ${description}`}`
244
+ };
245
+ });
246
+ const flagged = new Set(params.map(({ field }) => field).filter((field) => !positional.includes(field) && field !== stdin));
247
+ const stray = Object.keys(aliases).find((field) => !flagged.has(field));
248
+ if (stray !== void 0) throw new Error(`Not a flag's input field: ${stray}`);
249
+ return {
250
+ flags: Object.fromEntries(params.flatMap(({ field, param }) => flagged.has(field) ? [[field, param]] : [])),
251
+ positional: positionalOrder(params, positional),
252
+ decode: (parsed) => Option.match(fromStdin, {
253
+ onNone: () => decode(Record.getSomes(parsed)),
254
+ onSome: ({ field, value }) => Effect.flatMap(stdinTextOf(field), (text) => decode({
255
+ ...Record.getSomes(parsed),
256
+ [field]: value(text)
257
+ }))
258
+ }),
259
+ stdinHelp: Option.getOrUndefined(Option.map(fromStdin, ({ help }) => help))
260
+ };
261
+ }
262
+ if (positional.length > 0) throw new Error(`Positional arguments need named input fields: ${positional.join(", ")}`);
263
+ if (Object.keys(aliases).length > 0) throw new Error(`Aliases need named input fields: ${Object.keys(aliases).join(", ")}`);
264
+ if (stdin !== void 0) throw new Error(`Stdin needs named input fields: ${stdin}`);
265
+ return {
266
+ flags: { input: wholeInputFlag(encoded) },
267
+ positional: [],
268
+ decode: (parsed) => decode(Option.getOrElse(parsed["input"] ?? Option.none(), () => ({}))),
269
+ stdinHelp: void 0
270
+ };
271
+ };
272
+ const command = (action, execute, options, errors = []) => {
273
+ const name = options?.name ?? kebab(action.name);
274
+ const render = options?.render;
275
+ const { flags, positional, decode, stdinHelp } = inputConfig(action, options?.positional ?? [], options?.aliases ?? {}, options?.stdin);
276
+ const failure = userErrorOf(projectedErrors(action, errors));
277
+ assertDistinct("flag", [
278
+ ...Object.keys(flags).map((field) => [`--${kebab(field)}`, `field ${field}`]),
279
+ ...render === void 0 ? [] : [["--json", "render's --json"]],
280
+ ...Object.entries(options?.aliases ?? {}).flatMap(([field, alias]) => Predicate.isString(alias) ? [[`--${alias.replace(/^-+/, "")}`, `alias of field ${field}`]] : [])
281
+ ], ([flag]) => flag, ([, claimant]) => claimant);
282
+ const args = positional.map(({ param }) => param);
283
+ const parsedFields = (input, values) => ({
284
+ ...input,
285
+ ...Object.fromEntries(positional.map(({ field }, index) => [field, values[index] ?? Option.none()]))
286
+ });
287
+ const run = (parsed, rendered) => decode(parsed).pipe(Effect.flatMap(execute), Effect.catch(failure), Effect.flatMap((value) => printedOutput(action, value, rendered)), withStderrConsole, Effect.flatMap(Option.match({
288
+ onNone: () => Effect.void,
289
+ onSome: Console.log
290
+ })));
291
+ const command = render === void 0 ? Command.make(name, {
292
+ input: flags,
293
+ args
294
+ }, ({ input, args: values }) => run(parsedFields(input, values), void 0)) : Command.make(name, {
295
+ input: flags,
296
+ args,
297
+ json: renderedCommandJsonFlag
298
+ }, ({ input, args: values, json }) => run(parsedFields(input, values), json ? void 0 : render));
299
+ return stdinHelp === void 0 ? Command.withDescription(command, action.description) : command.pipe(Command.withDescription(`${action.description}\n\n${stdinHelp}`), Command.withShortDescription(action.description));
300
+ };
301
+ //#endregion
302
+ export { UserError, command, failingAsDerived, kebab };
@@ -1,12 +1,12 @@
1
1
  import "./implementation.mjs";
2
2
  import { Action_d_exports, Any } from "../Action.mjs";
3
3
  import { Effect, Schema } from "effect";
4
- //#region src/internal/call.d.ts
4
+ //#region src/contract/call.d.ts
5
5
  /**
6
6
  * A call of `A` answering `R`. It may leave its input out when `{}` is a valid encoded input,
7
7
  * such as for an action declared without `input` or one whose every field has a decoding
8
8
  * default, and then sends the input `{}` decodes to. Every client shares this rule:
9
- * `ActionHttp.client`, `Testing.mcpClient` and `Action.client`.
9
+ * `ActionHttp.client`, `ActionRpc.client`, `Testing.mcpClient` and `Action.client`.
10
10
  */
11
11
  export type Call<A extends Any, R> = {} extends A["input"]["Encoded"] ? (...input: [] | [input: A["input"]["Type"]]) => R : (input: A["input"]["Type"]) => R;
12
12
  //#endregion
@@ -0,0 +1,10 @@
1
+ import { InvalidInput } from "./errors.mjs";
2
+ import { Effect, Schema } from "effect";
3
+ //#region src/contract/call.ts
4
+ const inputOf = (action, args) => args.length === 0 ? Schema.decodeUnknownEffect(Schema.toCodecJson(action.input))({}) : Effect.succeed(args[0]);
5
+ const checked = (action, send) => {
6
+ const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(action.input), { errors: "all" });
7
+ return (...args) => inputOf(action, args).pipe(Effect.tap(encode), Effect.mapError(InvalidInput.fromSchemaError), Effect.flatMap(send));
8
+ };
9
+ //#endregion
10
+ export { checked, inputOf };
@@ -1,14 +1,14 @@
1
1
  import { Schema } from "effect";
2
- //#region src/internal/errors.d.ts
3
- /**
4
- * One thing wrong with an input: where, as the keys and indexes leading to it from the input's
5
- * root, none for the input itself, and what the schema expects there.
6
- */
2
+ //#region src/contract/errors.d.ts
7
3
  declare const Issue: Schema.Struct<{
8
4
  readonly path: Schema.$Array<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
9
5
  readonly message: Schema.String;
10
6
  }>;
11
- /** One thing wrong with an input, as `InvalidInput.issues` lists it. */
7
+ /**
8
+ * One thing wrong with an input, as `InvalidInput.issues` lists it: where, as the keys and
9
+ * indexes leading to it from the input's root, none for the input itself, and what the schema
10
+ * expects there.
11
+ */
12
12
  type Issue = (typeof Issue)["Type"];
13
13
  declare const InvalidInput_base: Schema.Class<InvalidInput, Schema.TaggedStruct<"InvalidInput", {
14
14
  readonly message: Schema.withConstructorDefault<Schema.String>;
@@ -24,7 +24,7 @@ declare const InvalidInput_base: Schema.Class<InvalidInput, Schema.TaggedStruct<
24
24
  * failing with it for input that decodes but cannot be served may name its own.
25
25
  */
26
26
  export declare class InvalidInput extends InvalidInput_base {
27
- private readonly "~builtIn";
27
+ private readonly "~declared in the action's error, or built in";
28
28
  /**
29
29
  * The `InvalidInput` for input that did not decode, as every surface answers it: the
30
30
  * schema's message, and each of its issues by path, for code that decodes input of its own,
@@ -40,7 +40,7 @@ declare const Unauthenticated_base: Schema.Class<Unauthenticated, Schema.TaggedS
40
40
  }>, import("effect/Cause").YieldableError>;
41
41
  /** The caller is not authenticated: authentication or an implementation's `authorize` refuses. */
42
42
  export declare class Unauthenticated extends Unauthenticated_base {
43
- private readonly "~builtIn";
43
+ private readonly "~declared in the action's error, or built in";
44
44
  }
45
45
  declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbidden", {
46
46
  readonly message: Schema.withConstructorDefault<Schema.String>;
@@ -49,10 +49,13 @@ declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbi
49
49
  /**
50
50
  * The caller may not run this action: an implementation's `authorize` refuses. `scopes`,
51
51
  * when given, are every OAuth scope the call needs: a refusal naming them is answered with
52
- * the `insufficient_scope` challenge an OAuth client re-authorizes on.
52
+ * the `insufficient_scope` challenge an OAuth client re-authorizes on, so name them only where
53
+ * re-authorizing can grant them. Over HTTP it is a 403 either way. Over MCP, from an authorizer
54
+ * or a handler, it is a 403 only when it names scopes on an endpoint under Bearer, and
55
+ * otherwise a tool result with `isError: true`, in a 200 response.
53
56
  */
54
57
  export declare class Forbidden extends Forbidden_base {
55
- private readonly "~builtIn";
58
+ private readonly "~declared in the action's error, or built in";
56
59
  }
57
60
  /**
58
61
  * What authentication or an implementation's `authorize` refuses a caller with, instead of
@@ -1,17 +1,11 @@
1
1
  import { Effect, Predicate, Schema, SchemaIssue } from "effect";
2
- //#region src/internal/errors.ts
3
- /** A message field that defaults, so `new Forbidden()` needs no argument. */
4
- const message = (fallback) => Schema.String.pipe(Schema.withConstructorDefault(Effect.succeed(fallback)));
5
- /** Each built-in error's HTTP status, by tag: its annotation, and what a refusal answers. */
2
+ //#region src/contract/errors.ts
3
+ const defaultedMessage = (fallback) => Schema.String.pipe(Schema.withConstructorDefault(Effect.succeed(fallback)));
6
4
  const statuses = {
7
5
  InvalidInput: 400,
8
6
  Unauthenticated: 401,
9
7
  Forbidden: 403
10
8
  };
11
- /**
12
- * One thing wrong with an input: where, as the keys and indexes leading to it from the input's
13
- * root, none for the input itself, and what the schema expects there.
14
- */
15
9
  const Issue = Schema.Struct({
16
10
  path: Schema.Array(Schema.Union([Schema.String, Schema.Finite])),
17
11
  message: Schema.String
@@ -24,7 +18,7 @@ const standardIssues = SchemaIssue.makeFormatterStandardSchemaV1();
24
18
  * failing with it for input that decodes but cannot be served may name its own.
25
19
  */
26
20
  var InvalidInput = class InvalidInput extends Schema.TaggedError()("InvalidInput", {
27
- message: message("The input does not match the action's input."),
21
+ message: defaultedMessage("The input does not match the action's input."),
28
22
  issues: Schema.Array(Issue).pipe(Schema.withConstructorDefault(Effect.succeed([])))
29
23
  }, { httpApiStatus: statuses.InvalidInput }) {
30
24
  /**
@@ -47,20 +41,21 @@ var InvalidInput = class InvalidInput extends Schema.TaggedError()("InvalidInput
47
41
  });
48
42
  };
49
43
  /** The caller is not authenticated: authentication or an implementation's `authorize` refuses. */
50
- var Unauthenticated = class extends Schema.TaggedError()("Unauthenticated", { message: message("Authentication is required.") }, { httpApiStatus: statuses.Unauthenticated }) {};
51
- /** An OAuth scope token (RFC 6749 §3.3): printable ASCII but space, `"` and `\`. */
44
+ var Unauthenticated = class extends Schema.TaggedError()("Unauthenticated", { message: defaultedMessage("Authentication is required.") }, { httpApiStatus: statuses.Unauthenticated }) {};
52
45
  const scopeToken = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
53
46
  const ScopeToken = Schema.String.check(Schema.isPattern(scopeToken));
54
47
  /**
55
48
  * The caller may not run this action: an implementation's `authorize` refuses. `scopes`,
56
49
  * when given, are every OAuth scope the call needs: a refusal naming them is answered with
57
- * the `insufficient_scope` challenge an OAuth client re-authorizes on.
50
+ * the `insufficient_scope` challenge an OAuth client re-authorizes on, so name them only where
51
+ * re-authorizing can grant them. Over HTTP it is a 403 either way. Over MCP, from an authorizer
52
+ * or a handler, it is a 403 only when it names scopes on an endpoint under Bearer, and
53
+ * otherwise a tool result with `isError: true`, in a 200 response.
58
54
  */
59
55
  var Forbidden = class extends Schema.TaggedError()("Forbidden", {
60
- message: message("Not allowed."),
56
+ message: defaultedMessage("Not allowed."),
61
57
  scopes: Schema.optionalKey(Schema.NonEmptyArray(ScopeToken))
62
58
  }, { httpApiStatus: statuses.Forbidden }) {};
63
- /** The refusals, as schemas. */
64
59
  const refusals = [Unauthenticated, Forbidden];
65
60
  /**
66
61
  * What authentication or an implementation's `authorize` refuses a caller with, instead of
@@ -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;