use-tus 0.7.3 → 0.8.1

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.
Files changed (35) hide show
  1. package/README.md +46 -8
  2. package/dist/TusClientProvider/store/tucClientActions.d.ts +7 -24
  3. package/dist/TusClientProvider/store/tusClientReducer.d.ts +2 -8
  4. package/dist/TusClientProvider/types.d.ts +3 -2
  5. package/dist/__stories__/Basic.stories.d.ts +1 -2
  6. package/dist/__stories__/CacheKey.stories.d.ts +6 -0
  7. package/dist/__stories__/DefaultOptions.stories.d.ts +6 -0
  8. package/dist/__tests__/createUpload.test.d.ts +1 -0
  9. package/dist/__tests__/utils/createMock.d.ts +1 -0
  10. package/dist/__tests__/utils/getDefaultOptions.d.ts +2 -2
  11. package/dist/__tests__/utils/mock.d.ts +3 -1
  12. package/dist/index.cjs.js +1 -1
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.esm.js +1 -1
  15. package/dist/index.js +243 -153
  16. package/dist/types.d.ts +41 -0
  17. package/dist/useTus/index.d.ts +0 -2
  18. package/dist/useTus/useTus.d.ts +2 -2
  19. package/dist/useTusClient/useTusClient.d.ts +1 -1
  20. package/dist/useTusStore/index.d.ts +1 -0
  21. package/dist/useTusStore/useTusStore.d.ts +2 -0
  22. package/dist/utils/core/createUpload.d.ts +16 -0
  23. package/dist/utils/core/index.d.ts +3 -0
  24. package/dist/utils/core/splitTusHooksUploadOptions.d.ts +6 -0
  25. package/dist/utils/index.d.ts +2 -0
  26. package/dist/utils/options/index.d.ts +2 -0
  27. package/dist/utils/options/mergeUseTusOptions.d.ts +8 -0
  28. package/dist/utils/options/useAutoAbort.d.ts +8 -0
  29. package/package.json +3 -3
  30. package/dist/useTus/types.d.ts +0 -14
  31. package/dist/useTus/useTusStore.d.ts +0 -2
  32. package/dist/useTus/utils/createUpload.d.ts +0 -7
  33. package/dist/useTus/utils/useAutoAbort.d.ts +0 -2
  34. package/dist/useTus/utils/useMergeTusOptions.d.ts +0 -6
  35. /package/dist/{useTus/utils → utils/core}/startOrResumeUpload.d.ts +0 -0
