@cloudbitmaps/s3 0.19.1 → 0.21.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 +4 -3
- package/dist/aws-errors.d.ts +4 -8
- package/dist/backend.d.ts +7 -5
- package/dist/bounded-body.d.ts +2 -1
- package/dist/client-probe.d.ts +7 -0
- package/dist/defined.d.ts +8 -0
- package/dist/index.js +286 -272
- package/dist/index.js.map +4 -4
- package/dist/read-timeout.d.ts +0 -4
- package/dist/registry.d.ts +3 -3
- package/dist/s3-errors.d.ts +11 -0
- package/dist/storage.d.ts +34 -16
- package/package.json +2 -2
- package/dist/keys.d.ts +0 -17
package/dist/index.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
// src/backend.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ValidationError as ValidationError5,
|
|
4
|
+
brandAsBackend,
|
|
5
|
+
refuseUnknownOptions,
|
|
6
|
+
requirePositiveInt as requirePositiveInt2
|
|
7
|
+
} from "@cloudbitmaps/core/driver-kit";
|
|
3
8
|
import { S3Client } from "@aws-sdk/client-s3";
|
|
4
9
|
|
|
10
|
+
// src/defined.ts
|
|
11
|
+
function defined(values) {
|
|
12
|
+
return Object.fromEntries(Object.entries(values).filter(([, v]) => v !== void 0));
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
// src/storage.ts
|
|
6
16
|
import {
|
|
7
17
|
IntegrityError,
|
|
@@ -9,61 +19,42 @@ import {
|
|
|
9
19
|
TransientError as TransientError2,
|
|
10
20
|
ValidationError as ValidationError3,
|
|
11
21
|
WriteConflictError,
|
|
22
|
+
concatBytes,
|
|
12
23
|
isNotFoundError,
|
|
13
24
|
isValidationError,
|
|
14
|
-
isWriteConflictError
|
|
25
|
+
isWriteConflictError,
|
|
26
|
+
normalizeObjectPrefix,
|
|
27
|
+
parseGenerationFromKey,
|
|
28
|
+
requireBool,
|
|
29
|
+
requirePositiveInt,
|
|
30
|
+
segmentObjectPrefix,
|
|
31
|
+
storageObjectKey,
|
|
32
|
+
toTransient
|
|
15
33
|
} from "@cloudbitmaps/core/driver-kit";
|
|
16
34
|
import { createHash, randomBytes } from "node:crypto";
|
|
17
35
|
import {
|
|
18
36
|
AbortMultipartUploadCommand,
|
|
19
37
|
CompleteMultipartUploadCommand,
|
|
20
38
|
CreateMultipartUploadCommand,
|
|
21
|
-
DeleteObjectCommand,
|
|
39
|
+
DeleteObjectCommand as DeleteObjectCommand2,
|
|
22
40
|
GetObjectCommand,
|
|
23
41
|
HeadObjectCommand,
|
|
24
42
|
ListObjectsV2Command,
|
|
25
|
-
PutObjectCommand,
|
|
43
|
+
PutObjectCommand as PutObjectCommand2,
|
|
26
44
|
UploadPartCommand
|
|
27
45
|
} from "@aws-sdk/client-s3";
|
|
28
46
|
|
|
29
|
-
// src/
|
|
30
|
-
import {
|
|
31
|
-
ValidationError,
|
|
32
|
-
encodeNameForKey,
|
|
33
|
-
namespaceKeyPart,
|
|
34
|
-
prefixPart,
|
|
35
|
-
validateSegmentRef
|
|
36
|
-
} from "@cloudbitmaps/core/driver-kit";
|
|
37
|
-
import { normalizeObjectPrefix } from "@cloudbitmaps/core/driver-kit";
|
|
38
|
-
var SUFFIX = ".crbm";
|
|
39
|
-
function segmentObjectPrefix(prefix, ref) {
|
|
40
|
-
validateSegmentRef(ref);
|
|
41
|
-
return `${prefixPart(prefix)}${namespaceKeyPart(ref.namespace)}/segments/${encodeNameForKey(ref.segment)}.`;
|
|
42
|
-
}
|
|
43
|
-
function storageObjectKey(prefix, key) {
|
|
44
|
-
if (!Number.isInteger(key.generation) || key.generation < 0) {
|
|
45
|
-
throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);
|
|
46
|
-
}
|
|
47
|
-
return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;
|
|
48
|
-
}
|
|
49
|
-
function parseGenerationFromKey(segmentPrefix, objectKey) {
|
|
50
|
-
if (!objectKey.startsWith(segmentPrefix) || !objectKey.endsWith(SUFFIX)) return null;
|
|
51
|
-
const middle = objectKey.slice(segmentPrefix.length, objectKey.length - SUFFIX.length);
|
|
52
|
-
if (!/^(0|[1-9]\d*)$/.test(middle)) return null;
|
|
53
|
-
const generation = Number(middle);
|
|
54
|
-
return Number.isSafeInteger(generation) ? generation : null;
|
|
55
|
-
}
|
|
47
|
+
// src/s3-errors.ts
|
|
48
|
+
import { ValidationError } from "@cloudbitmaps/core/driver-kit";
|
|
56
49
|
|
|
57
50
|
// src/aws-errors.ts
|
|
51
|
+
import { isTransportFault } from "@cloudbitmaps/core/driver-kit";
|
|
58
52
|
function httpStatus(err) {
|
|
59
53
|
return err?.$metadata?.httpStatusCode;
|
|
60
54
|
}
|
|
61
55
|
function errorName(err) {
|
|
62
56
|
return err?.name;
|
|
63
57
|
}
|
|
64
|
-
function errorCode(err) {
|
|
65
|
-
return err?.code;
|
|
66
|
-
}
|
|
67
58
|
function isSdkRetryable(err) {
|
|
68
59
|
return err?.$retryable != null;
|
|
69
60
|
}
|
|
@@ -78,19 +69,10 @@ var NETWORK_NAMES = /* @__PURE__ */ new Set([
|
|
|
78
69
|
"NetworkingError",
|
|
79
70
|
"AbortError"
|
|
80
71
|
]);
|
|
81
|
-
var NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
82
|
-
"ETIMEDOUT",
|
|
83
|
-
"ECONNRESET",
|
|
84
|
-
"ECONNREFUSED",
|
|
85
|
-
"EPIPE",
|
|
86
|
-
"ENOTFOUND",
|
|
87
|
-
"EAI_AGAIN",
|
|
88
|
-
"ECONNABORTED"
|
|
89
|
-
]);
|
|
90
72
|
var NETWORK_MESSAGE = /socket hang up|network (error|failure)|(connection|request|socket|operation|read|write)\s+tim(e|ed)\s?out|connection (reset|refused|aborted|closed)/i;
|
|
91
73
|
function isNetworkOrTimeout(err) {
|
|
92
74
|
if (NETWORK_NAMES.has(errorName(err) ?? "")) return true;
|
|
93
|
-
if (
|
|
75
|
+
if (isTransportFault(err)) return true;
|
|
94
76
|
return NETWORK_MESSAGE.test(err?.message ?? "");
|
|
95
77
|
}
|
|
96
78
|
|
|
@@ -110,6 +92,15 @@ function isNoSuchUpload(err) {
|
|
|
110
92
|
function isMissingBucket(err) {
|
|
111
93
|
return errorName(err) === "NoSuchBucket";
|
|
112
94
|
}
|
|
95
|
+
function asMissingBucket(err, bucket) {
|
|
96
|
+
if (!isMissingBucket(err)) return err;
|
|
97
|
+
return Object.assign(
|
|
98
|
+
new ValidationError(
|
|
99
|
+
`the S3 bucket does not exist: ${bucket}; check the bucket name, and the region and endpoint of the client`
|
|
100
|
+
),
|
|
101
|
+
{ cause: err }
|
|
102
|
+
);
|
|
103
|
+
}
|
|
113
104
|
function isNotFound(err) {
|
|
114
105
|
if (isMissingBucket(err)) return false;
|
|
115
106
|
const name = errorName(err);
|
|
@@ -126,6 +117,9 @@ function isTransient(err) {
|
|
|
126
117
|
if (isConditionalConflict(err) || isNotFound(err) || isInvalidRange(err)) return false;
|
|
127
118
|
return errorName(err) === "SlowDown" || isServerSide(err) || isNetworkOrTimeout(err) || isSdkRetryable(err) || isClockSkewCorrected(err);
|
|
128
119
|
}
|
|
120
|
+
function transientFaultMessage(err) {
|
|
121
|
+
return `transient S3 fault: ${errorName(err) ?? "unknown"}`;
|
|
122
|
+
}
|
|
129
123
|
function spanFromContentRange(contentRange) {
|
|
130
124
|
if (contentRange === void 0) return void 0;
|
|
131
125
|
const match = /^\s*bytes\s+(\d+)-(\d+)\//.exec(contentRange);
|
|
@@ -313,6 +307,128 @@ function scrubCredentials(err) {
|
|
|
313
307
|
return safe;
|
|
314
308
|
}
|
|
315
309
|
|
|
310
|
+
// src/client-probe.ts
|
|
311
|
+
import { DeleteObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
312
|
+
var PROBE_KEY = "cloudbitmaps-probe";
|
|
313
|
+
var PLACEHOLDER_CREDENTIALS = { accessKeyId: "cloudbitmaps-probe", secretAccessKey: "unused" };
|
|
314
|
+
var NEVER_SENDS = {
|
|
315
|
+
handle: () => Promise.reject(new Error("the client probe was asked to send a request")),
|
|
316
|
+
updateHttpClientConfig: () => {
|
|
317
|
+
},
|
|
318
|
+
httpHandlerConfigs: () => ({}),
|
|
319
|
+
destroy: () => {
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
var SILENT_LOGGER = {
|
|
323
|
+
debug: () => {
|
|
324
|
+
},
|
|
325
|
+
info: () => {
|
|
326
|
+
},
|
|
327
|
+
warn: () => {
|
|
328
|
+
},
|
|
329
|
+
error: () => {
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function cloneForProbe(client) {
|
|
333
|
+
const parts = client;
|
|
334
|
+
if (typeof parts.send !== "function" || typeof parts.config !== "object" || parts.config === null) {
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
if (typeof parts.constructor !== "function" || parts.constructor === Object) return void 0;
|
|
338
|
+
const Class = parts.constructor;
|
|
339
|
+
const clone = new Class({
|
|
340
|
+
...parts.config,
|
|
341
|
+
credentials: PLACEHOLDER_CREDENTIALS,
|
|
342
|
+
requestHandler: NEVER_SENDS,
|
|
343
|
+
logger: SILENT_LOGGER
|
|
344
|
+
});
|
|
345
|
+
return typeof clone.send === "function" && clone.middlewareStack != null ? clone : void 0;
|
|
346
|
+
}
|
|
347
|
+
async function serialise(client, command) {
|
|
348
|
+
let seen;
|
|
349
|
+
command.middlewareStack.addRelativeTo(
|
|
350
|
+
() => (args) => {
|
|
351
|
+
seen = args.request;
|
|
352
|
+
return Promise.resolve({ response: {}, output: { $metadata: {} } });
|
|
353
|
+
},
|
|
354
|
+
{ relation: "after", toMiddleware: "serializerMiddleware", name: "cloudbitmapsProbe" }
|
|
355
|
+
);
|
|
356
|
+
const handler = command.resolveMiddleware(client.middlewareStack, client.config, {});
|
|
357
|
+
await handler(command);
|
|
358
|
+
if (seen === void 0) throw new Error("the probe saw no request");
|
|
359
|
+
return seen;
|
|
360
|
+
}
|
|
361
|
+
var hasHeader = (request, name) => request.headers?.[name] !== void 0;
|
|
362
|
+
async function probeClient(client, bucket) {
|
|
363
|
+
let probe;
|
|
364
|
+
try {
|
|
365
|
+
probe = cloneForProbe(client);
|
|
366
|
+
if (probe === void 0) return void 0;
|
|
367
|
+
const del = await serialise(
|
|
368
|
+
probe,
|
|
369
|
+
new DeleteObjectCommand({ Bucket: bucket, Key: PROBE_KEY, IfMatch: '"probe"' })
|
|
370
|
+
);
|
|
371
|
+
const put = await serialise(
|
|
372
|
+
probe,
|
|
373
|
+
new PutObjectCommand({
|
|
374
|
+
Bucket: bucket,
|
|
375
|
+
Key: PROBE_KEY,
|
|
376
|
+
Body: new Uint8Array(0),
|
|
377
|
+
IfMatch: '"probe"',
|
|
378
|
+
IfNoneMatch: "*"
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
if (typeof del.hostname !== "string" || del.hostname === "") return void 0;
|
|
382
|
+
return {
|
|
383
|
+
host: del.hostname,
|
|
384
|
+
sendsDeleteIfMatch: hasHeader(del, "if-match"),
|
|
385
|
+
sendsPutIfMatch: hasHeader(put, "if-match"),
|
|
386
|
+
sendsPutIfNoneMatch: hasHeader(put, "if-none-match")
|
|
387
|
+
};
|
|
388
|
+
} catch {
|
|
389
|
+
return void 0;
|
|
390
|
+
} finally {
|
|
391
|
+
probe?.destroy();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function appliesDeleteIfMatch(explicit, facts) {
|
|
395
|
+
if (explicit === false) return false;
|
|
396
|
+
if (explicit === true) return facts?.sendsDeleteIfMatch !== false;
|
|
397
|
+
return facts !== void 0 && facts.sendsDeleteIfMatch && isAwsS3Host(facts.host);
|
|
398
|
+
}
|
|
399
|
+
var AWS_DOMAINS = [".amazonaws.com", ".amazonaws.com.cn"];
|
|
400
|
+
var S3_LABELS = /* @__PURE__ */ new Set([
|
|
401
|
+
"s3",
|
|
402
|
+
"s3-fips",
|
|
403
|
+
"s3-accesspoint",
|
|
404
|
+
"s3-accesspoint-fips",
|
|
405
|
+
"s3-global",
|
|
406
|
+
"s3-object-lambda",
|
|
407
|
+
"s3-outposts",
|
|
408
|
+
"s3-external-1"
|
|
409
|
+
]);
|
|
410
|
+
var S3_DASH_REGION_LABEL = /^s3-(fips-)?[a-z]{2}(-gov)?-[a-z]+-\d$/;
|
|
411
|
+
var S3_EXPRESS_LABEL = /^s3express-[a-z0-9-]+$/;
|
|
412
|
+
var REGION_LABEL = /^[a-z]{2}(-[a-z]+)+-\d+$/;
|
|
413
|
+
var isS3Label = (label) => S3_LABELS.has(label) || S3_DASH_REGION_LABEL.test(label) || S3_EXPRESS_LABEL.test(label);
|
|
414
|
+
function isAwsS3Host(hostname) {
|
|
415
|
+
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
416
|
+
const domain = AWS_DOMAINS.find((d) => host.endsWith(d));
|
|
417
|
+
if (domain === void 0) return false;
|
|
418
|
+
const labels = host.slice(0, -domain.length).split(".");
|
|
419
|
+
let at = -1;
|
|
420
|
+
labels.forEach((label, i) => {
|
|
421
|
+
if (isS3Label(label)) at = i;
|
|
422
|
+
});
|
|
423
|
+
if (at === -1) return false;
|
|
424
|
+
const rest = labels.slice(at + 1);
|
|
425
|
+
let k = 0;
|
|
426
|
+
if (rest[k] === "dualstack") k++;
|
|
427
|
+
if (k < rest.length && REGION_LABEL.test(rest[k])) k++;
|
|
428
|
+
if (rest[k] === "vpce") k++;
|
|
429
|
+
return k === rest.length;
|
|
430
|
+
}
|
|
431
|
+
|
|
316
432
|
// src/storage.ts
|
|
317
433
|
var S3_PART_BYTES = 8 * 1024 * 1024;
|
|
318
434
|
var S3_MAX_PARTS = 1e4;
|
|
@@ -324,6 +440,7 @@ var THROTTLE_BASE_DELAY_MS = 500;
|
|
|
324
440
|
var REAL_TIME = {
|
|
325
441
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
326
442
|
};
|
|
443
|
+
var REAL_RNG = { next: () => Math.random() };
|
|
327
444
|
var S3StorageDriver = class {
|
|
328
445
|
client;
|
|
329
446
|
bucket;
|
|
@@ -332,21 +449,21 @@ var S3StorageDriver = class {
|
|
|
332
449
|
partBytes;
|
|
333
450
|
readTimeoutMs;
|
|
334
451
|
clock;
|
|
452
|
+
rng;
|
|
335
453
|
sockets;
|
|
454
|
+
explicitConditionalDelete;
|
|
455
|
+
probed;
|
|
456
|
+
facts;
|
|
336
457
|
constructor(options) {
|
|
337
458
|
this.client = options.client;
|
|
459
|
+
this.explicitConditionalDelete = requireBool("conditionalDelete", options.conditionalDelete);
|
|
338
460
|
this.sockets = options.sockets;
|
|
339
461
|
this.bucket = options.bucket;
|
|
340
462
|
this.prefix = normalizeObjectPrefix(options.prefix);
|
|
341
463
|
this.clock = options.clock ?? REAL_TIME;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
]) {
|
|
346
|
-
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) {
|
|
347
|
-
throw new ValidationError3(`${name} must be a positive safe integer; got ${value}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
464
|
+
this.rng = options.rng ?? REAL_RNG;
|
|
465
|
+
requirePositiveInt("partBytes", options.partBytes);
|
|
466
|
+
requirePositiveInt("maxObjectBytes", options.maxObjectBytes);
|
|
350
467
|
if (options.maxObjectBytes !== void 0 && options.maxObjectBytes > S3_MAX_OBJECT_BYTES) {
|
|
351
468
|
throw new ValidationError3(
|
|
352
469
|
`maxObjectBytes must be at most S3's 5 TiB object limit (${S3_MAX_OBJECT_BYTES}); got ${options.maxObjectBytes}`
|
|
@@ -363,7 +480,19 @@ var S3StorageDriver = class {
|
|
|
363
480
|
this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);
|
|
364
481
|
}
|
|
365
482
|
capabilities() {
|
|
366
|
-
return {
|
|
483
|
+
return {
|
|
484
|
+
rangeRead: true,
|
|
485
|
+
maxObjectBytes: this.maxObjectBytes,
|
|
486
|
+
conditionalPut: true,
|
|
487
|
+
conditionalDelete: appliesDeleteIfMatch(this.explicitConditionalDelete, this.facts)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/** Ask the client once where its requests go and whether it sends `If-Match` on a delete; it sends nothing. */
|
|
491
|
+
settle() {
|
|
492
|
+
this.probed ??= probeClient(this.client, this.bucket).then((facts) => {
|
|
493
|
+
this.facts = facts;
|
|
494
|
+
});
|
|
495
|
+
return this.probed;
|
|
367
496
|
}
|
|
368
497
|
async putImmutable(key, write) {
|
|
369
498
|
const objectKey = storageObjectKey(this.prefix, key);
|
|
@@ -374,7 +503,8 @@ var S3StorageDriver = class {
|
|
|
374
503
|
this.partBytes,
|
|
375
504
|
this.maxObjectBytes,
|
|
376
505
|
this.readTimeoutMs,
|
|
377
|
-
this.clock
|
|
506
|
+
this.clock,
|
|
507
|
+
this.rng
|
|
378
508
|
);
|
|
379
509
|
try {
|
|
380
510
|
await write(sink);
|
|
@@ -432,8 +562,12 @@ var S3StorageDriver = class {
|
|
|
432
562
|
}
|
|
433
563
|
async getTail(key, maxBytes) {
|
|
434
564
|
const objectKey = storageObjectKey(this.prefix, key);
|
|
565
|
+
if (!Number.isSafeInteger(maxBytes)) {
|
|
566
|
+
throw new ValidationError3(`invalid tail length ${String(maxBytes)}`);
|
|
567
|
+
}
|
|
435
568
|
if (maxBytes <= 0) {
|
|
436
|
-
|
|
569
|
+
const head = await this.head(key, objectKey);
|
|
570
|
+
return withVersion({ bytes: new Uint8Array(0), size: head.size ?? 0 }, head.version);
|
|
437
571
|
}
|
|
438
572
|
const read = await this.read("GetObject", key, async (options) => {
|
|
439
573
|
try {
|
|
@@ -451,17 +585,18 @@ var S3StorageDriver = class {
|
|
|
451
585
|
() => this.badRead(key, "tail", `the response is longer than the ${maxBytes}B requested`),
|
|
452
586
|
res.ContentLength
|
|
453
587
|
);
|
|
454
|
-
return { bytes: bytes2, contentRange: res.ContentRange };
|
|
588
|
+
return { bytes: bytes2, contentRange: res.ContentRange, version: res.ETag };
|
|
455
589
|
} catch (err) {
|
|
456
590
|
if (isInvalidRange(err)) return { refused: err };
|
|
457
591
|
throw err;
|
|
458
592
|
}
|
|
459
593
|
});
|
|
460
594
|
if ("refused" in read) {
|
|
461
|
-
|
|
462
|
-
|
|
595
|
+
const head = await this.head(key, objectKey);
|
|
596
|
+
if (head.size !== 0) throw this.mapReadError(read.refused, key);
|
|
597
|
+
return withVersion({ bytes: new Uint8Array(0), size: 0 }, head.version);
|
|
463
598
|
}
|
|
464
|
-
const { bytes, contentRange } = read;
|
|
599
|
+
const { bytes, contentRange, version } = read;
|
|
465
600
|
let size = totalFromContentRange(contentRange);
|
|
466
601
|
if (contentRange !== void 0 && !(size === 0 && bytes.length === 0)) {
|
|
467
602
|
const span = spanFromContentRange(contentRange);
|
|
@@ -475,23 +610,26 @@ var S3StorageDriver = class {
|
|
|
475
610
|
}
|
|
476
611
|
}
|
|
477
612
|
if (size === void 0) {
|
|
478
|
-
size = bytes.length === maxBytes ? await this.
|
|
613
|
+
size = bytes.length === maxBytes ? (await this.head(key, objectKey)).size ?? bytes.length : bytes.length;
|
|
479
614
|
}
|
|
480
|
-
return { bytes, size };
|
|
615
|
+
return withVersion({ bytes, size }, version);
|
|
481
616
|
}
|
|
482
617
|
badRead(key, what, why) {
|
|
483
618
|
return new ValidationError3(
|
|
484
619
|
`S3 ${what} read of ${key.segment}.${key.generation} refused: ${why}`
|
|
485
620
|
);
|
|
486
621
|
}
|
|
487
|
-
/**
|
|
488
|
-
|
|
622
|
+
/**
|
|
623
|
+
* The object's size and ETag from a `HeadObject`, each `undefined` when the response does not carry it. A missing
|
|
624
|
+
* object throws {@link NotFoundError}.
|
|
625
|
+
*/
|
|
626
|
+
head(key, objectKey) {
|
|
489
627
|
return this.read("HeadObject", key, async (options) => {
|
|
490
628
|
const head = await this.client.send(
|
|
491
629
|
new HeadObjectCommand({ Bucket: this.bucket, Key: objectKey }),
|
|
492
630
|
options
|
|
493
631
|
);
|
|
494
|
-
return head.ContentLength;
|
|
632
|
+
return { size: head.ContentLength, version: head.ETag };
|
|
495
633
|
});
|
|
496
634
|
}
|
|
497
635
|
/**
|
|
@@ -509,15 +647,43 @@ var S3StorageDriver = class {
|
|
|
509
647
|
}
|
|
510
648
|
});
|
|
511
649
|
}
|
|
512
|
-
async delete(key) {
|
|
650
|
+
async delete(key, options) {
|
|
651
|
+
const objectKey = storageObjectKey(this.prefix, key);
|
|
652
|
+
const ifVersion = options?.ifVersion;
|
|
653
|
+
if (ifVersion !== void 0 && this.explicitConditionalDelete !== false) await this.settle();
|
|
654
|
+
const conditional = ifVersion !== void 0 && appliesDeleteIfMatch(this.explicitConditionalDelete, this.facts);
|
|
513
655
|
try {
|
|
514
656
|
await this.client.send(
|
|
515
|
-
new
|
|
657
|
+
new DeleteObjectCommand2({
|
|
658
|
+
Bucket: this.bucket,
|
|
659
|
+
Key: objectKey,
|
|
660
|
+
...conditional ? { IfMatch: ifVersion } : {}
|
|
661
|
+
})
|
|
516
662
|
);
|
|
517
|
-
} catch (
|
|
663
|
+
} catch (raw) {
|
|
664
|
+
const err = scrubCredentials(raw);
|
|
665
|
+
if (conditional) {
|
|
666
|
+
if (isNotFound(err)) return;
|
|
667
|
+
if (isConditionalConflict(err)) {
|
|
668
|
+
if (!await this.exists(key, objectKey)) return;
|
|
669
|
+
throw new WriteConflictError(
|
|
670
|
+
`generation ${key.segment}.${key.generation} is another object than the version given; not deleted`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
518
674
|
throw this.mapError(err);
|
|
519
675
|
}
|
|
520
676
|
}
|
|
677
|
+
/** Whether an object is under the key, from one `HeadObject`. A read that fails in any other way throws. */
|
|
678
|
+
async exists(key, objectKey) {
|
|
679
|
+
try {
|
|
680
|
+
await this.head(key, objectKey);
|
|
681
|
+
return true;
|
|
682
|
+
} catch (err) {
|
|
683
|
+
if (isNotFoundError(err)) return false;
|
|
684
|
+
throw err;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
521
687
|
async *list(ref) {
|
|
522
688
|
const prefix = segmentObjectPrefix(this.prefix, ref);
|
|
523
689
|
let token;
|
|
@@ -563,33 +729,21 @@ var S3StorageDriver = class {
|
|
|
563
729
|
return this.mapError(err);
|
|
564
730
|
}
|
|
565
731
|
/**
|
|
566
|
-
* Reclassify a transient S3 fault (throttle/5xx/dropped connection) as a retryable {@link TransientError},
|
|
567
|
-
*
|
|
568
|
-
* everything else propagates unchanged. The final fallback at every
|
|
569
|
-
* read retry only ever see typed errors.
|
|
732
|
+
* Reclassify a transient S3 fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}, so the
|
|
733
|
+
* store's read retry can ride it out and a write's caller can tell it from a deterministic failure, and a missing
|
|
734
|
+
* bucket as a `ValidationError` that names it; everything else propagates unchanged. The final fallback at every
|
|
735
|
+
* `client.send` site, so callers and the read retry only ever see typed errors.
|
|
570
736
|
*/
|
|
571
737
|
mapError(err) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return new TransientError2(
|
|
575
|
-
`transient S3 fault: ${err?.name ?? "unknown"}`,
|
|
576
|
-
{ cause: err }
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
return err;
|
|
738
|
+
const scrubbed = asMissingBucket(scrubCredentials(err), this.bucket);
|
|
739
|
+
return toTransient(scrubbed, isTransient, transientFaultMessage);
|
|
580
740
|
}
|
|
581
741
|
};
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
let offset = 0;
|
|
585
|
-
for (const p of parts) {
|
|
586
|
-
out.set(p, offset);
|
|
587
|
-
offset += p.length;
|
|
588
|
-
}
|
|
589
|
-
return out;
|
|
742
|
+
function withVersion(tail, version) {
|
|
743
|
+
return version === void 0 ? tail : { ...tail, version };
|
|
590
744
|
}
|
|
591
745
|
var S3MultipartSink = class {
|
|
592
|
-
constructor(client, bucket, objectKey, partBytes, maxObjectBytes, readTimeoutMs, clock) {
|
|
746
|
+
constructor(client, bucket, objectKey, partBytes, maxObjectBytes, readTimeoutMs, clock, rng) {
|
|
593
747
|
this.client = client;
|
|
594
748
|
this.bucket = bucket;
|
|
595
749
|
this.objectKey = objectKey;
|
|
@@ -597,6 +751,7 @@ var S3MultipartSink = class {
|
|
|
597
751
|
this.maxObjectBytes = maxObjectBytes;
|
|
598
752
|
this.readTimeoutMs = readTimeoutMs;
|
|
599
753
|
this.clock = clock;
|
|
754
|
+
this.rng = rng;
|
|
600
755
|
}
|
|
601
756
|
client;
|
|
602
757
|
bucket;
|
|
@@ -605,6 +760,7 @@ var S3MultipartSink = class {
|
|
|
605
760
|
maxObjectBytes;
|
|
606
761
|
readTimeoutMs;
|
|
607
762
|
clock;
|
|
763
|
+
rng;
|
|
608
764
|
hash = createHash("sha256");
|
|
609
765
|
pending = [];
|
|
610
766
|
pendingLen = 0;
|
|
@@ -641,7 +797,7 @@ var S3MultipartSink = class {
|
|
|
641
797
|
}
|
|
642
798
|
this.uploadId = res2.UploadId;
|
|
643
799
|
}
|
|
644
|
-
const body = concatBytes(this.pending
|
|
800
|
+
const body = concatBytes(this.pending);
|
|
645
801
|
this.pending.length = 0;
|
|
646
802
|
this.pendingLen = 0;
|
|
647
803
|
this.partNumber += 1;
|
|
@@ -663,12 +819,12 @@ var S3MultipartSink = class {
|
|
|
663
819
|
async finish() {
|
|
664
820
|
const sha256 = this.hash.digest("hex");
|
|
665
821
|
if (this.uploadId === void 0) {
|
|
666
|
-
const body = concatBytes(this.pending
|
|
822
|
+
const body = concatBytes(this.pending);
|
|
667
823
|
await this.commit(
|
|
668
824
|
"PutObject",
|
|
669
825
|
() => sendOnce(
|
|
670
826
|
this.client,
|
|
671
|
-
new
|
|
827
|
+
new PutObjectCommand2({
|
|
672
828
|
Bucket: this.bucket,
|
|
673
829
|
Key: this.objectKey,
|
|
674
830
|
Body: body,
|
|
@@ -740,7 +896,7 @@ var S3MultipartSink = class {
|
|
|
740
896
|
{ cause: err }
|
|
741
897
|
);
|
|
742
898
|
}
|
|
743
|
-
await this.clock.sleep(Math.floor(
|
|
899
|
+
await this.clock.sleep(Math.floor(this.rng.next() * THROTTLE_BASE_DELAY_MS * 2 ** resent));
|
|
744
900
|
}
|
|
745
901
|
}
|
|
746
902
|
}
|
|
@@ -784,10 +940,11 @@ import {
|
|
|
784
940
|
isIntegrityError,
|
|
785
941
|
MAX_ROW_BYTES,
|
|
786
942
|
ObjectStoreRegistry,
|
|
787
|
-
TransientError as TransientError3,
|
|
788
943
|
ValidationError as ValidationError4,
|
|
789
944
|
WriteConflictError as WriteConflictError2,
|
|
790
|
-
normalizeObjectPrefix as normalizeObjectPrefix2
|
|
945
|
+
normalizeObjectPrefix as normalizeObjectPrefix2,
|
|
946
|
+
requireBool as requireBool2,
|
|
947
|
+
toTransient as toTransient2
|
|
791
948
|
} from "@cloudbitmaps/core/driver-kit";
|
|
792
949
|
import {
|
|
793
950
|
DeleteObjectCommand as DeleteObjectCommand3,
|
|
@@ -795,125 +952,6 @@ import {
|
|
|
795
952
|
ListObjectsV2Command as ListObjectsV2Command2,
|
|
796
953
|
PutObjectCommand as PutObjectCommand3
|
|
797
954
|
} from "@aws-sdk/client-s3";
|
|
798
|
-
|
|
799
|
-
// src/client-probe.ts
|
|
800
|
-
import { DeleteObjectCommand as DeleteObjectCommand2, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
|
|
801
|
-
var PROBE_KEY = "cloudbitmaps-probe";
|
|
802
|
-
var PLACEHOLDER_CREDENTIALS = { accessKeyId: "cloudbitmaps-probe", secretAccessKey: "unused" };
|
|
803
|
-
var NEVER_SENDS = {
|
|
804
|
-
handle: () => Promise.reject(new Error("the client probe was asked to send a request")),
|
|
805
|
-
updateHttpClientConfig: () => {
|
|
806
|
-
},
|
|
807
|
-
httpHandlerConfigs: () => ({}),
|
|
808
|
-
destroy: () => {
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
var SILENT_LOGGER = {
|
|
812
|
-
debug: () => {
|
|
813
|
-
},
|
|
814
|
-
info: () => {
|
|
815
|
-
},
|
|
816
|
-
warn: () => {
|
|
817
|
-
},
|
|
818
|
-
error: () => {
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
function cloneForProbe(client) {
|
|
822
|
-
const parts = client;
|
|
823
|
-
if (typeof parts.send !== "function" || typeof parts.config !== "object" || parts.config === null) {
|
|
824
|
-
return void 0;
|
|
825
|
-
}
|
|
826
|
-
if (typeof parts.constructor !== "function" || parts.constructor === Object) return void 0;
|
|
827
|
-
const Class = parts.constructor;
|
|
828
|
-
const clone = new Class({
|
|
829
|
-
...parts.config,
|
|
830
|
-
credentials: PLACEHOLDER_CREDENTIALS,
|
|
831
|
-
requestHandler: NEVER_SENDS,
|
|
832
|
-
logger: SILENT_LOGGER
|
|
833
|
-
});
|
|
834
|
-
return typeof clone.send === "function" && clone.middlewareStack != null ? clone : void 0;
|
|
835
|
-
}
|
|
836
|
-
async function serialise(client, command) {
|
|
837
|
-
let seen;
|
|
838
|
-
command.middlewareStack.addRelativeTo(
|
|
839
|
-
() => (args) => {
|
|
840
|
-
seen = args.request;
|
|
841
|
-
return Promise.resolve({ response: {}, output: { $metadata: {} } });
|
|
842
|
-
},
|
|
843
|
-
{ relation: "after", toMiddleware: "serializerMiddleware", name: "cloudbitmapsProbe" }
|
|
844
|
-
);
|
|
845
|
-
const handler = command.resolveMiddleware(client.middlewareStack, client.config, {});
|
|
846
|
-
await handler(command);
|
|
847
|
-
if (seen === void 0) throw new Error("the probe saw no request");
|
|
848
|
-
return seen;
|
|
849
|
-
}
|
|
850
|
-
var hasHeader = (request, name) => request.headers?.[name] !== void 0;
|
|
851
|
-
async function probeClient(client, bucket) {
|
|
852
|
-
let probe;
|
|
853
|
-
try {
|
|
854
|
-
probe = cloneForProbe(client);
|
|
855
|
-
if (probe === void 0) return void 0;
|
|
856
|
-
const del = await serialise(
|
|
857
|
-
probe,
|
|
858
|
-
new DeleteObjectCommand2({ Bucket: bucket, Key: PROBE_KEY, IfMatch: '"probe"' })
|
|
859
|
-
);
|
|
860
|
-
const put = await serialise(
|
|
861
|
-
probe,
|
|
862
|
-
new PutObjectCommand2({
|
|
863
|
-
Bucket: bucket,
|
|
864
|
-
Key: PROBE_KEY,
|
|
865
|
-
Body: new Uint8Array(0),
|
|
866
|
-
IfMatch: '"probe"',
|
|
867
|
-
IfNoneMatch: "*"
|
|
868
|
-
})
|
|
869
|
-
);
|
|
870
|
-
if (typeof del.hostname !== "string" || del.hostname === "") return void 0;
|
|
871
|
-
return {
|
|
872
|
-
host: del.hostname,
|
|
873
|
-
sendsDeleteIfMatch: hasHeader(del, "if-match"),
|
|
874
|
-
sendsPutIfMatch: hasHeader(put, "if-match"),
|
|
875
|
-
sendsPutIfNoneMatch: hasHeader(put, "if-none-match")
|
|
876
|
-
};
|
|
877
|
-
} catch {
|
|
878
|
-
return void 0;
|
|
879
|
-
} finally {
|
|
880
|
-
probe?.destroy();
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
var AWS_DOMAINS = [".amazonaws.com", ".amazonaws.com.cn"];
|
|
884
|
-
var S3_LABELS = /* @__PURE__ */ new Set([
|
|
885
|
-
"s3",
|
|
886
|
-
"s3-fips",
|
|
887
|
-
"s3-accesspoint",
|
|
888
|
-
"s3-accesspoint-fips",
|
|
889
|
-
"s3-global",
|
|
890
|
-
"s3-object-lambda",
|
|
891
|
-
"s3-outposts",
|
|
892
|
-
"s3-external-1"
|
|
893
|
-
]);
|
|
894
|
-
var S3_DASH_REGION_LABEL = /^s3-(fips-)?[a-z]{2}(-gov)?-[a-z]+-\d$/;
|
|
895
|
-
var S3_EXPRESS_LABEL = /^s3express-[a-z0-9-]+$/;
|
|
896
|
-
var REGION_LABEL = /^[a-z]{2}(-[a-z]+)+-\d+$/;
|
|
897
|
-
var isS3Label = (label) => S3_LABELS.has(label) || S3_DASH_REGION_LABEL.test(label) || S3_EXPRESS_LABEL.test(label);
|
|
898
|
-
function isAwsS3Host(hostname) {
|
|
899
|
-
const host = hostname.toLowerCase().replace(/\.$/, "");
|
|
900
|
-
const domain = AWS_DOMAINS.find((d) => host.endsWith(d));
|
|
901
|
-
if (domain === void 0) return false;
|
|
902
|
-
const labels = host.slice(0, -domain.length).split(".");
|
|
903
|
-
let at = -1;
|
|
904
|
-
labels.forEach((label, i) => {
|
|
905
|
-
if (isS3Label(label)) at = i;
|
|
906
|
-
});
|
|
907
|
-
if (at === -1) return false;
|
|
908
|
-
const rest = labels.slice(at + 1);
|
|
909
|
-
let k = 0;
|
|
910
|
-
if (rest[k] === "dualstack") k++;
|
|
911
|
-
if (k < rest.length && REGION_LABEL.test(rest[k])) k++;
|
|
912
|
-
if (rest[k] === "vpce") k++;
|
|
913
|
-
return k === rest.length;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// src/registry.ts
|
|
917
955
|
var S3RegistryStore = class {
|
|
918
956
|
constructor(client, bucket, readTimeoutMs, explicitConditionalDelete) {
|
|
919
957
|
this.client = client;
|
|
@@ -934,10 +972,7 @@ var S3RegistryStore = class {
|
|
|
934
972
|
* vouched for the store.
|
|
935
973
|
*/
|
|
936
974
|
get conditionalDelete() {
|
|
937
|
-
|
|
938
|
-
const facts = this.facts;
|
|
939
|
-
if (this.explicitConditionalDelete === true) return facts?.sendsDeleteIfMatch !== false;
|
|
940
|
-
return facts !== void 0 && facts.sendsDeleteIfMatch && isAwsS3Host(facts.host);
|
|
975
|
+
return appliesDeleteIfMatch(this.explicitConditionalDelete, this.facts);
|
|
941
976
|
}
|
|
942
977
|
/** Ask the client once what it does with the registry's requests; every caller waits for the same answer. */
|
|
943
978
|
settle() {
|
|
@@ -967,7 +1002,7 @@ var S3RegistryStore = class {
|
|
|
967
1002
|
);
|
|
968
1003
|
} catch (err) {
|
|
969
1004
|
if (isNotFound(err)) return null;
|
|
970
|
-
throw mapError(err);
|
|
1005
|
+
throw mapError(err, this.bucket);
|
|
971
1006
|
}
|
|
972
1007
|
try {
|
|
973
1008
|
if ((res.ContentLength ?? 0) > MAX_ROW_BYTES) {
|
|
@@ -986,7 +1021,7 @@ var S3RegistryStore = class {
|
|
|
986
1021
|
return { bytes, version: res.ETag ?? "" };
|
|
987
1022
|
} catch (err) {
|
|
988
1023
|
destroyBody(res.Body);
|
|
989
|
-
throw isIntegrityError(err) ? err : mapError(err);
|
|
1024
|
+
throw isIntegrityError(err) ? err : mapError(err, this.bucket);
|
|
990
1025
|
}
|
|
991
1026
|
});
|
|
992
1027
|
}
|
|
@@ -1009,7 +1044,7 @@ var S3RegistryStore = class {
|
|
|
1009
1044
|
if (isConditionalConflict(err)) {
|
|
1010
1045
|
throw new WriteConflictError2(`registry OCC conflict for ${key}`);
|
|
1011
1046
|
}
|
|
1012
|
-
throw mapError(err);
|
|
1047
|
+
throw mapError(err, this.bucket);
|
|
1013
1048
|
}
|
|
1014
1049
|
}
|
|
1015
1050
|
async delete(key, expect) {
|
|
@@ -1022,7 +1057,7 @@ var S3RegistryStore = class {
|
|
|
1022
1057
|
if (isConditionalConflict(err) || isNotFound(err)) {
|
|
1023
1058
|
throw new WriteConflictError2(`registry OCC conflict deleting ${key}`);
|
|
1024
1059
|
}
|
|
1025
|
-
throw mapError(err);
|
|
1060
|
+
throw mapError(err, this.bucket);
|
|
1026
1061
|
}
|
|
1027
1062
|
}
|
|
1028
1063
|
async *listKeys(prefix) {
|
|
@@ -1039,7 +1074,7 @@ var S3RegistryStore = class {
|
|
|
1039
1074
|
})
|
|
1040
1075
|
);
|
|
1041
1076
|
} catch (err) {
|
|
1042
|
-
throw mapError(err);
|
|
1077
|
+
throw mapError(err, this.bucket);
|
|
1043
1078
|
}
|
|
1044
1079
|
for (const obj of res.Contents ?? []) {
|
|
1045
1080
|
if (obj.Key !== void 0) yield obj.Key;
|
|
@@ -1054,22 +1089,12 @@ var S3RegistryStore = class {
|
|
|
1054
1089
|
} while (token !== void 0);
|
|
1055
1090
|
}
|
|
1056
1091
|
};
|
|
1057
|
-
function mapError(err) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
return err;
|
|
1066
|
-
}
|
|
1067
|
-
function checkConditionalDelete(value) {
|
|
1068
|
-
if (value === void 0) return void 0;
|
|
1069
|
-
if (typeof value !== "boolean") {
|
|
1070
|
-
throw new ValidationError4(`conditionalDelete must be a boolean; got ${String(value)}`);
|
|
1071
|
-
}
|
|
1072
|
-
return value;
|
|
1092
|
+
function mapError(err, bucket) {
|
|
1093
|
+
return toTransient2(
|
|
1094
|
+
asMissingBucket(scrubCredentials(err), bucket),
|
|
1095
|
+
isTransient,
|
|
1096
|
+
transientFaultMessage
|
|
1097
|
+
);
|
|
1073
1098
|
}
|
|
1074
1099
|
var S3RegistryDriver = class extends ObjectStoreRegistry {
|
|
1075
1100
|
constructor(options) {
|
|
@@ -1078,7 +1103,7 @@ var S3RegistryDriver = class extends ObjectStoreRegistry {
|
|
|
1078
1103
|
options.client,
|
|
1079
1104
|
options.bucket,
|
|
1080
1105
|
resolveReadTimeoutMs(options.readTimeoutMs),
|
|
1081
|
-
|
|
1106
|
+
requireBool2("conditionalDelete", options.conditionalDelete)
|
|
1082
1107
|
),
|
|
1083
1108
|
normalizeObjectPrefix2(options.prefix),
|
|
1084
1109
|
options.now ?? (() => Date.now())
|
|
@@ -1216,20 +1241,6 @@ function limitSockets(client, maxSockets) {
|
|
|
1216
1241
|
return handle(request, options);
|
|
1217
1242
|
};
|
|
1218
1243
|
}
|
|
1219
|
-
function refuseUnknown(name, options, keys, hint) {
|
|
1220
|
-
if (options === null || typeof options !== "object") {
|
|
1221
|
-
throw new ValidationError5(
|
|
1222
|
-
`${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
|
|
1223
|
-
);
|
|
1224
|
-
}
|
|
1225
|
-
const unknown = Object.keys(options).filter((k) => !keys.includes(k));
|
|
1226
|
-
if (unknown.length > 0) {
|
|
1227
|
-
const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
|
|
1228
|
-
throw new ValidationError5(
|
|
1229
|
-
`${name} does not take ${list(unknown)}. It takes ${list(keys)}; ${hint}.`
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
1244
|
var S3Storage = class {
|
|
1234
1245
|
storage;
|
|
1235
1246
|
registry;
|
|
@@ -1237,7 +1248,12 @@ var S3Storage = class {
|
|
|
1237
1248
|
client;
|
|
1238
1249
|
sockets;
|
|
1239
1250
|
constructor(options) {
|
|
1240
|
-
|
|
1251
|
+
refuseUnknownOptions(
|
|
1252
|
+
"S3Storage",
|
|
1253
|
+
options,
|
|
1254
|
+
S3_STORAGE_OPTION_KEYS,
|
|
1255
|
+
"an S3 client goes in `client`"
|
|
1256
|
+
);
|
|
1241
1257
|
if (typeof options.bucket !== "string" || options.bucket.length === 0) {
|
|
1242
1258
|
throw new ValidationError5("S3Storage needs a `bucket`: the name of one, a non-empty string");
|
|
1243
1259
|
}
|
|
@@ -1255,37 +1271,35 @@ var S3Storage = class {
|
|
|
1255
1271
|
}
|
|
1256
1272
|
this.client = options.client;
|
|
1257
1273
|
} else {
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
...options.pathStyle === void 0 ? {} : { forcePathStyle: options.pathStyle },
|
|
1268
|
-
...options.credentials === void 0 ? {} : { credentials: options.credentials }
|
|
1269
|
-
});
|
|
1274
|
+
const maxSockets = requirePositiveInt2("maxSockets", options.maxSockets) ?? DEFAULT_MAX_SOCKETS;
|
|
1275
|
+
this.client = new S3Client(
|
|
1276
|
+
defined({
|
|
1277
|
+
credentials: options.credentials,
|
|
1278
|
+
region: options.region,
|
|
1279
|
+
endpoint: options.endpoint,
|
|
1280
|
+
forcePathStyle: options.pathStyle
|
|
1281
|
+
})
|
|
1282
|
+
);
|
|
1270
1283
|
limitSockets(this.client, maxSockets);
|
|
1271
1284
|
}
|
|
1272
1285
|
this.sockets = new SocketAdvisory(this.client, options.bucket);
|
|
1273
1286
|
const shared = {
|
|
1274
1287
|
client: this.client,
|
|
1275
1288
|
bucket: options.bucket,
|
|
1276
|
-
...
|
|
1277
|
-
...options.readTimeoutMs === void 0 ? {} : { readTimeoutMs: options.readTimeoutMs }
|
|
1289
|
+
...defined({ prefix: options.prefix, readTimeoutMs: options.readTimeoutMs })
|
|
1278
1290
|
};
|
|
1279
1291
|
this.storage = new S3StorageDriver({
|
|
1280
1292
|
...shared,
|
|
1281
1293
|
sockets: this.sockets,
|
|
1282
|
-
...
|
|
1283
|
-
|
|
1294
|
+
...defined({
|
|
1295
|
+
maxObjectBytes: options.maxObjectBytes,
|
|
1296
|
+
partBytes: options.partBytes,
|
|
1297
|
+
conditionalDelete: options.conditionalDelete
|
|
1298
|
+
})
|
|
1284
1299
|
});
|
|
1285
1300
|
this.registry = new S3RegistryDriver({
|
|
1286
1301
|
...shared,
|
|
1287
|
-
...
|
|
1288
|
-
...options.conditionalDelete === void 0 ? {} : { conditionalDelete: options.conditionalDelete }
|
|
1302
|
+
...defined({ now: options.now, conditionalDelete: options.conditionalDelete })
|
|
1289
1303
|
});
|
|
1290
1304
|
brandAsBackend(this);
|
|
1291
1305
|
}
|