@milaboratories/pl-drivers 1.15.6 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,11 +20,16 @@ import { Readable } from "node:stream";
20
20
  import type { Dispatcher } from "undici";
21
21
  import type { LocalStorageProjection } from "../drivers/types";
22
22
  import { type ContentHandler, RemoteFileDownloader } from "../helpers/download";
23
+ import { isDownloadNetworkError400 } from "../helpers/download_errors";
23
24
  import { validateAbsolute } from "../helpers/validate";
24
25
  import type { DownloadAPI_GetDownloadURL_Response } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol";
25
26
  import { DownloadClient } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client";
26
27
  import type { DownloadApiPaths, DownloadRestClientType } from "../proto-rest";
27
- import { type GetContentOptions } from "@milaboratories/pl-model-common";
28
+ import { type GetContentOptions, type BlobDriverMetrics } from "@milaboratories/pl-model-common";
29
+ import { DownloadUrlCache } from "./download_url_cache";
30
+
31
+ /** Subset of {@link BlobDriverMetrics} owned by the download client (presigned cache + in-flight downloads). */
32
+ type ClientDownloadMetrics = Omit<BlobDriverMetrics, "uncachedRequests" | "uncachedRequestBytes">;
28
33
 
29
34
  /** Gets URLs for downloading from pl-core, parses them and reads or downloads
30
35
  * files locally and from the web. */
@@ -38,6 +43,16 @@ export class ClientDownload {
38
43
  /** Concurrency limiter for local file reads - limit to 32 parallel reads */
39
44
  private readonly localFileReadLimiter = new ConcurrencyLimitingExecutor(32);
40
45
 
46
+ /** Caches presigned download URLs by resource id until they (almost) expire. */
47
+ private readonly urlCache: DownloadUrlCache;
48
+
49
+ /** Active remote downloads; in-flight gauges are summed over this set on read. */
50
+ private readonly inFlight = new Set<{ size: number; received: number }>();
51
+ private presignedUrlCacheHits = 0;
52
+ private presignedUrlCacheMisses = 0;
53
+ private presignedUrlStaleHits = 0;
54
+ private presignedUrlRequestSumLatencyMs = 0;
55
+
41
56
  constructor(
42
57
  wireClientProviderFactory: WireClientProviderFactory,
43
58
  public readonly httpClient: Dispatcher,
@@ -59,6 +74,7 @@ export class ClientDownload {
59
74
  });
60
75
  this.remoteFileDownloader = new RemoteFileDownloader(httpClient);
61
76
  this.localStorageIdsToRoot = newLocalStorageIdsToRoot(localProjections);
77
+ this.urlCache = new DownloadUrlCache(logger);
62
78
  }
63
79
 
64
80
  close() {}
