@fluojs/testing 1.0.0-beta.2 → 1.0.0-beta.4

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.
@@ -57,10 +57,26 @@ async function requestHttps(url) {
57
57
  request.end();
58
58
  });
59
59
  }
60
- async function closeSilently(app) {
60
+ async function runWithCleanup(app, adapterName, assertion) {
61
+ let hasAssertionError = false;
62
+ let assertionError;
63
+ try {
64
+ await assertion();
65
+ } catch (error) {
66
+ hasAssertionError = true;
67
+ assertionError = error;
68
+ }
61
69
  try {
62
70
  await app.close();
63
- } catch {}
71
+ } catch (cleanupError) {
72
+ if (hasAssertionError) {
73
+ throw new AggregateError([assertionError, cleanupError], `${adapterName} adapter portability assertion failed and app.close() also failed during harness cleanup.`);
74
+ }
75
+ throw new AggregateError([cleanupError], `${adapterName} adapter app.close() failed during portability harness cleanup.`);
76
+ }
77
+ if (hasAssertionError) {
78
+ throw assertionError;
79
+ }
64
80
  }
65
81
 
66
82
  /**
@@ -115,7 +131,7 @@ export class HttpAdapterPortabilityHarness {
115
131
  port
116
132
  });
117
133
  await app.listen();
118
- try {
134
+ await runWithCleanup(app, this.options.name, async () => {
119
135
  const response = await fetch(`http://127.0.0.1:${String(port)}/cookies`, {
120
136
  headers: {
121
137
  cookie: 'good=hello%20world; bad=%E0%A4%A'
@@ -128,9 +144,7 @@ export class HttpAdapterPortabilityHarness {
128
144
  if (typeof body !== 'object' || body === null || !('bad' in body) || !('good' in body) || body.bad !== '%E0%A4%A' || body.good !== 'hello world' || Object.keys(body).length !== 2) {
129
145
  throw new Error(`${this.options.name} adapter changed malformed-cookie normalization.`);
130
146
  }
131
- } finally {
132
- await closeSilently(app);
133
- }
147
+ });
134
148
  }
135
149
  async assertPreservesRawBodyForJsonAndText() {
136
150
  let _initProto2, _initClass2;
@@ -172,7 +186,7 @@ export class HttpAdapterPortabilityHarness {
172
186
  rawBody: true
173
187
  });
174
188
  await app.listen();
175
- try {
189
+ await runWithCleanup(app, this.options.name, async () => {
176
190
  const [jsonResponse, textResponse] = await Promise.all([fetch(`http://127.0.0.1:${String(port)}/webhooks/json`, {
177
191
  body: JSON.stringify({
178
192
  provider: 'stripe'
@@ -206,22 +220,75 @@ export class HttpAdapterPortabilityHarness {
206
220
  })) {
207
221
  throw new Error(`${this.options.name} adapter changed text rawBody semantics.`);
208
222
  }
209
- } finally {
210
- await closeSilently(app);
223
+ });
224
+ }
225
+ async assertPreservesExactRawBodyBytesForByteSensitivePayloads() {
226
+ let _initProto3, _initClass3;
227
+ let _WebhookController2;
228
+ class WebhookController {
229
+ static {
230
+ ({
231
+ e: [_initProto3],
232
+ c: [_WebhookController2, _initClass3]
233
+ } = _applyDecs(this, [Controller('/webhooks')], [[Post('/bytes'), 2, "handleBytes"]]));
234
+ }
235
+ constructor() {
236
+ _initProto3(this);
237
+ }
238
+ handleBytes(_input, context) {
239
+ return {
240
+ rawBytes: Array.from(context.request.rawBody ?? new Uint8Array())
241
+ };
242
+ }
243
+ static {
244
+ _initClass3();
245
+ }
211
246
  }
247
+ class AppModule {}
248
+ defineModule(AppModule, {
249
+ controllers: [_WebhookController2]
250
+ });
251
+ const port = await findAvailablePort();
252
+ const app = await this.options.bootstrap(AppModule, {
253
+ cors: false,
254
+ port,
255
+ rawBody: true
256
+ });
257
+ await this.options.prepareExactRawBodyByteTest?.(app);
258
+ await app.listen();
259
+ await runWithCleanup(app, this.options.name, async () => {
260
+ const payload = Uint8Array.from([0xe9, 0x41]);
261
+ const contentType = this.options.exactRawBodyByteContentType ?? 'text/plain; charset=latin1';
262
+ const response = await fetch(`http://127.0.0.1:${String(port)}/webhooks/bytes`, {
263
+ body: payload,
264
+ headers: {
265
+ 'content-type': contentType
266
+ },
267
+ method: 'POST'
268
+ });
269
+ if (response.status !== 201) {
270
+ throw new Error(`${this.options.name} adapter changed byte-sensitive rawBody response status semantics.`);
271
+ }
272
+ const body = await response.json();
273
+ if (JSON.stringify(body) !== JSON.stringify({
274
+ rawBytes: Array.from(payload)
275
+ })) {
276
+ throw new Error(`${this.options.name} adapter changed exact-byte rawBody semantics.`);
277
+ }
278
+ });
212
279
  }
213
280
  async assertExcludesRawBodyForMultipart() {
214
- let _initProto3, _initClass3;
281
+ let _initProto4, _initClass4;
215
282
  let _UploadController;
216
283
  class UploadController {
217
284
  static {
218
285
  ({
219
- e: [_initProto3],
220
- c: [_UploadController, _initClass3]
286
+ e: [_initProto4],
287
+ c: [_UploadController, _initClass4]
221
288
  } = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
222
289
  }
223
290
  constructor() {
224
- _initProto3(this);
291
+ _initProto4(this);
225
292
  }
226
293
  upload(_input, context) {
227
294
  return {
@@ -231,7 +298,7 @@ export class HttpAdapterPortabilityHarness {
231
298
  };
232
299
  }
233
300
  static {
234
- _initClass3();
301
+ _initClass4();
235
302
  }
236
303
  }
237
304
  class AppModule {}
@@ -245,7 +312,7 @@ export class HttpAdapterPortabilityHarness {
245
312
  rawBody: true
246
313
  });
247
314
  await app.listen();
248
- try {
315
+ await runWithCleanup(app, this.options.name, async () => {
249
316
  const form = new FormData();
250
317
  form.set('name', 'Ada');
251
318
  form.set('payload', new Blob(['hello'], {
@@ -268,22 +335,20 @@ export class HttpAdapterPortabilityHarness {
268
335
  })) {
269
336
  throw new Error(`${this.options.name} adapter changed multipart rawBody semantics.`);
270
337
  }
271
- } finally {
272
- await closeSilently(app);
273
- }
338
+ });
274
339
  }
275
340
  async assertDefaultsMultipartTotalLimitToMaxBodySize() {
276
- let _initProto4, _initClass4;
341
+ let _initProto5, _initClass5;
277
342
  let _UploadController2;
278
343
  class UploadController {
279
344
  static {
280
345
  ({
281
- e: [_initProto4],
282
- c: [_UploadController2, _initClass4]
346
+ e: [_initProto5],
347
+ c: [_UploadController2, _initClass5]
283
348
  } = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
284
349
  }
285
350
  constructor() {
286
- _initProto4(this);
351
+ _initProto5(this);
287
352
  }
288
353
  upload(_input, context) {
289
354
  return {
@@ -292,7 +357,7 @@ export class HttpAdapterPortabilityHarness {
292
357
  };
293
358
  }
294
359
  static {
295
- _initClass4();
360
+ _initClass5();
296
361
  }
297
362
  }
298
363
  class AppModule {}
@@ -309,7 +374,7 @@ export class HttpAdapterPortabilityHarness {
309
374
  port
310
375
  });
311
376
  await app.listen();
312
- try {
377
+ await runWithCleanup(app, this.options.name, async () => {
313
378
  const form = new FormData();
314
379
  form.set('name', 'Ada');
315
380
  form.set('payload', new Blob(['12345678'], {
@@ -326,22 +391,20 @@ export class HttpAdapterPortabilityHarness {
326
391
  if (typeof body !== 'object' || body === null || body.error?.code !== 'PAYLOAD_TOO_LARGE') {
327
392
  throw new Error(`${this.options.name} adapter changed multipart limit error semantics.`);
328
393
  }
329
- } finally {
330
- await closeSilently(app);
331
- }
394
+ });
332
395
  }
333
396
  async assertSupportsSseStreaming() {
334
- let _initProto5, _initClass5;
397
+ let _initProto6, _initClass6;
335
398
  let _EventsController;
336
399
  class EventsController {
337
400
  static {
338
401
  ({
339
- e: [_initProto5],
340
- c: [_EventsController, _initClass5]
402
+ e: [_initProto6],
403
+ c: [_EventsController, _initClass6]
341
404
  } = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
342
405
  }
343
406
  constructor() {
344
- _initProto5(this);
407
+ _initProto6(this);
345
408
  }
346
409
  stream(_input, context) {
347
410
  const stream = new SseResponse(context);
@@ -352,13 +415,13 @@ export class HttpAdapterPortabilityHarness {
352
415
  event: 'ready',
353
416
  id: 'evt-1'
354
417
  });
355
- setTimeout(() => {
418
+ queueMicrotask(() => {
356
419
  stream.close();
357
- }, 10);
420
+ });
358
421
  return stream;
359
422
  }
360
423
  static {
361
- _initClass5();
424
+ _initClass6();
362
425
  }
363
426
  }
364
427
  class AppModule {}
@@ -371,7 +434,7 @@ export class HttpAdapterPortabilityHarness {
371
434
  port
372
435
  });
373
436
  await app.listen();
374
- try {
437
+ await runWithCleanup(app, this.options.name, async () => {
375
438
  const response = await fetch(`http://127.0.0.1:${String(port)}/events`, {
376
439
  headers: {
377
440
  accept: 'text/event-stream'
@@ -388,9 +451,7 @@ export class HttpAdapterPortabilityHarness {
388
451
  if (!body.includes('event: ready') || !body.includes('data: {"ready":true}')) {
389
452
  throw new Error(`${this.options.name} adapter changed SSE body framing.`);
390
453
  }
391
- } finally {
392
- await closeSilently(app);
393
- }
454
+ });
394
455
  }
395
456
 
396
457
  /**
@@ -398,7 +459,7 @@ export class HttpAdapterPortabilityHarness {
398
459
  * closes before a `drain` event is emitted.
399
460
  */