package/README.md CHANGED
@@ -86,7 +86,7 @@ const Uploader = () => {
86
86
  ### `useTus` hooks
87
87
 
88
88
  ```tsx
89
- const { upload, setUpload, isSuccess, isAborted, error, remove } = useTus({ autoAbort, autoStart, uploadOptions });
89
+ const { upload, setUpload, isSuccess, isAborted, isUploading, error, remove } = useTus({ autoAbort, autoStart, uploadOptions, Upload });
90
90
  ```
91
91
 
92
92
  `useTus` is a hooks that creates an object to perform Resumable file upload.
@@ -98,9 +98,33 @@ const { upload, setUpload, isSuccess, isAborted, error, remove } = useTus({ auto
98
98
  - `autoStart` (type: `boolean | undefined`) (default: false)
99
99
  - Whether or not to start upload the file after `setUpload` function.
100
100
 
101
- - `uploadOptions` (type: `UploadOptions | undefined`) (default: undefined)
101
+ - `uploadOptions` (type: `TusHooksUploadFnOptions | undefined`) (default: undefined)
102
102
  - Option to used by upload object that generated by that hooks.
103
- - For detail type information of `UploadOptions`, please see [here](https://github.com/tus/tus-js-client/blob/master/lib/index.d.ts#L22).
103
+
104
+ - `Upload` (type: `Upload | undefined`) (default: undefined)
105
+ - Option to specify customized own Upload class with this hooks.
106
+
107
+ #### About `uploadOptions`
108
+ This option extends the UploadOptions provided by `tus-js-client`, but it has been extended so that every callback can receive the upload instance as the final argument.
109
+
110
+ For detail type information of `UploadOptions`, please see [here](https://github.com/tus/tus-js-client/blob/master/lib/index.d.ts#L22).
111
+
112
+ e.g.
113
+
114
+ ```ts
115
+ setUplaod(file, {
116
+ onSuccess: (upload) => {
117
+ console.log(upload.url)
118
+ },
119
+ onError: (error, upload) => {
120
+ console.log(error)
121
+
122
+ setTimeout(() => {
123
+ upload.start()
124
+ }, 1000)
125
+ }
126
+ })
127
+ ```
104
128
 
105
129
  ### Returns
106
130
  - `upload` (type: `tus.Upload | undefined`)
@@ -108,7 +132,7 @@ const { upload, setUpload, isSuccess, isAborted, error, remove } = useTus({ auto
108
132
  - This value is undefined unless the `setUpload` function called.
109
133
  - For detail usage, please see [here](https://github.com/tus/tus-js-client#example)
110
134
 
111
- - `setUpload` (type: `(file: tus.Upload['file'], options?: UploadOptions) => void`)
135
+ - `setUpload` (type: `(file: tus.Upload['file'], options?: TusHooksUploadFnOptions) => void`)
112
136
  - Function to create an `Upload`.
113
137
  - The property specified in `uploadOptions` will be overwritten if property of `options` are speicified.
114
138
 
@@ -118,6 +142,9 @@ const { upload, setUpload, isSuccess, isAborted, error, remove } = useTus({ auto
118
142
  - `isAborted` (type: `boolean`)
119
143
  - Whether the upload was aborted or not.
120
144
 
145
+ - `isUploading` (type: `boolean`)
146
+ - Indicates if an uploading is in progress or not.
147
+
121
148
  - `error` (type: `Error | undefined`)
122
149
  - Error when upload fails.
123
150
 
@@ -127,7 +154,7 @@ const { upload, setUpload, isSuccess, isAborted, error, remove } = useTus({ auto
127
154
  ### `useTusStore` hooks
128
155
 
129
156
  ```tsx
130
- const { upload, setUpload, isSuccess, isAborted, error, remove } = useTusStore(cacheKey, { autoAbort, autoStart, uploadOptions });
157
+ const { upload, setUpload, isSuccess, isAborted, isUploading, error, remove } = useTusStore(cacheKey, { autoAbort, autoStart, uploadOptions, Upload });
131
158
  ```
132
159
 
133
160
  `useTusStore` is a hooks that creates an object for resumable file upload and stores it in a context.
@@ -146,9 +173,17 @@ This hooks is useful when you want to handle uploads across pages or components.
146
173
  - `autoStart` (type: `boolean | undefined`) (default: false)
147
174
  - Whether or not to start upload the file after `setUpload` function.
148
175
 
149
- - `uploadOptions` (type: `UploadOptions | undefined`) (default: undefined)
176
+ - `uploadOptions` (type: `TusHooksUploadFnOptions | undefined`) (default: undefined)
150
177
  - Option to used by upload object that generated by that hooks.
151
- - For detail type information of `UploadOptions`, please see [here](https://github.com/tus/tus-js-client/blob/master/lib/index.d.ts#L22).
178
+ - For detail type information of `TusHooksUploadFnOptions`, please see [here](https://github.com/tus/tus-js-client/blob/master/lib/index.d.ts#L22).
179
+
180
+ - `Upload` (type: `Upload | undefined`) (default: undefined)
181
+ - Option to specify customized own Upload class with this hooks.
182
+
183
+ ### Returns
184
+ - `upload` (type: `tus.Upload | undefined`)
185
+ - Object to be used when performing Resumable file upload.
186
+ - This value is undefined unless the `setUpload` function called.
152
187
 
153
188
  ### Returns
154
189
  - `upload` (type: `tus.Upload | undefined`)
@@ -166,6 +201,9 @@ This hooks is useful when you want to handle uploads across pages or components.
166
201
  - `isAborted` (type: `boolean`)
167
202
  - Whether the upload was aborted or not.
168
203
 
204
+ - `isUploading` (type: `boolean`)
205
+ - Indicates if an uploading is in progress or not.
206
+
169
207
  - `error` (type: `Error | undefined`)
170
208
  - Error when upload fails.
171
209
 
@@ -185,7 +223,7 @@ This hooks is useful when you want to handle uploads across pages or components.
185
223
  `TusClientProvider` is the provider that stores the `Upload` with `useTusStore` hooks.
186
224
 
187
225
  ### Props
188
- - `defaultOptions` (type: `(file: tus.Upload['file']) => UploadOptions | undefined`)
226
+ - `defaultOptions` (type: `(file: tus.Upload['file']) => TusHooksUploadFnOptions | undefined`)
189
227
  - An object containing the default options used when creating a new upload. [detail](https://github.com/tus/tus-js-client/blob/master/docs/api.md#tusdefaultoptions)
190
228
 
191
229
  ### `useTusClient`
@@ -1,35 +1,18 @@
1
- import type { Upload } from "tus-js-client";
2
1
  import { DefaultOptions } from "../types";
3
- export type TusClientActions = ReturnType<typeof insertUploadInstance | typeof removeUploadInstance | typeof updateSuccessUpload | typeof updateErrorUpload | typeof updateIsAbortedUpload | typeof resetClient | typeof updateDefaultOptions>;
4
- export declare const insertUploadInstance: (cacheKey: string, upload: Upload) => {
2
+ import { TusTruthlyContext } from "../../types";
3
+ export type TusClientActions = ReturnType<typeof insertUploadInstance | typeof removeUploadInstance | typeof updateUploadContext | typeof resetClient | typeof updateDefaultOptions>;
4
+ export declare const insertUploadInstance: (cacheKey: string, state: TusTruthlyContext) => {
5
5
  readonly type: "INSERT_UPLOAD_INSTANCE";
6
6
  readonly payload: {
7
7
  readonly cacheKey: string;
8
- readonly uploadState: {
9
- readonly upload: Upload;
10
- readonly isSuccess: false;
11
- readonly isAborted: false;
12
- };
8
+ readonly uploadState: TusTruthlyContext;
13
9
  };
14
10
  };
15
- export declare const updateSuccessUpload: (cacheKey: string) => {
16
- readonly type: "UPDATE_SUCCESS_UPLOAD";
11
+ export declare const updateUploadContext: (cacheKey: string, context: Partial<TusTruthlyContext>) => {
12
+ readonly type: "UPDATE_UPLOAD_CONTEXT";
17
13
  readonly payload: {
18
14
  readonly cacheKey: string;
19
- };
20
- };
21
- export declare const updateErrorUpload: (cacheKey: string, error?: Error) => {
22
- readonly type: "UPDATE_ERROR_UPLOAD";
23
- readonly payload: {
24
- readonly cacheKey: string;
25
- readonly error: Error | undefined;
26
- };
27
- };
28
- export declare const updateIsAbortedUpload: (cacheKey: string, isAborted: boolean) => {
29
- readonly type: "UPDATE_IS_ABORTED_UPLOAD";
30
- readonly payload: {
31
- readonly cacheKey: string;
32
- readonly isAborted: boolean;
15
+ readonly context: Partial<TusTruthlyContext>;
33
16
  };
34
17
  };
35
18
  export declare const removeUploadInstance: (cacheKey: string) => {
@@ -1,16 +1,10 @@
1
1
  import type { Reducer } from "react";
2
- import type { Upload } from "tus-js-client";
3
2
  import { DefaultOptions } from "../types";
4
3
  import { TusClientActions } from "./tucClientActions";
5
- export type UploadState = {
6
- upload: Upload | undefined;
7
- isSuccess: boolean;
8
- isAborted: boolean;
9
- error?: Error;
10
- };
4
+ import { TusTruthlyContext } from "../../types";
11
5
  export type TusClientState = {
12
6
  uploads: {
13
- [cacheKey: string]: UploadState | undefined;
7
+ [cacheKey: string]: TusTruthlyContext | undefined;
14
8
  };
15
9
  defaultOptions: DefaultOptions | undefined;
16
10
  };
@@ -1,2 +1,3 @@
1
- import { Upload, UploadOptions } from "tus-js-client";
2
- export type DefaultOptions = (file: Upload["file"]) => UploadOptions;
1
+ import { Upload } from "tus-js-client";
2
+ import { TusHooksUploadOptions } from "../types";
3
+ export type DefaultOptions = (file: Upload["file"]) => TusHooksUploadOptions;
@@ -1,7 +1,6 @@
1
1
  /// <reference types="react" />
2
- export declare const Basic: () => JSX.Element;
3
2
  declare const _default: {
4
3
  title: string;
5
- component: () => JSX.Element;
6
4
  };
7
5
  export default _default;
6
+ export declare const Basic: () => JSX.Element;
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ declare const _default: {
3
+ title: string;
4
+ };
5
+ export default _default;
6
+ export declare const CacheKey: () => JSX.Element;
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ declare const _default: {
3
+ title: string;
4
+ };
5
+ export default _default;
6
+ export declare const WithDefaultOptions: () => JSX.Element;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function createMock<T>(value?: unknown): T;
@@ -1,2 +1,2 @@
1
- import { Upload } from "tus-js-client";
2
- export declare const getDefaultOptions: () => Upload["options"];
1
+ import { TusHooksUploadOptions } from "../../types";
2
+ export declare const getDefaultOptions: () => TusHooksUploadOptions;
@@ -1,4 +1,6 @@
1
1
  /// <reference types="jest" />
2
+ import type { Upload } from "tus-js-client";
3
+ export declare const createUploadMock: (start: jest.Mock, abort: jest.Mock) => typeof Upload;
2
4
  export declare const createConsoleErrorMock: () => jest.SpyInstance<void, [message?: any, ...optionalParams: any[]], any>;
3
5
  export declare const insertEnvValue: (value: NodeJS.Process["env"]) => void;
4
- export declare const startOrResumeUploadMock: jest.SpyInstance<void, [upload: import("tus-js-client").Upload], any>;
6
+ export declare const startOrResumeUploadMock: jest.SpyInstance<void, [upload: Upload], any>;
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var a=require("react"),_=require("tus-js-client");const P=(e,s,t)=>{const o=new _.Upload(e,s),r=o.start.bind(o),c=o.abort.bind(o),n=()=>{r(),t(!1)},u=async()=>{c(),t(!0)};return o.start=n,o.abort=u,{upload:o,originalStart:r,originalAbort:c}},D=e=>{e.findPreviousUploads().then(s=>{s.length&&e.resumeFromPreviousUpload(s[0]),e.start()})},f=(e,s,t)=>{a.useEffect(()=>{const o=async()=>{!e||!s||await s()};return()=>{t&&o()}},[t,e])},R=Object.freeze({autoAbort:!0,autoStart:!1}),v=e=>a.useMemo(()=>({...R,...e||{}}),[e]),C=Object.freeze({upload:void 0,isSuccess:!1,isAborted:!1,error:void 0,originalAbort:void 0}),I=e=>{const{autoAbort:s,autoStart:t,uploadOptions:o}=v(e),[r,c]=a.useState(C),n=a.useCallback(i=>{c(l=>({...l,...i}))},[]),u=a.useCallback((i,l={})=>{const A={...o,...l},T={...A,onSuccess:()=>{n({isSuccess:!0}),A?.onSuccess?.()},onError:p=>{n({error:p}),A?.onError?.(p)}},E=p=>{n({isAborted:p})},{upload:O,originalAbort:U}=P(i,T,E);t&&D(O),n({...C,upload:O,originalAbort:U})},[t,n,o]),S=a.useCallback(()=>{r?.originalAbort?.(),c(C)},[r]),d=a.useMemo(()=>({upload:r?.upload,isSuccess:r?.isSuccess??!1,error:r?.error,isAborted:r?.isAborted??!1,setUpload:u,remove:S}),[r,u,S]);return f(d.upload,r.originalAbort,s??!1),d},y={tusClientHasNotFounded:"No TusClient set, use TusClientProvider to set one",tusIsNotSupported:"This browser does not support uploads. Please use a modern browser instead."},N=a.createContext(void 0),h=a.createContext(void 0),m=()=>{const e=a.useContext(N);if(!e&&process.env.NODE_ENV!=="production")throw new Error(y.tusClientHasNotFounded);return a.useMemo(()=>e,[e])},L=()=>{const e=a.useContext(h);if(!e&&process.env.NODE_ENV!=="production")throw new Error(y.tusClientHasNotFounded);return a.useMemo(()=>e,[e])},M=(e,s)=>({type:"INSERT_UPLOAD_INSTANCE",payload:{cacheKey:e,uploadState:{upload:s,isSuccess:!1,isAborted:!1}}}),K=e=>({type:"UPDATE_SUCCESS_UPLOAD",payload:{cacheKey:e}}),g=(e,s)=>({type:"UPDATE_ERROR_UPLOAD",payload:{cacheKey:e,error:s}}),w=(e,s)=>({type:"UPDATE_IS_ABORTED_UPLOAD",payload:{cacheKey:e,isAborted:s}}),x=e=>({type:"REMOVE_UPLOAD_INSTANCE",payload:{cacheKey:e}}),F=e=>({type:"UPDATE_DEFAULT_OPTIONS",payload:{defaultOptions:e}}),k=(e,s)=>{const{autoAbort:t,autoStart:o,uploadOptions:r}=v(s),{defaultOptions:c,uploads:n}=m(),u=L(),S=a.useCallback((A,T={})=>{const E={...c?.(A),...r,...T},O={...E,onSuccess:()=>{u(K(e)),E?.onSuccess?.()},onError:b=>{u(g(e,b)),E?.onError?.(b)}},U=b=>{u(w(e,b))},{upload:p}=P(A,O,U);o&&D(p),u(M(e,p))},[o,e,c,u,r]),d=a.useMemo(()=>n[e],[e,n]),i=a.useCallback(()=>{d?.upload?.abort(),u(x(e))},[d,u,e]),l=a.useMemo(()=>({upload:d?.upload,isSuccess:d?.isSuccess??!1,error:d?.error,isAborted:d?.isAborted??!1,setUpload:S,remove:i}),[d,S,i]);return f(l.upload,l.upload?.abort,t??!1),l},V=(e,s)=>{switch(s.type){case"INSERT_UPLOAD_INSTANCE":{const{cacheKey:t,uploadState:o}=s.payload;return{...e,uploads:{...e.uploads,[t]:o}}}case"UPDATE_SUCCESS_UPLOAD":{const{cacheKey:t}=s.payload,o=e.uploads[t];return o?{...e,uploads:{...e.uploads,[t]:{...o||{},isSuccess:!0}}}:e}case"UPDATE_ERROR_UPLOAD":{const{cacheKey:t,error:o}=s.payload,r=e.uploads[t];return r?{...e,uploads:{...e.uploads,[t]:{...r,error:o}}}:e}case"UPDATE_IS_ABORTED_UPLOAD":{const{cacheKey:t,isAborted:o}=s.payload,r=e.uploads[t];return r?{...e,uploads:{...e.uploads,[t]:{...r,isAborted:o}}}:e}case"REMOVE_UPLOAD_INSTANCE":{const{cacheKey:t}=s.payload,o=e.uploads;return delete o[t],{...e,uploads:o}}case"UPDATE_DEFAULT_OPTIONS":{const{defaultOptions:t}=s.payload;return{...e,defaultOptions:t}}default:return e}},j={uploads:{},defaultOptions:void 0},H=({defaultOptions:e,children:s})=>{const[t,o]=a.useReducer(V,{...j,defaultOptions:e});a.useEffect(()=>{_.isSupported||process.env.NODE_ENV==="production"||console.error(y.tusIsNotSupported)},[]),a.useEffect(()=>{t.defaultOptions!==e&&o(F(e))},[e,t.defaultOptions]);const r=a.createElement(h.Provider,{value:o},s);return a.createElement(N.Provider,{value:t},r)};exports.TusClientProvider=H,exports.useTus=I,exports.useTusStore=k;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var r=require("react"),h=require("tus-js-client");const V=Object.freeze({autoAbort:!0,autoStart:!1,uploadOptions:void 0,Upload:h.Upload}),D=o=>({...V,...o}),m=({upload:o,abort:t,autoAbort:e})=>{r.useEffect(()=>{const s=async()=>{!o||!t||await t()};return()=>{e&&s()}},[e,o])},q=(o,t,e)=>{let s=o[e];const a=Object.getOwnPropertyDescriptor(o,e);Object.defineProperty(o,e,{get(){return a?.get?.()??s},set(n){a?.set?a.set.call(o,n):s=n,t(this)}})},I=({Upload:o,file:t,uploadOptions:e,uploadFnOptions:s,onChange:a,onStart:n,onAbort:d})=>{const u=new o(t,e),i=u.start.bind(u),l=u.abort.bind(u),U=()=>{i(),n()},E=async()=>{l(),d()};return u.start=U,u.abort=E,q(u,a,"url"),Object.entries(s).forEach(([A,p])=>{if(typeof p!="function")return;const c=(...O)=>p(...O,u);u.options[A]=c}),{upload:u,originalStart:i,originalAbort:l}};function R(o){const t=Object.entries(o).reduce((s,[a,n])=>({...s,[a]:typeof n!="function"?n:void 0}),{}),e=Object.entries(o).reduce((s,[a,n])=>({...s,[a]:typeof n=="function"?n:void 0}),{});return{uploadOptions:t,uploadFnOptions:e}}const F=o=>{o.findPreviousUploads().then(t=>{t.length&&o.resumeFromPreviousUpload(t[0]),o.start()})},w=Object.freeze({upload:void 0,isSuccess:!1,isAborted:!1,isUploading:!1,error:void 0}),z=(o={})=>{const{autoAbort:t,autoStart:e,uploadOptions:s,Upload:a}=D(o),[n,d]=r.useState(w),[u,i]=r.useState({originalAbort:void 0}),l=p=>{d(c=>c.upload===void 0?c:{...c,...p})},U=r.useCallback((p,c={})=>{const O={...s,...c};function T(){l({isSuccess:!0,isUploading:!1}),O?.onSuccess?.(b)}const v={...O,onSuccess:T,onError:f=>{l({error:f,isUploading:!1}),O?.onError?.(f,b)}},g=f=>{d(K=>({...K,upload:f}))},y=()=>{l({isUploading:!0,isAborted:!1})},N=()=>{l({isUploading:!1,isAborted:!0})},{uploadOptions:_,uploadFnOptions:S}=R(v),{upload:b,originalAbort:H}=I({Upload:a,file:p,uploadOptions:_,uploadFnOptions:S,onChange:g,onStart:y,onAbort:N});e&&F(b),d({upload:b,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1}),i({originalAbort:H})},[a,e,s]),E=r.useCallback(()=>{u?.originalAbort?.(),d(w),i({originalAbort:void 0})},[u]),A={...n,setUpload:U,remove:E};return m({upload:A.upload,abort:u.originalAbort,autoAbort:t??!1}),A},P={tusClientHasNotFounded:"No TusClient set, use TusClientProvider to set one",tusIsNotSupported:"This browser does not support uploads. Please use a modern browser instead."},x=r.createContext(void 0),L=r.createContext(void 0),k=()=>{const o=r.useContext(x);if(!o&&process.env.NODE_ENV!=="production")throw new Error(P.tusClientHasNotFounded);return r.useMemo(()=>o,[o])},j=()=>{const o=r.useContext(L);if(!o&&process.env.NODE_ENV!=="production")throw new Error(P.tusClientHasNotFounded);return r.useMemo(()=>o,[o])},X=(o,t)=>({type:"INSERT_UPLOAD_INSTANCE",payload:{cacheKey:o,uploadState:t}}),C=(o,t)=>({type:"UPDATE_UPLOAD_CONTEXT",payload:{cacheKey:o,context:t}}),M=o=>({type:"REMOVE_UPLOAD_INSTANCE",payload:{cacheKey:o}}),G=()=>({type:"RESET_CLIENT"}),J=o=>({type:"UPDATE_DEFAULT_OPTIONS",payload:{defaultOptions:o}}),B=(o,t={})=>{const{autoAbort:e,autoStart:s,uploadOptions:a,Upload:n}=D(t),{defaultOptions:d,uploads:u}=k(),i=j(),l=r.useCallback((p,c={})=>{const O={...d?.(p),...a,...c},T={...O,onSuccess:()=>{i(C(o,{isSuccess:!0,isUploading:!1})),O?.onSuccess?.(S)},onError:b=>{i(C(o,{error:b,isUploading:!1})),O?.onError?.(b,S)}},v=b=>{i(C(o,{upload:b}))},g=()=>{i(C(o,{isAborted:!1,isUploading:!0}))},y=()=>{i(C(o,{isAborted:!0,isUploading:!1}))},{uploadOptions:N,uploadFnOptions:_}=R(T),{upload:S}=I({Upload:n,file:p,uploadOptions:N,uploadFnOptions:_,onChange:v,onStart:g,onAbort:y});s&&F(S),i(X(o,{upload:S,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1}))},[n,s,o,d,a,i]),U=r.useMemo(()=>u[o],[o,u]),E=r.useCallback(()=>{U?.upload?.abort(),i(M(o))},[U,i,o]),A=U?{...U,setUpload:l,remove:E}:{upload:void 0,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1,setUpload:l,remove:E};return m({upload:A.upload,abort:A.upload?.abort,autoAbort:e??!1}),A},Q=()=>{const o=k(),t=j(),e=o.uploads,s=r.useCallback(n=>{t(M(n))},[t]),a=r.useCallback(()=>t(G()),[t]);return{state:e,removeUpload:s,reset:a}},W=(o,t)=>{switch(t.type){case"INSERT_UPLOAD_INSTANCE":{const{cacheKey:e,uploadState:s}=t.payload;return{...o,uploads:{...o.uploads,[e]:s}}}case"UPDATE_UPLOAD_CONTEXT":{const{cacheKey:e,context:s}=t.payload,a=o.uploads[e];return a?{...o,uploads:{...o.uploads,[e]:{...a,...s}}}:o}case"REMOVE_UPLOAD_INSTANCE":{const{cacheKey:e}=t.payload,s=o.uploads;return delete s[e],{...o,uploads:s}}case"UPDATE_DEFAULT_OPTIONS":{const{defaultOptions:e}=t.payload;return{...o,defaultOptions:e}}case"RESET_CLIENT":return{...o,uploads:{}};default:return o}},Y={uploads:{},defaultOptions:void 0},Z=({defaultOptions:o,children:t})=>{const[e,s]=r.useReducer(W,{...Y,defaultOptions:o});r.useEffect(()=>{h.isSupported||process.env.NODE_ENV==="production"||console.error(P.tusIsNotSupported)},[]),r.useEffect(()=>{e.defaultOptions!==o&&s(J(o))},[o,e.defaultOptions]);const a=r.createElement(L.Provider,{value:s},t);return r.createElement(x.Provider,{value:e},a)};exports.TusClientProvider=Z,exports.useTus=z,exports.useTusClient=Q,exports.useTusStore=B;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from "./useTus";
2
+ export * from "./useTusStore";
3
+ export * from "./useTusClient";
2
4
  export * from "./TusClientProvider";
3
- export type { Upload, UploadOptions, UrlStorage, PreviousUpload, FileReader, FileSource, SliceResult, HttpStack, HttpRequest, HttpResponse, } from "tus-js-client";
5
+ export type { TusHooksResult, TusHooksOptions, TusHooksUploadFnOptions, TusHooksUploadOptions, } from "./types";
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- import{useEffect as _,useMemo as O,useState as w,useCallback as T,createContext as N,useContext as f,useReducer as F,createElement as h}from"react";import{Upload as x,isSupported as M}from"tus-js-client";const R=(o,s,t)=>{const e=new x(o,s),r=e.start.bind(e),A=e.abort.bind(e),l=()=>{r(),t(!1)},S=async()=>{A(),t(!0)};return e.start=l,e.abort=S,{upload:e,originalStart:r,originalAbort:A}},I=o=>{o.findPreviousUploads().then(s=>{s.length&&o.resumeFromPreviousUpload(s[0]),o.start()})},m=(o,s,t)=>{_(()=>{const e=async()=>{!o||!s||await s()};return()=>{t&&e()}},[t,o])},V=Object.freeze({autoAbort:!0,autoStart:!1}),L=o=>O(()=>({...V,...o||{}}),[o]),D=Object.freeze({upload:void 0,isSuccess:!1,isAborted:!1,error:void 0,originalAbort:void 0}),H=o=>{const{autoAbort:s,autoStart:t,uploadOptions:e}=L(o),[r,A]=w(D),l=T(a=>{A(i=>({...i,...a}))},[]),S=T((a,i={})=>{const u={...e,...i},d={...u,onSuccess:()=>{var p;l({isSuccess:!0}),(p=u==null?void 0:u.onSuccess)==null||p.call(u)},onError:p=>{var b;l({error:p}),(b=u==null?void 0:u.onError)==null||b.call(u,p)}},E=p=>{l({isAborted:p})},{upload:c,originalAbort:y}=R(a,d,E);t&&I(c),l({...D,upload:c,originalAbort:y})},[t,l,e]),n=T(()=>{var a;(a=r==null?void 0:r.originalAbort)==null||a.call(r),A(D)},[r]),U=O(()=>{var a,i;return{upload:r==null?void 0:r.upload,isSuccess:(a=r==null?void 0:r.isSuccess)!=null?a:!1,error:r==null?void 0:r.error,isAborted:(i=r==null?void 0:r.isAborted)!=null?i:!1,setUpload:S,remove:n}},[r,S,n]);return m(U.upload,r.originalAbort,s!=null?s:!1),U},P={tusClientHasNotFounded:"No TusClient set, use TusClientProvider to set one",tusIsNotSupported:"This browser does not support uploads. Please use a modern browser instead."},K=N(void 0),g=N(void 0),j=()=>{const o=f(K);if(!o&&process.env.NODE_ENV!=="production")throw new Error(P.tusClientHasNotFounded);return O(()=>o,[o])},z=()=>{const o=f(g);if(!o&&process.env.NODE_ENV!=="production")throw new Error(P.tusClientHasNotFounded);return O(()=>o,[o])},B=(o,s)=>({type:"INSERT_UPLOAD_INSTANCE",payload:{cacheKey:o,uploadState:{upload:s,isSuccess:!1,isAborted:!1}}}),k=o=>({type:"UPDATE_SUCCESS_UPLOAD",payload:{cacheKey:o}}),G=(o,s)=>({type:"UPDATE_ERROR_UPLOAD",payload:{cacheKey:o,error:s}}),$=(o,s)=>({type:"UPDATE_IS_ABORTED_UPLOAD",payload:{cacheKey:o,isAborted:s}}),q=o=>({type:"REMOVE_UPLOAD_INSTANCE",payload:{cacheKey:o}}),J=o=>({type:"UPDATE_DEFAULT_OPTIONS",payload:{defaultOptions:o}}),Q=(o,s)=>{var t;const{autoAbort:e,autoStart:r,uploadOptions:A}=L(s),{defaultOptions:l,uploads:S}=j(),n=z(),U=T((d,E={})=>{const c={...l==null?void 0:l(d),...A,...E},y={...c,onSuccess:()=>{var v;n(k(o)),(v=c==null?void 0:c.onSuccess)==null||v.call(c)},onError:v=>{var C;n(G(o,v)),(C=c==null?void 0:c.onError)==null||C.call(c,v)}},p=v=>{n($(o,v))},{upload:b}=R(d,y,p);r&&I(b),n(B(o,b))},[r,o,l,n,A]),a=O(()=>S[o],[o,S]),i=T(()=>{var d;(d=a==null?void 0:a.upload)==null||d.abort(),n(q(o))},[a,n,o]),u=O(()=>{var d,E;return{upload:a==null?void 0:a.upload,isSuccess:(d=a==null?void 0:a.isSuccess)!=null?d:!1,error:a==null?void 0:a.error,isAborted:(E=a==null?void 0:a.isAborted)!=null?E:!1,setUpload:U,remove:i}},[a,U,i]);return m(u.upload,(t=u.upload)==null?void 0:t.abort,e!=null?e:!1),u},W=(o,s)=>{switch(s.type){case"INSERT_UPLOAD_INSTANCE":{const{cacheKey:t,uploadState:e}=s.payload;return{...o,uploads:{...o.uploads,[t]:e}}}case"UPDATE_SUCCESS_UPLOAD":{const{cacheKey:t}=s.payload,e=o.uploads[t];return e?{...o,uploads:{...o.uploads,[t]:{...e||{},isSuccess:!0}}}:o}case"UPDATE_ERROR_UPLOAD":{const{cacheKey:t,error:e}=s.payload,r=o.uploads[t];return r?{...o,uploads:{...o.uploads,[t]:{...r,error:e}}}:o}case"UPDATE_IS_ABORTED_UPLOAD":{const{cacheKey:t,isAborted:e}=s.payload,r=o.uploads[t];return r?{...o,uploads:{...o.uploads,[t]:{...r,isAborted:e}}}:o}case"REMOVE_UPLOAD_INSTANCE":{const{cacheKey:t}=s.payload,e=o.uploads;return delete e[t],{...o,uploads:e}}case"UPDATE_DEFAULT_OPTIONS":{const{defaultOptions:t}=s.payload;return{...o,defaultOptions:t}}default:return o}},X={uploads:{},defaultOptions:void 0},Y=({defaultOptions:o,children:s})=>{const[t,e]=F(W,{...X,defaultOptions:o});_(()=>{M||process.env.NODE_ENV==="production"||console.error(P.tusIsNotSupported)},[]),_(()=>{t.defaultOptions!==o&&e(J(o))},[o,t.defaultOptions]);const r=h(g.Provider,{value:e},s);return h(K.Provider,{value:t},r)};export{Y as TusClientProvider,H as useTus,Q as useTusStore};
1
+ import{useEffect as D,useState as R,useCallback as T,createContext as F,useContext as w,useMemo as m,useReducer as B,createElement as L}from"react";import{Upload as J,isSupported as Q}from"tus-js-client";const W=Object.freeze({autoAbort:!0,autoStart:!1,uploadOptions:void 0,Upload:J}),x=o=>({...W,...o}),j=({upload:o,abort:t,autoAbort:e})=>{D(()=>{const s=async()=>{!o||!t||await t()};return()=>{e&&s()}},[e,o])},Y=(o,t,e)=>{let s=o[e];const n=Object.getOwnPropertyDescriptor(o,e);Object.defineProperty(o,e,{get(){var r,d;return(d=(r=n==null?void 0:n.get)==null?void 0:r.call(n))!=null?d:s},set(r){n!=null&&n.set?n.set.call(o,r):s=r,t(this)}})},K=({Upload:o,file:t,uploadOptions:e,uploadFnOptions:s,onChange:n,onStart:r,onAbort:d})=>{const a=new o(t,e),E=a.start.bind(a),l=a.abort.bind(a),S=()=>{E(),r()},O=async()=>{l(),d()};return a.start=S,a.abort=O,Y(a,n,"url"),Object.entries(s).forEach(([A,u])=>{if(typeof u!="function")return;const i=(...p)=>u(...p,a);a.options[A]=i}),{upload:a,originalStart:E,originalAbort:l}};function H(o){const t=Object.entries(o).reduce((s,[n,r])=>({...s,[n]:typeof r!="function"?r:void 0}),{}),e=Object.entries(o).reduce((s,[n,r])=>({...s,[n]:typeof r=="function"?r:void 0}),{});return{uploadOptions:t,uploadFnOptions:e}}const V=o=>{o.findPreviousUploads().then(t=>{t.length&&o.resumeFromPreviousUpload(t[0]),o.start()})},k=Object.freeze({upload:void 0,isSuccess:!1,isAborted:!1,isUploading:!1,error:void 0}),Z=(o={})=>{const{autoAbort:t,autoStart:e,uploadOptions:s,Upload:n}=x(o),[r,d]=R(k),[a,E]=R({originalAbort:void 0}),l=u=>{d(i=>i.upload===void 0?i:{...i,...u})},S=T((u,i={})=>{const p={...s,...i};function v(){var c;l({isSuccess:!0,isUploading:!1}),(c=p==null?void 0:p.onSuccess)==null||c.call(p,b)}const g={...p,onSuccess:v,onError:c=>{var C;l({error:c,isUploading:!1}),(C=p==null?void 0:p.onError)==null||C.call(p,c,b)}},y=c=>{d(C=>({...C,upload:c}))},N=()=>{l({isUploading:!0,isAborted:!1})},_=()=>{l({isUploading:!1,isAborted:!0})},{uploadOptions:h,uploadFnOptions:P}=H(g),{upload:b,originalAbort:U}=K({Upload:n,file:u,uploadOptions:h,uploadFnOptions:P,onChange:y,onStart:N,onAbort:_});e&&V(b),d({upload:b,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1}),E({originalAbort:U})},[n,e,s]),O=T(()=>{var u;(u=a==null?void 0:a.originalAbort)==null||u.call(a),d(k),E({originalAbort:void 0})},[a]),A={...r,setUpload:S,remove:O};return j({upload:A.upload,abort:a.originalAbort,autoAbort:t!=null?t:!1}),A},I={tusClientHasNotFounded:"No TusClient set, use TusClientProvider to set one",tusIsNotSupported:"This browser does not support uploads. Please use a modern browser instead."},M=F(void 0),z=F(void 0),X=()=>{const o=w(M);if(!o&&process.env.NODE_ENV!=="production")throw new Error(I.tusClientHasNotFounded);return m(()=>o,[o])},G=()=>{const o=w(z);if(!o&&process.env.NODE_ENV!=="production")throw new Error(I.tusClientHasNotFounded);return m(()=>o,[o])},$=(o,t)=>({type:"INSERT_UPLOAD_INSTANCE",payload:{cacheKey:o,uploadState:t}}),f=(o,t)=>({type:"UPDATE_UPLOAD_CONTEXT",payload:{cacheKey:o,context:t}}),q=o=>({type:"REMOVE_UPLOAD_INSTANCE",payload:{cacheKey:o}}),oo=()=>({type:"RESET_CLIENT"}),to=o=>({type:"UPDATE_DEFAULT_OPTIONS",payload:{defaultOptions:o}}),eo=(o,t={})=>{var e;const{autoAbort:s,autoStart:n,uploadOptions:r,Upload:d}=x(t),{defaultOptions:a,uploads:E}=X(),l=G(),S=T((i,p={})=>{const v={...a==null?void 0:a(i),...r,...p},g={...v,onSuccess:()=>{var U;l(f(o,{isSuccess:!0,isUploading:!1})),(U=v==null?void 0:v.onSuccess)==null||U.call(v,b)},onError:U=>{var c;l(f(o,{error:U,isUploading:!1})),(c=v==null?void 0:v.onError)==null||c.call(v,U,b)}},y=U=>{l(f(o,{upload:U}))},N=()=>{l(f(o,{isAborted:!1,isUploading:!0}))},_=()=>{l(f(o,{isAborted:!0,isUploading:!1}))},{uploadOptions:h,uploadFnOptions:P}=H(g),{upload:b}=K({Upload:d,file:i,uploadOptions:h,uploadFnOptions:P,onChange:y,onStart:N,onAbort:_});n&&V(b),l($(o,{upload:b,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1}))},[d,n,o,a,r,l]),O=m(()=>E[o],[o,E]),A=T(()=>{var i;(i=O==null?void 0:O.upload)==null||i.abort(),l(q(o))},[O,l,o]),u=O?{...O,setUpload:S,remove:A}:{upload:void 0,error:void 0,isSuccess:!1,isAborted:!1,isUploading:!1,setUpload:S,remove:A};return j({upload:u.upload,abort:(e=u.upload)==null?void 0:e.abort,autoAbort:s!=null?s:!1}),u},so=()=>{const o=X(),t=G(),e=o.uploads,s=T(r=>{t(q(r))},[t]),n=T(()=>t(oo()),[t]);return{state:e,removeUpload:s,reset:n}},no=(o,t)=>{switch(t.type){case"INSERT_UPLOAD_INSTANCE":{const{cacheKey:e,uploadState:s}=t.payload;return{...o,uploads:{...o.uploads,[e]:s}}}case"UPDATE_UPLOAD_CONTEXT":{const{cacheKey:e,context:s}=t.payload,n=o.uploads[e];return n?{...o,uploads:{...o.uploads,[e]:{...n,...s}}}:o}case"REMOVE_UPLOAD_INSTANCE":{const{cacheKey:e}=t.payload,s=o.uploads;return delete s[e],{...o,uploads:s}}case"UPDATE_DEFAULT_OPTIONS":{const{defaultOptions:e}=t.payload;return{...o,defaultOptions:e}}case"RESET_CLIENT":return{...o,uploads:{}};default:return o}},ro={uploads:{},defaultOptions:void 0},ao=({defaultOptions:o,children:t})=>{const[e,s]=B(no,{...ro,defaultOptions:o});D(()=>{Q||process.env.NODE_ENV==="production"||console.error(I.tusIsNotSupported)},[]),D(()=>{e.defaultOptions!==o&&s(to(o))},[o,e.defaultOptions]);const n=L(z.Provider,{value:s},t);return L(M.Provider,{value:e},n)};export{ao as TusClientProvider,Z as useTus,so as useTusClient,eo as useTusStore};
package/dist/index.js CHANGED
@@ -1,23 +1,93 @@
1
- import { useEffect, useMemo, useState, useCallback, createContext, useContext, useReducer, createElement } from 'react';
1
+ import { useEffect, useState, useCallback, createContext, useContext, useMemo, useReducer, createElement } from 'react';
2
2
  import { Upload, isSupported } from 'tus-js-client';
3
3
 
4
- const createUpload = (file, options, dispatchIsAborted) => {
5
- const upload = new Upload(file, options);
4
+ const defaultUseTusOptionsValue = Object.freeze({
5
+ autoAbort: true,
6
+ autoStart: false,
7
+ uploadOptions: undefined,
8
+ Upload,
9
+ });
10
+ const mergeUseTusOptions = (options) => ({
11
+ ...defaultUseTusOptionsValue,
12
+ ...options,
13
+ });
14
+
15
+ const useAutoAbort = ({ upload, abort, autoAbort, }) => {
16
+ useEffect(() => {
17
+ const abortUploading = async () => {
18
+ if (!upload || !abort) {
19
+ return;
20
+ }
21
+ await abort();
22
+ };
23
+ return () => {
24
+ if (!autoAbort) {
25
+ return;
26
+ }
27
+ abortUploading();
28
+ };
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, [autoAbort, upload]);
31
+ };
32
+
33
+ const bindOnChange = (upload, onChange, key) => {
34
+ let property = upload[key];
35
+ const originalUrlDescriptor = Object.getOwnPropertyDescriptor(upload, key);
36
+ Object.defineProperty(upload, key, {
37
+ get() {
38
+ return originalUrlDescriptor?.get?.() ?? property;
39
+ },
40
+ set(value) {
41
+ if (originalUrlDescriptor?.set) {
42
+ originalUrlDescriptor.set.call(upload, value);
43
+ }
44
+ else {
45
+ property = value;
46
+ }
47
+ onChange(this);
48
+ },
49
+ });
50
+ };
51
+ const createUpload = ({ Upload, file, uploadOptions, uploadFnOptions, onChange, onStart, onAbort, }) => {
52
+ const upload = new Upload(file, uploadOptions);
6
53
  const originalStart = upload.start.bind(upload);
7
54
  const originalAbort = upload.abort.bind(upload);
8
55
  const start = () => {
9
56
  originalStart();
10
- dispatchIsAborted(false);
57
+ onStart();
11
58
  };
12
59
  const abort = async () => {
13
60
  originalAbort();
14
- dispatchIsAborted(true);
61
+ onAbort();
15
62
  };
16
63
  upload.start = start;
17
64
  upload.abort = abort;
65
+ bindOnChange(upload, onChange, "url");
66
+ Object.entries(uploadFnOptions).forEach(([key, value]) => {
67
+ if (typeof value !== "function") {
68
+ return;
69
+ }
70
+ const bindedFn = (...args) => value(...args, upload);
71
+ upload.options[key] = bindedFn;
72
+ });
18
73
  return { upload, originalStart, originalAbort };
19
74
  };
20
75
 
76
+ function splitTusHooksUploadOptions(options) {
77
+ const uploadOptions = Object.entries(options).reduce((acc, [key, value]) => ({
78
+ ...acc,
79
+ [key]: typeof value !== "function" ? value : undefined,
80
+ }), {});
81
+ const uploadFnOptions = Object.entries(options).reduce((acc, [key, value]) => ({
82
+ ...acc,
83
+ [key]: typeof value === "function" ? value : undefined,
84
+ }), {});
85
+ return {
86
+ uploadOptions,
87
+ uploadFnOptions,
88
+ };
89
+ }
90
+
21
91
  const startOrResumeUpload = (upload) => {
22
92
  upload.findPreviousUploads().then((previousUploads) => {
23
93
  if (previousUploads.length) {
@@ -27,91 +97,97 @@ const startOrResumeUpload = (upload) => {
27
97
  });
28
98
  };
29
99
 
30
- const useAutoAbort = (upload, abort, autoAbort) => {
31
- useEffect(() => {
32
- const abortUploading = async () => {
33
- if (!upload || !abort) {
34
- return;
35
- }
36
- await abort();
37
- };
38
- return () => {
39
- if (!autoAbort) {
40
- return;
41
- }
42
- abortUploading();
43
- };
44
- // eslint-disable-next-line react-hooks/exhaustive-deps
45
- }, [autoAbort, upload]);
46
- };
47
-
48
- const defaultUseTusOptionsValue = Object.freeze({
49
- autoAbort: true,
50
- autoStart: false,
51
- });
52
- const useMergeTusOptions = (options) => useMemo(() => ({
53
- ...defaultUseTusOptionsValue,
54
- ...(options || {}),
55
- }), [options]);
56
-
57
- const initialUseTusState = Object.freeze({
100
+ const initialTusContext = Object.freeze({
58
101
  upload: undefined,
59
102
  isSuccess: false,
60
103
  isAborted: false,
104
+ isUploading: false,
61
105
  error: undefined,
62
- originalAbort: undefined,
63
106
  });
64
- const useTus = (baseOption) => {
65
- const { autoAbort, autoStart, uploadOptions } = useMergeTusOptions(baseOption);
66
- const [tusState, setTusState] = useState(initialUseTusState);
67
- const updateTusState = useCallback((newOptions) => {
68
- setTusState((tus) => ({
69
- ...tus,
70
- ...newOptions,
71
- }));
72
- }, []);
107
+ const useTus = (baseOption = {}) => {
108
+ const { autoAbort, autoStart, uploadOptions: baseUploadOptions, Upload, } = mergeUseTusOptions(baseOption);
109
+ const [tusContext, setTusContext] = useState(initialTusContext);
110
+ const [tusInternalState, setTusInternalState] = useState({
111
+ originalAbort: undefined,
112
+ });
113
+ const updateTusTruthlyContext = (context) => {
114
+ setTusContext((prev) => {
115
+ if (prev.upload === undefined) {
116
+ return prev; // TODO: Add appriopriate error handling
117
+ }
118
+ return { ...prev, ...context };
119
+ });
120
+ };
73
121
  const setUpload = useCallback((file, options = {}) => {
74
122
  const targetOptions = {
75
- ...uploadOptions,
123
+ ...baseUploadOptions,
76
124
  ...options,
77
125
  };
78
- const onSuccess = () => {
79
- updateTusState({ isSuccess: true });
80
- targetOptions?.onSuccess?.();
81
- };
126
+ function onSuccess() {
127
+ updateTusTruthlyContext({ isSuccess: true, isUploading: false });
128
+ targetOptions?.onSuccess?.(upload);
129
+ }
82
130
  const onError = (error) => {
83
- updateTusState({ error });
84
- targetOptions?.onError?.(error);
131
+ updateTusTruthlyContext({
132
+ error,
133
+ isUploading: false,
134
+ });
135
+ targetOptions?.onError?.(error, upload);
85
136
  };
86
137
  const mergedUploadOptions = {
87
138
  ...targetOptions,
88
139
  onSuccess,
89
140
  onError,
90
141
  };
91
- const dispatchIsAborted = (isAborted) => {
92
- updateTusState({ isAborted });
142
+ const onChange = (newUpload) => {
143
+ // For re-rendering when `upload` object is changed.
144
+ setTusContext((prev) => ({ ...prev, upload: newUpload }));
145
+ };
146
+ const onStart = () => {
147
+ updateTusTruthlyContext({ isUploading: true, isAborted: false });
93
148
  };
94
- const { upload, originalAbort } = createUpload(file, mergedUploadOptions, dispatchIsAborted);
149
+ const onAbort = () => {
150
+ updateTusTruthlyContext({ isUploading: false, isAborted: true });
151
+ };
152
+ const { uploadOptions, uploadFnOptions } = splitTusHooksUploadOptions(mergedUploadOptions);
153
+ const { upload, originalAbort } = createUpload({
154
+ Upload,
155
+ file,
156
+ uploadOptions,
157
+ uploadFnOptions,
158
+ onChange,
159
+ onStart,
160
+ onAbort,
161
+ });
95
162
  if (autoStart) {
96
163
  startOrResumeUpload(upload);
97
164
  }
98
- updateTusState({ ...initialUseTusState, upload, originalAbort });
99
- }, [autoStart, updateTusState, uploadOptions]);
165
+ setTusContext({
166
+ upload,
167
+ error: undefined,
168
+ isSuccess: false,
169
+ isAborted: false,
170
+ isUploading: false,
171
+ });
172
+ setTusInternalState({ originalAbort });
173
+ }, [Upload, autoStart, baseUploadOptions]);
100
174
  const remove = useCallback(() => {
101
175
  // `upload.abort` function will set `isAborted` state.
102
176
  // So call the original function for restore state.
103
- tusState?.originalAbort?.();
104
- setTusState(initialUseTusState);
105
- }, [tusState]);
106
- const tusResult = useMemo(() => ({
107
- upload: tusState?.upload,
108
- isSuccess: tusState?.isSuccess ?? false,
109
- error: tusState?.error,
110
- isAborted: tusState?.isAborted ?? false,
177
+ tusInternalState?.originalAbort?.();
178
+ setTusContext(initialTusContext);
179
+ setTusInternalState({ originalAbort: undefined });
180
+ }, [tusInternalState]);
181
+ const tusResult = {
182
+ ...tusContext,
111
183
  setUpload,
112
184
  remove,
113
- }), [tusState, setUpload, remove]);
114
- useAutoAbort(tusResult.upload, tusState.originalAbort, autoAbort ?? false);
185
+ };
186
+ useAutoAbort({
187
+ upload: tusResult.upload,
188
+ abort: tusInternalState.originalAbort,
189
+ autoAbort: autoAbort ?? false,
190
+ });
115
191
  return tusResult;
116
192
  };
117
193
 
@@ -137,35 +213,18 @@ const useTusClientDispatch = () => {
137
213
  return useMemo(() => tusClientDispatch, [tusClientDispatch]);
138
214
  };
139
215
 
140
- const insertUploadInstance = (cacheKey, upload) => ({
216
+ const insertUploadInstance = (cacheKey, state) => ({
141
217
  type: "INSERT_UPLOAD_INSTANCE",
142
218
  payload: {
143
219
  cacheKey,
144
- uploadState: {
145
- upload,
146
- isSuccess: false,
147
- isAborted: false,
148
- },
220
+ uploadState: state,
149
221
  },
150
222
  });
151
- const updateSuccessUpload = (cacheKey) => ({
152
- type: "UPDATE_SUCCESS_UPLOAD",
223
+ const updateUploadContext = (cacheKey, context) => ({
224
+ type: "UPDATE_UPLOAD_CONTEXT",
153
225
  payload: {
154
226
  cacheKey,
155
- },
156
- });
157
- const updateErrorUpload = (cacheKey, error) => ({
158
- type: "UPDATE_ERROR_UPLOAD",
159
- payload: {
160
- cacheKey,
161
- error,
162
- },
163
- });
164
- const updateIsAbortedUpload = (cacheKey, isAborted) => ({
165
- type: "UPDATE_IS_ABORTED_UPLOAD",
166
- payload: {
167
- cacheKey,
168
- isAborted,
227
+ context,
169
228
  },
170
229
  });
171
230
  const removeUploadInstance = (cacheKey) => ({
@@ -174,6 +233,9 @@ const removeUploadInstance = (cacheKey) => ({
174
233
  cacheKey,
175
234
  },
176
235
  });
236
+ const resetClient = () => ({
237
+ type: "RESET_CLIENT",
238
+ });
177
239
  const updateDefaultOptions = (defaultOptions) => ({
178
240
  type: "UPDATE_DEFAULT_OPTIONS",
179
241
  payload: {
@@ -181,55 +243,116 @@ const updateDefaultOptions = (defaultOptions) => ({
181
243
  },
182
244
  });
183
245
 
184
- const useTusStore = (cacheKey, baseOption) => {
185
- const { autoAbort, autoStart, uploadOptions } = useMergeTusOptions(baseOption);
246
+ const useTusStore = (cacheKey, baseOption = {}) => {
247
+ const { autoAbort, autoStart, uploadOptions: defaultUploadOptions, Upload, } = mergeUseTusOptions(baseOption);
186
248
  const { defaultOptions, uploads } = useTusClientState();
187
249
  const tusClientDispatch = useTusClientDispatch();
188
250
  const setUpload = useCallback((file, options = {}) => {
189
251
  const targetOptions = {
190
252
  ...defaultOptions?.(file),
191
- ...uploadOptions,
253
+ ...defaultUploadOptions,
192
254
  ...options,
193
255
  };
194
256
  const onSuccess = () => {
195
- tusClientDispatch(updateSuccessUpload(cacheKey));
196
- targetOptions?.onSuccess?.();
257
+ tusClientDispatch(updateUploadContext(cacheKey, { isSuccess: true, isUploading: false }));
258
+ targetOptions?.onSuccess?.(upload);
197
259
  };
198
260
  const onError = (error) => {
199
- tusClientDispatch(updateErrorUpload(cacheKey, error));
200
- targetOptions?.onError?.(error);
261
+ tusClientDispatch(updateUploadContext(cacheKey, { error, isUploading: false }));
262
+ targetOptions?.onError?.(error, upload);
201
263
  };
202
264
  const mergedUploadOptions = {
203
265
  ...targetOptions,
204
266
  onSuccess,
205
267
  onError,
206
268
  };
207
- const dispatchIsAborted = (isAborted) => {
208
- tusClientDispatch(updateIsAbortedUpload(cacheKey, isAborted));
269
+ const onChange = (newUpload) => {
270
+ // For re-rendering when `upload` object is changed.
271
+ tusClientDispatch(updateUploadContext(cacheKey, { upload: newUpload }));
209
272
  };
210
- const { upload } = createUpload(file, mergedUploadOptions, dispatchIsAborted);
273
+ const onStart = () => {
274
+ tusClientDispatch(updateUploadContext(cacheKey, {
275
+ isAborted: false,
276
+ isUploading: true,
277
+ }));
278
+ };
279
+ const onAbort = () => {
280
+ tusClientDispatch(updateUploadContext(cacheKey, {
281
+ isAborted: true,
282
+ isUploading: false,
283
+ }));
284
+ };
285
+ const { uploadOptions, uploadFnOptions } = splitTusHooksUploadOptions(mergedUploadOptions);
286
+ const { upload } = createUpload({
287
+ Upload,
288
+ file,
289
+ uploadOptions,
290
+ uploadFnOptions,
291
+ onChange,
292
+ onStart,
293
+ onAbort,
294
+ });
211
295
  if (autoStart) {
212
296
  startOrResumeUpload(upload);
213
297
  }
214
- tusClientDispatch(insertUploadInstance(cacheKey, upload));
215
- }, [autoStart, cacheKey, defaultOptions, tusClientDispatch, uploadOptions]);
298
+ tusClientDispatch(insertUploadInstance(cacheKey, {
299
+ upload,
300
+ error: undefined,
301
+ isSuccess: false,
302
+ isAborted: false,
303
+ isUploading: false,
304
+ }));
305
+ }, [
306
+ Upload,
307
+ autoStart,
308
+ cacheKey,
309
+ defaultOptions,
310
+ defaultUploadOptions,
311
+ tusClientDispatch,
312
+ ]);
216
313
  const targetTusState = useMemo(() => uploads[cacheKey], [cacheKey, uploads]);
217
314
  const remove = useCallback(() => {
218
315
  targetTusState?.upload?.abort();
219
316
  tusClientDispatch(removeUploadInstance(cacheKey));
220
317
  }, [targetTusState, tusClientDispatch, cacheKey]);
221
- const tusResult = useMemo(() => ({
222
- upload: targetTusState?.upload,
223
- isSuccess: targetTusState?.isSuccess ?? false,
224
- error: targetTusState?.error,
225
- isAborted: targetTusState?.isAborted ?? false,
226
- setUpload,
227
- remove,
228
- }), [targetTusState, setUpload, remove]);
229
- useAutoAbort(tusResult.upload, tusResult.upload?.abort, autoAbort ?? false);
318
+ const tusResult = targetTusState
319
+ ? {
320
+ ...targetTusState,
321
+ setUpload,
322
+ remove,
323
+ }
324
+ : {
325
+ upload: undefined,
326
+ error: undefined,
327
+ isSuccess: false,
328
+ isAborted: false,
329
+ isUploading: false,
330
+ setUpload,
331
+ remove,
332
+ };
333
+ useAutoAbort({
334
+ upload: tusResult.upload,
335
+ abort: tusResult.upload?.abort,
336
+ autoAbort: autoAbort ?? false,
337
+ });
230
338
  return tusResult;
231
339
  };
232
340
 
341
+ const useTusClient = () => {
342
+ const tusClientState = useTusClientState();
343
+ const tusClientDispatch = useTusClientDispatch();
344
+ const state = tusClientState.uploads;
345
+ const removeUpload = useCallback((cacheKey) => {
346
+ tusClientDispatch(removeUploadInstance(cacheKey));
347
+ }, [tusClientDispatch]);
348
+ const reset = useCallback(() => tusClientDispatch(resetClient()), [tusClientDispatch]);
349
+ return {
350
+ state,
351
+ removeUpload,
352
+ reset,
353
+ };
354
+ };
355
+
233
356
  const tusClientReducer = (state, actions) => {
234
357
  switch (actions.type) {
235
358
  case "INSERT_UPLOAD_INSTANCE": {
@@ -242,55 +365,15 @@ const tusClientReducer = (state, actions) => {
242
365
  },
243
366
  };
244
367
  }
245
- case "UPDATE_SUCCESS_UPLOAD": {
246
- const { cacheKey } = actions.payload;
247
- const target = state.uploads[cacheKey];
248
- if (!target) {
249
- return state;
250
- }
251
- return {
252
- ...state,
253
- uploads: {
254
- ...state.uploads,
255
- [cacheKey]: {
256
- ...(target || {}),
257
- isSuccess: true,
258
- },
259
- },
260
- };
261
- }
262
- case "UPDATE_ERROR_UPLOAD": {
263
- const { cacheKey, error } = actions.payload;
264
- const target = state.uploads[cacheKey];
265
- if (!target) {
266
- return state;
267
- }
268
- return {
269
- ...state,
270
- uploads: {
271
- ...state.uploads,
272
- [cacheKey]: {
273
- ...target,
274
- error,
275
- },
276
- },
277
- };
278
- }
279
- case "UPDATE_IS_ABORTED_UPLOAD": {
280
- const { cacheKey, isAborted } = actions.payload;
368
+ case "UPDATE_UPLOAD_CONTEXT": {
369
+ const { cacheKey, context } = actions.payload;
281
370
  const target = state.uploads[cacheKey];
282
371
  if (!target) {
283
372
  return state;
284
373
  }
285
374
  return {
286
375
  ...state,
287
- uploads: {
288
- ...state.uploads,
289
- [cacheKey]: {
290
- ...target,
291
- isAborted,
292
- },
293
- },
376
+ uploads: { ...state.uploads, [cacheKey]: { ...target, ...context } },
294
377
  };
295
378
  }
296
379
  case "REMOVE_UPLOAD_INSTANCE": {
@@ -309,8 +392,15 @@ const tusClientReducer = (state, actions) => {
309
392
  defaultOptions,
310
393
  };
311
394
  }
312
- default:
395
+ case "RESET_CLIENT": {
396
+ return {
397
+ ...state,
398
+ uploads: {},
399
+ };
400
+ }
401
+ default: {
313
402
  return state;
403
+ }
314
404
  }
315
405
  };
316
406
  const tusClientInitialState = {
@@ -344,4 +434,4 @@ const TusClientProvider = ({ defaultOptions, children, }) => {
344
434
  }, tusClientDispatchContextProviderElement);
345
435
  };
346
436
 
347
- export { TusClientProvider, useTus, useTusStore };
437
+ export { TusClientProvider, useTus, useTusClient, useTusStore };
@@ -0,0 +1,41 @@
1
+ import type { Upload, UploadOptions } from "tus-js-client";
2
+ export interface TusHooksOptions {
3
+ autoAbort?: boolean;
4
+ autoStart?: boolean;
5
+ uploadOptions?: TusHooksUploadOptions;
6
+ Upload?: typeof Upload;
7
+ }
8
+ export type Merge<T extends Record<PropertyKey, any>, R extends Record<PropertyKey, any>> = {
9
+ [P in keyof Omit<T, keyof R>]: T[P];
10
+ } & R;
11
+ type AddUploadParamater<F> = F extends (...args: infer A) => infer R ? (...args: [...A, ...[upload: Upload]]) => R : never;
12
+ type Callbacks<T> = {
13
+ [P in keyof T as P extends `on${string}` ? NonNullable<T[P]> extends Function ? P : never : never]: T[P];
14
+ };
15
+ export type TusHooksUploadFnOptions = {
16
+ [K in keyof Callbacks<UploadOptions>]: AddUploadParamater<Callbacks<UploadOptions>[K]>;
17
+ };
18
+ export type TusHooksUploadOptions = Merge<UploadOptions, TusHooksUploadFnOptions>;
19
+ export type UploadFile = Upload["file"];
20
+ export type SetUpload = (file: Upload["file"], options?: TusHooksUploadOptions) => void;
21
+ export type TusHooksResultFn = {
22
+ setUpload: SetUpload;
23
+ remove: () => void;
24
+ };
25
+ export type TusFalselyContext = {
26
+ upload: undefined;
27
+ error: undefined;
28
+ isSuccess: false;
29
+ isAborted: false;
30
+ isUploading: false;
31
+ };
32
+ export type TusTruthlyContext = {
33
+ upload: Upload;
34
+ error?: Error;
35
+ isSuccess: boolean;
36
+ isAborted: boolean;
37
+ isUploading: boolean;
38
+ };
39
+ export type TusContext = TusFalselyContext | TusTruthlyContext;
40
+ export type TusHooksResult = TusContext & TusHooksResultFn;
41
+ export {};
@@ -1,3 +1 @@
1
1
  export { useTus } from "./useTus";
2
- export { useTusStore } from "./useTusStore";
3
- export type { UseTusResult, UseTusOptions } from "./types";
@@ -1,2 +1,2 @@
1
- import { UseTusOptions, UseTusResult } from "./types";
2
- export declare const useTus: (baseOption?: UseTusOptions) => UseTusResult;
1
+ import { TusHooksOptions, TusHooksResult } from "../types";
2
+ export declare const useTus: (baseOption?: TusHooksOptions) => TusHooksResult;
@@ -1,6 +1,6 @@
1
1
  export declare const useTusClient: () => {
2
2
  state: {
3
- [cacheKey: string]: import("../TusClientProvider/store/tusClientReducer").UploadState | undefined;
3
+ [cacheKey: string]: import("../types").TusTruthlyContext | undefined;
4
4
  };
5
5
  removeUpload: (cacheKey: string) => void;
6
6
  reset: () => void;
@@ -0,0 +1 @@
1
+ export { useTusStore } from "./useTusStore";
@@ -0,0 +1,2 @@
1
+ import { TusHooksOptions, TusHooksResult } from "../types";
2
+ export declare const useTusStore: (cacheKey: string, baseOption?: TusHooksOptions) => TusHooksResult;
@@ -0,0 +1,16 @@
1
+ import { UploadOptions, type Upload as UploadType } from "tus-js-client";
2
+ import { TusHooksUploadFnOptions, UploadFile } from "../../types";
3
+ export type CreateUploadParams = {
4
+ Upload: typeof UploadType;
5
+ file: UploadFile;
6
+ uploadOptions: UploadOptions;
7
+ uploadFnOptions: TusHooksUploadFnOptions;
8
+ onChange: (upload: UploadType) => void;
9
+ onStart: () => void;
10
+ onAbort: () => void;
11
+ };
12
+ export declare const createUpload: ({ Upload, file, uploadOptions, uploadFnOptions, onChange, onStart, onAbort, }: CreateUploadParams) => {
13
+ upload: UploadType;
14
+ originalStart: () => void;
15
+ originalAbort: (shouldTerminate?: boolean | undefined) => Promise<void>;
16
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./createUpload";
2
+ export * from "./splitTusHooksUploadOptions";
3
+ export * from "./startOrResumeUpload";
@@ -0,0 +1,6 @@
1
+ import { UploadOptions } from "tus-js-client";
2
+ import { TusHooksUploadFnOptions, TusHooksUploadOptions } from "../../types";
3
+ export declare function splitTusHooksUploadOptions(options: TusHooksUploadOptions): {
4
+ uploadOptions: UploadOptions;
5
+ uploadFnOptions: TusHooksUploadFnOptions;
6
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./options";
2
+ export * from "./core";
@@ -0,0 +1,2 @@
1
+ export * from "./mergeUseTusOptions";
2
+ export * from "./useAutoAbort";
@@ -0,0 +1,8 @@
1
+ import { Upload } from "tus-js-client";
2
+ import { TusHooksOptions } from "../../types";
3
+ export declare const mergeUseTusOptions: (options: TusHooksOptions) => {
4
+ autoAbort: boolean;
5
+ autoStart: boolean;
6
+ uploadOptions: import("../../types").TusHooksUploadOptions | undefined;
7
+ Upload: typeof Upload;
8
+ };
@@ -0,0 +1,8 @@
1
+ import { Upload } from "tus-js-client";
2
+ type UseAutoAbortParams = {
3
+ upload: Upload | undefined;
4
+ abort: (() => Promise<void>) | undefined;
5
+ autoAbort: boolean;
6
+ };
7
+ export declare const useAutoAbort: ({ upload, abort, autoAbort, }: UseAutoAbortParams) => void;
8
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-tus",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "React hooks for resumable file uploads using tus-js-client",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -73,13 +73,13 @@
73
73
  "storybook": "^7.5.1",
74
74
  "tailwindcss": "npm:@tailwindcss/postcss7-compat",
75
75
  "ts-jest": "^29.1.1",
76
- "tus-js-client": "^2.2.0",
76
+ "tus-js-client": "^4.0.0",
77
77
  "typescript": "^5.2.2",
78
78
  "vite": "^4.5.0"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "react": ">=16.8",
82
- "tus-js-client": "^2.2.0"
82
+ "tus-js-client": ">=2.2.0"
83
83
  },
84
84
  "packageManager": "pnpm@8.8.0",
85
85
  "scripts": {
@@ -1,14 +0,0 @@
1
- import type { Upload, UploadOptions } from "tus-js-client";
2
- export interface UseTusOptions {
3
- autoAbort?: boolean;
4
- autoStart?: boolean;
5
- uploadOptions?: UploadOptions;
6
- }
7
- export interface UseTusResult {
8
- upload?: Upload;
9
- setUpload: (file: Upload["file"], options?: Upload["options"]) => void;
10
- isSuccess: boolean;
11
- isAborted: boolean;
12
- error?: Error;
13
- remove: () => void;
14
- }
@@ -1,2 +0,0 @@
1
- import { UseTusOptions, UseTusResult } from "./types";
2
- export declare const useTusStore: (cacheKey: string, baseOption?: UseTusOptions) => UseTusResult;
@@ -1,7 +0,0 @@
1
- import { Upload } from "tus-js-client";
2
- export type DispatchIsAborted = (isAborted: boolean) => void;
3
- export declare const createUpload: (file: Upload["file"], options: Upload["options"], dispatchIsAborted: DispatchIsAborted) => {
4
- upload: Upload;
5
- originalStart: () => void;
6
- originalAbort: (shouldTerminate?: boolean | undefined) => Promise<void>;
7
- };
@@ -1,2 +0,0 @@
1
- import { Upload } from "tus-js-client";
2
- export declare const useAutoAbort: (upload: Upload | undefined, abort: Upload["abort"] | undefined, autoAbort: boolean) => void;
@@ -1,6 +0,0 @@
1
- import { UseTusOptions } from "../types";
2
- export declare const useMergeTusOptions: (options: UseTusOptions | undefined) => {
3
- autoAbort?: boolean | undefined;
4
- autoStart?: boolean | undefined;
5
- uploadOptions?: import("tus-js-client").UploadOptions | undefined;
6
- };