@aztec/blob-client 0.0.1-commit.c2eed6949 → 0.0.1-commit.c52d6e7
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/dest/archive/blobscan_archive_client.d.ts +8 -92
- package/dest/archive/blobscan_archive_client.d.ts.map +1 -1
- package/dest/client/config.d.ts +5 -1
- package/dest/client/config.d.ts.map +1 -1
- package/dest/client/config.js +12 -2
- package/dest/client/factory.d.ts +3 -5
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +8 -2
- package/dest/client/http.d.ts +11 -9
- package/dest/client/http.d.ts.map +1 -1
- package/dest/client/http.js +126 -82
- package/dest/client/interface.d.ts +2 -4
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/filestore/factory.d.ts +5 -4
- package/dest/filestore/factory.d.ts.map +1 -1
- package/dest/filestore/factory.js +4 -4
- package/package.json +8 -8
- package/src/client/config.ts +18 -2
- package/src/client/factory.ts +10 -3
- package/src/client/http.ts +139 -87
- package/src/client/interface.ts +1 -3
- package/src/filestore/factory.ts +7 -2
package/src/client/http.ts
CHANGED
|
@@ -30,6 +30,9 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
30
30
|
/** Cached beacon slot duration in seconds. Fetched once at startup. */
|
|
31
31
|
private beaconSecondsPerSlot?: number;
|
|
32
32
|
|
|
33
|
+
/** Indexes of consensus hosts that serve blob sidecars (supernodes). Populated by testSources(). */
|
|
34
|
+
private superNodeHostIndexes?: Set<number>;
|
|
35
|
+
|
|
33
36
|
constructor(
|
|
34
37
|
config?: BlobClientConfig,
|
|
35
38
|
private readonly opts: {
|
|
@@ -100,6 +103,8 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
100
103
|
let archiveSources = 0;
|
|
101
104
|
let blobSinks = 0;
|
|
102
105
|
|
|
106
|
+
const detectedSuperNodes = new Set<number>();
|
|
107
|
+
|
|
103
108
|
if (l1ConsensusHostUrls && l1ConsensusHostUrls.length > 0) {
|
|
104
109
|
for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
|
|
105
110
|
const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
|
|
@@ -134,9 +139,12 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
134
139
|
const blobRes = await this.fetch(blobUrl, blobOptions);
|
|
135
140
|
if (blobRes.ok) {
|
|
136
141
|
this.log.info(`L1 consensus host serves blob sidecars (supernode)`, { l1ConsensusHostUrl });
|
|
142
|
+
detectedSuperNodes.add(l1ConsensusHostIndex);
|
|
137
143
|
consensusSuperNodes++;
|
|
138
144
|
} else {
|
|
139
|
-
this.log.info(`L1 consensus host does not serve blob sidecars`, {
|
|
145
|
+
this.log.info(`L1 consensus host does not serve blob sidecars, skipping for blob fetching`, {
|
|
146
|
+
l1ConsensusHostUrl,
|
|
147
|
+
});
|
|
140
148
|
consensusNonSuperNodes++;
|
|
141
149
|
}
|
|
142
150
|
} else {
|
|
@@ -149,6 +157,8 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
this.superNodeHostIndexes = detectedSuperNodes;
|
|
161
|
+
|
|
152
162
|
if (this.archiveClient) {
|
|
153
163
|
try {
|
|
154
164
|
const latest = await this.archiveClient.getLatestBlock();
|
|
@@ -218,18 +228,15 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
/**
|
|
221
|
-
* Get the blob sidecar
|
|
231
|
+
* Get the blob sidecar.
|
|
222
232
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* Source ordering depends on sync state:
|
|
227
|
-
* - Historical sync: blob client → FileStore → L1 consensus → Archive
|
|
228
|
-
* - Near tip sync: blob client → FileStore → L1 consensus → FileStore (with retries) → Archive (eg blobscan)
|
|
233
|
+
* Alternates between two primary sources (consensus and filestore) in a retry loop,
|
|
234
|
+
* then falls back to archive if blobs are still missing. The order of the primary
|
|
235
|
+
* sources is configurable via `blobPreferFilestores`.
|
|
229
236
|
*
|
|
230
237
|
* @param blockHash - The block hash
|
|
231
238
|
* @param blobHashes - The blob hashes to fetch
|
|
232
|
-
* @param opts - Options
|
|
239
|
+
* @param opts - Options for slot resolution
|
|
233
240
|
* @returns The blobs
|
|
234
241
|
*/
|
|
235
242
|
public async getBlobSidecar(
|
|
@@ -242,12 +249,11 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
242
249
|
return [];
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
const isHistoricalSync = opts?.isHistoricalSync ?? false;
|
|
246
252
|
// Accumulate blobs across sources, preserving order and handling duplicates
|
|
247
253
|
// resultBlobs[i] will contain the blob for blobHashes[i], or undefined if not yet found
|
|
248
254
|
const resultBlobs: (Blob | undefined)[] = new Array(blobHashes.length).fill(undefined);
|
|
249
255
|
|
|
250
|
-
// Helper to get
|
|
256
|
+
// Helper to get missing blob hashes that we still need to fetch
|
|
251
257
|
const getMissingBlobHashes = (): Buffer[] =>
|
|
252
258
|
blobHashes
|
|
253
259
|
.map((bh, i) => (resultBlobs[i] === undefined ? bh : undefined))
|
|
@@ -276,84 +282,60 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
276
282
|
return blobs;
|
|
277
283
|
};
|
|
278
284
|
|
|
279
|
-
const { l1ConsensusHostUrls } = this.config;
|
|
280
|
-
|
|
281
285
|
const ctx = { blockHash, blobHashes: blobHashes.map(bufferToHex) };
|
|
282
286
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
287
|
+
// Lazily resolve the slot number — only resolved when consensus hosts are actually tried.
|
|
288
|
+
let slotNumber: number | undefined;
|
|
289
|
+
let slotResolved = false;
|
|
290
|
+
const getSlotNumber = async (): Promise<number | undefined> => {
|
|
291
|
+
if (!slotResolved) {
|
|
292
|
+
slotNumber = await this.resolveSlotNumber(blockHash, opts);
|
|
293
|
+
slotResolved = true;
|
|
288
294
|
}
|
|
289
|
-
|
|
295
|
+
return slotNumber;
|
|
296
|
+
};
|
|
290
297
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const consensusCtx = { l1ConsensusHostUrls, ...ctx };
|
|
295
|
-
this.log.trace(`Attempting to get slot number for block hash`, consensusCtx);
|
|
296
|
-
const slotNumber = await this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
|
|
297
|
-
this.log.debug(`Got slot number ${slotNumber} from consensus host for querying blobs`, consensusCtx);
|
|
298
|
-
|
|
299
|
-
if (slotNumber) {
|
|
300
|
-
let l1ConsensusHostUrl: string;
|
|
301
|
-
for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
|
|
302
|
-
const missingHashes = getMissingBlobHashes();
|
|
303
|
-
if (missingHashes.length === 0) {
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
298
|
+
// Build the two source-try functions. The order depends on the config.
|
|
299
|
+
const tryConsensus = () => this.tryConsensusHosts(getSlotNumber, getMissingBlobHashes, fillResults, ctx);
|
|
300
|
+
const tryFilestores = () => this.tryFileStores(getMissingBlobHashes, fillResults, ctx);
|
|
306
301
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
slotNumber,
|
|
310
|
-
l1ConsensusHostUrl,
|
|
311
|
-
...ctx,
|
|
312
|
-
});
|
|
313
|
-
const blobs = await this.getBlobsFromHost(
|
|
314
|
-
l1ConsensusHostUrl,
|
|
315
|
-
slotNumber,
|
|
316
|
-
l1ConsensusHostIndex,
|
|
317
|
-
getMissingBlobHashes(),
|
|
318
|
-
);
|
|
319
|
-
const result = await fillResults(blobs);
|
|
320
|
-
this.log.debug(
|
|
321
|
-
`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${blobHashes.length})`,
|
|
322
|
-
{ slotNumber, l1ConsensusHostUrl, ...ctx },
|
|
323
|
-
);
|
|
324
|
-
if (result.length === blobHashes.length) {
|
|
325
|
-
return returnWithCallback(result);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
302
|
+
const preferFilestores = this.config.blobPreferFilestores ?? false;
|
|
303
|
+
const [trySourceA, trySourceB] = preferFilestores ? [tryFilestores, tryConsensus] : [tryConsensus, tryFilestores];
|
|
330
304
|
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
305
|
+
// Historical sync: blobs should already exist, use shorter backoff for transient errors.
|
|
306
|
+
// Near-tip sync: blobs may still be uploading, use longer backoff for eventual consistency.
|
|
307
|
+
const isHistoricalSync = opts?.isHistoricalSync ?? false;
|
|
308
|
+
const backoff = isHistoricalSync ? [1, 1] : [1, 1, 1, 2, 2];
|
|
309
|
+
|
|
310
|
+
// Retry loop: alternate between the two primary sources with backoff.
|
|
311
|
+
try {
|
|
312
|
+
await retry(
|
|
313
|
+
async () => {
|
|
314
|
+
if (getMissingBlobHashes().length > 0) {
|
|
315
|
+
await trySourceA();
|
|
316
|
+
}
|
|
317
|
+
if (getMissingBlobHashes().length > 0) {
|
|
318
|
+
await trySourceB();
|
|
319
|
+
}
|
|
320
|
+
if (getMissingBlobHashes().length > 0) {
|
|
321
|
+
throw new Error('Still missing blobs after trying all primary sources');
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
'blob retrieval',
|
|
325
|
+
makeBackoff(backoff),
|
|
326
|
+
this.log,
|
|
327
|
+
true, // failSilently — expected during eventual consistency
|
|
328
|
+
);
|
|
329
|
+
return returnWithCallback(getFilledBlobs());
|
|
330
|
+
} catch {
|
|
331
|
+
// Exhausted retries, continue to archive fallback
|
|
351
332
|
}
|
|
352
333
|
|
|
353
|
-
|
|
354
|
-
|
|
334
|
+
// Archive fallback
|
|
335
|
+
const missingAfterPrimary = getMissingBlobHashes();
|
|
336
|
+
if (missingAfterPrimary.length > 0 && this.archiveClient) {
|
|
355
337
|
const archiveCtx = { archiveUrl: this.archiveClient.getBaseUrl(), ...ctx };
|
|
356
|
-
this.log.trace(`Attempting to get ${
|
|
338
|
+
this.log.trace(`Attempting to get ${missingAfterPrimary.length} blobs from archive`, archiveCtx);
|
|
357
339
|
const allBlobs = await this.archiveClient.getBlobsFromBlock(blockHash);
|
|
358
340
|
if (!allBlobs) {
|
|
359
341
|
this.log.debug('No blobs found from archive client', archiveCtx);
|
|
@@ -375,7 +357,7 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
375
357
|
this.log.warn(
|
|
376
358
|
`Failed to fetch all blobs for ${blockHash} from all blob sources (got ${result.length}/${blobHashes.length})`,
|
|
377
359
|
{
|
|
378
|
-
l1ConsensusHostUrls,
|
|
360
|
+
l1ConsensusHostUrls: this.config.l1ConsensusHostUrls,
|
|
379
361
|
archiveUrl: this.archiveClient?.getBaseUrl(),
|
|
380
362
|
fileStoreUrls: this.fileStoreClients.map(c => c.getBaseUrl()),
|
|
381
363
|
},
|
|
@@ -384,6 +366,71 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
384
366
|
return returnWithCallback(result);
|
|
385
367
|
}
|
|
386
368
|
|
|
369
|
+
/** Resolves the beacon slot number for the given block hash. Returns undefined if no consensus hosts. */
|
|
370
|
+
private resolveSlotNumber(
|
|
371
|
+
blockHash: `0x${string}`,
|
|
372
|
+
opts?: GetBlobSidecarOptions,
|
|
373
|
+
): Promise<number | undefined> | undefined {
|
|
374
|
+
const { l1ConsensusHostUrls } = this.config;
|
|
375
|
+
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
// If no supernodes, no point resolving the slot
|
|
379
|
+
if (this.superNodeHostIndexes && this.superNodeHostIndexes.size === 0) {
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
return this.getSlotNumber(blockHash, opts?.parentBeaconBlockRoot, opts?.l1BlockTimestamp);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Try all supernode consensus hosts for blob sidecars.
|
|
387
|
+
* Skips hosts that were detected as non-supernodes during testSources().
|
|
388
|
+
*/
|
|
389
|
+
private async tryConsensusHosts(
|
|
390
|
+
getSlotNumber: () => Promise<number | undefined>,
|
|
391
|
+
getMissingBlobHashes: () => Buffer[],
|
|
392
|
+
fillResults: (blobs: BlobJson[]) => Promise<Blob[]>,
|
|
393
|
+
ctx: { blockHash: string; blobHashes: string[] },
|
|
394
|
+
): Promise<void> {
|
|
395
|
+
const { l1ConsensusHostUrls } = this.config;
|
|
396
|
+
if (!l1ConsensusHostUrls || l1ConsensusHostUrls.length === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const slotNumber = await getSlotNumber();
|
|
401
|
+
if (!slotNumber) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (let l1ConsensusHostIndex = 0; l1ConsensusHostIndex < l1ConsensusHostUrls.length; l1ConsensusHostIndex++) {
|
|
406
|
+
const missingHashes = getMissingBlobHashes();
|
|
407
|
+
if (missingHashes.length === 0) {
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Skip non-supernode hosts if we've already detected supernodes
|
|
412
|
+
if (this.superNodeHostIndexes && !this.superNodeHostIndexes.has(l1ConsensusHostIndex)) {
|
|
413
|
+
this.log.trace(`Skipping non-supernode consensus host`, {
|
|
414
|
+
l1ConsensusHostUrl: l1ConsensusHostUrls[l1ConsensusHostIndex],
|
|
415
|
+
});
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const l1ConsensusHostUrl = l1ConsensusHostUrls[l1ConsensusHostIndex];
|
|
420
|
+
this.log.trace(`Attempting to get ${missingHashes.length} blobs from consensus host`, {
|
|
421
|
+
slotNumber,
|
|
422
|
+
l1ConsensusHostUrl,
|
|
423
|
+
...ctx,
|
|
424
|
+
});
|
|
425
|
+
const blobs = await this.getBlobsFromHost(l1ConsensusHostUrl, slotNumber, l1ConsensusHostIndex, missingHashes);
|
|
426
|
+
const result = await fillResults(blobs);
|
|
427
|
+
this.log.debug(
|
|
428
|
+
`Got ${blobs.length} blobs from consensus host (total: ${result.length}/${ctx.blobHashes.length})`,
|
|
429
|
+
{ slotNumber, l1ConsensusHostUrl, ...ctx },
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
387
434
|
/**
|
|
388
435
|
* Try all filestores once (shuffled for load distribution).
|
|
389
436
|
* @param getMissingBlobHashes - Function to get remaining blob hashes to fetch
|
|
@@ -499,16 +546,17 @@ export class HttpBlobClient implements BlobClientInterface {
|
|
|
499
546
|
baseUrl += `?${params.toString()}`;
|
|
500
547
|
}
|
|
501
548
|
|
|
502
|
-
const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
503
|
-
this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, { url, ...options });
|
|
504
|
-
|
|
549
|
+
const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
550
|
+
this.log.debug(`Fetching blob sidecar for ${blockHashOrSlot}`, { url: logSafeUrl, ...options });
|
|
551
|
+
// No retry here — this is called inside the main retry loop in getBlobSidecar
|
|
552
|
+
return fetch(url, options);
|
|
505
553
|
}
|
|
506
554
|
|
|
507
555
|
private async getLatestSlotNumber(hostUrl: string, l1ConsensusHostIndex?: number): Promise<number | undefined> {
|
|
508
556
|
try {
|
|
509
557
|
const baseUrl = `${hostUrl}/eth/v1/beacon/headers/head`;
|
|
510
|
-
const { url, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
511
|
-
this.log.debug(`Fetching latest slot number`, { url, ...options });
|
|
558
|
+
const { url, logSafeUrl, ...options } = getBeaconNodeFetchOptions(baseUrl, this.config, l1ConsensusHostIndex);
|
|
559
|
+
this.log.debug(`Fetching latest slot number`, { url: logSafeUrl, ...options });
|
|
512
560
|
const res = await this.fetch(url, options);
|
|
513
561
|
if (res.ok) {
|
|
514
562
|
const body = await res.json();
|
|
@@ -771,12 +819,16 @@ function getBeaconNodeFetchOptions(url: string, config: BlobClientConfig, l1Cons
|
|
|
771
819
|
l1ConsensusHostApiKeyHeaders[l1ConsensusHostIndex];
|
|
772
820
|
|
|
773
821
|
let formattedUrl = url;
|
|
822
|
+
let logSafeUrl = url;
|
|
774
823
|
if (l1ConsensusHostApiKey && l1ConsensusHostApiKey.getValue() !== '' && !l1ConsensusHostApiKeyHeader) {
|
|
775
|
-
|
|
824
|
+
const separator = formattedUrl.includes('?') ? '&' : '?';
|
|
825
|
+
formattedUrl += `${separator}key=${l1ConsensusHostApiKey.getValue()}`;
|
|
826
|
+
logSafeUrl += `${separator}key=[REDACTED]`;
|
|
776
827
|
}
|
|
777
828
|
|
|
778
829
|
return {
|
|
779
830
|
url: formattedUrl,
|
|
831
|
+
logSafeUrl,
|
|
780
832
|
...(l1ConsensusHostApiKey &&
|
|
781
833
|
l1ConsensusHostApiKeyHeader && {
|
|
782
834
|
headers: {
|
package/src/client/interface.ts
CHANGED
|
@@ -6,9 +6,7 @@ import type { Blob } from '@aztec/blob-lib';
|
|
|
6
6
|
export interface GetBlobSidecarOptions {
|
|
7
7
|
/**
|
|
8
8
|
* True if the archiver is catching up (historical sync), false if near tip.
|
|
9
|
-
*
|
|
10
|
-
* - Historical: FileStore first (data should exist), then L1 consensus, then archive (eg. blobscan)
|
|
11
|
-
* - Near tip: FileStore first with no retries (data should exist), L1 consensus second (freshest data), then FileStore with retries, then archive (eg. blobscan)
|
|
9
|
+
* Historical sync uses a shorter retry backoff since blobs should already exist.
|
|
12
10
|
*/
|
|
13
11
|
isHistoricalSync?: boolean;
|
|
14
12
|
/**
|
package/src/filestore/factory.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import {
|
|
3
3
|
type FileStore,
|
|
4
|
+
type HttpFileStoreOptions,
|
|
4
5
|
type ReadOnlyFileStore,
|
|
5
6
|
createFileStore,
|
|
6
7
|
createReadOnlyFileStore,
|
|
@@ -44,16 +45,19 @@ export async function createReadOnlyFileStoreBlobClient(
|
|
|
44
45
|
storeUrl: string,
|
|
45
46
|
metadata: BlobFileStoreMetadata,
|
|
46
47
|
logger?: Logger,
|
|
48
|
+
httpOptions?: HttpFileStoreOptions,
|
|
47
49
|
): Promise<FileStoreBlobClient>;
|
|
48
50
|
export async function createReadOnlyFileStoreBlobClient(
|
|
49
51
|
storeUrl: string | undefined,
|
|
50
52
|
metadata: BlobFileStoreMetadata,
|
|
51
53
|
logger?: Logger,
|
|
54
|
+
httpOptions?: HttpFileStoreOptions,
|
|
52
55
|
): Promise<FileStoreBlobClient | undefined>;
|
|
53
56
|
export async function createReadOnlyFileStoreBlobClient(
|
|
54
57
|
storeUrl: string | undefined,
|
|
55
58
|
metadata: BlobFileStoreMetadata,
|
|
56
59
|
logger?: Logger,
|
|
60
|
+
httpOptions?: HttpFileStoreOptions,
|
|
57
61
|
): Promise<FileStoreBlobClient | undefined> {
|
|
58
62
|
if (!storeUrl) {
|
|
59
63
|
return undefined;
|
|
@@ -64,7 +68,7 @@ export async function createReadOnlyFileStoreBlobClient(
|
|
|
64
68
|
|
|
65
69
|
log.debug(`Creating read-only filestore blob client`, { storeUrl, basePath });
|
|
66
70
|
|
|
67
|
-
const store: ReadOnlyFileStore = await createReadOnlyFileStore(storeUrl, log);
|
|
71
|
+
const store: ReadOnlyFileStore = await createReadOnlyFileStore(storeUrl, log, httpOptions);
|
|
68
72
|
return new FileStoreBlobClient(store, basePath, log);
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -80,6 +84,7 @@ export async function createReadOnlyFileStoreBlobClients(
|
|
|
80
84
|
storeUrls: string[] | undefined,
|
|
81
85
|
metadata: BlobFileStoreMetadata,
|
|
82
86
|
logger?: Logger,
|
|
87
|
+
httpOptions?: HttpFileStoreOptions,
|
|
83
88
|
): Promise<FileStoreBlobClient[]> {
|
|
84
89
|
if (!storeUrls || storeUrls.length === 0) {
|
|
85
90
|
return [];
|
|
@@ -90,7 +95,7 @@ export async function createReadOnlyFileStoreBlobClients(
|
|
|
90
95
|
|
|
91
96
|
for (const storeUrl of storeUrls) {
|
|
92
97
|
try {
|
|
93
|
-
const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log);
|
|
98
|
+
const client = await createReadOnlyFileStoreBlobClient(storeUrl, metadata, log, httpOptions);
|
|
94
99
|
if (client) {
|
|
95
100
|
clients.push(client);
|
|
96
101
|
}
|