@kiyasov/platform-hono 1.1.1 → 1.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kiyasov/platform-hono",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Nest adapter for Hono",
5
5
  "author": "Islam Kiiasov",
6
6
  "license": "MIT",
@@ -18,20 +18,25 @@
18
18
  "dependencies": {
19
19
  "@hono/graphql-server": "^0.4.3",
20
20
  "@hono/node-server": "^1.11.2",
21
- "hono": "^4.4.3"
21
+ "hono": "^4.4.4"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@apollo/server": "^4.10.4",
25
25
  "@nestjs/apollo": "^12.1.0",
26
26
  "@nestjs/cli": "^10.3.2",
27
- "@nestjs/common": "^10.3.8",
28
- "@nestjs/core": "^10.3.8",
27
+ "@nestjs/common": "^10.3.9",
28
+ "@nestjs/core": "^10.3.9",
29
29
  "@nestjs/graphql": "^12.1.1",
30
30
  "@swc/cli": "^0.3.12",
31
- "@swc/core": "^1.5.24",
31
+ "@swc/core": "^1.5.25",
32
+ "@types/autocannon": "^7",
33
+ "@types/bun": "^1.1.3",
32
34
  "@types/busboy": "^1.5.4",
35
+ "autocannon": "^7.15.0",
36
+ "bun": "^1.1.12",
33
37
  "graphql-subscriptions": "^2.0.0",
34
38
  "reflect-metadata": "^0.2.2",
39
+ "rimraf": "^5.0.7",
35
40
  "rxjs": "^7.8.1",
36
41
  "tsc": "^2.0.4",
37
42
  "typescript": "^5.4.5"
@@ -40,6 +45,11 @@
40
45
  "build:esm": "tsc --p tsconfig.esm.json",
41
46
  "build:cjs": "tsc --p tsconfig.cjs.json",
42
47
  "build": "npm run build:esm && npm run build:cjs",
43
- "dev": "cd example && yarn nest start -w --copy-files"
48
+ "dev": "cd example && yarn nest start -w --copy-files",
49
+ "dev:bun": "cd example && nest start -w --copy-files --exec \"bun run\"",
50
+ "benchmark": "yarn dev & ( sleep 5 && autocannon -c 200 -d 5 -p 10 http://localhost:3000 )",
51
+ "benchmark:bun": "yarn dev:bun & ( sleep 5 && autocannon -c 200 -d 5 -p 10 http://localhost:3000 )",
52
+ "benchmark:bun:file": "yarn run dev:bun & ( sleep 5 && bun run ./benchmarks/benchmark.mjs)",
53
+ "benchmark:node:file": "yarn run dev & ( sleep 5 && node ./benchmarks/benchmark.mjs)"
44
54
  }
45
55
  }
@@ -1,27 +1,27 @@
1
- import { Server } from 'node:net';
2
- import { HttpBindings, createAdaptorServer } from '@hono/node-server';
3
- import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';
4
- import { RequestMethod } from '@nestjs/common';
5
- import { HttpStatus, Logger } from '@nestjs/common';
6
- import { bodyLimit } from 'hono/body-limit';
1
+ import { Server } from "node:net";
2
+ import { HttpBindings, createAdaptorServer } from "@hono/node-server";
3
+ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
4
+ import { RequestMethod } from "@nestjs/common";
5
+ import { HttpStatus, Logger } from "@nestjs/common";
6
+ import { bodyLimit } from "hono/body-limit";
7
7
  import {
8
8
  ErrorHandler,
9
9
  NestApplicationOptions,
10
10
  RequestHandler,
11
- } from '@nestjs/common/interfaces';
11
+ } from "@nestjs/common/interfaces";
12
12
  import {
13
13
  ServeStaticOptions,
14
14
  serveStatic,
15
- } from '@hono/node-server/serve-static';
16
- import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
17
- import { Context, HonoRequest, Next } from 'hono';
18
- import { Hono } from 'hono';
19
- import { cors } from 'hono/cors';
20
- import { RedirectStatusCode, StatusCode } from 'hono/utils/http-status';
21
- import * as http from 'http';
22
- import { Http2SecureServer, Http2Server } from 'http2';
23
- import * as https from 'https';
24
- import { TypeBodyParser } from '../interfaces';
15
+ } from "@hono/node-server/serve-static";
16
+ import { AbstractHttpAdapter } from "@nestjs/core/adapters/http-adapter";
17
+ import { Context, HonoRequest, Next } from "hono";
18
+ import { Hono } from "hono";
19
+ import { cors } from "hono/cors";
20
+ import { RedirectStatusCode, StatusCode } from "hono/utils/http-status";
21
+ import * as http from "http";
22
+ import { Http2SecureServer, Http2Server } from "http2";
23
+ import * as https from "https";
24
+ import { TypeBodyParser } from "../interfaces";
25
25
 