@@ -75,24 +91,103 @@ export class ClientDownload {
75
91
  ops: GetContentOptions,
76
92
  handler: ContentHandler<T>,
77
93
  ): Promise<T> {
78
- const { downloadUrl, headers } = await this.grpcGetDownloadUrl(info, options, ops.signal);
79
-
80
- const remoteHeaders = Object.fromEntries(headers.map(({ name, value }) => [name, value]));
81
- this.logger.info(
82
- `blob ${stringifyWithResourceId(info)} download started, ` +
83
- `url: ${downloadUrl}, ` +
84
- `range: ${JSON.stringify(ops.range ?? null)}`,
85
- );
86
-
87
- const timer = PerfTimer.start();
88
- const result = isLocal(downloadUrl)
89
- ? await this.withLocalFileContent(downloadUrl, ops, handler)
90
- : await this.remoteFileDownloader.withContent(downloadUrl, remoteHeaders, ops, handler);
91
-
92
- this.logger.info(
93
- `blob ${stringifyWithResourceId(info)} download finished, ` + `took: ${timer.elapsed()}`,
94
- );
95
- return result;
94
+ const attempt = async ({
95
+ downloadUrl,
96
+ headers,
97
+ }: DownloadAPI_GetDownloadURL_Response): Promise<T> => {
98
+ const remoteHeaders = Object.fromEntries(headers.map(({ name, value }) => [name, value]));
99
+ this.logger.info(
100
+ `blob ${stringifyWithResourceId(info)} download started, ` +
101
+ `url: ${downloadUrl}, ` +
102
+ `range: ${JSON.stringify(ops.range ?? null)}`,
103
+ );
104
+
105
+ const timer = PerfTimer.start();
106
+ const result = isLocal(downloadUrl)
107
+ ? await this.withLocalFileContent(downloadUrl, ops, handler)
108
+ : await this.withTrackedRemoteContent(downloadUrl, remoteHeaders, ops, handler);
109
+
110
+ this.logger.info(
111
+ `blob ${stringifyWithResourceId(info)} download finished, ` + `took: ${timer.elapsed()}`,
112
+ );
113
+ return result;
114
+ };
115
+
116
+ const cached = this.urlCache.get(info.id);
117
+ if (cached !== undefined) {
118
+ try {
119
+ const result = await attempt(cached);
120
+ this.presignedUrlCacheHits++;
121
+ return result;
122
+ } catch (error) {
123
+ if (!isDownloadNetworkError400(error)) throw error;
124
+ this.urlCache.delete(info.id);
125
+ this.presignedUrlStaleHits++;
126
+ this.logger.info(
127
+ `cached download URL for blob ${stringifyWithResourceId(info)} rejected ` +
128
+ `(status ${error.statusCode}), re-fetching`,
129
+ );
130
+ }
131
+ } else {
132
+ this.presignedUrlCacheMisses++;
133
+ }
134
+
135
+ const urlFetchStartMs = performance.now();
136
+ const fresh = await this.grpcGetDownloadUrl(info, options, ops.signal);
137
+ this.presignedUrlRequestSumLatencyMs += performance.now() - urlFetchStartMs;
138
+ this.urlCache.set(info.id, fresh);
139
+ return await attempt(fresh);
140
+ }
141
+
142
+ /** Download-client-owned slice of {@link BlobDriverMetrics}: presigned-URL cache + in-flight downloads. */
143
+ metrics(): ClientDownloadMetrics {
144
+ let inFlightBytesTotal = 0;
145
+ let inFlightBytesReceived = 0;
146
+ for (const rec of this.inFlight) {
147
+ inFlightBytesTotal += rec.size;
148
+ inFlightBytesReceived += rec.received;
149
+ }
150
+ return {
151
+ downloadsInFlight: this.inFlight.size,
152
+ inFlightBytesTotal,
153
+ inFlightBytesReceived,
154
+ presignedUrlCacheHits: this.presignedUrlCacheHits,
155
+ presignedUrlCacheMisses: this.presignedUrlCacheMisses,
156
+ presignedUrlStaleHits: this.presignedUrlStaleHits,
157
+ presignedUrlRequestSumLatencyMs: this.presignedUrlRequestSumLatencyMs,
158
+ };
159
+ }
160
+
161
+ /** Wraps a remote download so it appears in the in-flight gauges and its received bytes are counted live. */
162
+ private async withTrackedRemoteContent<T>(
163
+ url: string,
164
+ headers: Record<string, string>,
165
+ ops: GetContentOptions,
166
+ handler: ContentHandler<T>,
167
+ ): Promise<T> {
168
+ const rec = { size: 0, received: 0 };
169
+ this.inFlight.add(rec);
170
+ try {
171
+ return await this.remoteFileDownloader.withContent(
172
+ url,
173
+ headers,
174
+ ops,
175
+ async (content, size) => {
176
+ rec.size = size;
177
+ const counted = content.pipeThrough(
178
+ new TransformStream<Uint8Array, Uint8Array>({
179
+ transform: (chunk, controller) => {
180
+ rec.received += chunk.byteLength;
181
+ controller.enqueue(chunk);
182
+ },
183
+ }),
184
+ );
185
+ return await handler(counted, size);
186
+ },
187
+ );
188
+ } finally {
189
+ this.inFlight.delete(rec);
190
+ }
96
191
  }
97
192
 
