@ailura/nestjs-hono-adapter 1.0.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/LICENSE +21 -0
- package/README.md +411 -0
- package/dist/body.d.ts +35 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +180 -0
- package/dist/body.js.map +1 -0
- package/dist/bridge.d.ts +64 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +168 -0
- package/dist/bridge.js.map +1 -0
- package/dist/closing.d.ts +13 -0
- package/dist/closing.d.ts.map +1 -0
- package/dist/closing.js +30 -0
- package/dist/closing.js.map +1 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/cors-middleware.d.ts +64 -0
- package/dist/cors-middleware.d.ts.map +1 -0
- package/dist/cors-middleware.js +211 -0
- package/dist/cors-middleware.js.map +1 -0
- package/dist/handler-bridge.d.ts +51 -0
- package/dist/handler-bridge.d.ts.map +1 -0
- package/dist/handler-bridge.js +122 -0
- package/dist/handler-bridge.js.map +1 -0
- package/dist/hono-lifecycle.d.ts +90 -0
- package/dist/hono-lifecycle.d.ts.map +1 -0
- package/dist/hono-lifecycle.js +169 -0
- package/dist/hono-lifecycle.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/path.d.ts +3 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +144 -0
- package/dist/path.js.map +1 -0
- package/dist/query.d.ts +19 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +238 -0
- package/dist/query.js.map +1 -0
- package/dist/response-helpers.d.ts +16 -0
- package/dist/response-helpers.d.ts.map +1 -0
- package/dist/response-helpers.js +45 -0
- package/dist/response-helpers.js.map +1 -0
- package/dist/response-writer.d.ts +28 -0
- package/dist/response-writer.d.ts.map +1 -0
- package/dist/response-writer.js +52 -0
- package/dist/response-writer.js.map +1 -0
- package/dist/route-adapter.d.ts +53 -0
- package/dist/route-adapter.d.ts.map +1 -0
- package/dist/route-adapter.js +141 -0
- package/dist/route-adapter.js.map +1 -0
- package/dist/server-adapter.d.ts +106 -0
- package/dist/server-adapter.d.ts.map +1 -0
- package/dist/server-adapter.js +153 -0
- package/dist/server-adapter.js.map +1 -0
- package/dist/sse.d.ts +67 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +211 -0
- package/dist/sse.js.map +1 -0
- package/dist/static-assets.d.ts +39 -0
- package/dist/static-assets.d.ts.map +1 -0
- package/dist/static-assets.js +155 -0
- package/dist/static-assets.js.map +1 -0
- package/dist/version-filter.d.ts +24 -0
- package/dist/version-filter.d.ts.map +1 -0
- package/dist/version-filter.js +107 -0
- package/dist/version-filter.js.map +1 -0
- package/dist/versioned-route.d.ts +21 -0
- package/dist/versioned-route.d.ts.map +1 -0
- package/dist/versioned-route.js +15 -0
- package/dist/versioned-route.js.map +1 -0
- package/dist/views.d.ts +42 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +110 -0
- package/dist/views.js.map +1 -0
- package/dist/ws-adapter.d.ts +81 -0
- package/dist/ws-adapter.d.ts.map +1 -0
- package/dist/ws-adapter.js +214 -0
- package/dist/ws-adapter.js.map +1 -0
- package/dist/ws-client.d.ts +68 -0
- package/dist/ws-client.d.ts.map +1 -0
- package/dist/ws-client.js +135 -0
- package/dist/ws-client.js.map +1 -0
- package/dist/ws-server.d.ts +23 -0
- package/dist/ws-server.d.ts.map +1 -0
- package/dist/ws-server.js +37 -0
- package/dist/ws-server.js.map +1 -0
- package/dist/ws.d.ts +14 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +12 -0
- package/dist/ws.js.map +1 -0
- package/package.json +99 -0
- package/src/body.ts +251 -0
- package/src/bridge.ts +308 -0
- package/src/closing.ts +38 -0
- package/src/context.ts +25 -0
- package/src/cors-middleware.ts +347 -0
- package/src/handler-bridge.ts +226 -0
- package/src/hono-lifecycle.ts +259 -0
- package/src/index.ts +27 -0
- package/src/path.ts +169 -0
- package/src/query.ts +304 -0
- package/src/response-helpers.ts +60 -0
- package/src/response-writer.ts +100 -0
- package/src/route-adapter.ts +261 -0
- package/src/server-adapter.ts +294 -0
- package/src/sse.ts +274 -0
- package/src/static-assets.ts +247 -0
- package/src/version-filter.ts +170 -0
- package/src/versioned-route.ts +30 -0
- package/src/views.ts +188 -0
- package/src/ws-adapter.ts +329 -0
- package/src/ws-client.ts +190 -0
- package/src/ws-server.ts +40 -0
- package/src/ws.ts +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ailura Code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# @ailura/nestjs-hono-adapter
|
|
2
|
+
|
|
3
|
+
An HTTP adapter that runs a NestJS application on
|
|
4
|
+
[Hono](https://hono.dev), with no Express or Fastify underneath.
|
|
5
|
+
|
|
6
|
+
Nest has no official Hono adapter. This package implements the
|
|
7
|
+
Nest 11/12 `AbstractHttpAdapter` contract directly on Hono:
|
|
8
|
+
routes are registered on a Hono application, and Hono's Web
|
|
9
|
+
`Request` and `Response` are translated to and from the objects
|
|
10
|
+
Nest reads and writes. The two published alternatives target
|
|
11
|
+
Nest 11 and answer incorrectly — one returns a success status to
|
|
12
|
+
a handler that threw, the other writes every response twice — so
|
|
13
|
+
owning the adapter is the smaller cost.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
bun add @ailura/nestjs-hono-adapter hono @hono/node-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @ailura/nestjs-hono-adapter hono @hono/node-server
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@nestjs/common`, `@nestjs/core`, `hono` and `@hono/node-server`
|
|
26
|
+
are peer dependencies, so the application decides their
|
|
27
|
+
versions.
|
|
28
|
+
|
|
29
|
+
## Use
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { NestFactory } from '@nestjs/core';
|
|
33
|
+
import { ServerAdapter } from '@ailura/nestjs-hono-adapter';
|
|
34
|
+
|
|
35
|
+
import { AppModule } from './app.module.ts';
|
|
36
|
+
|
|
37
|
+
const app = await NestFactory.create(
|
|
38
|
+
AppModule,
|
|
39
|
+
new ServerAdapter({ bodyLimit: '2mb', trustProxy: true }),
|
|
40
|
+
);
|
|
41
|
+
app.enableCors({
|
|
42
|
+
credentials: true,
|
|
43
|
+
origin: ['https://app.example.com'],
|
|
44
|
+
});
|
|
45
|
+
await app.listen(3000);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`getType()` answers `hono`, which is the value ecosystem
|
|
49
|
+
packages branch on.
|
|
50
|
+
|
|
51
|
+
## Options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Effect |
|
|
54
|
+
| --------------- | ------------------- | ------- | ------------------------------------------------------------------------------------ |
|
|
55
|
+
| `bodyLimit` | `number \| string` | `1mb` | Largest request body, as bytes or as a size such as `'512kb'`; `0` removes the limit |
|
|
56
|
+
| `rawBody` | `boolean` | `false` | Keep the bytes that were read in `NestRequest.rawBody` |
|
|
57
|
+
| `secureHeaders` | `boolean \| object` | `true` | Install `hono/secure-headers`, with its defaults or with the given options |
|
|
58
|
+
| `trustProxy` | `boolean` | `false` | Read `x-forwarded-proto`, `x-forwarded-for` and `x-forwarded-host` |
|
|
59
|
+
| `views` | `object` | — | The engine a `@Render()` handler renders with, and where templates are read from |
|
|
60
|
+
|
|
61
|
+
## Routes
|
|
62
|
+
|
|
63
|
+
Nest's path dialect is translated as the application starts:
|
|
64
|
+
|
|
65
|
+
| Nest | Hono |
|
|
66
|
+
| ------------------ | ------------------ |
|
|
67
|
+
| `/users/:id` | `/users/:id` |
|
|
68
|
+
| `/users/:id?` | `/users/:id?` |
|
|
69
|
+
| `/users{/:id}` | `/users/:id?` |
|
|
70
|
+
| `/users/:id(\\d+)` | `/users/:id{\\d+}` |
|
|
71
|
+
| `/files/*rest` | `/files/*` |
|
|
72
|
+
|
|
73
|
+
A path the router cannot read — `/users?`, a stray `}` or `(`,
|
|
74
|
+
or a group that holds more than one parameter — throws as the
|
|
75
|
+
application starts, rather than answering 404 later.
|
|
76
|
+
|
|
77
|
+
Versioning works over URI, header, media type and the custom
|
|
78
|
+
strategy, including versioned redirects.
|
|
79
|
+
|
|
80
|
+
## Query strings
|
|
81
|
+
|
|
82
|
+
`@Query()` receives the shape the platform parsers produce: a
|
|
83
|
+
repeated name becomes a list, `tags[]` appends, `tags[0]`
|
|
84
|
+
indexes and `filter[name]` nests. Values are percent-decoded,
|
|
85
|
+
`+` reads as a space, and `__proto__`, `constructor` and
|
|
86
|
+
`prototype` are dropped rather than written into the tree.
|
|
87
|
+
|
|
88
|
+
## Request bodies
|
|
89
|
+
|
|
90
|
+
The body is read when a handler reaches for it, not by
|
|
91
|
+
middleware:
|
|
92
|
+
|
|
93
|
+
- JSON, including the `+json` suffix types;
|
|
94
|
+
- URL-encoded and text bodies;
|
|
95
|
+
- `multipart/form-data`, through Hono's `parseBody()`; fields
|
|
96
|
+
land on `NestRequest.body` and files on `NestRequest.files`;
|
|
97
|
+
- every other content type as a `Buffer`.
|
|
98
|
+
|
|
99
|
+
A body that does not match its content type is refused with
|
|
100
|
+
Nest's own `BadRequestException`, and one over `bodyLimit` with
|
|
101
|
+
`PayloadTooLargeException`, so both travel through the exception
|
|
102
|
+
layer and its filters.
|
|
103
|
+
`app.useBodyParser('json', { limit: '1kb' })` overrides the
|
|
104
|
+
limit for the parser names it is called with.
|
|
105
|
+
|
|
106
|
+
With `rawBody: true` — the adapter option, or the same option on
|
|
107
|
+
`NestFactory.create` — `NestRequest.rawBody` holds the bytes
|
|
108
|
+
read, which is what a signed webhook needs. Multipart bodies
|
|
109
|
+
never fill it, because the platform parser consumes the stream.
|
|
110
|
+
|
|
111
|
+
## Responses
|
|
112
|
+
|
|
113
|
+
A returned value is answered as Nest answers it: an object as
|
|
114
|
+
JSON, a string as text, a number as a status. `@Header()`,
|
|
115
|
+
`@HttpCode()`, `@Redirect()` and `StreamableFile` are all
|
|
116
|
+
honoured.
|
|
117
|
+
|
|
118
|
+
`@Res()` works, and so does `@Res({ passthrough: true })`:
|
|
119
|
+
Hono's own response helpers (`json`, `text`, `html`, `body`,
|
|
120
|
+
`redirect`, `notFound`, `newResponse`) are the imperative API
|
|
121
|
+
here, and the adapter makes sure the response a handler builds
|
|
122
|
+
that way is the one that is sent. A `Response` assigned straight
|
|
123
|
+
to `context.res` is sent as well.
|
|
124
|
+
|
|
125
|
+
## Server-sent events
|
|
126
|
+
|
|
127
|
+
`@Sse()` is served like any other route, and its frames reach
|
|
128
|
+
the client as the observable emits them rather than when it
|
|
129
|
+
completes:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
@Sse('events')
|
|
133
|
+
events(): Observable<MessageEvent> {
|
|
134
|
+
return interval(1000).pipe(
|
|
135
|
+
map((n) => ({ data: { n }, id: String(n) })),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The answer carries `content-type: text/event-stream` and the
|
|
141
|
+
frame shape is Nest's: `data`, with an object serialised as
|
|
142
|
+
JSON, and, when present, `type` as `event:`, `id`, `retry` and
|
|
143
|
+
`comment`. A handler may answer an observable or a promise of
|
|
144
|
+
one, and `@Header()` reaches the stream. The status is the one
|
|
145
|
+
Nest computed for the route: `200` by default, and `@HttpCode()`
|
|
146
|
+
on the versions that carry it (11.2 and later).
|
|
147
|
+
|
|
148
|
+
A stream that fails before its first frame is answered by the
|
|
149
|
+
exception layer like any other failure; one that fails after a
|
|
150
|
+
frame has been sent ends the stream with an `error` event. A
|
|
151
|
+
client that disconnects unsubscribes the observable, so its
|
|
152
|
+
teardown runs and nothing is left ticking — and, on Nest 11.2
|
|
153
|
+
and later, the abort signal `@SseSignal()` injects is aborted at
|
|
154
|
+
the same moment. `app.close()` waits for an open stream to
|
|
155
|
+
finish unless `forceCloseConnections` is set, the same way it
|
|
156
|
+
waits for any long-lived request.
|
|
157
|
+
|
|
158
|
+
## Views
|
|
159
|
+
|
|
160
|
+
Rendering is left to the deployment: this adapter reads the
|
|
161
|
+
template and a function renders it, so no template engine
|
|
162
|
+
becomes a dependency of the package.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import handlebars from 'handlebars';
|
|
166
|
+
|
|
167
|
+
const adapter = new ServerAdapter({
|
|
168
|
+
views: {
|
|
169
|
+
directory: 'views',
|
|
170
|
+
engine: (source, data) => handlebars.compile(source)(data),
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`engine` receives the template source and the object the handler
|
|
176
|
+
returned, and may answer a promise, so any engine fits. The same
|
|
177
|
+
two things can be named on the application, which is what Nest
|
|
178
|
+
declares for the purpose:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
app.setBaseViewsDir('views');
|
|
182
|
+
app.setViewEngine('hbs');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A handler marks its route with `@Render('hello')`, and the
|
|
186
|
+
extension the engine named is appended, so the file looked up is
|
|
187
|
+
`views/hello.hbs`. A view that is missing is answered as
|
|
188
|
+
`NotFoundException`, and naming an engine before one was
|
|
189
|
+
configured throws, rather than rendering an empty page.
|
|
190
|
+
|
|
191
|
+
## Static assets
|
|
192
|
+
|
|
193
|
+
`app.useStaticAssets(path, options)` mounts one directory, or a
|
|
194
|
+
list of them, on Hono's own static handler. A request that names
|
|
195
|
+
no file in them travels on to the routes, so an application
|
|
196
|
+
keeps its own not-found answer.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
app.useStaticAssets('public', {
|
|
200
|
+
maxAge: '7d',
|
|
201
|
+
prefix: '/public',
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
| Option | Effect |
|
|
206
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
207
|
+
| `prefix` | Where the files hang; without it, the root |
|
|
208
|
+
| `index` | File a directory request is answered with; `index.html` unless set |
|
|
209
|
+
| `maxAge` | Writes `cache-control: public, max-age=…`; a number is milliseconds, a string is read as `ms`, `s`, `m`, `h`, `d`, `w` or `y` |
|
|
210
|
+
| `immutable` | Adds `immutable` to that header, and needs `maxAge` |
|
|
211
|
+
|
|
212
|
+
`redirect` is accepted and not acted on: a directory request is
|
|
213
|
+
answered with its index whether or not the path ends in a slash,
|
|
214
|
+
so a redirect would only add a round trip. The options Hono's
|
|
215
|
+
handler decides for itself are refused when they are passed, so
|
|
216
|
+
a deployment is told at startup rather than noticing later:
|
|
217
|
+
`setHeaders`, `extensions`, `fallthrough: false`, `dotfiles`,
|
|
218
|
+
`index: false`, `etag: false`, and `immutable` without a
|
|
219
|
+
`maxAge`. An ETag is always written.
|
|
220
|
+
|
|
221
|
+
## TLS, proxies and shutdown
|
|
222
|
+
|
|
223
|
+
Pass Node's TLS options to Nest and the adapter builds an
|
|
224
|
+
`https.Server`:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
await NestFactory.create(AppModule, new ServerAdapter(), {
|
|
228
|
+
httpsOptions: { key, cert },
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`getHttpServer()` returns that server, so an application can
|
|
233
|
+
read its address or attach a listener.
|
|
234
|
+
|
|
235
|
+
With `trustProxy: true`, `x-forwarded-proto`, `x-forwarded-for`
|
|
236
|
+
and `x-forwarded-host` fill `NestRequest.protocol`, `secure`,
|
|
237
|
+
`ip`, `ips` and `hostname`; without it they are ignored. The
|
|
238
|
+
socket address is always used as the peer.
|
|
239
|
+
|
|
240
|
+
`app.close()` stops accepting connections and then closes the
|
|
241
|
+
server. `return503OnClosing: true` answers `503` to the requests
|
|
242
|
+
that arrive while it is closing, and
|
|
243
|
+
`forceCloseConnections: true` destroys the connections the
|
|
244
|
+
server is still holding instead of waiting for them — both from
|
|
245
|
+
the same options object Nest accepts.
|
|
246
|
+
|
|
247
|
+
## Hono underneath
|
|
248
|
+
|
|
249
|
+
`getHono()` answers the typed `Hono` application, so a project
|
|
250
|
+
that already knows Hono can register its own middleware and
|
|
251
|
+
routes before the application listens:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const adapter = new ServerAdapter();
|
|
255
|
+
adapter.getHono().use('*', async (context, next) => {
|
|
256
|
+
context.header('x-served-by', 'hono');
|
|
257
|
+
await next();
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`getInstance()` answers the same application, which is the
|
|
262
|
+
untyped accessor Nest itself declares.
|
|
263
|
+
|
|
264
|
+
## WebSockets
|
|
265
|
+
|
|
266
|
+
Gateways have their own Nest adapter, kept behind the `./ws`
|
|
267
|
+
subpath so an HTTP-only deployment never resolves
|
|
268
|
+
`@nestjs/websockets` or `@hono/node-ws`: both are optional
|
|
269
|
+
peers, and neither is installed for the routes above.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { HonoWsAdapter } from '@ailura/nestjs-hono-adapter/ws';
|
|
273
|
+
|
|
274
|
+
const adapter = new ServerAdapter();
|
|
275
|
+
const app = await NestFactory.create(AppModule, adapter);
|
|
276
|
+
app.useWebSocketAdapter(new HonoWsAdapter(adapter));
|
|
277
|
+
await app.listen(3000);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
A gateway is declared the way Nest declares one, and its `path`
|
|
281
|
+
is registered on the same Hono application the routes hang on:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
@WebSocketGateway({ path: '/ws' })
|
|
285
|
+
class EventsGateway {
|
|
286
|
+
@SubscribeMessage('ping')
|
|
287
|
+
ping(@MessageBody() data: unknown): unknown {
|
|
288
|
+
return { event: 'ping', data };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
A client sends `{ event, data, id? }` — or a bare string, which
|
|
294
|
+
reads as the event name — and receives `{ event, data }`, with
|
|
295
|
+
the `id` echoed when it asked for one. A handler that returns an
|
|
296
|
+
observable is subscribed per connection and unsubscribed when
|
|
297
|
+
that connection closes, so nothing leaks between clients.
|
|
298
|
+
|
|
299
|
+
What cannot be honoured is refused when the gateway starts
|
|
300
|
+
rather than half served: a gateway with its own `port`, because
|
|
301
|
+
every gateway is served on the HTTP adapter's own server, and a
|
|
302
|
+
`namespace`, because the path is the whole address here.
|
|
303
|
+
`@ConnectedSocket()` receives the socket, with the `send`,
|
|
304
|
+
`close`, `on` and `once` Nest's contract declares. `@Ack()` is
|
|
305
|
+
not wired: the acknowledgement travels back as the echoed `id`,
|
|
306
|
+
which is what a client without a parser expects.
|
|
307
|
+
|
|
308
|
+
## Microservices
|
|
309
|
+
|
|
310
|
+
A microservice transport is Nest's own and never passes through
|
|
311
|
+
the HTTP adapter, so a hybrid application works: the routes are
|
|
312
|
+
served by Hono while the microservice listens on its transport,
|
|
313
|
+
and `app.close()` stops both.
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const app = await NestFactory.create(
|
|
317
|
+
AppModule,
|
|
318
|
+
new ServerAdapter(),
|
|
319
|
+
);
|
|
320
|
+
app.connectMicroservice({
|
|
321
|
+
options: { host: '127.0.0.1', port: 4000 },
|
|
322
|
+
transport: Transport.TCP,
|
|
323
|
+
});
|
|
324
|
+
await app.startAllMicroservices();
|
|
325
|
+
await app.listen(3000);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## CORS
|
|
329
|
+
|
|
330
|
+
`app.enableCors()` records the options instead of installing
|
|
331
|
+
Nest's Express middleware: the adapter answers with Hono's own
|
|
332
|
+
`cors` in front of every route.
|
|
333
|
+
|
|
334
|
+
An origin may be a string, a list, a regular expression, `'*'`,
|
|
335
|
+
or a callback in the shape Nest documents:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
app.enableCors({
|
|
339
|
+
origin: (origin, respond) => {
|
|
340
|
+
respond(
|
|
341
|
+
undefined,
|
|
342
|
+
allowed.has(origin ?? '') ? origin : false,
|
|
343
|
+
);
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The callback runs once per request, may answer an error to fail
|
|
349
|
+
the request outright, and is awaited, so an asynchronous
|
|
350
|
+
decision works. `preflightContinue: true` answers nothing
|
|
351
|
+
itself: the preflight headers are copied onto the response and
|
|
352
|
+
the router answers, which is what an application with its own
|
|
353
|
+
`OPTIONS` route wants.
|
|
354
|
+
|
|
355
|
+
Allowed origins are passed in by the application, which reads
|
|
356
|
+
them from its own configuration. This package reads no
|
|
357
|
+
environment variable, so one deployment policy is not baked into
|
|
358
|
+
the library.
|
|
359
|
+
|
|
360
|
+
## Requirements
|
|
361
|
+
|
|
362
|
+
- Node 22.12 or later (22.x and 24.x tested; 26.x ready as it
|
|
363
|
+
enters LTS).
|
|
364
|
+
- Nest 11 or 12.
|
|
365
|
+
- Hono 4 and `@hono/node-server` 2.
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
Bun installs and runs the workspace; the gates are oxlint,
|
|
370
|
+
oxfmt, `tsc` and `bun test`.
|
|
371
|
+
|
|
372
|
+
```sh
|
|
373
|
+
bun install
|
|
374
|
+
bun run check
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`bun run check` is exactly what CI runs: lint, format,
|
|
378
|
+
typecheck, test, build.
|
|
379
|
+
|
|
380
|
+
The cases start a real application on an ephemeral port and talk
|
|
381
|
+
to it over `fetch`, so they cover the path and query dialects,
|
|
382
|
+
every body type, the response forms, event streams, CORS, views,
|
|
383
|
+
static assets, TLS selection, proxy headers and shutdown. The
|
|
384
|
+
TLS case runs its check in a child Node process, because Bun
|
|
385
|
+
gives `node:http` and `node:https` the same `Server` class and
|
|
386
|
+
`instanceof` cannot tell them apart there.
|
|
387
|
+
|
|
388
|
+
The adapter builds against two Nest majors. The suite runs
|
|
389
|
+
against the version the lockfile pins, and the CI compatibility
|
|
390
|
+
job installs Nest 11 and 12 on Node 22 and 24 — every
|
|
391
|
+
`@nestjs/*` package moves together, because a mixed install is
|
|
392
|
+
what a gateway or a microservice case would fail on rather than
|
|
393
|
+
the adapter. To run it locally, move the four packages in one
|
|
394
|
+
call and put them back afterwards:
|
|
395
|
+
|
|
396
|
+
```sh
|
|
397
|
+
bun add --exact @nestjs/common@11.x @nestjs/core@11.x \
|
|
398
|
+
@nestjs/websockets@11.x @nestjs/microservices@11.x
|
|
399
|
+
bun run check
|
|
400
|
+
bun install
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Bun transpiles the tests from the root `tsconfig.json`, and the
|
|
404
|
+
fixtures are Nest controllers whose decorators are the legacy
|
|
405
|
+
kind, which is why that file turns `experimentalDecorators` and
|
|
406
|
+
`emitDecoratorMetadata` on. The library declares no decorator,
|
|
407
|
+
so neither flag changes what `bun run build` emits.
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
MIT
|
package/dist/body.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import type { NestContext } from './context.ts';
|
|
3
|
+
/**
|
|
4
|
+
* A payload as the pipeline reads it: the parsed value, the
|
|
5
|
+
* uploaded files when the request was multipart, and the bytes
|
|
6
|
+
* when the application asked to keep them.
|
|
7
|
+
*/
|
|
8
|
+
interface ParsedBody {
|
|
9
|
+
body: unknown;
|
|
10
|
+
files: Record<string, unknown> | undefined;
|
|
11
|
+
rawBody: Buffer | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface BodyOptions {
|
|
14
|
+
readonly bodyLimit: number | undefined;
|
|
15
|
+
readonly rawBody: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reads a size the way Nest's own parsers do, so a limit such
|
|
19
|
+
* as `100kb` means the same thing here as it does on Express.
|
|
20
|
+
*/
|
|
21
|
+
declare function toByteLimit(limit: number | string): number;
|
|
22
|
+
/**
|
|
23
|
+
* Reads the payload for the pipeline.
|
|
24
|
+
*
|
|
25
|
+
* On Express and Fastify Nest installs parser middleware that
|
|
26
|
+
* fills `req.body` before the pipeline runs. Hono parses on
|
|
27
|
+
* demand, so the adapter reads the body here and hands it over
|
|
28
|
+
* in the same place. A payload that does not match its content
|
|
29
|
+
* type is refused with the exception Nest raises for a failed
|
|
30
|
+
* parse, so filters and logging see it like any other failure.
|
|
31
|
+
*/
|
|
32
|
+
declare function readBody(context: NestContext, options: BodyOptions): Promise<ParsedBody>;
|
|
33
|
+
export { readBody, toByteLimit };
|
|
34
|
+
export type { ParsedBody };
|
|
35
|
+
//# sourceMappingURL=body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA+BhD;;;;GAIG;AACH,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC3C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAQD;;;GAGG;AACH,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAenD;AAiJD;;;;;;;;;GASG;AACH,iBAAS,QAAQ,CACf,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACjC,YAAY,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/body.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { BadRequestException, PayloadTooLargeException, } from '@nestjs/common';
|
|
3
|
+
import { parseQuery } from './query.js';
|
|
4
|
+
/** Methods that never carry a payload. */
|
|
5
|
+
const BODYLESS_METHODS = new Set(['GET', 'HEAD']);
|
|
6
|
+
/** The media types that carry a JSON document. */
|
|
7
|
+
const JSON_TYPE = /^application\/(?:.+\+)?json$/u;
|
|
8
|
+
/** How a limit may be written, in the notation Nest accepts. */
|
|
9
|
+
const LIMIT = /^\s*(?<amount>\d+(?:\.\d+)?)\s*(?<unit>b|kb|mb|gb|tb|pb)?\s*$/iu;
|
|
10
|
+
/** The size a kibibyte stands for, which every unit is built on. */
|
|
11
|
+
const BYTES_PER_KIB = 1024;
|
|
12
|
+
const BYTES_PER_MIB = BYTES_PER_KIB * BYTES_PER_KIB;
|
|
13
|
+
const BYTES_PER_GIB = BYTES_PER_MIB * BYTES_PER_KIB;
|
|
14
|
+
const BYTES_PER_TIB = BYTES_PER_GIB * BYTES_PER_KIB;
|
|
15
|
+
const BYTES_PER_PIB = BYTES_PER_TIB * BYTES_PER_KIB;
|
|
16
|
+
/** The size each unit of a limit stands for. */
|
|
17
|
+
const UNITS = new Map([
|
|
18
|
+
['b', 1],
|
|
19
|
+
['gb', BYTES_PER_GIB],
|
|
20
|
+
['kb', BYTES_PER_KIB],
|
|
21
|
+
['mb', BYTES_PER_MIB],
|
|
22
|
+
['pb', BYTES_PER_PIB],
|
|
23
|
+
['tb', BYTES_PER_TIB],
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Reads a size the way Nest's own parsers do, so a limit such
|
|
27
|
+
* as `100kb` means the same thing here as it does on Express.
|
|
28
|
+
*/
|
|
29
|
+
function toByteLimit(limit) {
|
|
30
|
+
if (typeof limit === 'number') {
|
|
31
|
+
return limit;
|
|
32
|
+
}
|
|
33
|
+
const match = LIMIT.exec(limit);
|
|
34
|
+
if (match === null) {
|
|
35
|
+
throw new TypeError(`The body limit "${limit}" is not a size such as ` +
|
|
36
|
+
'"100kb" or "1mb".');
|
|
37
|
+
}
|
|
38
|
+
const { amount, unit } = match.groups ?? {};
|
|
39
|
+
const bytes = Number(amount ?? '');
|
|
40
|
+
const suffix = (unit ?? 'b').toLowerCase();
|
|
41
|
+
return Math.floor(bytes * (UNITS.get(suffix) ?? 1));
|
|
42
|
+
}
|
|
43
|
+
function mediaType(context) {
|
|
44
|
+
const header = context.req.header('content-type') ?? '';
|
|
45
|
+
return (header.split(';')[0] ?? '').trim().toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
function assertWithinLength(context, limit) {
|
|
48
|
+
if (limit === undefined) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const declared = context.req.header('content-length');
|
|
52
|
+
if (declared === undefined) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const length = Number(declared);
|
|
56
|
+
if (Number.isFinite(length) && length > limit) {
|
|
57
|
+
throw new PayloadTooLargeException();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function assertWithinSize(size, limit) {
|
|
61
|
+
if (limit !== undefined && size > limit) {
|
|
62
|
+
throw new PayloadTooLargeException();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function readBytes(context, limit) {
|
|
66
|
+
const bytes = new Uint8Array(await context.req.arrayBuffer());
|
|
67
|
+
assertWithinSize(bytes.byteLength, limit);
|
|
68
|
+
return bytes;
|
|
69
|
+
}
|
|
70
|
+
function textOf(bytes) {
|
|
71
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
72
|
+
}
|
|
73
|
+
function parseJson(bytes) {
|
|
74
|
+
const text = textOf(bytes).trim();
|
|
75
|
+
if (text === '') {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(text);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new BadRequestException('Malformed request body');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function parseBytes(bytes, type) {
|
|
86
|
+
if (JSON_TYPE.test(type)) {
|
|
87
|
+
return parseJson(bytes);
|
|
88
|
+
}
|
|
89
|
+
if (type === 'application/x-www-form-urlencoded') {
|
|
90
|
+
return parseQuery(textOf(bytes));
|
|
91
|
+
}
|
|
92
|
+
if (type.startsWith('text/')) {
|
|
93
|
+
return textOf(bytes);
|
|
94
|
+
}
|
|
95
|
+
if (bytes.byteLength === 0) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return Buffer.from(bytes);
|
|
99
|
+
}
|
|
100
|
+
async function readForm(context) {
|
|
101
|
+
try {
|
|
102
|
+
return await context.req.parseBody();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new BadRequestException('Malformed request body');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function splitFields(form) {
|
|
109
|
+
const fields = {};
|
|
110
|
+
const files = {};
|
|
111
|
+
for (const [name, value] of Object.entries(form)) {
|
|
112
|
+
if (typeof value === 'string') {
|
|
113
|
+
fields[name] = value;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
files[name] = value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { fields, files };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Reads a multipart request into the two halves a controller
|
|
123
|
+
* expects: the text fields in the body, the uploads in `files`.
|
|
124
|
+
* The bytes are never buffered, so `rawBody` stays empty.
|
|
125
|
+
*/
|
|
126
|
+
async function readMultipart(context) {
|
|
127
|
+
const form = await readForm(context);
|
|
128
|
+
const parts = splitFields(form);
|
|
129
|
+
const parsed = {
|
|
130
|
+
body: parts.fields,
|
|
131
|
+
files: undefined,
|
|
132
|
+
rawBody: undefined,
|
|
133
|
+
};
|
|
134
|
+
if (Object.keys(parts.files).length > 0) {
|
|
135
|
+
parsed.files = parts.files;
|
|
136
|
+
}
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
function emptyBody() {
|
|
140
|
+
return {
|
|
141
|
+
body: undefined,
|
|
142
|
+
files: undefined,
|
|
143
|
+
rawBody: undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function readPayload(context, type, options) {
|
|
147
|
+
const bytes = await readBytes(context, options.bodyLimit);
|
|
148
|
+
const parsed = {
|
|
149
|
+
body: parseBytes(bytes, type),
|
|
150
|
+
files: undefined,
|
|
151
|
+
rawBody: undefined,
|
|
152
|
+
};
|
|
153
|
+
if (options.rawBody) {
|
|
154
|
+
parsed.rawBody = Buffer.from(bytes);
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Reads the payload for the pipeline.
|
|
160
|
+
*
|
|
161
|
+
* On Express and Fastify Nest installs parser middleware that
|
|
162
|
+
* fills `req.body` before the pipeline runs. Hono parses on
|
|
163
|
+
* demand, so the adapter reads the body here and hands it over
|
|
164
|
+
* in the same place. A payload that does not match its content
|
|
165
|
+
* type is refused with the exception Nest raises for a failed
|
|
166
|
+
* parse, so filters and logging see it like any other failure.
|
|
167
|
+
*/
|
|
168
|
+
function readBody(context, options) {
|
|
169
|
+
if (BODYLESS_METHODS.has(context.req.method)) {
|
|
170
|
+
return Promise.resolve(emptyBody());
|
|
171
|
+
}
|
|
172
|
+
const type = mediaType(context);
|
|
173
|
+
assertWithinLength(context, options.bodyLimit);
|
|
174
|
+
if (type.startsWith('multipart/form-data')) {
|
|
175
|
+
return readMultipart(context);
|
|
176
|
+
}
|
|
177
|
+
return readPayload(context, type, options);
|
|
178
|
+
}
|
|
179
|
+
export { readBody, toByteLimit };
|
|
180
|
+
//# sourceMappingURL=body.js.map
|
package/dist/body.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EACL,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,0CAA0C;AAC1C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAElD,kDAAkD;AAClD,MAAM,SAAS,GAAG,+BAA+B,CAAC;AAElD,gEAAgE;AAChE,MAAM,KAAK,GACT,iEAAiE,CAAC;AAEpE,oEAAoE;AACpE,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B,MAAM,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AACpD,MAAM,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AACpD,MAAM,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AACpD,MAAM,aAAa,GAAG,aAAa,GAAG,aAAa,CAAC;AAEpD,gDAAgD;AAChD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAiB;IACpC,CAAC,GAAG,EAAE,CAAC,CAAC;IACR,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,IAAI,EAAE,aAAa,CAAC;CACtB,CAAC,CAAC;AAwBH;;;GAGG;AACH,SAAS,WAAW,CAAC,KAAsB;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,SAAS,CACjB,mBAAmB,KAAK,0BAA0B;YAChD,mBAAmB,CACtB,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,OAAoB;IACrC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,kBAAkB,CACzB,OAAoB,EACpB,KAAyB;IAEzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO;IACT,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACtD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,KAAK,EAAE,CAAC;QAC9C,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACvC,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CACvB,IAAY,EACZ,KAAyB;IAEzB,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACxC,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACvC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,OAAoB,EACpB,KAAyB;IAEzB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAC9D,gBAAgB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,KAAiB;IAC/B,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,SAAS,CAAC,KAAiB;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAAC,wBAAwB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAiB,EAAE,IAAY;IACjD,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,KAAK,mCAAmC,EAAE,CAAC;QACjD,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,OAAoB;IAEpB,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAAC,wBAAwB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAAmC;IAEnC,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,aAAa,CAC1B,OAAoB;IAEpB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAe;QACzB,IAAI,EAAE,KAAK,CAAC,MAAM;QAClB,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,SAAS;KACnB,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC7B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,SAAS;IAChB,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,SAAS;KACnB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,OAAoB,EACpB,IAAY,EACZ,OAAoB;IAEpB,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAe;QACzB,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC;QAC7B,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,SAAS;KACnB,CAAC;IACF,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,QAAQ,CACf,OAAoB,EACpB,OAAoB;IAEpB,IAAI,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAChC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC3C,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC"}
|