11import type { R2PutOptions } from "@cloudflare/workers-types/experimental/index.ts" ;
2- import * as mf from "miniflare" ;
32import { isDeepStrictEqual } from "node:util" ;
43import type { Context } from "../context.ts" ;
54import { Resource , ResourceKind } from "../resource.ts" ;
65import { Scope } from "../scope.ts" ;
7- import { streamToBuffer } from "../serde.ts" ;
86import { isRetryableError } from "../state/r2-rest-state-store.ts" ;
97import { withExponentialBackoff } from "../util/retry.ts" ;
108import { CloudflareApiError , handleApiError } from "./api-error.ts" ;
@@ -22,7 +20,10 @@ import {
2220 type R2BucketCustomDomainOptions ,
2321} from "./bucket-custom-domain.ts" ;
2422import { deleteMiniflareBinding } from "./miniflare/delete.ts" ;
25- import { getDefaultPersistPath } from "./miniflare/paths.ts" ;
23+ import {
24+ makeAsyncProxy ,
25+ makeAsyncProxyForBinding ,
26+ } from "./miniflare/node-binding.ts" ;
2627
2728export type R2BucketJurisdiction = "default" | "eu" | "fedramp" ;
2829
@@ -306,23 +307,7 @@ export type R2Objects = {
306307 }
307308) ;
308309
309- export type R2Bucket = _R2Bucket & {
310- head ( key : string ) : Promise < R2ObjectMetadata | null > ;
311- get ( key : string ) : Promise < R2ObjectContent | null > ;
312- put (
313- key : string ,
314- value :
315- | ReadableStream
316- | ArrayBuffer
317- | ArrayBufferView
318- | string
319- | null
320- | Blob ,
321- options ?: Pick < R2PutOptions , "httpMetadata" > ,
322- ) : Promise < PutR2ObjectResponse > ;
323- delete ( key : string ) : Promise < Response > ;
324- list ( options ?: R2ListOptions ) : Promise < R2Objects > ;
325- } ;
310+ export type R2Bucket = _R2Bucket & globalThis . R2Bucket ;
326311
327312/**
328313 * Output returned after R2 Bucket creation/update
@@ -461,9 +446,6 @@ export async function R2Bucket(
461446 id : string ,
462447 props : BucketProps = { } ,
463448) : Promise < R2Bucket > {
464- const scope = Scope . current ;
465- const isLocal = scope . local && props . dev ?. remote !== true ;
466- const api = await createCloudflareApi ( props ) ;
467449 const bucket = await _R2Bucket ( id , {
468450 ...props ,
469451 dev : {
@@ -472,144 +454,31 @@ export async function R2Bucket(
472454 } ,
473455 } ) ;
474456
475- let _miniflare : mf . Miniflare | undefined ;
476- const miniflare = ( ) => {
477- if ( _miniflare ) {
478- return _miniflare ;
479- }
480- _miniflare = new mf . Miniflare ( {
481- script : "" ,
482- modules : true ,
483- defaultPersistRoot : getDefaultPersistPath ( scope . rootDir ) ,
484- r2Buckets : [ bucket . dev . id ] ,
485- log : process . env . DEBUG ? new mf . Log ( mf . LogLevel . DEBUG ) : undefined ,
486- } ) ;
487- scope . onCleanup ( async ( ) => _miniflare ?. dispose ( ) ) ;
488- return _miniflare ;
489- } ;
490- const localBucket = ( ) => miniflare ( ) . getR2Bucket ( bucket . dev . id ) ;
491-
492- return {
493- ...bucket ,
494- head : async ( key : string ) => {
495- if ( isLocal ) {
496- const result = await ( await localBucket ( ) ) . head ( key ) ;
497- if ( result ) {
498- return {
499- key : result . key ,
500- etag : result . etag ,
501- uploaded : result . uploaded ,
502- size : result . size ,
503- httpMetadata : result . httpMetadata ,
504- } as R2ObjectMetadata ;
505- }
506- return null ;
507- }
508- return headObject ( api , {
509- bucketName : bucket . name ,
510- key,
511- } ) ;
512- } ,
513- get : async ( key : string ) => {
514- if ( isLocal ) {
515- const result = await ( await localBucket ( ) ) . get ( key ) ;
516- if ( result ) {
517- // cast because workers vs node built-ins
518- return result as unknown as R2ObjectContent ;
519- }
520- return null ;
521- }
522- const response = await getObject ( api , {
523- bucketName : bucket . name ,
524- key,
525- } ) ;
526- if ( response . ok ) {
527- return parseR2Object ( key , response ) ;
528- } else if ( response . status === 404 ) {
529- return null ;
530- } else {
531- throw await handleApiError ( response , "get" , "object" , key ) ;
532- }
533- } ,
534- list : async ( options ?: R2ListOptions ) : Promise < R2Objects > => {
535- if ( isLocal ) {
536- return ( await localBucket ( ) ) . list ( options ) ;
537- }
538- return listObjects ( api , bucket . name , {
539- ...options ,
540- jurisdiction : bucket . jurisdiction ,
541- } ) ;
542- } ,
543- put : async (
544- key : string ,
545- value : PutObjectObject ,
546- options ?: Pick < R2PutOptions , "httpMetadata" > ,
547- ) : Promise < PutR2ObjectResponse > => {
548- if ( isLocal ) {
549- return await ( await localBucket ( ) ) . put (
550- key ,
551- typeof value === "string"
552- ? value
553- : Buffer . isBuffer ( value ) ||
554- value instanceof Uint8Array ||
555- value instanceof ArrayBuffer
556- ? new Uint8Array ( value )
557- : value instanceof Blob
558- ? new Uint8Array ( await value . arrayBuffer ( ) )
559- : value instanceof ReadableStream
560- ? new Uint8Array ( await streamToBuffer ( value ) )
561- : value ,
562- options ,
563- ) ;
564- }
565- const response = await putObject ( api , {
566- bucketName : bucket . name ,
567- key : key ,
568- object : value ,
569- options : options ,
570- } ) ;
571- const body = ( await response . json ( ) ) as {
572- result : {
573- key : string ;
574- etag : string ;
575- uploaded : string ;
576- version : string ;
577- size : string ;
578- } ;
579- } ;
580- return {
581- key : body . result . key ,
582- etag : body . result . etag ,
583- uploaded : new Date ( body . result . uploaded ) ,
584- version : body . result . version ,
585- size : Number ( body . result . size ) ,
586- } ;
587- } ,
588- delete : async ( key : string ) => {
589- if ( isLocal ) {
590- await ( await localBucket ( ) ) . delete ( key ) ;
591- }
592- return deleteObject ( api , {
593- bucketName : bucket . name ,
594- key : key ,
595- } ) ;
457+ return makeAsyncProxyForBinding ( {
458+ apiOptions : props ,
459+ name : id ,
460+ binding : bucket as Omit < R2Bucket , keyof globalThis . R2Bucket > ,
461+ properties : {
462+ createMultipartUpload : true ,
463+ delete : true ,
464+ get : true ,
465+ head : true ,
466+ list : true ,
467+ put : true ,
468+ resumeMultipartUpload : ( promise ) => ( key : string , uploadId : string ) =>
469+ makeAsyncProxy (
470+ { key, uploadId } ,
471+ promise . then ( ( bucket ) => bucket . resumeMultipartUpload ( key , uploadId ) ) ,
472+ {
473+ uploadPart : true ,
474+ abort : true ,
475+ complete : true ,
476+ } ,
477+ ) ,
596478 } ,
597- } ;
479+ } ) ;
598480}
599481
600- const parseR2Object = ( key : string , response : Response ) : R2ObjectContent => ( {
601- etag : response . headers . get ( "ETag" ) ! ,
602- uploaded : parseDate ( response . headers ) ,
603- key,
604- size : Number ( response . headers . get ( "Content-Length" ) ) ,
605- httpMetadata : mapHeadersToHttpMetadata ( response . headers ) ,
606- arrayBuffer : ( ) => response . arrayBuffer ( ) ,
607- bytes : ( ) => response . bytes ( ) ,
608- text : ( ) => response . text ( ) ,
609- json : ( ) => response . json ( ) ,
610- blob : ( ) => response . blob ( ) ,
611- } ) ;
612-
613482const parseDate = ( headers : Headers ) =>
614483 new Date ( headers . get ( "Last-Modified" ) ?? headers . get ( "Date" ) ! ) ;
615484
@@ -1143,7 +1012,9 @@ export async function putBucketLifecycleRules(
11431012 api . put (
11441013 `/accounts/${ api . accountId } /r2/buckets/${ bucketName } /lifecycle` ,
11451014 rulesBody ,
1146- { headers : withJurisdiction ( props ) } ,
1015+ {
1016+ headers : withJurisdiction ( props ) ,
1017+ } ,
11471018 ) ,
11481019 ) ;
11491020}
@@ -1158,7 +1029,9 @@ export async function getBucketLifecycleRules(
11581029) : Promise < R2BucketLifecycleRule [ ] > {
11591030 const res = await api . get (
11601031 `/accounts/${ api . accountId } /r2/buckets/${ bucketName } /lifecycle` ,
1161- { headers : withJurisdiction ( props ) } ,
1032+ {
1033+ headers : withJurisdiction ( props ) ,
1034+ } ,
11621035 ) ;
11631036 const json : any = await res . json ( ) ;
11641037 if ( ! json ?. success ) {
@@ -1196,7 +1069,9 @@ export async function putBucketLockRules(
11961069 api . put (
11971070 `/accounts/${ api . accountId } /r2/buckets/${ bucketName } /lock` ,
11981071 rulesBody ,
1199- { headers : withJurisdiction ( props ) } ,
1072+ {
1073+ headers : withJurisdiction ( props ) ,
1074+ } ,
12001075 ) ,
12011076 ) ;
12021077}
@@ -1211,7 +1086,9 @@ export async function getBucketLockRules(
12111086) : Promise < R2BucketLockRule [ ] > {
12121087 const res = await api . get (
12131088 `/accounts/${ api . accountId } /r2/buckets/${ bucketName } /lock` ,
1214- { headers : withJurisdiction ( props ) } ,
1089+ {
1090+ headers : withJurisdiction ( props ) ,
1091+ } ,
12151092 ) ;
12161093 const json : any = await res . json ( ) ;
12171094 if ( ! json ?. success ) {
0 commit comments