98
193
  async withLocalFileContent<T>(
@@ -0,0 +1,68 @@
1
+ import { test, expect, describe, vi, afterEach } from "vitest";
2
+ import { downloadUrlCacheTtlMs, extractUrlExpiryMs } from "./download_url_cache";
3
+
4
+ // 2026-06-16T12:00:00Z, expressed timezone-independently.
5
+ const SIGNED_AT = Date.UTC(2026, 5, 16, 12, 0, 0);
6
+
7
+ describe("extractUrlExpiryMs", () => {
8
+ test("AWS SigV4 URL (S3 / FS remote driver): X-Amz-Date + X-Amz-Expires", () => {
9
+ const url =
10
+ "https://bucket.s3.amazonaws.com/key?X-Amz-Algorithm=AWS4-HMAC-SHA256" +
11
+ "&X-Amz-Date=20260616T120000Z&X-Amz-Expires=3600&X-Amz-Signature=deadbeef";
12
+ expect(extractUrlExpiryMs(url)).toBe(SIGNED_AT + 3600_000);
13
+ });
14
+
15
+ test("GCS V4 signed URL: X-Goog-Date + X-Goog-Expires", () => {
16
+ const url =
17
+ "https://storage.googleapis.com/bucket/key?X-Goog-Algorithm=GOOG4-RSA-SHA256" +
18
+ "&X-Goog-Date=20260616T120000Z&X-Goog-Expires=600&X-Goog-Signature=deadbeef";
19
+ expect(extractUrlExpiryMs(url)).toBe(SIGNED_AT + 600_000);
20
+ });
21
+
22
+ test("the encoded timestamp is UTC, independent of host timezone", () => {
23
+ // The compact form ends with 'Z' (Zulu/UTC); the result must equal the
24
+ // UTC epoch and never shift by the runner's local offset.
25
+ const url = "https://x/key?X-Amz-Date=20260101T000000Z&X-Amz-Expires=1&X-Amz-Signature=x";
26
+ expect(extractUrlExpiryMs(url)).toBe(Date.UTC(2026, 0, 1, 0, 0, 0) + 1000);
27
+ });
28
+
29
+ test("local storage:// URL has no encoded expiry", () => {
30
+ expect(extractUrlExpiryMs("storage://main/some/relative/path.json")).toBeUndefined();
31
+ });
32
+
33
+ test("plain URL without presign params has no expiry", () => {
34
+ expect(extractUrlExpiryMs("https://example.com/file?foo=bar")).toBeUndefined();
35
+ });
36
+
37
+ test("malformed presign params yield null (present but invalid - do not cache)", () => {
38
+ const url = "https://x/key?X-Amz-Date=not-a-date&X-Amz-Expires=3600&X-Amz-Signature=x";
39
+ expect(extractUrlExpiryMs(url)).toBeNull();
40
+ });
41
+
42
+ test("non-URL input yields no expiry", () => {
43
+ expect(extractUrlExpiryMs("not a url")).toBeUndefined();
44
+ });
45
+ });
46
+
47
+ describe("downloadUrlCacheTtlMs", () => {
48
+ // downloadUrlCacheTtlMs reads Date.now(); pin it so the TTL math is deterministic.
49
+ afterEach(() => vi.useRealTimers());
50
+
51
+ test("subtracts the 30s safety margin from the encoded expiry", () => {
52
+ vi.useFakeTimers();
53
+ vi.setSystemTime(SIGNED_AT); // now = signing time -> raw remaining 3600s, minus 30s margin.
54
+ const url = "https://x/key?X-Amz-Date=20260616T120000Z&X-Amz-Expires=3600&X-Amz-Signature=x";
55
+ expect(downloadUrlCacheTtlMs(url)).toBe(3600_000 - 30_000);
56
+ });
57
+
58
+ test("returns non-positive when within the safety margin of expiry", () => {
59
+ vi.useFakeTimers();
60
+ vi.setSystemTime(SIGNED_AT + 50_000); // 10s of life left -> after a 30s margin, not worth caching.
61
+ const url = "https://x/key?X-Amz-Date=20260616T120000Z&X-Amz-Expires=60&X-Amz-Signature=x";
62
+ expect(downloadUrlCacheTtlMs(url)).toBeLessThanOrEqual(0);
63
+ });
64
+
65
+ test("falls back to a positive default TTL for URLs without encoded expiry", () => {
66
+ expect(downloadUrlCacheTtlMs("storage://main/path")).toBeGreaterThan(0);
67
+ });
68
+ });
@@ -0,0 +1,118 @@
1
+ import { LRUCache } from "lru-cache";
2
+ import type { SignedResourceId } from "@milaboratories/pl-client";
3
+ import type { MiLogger } from "@milaboratories/ts-helpers";
4
+ import type { DownloadAPI_GetDownloadURL_Response } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol";
5
+
6
+ /**
7
+ * Safety margin subtracted from the encoded URL expiry. Covers clock skew
8
+ * between this host and pl-core (the timestamp is the server's signing clock,
9
+ * not ours) plus in-flight time between signing and use.
10
+ */
11
+ const SAFETY_MARGIN_MS = 30_000;
12
+
13
+ /**
14
+ * TTL applied to download URLs that carry no expiry in their query string -
15
+ * notably local `storage://` URLs, which are deterministic for a given resource
16
+ * and effectively never expire. Bounded so a changed storage projection is
17
+ * eventually re-resolved.
18
+ */
19
+ const NO_EXPIRY_DEFAULT_TTL_MS = 60 * 60 * 1000; // 1h
20
+
21
+ /** Max number of cached {url, headers} entries. Each entry is tiny. */
22
+ const DEFAULT_MAX_ENTRIES = 4096;
23
+
24
+ /**
25
+ * Extracts the absolute expiry time (epoch ms) encoded in a presigned download
26
+ * URL, or `undefined` if the URL carries no expiry.
27
+ *
28
+ * Supports AWS SigV4 (`X-Amz-Date` + `X-Amz-Expires`, used by pl's S3 and FS
29
+ * remote drivers) and GCS V4 (`X-Goog-Date` + `X-Goog-Expires`). Both encode
30
+ * the date as a compact ISO-8601 UTC timestamp `YYYYMMDDTHHMMSSZ` (trailing `Z`
31
+ * = Zulu/UTC; verified against pl `util/storage/v4sign/presigner.go`) and the
32
+ * lifetime as integer seconds.
33
+ */
34
+ export function extractUrlExpiryMs(url: string): number | null | undefined {
35
+ let query: URLSearchParams;
36
+ try {
37
+ query = new URL(url).searchParams;
38
+ } catch {
39
+ return undefined;
40
+ }
41
+
42
+ for (const prefix of ["X-Amz", "X-Goog"]) {
43
+ const date = query.get(`${prefix}-Date`);
44
+ const expires = query.get(`${prefix}-Expires`);
45
+ if (date === null || expires === null) continue;
46
+
47
+ const signedAtMs = parseCompactIso8601Utc(date);
48
+ const expiresSec = Number(expires);
49
+ if (signedAtMs === undefined || !Number.isFinite(expiresSec) || expiresSec <= 0) return null;
50
+
51
+ return signedAtMs + expiresSec * 1000;
52
+ }
53
+
54
+ return undefined;
55
+ }
56
+
57
+ /**
58
+ * Parses a compact ISO-8601 UTC timestamp `YYYYMMDDTHHMMSSZ` into epoch ms.
59
+ * `new Date()` cannot parse the compact form, so we expand it to the extended
60
+ * form `YYYY-MM-DDTHH:MM:SSZ`; the trailing `Z` makes it UTC regardless of the
61
+ * host's local timezone.
62
+ */
63
+ function parseCompactIso8601Utc(value: string): number | undefined {
64
+ const m = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(value);
65
+ if (m === null) return undefined;
66
+ const [, y, mo, d, h, mi, s] = m;
67
+ const ms = Date.parse(`${y}-${mo}-${d}T${h}:${mi}:${s}Z`);
68
+ return Number.isNaN(ms) ? undefined : ms;
69
+ }
70
+
71
+ /**
72
+ * Computes the cache TTL (ms from now) for a download URL, with the safety
73
+ * margin already subtracted. Returns a non-positive number when the URL is
74
+ * already within the margin of expiring - the caller should then skip caching.
75
+ */
76
+ export function downloadUrlCacheTtlMs(url: string): number {
77
+ const expiry = extractUrlExpiryMs(url);
78
+ if (expiry === null) return 0;
79
+ if (expiry === undefined) return NO_EXPIRY_DEFAULT_TTL_MS;
80
+ return expiry - Date.now() - SAFETY_MARGIN_MS;
81
+ }
82
+
83
+ /**
84
+ * LRU cache of `GetDownloadURL` responses keyed by `SignedResourceId`. Each
85
+ * entry's TTL is derived from the expiry encoded in the presigned URL (minus a
86
+ * safety margin), so an entry never outlives the URL it holds.
87
+ *
88
+ * Note: the key intentionally omits `isInternalUse` because the only caller
89
+ * always requests `isInternalUse: false`. Revisit if that ever varies.
90
+ */
91
+ export class DownloadUrlCache {
92
+ private readonly cache: LRUCache<SignedResourceId, DownloadAPI_GetDownloadURL_Response>;
93
+
94
+ constructor(
95
+ private readonly logger: MiLogger,
96
+ maxEntries: number = DEFAULT_MAX_ENTRIES,
97
+ ) {
98
+ this.cache = new LRUCache<SignedResourceId, DownloadAPI_GetDownloadURL_Response>({
99
+ max: maxEntries,
100
+ // URL expiry is absolute, not sliding - do not extend TTL on access.
101
+ updateAgeOnGet: false,
102
+ });
103
+ }
104
+
105
+ get(key: SignedResourceId): DownloadAPI_GetDownloadURL_Response | undefined {
106
+ return this.cache.get(key);
107
+ }
108
+
109
+ set(key: SignedResourceId, value: DownloadAPI_GetDownloadURL_Response): void {
110
+ const ttl = downloadUrlCacheTtlMs(value.downloadUrl);
111
+ if (ttl <= 0) return; // Cache miss.
112
+ this.cache.set(key, value, { ttl });
113
+ }
114
+
115
+ delete(key: SignedResourceId): void {
116
+ this.cache.delete(key);
117
+ }
118
+ }
@@ -227,10 +227,54 @@ test("should get undefined when releasing a blob from a small cache and the blob
227
227
  });
