@getsoda/core 0.2.1 → 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.
Files changed (137) hide show
  1. package/README.md +103 -0
  2. package/dist/app/app.d.ts +15 -5
  3. package/dist/app/app.d.ts.map +1 -1
  4. package/dist/app/app.js +31 -8
  5. package/dist/app/app.js.map +1 -1
  6. package/dist/app/index.d.ts +1 -1
  7. package/dist/app/index.d.ts.map +1 -1
  8. package/dist/config/config.d.ts +57 -0
  9. package/dist/config/config.d.ts.map +1 -0
  10. package/dist/config/config.js +74 -0
  11. package/dist/config/config.js.map +1 -0
  12. package/dist/config/index.d.ts +9 -0
  13. package/dist/config/index.d.ts.map +1 -0
  14. package/dist/config/index.js +7 -0
  15. package/dist/config/index.js.map +1 -0
  16. package/dist/config/module.d.ts +29 -0
  17. package/dist/config/module.d.ts.map +1 -0
  18. package/dist/config/module.js +28 -0
  19. package/dist/config/module.js.map +1 -0
  20. package/dist/di/errors.d.ts.map +1 -1
  21. package/dist/di/errors.js +2 -0
  22. package/dist/di/errors.js.map +1 -1
  23. package/dist/http/arguments.d.ts +19 -6
  24. package/dist/http/arguments.d.ts.map +1 -1
  25. package/dist/http/arguments.js +7 -0
  26. package/dist/http/arguments.js.map +1 -1
  27. package/dist/http/cookies.d.ts.map +1 -1
  28. package/dist/http/cookies.js +2 -0
  29. package/dist/http/cookies.js.map +1 -1
  30. package/dist/http/decorators.d.ts +18 -6
  31. package/dist/http/decorators.d.ts.map +1 -1
  32. package/dist/http/decorators.js +7 -2
  33. package/dist/http/decorators.js.map +1 -1
  34. package/dist/http/errors.d.ts +3 -1
  35. package/dist/http/errors.d.ts.map +1 -1
  36. package/dist/http/errors.js +2 -0
  37. package/dist/http/errors.js.map +1 -1
  38. package/dist/http/index.d.ts +7 -5
  39. package/dist/http/index.d.ts.map +1 -1
  40. package/dist/http/index.js +3 -2
  41. package/dist/http/index.js.map +1 -1
  42. package/dist/http/parsers.d.ts +4 -0
  43. package/dist/http/parsers.d.ts.map +1 -1
  44. package/dist/http/parsers.js +9 -5
  45. package/dist/http/parsers.js.map +1 -1
  46. package/dist/http/pipeline.d.ts +4 -5
  47. package/dist/http/pipeline.d.ts.map +1 -1
  48. package/dist/http/pipeline.js +2 -2
  49. package/dist/http/pipeline.js.map +1 -1
  50. package/dist/http/routes.d.ts +27 -2
  51. package/dist/http/routes.d.ts.map +1 -1
  52. package/dist/http/routes.js +34 -20
  53. package/dist/http/routes.js.map +1 -1
  54. package/dist/http/schema.d.ts +2 -0
  55. package/dist/http/schema.d.ts.map +1 -1
  56. package/dist/http/schema.js +2 -1
  57. package/dist/http/schema.js.map +1 -1
  58. package/dist/http/sse.d.ts +39 -0
  59. package/dist/http/sse.d.ts.map +1 -0
  60. package/dist/http/sse.js +89 -0
  61. package/dist/http/sse.js.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +2 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/openapi/decorators.d.ts +41 -0
  67. package/dist/openapi/decorators.d.ts.map +1 -0
  68. package/dist/openapi/decorators.js +29 -0
  69. package/dist/openapi/decorators.js.map +1 -0
  70. package/dist/openapi/document.d.ts +62 -0
  71. package/dist/openapi/document.d.ts.map +1 -0
  72. package/dist/openapi/document.js +135 -0
  73. package/dist/openapi/document.js.map +1 -0
  74. package/dist/openapi/index.d.ts +11 -0
  75. package/dist/openapi/index.d.ts.map +1 -0
  76. package/dist/openapi/index.js +8 -0
  77. package/dist/openapi/index.js.map +1 -0
  78. package/dist/openapi/module.d.ts +26 -0
  79. package/dist/openapi/module.d.ts.map +1 -0
  80. package/dist/openapi/module.js +73 -0
  81. package/dist/openapi/module.js.map +1 -0
  82. package/dist/routing/errors.d.ts.map +1 -1
  83. package/dist/routing/errors.js +2 -0
  84. package/dist/routing/errors.js.map +1 -1
  85. package/dist/testing/index.d.ts +46 -0
  86. package/dist/testing/index.d.ts.map +1 -0
  87. package/dist/testing/index.js +49 -0
  88. package/dist/testing/index.js.map +1 -0
  89. package/dist/utils/errors.d.ts +11 -0
  90. package/dist/utils/errors.d.ts.map +1 -0
  91. package/dist/utils/errors.js +22 -0
  92. package/dist/utils/errors.js.map +1 -0
  93. package/dist/utils/object.d.ts +4 -0
  94. package/dist/utils/object.d.ts.map +1 -1
  95. package/dist/utils/object.js +16 -0
  96. package/dist/utils/object.js.map +1 -1
  97. package/dist/websocket/decorators.d.ts +31 -0
  98. package/dist/websocket/decorators.d.ts.map +1 -0
  99. package/dist/websocket/decorators.js +43 -0
  100. package/dist/websocket/decorators.js.map +1 -0
  101. package/dist/websocket/gateway.d.ts +61 -0
  102. package/dist/websocket/gateway.d.ts.map +1 -0
  103. package/dist/websocket/gateway.js +142 -0
  104. package/dist/websocket/gateway.js.map +1 -0
  105. package/dist/websocket/index.d.ts +7 -0
  106. package/dist/websocket/index.d.ts.map +1 -0
  107. package/dist/websocket/index.js +6 -0
  108. package/dist/websocket/index.js.map +1 -0
  109. package/package.json +12 -1
  110. package/src/app/app.ts +69 -18
  111. package/src/app/index.ts +1 -1
  112. package/src/config/config.ts +90 -0
  113. package/src/config/index.ts +9 -0
  114. package/src/config/module.ts +40 -0
  115. package/src/di/errors.ts +3 -0
  116. package/src/http/arguments.ts +31 -6
  117. package/src/http/cookies.ts +2 -0
  118. package/src/http/decorators.ts +25 -10
  119. package/src/http/errors.ts +7 -0
  120. package/src/http/index.ts +7 -4
  121. package/src/http/parsers.ts +18 -7
  122. package/src/http/pipeline.ts +6 -6
  123. package/src/http/routes.ts +68 -29
  124. package/src/http/schema.ts +2 -1
  125. package/src/http/sse.ts +120 -0
  126. package/src/index.ts +2 -0
  127. package/src/openapi/decorators.ts +50 -0
  128. package/src/openapi/document.ts +221 -0
  129. package/src/openapi/index.ts +11 -0
  130. package/src/openapi/module.ts +95 -0
  131. package/src/routing/errors.ts +3 -0
  132. package/src/testing/index.ts +73 -0
  133. package/src/utils/errors.ts +25 -0
  134. package/src/utils/object.ts +23 -0
  135. package/src/websocket/decorators.ts +55 -0
  136. package/src/websocket/gateway.ts +213 -0
  137. package/src/websocket/index.ts +7 -0
