@kiyasov/platform-hono 1.1.1 → 1.1.2

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.2",
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,10 +43,10 @@ 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
 
@@ -58,14 +58,14 @@ export class HonoAdapter extends AbstractHttpAdapter<
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);
61
+ const body = ctx.get("body");
62
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
63
63
  }
64
64
 
65
65
  public get(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
66
66
  const [routePath, routeHandler] = this.getRouteAndHandler(
67
67
  pathOrHandler,
68
- handler,
68
+ handler
69
69
  );
70
70
  this.instance.get(routePath, this.createRouteHandler(routeHandler));
71
71
  }
@@ -73,7 +73,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
73
73
  public post(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
74
74
  const [routePath, routeHandler] = this.getRouteAndHandler(
75
75
  pathOrHandler,
76
- handler,
76
+ handler
77
77
  );
78
78
  this.instance.post(routePath, this.createRouteHandler(routeHandler));
79
79
  }
@@ -81,7 +81,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
81
81
  public put(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
82
82
  const [routePath, routeHandler] = this.getRouteAndHandler(
83
83
  pathOrHandler,
84
- handler,
84
+ handler
85
85
  );
86
86
  this.instance.put(routePath, this.createRouteHandler(routeHandler));
87
87
  }
@@ -89,7 +89,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
89
89
  public delete(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
90
90
  const [routePath, routeHandler] = this.getRouteAndHandler(
91
91
  pathOrHandler,
92
- handler,
92
+ handler
93
93
  );
94
94
  this.instance.delete(routePath, this.createRouteHandler(routeHandler));
95
95
  }
@@ -97,7 +97,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
97
97
  public use(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
98
98
  const [routePath, routeHandler] = this.getRouteAndHandler(
99
99
  pathOrHandler,
100
- handler,
100
+ handler
101
101
  );
102
102
  this.instance.use(routePath, this.createRouteHandler(routeHandler));
103
103
  }
@@ -105,7 +105,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
105
105
  public patch(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
106
106
  const [routePath, routeHandler] = this.getRouteAndHandler(
107
107
  pathOrHandler,
108
- handler,
108
+ handler
109
109
  );
110
110
  this.instance.patch(routePath, this.createRouteHandler(routeHandler));
111
111
  }
@@ -113,7 +113,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
113
113
  public options(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
114
114
  const [routePath, routeHandler] = this.getRouteAndHandler(
115
115
  pathOrHandler,
116
- handler,
116
+ handler
117
117
  );
118
118
  this.instance.options(routePath, this.createRouteHandler(routeHandler));
119
119
  }
@@ -121,17 +121,17 @@ export class HonoAdapter extends AbstractHttpAdapter<
121
121
  public async reply(ctx: Context, body: any, statusCode?: StatusCode) {
122
122
  if (statusCode) ctx.status(statusCode);
123
123
 
124
- const responseContentType = this.getHeader(ctx, 'Content-Type');
124
+ const responseContentType = this.getHeader(ctx, "Content-Type");
125
125
  if (
126
- !responseContentType?.startsWith('application/json') &&
126
+ !responseContentType?.startsWith("application/json") &&
127
127
  body?.statusCode >= HttpStatus.BAD_REQUEST
128
128
  ) {
129
129
  Logger.warn(
130
- "Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses",
130
+ "Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses"
131
131
  );
132
- this.setHeader(ctx, 'Content-Type', 'application/json');
132
+ this.setHeader(ctx, "Content-Type", "application/json");
133
133
  }
134
- ctx.set('body', body);
134
+ ctx.set("body", body);
135
135
  }
136
136
 
137
137
  public status(ctx: Context, statusCode: StatusCode) {
@@ -143,7 +143,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
143
143
  }
144
144
 
145
145
  public render(response: any, view: string, options: any) {
146
- throw new Error('Method not implemented.');
146
+ throw new Error("Method not implemented.");
147
147
  }
148
148
 
149
149
  public redirect(ctx: Context, statusCode: RedirectStatusCode, url: string) {
@@ -153,24 +153,26 @@ export class HonoAdapter extends AbstractHttpAdapter<
153
153
  public setErrorHandler(handler: ErrorHandler) {
154
154
  this.instance.onError(async (err: Error, ctx: Context) => {
155
155
  await handler(err, ctx.req, ctx);
156
- return this.send(ctx);
156
+ const body = ctx.get("body");
157
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
157
158
  });
158
159
  }
159
160
 
160
161
  public setNotFoundHandler(handler: RequestHandler) {
161
162
  this.instance.notFound(async (ctx: Context) => {
162
163
  await handler(ctx.req, ctx);
163
- return this.send(ctx);
164
+ const body = ctx.get("body");
165
+ return typeof body === "string" ? ctx.text(body) : ctx.json(body);
164
166
  });
165
167
  }
166
168
 
167
169
  public useStaticAssets(path: string, options: ServeStaticOptions) {
168
- Logger.log('Registering static assets middleware');
170
+ Logger.log("Registering static assets middleware");
169
171
  this.instance.use(path, serveStatic(options));
170
172
  }
171
173
 
172
174
  public setViewEngine(options: any | string) {
173
- throw new Error('Method not implemented.');
175
+ throw new Error("Method not implemented.");
174
176
  }
175
177
 
176
178
  public isHeadersSent(ctx: Context): boolean {
@@ -206,16 +208,16 @@ export class HonoAdapter extends AbstractHttpAdapter<
206
208
  }
207
209
 
208
210
  public useBodyParser(type: TypeBodyParser, bodyLimit: number = 1e6) {
209
- Logger.log('Registering body parser middleware');
211
+ Logger.log("Registering body parser middleware");
210
212
  this.instance.use(this.bodyLimit(bodyLimit), async (ctx, next) => {
211
- const contentType = ctx.req.header('content-type');
213
+ const contentType = ctx.req.header("content-type");
212
214
  switch (type) {
213
- case 'application/json':
214
- if (contentType === 'application/json')
215
+ case "application/json":
216
+ if (contentType === "application/json")
215
217
  (ctx.req as any).body = await ctx.req.json();
216
218
  break;
217
- case 'text/plain':
218
- if (contentType === 'text/plain') {
219
+ case "text/plain":
220
+ if (contentType === "text/plain") {
219
221
  (ctx.req as any).rawBody = Buffer.from(await ctx.req.text());
220
222
  (ctx.req as any).body = await ctx.req.json();
221
223
  }
@@ -245,14 +247,14 @@ export class HonoAdapter extends AbstractHttpAdapter<
245
247
  }
246
248
 
247
249
  public getType(): string {
248
- return 'hono';
250
+ return "hono";
249
251
  }
250
252
 
251
253
  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');
254
+ Logger.log("Registering parser middleware");
255
+ this.useBodyParser("application/x-www-form-urlencoded");
256
+ this.useBodyParser("application/json");
257
+ this.useBodyParser("text/plain");
256
258
  }
257
259
 
258
260
  public async createMiddlewareFactory(requestMethod: RequestMethod) {
@@ -276,7 +278,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
276
278
  }
277
279
 
278
280
  public applyVersionFilter(): () => () => any {
279
- throw new Error('Versioning not yet supported in Hono');
281
+ throw new Error("Versioning not yet supported in Hono");
280
282
  }
281
283
 
282
284
  public listen(port: string | number, ...args: any[]): ServerType {
@@ -287,7 +289,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
287
289
  return bodyLimit({
288
290
  maxSize,
289
291
  onError: () => {
290
- throw new Error('Body too large');
292
+ throw new Error("Body too large");
291
293
  },
292
294
  });
293
295
  }
@@ -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"}