@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
@@ -1,180 +1,17 @@
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-LTYMA4U4.js';
6
+ import { InitializingUploadEvent } from './chunk-DN5B6PRW.js';
7
+ import { UploadCancelledEvent, UploadProgressEvent, UploadStateChangedEvent, UploadPausedEvent, UploadResumedEvent } from './chunk-6225YMFE.js';
8
+ import { __name } from './chunk-7QVYU63E.js';
9
+ import { BrowserEventDispatcher } from '@wlindabla/event_dispatcher';
10
+ import { HttpFetchError, safeFetch } from '@wlindabla/http_client';
11
+ import pLimit from 'p-limit';
2
12
 
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) {
13
+ var ChunkedFileUploader = class {
14
+ constructor(_uploadEventDispatcher = new BrowserEventDispatcher(), uploadResumeData, options) {
178
15
  this._uploadEventDispatcher = _uploadEventDispatcher;
179
16
  this.uploadResumeData = uploadResumeData;
180
17
  this.options = options;
@@ -184,12 +21,15 @@ class ChunkedFileUploader {
184
21
  this.startTime = 0;
185
22
  this.uploadedBytes = 0;
186
23
  this.abortController = new AbortController();
187
- this.state = types.UploadState.IDLE;
24
+ this.state = "idle" /* IDLE */;
188
25
  this.totalChunks = 0;
189
26
  this.percentage = 0;
190
27
  this.uploadedChunks = 0;
191
28
  this.lastUploadedChunkIndex = -1;
192
29
  }
30
+ _uploadEventDispatcher;
31
+ uploadResumeData;
32
+ options;
193
33
  static {
194
34
  __name(this, "ChunkedFileUploader");
195
35
  }
@@ -223,7 +63,7 @@ class ChunkedFileUploader {
223
63
  * ```
224
64
  */
225
65
  async upload() {
226
- this.setState(types.UploadState.INITIALIZING);
66
+ this.setState("initializing" /* INITIALIZING */);
227
67
  const {
228
68
  maxRetries = 3,
229
69
  config,
@@ -233,11 +73,11 @@ class ChunkedFileUploader {
233
73
  concurrency = 3
234
74
  } = this.options;
235
75
  const file = this.file;
236
- const fileHash = await utils.FileUtils.generateFileHash(file);
76
+ const fileHash = await FileUtils.generateFileHash(file);
237
77
  let fileId;
238
78
  try {
239
- const initializationEvent = this._uploadEventDispatcher.dispatch(
240
- new events.InitializingUploadEvent(
79
+ const initializationEvent = await this._uploadEventDispatcher.dispatchAsync(
80
+ new InitializingUploadEvent(
241
81
  {
242
82
  fileHash,
243
83
  fileName: this.file.name,
@@ -248,19 +88,19 @@ class ChunkedFileUploader {
248
88
  headers: headerInitialzingUpload
249
89
  }
250
90
  ),
251
- events.HttpFileUploaderEvents.INITIALIZE_UPLOAD
91
+ HttpFileUploaderEvents.INITIALIZE_UPLOAD
252
92
  );
253
93
  fileId = initializationEvent.mediaId;
254
94
  } catch (error) {
255
- this.setState(types.UploadState.FAILED);
95
+ this.setState("failed" /* FAILED */);
256
96
  throw error;
257
97
  }
258
- this.setState(types.UploadState.UPLOADING);
98
+ this.setState("uploading" /* UPLOADING */);
259
99
  this.startTime = Date.now();
260
100
  this.uploadedBytes = 0;
261
101
  this.uploadedChunks = 0;
262
102
  this.lastUploadedChunkIndex = -1;
263
- const chunkSize = this.options.chunkSize || utils.FileUtils.calculateChunkSize(file.size, speedMbps, config);
103
+ const chunkSize = this.options.chunkSize || FileUtils.calculateChunkSize(file.size, speedMbps, config);
264
104
  this.totalChunks = Math.ceil(file.size / chunkSize);
265
105
  try {
266
106
  await this.uploadChunksWithConcurrency(
@@ -325,7 +165,7 @@ class ChunkedFileUploader {
325
165
  */
326
166
  async uploadChunksWithConcurrency(file, chunkSize, fileId, fileHash, maxRetries, concurrency, startIndex = 0) {
327
167
  try {
328
- const limit = pLimit__default.default(concurrency);
168
+ const limit = pLimit(concurrency);
329
169
  const uploadPromises = [];
330
170
  for (let chunkIndex = startIndex; chunkIndex < this.totalChunks; chunkIndex++) {
331
171
  const limitedUpload = limit(
@@ -362,7 +202,7 @@ class ChunkedFileUploader {
362
202
  try {
363
203
  if (this.abortController.signal.aborted) {
364
204
  this._uploadEventDispatcher.dispatch(
365
- new events.UploadCancelledEvent(
205
+ new UploadCancelledEvent(
366
206
  file.name,
367
207
  this.totalChunks,
368
208
  this.uploadedBytes,
@@ -371,7 +211,7 @@ class ChunkedFileUploader {
371
211
  "Upload cancelled by user",
372
212
  Date.now()
373
213
  ),
374
- events.HttpFileUploaderEvents.UPLOAD_CANCELLED
214
+ HttpFileUploaderEvents.UPLOAD_CANCELLED
375
215
  );
376
216
  return;
377
217
  }
@@ -390,8 +230,8 @@ class ChunkedFileUploader {
390
230
  status: "pending"
391
231
  };
392
232
  this._uploadEventDispatcher.dispatch(
393
- new events.UploadChunkStartedEvent(chunkInfo),
394
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_STARTED
233
+ new UploadChunkStartedEvent(chunkInfo),
234
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_STARTED
395
235
  );
396
236
  await this.uploadChunkWithRetry(
397
237
  chunk,
@@ -427,15 +267,15 @@ class ChunkedFileUploader {
427
267
  );
428
268
  return;
429
269
  } catch (error) {
430
- if (error instanceof exceptions.ChunkUploadHttpErrorException) {
270
+ if (error instanceof ChunkUploadHttpErrorException) {
431
271
  this._uploadEventDispatcher.dispatch(
432
- new events.ChunkUploadHttpErrorResponseEvent(
272
+ new ChunkUploadHttpErrorResponseEvent(
433
273
  error.errorPayload,
434
274
  error.statusResponse,
435
275
  this.endPointOptions.upload,
436
276
  chunkInfo
437
277
  ),
438
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_HTTP_ERROR_RESPONSE
278
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_HTTP_ERROR_RESPONSE
439
279
  );
440
280
  }
441
281
  lastError = error;
@@ -446,8 +286,8 @@ class ChunkedFileUploader {
446
286
  attempt: attempt + 1,
447
287
  willRetry: attempt < maxRetries - 1
448
288
  };
449
- if (error instanceof http_client.HttpFetchError) {
450
- this._uploadEventDispatcher.dispatch(chunkError, events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_FAILED);
289
+ if (error instanceof HttpFetchError) {
290
+ this._uploadEventDispatcher.dispatch(chunkError, HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_FAILED);
451
291
  }
452
292
  if (attempt < maxRetries - 1) {
453
293
  const delay = Math.pow(2, attempt) * 1e3;
@@ -457,7 +297,7 @@ class ChunkedFileUploader {
457
297
  }
458
298
  }
459
299
  if (!success) {
460
- const fileUploadChunkError = new exceptions.FileUploadChunkError(
300
+ const fileUploadChunkError = new FileUploadChunkError(
461
301
  `Failed to upload chunk ${chunkInfo.index} after ${maxRetries} attempts`,
462
302
  {
463
303
  chunk: chunkInfo,
@@ -468,14 +308,14 @@ class ChunkedFileUploader {
468
308
  );
469
309
  this._uploadEventDispatcher.dispatch(
470
310
  fileUploadChunkError,
471
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_MAXRETRY_EXPIRE
311
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_MAXRETRY_EXPIRE
472
312
  );
473
313
  throw fileUploadChunkError;
474
314
  }
475
315
  }
476
316
  async uploadChunk(chunk, chunkInfo, mediaIdFromServer, fileHash, totalChunks) {
477
317
  const media = this.file;
478
- const chunkFormData = utils.createChunkFormData(
318
+ const chunkFormData = createChunkFormData(
479
319
  chunk,
480
320
  {
481
321
  chunkIndex: chunkInfo.index,
@@ -487,7 +327,7 @@ class ChunkedFileUploader {
487
327
  }
488
328
  );
489
329
  try {
490
- const response_of_server = await http_client.safeFetch({
330
+ const response_of_server = await safeFetch({
491
331
  url: this.endPointOptions.upload,
492
332
  headers: this.options.headers,
493
333
  data: chunkFormData,
@@ -500,7 +340,7 @@ class ChunkedFileUploader {
500
340
  });
501
341
  const statusResponse = response_of_server.status;
502
342
  if (response_of_server.failed) {
503
- throw new exceptions.ChunkUploadHttpErrorException(response_of_server.data, statusResponse);
343
+ throw new ChunkUploadHttpErrorException(response_of_server.data, statusResponse);
504
344
  }
505
345
  return response_of_server;
506
346
  } catch (error) {
@@ -535,14 +375,14 @@ class ChunkedFileUploader {
535
375
  // secondes écoulées
536
376
  };
537
377
  this._uploadEventDispatcher.dispatch(
538
- new events.UploadProgressEvent(
378
+ new UploadProgressEvent(
539
379
  progress,
540
380
  httpResponse.data,
541
381
  httpResponse.status
542
382
  )
543
383
  );
544
384
  console.info(
545
- `Progress: ${this.percentage}% | Speed: ${utils.FileUtils.formatBytes(speed)}/s | ETA: ${estimatedTimeRemaining ? utils.FileUtils.formatDuration(estimatedTimeRemaining) : "Calculating..."}`
385
+ `Progress: ${this.percentage}% | Speed: ${FileUtils.formatBytes(speed)}/s | ETA: ${estimatedTimeRemaining ? FileUtils.formatDuration(estimatedTimeRemaining) : "Calculating..."}`
546
386
  );
547
387
  }
548
388
  sleep(ms) {
@@ -576,51 +416,51 @@ class ChunkedFileUploader {
576
416
  const oldState = this.state;
577
417
  this.state = newState;
578
418
  this._uploadEventDispatcher.dispatch(
579
- new events.UploadStateChangedEvent(
419
+ new UploadStateChangedEvent(
580
420
  oldState,
581
421
  newState,
582
422
  Date.now()
583
423
  ),
584
- events.HttpFileUploaderEvents.UPLOAD_STATE_CHANGED
424
+ HttpFileUploaderEvents.UPLOAD_STATE_CHANGED
585
425
  );
586
426
  }
587
427
  getState() {
588
428
  return this.state;
589
429
  }
590
430
  handleUploadFailure(error) {
591
- this.setState(types.UploadState.FAILED);
592
- this._uploadEventDispatcher.dispatch(error, events.HttpFileUploaderEvents.DOWNLOAD_MEDIA_FAILURE);
431
+ this.setState("failed" /* FAILED */);
432
+ this._uploadEventDispatcher.dispatch(error, HttpFileUploaderEvents.DOWNLOAD_MEDIA_FAILURE);
593
433
  console.error("Upload failed:", error);
594
434
  }
595
435
  cancel() {
596
436
  this.abortController.abort();
597
- this.setState(types.UploadState.CANCELLED);
437
+ this.setState("cancelled" /* CANCELLED */);
598
438
  }
599
439
  pause() {
600
440
  this.isPaused = true;
601
- this.setState(types.UploadState.PAUSED);
441
+ this.setState("paused" /* PAUSED */);
602
442
  this._uploadEventDispatcher.dispatch(
603
- new events.UploadPausedEvent(
443
+ new UploadPausedEvent(
604
444
  this.file.name,
605
445
  this.totalChunks,
606
446
  this.uploadedBytes,
607
447
  this.percentage,
608
448
  Date.now()
609
449
  ),
610
- events.HttpFileUploaderEvents.UPLOAD_PAUSED
450
+ HttpFileUploaderEvents.UPLOAD_PAUSED
611
451
  );
612
452
  }
613
453
  resume() {
614
454
  this.isPaused = false;
615
- this.setState(types.UploadState.UPLOADING);
455
+ this.setState("uploading" /* UPLOADING */);
616
456
  this._uploadEventDispatcher.dispatch(
617
- new events.UploadResumedEvent(
457
+ new UploadResumedEvent(
618
458
  this.file.name,
619
459
  this.totalChunks,
620
460
  this.uploadedBytes,
621
461
  this.percentage
622
462
  ),
623
- events.HttpFileUploaderEvents.UPLOAD_RESUMED
463
+ HttpFileUploaderEvents.UPLOAD_RESUMED
624
464
  );
625
465
  }
626
466
  /**
@@ -658,13 +498,13 @@ class ChunkedFileUploader {
658
498
  const timeAlreadySpent = resumeData.lastBytePosition / assumedSpeed;
659
499
  this.startTime = Date.now() - timeAlreadySpent * 1e3;
660
500
  const { maxRetries = 3 } = this.options;
661
- const fileHash = await utils.FileUtils.generateFileHash(this.file);
501
+ const fileHash = await FileUtils.generateFileHash(this.file);
662
502
  const chunkSize = resumeData.chunkSize;
663
503
  this.totalChunks = Math.ceil(this.file.size / chunkSize);
664
504
  try {
665
505
  this._uploadEventDispatcher.dispatch(
666
- new events.ResumeUploadEvent(resumeData, __message),
667
- events.HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_RESUME
506
+ new ResumeUploadEvent(resumeData, __message),
507
+ HttpFileUploaderEvents.MEDIA_CHUNK_UPLOAD_RESUME
668
508
  );
669
509
  await this.uploadChunksWithConcurrency(
670
510
  this.file,
@@ -688,16 +528,16 @@ class ChunkedFileUploader {
688
528
  }
689
529
  async finalizeUpload(mediaId, fileHash) {
690
530
  try {
691
- const finalizeUploadEvent = this._uploadEventDispatcher.dispatch(
692
- new events.FinalizeUploadEvent(
531
+ const finalizeUploadEvent = await this._uploadEventDispatcher.dispatchAsync(
532
+ new FinalizeUploadEvent(
693
533
  this.endPointOptions.finalize,
694
534
  mediaId,
695
535
  fileHash,
696
536
  this.options.headerFinalezingUpload
697
537
  ),
698
- events.HttpFileUploaderEvents.FINALIZE_UPLOAD
538
+ HttpFileUploaderEvents.FINALIZE_UPLOAD
699
539
  );
700
- this.setState(types.UploadState.FINALIZING);
540
+ this.setState("finalizing" /* FINALIZING */);
701
541
  const duration = (Date.now() - this.startTime) / 1e3;
702
542
  const uploadResult = {
703
543
  success: true,
@@ -708,17 +548,176 @@ class ChunkedFileUploader {
708
548
  averageSpeed: this.file.size / duration,
709
549
  fileId: mediaId
710
550
  };
711
- this.setState(types.UploadState.COMPLETED);
551
+ this.setState("completed" /* COMPLETED */);
712
552
  this._uploadEventDispatcher.dispatch(
713
- new events.UploadMediaCompleteEvent(uploadResult),
714
- events.HttpFileUploaderEvents.DOWNLOAD_MEDIA_COMPLETE
553
+ new UploadMediaCompleteEvent(uploadResult),
554
+ HttpFileUploaderEvents.DOWNLOAD_MEDIA_COMPLETE
715
555
  );
716
556
  } catch (error) {
717
557
  throw error;
718
558
  }
719
559
  }
720
- }
560
+ };
561
+ /**
562
+ * ChunkedFileUploader
563
+ *
564
+ * A production-ready, event-driven chunked file upload engine for Browser and Node.js.
565
+ *
566
+ * Designed and developed by **AGBOKOUDJO Franck** at
567
+ * **INTERNATIONALES WEB APPS & SERVICES**, this class provides a robust,
568
+ * framework-agnostic solution for uploading large files to a remote server
569
+ * by splitting them into smaller chunks and sending them in parallel with
570
+ * configurable concurrency control.
571
+ *
572
+ * ---
573
+ *
574
+ * ### How It Works
575
+ *
576
+ * The upload process follows a strict three-phase lifecycle:
577
+ *
578
+ * 1. **Initialization** — A session is opened with the server via the `init` endpoint.
579
+ * The file is identified by its name, size, type, and a SHA-256 hash of its
580
+ * first megabyte. The server returns a unique `mediaId` that identifies the session.
581
+ *
582
+ * 2. **Chunk Upload** — The file is sliced into fixed-size chunks and uploaded
583
+ * concurrently using `p-limit`. Each chunk carries its index, the total number
584
+ * of chunks, and the session `mediaId`. Failed chunks are retried automatically
585
+ * with exponential backoff up to `maxRetries` attempts.
586
+ *
587
+ * 3. **Finalization** — Once all chunks are successfully uploaded, the `finalize`
588
+ * endpoint is called to instruct the server to assemble the chunks into the
589
+ * final file.
590
+ *
591
+ * ---
592
+ *
593
+ * ### Event-Driven Architecture
594
+ *
595
+ * This class follows the **Symfony EventDispatcher pattern** via
596
+ * `@wlindabla/event_dispatcher`. Every meaningful moment in the upload
597
+ * lifecycle emits a typed event that your application can listen to:
598
+ *
599
+ * ```
600
+ * IDLE → INITIALIZING → UPLOADING → FINALIZING → COMPLETED
601
+ * ↓ ↓
602
+ * FAILED PAUSED ↔ UPLOADING
603
+ * ↓
604
+ * CANCELLED
605
+ * ```
606
+ *
607
+ * All event name constants are centralized in {@link HttpFileUploaderEvents}.
608
+ *
609
+ * ---
610
+ *
611
+ * ### Subscriber Registration (Required)
612
+ *
613
+ * Before calling `.upload()`, you **must** register the two built-in subscribers
614
+ * on your dispatcher. They handle the HTTP communication for the init and finalize phases:
615
+ *
616
+ * ```typescript
617
+ * // Browser
618
+ * const dispatcher = new BrowserEventDispatcher(document);
619
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
620
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
621
+ *
622
+ * // Node.js
623
+ * const dispatcher = new NodeEventDispatcher();
624
+ * dispatcher.addSubscriber(new InitializeUploadSubscriber(dispatcher));
625
+ * dispatcher.addSubscriber(new FinalizeUploadSubscriber(dispatcher));
626
+ * ```
627
+ *
628
+ * ---
629
+ *
630
+ * ### Fluent Builder API
631
+ *
632
+ * The class exposes a fluent API to configure the upload before starting:
633
+ *
634
+ * ```typescript
635
+ * const uploader = new ChunkedFileUploader(dispatcher, cache, options);
636
+ *
637
+ * await uploader
638
+ * .withFile(file)
639
+ * .withEndpoints({
640
+ * init: 'https://api.example.com/upload/init',
641
+ * upload: 'https://api.example.com/upload/chunk',
642
+ * finalize: 'https://api.example.com/upload/finalize'
643
+ * })
644
+ * .upload();
645
+ * ```
646
+ *
647
+ * ---
648
+ *
649
+ * ### Resumable Uploads
650
+ *
651
+ * When `autoSave: true` is set in options, the upload progress is persisted
652
+ * after each successful chunk via the {@link UploadResumeCacheInterface}.
653
+ * A failed or interrupted upload can be resumed later:
654
+ *
655
+ * ```typescript
656
+ * const resumeData = await uploader.loadResumeData(file.name);
657
+ *
658
+ * if (resumeData) {
659
+ * await uploader
660
+ * .withFile(file)
661
+ * .withEndpoints(endpoints)
662
+ * .resumeUpload(resumeData);
663
+ * }
664
+ * ```
665
+ *
666
+ * ---
667
+ *
668
+ * ### Concurrency
669
+ *
670
+ * Multiple chunks can be uploaded simultaneously. The `concurrency` option
671
+ * controls how many parallel uploads are active at any given time.
672
+ * The default value is `3`, which provides a good balance between speed
673
+ * and server/network load:
674
+ *
675
+ * ```typescript
676
+ * // Upload 5 chunks in parallel
677
+ * new ChunkedFileUploader(dispatcher, cache, { concurrency: 5 });
678
+ * ```
679
+ *
680
+ * ---
681
+ *
682
+ * ### Pause, Resume and Cancel
683
+ *
684
+ * The upload can be paused, resumed, or cancelled at any time:
685
+ *
686
+ * ```typescript
687
+ * uploader.pause(); // Pauses after the current chunk finishes
688
+ * uploader.resume(); // Resumes from where it was paused
689
+ * uploader.cancel(); // Aborts immediately via AbortController
690
+ * ```
691
+ *
692
+ * ---
693
+ *
694
+ * ### Cache
695
+ *
696
+ * The library does **not** provide a built-in cache implementation to stay
697
+ * lightweight and environment-agnostic. You must implement
698
+ * {@link UploadResumeCacheInterface} with your preferred storage backend
699
+ * (localStorage, IndexedDB, Redis, filesystem, etc.).
700
+ *
701
+ * ---
702
+ *
703
+ * @author AGBOKOUDJO Franck <internationaleswebservices@gmail.com>
704
+ * @company INTERNATIONALES WEB APPS & SERVICES
705
+ * @phone +229 0167 25 18 86
706
+ * @linkedin https://www.linkedin.com/in/internationales-web-apps-services-120520193/
707
+ * @github https://github.com/Agbokoudjo/file_uploader
708
+ *
709
+ * @version 1.0.0
710
+ * @since 1.0.0
711
+ * @license MIT
712
+ *
713
+ * @see {@link HttpFileUploaderEvents} All event name constants
714
+ * @see {@link UploadResumeCacheInterface} Cache interface to implement
715
+ * @see {@link InitializeUploadSubscriber} Handles the init HTTP phase
716
+ * @see {@link FinalizeUploadSubscriber} Handles the finalize HTTP phase
717
+ * @see {@link UploadOptions} Full configuration reference
718
+ * @see {@link https://github.com/Agbokoudjo/file_uploader | GitHub Repository}
719
+ */
721
720
 
722
- exports.ChunkedFileUploader = ChunkedFileUploader;
723
- //# sourceMappingURL=index.js.map
724
- //# sourceMappingURL=index.js.map
721
+ export { ChunkedFileUploader };
722
+ //# sourceMappingURL=chunk-6DIKDA6J.js.map
723
+ //# sourceMappingURL=chunk-6DIKDA6J.js.map