@better-s3/core 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/LICENSE +21 -0
- package/dist/create-s3-api.d.ts +2 -0
- package/dist/helpers/build-content-disposition.d.ts +7 -0
- package/dist/helpers/build-object-key.d.ts +1 -0
- package/dist/helpers/format-file-size.d.ts +1 -0
- package/dist/helpers/get-file-extension.d.ts +1 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/parse-file-name.d.ts +1 -0
- package/dist/helpers/sanitize-file-name.d.ts +2 -0
- package/dist/helpers/truncate-filename.d.ts +4 -0
- package/dist/helpers/validate-file.d.ts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/dist/routes.d.ts +14 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/s3-api.d.ts +109 -0
- package/dist/types/s3-payloads.d.ts +110 -0
- package/dist/types/upload-presign-method.d.ts +2 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamidreza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,7 @@
|
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildContentDisposition(fileName: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildObjectKey(...parts: string[]): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatFileSize(bytes: number): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getFileExtension(filename: string): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { validateFile } from "./validate-file";
|
|
2
|
+
export { buildObjectKey } from "./build-object-key";
|
|
3
|
+
export { getFileExtension } from "./get-file-extension";
|
|
4
|
+
export { parseFileName } from "./parse-file-name";
|
|
5
|
+
export { formatFileSize } from "./format-file-size";
|
|
6
|
+
export { buildContentDisposition } from "./build-content-disposition";
|
|
7
|
+
export { sanitizeFileName } from "./sanitize-file-name";
|
|
8
|
+
export { truncateFilename } from "./truncate-filename";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseFileName(contentDisposition: string | null | undefined): string | undefined;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/helpers/validate-file.ts
|
|
2
|
+
function validateFile(file, options) {
|
|
3
|
+
if (options.accept?.length) {
|
|
4
|
+
const allowed = options.accept.some((type) => {
|
|
5
|
+
if (type.startsWith(".")) {
|
|
6
|
+
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
|
7
|
+
}
|
|
8
|
+
if (type.endsWith("/*")) {
|
|
9
|
+
return file.type.startsWith(type.replace("/*", "/"));
|
|
10
|
+
}
|
|
11
|
+
return file.type === type;
|
|
12
|
+
});
|
|
13
|
+
if (!allowed) {
|
|
14
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop() : null;
|
|
15
|
+
return `File type "${ext ? `.${ext}` : file.type || "unknown"}" is not allowed`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (file.size === 0) {
|
|
19
|
+
return "File is empty";
|
|
20
|
+
}
|
|
21
|
+
if (options.maxFileSize && file.size > options.maxFileSize) {
|
|
22
|
+
const maxMB = (options.maxFileSize / (1024 * 1024)).toFixed(1);
|
|
23
|
+
return `File size exceeds ${maxMB} MB limit`;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/helpers/build-object-key.ts
|
|
29
|
+
function buildObjectKey(...parts) {
|
|
30
|
+
return parts.map((p) => p.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/helpers/get-file-extension.ts
|
|
34
|
+
function getFileExtension(filename) {
|
|
35
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
36
|
+
if (dotIndex < 1) return "";
|
|
37
|
+
return filename.slice(dotIndex + 1).toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/helpers/parse-file-name.ts
|
|
41
|
+
function parseFileName(contentDisposition) {
|
|
42
|
+
if (!contentDisposition) return void 0;
|
|
43
|
+
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;,\s]+)/i);
|
|
44
|
+
if (utf8Match) {
|
|
45
|
+
try {
|
|
46
|
+
return decodeURIComponent(utf8Match[1]);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const asciiMatch = contentDisposition.match(/filename="([^"]*)"/i);
|
|
51
|
+
return asciiMatch?.[1];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/helpers/format-file-size.ts
|
|
55
|
+
function formatFileSize(bytes) {
|
|
56
|
+
if (bytes === 0) return "0 B";
|
|
57
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
58
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
59
|
+
const size = bytes / Math.pow(1024, i);
|
|
60
|
+
return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/helpers/build-content-disposition.ts
|
|
64
|
+
function buildContentDisposition(fileName) {
|
|
65
|
+
const ascii = fileName.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "_");
|
|
66
|
+
const encoded = encodeURIComponent(fileName);
|
|
67
|
+
return `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/helpers/sanitize-file-name.ts
|
|
71
|
+
function sanitizeFileName(fileName) {
|
|
72
|
+
return fileName.replace(/["\\\r\n]/g, "_");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/helpers/truncate-filename.ts
|
|
76
|
+
function truncateFilename(name, maxChars = 26) {
|
|
77
|
+
if (name.length <= maxChars) return name;
|
|
78
|
+
const dotIndex = name.lastIndexOf(".");
|
|
79
|
+
if (dotIndex <= 0) {
|
|
80
|
+
return name.slice(0, maxChars - 1) + "\u2026";
|
|
81
|
+
}
|
|
82
|
+
const ext = name.slice(dotIndex);
|
|
83
|
+
const available = maxChars - ext.length - 1;
|
|
84
|
+
if (available <= 0) {
|
|
85
|
+
return name.slice(0, maxChars - 1) + "\u2026";
|
|
86
|
+
}
|
|
87
|
+
return name.slice(0, available) + "\u2026 " + ext;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/routes.ts
|
|
91
|
+
var S3_API_BASE_PATH = "/api/s3";
|
|
92
|
+
function normalizeS3ApiBasePath(basePath) {
|
|
93
|
+
return basePath.replace(/\/$/, "");
|
|
94
|
+
}
|
|
95
|
+
var S3_API_ROUTES = {
|
|
96
|
+
upload: "presign/upload",
|
|
97
|
+
uploadConfirm: "presign/upload/confirm",
|
|
98
|
+
download: "presign/download",
|
|
99
|
+
delete: "delete",
|
|
100
|
+
multipartInit: "presign/multipart/init",
|
|
101
|
+
multipartPart: "presign/multipart/part",
|
|
102
|
+
multipartComplete: "presign/multipart/complete",
|
|
103
|
+
multipartAbort: "presign/multipart/abort",
|
|
104
|
+
multipartListParts: "presign/multipart/parts"
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/create-s3-api.ts
|
|
108
|
+
function createS3Api(basePath = S3_API_BASE_PATH) {
|
|
109
|
+
const base = normalizeS3ApiBasePath(basePath);
|
|
110
|
+
const json = async (url, init) => {
|
|
111
|
+
const res = await fetch(url, init);
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const body = await res.json().catch(() => ({}));
|
|
114
|
+
throw new Error(body.message ?? res.statusText);
|
|
115
|
+
}
|
|
116
|
+
return res.json();
|
|
117
|
+
};
|
|
118
|
+
const post = (url, body) => json(url, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify(body)
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
upload(payload) {
|
|
125
|
+
return post(
|
|
126
|
+
`${base}/${S3_API_ROUTES.upload}`,
|
|
127
|
+
payload
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
confirm(payload) {
|
|
131
|
+
return post(
|
|
132
|
+
`${base}/${S3_API_ROUTES.uploadConfirm}`,
|
|
133
|
+
payload
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
download(key, options) {
|
|
137
|
+
const params = new URLSearchParams({ key });
|
|
138
|
+
if (options?.fileName) {
|
|
139
|
+
params.set("fileName", sanitizeFileName(options.fileName));
|
|
140
|
+
}
|
|
141
|
+
if (options?.bucket) params.set("bucket", options.bucket);
|
|
142
|
+
return json(
|
|
143
|
+
`${base}/${S3_API_ROUTES.download}?${params}`
|
|
144
|
+
);
|
|
145
|
+
},
|
|
146
|
+
delete(key, options) {
|
|
147
|
+
const params = new URLSearchParams({ key });
|
|
148
|
+
if (options?.bucket) params.set("bucket", options.bucket);
|
|
149
|
+
return json(
|
|
150
|
+
`${base}/${S3_API_ROUTES.delete}?${params}`,
|
|
151
|
+
{ method: "DELETE" }
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
multipart: {
|
|
155
|
+
init(payload) {
|
|
156
|
+
return post(
|
|
157
|
+
`${base}/${S3_API_ROUTES.multipartInit}`,
|
|
158
|
+
payload
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
signPart(payload) {
|
|
162
|
+
return post(
|
|
163
|
+
`${base}/${S3_API_ROUTES.multipartPart}`,
|
|
164
|
+
payload
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
listParts(payload) {
|
|
168
|
+
const params = new URLSearchParams({
|
|
169
|
+
key: payload.key,
|
|
170
|
+
uploadId: payload.uploadId
|
|
171
|
+
});
|
|
172
|
+
if (payload.bucket) params.set("bucket", payload.bucket);
|
|
173
|
+
return json(
|
|
174
|
+
`${base}/${S3_API_ROUTES.multipartListParts}?${params}`
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
complete(payload) {
|
|
178
|
+
return post(`${base}/${S3_API_ROUTES.multipartComplete}`, payload);
|
|
179
|
+
},
|
|
180
|
+
abort(payload) {
|
|
181
|
+
return post(
|
|
182
|
+
`${base}/${S3_API_ROUTES.multipartAbort}`,
|
|
183
|
+
payload
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { S3_API_BASE_PATH, S3_API_ROUTES, buildContentDisposition, buildObjectKey, createS3Api, formatFileSize, getFileExtension, normalizeS3ApiBasePath, parseFileName, sanitizeFileName, truncateFilename, validateFile };
|
|
191
|
+
//# sourceMappingURL=index.js.map
|
|
192
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/helpers/validate-file.ts","../src/helpers/build-object-key.ts","../src/helpers/get-file-extension.ts","../src/helpers/parse-file-name.ts","../src/helpers/format-file-size.ts","../src/helpers/build-content-disposition.ts","../src/helpers/sanitize-file-name.ts","../src/helpers/truncate-filename.ts","../src/routes.ts","../src/create-s3-api.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export function validateFile(\n file: File,\n options: { accept?: string[]; maxFileSize?: number },\n): string | null {\n if (options.accept?.length) {\n const allowed = options.accept.some((type) => {\n if (type.startsWith(\".\")) {\n return file.name.toLowerCase().endsWith(type.toLowerCase());\n }\n if (type.endsWith(\"/*\")) {\n return file.type.startsWith(type.replace(\"/*\", \"/\"));\n }\n return file.type === type;\n });\n if (!allowed) {\n const ext = file.name.includes(\".\") ? file.name.split(\".\").pop() : null;\n return `File type \"${ext ? `.${ext}` : file.type || \"unknown\"}\" is not allowed`;\n }\n }\n\n if (file.size === 0) {\n return \"File is empty\";\n }\n\n if (options.maxFileSize && file.size > options.maxFileSize) {\n const maxMB = (options.maxFileSize / (1024 * 1024)).toFixed(1);\n return `File size exceeds ${maxMB} MB limit`;\n }\n\n return null;\n}\n","export function buildObjectKey(...parts: string[]): string {\n return parts\n .map((p) => p.replace(/^\\/+|\\/+$/g, \"\"))\n .filter(Boolean)\n .join(\"/\");\n}\n","export function getFileExtension(filename: string): string {\n const dotIndex = filename.lastIndexOf(\".\");\n if (dotIndex < 1) return \"\";\n return filename.slice(dotIndex + 1).toLowerCase();\n}\n","export function parseFileName(\n contentDisposition: string | null | 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","export function formatFileSize(bytes: number): string {\n if (bytes === 0) return \"0 B\";\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\n const size = bytes / Math.pow(1024, i);\n return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;\n}\n","/**\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.\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","/** Sanitizes a file name for safe use in URL query parameters. */\nexport function sanitizeFileName(fileName: string): string {\n return fileName.replace(/[\"\\\\\\r\\n]/g, \"_\");\n}\n","/**\n * Truncates a filename while preserving the extension when possible.\n */\nexport function truncateFilename(name: string, maxChars = 26): string {\n if (name.length <= maxChars) return name;\n const dotIndex = name.lastIndexOf(\".\");\n if (dotIndex <= 0) {\n return name.slice(0, maxChars - 1) + \"…\";\n }\n const ext = name.slice(dotIndex);\n const available = maxChars - ext.length - 1;\n if (available <= 0) {\n return name.slice(0, maxChars - 1) + \"…\";\n }\n return name.slice(0, available) + \"… \" + ext;\n}\n","/** Default mount path for the catch-all route (`app/api/s3/[...s3]/route.ts`). */\nexport const S3_API_BASE_PATH = \"/api/s3\";\n\nexport function normalizeS3ApiBasePath(basePath: string): string {\n return basePath.replace(/\\/$/, \"\");\n}\n\nexport const S3_API_ROUTES = {\n upload: \"presign/upload\",\n uploadConfirm: \"presign/upload/confirm\",\n download: \"presign/download\",\n delete: \"delete\",\n multipartInit: \"presign/multipart/init\",\n multipartPart: \"presign/multipart/part\",\n multipartComplete: \"presign/multipart/complete\",\n multipartAbort: \"presign/multipart/abort\",\n multipartListParts: \"presign/multipart/parts\",\n} as const;\n","import type {\n MultipartInitResponse,\n MultipartListPartsResponse,\n MultipartPartResponse,\n PresignResponse,\n S3Api,\n UploadConfirmResponse,\n UploadPresignResponse,\n} from \"./types/s3-api\";\nimport { sanitizeFileName } from \"./helpers/sanitize-file-name\";\nimport {\n normalizeS3ApiBasePath,\n S3_API_BASE_PATH,\n S3_API_ROUTES,\n} from \"./routes\";\n\nexport function createS3Api(basePath: string = S3_API_BASE_PATH): S3Api {\n const base = normalizeS3ApiBasePath(basePath);\n\n const json = async <T>(url: string, init?: RequestInit): Promise<T> => {\n const res = await fetch(url, init);\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error((body as { message?: string }).message ?? res.statusText);\n }\n return res.json() as Promise<T>;\n };\n\n const post = <T>(url: string, body: unknown): Promise<T> =>\n json<T>(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n return {\n upload(payload) {\n return post<UploadPresignResponse>(\n `${base}/${S3_API_ROUTES.upload}`,\n payload,\n );\n },\n\n confirm(payload) {\n return post<UploadConfirmResponse>(\n `${base}/${S3_API_ROUTES.uploadConfirm}`,\n payload,\n );\n },\n\n download(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.fileName) {\n params.set(\"fileName\", sanitizeFileName(options.fileName));\n }\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<PresignResponse>(\n `${base}/${S3_API_ROUTES.download}?${params}`,\n );\n },\n\n delete(key, options?) {\n const params = new URLSearchParams({ key });\n if (options?.bucket) params.set(\"bucket\", options.bucket);\n return json<{ success: boolean; bucket: string; key: string }>(\n `${base}/${S3_API_ROUTES.delete}?${params}`,\n { method: \"DELETE\" },\n );\n },\n\n multipart: {\n init(payload) {\n return post<MultipartInitResponse>(\n `${base}/${S3_API_ROUTES.multipartInit}`,\n payload,\n );\n },\n\n signPart(payload) {\n return post<MultipartPartResponse>(\n `${base}/${S3_API_ROUTES.multipartPart}`,\n payload,\n );\n },\n\n listParts(payload) {\n const params = new URLSearchParams({\n key: payload.key,\n uploadId: payload.uploadId,\n });\n if (payload.bucket) params.set(\"bucket\", payload.bucket);\n return json<MultipartListPartsResponse>(\n `${base}/${S3_API_ROUTES.multipartListParts}?${params}`,\n );\n },\n\n complete(payload) {\n return post<{\n key: string;\n bucket: string;\n uploadId: string;\n contentLength: number;\n contentType?: string;\n eTag?: string;\n metadata: Record<string, string>;\n }>(`${base}/${S3_API_ROUTES.multipartComplete}`, payload);\n },\n\n abort(payload) {\n return post<{ aborted: boolean }>(\n `${base}/${S3_API_ROUTES.multipartAbort}`,\n payload,\n );\n },\n },\n };\n}\n"]}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Default mount path for the catch-all route (`app/api/s3/[...s3]/route.ts`). */
|
|
2
|
+
export declare const S3_API_BASE_PATH = "/api/s3";
|
|
3
|
+
export declare function normalizeS3ApiBasePath(basePath: string): string;
|
|
4
|
+
export declare const S3_API_ROUTES: {
|
|
5
|
+
readonly upload: "presign/upload";
|
|
6
|
+
readonly uploadConfirm: "presign/upload/confirm";
|
|
7
|
+
readonly download: "presign/download";
|
|
8
|
+
readonly delete: "delete";
|
|
9
|
+
readonly multipartInit: "presign/multipart/init";
|
|
10
|
+
readonly multipartPart: "presign/multipart/part";
|
|
11
|
+
readonly multipartComplete: "presign/multipart/complete";
|
|
12
|
+
readonly multipartAbort: "presign/multipart/abort";
|
|
13
|
+
readonly multipartListParts: "presign/multipart/parts";
|
|
14
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { UploadPresignMethod } from "./upload-presign-method";
|
|
2
|
+
export type { S3ObjectAcl, UploadPayload, ConfirmPayload, DownloadOptions, DeleteOptions, DeleteResponse, MultipartInitPayload, MultipartSignPartPayload, MultipartListPartsPayload, MultipartCompletedPartRef, MultipartCompletePayload, MultipartCompleteResponse, MultipartAbortPayload, MultipartAbortResponse, } from "./s3-payloads";
|
|
3
|
+
export type { PresignResponse, UploadPresignResponse, MultipartInitResponse, MultipartPartResponse, MultipartPartInfo, MultipartListPartsResponse, UploadConfirmResponse, S3Api, } from "./s3-api";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { UploadPresignMethod } from "./upload-presign-method";
|
|
2
|
+
import type { ConfirmPayload, DeleteOptions, DeleteResponse, DownloadOptions, MultipartAbortPayload, MultipartAbortResponse, MultipartCompletePayload, MultipartCompleteResponse, MultipartInitPayload, MultipartListPartsPayload, MultipartSignPartPayload, S3ObjectAcl, UploadPayload } from "./s3-payloads";
|
|
3
|
+
export type { S3ObjectAcl } from "./s3-payloads";
|
|
4
|
+
/** Presigned GET URL for download. */
|
|
5
|
+
export type PresignResponse = {
|
|
6
|
+
/** S3 object key. */
|
|
7
|
+
key: string;
|
|
8
|
+
/** Target bucket. */
|
|
9
|
+
bucket: string;
|
|
10
|
+
/** Presigned URL. */
|
|
11
|
+
url: string;
|
|
12
|
+
/** Validity in seconds. */
|
|
13
|
+
expiresIn: number;
|
|
14
|
+
};
|
|
15
|
+
/** Presigned upload URL and method-specific fields. */
|
|
16
|
+
export type UploadPresignResponse = {
|
|
17
|
+
/** S3 object key. */
|
|
18
|
+
key: string;
|
|
19
|
+
/** Target bucket. */
|
|
20
|
+
bucket: string;
|
|
21
|
+
/** Presigned upload URL. */
|
|
22
|
+
url: string;
|
|
23
|
+
/** Validity in seconds. */
|
|
24
|
+
expiresIn: number;
|
|
25
|
+
/** HTTP method for the upload request. */
|
|
26
|
+
method: UploadPresignMethod;
|
|
27
|
+
/** Present when `method` is `"POST"`. Must be appended to FormData before the file. */
|
|
28
|
+
fields?: Record<string, string>;
|
|
29
|
+
/** Present when `method` is `"PUT"`. Must be set as request headers on the PUT request. */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
/** Response from multipart upload initialization. */
|
|
33
|
+
export type MultipartInitResponse = {
|
|
34
|
+
/** S3 object key. */
|
|
35
|
+
key: string;
|
|
36
|
+
/** Target bucket. */
|
|
37
|
+
bucket: string;
|
|
38
|
+
/** S3 multipart upload ID. */
|
|
39
|
+
uploadId: string;
|
|
40
|
+
};
|
|
41
|
+
/** Presigned URL for a single multipart part. */
|
|
42
|
+
export type MultipartPartResponse = {
|
|
43
|
+
/** Presigned PUT URL for this part. */
|
|
44
|
+
presignedUrl: string;
|
|
45
|
+
/** 1-based part number. */
|
|
46
|
+
partNumber: number;
|
|
47
|
+
/** Multipart upload ID. */
|
|
48
|
+
uploadId: string;
|
|
49
|
+
/** Target bucket. */
|
|
50
|
+
bucket: string;
|
|
51
|
+
/** Validity in seconds. */
|
|
52
|
+
expiresIn: number;
|
|
53
|
+
/** Exact byte size locked into this part's presigned URL signature. */
|
|
54
|
+
partSize?: number;
|
|
55
|
+
};
|
|
56
|
+
/** A part already uploaded in a multipart session. */
|
|
57
|
+
export type MultipartPartInfo = {
|
|
58
|
+
/** 1-based part number. */
|
|
59
|
+
partNumber: number;
|
|
60
|
+
/** Part size in bytes. */
|
|
61
|
+
size: number;
|
|
62
|
+
/** Part ETag from S3. */
|
|
63
|
+
eTag: string;
|
|
64
|
+
};
|
|
65
|
+
/** Response from listing uploaded multipart parts. */
|
|
66
|
+
export type MultipartListPartsResponse = {
|
|
67
|
+
parts: MultipartPartInfo[];
|
|
68
|
+
};
|
|
69
|
+
/** Verified object metadata after upload confirmation. */
|
|
70
|
+
export type UploadConfirmResponse = {
|
|
71
|
+
/** S3 object key. */
|
|
72
|
+
key: string;
|
|
73
|
+
/** Target bucket. */
|
|
74
|
+
bucket: string;
|
|
75
|
+
/** MIME type from HeadObject. */
|
|
76
|
+
contentType?: string;
|
|
77
|
+
/** Verified size in bytes. */
|
|
78
|
+
contentLength: number;
|
|
79
|
+
/** ETag from HeadObject. */
|
|
80
|
+
eTag?: string;
|
|
81
|
+
/** Object metadata. */
|
|
82
|
+
metadata: Record<string, string>;
|
|
83
|
+
/** Object ACL. */
|
|
84
|
+
acl?: S3ObjectAcl;
|
|
85
|
+
/** Stored filename. */
|
|
86
|
+
fileName?: string;
|
|
87
|
+
/** S3 version ID. */
|
|
88
|
+
versionId?: string;
|
|
89
|
+
/** Last modified timestamp. */
|
|
90
|
+
lastModified?: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Presign API protocol shared by `@better-s3/react` hooks and `@better-s3/server`.
|
|
94
|
+
*
|
|
95
|
+
* Implement with `createS3Api()` from `@better-s3/core`, or any custom backend.
|
|
96
|
+
*/
|
|
97
|
+
export type S3Api = {
|
|
98
|
+
upload: (payload: UploadPayload) => Promise<UploadPresignResponse>;
|
|
99
|
+
confirm: (payload: ConfirmPayload) => Promise<UploadConfirmResponse>;
|
|
100
|
+
download: (key: string, options?: DownloadOptions) => Promise<PresignResponse>;
|
|
101
|
+
delete: (key: string, options?: DeleteOptions) => Promise<DeleteResponse>;
|
|
102
|
+
multipart: {
|
|
103
|
+
init: (payload: MultipartInitPayload) => Promise<MultipartInitResponse>;
|
|
104
|
+
signPart: (payload: MultipartSignPartPayload) => Promise<MultipartPartResponse>;
|
|
105
|
+
listParts: (payload: MultipartListPartsPayload) => Promise<MultipartListPartsResponse>;
|
|
106
|
+
complete: (payload: MultipartCompletePayload) => Promise<MultipartCompleteResponse>;
|
|
107
|
+
abort: (payload: MultipartAbortPayload) => Promise<MultipartAbortResponse>;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** S3 object ACL. */
|
|
2
|
+
export type S3ObjectAcl = "private" | "public-read";
|
|
3
|
+
/** Payload for {@link S3Api.upload}. */
|
|
4
|
+
export type UploadPayload = {
|
|
5
|
+
/** S3 object key. */
|
|
6
|
+
key: string;
|
|
7
|
+
/** Declared MIME type. */
|
|
8
|
+
contentType?: string;
|
|
9
|
+
/** Declared size in bytes. */
|
|
10
|
+
fileSize?: number;
|
|
11
|
+
/** Custom object metadata (`x-amz-meta-*`). */
|
|
12
|
+
metadata?: Record<string, string>;
|
|
13
|
+
/** Override default bucket. */
|
|
14
|
+
bucket?: string;
|
|
15
|
+
/** Requested ACL. */
|
|
16
|
+
acl?: S3ObjectAcl;
|
|
17
|
+
/** Original filename for `Content-Disposition`. */
|
|
18
|
+
fileName?: string;
|
|
19
|
+
};
|
|
20
|
+
/** Payload for {@link S3Api.confirm}. */
|
|
21
|
+
export type ConfirmPayload = {
|
|
22
|
+
/** S3 object key. */
|
|
23
|
+
key: string;
|
|
24
|
+
/** Override default bucket. */
|
|
25
|
+
bucket?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Options for {@link S3Api.download}. */
|
|
28
|
+
export type DownloadOptions = {
|
|
29
|
+
/** Download filename for `Content-Disposition`. */
|
|
30
|
+
fileName?: string;
|
|
31
|
+
/** Override default bucket. */
|
|
32
|
+
bucket?: string;
|
|
33
|
+
};
|
|
34
|
+
/** Options for {@link S3Api.delete}. */
|
|
35
|
+
export type DeleteOptions = {
|
|
36
|
+
/** Override default bucket. */
|
|
37
|
+
bucket?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Response from {@link S3Api.delete}. */
|
|
40
|
+
export type DeleteResponse = {
|
|
41
|
+
success: boolean;
|
|
42
|
+
bucket: string;
|
|
43
|
+
key: string;
|
|
44
|
+
};
|
|
45
|
+
/** Payload for {@link S3Api.multipart.init}. Same fields as {@link UploadPayload}. */
|
|
46
|
+
export type MultipartInitPayload = UploadPayload;
|
|
47
|
+
/** Payload for {@link S3Api.multipart.signPart}. */
|
|
48
|
+
export type MultipartSignPartPayload = {
|
|
49
|
+
/** S3 object key. */
|
|
50
|
+
key: string;
|
|
51
|
+
/** Multipart upload ID. */
|
|
52
|
+
uploadId: string;
|
|
53
|
+
/** 1-based part number. */
|
|
54
|
+
partNumber: number;
|
|
55
|
+
/** Byte size locked into the presigned URL signature. */
|
|
56
|
+
partSize?: number;
|
|
57
|
+
/** Override default bucket. */
|
|
58
|
+
bucket?: string;
|
|
59
|
+
};
|
|
60
|
+
/** Payload for {@link S3Api.multipart.listParts}. */
|
|
61
|
+
export type MultipartListPartsPayload = {
|
|
62
|
+
/** S3 object key. */
|
|
63
|
+
key: string;
|
|
64
|
+
/** Multipart upload ID. */
|
|
65
|
+
uploadId: string;
|
|
66
|
+
/** Override default bucket. */
|
|
67
|
+
bucket?: string;
|
|
68
|
+
};
|
|
69
|
+
/** A completed multipart part reference for {@link S3Api.multipart.complete}. */
|
|
70
|
+
export type MultipartCompletedPartRef = {
|
|
71
|
+
/** 1-based part number. */
|
|
72
|
+
partNumber: number;
|
|
73
|
+
};
|
|
74
|
+
/** Payload for {@link S3Api.multipart.complete}. */
|
|
75
|
+
export type MultipartCompletePayload = {
|
|
76
|
+
/** S3 object key. */
|
|
77
|
+
key: string;
|
|
78
|
+
/** Multipart upload ID. */
|
|
79
|
+
uploadId: string;
|
|
80
|
+
/** Parts to assemble (order does not matter — S3 sorts by part number). */
|
|
81
|
+
parts: MultipartCompletedPartRef[];
|
|
82
|
+
/** Override default bucket. */
|
|
83
|
+
bucket?: string;
|
|
84
|
+
};
|
|
85
|
+
/** Response from {@link S3Api.multipart.complete}. */
|
|
86
|
+
export type MultipartCompleteResponse = {
|
|
87
|
+
key: string;
|
|
88
|
+
bucket: string;
|
|
89
|
+
uploadId: string;
|
|
90
|
+
/** Verified size in bytes. */
|
|
91
|
+
contentLength: number;
|
|
92
|
+
contentType?: string;
|
|
93
|
+
eTag?: string;
|
|
94
|
+
metadata: Record<string, string>;
|
|
95
|
+
versionId?: string;
|
|
96
|
+
lastModified?: string;
|
|
97
|
+
};
|
|
98
|
+
/** Payload for {@link S3Api.multipart.abort}. */
|
|
99
|
+
export type MultipartAbortPayload = {
|
|
100
|
+
/** S3 object key. */
|
|
101
|
+
key: string;
|
|
102
|
+
/** Multipart upload ID. */
|
|
103
|
+
uploadId: string;
|
|
104
|
+
/** Override default bucket. */
|
|
105
|
+
bucket?: string;
|
|
106
|
+
};
|
|
107
|
+
/** Response from {@link S3Api.multipart.abort}. */
|
|
108
|
+
export type MultipartAbortResponse = {
|
|
109
|
+
aborted: boolean;
|
|
110
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-s3/core",
|
|
3
|
+
"version": "3.1049.0",
|
|
4
|
+
"description": "Shared types and pure helpers for @better-s3/react and @better-s3/server",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"s3",
|
|
7
|
+
"types",
|
|
8
|
+
"upload",
|
|
9
|
+
"file-upload"
|
|
10
|
+
],
|
|
11
|
+
"author": "Hamidrezakz",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/hamidrezakz/better-s3.git",
|
|
16
|
+
"directory": "packages/better-s3-core"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/hamidrezakz/better-s3/tree/master/packages/better-s3-core",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/hamidrezakz/better-s3/issues"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "6.0.3"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup --config tsup.config.ts && tsc --emitDeclarationOnly",
|
|
42
|
+
"check-types": "tsc --noEmit"
|
|
43
|
+
}
|
|
44
|
+
}
|