@afora/core 0.0.8 → 0.0.11

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 (226) hide show
  1. package/README.md +149 -23
  2. package/dist/adapters.d.ts +25 -0
  3. package/dist/adapters.js +32 -0
  4. package/dist/core/application/application.d.ts +30 -0
  5. package/dist/core/application/application.js +1291 -0
  6. package/dist/core/application/exceptions.d.ts +17 -0
  7. package/dist/core/application/exceptions.js +85 -0
  8. package/dist/core/application/migrate.d.ts +19 -0
  9. package/dist/core/application/migrate.js +67 -0
  10. package/dist/core/application/providers.js +108 -0
  11. package/dist/core/application/registry.js +23 -0
  12. package/dist/core/application/schema.d.ts +30 -0
  13. package/dist/core/application/schema.js +398 -0
  14. package/dist/core/application/start.js +162 -0
  15. package/dist/core/application/types/application.interface.d.ts +60 -3
  16. package/dist/core/auth/jwt.d.ts +30 -0
  17. package/dist/core/auth/jwt.js +200 -0
  18. package/dist/core/auth/types/jwt.interface.d.ts +74 -0
  19. package/dist/core/blueprint/blueprint-plugin.js +7 -0
  20. package/dist/core/blueprint/blueprint.js +146 -0
  21. package/dist/core/blueprint/callable.js +13 -0
  22. package/dist/core/blueprint/exceptions.d.ts +5 -0
  23. package/dist/core/blueprint/exceptions.js +20 -0
  24. package/dist/core/blueprint/is-blueprint.js +4 -0
  25. package/dist/core/blueprint/list.js +19 -0
  26. package/dist/core/blueprint/optional.d.ts +3 -1
  27. package/dist/core/blueprint/optional.js +69 -0
  28. package/dist/core/blueprint/record-field.js +14 -0
  29. package/dist/core/blueprint/slice.js +127 -0
  30. package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
  31. package/dist/core/blueprint/variant.d.ts +32 -0
  32. package/dist/core/blueprint/variant.js +72 -0
  33. package/dist/core/command/command.js +428 -0
  34. package/dist/core/command/exceptions.js +30 -0
  35. package/dist/core/command/types/command.interface.d.ts +10 -1
  36. package/dist/core/entity/abstract-entity.d.ts +24 -0
  37. package/dist/core/entity/abstract-entity.js +181 -0
  38. package/dist/core/entity/entity.d.ts +1 -1
  39. package/dist/core/entity/entity.js +209 -0
  40. package/dist/core/entity/event.d.ts +39 -0
  41. package/dist/core/entity/event.js +80 -0
  42. package/dist/core/entity/exceptions.d.ts +9 -0
  43. package/dist/core/entity/exceptions.js +27 -0
  44. package/dist/core/entity/trust.d.ts +24 -0
  45. package/dist/core/entity/trust.js +7 -0
  46. package/dist/core/entity/types/entity.interface.d.ts +30 -0
  47. package/dist/core/entity/version.js +13 -0
  48. package/dist/core/environment/environment.d.ts +26 -3
  49. package/dist/core/environment/environment.js +71 -0
  50. package/dist/core/event-policy/event-policy.js +76 -0
  51. package/dist/core/event-policy/exceptions.js +12 -0
  52. package/dist/core/event-policy/publish.js +98 -0
  53. package/dist/core/event-policy/publishers.js +18 -0
  54. package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
  55. package/dist/core/exception/enums/exception-layers.js +15 -0
  56. package/dist/core/exception/exception.d.ts +15 -0
  57. package/dist/core/exception/exception.js +128 -0
  58. package/dist/core/exception/exported.js +49 -0
  59. package/dist/core/exception/guard.d.ts +20 -1
  60. package/dist/core/exception/guard.js +16 -0
  61. package/dist/core/exception/types/exception.interface.d.ts +13 -0
  62. package/dist/core/json.js +19 -0
  63. package/dist/core/logger/exceptions.js +7 -0
  64. package/dist/core/logger/logger.d.ts +14 -1
  65. package/dist/core/logger/logger.js +178 -0
  66. package/dist/core/openapi/exceptions.js +7 -0
  67. package/dist/core/openapi/openapi.js +132 -0
  68. package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
  69. package/dist/core/output/access.d.ts +16 -0
  70. package/dist/core/output/access.js +66 -0
  71. package/dist/core/output/coerce.js +38 -0
  72. package/dist/core/output/exceptions.d.ts +16 -0
  73. package/dist/core/output/exceptions.js +22 -0
  74. package/dist/core/output/http.d.ts +36 -0
  75. package/dist/core/output/http.js +351 -0
  76. package/dist/core/output/openapi-sink.d.ts +33 -4
  77. package/dist/core/output/openapi-sink.js +571 -0
  78. package/dist/core/output/output.js +192 -0
  79. package/dist/core/output/problem.js +30 -0
  80. package/dist/core/output/select.js +28 -0
  81. package/dist/core/output/stage.d.ts +7 -2
  82. package/dist/core/output/stage.js +111 -0
  83. package/dist/core/output/status.d.ts +1 -1
  84. package/dist/core/output/status.js +52 -0
  85. package/dist/core/output/types/http.interface.d.ts +134 -0
  86. package/dist/core/output/types/output.interface.d.ts +49 -5
  87. package/dist/core/parse/issue.d.ts +28 -3
  88. package/dist/core/parse/issue.js +31 -0
  89. package/dist/core/parse/parse.d.ts +16 -0
  90. package/dist/core/parse/parse.js +323 -0
  91. package/dist/core/parse/strip.js +48 -0
  92. package/dist/core/property/abstract-property.d.ts +9 -0
  93. package/dist/core/property/abstract-property.js +37 -0
  94. package/dist/core/property/define-property.d.ts +34 -6
  95. package/dist/core/property/define-property.js +209 -0
  96. package/dist/core/property/enums/property-states.js +8 -0
  97. package/dist/core/property/exceptions.js +12 -0
  98. package/dist/core/property/input.js +10 -0
  99. package/dist/core/property/is-property.js +3 -0
  100. package/dist/core/property/lineage.js +8 -0
  101. package/dist/core/property/property.d.ts +27 -0
  102. package/dist/core/property/property.js +46 -0
  103. package/dist/core/property/refine.js +103 -0
  104. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  105. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  106. package/dist/core/record/abstract-record.d.ts +8 -0
  107. package/dist/core/record/abstract-record.js +307 -0
  108. package/dist/core/record/exceptions.js +49 -0
  109. package/dist/core/record/marks.js +7 -0
  110. package/dist/core/record/mask.js +60 -0
  111. package/dist/core/record/name.js +23 -0
  112. package/dist/core/record/plugin-catalog.js +115 -0
  113. package/dist/core/record/plugin.js +198 -0
  114. package/dist/core/record/record.js +117 -0
  115. package/dist/core/record/types/record.interface.d.ts +17 -1
  116. package/dist/core/repository/cursor.js +83 -0
  117. package/dist/core/repository/exceptions.d.ts +13 -0
  118. package/dist/core/repository/exceptions.js +72 -0
  119. package/dist/core/repository/in-memory.d.ts +2 -2
  120. package/dist/core/repository/in-memory.js +196 -0
  121. package/dist/core/repository/migrate.d.ts +20 -0
  122. package/dist/core/repository/migrate.js +254 -0
  123. package/dist/core/repository/plan.js +27 -0
  124. package/dist/core/repository/provider.js +29 -0
  125. package/dist/core/repository/query.js +153 -0
  126. package/dist/core/repository/references.js +196 -0
  127. package/dist/core/repository/repository.d.ts +22 -2
  128. package/dist/core/repository/repository.js +720 -0
  129. package/dist/core/repository/state.d.ts +2 -0
  130. package/dist/core/repository/state.js +138 -0
  131. package/dist/core/repository/types/query.interface.d.ts +13 -0
  132. package/dist/core/repository/types/repository.interface.d.ts +50 -5
  133. package/dist/core/repository/types/schema.interface.d.ts +132 -0
  134. package/dist/core/repository/upcast.d.ts +64 -0
  135. package/dist/core/repository/upcast.js +179 -0
  136. package/dist/core/schema/schema.d.ts +37 -0
  137. package/dist/core/schema/schema.js +542 -0
  138. package/dist/core/standard/standard-schema.js +1 -0
  139. package/dist/core/standard/standard.js +23 -0
  140. package/dist/core/symbols/demo.d.ts +4 -0
  141. package/dist/core/symbols/demo.js +1 -0
  142. package/dist/core/symbols/kind.d.ts +1 -1
  143. package/dist/core/symbols/kind.js +1 -0
  144. package/dist/core/symbols/meta.js +1 -0
  145. package/dist/core/symbols/state.d.ts +5 -0
  146. package/dist/core/symbols/state.js +1 -0
  147. package/dist/core/tracing/exceptions.d.ts +4 -0
  148. package/dist/core/tracing/exceptions.js +7 -0
  149. package/dist/core/tracing/tracing.d.ts +72 -0
  150. package/dist/core/tracing/tracing.js +101 -0
  151. package/dist/core/use-case/access.d.ts +12 -0
  152. package/dist/core/use-case/access.js +56 -0
  153. package/dist/core/use-case/decorators.d.ts +40 -0
  154. package/dist/core/use-case/decorators.js +65 -0
  155. package/dist/core/use-case/exceptions.d.ts +5 -1
  156. package/dist/core/use-case/exceptions.js +48 -0
  157. package/dist/core/use-case/plugin-catalog.js +68 -0
  158. package/dist/core/use-case/plugin.js +133 -0
  159. package/dist/core/use-case/route.d.ts +18 -0
  160. package/dist/core/use-case/route.js +200 -0
  161. package/dist/core/use-case/scope.d.ts +5 -0
  162. package/dist/core/use-case/scope.js +4 -0
  163. package/dist/core/use-case/steps/describe.d.ts +5 -1
  164. package/dist/core/use-case/steps/describe.js +24 -0
  165. package/dist/core/use-case/steps/references.js +105 -0
  166. package/dist/core/use-case/steps/run.js +139 -0
  167. package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
  168. package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
  169. package/dist/core/use-case/steps/validate.js +368 -0
  170. package/dist/core/use-case/target.js +100 -0
  171. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  172. package/dist/core/use-case/types/route.interface.d.ts +86 -0
  173. package/dist/core/use-case/use-case.d.ts +127 -2
  174. package/dist/core/use-case/use-case.js +493 -0
  175. package/dist/experimental.d.ts +18 -1
  176. package/dist/experimental.js +18 -123
  177. package/dist/index.d.ts +49 -51
  178. package/dist/index.js +213 -443
  179. package/dist/std/blueprint-plugin.js +42 -0
  180. package/dist/std/blueprint.d.ts +21 -0
  181. package/dist/std/blueprint.js +3 -0
  182. package/dist/std/command.js +3 -0
  183. package/dist/std/entity.d.ts +20 -1
  184. package/dist/std/entity.js +30 -0
  185. package/dist/std/event-policies.js +3 -0
  186. package/dist/std/properties/contact.d.ts +3 -0
  187. package/dist/std/properties/contact.js +29 -0
  188. package/dist/std/properties/enum.js +26 -0
  189. package/dist/std/properties/exceptions.js +14 -0
  190. package/dist/std/properties/headless.js +45 -0
  191. package/dist/std/properties/identity.js +28 -0
  192. package/dist/std/properties/json.js +161 -0
  193. package/dist/std/properties/password.d.ts +0 -2
  194. package/dist/std/properties/password.js +79 -0
  195. package/dist/std/properties/primitives.d.ts +4 -0
  196. package/dist/std/properties/primitives.js +28 -0
  197. package/dist/std/properties/time.js +55 -0
  198. package/dist/std/record-plugins.d.ts +2 -2
  199. package/dist/std/record-plugins.js +73 -0
  200. package/dist/std/record.d.ts +20 -1
  201. package/dist/std/record.js +3 -0
  202. package/dist/std/resource.d.ts +31 -0
  203. package/dist/std/resource.js +39 -0
  204. package/dist/std/runtime.d.ts +6 -0
  205. package/dist/std/runtime.js +193 -0
  206. package/dist/std/stages.js +4 -0
  207. package/dist/std/use-cases/auth.d.ts +5 -1
  208. package/dist/std/use-cases/auth.js +193 -0
  209. package/dist/std/use-cases/crud.js +261 -0
  210. package/dist/std/use-cases/index.d.ts +1 -1
  211. package/dist/std/use-cases/index.js +12 -0
  212. package/dist/std/use-cases/lifecycle.js +97 -0
  213. package/dist/std/use-cases/shared.d.ts +2 -2
  214. package/dist/std/use-cases/shared.js +232 -0
  215. package/dist/std/use-cases/types.d.ts +102 -2
  216. package/dist/std/use-cases/types.js +0 -0
  217. package/dist/testing/http-provider-contract.d.ts +32 -0
  218. package/dist/testing/http-provider-contract.js +344 -0
  219. package/dist/testing/index.d.ts +2 -0
  220. package/dist/testing/index.js +3 -944
  221. package/dist/testing/memory-publisher.js +12 -0
  222. package/dist/testing/provider-contract.d.ts +26 -1
  223. package/dist/testing/provider-contract.js +1071 -0
  224. package/package.json +23 -3
  225. package/dist/index-qxfkyd6k.js +0 -1721
  226. package/dist/index-t5qwtyj5.js +0 -8194
