@fougere/adapter-rest 0.23.0-alpha.0 → 0.25.0-alpha.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 (39) hide show
  1. package/dist/RestProtocol.d.ts +11 -1
  2. package/dist/RestProtocol.d.ts.map +1 -1
  3. package/dist/RestProtocol.js +12 -0
  4. package/dist/RestProtocol.js.map +1 -1
  5. package/dist/door.d.ts +48 -0
  6. package/dist/door.d.ts.map +1 -0
  7. package/dist/door.js +97 -0
  8. package/dist/door.js.map +1 -0
  9. package/dist/index.d.ts +5 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +7 -5
  12. package/dist/index.js.map +1 -1
  13. package/dist/openapi/described.d.ts +19 -0
  14. package/dist/openapi/described.d.ts.map +1 -0
  15. package/dist/openapi/described.js +69 -0
  16. package/dist/openapi/described.js.map +1 -0
  17. package/dist/openapi/document.d.ts +25 -0
  18. package/dist/openapi/document.d.ts.map +1 -0
  19. package/dist/openapi/document.js +171 -0
  20. package/dist/openapi/document.js.map +1 -0
  21. package/dist/register.d.ts +9 -2
  22. package/dist/register.d.ts.map +1 -1
  23. package/dist/register.js +20 -37
  24. package/dist/register.js.map +1 -1
  25. package/dist/routes/OperationMeta.d.ts +1 -1
  26. package/dist/routes/OperationMeta.d.ts.map +1 -1
  27. package/dist/routes/RouteDefinition.d.ts +13 -3
  28. package/dist/routes/RouteDefinition.d.ts.map +1 -1
  29. package/dist/routes/RouteDefinition.js +17 -5
  30. package/dist/routes/RouteDefinition.js.map +1 -1
  31. package/package.json +4 -4
  32. package/src/RestProtocol.ts +14 -0
  33. package/src/door.ts +152 -0
  34. package/src/index.ts +8 -6
  35. package/src/openapi/described.ts +90 -0
  36. package/src/openapi/document.ts +199 -0
  37. package/src/register.ts +23 -42
  38. package/src/routes/OperationMeta.ts +1 -1
  39. package/src/routes/RouteDefinition.ts +32 -7
@@ -1,9 +1,12 @@
1
1
  import { Format, type Accepted } from '@fougere/schema';
2
2
  import { type ProtocolConfig } from '@fougere/core';
3
- /** Where one operation answers, when what its contract derives is not what it should be. */
4
3
  declare const ROUTE: Format<{
5
4
  method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
6
5
  path?: string | undefined;
6
+ responses?: {
7
+ headers?: string[] | undefined;
8
+ status?: number | undefined;
9
+ }[] | undefined;
7
10
  status?: number | undefined;
8
11
  }>;
9
12
  /**
@@ -11,9 +14,16 @@ declare const ROUTE: Format<{
11
14
  * A frond of this project says its own in its `frond.config.ts`.
12
15
  */
