@wlindabla/file_uploader 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +51 -20
  2. package/dist/cjs/cache/index.d.ts +198 -0
  3. package/dist/cjs/cache/index.js +318 -0
  4. package/dist/cjs/cache/index.js.map +1 -0
  5. package/dist/cjs/core/index.d.ts +267 -0
  6. package/dist/cjs/core/index.js +753 -0
  7. package/dist/cjs/core/index.js.map +1 -0
  8. package/dist/cjs/events/chunk/index.d.ts +27 -0
  9. package/dist/cjs/events/chunk/index.js +70 -0
  10. package/dist/cjs/events/chunk/index.js.map +1 -0
  11. package/dist/cjs/events/complete/index.d.ts +63 -0
  12. package/dist/cjs/events/complete/index.js +152 -0
  13. package/dist/cjs/events/complete/index.js.map +1 -0
  14. package/dist/cjs/events/index.d.ts +94 -0
  15. package/dist/cjs/events/index.js +85 -0
  16. package/dist/cjs/events/index.js.map +1 -0
  17. package/dist/cjs/events/initialize/index.d.ts +45 -0
  18. package/dist/cjs/events/initialize/index.js +105 -0
  19. package/dist/cjs/events/initialize/index.js.map +1 -0
  20. package/dist/cjs/events/state/index.d.ts +67 -0
  21. package/dist/cjs/events/state/index.js +145 -0
  22. package/dist/cjs/events/state/index.js.map +1 -0
  23. package/dist/cjs/exceptions/index.d.ts +84 -0
  24. package/dist/{exceptions → cjs/exceptions}/index.js +38 -18
  25. package/dist/cjs/exceptions/index.js.map +1 -0
  26. package/dist/cjs/index.d.ts +13 -0
  27. package/dist/cjs/index.js +33 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/cjs/subscribers/index.d.ts +33 -0
  30. package/dist/cjs/subscribers/index.js +187 -0
  31. package/dist/cjs/subscribers/index.js.map +1 -0
  32. package/dist/cjs/types/index.d.ts +110 -0
  33. package/dist/cjs/types/index.js +53 -0
  34. package/dist/cjs/types/index.js.map +1 -0
  35. package/dist/cjs/utils/index.d.ts +72 -0
  36. package/dist/cjs/utils/index.js +208 -0
  37. package/dist/cjs/utils/index.js.map +1 -0
  38. package/dist/esm/cache/index.d.mts +198 -0
  39. package/dist/esm/cache/index.js +4 -0
  40. package/dist/{index.js.map → esm/cache/index.js.map} +1 -1
  41. package/dist/{subscribers/index.js → esm/chunk-332NNKOW.js} +36 -34
  42. package/dist/esm/chunk-332NNKOW.js.map +1 -0
  43. package/dist/{events/state/index.js → esm/chunk-6225YMFE.js} +38 -20
  44. package/dist/esm/chunk-6225YMFE.js.map +1 -0
  45. package/dist/{core/index.js → esm/chunk-6DIKDA6J.js} +226 -227
  46. package/dist/esm/chunk-6DIKDA6J.js.map +1 -0
  47. package/dist/esm/chunk-7QVYU63E.js +6 -0
  48. package/dist/esm/chunk-7QVYU63E.js.map +1 -0
  49. package/dist/{events/initialize/index.js → esm/chunk-DN5B6PRW.js} +25 -19
  50. package/dist/esm/chunk-DN5B6PRW.js.map +1 -0
  51. package/dist/{events/index.js → esm/chunk-JDL3U4OX.js} +7 -37
  52. package/dist/esm/chunk-JDL3U4OX.js.map +1 -0
  53. package/dist/{events/chunk/index.js → esm/chunk-LD2DWZRJ.js} +14 -11
  54. package/dist/esm/chunk-LD2DWZRJ.js.map +1 -0
  55. package/dist/{events/complete/index.js → esm/chunk-LTYMA4U4.js} +25 -19
  56. package/dist/{events/complete/index.js.map → esm/chunk-LTYMA4U4.js.map} +1 -1
  57. package/dist/{utils/index.js → esm/chunk-MFYC4PBP.js} +15 -22
  58. package/dist/esm/chunk-MFYC4PBP.js.map +1 -0
  59. package/dist/esm/chunk-NXYS73I4.js +125 -0
  60. package/dist/esm/chunk-NXYS73I4.js.map +1 -0
  61. package/dist/{cache/index.js → esm/chunk-PFALORWQ.js} +10 -11
  62. package/dist/esm/chunk-PFALORWQ.js.map +1 -0
  63. package/dist/{types/index.js → esm/chunk-X757PBC5.js} +5 -7
  64. package/dist/esm/chunk-X757PBC5.js.map +1 -0
  65. package/dist/esm/core/index.d.mts +267 -0
  66. package/dist/esm/core/index.js +12 -0
  67. package/dist/esm/core/index.js.map +1 -0
  68. package/dist/esm/events/chunk/index.d.mts +27 -0
  69. package/dist/esm/events/chunk/index.js +4 -0
  70. package/dist/esm/events/chunk/index.js.map +1 -0
  71. package/dist/esm/events/complete/index.d.mts +63 -0
  72. package/dist/esm/events/complete/index.js +4 -0
  73. package/dist/esm/events/complete/index.js.map +1 -0
  74. package/dist/esm/events/index.d.mts +94 -0
  75. package/dist/esm/events/index.js +8 -0
  76. package/dist/esm/events/index.js.map +1 -0
  77. package/dist/esm/events/initialize/index.d.mts +45 -0
  78. package/dist/esm/events/initialize/index.js +4 -0
  79. package/dist/esm/events/initialize/index.js.map +1 -0
  80. package/dist/esm/events/state/index.d.mts +67 -0
  81. package/dist/esm/events/state/index.js +4 -0
  82. package/dist/esm/events/state/index.js.map +1 -0
  83. package/dist/esm/exceptions/index.d.mts +84 -0
  84. package/dist/esm/exceptions/index.js +4 -0
  85. package/dist/esm/exceptions/index.js.map +1 -0
  86. package/dist/esm/index.d.mts +13 -0
  87. package/dist/esm/index.js +14 -0
  88. package/dist/esm/index.js.map +1 -0
  89. package/dist/esm/subscribers/index.d.mts +33 -0
  90. package/dist/esm/subscribers/index.js +10 -0
  91. package/dist/esm/subscribers/index.js.map +1 -0
  92. package/dist/esm/types/index.d.mts +110 -0
  93. package/dist/esm/types/index.js +4 -0
  94. package/dist/esm/types/index.js.map +1 -0
  95. package/dist/esm/utils/index.d.mts +72 -0
  96. package/dist/esm/utils/index.js +5 -0
  97. package/dist/esm/utils/index.js.map +1 -0
  98. package/package.json +165 -14
  99. package/dist/cache/index.js.map +0 -1
  100. package/dist/core/index.js.map +0 -1
  101. package/dist/events/chunk/index.js.map +0 -1
  102. package/dist/events/index.js.map +0 -1
  103. package/dist/events/initialize/index.js.map +0 -1
  104. package/dist/events/state/index.js.map +0 -1
  105. package/dist/exceptions/index.js.map +0 -1
  106. package/dist/index.js +0 -49
  107. package/dist/subscribers/index.js.map +0 -1
  108. package/dist/types/index.js.map +0 -1
  109. package/dist/utils/index.js.map +0 -1
