@better-s3/server 3.1048.0 → 3.1049.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 +1 -1
- package/dist/adapters/next.js +11 -37
- package/dist/adapters/next.js.map +1 -1
- package/dist/helpers/index.d.ts +1 -2
- package/dist/index.d.ts +1 -7
- package/dist/index.js +12 -146
- package/dist/index.js.map +1 -1
- package/dist/types/hook-contexts.d.ts +127 -0
- package/dist/types/index.d.ts +2 -13
- package/dist/types/s3-handler-config.d.ts +47 -52
- package/package.json +5 -8
- package/dist/api.d.ts +0 -154
- package/dist/constants.d.ts +0 -3
- package/dist/helpers/build-content-disposition.d.ts +0 -13
- package/dist/helpers/index.js +0 -24
- package/dist/helpers/index.js.map +0 -1
- package/dist/helpers/parse-file-name.d.ts +0 -9
- package/dist/helpers/server/index.d.ts +0 -1
- package/dist/helpers/server/index.js +0 -26
- package/dist/helpers/server/index.js.map +0 -1
- package/dist/helpers/validate-file.d.ts +0 -4
- package/dist/types/delete-hook-context.d.ts +0 -5
- package/dist/types/download-hook-context.d.ts +0 -6
- package/dist/types/download-success-context.d.ts +0 -5
- package/dist/types/hook-context.d.ts +0 -3
- package/dist/types/multipart-complete-success-context.d.ts +0 -13
- package/dist/types/multipart-hook-context.d.ts +0 -7
- package/dist/types/multipart-init-success-context.d.ts +0 -8
- package/dist/types/multipart-list-success-context.d.ts +0 -11
- package/dist/types/upload-complete-context.d.ts +0 -15
- package/dist/types/upload-hook-context.d.ts +0 -10
- package/dist/types/upload-presign-method.d.ts +0 -2
- package/dist/types/upload-success-context.d.ts +0 -5
- /package/dist/helpers/{server/resolve-object-acl.d.ts → resolve-object-acl.d.ts} +0 -0
package/dist/api.d.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import type { UploadPresignMethod } from "./types/upload-presign-method";
|
|
2
|
-
export type PresignResponse = {
|
|
3
|
-
key: string;
|
|
4
|
-
bucket: string;
|
|
5
|
-
url: string;
|
|
6
|
-
expiresIn: number;
|
|
7
|
-
};
|
|
8
|
-
export type UploadPresignResponse = {
|
|
9
|
-
key: string;
|
|
10
|
-
bucket: string;
|
|
11
|
-
url: string;
|
|
12
|
-
expiresIn: number;
|
|
13
|
-
method: UploadPresignMethod;
|
|
14
|
-
/** Present when `method` is `"POST"`. Must be appended to FormData before the file. */
|
|
15
|
-
fields?: Record<string, string>;
|
|
16
|
-
/** Present when `method` is `"PUT"`. Must be set as request headers on the PUT request. */
|
|
17
|
-
headers?: Record<string, string>;
|
|
18
|
-
};
|
|
19
|
-
/** @deprecated Use {@link UploadPresignResponse} instead. */
|
|
20
|
-
export type PresignedPostResponse = UploadPresignResponse;
|
|
21
|
-
export type MultipartInitResponse = {
|
|
22
|
-
key: string;
|
|
23
|
-
bucket: string;
|
|
24
|
-
uploadId: string;
|
|
25
|
-
};
|
|
26
|
-
export type MultipartPartResponse = {
|
|
27
|
-
presignedUrl: string;
|
|
28
|
-
partNumber: number;
|
|
29
|
-
uploadId: string;
|
|
30
|
-
bucket: string;
|
|
31
|
-
expiresIn: number;
|
|
32
|
-
/** Exact byte size locked into this part's presigned URL signature. Only present when partSize was supplied to signPart. */
|
|
33
|
-
partSize?: number;
|
|
34
|
-
};
|
|
35
|
-
export type MultipartListPartsResponse = {
|
|
36
|
-
parts: Array<{
|
|
37
|
-
partNumber: number;
|
|
38
|
-
/** Byte size of the uploaded part. */
|
|
39
|
-
size: number;
|
|
40
|
-
/** ETag of the uploaded part. */
|
|
41
|
-
eTag: string;
|
|
42
|
-
}>;
|
|
43
|
-
};
|
|
44
|
-
export type UploadConfirmResponse = {
|
|
45
|
-
key: string;
|
|
46
|
-
bucket: string;
|
|
47
|
-
contentType?: string;
|
|
48
|
-
contentLength: number;
|
|
49
|
-
eTag?: string;
|
|
50
|
-
/** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */
|
|
51
|
-
metadata: Record<string, string>;
|
|
52
|
-
/** Resolved ACL. Omitted when ACL lookup is disabled or unsupported. */
|
|
53
|
-
acl?: "private" | "public-read";
|
|
54
|
-
/** Display file name parsed from the object's Content-Disposition header. */
|
|
55
|
-
fileName?: string;
|
|
56
|
-
/** Object version ID. Only present when bucket versioning is enabled. */
|
|
57
|
-
versionId?: string;
|
|
58
|
-
/** Last-modified timestamp from HeadObject (ISO 8601 string). */
|
|
59
|
-
lastModified?: string;
|
|
60
|
-
};
|
|
61
|
-
export type S3Api = {
|
|
62
|
-
upload: (payload: {
|
|
63
|
-
key: string;
|
|
64
|
-
contentType?: string;
|
|
65
|
-
/**
|
|
66
|
-
* Exact byte size of the file. When provided the presigned POST policy
|
|
67
|
-
* locks `content-length-range` to `[fileSize, fileSize]` so S3 rejects
|
|
68
|
-
* uploads of any other size at the storage layer.
|
|
69
|
-
*/
|
|
70
|
-
fileSize?: number;
|
|
71
|
-
metadata?: Record<string, string>;
|
|
72
|
-
bucket?: string;
|
|
73
|
-
acl?: "private" | "public-read";
|
|
74
|
-
/** Original file name. Stored as `Content-Disposition` on the S3 object. */
|
|
75
|
-
fileName?: string;
|
|
76
|
-
}) => Promise<UploadPresignResponse>;
|
|
77
|
-
confirm: (payload: {
|
|
78
|
-
key: string;
|
|
79
|
-
bucket?: string;
|
|
80
|
-
}) => Promise<UploadConfirmResponse>;
|
|
81
|
-
download: (key: string, options?: {
|
|
82
|
-
fileName?: string;
|
|
83
|
-
bucket?: string;
|
|
84
|
-
}) => Promise<PresignResponse>;
|
|
85
|
-
delete: (key: string, options?: {
|
|
86
|
-
bucket?: string;
|
|
87
|
-
}) => Promise<{
|
|
88
|
-
success: boolean;
|
|
89
|
-
bucket: string;
|
|
90
|
-
key: string;
|
|
91
|
-
}>;
|
|
92
|
-
multipart: {
|
|
93
|
-
init: (payload: {
|
|
94
|
-
key: string;
|
|
95
|
-
contentType?: string;
|
|
96
|
-
/** Declared total byte size of the file. Used for quota/guard checks and stored in `onInit` context. */
|
|
97
|
-
fileSize?: number;
|
|
98
|
-
metadata?: Record<string, string>;
|
|
99
|
-
bucket?: string;
|
|
100
|
-
acl?: "private" | "public-read";
|
|
101
|
-
/** Original file name. Stored as `Content-Disposition` on the S3 object. */
|
|
102
|
-
fileName?: string;
|
|
103
|
-
}) => Promise<MultipartInitResponse>;
|
|
104
|
-
signPart: (payload: {
|
|
105
|
-
key: string;
|
|
106
|
-
uploadId: string;
|
|
107
|
-
partNumber: number;
|
|
108
|
-
/**
|
|
109
|
-
* Exact byte size of the part. When provided, Content-Length is included
|
|
110
|
-
* in the HMAC signature so S3 rejects any part body of a different size.
|
|
111
|
-
*/
|
|
112
|
-
partSize?: number;
|
|
113
|
-
bucket?: string;
|
|
114
|
-
}) => Promise<MultipartPartResponse>;
|
|
115
|
-
/**
|
|
116
|
-
* List already-uploaded parts for an in-progress multipart upload.
|
|
117
|
-
* Used by the resumable upload flow to skip already-completed parts.
|
|
118
|
-
*/
|
|
119
|
-
listParts: (payload: {
|
|
120
|
-
key: string;
|
|
121
|
-
uploadId: string;
|
|
122
|
-
bucket?: string;
|
|
123
|
-
}) => Promise<MultipartListPartsResponse>;
|
|
124
|
-
complete: (payload: {
|
|
125
|
-
key: string;
|
|
126
|
-
uploadId: string;
|
|
127
|
-
parts: Array<{
|
|
128
|
-
partNumber: number;
|
|
129
|
-
}>;
|
|
130
|
-
bucket?: string;
|
|
131
|
-
}) => Promise<{
|
|
132
|
-
key: string;
|
|
133
|
-
bucket: string;
|
|
134
|
-
uploadId: string;
|
|
135
|
-
contentLength: number;
|
|
136
|
-
contentType?: string;
|
|
137
|
-
eTag?: string;
|
|
138
|
-
/** S3 object metadata (x-amz-meta-*). Always present; empty object when no metadata was stored. */
|
|
139
|
-
metadata: Record<string, string>;
|
|
140
|
-
/** Object version ID. Only present when bucket versioning is enabled. */
|
|
141
|
-
versionId?: string;
|
|
142
|
-
/** Last-modified timestamp from HeadObject (ISO 8601 string). */
|
|
143
|
-
lastModified?: string;
|
|
144
|
-
}>;
|
|
145
|
-
abort: (payload: {
|
|
146
|
-
key: string;
|
|
147
|
-
uploadId: string;
|
|
148
|
-
bucket?: string;
|
|
149
|
-
}) => Promise<{
|
|
150
|
-
aborted: boolean;
|
|
151
|
-
}>;
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
|
-
export declare function createS3Api(basePath?: string): S3Api;
|
package/dist/constants.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds a RFC 6266 `Content-Disposition` value for a file attachment.
|
|
3
|
-
*
|
|
4
|
-
* Includes both the ASCII-safe `filename` fallback and the RFC 5987
|
|
5
|
-
* `filename*` parameter for full Unicode support, so the original file
|
|
6
|
-
* name (including non-ASCII characters) is preserved across all browsers
|
|
7
|
-
* and HTTP clients.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* buildContentDisposition("résumé finale.pdf")
|
|
11
|
-
* // → 'attachment; filename="resume finale.pdf"; filename*=UTF-8\'\'r%C3%A9sum%C3%A9%20finale.pdf'
|
|
12
|
-
*/
|
|
13
|
-
export declare function buildContentDisposition(fileName: string): string;
|
package/dist/helpers/index.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// src/helpers/build-content-disposition.ts
|
|
2
|
-
function buildContentDisposition(fileName) {
|
|
3
|
-
const ascii = fileName.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "_");
|
|
4
|
-
const encoded = encodeURIComponent(fileName);
|
|
5
|
-
return `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// src/helpers/parse-file-name.ts
|
|
9
|
-
function parseFileName(contentDisposition) {
|
|
10
|
-
if (!contentDisposition) return void 0;
|
|
11
|
-
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;\s]+)/i);
|
|
12
|
-
if (utf8Match) {
|
|
13
|
-
try {
|
|
14
|
-
return decodeURIComponent(utf8Match[1]);
|
|
15
|
-
} catch {
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
const asciiMatch = contentDisposition.match(/filename="([^"]*)"/i);
|
|
19
|
-
return asciiMatch?.[1];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export { buildContentDisposition, parseFileName };
|
|
23
|
-
//# sourceMappingURL=index.js.map
|
|
24
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/helpers/build-content-disposition.ts","../../src/helpers/parse-file-name.ts"],"names":[],"mappings":";AAYO,SAAS,wBAAwB,QAAA,EAA0B;AAChE,EAAA,MAAM,KAAA,GAAQ,SAAS,OAAA,CAAQ,eAAA,EAAiB,GAAG,CAAA,CAAE,OAAA,CAAQ,UAAU,GAAG,CAAA;AAC1E,EAAA,MAAM,OAAA,GAAU,mBAAmB,QAAQ,CAAA;AAC3C,EAAA,OAAO,CAAA,sBAAA,EAAyB,KAAK,CAAA,oBAAA,EAAuB,OAAO,CAAA,CAAA;AACrE;;;ACRO,SAAS,cACd,kBAAA,EACoB;AACpB,EAAA,IAAI,CAAC,oBAAoB,OAAO,MAAA;AAEhC,EAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,KAAA,CAAM,8BAA8B,CAAA;AACzE,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,IAAI;AACF,MAAA,OAAO,kBAAA,CAAmB,SAAA,CAAU,CAAC,CAAC,CAAA;AAAA,IACxC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAA,CAAmB,KAAA,CAAM,qBAAqB,CAAA;AACjE,EAAA,OAAO,aAAa,CAAC,CAAA;AACvB","file":"index.js","sourcesContent":["/**\n * Builds a RFC 6266 `Content-Disposition` value for a file attachment.\n *\n * Includes both the ASCII-safe `filename` fallback and the RFC 5987\n * `filename*` parameter for full Unicode support, so the original file\n * name (including non-ASCII characters) is preserved across all browsers\n * and HTTP clients.\n *\n * @example\n * buildContentDisposition(\"résumé finale.pdf\")\n * // → 'attachment; filename=\"resume finale.pdf\"; filename*=UTF-8\\'\\'r%C3%A9sum%C3%A9%20finale.pdf'\n */\nexport function buildContentDisposition(fileName: string): string {\n const ascii = fileName.replace(/[^\\x20-\\x7E]/g, \"_\").replace(/[\"\\\\]/g, \"_\");\n const encoded = encodeURIComponent(fileName);\n return `attachment; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * Parses the display file name from a `Content-Disposition` header value.\n *\n * Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over\n * the plain ASCII `filename=\"…\"` fallback.\n *\n * Returns `undefined` when no filename can be extracted.\n */\nexport function parseFileName(\n contentDisposition: string | undefined,\n): string | undefined {\n if (!contentDisposition) return undefined;\n\n const utf8Match = contentDisposition.match(/filename\\*=UTF-8''([^;\\s]+)/i);\n if (utf8Match) {\n try {\n return decodeURIComponent(utf8Match[1]);\n } catch {\n /* ignore malformed encoding */\n }\n }\n\n const asciiMatch = contentDisposition.match(/filename=\"([^\"]*)\"/i);\n return asciiMatch?.[1];\n}\n"]}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses the display file name from a `Content-Disposition` header value.
|
|
3
|
-
*
|
|
4
|
-
* Prefers the RFC 5987 `filename*=UTF-8''…` encoding (full Unicode) over
|
|
5
|
-
* the plain ASCII `filename="…"` fallback.
|
|
6
|
-
*
|
|
7
|
-
* Returns `undefined` when no filename can be extracted.
|
|
8
|
-
*/
|
|
9
|
-
export declare function parseFileName(contentDisposition: string | undefined): string | undefined;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { resolveObjectAcl } from "./resolve-object-acl";
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { GetObjectAclCommand } from '@aws-sdk/client-s3';
|
|
2
|
-
|
|
3
|
-
// src/helpers/server/resolve-object-acl.ts
|
|
4
|
-
var DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1500;
|
|
5
|
-
async function resolveObjectAcl(s3, bucket, key, timeoutMs = DEFAULT_ACL_LOOKUP_TIMEOUT_MS) {
|
|
6
|
-
const controller = new AbortController();
|
|
7
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
8
|
-
try {
|
|
9
|
-
const result = await s3.send(
|
|
10
|
-
new GetObjectAclCommand({ Bucket: bucket, Key: key }),
|
|
11
|
-
{ abortSignal: controller.signal }
|
|
12
|
-
);
|
|
13
|
-
const isPublic = result.Grants?.some(
|
|
14
|
-
(g) => g.Grantee?.URI === "http://acs.amazonaws.com/groups/global/AllUsers" && (g.Permission === "READ" || g.Permission === "FULL_CONTROL")
|
|
15
|
-
);
|
|
16
|
-
return isPublic ? "public-read" : "private";
|
|
17
|
-
} catch {
|
|
18
|
-
return void 0;
|
|
19
|
-
} finally {
|
|
20
|
-
clearTimeout(timeout);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export { resolveObjectAcl };
|
|
25
|
-
//# sourceMappingURL=index.js.map
|
|
26
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/helpers/server/resolve-object-acl.ts"],"names":[],"mappings":";;;AAEA,IAAM,6BAAA,GAAgC,IAAA;AAStC,eAAsB,gBAAA,CACpB,EAAA,EACA,MAAA,EACA,GAAA,EACA,YAAoB,6BAAA,EAC4B;AAChD,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,UAAU,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,SAAS,CAAA;AAE9D,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,IAAA;AAAA,MACtB,IAAI,mBAAA,CAAoB,EAAE,QAAQ,MAAA,EAAQ,GAAA,EAAK,KAAK,CAAA;AAAA,MACpD,EAAE,WAAA,EAAa,UAAA,CAAW,MAAA;AAAO,KACnC;AACA,IAAA,MAAM,QAAA,GAAW,OAAO,MAAA,EAAQ,IAAA;AAAA,MAC9B,CAAC,CAAA,KACC,CAAA,CAAE,OAAA,EAAS,GAAA,KAAQ,sDAClB,CAAA,CAAE,UAAA,KAAe,MAAA,IAAU,CAAA,CAAE,UAAA,KAAe,cAAA;AAAA,KACjD;AACA,IAAA,OAAO,WAAW,aAAA,GAAgB,SAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,OAAO,CAAA;AAAA,EACtB;AACF","file":"index.js","sourcesContent":["import { GetObjectAclCommand, type S3Client } from \"@aws-sdk/client-s3\";\n\nconst DEFAULT_ACL_LOOKUP_TIMEOUT_MS = 1_500;\n\n/**\n * Determines the ACL of an S3 object by querying `GetObjectAcl`.\n *\n * Returns `\"public-read\"` when the `AllUsers` grantee has `READ` or\n * `FULL_CONTROL` permission, `\"private\"` when ACL is resolvable but not public,\n * and `undefined` when ACL cannot be resolved (e.g. unsupported providers).\n */\nexport async function resolveObjectAcl(\n s3: S3Client,\n bucket: string,\n key: string,\n timeoutMs: number = DEFAULT_ACL_LOOKUP_TIMEOUT_MS,\n): Promise<\"public-read\" | \"private\" | undefined> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const result = await s3.send(\n new GetObjectAclCommand({ Bucket: bucket, Key: key }),\n { abortSignal: controller.signal },\n );\n const isPublic = result.Grants?.some(\n (g) =>\n g.Grantee?.URI === \"http://acs.amazonaws.com/groups/global/AllUsers\" &&\n (g.Permission === \"READ\" || g.Permission === \"FULL_CONTROL\"),\n );\n return isPublic ? \"public-read\" : \"private\";\n } catch {\n return undefined;\n } finally {\n clearTimeout(timeout);\n }\n}\n"]}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { MultipartHookContext } from "./multipart-hook-context";
|
|
2
|
-
export type MultipartCompleteSuccessContext = MultipartHookContext & {
|
|
3
|
-
uploadId: string;
|
|
4
|
-
contentLength: number;
|
|
5
|
-
contentType?: string;
|
|
6
|
-
eTag?: string;
|
|
7
|
-
metadata: Record<string, string>;
|
|
8
|
-
/** Resolved ACL. Omitted when ACL lookup is disabled or unsupported. */
|
|
9
|
-
acl?: "private" | "public-read";
|
|
10
|
-
fileName?: string;
|
|
11
|
-
versionId?: string;
|
|
12
|
-
lastModified?: string;
|
|
13
|
-
};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { MultipartHookContext } from "./multipart-hook-context";
|
|
2
|
-
export type MultipartInitSuccessContext = MultipartHookContext & {
|
|
3
|
-
uploadId: string;
|
|
4
|
-
contentType?: string;
|
|
5
|
-
fileSize?: number;
|
|
6
|
-
metadata?: Record<string, string>;
|
|
7
|
-
acl?: "private" | "public-read";
|
|
8
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { HookContext } from "./hook-context";
|
|
2
|
-
/** contentLength and eTag are verified by S3 via HeadObject. */
|
|
3
|
-
export type UploadCompleteContext = HookContext & {
|
|
4
|
-
key: string;
|
|
5
|
-
bucket: string;
|
|
6
|
-
contentType?: string;
|
|
7
|
-
contentLength: number;
|
|
8
|
-
eTag?: string;
|
|
9
|
-
metadata?: Record<string, string>;
|
|
10
|
-
/** Resolved ACL. Omitted when ACL lookup is disabled or unsupported. */
|
|
11
|
-
acl?: "private" | "public-read";
|
|
12
|
-
fileName?: string;
|
|
13
|
-
versionId?: string;
|
|
14
|
-
lastModified?: string;
|
|
15
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { HookContext } from "./hook-context";
|
|
2
|
-
/** Values are client-declared and not verified by S3. */
|
|
3
|
-
export type UploadHookContext = HookContext & {
|
|
4
|
-
key: string;
|
|
5
|
-
bucket: string;
|
|
6
|
-
contentType?: string;
|
|
7
|
-
fileSize?: number;
|
|
8
|
-
metadata?: Record<string, string>;
|
|
9
|
-
acl?: "private" | "public-read";
|
|
10
|
-
};
|
|
File without changes
|