@@ -298,68 +298,77 @@ export class S3FileStorage implements FileStorage {
298
298
299
299
/**
300
300
* Returns the file with the given key, or `null` if no such key exists.
301
- * Uses a HEAD request to get metadata and creates a LazyFile that will only fetch the content when needed.
301
+ * If `eager` is true, the file content is fetched immediately.
302
+ * Otherwise, a HEAD request is used to get metadata, and a LazyFile is created
303
+ * that will only fetch the content when its stream is accessed.
302
304
*/
303
305
async get ( key : string ) : Promise < File | null > {
304
- return this . eager ? this . getEager ( key ) : this . getLazy ( key ) ;
305
- }
306
-
307
- private async getEager ( key : string ) : Promise < LazyFile | null > {
308
306
const objectUrl = this . getObjectUrl ( key ) ;
309
-
310
- const initial = await this . aws . fetch ( objectUrl , {
311
- method : 'GET' ,
312
- } ) ;
313
-
314
- if ( ! initial . ok ) {
315
- return null ;
307
+ let initialResponse : Response | null = null ;
308
+ let responseHeaders : Headers ;
309
+
310
+ if ( this . eager ) {
311
+ const eagerResponse = await this . aws . fetch ( objectUrl , { method : 'GET' } ) ;
312
+ if ( ! eagerResponse . ok ) {
313
+ if ( eagerResponse . status === 404 ) return null ;
314
+ throw new Error ( `Failed to get file: ${ eagerResponse . statusText } ` ) ;
315
+ }
316
+ initialResponse = eagerResponse ;
317
+ responseHeaders = initialResponse . headers ;
318
+ } else {
319
+ const lazyResponse = await this . aws . fetch ( objectUrl , { method : 'HEAD' } ) ;
320
+ if ( ! lazyResponse . ok ) {
321
+ if ( lazyResponse . status === 404 ) return null ;
322
+ throw new Error ( `Failed to get file metadata: ${ lazyResponse . statusText } ` ) ;
323
+ }
324
+ responseHeaders = lazyResponse . headers ;
316
325
}
317
326
318
327
const {
319
328
name,
320
329
lastModified,
321
330
type,
322
331
size
323
- } = this . extractMetadata ( key , initial . headers ) ;
332
+ } = this . extractMetadata ( key , responseHeaders ) ;
324
333
325
- // Store AWS client and key in variables that can be captured by the closure
334
+ // Store AWS client in a variable that can be captured by the closure
326
335
const aws = this . aws ;
327
336
328
- // Create LazyContent implementation that will fetch the file only when needed
329
337
const lazyContent : LazyContent = {
330
338
byteLength : size ,
331
339
stream ( start ?: number , end ?: number ) : ReadableStream < Uint8Array > {
332
340
return new ReadableStream ( {
333
341
async start ( controller ) {
334
342
const headers : Record < string , string > = { } ;
335
343
if ( start !== undefined || end !== undefined ) {
336
-
337
- // it's valid to pass a start without an end
338
344
let range = `bytes=${ start ?? 0 } -` ;
339
345
if ( end !== undefined ) {
346
+ // Range header is inclusive, so subtract 1 from end if specified
340
347
range += ( end - 1 ) ;
341
348
}
342
-
343
349
headers [ 'Range' ] = range ;
344
350
}
345
351
346
352
try {
347
353
let reader : ReadableStreamDefaultReader < Uint8Array > ;
348
- if ( ! headers [ 'Range' ] && ! initial . bodyUsed ) {
349
- // If no range is specified and the body has not been used, we can use the initial response's body
350
- reader = initial . body ! . getReader ( ) ;
351
-
354
+ // If eager loading provided an initial response, no range is requested,
355
+ // and its body hasn't been used, we can use its body.
356
+ if ( ! headers [ 'Range' ] && initialResponse ? .body && ! initialResponse . bodyUsed ) {
357
+ reader = initialResponse . body . getReader ( ) ;
352
358
} else {
359
+ // Otherwise, fetch the content (or range)
353
360
const response = await aws . fetch ( objectUrl , {
354
361
method : 'GET' ,
355
362
headers
356
363
} ) ;
357
364
358
365
if ( ! response . ok ) {
359
- throw new Error ( `Failed to fetch file: ${ response . statusText } ` ) ;
366
+ throw new Error ( `Failed to fetch file content : ${ response . statusText } ` ) ;
360
367
}
361
-
362
- reader = response . body ! . getReader ( ) ;
368
+ if ( ! response . body ) {
369
+ throw new Error ( 'Response body is null' ) ;
370
+ }
371
+ reader = response . body . getReader ( ) ;
363
372
}
364
373
365
374
while ( true ) {
@@ -387,91 +396,6 @@ export class S3FileStorage implements FileStorage {
387
396
) ;
388
397
}
389
398
390
- private async getLazy ( key : string ) : Promise < File | null > {
391
- // First do a HEAD request to get metadata without downloading the file
392
- const headResponse = await this . aws . fetch ( this . getObjectUrl ( key ) , {
393
- method : 'HEAD' ,
394
- } ) ;
395
-
396
- if ( ! headResponse . ok ) {
397
- return null ;
398
- }
399
-
400
- const contentLength = headResponse . headers . get ( 'content-length' ) ;
401
- const contentType = headResponse . headers . get ( 'content-type' ) || '' ;
402
- const lastModifiedHeader = headResponse . headers . get ( 'last-modified' ) ;
403
- const lastModified = lastModifiedHeader ? new Date ( lastModifiedHeader ) . getTime ( ) : Date . now ( ) ;
404
-
405
- // Try to get the file name from metadata
406
- let fileName = key . split ( '/' ) . pop ( ) || key ;
407
-
408
- const metadataName = headResponse . headers . get ( 'x-amz-meta-name' ) ;
409
- const metadataLastModified = headResponse . headers . get ( 'x-amz-meta-lastModified' ) ;
410
- const metadataType = headResponse . headers . get ( 'x-amz-meta-type' ) ;
411
-
412
- if ( metadataName ) {
413
- fileName = decodeURI ( metadataName ) ;
414
- }
415
-
416
- // Store AWS client and key in variables that can be captured by the closure
417
- const aws = this . aws ;
418
- const objectUrl = this . getObjectUrl ( key ) ;
419
-
420
- // Create LazyContent implementation that will fetch the file only when needed
421
- const lazyContent : LazyContent = {
422
- byteLength : contentLength ? parseInt ( contentLength , 10 ) : 0 ,
423
- stream ( start ?: number , end ?: number ) : ReadableStream < Uint8Array > {
424
- return new ReadableStream ( {
425
- async start ( controller ) {
426
- const headers : Record < string , string > = { } ;
427
- if ( start !== undefined || end !== undefined ) {
428
-
429
- // it's valid to pass a start without an end
430
- let range = `bytes=${ start ?? 0 } -` ;
431
- if ( end !== undefined ) {
432
- range += ( end - 1 ) ;
433
- }
434
-
435
- headers [ 'Range' ] = range ;
436
- }
437
-
438
- try {
439
- const response = await aws . fetch ( objectUrl , {
440
- method : 'GET' ,
441
- headers
442
- } ) ;
443
-
444
- if ( ! response . ok ) {
445
- throw new Error ( `Failed to fetch file: ${ response . statusText } ` ) ;
446
- }
447
-
448
- const reader = response . body ! . getReader ( ) ;
449
-
450
- while ( true ) {
451
- const { done, value } = await reader . read ( ) ;
452
- if ( done ) break ;
453
- controller . enqueue ( value ) ;
454
- }
455
-
456
- controller . close ( ) ;
457
- } catch ( error ) {
458
- controller . error ( error ) ;
459
- }
460
- }
461
- } ) ;
462
- }
463
- } ;
464
-
465
- return new LazyFile (
466
- lazyContent ,
467
- fileName ,
468
- {
469
- type : metadataType || contentType ,
470
- lastModified : metadataLastModified ? parseInt ( metadataLastModified , 10 ) : lastModified
471
- }
472
- ) ;
473
- }
474
-
475
399
async put ( key : string , file : File ) : Promise < File > {
476
400
await this . set ( key , file ) ;
477
401
return ( await this . get ( key ) ) ! ;
0 commit comments