@@ -0,0 +1,267 @@
1
+ import { EventDispatcherInterface } from '@wlindabla/event_dispatcher';
2
+ import { UploadOptions, UploadEndpoints, UploadState, ResumeData } from '../types/index.js';
3
+ import { UploadResumeCacheInterface } from '../cache/index.js';
4
+ import '@wlindabla/http_client';
5
+
6
+ /**
7
+ * ChunkedFileUploader
8
+ *
9
+ * A production-ready, event-driven chunked file upload engine for Browser and Node.js.
10
+ *
11
+ * Designed and developed by **AGBOKOUDJO Franck** at
12
+ * **INTERNATIONALES WEB APPS & SERVICES**, this class provides a robust,
13
+ * framework-agnostic solution for uploading large files to a remote server
14
+ * by splitting them into smaller chunks and sending them in parallel with
15
+ * configurable concurrency control.
16
+ *
17
+ * ---
18
+ *
19
+ * ### How It Works
20
+ *
21
+ * The upload process follows a strict three-phase lifecycle:
22
+ *
23
+ * 1. **Initialization** — A session is opened with the server via the `init` endpoint.
24
+ * The file is identified by its name, size, type, and a SHA-256 hash of its
25
+ * first megabyte. The server returns a unique `mediaId` that identifies the session.
26
+ *
27
+ * 2. **Chunk Upload** — The file is sliced into fixed-size chunks and uploaded
28
+ * concurrently using `p-limit`. Each chunk carries its index, the total number
29
+ * of chunks, and the session `mediaId`. Failed chunks are retried automatically
30
+ * with exponential backoff up to `maxRetries` attempts.
31
+ *
32
+ * 3. **Finalization** — Once all chunks are successfully uploaded, the `finalize`
33
+ * endpoint is called to instruct the server to assemble the chunks into the
34
+ * final file.
35
+ *
36
+ * ---
37
+ *
38
+ * ### Event-Driven Architecture
39
+ *
40
+ * This class follows the **Symfony EventDispatcher pattern** via
41
+ * `@wlindabla/event_dispatcher`. Every meaningful moment in the upload
42
+ * lifecycle emits a typed event that your application can listen to:
43
+ *
44
+ * ```
45
+ * IDLE → INITIALIZING → UPLOADING → FINALIZING → COMPLETED
46
+ * ↓ ↓
47
+ * FAILED PAUSED ↔ UPLOADING
48
+ * ↓
49
+ * CANCELLED
50
+ * ```
51
+ *
52
+ * All event name constants are centralized in {@link HttpFileUploaderEvents}.
53
+ *
54
+ * ---
55
+ *
56
+ * ### Subscriber Registration (Required)
57
+ *
58
+ * Before calling `.upload()`, you **must** register the two built-in subscribers
59
+ * on your dispatcher. They handle the HTTP communication for the init and finalize phases:
60
+ *
61
+ * ```typescript
62
+ * // Browser
63
+ * const dispatcher = new BrowserEventDispatcher(document);
64
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
65
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
66
+ *
67
+ * // Node.js
68
+ * const dispatcher = new NodeEventDispatcher();
69
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
70
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
71
+ * ```
72
+ *
73
+ * ---
74
+ *
75
+ * ### Fluent Builder API
76
+ *
77
+ * The class exposes a fluent API to configure the upload before starting:
78
+ *
79
+ * ```typescript
80
+ * const uploader = new ChunkedFileUploader(dispatcher, cache, options);
81
+ *
82
+ * await uploader
83
+ * .withFile(file)
84
+ * .withEndpoints({
85
+ * init: 'https://api.example.com/upload/init',
86
+ * upload: 'https://api.example.com/upload/chunk',
87
+ * finalize: 'https://api.example.com/upload/finalize'
88
+ * })
89
+ * .upload();
90
+ * ```
91
+ *
92
+ * ---
93
+ *
94
+ * ### Resumable Uploads
95
+ *
96
+ * When `autoSave: true` is set in options, the upload progress is persisted
97
+ * after each successful chunk via the {@link UploadResumeCacheInterface}.
98
+ * A failed or interrupted upload can be resumed later:
99
+ *
100
+ * ```typescript
101
+ * const resumeData = await uploader.loadResumeData(file.name);
102
+ *
103
+ * if (resumeData) {
104
+ * await uploader
105
+ * .withFile(file)
106
+ * .withEndpoints(endpoints)
107
+ * .resumeUpload(resumeData);
108
+ * }
109
+ * ```
110
+ *
111
+ * ---
112
+ *
113
+ * ### Concurrency
114
+ *
115
+ * Multiple chunks can be uploaded simultaneously. The `concurrency` option
116
+ * controls how many parallel uploads are active at any given time.
117
+ * The default value is `3`, which provides a good balance between speed
118
+ * and server/network load:
119
+ *
120
+ * ```typescript
121
+ * // Upload 5 chunks in parallel
122
+ * new ChunkedFileUploader(dispatcher, cache, { concurrency: 5 });
123
+ * ```
124
+ *
125
+ * ---
126
+ *
127
+ * ### Pause, Resume and Cancel
128
+ *
129
+ * The upload can be paused, resumed, or cancelled at any time:
130
+ *
131
+ * ```typescript
132
+ * uploader.pause(); // Pauses after the current chunk finishes
133
+ * uploader.resume(); // Resumes from where it was paused
134
+ * uploader.cancel(); // Aborts immediately via AbortController
135
+ * ```
136
+ *
137
+ * ---
138
+ *
139
+ * ### Cache
140
+ *
141
+ * The library does **not** provide a built-in cache implementation to stay
142
+ * lightweight and environment-agnostic. You must implement
143
+ * {@link UploadResumeCacheInterface} with your preferred storage backend
144
+ * (localStorage, IndexedDB, Redis, filesystem, etc.).
145
+ *
146
+ * ---
147
+ *
148
+ * @author AGBOKOUDJO Franck <internationaleswebservices@gmail.com>
149
+ * @company INTERNATIONALES WEB APPS & SERVICES
150
+ * @phone +229 0167 25 18 86
151
+ * @linkedin https://www.linkedin.com/in/internationales-web-apps-services-120520193/
152
+ * @github https://github.com/Agbokoudjo/file_uploader
153
+ *
154
+ * @version 1.0.0
155
+ * @since 1.0.0
156
+ * @license MIT
157
+ *
158
+ * @see {@link HttpFileUploaderEvents} All event name constants
159
+ * @see {@link UploadResumeCacheInterface} Cache interface to implement
160
+ * @see {@link InitializeUploadSubscriber} Handles the init HTTP phase
161
+ * @see {@link FinalizeUploadSubscriber} Handles the finalize HTTP phase
162
+ * @see {@link UploadOptions} Full configuration reference
163
+ * @see {@link https://github.com/Agbokoudjo/file_uploader | GitHub Repository}
164
+ */
165
+ declare class ChunkedFileUploader {
166
+ private readonly _uploadEventDispatcher;
167
+ private readonly uploadResumeData;
168
+ private options;
169
+ private _file;
170
+ private _endpoints;
171
+ private isPaused;
172
+ private startTime;
173
+ private uploadedBytes;
174
+ private abortController;
175
+ private state;
176
+ private totalChunks;
177
+ private percentage;
178
+ private uploadedChunks;
179
+ private lastUploadedChunkIndex;
180
+ constructor(_uploadEventDispatcher: EventDispatcherInterface | undefined, //or new NodeJSEventDispatcher() if you have an environment NodeJs
181
+ uploadResumeData: UploadResumeCacheInterface, options: UploadOptions);
182
+ /**
183
+ * Starts the chunked file upload process.
184
+ *
185
+ * @throws {Error} If file or endpoints are not set
186
+ * @throws {InitializeUploadFailureException} If server initialization fails
187
+ * @throws {FileUploadChunkError} If a chunk fails after all retries
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const uploader = new ChunkedFileUploader(dispatcher, cache, options);
192
+ *
193
+ * uploader
194
+ * .withFile(file)
195
+ * .withEndpoints({ init, upload, finalize });
196
+ *
197
+ * await uploader.upload();
198
+ * ```
199
+ */
200
+ upload(): Promise<void>;
201
+ withFile(file: File): this;
202
+ withEndpoints(endpoints: UploadEndpoints): this;
203
+ private get endPointOptions();
204
+ private get file();
205
+ /**
206
+ * Upload all chunks with concurrency control using p-limit
207
+ *
208
+ * @param file - File to upload
209
+ * @param chunkSize - Size of each chunk
210
+ * @param fileId - Server file ID
211
+ * @param fileHash - File hash
212
+ * @param maxRetries - Max retry attempts per chunk
213
+ * @param concurrency - Number of concurrent uploads (default: 3)
214
+ */
215
+ private uploadChunksWithConcurrency;
216
+ /**
217
+ * Process a single chunk: slice, upload with retry, and save progress.
218
+ *
219
+ * @param file - The file being uploaded
220
+ * @param currentChunkIndex - Index of the current chunk (0-based)
221
+ * @param chunkSize - Size of each chunk in bytes
222
+ * @param fileId - Server-provided file/session ID
223
+ * @param fileHash - SHA-256 hash of the file
224
+ * @param maxRetries - Maximum number of retry attempts
225
+ *
226
+ * @throws {UploadCancelledException} If upload is cancelled
227
+ * @throws {FileUploadChunkError} If chunk upload fails after all retries
228
+ */
229
+ private processChunk;
230
+ private uploadChunkWithRetry;
231
+ private uploadChunk;
232
+ private createChunkAbortSignal;
233
+ private notifyProgress;
234
+ private sleep;
235
+ /**
236
+ * Save current upload progress to cache for resume capability
237
+ *
238
+ * @param fileId - Server-provided file/session ID
239
+ * @param chunkSize - Size of each chunk in bytes
240
+ * @returns Saved resume data
241
+ */
242
+ private saveResumeData;
243
+ private setState;
244
+ getState(): UploadState;
245
+ private handleUploadFailure;
246
+ cancel(): void;
247
+ pause(): void;
248
+ resume(): void;
249
+ /**
250
+ * Load previously saved resume data
251
+ *
252
+ * @param fileName - Name of the file to resume
253
+ * @returns Resume data or null if not found
254
+ */
255
+ loadResumeData(fileName: string): Promise<ResumeData | null>;
256
+ /**
257
+ * Resume upload from saved state
258
+ *
259
+ * @param resumeData - Previously saved resume data
260
+ * @returns Upload result
261
+ */
262
+ resumeUpload(resumeData: ResumeData): Promise<void>;
263
+ private updateProgress;
264
+ private finalizeUpload;
265
+ }
266
+
267
+ export { ChunkedFileUploader };