@fluojs/testing 1.0.0-beta.2 → 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/README.ko.md +66 -17
- package/README.md +64 -15
- package/dist/app.d.ts +2 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -1
- package/dist/babel-decorators-plugin.d.ts +2 -2
- package/dist/babel-decorators-plugin.d.ts.map +1 -1
- package/dist/babel-decorators-plugin.js +26 -12
- package/dist/conformance/fetch-style-websocket-conformance.d.ts +12 -0
- package/dist/conformance/fetch-style-websocket-conformance.d.ts.map +1 -1
- package/dist/conformance/fetch-style-websocket-conformance.js +14 -0
- package/dist/conformance/platform-conformance.d.ts +21 -0
- package/dist/conformance/platform-conformance.d.ts.map +1 -1
- package/dist/conformance/platform-conformance.js +27 -0
- package/dist/mock.d.ts +17 -0
- package/dist/mock.d.ts.map +1 -1
- package/dist/mock.js +19 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +106 -11
- package/dist/portability/http-adapter-portability.d.ts +9 -0
- package/dist/portability/http-adapter-portability.d.ts.map +1 -1
- package/dist/portability/http-adapter-portability.js +102 -42
- package/dist/portability/web-runtime-adapter-portability.d.ts +13 -0
- package/dist/portability/web-runtime-adapter-portability.d.ts.map +1 -1
- package/dist/portability/web-runtime-adapter-portability.js +76 -20
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -9
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 {
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
286
|
+
factoryResolutionKinds,
|
|
287
|
+
introspection,
|
|
213
288
|
resolutionChain: new Set(),
|
|
214
|
-
singletonCache:
|
|
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) {
|
|
@@ -327,12 +413,16 @@ class DefaultTestingModuleBuilder {
|
|
|
327
413
|
}
|
|
328
414
|
createTestingModuleRef(bootstrapped) {
|
|
329
415
|
const dispatcher = createTestingDispatcher(bootstrapped);
|
|
330
|
-
const
|
|
416
|
+
const syncResolver = createSyncResolver(bootstrapped.container);
|
|
331
417
|
return {
|
|
332
418
|
...bootstrapped,
|
|
333
419
|
has: token => bootstrapped.container.has(token),
|
|
334
|
-
get: token =>
|
|
335
|
-
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
|
+
},
|
|
336
426
|
resolveAll: async tokens => {
|
|
337
427
|
const results = [];
|
|
338
428
|
const errors = [];
|
|
@@ -353,9 +443,14 @@ class DefaultTestingModuleBuilder {
|
|
|
353
443
|
}) => ` - ${String(token)}: ${error instanceof Error ? error.message : String(error)}`).join('\n');
|
|
354
444
|
throw new Error(`Failed to resolve ${errors.length} of ${tokens.length} tokens:\n${summary}`);
|
|
355
445
|
}
|
|
446
|
+
await syncResolver.syncFromContainer();
|
|
356
447
|
return results;
|
|
357
448
|
},
|
|
358
|
-
dispatch: request =>
|
|
449
|
+
dispatch: async request => {
|
|
450
|
+
const response = await makeRequest(dispatcher, request);
|
|
451
|
+
await syncResolver.syncFromContainer();
|
|
452
|
+
return response;
|
|
453
|
+
}
|
|
359
454
|
};
|
|
360
455
|
}
|
|
361
456
|
_applyModuleReplacements(module) {
|
|
@@ -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,6 +68,7 @@ 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>;
|
|
64
73
|
assertDefaultsMultipartTotalLimitToMaxBodySize(): Promise<void>;
|
|
65
74
|
assertSupportsSseStreaming(): Promise<void>;
|
|
@@ -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,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;
|
|
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"}
|
|
@@ -210,18 +210,75 @@ export class HttpAdapterPortabilityHarness {
|
|
|
210
210
|
await closeSilently(app);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
async
|
|
213
|
+
async assertPreservesExactRawBodyBytesForByteSensitivePayloads() {
|
|
214
214
|
let _initProto3, _initClass3;
|
|
215
|
+
let _WebhookController2;
|
|
216
|
+
class WebhookController {
|
|
217
|
+
static {
|
|
218
|
+
({
|
|
219
|
+
e: [_initProto3],
|
|
220
|
+
c: [_WebhookController2, _initClass3]
|
|
221
|
+
} = _applyDecs(this, [Controller('/webhooks')], [[Post('/bytes'), 2, "handleBytes"]]));
|
|
222
|
+
}
|
|
223
|
+
constructor() {
|
|
224
|
+
_initProto3(this);
|
|
225
|
+
}
|
|
226
|
+
handleBytes(_input, context) {
|
|
227
|
+
return {
|
|
228
|
+
rawBytes: Array.from(context.request.rawBody ?? new Uint8Array())
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
static {
|
|
232
|
+
_initClass3();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
class AppModule {}
|
|
236
|
+
defineModule(AppModule, {
|
|
237
|
+
controllers: [_WebhookController2]
|
|
238
|
+
});
|
|
239
|
+
const port = await findAvailablePort();
|
|
240
|
+
const app = await this.options.bootstrap(AppModule, {
|
|
241
|
+
cors: false,
|
|
242
|
+
port,
|
|
243
|
+
rawBody: true
|
|
244
|
+
});
|
|
245
|
+
await this.options.prepareExactRawBodyByteTest?.(app);
|
|
246
|
+
await app.listen();
|
|
247
|
+
try {
|
|
248
|
+
const payload = Uint8Array.from([0xe9, 0x41]);
|
|
249
|
+
const contentType = this.options.exactRawBodyByteContentType ?? 'text/plain; charset=latin1';
|
|
250
|
+
const response = await fetch(`http://127.0.0.1:${String(port)}/webhooks/bytes`, {
|
|
251
|
+
body: payload,
|
|
252
|
+
headers: {
|
|
253
|
+
'content-type': contentType
|
|
254
|
+
},
|
|
255
|
+
method: 'POST'
|
|
256
|
+
});
|
|
257
|
+
if (response.status !== 201) {
|
|
258
|
+
throw new Error(`${this.options.name} adapter changed byte-sensitive rawBody response status semantics.`);
|
|
259
|
+
}
|
|
260
|
+
const body = await response.json();
|
|
261
|
+
if (JSON.stringify(body) !== JSON.stringify({
|
|
262
|
+
rawBytes: Array.from(payload)
|
|
263
|
+
})) {
|
|
264
|
+
throw new Error(`${this.options.name} adapter changed exact-byte rawBody semantics.`);
|
|
265
|
+
}
|
|
266
|
+
} finally {
|
|
267
|
+
await closeSilently(app);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async assertExcludesRawBodyForMultipart() {
|
|
271
|
+
let _initProto4, _initClass4;
|
|
215
272
|
let _UploadController;
|
|
216
273
|
class UploadController {
|
|
217
274
|
static {
|
|
218
275
|
({
|
|
219
|
-
e: [
|
|
220
|
-
c: [_UploadController,
|
|
276
|
+
e: [_initProto4],
|
|
277
|
+
c: [_UploadController, _initClass4]
|
|
221
278
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
222
279
|
}
|
|
223
280
|
constructor() {
|
|
224
|
-
|
|
281
|
+
_initProto4(this);
|
|
225
282
|
}
|
|
226
283
|
upload(_input, context) {
|
|
227
284
|
return {
|
|
@@ -231,7 +288,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
231
288
|
};
|
|
232
289
|
}
|
|
233
290
|
static {
|
|
234
|
-
|
|
291
|
+
_initClass4();
|
|
235
292
|
}
|
|
236
293
|
}
|
|
237
294
|
class AppModule {}
|
|
@@ -273,17 +330,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
273
330
|
}
|
|
274
331
|
}
|
|
275
332
|
async assertDefaultsMultipartTotalLimitToMaxBodySize() {
|
|
276
|
-
let
|
|
333
|
+
let _initProto5, _initClass5;
|
|
277
334
|
let _UploadController2;
|
|
278
335
|
class UploadController {
|
|
279
336
|
static {
|
|
280
337
|
({
|
|
281
|
-
e: [
|
|
282
|
-
c: [_UploadController2,
|
|
338
|
+
e: [_initProto5],
|
|
339
|
+
c: [_UploadController2, _initClass5]
|
|
283
340
|
} = _applyDecs(this, [Controller('/uploads')], [[Post('/'), 2, "upload"]]));
|
|
284
341
|
}
|
|
285
342
|
constructor() {
|
|
286
|
-
|
|
343
|
+
_initProto5(this);
|
|
287
344
|
}
|
|
288
345
|
upload(_input, context) {
|
|
289
346
|
return {
|
|
@@ -292,7 +349,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
292
349
|
};
|
|
293
350
|
}
|
|
294
351
|
static {
|
|
295
|
-
|
|
352
|
+
_initClass5();
|
|
296
353
|
}
|
|
297
354
|
}
|
|
298
355
|
class AppModule {}
|
|
@@ -331,17 +388,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
331
388
|
}
|
|
332
389
|
}
|
|
333
390
|
async assertSupportsSseStreaming() {
|
|
334
|
-
let
|
|
391
|
+
let _initProto6, _initClass6;
|
|
335
392
|
let _EventsController;
|
|
336
393
|
class EventsController {
|
|
337
394
|
static {
|
|
338
395
|
({
|
|
339
|
-
e: [
|
|
340
|
-
c: [_EventsController,
|
|
396
|
+
e: [_initProto6],
|
|
397
|
+
c: [_EventsController, _initClass6]
|
|
341
398
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
342
399
|
}
|
|
343
400
|
constructor() {
|
|
344
|
-
|
|
401
|
+
_initProto6(this);
|
|
345
402
|
}
|
|
346
403
|
stream(_input, context) {
|
|
347
404
|
const stream = new SseResponse(context);
|
|
@@ -352,13 +409,13 @@ export class HttpAdapterPortabilityHarness {
|
|
|
352
409
|
event: 'ready',
|
|
353
410
|
id: 'evt-1'
|
|
354
411
|
});
|
|
355
|
-
|
|
412
|
+
queueMicrotask(() => {
|
|
356
413
|
stream.close();
|
|
357
|
-
}
|
|
414
|
+
});
|
|
358
415
|
return stream;
|
|
359
416
|
}
|
|
360
417
|
static {
|
|
361
|
-
|
|
418
|
+
_initClass6();
|
|
362
419
|
}
|
|
363
420
|
}
|
|
364
421
|
class AppModule {}
|
|
@@ -398,7 +455,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
398
455
|
* closes before a `drain` event is emitted.
|
|
399
456
|
*/
|
|
400
457
|
async assertSettlesStreamDrainWaitOnClose() {
|
|
401
|
-
let
|
|
458
|
+
let _initProto7, _initClass7;
|
|
402
459
|
const adapterName = this.options.name;
|
|
403
460
|
let resolveDrainWait;
|
|
404
461
|
const drainWaitSettled = new Promise(resolve => {
|
|
@@ -408,12 +465,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
408
465
|
class EventsController {
|
|
409
466
|
static {
|
|
410
467
|
({
|
|
411
|
-
e: [
|
|
412
|
-
c: [_EventsController2,
|
|
468
|
+
e: [_initProto7],
|
|
469
|
+
c: [_EventsController2, _initClass7]
|
|
413
470
|
} = _applyDecs(this, [Controller('/events')], [[Get('/'), 2, "stream"]]));
|
|
414
471
|
}
|
|
415
472
|
constructor() {
|
|
416
|
-
|
|
473
|
+
_initProto7(this);
|
|
417
474
|
}
|
|
418
475
|
async stream(_input, context) {
|
|
419
476
|
const stream = new SseResponse(context);
|
|
@@ -428,7 +485,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
428
485
|
return stream;
|
|
429
486
|
}
|
|
430
487
|
static {
|
|
431
|
-
|
|
488
|
+
_initClass7();
|
|
432
489
|
}
|
|
433
490
|
}
|
|
434
491
|
class AppModule {}
|
|
@@ -457,7 +514,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
457
514
|
}
|
|
458
515
|
}
|
|
459
516
|
async assertReportsConfiguredHostInStartupLogs() {
|
|
460
|
-
let
|
|
517
|
+
let _initProto8, _initClass8;
|
|
461
518
|
const loggerEvents = [];
|
|
462
519
|
const logger = {
|
|
463
520
|
debug() {},
|
|
@@ -473,12 +530,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
473
530
|
class HealthController {
|
|
474
531
|
static {
|
|
475
532
|
({
|
|
476
|
-
e: [
|
|
477
|
-
c: [_HealthController,
|
|
533
|
+
e: [_initProto8],
|
|
534
|
+
c: [_HealthController, _initClass8]
|
|
478
535
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
479
536
|
}
|
|
480
537
|
constructor() {
|
|
481
|
-
|
|
538
|
+
_initProto8(this);
|
|
482
539
|
}
|
|
483
540
|
getHealth() {
|
|
484
541
|
return {
|
|
@@ -486,7 +543,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
486
543
|
};
|
|
487
544
|
}
|
|
488
545
|
static {
|
|
489
|
-
|
|
546
|
+
_initClass8();
|
|
490
547
|
}
|
|
491
548
|
}
|
|
492
549
|
class AppModule {}
|
|
@@ -520,7 +577,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
520
577
|
}
|
|
521
578
|
}
|
|
522
579
|
async assertReportsHttpsStartupUrl(https) {
|
|
523
|
-
let
|
|
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: [
|
|
540
|
-
c: [_HealthController2,
|
|
596
|
+
e: [_initProto9],
|
|
597
|
+
c: [_HealthController2, _initClass9]
|
|
541
598
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
542
599
|
}
|
|
543
600
|
constructor() {
|
|
544
|
-
|
|
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
|
-
|
|
609
|
+
_initClass9();
|
|
553
610
|
}
|
|
554
611
|
}
|
|
555
612
|
class AppModule {}
|
|
@@ -583,7 +640,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
583
640
|
}
|
|
584
641
|
}
|
|
585
642
|
async assertRemovesShutdownSignalListenersAfterClose() {
|
|
586
|
-
let
|
|
643
|
+
let _initProto0, _initClass0;
|
|
587
644
|
const logger = {
|
|
588
645
|
debug() {},
|
|
589
646
|
error() {},
|
|
@@ -594,12 +651,12 @@ export class HttpAdapterPortabilityHarness {
|
|
|
594
651
|
class HealthController {
|
|
595
652
|
static {
|
|
596
653
|
({
|
|
597
|
-
e: [
|
|
598
|
-
c: [_HealthController3,
|
|
654
|
+
e: [_initProto0],
|
|
655
|
+
c: [_HealthController3, _initClass0]
|
|
599
656
|
} = _applyDecs(this, [Controller('/health')], [[Get('/'), 2, "getHealth"]]));
|
|
600
657
|
}
|
|
601
658
|
constructor() {
|
|
602
|
-
|
|
659
|
+
_initProto0(this);
|
|
603
660
|
}
|
|
604
661
|
getHealth() {
|
|
605
662
|
return {
|
|
@@ -607,7 +664,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
607
664
|
};
|
|
608
665
|
}
|
|
609
666
|
static {
|
|
610
|
-
|
|
667
|
+
_initClass0();
|
|
611
668
|
}
|
|
612
669
|
}
|
|
613
670
|
class AppModule {}
|
|
@@ -615,7 +672,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
615
672
|
controllers: [_HealthController3]
|
|
616
673
|
});
|
|
617
674
|
const signal = 'SIGTERM';
|
|
618
|
-
const listenersBefore = process.listeners(signal)
|
|
675
|
+
const listenersBefore = new Set(process.listeners(signal));
|
|
619
676
|
const port = await findAvailablePort();
|
|
620
677
|
const app = await this.options.run(AppModule, {
|
|
621
678
|
cors: false,
|
|
@@ -623,14 +680,17 @@ export class HttpAdapterPortabilityHarness {
|
|
|
623
680
|
port,
|
|
624
681
|
shutdownSignals: [signal]
|
|
625
682
|
});
|
|
683
|
+
const registeredListeners = process.listeners(signal).filter(listener => !listenersBefore.has(listener));
|
|
626
684
|
try {
|
|
627
|
-
if (
|
|
685
|
+
if (registeredListeners.length === 0) {
|
|
628
686
|
throw new Error(`${this.options.name} adapter did not register the expected shutdown listener.`);
|
|
629
687
|
}
|
|
630
688
|
} finally {
|
|
631
689
|
await closeSilently(app);
|
|
632
690
|
}
|
|
633
|
-
|
|
691
|
+
const remainingListeners = process.listeners(signal);
|
|
692
|
+
const leakedListeners = registeredListeners.filter(listener => remainingListeners.includes(listener));
|
|
693
|
+
if (leakedListeners.length > 0) {
|
|
634
694
|
throw new Error(`${this.options.name} adapter leaked shutdown signal listeners after close().`);
|
|
635
695
|
}
|
|
636
696
|
}
|
|
@@ -648,14 +708,14 @@ export class HttpAdapterPortabilityHarness {
|
|
|
648
708
|
export function createHttpAdapterPortabilityHarness(options) {
|
|
649
709
|
return new HttpAdapterPortabilityHarness(options);
|
|
650
710
|
}
|
|
651
|
-
function withTimeout(promise, timeoutMs, message) {
|
|
711
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
652
712
|
let timeout;
|
|
653
713
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
654
714
|
timeout = setTimeout(() => {
|
|
655
715
|
reject(new Error(message));
|
|
656
716
|
}, timeoutMs);
|
|
657
717
|
});
|
|
658
|
-
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
718
|
+
return await Promise.race([promise, timeoutPromise]).finally(() => {
|
|
659
719
|
if (timeout !== undefined) {
|
|
660
720
|
clearTimeout(timeout);
|
|
661
721
|
}
|
|
@@ -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":"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;;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;AAYD;;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;IAuCtD,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;;;;;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"}
|