@aglyn/shared-util-http 1.0.0-beta.222 → 1.0.0-beta.223

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.md CHANGED
@@ -24,6 +24,7 @@ By subpath only (not re-exported from the root):
24
24
 
25
25
  - `@aglyn/shared-util-http/safe-redirect` - `isSameOriginPath(candidate)` and `safeSameOriginPath(candidate, fallback = '/')`. Instead of pattern-matching the string, they resolve it with the URL parser against a probe origin and accept it only if the origin is unchanged, which catches `//host`, `/\host`, and tab or newline tricks alike.
26
26
  - `@aglyn/shared-util-http/safe-url-scheme` - `hasSafeLinkScheme(value)` (`http`, `https`, `mailto`, `tel`, `sms`, or no scheme) and `hasSafeMediaScheme(value)` (`http`, `https`, or no scheme). They check the scheme only; you still have to escape the value into the attribute afterwards.
27
+ - `@aglyn/shared-util-http/bounded-await` - `boundedAwait(work, ms, fallback, label)` and `createSettledValueCache()`. The first answers with `work`'s result, or with `fallback` once `ms` has passed, using a real timer rather than trusting an abort signal; `work` may be a function of an `AbortSignal` that aborts at the deadline. A deadline that wins writes one structured `console.warn` per label a minute. The second is a TTL cache that holds settled values and refuses a promise, with a `readThrough(key, load, ttlFor)` that stores only once `load` has settled.
27
28
  - `@aglyn/shared-util-http/authorized-token` - `resolveIdToken(user, options?)`, `authorizedFetch(user, input, init?, options?)`, `describeCallFailure(error, fallback)`, `AuthorizationUnavailableError`, `ID_TOKEN_TIMEOUT_MS`. `user` is anything with a `getIdToken()` method. The token wait has a deadline, and when no token can be had `authorizedFetch` sends nothing and resolves to a `401`-shaped response carrying the reason.
28
29
 
29
30
  ## Usage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-util-http",
3
- "version": "1.0.0-beta.222",
3
+ "version": "1.0.0-beta.223",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-enums": "1.0.0-beta.222",
28
+ "@aglyn/shared-data-enums": "1.0.0-beta.223",
29
29
  "@swc/helpers": "0.5.23"
30
30
  },
