@getsoda/core 0.2.2 → 0.3.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.
- package/README.md +80 -0
- package/dist/app/app.d.ts +15 -5
- package/dist/app/app.d.ts.map +1 -1
- package/dist/app/app.js +31 -8
- package/dist/app/app.js.map +1 -1
- package/dist/app/index.d.ts +1 -1
- package/dist/app/index.d.ts.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +2 -0
- package/dist/config/config.js.map +1 -1
- package/dist/di/errors.d.ts.map +1 -1
- package/dist/di/errors.js +2 -0
- package/dist/di/errors.js.map +1 -1
- package/dist/http/arguments.d.ts +19 -6
- package/dist/http/arguments.d.ts.map +1 -1
- package/dist/http/arguments.js +7 -0
- package/dist/http/arguments.js.map +1 -1
- package/dist/http/cookies.d.ts.map +1 -1
- package/dist/http/cookies.js +2 -0
- package/dist/http/cookies.js.map +1 -1
- package/dist/http/decorators.d.ts +12 -5
- package/dist/http/decorators.d.ts.map +1 -1
- package/dist/http/decorators.js +7 -2
- package/dist/http/decorators.js.map +1 -1
- package/dist/http/errors.d.ts +3 -1
- package/dist/http/errors.d.ts.map +1 -1
- package/dist/http/errors.js +2 -0
- package/dist/http/errors.js.map +1 -1
- package/dist/http/index.d.ts +6 -4
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +3 -2
- package/dist/http/index.js.map +1 -1
- package/dist/http/parsers.d.ts +4 -0
- package/dist/http/parsers.d.ts.map +1 -1
- package/dist/http/parsers.js +9 -5
- package/dist/http/parsers.js.map +1 -1
- package/dist/http/pipeline.d.ts +4 -5
- package/dist/http/pipeline.d.ts.map +1 -1
- package/dist/http/pipeline.js +2 -2
- package/dist/http/pipeline.js.map +1 -1
- package/dist/http/routes.d.ts +27 -2
- package/dist/http/routes.d.ts.map +1 -1
- package/dist/http/routes.js +34 -20
- package/dist/http/routes.js.map +1 -1
- package/dist/http/sse.d.ts +39 -0
- package/dist/http/sse.d.ts.map +1 -0
- package/dist/http/sse.js +89 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi/decorators.d.ts +41 -0
- package/dist/openapi/decorators.d.ts.map +1 -0
- package/dist/openapi/decorators.js +29 -0
- package/dist/openapi/decorators.js.map +1 -0
- package/dist/openapi/document.d.ts +62 -0
- package/dist/openapi/document.d.ts.map +1 -0
- package/dist/openapi/document.js +135 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/index.d.ts +11 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +8 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/module.d.ts +26 -0
- package/dist/openapi/module.d.ts.map +1 -0
- package/dist/openapi/module.js +73 -0
- package/dist/openapi/module.js.map +1 -0
- package/dist/routing/errors.d.ts.map +1 -1
- package/dist/routing/errors.js +2 -0
- package/dist/routing/errors.js.map +1 -1
- package/dist/testing/index.d.ts +46 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +49 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/utils/errors.d.ts +11 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +22 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/object.d.ts +4 -0
- package/dist/utils/object.d.ts.map +1 -1
- package/dist/utils/object.js +16 -0
- package/dist/utils/object.js.map +1 -1
- package/dist/websocket/decorators.d.ts +31 -0
- package/dist/websocket/decorators.d.ts.map +1 -0
- package/dist/websocket/decorators.js +43 -0
- package/dist/websocket/decorators.js.map +1 -0
- package/dist/websocket/gateway.d.ts +61 -0
- package/dist/websocket/gateway.d.ts.map +1 -0
- package/dist/websocket/gateway.js +142 -0
- package/dist/websocket/gateway.js.map +1 -0
- package/dist/websocket/index.d.ts +7 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +6 -0
- package/dist/websocket/index.js.map +1 -0
- package/package.json +12 -1
- package/src/app/app.ts +69 -18
- package/src/app/index.ts +1 -1
- package/src/config/config.ts +2 -0
- package/src/di/errors.ts +3 -0
- package/src/http/arguments.ts +31 -6
- package/src/http/cookies.ts +2 -0
- package/src/http/decorators.ts +16 -8
- package/src/http/errors.ts +7 -0
- package/src/http/index.ts +6 -4
- package/src/http/parsers.ts +18 -7
- package/src/http/pipeline.ts +6 -6
- package/src/http/routes.ts +68 -29
- package/src/http/sse.ts +120 -0
- package/src/index.ts +1 -0
- package/src/openapi/decorators.ts +50 -0
- package/src/openapi/document.ts +221 -0
- package/src/openapi/index.ts +11 -0
- package/src/openapi/module.ts +95 -0
- package/src/routing/errors.ts +3 -0
- package/src/testing/index.ts +73 -0
- package/src/utils/errors.ts +25 -0
- package/src/utils/object.ts +23 -0
- package/src/websocket/decorators.ts +55 -0
- package/src/websocket/gateway.ts +213 -0
- package/src/websocket/index.ts +7 -0
package/dist/http/sse.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Server-sent events: a route decorated with `@Sse()` returns an async
|
|
4
|
+
* iterable of messages, streamed as `text/event-stream`, as Nest's `@Sse()`
|
|
5
|
+
* streams an observable.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* @Sse('ticks')
|
|
9
|
+
* async *ticks(): AsyncGenerator<SseMessage> {
|
|
10
|
+
* for (let i = 0; ; i++) {
|
|
11
|
+
* yield { data: { tick: i } }
|
|
12
|
+
* await Bun.sleep(1000)
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* When the client disconnects, the iterator is returned: a generator runs its
|
|
18
|
+
* `finally` blocks. An error thrown while streaming ends the stream and is logged.
|
|
19
|
+
*/
|
|
20
|
+
/** Headers of Nest's SSE responses: no caching, no buffering by nginx. */
|
|
21
|
+
const SSE_HEADERS = [
|
|
22
|
+
['content-type', 'text/event-stream'],
|
|
23
|
+
['cache-control', 'private, no-cache, no-store, must-revalidate, max-age=0, no-transform'],
|
|
24
|
+
['x-accel-buffering', 'no'],
|
|
25
|
+
];
|
|
26
|
+
const NEWLINE = /\r\n|\r|\n/;
|
|
27
|
+
const encoder = new TextEncoder();
|
|
28
|
+
const assertSingleLine = (name, value) => {
|
|
29
|
+
if (NEWLINE.test(value)) {
|
|
30
|
+
throw new TypeError(`SSE ${name} must not contain line breaks: ${JSON.stringify(value)}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** A message in the event stream format, one `data:` line per line of data. */
|
|
34
|
+
export const formatSseMessage = (message) => {
|
|
35
|
+
const data = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
|
|
36
|
+
let text = '';
|
|
37
|
+
if (message.event !== undefined) {
|
|
38
|
+
assertSingleLine('event', message.event);
|
|
39
|
+
text += `event: ${message.event}\n`;
|
|
40
|
+
}
|
|
41
|
+
if (message.id !== undefined) {
|
|
42
|
+
assertSingleLine('id', message.id);
|
|
43
|
+
text += `id: ${message.id}\n`;
|
|
44
|
+
}
|
|
45
|
+
if (message.retry !== undefined) {
|
|
46
|
+
text += `retry: ${Math.trunc(message.retry)}\n`;
|
|
47
|
+
}
|
|
48
|
+
for (const line of (data ?? '').split(NEWLINE)) {
|
|
49
|
+
text += `data: ${line}\n`;
|
|
50
|
+
}
|
|
51
|
+
return `${text}\n`;
|
|
52
|
+
};
|
|
53
|
+
const isAsyncIterable = (value) => typeof value === 'object' && value !== null && Symbol.asyncIterator in value;
|
|
54
|
+
/**
|
|
55
|
+
* Creates the function streaming what an `@Sse()` route returns. `headers`
|
|
56
|
+
* come from `@Header()`; errors while streaming go to `onError`.
|
|
57
|
+
*/
|
|
58
|
+
export const createSseResponder = (headers, onError) => {
|
|
59
|
+
const init = { status: 200, headers: [...SSE_HEADERS, ...headers] };
|
|
60
|
+
return (value, ctx) => {
|
|
61
|
+
if (!isAsyncIterable(value)) {
|
|
62
|
+
throw new TypeError(`${ctx.route.controller.name}.${ctx.route.handler} is an @Sse() route: it must return an async iterable`);
|
|
63
|
+
}
|
|
64
|
+
const iterator = value[Symbol.asyncIterator]();
|
|
65
|
+
const body = new ReadableStream({
|
|
66
|
+
async pull(controller) {
|
|
67
|
+
try {
|
|
68
|
+
const next = await iterator.next();
|
|
69
|
+
if (next.done === true) {
|
|
70
|
+
controller.close();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
controller.enqueue(encoder.encode(formatSseMessage(next.value)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
onError(error, ctx.req);
|
|
78
|
+
controller.close();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
// The client went away: let the source clean up.
|
|
82
|
+
async cancel() {
|
|
83
|
+
await iterator.return?.();
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
return new Response(body, init);
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/http/sse.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Decorators describing routes in the OpenAPI document, as `@nestjs/swagger` does.
|
|
4
|
+
*/
|
|
5
|
+
import type { ClassOrMethodDecorator } from '../http/enhancers.ts';
|
|
6
|
+
import type { StandardSchemaV1 } from '../http/schema.ts';
|
|
7
|
+
export interface ApiOperationOptions {
|
|
8
|
+
readonly summary?: string;
|
|
9
|
+
readonly description?: string;
|
|
10
|
+
readonly deprecated?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ApiResponseOptions {
|
|
13
|
+
readonly description: string;
|
|
14
|
+
/** Schema of the JSON body; needs a library implementing Standard JSON Schema, such as zod. */
|
|
15
|
+
readonly schema?: StandardSchemaV1;
|
|
16
|
+
}
|
|
17
|
+
/** Summary, description and deprecation of an operation. */
|
|
18
|
+
export declare const ApiOperation: (options: ApiOperationOptions) => ClassOrMethodDecorator;
|
|
19
|
+
/** Tags grouping operations, on a controller or a method (which wins). */
|
|
20
|
+
export declare const ApiTags: (...names: string[]) => ClassOrMethodDecorator;
|
|
21
|
+
/**
|
|
22
|
+
* Responses of an operation, by status. Without it, the operation has the
|
|
23
|
+
* status of its route (`200`, `201` for `POST`, or `@HttpCode()`).
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* @ApiResponses({ 200: { description: 'The user', schema: User }, 404: { description: 'No such user' } })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const ApiResponses: (byStatus: Readonly<Record<number, ApiResponseOptions>>) => ClassOrMethodDecorator;
|
|
30
|
+
/** Operations need `Authorization: Bearer <token>`, as `JwtGuard` reads. */
|
|
31
|
+
export declare const ApiBearerAuth: () => ClassOrMethodDecorator;
|
|
32
|
+
/** Leaves a controller or a method out of the document. */
|
|
33
|
+
export declare const ApiExclude: () => ClassOrMethodDecorator;
|
|
34
|
+
export declare const KEYS: {
|
|
35
|
+
readonly operation: symbol;
|
|
36
|
+
readonly tags: symbol;
|
|
37
|
+
readonly responses: symbol;
|
|
38
|
+
readonly bearer: symbol;
|
|
39
|
+
readonly exclude: symbol;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=decorators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/openapi/decorators.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;CACnC;AAQD,4DAA4D;AAC5D,eAAO,MAAM,YAAY,YAAa,mBAAmB,KAAG,sBAA4C,CAAA;AAExG,0EAA0E;AAC1E,eAAO,MAAM,OAAO,aAAc,MAAM,EAAE,KAAG,sBAAqC,CAAA;AAElF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,aAAc,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,KAAG,sBAA6C,CAAA;AAEnI,4EAA4E;AAC5E,eAAO,MAAM,aAAa,QAAO,sBAAsC,CAAA;AAEvE,2DAA2D;AAC3D,eAAO,MAAM,UAAU,QAAO,sBAAuC,CAAA;AAErE,eAAO,MAAM,IAAI;aAAK,SAAS;aAAiB,IAAI;aAAY,SAAS;aAAiB,MAAM;aAAc,OAAO;CAAwB,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Decorators describing routes in the OpenAPI document, as `@nestjs/swagger` does.
|
|
4
|
+
*/
|
|
5
|
+
import { createDecorator } from '../http/enhancers.js';
|
|
6
|
+
const operation = createDecorator('ApiOperation');
|
|
7
|
+
const tags = createDecorator('ApiTags');
|
|
8
|
+
const responses = createDecorator('ApiResponses');
|
|
9
|
+
const bearer = createDecorator('ApiBearerAuth');
|
|
10
|
+
const exclude = createDecorator('ApiExclude');
|
|
11
|
+
/** Summary, description and deprecation of an operation. */
|
|
12
|
+
export const ApiOperation = (options) => operation(options);
|
|
13
|
+
/** Tags grouping operations, on a controller or a method (which wins). */
|
|
14
|
+
export const ApiTags = (...names) => tags(names);
|
|
15
|
+
/**
|
|
16
|
+
* Responses of an operation, by status. Without it, the operation has the
|
|
17
|
+
* status of its route (`200`, `201` for `POST`, or `@HttpCode()`).
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* @ApiResponses({ 200: { description: 'The user', schema: User }, 404: { description: 'No such user' } })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const ApiResponses = (byStatus) => responses(byStatus);
|
|
24
|
+
/** Operations need `Authorization: Bearer <token>`, as `JwtGuard` reads. */
|
|
25
|
+
export const ApiBearerAuth = () => bearer(true);
|
|
26
|
+
/** Leaves a controller or a method out of the document. */
|
|
27
|
+
export const ApiExclude = () => exclude(true);
|
|
28
|
+
export const KEYS = { operation: operation.key, tags: tags.key, responses: responses.key, bearer: bearer.key, exclude: exclude.key };
|
|
29
|
+
//# sourceMappingURL=decorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/openapi/decorators.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAgBtD,MAAM,SAAS,GAAG,eAAe,CAAsB,cAAc,CAAC,CAAA;AACtE,MAAM,IAAI,GAAG,eAAe,CAAoB,SAAS,CAAC,CAAA;AAC1D,MAAM,SAAS,GAAG,eAAe,CAA+C,cAAc,CAAC,CAAA;AAC/F,MAAM,MAAM,GAAG,eAAe,CAAU,eAAe,CAAC,CAAA;AACxD,MAAM,OAAO,GAAG,eAAe,CAAU,YAAY,CAAC,CAAA;AAEtD,4DAA4D;AAC5D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAA4B,EAA0B,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;AAExG,0EAA0E;AAC1E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAG,KAAe,EAA0B,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAElF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAsD,EAA0B,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;AAEnI,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,GAA2B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AAEvE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,GAA2B,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAErE,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAW,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Building an OpenAPI 3.1 document from the routes of an application.
|
|
4
|
+
*
|
|
5
|
+
* Schemas come from what validates requests: argument parsers (`int()`,
|
|
6
|
+
* `uuid()`...) and Standard Schemas implementing Standard JSON Schema, as zod
|
|
7
|
+
* does. Paths with optional parameters give one path per form, as OpenAPI has
|
|
8
|
+
* no optional path parameters; `@All()` routes are left out, OpenAPI has no
|
|
9
|
+
* such method.
|
|
10
|
+
*/
|
|
11
|
+
import type { MappedController } from '../http/routes.ts';
|
|
12
|
+
export type JsonSchema = Readonly<Record<string, unknown>>;
|
|
13
|
+
export interface OpenApiOptions {
|
|
14
|
+
readonly info: {
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly version: string;
|
|
17
|
+
readonly description?: string;
|
|
18
|
+
};
|
|
19
|
+
readonly servers?: readonly {
|
|
20
|
+
readonly url: string;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
export interface OpenApiParameter {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly in: 'path' | 'query' | 'header' | 'cookie';
|
|
27
|
+
readonly required: boolean;
|
|
28
|
+
readonly schema: JsonSchema;
|
|
29
|
+
}
|
|
30
|
+
export interface OpenApiOperation {
|
|
31
|
+
readonly operationId: string;
|
|
32
|
+
readonly summary?: string;
|
|
33
|
+
readonly description?: string;
|
|
34
|
+
readonly deprecated?: boolean;
|
|
35
|
+
readonly tags?: readonly string[];
|
|
36
|
+
readonly parameters?: readonly OpenApiParameter[];
|
|
37
|
+
readonly requestBody?: {
|
|
38
|
+
readonly required: boolean;
|
|
39
|
+
readonly content: Readonly<Record<string, {
|
|
40
|
+
readonly schema: JsonSchema;
|
|
41
|
+
}>>;
|
|
42
|
+
};
|
|
43
|
+
readonly responses: Readonly<Record<string, {
|
|
44
|
+
readonly description: string;
|
|
45
|
+
readonly content?: Readonly<Record<string, {
|
|
46
|
+
readonly schema: JsonSchema;
|
|
47
|
+
}>>;
|
|
48
|
+
}>>;
|
|
49
|
+
readonly security?: readonly Readonly<Record<string, readonly string[]>>[];
|
|
50
|
+
}
|
|
51
|
+
export interface OpenApiDocument {
|
|
52
|
+
readonly openapi: '3.1.0';
|
|
53
|
+
readonly info: OpenApiOptions['info'];
|
|
54
|
+
readonly servers?: OpenApiOptions['servers'];
|
|
55
|
+
readonly paths: Readonly<Record<string, Readonly<Record<string, OpenApiOperation>>>>;
|
|
56
|
+
readonly components?: {
|
|
57
|
+
readonly securitySchemes: Readonly<Record<string, JsonSchema>>;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** The OpenAPI document of `controllers`, as `inject(ROUTES).controllers` lists them. */
|
|
61
|
+
export declare const createOpenApiDocument: (controllers: readonly MappedController[], options: OpenApiOptions) => OpenApiDocument;
|
|
62
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/openapi/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAe,MAAM,mBAAmB,CAAA;AAQtE,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CACtF;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;SAAE,CAAC,CAAC,CAAA;KAAE,CAAA;IAClI,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;SAAE,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC,CAAA;IAC5J,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EAAE,CAAA;CAC3E;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAA;IAC5C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAA;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;KAAE,CAAA;CACzF;AAqJD,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,gBAAiB,SAAS,gBAAgB,EAAE,WAAW,cAAc,KAAG,eAiBzG,CAAA"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Building an OpenAPI 3.1 document from the routes of an application.
|
|
4
|
+
*
|
|
5
|
+
* Schemas come from what validates requests: argument parsers (`int()`,
|
|
6
|
+
* `uuid()`...) and Standard Schemas implementing Standard JSON Schema, as zod
|
|
7
|
+
* does. Paths with optional parameters give one path per form, as OpenAPI has
|
|
8
|
+
* no optional path parameters; `@All()` routes are left out, OpenAPI has no
|
|
9
|
+
* such method.
|
|
10
|
+
*/
|
|
11
|
+
import { isStandardSchema } from '../http/schema.js';
|
|
12
|
+
import { expandOptional, parsePath } from '../routing/path.js';
|
|
13
|
+
import { KEYS } from './decorators.js';
|
|
14
|
+
const METHODS = new Set(['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'TRACE']);
|
|
15
|
+
const IN = { query: 'query', header: 'header', cookie: 'cookie' };
|
|
16
|
+
const STRING = { type: 'string' };
|
|
17
|
+
const FORM_TYPES = ['multipart/form-data', 'application/x-www-form-urlencoded'];
|
|
18
|
+
/** JSON Schema of what a parser or schema accepts, `{}` when it cannot tell. */
|
|
19
|
+
const toJsonSchema = (source) => {
|
|
20
|
+
if (source === undefined) {
|
|
21
|
+
return STRING;
|
|
22
|
+
}
|
|
23
|
+
if (!isStandardSchema(source)) {
|
|
24
|
+
return source.jsonSchema ?? STRING;
|
|
25
|
+
}
|
|
26
|
+
const converter = source['~standard'].jsonSchema;
|
|
27
|
+
if (converter === undefined) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
const { $schema: _, ...schema } = converter.input({ target: 'draft-2020-12' });
|
|
31
|
+
return schema;
|
|
32
|
+
};
|
|
33
|
+
/** A value may be missing when its parser says so, or its schema accepts `undefined`. */
|
|
34
|
+
const isOptional = (source) => {
|
|
35
|
+
if (source === undefined) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (!isStandardSchema(source)) {
|
|
39
|
+
return source.optional === true;
|
|
40
|
+
}
|
|
41
|
+
const result = source['~standard'].validate(undefined);
|
|
42
|
+
return !(result instanceof Promise) && result.issues === undefined;
|
|
43
|
+
};
|
|
44
|
+
const toOpenApiPath = (segments) => `/${segments
|
|
45
|
+
.map((segment) => segment.type === 'static' ? decodeURIComponent(segment.value) : `{${segment.type === 'param' ? segment.name : (segment.name ?? 'wildcard')}}`)
|
|
46
|
+
.join('/')}`;
|
|
47
|
+
const pathParameters = (segments, args) => {
|
|
48
|
+
const parameters = [];
|
|
49
|
+
for (const segment of segments) {
|
|
50
|
+
if (segment.type === 'static') {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const name = segment.type === 'param' ? segment.name : (segment.name ?? 'wildcard');
|
|
54
|
+
const declared = args.find((arg) => arg.kind === 'param' && arg.name === name);
|
|
55
|
+
const pattern = segment.type === 'param' && segment.pattern !== undefined ? { pattern: `^(?:${segment.pattern})$` } : {};
|
|
56
|
+
parameters.push({ name, in: 'path', required: true, schema: { ...toJsonSchema(declared?.source), ...pattern } });
|
|
57
|
+
}
|
|
58
|
+
return parameters;
|
|
59
|
+
};
|
|
60
|
+
const requestBody = (args) => {
|
|
61
|
+
const body = args.find((arg) => arg.kind === 'body');
|
|
62
|
+
if (body === undefined) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const schema = body.source === undefined ? {} : toJsonSchema(body.source);
|
|
66
|
+
const types = body.multipart === true ? FORM_TYPES : ['application/json'];
|
|
67
|
+
return { required: !isOptional(body.source), content: Object.fromEntries(types.map((type) => [type, { schema }])) };
|
|
68
|
+
};
|
|
69
|
+
const responsesOf = (route) => {
|
|
70
|
+
const declared = route.info.metadata.get(KEYS.responses);
|
|
71
|
+
if (declared === undefined) {
|
|
72
|
+
return route.sse
|
|
73
|
+
? { 200: { description: 'Event stream', content: { 'text/event-stream': { schema: STRING } } } }
|
|
74
|
+
: { [route.status]: { description: 'Success' } };
|
|
75
|
+
}
|
|
76
|
+
return Object.fromEntries(Object.entries(declared).map(([status, response]) => [
|
|
77
|
+
status,
|
|
78
|
+
response.schema === undefined
|
|
79
|
+
? { description: response.description }
|
|
80
|
+
: { description: response.description, content: { 'application/json': { schema: toJsonSchema(response.schema) } } },
|
|
81
|
+
]));
|
|
82
|
+
};
|
|
83
|
+
const operationOf = (route, segments, suffix) => {
|
|
84
|
+
const { info } = route;
|
|
85
|
+
const args = route.args ?? [];
|
|
86
|
+
const parameters = pathParameters(segments, args);
|
|
87
|
+
for (const arg of args) {
|
|
88
|
+
if (arg.kind === 'query' || arg.kind === 'header' || arg.kind === 'cookie') {
|
|
89
|
+
parameters.push({ name: arg.name, in: IN[arg.kind], required: !isOptional(arg.source), schema: toJsonSchema(arg.source) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const options = info.metadata.get(KEYS.operation) ?? {};
|
|
93
|
+
const tags = info.metadata.get(KEYS.tags);
|
|
94
|
+
const body = requestBody(args);
|
|
95
|
+
return {
|
|
96
|
+
operationId: `${info.controller.name}_${info.handler}${suffix}`,
|
|
97
|
+
...options,
|
|
98
|
+
...(tags === undefined ? {} : { tags }),
|
|
99
|
+
...(parameters.length === 0 ? {} : { parameters }),
|
|
100
|
+
...(body === undefined ? {} : { requestBody: body }),
|
|
101
|
+
responses: responsesOf(route),
|
|
102
|
+
...(info.metadata.get(KEYS.bearer) === true ? { security: [{ bearer: [] }] } : {}),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
/** Adds the operations of `route` to `paths`, one per form of its path; `false` when it is left out. */
|
|
106
|
+
const addRoute = (paths, route) => {
|
|
107
|
+
const { info } = route;
|
|
108
|
+
if (!METHODS.has(info.method) || info.metadata.get(KEYS.exclude) === true) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
for (const [index, segments] of expandOptional(info.path, parsePath(info.path)).entries()) {
|
|
112
|
+
const path = toOpenApiPath(segments);
|
|
113
|
+
paths[path] ??= {};
|
|
114
|
+
paths[path][info.method.toLowerCase()] = operationOf(route, segments, index === 0 ? '' : `_${index}`);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
/** The OpenAPI document of `controllers`, as `inject(ROUTES).controllers` lists them. */
|
|
119
|
+
export const createOpenApiDocument = (controllers, options) => {
|
|
120
|
+
const paths = {};
|
|
121
|
+
let bearer = false;
|
|
122
|
+
for (const controller of controllers) {
|
|
123
|
+
for (const route of controller.routes) {
|
|
124
|
+
bearer = (addRoute(paths, route) && route.info.metadata.get(KEYS.bearer) === true) || bearer;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
openapi: '3.1.0',
|
|
129
|
+
info: options.info,
|
|
130
|
+
...(options.servers === undefined ? {} : { servers: options.servers }),
|
|
131
|
+
paths,
|
|
132
|
+
...(bearer ? { components: { securitySchemes: { bearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } } } : {}),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/openapi/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9D,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAqCtC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAC9F,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAW,CAAA;AAC1E,MAAM,MAAM,GAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAC7C,MAAM,UAAU,GAAG,CAAC,qBAAqB,EAAE,mCAAmC,CAAC,CAAA;AAO/E,gFAAgF;AAChF,MAAM,YAAY,GAAG,CAAC,MAAmC,EAAc,EAAE;IACvE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAQ,MAA0B,CAAC,UAAU,IAAI,MAAM,CAAA;IACzD,CAAC;IAED,MAAM,SAAS,GAAI,MAAyB,CAAC,WAAW,CAAC,CAAC,UAAU,CAAA;IAEpE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;IAE9E,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,yFAAyF;AACzF,MAAM,UAAU,GAAG,CAAC,MAAmC,EAAW,EAAE;IAClE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAQ,MAA0B,CAAC,QAAQ,KAAK,IAAI,CAAA;IACtD,CAAC;IAED,MAAM,MAAM,GAAI,MAA2B,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAE5E,OAAO,CAAC,CAAC,MAAM,YAAY,OAAO,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAA;AACpE,CAAC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,QAA4B,EAAU,EAAE,CAC7D,IAAI,QAAQ;KACT,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACf,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC,GAAG,CAC9I;KACA,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;AAEhB,MAAM,cAAc,GAAG,CAAC,QAA4B,EAAE,IAAkC,EAAsB,EAAE;IAC9G,MAAM,UAAU,GAAuB,EAAE,CAAA;IAEzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,SAAQ;QACV,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC,CAAA;QACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QAC9E,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAExH,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,CAAA;IAClH,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,IAAkC,EAAmC,EAAE;IAC1F,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;IAEpD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAA;IAEzE,OAAO,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACrH,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,KAAkB,EAAiC,EAAE;IACxE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAA6D,CAAA;IAEpH,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,GAAG;YACd,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE;YAChG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,CAAA;IACpD,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;QACnD,MAAM;QACN,QAAQ,CAAC,MAAM,KAAK,SAAS;YAC3B,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE;YACvC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE;KACtH,CAAC,CACH,CAAA;AACH,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,KAAkB,EAAE,QAA4B,EAAE,MAAc,EAAoB,EAAE;IACzG,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;IACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAA;IAC7B,MAAM,UAAU,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAEjD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3E,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC5H,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAqC,IAAI,EAAE,CAAA;IAC5F,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAkC,CAAA;IAC1E,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAE9B,OAAO;QACL,WAAW,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,GAAG,MAAM,EAAE;QAC/D,GAAG,OAAO;QACV,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACvC,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;QAClD,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QACpD,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC;QAC7B,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnF,CAAA;AACH,CAAC,CAAA;AAED,wGAAwG;AACxG,MAAM,QAAQ,GAAG,CAAC,KAAuD,EAAE,KAAkB,EAAW,EAAE;IACxG,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAA;IAEtB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1E,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1F,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;QAEpC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QAClB,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAA;IACvG,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,WAAwC,EAAE,OAAuB,EAAmB,EAAE;IAC1H,MAAM,KAAK,GAAqD,EAAE,CAAA;IAClE,IAAI,MAAM,GAAG,KAAK,CAAA;IAElB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YACtC,MAAM,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,IAAI,MAAM,CAAA;QAC9F,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtE,KAAK;QACL,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,eAAe,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5H,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* OpenAPI documents of an application, and a Swagger UI page, as `@nestjs/swagger`.
|
|
4
|
+
*/
|
|
5
|
+
export { ApiBearerAuth, ApiExclude, ApiOperation, ApiResponses, ApiTags } from './decorators.ts';
|
|
6
|
+
export type { ApiOperationOptions, ApiResponseOptions } from './decorators.ts';
|
|
7
|
+
export { createOpenApiDocument } from './document.ts';
|
|
8
|
+
export type { JsonSchema, OpenApiDocument, OpenApiOperation, OpenApiOptions, OpenApiParameter } from './document.ts';
|
|
9
|
+
export { OpenApiModule } from './module.ts';
|
|
10
|
+
export type { OpenApiModuleOptions } from './module.ts';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/openapi/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAChG,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACrD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACpH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* OpenAPI documents of an application, and a Swagger UI page, as `@nestjs/swagger`.
|
|
4
|
+
*/
|
|
5
|
+
export { ApiBearerAuth, ApiExclude, ApiOperation, ApiResponses, ApiTags } from './decorators.js';
|
|
6
|
+
export { createOpenApiDocument } from './document.js';
|
|
7
|
+
export { OpenApiModule } from './module.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/openapi/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEhG,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* `OpenApiModule`: serves the OpenAPI document of the application, and a
|
|
4
|
+
* Swagger UI page, as `SwaggerModule.setup()` of `@nestjs/swagger` does.
|
|
5
|
+
*/
|
|
6
|
+
import type { DynamicModule } from '../di/index.ts';
|
|
7
|
+
import type { OpenApiOptions } from './document.ts';
|
|
8
|
+
export interface OpenApiModuleOptions extends OpenApiOptions {
|
|
9
|
+
/** Path of the JSON document. Defaults to `/openapi.json`. */
|
|
10
|
+
readonly path?: string;
|
|
11
|
+
/** Path of a Swagger UI page reading the document, such as `/docs`. None by default. */
|
|
12
|
+
readonly ui?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* ```ts
|
|
16
|
+
* @Module({ imports: [OpenApiModule.forRoot({ info: { title: 'Shop', version: '1.0.0' }, ui: '/docs' })] })
|
|
17
|
+
* class AppModule {}
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The document is built on the first request, from every route of the
|
|
21
|
+
* application (these two excepted), and kept.
|
|
22
|
+
*/
|
|
23
|
+
export declare class OpenApiModule {
|
|
24
|
+
static forRoot(options: OpenApiModuleOptions): DynamicModule;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/openapi/module.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAKnD,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,eAAe,CAAA;AAEpE,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC1D,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CACrB;AAqBD;;;;;;;;GAQG;AACH,qBACa,aAAa;IACxB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CA0C3D;CACF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* `OpenApiModule`: serves the OpenAPI document of the application, and a
|
|
4
|
+
* Swagger UI page, as `SwaggerModule.setup()` of `@nestjs/swagger` does.
|
|
5
|
+
*/
|
|
6
|
+
import { inject, Module } from '../di/index.js';
|
|
7
|
+
import { Controller, Get } from '../http/decorators.js';
|
|
8
|
+
import { ROUTES } from '../http/routes.js';
|
|
9
|
+
import { ApiExclude } from './decorators.js';
|
|
10
|
+
import { createOpenApiDocument } from './document.js';
|
|
11
|
+
/** Swagger UI, from a CDN at a fixed version. */
|
|
12
|
+
const SWAGGER_UI = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.34.0';
|
|
13
|
+
const page = (title, documentUrl) => `<!doctype html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
+
<title>${Bun.escapeHTML(title)}</title>
|
|
19
|
+
<link rel="stylesheet" href="${SWAGGER_UI}/swagger-ui.css">
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="docs"></div>
|
|
23
|
+
<script src="${SWAGGER_UI}/swagger-ui-bundle.js" crossorigin></script>
|
|
24
|
+
<script>SwaggerUIBundle({ url: ${JSON.stringify(documentUrl)}, dom_id: '#docs' })</script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
27
|
+
`;
|
|
28
|
+
/**
|
|
29
|
+
* ```ts
|
|
30
|
+
* @Module({ imports: [OpenApiModule.forRoot({ info: { title: 'Shop', version: '1.0.0' }, ui: '/docs' })] })
|
|
31
|
+
* class AppModule {}
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* The document is built on the first request, from every route of the
|
|
35
|
+
* application (these two excepted), and kept.
|
|
36
|
+
*/
|
|
37
|
+
@Module({})
|
|
38
|
+
export class OpenApiModule {
|
|
39
|
+
static forRoot(options) {
|
|
40
|
+
const documentPath = options.path ?? '/openapi.json';
|
|
41
|
+
@Controller()
|
|
42
|
+
@ApiExclude()
|
|
43
|
+
class OpenApiController {
|
|
44
|
+
#routes = inject(ROUTES);
|
|
45
|
+
#document;
|
|
46
|
+
@Get(documentPath)
|
|
47
|
+
document() {
|
|
48
|
+
this.#document ??= createOpenApiDocument(this.#routes.controllers, options);
|
|
49
|
+
return this.#document;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const controllers = [OpenApiController];
|
|
53
|
+
if (options.ui !== undefined) {
|
|
54
|
+
const ui = options.ui;
|
|
55
|
+
@Controller()
|
|
56
|
+
@ApiExclude()
|
|
57
|
+
class OpenApiUiController {
|
|
58
|
+
#routes = inject(ROUTES);
|
|
59
|
+
@Get(ui)
|
|
60
|
+
page() {
|
|
61
|
+
// The document's own path, global prefix included.
|
|
62
|
+
const documentUrl = this.#routes.controllers.find((mapped) => mapped.controller === OpenApiController)?.routes[0]?.info.path;
|
|
63
|
+
return new Response(page(options.info.title, documentUrl ?? documentPath), {
|
|
64
|
+
headers: { 'content-type': 'text/html;charset=utf-8' },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
controllers.push(OpenApiUiController);
|
|
69
|
+
}
|
|
70
|
+
return { module: OpenApiModule, controllers };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/openapi/module.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAUrD,iDAAiD;AACjD,MAAM,UAAU,GAAG,qDAAqD,CAAA;AAExE,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,WAAmB,EAAU,EAAE,CAAC;;;;;SAKpD,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC;+BACC,UAAU;;;;eAI1B,UAAU;iCACQ,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;CAG3D,CAAA;AAED;;;;;;;;GAQG;AACH,CAAC,MAAM,CAAC,EAAE,CAAC;AACX,MAAM,OAAO,aAAa;IACxB,MAAM,CAAC,OAAO,CAAC,OAA6B;QAC1C,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,IAAI,eAAe,CAAA;QAEpD,CAAC,UAAU,EAAE;QACb,CAAC,UAAU,EAAE;cACP,iBAAiB;YACZ,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;YACjC,SAAS,CAA6B;YAEtC,CAAC,GAAG,CAAC,YAAY,CAAC;YAClB,QAAQ;gBACN,IAAI,CAAC,SAAS,KAAK,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;gBAE3E,OAAO,IAAI,CAAC,SAAS,CAAA;YACvB,CAAC;SACF;QAED,MAAM,WAAW,GAA0B,CAAC,iBAAiB,CAAC,CAAA;QAE9D,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAA;YAErB,CAAC,UAAU,EAAE;YACb,CAAC,UAAU,EAAE;kBACP,mBAAmB;gBACd,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;gBAEjC,CAAC,GAAG,CAAC,EAAE,CAAC;gBACR,IAAI;oBACF,mDAAmD;oBACnD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,KAAK,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAA;oBAE5H,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,IAAI,YAAY,CAAC,EAAE;wBACzE,OAAO,EAAE,EAAE,cAAc,EAAE,yBAAyB,EAAE;qBACvD,CAAC,CAAA;gBACJ,CAAC;aACF;YAED,WAAW,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACvC,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;IAC/C,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/routing/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/routing/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,gBAAgB,GAAG,UAAU,GAAG,aAAa,CAAA;AAErF;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAIjC,QAAQ,CAAC,IAAI,EAAE,cAAc;IAH/B,SAAkB,IAAI,gBAAe;IAErC,YACW,IAAI,EAAE,cAAc,EAC7B,OAAO,EAAE,MAAM,EAIhB;CACF"}
|
package/dist/routing/errors.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* Errors raised by the route compiler.
|
|
4
4
|
*/
|
|
5
|
+
import { hideFrameworkFrames } from '../utils/errors.js';
|
|
5
6
|
/**
|
|
6
7
|
* Error thrown by `compileRoutes`.
|
|
7
8
|
*
|
|
@@ -13,6 +14,7 @@ export class RouteError extends Error {
|
|
|
13
14
|
constructor(code, message) {
|
|
14
15
|
super(message);
|
|
15
16
|
this.code = code;
|
|
17
|
+
hideFrameworkFrames(this);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/routing/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/routing/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAYxD;;;;GAIG;AACH,MAAM,OAAO,UAAW,SAAQ,KAAK;IAIxB,IAAI;IAHG,IAAI,GAAG,YAAY,CAAA;IAErC,YACW,IAAoB,EAC7B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAA;oBAHL,IAAI;QAIb,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Testing an application over HTTP, as Nest's `Test.createTestingModule()`
|
|
4
|
+
* with supertest does, on a real Bun server bound to a free port.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { createTestApp } from '@getsoda/core/testing'
|
|
8
|
+
*
|
|
9
|
+
* const app = await createTestApp(AppModule, {
|
|
10
|
+
* overrides: [provide(UsersRepository, { useValue: new MemoryUsersRepository() })],
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* afterAll(() => app.close())
|
|
14
|
+
*
|
|
15
|
+
* test('creates a user', async () => {
|
|
16
|
+
* const response = await app.request('/users', { method: 'POST', json: { name: 'Bo' } })
|
|
17
|
+
*
|
|
18
|
+
* expect(response.status).toBe(201)
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A single service needs no server: `createContainer(Module, { overrides })`.
|
|
23
|
+
*/
|
|
24
|
+
import type { App, AppOptions } from '../app/app.ts';
|
|
25
|
+
import type { ModuleImport, Token } from '../di/index.ts';
|
|
26
|
+
export interface TestRequestInit extends RequestInit {
|
|
27
|
+
/** Sent as the JSON body, with `Content-Type: application/json`. */
|
|
28
|
+
readonly json?: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** A started application, closed by `close()` or at the end of an `await using` block. */
|
|
31
|
+
export interface TestApp extends AsyncDisposable {
|
|
32
|
+
readonly app: App;
|
|
33
|
+
/** Where the application listens, on a free port of localhost. */
|
|
34
|
+
readonly url: URL;
|
|
35
|
+
/** `app.container.get()`: a provider as the root module sees it. */
|
|
36
|
+
get<T>(token: Token<T>): T;
|
|
37
|
+
/** Sends a request to `path`, relative to the application. */
|
|
38
|
+
request(path: string, init?: TestRequestInit): Promise<Response>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates `root` and serves it on a free port. Options are those of
|
|
43
|
+
* `createApp`: `overrides` replace providers; the logger is silent unless one is given.
|
|
44
|
+
*/
|
|
45
|
+
export declare const createTestApp: (root: ModuleImport, options?: AppOptions) => Promise<TestApp>;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAEzD,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,0FAA0F;AAC1F,MAAM,WAAW,OAAQ,SAAQ,eAAe;IAC9C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,oEAAoE;IACpE,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1B,8DAA8D;IAC9D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,SAAgB,YAAY,YAAW,UAAU,KAAQ,OAAO,CAAC,OAAO,CAuBjG,CAAA"}
|