228
228
  });
229
229
 
230
+ test("getContentDirect returns the same content but bypasses the ranges cache", async () => {
231
+ await TestHelpers.withTempRoot(async (client) => {
232
+ const dir = await fsp.mkdtemp(path.join(os.tmpdir(), "test-download-direct-"));
233
+ const rangesCacheDir = await fsp.mkdtemp(path.join(os.tmpdir(), "test-download-ranges-"));
234
+
235
+ const driver = await genDriver(client, dir, rangesCacheDir, genSigner());
236
+ const downloadable = await makeDownloadableBlobFromAssets(client, fileName);
237
+
238
+ // On-demand (remote) handle - this is the path that uses the ranges cache.
239
+ const c = driver.getOnDemandBlob(downloadable);
240
+ const blob = await c.getValue();
241
+ expect(blob).toBeDefined();
242
+ expect(blob.size).toEqual(3);
243
+
244
+ const baseline = await dirSizeBytes(rangesCacheDir);
245
+
246
+ // Direct reads return the correct bytes (full + range) ...
247
+ expect((await driver.getContentDirect(blob.handle))?.toString()).toBe("42\n");
248
+ expect(
249
+ (await driver.getContentDirect(blob.handle, { range: { from: 0, to: 2 } }))?.toString(),
250
+ ).toBe("42");
251
+
252
+ // ... and leave the ranges cache untouched (cache writes are fire-and-forget, so settle first).
253
+ await scheduler.wait(100);
254
+ expect(await dirSizeBytes(rangesCacheDir)).toBe(baseline);
255
+
256
+ // A normal cached read of the same handle DOES populate the ranges cache.
257
+ expect((await driver.getContent(blob.handle))?.toString()).toBe("42\n");
258
+ await scheduler.wait(100);
259
+ expect(await dirSizeBytes(rangesCacheDir)).toBeGreaterThan(baseline);
260
+ });
261
+ });
262
+
230
263
  function genSigner() {
231
264
  return new HmacSha256Signer(HmacSha256Signer.generateSecret());
232
265
  }
