@fluojs/testing 1.0.0-beta.1

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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +117 -0
  3. package/README.md +115 -0
  4. package/dist/app.d.ts +16 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +54 -0
  7. package/dist/babel-decorators-plugin.d.ts +36 -0
  8. package/dist/babel-decorators-plugin.d.ts.map +1 -0
  9. package/dist/babel-decorators-plugin.js +67 -0
  10. package/dist/conformance/fetch-style-websocket-conformance.d.ts +14 -0
  11. package/dist/conformance/fetch-style-websocket-conformance.d.ts.map +1 -0
  12. package/dist/conformance/fetch-style-websocket-conformance.js +34 -0
  13. package/dist/conformance/platform-conformance.d.ts +42 -0
  14. package/dist/conformance/platform-conformance.d.ts.map +1 -0
  15. package/dist/conformance/platform-conformance.js +193 -0
  16. package/dist/http.d.ts +73 -0
  17. package/dist/http.d.ts.map +1 -0
  18. package/dist/http.js +239 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +3 -0
  22. package/dist/mock.d.ts +26 -0
  23. package/dist/mock.d.ts.map +1 -0
  24. package/dist/mock.js +60 -0
  25. package/dist/module.d.ts +45 -0
  26. package/dist/module.d.ts.map +1 -0
  27. package/dist/module.js +405 -0
  28. package/dist/portability/http-adapter-portability.d.ts +83 -0
  29. package/dist/portability/http-adapter-portability.d.ts.map +1 -0
  30. package/dist/portability/http-adapter-portability.js +528 -0
  31. package/dist/portability/web-runtime-adapter-portability.d.ts +26 -0
  32. package/dist/portability/web-runtime-adapter-portability.d.ts.map +1 -0
  33. package/dist/portability/web-runtime-adapter-portability.js +260 -0
  34. package/dist/types.d.ts +76 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +1 -0
  37. package/dist/vitest.d.ts +9 -0
  38. package/dist/vitest.d.ts.map +1 -0
  39. package/dist/vitest.js +11 -0
  40. package/package.json +102 -0