@@ -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":"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"}
@@ -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;AAEH;;;;;;;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,EAGhB;CACF"}
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"}
@@ -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;AAYH;;;;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;IAIf,CAAC;CACF"}
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"}
@@ -0,0 +1,49 @@
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 { createApp } from '../app/app.js';
25
+ /**
26
+ * Creates `root` and serves it on a free port. Options are those of
27
+ * `createApp`: `overrides` replace providers; the logger is silent unless one is given.
28
+ */
29
+ export const createTestApp = async (root, options = {}) => {
30
+ const app = await createApp(root, { logger: false, ...options });
31
+ const server = await app.listen({ port: 0, hostname: '127.0.0.1' });
32
+ const close = () => app.close();
33
+ return {
34
+ app,
35
+ url: server.url,
36
+ get: (token) => app.container.get(token),
37
+ request: (path, { json, ...init } = {}) => {
38
+ if (json === undefined) {
39
+ return fetch(new URL(path, server.url), init);
40
+ }
41
+ const headers = new Headers(init.headers);
42
+ headers.set('content-type', 'application/json');
43
+ return fetch(new URL(path, server.url), { ...init, headers, body: JSON.stringify(json) });
44
+ },
45
+ close,
46
+ [Symbol.asyncDispose]: close,
47
+ };
48
+ };
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAqBzC;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,IAAkB,EAAE,OAAO,GAAe,EAAE,EAAoB,EAAE;IACpG,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAA;IACnE,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;IAE/B,OAAO;QACL,GAAG;QACH,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;QACxC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;YACxC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,OAAO,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;YAC/C,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAEzC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAA;YAE/C,OAAO,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC3F,CAAC;QACD,KAAK;QACL,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK;KAC7B,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module
3
+ * Error utilities.
4
+ */
5
+ /**
6
+ * Removes the frames of the framework from the stack of an error about the
7
+ * application's code (a missing provider, an invalid route): the runtime then
8
+ * points at the application, not at the framework, and the message says the rest.
9
+ */
10
+ export declare const hideFrameworkFrames: (error: Error) => void;
11
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,UAAW,KAAK,KAAG,IASlD,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module
3
+ * Error utilities.
4
+ */
5
+ import { fileURLToPath } from 'node:url';
6
+ /** Where the framework's own files are: `src/` from sources, `dist/` once built. */
7
+ const FRAMEWORK = fileURLToPath(new URL('../', import.meta.url));
8
+ /**
9
+ * Removes the frames of the framework from the stack of an error about the
10
+ * application's code (a missing provider, an invalid route): the runtime then
11
+ * points at the application, not at the framework, and the message says the rest.
12
+ */
13
+ export const hideFrameworkFrames = (error) => {
14
+ if (error.stack === undefined) {
15
+ return;
16
+ }
17
+ error.stack = error.stack
18
+ .split('\n')
19
+ .filter((line) => !(line.trimStart().startsWith('at ') && line.includes(FRAMEWORK)))
20
+ .join('\n');
21
+ };
22
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,oFAAoF;AACpF,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAEhE;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAY,EAAQ,EAAE;IACxD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAM;IACR,CAAC;IAED,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;SACtB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;SACnF,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC,CAAA"}
@@ -11,4 +11,8 @@
11
11
  * ```
12
12
  */
13
13
  export declare const createNullObject: <T extends object>() => T;
14
+ /**
15
+ * Public method names along the prototype chain, most derived first, without duplicates.
16
+ */
17
+ export declare const methodNames: (instance: object) => string[];
14
18
  //# sourceMappingURL=object.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../src/utils/object.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,MAAM,OAAK,CAA6B,CAAA"}
1
+ {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../src/utils/object.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,MAAM,OAAK,CAA6B,CAAA;AAEnF;;GAEG;AACH,eAAO,MAAM,WAAW,aAAc,MAAM,KAAG,MAAM,EAkBpD,CAAA"}
@@ -11,4 +11,20 @@
11
11
  * ```
