@wlindabla/file_uploader 1.0.0 → 2.0.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 (110) hide show
  1. package/README.md +54 -28
  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 +271 -0
  6. package/dist/cjs/core/index.js +762 -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 +64 -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 +95 -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 +14 -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 +34 -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/{events/initialize/index.js → esm/chunk-3JTTZCSQ.js} +25 -19
  42. package/dist/esm/chunk-3JTTZCSQ.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/esm/chunk-7QVYU63E.js +6 -0
  46. package/dist/esm/chunk-7QVYU63E.js.map +1 -0
  47. package/dist/{events/complete/index.js → esm/chunk-BNMI7DW3.js} +25 -19
  48. package/dist/esm/chunk-BNMI7DW3.js.map +1 -0
  49. package/dist/{subscribers/index.js → esm/chunk-HYNJBWW5.js} +36 -34
  50. package/dist/esm/chunk-HYNJBWW5.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/{utils/index.js → esm/chunk-MFYC4PBP.js} +15 -22
  56. package/dist/esm/chunk-MFYC4PBP.js.map +1 -0
  57. package/dist/esm/chunk-NXYS73I4.js +125 -0
  58. package/dist/esm/chunk-NXYS73I4.js.map +1 -0
  59. package/dist/{cache/index.js → esm/chunk-PFALORWQ.js} +10 -11
  60. package/dist/esm/chunk-PFALORWQ.js.map +1 -0
  61. package/dist/{core/index.js → esm/chunk-TONVXBLH.js} +239 -231
  62. package/dist/esm/chunk-TONVXBLH.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 +271 -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 +64 -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 +95 -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 +14 -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 +34 -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/complete/index.js.map +0 -1
  103. package/dist/events/index.js.map +0 -1
  104. package/dist/events/initialize/index.js.map +0 -1
  105. package/dist/events/state/index.js.map +0 -1
  106. package/dist/exceptions/index.js.map +0 -1
  107. package/dist/index.js +0 -49
  108. package/dist/subscribers/index.js.map +0 -1
  109. package/dist/types/index.js.map +0 -1
  110. package/dist/utils/index.js.map +0 -1
@@ -1,180 +1,21 @@
1
- 'use strict';
1
+ import { ChunkUploadHttpErrorException, FileUploadChunkError } from './chunk-NXYS73I4.js';
2
+ import { FileUtils, createChunkFormData } from './chunk-MFYC4PBP.js';
3
+ import { HttpFileUploaderEvents } from './chunk-JDL3U4OX.js';
4
+ import { UploadChunkStartedEvent, ChunkUploadHttpErrorResponseEvent } from './chunk-LD2DWZRJ.js';
5
+ import { ResumeUploadEvent, FinalizeUploadEvent, UploadMediaCompleteEvent } from './chunk-BNMI7DW3.js';
6
+ import { InitializingUploadEvent } from './chunk-3JTTZCSQ.js';
7
+ import { UploadCancelledEvent, UploadProgressEvent, UploadStateChangedEvent, UploadPausedEvent, UploadResumedEvent } from './chunk-6225YMFE.js';
8
+ import { __name } from './chunk-7QVYU63E.js';
9
+ import { HttpFetchError, safeFetch } from '@wlindabla/http_client/core';
10
+ import pLimit from 'p-limit';
2
11
 
