@better-s3/react 3.1047.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 +33 -53
- package/dist/api.d.ts +3 -5
- package/dist/helpers/index.d.ts +0 -6
- package/dist/hooks/use-delete.d.ts +11 -3
- package/dist/hooks/use-download.d.ts +10 -5
- package/dist/hooks/use-fetch-download.d.ts +11 -2
- package/dist/hooks/use-multi-upload-controls.d.ts +9 -0
- package/dist/hooks/use-multi-upload.d.ts +11 -3
- package/dist/hooks/use-upload-controls.d.ts +9 -0
- package/dist/hooks/use-upload.d.ts +8 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.js +4 -89
- package/dist/index.js.map +1 -1
- package/dist/s3-provider.d.ts +7 -7
- package/dist/types/delete.d.ts +3 -0
- package/dist/types/download.d.ts +6 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/multi-upload.d.ts +5 -0
- package/dist/types/upload.d.ts +28 -25
- package/dist/upload/multipart.d.ts +1 -1
- package/dist/upload/simple.d.ts +1 -1
- package/dist/upload/upload-file.d.ts +1 -1
- package/dist/upload/upload-files.d.ts +1 -1
- package/package.json +6 -3
- package/dist/helpers/build-object-key.d.ts +0 -20
- package/dist/helpers/format-file-size.d.ts +0 -11
- package/dist/helpers/get-file-extension.d.ts +0 -13
- package/dist/helpers/parse-content-disposition.d.ts +0 -18
- package/dist/helpers/truncate-filename.d.ts +0 -9
- package/dist/helpers/validate-file.d.ts +0 -22
- package/dist/types/s3-api.d.ts +0 -158
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @better-s3/react
|
|
2
2
|
|
|
3
|
-
Headless React hooks for S3
|
|
3
|
+
Headless React hooks for S3 upload, download, and delete — state, progress, and cancellation built in.
|
|
4
4
|
|
|
5
5
|
Requires [`@better-s3/server`](../better-s3-server) on the backend. For ready-made components, see [`@better-s3/ui`](../better-s3-ui).
|
|
6
6
|
|
|
@@ -13,85 +13,65 @@ pnpm add @better-s3/react @better-s3/server
|
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { createS3Api } from "@better-s3/
|
|
16
|
+
import { createS3Api } from "@better-s3/core";
|
|
17
17
|
|
|
18
|
-
const api = createS3Api(
|
|
18
|
+
const api = createS3Api();
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Hooks
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
| Hook | Use case |
|
|
24
|
+
| ------------------------ | --------------------------------------- |
|
|
25
|
+
| `useUploadControls` | Single file — picker + drag-and-drop |
|
|
26
|
+
| `useMultiUploadControls` | Multiple files — picker + drag-and-drop |
|
|
27
|
+
| `useUpload` | Single file — call `upload()` yourself |
|
|
28
|
+
| `useMultiUpload` | Batch — call `upload()` yourself |
|
|
29
|
+
| `useDownload` | Presigned URL → native browser download |
|
|
30
|
+
| `useFetchDownload` | Fetch with progress and cancel |
|
|
31
|
+
| `useDelete` | Two-step delete with confirmation |
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
const { phase, progress, error, upload, cancel, reset } = useUpload({
|
|
27
|
-
api,
|
|
28
|
-
accept: ["image/*", ".pdf"],
|
|
29
|
-
maxFileSize: 10 * 1024 * 1024, // client-side pre-validation (UX)
|
|
30
|
-
onSuccess: (_file, result) => console.log("Uploaded:", result.key),
|
|
31
|
-
onError: (_file, error) => {
|
|
32
|
-
// error.message for size violations:
|
|
33
|
-
// Simple: S3 returns 403 — "Upload failed: 403 Forbidden"
|
|
34
|
-
// Multipart: server returns 422 — "File size (X bytes) exceeds …"
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await upload(file, `uploads/${file.name}`, { metadata: { source: "web" } });
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Phases:** `idle → validating → uploading → success | error`
|
|
42
|
-
|
|
43
|
-
> `maxFileSize` performs a client-side check before the upload starts (good UX). The real enforcement is server-side:
|
|
44
|
-
>
|
|
45
|
-
> - **Simple uploads** — S3 enforces exact file size via a signed `content-length-range` policy. The client cannot upload a different-sized file with that URL.
|
|
46
|
-
> - **Multipart uploads** — Server verifies via `HeadObject` after `CompleteMultipartUpload` and deletes the object if the limit is exceeded.
|
|
47
|
-
|
|
48
|
-
### `useUploadControls` — Upload with file picker & drag-drop
|
|
33
|
+
### Upload — single file
|
|
49
34
|
|
|
50
35
|
```tsx
|
|
51
|
-
const {
|
|
36
|
+
const { openFilePicker, inputProps, dropHandlers, progress, isUploading } =
|
|
52
37
|
useUploadControls({
|
|
53
38
|
api,
|
|
54
39
|
objectKey: (file) => `uploads/${file.name}`,
|
|
55
|
-
|
|
40
|
+
accept: ["image/*"],
|
|
41
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
56
42
|
});
|
|
43
|
+
|
|
44
|
+
// <input {...inputProps} />
|
|
45
|
+
// <div {...dropHandlers} onClick={openFilePicker}>…</div>
|
|
57
46
|
```
|
|
58
47
|
|
|
59
|
-
###
|
|
48
|
+
### Upload — multiple files
|
|
60
49
|
|
|
61
50
|
```tsx
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
const { openFilePicker, inputProps, files, totalProgress } =
|
|
52
|
+
useMultiUploadControls({
|
|
53
|
+
api,
|
|
54
|
+
objectKey: (file) => `uploads/${file.name}`,
|
|
55
|
+
maxFiles: 5,
|
|
56
|
+
concurrentFiles: 3,
|
|
57
|
+
});
|
|
69
58
|
```
|
|
70
59
|
|
|
71
|
-
###
|
|
60
|
+
### Download
|
|
72
61
|
|
|
73
62
|
```tsx
|
|
74
|
-
const {
|
|
63
|
+
const { download } = useDownload({ api });
|
|
75
64
|
download("uploads/photo.jpg", "photo.jpg");
|
|
76
65
|
```
|
|
77
66
|
|
|
78
|
-
###
|
|
67
|
+
### Delete
|
|
79
68
|
|
|
80
69
|
```tsx
|
|
81
|
-
const {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
requestDelete("uploads/photo.jpg"); // phase → "confirming"
|
|
85
|
-
confirmDelete(); // phase → "deleting" → "success"
|
|
70
|
+
const { requestDelete, confirmDelete } = useDelete({ api });
|
|
71
|
+
requestDelete("uploads/photo.jpg");
|
|
72
|
+
await confirmDelete();
|
|
86
73
|
```
|
|
87
74
|
|
|
88
|
-
## Upload modes
|
|
89
|
-
|
|
90
|
-
| Mode | When used | Size enforcement |
|
|
91
|
-
| --------- | ------------------------------------------------ | --------------------------------------------------------- |
|
|
92
|
-
| Simple | `file.size < multipartThreshold` (default 50 MB) | S3 presigned POST policy — exact `content-length-range` |
|
|
93
|
-
| Multipart | `multipart: true` or file ≥ threshold | Server `HeadObject` check after `CompleteMultipartUpload` |
|
|
94
|
-
|
|
95
75
|
## License
|
|
96
76
|
|
|
97
77
|
MIT
|
package/dist/api.d.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* endpoint, tRPC mutation, GraphQL query, or anything else. The object is
|
|
6
6
|
* returned as-is; `createS3Client` just enforces the type contract.
|
|
7
7
|
*
|
|
8
|
-
* If you are using
|
|
9
|
-
*
|
|
8
|
+
* If you are using the default better-s3 route layout, use `createS3Api` from
|
|
9
|
+
* `@better-s3/core` — it builds a standard fetch client from a base path.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
@@ -24,7 +24,5 @@
|
|
|
24
24
|
* });
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
|
-
import type { S3Api } from "
|
|
27
|
+
import type { S3Api } from "@better-s3/core";
|
|
28
28
|
export declare function createS3Client(input: S3Api): S3Api;
|
|
29
|
-
/** @deprecated Use {@link createS3Client} instead. */
|
|
30
|
-
export declare const createS3Api: typeof createS3Client;
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export { formatFileSize } from "./format-file-size";
|
|
2
|
-
export { validateFile } from "./validate-file";
|
|
3
|
-
export { parseContentDispositionFilename } from "./parse-content-disposition";
|
|
4
1
|
export { formatUploadProgress } from "./format-upload-progress";
|
|
5
2
|
export { formatSpeed } from "./format-speed";
|
|
6
3
|
export { formatEta } from "./format-eta";
|
|
7
|
-
export { buildObjectKey } from "./build-object-key";
|
|
8
|
-
export { getFileExtension } from "./get-file-extension";
|
|
9
|
-
export { truncateFilename } from "./truncate-filename";
|
|
10
4
|
export { createSpeedTracker, type SpeedTracker } from "./speed-tracker";
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import type { S3Api } from "
|
|
1
|
+
import type { S3Api } from "@better-s3/core";
|
|
2
2
|
import type { DeletePhase, DeleteHooks } from "../types";
|
|
3
|
+
/** Options for {@link useDelete}. */
|
|
3
4
|
export type UseDeleteOptions = DeleteHooks & {
|
|
4
5
|
/** S3Api client. Optional when an `<S3Provider>` is present in the tree. */
|
|
5
6
|
api?: S3Api;
|
|
6
|
-
/** Target bucket (overrides server default) */
|
|
7
|
+
/** Target bucket (overrides server default). */
|
|
7
8
|
bucket?: string;
|
|
8
9
|
};
|
|
9
10
|
export type UseDeleteState = {
|
|
11
|
+
/** Current delete phase. */
|
|
10
12
|
phase: DeletePhase;
|
|
13
|
+
/** Error message, or `null`. */
|
|
11
14
|
error: string | null;
|
|
12
15
|
};
|
|
13
16
|
export type UseDeleteReturn = UseDeleteState & {
|
|
17
|
+
/** Key awaiting confirmation, or `null`. */
|
|
18
|
+
pendingKey: string | null;
|
|
19
|
+
/** Move to the `confirming` phase for the given key. */
|
|
14
20
|
requestDelete: (key: string) => void;
|
|
21
|
+
/** Send the delete request for the pending key. */
|
|
15
22
|
confirmDelete: () => Promise<void>;
|
|
23
|
+
/** Cancel confirmation and return to `idle`. */
|
|
16
24
|
cancelDelete: () => void;
|
|
25
|
+
/** Reset state to `idle`. */
|
|
17
26
|
reset: () => void;
|
|
18
|
-
pendingKey: string | null;
|
|
19
27
|
};
|
|
20
28
|
export declare function useDelete(options: UseDeleteOptions): UseDeleteReturn;
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import type { S3Api } from "
|
|
1
|
+
import type { S3Api } from "@better-s3/core";
|
|
2
2
|
export type { DownloadPhase, DownloadHooks } from "../types/download";
|
|
3
3
|
import type { DownloadPhase, DownloadHooks } from "../types/download";
|
|
4
|
+
/** Options for {@link useDownload}. */
|
|
4
5
|
export type UseDownloadOptions = DownloadHooks & {
|
|
5
6
|
/** S3Api client. Optional when an `<S3Provider>` is present in the tree. */
|
|
6
7
|
api?: S3Api;
|
|
7
|
-
/** Target bucket (overrides server default) */
|
|
8
|
+
/** Target bucket (overrides server default). */
|
|
8
9
|
bucket?: string;
|
|
9
10
|
};
|
|
10
11
|
export type UseDownloadState = {
|
|
12
|
+
/** Current download phase. */
|
|
11
13
|
phase: DownloadPhase;
|
|
14
|
+
/** Error message, or `null`. */
|
|
12
15
|
error: string | null;
|
|
13
|
-
/** Presigned URL — set after a successful presign, cleared on reset */
|
|
16
|
+
/** Presigned URL — set after a successful presign, cleared on reset. */
|
|
14
17
|
url: string | null;
|
|
15
|
-
/** Validity window in seconds for the presigned URL */
|
|
18
|
+
/** Validity window in seconds for the presigned URL. */
|
|
16
19
|
expiresIn: number | null;
|
|
17
20
|
};
|
|
18
21
|
export type UseDownloadReturn = UseDownloadState & {
|
|
22
|
+
/** Presign and trigger a native browser download. */
|
|
19
23
|
download: (key: string, downloadName?: string) => Promise<void>;
|
|
20
|
-
/** Fetch the presigned URL without triggering a browser download
|
|
24
|
+
/** Fetch the presigned URL without triggering a browser download. */
|
|
21
25
|
presign: (key: string, downloadName?: string) => Promise<{
|
|
22
26
|
url: string;
|
|
23
27
|
expiresIn: number;
|
|
24
28
|
} | null>;
|
|
29
|
+
/** Reset state to `idle`. */
|
|
25
30
|
reset: () => void;
|
|
26
31
|
};
|
|
27
32
|
export declare function useDownload(options: UseDownloadOptions): UseDownloadReturn;
|
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import type { S3Api } from "
|
|
1
|
+
import type { S3Api } from "@better-s3/core";
|
|
2
2
|
import type { FetchDownloadPhase, FetchDownloadHooks } from "../types/download";
|
|
3
3
|
import type { UploadProgress } from "../types/upload";
|
|
4
4
|
export type { FetchDownloadPhase, FetchDownloadProgress, FetchDownloadHooks, } from "../types/download";
|
|
5
|
+
/** Options for {@link useFetchDownload}. */
|
|
5
6
|
export type UseFetchDownloadOptions = FetchDownloadHooks & {
|
|
6
7
|
/** S3Api client. Optional when an `<S3Provider>` is present in the tree. */
|
|
7
8
|
api?: S3Api;
|
|
8
|
-
/** Target bucket (overrides server default) */
|
|
9
|
+
/** Target bucket (overrides server default). */
|
|
9
10
|
bucket?: string;
|
|
10
11
|
};
|
|
11
12
|
export type UseFetchDownloadState = {
|
|
13
|
+
/** Current download phase. */
|
|
12
14
|
phase: FetchDownloadPhase;
|
|
15
|
+
/** Byte transfer progress. */
|
|
13
16
|
progress: UploadProgress;
|
|
17
|
+
/** Error message, or `null`. */
|
|
14
18
|
error: string | null;
|
|
19
|
+
/** Resolved download filename. */
|
|
15
20
|
fileName: string | null;
|
|
21
|
+
/** Total file size in bytes. */
|
|
16
22
|
fileSize: number | null;
|
|
17
23
|
};
|
|
18
24
|
export type UseFetchDownloadReturn = UseFetchDownloadState & {
|
|
25
|
+
/** Presign, fetch bytes, and save via the browser. */
|
|
19
26
|
download: (key: string, downloadName?: string) => Promise<void>;
|
|
27
|
+
/** Abort the active download. */
|
|
20
28
|
cancel: () => void;
|
|
29
|
+
/** Reset state to `idle`. */
|
|
21
30
|
reset: () => void;
|
|
22
31
|
};
|
|
23
32
|
export declare function useFetchDownload(options: UseFetchDownloadOptions): UseFetchDownloadReturn;
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import type { UploadProgress, MultiUploadFileState, MultiUploadPhase } from "../types";
|
|
2
2
|
import { type UseMultiUploadOptions } from "./use-multi-upload";
|
|
3
|
+
/** Options for {@link useMultiUploadControls}. */
|
|
3
4
|
export type UseMultiUploadControlsOptions = UseMultiUploadOptions & {
|
|
5
|
+
/** S3 object key, or a function that derives it from each file. */
|
|
4
6
|
objectKey: string | ((file: File) => string);
|
|
5
7
|
};
|
|
6
8
|
export type UseMultiUploadControlsReturn = {
|
|
9
|
+
/** Current batch upload phase. */
|
|
7
10
|
phase: MultiUploadPhase;
|
|
8
11
|
/** Per-file upload states. */
|
|
9
12
|
files: MultiUploadFileState[];
|
|
10
13
|
/** Aggregated progress across all files. */
|
|
11
14
|
totalProgress: UploadProgress;
|
|
15
|
+
/** Batch-level error message, or `null`. */
|
|
12
16
|
error: string | null;
|
|
17
|
+
/** `true` while uploading. */
|
|
13
18
|
isUploading: boolean;
|
|
19
|
+
/** Handle files from drag-and-drop or a file input. */
|
|
14
20
|
handleFiles: (files: FileList | null) => void;
|
|
21
|
+
/** Open the hidden file picker. */
|
|
15
22
|
openFilePicker: () => void;
|
|
23
|
+
/** Abort all in-flight uploads. */
|
|
16
24
|
cancel: () => void;
|
|
25
|
+
/** Reset state to `idle`. */
|
|
17
26
|
reset: () => void;
|
|
18
27
|
/** Spread on a hidden `<input>` element. */
|
|
19
28
|
inputProps: {
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
-
import type { S3Api } from "
|
|
1
|
+
import type { S3Api } from "@better-s3/core";
|
|
2
2
|
import type { UploadConfig, UploadProgress, UploadRequestOptions, MultiUploadPhase, MultiUploadFileState, MultiUploadHooks } from "../types";
|
|
3
|
+
/** Options for {@link useMultiUpload}. */
|
|
3
4
|
export type UseMultiUploadOptions = UploadConfig & MultiUploadHooks & {
|
|
4
5
|
/** S3Api client. Optional when an `<S3Provider>` is present in the tree. */
|
|
5
6
|
api?: S3Api;
|
|
6
|
-
/** Static request options applied to all files */
|
|
7
|
+
/** Static request options applied to all files. */
|
|
7
8
|
uploadOptions?: UploadRequestOptions;
|
|
8
|
-
/** Per-file request options (overrides uploadOptions) */
|
|
9
|
+
/** Per-file request options (overrides `uploadOptions`). */
|
|
9
10
|
getUploadOptions?: (file: File) => UploadRequestOptions;
|
|
10
11
|
};
|
|
11
12
|
export type UseMultiUploadState = {
|
|
13
|
+
/** Current batch upload phase. */
|
|
12
14
|
phase: MultiUploadPhase;
|
|
15
|
+
/** Per-file upload states. */
|
|
13
16
|
files: MultiUploadFileState[];
|
|
17
|
+
/** Aggregated progress across all files. */
|
|
14
18
|
totalProgress: UploadProgress;
|
|
19
|
+
/** Batch-level error message, or `null`. */
|
|
15
20
|
error: string | null;
|
|
16
21
|
};
|
|
17
22
|
export type UseMultiUploadReturn = UseMultiUploadState & {
|
|
23
|
+
/** Upload multiple files. */
|
|
18
24
|
upload: (files: File[], resolveKey: (file: File) => string) => Promise<void>;
|
|
25
|
+
/** Abort all in-flight uploads. */
|
|
19
26
|
cancel: () => void;
|
|
27
|
+
/** Reset state to `idle`. */
|
|
20
28
|
reset: () => void;
|
|
21
29
|
};
|
|
22
30
|
export declare function useMultiUpload(options: UseMultiUploadOptions): UseMultiUploadReturn;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { UploadPhase, UploadProgress, UploadRequestOptions } from "../types";
|
|
2
2
|
import { type UseUploadOptions } from "./use-upload";
|
|
3
|
+
/** Options for {@link useUploadControls}. */
|
|
3
4
|
export type UseUploadControlsOptions = UseUploadOptions & {
|
|
5
|
+
/** S3 object key, or a function that derives it from the file. */
|
|
4
6
|
objectKey: string | ((file: File) => string);
|
|
5
7
|
/** Static request options applied to the upload. */
|
|
6
8
|
uploadOptions?: UploadRequestOptions;
|
|
@@ -8,17 +10,24 @@ export type UseUploadControlsOptions = UseUploadOptions & {
|
|
|
8
10
|
getUploadOptions?: (file: File) => UploadRequestOptions;
|
|
9
11
|
};
|
|
10
12
|
export type UseUploadControlsReturn = {
|
|
13
|
+
/** Current upload phase. */
|
|
11
14
|
phase: UploadPhase;
|
|
12
15
|
/** Info about the selected file. */
|
|
13
16
|
fileInfo: {
|
|
14
17
|
name: string;
|
|
15
18
|
size: number;
|
|
16
19
|
} | null;
|
|
20
|
+
/** Byte transfer progress. */
|
|
17
21
|
progress: UploadProgress;
|
|
22
|
+
/** Error message, or `null`. */
|
|
18
23
|
error: string | null;
|
|
24
|
+
/** `true` while uploading. */
|
|
19
25
|
isUploading: boolean;
|
|
26
|
+
/** Handle files from drag-and-drop or a file input. */
|
|
20
27
|
handleFiles: (files: FileList | null) => void;
|
|
28
|
+
/** Open the hidden file picker. */
|
|
21
29
|
openFilePicker: () => void;
|
|
30
|
+
/** Abort and reset to idle. */
|
|
22
31
|
cancel: () => void;
|
|
23
32
|
/**
|
|
24
33
|
* Soft-stop: preserves S3 parts and store entry so a future `upload()` can
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type { S3Api } from "
|
|
1
|
+
import type { S3Api } from "@better-s3/core";
|
|
2
2
|
import type { UploadConfig, UploadHooks, UploadPhase, UploadProgress, UploadResult, UploadRequestOptions } from "../types";
|
|
3
|
+
/** Options for {@link useUpload}. */
|
|
3
4
|
export type UseUploadOptions = UploadConfig & UploadHooks & {
|
|
4
5
|
/** S3Api client. Optional when an `<S3Provider>` is present in the tree. */
|
|
5
6
|
api?: S3Api;
|
|
6
7
|
};
|
|
7
8
|
export type UseUploadState = {
|
|
9
|
+
/** Current upload phase. */
|
|
8
10
|
phase: UploadPhase;
|
|
11
|
+
/** Byte transfer progress. */
|
|
9
12
|
progress: UploadProgress;
|
|
13
|
+
/** Error message, or `null`. */
|
|
10
14
|
error: string | null;
|
|
15
|
+
/** Result after success, or `null`. */
|
|
11
16
|
result: UploadResult | null;
|
|
17
|
+
/** Name of the file being uploaded. */
|
|
12
18
|
fileName: string | null;
|
|
19
|
+
/** Size of the file being uploaded in bytes. */
|
|
13
20
|
fileSize: number | null;
|
|
14
21
|
};
|
|
15
22
|
export type UseUploadReturn = UseUploadState & {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export { createS3Client
|
|
3
|
-
/** @deprecated Use {@link createS3Client} instead. */
|
|
4
|
-
export { createS3Api as createPresignApi } from "./api";
|
|
2
|
+
export { createS3Client } from "./api";
|
|
5
3
|
export { S3Provider, useS3Client, S3Context } from "./s3-provider";
|
|
6
4
|
export { createLocalStorageStore, createMemoryStore } from "./store";
|
|
7
5
|
export { uploadFile, uploadFiles, type UploadEngineCallbacks, type FileItem, type FileItemStatus, type MultiUploadCallbacks, } from "./upload";
|
|
8
|
-
export {
|
|
6
|
+
export { formatUploadProgress, formatSpeed, formatEta, createSpeedTracker, type SpeedTracker, } from "./helpers";
|
|
9
7
|
export { useUpload, type UseUploadOptions, type UseUploadState, type UseUploadReturn, } from "./hooks/use-upload";
|
|
10
8
|
export { useMultiUpload, type UseMultiUploadOptions, type UseMultiUploadState, type UseMultiUploadReturn, } from "./hooks/use-multi-upload";
|
|
11
9
|
export { useUploadControls, type UseUploadControlsOptions, type UseUploadControlsReturn, } from "./hooks/use-upload-controls";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useRef, useCallback } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { formatFileSize, validateFile, parseFileName } from '@better-s3/core';
|
|
3
4
|
|
|
4
5
|
// src/types/error.ts
|
|
5
6
|
var S3UploadError = class extends Error {
|
|
@@ -16,7 +17,6 @@ var S3UploadError = class extends Error {
|
|
|
16
17
|
function createS3Client(input) {
|
|
17
18
|
return input;
|
|
18
19
|
}
|
|
19
|
-
var createS3Api = createS3Client;
|
|
20
20
|
var S3Context = createContext(null);
|
|
21
21
|
function S3Provider({
|
|
22
22
|
client,
|
|
@@ -414,7 +414,7 @@ async function uploadFile(api, file, objectKey, config = {}, callbacks = {}, sig
|
|
|
414
414
|
acl: requestOptions?.acl
|
|
415
415
|
});
|
|
416
416
|
callbacks.onPhaseChange?.("uploading");
|
|
417
|
-
if (presign.method === "
|
|
417
|
+
if (presign.method === "PUT") {
|
|
418
418
|
await uploadPut(
|
|
419
419
|
file,
|
|
420
420
|
presign.url,
|
|
@@ -517,66 +517,11 @@ async function uploadFiles(api, items, config = {}, callbacks = {}, signal, getR
|
|
|
517
517
|
await Promise.all(workers);
|
|
518
518
|
return results;
|
|
519
519
|
}
|
|
520
|
-
|
|
521
|
-
// src/helpers/format-file-size.ts
|
|
522
|
-
function formatFileSize(bytes) {
|
|
523
|
-
if (bytes === 0) return "0 B";
|
|
524
|
-
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
525
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
526
|
-
const size = bytes / Math.pow(1024, i);
|
|
527
|
-
return `${size.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// src/helpers/validate-file.ts
|
|
531
|
-
function validateFile(file, options) {
|
|
532
|
-
if (options.accept?.length) {
|
|
533
|
-
const allowed = options.accept.some((type) => {
|
|
534
|
-
if (type.startsWith(".")) {
|
|
535
|
-
return file.name.toLowerCase().endsWith(type.toLowerCase());
|
|
536
|
-
}
|
|
537
|
-
if (type.endsWith("/*")) {
|
|
538
|
-
return file.type.startsWith(type.replace("/*", "/"));
|
|
539
|
-
}
|
|
540
|
-
return file.type === type;
|
|
541
|
-
});
|
|
542
|
-
if (!allowed) {
|
|
543
|
-
const ext = file.name.includes(".") ? file.name.split(".").pop() : null;
|
|
544
|
-
return `File type "${ext ? `.${ext}` : file.type || "unknown"}" is not allowed`;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (file.size === 0) {
|
|
548
|
-
return "File is empty";
|
|
549
|
-
}
|
|
550
|
-
if (options.maxFileSize && file.size > options.maxFileSize) {
|
|
551
|
-
const maxMB = (options.maxFileSize / (1024 * 1024)).toFixed(1);
|
|
552
|
-
return `File size exceeds ${maxMB} MB limit`;
|
|
553
|
-
}
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// src/helpers/parse-content-disposition.ts
|
|
558
|
-
function parseContentDispositionFilename(header, fallback) {
|
|
559
|
-
if (!header) return fallback;
|
|
560
|
-
const starMatch = header.match(/filename\*=UTF-8''([^;,\s]+)/i);
|
|
561
|
-
if (starMatch) {
|
|
562
|
-
try {
|
|
563
|
-
return decodeURIComponent(starMatch[1]);
|
|
564
|
-
} catch {
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
const match = header.match(/filename="([^"]+)"/i);
|
|
568
|
-
if (match) return match[1];
|
|
569
|
-
return fallback;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// src/helpers/format-upload-progress.ts
|
|
573
520
|
function formatUploadProgress(loaded, total, percent) {
|
|
574
521
|
const loadedStr = formatFileSize(loaded);
|
|
575
522
|
if (!total) return loadedStr;
|
|
576
523
|
return `${loadedStr} / ${formatFileSize(total)} (${Math.round(percent)}%)`;
|
|
577
524
|
}
|
|
578
|
-
|
|
579
|
-
// src/helpers/format-speed.ts
|
|
580
525
|
function formatSpeed(bytesPerSecond) {
|
|
581
526
|
return `${formatFileSize(bytesPerSecond)}/s`;
|
|
582
527
|
}
|
|
@@ -593,33 +538,6 @@ function formatEta(remainingBytes, bytesPerSecond) {
|
|
|
593
538
|
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
594
539
|
}
|
|
595
540
|
|
|
596
|
-
// src/helpers/build-object-key.ts
|
|
597
|
-
function buildObjectKey(...parts) {
|
|
598
|
-
return parts.map((p) => p.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// src/helpers/get-file-extension.ts
|
|
602
|
-
function getFileExtension(filename) {
|
|
603
|
-
const dotIndex = filename.lastIndexOf(".");
|
|
604
|
-
if (dotIndex < 1) return "";
|
|
605
|
-
return filename.slice(dotIndex + 1).toLowerCase();
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// src/helpers/truncate-filename.ts
|
|
609
|
-
function truncateFilename(name, maxChars = 26) {
|
|
610
|
-
if (name.length <= maxChars) return name;
|
|
611
|
-
const dotIndex = name.lastIndexOf(".");
|
|
612
|
-
if (dotIndex <= 0) {
|
|
613
|
-
return name.slice(0, maxChars - 1) + "\u2026";
|
|
614
|
-
}
|
|
615
|
-
const ext = name.slice(dotIndex);
|
|
616
|
-
const available = maxChars - ext.length - 1;
|
|
617
|
-
if (available <= 0) {
|
|
618
|
-
return name.slice(0, maxChars - 1) + "\u2026";
|
|
619
|
-
}
|
|
620
|
-
return name.slice(0, available) + "\u2026 " + ext;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
541
|
// src/helpers/speed-tracker.ts
|
|
624
542
|
function createSpeedTracker(windowMs = 3e3) {
|
|
625
543
|
const samples = [];
|
|
@@ -1270,10 +1188,7 @@ function useFetchDownload(options) {
|
|
|
1270
1188
|
);
|
|
1271
1189
|
}
|
|
1272
1190
|
const contentLength = Number(res.headers.get("content-length") || 0);
|
|
1273
|
-
const name = downloadName ??
|
|
1274
|
-
res.headers.get("content-disposition"),
|
|
1275
|
-
fallback
|
|
1276
|
-
);
|
|
1191
|
+
const name = downloadName ?? parseFileName(res.headers.get("content-disposition")) ?? fallback;
|
|
1277
1192
|
setState((s) => ({
|
|
1278
1193
|
...s,
|
|
1279
1194
|
fileName: name,
|
|
@@ -1413,6 +1328,6 @@ function useDelete(options) {
|
|
|
1413
1328
|
};
|
|
1414
1329
|
}
|
|
1415
1330
|
|
|
1416
|
-
export { S3Context, S3Provider, S3UploadError,
|
|
1331
|
+
export { S3Context, S3Provider, S3UploadError, createLocalStorageStore, createMemoryStore, createS3Client, createSpeedTracker, formatEta, formatSpeed, formatUploadProgress, uploadFile, uploadFiles, useDelete, useDownload, useFetchDownload, useMultiUpload, useMultiUploadControls, useS3Client, useUpload, useUploadControls };
|
|
1417
1332
|
//# sourceMappingURL=index.js.map
|
|
1418
1333
|
//# sourceMappingURL=index.js.map
|