@fluojs/testing 1.0.0-beta.1 → 1.0.0-beta.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/dist/mock.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { vi } from 'vitest';
2
+
3
+ /**
4
+ * Defines the mocked methods type.
5
+ */
6
+
2
7
  /**
3
8
  * Creates a proxy mock object with optional strict missing-property checks.
9
+ *
10
+ * @param partial The partial.
11
+ * @param options The options.
12
+ * @returns The create mock result.
4
13
  */
5
14
  export function createMock(partial = {}, options = {}) {
6
15
  const autoMocks = new Map();
@@ -24,6 +33,9 @@ export function createMock(partial = {}, options = {}) {
24
33
 
25
34
  /**
26
35
  * Casts a function to a strongly typed Vitest mock.
36
+ *
37
+ * @param fn The fn.
38
+ * @returns The as mock result.
27
39
  */
28
40
  export function asMock(fn) {
29
41
  return vi.mocked(fn);
@@ -31,6 +43,9 @@ export function asMock(fn) {
31
43
 
32
44
  /**
33
45
  * Creates a deep mock by replacing prototype methods with `vi.fn()` spies.
46
+ *
47
+ * @param type The type.
48
+ * @returns The create deep mock result.
34
49
  */
35
50
  export function createDeepMock(type) {
36
51
  const spies = {};
@@ -51,6 +66,10 @@ export function createDeepMock(type) {
51
66
 
52
67
  /**
53
68
  * Creates a `useValue` provider for overriding a token in tests.
69
+ *
70
+ * @param token The token.
71
+ * @param partial The partial.
72
+ * @returns The mock token result.
54
73
  */
55
74
  export function mockToken(token, partial = {}) {
56
75
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAqC,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMrF,OAAO,KAAK,EAA2B,oBAAoB,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAExH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAQzE;AAiaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAEvF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;;CAEhB,CAAC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAqC,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMrF,OAAO,KAAK,EAA2B,oBAAoB,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAExH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAQzE;AAkkBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAEvF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;;CAEhB,CAAC"}
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getModuleMetadata } from '@fluojs/core/internal';
1
+ import { getModuleMetadata } from '@fluojs/core';
2
2
  import { isForwardRef, isOptionalToken } from '@fluojs/di';
3
3
  import { bootstrapModule, defineModule } from '@fluojs/runtime';
4
4
  import { createDispatcher, createHandlerMapping } from '@fluojs/http';
@@ -97,7 +97,7 @@ function isContainerIntrospection(value) {
97
97
  const candidate = value;
98
98
  const parentValid = candidate.parent === undefined || isContainerIntrospection(candidate.parent);
99
99
  const requestScopeValid = candidate.requestScopeEnabled === undefined || typeof candidate.requestScopeEnabled === 'boolean';
100
- return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && parentValid && requestScopeValid;
100
+ return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && candidate.singletonCache instanceof Map && parentValid && requestScopeValid;
101
101
  }
102
102
  function toContainerIntrospection(container) {
103
103
  if (!isContainerIntrospection(container)) {
@@ -108,6 +108,9 @@ function toContainerIntrospection(container) {
108
108
  function isPromiseLike(value) {
109
109
  return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
110
110
  }
111
+ function rootContainerIntrospection(target) {
112
+ return target.parent ? rootContainerIntrospection(target.parent) : target;
113
+ }
111
114
  function collectMultiProviders(target, token) {
112
115
  const fromParent = target.parent ? collectMultiProviders(target.parent, token) : [];
113
116
  const local = target.multiRegistrations.get(token) ?? [];
@@ -123,6 +126,70 @@ function lookupProvider(target, token) {
123
126
  function hasToken(state, token) {
124
127
  return lookupProvider(state.introspection, token) !== undefined || collectMultiProviders(state.introspection, token).length > 0;
125
128
  }
129
+ function dependencyToken(entry) {
130
+ if (isOptionalToken(entry)) {
131
+ return entry.token;
132
+ }
133
+ if (isForwardRef(entry)) {
134
+ return entry.forwardRef();
135
+ }
136
+ return entry;
137
+ }
138
+ function trackFactoryResolutionKind(provider, factoryResolutionKinds) {
139
+ if (provider.type !== 'factory' || !provider.useFactory) {
140
+ return;
141
+ }
142
+ const originalFactory = provider.useFactory;
143
+ provider.useFactory = (...deps) => {
144
+ const value = originalFactory(...deps);
145
+ factoryResolutionKinds.set(provider, isPromiseLike(value) ? 'async' : 'sync');
146
+ return value;
147
+ };
148
+ }
149
+ function installFactoryResolutionTracking(target, factoryResolutionKinds) {
150
+ if (target.parent) {
151
+ installFactoryResolutionTracking(target.parent, factoryResolutionKinds);
152
+ }
153
+ for (const provider of target.registrations.values()) {
154
+ trackFactoryResolutionKind(provider, factoryResolutionKinds);
155
+ }
156
+ for (const providers of target.multiRegistrations.values()) {
157
+ for (const provider of providers) {
158
+ trackFactoryResolutionKind(provider, factoryResolutionKinds);
159
+ }
160
+ }
161
+ }
162
+ function providerGraphIsSyncResolvable(state, token, visited = new Set()) {
163
+ if (visited.has(token)) {
164
+ return true;
165
+ }
166
+ visited.add(token);
167
+ try {
168
+ const provider = lookupProvider(state.introspection, token);
169
+ const multiProviders = collectMultiProviders(state.introspection, token);
170
+ const providers = provider ? [provider, ...multiProviders] : multiProviders;
171
+ return providers.every(candidate => {
172
+ if (candidate.type === 'factory') {
173
+ return state.factoryResolutionKinds.get(candidate) === 'sync';
174
+ }
175
+ if (candidate.type === 'existing') {
176
+ return candidate.useExisting !== undefined && providerGraphIsSyncResolvable(state, candidate.useExisting, visited);
177
+ }
178
+ return candidate.inject.every(entry => {
179
+ if (isOptionalToken(entry) && !hasToken(state, entry.token)) {
180
+ return true;
181
+ }
182
+ return providerGraphIsSyncResolvable(state, dependencyToken(entry), visited);
183
+ });
184
+ });
185
+ } finally {
186
+ visited.delete(token);
187
+ }
188
+ }
189
+ function canPromoteCachedSingleton(state, token) {
190
+ const provider = lookupProvider(state.introspection, token);
191
+ return provider !== undefined && provider.scope !== 'request' && providerGraphIsSyncResolvable(state, token);
192
+ }
126
193
  function resolveSyncDependency(entry, state) {
127
194
  if (isOptionalToken(entry)) {
128
195
  if (!hasToken(state, entry.token)) {
@@ -181,11 +248,15 @@ function resolveSyncProvider(provider, state) {
181
248
  if (provider.scope === 'transient') {
182
249
  return instantiateSyncProvider(provider, state);
183
250
  }
251
+ if (state.syncSingletonValues.has(provider.provide)) {
252
+ return state.syncSingletonValues.get(provider.provide);
253
+ }
184
254
  if (state.singletonCache.has(provider.provide)) {
185
- return state.singletonCache.get(provider.provide);
255
+ throw new Error(`Token ${String(provider.provide)} was already resolved asynchronously. Use resolve() instead of get() for this provider.`);
186
256
  }
187
257
  const instance = instantiateSyncProvider(provider, state);
188
- state.singletonCache.set(provider.provide, instance);
258
+ state.syncSingletonValues.set(provider.provide, instance);
259
+ state.singletonCache.set(provider.provide, Promise.resolve(instance));
189
260
  return instance;
190
261
  }
191
262
  function resolveSyncToken(token, state) {
@@ -208,12 +279,27 @@ function resolveSyncToken(token, state) {
208
279
  }
209
280
  }
210
281
  function createSyncResolver(container) {
282
+ const introspection = toContainerIntrospection(container);
283
+ const factoryResolutionKinds = new WeakMap();
284
+ installFactoryResolutionTracking(introspection, factoryResolutionKinds);
211
285
  const state = {
212
- introspection: toContainerIntrospection(container),
286
+ factoryResolutionKinds,
287
+ introspection,
213
288
  resolutionChain: new Set(),
214
- singletonCache: new Map()
289
+ singletonCache: rootContainerIntrospection(introspection).singletonCache,
290
+ syncSingletonValues: new Map()
291
+ };
292
+ return {
293
+ get: token => resolveSyncToken(token, state),
294
+ syncFromContainer: async () => {
295
+ for (const [token, promise] of state.singletonCache) {
296
+ if (!canPromoteCachedSingleton(state, token)) {
297
+ continue;
298
+ }
299
+ state.syncSingletonValues.set(token, await promise);
300
+ }
301
+ }
215
302
  };
216
- return token => resolveSyncToken(token, state);
217
303
  }
218
304
  class DefaultOverrideProviderBuilder {
219
305
  constructor(builder, token) {
@@ -250,6 +336,7 @@ class DefaultOverrideProviderBuilder {
250
336
  class DefaultTestingModuleBuilder {
251
337
  overrides = [];
252
338
  moduleReplacements = new Map();
339
+ originalModuleDefinitions = new Map();
253
340
  constructor(options) {
254
341
  this.options = options;
255
342
  }
@@ -308,23 +395,34 @@ class DefaultTestingModuleBuilder {
308
395
  return this.createTestingModuleRef(bootstrapped);
309
396
  }
310
397
  bootstrapTestingModule() {
311
- const rootModule = this._applyModuleReplacements(this.options.rootModule);
312
- const bootstrapped = bootstrapModule(rootModule, {
313
- providers: this.options.providers
314
- });
398
+ const bootstrapped = this.bootstrapWithPatchedModuleImports();
315
399
  if (this.overrides.length > 0) {
316
400
  bootstrapped.container.override(...this.overrides);
317
401
  }
318
402
  return bootstrapped;
319
403
  }
404
+ bootstrapWithPatchedModuleImports() {
405
+ try {
406
+ const rootModule = this._applyModuleReplacements(this.options.rootModule);
407
+ return bootstrapModule(rootModule, {
408
+ providers: this.options.providers
409
+ });
410
+ } finally {
411
+ this.restorePatchedModuleImports();
412
+ }
413
+ }
320
414
  createTestingModuleRef(bootstrapped) {
321
415
  const dispatcher = createTestingDispatcher(bootstrapped);
322
- const getSync = createSyncResolver(bootstrapped.container);
416
+ const syncResolver = createSyncResolver(bootstrapped.container);
323
417
  return {
324
418
  ...bootstrapped,
325
419
  has: token => bootstrapped.container.has(token),
326
- get: token => getSync(token),
327
- resolve: token => bootstrapped.container.resolve(token),
420
+ get: token => syncResolver.get(token),
421
+ resolve: async token => {
422
+ const value = await bootstrapped.container.resolve(token);
423
+ await syncResolver.syncFromContainer();
424
+ return value;
425
+ },
328
426
  resolveAll: async tokens => {
329
427
  const results = [];
330
428
  const errors = [];
@@ -345,9 +443,14 @@ class DefaultTestingModuleBuilder {
345
443
  }) => ` - ${String(token)}: ${error instanceof Error ? error.message : String(error)}`).join('\n');
346
444
  throw new Error(`Failed to resolve ${errors.length} of ${tokens.length} tokens:\n${summary}`);
347
445
  }
446
+ await syncResolver.syncFromContainer();
348
447
  return results;
349
448
  },
350
- dispatch: request => makeRequest(dispatcher, request)
449
+ dispatch: async request => {
450
+ const response = await makeRequest(dispatcher, request);
451
+ await syncResolver.syncFromContainer();
452
+ return response;
453
+ }
351
454
  };
352
455
  }
353
456
  _applyModuleReplacements(module) {
@@ -367,17 +470,27 @@ class DefaultTestingModuleBuilder {
367
470
  if (!hasChange) {
368
471
  return module;
369
472
  }
370
- class PatchedModule {}
371
- const patchedModule = PatchedModule;
372
- defineModule(patchedModule, {
373
- ...metadata,
374
- imports: rewrittenImports
375
- });
376
- return patchedModule;
473
+ this.patchModuleImports(module, metadata, rewrittenImports);
474
+ return module;
377
475
  }
378
476
  rewriteModuleImports(imports) {
379
477
  return imports.map(moduleImport => this._applyModuleReplacements(moduleImport));
380
478
  }
479
+ patchModuleImports(module, metadata, imports) {
480
+ if (!this.originalModuleDefinitions.has(module)) {
481
+ this.originalModuleDefinitions.set(module, metadata);
482
+ }
483
+ defineModule(module, {
484
+ ...metadata,
485
+ imports
486
+ });
487
+ }
488
+ restorePatchedModuleImports() {
489
+ for (const [module, metadata] of this.originalModuleDefinitions) {
490
+ defineModule(module, metadata);
491
+ }
492
+ this.originalModuleDefinitions.clear();
493
+ }
381
494
  }
382
495
 
383
496
  /**
@@ -25,6 +25,14 @@ export interface HttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends
25
25
  * @returns A promise that resolves to the application instance.
26
26
  */
27
27
  bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
28
+ /**
29
+ * Optional adapter-specific content type used by the exact-byte raw-body portability assertion.
30
+ */
31
+ exactRawBodyByteContentType?: string;
32
+ /**
33
+ * Optional adapter-specific preparation used before the exact-byte raw-body portability assertion.
34
+ */
35
+ prepareExactRawBodyByteTest?: (app: TApp) => void | Promise<void>;
28
36
  /**
29
37
  * The name of the adapter being tested.
30
38
  */
@@ -60,8 +68,15 @@ export declare class HttpAdapterPortabilityHarness<TBootstrapOptions extends obj
60
68
  */
61
69
  assertPreservesMalformedCookieValues(): Promise<void>;
62
70
  assertPreservesRawBodyForJsonAndText(): Promise<void>;
71
+ assertPreservesExactRawBodyBytesForByteSensitivePayloads(): Promise<void>;
63
72
  assertExcludesRawBodyForMultipart(): Promise<void>;
73
+ assertDefaultsMultipartTotalLimitToMaxBodySize(): Promise<void>;
64
74
  assertSupportsSseStreaming(): Promise<void>;
75
+ /**
76
+ * Asserts that adapter stream backpressure waiters settle when the response
77
+ * closes before a `drain` event is emitted.
78
+ */
79
+ assertSettlesStreamDrainWaitOnClose(): Promise<void>;
65
80
  assertReportsConfiguredHostInStartupLogs(): Promise<void>;
66
81
  assertReportsHttpsStartupUrl(https: {
67
82
  cert: string;
@@ -1 +1 @@
1
- {"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,OAAO,GAAG;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oCAAoC,CACnD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAE9B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAuDD;;;;;;;GAOG;AACH,qBAAa,6BAA6B,CACxC,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC;IAEhH;;;OAGG;IACG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA+CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8DrD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8ClD,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD3C,wCAAwC,IAAI,OAAO,CAAC,IAAI,CAAC;IAuDzD,4BAA4B,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDjF,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;CA2CtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO,EAE9B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,GAClF,6BAA6B,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,CAErE"}
1
+ {"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"AAUA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,OAAO,GAAG;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,oCAAoC,CACnD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAE9B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAuDD;;;;;;;GAOG;AACH,qBAAa,6BAA6B,CACxC,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC;IAEhH;;;OAGG;IACG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA+CrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8DrD,wDAAwD,IAAI,OAAO,CAAC,IAAI,CAAC;IA6CzE,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IA8ClD,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;IAwD/D,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDjD;;;OAGG;IACG,mCAAmC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDpD,wCAAwC,IAAI,OAAO,CAAC,IAAI,CAAC;IAuDzD,4BAA4B,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDjF,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;CA8CtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO,EAE9B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,GAClF,6BAA6B,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,CAErE"}