26
26
  type HonoHandler = RequestHandler<HonoRequest, Context>;
27
27
 
@@ -43,29 +43,25 @@ export class HonoAdapter extends AbstractHttpAdapter<
43
43
 
44
44
  private getRouteAndHandler(
45
45
  pathOrHandler: string | HonoHandler,
46
- handler?: HonoHandler,
46
+ handler?: HonoHandler
47
47
  ): [string, HonoHandler] {
48
- let path = typeof pathOrHandler === 'function' ? '' : pathOrHandler;
49
- handler = typeof pathOrHandler === 'function' ? pathOrHandler : handler;
48
+ let path = typeof pathOrHandler === "function" ? "" : pathOrHandler;
49
+ handler = typeof pathOrHandler === "function" ? pathOrHandler : handler;
50
50
  return [path, handler];
51
51
  }
52
52
 
53
53
  private createRouteHandler(routeHandler: HonoHandler) {
54
54
  return async (ctx: Context, next: Next) => {
55
55
  await routeHandler(ctx.req, ctx, next);
56
- return this.send(ctx);
56
+ const body = ctx.get("body");
57
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
57
58
  };
58
59
  }
59
60
 
60
- private send(ctx: Context) {
61
- const body = ctx.get('body');
62
- return typeof body === 'string' ? ctx.text(body) : ctx.json(body);
63
- }
64
-
65
61
  public get(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
66
62
  const [routePath, routeHandler] = this.getRouteAndHandler(
67
63
  pathOrHandler,
68
- handler,
64
+ handler
69
65
  );
70
66
  this.instance.get(routePath, this.createRouteHandler(routeHandler));
71
67
  }
@@ -73,7 +69,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
73
69
  public post(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
74
70
  const [routePath, routeHandler] = this.getRouteAndHandler(
75
71
  pathOrHandler,
76
- handler,
72
+ handler
77
73
  );
78
74
  this.instance.post(routePath, this.createRouteHandler(routeHandler));
79
75
  }
@@ -81,7 +77,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
81
77
  public put(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
82
78
  const [routePath, routeHandler] = this.getRouteAndHandler(
83
79
  pathOrHandler,
84
- handler,
80
+ handler
85
81
  );
86
82
  this.instance.put(routePath, this.createRouteHandler(routeHandler));
87
83
  }
@@ -89,7 +85,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
89
85
  public delete(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
90
86
  const [routePath, routeHandler] = this.getRouteAndHandler(
91
87
  pathOrHandler,
92
- handler,
88
+ handler
93
89
  );
94
90
  this.instance.delete(routePath, this.createRouteHandler(routeHandler));
95
91
  }
@@ -97,7 +93,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
97
93
  public use(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
98
94
  const [routePath, routeHandler] = this.getRouteAndHandler(
99
95
  pathOrHandler,
100
- handler,
96
+ handler
101
97
  );
102
98
  this.instance.use(routePath, this.createRouteHandler(routeHandler));
103
99
  }