package/dist/module.js ADDED
@@ -0,0 +1,405 @@
1
+ import { getModuleMetadata } from '@fluojs/core/internal';
2
+ import { isForwardRef, isOptionalToken } from '@fluojs/di';
3
+ import { bootstrapModule, defineModule } from '@fluojs/runtime';
4
+ import { createDispatcher, createHandlerMapping } from '@fluojs/http';
5
+ import { createTestRequestContextMiddleware, makeRequest } from './http.js';
6
+ /**
7
+ * Returns providers declared on a module metadata definition.
8
+ *
9
+ * @param moduleType Module type whose `providers` metadata should be inspected.
10
+ * @returns The declared provider list, or an empty array when the module has none.
11
+ */
12
+ export function extractModuleProviders(moduleType) {
13
+ const metadata = getModuleMetadata(moduleType);
14
+ if (!metadata || !Array.isArray(metadata.providers)) {
15
+ return [];
16
+ }
17
+ return metadata.providers;
18
+ }
19
+
20
+ /**
21
+ * Returns controllers declared on a module metadata definition.
22
+ *
23
+ * @param moduleType Module type whose `controllers` metadata should be inspected.
24
+ * @returns The declared controller list, or an empty array when the module has none.
25
+ */
26
+ export function extractModuleControllers(moduleType) {
27
+ const metadata = getModuleMetadata(moduleType);
28
+ if (!metadata || !Array.isArray(metadata.controllers)) {
29
+ return [];
30
+ }
31
+ return metadata.controllers;
32
+ }
33
+
34
+ /**
35
+ * Returns imported modules declared on a module metadata definition.
36
+ *
37
+ * @param moduleType Module type whose `imports` metadata should be inspected.
38
+ * @returns The declared import list, or an empty array when the module has none.
39
+ */
40
+ export function extractModuleImports(moduleType) {
41
+ const metadata = getModuleMetadata(moduleType);
42
+ if (!metadata || !Array.isArray(metadata.imports)) {
43
+ return [];
44
+ }
45
+ return metadata.imports;
46
+ }
47
+ function createHandlerSources(bootstrappedModules) {
48
+ return bootstrappedModules.flatMap(compiledModule => (compiledModule.definition.controllers ?? []).map(controllerToken => ({
49
+ controllerToken,
50
+ moduleMiddleware: compiledModule.definition.middleware ?? [],
51
+ moduleType: compiledModule.type
52
+ })));
53
+ }
54
+ function createTestingDispatcher(bootstrapped) {
55
+ const handlerMapping = createHandlerMapping(createHandlerSources(bootstrapped.modules));
56
+ return createDispatcher({
57
+ appMiddleware: [createTestRequestContextMiddleware()],
58
+ handlerMapping,
59
+ rootContainer: bootstrapped.container
60
+ });
61
+ }
62
+ function isProviderDescriptor(value) {
63
+ return typeof value === 'object' && value !== null && 'provide' in value && ('useClass' in value || 'useFactory' in value || 'useValue' in value || 'useExisting' in value);
64
+ }
65
+ function isClassConstructor(value) {
66
+ if (typeof value !== 'function') {
67
+ return false;
68
+ }
69
+ const source = Function.prototype.toString.call(value);
70
+ return source.startsWith('class ');
71
+ }
72
+ function normalizeOverride(token, value) {
73
+ if (isProviderDescriptor(value)) {
74
+ if (value.provide !== token) {
75
+ throw new Error(`overrideProvider token mismatch: expected ${String(token)} but received provider for ${String(value.provide)}.`);
76
+ }
77
+ return {
78
+ ...value,
79
+ provide: token
80
+ };
81
+ }
82
+ if (isClassConstructor(value)) {
83
+ return {
84
+ provide: token,
85
+ useClass: value
86
+ };
87
+ }
88
+ return {
89
+ provide: token,
90
+ useValue: value
91
+ };
92
+ }
93
+ function isContainerIntrospection(value) {
94
+ if (typeof value !== 'object' || value === null) {
95
+ return false;
96
+ }
97
+ const candidate = value;
98
+ const parentValid = candidate.parent === undefined || isContainerIntrospection(candidate.parent);
99
+ const requestScopeValid = candidate.requestScopeEnabled === undefined || typeof candidate.requestScopeEnabled === 'boolean';
100
+ return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && parentValid && requestScopeValid;
101
+ }
102
+ function toContainerIntrospection(container) {
103
+ if (!isContainerIntrospection(container)) {
104
+ throw new Error('Testing container introspection is unavailable for the current container implementation.');
105
+ }
106
+ return container;
107
+ }
108
+ function isPromiseLike(value) {
109
+ return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
110
+ }
111
+ function collectMultiProviders(target, token) {
112
+ const fromParent = target.parent ? collectMultiProviders(target.parent, token) : [];
113
+ const local = target.multiRegistrations.get(token) ?? [];
114
+ return [...fromParent, ...local];
115
+ }
116
+ function lookupProvider(target, token) {
117
+ const local = target.registrations.get(token);
118
+ if (local) {
119
+ return local;
120
+ }
121
+ return target.parent ? lookupProvider(target.parent, token) : undefined;
122
+ }
123
+ function hasToken(state, token) {
124
+ return lookupProvider(state.introspection, token) !== undefined || collectMultiProviders(state.introspection, token).length > 0;
125
+ }
126
+ function resolveSyncDependency(entry, state) {
127
+ if (isOptionalToken(entry)) {
128
+ if (!hasToken(state, entry.token)) {
129
+ return undefined;
130
+ }
131
+ return resolveSyncToken(entry.token, state);
132
+ }
133
+ if (isForwardRef(entry)) {
134
+ return resolveSyncToken(entry.forwardRef(), state);
135
+ }
136
+ return resolveSyncToken(entry, state);
137
+ }
138
+ function instantiateSyncProvider(provider, state) {
139
+ switch (provider.type) {
140
+ case 'value':
141
+ {
142
+ return provider.useValue;
143
+ }
144
+ case 'existing':
145
+ {
146
+ if (!provider.useExisting) {
147
+ throw new Error('Existing provider is missing useExisting token.');
148
+ }
149
+ return resolveSyncToken(provider.useExisting, state);
150
+ }
151
+ case 'factory':
152
+ {
153
+ if (!provider.useFactory) {
154
+ throw new Error('Factory provider is missing useFactory.');
155
+ }
156
+ const deps = provider.inject.map(entry => resolveSyncDependency(entry, state));
157
+ const value = provider.useFactory(...deps);
158
+ if (isPromiseLike(value)) {
159
+ throw new Error(`Token ${String(provider.provide)} requires async resolution. Use resolve() instead of get() for async providers.`);
160
+ }
161
+ return value;
162
+ }
163
+ case 'class':
164
+ {
165
+ if (!provider.useClass) {
166
+ throw new Error('Class provider is missing useClass.');
167
+ }
168
+ const deps = provider.inject.map(entry => resolveSyncDependency(entry, state));
169
+ return new provider.useClass(...deps);
170
+ }
171
+ default:
172
+ {
173
+ throw new Error('Unknown provider type.');
174
+ }
175
+ }
176
+ }
177
+ function resolveSyncProvider(provider, state) {
178
+ if (provider.scope === 'request' && !state.introspection.requestScopeEnabled) {
179
+ throw new Error(`Request-scoped provider ${String(provider.provide)} cannot be resolved outside request scope.`);
180
+ }
181
+ if (provider.scope === 'transient') {
182
+ return instantiateSyncProvider(provider, state);
183
+ }
184
+ if (state.singletonCache.has(provider.provide)) {
185
+ return state.singletonCache.get(provider.provide);
186
+ }
187
+ const instance = instantiateSyncProvider(provider, state);
188
+ state.singletonCache.set(provider.provide, instance);
189
+ return instance;
190
+ }
191
+ function resolveSyncToken(token, state) {
192
+ if (state.resolutionChain.has(token)) {
193
+ throw new Error(`Circular dependency detected while resolving token ${String(token)} via get().`);
194
+ }
195
+ state.resolutionChain.add(token);
196
+ try {
197
+ const multiProviders = collectMultiProviders(state.introspection, token);
198
+ if (multiProviders.length > 0) {
199
+ return multiProviders.map(provider => instantiateSyncProvider(provider, state));
200
+ }
201
+ const provider = lookupProvider(state.introspection, token);
202
+ if (!provider) {
203
+ throw new Error(`No provider registered for token ${String(token)}.`);
204
+ }
205
+ return resolveSyncProvider(provider, state);
206
+ } finally {
207
+ state.resolutionChain.delete(token);
208
+ }
209
+ }
210
+ function createSyncResolver(container) {
211
+ const state = {
212
+ introspection: toContainerIntrospection(container),
213
+ resolutionChain: new Set(),
214
+ singletonCache: new Map()
215
+ };
216
+ return token => resolveSyncToken(token, state);
217
+ }
218
+ class DefaultOverrideProviderBuilder {
219
+ constructor(builder, token) {
220
+ this.builder = builder;
221
+ this.token = token;
222
+ }
223
+ useValue(value) {
224
+ this.builder.addOverride(normalizeOverride(this.token, value));
225
+ return this.builder;
226
+ }
227
+ useClass(cls) {
228
+ this.builder.addOverride({
229
+ provide: this.token,
230
+ useClass: cls
231
+ });
232
+ return this.builder;
233
+ }
234
+ useFactory(factory, inject) {
235
+ this.builder.addOverride({
236
+ provide: this.token,
237
+ useFactory: factory,
238
+ inject
239
+ });
240
+ return this.builder;
241
+ }
242
+ useExisting(token) {
243
+ this.builder.addOverride({
244
+ provide: this.token,
245
+ useExisting: token
246
+ });
247
+ return this.builder;
248
+ }
249
+ }
250
+ class DefaultTestingModuleBuilder {
251
+ overrides = [];
252
+ moduleReplacements = new Map();
253
+ constructor(options) {
254
+ this.options = options;
255
+ }
256
+ addOverride(provider) {
257
+ this.overrides.push(provider);
258
+ }
259
+ overrideProvider(token, ...rest) {
260
+ if (rest.length < 1) {
261
+ return new DefaultOverrideProviderBuilder(this, token);
262
+ }
263
+ const [value] = rest;
264
+ this.overrides.push(normalizeOverride(token, value));
265
+ return this;
266
+ }
267
+ overrideProviders(overrides) {
268
+ for (const [token, value] of overrides) {
269
+ this.overrideProvider(token, value);
270
+ }
271
+ return this;
272
+ }
273
+ overrideGuard(guard, fake = {}) {
274
+ const passthrough = {
275
+ canActivate: () => true,
276
+ ...fake
277
+ };
278
+ this.overrides.push({
279
+ provide: guard,
280
+ useValue: passthrough
281
+ });
282
+ return this;
283
+ }
284
+ overrideInterceptor(interceptor, fake = {}) {
285
+ const passthrough = {
286
+ intercept: (_ctx, next) => next.handle(),
287
+ ...fake
288
+ };
289
+ this.overrides.push({
290
+ provide: interceptor,
291
+ useValue: passthrough
292
+ });
293
+ return this;
294
+ }
295
+ overrideFilter(filter, fake = {}) {
296
+ this.overrides.push({
297
+ provide: filter,
298
+ useValue: fake
299
+ });
300
+ return this;
301
+ }
302
+ overrideModule(module, replacement) {
303
+ this.moduleReplacements.set(module, replacement);
304
+ return this;
305
+ }
306
+ async compile() {
307
+ const bootstrapped = this.bootstrapTestingModule();
308
+ return this.createTestingModuleRef(bootstrapped);
309
+ }
310
+ bootstrapTestingModule() {
311
+ const rootModule = this._applyModuleReplacements(this.options.rootModule);
312
+ const bootstrapped = bootstrapModule(rootModule, {
313
+ providers: this.options.providers
314
+ });
315
+ if (this.overrides.length > 0) {
316
+ bootstrapped.container.override(...this.overrides);
317
+ }
318
+ return bootstrapped;
319
+ }
320
+ createTestingModuleRef(bootstrapped) {
321
+ const dispatcher = createTestingDispatcher(bootstrapped);
322
+ const getSync = createSyncResolver(bootstrapped.container);
323
+ return {
324
+ ...bootstrapped,
325
+ has: token => bootstrapped.container.has(token),
326
+ get: token => getSync(token),
327
+ resolve: token => bootstrapped.container.resolve(token),
328
+ resolveAll: async tokens => {
329
+ const results = [];
330
+ const errors = [];
331
+ for (const token of tokens) {
332
+ try {
333
+ results.push(await bootstrapped.container.resolve(token));
334
+ } catch (error) {
335
+ errors.push({
336
+ token,
337
+ error
338
+ });
339
+ }
340
+ }
341
+ if (errors.length > 0) {
342
+ const summary = errors.map(({
343
+ token,
344
+ error
345
+ }) => ` - ${String(token)}: ${error instanceof Error ? error.message : String(error)}`).join('\n');
346
+ throw new Error(`Failed to resolve ${errors.length} of ${tokens.length} tokens:\n${summary}`);
347
+ }
348
+ return results;
349
+ },
350
+ dispatch: request => makeRequest(dispatcher, request)
351
+ };
352
+ }
353
+ _applyModuleReplacements(module) {
354
+ if (this.moduleReplacements.size === 0) {
355
+ return module;
356
+ }
357
+ const replacement = this.moduleReplacements.get(module);
358
+ if (replacement) {
359
+ return replacement;
360
+ }
361
+ const metadata = getModuleMetadata(module);
362
+ if (!metadata?.imports || metadata.imports.length === 0) {
363
+ return module;
364
+ }
365
+ const rewrittenImports = this.rewriteModuleImports(metadata.imports);
366
+ const hasChange = rewrittenImports.some((imp, i) => imp !== metadata.imports[i]);
367
+ if (!hasChange) {
368
+ return module;
369
+ }
370
+ class PatchedModule {}
371
+ const patchedModule = PatchedModule;
372
+ defineModule(patchedModule, {
373
+ ...metadata,
374
+ imports: rewrittenImports
375
+ });
376
+ return patchedModule;
377
+ }
378
+ rewriteModuleImports(imports) {
379
+ return imports.map(moduleImport => this._applyModuleReplacements(moduleImport));
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Creates a fluent testing-module builder for overriding providers and compiling a test graph.
385
+ *
386
+ * @param options Bootstrap options plus the root module that should be compiled for the test.
387
+ * @returns A builder that supports provider and module overrides before compilation.
388
+ *
389
+ * @example
390
+ * ```ts
391
+ * const module = await createTestingModule({ rootModule: AppModule })
392
+ * .overrideProvider(USER_REPOSITORY, fakeUserRepository)
393
+ * .compile();
394
+ * ```
395
+ */
396
+ export function createTestingModule(options) {
397
+ return new DefaultTestingModuleBuilder(options);
398
+ }
399
+
400
+ /**
401
+ * Namespace-style access point for `createTestingModule(...)`.
402
+ */
403
+ export const Test = {
404
+ createTestingModule
405
+ };
@@ -0,0 +1,83 @@
1
+ import { type ModuleType, type UploadedFile } from '@fluojs/runtime';
2
+ declare module '@fluojs/http' {
3
+ interface FrameworkRequest {
4
+ files?: UploadedFile[];
5
+ rawBody?: Uint8Array;
6
+ }
7
+ }
8
+ type AppLike = {
9
+ close(): Promise<void>;
10
+ listen(): Promise<void>;
11
+ };
12
+ /**
13
+ * Options for configuring the HTTP adapter portability harness.
14
+ *
15
+ * @template TBootstrapOptions - Type for bootstrap-specific options.
16
+ * @template TRunOptions - Type for run-specific options.
17
+ * @template TApp - Type for the application instance.
18
+ */
19
+ export interface HttpAdapterPortabilityHarnessOptions<TBootstrapOptions extends object, TRunOptions extends object, TApp extends AppLike = AppLike> {
20
+ /**
21
+ * Function to bootstrap the application with the given root module and options.
22
+ *
23
+ * @param rootModule - The root module of the application.
24
+ * @param options - The bootstrap options.
25
+ * @returns A promise that resolves to the application instance.
26
+ */
27
+ bootstrap: (rootModule: ModuleType, options: TBootstrapOptions) => Promise<TApp>;
28
+ /**
29
+ * The name of the adapter being tested.
30
+ */
31
+ name: string;
32
+ /**
33
+ * Function to run the application with the given root module and options.
34
+ *
35
+ * @param rootModule - The root module of the application.
36
+ * @param options - The run options.
37
+ * @returns A promise that resolves to the application instance.
38
+ */
39
+ run: (rootModule: ModuleType, options: TRunOptions) => Promise<TApp>;
40
+ }
41
+ /**
42
+ * A portability harness for testing HTTP adapters to ensure they behave
43
+ * consistently across different environments.
44
+ *
45
+ * @template TBootstrapOptions - Type for bootstrap-specific options.
46
+ * @template TRunOptions - Type for run-specific options.
47
+ * @template TApp - Type for the application instance.
48
+ */
49
+ export declare class HttpAdapterPortabilityHarness<TBootstrapOptions extends object, TRunOptions extends object, TApp extends AppLike = AppLike> {
50
+ private readonly options;
51
+ /**
52
+ * Creates a new instance of the {@link HttpAdapterPortabilityHarness}.
53
+ *
54
+ * @param options - Configuration options for the harness.
55
+ */
56
+ constructor(options: HttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TRunOptions, TApp>);
57
+ /**
58
+ * Asserts that the adapter preserves malformed cookie values without crashing
59
+ * or incorrectly normalizing them.
60
+ */
61
+ assertPreservesMalformedCookieValues(): Promise<void>;
62
+ assertPreservesRawBodyForJsonAndText(): Promise<void>;
63
+ assertExcludesRawBodyForMultipart(): Promise<void>;
64
+ assertSupportsSseStreaming(): Promise<void>;
65
+ assertReportsConfiguredHostInStartupLogs(): Promise<void>;
66
+ assertReportsHttpsStartupUrl(https: {
67
+ cert: string;
68
+ key: string;
69
+ }): Promise<void>;
70
+ assertRemovesShutdownSignalListenersAfterClose(): Promise<void>;
71
+ }
72
+ /**
73
+ * Creates a new {@link HttpAdapterPortabilityHarness} instance with the provided options.
74
+ *
75
+ * @template TBootstrapOptions - Type for bootstrap-specific options.
76
+ * @template TRunOptions - Type for run-specific options.
77
+ * @template TApp - Type for the application instance.
78
+ * @param options - Configuration options for the harness.
79
+ * @returns A new portability harness instance.
80
+ */
81
+ export declare function createHttpAdapterPortabilityHarness<TBootstrapOptions extends object, TRunOptions extends object, TApp extends AppLike = AppLike>(options: HttpAdapterPortabilityHarnessOptions<TBootstrapOptions, TRunOptions, TApp>): HttpAdapterPortabilityHarness<TBootstrapOptions, TRunOptions, TApp>;
82
+ export {};
83
+ //# sourceMappingURL=http-adapter-portability.d.ts.map
@@ -0,0 +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"}