3
- var event_dispatcher = require('@wlindabla/event_dispatcher');
4
- var http_client = require('@wlindabla/http_client');
5
- var types = require('../types');
6
- var utils = require('../utils');
7
- var events = require('../events');
8
- var exceptions = require('../exceptions');
9
- var pLimit = require('p-limit');
10
-
11
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
-
13
- var pLimit__default = /*#__PURE__*/_interopDefault(pLimit);
14
-
15
- var __defProp = Object.defineProperty;
16
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
17
- /**
18
- * ChunkedFileUploader
19
- *
20
- * A production-ready, event-driven chunked file upload engine for Browser and Node.js.
21
- *
22
- * Designed and developed by **AGBOKOUDJO Franck** at
23
- * **INTERNATIONALES WEB APPS & SERVICES**, this class provides a robust,
24
- * framework-agnostic solution for uploading large files to a remote server
25
- * by splitting them into smaller chunks and sending them in parallel with
26
- * configurable concurrency control.
27
- *
28
- * ---
29
- *
30
- * ### How It Works
31
- *
32
- * The upload process follows a strict three-phase lifecycle:
33
- *
34
- * 1. **Initialization** — A session is opened with the server via the `init` endpoint.
35
- * The file is identified by its name, size, type, and a SHA-256 hash of its
36
- * first megabyte. The server returns a unique `mediaId` that identifies the session.
37
- *
38
- * 2. **Chunk Upload** — The file is sliced into fixed-size chunks and uploaded
39
- * concurrently using `p-limit`. Each chunk carries its index, the total number
40
- * of chunks, and the session `mediaId`. Failed chunks are retried automatically
41
- * with exponential backoff up to `maxRetries` attempts.
42
- *
43
- * 3. **Finalization** — Once all chunks are successfully uploaded, the `finalize`
44
- * endpoint is called to instruct the server to assemble the chunks into the
45
- * final file.
46
- *
47
- * ---
48
- *
49
- * ### Event-Driven Architecture
50
- *
51
- * This class follows the **Symfony EventDispatcher pattern** via
52
- * `@wlindabla/event_dispatcher`. Every meaningful moment in the upload
53
- * lifecycle emits a typed event that your application can listen to:
54
- *
55
- * ```
56
- * IDLE → INITIALIZING → UPLOADING → FINALIZING → COMPLETED
57
- * ↓ ↓
58
- * FAILED PAUSED ↔ UPLOADING
59
- * ↓
60
- * CANCELLED
61
- * ```
62
- *
63
- * All event name constants are centralized in {@link HttpFileUploaderEvents}.
64
- *
65
- * ---
66
- *
67
- * ### Subscriber Registration (Required)
68
- *
69
- * Before calling `.upload()`, you **must** register the two built-in subscribers
70
- * on your dispatcher. They handle the HTTP communication for the init and finalize phases:
71
- *
72
- * ```typescript
73
- * // Browser
74
- * const dispatcher = new BrowserEventDispatcher(document);
75
- * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
76
- * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
77
- *
78
- * // Node.js
79
- * const dispatcher = new NodeEventDispatcher();
80
- * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
81
- * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
82
- * ```
83
- *
84
- * ---
85
- *
86
- * ### Fluent Builder API
87
- *
88
- * The class exposes a fluent API to configure the upload before starting:
89
- *
90
- * ```typescript
91
- * const uploader = new ChunkedFileUploader(dispatcher, cache, options);
92
- *
93
- * await uploader
94
- * .withFile(file)
95
- * .withEndpoints({
96
- * init: 'https://api.example.com/upload/init',
97
- * upload: 'https://api.example.com/upload/chunk',
98
- * finalize: 'https://api.example.com/upload/finalize'
99
- * })
100
- * .upload();
101
- * ```
102
- *
103
- * ---
104
- *
105
- * ### Resumable Uploads
106
- *
107
- * When `autoSave: true` is set in options, the upload progress is persisted
108
- * after each successful chunk via the {@link UploadResumeCacheInterface}.
109
- * A failed or interrupted upload can be resumed later:
110
- *
111
- * ```typescript
112
- * const resumeData = await uploader.loadResumeData(file.name);
113
- *
114
- * if (resumeData) {
115
- * await uploader
116
- * .withFile(file)
117
- * .withEndpoints(endpoints)
118
- * .resumeUpload(resumeData);
119
- * }
120
- * ```
121
- *
122
- * ---
123
- *
124
- * ### Concurrency
125
- *
126
- * Multiple chunks can be uploaded simultaneously. The `concurrency` option
127
- * controls how many parallel uploads are active at any given time.
128
- * The default value is `3`, which provides a good balance between speed
129
- * and server/network load:
130
- *
131
- * ```typescript
132
- * // Upload 5 chunks in parallel
133
- * new ChunkedFileUploader(dispatcher, cache, { concurrency: 5 });
134
- * ```
135
- *
136
- * ---
137
- *
138
- * ### Pause, Resume and Cancel
139
- *
140
- * The upload can be paused, resumed, or cancelled at any time:
141
- *
142
- * ```typescript
143
- * uploader.pause(); // Pauses after the current chunk finishes
144
- * uploader.resume(); // Resumes from where it was paused
145
- * uploader.cancel(); // Aborts immediately via AbortController
146
- * ```
147
- *
148
- * ---
149
- *
150
- * ### Cache
151
- *
152
- * The library does **not** provide a built-in cache implementation to stay
153
- * lightweight and environment-agnostic. You must implement
154
- * {@link UploadResumeCacheInterface} with your preferred storage backend
155
- * (localStorage, IndexedDB, Redis, filesystem, etc.).
156
- *
157
- * ---
158
- *
159
- * @author AGBOKOUDJO Franck <internationaleswebservices@gmail.com>
160
- * @company INTERNATIONALES WEB APPS & SERVICES
161
- * @phone +229 0167 25 18 86
162
- * @linkedin https://www.linkedin.com/in/internationales-web-apps-services-120520193/
163
- * @github https://github.com/Agbokoudjo/file_uploader
164
- *
165
- * @version 1.0.0
166
- * @since 1.0.0
167
- * @license MIT
168
- *
169
- * @see {@link HttpFileUploaderEvents} All event name constants
170
- * @see {@link UploadResumeCacheInterface} Cache interface to implement
171
- * @see {@link InitializeUploadSubscriber} Handles the init HTTP phase
172
- * @see {@link FinalizeUploadSubscriber} Handles the finalize HTTP phase
173
- * @see {@link UploadOptions} Full configuration reference
174
- * @see {@link https://github.com/Agbokoudjo/file_uploader | GitHub Repository}
175
- */
176
- class ChunkedFileUploader {
177
- constructor(_uploadEventDispatcher = new event_dispatcher.BrowserEventDispatcher(), uploadResumeData, options) {
12
+ var ChunkedFileUploader = class {
13
+ /**
14
+ * @param _uploadEventDispatcher: new BrowserEventDispatcher(), //or new NodeJSEventDispatcher() if you have an environment NodeJs
15
+ * @param uploadResumeData: UploadResumeCacheInterface
16
+ * @param options: UploadOptions
17
+ */
18
+ constructor(_uploadEventDispatcher, uploadResumeData, options) {
178
19
  this._uploadEventDispatcher = _uploadEventDispatcher;
179
20
  this.uploadResumeData = uploadResumeData;
180
21
  this.options = options;
@@ -184,12 +25,15 @@ class ChunkedFileUploader {
184
25
  this.startTime = 0;
185
26
  this.uploadedBytes = 0;
186
27
  this.abortController = new AbortController();
187
- this.state = types.UploadState.IDLE;
28
+ this.state = "idle" /* IDLE */;
188
29
  this.totalChunks = 0;
189
30
  this.percentage = 0;
190
31
  this.uploadedChunks = 0;
191
32
  this.lastUploadedChunkIndex = -1;
192
33
  }
34
+ _uploadEventDispatcher;
35
+ uploadResumeData;
36
+ options;
193
37
  static {
194
38
  __name(this, "ChunkedFileUploader");
195
39
  }
@@ -223,7 +67,7 @@ class ChunkedFileUploader {
223
67
  * ```
224
68
  */
225
69
  async upload() {
226
- this.setState(types.UploadState.INITIALIZING);
70
+ this.setState("initializing" /* INITIALIZING */);
227
71
  const {
228
72
  maxRetries = 3,
229
73
  config,
@@ -232,35 +76,40 @@ class ChunkedFileUploader {
232
76
  headerInitialzingUpload,
233
77
  concurrency = 3
234
78
  } = this.options;
235
- const file = this.file;
236
- const fileHash = await utils.FileUtils.generateFileHash(file);
79
+ let file;
80
+ try {
81
+ file = this.file;
82
+ } catch (error) {
83
+ throw error;
84
+ }
85
+ const fileHash = await FileUtils.generateFileHash(this.file);
237
86
  let fileId;
238
87
  try {
239
- const initializationEvent = this._uploadEventDispatcher.dispatch(
240
- new events.InitializingUploadEvent(
88
+ const initializationEvent = await this._uploadEventDispatcher.dispatchAsync(
89
+ new InitializingUploadEvent(
241
90
  {
242
91
  fileHash,
243
- fileName: this.file.name,
244
- fileSize: this.file.size,
245
- fileType: this.file.type,
92
+ fileName: file.name,
93
+ fileSize: file.size,
94
+ fileType: file.type,
246
95
  metadata,
247
96
  endpointInit: this.endPointOptions.init,
248
97
  headers: headerInitialzingUpload
249
98
  }
250
99
  ),
251
- events.HttpFileUploaderEvents.INITIALIZE_UPLOAD
100
+ HttpFileUploaderEvents.INITIALIZE_UPLOAD
252
101
  );
253
102
  fileId = initializationEvent.mediaId;
254
103
  } catch (error) {
255
- this.setState(types.UploadState.FAILED);
104
+ this.setState("failed" /* FAILED */);
256
105
  throw error;
257
106
  }
258
- this.setState(types.UploadState.UPLOADING);
107
+ this.setState("uploading" /* UPLOADING */);
259
108
  this.startTime = Date.now();
260
109
  this.uploadedBytes = 0;
261
110
  this.uploadedChunks = 0;
262
111
  this.lastUploadedChunkIndex = -1;
263
- const chunkSize = this.options.chunkSize || utils.FileUtils.calculateChunkSize(file.size, speedMbps, config);
112
+ const chunkSize = this.options.chunkSize || FileUtils.calculateChunkSize(file.size, speedMbps, config);
264
113
  this.totalChunks = Math.ceil(file.size / chunkSize);
265
114
  try {
266
115
  await this.uploadChunksWithConcurrency(
@@ -325,7 +174,7 @@ class ChunkedFileUploader {
325
174
  */
326
175
  async uploadChunksWithConcurrency(file, chunkSize, fileId, fileHash, maxRetries, concurrency, startIndex = 0) {
327
176
  try {
328
- const limit = pLimit__default.default(concurrency);
177
+ const limit = pLimit(concurrency);
329
178
  const uploadPromises = [];
330
179
  for (let chunkIndex = startIndex; chunkIndex < this.totalChunks; chunkIndex++) {
331
180
  const limitedUpload = limit(
@@ -362,7 +211,7 @@ class ChunkedFileUploader {
362
211
  try {
363
212
  if (this.abortController.signal.aborted) {
364
213
  this._uploadEventDispatcher.dispatch(
365
- new events.UploadCancelledEvent(
214
+ new UploadCancelledEvent(
366
215
  file.name,
367
216
  this.totalChunks,
368
217
  this.uploadedBytes,
@@ -371,7 +220,7 @@ class ChunkedFileUploader {
371
220
  "Upload cancelled by user",
372
221
  Date.now()
373
222
  ),
374
- events.HttpFileUploaderEvents.UPLOAD_CANCELLED
223
+ HttpFileUploaderEvents.UPLOAD_CANCELLED
375
224
  );
376
225
  return;
377
226
  }
@@ -390,8 +239,8 @@ class ChunkedFileUploader {
390
239
  status: "pending"
391
240
  };
392
241
  this._uploadEventDispatcher.dispatch(
393
- new events.UploadChunkStartedEvent(chunkInfo),
394
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_STARTED
242
+ new UploadChunkStartedEvent(chunkInfo),
243
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_STARTED
395
244
  );
396
245
  await this.uploadChunkWithRetry(
397
246
  chunk,
@@ -427,15 +276,15 @@ class ChunkedFileUploader {
427
276
  );
428
277
  return;
429
278
  } catch (error) {
430
- if (error instanceof exceptions.ChunkUploadHttpErrorException) {
279
+ if (error instanceof ChunkUploadHttpErrorException) {
431
280
  this._uploadEventDispatcher.dispatch(
432
- new events.ChunkUploadHttpErrorResponseEvent(
281
+ new ChunkUploadHttpErrorResponseEvent(
433
282
  error.errorPayload,
434
283
  error.statusResponse,
435
284
  this.endPointOptions.upload,
436
285
  chunkInfo
437
286
  ),
438
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_HTTP_ERROR_RESPONSE
287
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_HTTP_ERROR_RESPONSE
439
288
  );
440
289
  }
441
290
  lastError = error;
@@ -446,8 +295,8 @@ class ChunkedFileUploader {
446
295
  attempt: attempt + 1,
447
296
  willRetry: attempt < maxRetries - 1
448
297
  };
449
- if (error instanceof http_client.HttpFetchError) {
450
- this._uploadEventDispatcher.dispatch(chunkError, events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_FAILED);
298
+ if (error instanceof HttpFetchError) {
299
+ this._uploadEventDispatcher.dispatch(chunkError, HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_FAILED);
451
300
  }
452
301
  if (attempt < maxRetries - 1) {
453
302
  const delay = Math.pow(2, attempt) * 1e3;
@@ -457,7 +306,7 @@ class ChunkedFileUploader {
457
306
  }
458
307
  }
459
308
  if (!success) {
460
- const fileUploadChunkError = new exceptions.FileUploadChunkError(
309
+ const fileUploadChunkError = new FileUploadChunkError(
461
310
  `Failed to upload chunk ${chunkInfo.index} after ${maxRetries} attempts`,
462
311
  {
463
312
  chunk: chunkInfo,
@@ -468,14 +317,14 @@ class ChunkedFileUploader {
468
317
  );
469
318
  this._uploadEventDispatcher.dispatch(
470
319
  fileUploadChunkError,
471
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_MAXRETRY_EXPIRE
320
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_MAXRETRY_EXPIRE
472
321
  );
473
322
  throw fileUploadChunkError;
474
323
  }
475
324
  }
476
325
  async uploadChunk(chunk, chunkInfo, mediaIdFromServer, fileHash, totalChunks) {
477
326
  const media = this.file;
478
- const chunkFormData = utils.createChunkFormData(
327
+ const chunkFormData = createChunkFormData(
479
328
  chunk,
480
329
  {
481
330
  chunkIndex: chunkInfo.index,
@@ -487,7 +336,7 @@ class ChunkedFileUploader {
487
336
  }
488
337
  );
489
338
  try {
490
- const response_of_server = await http_client.safeFetch({
339
+ const response_of_server = await safeFetch({
491
340
  url: this.endPointOptions.upload,
492
341
  headers: this.options.headers,
493
342
  data: chunkFormData,
@@ -500,7 +349,7 @@ class ChunkedFileUploader {
500
349
  });
501
350
  const statusResponse = response_of_server.status;
502
351
  if (response_of_server.failed) {
503
- throw new exceptions.ChunkUploadHttpErrorException(response_of_server.data, statusResponse);
352
+ throw new ChunkUploadHttpErrorException(response_of_server.data, statusResponse);
504
353
  }
505
354
  return response_of_server;
506
355
  } catch (error) {
@@ -535,14 +384,14 @@ class ChunkedFileUploader {
535
384
  // secondes écoulées
536
385
  };
537
386
  this._uploadEventDispatcher.dispatch(
538
- new events.UploadProgressEvent(
387
+ new UploadProgressEvent(
539
388
  progress,
540
389
  httpResponse.data,
541
390
  httpResponse.status
542
391
  )
543
392
  );
544
393
  console.info(
545
- `Progress: ${this.percentage}% | Speed: ${utils.FileUtils.formatBytes(speed)}/s | ETA: ${estimatedTimeRemaining ? utils.FileUtils.formatDuration(estimatedTimeRemaining) : "Calculating..."}`
394
+ `Progress: ${this.percentage}% | Speed: ${FileUtils.formatBytes(speed)}/s | ETA: ${estimatedTimeRemaining ? FileUtils.formatDuration(estimatedTimeRemaining) : "Calculating..."}`
546
395
  );
547
396
  }
548
397
  sleep(ms) {
@@ -576,51 +425,51 @@ class ChunkedFileUploader {
576
425
  const oldState = this.state;
577
426
  this.state = newState;
578
427
  this._uploadEventDispatcher.dispatch(
579
- new events.UploadStateChangedEvent(
428
+ new UploadStateChangedEvent(
580
429
  oldState,
581
430
  newState,
582
431
  Date.now()
583
432
  ),
584
- events.HttpFileUploaderEvents.UPLOAD_STATE_CHANGED
433
+ HttpFileUploaderEvents.UPLOAD_STATE_CHANGED
585
434
  );
586
435
  }
587
436
  getState() {
588
437
  return this.state;
589
438
  }
590
439
  handleUploadFailure(error) {
591
- this.setState(types.UploadState.FAILED);
592
- this._uploadEventDispatcher.dispatch(error, events.HttpFileUploaderEvents.DOWNLOAD_MEDIA_FAILURE);
440
+ this.setState("failed" /* FAILED */);
441
+ this._uploadEventDispatcher.dispatch(error, HttpFileUploaderEvents.DOWNLOAD_MEDIA_FAILURE);
593
442
  console.error("Upload failed:", error);
594
443
  }
595
444
  cancel() {
596
445
  this.abortController.abort();
597
- this.setState(types.UploadState.CANCELLED);
446
+ this.setState("cancelled" /* CANCELLED */);
598
447
  }
599
448
  pause() {
600
449
  this.isPaused = true;
601
- this.setState(types.UploadState.PAUSED);
450
+ this.setState("paused" /* PAUSED */);
602
451
  this._uploadEventDispatcher.dispatch(
603
- new events.UploadPausedEvent(
452
+ new UploadPausedEvent(
604
453
  this.file.name,
605
454
  this.totalChunks,
606
455
  this.uploadedBytes,
607
456
  this.percentage,
608
457
  Date.now()
609
458
  ),
610
- events.HttpFileUploaderEvents.UPLOAD_PAUSED
459
+ HttpFileUploaderEvents.UPLOAD_PAUSED
611
460
  );
612
461
  }
613
462
  resume() {
614
463
  this.isPaused = false;
615
- this.setState(types.UploadState.UPLOADING);
464
+ this.setState("uploading" /* UPLOADING */);
616
465
  this._uploadEventDispatcher.dispatch(
617
- new events.UploadResumedEvent(
466
+ new UploadResumedEvent(
618
467
  this.file.name,
619
468
  this.totalChunks,
620
469
  this.uploadedBytes,
621
470
  this.percentage
622
471
  ),
623
- events.HttpFileUploaderEvents.UPLOAD_RESUMED
472
+ HttpFileUploaderEvents.UPLOAD_RESUMED
624
473
  );
625
474
  }
626
475
  /**
@@ -658,13 +507,13 @@ class ChunkedFileUploader {
658
507
  const timeAlreadySpent = resumeData.lastBytePosition / assumedSpeed;
659
508
  this.startTime = Date.now() - timeAlreadySpent * 1e3;
660
509
  const { maxRetries = 3 } = this.options;
661
- const fileHash = await utils.FileUtils.generateFileHash(this.file);
510
+ const fileHash = await FileUtils.generateFileHash(this.file);
662
511
  const chunkSize = resumeData.chunkSize;
663
512
  this.totalChunks = Math.ceil(this.file.size / chunkSize);
664
513
  try {
665
514
  this._uploadEventDispatcher.dispatch(
666
- new events.ResumeUploadEvent(resumeData, __message),
667
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_RESUME
515
+ new ResumeUploadEvent(resumeData, __message),
516
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_RESUME
668
517
  );
669
518
  await this.uploadChunksWithConcurrency(
670
519
  this.file,
@@ -688,16 +537,16 @@ class ChunkedFileUploader {
688
537
  }
689
538
  async finalizeUpload(mediaId, fileHash) {
690
539
  try {
691
- const finalizeUploadEvent = this._uploadEventDispatcher.dispatch(
692
- new events.FinalizeUploadEvent(
540
+ const finalizeUploadEvent = await this._uploadEventDispatcher.dispatchAsync(
541
+ new FinalizeUploadEvent(
693
542
  this.endPointOptions.finalize,
694
543
  mediaId,
695
544
  fileHash,
696
545
  this.options.headerFinalezingUpload
697
546
  ),
698
- events.HttpFileUploaderEvents.FINALIZE_UPLOAD
547
+ HttpFileUploaderEvents.FINALIZE_UPLOAD
699
548
  );
700
- this.setState(types.UploadState.FINALIZING);
549
+ this.setState("finalizing" /* FINALIZING */);
701
550
  const duration = (Date.now() - this.startTime) / 1e3;
702
551
  const uploadResult = {
703
552
  success: true,
@@ -708,17 +557,176 @@ class ChunkedFileUploader {
708
557
  averageSpeed: this.file.size / duration,
709
558
  fileId: mediaId
710
559
  };
711
- this.setState(types.UploadState.COMPLETED);
560
+ this.setState("completed" /* COMPLETED */);
712
561
  this._uploadEventDispatcher.dispatch(
713
- new events.UploadMediaCompleteEvent(uploadResult),
714
- events.HttpFileUploaderEvents.DOWNLOAD_MEDIA_COMPLETE
562
+ new UploadMediaCompleteEvent(uploadResult),
563
+ HttpFileUploaderEvents.DOWNLOAD_MEDIA_COMPLETE
715
564
  );
716
565
  } catch (error) {
717
566
  throw error;
718
567
  }
719
568
  }
720
- }
569
+ };
570
+ /**
571
+ * ChunkedFileUploader
572
+ *
573
+ * A production-ready, event-driven chunked file upload engine for Browser and Node.js.
574
+ *
575
+ * Designed and developed by **AGBOKOUDJO Franck** at
576
+ * **INTERNATIONALES WEB APPS & SERVICES**, this class provides a robust,
577
+ * framework-agnostic solution for uploading large files to a remote server
578
+ * by splitting them into smaller chunks and sending them in parallel with
579
+ * configurable concurrency control.
580
+ *
581
+ * ---
582
+ *
583
+ * ### How It Works
584
+ *
585
+ * The upload process follows a strict three-phase lifecycle:
586
+ *
587
+ * 1. **Initialization** — A session is opened with the server via the `init` endpoint.
588
+ * The file is identified by its name, size, type, and a SHA-256 hash of its
589
+ * first megabyte. The server returns a unique `mediaId` that identifies the session.
590
+ *
591
+ * 2. **Chunk Upload** — The file is sliced into fixed-size chunks and uploaded
592
+ * concurrently using `p-limit`. Each chunk carries its index, the total number
593
+ * of chunks, and the session `mediaId`. Failed chunks are retried automatically
594
+ * with exponential backoff up to `maxRetries` attempts.
595
+ *
596
+ * 3. **Finalization** — Once all chunks are successfully uploaded, the `finalize`
597
+ * endpoint is called to instruct the server to assemble the chunks into the
598
+ * final file.
599
+ *
600
+ * ---
601
+ *
602
+ * ### Event-Driven Architecture
603
+ *
604
+ * This class follows the **Symfony EventDispatcher pattern** via
605
+ * `@wlindabla/event_dispatcher`. Every meaningful moment in the upload
606
+ * lifecycle emits a typed event that your application can listen to:
607
+ *
608
+ * ```
609
+ * IDLE → INITIALIZING → UPLOADING → FINALIZING → COMPLETED
610
+ * ↓ ↓
611
+ * FAILED PAUSED ↔ UPLOADING
612
+ * ↓
613
+ * CANCELLED
614
+ * ```
615
+ *
616
+ * All event name constants are centralized in {@link HttpFileUploaderEvents}.
617
+ *
618
+ * ---
619
+ *
620
+ * ### Subscriber Registration (Required)
621
+ *
622
+ * Before calling `.upload()`, you **must** register the two built-in subscribers
623
+ * on your dispatcher. They handle the HTTP communication for the init and finalize phases:
624
+ *
625
+ * ```typescript
626
+ * // Browser
627
+ * const dispatcher = new BrowserEventDispatcher(document);
628
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
629
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
630
+ *
631
+ * // Node.js
632
+ * const dispatcher = new NodeEventDispatcher();
633
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
634
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
635
+ * ```
636
+ *
637
+ * ---
638
+ *
639
+ * ### Fluent Builder API
640
+ *
641
+ * The class exposes a fluent API to configure the upload before starting:
642
+ *
643
+ * ```typescript
644
+ * const uploader = new ChunkedFileUploader(dispatcher, cache, options);
645
+ *
646
+ * await uploader
647
+ * .withFile(file)
648
+ * .withEndpoints({
649
+ * init: 'https://api.example.com/upload/init',
650
+ * upload: 'https://api.example.com/upload/chunk',
651
+ * finalize: 'https://api.example.com/upload/finalize'
652
+ * })
653
+ * .upload();
654
+ * ```
655
+ *
656
+ * ---
657
+ *
658
+ * ### Resumable Uploads
659
+ *
660
+ * When `autoSave: true` is set in options, the upload progress is persisted
661
+ * after each successful chunk via the {@link UploadResumeCacheInterface}.
662
+ * A failed or interrupted upload can be resumed later:
663
+ *
664
+ * ```typescript
665
+ * const resumeData = await uploader.loadResumeData(file.name);
666
+ *
667
+ * if (resumeData) {
668
+ * await uploader
669
+ * .withFile(file)
670
+ * .withEndpoints(endpoints)
671
+ * .resumeUpload(resumeData);
672
+ * }
673
+ * ```
674
+ *
675
+ * ---
676
+ *
677
+ * ### Concurrency
678
+ *
679
+ * Multiple chunks can be uploaded simultaneously. The `concurrency` option
680
+ * controls how many parallel uploads are active at any given time.
681
+ * The default value is `3`, which provides a good balance between speed
682
+ * and server/network load:
683
+ *
684
+ * ```typescript
685
+ * // Upload 5 chunks in parallel
686
+ * new ChunkedFileUploader(dispatcher, cache, { concurrency: 5 });
687
+ * ```
688
+ *
689
+ * ---
690
+ *
691
+ * ### Pause, Resume and Cancel
692
+ *
693
+ * The upload can be paused, resumed, or cancelled at any time:
694
+ *
695
+ * ```typescript
696
+ * uploader.pause(); // Pauses after the current chunk finishes
697
+ * uploader.resume(); // Resumes from where it was paused
698
+ * uploader.cancel(); // Aborts immediately via AbortController
699
+ * ```
700
+ *
701
+ * ---
702
+ *
703
+ * ### Cache
704
+ *
705
+ * The library does **not** provide a built-in cache implementation to stay
706
+ * lightweight and environment-agnostic. You must implement
707
+ * {@link UploadResumeCacheInterface} with your preferred storage backend
708
+ * (localStorage, IndexedDB, Redis, filesystem, etc.).
709
+ *
710
+ * ---
711
+ *
712
+ * @author AGBOKOUDJO Franck <internationaleswebservices@gmail.com>
713
+ * @company INTERNATIONALES WEB APPS & SERVICES
714
+ * @phone +229 0167 25 18 86
715
+ * @linkedin https://www.linkedin.com/in/internationales-web-apps-services-120520193/
716
+ * @github https://github.com/Agbokoudjo/file_uploader
717
+ *
718
+ * @version 2.0.1
719
+ * @since 1.0.0
720
+ * @license MIT
721
+ *
722
+ * @see {@link HttpFileUploaderEvents} All event name constants
723
+ * @see {@link UploadResumeCacheInterface} Cache interface to implement
724
+ * @see {@link InitializeUploadSubscriber} Handles the init HTTP phase
725
+ * @see {@link FinalizeUploadSubscriber} Handles the finalize HTTP phase
726
+ * @see {@link UploadOptions} Full configuration reference
727
+ * @see {@link https://github.com/Agbokoudjo/file_uploader | GitHub Repository}
728
+ */
721
729
 
722
- exports.ChunkedFileUploader = ChunkedFileUploader;
723
- //# sourceMappingURL=index.js.map
724
- //# sourceMappingURL=index.js.map
730
+ export { ChunkedFileUploader };
731
+ //# sourceMappingURL=chunk-TONVXBLH.js.map
732
+ //# sourceMappingURL=chunk-TONVXBLH.js.map