@@ -105,7 +101,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
105
101
  public patch(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
106
102
  const [routePath, routeHandler] = this.getRouteAndHandler(
107
103
  pathOrHandler,
108
- handler,
104
+ handler
109
105
  );
110
106
  this.instance.patch(routePath, this.createRouteHandler(routeHandler));
111
107
  }
@@ -113,7 +109,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
113
109
  public options(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
114
110
  const [routePath, routeHandler] = this.getRouteAndHandler(
115
111
  pathOrHandler,
116
- handler,
112
+ handler
117
113
  );
118
114
  this.instance.options(routePath, this.createRouteHandler(routeHandler));
119
115
  }
@@ -121,17 +117,17 @@ export class HonoAdapter extends AbstractHttpAdapter<
121
117
  public async reply(ctx: Context, body: any, statusCode?: StatusCode) {
122
118
  if (statusCode) ctx.status(statusCode);
123
119
 
124
- const responseContentType = this.getHeader(ctx, 'Content-Type');
120
+ const responseContentType = this.getHeader(ctx, "Content-Type");
125
121
  if (
126
- !responseContentType?.startsWith('application/json') &&
122
+ !responseContentType?.startsWith("application/json") &&
127
123
  body?.statusCode >= HttpStatus.BAD_REQUEST
128
124
  ) {
129
125
  Logger.warn(
130
- "Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses",
126
+ "Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses"
131
127
  );
132
- this.setHeader(ctx, 'Content-Type', 'application/json');
128
+ this.setHeader(ctx, "Content-Type", "application/json");
133
129
  }
134
- ctx.set('body', body);
130
+ ctx.set("body", body);
135
131
  }
136
132
 
137
133
  public status(ctx: Context, statusCode: StatusCode) {
@@ -143,7 +139,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
143
139
  }
144
140
 
145
141
  public render(response: any, view: string, options: any) {
146
- throw new Error('Method not implemented.');
142
+ throw new Error("Method not implemented.");
147
143
  }
148
144
 
