@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 +1 -0
- package/package.json +2 -2
- package/src/lib/bounded-await.d.ts +101 -0
- package/src/lib/bounded-await.js +158 -0
- package/src/lib/bounded-await.js.map +1 -0
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.
|
|
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.
|
|
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"}
|