@dimah-s3/server 0.8.6 → 1.0.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/dist/adapters/express.js +13 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.js +13 -1
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/node.js +13 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/svelte-kit.d.ts +2 -0
- package/dist/adapters/svelte-kit.d.ts.map +1 -1
- package/dist/adapters/svelte-kit.js.map +1 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/api/assert-feature-enabled.d.ts.map +1 -1
- package/dist/api/bind-endpoints.d.ts.map +1 -1
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +218 -147
- package/dist/api/index.js.map +1 -1
- package/dist/{router.d.ts → api/router.d.ts} +3 -2
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/routes/confirm.d.ts.map +1 -1
- package/dist/api/routes/delete.d.ts.map +1 -1
- package/dist/api/routes/download.d.ts.map +1 -1
- package/dist/api/routes/index.d.ts +1 -7
- package/dist/api/routes/index.d.ts.map +1 -1
- package/dist/api/routes/multipart/abort.d.ts +1 -5
- package/dist/api/routes/multipart/abort.d.ts.map +1 -1
- package/dist/api/routes/multipart/complete.d.ts.map +1 -1
- package/dist/api/routes/multipart/init.d.ts +0 -2
- package/dist/api/routes/multipart/init.d.ts.map +1 -1
- package/dist/api/routes/multipart/list-parts.d.ts.map +1 -1
- package/dist/api/routes/multipart/part.d.ts.map +1 -1
- package/dist/api/routes/upload.d.ts.map +1 -1
- package/dist/errors.d.ts +4 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/helpers/expires.d.ts +6 -0
- package/dist/helpers/expires.d.ts.map +1 -0
- package/dist/helpers/features.d.ts +34 -0
- package/dist/helpers/features.d.ts.map +1 -0
- package/dist/helpers/head-object.d.ts +11 -0
- package/dist/helpers/head-object.d.ts.map +1 -0
- package/dist/{internal-helpers.d.ts → helpers/hooks.d.ts} +1 -4
- package/dist/helpers/hooks.d.ts.map +1 -0
- package/dist/helpers/index.d.ts +12 -1
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/is-aws-not-found.d.ts +3 -1
- package/dist/helpers/is-aws-not-found.d.ts.map +1 -1
- package/dist/helpers/list-parts.d.ts +8 -0
- package/dist/helpers/list-parts.d.ts.map +1 -0
- package/dist/helpers/request.d.ts +3 -0
- package/dist/helpers/request.d.ts.map +1 -0
- package/dist/helpers/resolve-request-acl.d.ts +10 -0
- package/dist/helpers/resolve-request-acl.d.ts.map +1 -0
- package/dist/helpers/resolve-target.d.ts +2 -33
- package/dist/helpers/resolve-target.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +188 -111
- package/dist/index.js.map +1 -1
- package/dist/plugin/apply-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +5 -5
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/plugins.js +184 -104
- package/dist/plugins.js.map +1 -1
- package/dist/types/config.d.ts +27 -9
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/hook-contexts.d.ts +8 -8
- package/dist/types/hook-contexts.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/internal-helpers.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
package/dist/api/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createMiddleware, createEndpoint } from 'better-call';
|
|
2
|
-
import { S3_API_ROUTES, isAPIError, DimahS3Error, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, downloadQuerySchema, multipartAbortBodySchema, multipartCompleteBodySchema, multipartInitBodySchema, multipartListPartsQuerySchema, multipartSignPartBodySchema, uploadBodySchema, parseFileName, buildContentDisposition } from '@dimah-s3/core';
|
|
3
|
-
import {
|
|
2
|
+
import { S3_API_ROUTES, isAPIError, DimahS3Error, S3_ERROR_CODES, confirmBodySchema, deleteQuerySchema, downloadQuerySchema, multipartAbortBodySchema, multipartCompleteBodySchema, multipartInitBodySchema, multipartListPartsQuerySchema, multipartSignPartBodySchema, uploadBodySchema, parseFileName, S3_MAX_EXPIRES_IN, buildContentDisposition, S3_MAX_POST_OBJECT_BYTES, S3_DEFAULT_EXPIRES_IN } from '@dimah-s3/core';
|
|
3
|
+
import { DeleteObjectCommand, GetObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, PutObjectCommand, GetObjectAclCommand, ListPartsCommand, CompleteMultipartUploadCommand, HeadObjectCommand, AbortMultipartUploadCommand } from '@aws-sdk/client-s3';
|
|
4
4
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
5
5
|
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
6
6
|
|
|
@@ -39,11 +39,69 @@ var errors = {
|
|
|
39
39
|
"BAD_REQUEST",
|
|
40
40
|
S3_ERROR_CODES.FILE_SIZE_REQUIRED_MULTIPART
|
|
41
41
|
),
|
|
42
|
+
multipartPartMissing: (partNumber) => DimahS3Error.from("BAD_REQUEST", {
|
|
43
|
+
code: S3_ERROR_CODES.MULTIPART_PART_MISSING.code,
|
|
44
|
+
message: S3_ERROR_CODES.MULTIPART_PART_MISSING.message.replaceAll(
|
|
45
|
+
"{partNumber}",
|
|
46
|
+
String(partNumber)
|
|
47
|
+
),
|
|
48
|
+
params: { partNumber }
|
|
49
|
+
}),
|
|
50
|
+
payloadTooLarge: (message = S3_ERROR_CODES.PAYLOAD_TOO_LARGE.message) => DimahS3Error.from("PAYLOAD_TOO_LARGE", {
|
|
51
|
+
...S3_ERROR_CODES.PAYLOAD_TOO_LARGE,
|
|
52
|
+
message
|
|
53
|
+
}),
|
|
42
54
|
validationError: (message) => DimahS3Error.from("BAD_REQUEST", {
|
|
43
55
|
code: S3_ERROR_CODES.VALIDATION_ERROR.code,
|
|
44
56
|
message
|
|
45
57
|
})
|
|
46
58
|
};
|
|
59
|
+
|
|
60
|
+
// src/helpers/is-aws-not-found.ts
|
|
61
|
+
function isAwsNotFound(err) {
|
|
62
|
+
const e = err;
|
|
63
|
+
return e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.name === "NoSuchUpload" || e?.Code === "NoSuchKey" || e?.Code === "NotFound" || e?.Code === "NoSuchUpload" || e?.$metadata?.httpStatusCode === 404;
|
|
64
|
+
}
|
|
65
|
+
async function sendOrObjectNotFound(send) {
|
|
66
|
+
try {
|
|
67
|
+
return await send();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (isAwsNotFound(err)) throw errors.objectNotFound();
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
var DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1500;
|
|
74
|
+
async function resolveObjectAcl(s3, bucket, key, timeoutMs = DEFAULT_ACL_LOOKUP_TIMEOUT_MS) {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
77
|
+
try {
|
|
78
|
+
const result = await s3.send(
|
|
79
|
+
new GetObjectAclCommand({ Bucket: bucket, Key: key }),
|
|
80
|
+
{ abortSignal: controller.signal }
|
|
81
|
+
);
|
|
82
|
+
const isPublic = result.Grants?.some(
|
|
83
|
+
(g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL")
|
|
84
|
+
);
|
|
85
|
+
return isPublic ? "public-read" : "private";
|
|
86
|
+
} catch {
|
|
87
|
+
return void 0;
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/helpers/resolve-request-acl.ts
|
|
94
|
+
function resolveRequestAcl(policy, requested) {
|
|
95
|
+
if (policy?.acl) return policy.acl;
|
|
96
|
+
if (policy?.allowClientAcl && requested) return requested;
|
|
97
|
+
return "private";
|
|
98
|
+
}
|
|
99
|
+
function normalizeExpiresIn(value, maxExpiresIn = S3_MAX_EXPIRES_IN) {
|
|
100
|
+
const n = Number(value);
|
|
101
|
+
const expires = Number.isFinite(n) && n > 0 ? Math.floor(n) : S3_DEFAULT_EXPIRES_IN;
|
|
102
|
+
const max = Number.isFinite(maxExpiresIn) && maxExpiresIn > 0 ? Math.floor(maxExpiresIn) : S3_MAX_EXPIRES_IN;
|
|
103
|
+
return Math.min(expires, max);
|
|
104
|
+
}
|
|
47
105
|
async function runHook(hook, context) {
|
|
48
106
|
if (!hook) return;
|
|
49
107
|
try {
|
|
@@ -69,84 +127,12 @@ async function runLifecycleHook(hook, context) {
|
|
|
69
127
|
throw errors.internalError();
|
|
70
128
|
}
|
|
71
129
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;
|
|
75
|
-
}
|
|
130
|
+
|
|
131
|
+
// src/helpers/request.ts
|
|
76
132
|
function requestFromHeaders(headers) {
|
|
77
133
|
return new Request("http://dimah-s3.local", { headers });
|
|
78
134
|
}
|
|
79
135
|
|
|
80
|
-
// src/api/create-s3-middleware.ts
|
|
81
|
-
var s3ContextMiddleware = createMiddleware(async (ctx) => {
|
|
82
|
-
const injected = ctx.context;
|
|
83
|
-
if (!injected?.config) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
"createS3Endpoint requires dimahS3 router context. Call endpoints through dimahS3().api or the HTTP handler."
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
const request = ctx.request ?? injected.request ?? requestFromHeaders(ctx.headers);
|
|
89
|
-
await runHook(injected.config.guard, { request });
|
|
90
|
-
return {
|
|
91
|
-
config: injected.config,
|
|
92
|
-
request
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
var createS3Middleware = createMiddleware.create({
|
|
96
|
-
use: [s3ContextMiddleware]
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// src/api/create-s3-endpoint.ts
|
|
100
|
-
var createEndpointWithContext = createEndpoint.create({
|
|
101
|
-
use: [s3ContextMiddleware]
|
|
102
|
-
});
|
|
103
|
-
function withS3Validation(options) {
|
|
104
|
-
return {
|
|
105
|
-
...options,
|
|
106
|
-
onValidationError: options.onValidationError ?? (({ message }) => {
|
|
107
|
-
throw errors.validationError(message);
|
|
108
|
-
})
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
var createS3Endpoint = ((pathOrOptions, optionsOrHandler, maybeHandler) => {
|
|
112
|
-
if (typeof pathOrOptions === "string") {
|
|
113
|
-
return createEndpointWithContext(
|
|
114
|
-
pathOrOptions,
|
|
115
|
-
withS3Validation(optionsOrHandler),
|
|
116
|
-
maybeHandler
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
return createEndpointWithContext(
|
|
120
|
-
withS3Validation(pathOrOptions),
|
|
121
|
-
optionsOrHandler
|
|
122
|
-
);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// src/helpers/is-aws-not-found.ts
|
|
126
|
-
function isAwsNotFound(err) {
|
|
127
|
-
const e = err;
|
|
128
|
-
return e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.Code === "NoSuchKey" || e?.Code === "NotFound" || e?.$metadata?.httpStatusCode === 404;
|
|
129
|
-
}
|
|
130
|
-
var DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1500;
|
|
131
|
-
async function resolveObjectAcl(s3, bucket, key, timeoutMs = DEFAULT_ACL_LOOKUP_TIMEOUT_MS) {
|
|
132
|
-
const controller = new AbortController();
|
|
133
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
134
|
-
try {
|
|
135
|
-
const result = await s3.send(
|
|
136
|
-
new GetObjectAclCommand({ Bucket: bucket, Key: key }),
|
|
137
|
-
{ abortSignal: controller.signal }
|
|
138
|
-
);
|
|
139
|
-
const isPublic = result.Grants?.some(
|
|
140
|
-
(g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL")
|
|
141
|
-
);
|
|
142
|
-
return isPublic ? "public-read" : "private";
|
|
143
|
-
} catch {
|
|
144
|
-
return void 0;
|
|
145
|
-
} finally {
|
|
146
|
-
clearTimeout(timeout);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
136
|
// src/helpers/resolve-target.ts
|
|
151
137
|
function assertSafeObjectKey(key) {
|
|
152
138
|
const trimmed = key.replace(/^\/+/u, "").replace(/\/+$/u, "");
|
|
@@ -198,10 +184,100 @@ async function resolveRequestTarget(config, policy, input) {
|
|
|
198
184
|
});
|
|
199
185
|
return { key, bucket };
|
|
200
186
|
}
|
|
187
|
+
async function headObjectOrNotFound(client, bucket, key) {
|
|
188
|
+
return sendOrObjectNotFound(
|
|
189
|
+
() => client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
async function headObjectAfterMultipartComplete(client, bucket, key) {
|
|
193
|
+
const send = () => sendOrObjectNotFound(
|
|
194
|
+
() => client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }))
|
|
195
|
+
);
|
|
196
|
+
let head = await send();
|
|
197
|
+
for (let attempt = 0; attempt < 4 && !head.ContentLength; attempt++) {
|
|
198
|
+
await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));
|
|
199
|
+
head = await send();
|
|
200
|
+
}
|
|
201
|
+
return head;
|
|
202
|
+
}
|
|
203
|
+
function requireContentLength(head) {
|
|
204
|
+
if (typeof head.ContentLength !== "number") {
|
|
205
|
+
throw errors.internalError();
|
|
206
|
+
}
|
|
207
|
+
return head.ContentLength;
|
|
208
|
+
}
|
|
209
|
+
async function listAllParts(client, input) {
|
|
210
|
+
const parts = [];
|
|
211
|
+
let partNumberMarker;
|
|
212
|
+
let isTruncated = true;
|
|
213
|
+
while (isTruncated) {
|
|
214
|
+
const response = await sendOrObjectNotFound(
|
|
215
|
+
() => client.send(
|
|
216
|
+
new ListPartsCommand({
|
|
217
|
+
Bucket: input.bucket,
|
|
218
|
+
Key: input.key,
|
|
219
|
+
UploadId: input.uploadId,
|
|
220
|
+
...partNumberMarker ? { PartNumberMarker: partNumberMarker } : {}
|
|
221
|
+
})
|
|
222
|
+
)
|
|
223
|
+
);
|
|
224
|
+
parts.push(...response.Parts ?? []);
|
|
225
|
+
isTruncated = response.IsTruncated === true;
|
|
226
|
+
partNumberMarker = isTruncated ? response.NextPartNumberMarker : void 0;
|
|
227
|
+
}
|
|
228
|
+
return parts;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/helpers/features.ts
|
|
201
232
|
function isFeatureEnabled(config, feature) {
|
|
202
233
|
return config[feature]?.enabled === true;
|
|
203
234
|
}
|
|
204
235
|
|
|
236
|
+
// src/api/create-s3-middleware.ts
|
|
237
|
+
var s3ContextMiddleware = createMiddleware(async (ctx) => {
|
|
238
|
+
const injected = ctx.context;
|
|
239
|
+
if (!injected?.config) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"createS3Endpoint requires dimahS3 router context. Call endpoints through dimahS3().api or the HTTP handler."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const request = ctx.request ?? injected.request ?? requestFromHeaders(ctx.headers);
|
|
245
|
+
await runHook(injected.config.guard, { request });
|
|
246
|
+
return {
|
|
247
|
+
config: injected.config,
|
|
248
|
+
request
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
var createS3Middleware = createMiddleware.create({
|
|
252
|
+
use: [s3ContextMiddleware]
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// src/api/create-s3-endpoint.ts
|
|
256
|
+
var createEndpointWithContext = createEndpoint.create({
|
|
257
|
+
use: [s3ContextMiddleware]
|
|
258
|
+
});
|
|
259
|
+
function withS3Validation(options) {
|
|
260
|
+
return {
|
|
261
|
+
...options,
|
|
262
|
+
onValidationError: options.onValidationError ?? (({ message }) => {
|
|
263
|
+
throw errors.validationError(message);
|
|
264
|
+
})
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
var createS3Endpoint = ((pathOrOptions, optionsOrHandler, maybeHandler) => {
|
|
268
|
+
if (typeof pathOrOptions === "string") {
|
|
269
|
+
return createEndpointWithContext(
|
|
270
|
+
pathOrOptions,
|
|
271
|
+
withS3Validation(optionsOrHandler),
|
|
272
|
+
maybeHandler
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return createEndpointWithContext(
|
|
276
|
+
withS3Validation(pathOrOptions),
|
|
277
|
+
optionsOrHandler
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
205
281
|
// src/api/assert-feature-enabled.ts
|
|
206
282
|
function assertFeatureEnabled(config, feature) {
|
|
207
283
|
if (!isFeatureEnabled(config, feature)) {
|
|
@@ -221,9 +297,7 @@ async function handleConfirm(config, input, request) {
|
|
|
221
297
|
key,
|
|
222
298
|
bucket
|
|
223
299
|
});
|
|
224
|
-
const head = await config.client
|
|
225
|
-
new HeadObjectCommand({ Bucket: bucket, Key: key })
|
|
226
|
-
);
|
|
300
|
+
const head = await headObjectOrNotFound(config.client, bucket, key);
|
|
227
301
|
const acl = config.resolveObjectAcl ? await resolveObjectAcl(config.client, bucket, key) : void 0;
|
|
228
302
|
const fileName = parseFileName(head.ContentDisposition);
|
|
229
303
|
const context = {
|
|
@@ -231,7 +305,7 @@ async function handleConfirm(config, input, request) {
|
|
|
231
305
|
key,
|
|
232
306
|
bucket,
|
|
233
307
|
contentType: head.ContentType,
|
|
234
|
-
contentLength: head
|
|
308
|
+
contentLength: requireContentLength(head),
|
|
235
309
|
eTag: head.ETag?.replace(/"/g, ""),
|
|
236
310
|
metadata: head.Metadata,
|
|
237
311
|
acl,
|
|
@@ -272,16 +346,7 @@ async function handleDelete(config, input, request) {
|
|
|
272
346
|
key,
|
|
273
347
|
bucket
|
|
274
348
|
});
|
|
275
|
-
|
|
276
|
-
await config.client.send(
|
|
277
|
-
new HeadObjectCommand({ Bucket: bucket, Key: key })
|
|
278
|
-
);
|
|
279
|
-
} catch (err) {
|
|
280
|
-
if (isAwsNotFound(err)) {
|
|
281
|
-
throw errors.objectNotFound();
|
|
282
|
-
}
|
|
283
|
-
throw err;
|
|
284
|
-
}
|
|
349
|
+
await headObjectOrNotFound(config.client, bucket, key);
|
|
285
350
|
await config.client.send(
|
|
286
351
|
new DeleteObjectCommand({ Bucket: bucket, Key: key })
|
|
287
352
|
);
|
|
@@ -303,7 +368,7 @@ async function handleDownload(config, input, request) {
|
|
|
303
368
|
bucket: input.bucket,
|
|
304
369
|
fileName: input.fileName
|
|
305
370
|
});
|
|
306
|
-
const expiresIn = normalizeExpiresIn(input.expiresIn);
|
|
371
|
+
const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
|
|
307
372
|
const fileName = input.fileName;
|
|
308
373
|
await runHook(config.download?.guard, {
|
|
309
374
|
request,
|
|
@@ -311,16 +376,7 @@ async function handleDownload(config, input, request) {
|
|
|
311
376
|
bucket,
|
|
312
377
|
fileName
|
|
313
378
|
});
|
|
314
|
-
|
|
315
|
-
await config.client.send(
|
|
316
|
-
new HeadObjectCommand({ Bucket: bucket, Key: key })
|
|
317
|
-
);
|
|
318
|
-
} catch (err) {
|
|
319
|
-
if (isAwsNotFound(err)) {
|
|
320
|
-
throw errors.objectNotFound();
|
|
321
|
-
}
|
|
322
|
-
throw err;
|
|
323
|
-
}
|
|
379
|
+
await headObjectOrNotFound(config.client, bucket, key);
|
|
324
380
|
const url = await getSignedUrl(
|
|
325
381
|
config.client,
|
|
326
382
|
new GetObjectCommand({
|
|
@@ -361,12 +417,14 @@ async function handleAbort(config, input, request) {
|
|
|
361
417
|
bucket,
|
|
362
418
|
uploadId
|
|
363
419
|
});
|
|
364
|
-
await
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
420
|
+
await sendOrObjectNotFound(
|
|
421
|
+
() => config.client.send(
|
|
422
|
+
new AbortMultipartUploadCommand({
|
|
423
|
+
Bucket: bucket,
|
|
424
|
+
Key: key,
|
|
425
|
+
UploadId: uploadId
|
|
426
|
+
})
|
|
427
|
+
)
|
|
370
428
|
);
|
|
371
429
|
await runLifecycleHook(config.multipart?.onAbort, {
|
|
372
430
|
request,
|
|
@@ -400,32 +458,34 @@ async function handleComplete(config, input, request) {
|
|
|
400
458
|
uploadId,
|
|
401
459
|
parts: partRefs
|
|
402
460
|
});
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
461
|
+
const listedParts = await listAllParts(config.client, {
|
|
462
|
+
bucket,
|
|
463
|
+
key,
|
|
464
|
+
uploadId
|
|
465
|
+
});
|
|
407
466
|
const completeParts = parts.map((partNumber) => {
|
|
408
467
|
const found = listedParts.find((p) => p.PartNumber === partNumber);
|
|
409
|
-
|
|
468
|
+
if (!found?.ETag) {
|
|
469
|
+
throw errors.multipartPartMissing(partNumber);
|
|
470
|
+
}
|
|
471
|
+
return { PartNumber: partNumber, ETag: found.ETag };
|
|
410
472
|
});
|
|
411
|
-
const completeResult = await
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
473
|
+
const completeResult = await sendOrObjectNotFound(
|
|
474
|
+
() => config.client.send(
|
|
475
|
+
new CompleteMultipartUploadCommand({
|
|
476
|
+
Bucket: bucket,
|
|
477
|
+
Key: key,
|
|
478
|
+
UploadId: uploadId,
|
|
479
|
+
MultipartUpload: { Parts: completeParts }
|
|
480
|
+
})
|
|
481
|
+
)
|
|
418
482
|
);
|
|
419
|
-
|
|
420
|
-
|
|
483
|
+
const head = await headObjectAfterMultipartComplete(
|
|
484
|
+
config.client,
|
|
485
|
+
bucket,
|
|
486
|
+
key
|
|
421
487
|
);
|
|
422
|
-
|
|
423
|
-
await new Promise((r) => setTimeout(r, 250 * 2 ** attempt));
|
|
424
|
-
head = await config.client.send(
|
|
425
|
-
new HeadObjectCommand({ Bucket: bucket, Key: key })
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
const contentLength = head.ContentLength ?? 0;
|
|
488
|
+
const contentLength = requireContentLength(head);
|
|
429
489
|
const contentType = head.ContentType;
|
|
430
490
|
const eTag = (head.ETag ?? completeResult.ETag ?? "").replace(/"/g, "");
|
|
431
491
|
const metadata = head.Metadata ?? {};
|
|
@@ -477,7 +537,7 @@ async function handleMultipartInit(config, input, request) {
|
|
|
477
537
|
fileName: input.fileName,
|
|
478
538
|
contentType: input.contentType
|
|
479
539
|
});
|
|
480
|
-
const acl = input.acl
|
|
540
|
+
const acl = resolveRequestAcl(config.multipart, input.acl);
|
|
481
541
|
const fileSize = typeof input.fileSize === "number" && input.fileSize > 0 ? Math.floor(input.fileSize) : void 0;
|
|
482
542
|
if (config.multipart?.requireFileSize && fileSize === void 0) {
|
|
483
543
|
throw errors.fileSizeRequiredMultipart();
|
|
@@ -486,7 +546,11 @@ async function handleMultipartInit(config, input, request) {
|
|
|
486
546
|
request,
|
|
487
547
|
key,
|
|
488
548
|
bucket,
|
|
489
|
-
fileSize
|
|
549
|
+
fileSize,
|
|
550
|
+
contentType: input.contentType,
|
|
551
|
+
metadata: input.metadata,
|
|
552
|
+
acl,
|
|
553
|
+
fileName: input.fileName
|
|
490
554
|
});
|
|
491
555
|
const { UploadId } = await config.client.send(
|
|
492
556
|
new CreateMultipartUploadCommand({
|
|
@@ -498,6 +562,9 @@ async function handleMultipartInit(config, input, request) {
|
|
|
498
562
|
ACL: acl
|
|
499
563
|
})
|
|
500
564
|
);
|
|
565
|
+
if (!UploadId) {
|
|
566
|
+
throw errors.internalError();
|
|
567
|
+
}
|
|
501
568
|
await runLifecycleHook(config.multipart?.onInit, {
|
|
502
569
|
request,
|
|
503
570
|
key,
|
|
@@ -537,14 +604,8 @@ async function handleListParts(config, input, request) {
|
|
|
537
604
|
bucket,
|
|
538
605
|
uploadId
|
|
539
606
|
});
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
Bucket: bucket,
|
|
543
|
-
Key: key,
|
|
544
|
-
UploadId: uploadId
|
|
545
|
-
})
|
|
546
|
-
);
|
|
547
|
-
const parts = (response.Parts ?? []).map((p) => ({
|
|
607
|
+
const listed = await listAllParts(config.client, { bucket, key, uploadId });
|
|
608
|
+
const parts = listed.map((p) => ({
|
|
548
609
|
partNumber: p.PartNumber ?? 0,
|
|
549
610
|
size: p.Size ?? 0,
|
|
550
611
|
eTag: (p.ETag ?? "").replace(/"/g, "")
|
|
@@ -574,7 +635,7 @@ async function handleSignPart(config, input, request) {
|
|
|
574
635
|
});
|
|
575
636
|
const uploadId = input.uploadId;
|
|
576
637
|
const partNumber = input.partNumber;
|
|
577
|
-
const expiresIn = normalizeExpiresIn(input.expiresIn);
|
|
638
|
+
const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
|
|
578
639
|
const partSize = typeof input.partSize === "number" && input.partSize > 0 ? Math.floor(input.partSize) : null;
|
|
579
640
|
await runHook(config.multipart?.partGuard, {
|
|
580
641
|
request,
|
|
@@ -615,6 +676,14 @@ var multipartPart = createS3Endpoint(
|
|
|
615
676
|
return handleSignPart(ctx.context.config, ctx.body, ctx.context.request);
|
|
616
677
|
}
|
|
617
678
|
);
|
|
679
|
+
function putMetadataHeaders(metadata) {
|
|
680
|
+
if (!metadata) return {};
|
|
681
|
+
const headers = {};
|
|
682
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
683
|
+
headers[`x-amz-meta-${k}`] = v;
|
|
684
|
+
}
|
|
685
|
+
return headers;
|
|
686
|
+
}
|
|
618
687
|
async function handleUpload(config, input, request) {
|
|
619
688
|
const { key, bucket } = await resolveRequestTarget(config, config.upload, {
|
|
620
689
|
request,
|
|
@@ -623,10 +692,13 @@ async function handleUpload(config, input, request) {
|
|
|
623
692
|
fileName: input.fileName,
|
|
624
693
|
contentType: input.contentType
|
|
625
694
|
});
|
|
626
|
-
const expiresIn = normalizeExpiresIn(input.expiresIn);
|
|
627
|
-
const acl = input.acl
|
|
695
|
+
const expiresIn = normalizeExpiresIn(input.expiresIn, config.maxExpiresIn);
|
|
696
|
+
const acl = resolveRequestAcl(config.upload, input.acl);
|
|
628
697
|
const contentType = input.contentType ?? "application/octet-stream";
|
|
629
698
|
const fileSize = typeof input.fileSize === "number" && input.fileSize > 0 ? Math.floor(input.fileSize) : null;
|
|
699
|
+
if (config.upload?.requireFileSize && fileSize === null) {
|
|
700
|
+
throw errors.fileSizeRequiredUpload();
|
|
701
|
+
}
|
|
630
702
|
await runHook(config.upload?.guard, {
|
|
631
703
|
request,
|
|
632
704
|
key,
|
|
@@ -638,12 +710,10 @@ async function handleUpload(config, input, request) {
|
|
|
638
710
|
fileName: input.fileName
|
|
639
711
|
});
|
|
640
712
|
const method = config.upload?.method ?? "POST";
|
|
641
|
-
if (method === "PUT" && config.upload?.requireFileSize && fileSize === null) {
|
|
642
|
-
throw errors.fileSizeRequiredUpload();
|
|
643
|
-
}
|
|
644
713
|
if (method === "PUT") {
|
|
645
714
|
const putHeaders = {
|
|
646
|
-
"Content-Type": contentType
|
|
715
|
+
"Content-Type": contentType,
|
|
716
|
+
...putMetadataHeaders(input.metadata)
|
|
647
717
|
};
|
|
648
718
|
if (input.fileName) {
|
|
649
719
|
putHeaders["Content-Disposition"] = buildContentDisposition(
|
|
@@ -657,6 +727,7 @@ async function handleUpload(config, input, request) {
|
|
|
657
727
|
Key: key,
|
|
658
728
|
ContentType: contentType,
|
|
659
729
|
ACL: acl,
|
|
730
|
+
Metadata: input.metadata,
|
|
660
731
|
...input.fileName ? { ContentDisposition: buildContentDisposition(input.fileName) } : {},
|
|
661
732
|
...fileSize !== null ? { ContentLength: fileSize } : {}
|
|
662
733
|
}),
|
|
@@ -702,7 +773,7 @@ async function handleUpload(config, input, request) {
|
|
|
702
773
|
{
|
|
703
774
|
Bucket: bucket,
|
|
704
775
|
Key: key,
|
|
705
|
-
Conditions: rangeMax !== void 0 ? [["content-length-range", rangeMin, rangeMax]] : [["content-length-range", rangeMin,
|
|
776
|
+
Conditions: rangeMax !== void 0 ? [["content-length-range", rangeMin, rangeMax]] : [["content-length-range", rangeMin, S3_MAX_POST_OBJECT_BYTES]],
|
|
706
777
|
Fields: fields,
|
|
707
778
|
Expires: expiresIn
|
|
708
779
|
}
|
|
@@ -753,6 +824,6 @@ var CORE_ENDPOINT_NAMES = Object.keys(
|
|
|
753
824
|
coreEndpoints
|
|
754
825
|
);
|
|
755
826
|
|
|
756
|
-
export { CORE_ENDPOINT_NAMES,
|
|
827
|
+
export { CORE_ENDPOINT_NAMES, createS3Endpoint, createS3Middleware };
|
|
757
828
|
//# sourceMappingURL=index.js.map
|
|
758
829
|
//# sourceMappingURL=index.js.map
|