@@ -0,0 +1,351 @@
1
+ import { jwtOf, jwtOptionsIssue } from "../auth/jwt.js";
2
+ import { Exception } from "../exception/exception.js";
3
+ import { HEADER_NAME } from "../use-case/route.js";
4
+ import { coerceTextValues } from "./coerce.js";
5
+ import { InvalidOutputStage } from "./exceptions.js";
6
+ import {
7
+ openapi,
8
+ operationRoutes,
9
+ ROUTE_OPTION_KEYS,
10
+ requestIdHeaderOf,
11
+ routeOptionsIssue
12
+ } from "./openapi-sink.js";
13
+ import { outputValueOf, PROBLEM_MEDIA_TYPE, problemOf } from "./problem.js";
14
+ import { statusOf } from "./status.js";
15
+ function isObject(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function onlyKeys(value, allowed, where) {
19
+ for (const key of Object.keys(value)) {
20
+ if (!allowed.includes(key))
21
+ return `${where} has no option "${key}".`;
22
+ }
23
+ return;
24
+ }
25
+ export const HTTP_OPTION_KEYS = [
26
+ ...ROUTE_OPTION_KEYS,
27
+ "docs",
28
+ "debug",
29
+ "context",
30
+ "auth"
31
+ ];
32
+ export function httpOptionsIssue(options, where) {
33
+ const given = options;
34
+ const routing = routeOptionsIssue(options, where);
35
+ if (routing)
36
+ return routing;
37
+ if (given.debug !== undefined && typeof given.debug !== "boolean") {
38
+ return `Expected ${where}.debug to be a boolean.`;
39
+ }
40
+ const { docs, context, auth } = given;
41
+ if (auth !== undefined) {
42
+ if (!isObject(auth) || Object.keys(auth).join() !== "jwt") {
43
+ return `Expected ${where}.auth to be { jwt: { secret, ... } }.`;
44
+ }
45
+ const bad = jwtOptionsIssue(auth.jwt, `${where}.auth.jwt`, ["header", "cookie"], true);
46
+ if (bad)
47
+ return bad;
48
+ const { header, cookie } = auth.jwt;
49
+ for (const [key, value] of [
50
+ ["header", header],
51
+ ["cookie", cookie]
52
+ ]) {
53
+ if (value !== undefined && (typeof value !== "string" || !HEADER_NAME.test(value))) {
54
+ return `Expected ${where}.auth.jwt.${key} to be the name of a ${key}.`;
55
+ }
56
+ }
57
+ }
58
+ if (docs !== undefined) {
59
+ if (!isObject(docs))
60
+ return `Expected ${where}.docs to be an object.`;
61
+ const docsBad = onlyKeys(docs, ["path", "info", "servers"], `${where}.docs`);
62
+ if (docsBad)
63
+ return docsBad;
64
+ if (typeof docs.path !== "string" || !docs.path.startsWith("/") || /[{}?#\s:*]/.test(docs.path)) {
65
+ return `Expected ${where}.docs.path to be a path such as "/openapi.json".`;
66
+ }
67
+ try {
68
+ openapi({
69
+ info: docs.info,
70
+ ...docs.servers ? { servers: docs.servers } : {}
71
+ });
72
+ } catch (error) {
73
+ const reason = error.details?.reason;
74
+ return (reason ?? `Expected ${where}.docs to be valid.`).replaceAll("openapi.", `${where}.docs.`);
75
+ }
76
+ }
77
+ if (context !== undefined) {
78
+ if (!isObject(context))
79
+ return `Expected ${where}.context to be an object.`;
80
+ const contextBad = onlyKeys(context, ["headers", "variables"], `${where}.context`);
81
+ if (contextBad)
82
+ return contextBad;
83
+ const { headers, variables } = context;
84
+ if (headers !== undefined) {
85
+ if (!isObject(headers)) {
86
+ return `Expected ${where}.context.headers to be an object.`;
87
+ }
88
+ for (const [key, name] of Object.entries(headers)) {
89
+ if (typeof name !== "string" || !HEADER_NAME.test(name)) {
90
+ return `Expected ${where}.context.headers["${key}"] to be a header name.`;
91
+ }
92
+ }
93
+ }
94
+ if (variables !== undefined) {
95
+ if (!isObject(variables)) {
96
+ return `Expected ${where}.context.variables to be an object.`;
97
+ }
98
+ for (const [key, name] of Object.entries(variables)) {
99
+ if (typeof name !== "string" || name === "") {
100
+ return `Expected ${where}.context.variables["${key}"] to be the name of a variable.`;
101
+ }
102
+ if (isObject(headers) && Object.hasOwn(headers, key)) {
103
+ return `${where}.context maps "${key}" both from a header and from a variable; keep one.`;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return;
109
+ }
110
+ const REQUEST_ID = /^[\x21-\x7e]{1,200}$/;
111
+ const JSON_TYPE = "application/json";
112
+ const NO_COOKIES = Object.freeze([]);
113
+ function cookiesOf(header) {
114
+ const cookies = new Map;
115
+ if (!header)
116
+ return cookies;
117
+ for (const pair of header.split(";")) {
118
+ const at = pair.indexOf("=");
119
+ if (at < 0)
120
+ continue;
121
+ const name = pair.slice(0, at).trim();
122
+ if (name === "" || cookies.has(name))
123
+ continue;
124
+ let value = pair.slice(at + 1).trim();
125
+ if (value.length > 1 && value.startsWith('"') && value.endsWith('"')) {
126
+ value = value.slice(1, -1);
127
+ }
128
+ if (value.includes("%")) {
129
+ try {
130
+ value = decodeURIComponent(value);
131
+ } catch {}
132
+ }
133
+ cookies.set(name, value);
134
+ }
135
+ return cookies;
136
+ }
137
+ function cookieWriterOf(cookie) {
138
+ const sameSite = cookie.sameSite ?? "lax";
139
+ const attributes = [
140
+ `Path=${cookie.path ?? "/"}`,
141
+ ...cookie.domain === undefined ? [] : [`Domain=${cookie.domain}`],
142
+ ...cookie.httpOnly === false ? [] : ["HttpOnly"],
143
+ ...cookie.secure === false ? [] : ["Secure"],
144
+ `SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`
145
+ ].join("; ");
146
+ const lasting = cookie.maxAge === undefined ? "" : `; Max-Age=${cookie.maxAge}`;
147
+ return (value) => value === "" ? `${cookie.name}=; ${attributes}; Max-Age=0` : `${cookie.name}=${encodeURIComponent(value)}; ${attributes}${lasting}`;
148
+ }
149
+ export function compileRoutes(operations, options, context, settings) {
150
+ const { source } = settings;
151
+ const { output } = context;
152
+ let authenticator = settings.authenticator;
153
+ if (!authenticator && options.auth) {
154
+ const { header, cookie, ...jwt } = options.auth.jwt;
155
+ authenticator = jwtOf(jwt).authenticator({
156
+ ...header === undefined ? {} : { header },
157
+ ...cookie === undefined ? {} : { cookie }
158
+ });
159
+ }
160
+ const routes = operationRoutes(operations, options, source);
161
+ const debug = options.debug === true;
162
+ const contextHeaders = options.context?.headers ?? {};
163
+ const contextVariables = options.context?.variables ?? {};
164
+ const requestIdHeader = requestIdHeaderOf(options);
165
+ const idOf = (request) => {
166
+ const sent = request?.header(requestIdHeader);
167
+ return sent !== undefined && REQUEST_ID.test(sent) ? sent : crypto.randomUUID();
168
+ };
169
+ const headersOf = (headers, request) => requestIdHeader === undefined ? headers : { ...headers, [requestIdHeader]: idOf(request) };
170
+ const problemHeaders = Object.freeze({ "content-type": PROBLEM_MEDIA_TYPE });
171
+ const problem = (body, status, request) => ({
172
+ status,
173
+ headers: headersOf(problemHeaders, request),
174
+ cookies: NO_COOKIES,
175
+ body: JSON.stringify(body)
176
+ });
177
+ const fail = (error, operation, request, fallback) => {
178
+ const status = fallback !== undefined && !(error instanceof Exception && Object.hasOwn(output.statuses, error.name)) ? fallback : statusOf(error, operation, output.statuses);
179
+ return problem(problemOf(error, { status, debug }), status, request);
180
+ };
181
+ const failure = (error, given) => fail(error, given?.operation, given?.request, given?.status);
182
+ const compiled = operations.map((operation) => {
183
+ const route = routes.get(operation.id);
184
+ const { method, path, parameters, status, body } = route;
185
+ const queryKeys = body ? route.query : [];
186
+ const headerKeys = Object.entries(route.requestHeaders);
187
+ const cookieKeys = Object.entries(route.cookies);
188
+ const placed = parameters.length + queryKeys.length + headerKeys.length + cookieKeys.length > 0;
189
+ const declared = operation.context ? Object.keys(operation.context.properties) : [];
190
+ const contextHeaderKeys = declared.filter((key) => Object.hasOwn(contextHeaders, key)).map((key) => [key, contextHeaders[key].toLowerCase()]);
191
+ const contextVariableKeys = declared.filter((key) => Object.hasOwn(contextVariables, key)).map((key) => [key, contextVariables[key]]);
192
+ const guarded = operation.access !== undefined && operation.access.public !== true;
193
+ const setCookies = Object.entries(route.setCookies).map(([key, cookie]) => [key, cookieWriterOf(cookie)]);
194
+ const successHeaders = Object.freeze(status === 204 ? { ...route.headers } : { "content-type": JSON_TYPE, ...route.headers });
195
+ const readsCookies = cookieKeys.length > 0;
196
+ const authenticates = guarded && authenticator !== undefined;
197
+ const hasContext = operation.context !== undefined;
198
+ const handle = async (request) => {
199
+ try {
200
+ let cookies;
201
+ let principal;
202
+ if (authenticates) {
203
+ principal = await authenticator({
204
+ header: (name) => request.header(name),
205
+ cookie: (name) => {
206
+ cookies ??= cookiesOf(request.header("cookie"));
207
+ return cookies.get(name);
208
+ },
209
+ operation,
210
+ native: request.native
211
+ });
212
+ }
213
+ let input;
214
+ if (body) {
215
+ const text = await request.text();
216
+ try {
217
+ input = text.trim() === "" ? {} : JSON.parse(text);
218
+ } catch {
219
+ return problem({
220
+ type: "urn:afora:exception:InvalidRequestBody",
221
+ title: "InvalidRequestBody",
222
+ status: 400,
223
+ detail: "The request body is not valid JSON."
224
+ }, 400, request);
225
+ }
226
+ } else {
227
+ input = coerceTextValues(operation.input, request.query());
228
+ }
229
+ if (placed && isObject(input)) {
230
+ const texts = {};
231
+ if (queryKeys.length > 0) {
232
+ const query = request.query();
233
+ for (const key of queryKeys)
234
+ texts[key] = query[key];
235
+ }
236
+ for (const [key, name] of headerKeys) {
237
+ texts[key] = request.header(name);
238
+ }
239
+ if (readsCookies) {
240
+ cookies ??= cookiesOf(request.header("cookie"));
241
+ for (const [key, name] of cookieKeys)
242
+ texts[key] = cookies.get(name);
243
+ }
244
+ for (const name of parameters)
245
+ texts[name] = request.param(name);
246
+ input = { ...input, ...coerceTextValues(operation.input, texts) };
247
+ }
248
+ let call;
249
+ if (hasContext) {
250
+ const raw = {};
251
+ for (const [key, name] of contextHeaderKeys) {
252
+ raw[key] = request.header(name);
253
+ }
254
+ const values = coerceTextValues(operation.context, raw);
255
+ for (const [key, name] of contextVariableKeys) {
256
+ const value = request.variable?.(name);
257
+ if (value !== undefined)
258
+ values[key] = value;
259
+ }
260
+ call = guarded ? { context: values, principal: principal ?? null } : { context: values };
261
+ } else if (guarded)
262
+ call = { principal: principal ?? null };
263
+ const response = await operation.invoke(input, call);
264
+ if (setCookies.length === 0) {
265
+ return {
266
+ status,
267
+ headers: headersOf(successHeaders, request),
268
+ cookies: NO_COOKIES,
269
+ body: status === 204 ? null : JSON.stringify(response) ?? "null"
270
+ };
271
+ }
272
+ const value = outputValueOf(response);
273
+ const sent = [];
274
+ if (isObject(value)) {
275
+ for (const [key, write] of setCookies) {
276
+ const cookie = value[key];
277
+ delete value[key];
278
+ if (cookie !== undefined && cookie !== null) {
279
+ sent.push(write(String(cookie)));
280
+ }
281
+ }
282
+ }
283
+ return {
284
+ status,
285
+ headers: headersOf(successHeaders, request),
286
+ cookies: sent,
287
+ body: status === 204 ? null : JSON.stringify(value) ?? "null"
288
+ };
289
+ } catch (error) {
290
+ return fail(error, operation, request);
291
+ }
292
+ };
293
+ return Object.freeze({ operation, method, path, parameters, handle });
294
+ });
295
+ let docs;
296
+ if (options.docs) {
297
+ const { path, info, servers } = options.docs;
298
+ if (compiled.some((route) => route.method === "get" && route.path === path)) {
299
+ throw new InvalidOutputStage({
300
+ source,
301
+ details: {
302
+ reason: `${source}.docs.path "${path}" collides with a GET operation route.`
303
+ }
304
+ });
305
+ }
306
+ const routing = {};
307
+ for (const key of ROUTE_OPTION_KEYS) {
308
+ if (options[key] !== undefined)
309
+ routing[key] = options[key];
310
+ }
311
+ if (options.auth && routing.security === undefined) {
312
+ const { header, cookie } = options.auth.jwt;
313
+ routing.security = {
314
+ schemes: cookie !== undefined && header === undefined ? { session: { type: "apiKey", in: "cookie", name: cookie } } : header !== undefined && header.toLowerCase() !== "authorization" ? { token: { type: "apiKey", in: "header", name: header } } : {
315
+ bearer: {
316
+ type: "http",
317
+ scheme: "bearer",
318
+ bearerFormat: "JWT"
319
+ }
320
+ }
321
+ };
322
+ }
323
+ const document = openapi({
324
+ info,
325
+ ...servers ? { servers } : {},
326
+ ...routing
327
+ }).build(operations, context);
328
+ const text = JSON.stringify(settings.document ? settings.document(document) : document);
329
+ const headers = Object.freeze({ "content-type": JSON_TYPE });
330
+ docs = Object.freeze({
331
+ path,
332
+ handle: (request) => ({
333
+ status: 200,
334
+ headers: headersOf(headers, request),
335
+ cookies: NO_COOKIES,
336
+ body: text
337
+ })
338
+ });
339
+ }
340
+ return Object.freeze({
341
+ routes: Object.freeze(compiled),
342
+ ...docs ? { docs } : {},
343
+ notFound: (request) => problem({
344
+ type: "urn:afora:exception:NotFound",
345
+ title: "NotFound",
346
+ status: 404,
347
+ detail: "No operation is served at this method and path."
348
+ }, 404, request),
349
+ failure
350
+ });
351
+ }
@@ -1,4 +1,5 @@
1
1
  import type { OpenAPISchema } from "../openapi/types/openapi.interface.js";
2
+ import type { RouteCookie } from "../use-case/types/route.interface.js";
2
3
  import type { OperationDescriptor } from "./types/output.interface.js";
3
4
  declare const METHODS: readonly ["get", "post", "put", "patch", "delete"];
4
5
  /** An HTTP method an operation can be routed to. */
@@ -44,7 +45,11 @@ export interface SecurityRequirement {
44
45
  * sinks that serve HTTP, so the served routes and the document agree.
45
46
  */
46
47
  export interface RouteOptions {
47
- /** Prefix of every path, e.g. `"/api"`. Empty by default. */
48
+ /**
49
+ * Prefix of every path, e.g. `"/api"`.
50
+ *
51
+ * @default ""
52
+ */
48
53
  readonly basePath?: string;
49
54
  /**
50
55
  * Method, path and success status by operation id, e.g.
@@ -55,7 +60,11 @@ export interface RouteOptions {
55
60
  readonly [operationId: string]: {
56
61
  readonly method?: OpenAPIMethod;
57
62
  readonly path?: string;
58
- /** `200` by default. */
63
+ /**
64
+ * The status of a successful response.
65
+ *
66
+ * @default 200
67
+ */
59
68
  readonly status?: RouteStatus;
60
69
  };
61
70
  };
@@ -146,10 +155,30 @@ export interface OperationRoute {
146
155
  readonly headers: {
147
156
  readonly [name: string]: string;
148
157
  };
158
+ /**
159
+ * Whether the input keys with no other place are a JSON body (`post`,
160
+ * `put`, `patch`); otherwise they are read from the query.
161
+ */
162
+ readonly body: boolean;
163
+ /** The input keys the use case route reads from the query, whatever the method. */
164
+ readonly query: readonly string[];
165
+ /** The input keys read from request headers, with the header of each (lower case). */
166
+ readonly requestHeaders: {
167
+ readonly [inputKey: string]: string;
168
+ };
169
+ /** The input keys read from cookies, with the cookie of each. */
170
+ readonly cookies: {
171
+ readonly [inputKey: string]: string;
172
+ };
173
+ /** The response keys sent as cookies instead of in the body, with the cookie of each. */
174
+ readonly setCookies: {
175
+ readonly [responseKey: string]: RouteCookie;
176
+ };
149
177
  }
150
178
  /**
151
179
  * The route of every operation, by id: `POST {basePath}/{kebab(command)}/{kebab(operation)}`
152
- * with `200`, changed per operation id with `route` and `responses`. Throws
180
+ * with `200`, changed by the route the use case declares (`operation.route`)
181
+ * and, over it, per operation id with `route` and `responses`. Throws
153
182
  * `InvalidOutputStage` (with `source`) when `route`, `responses` or
154
183
  * `security.operations` name an unknown operation, a path parameter is not a
155
184
  * scalar key of the input, or two operations share a method and path. The
@@ -179,5 +208,5 @@ export declare function operationRoutes(operations: readonly OperationDescriptor
179
208
  * transport-specific and not part of the document; sinks that serve HTTP map
180
209
  * it (headers, middleware).
181
210
  */
182
- export declare const openapi: import("./types/output.interface.js").SinkFactory<OpenAPIDocument, OpenAPISinkOptions>;
211
+ export declare const openapi: import("./types/output.interface.js").SinkFactory<OpenAPIDocument, OpenAPISinkOptions, never>;
183
212
  export {};