1
- import assert from "assert" ;
2
- import crypto from "crypto" ;
3
- import { TextEncoder } from "util" ;
1
+ import { Blob } from "buffer" ;
2
+ import { ReadableStream , TransformStream } from "stream/web" ;
4
3
import type { R2StringChecksums } from "@cloudflare/workers-types/experimental" ;
5
4
import { R2Objects } from "./gateway" ;
6
5
import {
7
- BASE64_REGEXP ,
8
6
HEX_REGEXP ,
7
+ ObjectRow ,
9
8
R2HeadResponse ,
10
9
R2HttpFields ,
11
10
R2Range ,
12
11
} from "./schemas" ;
13
12
14
- const encoder = new TextEncoder ( ) ;
15
-
16
- export interface R2ObjectMetadata {
17
- // The object’s key.
18
- key : string ;
19
- // Random unique string associated with a specific upload of a key.
20
- version : string ;
21
- // Size of the object in bytes.
22
- size : number ;
23
- // The etag associated with the object upload.
24
- etag : string ;
25
- // The object's etag, in quotes to be returned as a header.
26
- httpEtag : string ;
27
- // The time the object was uploaded.
28
- uploaded : number ;
29
- // Various HTTP headers associated with the object. Refer to HTTP Metadata:
30
- // https://developers.cloudflare.com/r2/runtime-apis/#http-metadata.
31
- httpMetadata : R2HttpFields ;
32
- // A map of custom, user-defined metadata associated with the object.
33
- customMetadata : Record < string , string > ;
34
- // If a GET request was made with a range option, this will be added
35
- range ?: R2Range ;
36
- // Hashes used to check the received object’s integrity. At most one can be
37
- // specified.
38
- checksums ?: R2StringChecksums ;
39
- }
40
-
41
13
export interface EncodedMetadata {
42
14
metadataSize : number ;
43
- value : Uint8Array ;
15
+ value : ReadableStream < Uint8Array > ;
44
16
}
45
17
46
- export function createVersion ( ) : string {
47
- return crypto . randomBytes ( 16 ) . toString ( "hex" ) ;
48
- }
49
-
50
- /**
51
- * R2Object is created when you PUT an object into an R2 bucket.
52
- * R2Object represents the metadata of an object based on the information
53
- * provided by the uploader. Every object that you PUT into an R2 bucket
54
- * will have an R2Object created.
55
- */
56
- export class R2Object implements R2ObjectMetadata {
18
+ export class R2Object {
57
19
readonly key : string ;
58
20
readonly version : string ;
59
21
readonly size : number ;
60
22
readonly etag : string ;
61
- readonly httpEtag : string ;
62
23
readonly uploaded : number ;
63
24
readonly httpMetadata : R2HttpFields ;
64
25
readonly customMetadata : Record < string , string > ;
65
26
readonly range ?: R2Range ;
66
27
readonly checksums : R2StringChecksums ;
67
28
68
- constructor ( metadata : R2ObjectMetadata ) {
69
- this . key = metadata . key ;
70
- this . version = metadata . version ;
71
- this . size = metadata . size ;
72
- this . etag = metadata . etag ;
73
- this . httpEtag = metadata . httpEtag ;
74
- this . uploaded = metadata . uploaded ;
75
- this . httpMetadata = metadata . httpMetadata ;
76
- this . customMetadata = metadata . customMetadata ;
77
- this . range = metadata . range ;
29
+ constructor ( row : Omit < ObjectRow , "blob_id" > , range ?: R2Range ) {
30
+ this . key = row . key ;
31
+ this . version = row . version ;
32
+ this . size = row . size ;
33
+ this . etag = row . etag ;
34
+ this . uploaded = row . uploaded ;
35
+ this . httpMetadata = JSON . parse ( row . http_metadata ) ;
36
+ this . customMetadata = JSON . parse ( row . custom_metadata ) ;
37
+ this . range = range ;
78
38
79
39
// For non-multipart uploads, we always need to store an MD5 hash in
80
- // `checksums`, but never explicitly stored one. Luckily, `R2Bucket#put()`
81
- // always makes `etag` an MD5 hash.
82
- const checksums : R2StringChecksums = { ...metadata . checksums } ;
83
- const etag = metadata . etag ;
84
- if ( etag . length === 32 && HEX_REGEXP . test ( etag ) ) {
85
- checksums . md5 = metadata . etag ;
86
- } else if ( etag . length === 24 && BASE64_REGEXP . test ( etag ) ) {
87
- // TODO: remove this when we switch underlying storage mechanisms
88
- // Previous versions of Miniflare 3 base64 encoded `etag` instead
89
- checksums . md5 = Buffer . from ( etag , "base64" ) . toString ( "hex" ) ;
90
- } else {
91
- assert . fail ( "Expected `etag` to be an MD5 hash" ) ;
40
+ // `checksums`. To avoid data duplication, we just use `etag` for this.
41
+ const checksums : R2StringChecksums = JSON . parse ( row . checksums ) ;
42
+ if ( this . etag . length === 32 && HEX_REGEXP . test ( this . etag ) ) {
43
+ checksums . md5 = row . etag ;
92
44
}
93
45
this . checksums = checksums ;
94
46
}
95
47
96
48
// Format for return to the Workers Runtime
97
49
#rawProperties( ) : R2HeadResponse {
98
50
return {
99
- ...this ,
100
51
name : this . key ,
52
+ version : this . version ,
53
+ size : this . size ,
54
+ etag : this . etag ,
55
+ uploaded : this . uploaded ,
101
56
httpFields : this . httpMetadata ,
102
57
customFields : Object . entries ( this . customMetadata ) . map ( ( [ k , v ] ) => ( {
103
58
k,
104
59
v,
105
60
} ) ) ,
61
+ range : this . range ,
106
62
checksums : {
107
63
0 : this . checksums . md5 ,
108
64
1 : this . checksums . sha1 ,
@@ -115,36 +71,38 @@ export class R2Object implements R2ObjectMetadata {
115
71
116
72
encode ( ) : EncodedMetadata {
117
73
const json = JSON . stringify ( this . #rawProperties( ) ) ;
118
- const bytes = encoder . encode ( json ) ;
119
- return { metadataSize : bytes . length , value : bytes } ;
74
+ const blob = new Blob ( [ json ] ) ;
75
+ return { metadataSize : blob . size , value : blob . stream ( ) } ;
120
76
}
121
77
122
78
static encodeMultiple ( objects : R2Objects ) : EncodedMetadata {
123
79
const json = JSON . stringify ( {
124
80
...objects ,
125
81
objects : objects . objects . map ( ( o ) => o . #rawProperties( ) ) ,
126
82
} ) ;
127
- const bytes = encoder . encode ( json ) ;
128
- return { metadataSize : bytes . length , value : bytes } ;
83
+ const blob = new Blob ( [ json ] ) ;
84
+ return { metadataSize : blob . size , value : blob . stream ( ) } ;
129
85
}
130
86
}
131
87
132
88
export class R2ObjectBody extends R2Object {
133
- readonly body : Uint8Array ;
134
-
135
- constructor ( metadata : R2ObjectMetadata , body : Uint8Array ) {
136
- super ( metadata ) ;
137
- this . body = body ;
89
+ constructor (
90
+ metadata : Omit < ObjectRow , "blob_id" > ,
91
+ readonly body : ReadableStream < Uint8Array > ,
92
+ range ?: R2Range
93
+ ) {
94
+ super ( metadata , range ) ;
138
95
}
139
96
140
97
encode ( ) : EncodedMetadata {
141
98
const { metadataSize, value : metadata } = super . encode ( ) ;
142
- const merged = new Uint8Array ( metadataSize + this . body . length ) ;
143
- merged . set ( metadata ) ;
144
- merged . set ( this . body , metadataSize ) ;
99
+ const identity = new TransformStream ( ) ;
100
+ void metadata
101
+ . pipeTo ( identity . writable , { preventClose : true } )
102
+ . then ( ( ) => this . body . pipeTo ( identity . writable ) ) ;
145
103
return {
146
104
metadataSize : metadataSize ,
147
- value : merged ,
105
+ value : identity . readable ,
148
106
} ;
149
107
}
150
108
}
0 commit comments