149
145
  public redirect(ctx: Context, statusCode: RedirectStatusCode, url: string) {
@@ -153,24 +149,26 @@ export class HonoAdapter extends AbstractHttpAdapter<
153
149
  public setErrorHandler(handler: ErrorHandler) {
154
150
  this.instance.onError(async (err: Error, ctx: Context) => {
155
151
  await handler(err, ctx.req, ctx);
156
- return this.send(ctx);
152
+ const body = ctx.get("body");
153
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
157
154
  });
158
155
  }
159
156
 
160
157
  public setNotFoundHandler(handler: RequestHandler) {
161
158
  this.instance.notFound(async (ctx: Context) => {
162
159
  await handler(ctx.req, ctx);
163
- return this.send(ctx);
160
+ const body = ctx.get("body");
161
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
164
162
  });
165
163
  }
166
164
 
167
165
  public useStaticAssets(path: string, options: ServeStaticOptions) {
168
- Logger.log('Registering static assets middleware');
166
+ Logger.log("Registering static assets middleware");
169
167
  this.instance.use(path, serveStatic(options));
170
168
  }
171
169
 
172
170
  public setViewEngine(options: any | string) {
173
- throw new Error('Method not implemented.');
171
+ throw new Error("Method not implemented.");
174
172
  }
175
173
 
176
174
  public isHeadersSent(ctx: Context): boolean {
@@ -206,16 +204,16 @@ export class HonoAdapter extends AbstractHttpAdapter<
206
204
  }
207
205
 
208
206
  public useBodyParser(type: TypeBodyParser, bodyLimit: number = 1e6) {
209
- Logger.log('Registering body parser middleware');
207
+ Logger.log("Registering body parser middleware");
210
208
  this.instance.use(this.bodyLimit(bodyLimit), async (ctx, next) => {
211
- const contentType = ctx.req.header('content-type');
209
+ const contentType = ctx.req.header("content-type");
212
210
  switch (type) {
213
- case 'application/json':
214
- if (contentType === 'application/json')
211
+ case "application/json":
212
+ if (contentType === "application/json")
215
213
  (ctx.req as any).body = await ctx.req.json();
216
214
  break;
217
- case 'text/plain':
218
- if (contentType === 'text/plain') {
215
+ case "text/plain":
216
+ if (contentType === "text/plain") {
219
217
  (ctx.req as any).rawBody = Buffer.from(await ctx.req.text());
220
218
  (ctx.req as any).body = await ctx.req.json();
221
219
  }
@@ -245,14 +243,14 @@ export class HonoAdapter extends AbstractHttpAdapter<
245
243
  }
246
244
 
247
245
  public getType(): string {
248
- return 'hono';
246
+ return "hono";
249
247
  }
250
248
 
251
249
  public registerParserMiddleware(prefix?: string, rawBody?: boolean) {
252
- Logger.log('Registering parser middleware');
253
- this.useBodyParser('application/x-www-form-urlencoded');
254
- this.useBodyParser('application/json');
255
- this.useBodyParser('text/plain');
250
+ Logger.log("Registering parser middleware");
251
+ this.useBodyParser("application/x-www-form-urlencoded");
252
+ this.useBodyParser("application/json");
253
+ this.useBodyParser("text/plain");
256
254
  }
257
255
 
258
256
  public async createMiddlewareFactory(requestMethod: RequestMethod) {
@@ -276,7 +274,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
276
274
  }
277
275
 
278
276
  public applyVersionFilter(): () => () => any {
279
- throw new Error('Versioning not yet supported in Hono');
277
+ throw new Error("Versioning not yet supported in Hono");
280
278
  }
281
279
 
282
280
  public listen(port: string | number, ...args: any[]): ServerType {
@@ -287,7 +285,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
287
285
  return bodyLimit({
288
286
  maxSize,
289
287
  onError: () => {
290
- throw new Error('Body too large');
288
+ throw new Error("Body too large");
291
289
  },
292
290
  });
293
291
  }
@@ -1,2 +0,0 @@
1
- import { Context } from 'hono';
2
- export declare function processRequest(ctx: Context): Promise<Record<string, any>>;
@@ -1,45 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.processRequest = void 0;
4
- const graphql_upload_ts_1 = require("graphql-upload-ts");
5
- const stream_1 = require("stream");
6
- async function processRequest(ctx) {
7
- const body = await ctx.req.parseBody();
8
- const operations = JSON.parse(body.operations);
9
- const map = new Map(Object.entries(JSON.parse(body.map)));
10
- for (const [fieldName, file] of Object.entries(body)) {
11
- if (fieldName === 'operations' ||
12
- fieldName === 'map' ||
13
- !(file instanceof File))
14
- continue;
15
- const fileKeys = map.get(fieldName);
16
- if (!Array.isArray(fileKeys) || !fileKeys.length)
17
- continue;
18
- const buffer = Buffer.from(await file.arrayBuffer());
19
- const capacitor = new graphql_upload_ts_1.WriteStream();
20
- stream_1.Readable.from(buffer).pipe(capacitor);
21
- const upload = new graphql_upload_ts_1.Upload();
22
- upload.file = {
23
- filename: file.name,
24
- mimetype: file.type,
25
- fieldName,
26
- encoding: '7bit',
27
- createReadStream: (options) => capacitor.createReadStream(options),
28
- capacitor,
29
- };
30
- upload.resolve(upload.file);
31
- for (const fileKey of fileKeys) {
32
- const pathSegments = fileKey.split('.');
33
- let current = operations;
34
- for (let i = 0; i < pathSegments.length - 1; i++) {
35
- if (!current[pathSegments[i]])
36
- current[pathSegments[i]] = {};
37
- current = current[pathSegments[i]];
38
- }
39
- current[pathSegments[pathSegments.length - 1]] = upload;
40
- }
41
- }
42
- return operations;
43
- }
44
- exports.processRequest = processRequest;
45
- //# sourceMappingURL=processRequest.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"processRequest.js","sourceRoot":"","sources":["../../../../../src/drivers/services/processRequest.ts"],"names":[],"mappings":";;;AACA,yDAAwD;AACxD,mCAAkC;AAE3B,KAAK,UAAU,cAAc,CAClC,GAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAoB,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAa,CAAC,CAAC,CAAC,CAAC;IAEpE,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrD,IACE,SAAS,KAAK,YAAY;YAC1B,SAAS,KAAK,KAAK;YACnB,CAAC,CAAC,IAAI,YAAY,IAAI,CAAC;YAEvB,SAAS;QAEX,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,SAAS;QAE3D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,IAAI,+BAAW,EAAE,CAAC;QACpC,iBAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,IAAI,0BAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,GAAG;YACZ,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,SAAS;YACT,QAAQ,EAAE,MAAM;YAChB,gBAAgB,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAClE,SAAS;SACV,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE5B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO,GAAG,UAAU,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;gBAC7D,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AA7CD,wCA6CC"}
@@ -1,2 +0,0 @@
1
- import { Context } from 'hono';
2
- export declare function processRequest(ctx: Context): Promise<Record<string, any>>;
@@ -1,41 +0,0 @@
1
- import { WriteStream, Upload } from 'graphql-upload-ts';
2
- import { Readable } from 'stream';
3
- export async function processRequest(ctx) {
4
- const body = await ctx.req.parseBody();
5
- const operations = JSON.parse(body.operations);
6
- const map = new Map(Object.entries(JSON.parse(body.map)));
7
- for (const [fieldName, file] of Object.entries(body)) {
8
- if (fieldName === 'operations' ||
9
- fieldName === 'map' ||
10
- !(file instanceof File))
11
- continue;
12
- const fileKeys = map.get(fieldName);
13
- if (!Array.isArray(fileKeys) || !fileKeys.length)
14
- continue;
15
- const buffer = Buffer.from(await file.arrayBuffer());
16
- const capacitor = new WriteStream();
17
- Readable.from(buffer).pipe(capacitor);
18
- const upload = new Upload();
19
- upload.file = {
20
- filename: file.name,
21
- mimetype: file.type,
22
- fieldName,
23
- encoding: '7bit',
24
- createReadStream: (options) => capacitor.createReadStream(options),
25
- capacitor,
26
- };
27
- upload.resolve(upload.file);
28
- for (const fileKey of fileKeys) {
29
- const pathSegments = fileKey.split('.');
30
- let current = operations;
31
- for (let i = 0; i < pathSegments.length - 1; i++) {
32
- if (!current[pathSegments[i]])
33
- current[pathSegments[i]] = {};
34
- current = current[pathSegments[i]];
35
- }
36
- current[pathSegments[pathSegments.length - 1]] = upload;
37
- }
38
- }
39
- return operations;
40
- }
41
- //# sourceMappingURL=processRequest.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"processRequest.js","sourceRoot":"","sources":["../../../../../src/drivers/services/processRequest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAY;IAEZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAoB,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAa,CAAC,CAAC,CAAC,CAAC;IAEpE,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrD,IACE,SAAS,KAAK,YAAY;YAC1B,SAAS,KAAK,KAAK;YACnB,CAAC,CAAC,IAAI,YAAY,IAAI,CAAC;YAEvB,SAAS;QAEX,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM;YAAE,SAAS;QAE3D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,IAAI,WAAW,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,GAAG;YACZ,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,SAAS;YACT,QAAQ,EAAE,MAAM;YAChB,gBAAgB,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAClE,SAAS;SACV,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE5B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,OAAO,GAAG,UAAU,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;gBAC7D,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"}