400
461
  async assertSettlesStreamDrainWaitOnClose() {
401
- let _initProto6, _initClass6;
462
+ let _initProto7, _initClass7;
402
463
  const adapterName = this.options.name;
403
464
  let resolveDrainWait;
404
465
  const drainWaitSettled = new Promise(resolve => {
@@ -408,12 +469,12 @@ export class HttpAdapterPortabilityHarness {
408
469
  class EventsController {
409
470
  static {
410
471
  ({
411
- e: [_initProto6],
412
- c: [_EventsController2, _initClass6]
472
+ e: [_initProto7],
473
+ c: [_EventsController2, _initClass7]
413
474
  } = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
414
475
  }
415
476
  constructor() {
416
- _initProto6(this);
477
+ _initProto7(this);
417
478
  }
418
479
  async stream(_input, context) {
419
480
  const stream = new SseResponse(context);
@@ -428,7 +489,7 @@ export class HttpAdapterPortabilityHarness {
428
489
  return stream;
429
490
  }
430
491
  static {
431
- _initClass6();
492
+ _initClass7();
432
493
  }
433
494
  }
434
495
  class AppModule {}
@@ -441,7 +502,7 @@ export class HttpAdapterPortabilityHarness {
441
502
  port
442
503
  });
443
504
  await app.listen();
444
- try {
505
+ await runWithCleanup(app, this.options.name, async () => {
445
506
  const response = await fetch(`http://127.0.0.1:${String(port)}/events`, {
446
507
  headers: {
447
508
  accept: 'text/event-stream'
@@ -452,12 +513,10 @@ export class HttpAdapterPortabilityHarness {
452
513
  }
453
514
  await response.text();
454
515
  await withTimeout(drainWaitSettled, 2_000, `${this.options.name} adapter left response.stream.waitForDrain() pending after close.`);
455
- } finally {
456
- await closeSilently(app);
457
- }
516
+ });
458
517
  }
459
518
  async assertReportsConfiguredHostInStartupLogs() {
460
- let _initProto7, _initClass7;
519
+ let _initProto8, _initClass8;
461
520
  const loggerEvents = [];
462
521
  const logger = {
463
522
  debug() {},
@@ -473,12 +532,12 @@ export class HttpAdapterPortabilityHarness {
473
532
  class HealthController {
474
533
  static {
475
534
  ({
476
- e: [_initProto7],
477
- c: [_HealthController, _initClass7]
535
+ e: [_initProto8],
536
+ c: [_HealthController, _initClass8]
478
537
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
479
538
  }
480
539
  constructor() {
481
- _initProto7(this);
540
+ _initProto8(this);
482
541
  }
483
542
  getHealth() {
484
543
  return {
@@ -486,7 +545,7 @@ export class HttpAdapterPortabilityHarness {
486
545
  };
487
546
  }
488
547
  static {
489
- _initClass7();
548
+ _initClass8();
490
549
  }
491
550
  }
492
551
  class AppModule {}
@@ -500,7 +559,7 @@ export class HttpAdapterPortabilityHarness {
500
559
  logger,
501
560
  port
502
561
  });
503
- try {
562
+ await runWithCleanup(app, this.options.name, async () => {
504
563
  const response = await fetch(`http://127.0.0.1:${String(port)}/health`);
505
564
  if (response.status !== 200) {
506
565
  throw new Error(`${this.options.name} adapter changed host-bound health response semantics.`);
@@ -515,12 +574,10 @@ export class HttpAdapterPortabilityHarness {
515
574
  if (!loggerEvents.includes(expectedLog)) {
516
575
  throw new Error(`${this.options.name} adapter changed startup host logging.`);
517
576
  }
518
- } finally {
519
- await closeSilently(app);
520
- }
577
+ });
521
578
  }
522
579
  async assertReportsHttpsStartupUrl(https) {
523
- let _initProto8, _initClass8;
580
+ let _initProto9, _initClass9;
524
581
  const loggerEvents = [];
525
582
  const logger = {
526
583
  debug() {},
@@ -536,12 +593,12 @@ export class HttpAdapterPortabilityHarness {
536
593
  class HealthController {
537
594
  static {
538
595
  ({
539
- e: [_initProto8],
540
- c: [_HealthController2, _initClass8]
596
+ e: [_initProto9],
597
+ c: [_HealthController2, _initClass9]
541
598
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
542
599
  }
543
600
  constructor() {
544
- _initProto8(this);
601
+ _initProto9(this);
545
602
  }
546
603
  getHealth() {
547
604
  return {
@@ -549,7 +606,7 @@ export class HttpAdapterPortabilityHarness {
549
606
  };
550
607
  }
551
608
  static {
552
- _initClass8();
609
+ _initClass9();
553
610
  }
554
611
  }
555
612
  class AppModule {}
@@ -564,7 +621,7 @@ export class HttpAdapterPortabilityHarness {
564
621
  logger,
565
622
  port
566
623
  });
567
- try {
624
+ await runWithCleanup(app, this.options.name, async () => {
568
625
  const response = await requestHttps(`https://127.0.0.1:${String(port)}/health`);
569
626
  if (response.statusCode !== 200) {
570
627
  throw new Error(`${this.options.name} adapter changed HTTPS response status semantics.`);
@@ -578,12 +635,10 @@ export class HttpAdapterPortabilityHarness {
578
635
  if (!loggerEvents.includes(expectedLog)) {
579
636
  throw new Error(`${this.options.name} adapter changed HTTPS startup logging.`);
580
637
  }
581
- } finally {
582
- await closeSilently(app);
583
- }
638
+ });
584
639
  }
585
640
  async assertRemovesShutdownSignalListenersAfterClose() {
586
- let _initProto9, _initClass9;
641
+ let _initProto0, _initClass0;
587
642
  const logger = {
588
643
  debug() {},
589
644
  error() {},
@@ -594,12 +649,12 @@ export class HttpAdapterPortabilityHarness {
594
649
  class HealthController {
595
650
  static {
596
651
  ({
597
- e: [_initProto9],
598
- c: [_HealthController3, _initClass9]
652
+ e: [_initProto0],
653
+ c: [_HealthController3, _initClass0]
599
654
  } = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
600
655
  }
601
656
  constructor() {
602
- _initProto9(this);
657
+ _initProto0(this);
603
658
  }
604
659
  getHealth() {
605
660
  return {
@@ -607,7 +662,7 @@ export class HttpAdapterPortabilityHarness {
607
662
  };
608
663
  }
609
664
  static {
610
- _initClass9();
665
+ _initClass0();
611
666
  }
612
667
  }
613
668
  class AppModule {}
@@ -615,7 +670,7 @@ export class HttpAdapterPortabilityHarness {
615
670
  controllers: [_HealthController3]
616
671
  });
617
672
  const signal = 'SIGTERM';
618
- const listenersBefore = process.listeners(signal).length;
673
+ const listenersBefore = new Set(process.listeners(signal));
619
674
  const port = await findAvailablePort();
620
675
  const app = await this.options.run(AppModule, {
621
676
  cors: false,
@@ -623,14 +678,15 @@ export class HttpAdapterPortabilityHarness {
623
678
  port,
624
679
  shutdownSignals: [signal]
625
680
  });
626
- try {
627
- if (process.listeners(signal).length !== listenersBefore + 1) {
681
+ const registeredListeners = process.listeners(signal).filter(listener => !listenersBefore.has(listener));
682
+ await runWithCleanup(app, this.options.name, async () => {
683
+ if (registeredListeners.length === 0) {
628
684
  throw new Error(`${this.options.name} adapter did not register the expected shutdown listener.`);
629
685
  }
630
- } finally {
631
- await closeSilently(app);
632
- }
633
- if (process.listeners(signal).length !== listenersBefore) {
686
+ });
687
+ const remainingListeners = process.listeners(signal);
688
+ const leakedListeners = registeredListeners.filter(listener => remainingListeners.includes(listener));
689
+ if (leakedListeners.length > 0) {
634
690
  throw new Error(`${this.options.name} adapter leaked shutdown signal listeners after close().`);
635
691
  }
636
692
  }
@@ -648,14 +704,14 @@ export class HttpAdapterPortabilityHarness {
648
704
  export function createHttpAdapterPortabilityHarness(options) {
649
705
  return new HttpAdapterPortabilityHarness(options);
650
706
  }
651
- function withTimeout(promise, timeoutMs, message) {
707
+ async function withTimeout(promise, timeoutMs, message) {
652
708
  let timeout;
653
709
  const timeoutPromise = new Promise((_resolve, reject) => {
654
710
  timeout = setTimeout(() => {
655
711
  reject(new Error(message));
656
712
  }, timeoutMs);
657
713
  });
658
- return Promise.race([promise, timeoutPromise]).finally(() => {
714
+ return await Promise.race([promise, timeoutPromise]).finally(() => {
659
715
  if (timeout !== undefined) {
660
716
  clearTimeout(timeout);
661
717
  }
@@ -9,18 +9,31 @@ type WebRuntimePortabilityAppLike = {
9
9
  close(): Promise<void>;
10
10
  dispatch(request: Request): Promise<Response>;
11
11
  };
12
+ /**
13
+ * Describes the web runtime http adapter portability harness options contract.
14
+ */
12
15
  export interface WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike> {
13
16
  bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
14
17
  name: string;
15
18
  }
19
+ /**
20
+ * Represents the web runtime http adapter portability harness.
21
+ */
16
22
  export declare class WebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike> {
17
23
  private readonly options;
18
24
  constructor(options: WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TApp>);
25
+ assertPreservesQueryArraysAndDecoding(): Promise<void>;
19
26
  assertPreservesMalformedCookieValues(): Promise<void>;
20
27
  assertPreservesRawBodyForJsonAndText(): Promise<void>;
21
28
  assertExcludesRawBodyForMultipart(): Promise<void>;
22
29
  assertSupportsSseStreaming(): Promise<void>;
23
30
  }
31
+ /**
32
+ * Create web runtime http adapter portability harness.
33
+ *
34
+ * @param options The options.
35
+ * @returns The create web runtime http adapter portability harness result.
36
+ */
24
37
  export declare function createWebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TApp extends WebRuntimePortabilityAppLike = WebRuntimePortabilityAppLike>(options: WebRuntimeHttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TApp>): WebRuntimeHttpAdapterPortabilityHarness<TBootstrapOptions, TApp>;
25
38
  export {};
26
39
  //# sourceMappingURL=web-runtime-adapter-portability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"web-runtime-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/web-runtime-adapter-portability.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,4BAA4B,GAAG;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,WAAW,8CAA8C,CAC7D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAExE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,qBAAa,uCAAuC,CAClD,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAE5D,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC;IAEvG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA4CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DrD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA2ClD,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CA4ClD;AAED,wBAAgB,6CAA6C,CAC3D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B,EAExE,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAC/E,uCAAuC,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAElE"}
1
+ {"version":3,"file":"web-runtime-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/web-runtime-adapter-portability.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnF,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,4BAA4B,GAAG;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC/C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,8CAA8C,CAC7D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAExE,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AA0CD;;GAEG;AACH,qBAAa,uCAAuC,CAClD,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B;IAE5D,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC;IAEvG,qCAAqC,IAAI,OAAO,CAAC,IAAI,CAAC;IAyCtD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IAgDrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IAsErD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDlD,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;CA8ClD;AAED;;;;;GAKG;AACH,wBAAgB,6CAA6C,CAC3D,iBAAiB,SAAS,MAAM,EAChC,IAAI,SAAS,4BAA4B,GAAG,4BAA4B,EAExE,OAAO,EAAE,8CAA8C,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAC/E,uCAAuC,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAElE"}