@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.
- package/README.md +103 -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 +57 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +74 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +7 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/module.d.ts +29 -0
- package/dist/config/module.d.ts.map +1 -0
- package/dist/config/module.js +28 -0
- package/dist/config/module.js.map +1 -0
- 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 +18 -6
- 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 +7 -5
- 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/schema.d.ts +2 -0
- package/dist/http/schema.d.ts.map +1 -1
- package/dist/http/schema.js +2 -1
- package/dist/http/schema.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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +90 -0
- package/src/config/index.ts +9 -0
- package/src/config/module.ts +40 -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 +25 -10
- package/src/http/errors.ts +7 -0
- package/src/http/index.ts +7 -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/schema.ts +2 -1
- package/src/http/sse.ts +120 -0
- package/src/index.ts +2 -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/README.md
CHANGED
|
@@ -72,6 +72,29 @@ app.enableShutdownSignals()
|
|
|
72
72
|
await app.listen(3000)
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
A configuration is a schema over the environment (Bun loads `.env` files into it), validated when the application starts and injected with its type:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { ConfigModule, defineConfig } from '@getsoda/core'
|
|
81
|
+
import { z } from 'zod'
|
|
82
|
+
|
|
83
|
+
export const AppConfig = defineConfig('app', z.object({
|
|
84
|
+
PORT: z.coerce.number().default(3000),
|
|
85
|
+
JWT_SECRET: z.string().min(32),
|
|
86
|
+
}))
|
|
87
|
+
|
|
88
|
+
@Module({ imports: [ConfigModule.forRoot({ load: [AppConfig], global: true })] })
|
|
89
|
+
class AppModule {}
|
|
90
|
+
|
|
91
|
+
class AuthService {
|
|
92
|
+
readonly #config = inject(AppConfig) // { PORT: number; JWT_SECRET: string }
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
An invalid environment stops the startup with every issue listed. `defineConfig(name, schema, (env) => ({ ... }))` builds nested configurations; tests replace one with `overrides: [provide(AppConfig, { useValue })]`.
|
|
97
|
+
|
|
75
98
|
## Cookies
|
|
76
99
|
|
|
77
100
|
`ctx.cookies` is Bun's `CookieMap` for the request: what you set or delete is sent with the response, whatever the response is. Signed cookies can be read by the client but not forged:
|
|
@@ -97,6 +120,44 @@ class SessionController {
|
|
|
97
120
|
|
|
98
121
|
Name cookies that guard sessions with the `__Host-` prefix: browsers then refuse them from subdomains and over HTTP.
|
|
99
122
|
|
|
123
|
+
## Server-sent events
|
|
124
|
+
|
|
125
|
+
An `@Sse()` route returns an async iterable of messages, streamed as `text/event-stream`; when the client disconnects, the generator is returned and its `finally` runs:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
@Sse('ticks')
|
|
129
|
+
async *ticks(): AsyncGenerator<SseMessage> {
|
|
130
|
+
for (let i = 0; ; i++) {
|
|
131
|
+
yield { data: { tick: i }, id: String(i) } // also event and retry
|
|
132
|
+
await Bun.sleep(1000)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## WebSockets
|
|
138
|
+
|
|
139
|
+
A gateway is listed in `controllers` like a controller; its guards run on the handshake, and frames follow the protocol of Nest's `WsAdapter`, `{ "event": "...", "data": ... }`:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
@WebSocketGateway('/chat')
|
|
143
|
+
@UseGuards(JwtGuard) // browsers send cookies with the handshake
|
|
144
|
+
class ChatGateway implements OnGatewayConnection {
|
|
145
|
+
handleConnection(socket: GatewaySocket) {
|
|
146
|
+
socket.subscribe('room')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@SubscribeMessage('say')
|
|
150
|
+
say(data: string, socket: GatewaySocket) {
|
|
151
|
+
const user = socket.data.context.get(JWT_PAYLOAD).sub
|
|
152
|
+
|
|
153
|
+
socket.publish('room', JSON.stringify({ event: 'said', data: { user, text: data } }))
|
|
154
|
+
return { event: 'sent' } // sent back to this socket
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`socket` is Bun's `ServerWebSocket`, with its pub/sub. Frames that are not such JSON are dropped; when the application closes, sockets are closed with code 1001.
|
|
160
|
+
|
|
100
161
|
## JWT
|
|
101
162
|
|
|
102
163
|
`@getsoda/core/jwt` signs and verifies JSON Web Tokens with WebCrypto: HS256/384/512, RS, PS, ES and EdDSA, keys as secrets, PEM, JWK or `CryptoKey`. The algorithm is fixed by the options, never read from the token, so `alg: none` and algorithm confusion are refused.
|
|
@@ -130,6 +191,48 @@ class AuthModule {}
|
|
|
130
191
|
|
|
131
192
|
`JwtModule.registerAsync({ inject, useFactory })` takes the options from other providers; `new JwtService(options)` works without the module.
|
|
132
193
|
|
|
194
|
+
## OpenAPI
|
|
195
|
+
|
|
196
|
+
`@getsoda/core/openapi` describes the routes from what validates them: argument parsers and schemas implementing [Standard JSON Schema](https://standardschema.dev) (zod does). Import the module to serve the document and a Swagger UI page:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { ApiBearerAuth, ApiOperation, ApiResponses, ApiTags, OpenApiModule } from '@getsoda/core/openapi'
|
|
200
|
+
|
|
201
|
+
@Controller('users')
|
|
202
|
+
@ApiTags('users')
|
|
203
|
+
class UsersController {
|
|
204
|
+
@Get(':id', [param('id', int())])
|
|
205
|
+
@ApiOperation({ summary: 'One user' })
|
|
206
|
+
@ApiResponses({ 200: { description: 'The user', schema: User }, 404: { description: 'No such user' } })
|
|
207
|
+
findOne(id: number) {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@Module({ imports: [OpenApiModule.forRoot({ info: { title: 'Shop', version: '1.0.0' }, ui: '/docs' })] })
|
|
211
|
+
class AppModule {}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`/openapi.json` and `/docs` follow the global prefix; `@ApiExclude()` leaves a route out, `createOpenApiDocument(inject(ROUTES).controllers, options)` gives the document itself.
|
|
215
|
+
|
|
216
|
+
## Testing
|
|
217
|
+
|
|
218
|
+
`@getsoda/core/testing` serves the application on a free port with a silent logger; `overrides` replace providers, as Nest's `overrideProvider()`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { createTestApp } from '@getsoda/core/testing'
|
|
222
|
+
|
|
223
|
+
const app = await createTestApp(AppModule, { overrides: [provide(Mailer, { useValue: fakeMailer })] })
|
|
224
|
+
|
|
225
|
+
afterAll(() => app.close()) // or `await using app = ...` in a block
|
|
226
|
+
|
|
227
|
+
test('signs up', async () => {
|
|
228
|
+
const response = await app.request('/users', { method: 'POST', json: { name: 'Bo' } })
|
|
229
|
+
|
|
230
|
+
expect(response.status).toBe(201)
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A single service needs no server: `createContainer(UsersModule, { overrides })`.
|
|
235
|
+
|
|
133
236
|
## License
|
|
134
237
|
|
|
135
238
|
[MIT](./LICENSE)
|
package/dist/app/app.d.ts
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
import type { Container, ContainerOptions, ModuleImport } from '../di/index.ts';
|
|
22
22
|
import type { ControllerRoutesOptions, Middleware } from '../http/index.ts';
|
|
23
23
|
import { Logger } from '../logger/index.ts';
|
|
24
|
-
import type { CompiledRoutes, CompileOptions } from '../routing/index.ts';
|
|
24
|
+
import type { CompiledRoutes, CompileOptions, NativeHandler } from '../routing/index.ts';
|
|
25
|
+
import type { BuiltGateway, GatewaySocketData } from '../websocket/gateway.ts';
|
|
25
26
|
/**
|
|
26
27
|
* Options of `createApp`.
|
|
27
28
|
*/
|
|
@@ -54,16 +55,16 @@ export interface ListenOptions {
|
|
|
54
55
|
*/
|
|
55
56
|
export interface App {
|
|
56
57
|
readonly container: Container;
|
|
57
|
-
/** Routes for `Bun.serve()`, when serving them yourself instead of calling `listen()`. */
|
|
58
|
-
readonly routes:
|
|
58
|
+
/** Routes (and WebSocket handlers) for `Bun.serve()`, when serving them yourself instead of calling `listen()`. */
|
|
59
|
+
readonly routes: ServeRoutes;
|
|
59
60
|
/** The server once `listen()` resolved. */
|
|
60
|
-
readonly server: Bun.Server<
|
|
61
|
+
readonly server: Bun.Server<GatewaySocketData> | undefined;
|
|
61
62
|
/**
|
|
62
63
|
* Starts serving. Under `bun --hot`, a new version of the application
|
|
63
64
|
* listening on the same port takes the server over from the previous one,
|
|
64
65
|
* which is then destroyed (its server is not stopped: it is shared).
|
|
65
66
|
*/
|
|
66
|
-
listen(options?: number | ListenOptions): Promise<Bun.Server<
|
|
67
|
+
listen(options?: number | ListenOptions): Promise<Bun.Server<GatewaySocketData>>;
|
|
67
68
|
/**
|
|
68
69
|
* Graceful shutdown: refuses new requests, waits for those in flight, then
|
|
69
70
|
* runs `onDestroy` hooks. Safe to call more than once, even from a hook.
|
|
@@ -75,6 +76,15 @@ export interface App {
|
|
|
75
76
|
*/
|
|
76
77
|
enableShutdownSignals(signals?: readonly NodeJS.Signals[]): this;
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Routes and WebSocket handlers for `Bun.serve()`: the handshakes of gateways
|
|
81
|
+
* are native routes of their own, outside middleware, as an upgrade returns no response.
|
|
82
|
+
*/
|
|
83
|
+
export interface ServeRoutes {
|
|
84
|
+
readonly routes: Record<string, NativeHandler | BuiltGateway['handle']>;
|
|
85
|
+
readonly fetch: CompiledRoutes['fetch'];
|
|
86
|
+
readonly websocket: Bun.WebSocketHandler<GatewaySocketData>;
|
|
87
|
+
}
|
|
78
88
|
/**
|
|
79
89
|
* Creates the container and the routes, and runs every `onInit` hook.
|
|
80
90
|
*
|
package/dist/app/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG/E,OAAO,KAAK,EAAE,uBAAuB,EAAoB,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7F,OAAO,EAAiB,MAAM,EAAgB,MAAM,oBAAoB,CAAA;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG/E,OAAO,KAAK,EAAE,uBAAuB,EAAoB,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAG7F,OAAO,EAAiB,MAAM,EAAgB,MAAM,oBAAoB,CAAA;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExF,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAG9E;;GAEG;AACH,MAAM,WAAW,UAAW,SAAQ,gBAAgB,EAAE,uBAAuB,EAAE,cAAc;IAC3F;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAC3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CACrC;AAKD;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,mHAAmH;IACnH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAAA;IAC1D;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAChF;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB;;;OAGG;IACH,qBAAqB,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAA;CACjE;AA0KD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;IACvE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;IACvC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAA;CAC5D;AA2CD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,SAAgB,YAAY,YAAW,UAAU,KAAQ,OAAO,CAAC,GAAG,CA8CzF,CAAA"}
|
package/dist/app/app.js
CHANGED
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { createContainer, provide } from '../di/index.js';
|
|
22
22
|
import { destroyAll, initializeAll } from '../di/lifecycle.js';
|
|
23
|
-
import { buildRoutes } from '../http/index.js';
|
|
23
|
+
import { buildRoutes, ControllerError, ROUTES } from '../http/index.js';
|
|
24
24
|
import { errorResponse } from '../http/exceptions.js';
|
|
25
25
|
import { applyMiddleware, resolveMiddleware } from '../http/middleware.js';
|
|
26
26
|
import { ConsoleLogger, Logger, SilentLogger } from '../logger/index.js';
|
|
27
27
|
import { compileRoutes } from '../routing/index.js';
|
|
28
|
+
import { websocketHandler } from '../websocket/gateway.js';
|
|
28
29
|
import { release, takeOver } from './hot.js';
|
|
29
30
|
/** Signals `enableShutdownSignals()` listens to by default. */
|
|
30
31
|
const DEFAULT_SIGNALS = ['SIGTERM', 'SIGINT'];
|
|
@@ -38,9 +39,11 @@ class Application {
|
|
|
38
39
|
#enhancers;
|
|
39
40
|
#signals = new Map();
|
|
40
41
|
#logger;
|
|
41
|
-
|
|
42
|
+
#gateways;
|
|
43
|
+
constructor(container, routes, gateways, enhancers, logger) {
|
|
42
44
|
this.container = container;
|
|
43
45
|
this.routes = routes;
|
|
46
|
+
this.#gateways = gateways;
|
|
44
47
|
this.#enhancers = enhancers;
|
|
45
48
|
this.#logger = logger;
|
|
46
49
|
}
|
|
@@ -104,6 +107,10 @@ class Application {
|
|
|
104
107
|
async #shutdown() {
|
|
105
108
|
const server = this.#server;
|
|
106
109
|
try {
|
|
110
|
+
// Open sockets would keep stop() waiting; under --hot, clients reconnect to the new version.
|
|
111
|
+
for (const gateway of this.#gateways) {
|
|
112
|
+
gateway.runtime.closeAll();
|
|
113
|
+
}
|
|
107
114
|
if (server !== undefined && !this.#handedOver) {
|
|
108
115
|
await server.stop();
|
|
109
116
|
}
|
|
@@ -150,8 +157,18 @@ const destroyEverything = async (enhancers, container) => {
|
|
|
150
157
|
throw new AggregateError(errors, `${errors.length} onDestroy hooks failed`);
|
|
151
158
|
}
|
|
152
159
|
};
|
|
160
|
+
const withGateways = (compiled, gateways) => {
|
|
161
|
+
const routes = { ...compiled.routes };
|
|
162
|
+
for (const gateway of gateways) {
|
|
163
|
+
if (Object.hasOwn(routes, gateway.path)) {
|
|
164
|
+
throw new ControllerError('DUPLICATE_ROUTE', `The WebSocket gateway on ${gateway.path} has the path of another gateway or route`);
|
|
165
|
+
}
|
|
166
|
+
routes[gateway.path] = gateway.handle;
|
|
167
|
+
}
|
|
168
|
+
return { routes, fetch: compiled.fetch, websocket: websocketHandler };
|
|
169
|
+
};
|
|
153
170
|
/** What Nest logs at startup: the modules, then each controller and its routes. */
|
|
154
|
-
const logStartup = (logger, container, controllers) => {
|
|
171
|
+
const logStartup = (logger, container, controllers, gateways) => {
|
|
155
172
|
const modules = logger.child('Container');
|
|
156
173
|
const routes = logger.child('Routes');
|
|
157
174
|
for (const module of container.modules) {
|
|
@@ -159,10 +176,13 @@ const logStartup = (logger, container, controllers) => {
|
|
|
159
176
|
}
|
|
160
177
|
for (const { controller, path, routes: mapped } of controllers) {
|
|
161
178
|
routes.log(`${controller.name} {${path}}:`);
|
|
162
|
-
for (const
|
|
163
|
-
routes.log(`Mapped {${
|
|
179
|
+
for (const { info } of mapped) {
|
|
180
|
+
routes.log(`Mapped {${info.path}, ${info.method}} route`);
|
|
164
181
|
}
|
|
165
182
|
}
|
|
183
|
+
for (const gateway of gateways) {
|
|
184
|
+
routes.log(`WebSocket gateway {${gateway.path}}, events: ${gateway.runtime.events.join(', ') || 'none'}`);
|
|
185
|
+
}
|
|
166
186
|
};
|
|
167
187
|
/**
|
|
168
188
|
* Creates the container and the routes, and runs every `onInit` hook.
|
|
@@ -177,9 +197,11 @@ export const createApp = async (root, options = {}) => {
|
|
|
177
197
|
const onError = options.onError ??
|
|
178
198
|
((error, req) => own.error('Unhandled error while handling a request', { error, method: req.method, url: req.url }));
|
|
179
199
|
own.log('Starting application');
|
|
200
|
+
// Filled once the routes are built; read by what describes them (OpenAPI).
|
|
201
|
+
const registry = { controllers: [] };
|
|
180
202
|
const container = await createContainer(root, {
|
|
181
203
|
...options,
|
|
182
|
-
providers: [...(options.providers ?? []), provide(Logger, { useValue: logger })],
|
|
204
|
+
providers: [...(options.providers ?? []), provide(Logger, { useValue: logger }), provide(ROUTES, { useValue: registry })],
|
|
183
205
|
});
|
|
184
206
|
const enhancers = [];
|
|
185
207
|
try {
|
|
@@ -188,13 +210,14 @@ export const createApp = async (root, options = {}) => {
|
|
|
188
210
|
enhancers.push(instance);
|
|
189
211
|
});
|
|
190
212
|
const built = buildRoutes(container, { ...options, onError });
|
|
191
|
-
|
|
213
|
+
registry.controllers = built.controllers;
|
|
214
|
+
logStartup(logger, container, built.controllers, built.gateways);
|
|
192
215
|
enhancers.push(...built.enhancers);
|
|
193
216
|
await initializeAll(enhancers);
|
|
194
217
|
own.log('Application started');
|
|
195
218
|
const compiled = compileRoutes(built.routes, options);
|
|
196
219
|
const routes = applyMiddleware(middleware, compiled.routes, compiled.fetch, (error, req) => errorResponse(error, (unknown) => onError(unknown, req)));
|
|
197
|
-
return new Application(container, routes, enhancers, own);
|
|
220
|
+
return new Application(container, withGateways(routes, built.gateways), built.gateways, enhancers, own);
|
|
198
221
|
}
|
|
199
222
|
catch (error) {
|
|
200
223
|
try {
|
package/dist/app/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEzD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEzD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AA+B5C,+DAA+D;AAC/D,MAAM,eAAe,GAA8B,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;AA6BxE,MAAM,WAAW;IACN,SAAS,CAAW;IACpB,MAAM,CAAa;IAC5B,OAAO,CAA2C;IAClD,2EAA2E;IAC3E,WAAW,GAAG,KAAK,CAAA;IACnB,QAAQ,CAA2B;IAC1B,UAAU,CAAoB;IAC9B,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAA;IAChD,OAAO,CAAQ;IACf,SAAS,CAAyB;IAE3C,YACE,SAAoB,EACpB,MAAmB,EACnB,QAAiC,EACjC,SAA6B,EAC7B,MAAc;QAEd,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;QACzB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;QAC3B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;IACvB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAO,GAA2B,EAAE;QAC/C,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;QAC7D,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;QAChF,CAAC;QAED,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAA;QAE5G,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAEhF,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QAErB,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACnC,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;QAEnD,OAAO,MAAM,CAAA;IACf,CAAC;IAED,kFAAkF;IAClF,QAAQ;QACN,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QAEvB,OAAO,IAAI,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,8EAA8E;YAC9E,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAA;YAElE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;YACvB,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACxC,CAAC;QAED,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,qBAAqB,CAAC,OAAO,GAA8B,eAAe;QACxE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,SAAQ;YACV,CAAC;YAED,MAAM,QAAQ,GAAG,GAAS,EAAE;gBAC1B,4EAA4E;gBAC5E,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAChC,OAAM;gBACR,CAAC;gBAED,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CACf,GAAG,EAAE;oBACH,IAAI,CAAC,sBAAsB,EAAE,CAAA;oBAC7B,0DAA0D;oBAC1D,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;gBACnC,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;oBACjB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;oBAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;gBACjB,CAAC,CACF,CAAA;YACH,CAAC,CAAA;YAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;YACnC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC9B,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAA;QAE3B,IAAI,CAAC;YACH,6FAA6F;YAC7F,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACrC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;YAC5B,CAAC;YAED,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC9C,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YACrB,CAAC;YAED,MAAM,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;QAC1D,CAAC;gBAAS,CAAC;YACT,+DAA+D;YAC/D,IAAI,CAAC,sBAAsB,EAAE,CAAA;YAE7B,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,sBAAsB;QACpB,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;IACvB,CAAC;CACF;AAED,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAa,EAAE,CAAC,CAAC,KAAK,YAAY,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;AAE1G;;;GAGG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAAE,SAA6B,EAAE,SAAoB,EAAiB,EAAE;IACrG,MAAM,MAAM,GAAc,EAAE,CAAA;IAE5B,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,SAAS,CAAC,CAAA;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IACjC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,KAAK,EAAE,CAAA;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IACjC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,MAAM,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,cAAc,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,yBAAyB,CAAC,CAAA;IAC7E,CAAC;AACH,CAAC,CAAA;AAYD,MAAM,YAAY,GAAG,CAAC,QAAwB,EAAE,QAAiC,EAAe,EAAE;IAChG,MAAM,MAAM,GAA0B,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAA;IAE5D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,4BAA4B,OAAO,CAAC,IAAI,2CAA2C,CAAC,CAAA;QACnI,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;IACvC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAA;AACvE,CAAC,CAAA;AAED,mFAAmF;AACnF,MAAM,UAAU,GAAG,CACjB,MAAc,EACd,SAAoB,EACpB,WAAwC,EACxC,QAAiC,EAC3B,EAAE;IACR,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAErC,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,2BAA2B,CAAC,CAAA;IACxD,CAAC;IAED,KAAK,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC/D,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAA;QAE3C,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,SAAS,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,CAAC,sBAAsB,OAAO,CAAC,IAAI,cAAc,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC,CAAA;IAC3G,CAAC;AACH,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,IAAkB,EAAE,OAAO,GAAe,EAAE,EAAgB,EAAE;IAC5F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC,CAAA;IACtG,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IAChC,MAAM,OAAO,GACX,OAAO,CAAC,OAAO;QACf,CAAC,CAAC,KAAc,EAAE,GAAY,EAAE,EAAE,CAChC,GAAG,CAAC,KAAK,CAAC,0CAA0C,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAEvG,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;IAE/B,2EAA2E;IAC3E,MAAM,QAAQ,GAAiD,EAAE,WAAW,EAAE,EAAE,EAAE,CAAA;IAClF,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE;QAC5C,GAAG,OAAO;QACV,SAAS,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;KAC1H,CAAC,CAAA;IACF,MAAM,SAAS,GAAc,EAAE,CAAA;IAE/B,IAAI,CAAC;QACH,wFAAwF;QACxF,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE;YAC9G,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;QACF,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;QAE7D,QAAQ,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAA;QACxC,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;QAChE,SAAS,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAA;QAClC,MAAM,aAAa,CAAC,SAAS,CAAC,CAAA;QAC9B,GAAG,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;QAE9B,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACrD,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CACzF,aAAa,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CACzD,CAAA;QAED,OAAO,IAAI,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;IACzG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,iBAAiB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC/C,CAAC;QAAC,OAAO,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,+DAA+D,CAAC,CAAA;QAC/H,CAAC;QAED,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC,CAAA"}
|
package/dist/app/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* The application: container, routes and server, started and stopped together.
|
|
4
4
|
*/
|
|
5
5
|
export { createApp } from './app.ts';
|
|
6
|
-
export type { App, AppOptions, ListenOptions } from './app.ts';
|
|
6
|
+
export type { App, AppOptions, ListenOptions, ServeRoutes } from './app.ts';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/app/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Configuration typed by a schema and validated at startup.
|
|
4
|
+
*
|
|
5
|
+
* What `@nestjs/config` does with `registerAs()` and a `validationSchema`,
|
|
6
|
+
* typed by the schema instead of read with string keys. Bun already loads
|
|
7
|
+
* `.env`, `.env.local` and `.env.<NODE_ENV>` into `process.env`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* export const AppConfig = defineConfig('app', z.object({
|
|
11
|
+
* PORT: z.coerce.number().default(3000),
|
|
12
|
+
* JWT_SECRET: z.string().min(32),
|
|
13
|
+
* }))
|
|
14
|
+
*
|
|
15
|
+
* @Module({ imports: [ConfigModule.forRoot({ load: [AppConfig], global: true })] })
|
|
16
|
+
* class AppModule {}
|
|
17
|
+
*
|
|
18
|
+
* class AuthService {
|
|
19
|
+
* readonly #config = inject(AppConfig) // { PORT: number; JWT_SECRET: string }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { InjectionToken } from '../di/token.ts';
|
|
24
|
+
import type { InferOutput, StandardSchemaV1 } from '../http/schema.ts';
|
|
25
|
+
/** Environment variables, as in `process.env`. */
|
|
26
|
+
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
27
|
+
/** A configuration that does not match its schema; `issues` lists every problem. */
|
|
28
|
+
export declare class ConfigError extends Error {
|
|
29
|
+
readonly config: string;
|
|
30
|
+
readonly issues: readonly string[];
|
|
31
|
+
readonly name = "ConfigError";
|
|
32
|
+
constructor(config: string, issues: readonly string[]);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A configuration, and the DI token of its value: `inject(AppConfig)`.
|
|
36
|
+
* `ConfigModule.forRoot()` loads it at startup; `load()` works without DI.
|
|
37
|
+
*/
|
|
38
|
+
export declare class Config<T> extends InjectionToken<T> {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(name: string, schema: StandardSchemaV1<unknown, T>, source: (env: Environment) => unknown);
|
|
41
|
+
/** The value read from `env` and validated; throws a {@link ConfigError} listing every issue. */
|
|
42
|
+
load(env?: Environment): Promise<T>;
|
|
43
|
+
toString(): string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Defines a configuration validated by `schema`. Its input is the environment,
|
|
47
|
+
* or what `source` builds from it, for a nested configuration:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* const DatabaseConfig = defineConfig('database', DatabaseSchema, (env) => ({
|
|
51
|
+
* url: env.DATABASE_URL,
|
|
52
|
+
* pool: { max: env.DATABASE_POOL_MAX },
|
|
53
|
+
* }))
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare const defineConfig: <S extends StandardSchemaV1>(name: string, schema: S, source?: (env: Environment) => unknown) => Config<InferOutput<S>>;
|
|
57
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAGtE,kDAAkD;AAClD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAEtE,oFAAoF;AACpF,qBAAa,WAAY,SAAQ,KAAK;IAIlC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE;IAJpC,SAAkB,IAAI,iBAAgB;IAEtC,YACW,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,MAAM,EAAE,EAInC;CACF;AAED;;;GAGG;AACH,qBAAa,MAAM,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;;IAI9C,YAAY,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,EAIpG;IAED,iGAAiG;IAC3F,IAAI,CAAC,GAAG,GAAE,WAAyB,GAAG,OAAO,CAAC,CAAC,CAAC,CAQrD;IAEQ,QAAQ,IAAI,MAAM,CAE1B;CACF;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,gBAAgB,QAC/C,MAAM,UACJ,CAAC,WACD,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,KACpC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAkF,CAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Configuration typed by a schema and validated at startup.
|
|
4
|
+
*
|
|
5
|
+
* What `@nestjs/config` does with `registerAs()` and a `validationSchema`,
|
|
6
|
+
* typed by the schema instead of read with string keys. Bun already loads
|
|
7
|
+
* `.env`, `.env.local` and `.env.<NODE_ENV>` into `process.env`.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* export const AppConfig = defineConfig('app', z.object({
|
|
11
|
+
* PORT: z.coerce.number().default(3000),
|
|
12
|
+
* JWT_SECRET: z.string().min(32),
|
|
13
|
+
* }))
|
|
14
|
+
*
|
|
15
|
+
* @Module({ imports: [ConfigModule.forRoot({ load: [AppConfig], global: true })] })
|
|
16
|
+
* class AppModule {}
|
|
17
|
+
*
|
|
18
|
+
* class AuthService {
|
|
19
|
+
* readonly #config = inject(AppConfig) // { PORT: number; JWT_SECRET: string }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { InjectionToken } from '../di/token.js';
|
|
24
|
+
import { formatIssues } from '../http/schema.js';
|
|
25
|
+
import { hideFrameworkFrames } from '../utils/errors.js';
|
|
26
|
+
/** A configuration that does not match its schema; `issues` lists every problem. */
|
|
27
|
+
export class ConfigError extends Error {
|
|
28
|
+
config;
|
|
29
|
+
issues;
|
|
30
|
+
name = 'ConfigError';
|
|
31
|
+
constructor(config, issues) {
|
|
32
|
+
super(`Invalid configuration "${config}":\n ${issues.join('\n ')}`);
|
|
33
|
+
this.config = config;
|
|
34
|
+
this.issues = issues;
|
|
35
|
+
hideFrameworkFrames(this);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A configuration, and the DI token of its value: `inject(AppConfig)`.
|
|
40
|
+
* `ConfigModule.forRoot()` loads it at startup; `load()` works without DI.
|
|
41
|
+
*/
|
|
42
|
+
export class Config extends InjectionToken {
|
|
43
|
+
#schema;
|
|
44
|
+
#source;
|
|
45
|
+
constructor(name, schema, source) {
|
|
46
|
+
super(name);
|
|
47
|
+
this.#schema = schema;
|
|
48
|
+
this.#source = source;
|
|
49
|
+
}
|
|
50
|
+
/** The value read from `env` and validated; throws a {@link ConfigError} listing every issue. */
|
|
51
|
+
async load(env = process.env) {
|
|
52
|
+
const result = await this.#schema['~standard'].validate(this.#source(env));
|
|
53
|
+
if (result.issues !== undefined) {
|
|
54
|
+
throw new ConfigError(this.description, formatIssues(result.issues, ''));
|
|
55
|
+
}
|
|
56
|
+
return result.value;
|
|
57
|
+
}
|
|
58
|
+
toString() {
|
|
59
|
+
return `Config(${this.description})`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Defines a configuration validated by `schema`. Its input is the environment,
|
|
64
|
+
* or what `source` builds from it, for a nested configuration:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* const DatabaseConfig = defineConfig('database', DatabaseSchema, (env) => ({
|
|
68
|
+
* url: env.DATABASE_URL,
|
|
69
|
+
* pool: { max: env.DATABASE_POOL_MAX },
|
|
70
|
+
* }))
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export const defineConfig = (name, schema, source = (env) => env) => new Config(name, schema, source);
|
|
74
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAKxD,oFAAoF;AACpF,MAAM,OAAO,WAAY,SAAQ,KAAK;IAIzB,MAAM;IACN,MAAM;IAJC,IAAI,GAAG,aAAa,CAAA;IAEtC,YACW,MAAc,EACd,MAAyB;QAElC,KAAK,CAAC,0BAA0B,MAAM,SAAS,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;sBAH5D,MAAM;sBACN,MAAM;QAGf,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,MAAU,SAAQ,cAAiB;IACrC,OAAO,CAA8B;IACrC,OAAO,CAA+B;IAE/C,YAAY,IAAY,EAAE,MAAoC,EAAE,MAAqC;QACnG,KAAK,CAAC,IAAI,CAAC,CAAA;QACX,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;IACvB,CAAC;IAED,iGAAiG;IACjG,KAAK,CAAC,IAAI,CAAC,GAAG,GAAgB,OAAO,CAAC,GAAG;QACvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;QAE1E,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;QAC1E,CAAC;QAED,OAAO,MAAM,CAAC,KAAK,CAAA;IACrB,CAAC;IAEQ,QAAQ;QACf,OAAO,UAAU,IAAI,CAAC,WAAW,GAAG,CAAA;IACtC,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,IAAY,EACZ,MAAS,EACT,MAAM,GAAkC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAC5B,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,MAAmD,EAAE,MAAM,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Configuration typed by a schema and validated at startup.
|
|
4
|
+
*/
|
|
5
|
+
export { Config, ConfigError, defineConfig } from './config.ts';
|
|
6
|
+
export type { Environment } from './config.ts';
|
|
7
|
+
export { ConfigModule } from './module.ts';
|
|
8
|
+
export type { ConfigModuleOptions } from './module.ts';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/D,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* `ConfigModule`: loads configurations at startup, like `ConfigModule.forRoot()` of `@nestjs/config`.
|
|
4
|
+
*/
|
|
5
|
+
import type { DynamicModule } from '../di/index.ts';
|
|
6
|
+
import type { Config, Environment } from './config.ts';
|
|
7
|
+
export interface ConfigModuleOptions {
|
|
8
|
+
/** Configurations to load and provide. */
|
|
9
|
+
readonly load: readonly Config<unknown>[];
|
|
10
|
+
/** Makes them injectable in every module. Defaults to `false`, as in Nest. */
|
|
11
|
+
readonly global?: boolean;
|
|
12
|
+
/** Where variables come from, read when the container starts. Defaults to `process.env`. */
|
|
13
|
+
readonly env?: Environment;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ```ts
|
|
17
|
+
* @Module({ imports: [ConfigModule.forRoot({ load: [AppConfig, DatabaseConfig], global: true })] })
|
|
18
|
+
* class AppModule {}
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Each configuration is loaded when the container starts; an invalid one
|
|
22
|
+
* fails the startup with a `ConfigError` listing all its issues. Tests
|
|
23
|
+
* replace a value with `overrides: [provide(AppConfig, { useValue })]`, and
|
|
24
|
+
* the replaced configuration is not loaded.
|
|
25
|
+
*/
|
|
26
|
+
export declare class ConfigModule {
|
|
27
|
+
static forRoot(options: ConfigModuleOptions): DynamicModule;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/config/module.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;IACzC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,4FAA4F;IAC5F,QAAQ,CAAC,GAAG,CAAC,EAAE,WAAW,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,qBACa,YAAY;IACvB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa,CAO1D;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* `ConfigModule`: loads configurations at startup, like `ConfigModule.forRoot()` of `@nestjs/config`.
|
|
4
|
+
*/
|
|
5
|
+
import { Module, provide } from '../di/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* ```ts
|
|
8
|
+
* @Module({ imports: [ConfigModule.forRoot({ load: [AppConfig, DatabaseConfig], global: true })] })
|
|
9
|
+
* class AppModule {}
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Each configuration is loaded when the container starts; an invalid one
|
|
13
|
+
* fails the startup with a `ConfigError` listing all its issues. Tests
|
|
14
|
+
* replace a value with `overrides: [provide(AppConfig, { useValue })]`, and
|
|
15
|
+
* the replaced configuration is not loaded.
|
|
16
|
+
*/
|
|
17
|
+
@Module({})
|
|
18
|
+
export class ConfigModule {
|
|
19
|
+
static forRoot(options) {
|
|
20
|
+
return {
|
|
21
|
+
module: ConfigModule,
|
|
22
|
+
global: options.global ?? false,
|
|
23
|
+
providers: options.load.map((config) => provide(config, { useFactory: () => config.load(options.env) })),
|
|
24
|
+
exports: options.load,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/config/module.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAahD;;;;;;;;;;GAUG;AACH,CAAC,MAAM,CAAC,EAAE,CAAC;AACX,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,OAAO,CAAC,OAA4B;QACzC,OAAO;YACL,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;YAC/B,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxG,OAAO,EAAE,OAAO,CAAC,IAAI;SACtB,CAAA;IACH,CAAC;CACF"}
|
package/dist/di/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/di/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/di/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,WAAW,GACnB,aAAa,GACb,oBAAoB,GACpB,qBAAqB,GACrB,kBAAkB,GAClB,2BAA2B,GAC3B,kBAAkB,GAClB,oBAAoB,GACpB,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,CAAA;AAEtB;;;;GAIG;AACH,qBAAa,OAAQ,SAAQ,KAAK;IAI9B,QAAQ,CAAC,IAAI,EAAE,WAAW;IAH5B,SAAkB,IAAI,aAAY;IAElC,YACW,IAAI,EAAE,WAAW,EAC1B,OAAO,EAAE,MAAM,EAIhB;CACF"}
|
package/dist/di/errors.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* Errors raised by the dependency injection container.
|
|
4
4
|
*/
|
|
5
|
+
import { hideFrameworkFrames } from '../utils/errors.js';
|
|
5
6
|
/**
|
|
6
7
|
* Error thrown by the container.
|
|
7
8
|
*
|
|
@@ -13,6 +14,7 @@ export class DiError 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
|
package/dist/di/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/di/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/di/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AA4BxD;;;;GAIG;AACH,MAAM,OAAO,OAAQ,SAAQ,KAAK;IAIrB,IAAI;IAHG,IAAI,GAAG,SAAS,CAAA;IAElC,YACW,IAAiB,EAC1B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAA;oBAHL,IAAI;QAIb,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;CACF"}
|