31
31
  "peerDependencies": {
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A server render never waits on outside I/O past a deadline (AGL-3565).
19
+ *
20
+ * ## Why an abort signal is not a deadline
21
+ *
22
+ * A request's `AbortSignal` bounds the REQUEST, and only when whatever is
23
+ * answering honors it. Next's patched `fetch` on Vercel handed back a promise
24
+ * that never settled — with `AbortSignal.timeout(2500)` on it, on cold
25
+ * instances as well as warm ones — and the site layout awaited it. Every
26
+ * uncached page render on the tenant then sat until the platform's 60 s limit.
27
+ *
28
+ * So the bound here is a timer the awaiting code owns: the work races a
29
+ * `setTimeout`, and the timer wins whatever the work does or fails to do. The
30
+ * signal handed to the work is still aborted at the deadline, so a request
31
+ * that CAN be canceled is; but nothing depends on that happening.
32
+ *
33
+ * ## Why a settled-value cache sits beside it
34
+ *
35
+ * The same incident's second half: the module cached the in-flight PROMISE
36
+ * for a day, so every later render on the instance awaited the one that never
37
+ * settled — each with its own fresh deadline in front of nothing. A process
38
+ * cache that outlives a request holds what a request LEARNED, never the work
39
+ * still being done. {@link createSettledValueCache} refuses a thenable.
40
+ */
41
+ /** What a bounded await is handed: the work, or a function that starts it. */
42
+ export type BoundedWork<T> = Promise<T> | ((signal: AbortSignal) => Promise<T>);
43
+ /** The structured line a deadline writes when it wins. */
44
+ export interface BoundedAwaitTimeout {
45
+ event: 'bounded-await-timeout';
46
+ /** Names the await, `area.what` — the line is searched for by it. */
47
+ label: string;
48
+ /** The deadline that passed. */
49
+ ms: number;
50
+ /** Deadlines of this label that passed since the last line, unwritten. */
51
+ suppressed: number;
52
+ }
53
+ /** Test seam: forget which labels have warned. */
54
+ export declare function resetBoundedAwaitWarningsForTests(): void;
55
+ /**
56
+ * `work`'s answer, or `fallback` once `ms` has passed without one.
57
+ *
58
+ * - The deadline is a real timer, cleared as soon as either side settles, so
59
+ * a fast answer leaves nothing scheduled behind it.
60
+ * - Given a function, the work is started with an `AbortSignal` that aborts
61
+ * at the deadline — pass it to `fetch` so the request is canceled too.
62
+ * - A rejection before the deadline is the caller's, unchanged: this bounds
63
+ * time, it does not swallow errors. A rejection after it is ignored.
64
+ * - The deadline winning writes a `console.warn` carrying a
65
+ * {@link BoundedAwaitTimeout}, so a stalled dependency is visible in the
66
+ * logs rather than only as pages that are a little plainer — at most one
67
+ * per label a minute, the rest counted onto the next.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const faces = await boundedAwait(
72
+ * (signal) => fetch(url, { signal }).then(parse).catch(() => null),
73
+ * 2_500,
74
+ * null,
75
+ * 'self-hosted-fonts.stylesheet',
76
+ * )
77
+ * ```
78
+ */
79
+ export declare function boundedAwait<T, F>(work: BoundedWork<T>, ms: number, fallback: F, label: string): Promise<T | F>;
80
+ /** A process cache whose entries are answers, never work in progress. */
81
+ export interface SettledValueCache<K, V> {
82
+ /** The held answer while it is fresh, as `{ value }`, else undefined. */
83
+ lookup(key: K, nowMs?: number): {
84
+ value: V;
85
+ } | undefined;
86
+ /** Hold `value` for `ttlMs`. A promise or other thenable is refused. */
87
+ set(key: K, value: V, ttlMs: number, nowMs?: number): void;
88
+ /**
89
+ * The held answer, or `load`'s once it settles — and only then is it held,
90
+ * for `ttlFor(value)`. Concurrent misses each run `load`; none of them
91
+ * waits on another's work, which is the point.
92
+ */
93
+ readThrough(key: K, load: () => Promise<V>, ttlFor: (value: V) => number): Promise<V>;
94
+ delete(key: K): void;
95
+ clear(): void;
96
+ }
97
+ /**
98
+ * A process-lifetime cache for values a render learned — see the module
99
+ * note for why it will not hold a promise.
100
+ */
101
+ export declare function createSettledValueCache<K, V>(): SettledValueCache<K, V>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * A server render never waits on outside I/O past a deadline (AGL-3565).
18
+ *
19
+ * ## Why an abort signal is not a deadline
20
+ *
21
+ * A request's `AbortSignal` bounds the REQUEST, and only when whatever is
22
+ * answering honors it. Next's patched `fetch` on Vercel handed back a promise
23
+ * that never settled — with `AbortSignal.timeout(2500)` on it, on cold
24
+ * instances as well as warm ones — and the site layout awaited it. Every
25
+ * uncached page render on the tenant then sat until the platform's 60 s limit.
26
+ *
27
+ * So the bound here is a timer the awaiting code owns: the work races a
28
+ * `setTimeout`, and the timer wins whatever the work does or fails to do. The
29
+ * signal handed to the work is still aborted at the deadline, so a request
30
+ * that CAN be canceled is; but nothing depends on that happening.
31
+ *
32
+ * ## Why a settled-value cache sits beside it
33
+ *
34
+ * The same incident's second half: the module cached the in-flight PROMISE
35
+ * for a day, so every later render on the instance awaited the one that never
36
+ * settled — each with its own fresh deadline in front of nothing. A process
37
+ * cache that outlives a request holds what a request LEARNED, never the work
38
+ * still being done. {@link createSettledValueCache} refuses a thenable.
39
+ */ /** What a bounded await is handed: the work, or a function that starts it. */ /**
40
+ * One line per label per window. A dependency that is down misses its
41
+ * deadline on every render that asks it; a line per render would bury the
42
+ * logs in the one fact, so the rest are counted onto the next line.
43
+ */ const WARN_WINDOW_MS = 60000;
44
+ const lastWarned = new Map();
45
+ function warnTimeout(label, ms) {
46
+ var _ref;
47
+ const nowMs = Date.now();
48
+ const held = lastWarned.get(label);
49
+ if (held && nowMs - held.atMs < WARN_WINDOW_MS) {
50
+ held.suppressed += 1;
51
+ return;
52
+ }
53
+ const line = {
54
+ event: 'bounded-await-timeout',
55
+ label,
56
+ ms,
57
+ suppressed: (_ref = held == null ? void 0 : held.suppressed) != null ? _ref : 0
58
+ };
59
+ lastWarned.set(label, {
60
+ atMs: nowMs,
61
+ suppressed: 0
62
+ });
63
+ console.warn('[bounded-await] deadline passed; using the fallback', line);
64
+ }
65
+ /** Test seam: forget which labels have warned. */ export function resetBoundedAwaitWarningsForTests() {
66
+ lastWarned.clear();
67
+ }
68
+ /**
69
+ * `work`'s answer, or `fallback` once `ms` has passed without one.
70
+ *
71
+ * - The deadline is a real timer, cleared as soon as either side settles, so
72
+ * a fast answer leaves nothing scheduled behind it.
73
+ * - Given a function, the work is started with an `AbortSignal` that aborts
74
+ * at the deadline — pass it to `fetch` so the request is canceled too.
75
+ * - A rejection before the deadline is the caller's, unchanged: this bounds
76
+ * time, it does not swallow errors. A rejection after it is ignored.
77
+ * - The deadline winning writes a `console.warn` carrying a
78
+ * {@link BoundedAwaitTimeout}, so a stalled dependency is visible in the
79
+ * logs rather than only as pages that are a little plainer — at most one
80
+ * per label a minute, the rest counted onto the next.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const faces = await boundedAwait(
85
+ * (signal) => fetch(url, { signal }).then(parse).catch(() => null),
86
+ * 2_500,
87
+ * null,
88
+ * 'self-hosted-fonts.stylesheet',
89
+ * )
90
+ * ```
91
+ */ export function boundedAwait(work, ms, fallback, label) {
92
+ const controller = new AbortController();
93
+ let timer;
94
+ let started;
95
+ try {
96
+ started = typeof work === 'function' ? work(controller.signal) : work;
97
+ } catch (error) {
98
+ return Promise.reject(error);
99
+ }
100
+ const deadline = new Promise((resolve)=>{
101
+ timer = setTimeout(()=>{
102
+ controller.abort(new Error(`${label} passed its ${ms} ms deadline`));
103
+ warnTimeout(label, ms);
104
+ resolve(fallback);
105
+ }, ms);
106
+ });
107
+ return Promise.race([
108
+ started,
109
+ deadline
110
+ ]).finally(()=>clearTimeout(timer));
111
+ }
112
+ function isThenable(value) {
113
+ return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
114
+ }
115
+ /**
116
+ * A process-lifetime cache for values a render learned — see the module
117
+ * note for why it will not hold a promise.
118
+ */ export function createSettledValueCache() {
119
+ const entries = new Map();
120
+ const cache = {
121
+ lookup (key, nowMs = Date.now()) {
122
+ const held = entries.get(key);
123
+ if (!held) return undefined;
124
+ if (held.expires <= nowMs) {
125
+ entries.delete(key);
126
+ return undefined;
127
+ }
128
+ return {
129
+ value: held.value
130
+ };
131
+ },
132
+ set (key, value, ttlMs, nowMs = Date.now()) {
133
+ if (isThenable(value)) {
134
+ throw new TypeError('A settled-value cache holds answers, not work in progress: await it first (AGL-3565)');
135
+ }
136
+ entries.set(key, {
137
+ value,
138
+ expires: nowMs + ttlMs
139
+ });
140
+ },
141
+ async readThrough (key, load, ttlFor) {
142
+ const held = cache.lookup(key);
143
+ if (held) return held.value;
144
+ const value = await load();
145
+ cache.set(key, value, ttlFor(value));
146
+ return value;
147
+ },
148
+ delete (key) {
149
+ entries.delete(key);
150
+ },
151
+ clear () {
152
+ entries.clear();
153
+ }
154
+ };
155
+ return cache;
156
+ }
157
+
158
+ //# sourceMappingURL=bounded-await.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/shared/util/http/src/lib/bounded-await.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A server render never waits on outside I/O past a deadline (AGL-3565).\n *\n * ## Why an abort signal is not a deadline\n *\n * A request's `AbortSignal` bounds the REQUEST, and only when whatever is\n * answering honors it. Next's patched `fetch` on Vercel handed back a promise\n * that never settled — with `AbortSignal.timeout(2500)` on it, on cold\n * instances as well as warm ones — and the site layout awaited it. Every\n * uncached page render on the tenant then sat until the platform's 60 s limit.\n *\n * So the bound here is a timer the awaiting code owns: the work races a\n * `setTimeout`, and the timer wins whatever the work does or fails to do. The\n * signal handed to the work is still aborted at the deadline, so a request\n * that CAN be canceled is; but nothing depends on that happening.\n *\n * ## Why a settled-value cache sits beside it\n *\n * The same incident's second half: the module cached the in-flight PROMISE\n * for a day, so every later render on the instance awaited the one that never\n * settled — each with its own fresh deadline in front of nothing. A process\n * cache that outlives a request holds what a request LEARNED, never the work\n * still being done. {@link createSettledValueCache} refuses a thenable.\n */\n\n/** What a bounded await is handed: the work, or a function that starts it. */\nexport type BoundedWork<T> = Promise<T> | ((signal: AbortSignal) => Promise<T>)\n\n/** The structured line a deadline writes when it wins. */\nexport interface BoundedAwaitTimeout {\n event: 'bounded-await-timeout'\n /** Names the await, `area.what` — the line is searched for by it. */\n label: string\n /** The deadline that passed. */\n ms: number\n /** Deadlines of this label that passed since the last line, unwritten. */\n suppressed: number\n}\n\n/**\n * One line per label per window. A dependency that is down misses its\n * deadline on every render that asks it; a line per render would bury the\n * logs in the one fact, so the rest are counted onto the next line.\n */\nconst WARN_WINDOW_MS = 60_000\nconst lastWarned = new Map<string, { atMs: number; suppressed: number }>()\n\nfunction warnTimeout(label: string, ms: number): void {\n const nowMs = Date.now()\n const held = lastWarned.get(label)\n if (held && nowMs - held.atMs < WARN_WINDOW_MS) {\n held.suppressed += 1\n return\n }\n const line: BoundedAwaitTimeout = {\n event: 'bounded-await-timeout',\n label,\n ms,\n suppressed: held?.suppressed ?? 0,\n }\n lastWarned.set(label, { atMs: nowMs, suppressed: 0 })\n console.warn('[bounded-await] deadline passed; using the fallback', line)\n}\n\n/** Test seam: forget which labels have warned. */\nexport function resetBoundedAwaitWarningsForTests(): void {\n lastWarned.clear()\n}\n\n/**\n * `work`'s answer, or `fallback` once `ms` has passed without one.\n *\n * - The deadline is a real timer, cleared as soon as either side settles, so\n * a fast answer leaves nothing scheduled behind it.\n * - Given a function, the work is started with an `AbortSignal` that aborts\n * at the deadline — pass it to `fetch` so the request is canceled too.\n * - A rejection before the deadline is the caller's, unchanged: this bounds\n * time, it does not swallow errors. A rejection after it is ignored.\n * - The deadline winning writes a `console.warn` carrying a\n * {@link BoundedAwaitTimeout}, so a stalled dependency is visible in the\n * logs rather than only as pages that are a little plainer — at most one\n * per label a minute, the rest counted onto the next.\n *\n * @example\n * ```ts\n * const faces = await boundedAwait(\n * (signal) => fetch(url, { signal }).then(parse).catch(() => null),\n * 2_500,\n * null,\n * 'self-hosted-fonts.stylesheet',\n * )\n * ```\n */\nexport function boundedAwait<T, F>(\n work: BoundedWork<T>,\n ms: number,\n fallback: F,\n label: string,\n): Promise<T | F> {\n const controller = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n let started: Promise<T>\n try {\n started = typeof work === 'function' ? work(controller.signal) : work\n } catch (error) {\n return Promise.reject(error)\n }\n const deadline = new Promise<F>((resolve) => {\n timer = setTimeout(() => {\n controller.abort(new Error(`${label} passed its ${ms} ms deadline`))\n warnTimeout(label, ms)\n resolve(fallback)\n }, ms)\n })\n return Promise.race([started, deadline]).finally(() => clearTimeout(timer))\n}\n\n/** A process cache whose entries are answers, never work in progress. */\nexport interface SettledValueCache<K, V> {\n /** The held answer while it is fresh, as `{ value }`, else undefined. */\n lookup(key: K, nowMs?: number): { value: V } | undefined\n /** Hold `value` for `ttlMs`. A promise or other thenable is refused. */\n set(key: K, value: V, ttlMs: number, nowMs?: number): void\n /**\n * The held answer, or `load`'s once it settles — and only then is it held,\n * for `ttlFor(value)`. Concurrent misses each run `load`; none of them\n * waits on another's work, which is the point.\n */\n readThrough(\n key: K,\n load: () => Promise<V>,\n ttlFor: (value: V) => number,\n ): Promise<V>\n delete(key: K): void\n clear(): void\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n typeof (value as { then?: unknown }).then === 'function'\n )\n}\n\n/**\n * A process-lifetime cache for values a render learned — see the module\n * note for why it will not hold a promise.\n */\nexport function createSettledValueCache<K, V>(): SettledValueCache<K, V> {\n const entries = new Map<K, { value: V; expires: number }>()\n const cache: SettledValueCache<K, V> = {\n lookup(key, nowMs = Date.now()) {\n const held = entries.get(key)\n if (!held) return undefined\n if (held.expires <= nowMs) {\n entries.delete(key)\n return undefined\n }\n return { value: held.value }\n },\n set(key, value, ttlMs, nowMs = Date.now()) {\n if (isThenable(value)) {\n throw new TypeError(\n 'A settled-value cache holds answers, not work in progress: await it first (AGL-3565)',\n )\n }\n entries.set(key, { value, expires: nowMs + ttlMs })\n },\n async readThrough(key, load, ttlFor) {\n const held = cache.lookup(key)\n if (held) return held.value\n const value = await load()\n cache.set(key, value, ttlFor(value))\n return value\n },\n delete(key) {\n entries.delete(key)\n },\n clear() {\n entries.clear()\n },\n }\n return cache\n}\n"],"names":["WARN_WINDOW_MS","lastWarned","Map","warnTimeout","label","ms","nowMs","Date","now","held","get","atMs","suppressed","line","event","set","console","warn","resetBoundedAwaitWarningsForTests","clear","boundedAwait","work","fallback","controller","AbortController","timer","started","signal","error","Promise","reject","deadline","resolve","setTimeout","abort","Error","race","finally","clearTimeout","isThenable","value","then","createSettledValueCache","entries","cache","lookup","key","undefined","expires","delete","ttlMs","TypeError","readThrough","load","ttlFor"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED,4EAA4E,GAc5E;;;;CAIC,GACD,MAAMA,iBAAiB;AACvB,MAAMC,aAAa,IAAIC;AAEvB,SAASC,YAAYC,KAAa,EAAEC,EAAU;;IAC5C,MAAMC,QAAQC,KAAKC,GAAG;IACtB,MAAMC,OAAOR,WAAWS,GAAG,CAACN;IAC5B,IAAIK,QAAQH,QAAQG,KAAKE,IAAI,GAAGX,gBAAgB;QAC9CS,KAAKG,UAAU,IAAI;QACnB;IACF;IACA,MAAMC,OAA4B;QAChCC,OAAO;QACPV;QACAC;QACAO,UAAU,UAAEH,wBAAAA,KAAMG,UAAU,mBAAI;IAClC;IACAX,WAAWc,GAAG,CAACX,OAAO;QAAEO,MAAML;QAAOM,YAAY;IAAE;IACnDI,QAAQC,IAAI,CAAC,uDAAuDJ;AACtE;AAEA,gDAAgD,GAChD,OAAO,SAASK;IACdjB,WAAWkB,KAAK;AAClB;AAEA;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GACD,OAAO,SAASC,aACdC,IAAoB,EACpBhB,EAAU,EACViB,QAAW,EACXlB,KAAa;IAEb,MAAMmB,aAAa,IAAIC;IACvB,IAAIC;IACJ,IAAIC;IACJ,IAAI;QACFA,UAAU,OAAOL,SAAS,aAAaA,KAAKE,WAAWI,MAAM,IAAIN;IACnE,EAAE,OAAOO,OAAO;QACd,OAAOC,QAAQC,MAAM,CAACF;IACxB;IACA,MAAMG,WAAW,IAAIF,QAAW,CAACG;QAC/BP,QAAQQ,WAAW;YACjBV,WAAWW,KAAK,CAAC,IAAIC,MAAM,GAAG/B,MAAM,YAAY,EAAEC,GAAG,YAAY,CAAC;YAClEF,YAAYC,OAAOC;YACnB2B,QAAQV;QACV,GAAGjB;IACL;IACA,OAAOwB,QAAQO,IAAI,CAAC;QAACV;QAASK;KAAS,EAAEM,OAAO,CAAC,IAAMC,aAAab;AACtE;AAsBA,SAASc,WAAWC,KAAc;IAChC,OACE,AAAC,CAAA,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAS,KACxDA,UAAU,QACV,OAAO,AAACA,MAA6BC,IAAI,KAAK;AAElD;AAEA;;;CAGC,GACD,OAAO,SAASC;IACd,MAAMC,UAAU,IAAIzC;IACpB,MAAM0C,QAAiC;QACrCC,QAAOC,GAAG,EAAExC,QAAQC,KAAKC,GAAG,EAAE;YAC5B,MAAMC,OAAOkC,QAAQjC,GAAG,CAACoC;YACzB,IAAI,CAACrC,MAAM,OAAOsC;YAClB,IAAItC,KAAKuC,OAAO,IAAI1C,OAAO;gBACzBqC,QAAQM,MAAM,CAACH;gBACf,OAAOC;YACT;YACA,OAAO;gBAAEP,OAAO/B,KAAK+B,KAAK;YAAC;QAC7B;QACAzB,KAAI+B,GAAG,EAAEN,KAAK,EAAEU,KAAK,EAAE5C,QAAQC,KAAKC,GAAG,EAAE;YACvC,IAAI+B,WAAWC,QAAQ;gBACrB,MAAM,IAAIW,UACR;YAEJ;YACAR,QAAQ5B,GAAG,CAAC+B,KAAK;gBAAEN;gBAAOQ,SAAS1C,QAAQ4C;YAAM;QACnD;QACA,MAAME,aAAYN,GAAG,EAAEO,IAAI,EAAEC,MAAM;YACjC,MAAM7C,OAAOmC,MAAMC,MAAM,CAACC;YAC1B,IAAIrC,MAAM,OAAOA,KAAK+B,KAAK;YAC3B,MAAMA,QAAQ,MAAMa;YACpBT,MAAM7B,GAAG,CAAC+B,KAAKN,OAAOc,OAAOd;YAC7B,OAAOA;QACT;QACAS,QAAOH,GAAG;YACRH,QAAQM,MAAM,CAACH;QACjB;QACA3B;YACEwB,QAAQxB,KAAK;QACf;IACF;IACA,OAAOyB;AACT"}