13
16
  declare const REST: Format<{
17
+ openapi?: {
18
+ ui?: "scalar" | "swagger" | boolean | undefined;
19
+ } | undefined;
14
20
  overrides?: Record<string, {
15
21
  method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" | undefined;
16
22
  path?: string | undefined;
23
+ responses?: {
24
+ headers?: string[] | undefined;
25
+ status?: number | undefined;
26
+ }[] | undefined;
17
27
  status?: number | undefined;
18
28
  }> | undefined;
19
29
  prefix?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"RestProtocol.d.ts","sourceRoot":"","sources":["../src/RestProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,EAA4D,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE9G,4FAA4F;AAC5F,QAAA,MAAM,KAAK;;;;EAIA,CAAC;AAEZ;;;GAGG;AACH,QAAA,MAAM,IAAI;;;;;;;EAGC,CAAC;AAEZ,iGAAiG;AACjG,QAAA,MAAM,KAAK;;EAEA,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;AAE/C,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,gBAAgB;QACxB,IAAI,EAAE,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;KAC7B;IACD,UAAU,yBAAyB;QACjC,IAAI,CAAC,EAAE,SAAS,CAAC;KAClB;IACD,UAAU,qBAAqB;QAC7B,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;KAC/B;CACF;AAED,uEAAuE;AACvE,eAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,wBAAgB,QAAQ,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAEtE"}
1
+ {"version":3,"file":"RestProtocol.d.ts","sourceRoot":"","sources":["../src/RestProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,EAA4D,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAS9G,QAAA,MAAM,KAAK;;;;;;;;EAKA,CAAC;AAOZ;;;GAGG;AACH,QAAA,MAAM,IAAI;;;;;;;;;;;;;;EAKC,CAAC;AAEZ,iGAAiG;AACjG,QAAA,MAAM,KAAK;;EAEA,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;AAE/C,OAAO,QAAQ,eAAe,CAAC;IAC7B,UAAU,gBAAgB;QACxB,IAAI,EAAE,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;KAC7B;IACD,UAAU,yBAAyB;QACjC,IAAI,CAAC,EAAE,SAAS,CAAC;KAClB;IACD,UAAU,qBAAqB;QAC7B,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,KAAK,CAAC,CAAC;KAC/B;CACF;AAED,uEAAuE;AACvE,eAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,wBAAgB,QAAQ,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAEtE"}
@@ -2,10 +2,20 @@ import { Format } from '@fougere/schema';
2
2
  import { HTTP_METHODS } from '@fougere/http';
3
3
  import { Protocols, RouteAddress, isScanned } from '@fougere/core';
4
4
  /** Where one operation answers, when what its contract derives is not what it should be. */
5
+ /** An answer the op may give besides its own — a redirect, say — and the headers it sets. */
6
+ const RESPONSE = Format.of()
7
+ .key('status', Format.count)
8
+ .key('headers', Format.listOf(Format.text))
9
+ .closed();
5
10
  const ROUTE = Format.of('protocol/rest/route')
6
11
  .key('method', Format.tokens(HTTP_METHODS))
7
12
  .key('path', Format.text)
8
13
  .key('status', Format.count)
14
+ .key('responses', Format.listOf(RESPONSE))
15
+ .closed();
16
+ /** The page reading the document, at `{prefix}/docs` — `true` is Swagger UI. None without it. */
17
+ const OPENAPI = Format.of('protocol/rest/openapi')
18
+ .key('ui', Format.either(Format.tokens(['swagger', 'scalar']), Format.flag))
9
19
  .closed();
10
20
  /**
11
21
  * Where the app mounts REST, and the routes of a frond it cannot edit — `betterAuth()`, a package's.
@@ -13,6 +23,8 @@ const ROUTE = Format.of('protocol/rest/route')
13
23
  */
14
24
  const REST = Format.of('protocol/rest')
15
25
  .key('prefix', Format.text)
26
+ /** Publish what REST serves at `{prefix}/openapi.json`. Nothing without it. */
27
+ .key('openapi', OPENAPI)
16
28
  .key('overrides', Format.recordOf(ROUTE))
17
29
  .closed();
18
30
  /** What a frond says of itself: the segment it answers under, `/journal` rather than `/blog`. */
@@ -1 +1 @@
1
- {"version":3,"file":"RestProtocol.js","sourceRoot":"","sources":["../src/RestProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAiB,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAA6C,MAAM,eAAe,CAAC;AAE9G,4FAA4F;AAC5F,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC;KAC3C,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;KAC1C,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;KACxB,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC;KAC3B,MAAM,EAAE,CAAC;AAEZ;;;GAGG;AACH,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC;KACpC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC;KAC1B,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;KACxC,MAAM,EAAE,CAAC;AAEZ,iGAAiG;AACjG,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC;KAC3C,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;KACxB,MAAM,EAAE,CAAC;AAgBZ,uEAAuE;AACvE,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC;AAE7B,MAAM,UAAU,QAAQ,CAAC,SAAqC;IAC5D,OAAO,SAAS,EAAE,IAAI,EAAE,MAAM,IAAI,MAAM,CAAC;AAC3C,CAAC;AAED,yGAAyG;AACzG,SAAS,SAAS,CAAC,SAAgD,EAAE,MAAkC;IACrG,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QAC9D,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,eAAe,GAAG,sDAAsD,CAAC,CAAC;QAE9F,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1G,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,eAAe,GAAG,oCAAoC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QAC7F,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEjC,OAAO;YACL,eAAe,GAAG,gBAAgB,KAAK,CAAC,IAAI,gEAAgE;kBACxG,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,qCAAqC,GAAG,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM;SAC5G,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE;IACzB,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,CAAC,OAA8B,EAAE,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3F,CAAC,CAAC"}
1
+ {"version":3,"file":"RestProtocol.js","sourceRoot":"","sources":["../src/RestProtocol.ts"],"names":[],"mappings":"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"}
package/dist/door.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { DoorMiddleware, type Door } from '@fougere/core';
2
+ import { type ResponseResult } from '@fougere/http';
3
+ import type { RouteDefinition } from './routes/RouteDefinition.js';
4
+ /** What a host hands the REST door for one matched route — the body still to read, so the door reads it. */
5
+ export interface RestRequest {
6
+ /** The op the route names — what a middleware bound to a handler stands around. */
7
+ operation: string;
8
+ headers: Headers;
9
+ params: Record<string, string>;
10
+ query: Record<string, unknown>;
11
+ body: () => Promise<unknown>;
12
+ state: Record<string, unknown>;
13
+ }
14
+ /**
15
+ * An answer a REST middleware may give in the op's place, declared in its return type — the status
16
+ * and the headers are what the contract reads, in the vocabulary of OpenAPI's `responses`.
17
+ */
18
+ export interface RestResponse<Status extends number, Headers extends Record<string, string> = Record<never, string>, Body = null> extends ResponseResult {
19
+ status: Status;
20
+ headers: Headers;
21
+ data: Body;
22
+ }
23
+ type HandlerClass = abstract new (...args: never[]) => unknown;
24
+ /** A REST middleware standing around its whole frond: `class Powered extends RestMiddleware()`. */
25
+ export declare function RestMiddleware(): abstract new () => DoorMiddleware<RestRequest, ResponseResult>;
26
+ /**
27
+ * A REST middleware standing around one handler's operations, one method per op it names:
28
+ * `class PostRest extends RestMiddleware(PostHandler) { bySlug(request, next) { … } }`. An op it
29
+ * names nothing for goes straight through.
30
+ */
31
+ export declare function RestMiddleware(handler: HandlerClass): new () => {
32
+ around(request: RestRequest, next: () => Promise<ResponseResult>): Promise<ResponseResult>;
33
+ };
34
+ /** What a route's op is called with. */
35
+ export interface RestInvocation {
36
+ params: Record<string, string>;
37
+ query: Record<string, unknown>;
38
+ input: unknown;
39
+ state: Record<string, unknown>;
40
+ }
41
+ /**
42
+ * REST's edges for one route: the body read within the cycle, the op called, and the answer written
43
+ * as HTTP — the one place REST decides a status, read off the contract and never off the op's name.
44
+ * Every host runs it, whoever matched the route: a router by its own paths, a web host by `matchRoute`.
45
+ */
46
+ export declare function restDoor(route: RouteDefinition): Door<RestRequest, RestInvocation, ResponseResult>;
47
+ export {};
48
+ //# sourceMappingURL=door.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"door.d.ts","sourceRoot":"","sources":["../src/door.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAwC,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAGnE,4GAA4G;AAC5G,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAC9H,SAAQ,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,KAAK,YAAY,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC;AAE/D,mGAAmG;AACnG,wBAAgB,cAAc,IAAI,QAAQ,WAAW,cAAc,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;AACjG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU;IAAE,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CAAE,CAAC;AAwBhK,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,cAAc,CAAC,CAmBlG"}
package/dist/door.js ADDED
@@ -0,0 +1,97 @@
1
+ import { DoorMiddleware, ErrorCode, FougereError, toHttpError } from '@fougere/core';
2
+ import { HttpError } from '@fougere/http';
3
+ export function RestMiddleware(handler) {
4
+ if (!handler) {
5
+ class AtRest extends DoorMiddleware {
6
+ static door = 'rest';
7
+ }
8
+ return AtRest;
9
+ }
10
+ class AtHandler extends DoorMiddleware {
11
+ static door = 'rest';
12
+ static handler = handler;
13
+ around(request, next) {
14
+ const own = this[request.operation];
15
+ return typeof own === 'function' ? own.call(this, request, next) : next();
16
+ }
17
+ }
18
+ return AtHandler;
19
+ }
20
+ /**
21
+ * REST's edges for one route: the body read within the cycle, the op called, and the answer written
22
+ * as HTTP — the one place REST decides a status, read off the contract and never off the op's name.
23
+ * Every host runs it, whoever matched the route: a router by its own paths, a web host by `matchRoute`.
24
+ */
25
+ export function restDoor(route) {
26
+ return {
27
+ name: 'rest',
28
+ address: route.address,
29
+ carries: 'http',
30
+ carrier: ({ headers }) => ({ request: headers, response: new Headers() }),
31
+ // A read carries no body, so what its input view names arrives in the query string.
32
+ decode: async ({ params, query, body, state }) => ({
33
+ params,
34
+ query,
35
+ input: route.method === 'GET' && route.inputFields ? inputOf(route.inputFields, query) : await read(body),
36
+ state,
37
+ }),
38
+ run: (invocation) => route.handler(invocation),
39
+ encode: (outcome, _request, carrier) => leaving('refused' in outcome ? refused(outcome.refused) : responseOf(route, outcome.answered), carrier),
40
+ };
41
+ }
42
+ /** The fields of an input view the query string carries, read as the shape of each says — a query holds only text. */
43
+ function inputOf(fields, query) {
44
+ return Object.fromEntries(Object.entries(fields).flatMap(([key, field]) => {
45
+ if (query[key] === undefined)
46
+ return [];
47
+ const text = String(query[key]);
48
+ const type = field.shape.type;
49
+ if (type === 'number' || type === 'integer')
50
+ return [[key, text.trim() === '' ? text : Number(text)]];
51
+ if (type === 'boolean')
52
+ return [[key, text === 'true' ? true : text === 'false' ? false : text]];
53
+ return [[key, text]];
54
+ }));
55
+ }
56
+ /** `@fougere/http` refuses a body in its own words, since it takes no dependency on core. */
57
+ async function read(body) {
58
+ try {
59
+ return await body();
60
+ }
61
+ catch (failure) {
62
+ if (!(failure instanceof HttpError))
63
+ throw failure;
64
+ throw new FougereError({ code: failure.code, message: failure.message, cause: failure });
65
+ }
66
+ }
67
+ function responseOf(route, result) {
68
+ if (route.method === 'DELETE' && typeof result === 'boolean') {
69
+ return result ? { status: route.successStatus ?? 204, data: null } : refused(notFound(route));
70
+ }
71
+ if (result == null && (route.cardinality === 'maybe' || route.cardinality === 'one'))
72
+ return refused(notFound(route));
73
+ if (result === undefined)
74
+ return { status: route.successStatus ?? 204, data: null };
75
+ const status = route.successStatus ?? (route.creates ? 201 : 200);
76
+ if (route.cardinality === 'page' && Array.isArray(result)) {
77
+ // The page-level facts ride beside the rows: on the array itself, JSON drops them.
78
+ const page = result;
79
+ return { status, data: { items: [...result], total: page.total, hasMore: page.hasMore, endCursor: page.endCursor } };
80
+ }
81
+ return { status, data: result };
82
+ }
83
+ /** What the keepers of `state:` wrote on the carrier goes out with the answer — every `Set-Cookie` line kept apart. */
84
+ function leaving(answer, carrier) {
85
+ const cookies = carrier?.response.getSetCookie() ?? [];
86
+ if (cookies.length === 0)
87
+ return answer;
88
+ return { ...answer, headers: { ...answer.headers, 'set-cookie': cookies } };
89
+ }
90
+ function notFound(route) {
91
+ return new FougereError({ code: ErrorCode.NOT_FOUND, message: `No row answers ${route.method} ${route.path}.` });
92
+ }
93
+ function refused(failure) {
94
+ const { status, body } = toHttpError(failure);
95
+ return { status, data: body };
96
+ }
97
+ //# sourceMappingURL=door.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"door.js","sourceRoot":"","sources":["../src/door.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import './RestProtocol.js';
2
2
  export { generateRoutes, type RouteDefinition } from './routes/RouteDefinition.js';
3
- export type { HttpMethod } from '@fougere/http';
3
+ export type { HttpMethod, ResponseResult } from '@fougere/http';
4
4
  export { PREFIX, prefixOf, type RestRoute } from './RestProtocol.js';
5
- export { registerRoutes } from './register.js';
5
+ export { registerDescribed, registerRoutes, type RegisterOptions } from './register.js';
6
+ export { openApiOf, type OpenApiDocument } from './openapi/document.js';
7
+ export { describedAt, describedPaths, type DocsPage } from './openapi/described.js';
8
+ export { RestMiddleware, restDoor, type RestInvocation, type RestRequest, type RestResponse } from './door.js';
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import './RestProtocol.js';
2
2
  export { generateRoutes } from './routes/RouteDefinition.js';
3
3
  export { PREFIX, prefixOf } from './RestProtocol.js';
4
- export { registerRoutes } from './register.js';
5
- // No schema endpoint here. Discovery is `rpc.discover` on the envelope — one
6
- // surface, which answers with what the host SERVES. A second GET published
7
- // every scanned entity instead, façade or not: the auth tables of a host that
8
- // mounted it were readable by anyone.
4
+ export { registerDescribed, registerRoutes } from './register.js';
5
+ export { openApiOf } from './openapi/document.js';
6
+ export { describedAt, describedPaths } from './openapi/described.js';
7
+ export { RestMiddleware, restDoor } from './door.js';
8
+ // The document is read off what the process SERVES, and published only on request. A schema GET
9
+ // once published every scanned entity, façade or not: the auth tables of a host that mounted it
10
+ // were readable by anyone.
9
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAwB,MAAM,6BAA6B,CAAC;AAEnF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AAC9E,sCAAsC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAwB,MAAM,6BAA6B,CAAC;AAEnF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAwB,MAAM,eAAe,CAAC;AACxF,OAAO,EAAE,SAAS,EAAwB,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAiB,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAA4D,MAAM,WAAW,CAAC;AAE/G,gGAAgG;AAChG,gGAAgG;AAChG,2BAA2B"}
@@ -0,0 +1,19 @@
1
+ import type { ResponseResult } from '@fougere/http';
2
+ import type { AppLike } from '../routes/RouteDefinition.js';
3
+ /** The page that reads the document, by the word `openapi: { ui }` names it — `true` is Swagger UI. */
4
+ declare const PAGES: {
5
+ swagger: (url: string) => string;
6
+ scalar: (url: string) => string;
7
+ };
8
+ export type DocsPage = keyof typeof PAGES;
9
+ /**
10
+ * What REST publishes of itself — `{prefix}/openapi.json` when `protocols: { rest: { openapi: {} } }`
11
+ * says so, and `{prefix}/docs` reading it when `ui` says which page — or `undefined` for any other
12
+ * request. Nothing is published without the line: the document names every op and what each refuses
13
+ * at once, and a page loads code from elsewhere, which is a decision of its own.
14
+ */
15
+ export declare function describedAt(app: AppLike, method: string, path: string): ResponseResult | undefined;
16
+ /** The paths `describedAt` answers, for a router that mounts each one. */
17
+ export declare function describedPaths(app: AppLike): string[];
18
+ export {};
19
+ //# sourceMappingURL=described.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"described.d.ts","sourceRoot":"","sources":["../../src/openapi/described.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAyB5D,uGAAuG;AACvG,QAAA,MAAM,KAAK;IACT,OAAO,QAAQ,MAAM;IAOrB,MAAM,QAAQ,MAAM;CAMrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,KAAK,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAYlG;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAMrD"}
@@ -0,0 +1,69 @@
1
+ import { prefixOf } from '../RestProtocol.js';
2
+ import { openApiOf } from './document.js';
3
+ const SWAGGER = {
4
+ script: {
5
+ url: 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1/swagger-ui-bundle.js',
6
+ integrity: 'sha384-ZPehFMQommnnuaZ4rpxgkgTT2DKFVp4hZC/7pLit+9Lek9T1YGSo23eHFbvNkXkw',
7
+ },
8
+ style: {
9
+ url: 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1/swagger-ui.css',
10
+ integrity: 'sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW',
11
+ },
12
+ };
13
+ const SCALAR = {
14
+ url: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.3/dist/browser/standalone.js',
15
+ integrity: 'sha384-HWi/QCSPi64AQ0xBXFGDk+7gmvZ4hJ/7sZMIXqWVz6Ikb6+Cxej/hWKaomOStyFb',
16
+ };
17
+ /** The page that reads the document, by the word `openapi: { ui }` names it — `true` is Swagger UI. */
18
+ const PAGES = {
19
+ swagger: (url) => page(`<link rel="stylesheet" href="${SWAGGER.style.url}" integrity="${SWAGGER.style.integrity}" crossorigin="anonymous">`, `<div id="swagger-ui"></div>
20
+ <script src="${SWAGGER.script.url}" integrity="${SWAGGER.script.integrity}" crossorigin="anonymous"></script>
21
+ <script>SwaggerUIBundle({ url: ${JSON.stringify(url)}, dom_id: '#swagger-ui' })</script>`),
22
+ // Scalar sends usage to its makers unless told not to.
23
+ scalar: (url) => page('', `<div id="app"></div>
24
+ <script src="${SCALAR.url}" integrity="${SCALAR.integrity}" crossorigin="anonymous"></script>
25
+ <script>Scalar.createApiReference('#app', { url: ${JSON.stringify(url)}, telemetry: false })</script>`),
26
+ };
27
+ /**
28
+ * What REST publishes of itself — `{prefix}/openapi.json` when `protocols: { rest: { openapi: {} } }`
29
+ * says so, and `{prefix}/docs` reading it when `ui` says which page — or `undefined` for any other
30
+ * request. Nothing is published without the line: the document names every op and what each refuses
31
+ * at once, and a page loads code from elsewhere, which is a decision of its own.
32
+ */
33
+ export function describedAt(app, method, path) {
34
+ const openapi = app.protocols?.rest?.openapi;
35
+ if (method !== 'GET' || !openapi)
36
+ return undefined;
37
+ const prefix = prefixOf(app.protocols);
38
+ if (path === `${prefix}/openapi.json`)
39
+ return { status: 200, data: openApiOf(app) };
40
+ const ui = openapi.ui === true ? 'swagger' : openapi.ui || undefined;
41
+ if (ui && path === `${prefix}/docs`) {
42
+ return { status: 200, data: PAGES[ui](`${prefix}/openapi.json`), raw: true, headers: { 'content-type': 'text/html; charset=utf-8' } };
43
+ }
44
+ return undefined;
45
+ }
46
+ /** The paths `describedAt` answers, for a router that mounts each one. */
47
+ export function describedPaths(app) {
48
+ const openapi = app.protocols?.rest?.openapi;
49
+ if (!openapi)
50
+ return [];
51
+ const prefix = prefixOf(app.protocols);
52
+ return [`${prefix}/openapi.json`, ...(openapi.ui ? [`${prefix}/docs`] : [])];
53
+ }
54
+ function page(head, body) {
55
+ return `<!doctype html>
56
+ <html>
57
+ <head>
58
+ <meta charset="utf-8">
59
+ <meta name="viewport" content="width=device-width, initial-scale=1">
60
+ <title>API reference</title>
61
+ ${head}
62
+ </head>
63
+ <body>
64
+ ${body}
65
+ </body>
66
+ </html>
67
+ `;
68
+ }
69
+ //# sourceMappingURL=described.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"described.js","sourceRoot":"","sources":["../../src/openapi/described.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAQ1C,MAAM,OAAO,GAAG;IACd,MAAM,EAAE;QACN,GAAG,EAAE,0EAA0E;QAC/E,SAAS,EAAE,yEAAyE;KACrF;IACD,KAAK,EAAE;QACL,GAAG,EAAE,oEAAoE;QACzE,SAAS,EAAE,yEAAyE;KACrF;CAC+B,CAAC;AAEnC,MAAM,MAAM,GAAW;IACrB,GAAG,EAAE,sFAAsF;IAC3F,SAAS,EAAE,yEAAyE;CACrF,CAAC;AAEF,uGAAuG;AACvG,MAAM,KAAK,GAAG;IACZ,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAC5B,gCAAgC,OAAO,CAAC,KAAK,CAAC,GAAG,gBAAgB,OAAO,CAAC,KAAK,CAAC,SAAS,4BAA4B,EACpH;iBACa,OAAO,CAAC,MAAM,CAAC,GAAG,gBAAgB,OAAO,CAAC,MAAM,CAAC,SAAS;mCACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,qCAAqC,CACxF;IACD,uDAAuD;IACvD,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAC3B,EAAE,EACF;iBACa,MAAM,CAAC,GAAG,gBAAgB,MAAM,CAAC,SAAS;qDACN,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,gCAAgC,CACrG;CACF,CAAC;AAIF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY,EAAE,MAAc,EAAE,IAAY;IACpE,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC;IAC7C,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IACnD,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAEvC,IAAI,IAAI,KAAK,GAAG,MAAM,eAAe;QAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACpF,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,SAAS,CAAC;IACrE,IAAI,EAAE,IAAI,IAAI,KAAK,GAAG,MAAM,OAAO,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,EAAE,CAAC;IACxI,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAEvC,OAAO,CAAC,GAAG,MAAM,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,IAAY;IACtC,OAAO;;;;;;IAML,IAAI;;;IAGJ,IAAI;;;CAGP,CAAC;AACF,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type AppLike } from '../routes/RouteDefinition.js';
2
+ type Schema = Record<string, unknown>;
3
+ /** OpenAPI 3.1 — the part of it this writes. */
4
+ export interface OpenApiDocument {
5
+ openapi: '3.1.0';
6
+ info: {
7
+ title: string;
8
+ version: string;
9
+ };
10
+ tags: {
11
+ name: string;
12
+ }[];
13
+ paths: Record<string, Record<string, Schema>>;
14
+ components: {
15
+ schemas: Record<string, Schema>;
16
+ };
17
+ }
18
+ /**
19
+ * What this process serves on REST, as OpenAPI 3.1 — read off the routes `generateRoutes` already
20
+ * answers, never declared beside them. A schema is the card's descriptor, which IS JSON Schema; a
21
+ * refusal is the op's own `errors` and the half the framework implies, at the status the door says.
22
+ */
23
+ export declare function openApiOf(app: AppLike): OpenApiDocument;
24
+ export {};
25
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/openapi/document.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,OAAO,EAAwB,MAAM,8BAA8B,CAAC;AAGlG,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACjD;AAaD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CA+CvD"}
@@ -0,0 +1,171 @@
1
+ import { Card } from '@fougere/schema';
2
+ import { ErrorCode, FougereError, refusalsOf, toHttpError } from '@fougere/core';
3
+ import { generateRoutes } from '../routes/RouteDefinition.js';
4
+ /** What every refusal answers, whatever the door: `toHttpError`'s body. */
5
+ const REFUSAL = {
6
+ type: 'object',
7
+ properties: {
8
+ code: { type: 'string', enum: Object.values(ErrorCode) },
9
+ message: { type: 'string' },
10
+ details: {},
11
+ },
12
+ required: ['code', 'message'],
13
+ };
14
+ /**
15
+ * What this process serves on REST, as OpenAPI 3.1 — read off the routes `generateRoutes` already
16
+ * answers, never declared beside them. A schema is the card's descriptor, which IS JSON Schema; a
17
+ * refusal is the op's own `errors` and the half the framework implies, at the status the door says.
18
+ */
19
+ export function openApiOf(app) {
20
+ const schemas = { Refusal: REFUSAL };
21
+ const named = new Map();
22
+ // An entity keeps its own name; a view cut from it is named by where it is used — `PostUpdateInput` —
23
+ // unless it states what a schema already written states. What a view borrowed from is not a shape.
24
+ const refOf = (view, usedAs) => {
25
+ let name = named.get(view);
26
+ if (!name) {
27
+ const { 'x-fougere-derived': derived, ...descriptor } = Card.fromSchema(view).descriptor;
28
+ // A patch view requires nothing: every field it carries is a change, and none must be sent.
29
+ const patch = view.getOpts().patch === true;
30
+ if (patch)
31
+ delete descriptor.required;
32
+ descriptor.properties = writtenBy(descriptor.properties, patch);
33
+ const shape = JSON.stringify(descriptor);
34
+ name = Object.keys(schemas).find((one) => JSON.stringify(schemas[one]) === shape);
35
+ if (!name) {
36
+ const title = descriptor.title ?? 'Row';
37
+ const wanted = derived ? title + usedAs : title;
38
+ name = wanted;
39
+ for (let n = 2; schemas[name]; n++)
40
+ name = `${wanted}${n}`;
41
+ // A reader shows a schema by its title, so a view says its own name and not its entity's.
42
+ schemas[name] = { ...descriptor, title: name };
43
+ }
44
+ named.set(view, name);
45
+ }
46
+ return { $ref: `#/components/schemas/${name}` };
47
+ };
48
+ const paths = {};
49
+ const tags = new Set();
50
+ for (const route of generateRoutes(app)) {
51
+ const meta = app.operationsFor(route.address)?.get(route.operationName);
52
+ if (!meta)
53
+ continue;
54
+ const frond = app.fronds.find((one) => one.handlers.some((handler) => handler.address === route.address)).name;
55
+ tags.add(frond);
56
+ const path = route.path.replace(/:(\w+)/g, '{$1}');
57
+ paths[path] = { ...paths[path], [route.method.toLowerCase()]: operationOf(route, meta, frond, refOf) };
58
+ }
59
+ return {
60
+ openapi: '3.1.0',
61
+ info: { title: [...tags].join(', ') || 'API', version: 'unversioned' },
62
+ tags: [...tags].map((name) => ({ name })),
63
+ paths,
64
+ components: { schemas },
65
+ };
66
+ }
67
+ function operationOf(route, meta, frond, refOf) {
68
+ const inPath = new Set([...route.path.matchAll(/:(\w+)/g)].map((match) => match[1]));
69
+ const params = (meta.binding ?? []).flatMap(({ source, optional }) => (source.kind === 'param' ? [{ ...source, optional }] : []));
70
+ const reads = route.method === 'GET' || route.method === 'DELETE';
71
+ const parameters = params
72
+ .filter((param) => inPath.has(param.name) || reads)
73
+ .map((param) => ({
74
+ name: param.name,
75
+ in: inPath.has(param.name) ? 'path' : 'query',
76
+ required: inPath.has(param.name) || !param.optional,
77
+ schema: { type: param.coerce ?? 'string' },
78
+ }));
79
+ // A read carries no body: the fields of its input view are its query string.
80
+ const queried = route.method === 'GET' && meta.input ? queryOf(meta.input) : [];
81
+ const operation = route.operationName[0].toUpperCase() + route.operationName.slice(1);
82
+ return {
83
+ operationId: `${route.address}.${route.operationName}`,
84
+ tags: [frond],
85
+ ...(meta.description && { description: meta.description }),
86
+ ...(parameters.length + queried.length > 0 && { parameters: [...parameters, ...queried] }),
87
+ ...(route.method === 'GET' ? {} : requestBodyOf(meta, reads ? [] : params.filter((param) => !inPath.has(param.name)), (view) => refOf(view, `${operation}Input`))),
88
+ responses: responsesOf(route, meta, inPath.size > 0, (view) => refOf(view, `${operation}Output`)),
89
+ };
90
+ }
91
+ function queryOf(view) {
92
+ const { properties, required = [] } = Card.fromSchema(view).descriptor;
93
+ return Object.entries(properties).map(([name, schema]) => ({ name, in: 'query', required: required.includes(name), schema }));
94
+ }
95
+ /** The input view whole, or the fields a change carries — the path designates, the body carries what changes. */
96
+ function requestBodyOf(meta, carried, refOf) {
97
+ if (meta.input)
98
+ return { requestBody: { required: true, content: { 'application/json': { schema: refOf(meta.input) } } } };
99
+ if (carried.length === 0)
100
+ return {};
101
+ const schema = {
102
+ type: 'object',
103
+ properties: Object.fromEntries(carried.map((param) => [param.name, { type: param.coerce ?? 'string' }])),
104
+ required: carried.filter((param) => !param.optional).map((param) => param.name),
105
+ };
106
+ return { requestBody: { required: true, content: { 'application/json': { schema } } } };
107
+ }
108
+ function responsesOf(route, meta, designates, refOf) {
109
+ const responses = {};
110
+ const answers = meta.output && meta.cardinality !== 'none' && route.method !== 'DELETE';
111
+ if (answers) {
112
+ const row = refOf(meta.output);
113
+ const schema = meta.cardinality === 'page' ? pageOf(row) : meta.cardinality === 'many' ? { type: 'array', items: row } : row;
114
+ responses[String(route.successStatus ?? (route.creates ? 201 : 200))] = { description: 'Answered.', content: { 'application/json': { schema } } };
115
+ }
116
+ else {
117
+ responses[String(route.successStatus ?? 204)] = { description: 'Done, nothing to answer.' };
118
+ }
119
+ for (const answer of route.responses ?? []) {
120
+ responses[String(answer.status)] = {
121
+ description: 'Answered in the op\'s place by a door middleware.',
122
+ ...(answer.headers && { headers: Object.fromEntries(answer.headers.map((name) => [name, { schema: { type: 'string' } }])) }),
123
+ };
124
+ }
125
+ const missing = designates && (route.method === 'DELETE' || meta.cardinality === 'one' || meta.cardinality === 'maybe');
126
+ const codes = new Set([...refusalsOf({ kind: meta.kind, input: meta.input, errors: meta.errors }), ...(missing ? [ErrorCode.NOT_FOUND] : [])]);
127
+ const refusedAt = new Map();
128
+ for (const code of codes) {
129
+ const { status } = toHttpError(new FougereError({ code, message: code }));
130
+ refusedAt.set(status, [...(refusedAt.get(status) ?? []), code]);
131
+ }
132
+ // Each status names its own codes, so a reader's example is one this status can answer.
133
+ for (const [status, refused] of refusedAt) {
134
+ responses[String(status)] = {
135
+ description: `Refused: ${refused.join(', ')}.`,
136
+ content: {
137
+ 'application/json': {
138
+ schema: { allOf: [{ $ref: '#/components/schemas/Refusal' }, { properties: { code: { enum: refused } } }] },
139
+ example: { code: refused[0], message: 'What was refused, in words.' },
140
+ },
141
+ },
142
+ };
143
+ }
144
+ return responses;
145
+ }
146
+ /**
147
+ * What the lifecycle axis says of who writes a field, in OpenAPI's words: a value the server fills
148
+ * at creation is `readOnly`, so no request sends it; a field no update may change is not in a patch.
149
+ */
150
+ function writtenBy(properties, patch) {
151
+ return Object.fromEntries(Object.entries(properties).flatMap(([key, property]) => {
152
+ const lifecycle = property['x-fougere']?.lifecycle;
153
+ if (patch && lifecycle?.update === 'forbidden')
154
+ return [];
155
+ const filled = lifecycle?.create === 'now' || (typeof lifecycle?.create === 'object' && lifecycle.create !== null && 'generate' in lifecycle.create);
156
+ return [[key, filled ? { ...property, readOnly: true } : property]];
157
+ }));
158
+ }
159
+ function pageOf(row) {
160
+ return {
161
+ type: 'object',
162
+ properties: {
163
+ items: { type: 'array', items: row },
164
+ total: { type: 'integer' },
165
+ hasMore: { type: 'boolean' },
166
+ endCursor: { type: 'string' },
167
+ },
168
+ required: ['items'],
169
+ };
170
+ }
171
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/openapi/document.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * Framework-agnostic route registration — translates RouteDefinition[] into HttpRouter calls.
3
3
  */
4
+ import { type Answering } from '@fougere/core';
4
5
  import type { HttpRouter } from '@fougere/http';
5
- import type { RouteDefinition } from './routes/RouteDefinition.js';
6
+ import type { AppLike, RouteDefinition } from './routes/RouteDefinition.js';
7
+ export interface RegisterOptions {
8
+ /** The app's cycle, which watches each request — `app.answer`. Without it the door runs unwatched. */
9
+ answer?: Answering;
10
+ }
6
11
  /** Register all route definitions on an HttpRouter. */
7
- export declare function registerRoutes(router: HttpRouter, routes: RouteDefinition[]): void;
12
+ export declare function registerRoutes(router: HttpRouter, routes: RouteDefinition[], { answer }?: RegisterOptions): void;
13
+ /** What REST publishes of itself, on a router — nothing unless `protocols: { rest: { openapi: {} } }`. */
14
+ export declare function registerDescribed(router: HttpRouter, app: AppLike): void;
8
15
  //# sourceMappingURL=register.d.ts.map