@selvajs/compute 5.0.1 → 5.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/grasshopper.d.ts +18 -1
- package/dist/grasshopper.js +17 -3
- package/dist/grasshopper.js.map +1 -1
- package/dist/{handle-files-D-4X4Uh_.js → handle-files-CzqBN3MP.js} +193 -162
- package/dist/handle-files-CzqBN3MP.js.map +1 -0
- package/dist/{types-Dm9QFVvA.d.ts → types-CcKxH26w.d.ts} +26 -3
- package/package.json +1 -1
- package/dist/handle-files-D-4X4Uh_.js.map +0 -1
|
@@ -1,72 +1,3 @@
|
|
|
1
|
-
// src/core/compute-fetch/retry.ts
|
|
2
|
-
const DEFAULT_RETRY = {
|
|
3
|
-
attempts: 0,
|
|
4
|
-
baseDelayMs: 500,
|
|
5
|
-
maxDelayMs: 3e4,
|
|
6
|
-
retryOn429: true
|
|
7
|
-
};
|
|
8
|
-
const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
9
|
-
502,
|
|
10
|
-
503,
|
|
11
|
-
504
|
|
12
|
-
]);
|
|
13
|
-
/**
|
|
14
|
-
* Ceiling on a server-sent `Retry-After` wait. The server's window outranks
|
|
15
|
-
* `RetryPolicy.maxDelayMs` (retrying sooner all but guarantees another 429), but a bad or hostile
|
|
16
|
-
* header must not park the client for minutes. A `maxDelayMs` above this cap raises it.
|
|
17
|
-
*/
|
|
18
|
-
const RETRY_AFTER_CAP_MS = 6e4;
|
|
19
|
-
function resolveRetryPolicy(policy) {
|
|
20
|
-
if (!policy) return DEFAULT_RETRY;
|
|
21
|
-
return {
|
|
22
|
-
attempts: policy.attempts ?? DEFAULT_RETRY.attempts,
|
|
23
|
-
baseDelayMs: policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,
|
|
24
|
-
maxDelayMs: policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,
|
|
25
|
-
retryOn429: policy.retryOn429 ?? DEFAULT_RETRY.retryOn429
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
function isRetryableStatus(status, policy) {
|
|
29
|
-
return RETRYABLE_STATUS.has(status) || policy.retryOn429 && status === 429;
|
|
30
|
-
}
|
|
31
|
-
/** Delay before the next attempt: the server's `Retry-After` when it sent one, else backoff. */
|
|
32
|
-
function retryDelay(retryAfter, attempt, policy) {
|
|
33
|
-
const retryAfterMs = parseRetryAfter(retryAfter);
|
|
34
|
-
return retryAfterMs === null ? backoffDelay(attempt, policy) : Math.min(retryAfterMs, Math.max(policy.maxDelayMs, RETRY_AFTER_CAP_MS));
|
|
35
|
-
}
|
|
36
|
-
/** A `Retry-After` value (seconds or HTTP-date) in ms; null when missing or unparseable. */
|
|
37
|
-
function parseRetryAfter(headerValue) {
|
|
38
|
-
if (!headerValue) return null;
|
|
39
|
-
const seconds = Number(headerValue);
|
|
40
|
-
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
41
|
-
const dateMs = Date.parse(headerValue);
|
|
42
|
-
if (Number.isFinite(dateMs)) {
|
|
43
|
-
const delta = dateMs - Date.now();
|
|
44
|
-
return delta > 0 ? delta : 0;
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
function backoffDelay(attempt, policy) {
|
|
49
|
-
const exponential = policy.baseDelayMs * Math.pow(2, attempt);
|
|
50
|
-
const jitter = Math.random() * policy.baseDelayMs;
|
|
51
|
-
return Math.min(exponential + jitter, policy.maxDelayMs);
|
|
52
|
-
}
|
|
53
|
-
function sleep(ms, signal) {
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
if (signal?.aborted) {
|
|
56
|
-
reject(new DOMException("Aborted", "AbortError"));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const id = setTimeout(() => {
|
|
60
|
-
signal?.removeEventListener("abort", onAbort);
|
|
61
|
-
resolve();
|
|
62
|
-
}, ms);
|
|
63
|
-
const onAbort = () => {
|
|
64
|
-
clearTimeout(id);
|
|
65
|
-
reject(new DOMException("Aborted", "AbortError"));
|
|
66
|
-
};
|
|
67
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
1
|
// src/core/errors.ts
|
|
71
2
|
const ErrorCodes = {
|
|
72
3
|
/** Connection failure, a gateway status (502/503/504), or a 2xx body cut off mid-stream. */
|
|
@@ -208,6 +139,179 @@ function setLogger(logger) {
|
|
|
208
139
|
function enableDebugLogging() {
|
|
209
140
|
setLogger(console);
|
|
210
141
|
}
|
|
142
|
+
// src/core/utils/warnings.ts
|
|
143
|
+
const warned = /* @__PURE__ */ new Set();
|
|
144
|
+
/** Log `message` the first time `key` is seen: what it reports doesn't change per call. */
|
|
145
|
+
function warnOnce(key, message) {
|
|
146
|
+
if (warned.has(key)) return;
|
|
147
|
+
warned.add(key);
|
|
148
|
+
getLogger().warn(message);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* True only in a real browser, where an API key in the config is exposed to users. jsdom defines
|
|
152
|
+
* `window` too, so Node is ruled out by `process.versions.node`, and by jsdom's user-agent marker
|
|
153
|
+
* for setups that hide `process`.
|
|
154
|
+
*/
|
|
155
|
+
function isRealBrowser() {
|
|
156
|
+
if (typeof window === "undefined") return false;
|
|
157
|
+
if (globalThis.process?.versions?.node != null) return false;
|
|
158
|
+
const nav = globalThis.navigator;
|
|
159
|
+
const userAgent = typeof nav?.userAgent === "string" ? nav.userAgent : "";
|
|
160
|
+
if (/jsdom/i.test(userAgent)) return false;
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
function warnIfClientSide(functionName, suppress) {
|
|
164
|
+
if (suppress || !isRealBrowser()) return;
|
|
165
|
+
warnOnce(`client-side:${functionName}`, `Warning: ${functionName} is running on the client side. For better performance and security, consider running this on the server side.`);
|
|
166
|
+
}
|
|
167
|
+
// src/core/compute-fetch/request.ts
|
|
168
|
+
/** `serverUrl` as `validateServerUrl` returns it: no trailing slash. */
|
|
169
|
+
function buildUrl(endpoint, serverUrl) {
|
|
170
|
+
return `${serverUrl}/${endpoint.replace(/^\/+/, "")}`;
|
|
171
|
+
}
|
|
172
|
+
function isLocalhost(serverUrl) {
|
|
173
|
+
const hostname = new URL(serverUrl).hostname.toLowerCase();
|
|
174
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
175
|
+
}
|
|
176
|
+
const DEFAULT_API_KEY_HEADER = "RhinoComputeKey";
|
|
177
|
+
/**
|
|
178
|
+
* Caller headers plus credentials. Every request to the server builds its headers here,
|
|
179
|
+
* so a liveness probe authenticates exactly like the solve it gates.
|
|
180
|
+
*/
|
|
181
|
+
function buildAuthHeaders(auth) {
|
|
182
|
+
return assertSendable(mergeUnder(auth.headers, credentialHeaders(auth)));
|
|
183
|
+
}
|
|
184
|
+
function buildHeaders(requestId, config) {
|
|
185
|
+
const headers = assertSendable(mergeUnder(config.headers, {
|
|
186
|
+
...credentialHeaders(config),
|
|
187
|
+
...sessionHeader(config.sessionId),
|
|
188
|
+
"X-Request-ID": requestId,
|
|
189
|
+
"Content-Type": "application/json"
|
|
190
|
+
}));
|
|
191
|
+
if (!config.apiKey && !config.authToken && !isLocalhost(config.serverUrl)) warnOnce(`no-auth:${config.serverUrl}`, `[Compute] Request [${requestId}] targets remote server (${config.serverUrl}) but no API key or auth token is configured. Requests may fail or be rate-limited. (warned once per server)`);
|
|
192
|
+
return headers;
|
|
193
|
+
}
|
|
194
|
+
/** `headers` with a JSON `Content-Type` that replaces one of any casing already there. */
|
|
195
|
+
function withJsonContentType(headers) {
|
|
196
|
+
return mergeUnder(headers, { "Content-Type": "application/json" });
|
|
197
|
+
}
|
|
198
|
+
function credentialHeaders(auth) {
|
|
199
|
+
return {
|
|
200
|
+
...auth.authToken && { Authorization: auth.authToken },
|
|
201
|
+
...auth.apiKey && { [auth.apiKeyHeader ?? DEFAULT_API_KEY_HEADER]: auth.apiKey }
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const SESSION_ID = /^[!-~]{1,128}$/;
|
|
205
|
+
/**
|
|
206
|
+
* Compute drops a malformed id without an error, so the request would quietly lose its routing.
|
|
207
|
+
* The client and scheduler call this on entry too: by `buildHeaders` a solve has already taken a
|
|
208
|
+
* slot and loaded its definition.
|
|
209
|
+
*/
|
|
210
|
+
function assertSessionId(sessionId) {
|
|
211
|
+
if (sessionId === void 0 || SESSION_ID.test(sessionId)) return;
|
|
212
|
+
throw new ComputeError("sessionId must be 1 to 128 characters from \"!\" to \"~\"", ErrorCodes.INVALID_CONFIG, { context: { length: sessionId.length } });
|
|
213
|
+
}
|
|
214
|
+
function sessionHeader(sessionId) {
|
|
215
|
+
assertSessionId(sessionId);
|
|
216
|
+
return sessionId === void 0 ? {} : { "X-Session-Id": sessionId };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Header names are case-insensitive and fetch joins duplicates, so a caller key
|
|
220
|
+
* matching a transport key in any case is dropped rather than sent alongside it.
|
|
221
|
+
*/
|
|
222
|
+
function mergeUnder(caller, transport) {
|
|
223
|
+
const taken = new Set(Object.keys(transport).map((name) => name.toLowerCase()));
|
|
224
|
+
const kept = Object.entries(caller ?? {}).filter(([name]) => !taken.has(name.toLowerCase()));
|
|
225
|
+
return {
|
|
226
|
+
...Object.fromEntries(kept),
|
|
227
|
+
...transport
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Left to fetch, a bad value becomes a retried TypeError that quotes it, and the value may be the
|
|
232
|
+
* API key. `Headers` throws the same message, so its error is not kept as the cause.
|
|
233
|
+
*/
|
|
234
|
+
function assertSendable(headers) {
|
|
235
|
+
const check = new Headers();
|
|
236
|
+
for (const [name, value] of Object.entries(headers)) try {
|
|
237
|
+
check.set(name, value);
|
|
238
|
+
} catch {
|
|
239
|
+
throw new ComputeError(`Header "${name}" has an invalid name or value`, ErrorCodes.INVALID_CONFIG, { context: { header: name } });
|
|
240
|
+
}
|
|
241
|
+
return headers;
|
|
242
|
+
}
|
|
243
|
+
function generateRequestId() {
|
|
244
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
245
|
+
}
|
|
246
|
+
// src/core/compute-fetch/retry.ts
|
|
247
|
+
const DEFAULT_RETRY = {
|
|
248
|
+
attempts: 0,
|
|
249
|
+
baseDelayMs: 500,
|
|
250
|
+
maxDelayMs: 3e4,
|
|
251
|
+
retryOn429: true
|
|
252
|
+
};
|
|
253
|
+
const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
254
|
+
502,
|
|
255
|
+
503,
|
|
256
|
+
504
|
|
257
|
+
]);
|
|
258
|
+
/**
|
|
259
|
+
* Ceiling on a server-sent `Retry-After` wait. The server's window outranks
|
|
260
|
+
* `RetryPolicy.maxDelayMs` (retrying sooner all but guarantees another 429), but a bad or hostile
|
|
261
|
+
* header must not park the client for minutes. A `maxDelayMs` above this cap raises it.
|
|
262
|
+
*/
|
|
263
|
+
const RETRY_AFTER_CAP_MS = 6e4;
|
|
264
|
+
function resolveRetryPolicy(policy) {
|
|
265
|
+
if (!policy) return DEFAULT_RETRY;
|
|
266
|
+
return {
|
|
267
|
+
attempts: policy.attempts ?? DEFAULT_RETRY.attempts,
|
|
268
|
+
baseDelayMs: policy.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,
|
|
269
|
+
maxDelayMs: policy.maxDelayMs ?? DEFAULT_RETRY.maxDelayMs,
|
|
270
|
+
retryOn429: policy.retryOn429 ?? DEFAULT_RETRY.retryOn429
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function isRetryableStatus(status, policy) {
|
|
274
|
+
return RETRYABLE_STATUS.has(status) || policy.retryOn429 && status === 429;
|
|
275
|
+
}
|
|
276
|
+
/** Delay before the next attempt: the server's `Retry-After` when it sent one, else backoff. */
|
|
277
|
+
function retryDelay(retryAfter, attempt, policy) {
|
|
278
|
+
const retryAfterMs = parseRetryAfter(retryAfter);
|
|
279
|
+
return retryAfterMs === null ? backoffDelay(attempt, policy) : Math.min(retryAfterMs, Math.max(policy.maxDelayMs, RETRY_AFTER_CAP_MS));
|
|
280
|
+
}
|
|
281
|
+
/** A `Retry-After` value (seconds or HTTP-date) in ms; null when missing or unparseable. */
|
|
282
|
+
function parseRetryAfter(headerValue) {
|
|
283
|
+
if (!headerValue) return null;
|
|
284
|
+
const seconds = Number(headerValue);
|
|
285
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
286
|
+
const dateMs = Date.parse(headerValue);
|
|
287
|
+
if (Number.isFinite(dateMs)) {
|
|
288
|
+
const delta = dateMs - Date.now();
|
|
289
|
+
return delta > 0 ? delta : 0;
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
function backoffDelay(attempt, policy) {
|
|
294
|
+
const exponential = policy.baseDelayMs * Math.pow(2, attempt);
|
|
295
|
+
const jitter = Math.random() * policy.baseDelayMs;
|
|
296
|
+
return Math.min(exponential + jitter, policy.maxDelayMs);
|
|
297
|
+
}
|
|
298
|
+
function sleep(ms, signal) {
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
if (signal?.aborted) {
|
|
301
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const id = setTimeout(() => {
|
|
305
|
+
signal?.removeEventListener("abort", onAbort);
|
|
306
|
+
resolve();
|
|
307
|
+
}, ms);
|
|
308
|
+
const onAbort = () => {
|
|
309
|
+
clearTimeout(id);
|
|
310
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
311
|
+
};
|
|
312
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
313
|
+
});
|
|
314
|
+
}
|
|
211
315
|
// src/core/utils/read-field.ts
|
|
212
316
|
/**
|
|
213
317
|
* Case-insensitive field reads for wire payloads.
|
|
@@ -312,95 +416,6 @@ function trimTrailing(value, char) {
|
|
|
312
416
|
while (end > 0 && value[end - 1] === char) end--;
|
|
313
417
|
return value.slice(0, end);
|
|
314
418
|
}
|
|
315
|
-
// src/core/utils/warnings.ts
|
|
316
|
-
const warned = /* @__PURE__ */ new Set();
|
|
317
|
-
/** Log `message` the first time `key` is seen: what it reports doesn't change per call. */
|
|
318
|
-
function warnOnce(key, message) {
|
|
319
|
-
if (warned.has(key)) return;
|
|
320
|
-
warned.add(key);
|
|
321
|
-
getLogger().warn(message);
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* True only in a real browser, where an API key in the config is exposed to users. jsdom defines
|
|
325
|
-
* `window` too, so Node is ruled out by `process.versions.node`, and by jsdom's user-agent marker
|
|
326
|
-
* for setups that hide `process`.
|
|
327
|
-
*/
|
|
328
|
-
function isRealBrowser() {
|
|
329
|
-
if (typeof window === "undefined") return false;
|
|
330
|
-
if (globalThis.process?.versions?.node != null) return false;
|
|
331
|
-
const nav = globalThis.navigator;
|
|
332
|
-
const userAgent = typeof nav?.userAgent === "string" ? nav.userAgent : "";
|
|
333
|
-
if (/jsdom/i.test(userAgent)) return false;
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
function warnIfClientSide(functionName, suppress) {
|
|
337
|
-
if (suppress || !isRealBrowser()) return;
|
|
338
|
-
warnOnce(`client-side:${functionName}`, `Warning: ${functionName} is running on the client side. For better performance and security, consider running this on the server side.`);
|
|
339
|
-
}
|
|
340
|
-
// src/core/compute-fetch/request.ts
|
|
341
|
-
/** `serverUrl` as `validateServerUrl` returns it: no trailing slash. */
|
|
342
|
-
function buildUrl(endpoint, serverUrl) {
|
|
343
|
-
return `${serverUrl}/${endpoint.replace(/^\/+/, "")}`;
|
|
344
|
-
}
|
|
345
|
-
function isLocalhost(serverUrl) {
|
|
346
|
-
const hostname = new URL(serverUrl).hostname.toLowerCase();
|
|
347
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
348
|
-
}
|
|
349
|
-
const DEFAULT_API_KEY_HEADER = "RhinoComputeKey";
|
|
350
|
-
/**
|
|
351
|
-
* Caller headers plus credentials. Every request to the server builds its headers here,
|
|
352
|
-
* so a liveness probe authenticates exactly like the solve it gates.
|
|
353
|
-
*/
|
|
354
|
-
function buildAuthHeaders(auth) {
|
|
355
|
-
return assertSendable(mergeUnder(auth.headers, credentialHeaders(auth)));
|
|
356
|
-
}
|
|
357
|
-
function buildHeaders(requestId, config) {
|
|
358
|
-
const headers = assertSendable(mergeUnder(config.headers, {
|
|
359
|
-
...credentialHeaders(config),
|
|
360
|
-
"X-Request-ID": requestId,
|
|
361
|
-
"Content-Type": "application/json"
|
|
362
|
-
}));
|
|
363
|
-
if (!config.apiKey && !config.authToken && !isLocalhost(config.serverUrl)) warnOnce(`no-auth:${config.serverUrl}`, `[Compute] Request [${requestId}] targets remote server (${config.serverUrl}) but no API key or auth token is configured. Requests may fail or be rate-limited. (warned once per server)`);
|
|
364
|
-
return headers;
|
|
365
|
-
}
|
|
366
|
-
/** `headers` with a JSON `Content-Type` that replaces one of any casing already there. */
|
|
367
|
-
function withJsonContentType(headers) {
|
|
368
|
-
return mergeUnder(headers, { "Content-Type": "application/json" });
|
|
369
|
-
}
|
|
370
|
-
function credentialHeaders(auth) {
|
|
371
|
-
return {
|
|
372
|
-
...auth.authToken && { Authorization: auth.authToken },
|
|
373
|
-
...auth.apiKey && { [auth.apiKeyHeader ?? DEFAULT_API_KEY_HEADER]: auth.apiKey }
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Header names are case-insensitive and fetch joins duplicates, so a caller key
|
|
378
|
-
* matching a transport key in any case is dropped rather than sent alongside it.
|
|
379
|
-
*/
|
|
380
|
-
function mergeUnder(caller, transport) {
|
|
381
|
-
const taken = new Set(Object.keys(transport).map((name) => name.toLowerCase()));
|
|
382
|
-
const kept = Object.entries(caller ?? {}).filter(([name]) => !taken.has(name.toLowerCase()));
|
|
383
|
-
return {
|
|
384
|
-
...Object.fromEntries(kept),
|
|
385
|
-
...transport
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Left to fetch, a bad value becomes a retried TypeError that quotes it, and the value may be the
|
|
390
|
-
* API key. `Headers` throws the same message, so its error is not kept as the cause.
|
|
391
|
-
*/
|
|
392
|
-
function assertSendable(headers) {
|
|
393
|
-
const check = new Headers();
|
|
394
|
-
for (const [name, value] of Object.entries(headers)) try {
|
|
395
|
-
check.set(name, value);
|
|
396
|
-
} catch {
|
|
397
|
-
throw new ComputeError(`Header "${name}" has an invalid name or value`, ErrorCodes.INVALID_CONFIG, { context: { header: name } });
|
|
398
|
-
}
|
|
399
|
-
return headers;
|
|
400
|
-
}
|
|
401
|
-
function generateRequestId() {
|
|
402
|
-
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
403
|
-
}
|
|
404
419
|
// src/core/utils/encoding.ts
|
|
405
420
|
function getNodeBuffer() {
|
|
406
421
|
const buf = globalThis.Buffer;
|
|
@@ -538,12 +553,20 @@ async function readBodyText(response, limit = Infinity) {
|
|
|
538
553
|
truncated: false
|
|
539
554
|
};
|
|
540
555
|
}
|
|
556
|
+
// src/core/types.ts
|
|
557
|
+
/** Routing outcomes the VektorNode fork reports in `Server-Timing`; the parser drops any other. */
|
|
558
|
+
const AFFINITY_OUTCOMES = [
|
|
559
|
+
"hit",
|
|
560
|
+
"claimed",
|
|
561
|
+
"spilled",
|
|
562
|
+
"none"
|
|
563
|
+
];
|
|
541
564
|
// src/core/compute-fetch/server-timing.ts
|
|
542
565
|
/**
|
|
543
566
|
* Parse a W3C Server Timing header, as the solve endpoint emits it:
|
|
544
567
|
* `decode;dur=3, solve;dur=120, gh;dur=110, encode;dur=8, recomputed;desc="4/97"`, or
|
|
545
|
-
* `cache;desc="hit"` when the server answered from its result cache.
|
|
546
|
-
* recognised
|
|
568
|
+
* `cache;desc="hit"` when the server answered from its result cache. A proxy's `affinity` can
|
|
569
|
+
* arrive alone, with no durations. Null when it carries no recognised entry.
|
|
547
570
|
*/
|
|
548
571
|
function parseServerTiming(headerValue) {
|
|
549
572
|
if (!headerValue) return null;
|
|
@@ -568,10 +591,18 @@ function parseServerTiming(headerValue) {
|
|
|
568
591
|
} else if (name === "cache" && param(params, "desc") === "hit") {
|
|
569
592
|
timing.cacheHit = true;
|
|
570
593
|
sawMetric = true;
|
|
594
|
+
} else if (name === "affinity") {
|
|
595
|
+
const outcome = param(params, "desc");
|
|
596
|
+
if (!isAffinity(outcome)) continue;
|
|
597
|
+
timing.affinity = outcome;
|
|
598
|
+
sawMetric = true;
|
|
571
599
|
}
|
|
572
600
|
}
|
|
573
601
|
return sawMetric ? timing : null;
|
|
574
602
|
}
|
|
603
|
+
function isAffinity(value) {
|
|
604
|
+
return AFFINITY_OUTCOMES.includes(value);
|
|
605
|
+
}
|
|
575
606
|
function param(params, key) {
|
|
576
607
|
for (const p of params) {
|
|
577
608
|
const [k, ...v] = p.split("=");
|
|
@@ -1374,6 +1405,6 @@ function saveFile(blob, filename) {
|
|
|
1374
1405
|
a.remove();
|
|
1375
1406
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
1376
1407
|
}
|
|
1377
|
-
export {
|
|
1408
|
+
export { buildAuthHeaders as A, validateServerUrl as C, readField as D, hasField as E, getLogger as F, setLogger as I, ComputeError as L, withJsonContentType as M, warnIfClientSide as N, sleep as O, enableDebugLogging as P, ErrorCodes as R, DEFAULT_BLOCKED_HOST as S, redactUrl as T, readBodyText as _, groupFilesByRoot as a, detectBase64Payload as b, isDefinitionUrl as c, classifyProbeFailure as d, isRedirect as f, raceSignal as g, composeSignal as h, isBase64Flag as i, mergeUnder as j, assertSessionId as k, fetchCompute as l, setResponseWireSize as m, downloadFileDataByRoot as n, subFolderSegments as o, getResponseWireSize as p, extractFilesFromComputeResponse as r, isDefinitionRef as s, downloadFileData as t, validateNumbers as u, base64ByteArray as v, tryParseJson as w, encodeStringToBase64 as x, decodeBase64ToBinary as y, abortedByCaller as z };
|
|
1378
1409
|
|
|
1379
|
-
//# sourceMappingURL=handle-files-
|
|
1410
|
+
//# sourceMappingURL=handle-files-CzqBN3MP.js.map
|