233
266
 
267
+ /** Total bytes of all files under `dir` (recursive), for asserting cache population. */
268
+ async function dirSizeBytes(dir: string): Promise<number> {
269
+ let total = 0;
270
+ for (const entry of await fsp.readdir(dir, { withFileTypes: true })) {
271
+ const full = path.join(dir, entry.name);
272
+ if (entry.isDirectory()) total += await dirSizeBytes(full);
273
+ else if (entry.isFile()) total += (await fsp.stat(full)).size;
274
+ }
275
+ return total;
276
+ }
277
+
234
278
  async function genDriver(
235
279
  client: PlClient,
236
280
  dir: string,
@@ -9,6 +9,7 @@ import {
9
9
  import type {
10
10
  AnyLogHandle,
11
11
  BlobDriver,
12
+ BlobDriverMetrics,
12
13
  ContentHandler,
13
14
  GetContentOptions,
14
15
  LocalBlobHandle,
@@ -101,6 +102,10 @@ export class DownloadDriver implements BlobDriver, AsyncDisposable {
101
102
 
102
103
  private readonly saveDir: string;
103
104
 
105
+ /** Downloads that bypassed the ranges cache; counted when issued. */
106
+ private uncachedRequests = 0;
107
+ private uncachedRequestBytes = 0;
108
+
104
109
  constructor(
105
110
  private readonly logger: MiLogger,
106
111
  private readonly clientDownload: ClientDownload,
@@ -322,9 +327,50 @@ export class DownloadDriver implements BlobDriver, AsyncDisposable {
322
327
  }
323
328
  }
324
329
 
330
+ return await this.getContentImpl({ handle, options });
331
+ }
332
+
333
+ /**
334
+ * Same as {@link getContent}, but bypasses the ranges cache entirely (no read, no write).
335
+ * For local handles this is identical to {@link getContent}, since local content never
336
+ * uses the ranges cache.
337
+ */
338
+ public async getContentDirect(
339
+ handle: LocalBlobHandle | RemoteBlobHandle,
340
+ options?: GetContentOptions,
341
+ ): Promise<Uint8Array> {
342
+ return await this.getContentImpl({
343
+ handle,
344
+ options: options ?? {},
345
+ bypassRangesCache: true,
346
+ });
347
+ }
348
+
349
+ /**
350
+ * Operational metrics for a monitoring panel. Serv cache metrics are reported separately
351
+ * (different owner) — the panel composes both.
352
+ */
353
+ public getMetrics(): BlobDriverMetrics {
354
+ return {
355
+ uncachedRequests: this.uncachedRequests,
356
+ uncachedRequestBytes: this.uncachedRequestBytes,
357
+ ...this.clientDownload.metrics(),
358
+ };
359
+ }
360
+
361
+ private async getContentImpl({
362
+ handle,
363
+ options,
364
+ bypassRangesCache = false,
365
+ }: {
366
+ handle: LocalBlobHandle | RemoteBlobHandle;
367
+ options: GetContentOptions;
368
+ bypassRangesCache?: boolean;
369
+ }): Promise<Uint8Array> {
325
370
  const request = () =>
326
371
  this.withContent(handle, {
327
372
  ...options,
373
+ bypassRangesCache,
328
374
  handler: async (content) => {
329
375
  const chunks: Uint8Array[] = [];
330
376
  for await (const chunk of content) {
@@ -350,9 +396,10 @@ export class DownloadDriver implements BlobDriver, AsyncDisposable {
350
396
  handle: LocalBlobHandle | RemoteBlobHandle,
351
397
  options: GetContentOptions & {
352
398
  handler: ContentHandler<T>;
399
+ bypassRangesCache?: boolean;
353
400
  },
354
401
  ): Promise<T> {
355
- const { range, signal, handler } = options;
402
+ const { range, signal, handler, bypassRangesCache } = options;
356
403
 
357
404
  if (isLocalBlobHandle(handle)) {
358
405
  return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });
@@ -362,16 +409,32 @@ export class DownloadDriver implements BlobDriver, AsyncDisposable {
362
409
  const result = parseRemoteHandle(handle, this.signer);
363
410
 
364
411
  const key = blobKey(result.info.id);
365
- const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });
412
+ const filePath = bypassRangesCache
413
+ ? undefined
414
+ : await this.rangesCache.get(key, range ?? { from: 0, to: result.size });
366
415
  signal?.throwIfAborted();
367
416
 
368
417
  if (filePath) return await withFileContent({ path: filePath, range, signal, handler });
369
418
 
419
+ if (bypassRangesCache) this.uncachedRequests++;
420
+
370
421
  return await this.clientDownload.withBlobContent(
371
422
  result.info,
372
423
  { signal },
373
424
  options,
374
425
  async (content, size) => {
426
+ if (bypassRangesCache) {
427
+ const counted = content.pipeThrough(
428
+ new TransformStream<Uint8Array, Uint8Array>({
429
+ transform: (chunk, controller) => {
430
+ this.uncachedRequestBytes += chunk.byteLength;
431
+ controller.enqueue(chunk);
432
+ },
433
+ }),
434
+ );
435
+ return await handler(counted, size);
436
+ }
437
+
375
438
  const [handlerStream, cacheStream] = content.tee();
376
439
 
377
440
  const handlerPromise = handler(handlerStream, size);