12
12
  */
13
13
  export const createNullObject = () => Object.create(null);
14
+ /**
15
+ * Public method names along the prototype chain, most derived first, without duplicates.
16
+ */
17
+ export const methodNames = (instance) => {
18
+ const names = [];
19
+ const seen = new Set(['constructor']);
20
+ for (let prototype = Object.getPrototypeOf(instance); prototype !== null && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)) {
21
+ for (const name of Object.getOwnPropertyNames(prototype)) {
22
+ if (!seen.has(name)) {
23
+ seen.add(name);
24
+ names.push(name);
25
+ }
26
+ }
27
+ }
28
+ return names;
29
+ };
14
30
  //# sourceMappingURL=object.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"object.js","sourceRoot":"","sources":["../../src/utils/object.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAwB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAM,CAAA"}
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../../src/utils/object.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAwB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAM,CAAA;AAEnF;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAgB,EAAY,EAAE;IACxD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,CAAC,aAAa,CAAC,CAAC,CAAA;IAE7C,KACE,IAAI,SAAS,GAAkB,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,EAC9D,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,EACpD,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,EAC5C,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;gBACd,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module
3
+ * Decorators of WebSocket gateways, as in `@nestjs/websockets`.
4
+ */
5
+ import type { Constructor } from '../di/index.ts';
6
+ /**
7
+ * Declares a gateway: WebSocket connections upgraded from `GET path`. List it
8
+ * in the `controllers` of its module: like a controller, it is an entry point
9
+ * the framework creates, and its guards run on the handshake.
10
+ *
11
+ * ```ts
12
+ * @WebSocketGateway('/chat')
13
+ * @UseGuards(JwtGuard)
14
+ * class ChatGateway {
15
+ * @SubscribeMessage('message')
16
+ * message(data: unknown, socket: GatewaySocket) {
17
+ * return { event: 'message', data }
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ export declare const WebSocketGateway: (path: string) => (target: Constructor<unknown>, _context: ClassDecoratorContext<Constructor<unknown>>) => void;
23
+ /**
24
+ * Handles the frames `{ "event": event, "data": ... }` of a gateway. The
25
+ * method receives `(data, socket)`; what it returns, unless `undefined`, is
26
+ * sent back as JSON.
27
+ */
28
+ export declare const SubscribeMessage: (event: string) => <This, M extends (this: This, ...args: any[]) => unknown>(method: M, context: ClassMethodDecoratorContext<This, M>) => void;
29
+ export declare const getGatewayPath: (target: Constructor<unknown>) => string | undefined;
30
+ export declare const getSubscribedEvent: (method: object) => string | undefined;
31
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/websocket/decorators.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAMjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,SACpB,MAAM,cACJ,WAAW,CAAC,OAAO,CAAC,YAAY,qBAAqB,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,KAAG,IAMtF,CAAA;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UACnB,MAAM,MACb,IAAI,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,UAAU,CAAC,WAAW,2BAA2B,CAAC,IAAI,EAAE,CAAC,CAAC,KAAG,IAMpH,CAAA;AAEH,eAAO,MAAM,cAAc,WAAY,WAAW,CAAC,OAAO,CAAC,KAAG,MAAM,GAAG,SAAiC,CAAA;AAExG,eAAO,MAAM,kBAAkB,WAAY,MAAM,KAAG,MAAM,GAAG,SAA+B,CAAA"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module
3
+ * Decorators of WebSocket gateways, as in `@nestjs/websockets`.
4
+ */
5
+ import { ControllerError } from '../http/errors.js';
6
+ const gateways = new WeakMap();
7
+ const events = new WeakMap();
8
+ /**
9
+ * Declares a gateway: WebSocket connections upgraded from `GET path`. List it
10
+ * in the `controllers` of its module: like a controller, it is an entry point
11
+ * the framework creates, and its guards run on the handshake.
12
+ *
13
+ * ```ts
14
+ * @WebSocketGateway('/chat')
15
+ * @UseGuards(JwtGuard)
16
+ * class ChatGateway {
17
+ * @SubscribeMessage('message')
18
+ * message(data: unknown, socket: GatewaySocket) {
19
+ * return { event: 'message', data }
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export const WebSocketGateway = (path) => (target, _context) => {
25
+ if (!path.startsWith('/') || /[:*?]/.test(path)) {
26
+ throw new ControllerError('INVALID_PATH', `@WebSocketGateway('${path}'): a gateway path is static and starts with "/"`);
27
+ }
28
+ gateways.set(target, path);
29
+ };
30
+ /**
31
+ * Handles the frames `{ "event": event, "data": ... }` of a gateway. The
32
+ * method receives `(data, socket)`; what it returns, unless `undefined`, is
33
+ * sent back as JSON.
34
+ */
35
+ export const SubscribeMessage = (event) => (method, context) => {
36
+ if (context.static || context.private) {
37
+ throw new ControllerError('INVALID_HANDLER', `@SubscribeMessage('${event}') must decorate a public instance method`);
38
+ }
39
+ events.set(method, event);
40
+ };
41
+ export const getGatewayPath = (target) => gateways.get(target);
42
+ export const getSubscribedEvent = (method) => events.get(method);
43
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/websocket/decorators.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAgC,CAAA;AAC5D,MAAM,MAAM,GAAG,IAAI,OAAO,EAAkB,CAAA;AAE5C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAC,IAAY,EAAE,EAAE,CACjB,CAAC,MAA4B,EAAE,QAAqD,EAAQ,EAAE;IAC5F,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,sBAAsB,IAAI,kDAAkD,CAAC,CAAA;IACzH,CAAC;IAED,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AAC5B,CAAC,CAAA;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAC,KAAa,EAAE,EAAE,CAClB,CAA0D,MAAS,EAAE,OAA6C,EAAQ,EAAE;IAC1H,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,sBAAsB,KAAK,2CAA2C,CAAC,CAAA;IACtH,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAC3B,CAAC,CAAA;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAA4B,EAAsB,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAExG,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAc,EAAsB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA"}