@depup/miniflare 4.20260317.0-depup.0
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/README.md +31 -0
- package/bootstrap.js +11 -0
- package/changes.json +10 -0
- package/dist/local-explorer-ui/assets/Breadcrumbs-7hjI0sYt.js +1 -0
- package/dist/local-explorer-ui/assets/TableSelect-_bi6l6Iv.js +25 -0
- package/dist/local-explorer-ui/assets/_className-gSkjniQn.js +1 -0
- package/dist/local-explorer-ui/assets/_databaseId-DWcfgBis.js +1 -0
- package/dist/local-explorer-ui/assets/_namespaceId-8ViM8VDL.js +1 -0
- package/dist/local-explorer-ui/assets/_objectId-X6x-JUph.js +1 -0
- package/dist/local-explorer-ui/assets/dropdown-DFeFcKfn-BCd_NRZS.js +1 -0
- package/dist/local-explorer-ui/assets/index-BFNDwiew.js +42 -0
- package/dist/local-explorer-ui/assets/index-CLSFsgi0.js +1 -0
- package/dist/local-explorer-ui/assets/index-KG4JeHCX.js +1 -0
- package/dist/local-explorer-ui/assets/index-mgoUmSld.css +1 -0
- package/dist/local-explorer-ui/assets/table-BUmvaBj8-v-EIZgOz.js +1 -0
- package/dist/local-explorer-ui/favicon.svg +3 -0
- package/dist/local-explorer-ui/index.html +14 -0
- package/dist/src/index.d.ts +8703 -0
- package/dist/src/index.js +83666 -0
- package/dist/src/index.js.map +6 -0
- package/dist/src/shared/dev-registry.worker.js +69801 -0
- package/dist/src/shared/dev-registry.worker.js.map +6 -0
- package/dist/src/workers/analytics-engine/analytics-engine.worker.js +15 -0
- package/dist/src/workers/analytics-engine/analytics-engine.worker.js.map +6 -0
- package/dist/src/workers/assets/assets-kv.worker.js +28 -0
- package/dist/src/workers/assets/assets-kv.worker.js.map +6 -0
- package/dist/src/workers/assets/assets.worker.js +9014 -0
- package/dist/src/workers/assets/assets.worker.js.map +6 -0
- package/dist/src/workers/assets/router.worker.js +9625 -0
- package/dist/src/workers/assets/router.worker.js.map +6 -0
- package/dist/src/workers/assets/rpc-proxy.worker.js +29 -0
- package/dist/src/workers/assets/rpc-proxy.worker.js.map +6 -0
- package/dist/src/workers/browser-rendering/binding.worker.js +129 -0
- package/dist/src/workers/browser-rendering/binding.worker.js.map +6 -0
- package/dist/src/workers/cache/cache-entry-noop.worker.js +19 -0
- package/dist/src/workers/cache/cache-entry-noop.worker.js.map +6 -0
- package/dist/src/workers/cache/cache-entry.worker.js +28 -0
- package/dist/src/workers/cache/cache-entry.worker.js.map +6 -0
- package/dist/src/workers/cache/cache.worker.js +653 -0
- package/dist/src/workers/cache/cache.worker.js.map +6 -0
- package/dist/src/workers/core/do-wrapper.worker.js +43 -0
- package/dist/src/workers/core/do-wrapper.worker.js.map +6 -0
- package/dist/src/workers/core/entry.worker.js +4633 -0
- package/dist/src/workers/core/entry.worker.js.map +6 -0
- package/dist/src/workers/core/strip-cf-connecting-ip.worker.js +11 -0
- package/dist/src/workers/core/strip-cf-connecting-ip.worker.js.map +6 -0
- package/dist/src/workers/d1/database.worker.js +219 -0
- package/dist/src/workers/d1/database.worker.js.map +6 -0
- package/dist/src/workers/dispatch-namespace/dispatch-namespace-proxy.worker.js +2271 -0
- package/dist/src/workers/dispatch-namespace/dispatch-namespace-proxy.worker.js.map +6 -0
- package/dist/src/workers/dispatch-namespace/dispatch-namespace.worker.js +12 -0
- package/dist/src/workers/dispatch-namespace/dispatch-namespace.worker.js.map +6 -0
- package/dist/src/workers/email/email.worker.js +23 -0
- package/dist/src/workers/email/email.worker.js.map +6 -0
- package/dist/src/workers/email/send_email.worker.js +3294 -0
- package/dist/src/workers/email/send_email.worker.js.map +6 -0
- package/dist/src/workers/hello-world/binding.worker.js +19 -0
- package/dist/src/workers/hello-world/binding.worker.js.map +6 -0
- package/dist/src/workers/hello-world/object.worker.js +14 -0
- package/dist/src/workers/hello-world/object.worker.js.map +6 -0
- package/dist/src/workers/images/images.worker.js +155 -0
- package/dist/src/workers/images/images.worker.js.map +6 -0
- package/dist/src/workers/kv/namespace.worker.js +322 -0
- package/dist/src/workers/kv/namespace.worker.js.map +6 -0
- package/dist/src/workers/kv/sites.worker.js +146 -0
- package/dist/src/workers/kv/sites.worker.js.map +6 -0
- package/dist/src/workers/local-explorer/explorer.worker.js +5245 -0
- package/dist/src/workers/local-explorer/explorer.worker.js.map +6 -0
- package/dist/src/workers/pipelines/pipeline.worker.js +10 -0
- package/dist/src/workers/pipelines/pipeline.worker.js.map +6 -0
- package/dist/src/workers/queues/broker.worker.js +289 -0
- package/dist/src/workers/queues/broker.worker.js.map +6 -0
- package/dist/src/workers/r2/bucket.worker.js +1134 -0
- package/dist/src/workers/r2/bucket.worker.js.map +6 -0
- package/dist/src/workers/ratelimit/ratelimit.worker.js +54 -0
- package/dist/src/workers/ratelimit/ratelimit.worker.js.map +6 -0
- package/dist/src/workers/secrets-store/secret.worker.js +65 -0
- package/dist/src/workers/secrets-store/secret.worker.js.map +6 -0
- package/dist/src/workers/shared/index.worker.js +693 -0
- package/dist/src/workers/shared/index.worker.js.map +6 -0
- package/dist/src/workers/shared/object-entry.worker.js +21 -0
- package/dist/src/workers/shared/object-entry.worker.js.map +6 -0
- package/dist/src/workers/shared/remote-proxy-client.worker.js +2271 -0
- package/dist/src/workers/shared/remote-proxy-client.worker.js.map +6 -0
- package/dist/src/workers/shared/zod.worker.js +2954 -0
- package/dist/src/workers/shared/zod.worker.js.map +6 -0
- package/dist/src/workers/workflows/binding.worker.js +2422 -0
- package/dist/src/workers/workflows/binding.worker.js.map +6 -0
- package/dist/src/workers/workflows/wrapped-binding.worker.js +71 -0
- package/dist/src/workers/workflows/wrapped-binding.worker.js.map +6 -0
- package/package.json +139 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
for (var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target, i = decorators.length - 1, decorator; i >= 0; i--)
|
|
5
|
+
(decorator = decorators[i]) && (result = (kind ? decorator(target, key, result) : decorator(result)) || result);
|
|
6
|
+
return kind && result && __defProp(target, key, result), result;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/workers/r2/bucket.worker.ts
|
|
10
|
+
import assert2 from "node:assert";
|
|
11
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import {
|
|
14
|
+
all,
|
|
15
|
+
base64Decode,
|
|
16
|
+
base64Encode,
|
|
17
|
+
DeferredPromise,
|
|
18
|
+
GET,
|
|
19
|
+
get,
|
|
20
|
+
maybeApply,
|
|
21
|
+
MiniflareDurableObject,
|
|
22
|
+
PUT,
|
|
23
|
+
readPrefix,
|
|
24
|
+
WaitGroup
|
|
25
|
+
} from "miniflare:shared";
|
|
26
|
+
|
|
27
|
+
// src/workers/r2/constants.ts
|
|
28
|
+
var R2Limits = {
|
|
29
|
+
MAX_LIST_KEYS: 1e3,
|
|
30
|
+
MAX_KEY_SIZE: 1024,
|
|
31
|
+
// https://developers.cloudflare.com/r2/platform/limits/
|
|
32
|
+
MAX_VALUE_SIZE: 5363466240,
|
|
33
|
+
// 5 GiB - 5 MiB
|
|
34
|
+
MAX_METADATA_SIZE: 2048,
|
|
35
|
+
// 2048 B
|
|
36
|
+
MIN_MULTIPART_PART_SIZE: 5242880,
|
|
37
|
+
MIN_MULTIPART_PART_SIZE_TEST: 50
|
|
38
|
+
}, R2Headers = {
|
|
39
|
+
ERROR: "cf-r2-error",
|
|
40
|
+
REQUEST: "cf-r2-request",
|
|
41
|
+
METADATA_SIZE: "cf-r2-metadata-size"
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/workers/r2/errors.worker.ts
|
|
45
|
+
import { HttpError } from "miniflare:shared";
|
|
46
|
+
var R2ErrorCode = {
|
|
47
|
+
INTERNAL_ERROR: 10001,
|
|
48
|
+
NO_SUCH_OBJECT_KEY: 10007,
|
|
49
|
+
ENTITY_TOO_LARGE: 100100,
|
|
50
|
+
ENTITY_TOO_SMALL: 10011,
|
|
51
|
+
METADATA_TOO_LARGE: 10012,
|
|
52
|
+
INVALID_OBJECT_NAME: 10020,
|
|
53
|
+
INVALID_MAX_KEYS: 10022,
|
|
54
|
+
NO_SUCH_UPLOAD: 10024,
|
|
55
|
+
INVALID_PART: 10025,
|
|
56
|
+
INVALID_ARGUMENT: 10029,
|
|
57
|
+
PRECONDITION_FAILED: 10031,
|
|
58
|
+
BAD_DIGEST: 10037,
|
|
59
|
+
INVALID_RANGE: 10039,
|
|
60
|
+
BAD_UPLOAD: 10048
|
|
61
|
+
}, R2Error = class extends HttpError {
|
|
62
|
+
constructor(code, message, v4Code) {
|
|
63
|
+
super(code, message);
|
|
64
|
+
this.v4Code = v4Code;
|
|
65
|
+
}
|
|
66
|
+
object;
|
|
67
|
+
toResponse() {
|
|
68
|
+
if (this.object !== void 0) {
|
|
69
|
+
let { metadataSize, value } = this.object.encode();
|
|
70
|
+
return new Response(value, {
|
|
71
|
+
status: this.code,
|
|
72
|
+
headers: {
|
|
73
|
+
[R2Headers.METADATA_SIZE]: `${metadataSize}`,
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
[R2Headers.ERROR]: JSON.stringify({
|
|
76
|
+
message: this.message,
|
|
77
|
+
version: 1,
|
|
78
|
+
// Note the lowercase 'c', which the runtime expects
|
|
79
|
+
v4code: this.v4Code
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return new Response(null, {
|
|
85
|
+
status: this.code,
|
|
86
|
+
headers: {
|
|
87
|
+
[R2Headers.ERROR]: JSON.stringify({
|
|
88
|
+
message: this.message,
|
|
89
|
+
version: 1,
|
|
90
|
+
// Note the lowercase 'c', which the runtime expects
|
|
91
|
+
v4code: this.v4Code
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
context(info) {
|
|
97
|
+
return this.message += ` (${info})`, this;
|
|
98
|
+
}
|
|
99
|
+
attach(object) {
|
|
100
|
+
return this.object = object, this;
|
|
101
|
+
}
|
|
102
|
+
}, InvalidMetadata = class extends R2Error {
|
|
103
|
+
constructor() {
|
|
104
|
+
super(400, "Metadata missing or invalid", R2ErrorCode.INVALID_ARGUMENT);
|
|
105
|
+
}
|
|
106
|
+
}, InternalError = class extends R2Error {
|
|
107
|
+
constructor() {
|
|
108
|
+
super(
|
|
109
|
+
500,
|
|
110
|
+
"We encountered an internal error. Please try again.",
|
|
111
|
+
R2ErrorCode.INTERNAL_ERROR
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}, NoSuchKey = class extends R2Error {
|
|
115
|
+
constructor() {
|
|
116
|
+
super(
|
|
117
|
+
404,
|
|
118
|
+
"The specified key does not exist.",
|
|
119
|
+
R2ErrorCode.NO_SUCH_OBJECT_KEY
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}, EntityTooLarge = class extends R2Error {
|
|
123
|
+
constructor() {
|
|
124
|
+
super(
|
|
125
|
+
400,
|
|
126
|
+
"Your proposed upload exceeds the maximum allowed object size.",
|
|
127
|
+
R2ErrorCode.ENTITY_TOO_LARGE
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}, EntityTooSmall = class extends R2Error {
|
|
131
|
+
constructor() {
|
|
132
|
+
super(
|
|
133
|
+
400,
|
|
134
|
+
"Your proposed upload is smaller than the minimum allowed object size.",
|
|
135
|
+
R2ErrorCode.ENTITY_TOO_SMALL
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}, MetadataTooLarge = class extends R2Error {
|
|
139
|
+
constructor() {
|
|
140
|
+
super(
|
|
141
|
+
400,
|
|
142
|
+
"Your metadata headers exceed the maximum allowed metadata size.",
|
|
143
|
+
R2ErrorCode.METADATA_TOO_LARGE
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}, BadDigest = class extends R2Error {
|
|
147
|
+
constructor(algorithm, provided, calculated) {
|
|
148
|
+
super(
|
|
149
|
+
400,
|
|
150
|
+
[
|
|
151
|
+
`The ${algorithm} checksum you specified did not match what we received.`,
|
|
152
|
+
`You provided a ${algorithm} checksum with value: ${provided.toString(
|
|
153
|
+
"hex"
|
|
154
|
+
)}`,
|
|
155
|
+
`Actual ${algorithm} was: ${calculated.toString("hex")}`
|
|
156
|
+
].join(`
|
|
157
|
+
`),
|
|
158
|
+
R2ErrorCode.BAD_DIGEST
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}, InvalidObjectName = class extends R2Error {
|
|
162
|
+
constructor() {
|
|
163
|
+
super(
|
|
164
|
+
400,
|
|
165
|
+
"The specified object name is not valid.",
|
|
166
|
+
R2ErrorCode.INVALID_OBJECT_NAME
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}, InvalidMaxKeys = class extends R2Error {
|
|
170
|
+
constructor() {
|
|
171
|
+
super(
|
|
172
|
+
400,
|
|
173
|
+
"MaxKeys params must be positive integer <= 1000.",
|
|
174
|
+
R2ErrorCode.INVALID_MAX_KEYS
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}, NoSuchUpload = class extends R2Error {
|
|
178
|
+
constructor() {
|
|
179
|
+
super(
|
|
180
|
+
400,
|
|
181
|
+
"The specified multipart upload does not exist.",
|
|
182
|
+
R2ErrorCode.NO_SUCH_UPLOAD
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}, InvalidPart = class extends R2Error {
|
|
186
|
+
constructor() {
|
|
187
|
+
super(
|
|
188
|
+
400,
|
|
189
|
+
"One or more of the specified parts could not be found.",
|
|
190
|
+
R2ErrorCode.INVALID_PART
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}, PreconditionFailed = class extends R2Error {
|
|
194
|
+
constructor() {
|
|
195
|
+
super(
|
|
196
|
+
412,
|
|
197
|
+
"At least one of the pre-conditions you specified did not hold.",
|
|
198
|
+
R2ErrorCode.PRECONDITION_FAILED
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}, InvalidRange = class extends R2Error {
|
|
202
|
+
constructor() {
|
|
203
|
+
super(
|
|
204
|
+
416,
|
|
205
|
+
"The requested range is not satisfiable",
|
|
206
|
+
R2ErrorCode.INVALID_RANGE
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}, BadUpload = class extends R2Error {
|
|
210
|
+
constructor() {
|
|
211
|
+
super(
|
|
212
|
+
500,
|
|
213
|
+
"There was a problem with the multipart upload.",
|
|
214
|
+
R2ErrorCode.BAD_UPLOAD
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/workers/r2/r2Object.worker.ts
|
|
220
|
+
import { HEX_REGEXP } from "miniflare:zod";
|
|
221
|
+
var InternalR2Object = class {
|
|
222
|
+
key;
|
|
223
|
+
version;
|
|
224
|
+
size;
|
|
225
|
+
etag;
|
|
226
|
+
uploaded;
|
|
227
|
+
httpMetadata;
|
|
228
|
+
customMetadata;
|
|
229
|
+
range;
|
|
230
|
+
checksums;
|
|
231
|
+
constructor(row, range) {
|
|
232
|
+
this.key = row.key, this.version = row.version, this.size = row.size, this.etag = row.etag, this.uploaded = row.uploaded, this.httpMetadata = JSON.parse(row.http_metadata), this.customMetadata = JSON.parse(row.custom_metadata), this.range = range;
|
|
233
|
+
let checksums = JSON.parse(row.checksums);
|
|
234
|
+
this.etag.length === 32 && HEX_REGEXP.test(this.etag) && (checksums.md5 = row.etag), this.checksums = checksums;
|
|
235
|
+
}
|
|
236
|
+
// Format for return to the Workers Runtime
|
|
237
|
+
#rawProperties() {
|
|
238
|
+
return {
|
|
239
|
+
name: this.key,
|
|
240
|
+
version: this.version,
|
|
241
|
+
size: this.size,
|
|
242
|
+
etag: this.etag,
|
|
243
|
+
uploaded: this.uploaded,
|
|
244
|
+
httpFields: this.httpMetadata,
|
|
245
|
+
customFields: Object.entries(this.customMetadata).map(([k, v]) => ({
|
|
246
|
+
k,
|
|
247
|
+
v
|
|
248
|
+
})),
|
|
249
|
+
range: this.range,
|
|
250
|
+
checksums: {
|
|
251
|
+
0: this.checksums.md5,
|
|
252
|
+
1: this.checksums.sha1,
|
|
253
|
+
2: this.checksums.sha256,
|
|
254
|
+
3: this.checksums.sha384,
|
|
255
|
+
4: this.checksums.sha512
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
encode() {
|
|
260
|
+
let json = JSON.stringify(this.#rawProperties()), blob = new Blob([json]);
|
|
261
|
+
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
|
|
262
|
+
}
|
|
263
|
+
static encodeMultiple(objects) {
|
|
264
|
+
let json = JSON.stringify({
|
|
265
|
+
...objects,
|
|
266
|
+
objects: objects.objects.map((o) => o.#rawProperties())
|
|
267
|
+
}), blob = new Blob([json]);
|
|
268
|
+
return { metadataSize: blob.size, value: blob.stream(), size: blob.size };
|
|
269
|
+
}
|
|
270
|
+
}, InternalR2ObjectBody = class extends InternalR2Object {
|
|
271
|
+
constructor(metadata, body, range) {
|
|
272
|
+
super(metadata, range);
|
|
273
|
+
this.body = body;
|
|
274
|
+
}
|
|
275
|
+
encode() {
|
|
276
|
+
let { metadataSize, value: metadata } = super.encode(), size = this.range?.length ?? this.size, identity2 = new FixedLengthStream(size + metadataSize);
|
|
277
|
+
return metadata.pipeTo(identity2.writable, { preventClose: !0 }).then(() => this.body.pipeTo(identity2.writable)), {
|
|
278
|
+
metadataSize,
|
|
279
|
+
value: identity2.readable,
|
|
280
|
+
size
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/workers/r2/schemas.worker.ts
|
|
286
|
+
import { Base64DataSchema, HexDataSchema, z } from "miniflare:zod";
|
|
287
|
+
var MultipartUploadState = {
|
|
288
|
+
IN_PROGRESS: 0,
|
|
289
|
+
COMPLETED: 1,
|
|
290
|
+
ABORTED: 2
|
|
291
|
+
}, SQL_SCHEMA = `
|
|
292
|
+
CREATE TABLE IF NOT EXISTS _mf_objects (
|
|
293
|
+
key TEXT PRIMARY KEY,
|
|
294
|
+
blob_id TEXT,
|
|
295
|
+
version TEXT NOT NULL,
|
|
296
|
+
size INTEGER NOT NULL,
|
|
297
|
+
etag TEXT NOT NULL,
|
|
298
|
+
uploaded INTEGER NOT NULL,
|
|
299
|
+
checksums TEXT NOT NULL,
|
|
300
|
+
http_metadata TEXT NOT NULL,
|
|
301
|
+
custom_metadata TEXT NOT NULL
|
|
302
|
+
);
|
|
303
|
+
CREATE TABLE IF NOT EXISTS _mf_multipart_uploads (
|
|
304
|
+
upload_id TEXT PRIMARY KEY,
|
|
305
|
+
key TEXT NOT NULL,
|
|
306
|
+
http_metadata TEXT NOT NULL,
|
|
307
|
+
custom_metadata TEXT NOT NULL,
|
|
308
|
+
state TINYINT DEFAULT 0 NOT NULL
|
|
309
|
+
);
|
|
310
|
+
CREATE TABLE IF NOT EXISTS _mf_multipart_parts (
|
|
311
|
+
upload_id TEXT NOT NULL REFERENCES _mf_multipart_uploads(upload_id),
|
|
312
|
+
part_number INTEGER NOT NULL,
|
|
313
|
+
blob_id TEXT NOT NULL,
|
|
314
|
+
size INTEGER NOT NULL,
|
|
315
|
+
etag TEXT NOT NULL,
|
|
316
|
+
checksum_md5 TEXT NOT NULL,
|
|
317
|
+
object_key TEXT REFERENCES _mf_objects(key) DEFERRABLE INITIALLY DEFERRED,
|
|
318
|
+
PRIMARY KEY (upload_id, part_number)
|
|
319
|
+
);
|
|
320
|
+
`, DateSchema = z.coerce.number().transform((value) => new Date(value)), RecordSchema = z.object({
|
|
321
|
+
k: z.string(),
|
|
322
|
+
v: z.string()
|
|
323
|
+
}).array().transform(
|
|
324
|
+
(entries) => Object.fromEntries(entries.map(({ k, v }) => [k, v]))
|
|
325
|
+
), R2RangeSchema = z.object({
|
|
326
|
+
offset: z.coerce.number().optional(),
|
|
327
|
+
length: z.coerce.number().optional(),
|
|
328
|
+
suffix: z.coerce.number().optional()
|
|
329
|
+
}), R2EtagSchema = z.discriminatedUnion("type", [
|
|
330
|
+
z.object({ type: z.literal("strong"), value: z.string() }),
|
|
331
|
+
z.object({ type: z.literal("weak"), value: z.string() }),
|
|
332
|
+
z.object({ type: z.literal("wildcard") })
|
|
333
|
+
]), R2EtagMatchSchema = R2EtagSchema.array().min(1).optional(), R2ConditionalSchema = z.object({
|
|
334
|
+
// Performs the operation if the object's ETag matches the given string
|
|
335
|
+
etagMatches: R2EtagMatchSchema,
|
|
336
|
+
// "If-Match"
|
|
337
|
+
// Performs the operation if the object's ETag does NOT match the given string
|
|
338
|
+
etagDoesNotMatch: R2EtagMatchSchema,
|
|
339
|
+
// "If-None-Match"
|
|
340
|
+
// Performs the operation if the object was uploaded BEFORE the given date
|
|
341
|
+
uploadedBefore: DateSchema.optional(),
|
|
342
|
+
// "If-Unmodified-Since"
|
|
343
|
+
// Performs the operation if the object was uploaded AFTER the given date
|
|
344
|
+
uploadedAfter: DateSchema.optional(),
|
|
345
|
+
// "If-Modified-Since"
|
|
346
|
+
// Truncates dates to seconds before performing comparisons
|
|
347
|
+
secondsGranularity: z.oboolean()
|
|
348
|
+
}), R2ChecksumsSchema = z.object({
|
|
349
|
+
0: HexDataSchema.optional(),
|
|
350
|
+
1: HexDataSchema.optional(),
|
|
351
|
+
2: HexDataSchema.optional(),
|
|
352
|
+
3: HexDataSchema.optional(),
|
|
353
|
+
4: HexDataSchema.optional()
|
|
354
|
+
}).transform((checksums) => ({
|
|
355
|
+
md5: checksums[0],
|
|
356
|
+
sha1: checksums[1],
|
|
357
|
+
sha256: checksums[2],
|
|
358
|
+
sha384: checksums[3],
|
|
359
|
+
sha512: checksums[4]
|
|
360
|
+
})), R2PublishedPartSchema = z.object({
|
|
361
|
+
etag: z.string(),
|
|
362
|
+
part: z.number()
|
|
363
|
+
}), R2HttpFieldsSchema = z.object({
|
|
364
|
+
contentType: z.ostring(),
|
|
365
|
+
contentLanguage: z.ostring(),
|
|
366
|
+
contentDisposition: z.ostring(),
|
|
367
|
+
contentEncoding: z.ostring(),
|
|
368
|
+
cacheControl: z.ostring(),
|
|
369
|
+
cacheExpiry: z.coerce.number().optional()
|
|
370
|
+
}), R2HeadRequestSchema = z.object({
|
|
371
|
+
method: z.literal("head"),
|
|
372
|
+
object: z.string()
|
|
373
|
+
}), R2GetRequestSchema = z.object({
|
|
374
|
+
method: z.literal("get"),
|
|
375
|
+
object: z.string(),
|
|
376
|
+
// Specifies that only a specific length (from an optional offset) or suffix
|
|
377
|
+
// of bytes from the object should be returned. Refer to
|
|
378
|
+
// https://developers.cloudflare.com/r2/runtime-apis/#ranged-reads.
|
|
379
|
+
range: R2RangeSchema.optional(),
|
|
380
|
+
rangeHeader: z.ostring(),
|
|
381
|
+
// Specifies that the object should only be returned given satisfaction of
|
|
382
|
+
// certain conditions in the R2Conditional. Refer to R2Conditional above.
|
|
383
|
+
onlyIf: R2ConditionalSchema.optional()
|
|
384
|
+
}), R2PutRequestSchema = z.object({
|
|
385
|
+
method: z.literal("put"),
|
|
386
|
+
object: z.string(),
|
|
387
|
+
customFields: RecordSchema.optional(),
|
|
388
|
+
// (renamed in transform)
|
|
389
|
+
httpFields: R2HttpFieldsSchema.optional(),
|
|
390
|
+
// (renamed in transform)
|
|
391
|
+
onlyIf: R2ConditionalSchema.optional(),
|
|
392
|
+
md5: Base64DataSchema.optional(),
|
|
393
|
+
// (intentionally base64, not hex)
|
|
394
|
+
sha1: HexDataSchema.optional(),
|
|
395
|
+
sha256: HexDataSchema.optional(),
|
|
396
|
+
sha384: HexDataSchema.optional(),
|
|
397
|
+
sha512: HexDataSchema.optional()
|
|
398
|
+
}).transform((value) => ({
|
|
399
|
+
method: value.method,
|
|
400
|
+
object: value.object,
|
|
401
|
+
customMetadata: value.customFields,
|
|
402
|
+
httpMetadata: value.httpFields,
|
|
403
|
+
onlyIf: value.onlyIf,
|
|
404
|
+
md5: value.md5,
|
|
405
|
+
sha1: value.sha1,
|
|
406
|
+
sha256: value.sha256,
|
|
407
|
+
sha384: value.sha384,
|
|
408
|
+
sha512: value.sha512
|
|
409
|
+
})), R2CreateMultipartUploadRequestSchema = z.object({
|
|
410
|
+
method: z.literal("createMultipartUpload"),
|
|
411
|
+
object: z.string(),
|
|
412
|
+
customFields: RecordSchema.optional(),
|
|
413
|
+
// (renamed in transform)
|
|
414
|
+
httpFields: R2HttpFieldsSchema.optional()
|
|
415
|
+
// (renamed in transform)
|
|
416
|
+
}).transform((value) => ({
|
|
417
|
+
method: value.method,
|
|
418
|
+
object: value.object,
|
|
419
|
+
customMetadata: value.customFields,
|
|
420
|
+
httpMetadata: value.httpFields
|
|
421
|
+
})), R2UploadPartRequestSchema = z.object({
|
|
422
|
+
method: z.literal("uploadPart"),
|
|
423
|
+
object: z.string(),
|
|
424
|
+
uploadId: z.string(),
|
|
425
|
+
partNumber: z.number()
|
|
426
|
+
}), R2CompleteMultipartUploadRequestSchema = z.object({
|
|
427
|
+
method: z.literal("completeMultipartUpload"),
|
|
428
|
+
object: z.string(),
|
|
429
|
+
uploadId: z.string(),
|
|
430
|
+
parts: R2PublishedPartSchema.array()
|
|
431
|
+
}), R2AbortMultipartUploadRequestSchema = z.object({
|
|
432
|
+
method: z.literal("abortMultipartUpload"),
|
|
433
|
+
object: z.string(),
|
|
434
|
+
uploadId: z.string()
|
|
435
|
+
}), R2ListRequestSchema = z.object({
|
|
436
|
+
method: z.literal("list"),
|
|
437
|
+
limit: z.onumber(),
|
|
438
|
+
prefix: z.ostring(),
|
|
439
|
+
cursor: z.ostring(),
|
|
440
|
+
delimiter: z.ostring(),
|
|
441
|
+
startAfter: z.ostring(),
|
|
442
|
+
include: z.union([z.literal(0), z.literal(1)]).transform((value) => value === 0 ? "httpMetadata" : "customMetadata").array().optional()
|
|
443
|
+
}), R2DeleteRequestSchema = z.intersection(
|
|
444
|
+
z.object({ method: z.literal("delete") }),
|
|
445
|
+
z.union([
|
|
446
|
+
z.object({ object: z.string() }),
|
|
447
|
+
z.object({ objects: z.string().array() })
|
|
448
|
+
])
|
|
449
|
+
), R2BindingRequestSchema = z.union([
|
|
450
|
+
R2HeadRequestSchema,
|
|
451
|
+
R2GetRequestSchema,
|
|
452
|
+
R2PutRequestSchema,
|
|
453
|
+
R2CreateMultipartUploadRequestSchema,
|
|
454
|
+
R2UploadPartRequestSchema,
|
|
455
|
+
R2CompleteMultipartUploadRequestSchema,
|
|
456
|
+
R2AbortMultipartUploadRequestSchema,
|
|
457
|
+
R2ListRequestSchema,
|
|
458
|
+
R2DeleteRequestSchema
|
|
459
|
+
]);
|
|
460
|
+
|
|
461
|
+
// src/workers/r2/validator.worker.ts
|
|
462
|
+
import assert from "node:assert";
|
|
463
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
464
|
+
import { parseRanges } from "miniflare:shared";
|
|
465
|
+
function identity(ms) {
|
|
466
|
+
return ms;
|
|
467
|
+
}
|
|
468
|
+
function truncateToSeconds(ms) {
|
|
469
|
+
return Math.floor(ms / 1e3) * 1e3;
|
|
470
|
+
}
|
|
471
|
+
function includesEtag(conditions, etag, comparison) {
|
|
472
|
+
for (let condition of conditions)
|
|
473
|
+
if (condition.type === "wildcard" || condition.value === etag && (condition.type === "strong" || comparison === "weak"))
|
|
474
|
+
return !0;
|
|
475
|
+
return !1;
|
|
476
|
+
}
|
|
477
|
+
function _testR2Conditional(cond, metadata) {
|
|
478
|
+
if (metadata === void 0) {
|
|
479
|
+
let ifMatch2 = cond.etagMatches === void 0, ifModifiedSince2 = cond.uploadedAfter === void 0;
|
|
480
|
+
return ifMatch2 && ifModifiedSince2;
|
|
481
|
+
}
|
|
482
|
+
let { etag, uploaded: lastModifiedRaw } = metadata, ifMatch = cond.etagMatches === void 0 || includesEtag(cond.etagMatches, etag, "strong"), ifNoneMatch = cond.etagDoesNotMatch === void 0 || !includesEtag(cond.etagDoesNotMatch, etag, "weak"), maybeTruncate = cond.secondsGranularity ? truncateToSeconds : identity, lastModified = maybeTruncate(lastModifiedRaw), ifModifiedSince = cond.uploadedAfter === void 0 || maybeTruncate(cond.uploadedAfter.getTime()) < lastModified || cond.etagDoesNotMatch !== void 0 && ifNoneMatch, ifUnmodifiedSince = cond.uploadedBefore === void 0 || lastModified < maybeTruncate(cond.uploadedBefore.getTime()) || cond.etagMatches !== void 0 && ifMatch;
|
|
483
|
+
return ifMatch && ifNoneMatch && ifModifiedSince && ifUnmodifiedSince;
|
|
484
|
+
}
|
|
485
|
+
var R2_HASH_ALGORITHMS = [
|
|
486
|
+
{ name: "MD5", field: "md5" },
|
|
487
|
+
{ name: "SHA-1", field: "sha1" },
|
|
488
|
+
{ name: "SHA-256", field: "sha256" },
|
|
489
|
+
{ name: "SHA-384", field: "sha384" },
|
|
490
|
+
{ name: "SHA-512", field: "sha512" }
|
|
491
|
+
];
|
|
492
|
+
function serialisedLength(x) {
|
|
493
|
+
for (let i = 0; i < x.length; i++)
|
|
494
|
+
if (x.charCodeAt(i) >= 256) return x.length * 2;
|
|
495
|
+
return x.length;
|
|
496
|
+
}
|
|
497
|
+
var Validator = class {
|
|
498
|
+
hash(digests, hashes) {
|
|
499
|
+
let checksums = {};
|
|
500
|
+
for (let { name, field } of R2_HASH_ALGORITHMS) {
|
|
501
|
+
let providedHash = hashes[field];
|
|
502
|
+
if (providedHash !== void 0) {
|
|
503
|
+
let computedHash = digests.get(name);
|
|
504
|
+
if (assert(computedHash !== void 0), !providedHash.equals(computedHash))
|
|
505
|
+
throw new BadDigest(name, providedHash, computedHash);
|
|
506
|
+
checksums[field] = computedHash.toString("hex");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return checksums;
|
|
510
|
+
}
|
|
511
|
+
condition(meta, onlyIf) {
|
|
512
|
+
if (onlyIf !== void 0 && !_testR2Conditional(onlyIf, meta))
|
|
513
|
+
throw new PreconditionFailed();
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
range(options, size) {
|
|
517
|
+
if (options.rangeHeader !== void 0) {
|
|
518
|
+
let ranges = parseRanges(options.rangeHeader, size);
|
|
519
|
+
if (ranges?.length === 1) return ranges[0];
|
|
520
|
+
} else if (options.range !== void 0) {
|
|
521
|
+
let { offset, length, suffix } = options.range;
|
|
522
|
+
if (suffix !== void 0) {
|
|
523
|
+
if (suffix <= 0) throw new InvalidRange();
|
|
524
|
+
suffix > size && (suffix = size), offset = size - suffix, length = suffix;
|
|
525
|
+
}
|
|
526
|
+
if (offset === void 0 && (offset = 0), length === void 0 && (length = size - offset), offset < 0 || offset > size || length <= 0) throw new InvalidRange();
|
|
527
|
+
return offset + length > size && (length = size - offset), { start: offset, end: offset + length - 1 };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
size(size) {
|
|
531
|
+
if (size > R2Limits.MAX_VALUE_SIZE)
|
|
532
|
+
throw new EntityTooLarge();
|
|
533
|
+
return this;
|
|
534
|
+
}
|
|
535
|
+
metadataSize(customMetadata) {
|
|
536
|
+
if (customMetadata === void 0) return this;
|
|
537
|
+
let metadataLength = 0;
|
|
538
|
+
for (let [key, value] of Object.entries(customMetadata))
|
|
539
|
+
metadataLength += serialisedLength(key) + serialisedLength(value);
|
|
540
|
+
if (metadataLength > R2Limits.MAX_METADATA_SIZE)
|
|
541
|
+
throw new MetadataTooLarge();
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
key(key) {
|
|
545
|
+
if (Buffer2.byteLength(key) > R2Limits.MAX_KEY_SIZE)
|
|
546
|
+
throw new InvalidObjectName();
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
limit(limit) {
|
|
550
|
+
if (limit !== void 0 && (limit < 1 || limit > R2Limits.MAX_LIST_KEYS))
|
|
551
|
+
throw new InvalidMaxKeys();
|
|
552
|
+
return this;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// src/workers/r2/bucket.worker.ts
|
|
557
|
+
var DigestingStream = class extends TransformStream {
|
|
558
|
+
digests;
|
|
559
|
+
constructor(algorithms) {
|
|
560
|
+
let digests = new DeferredPromise(), hashes = algorithms.map((alg) => {
|
|
561
|
+
let stream = new crypto.DigestStream(alg), writer = stream.getWriter();
|
|
562
|
+
return { stream, writer };
|
|
563
|
+
});
|
|
564
|
+
super({
|
|
565
|
+
async transform(chunk, controller) {
|
|
566
|
+
for (let hash of hashes) await hash.writer.write(chunk);
|
|
567
|
+
controller.enqueue(chunk);
|
|
568
|
+
},
|
|
569
|
+
async flush() {
|
|
570
|
+
let result = /* @__PURE__ */ new Map();
|
|
571
|
+
for (let i = 0; i < hashes.length; i++)
|
|
572
|
+
await hashes[i].writer.close(), result.set(algorithms[i], Buffer3.from(await hashes[i].stream.digest));
|
|
573
|
+
digests.resolve(result);
|
|
574
|
+
}
|
|
575
|
+
}), this.digests = digests;
|
|
576
|
+
}
|
|
577
|
+
}, validate = new Validator(), decoder = new TextDecoder();
|
|
578
|
+
function generateVersion() {
|
|
579
|
+
return Buffer3.from(crypto.getRandomValues(new Uint8Array(16))).toString(
|
|
580
|
+
"hex"
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
function generateId() {
|
|
584
|
+
return Buffer3.from(crypto.getRandomValues(new Uint8Array(128))).toString(
|
|
585
|
+
"base64url"
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
function generateMultipartEtag(md5Hexes) {
|
|
589
|
+
let hash = createHash("md5");
|
|
590
|
+
for (let md5Hex of md5Hexes) hash.update(md5Hex, "hex");
|
|
591
|
+
return `${hash.digest("hex")}-${md5Hexes.length}`;
|
|
592
|
+
}
|
|
593
|
+
function rangeOverlaps(a, b) {
|
|
594
|
+
return a.start <= b.end && b.start <= a.end;
|
|
595
|
+
}
|
|
596
|
+
async function decodeMetadata(req) {
|
|
597
|
+
let metadataSize = parseInt(req.headers.get(R2Headers.METADATA_SIZE));
|
|
598
|
+
if (Number.isNaN(metadataSize)) throw new InvalidMetadata();
|
|
599
|
+
assert2(req.body !== null);
|
|
600
|
+
let body = req.body, [metadataBuffer, value] = await readPrefix(body, metadataSize), metadataJson = decoder.decode(metadataBuffer);
|
|
601
|
+
return { metadata: R2BindingRequestSchema.parse(JSON.parse(metadataJson)), metadataSize, value };
|
|
602
|
+
}
|
|
603
|
+
function decodeHeaderMetadata(req) {
|
|
604
|
+
let header = req.headers.get(R2Headers.REQUEST);
|
|
605
|
+
if (header === null) throw new InvalidMetadata();
|
|
606
|
+
return R2BindingRequestSchema.parse(JSON.parse(header));
|
|
607
|
+
}
|
|
608
|
+
function encodeResult(result) {
|
|
609
|
+
let encoded;
|
|
610
|
+
return result instanceof InternalR2Object ? encoded = result.encode() : encoded = InternalR2Object.encodeMultiple(result), new Response(encoded.value, {
|
|
611
|
+
headers: {
|
|
612
|
+
[R2Headers.METADATA_SIZE]: `${encoded.metadataSize}`,
|
|
613
|
+
"Content-Type": "application/json",
|
|
614
|
+
"Content-Length": `${encoded.size}`
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function encodeJSONResult(result) {
|
|
619
|
+
let encoded = JSON.stringify(result);
|
|
620
|
+
return new Response(encoded, {
|
|
621
|
+
headers: {
|
|
622
|
+
[R2Headers.METADATA_SIZE]: `${Buffer3.byteLength(encoded)}`,
|
|
623
|
+
"Content-Type": "application/json"
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
function sqlStmts(db) {
|
|
628
|
+
let stmtGetPreviousByKey = db.stmt("SELECT blob_id, etag, uploaded FROM _mf_objects WHERE key = :key"), stmtGetByKey = db.stmt(`
|
|
629
|
+
SELECT key, blob_id, version, size, etag, uploaded, checksums, http_metadata, custom_metadata
|
|
630
|
+
FROM _mf_objects WHERE key = :key
|
|
631
|
+
`), stmtPut = db.stmt(`
|
|
632
|
+
INSERT OR REPLACE INTO _mf_objects (key, blob_id, version, size, etag, uploaded, checksums, http_metadata, custom_metadata)
|
|
633
|
+
VALUES (:key, :blob_id, :version, :size, :etag, :uploaded, :checksums, :http_metadata, :custom_metadata)
|
|
634
|
+
`), stmtDelete = db.stmt("DELETE FROM _mf_objects WHERE key = :key RETURNING blob_id");
|
|
635
|
+
function stmtListWithoutDelimiter(...extraColumns) {
|
|
636
|
+
let columns = [
|
|
637
|
+
"key",
|
|
638
|
+
"version",
|
|
639
|
+
"size",
|
|
640
|
+
"etag",
|
|
641
|
+
"uploaded",
|
|
642
|
+
"checksums",
|
|
643
|
+
...extraColumns
|
|
644
|
+
];
|
|
645
|
+
return db.stmt(`
|
|
646
|
+
SELECT ${columns.join(", ")}
|
|
647
|
+
FROM _mf_objects
|
|
648
|
+
WHERE substr(key, 1, length(:prefix)) = :prefix
|
|
649
|
+
AND (:start_after IS NULL OR key > :start_after)
|
|
650
|
+
ORDER BY key LIMIT :limit
|
|
651
|
+
`);
|
|
652
|
+
}
|
|
653
|
+
let stmtGetUploadState = db.stmt(
|
|
654
|
+
// For checking current upload state
|
|
655
|
+
"SELECT state FROM _mf_multipart_uploads WHERE upload_id = :upload_id AND key = :key"
|
|
656
|
+
), stmtGetUploadMetadata = db.stmt(
|
|
657
|
+
// For checking current upload state, and getting metadata for completion
|
|
658
|
+
"SELECT http_metadata, custom_metadata, state FROM _mf_multipart_uploads WHERE upload_id = :upload_id AND key = :key"
|
|
659
|
+
), stmtUpdateUploadState = db.stmt(
|
|
660
|
+
// For completing/aborting uploads
|
|
661
|
+
"UPDATE _mf_multipart_uploads SET state = :state WHERE upload_id = :upload_id"
|
|
662
|
+
), stmtGetPreviousPartByNumber = db.stmt(
|
|
663
|
+
// For getting part number's previous blob ID to garbage collect
|
|
664
|
+
"SELECT blob_id FROM _mf_multipart_parts WHERE upload_id = :upload_id AND part_number = :part_number"
|
|
665
|
+
), stmtPutPart = db.stmt(
|
|
666
|
+
// For recording metadata when uploading parts
|
|
667
|
+
`INSERT OR REPLACE INTO _mf_multipart_parts (upload_id, part_number, blob_id, size, etag, checksum_md5)
|
|
668
|
+
VALUES (:upload_id, :part_number, :blob_id, :size, :etag, :checksum_md5)`
|
|
669
|
+
), stmtLinkPart = db.stmt(
|
|
670
|
+
// For linking parts with an object when completing uploads
|
|
671
|
+
`UPDATE _mf_multipart_parts SET object_key = :object_key
|
|
672
|
+
WHERE upload_id = :upload_id AND part_number = :part_number`
|
|
673
|
+
), stmtDeletePartsByUploadId = db.stmt(
|
|
674
|
+
// For deleting parts when aborting uploads
|
|
675
|
+
"DELETE FROM _mf_multipart_parts WHERE upload_id = :upload_id RETURNING blob_id"
|
|
676
|
+
), stmtDeleteUnlinkedPartsByUploadId = db.stmt(
|
|
677
|
+
// For deleting unused parts when completing uploads
|
|
678
|
+
"DELETE FROM _mf_multipart_parts WHERE upload_id = :upload_id AND object_key IS NULL RETURNING blob_id"
|
|
679
|
+
), stmtDeletePartsByKey = db.stmt(
|
|
680
|
+
// For deleting dangling parts when overwriting an existing key
|
|
681
|
+
"DELETE FROM _mf_multipart_parts WHERE object_key = :object_key RETURNING blob_id"
|
|
682
|
+
), stmtListPartsByUploadId = db.stmt(
|
|
683
|
+
// For getting part metadata when completing uploads
|
|
684
|
+
`SELECT upload_id, part_number, blob_id, size, etag, checksum_md5, object_key
|
|
685
|
+
FROM _mf_multipart_parts WHERE upload_id = :upload_id`
|
|
686
|
+
), stmtListPartsByKey = db.stmt(
|
|
687
|
+
// For getting part metadata when getting values, size included for range
|
|
688
|
+
// requests, so we only need to read blobs containing the required data
|
|
689
|
+
"SELECT blob_id, size FROM _mf_multipart_parts WHERE object_key = :object_key ORDER BY part_number"
|
|
690
|
+
);
|
|
691
|
+
return {
|
|
692
|
+
getByKey: stmtGetByKey,
|
|
693
|
+
getPartsByKey: db.txn((key) => {
|
|
694
|
+
let row = get(stmtGetByKey({ key }));
|
|
695
|
+
if (row !== void 0)
|
|
696
|
+
if (row.blob_id === null) {
|
|
697
|
+
let partsRows = all(stmtListPartsByKey({ object_key: key }));
|
|
698
|
+
return { row, parts: partsRows };
|
|
699
|
+
} else
|
|
700
|
+
return { row };
|
|
701
|
+
}),
|
|
702
|
+
put: db.txn((newRow, onlyIf) => {
|
|
703
|
+
let key = newRow.key, row = get(stmtGetPreviousByKey({ key }));
|
|
704
|
+
onlyIf !== void 0 && validate.condition(row, onlyIf), stmtPut(newRow);
|
|
705
|
+
let maybeOldBlobId = row?.blob_id;
|
|
706
|
+
return maybeOldBlobId === void 0 ? [] : maybeOldBlobId === null ? all(stmtDeletePartsByKey({ object_key: key })).map(({ blob_id }) => blob_id) : [maybeOldBlobId];
|
|
707
|
+
}),
|
|
708
|
+
deleteByKeys: db.txn((keys) => {
|
|
709
|
+
let oldBlobIds = [];
|
|
710
|
+
for (let key of keys) {
|
|
711
|
+
let maybeOldBlobId = get(stmtDelete({ key }))?.blob_id;
|
|
712
|
+
if (maybeOldBlobId === null) {
|
|
713
|
+
let partRows = stmtDeletePartsByKey({ object_key: key });
|
|
714
|
+
for (let partRow of partRows) oldBlobIds.push(partRow.blob_id);
|
|
715
|
+
} else maybeOldBlobId !== void 0 && oldBlobIds.push(maybeOldBlobId);
|
|
716
|
+
}
|
|
717
|
+
return oldBlobIds;
|
|
718
|
+
}),
|
|
719
|
+
listWithoutDelimiter: stmtListWithoutDelimiter(),
|
|
720
|
+
listHttpMetadataWithoutDelimiter: stmtListWithoutDelimiter("http_metadata"),
|
|
721
|
+
listCustomMetadataWithoutDelimiter: stmtListWithoutDelimiter("custom_metadata"),
|
|
722
|
+
listHttpCustomMetadataWithoutDelimiter: stmtListWithoutDelimiter(
|
|
723
|
+
"http_metadata",
|
|
724
|
+
"custom_metadata"
|
|
725
|
+
),
|
|
726
|
+
listMetadata: db.stmt(`
|
|
727
|
+
SELECT
|
|
728
|
+
-- When grouping by a delimited prefix, this will give us the last key with that prefix.
|
|
729
|
+
-- NOTE: we'll use this for the next cursor. If we didn't return the last key, the next page may return the
|
|
730
|
+
-- same delimited prefix. Essentially, we're skipping over all keys with this group's delimited prefix.
|
|
731
|
+
-- When grouping by a key, this will just give us the key.
|
|
732
|
+
max(key) AS last_key,
|
|
733
|
+
iif(
|
|
734
|
+
-- Try get 1-indexed position \`i\` of :delimiter in rest of key after :prefix...
|
|
735
|
+
instr(substr(key, length(:prefix) + 1), :delimiter),
|
|
736
|
+
-- ...if found, we have a delimited prefix of the :prefix followed by the rest of key up to and including the :delimiter
|
|
737
|
+
'dlp:' || substr(key, 1, length(:prefix) + instr(substr(key, length(:prefix) + 1), :delimiter) + length(:delimiter) - 1),
|
|
738
|
+
-- ...otherwise, we just have a regular key
|
|
739
|
+
'key:' || key
|
|
740
|
+
) AS delimited_prefix_or_key,
|
|
741
|
+
-- NOTE: we'll ignore metadata for delimited prefix rows, so it doesn't matter which keys' we return
|
|
742
|
+
version, size, etag, uploaded, checksums, http_metadata, custom_metadata
|
|
743
|
+
FROM _mf_objects
|
|
744
|
+
WHERE substr(key, 1, length(:prefix)) = :prefix
|
|
745
|
+
AND (:start_after IS NULL OR key > :start_after)
|
|
746
|
+
GROUP BY delimited_prefix_or_key -- Group keys with same delimited prefix into a row, leaving others in their own rows
|
|
747
|
+
ORDER BY last_key LIMIT :limit;
|
|
748
|
+
`),
|
|
749
|
+
createMultipartUpload: db.stmt(`
|
|
750
|
+
INSERT INTO _mf_multipart_uploads (upload_id, key, http_metadata, custom_metadata)
|
|
751
|
+
VALUES (:upload_id, :key, :http_metadata, :custom_metadata)
|
|
752
|
+
`),
|
|
753
|
+
putPart: db.txn(
|
|
754
|
+
(key, newRow) => {
|
|
755
|
+
if (get(
|
|
756
|
+
stmtGetUploadState({
|
|
757
|
+
key,
|
|
758
|
+
upload_id: newRow.upload_id
|
|
759
|
+
})
|
|
760
|
+
)?.state !== MultipartUploadState.IN_PROGRESS)
|
|
761
|
+
throw new NoSuchUpload();
|
|
762
|
+
let partRow = get(
|
|
763
|
+
stmtGetPreviousPartByNumber({
|
|
764
|
+
upload_id: newRow.upload_id,
|
|
765
|
+
part_number: newRow.part_number
|
|
766
|
+
})
|
|
767
|
+
);
|
|
768
|
+
return stmtPutPart(newRow), partRow?.blob_id;
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
completeMultipartUpload: db.txn(
|
|
772
|
+
(key, upload_id, selectedParts, minPartSize) => {
|
|
773
|
+
let uploadRow = get(stmtGetUploadMetadata({ key, upload_id }));
|
|
774
|
+
if (uploadRow === void 0)
|
|
775
|
+
throw new InternalError();
|
|
776
|
+
if (uploadRow.state > MultipartUploadState.IN_PROGRESS)
|
|
777
|
+
throw new NoSuchUpload();
|
|
778
|
+
let partNumberSet = /* @__PURE__ */ new Set();
|
|
779
|
+
for (let { part } of selectedParts) {
|
|
780
|
+
if (partNumberSet.has(part)) throw new InternalError();
|
|
781
|
+
partNumberSet.add(part);
|
|
782
|
+
}
|
|
783
|
+
let uploadedPartRows = stmtListPartsByUploadId({ upload_id }), uploadedParts = /* @__PURE__ */ new Map();
|
|
784
|
+
for (let row of uploadedPartRows)
|
|
785
|
+
uploadedParts.set(row.part_number, row);
|
|
786
|
+
let parts = selectedParts.map((selectedPart) => {
|
|
787
|
+
let uploadedPart = uploadedParts.get(selectedPart.part);
|
|
788
|
+
if (uploadedPart?.etag !== selectedPart.etag)
|
|
789
|
+
throw new InvalidPart();
|
|
790
|
+
return uploadedPart;
|
|
791
|
+
});
|
|
792
|
+
for (let part of parts.slice(0, -1))
|
|
793
|
+
if (part.size < minPartSize)
|
|
794
|
+
throw new EntityTooSmall();
|
|
795
|
+
parts.sort((a, b) => a.part_number - b.part_number);
|
|
796
|
+
let partSize;
|
|
797
|
+
for (let part of parts.slice(0, -1))
|
|
798
|
+
if (partSize ??= part.size, part.size < minPartSize || part.size !== partSize)
|
|
799
|
+
throw new BadUpload();
|
|
800
|
+
if (partSize !== void 0 && parts[parts.length - 1].size > partSize)
|
|
801
|
+
throw new BadUpload();
|
|
802
|
+
let oldBlobIds = [], maybeOldBlobId = get(stmtGetPreviousByKey({ key }))?.blob_id;
|
|
803
|
+
if (maybeOldBlobId === null) {
|
|
804
|
+
let partRows2 = stmtDeletePartsByKey({ object_key: key });
|
|
805
|
+
for (let partRow of partRows2) oldBlobIds.push(partRow.blob_id);
|
|
806
|
+
} else maybeOldBlobId !== void 0 && oldBlobIds.push(maybeOldBlobId);
|
|
807
|
+
let totalSize = parts.reduce((acc, { size }) => acc + size, 0), etag = generateMultipartEtag(
|
|
808
|
+
parts.map(({ checksum_md5 }) => checksum_md5)
|
|
809
|
+
), newRow = {
|
|
810
|
+
key,
|
|
811
|
+
blob_id: null,
|
|
812
|
+
version: generateVersion(),
|
|
813
|
+
size: totalSize,
|
|
814
|
+
etag,
|
|
815
|
+
uploaded: Date.now(),
|
|
816
|
+
checksums: "{}",
|
|
817
|
+
http_metadata: uploadRow.http_metadata,
|
|
818
|
+
custom_metadata: uploadRow.custom_metadata
|
|
819
|
+
};
|
|
820
|
+
stmtPut(newRow);
|
|
821
|
+
for (let part of parts)
|
|
822
|
+
stmtLinkPart({
|
|
823
|
+
upload_id,
|
|
824
|
+
part_number: part.part_number,
|
|
825
|
+
object_key: key
|
|
826
|
+
});
|
|
827
|
+
let partRows = stmtDeleteUnlinkedPartsByUploadId({ upload_id });
|
|
828
|
+
for (let partRow of partRows) oldBlobIds.push(partRow.blob_id);
|
|
829
|
+
return stmtUpdateUploadState({
|
|
830
|
+
upload_id,
|
|
831
|
+
state: MultipartUploadState.COMPLETED
|
|
832
|
+
}), { newRow, oldBlobIds };
|
|
833
|
+
}
|
|
834
|
+
),
|
|
835
|
+
abortMultipartUpload: db.txn((key, upload_id) => {
|
|
836
|
+
let uploadRow = get(stmtGetUploadState({ key, upload_id }));
|
|
837
|
+
if (uploadRow === void 0)
|
|
838
|
+
throw new InternalError();
|
|
839
|
+
if (uploadRow.state > MultipartUploadState.IN_PROGRESS)
|
|
840
|
+
return [];
|
|
841
|
+
let oldBlobIds = all(stmtDeletePartsByUploadId({ upload_id })).map(({ blob_id }) => blob_id);
|
|
842
|
+
return stmtUpdateUploadState({
|
|
843
|
+
upload_id,
|
|
844
|
+
state: MultipartUploadState.ABORTED
|
|
845
|
+
}), oldBlobIds;
|
|
846
|
+
})
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
var R2BucketObject = class extends MiniflareDurableObject {
|
|
850
|
+
#stmts;
|
|
851
|
+
// Multipart uploads are stored as multiple blobs. Therefore, when reading a
|
|
852
|
+
// multipart upload, we'll be reading multiple blobs. When an object is
|
|
853
|
+
// deleted, all its blobs are deleted in the background.
|
|
854
|
+
//
|
|
855
|
+
// Normally for single part objects, this is fine, since we'd open a handle to
|
|
856
|
+
// a single blob, which we'd have until we closed it, at which point the blob
|
|
857
|
+
// may be deleted. With multipart, we don't want to open handles for all blobs
|
|
858
|
+
// as we could hit open file descriptor limits. Similarly, we don't want to
|
|
859
|
+
// read all blobs first, as we'd have to buffer them.
|
|
860
|
+
//
|
|
861
|
+
// Instead, we set up in-process locking on blobs needed for multipart reads.
|
|
862
|
+
// When we start a multipart read, we acquire all the blobs we need, then
|
|
863
|
+
// release them as we've streamed each part. Multiple multipart reads may be
|
|
864
|
+
// in-progress at any given time, so we use a wait group.
|
|
865
|
+
//
|
|
866
|
+
// This assumes we only ever have a single Miniflare instance operating on a
|
|
867
|
+
// blob store, which is always true for in-memory stores, and usually true for
|
|
868
|
+
// on-disk ones. If we really wanted to do this properly, we could store the
|
|
869
|
+
// bookkeeping for the wait group in SQLite, but then we'd have to implement
|
|
870
|
+
// some inter-process signalling/subscription system.
|
|
871
|
+
#inUseBlobs = /* @__PURE__ */ new Map();
|
|
872
|
+
constructor(state, env) {
|
|
873
|
+
super(state, env), this.db.exec("PRAGMA case_sensitive_like = TRUE"), this.db.exec(SQL_SCHEMA), this.#stmts = sqlStmts(this.db);
|
|
874
|
+
}
|
|
875
|
+
#acquireBlob(blobId) {
|
|
876
|
+
let waitGroup = this.#inUseBlobs.get(blobId);
|
|
877
|
+
waitGroup === void 0 ? (waitGroup = new WaitGroup(), this.#inUseBlobs.set(blobId, waitGroup), waitGroup.add(), waitGroup.wait().then(() => this.#inUseBlobs.delete(blobId))) : waitGroup.add();
|
|
878
|
+
}
|
|
879
|
+
#releaseBlob(blobId) {
|
|
880
|
+
this.#inUseBlobs.get(blobId)?.done();
|
|
881
|
+
}
|
|
882
|
+
#backgroundDelete(blobId) {
|
|
883
|
+
this.timers.queueMicrotask(async () => (await this.#inUseBlobs.get(blobId)?.wait(), this.blob.delete(blobId).catch((e) => {
|
|
884
|
+
console.error("R2BucketObject##backgroundDelete():", e);
|
|
885
|
+
})));
|
|
886
|
+
}
|
|
887
|
+
#assembleMultipartValue(parts, queryRange) {
|
|
888
|
+
let requiredParts = [], start = 0;
|
|
889
|
+
for (let part of parts) {
|
|
890
|
+
let partRange = { start, end: start + part.size - 1 };
|
|
891
|
+
if (rangeOverlaps(partRange, queryRange)) {
|
|
892
|
+
let range = {
|
|
893
|
+
start: Math.max(partRange.start, queryRange.start) - partRange.start,
|
|
894
|
+
end: Math.min(partRange.end, queryRange.end) - partRange.start
|
|
895
|
+
};
|
|
896
|
+
this.#acquireBlob(part.blob_id), requiredParts.push({ blobId: part.blob_id, range });
|
|
897
|
+
}
|
|
898
|
+
start = partRange.end + 1;
|
|
899
|
+
}
|
|
900
|
+
let identity2 = new TransformStream();
|
|
901
|
+
return (async () => {
|
|
902
|
+
let i = 0;
|
|
903
|
+
try {
|
|
904
|
+
for (; i < requiredParts.length; i++) {
|
|
905
|
+
let { blobId, range } = requiredParts[i], value = await this.blob.get(blobId, range), msg = `Expected to find blob "${blobId}" for multipart value`;
|
|
906
|
+
assert2(value !== null, msg), await value.pipeTo(identity2.writable, { preventClose: !0 }), this.#releaseBlob(blobId);
|
|
907
|
+
}
|
|
908
|
+
await identity2.writable.close();
|
|
909
|
+
} catch (e) {
|
|
910
|
+
await identity2.writable.abort(e);
|
|
911
|
+
} finally {
|
|
912
|
+
for (; i < requiredParts.length; i++)
|
|
913
|
+
this.#releaseBlob(requiredParts[i].blobId);
|
|
914
|
+
}
|
|
915
|
+
})(), identity2.readable;
|
|
916
|
+
}
|
|
917
|
+
async #head(key) {
|
|
918
|
+
validate.key(key);
|
|
919
|
+
let row = get(this.#stmts.getByKey({ key }));
|
|
920
|
+
if (row === void 0) throw new NoSuchKey();
|
|
921
|
+
let range = { offset: 0, length: row.size };
|
|
922
|
+
return new InternalR2Object(row, range);
|
|
923
|
+
}
|
|
924
|
+
async #get(key, opts) {
|
|
925
|
+
validate.key(key);
|
|
926
|
+
let result = this.#stmts.getPartsByKey(key);
|
|
927
|
+
if (result === void 0) throw new NoSuchKey();
|
|
928
|
+
let { row, parts } = result, defaultR2Range = { offset: 0, length: row.size };
|
|
929
|
+
try {
|
|
930
|
+
validate.condition(row, opts.onlyIf);
|
|
931
|
+
} catch (e) {
|
|
932
|
+
throw e instanceof PreconditionFailed && e.attach(new InternalR2Object(row, defaultR2Range)), e;
|
|
933
|
+
}
|
|
934
|
+
let range = validate.range(opts, row.size), r2Range;
|
|
935
|
+
if (range === void 0)
|
|
936
|
+
r2Range = defaultR2Range;
|
|
937
|
+
else {
|
|
938
|
+
let start = range.start, end = Math.min(range.end, row.size);
|
|
939
|
+
r2Range = { offset: start, length: end - start + 1 };
|
|
940
|
+
}
|
|
941
|
+
let value;
|
|
942
|
+
if (row.blob_id === null) {
|
|
943
|
+
assert2(parts !== void 0);
|
|
944
|
+
let defaultRange = { start: 0, end: row.size - 1 };
|
|
945
|
+
value = this.#assembleMultipartValue(parts, range ?? defaultRange);
|
|
946
|
+
} else if (value = await this.blob.get(row.blob_id, range), value === null) throw new NoSuchKey();
|
|
947
|
+
return new InternalR2ObjectBody(row, value, r2Range);
|
|
948
|
+
}
|
|
949
|
+
async #put(key, value, valueSize, opts) {
|
|
950
|
+
let algorithms = [];
|
|
951
|
+
for (let { name, field } of R2_HASH_ALGORITHMS)
|
|
952
|
+
(field === "md5" || opts[field] !== void 0) && algorithms.push(name);
|
|
953
|
+
let digesting = new DigestingStream(algorithms), blobId = await this.blob.put(value.pipeThrough(digesting)), digests = await digesting.digests, md5Digest = digests.get("MD5");
|
|
954
|
+
assert2(md5Digest !== void 0);
|
|
955
|
+
let md5DigestHex = md5Digest.toString("hex"), checksums = validate.key(key).size(valueSize).metadataSize(opts.customMetadata).hash(digests, opts), row = {
|
|
956
|
+
key,
|
|
957
|
+
blob_id: blobId,
|
|
958
|
+
version: generateVersion(),
|
|
959
|
+
size: valueSize,
|
|
960
|
+
etag: md5DigestHex,
|
|
961
|
+
uploaded: Date.now(),
|
|
962
|
+
checksums: JSON.stringify(checksums),
|
|
963
|
+
http_metadata: JSON.stringify(opts.httpMetadata ?? {}),
|
|
964
|
+
custom_metadata: JSON.stringify(opts.customMetadata ?? {})
|
|
965
|
+
}, oldBlobIds;
|
|
966
|
+
try {
|
|
967
|
+
oldBlobIds = this.#stmts.put(row, opts.onlyIf);
|
|
968
|
+
} catch (e) {
|
|
969
|
+
throw this.#backgroundDelete(blobId), e;
|
|
970
|
+
}
|
|
971
|
+
if (oldBlobIds !== void 0)
|
|
972
|
+
for (let blobId2 of oldBlobIds) this.#backgroundDelete(blobId2);
|
|
973
|
+
return new InternalR2Object(row);
|
|
974
|
+
}
|
|
975
|
+
#delete(keys) {
|
|
976
|
+
Array.isArray(keys) || (keys = [keys]);
|
|
977
|
+
for (let key of keys) validate.key(key);
|
|
978
|
+
let oldBlobIds = this.#stmts.deleteByKeys(keys);
|
|
979
|
+
for (let blobId of oldBlobIds) this.#backgroundDelete(blobId);
|
|
980
|
+
}
|
|
981
|
+
#listWithoutDelimiterQuery(excludeHttp, excludeCustom) {
|
|
982
|
+
return excludeHttp && excludeCustom ? this.#stmts.listWithoutDelimiter : excludeHttp ? this.#stmts.listCustomMetadataWithoutDelimiter : excludeCustom ? this.#stmts.listHttpMetadataWithoutDelimiter : this.#stmts.listHttpCustomMetadataWithoutDelimiter;
|
|
983
|
+
}
|
|
984
|
+
async #list(opts) {
|
|
985
|
+
let prefix = opts.prefix ?? "", limit = opts.limit ?? R2Limits.MAX_LIST_KEYS;
|
|
986
|
+
validate.limit(limit);
|
|
987
|
+
let include = opts.include ?? [];
|
|
988
|
+
include.length > 0 && (limit = Math.min(limit, 100));
|
|
989
|
+
let excludeHttp = !include.includes("httpMetadata"), excludeCustom = !include.includes("customMetadata"), rowObject = (row) => ((row.http_metadata === void 0 || excludeHttp) && (row.http_metadata = "{}"), (row.custom_metadata === void 0 || excludeCustom) && (row.custom_metadata = "{}"), new InternalR2Object(row)), startAfter = opts.startAfter;
|
|
990
|
+
if (opts.cursor !== void 0) {
|
|
991
|
+
let cursorStartAfter = base64Decode(opts.cursor);
|
|
992
|
+
(startAfter === void 0 || cursorStartAfter > startAfter) && (startAfter = cursorStartAfter);
|
|
993
|
+
}
|
|
994
|
+
let delimiter = opts.delimiter;
|
|
995
|
+
delimiter === "" && (delimiter = void 0);
|
|
996
|
+
let params = {
|
|
997
|
+
prefix,
|
|
998
|
+
start_after: startAfter ?? null,
|
|
999
|
+
// Increase the queried limit by 1, if we return this many results, we
|
|
1000
|
+
// know there are more rows. We'll truncate to the original limit before
|
|
1001
|
+
// returning results.
|
|
1002
|
+
limit: limit + 1
|
|
1003
|
+
}, objects, delimitedPrefixes = [], nextCursorStartAfter;
|
|
1004
|
+
if (delimiter !== void 0) {
|
|
1005
|
+
let rows = all(this.#stmts.listMetadata({ ...params, delimiter })), hasMoreRows = rows.length === limit + 1;
|
|
1006
|
+
rows.splice(limit, 1), objects = [];
|
|
1007
|
+
for (let row of rows)
|
|
1008
|
+
row.delimited_prefix_or_key.startsWith("dlp:") ? delimitedPrefixes.push(row.delimited_prefix_or_key.substring(4)) : objects.push(rowObject({ ...row, key: row.last_key }));
|
|
1009
|
+
hasMoreRows && (nextCursorStartAfter = rows[limit - 1].last_key);
|
|
1010
|
+
} else {
|
|
1011
|
+
let query = this.#listWithoutDelimiterQuery(excludeHttp, excludeCustom), rows = all(query(params)), hasMoreRows = rows.length === limit + 1;
|
|
1012
|
+
rows.splice(limit, 1), objects = rows.map(rowObject), hasMoreRows && (nextCursorStartAfter = rows[limit - 1].key);
|
|
1013
|
+
}
|
|
1014
|
+
let nextCursor = maybeApply(base64Encode, nextCursorStartAfter);
|
|
1015
|
+
return {
|
|
1016
|
+
objects,
|
|
1017
|
+
truncated: nextCursor !== void 0,
|
|
1018
|
+
cursor: nextCursor,
|
|
1019
|
+
delimitedPrefixes
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
async #createMultipartUpload(key, opts) {
|
|
1023
|
+
validate.key(key);
|
|
1024
|
+
let uploadId = generateId();
|
|
1025
|
+
return this.#stmts.createMultipartUpload({
|
|
1026
|
+
key,
|
|
1027
|
+
upload_id: uploadId,
|
|
1028
|
+
http_metadata: JSON.stringify(opts.httpMetadata ?? {}),
|
|
1029
|
+
custom_metadata: JSON.stringify(opts.customMetadata ?? {})
|
|
1030
|
+
}), { uploadId };
|
|
1031
|
+
}
|
|
1032
|
+
async #uploadPart(key, uploadId, partNumber, value, valueSize) {
|
|
1033
|
+
validate.key(key);
|
|
1034
|
+
let digesting = new DigestingStream(["MD5"]), blobId = await this.blob.put(value.pipeThrough(digesting)), md5Digest = (await digesting.digests).get("MD5");
|
|
1035
|
+
assert2(md5Digest !== void 0);
|
|
1036
|
+
let etag = generateId(), maybeOldBlobId;
|
|
1037
|
+
try {
|
|
1038
|
+
maybeOldBlobId = this.#stmts.putPart(key, {
|
|
1039
|
+
upload_id: uploadId,
|
|
1040
|
+
part_number: partNumber,
|
|
1041
|
+
blob_id: blobId,
|
|
1042
|
+
size: valueSize,
|
|
1043
|
+
etag,
|
|
1044
|
+
checksum_md5: md5Digest.toString("hex")
|
|
1045
|
+
});
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
throw this.#backgroundDelete(blobId), e;
|
|
1048
|
+
}
|
|
1049
|
+
return maybeOldBlobId !== void 0 && this.#backgroundDelete(maybeOldBlobId), { etag };
|
|
1050
|
+
}
|
|
1051
|
+
async #completeMultipartUpload(key, uploadId, parts) {
|
|
1052
|
+
validate.key(key);
|
|
1053
|
+
let minPartSize = this.beingTested ? R2Limits.MIN_MULTIPART_PART_SIZE_TEST : R2Limits.MIN_MULTIPART_PART_SIZE, { newRow, oldBlobIds } = this.#stmts.completeMultipartUpload(
|
|
1054
|
+
key,
|
|
1055
|
+
uploadId,
|
|
1056
|
+
parts,
|
|
1057
|
+
minPartSize
|
|
1058
|
+
);
|
|
1059
|
+
for (let blobId of oldBlobIds) this.#backgroundDelete(blobId);
|
|
1060
|
+
return new InternalR2Object(newRow);
|
|
1061
|
+
}
|
|
1062
|
+
async #abortMultipartUpload(key, uploadId) {
|
|
1063
|
+
validate.key(key);
|
|
1064
|
+
let oldBlobIds = this.#stmts.abortMultipartUpload(key, uploadId);
|
|
1065
|
+
for (let blobId of oldBlobIds) this.#backgroundDelete(blobId);
|
|
1066
|
+
}
|
|
1067
|
+
get = async (req) => {
|
|
1068
|
+
let metadata = decodeHeaderMetadata(req), result;
|
|
1069
|
+
if (metadata.method === "head")
|
|
1070
|
+
result = await this.#head(metadata.object);
|
|
1071
|
+
else if (metadata.method === "get")
|
|
1072
|
+
result = await this.#get(metadata.object, metadata);
|
|
1073
|
+
else if (metadata.method === "list")
|
|
1074
|
+
result = await this.#list(metadata);
|
|
1075
|
+
else
|
|
1076
|
+
throw new InternalError();
|
|
1077
|
+
return encodeResult(result);
|
|
1078
|
+
};
|
|
1079
|
+
put = async (req) => {
|
|
1080
|
+
let { metadata, metadataSize, value } = await decodeMetadata(req);
|
|
1081
|
+
if (metadata.method === "delete")
|
|
1082
|
+
return await this.#delete(
|
|
1083
|
+
"object" in metadata ? metadata.object : metadata.objects
|
|
1084
|
+
), new Response();
|
|
1085
|
+
if (metadata.method === "put") {
|
|
1086
|
+
let contentLength = parseInt(req.headers.get("Content-Length"));
|
|
1087
|
+
assert2(!isNaN(contentLength));
|
|
1088
|
+
let valueSize = contentLength - metadataSize, result = await this.#put(
|
|
1089
|
+
metadata.object,
|
|
1090
|
+
value,
|
|
1091
|
+
valueSize,
|
|
1092
|
+
metadata
|
|
1093
|
+
);
|
|
1094
|
+
return encodeResult(result);
|
|
1095
|
+
} else if (metadata.method === "createMultipartUpload") {
|
|
1096
|
+
let result = await this.#createMultipartUpload(
|
|
1097
|
+
metadata.object,
|
|
1098
|
+
metadata
|
|
1099
|
+
);
|
|
1100
|
+
return encodeJSONResult(result);
|
|
1101
|
+
} else if (metadata.method === "uploadPart") {
|
|
1102
|
+
let contentLength = parseInt(req.headers.get("Content-Length"));
|
|
1103
|
+
assert2(!isNaN(contentLength));
|
|
1104
|
+
let valueSize = contentLength - metadataSize, result = await this.#uploadPart(
|
|
1105
|
+
metadata.object,
|
|
1106
|
+
metadata.uploadId,
|
|
1107
|
+
metadata.partNumber,
|
|
1108
|
+
value,
|
|
1109
|
+
valueSize
|
|
1110
|
+
);
|
|
1111
|
+
return encodeJSONResult(result);
|
|
1112
|
+
} else if (metadata.method === "completeMultipartUpload") {
|
|
1113
|
+
let result = await this.#completeMultipartUpload(
|
|
1114
|
+
metadata.object,
|
|
1115
|
+
metadata.uploadId,
|
|
1116
|
+
metadata.parts
|
|
1117
|
+
);
|
|
1118
|
+
return encodeResult(result);
|
|
1119
|
+
} else {
|
|
1120
|
+
if (metadata.method === "abortMultipartUpload")
|
|
1121
|
+
return await this.#abortMultipartUpload(metadata.object, metadata.uploadId), new Response();
|
|
1122
|
+
throw new InternalError();
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
__decorateClass([
|
|
1127
|
+
GET("/")
|
|
1128
|
+
], R2BucketObject.prototype, "get", 2), __decorateClass([
|
|
1129
|
+
PUT("/")
|
|
1130
|
+
], R2BucketObject.prototype, "put", 2);
|
|
1131
|
+
export {
|
|
1132
|
+
R2BucketObject
|
|
1133
|
+
};
|
|
1134
|
+
//# sourceMappingURL=bucket.worker.js.map
|