@ardrive/turbo-sdk 1.25.0 → 1.26.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 (48) hide show
  1. package/README.md +170 -62
  2. package/bundles/web.bundle.min.js +1584 -730
  3. package/lib/cjs/common/events.js +256 -0
  4. package/lib/cjs/common/events.test.js +470 -0
  5. package/lib/cjs/common/http.js +4 -13
  6. package/lib/cjs/common/turbo.js +6 -4
  7. package/lib/cjs/common/upload.js +65 -37
  8. package/lib/cjs/node/signer.js +30 -11
  9. package/lib/cjs/node/upload.js +7 -1
  10. package/lib/cjs/utils/axiosClient.js +3 -0
  11. package/lib/cjs/utils/readableStream.js +15 -0
  12. package/lib/cjs/version.js +1 -1
  13. package/lib/cjs/web/signer.js +55 -28
  14. package/lib/esm/common/events.js +249 -0
  15. package/lib/esm/common/events.test.js +468 -0
  16. package/lib/esm/common/http.js +4 -13
  17. package/lib/esm/common/turbo.js +6 -4
  18. package/lib/esm/common/upload.js +66 -38
  19. package/lib/esm/node/signer.js +30 -11
  20. package/lib/esm/node/upload.js +7 -1
  21. package/lib/esm/utils/axiosClient.js +3 -0
  22. package/lib/esm/utils/readableStream.js +15 -0
  23. package/lib/esm/version.js +1 -1
  24. package/lib/esm/web/signer.js +55 -28
  25. package/lib/types/common/events.d.ts +56 -0
  26. package/lib/types/common/events.d.ts.map +1 -0
  27. package/lib/types/common/events.test.d.ts +2 -0
  28. package/lib/types/common/events.test.d.ts.map +1 -0
  29. package/lib/types/common/http.d.ts +1 -2
  30. package/lib/types/common/http.d.ts.map +1 -1
  31. package/lib/types/common/signer.d.ts +1 -1
  32. package/lib/types/common/signer.d.ts.map +1 -1
  33. package/lib/types/common/turbo.d.ts +4 -4
  34. package/lib/types/common/turbo.d.ts.map +1 -1
  35. package/lib/types/common/upload.d.ts +13 -5
  36. package/lib/types/common/upload.d.ts.map +1 -1
  37. package/lib/types/node/signer.d.ts +1 -1
  38. package/lib/types/node/signer.d.ts.map +1 -1
  39. package/lib/types/node/upload.d.ts.map +1 -1
  40. package/lib/types/types.d.ts +61 -7
  41. package/lib/types/types.d.ts.map +1 -1
  42. package/lib/types/utils/axiosClient.d.ts.map +1 -1
  43. package/lib/types/utils/readableStream.d.ts +0 -1
  44. package/lib/types/utils/readableStream.d.ts.map +1 -1
  45. package/lib/types/version.d.ts +1 -1
  46. package/lib/types/web/signer.d.ts +1 -1
  47. package/lib/types/web/signer.d.ts.map +1 -1
  48. package/package.json +9 -7
@@ -0,0 +1,470 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_assert_1 = require("node:assert");
4
+ const node_stream_1 = require("node:stream");
5
+ const node_test_1 = require("node:test");
6
+ const events_js_1 = require("./events.js");
7
+ (0, node_test_1.describe)('createStreamWithUploadEvents', () => {
8
+ (0, node_test_1.describe)('Readable', () => {
9
+ (0, node_test_1.it)('should call onUploadProgress and onUploadSuccess callback and emit progress events when stream is consumed', async () => {
10
+ let onProgressCalled = false;
11
+ let progressEventEmitted = false;
12
+ let onSuccessCalled = false;
13
+ let successEventEmitted = false;
14
+ const emitter = new events_js_1.TurboEventEmitter({
15
+ onUploadProgress: () => {
16
+ onProgressCalled = true;
17
+ },
18
+ onUploadSuccess: () => {
19
+ onSuccessCalled = true;
20
+ },
21
+ });
22
+ const data = new node_stream_1.Readable({
23
+ read() {
24
+ this.push(Buffer.from('test'));
25
+ this.push(null); // End the stream
26
+ },
27
+ });
28
+ emitter.on('upload-progress', () => {
29
+ progressEventEmitted = true;
30
+ });
31
+ emitter.on('upload-success', () => {
32
+ successEventEmitted = true;
33
+ });
34
+ const { stream, resume } = (0, events_js_1.createStreamWithUploadEvents)({
35
+ data,
36
+ dataSize: 4,
37
+ emitter,
38
+ });
39
+ // Promise that resolves when all events have fired
40
+ const streamConsumerPromise = new Promise((resolve) => {
41
+ stream.on('data', () => {
42
+ // data starts flowing through the stream
43
+ });
44
+ stream.on('end', () => {
45
+ resolve();
46
+ });
47
+ stream.on('error', (error) => {
48
+ throw error;
49
+ });
50
+ });
51
+ // allow bytes to start flowing
52
+ resume();
53
+ // consume the full stream
54
+ await streamConsumerPromise;
55
+ // Assert that the events were called after the stream has been fully consumed
56
+ node_assert_1.strict.equal(onProgressCalled, true, 'onProgressCalled should be true');
57
+ node_assert_1.strict.equal(progressEventEmitted, true, 'progressEventEmitted should be true');
58
+ node_assert_1.strict.equal(onSuccessCalled, true, 'onSuccessCalled should be true');
59
+ node_assert_1.strict.equal(successEventEmitted, true, 'successEventEmitted should be true');
60
+ });
61
+ (0, node_test_1.it)('should call onUploadError callback and emit error events when stream errors', async () => {
62
+ let onErrorCalled = false;
63
+ let errorEventEmitted = false;
64
+ const testError = new Error('Test error');
65
+ const emitter = new events_js_1.TurboEventEmitter({
66
+ onUploadError: () => {
67
+ onErrorCalled = true;
68
+ },
69
+ });
70
+ // Create a readable stream that will emit an error
71
+ const data = new node_stream_1.Readable({
72
+ read() {
73
+ this.emit('error', testError);
74
+ },
75
+ });
76
+ emitter.on('upload-error', () => {
77
+ errorEventEmitted = true;
78
+ });
79
+ const { stream, resume } = (0, events_js_1.createStreamWithUploadEvents)({
80
+ data,
81
+ dataSize: 10,
82
+ emitter,
83
+ });
84
+ const streamErrorPromise = new Promise((_, reject) => {
85
+ stream.on('error', (error) => {
86
+ reject(error);
87
+ });
88
+ });
89
+ // allow bytes to start flowing
90
+ resume();
91
+ try {
92
+ // consume the full stream and wait for the error to be thrown
93
+ await streamErrorPromise;
94
+ }
95
+ catch (error) {
96
+ // Error is expected
97
+ }
98
+ node_assert_1.strict.equal(onErrorCalled, true);
99
+ node_assert_1.strict.equal(errorEventEmitted, true);
100
+ });
101
+ });
102
+ (0, node_test_1.describe)('ReadableStream', () => {
103
+ (0, node_test_1.it)('should call onUploadProgress and onUploadSuccess callback and emit progress events when stream is consumed', async () => {
104
+ let onProgressCalled = false;
105
+ let progressEventEmitted = false;
106
+ let onSuccessCalled = false;
107
+ let successEventEmitted = false;
108
+ let onErrorCalled = false;
109
+ let errorEventEmitted = false;
110
+ const emitter = new events_js_1.TurboEventEmitter({
111
+ onUploadProgress: () => {
112
+ onProgressCalled = true;
113
+ },
114
+ onUploadSuccess: () => {
115
+ onSuccessCalled = true;
116
+ },
117
+ onUploadError: () => {
118
+ onErrorCalled = true;
119
+ },
120
+ });
121
+ const data = new ReadableStream({
122
+ start(controller) {
123
+ controller.enqueue(Buffer.from('test test test test test'));
124
+ controller.close();
125
+ },
126
+ });
127
+ const { stream } = (0, events_js_1.createStreamWithUploadEvents)({
128
+ data,
129
+ dataSize: 4,
130
+ emitter,
131
+ });
132
+ emitter.on('upload-progress', () => {
133
+ progressEventEmitted = true;
134
+ });
135
+ emitter.on('upload-error', () => {
136
+ errorEventEmitted = true;
137
+ });
138
+ emitter.on('upload-success', () => {
139
+ successEventEmitted = true;
140
+ });
141
+ // TODO: ideally use generics to avoid needing to cast here
142
+ const reader = stream.getReader();
143
+ // read the stream to the end
144
+ while (true) {
145
+ const { done } = await reader.read();
146
+ if (done) {
147
+ break;
148
+ }
149
+ }
150
+ // progress events called
151
+ node_assert_1.strict.equal(onProgressCalled, true, 'onProgressCalled should be true');
152
+ node_assert_1.strict.equal(progressEventEmitted, true, 'progressEventEmitted should be true');
153
+ // error event not called
154
+ node_assert_1.strict.equal(errorEventEmitted, false, 'errorEventEmitted should be false');
155
+ node_assert_1.strict.equal(onErrorCalled, false, 'onErrorCalled should be false');
156
+ // success event called
157
+ node_assert_1.strict.equal(onSuccessCalled, true, 'onSuccessCalled should be true');
158
+ node_assert_1.strict.equal(successEventEmitted, true, 'successEventEmitted should be true');
159
+ });
160
+ (0, node_test_1.it)('should call onUploadError callback and emit error events when stream errors', async () => {
161
+ let onErrorCalled = false;
162
+ let errorEventEmitted = false;
163
+ const testError = new Error('Test error');
164
+ const onUploadError = () => {
165
+ onErrorCalled = true;
166
+ };
167
+ // Create a ReadableStream that will throw an error
168
+ const data = new ReadableStream({
169
+ pull() {
170
+ throw testError;
171
+ },
172
+ });
173
+ const emitter = new events_js_1.TurboEventEmitter({ onUploadError });
174
+ const { stream } = (0, events_js_1.createStreamWithUploadEvents)({
175
+ data,
176
+ dataSize: 4,
177
+ emitter,
178
+ });
179
+ emitter.on('upload-error', () => {
180
+ errorEventEmitted = true;
181
+ });
182
+ // Trigger error
183
+ try {
184
+ const reader = stream.getReader();
185
+ await reader.read();
186
+ }
187
+ catch (error) {
188
+ // Error is expected
189
+ }
190
+ node_assert_1.strict.equal(onErrorCalled, true, 'onErrorCalled should be true');
191
+ node_assert_1.strict.equal(errorEventEmitted, true, 'errorEventEmitted should be true');
192
+ });
193
+ });
194
+ });
195
+ (0, node_test_1.describe)('createStreamWithSigningEvents', () => {
196
+ (0, node_test_1.describe)('Readable', () => {
197
+ (0, node_test_1.it)('should call onSigningProgress and onSigningSuccess callback and emit progress events when stream is consumed', async () => {
198
+ let onProgressCalled = false;
199
+ let progressEventEmitted = false;
200
+ let onErrorCalled = false;
201
+ let errorEventEmitted = false;
202
+ let onSuccessCalled = false;
203
+ let successEventEmitted = false;
204
+ const emitter = new events_js_1.TurboEventEmitter({
205
+ onSigningProgress: () => {
206
+ onProgressCalled = true;
207
+ },
208
+ onSigningError: () => {
209
+ onErrorCalled = true;
210
+ },
211
+ onSigningSuccess: () => {
212
+ onSuccessCalled = true;
213
+ },
214
+ });
215
+ emitter.on('signing-progress', () => {
216
+ progressEventEmitted = true;
217
+ });
218
+ emitter.on('signing-success', () => {
219
+ successEventEmitted = true;
220
+ });
221
+ emitter.on('signing-error', () => {
222
+ errorEventEmitted = true;
223
+ });
224
+ const data = node_stream_1.Readable.from(['test']);
225
+ const { stream, resume } = (0, events_js_1.createStreamWithSigningEvents)({
226
+ data,
227
+ dataSize: 50,
228
+ emitter,
229
+ });
230
+ // Promise that resolves when all events have fired
231
+ const streamConsumerPromise = new Promise((resolve) => {
232
+ stream.on('data', () => {
233
+ // data starts flowing through the stream
234
+ });
235
+ stream.on('end', () => {
236
+ resolve();
237
+ });
238
+ stream.on('error', (error) => {
239
+ throw error;
240
+ });
241
+ });
242
+ // allow bytes to start flowing
243
+ resume();
244
+ // consume the full stream
245
+ await streamConsumerPromise;
246
+ node_assert_1.strict.equal(onProgressCalled, true);
247
+ node_assert_1.strict.equal(progressEventEmitted, true);
248
+ node_assert_1.strict.equal(onSuccessCalled, true);
249
+ node_assert_1.strict.equal(successEventEmitted, true);
250
+ node_assert_1.strict.equal(onErrorCalled, false);
251
+ node_assert_1.strict.equal(errorEventEmitted, false);
252
+ });
253
+ (0, node_test_1.it)('should call onSigningError callback and emit error events when stream errors', async () => {
254
+ let onErrorCalled = false;
255
+ let errorEventEmitted = false;
256
+ const testError = new Error('Test error');
257
+ const emitter = new events_js_1.TurboEventEmitter({
258
+ onSigningError: () => {
259
+ onErrorCalled = true;
260
+ },
261
+ });
262
+ // Create a readable stream that will emit an error
263
+ const data = new node_stream_1.Readable({
264
+ read() {
265
+ this.emit('error', testError);
266
+ },
267
+ });
268
+ emitter.on('signing-error', () => {
269
+ errorEventEmitted = true;
270
+ });
271
+ const { stream, resume } = (0, events_js_1.createStreamWithSigningEvents)({
272
+ data,
273
+ dataSize: 10,
274
+ emitter,
275
+ });
276
+ const streamErrorPromise = new Promise((_, reject) => {
277
+ stream.on('error', (error) => {
278
+ reject(error);
279
+ });
280
+ });
281
+ // allow bytes to start flowing
282
+ resume();
283
+ try {
284
+ // consume the full stream
285
+ await streamErrorPromise;
286
+ }
287
+ catch (error) {
288
+ // Error is expected
289
+ }
290
+ node_assert_1.strict.equal(onErrorCalled, true);
291
+ node_assert_1.strict.equal(errorEventEmitted, true);
292
+ });
293
+ });
294
+ (0, node_test_1.describe)('ReadableStream', () => {
295
+ (0, node_test_1.it)('should call onSigningProgress and onSigningSuccess callback and emit progress events when stream is consumed', async () => {
296
+ let onProgressCalled = false;
297
+ let progressEventEmitted = false;
298
+ let onErrorCalled = false;
299
+ let errorEventEmitted = false;
300
+ let onSuccessCalled = false;
301
+ let successEventEmitted = false;
302
+ const data = new ReadableStream({
303
+ start(controller) {
304
+ controller.enqueue(Buffer.from('test'));
305
+ controller.close();
306
+ },
307
+ });
308
+ const emitter = new events_js_1.TurboEventEmitter({
309
+ onSigningProgress: () => {
310
+ onProgressCalled = true;
311
+ },
312
+ onSigningError: () => {
313
+ onErrorCalled = true;
314
+ },
315
+ onSigningSuccess: () => {
316
+ onSuccessCalled = true;
317
+ },
318
+ });
319
+ const { stream } = (0, events_js_1.createStreamWithSigningEvents)({
320
+ data,
321
+ dataSize: 10,
322
+ emitter,
323
+ });
324
+ emitter.on('signing-progress', () => {
325
+ progressEventEmitted = true;
326
+ });
327
+ emitter.on('signing-success', () => {
328
+ successEventEmitted = true;
329
+ });
330
+ emitter.on('signing-error', () => {
331
+ errorEventEmitted = true;
332
+ });
333
+ // TODO: ideally use generics to avoid needing to cast here
334
+ const reader = stream.getReader();
335
+ // read the stream to the end
336
+ while (true) {
337
+ const { done } = await reader.read();
338
+ if (done) {
339
+ break;
340
+ }
341
+ }
342
+ node_assert_1.strict.equal(onProgressCalled, true);
343
+ node_assert_1.strict.equal(progressEventEmitted, true);
344
+ node_assert_1.strict.equal(onSuccessCalled, true);
345
+ node_assert_1.strict.equal(successEventEmitted, true);
346
+ node_assert_1.strict.equal(onErrorCalled, false);
347
+ node_assert_1.strict.equal(errorEventEmitted, false);
348
+ });
349
+ (0, node_test_1.it)('should call onSigningError callback and emit error events when stream errors', async () => {
350
+ let onErrorCalled = false;
351
+ let errorEventEmitted = false;
352
+ let onSuccessCalled = false;
353
+ let successEventEmitted = false;
354
+ const testError = new Error('Test error');
355
+ // Create a ReadableStream that will throw an error
356
+ const data = new ReadableStream({
357
+ pull() {
358
+ throw testError;
359
+ },
360
+ });
361
+ const emitter = new events_js_1.TurboEventEmitter({
362
+ onSigningError: () => {
363
+ onErrorCalled = true;
364
+ },
365
+ onSigningSuccess: () => {
366
+ onSuccessCalled = true;
367
+ },
368
+ });
369
+ const { stream } = (0, events_js_1.createStreamWithSigningEvents)({
370
+ data,
371
+ dataSize: 10,
372
+ emitter,
373
+ });
374
+ emitter.on('signing-error', () => {
375
+ errorEventEmitted = true;
376
+ });
377
+ emitter.on('signing-success', () => {
378
+ successEventEmitted = true;
379
+ });
380
+ try {
381
+ // consume the full stream
382
+ const reader = stream.getReader();
383
+ while (true) {
384
+ const { done } = await reader.read();
385
+ if (done) {
386
+ break;
387
+ }
388
+ }
389
+ }
390
+ catch (error) {
391
+ // Error is expected
392
+ }
393
+ node_assert_1.strict.equal(onErrorCalled, true);
394
+ node_assert_1.strict.equal(errorEventEmitted, true);
395
+ node_assert_1.strict.equal(onSuccessCalled, false);
396
+ node_assert_1.strict.equal(successEventEmitted, false);
397
+ });
398
+ });
399
+ });
400
+ (0, node_test_1.describe)('TurboEventEmitter', () => {
401
+ (0, node_test_1.it)('should emit overall-success event when upload-success event is emitted', () => {
402
+ const emitter = new events_js_1.TurboEventEmitter();
403
+ let overallSuccessCalled = false;
404
+ emitter.on('overall-success', () => {
405
+ overallSuccessCalled = true;
406
+ });
407
+ emitter.emit('upload-success');
408
+ node_assert_1.strict.equal(overallSuccessCalled, true);
409
+ });
410
+ (0, node_test_1.it)('should emit progress events when signing-progress event is emitted', () => {
411
+ const emitter = new events_js_1.TurboEventEmitter();
412
+ let overallProgressCalled = false;
413
+ let overallProgressPayload;
414
+ emitter.on('overall-progress', (event) => {
415
+ overallProgressCalled = true;
416
+ overallProgressPayload = event;
417
+ });
418
+ emitter.emit('signing-progress', {
419
+ processedBytes: 100,
420
+ totalBytes: 1000,
421
+ });
422
+ node_assert_1.strict.equal(overallProgressCalled, true);
423
+ node_assert_1.strict.equal(overallProgressPayload?.processedBytes, 50);
424
+ node_assert_1.strict.equal(overallProgressPayload?.totalBytes, 1000);
425
+ node_assert_1.strict.equal(overallProgressPayload?.step, 'signing');
426
+ });
427
+ (0, node_test_1.it)('should emit error events when signing-error event is emitted', () => {
428
+ const emitter = new events_js_1.TurboEventEmitter();
429
+ const testError = new Error('Signing error');
430
+ let overallErrorCalled = false;
431
+ let overallErrorPayload;
432
+ emitter.on('overall-error', (error) => {
433
+ overallErrorCalled = true;
434
+ overallErrorPayload = error;
435
+ });
436
+ emitter.emit('signing-error', testError);
437
+ node_assert_1.strict.equal(overallErrorCalled, true);
438
+ node_assert_1.strict.deepStrictEqual(overallErrorPayload, testError);
439
+ });
440
+ (0, node_test_1.it)('should emit error events when upload-error event is emitted', () => {
441
+ const emitter = new events_js_1.TurboEventEmitter();
442
+ const testError = new Error('Upload error');
443
+ let overallErrorCalled = false;
444
+ let overallErrorPayload;
445
+ emitter.on('overall-error', (event) => {
446
+ overallErrorCalled = true;
447
+ overallErrorPayload = event;
448
+ });
449
+ emitter.emit('upload-error', testError);
450
+ node_assert_1.strict.equal(overallErrorCalled, true);
451
+ node_assert_1.strict.deepStrictEqual(overallErrorPayload, testError);
452
+ });
453
+ (0, node_test_1.it)('should emit progress events when upload-progress event is emitted', () => {
454
+ const emitter = new events_js_1.TurboEventEmitter();
455
+ let overallProgressCalled = false;
456
+ let overallProgressPayload;
457
+ emitter.on('overall-progress', (event) => {
458
+ overallProgressCalled = true;
459
+ overallProgressPayload = event;
460
+ });
461
+ emitter.emit('upload-progress', {
462
+ processedBytes: 100,
463
+ totalBytes: 1000,
464
+ });
465
+ node_assert_1.strict.equal(overallProgressCalled, true);
466
+ node_assert_1.strict.equal(overallProgressPayload?.processedBytes, 500 + 100 / 2);
467
+ node_assert_1.strict.equal(overallProgressPayload?.totalBytes, 1000);
468
+ node_assert_1.strict.equal(overallProgressPayload?.step, 'upload');
469
+ });
470
+ });
@@ -26,16 +26,6 @@ class TurboHTTPService {
26
26
  axiosConfig: {
27
27
  baseURL: url,
28
28
  maxRedirects: 0, // prevents backpressure issues when uploading larger streams via https
29
- onUploadProgress: (progressEvent) => {
30
- this.logger.debug(`Uploading...`, {
31
- percent: Math.floor((progressEvent.progress ?? 0) * 100),
32
- loaded: `${progressEvent.loaded} bytes`,
33
- total: `${progressEvent.total} bytes`,
34
- });
35
- if (progressEvent.progress === 1) {
36
- this.logger.debug(`Upload complete!`);
37
- }
38
- },
39
29
  },
40
30
  retryConfig,
41
31
  logger: this.logger,
@@ -58,10 +48,11 @@ class TurboHTTPService {
58
48
  return data;
59
49
  }
60
50
  catch (error) {
61
- if (error instanceof axios_1.CanceledError) {
62
- throw error;
51
+ if (error instanceof axios_1.AxiosError &&
52
+ error.code === axios_1.AxiosError.ERR_CANCELED) {
53
+ throw new axios_1.CanceledError();
63
54
  }
64
- if (error instanceof axios_1.AxiosError) {
55
+ else if (error instanceof axios_1.AxiosError) {
65
56
  throw new errors_js_1.FailedRequestError(error.code ?? error.message, error.status);
66
57
  }
67
58
  throw error;
@@ -87,11 +87,12 @@ class TurboUnauthenticatedClient {
87
87
  /**
88
88
  * Uploads a signed data item to the Turbo Upload Service.
89
89
  */
90
- uploadSignedDataItem({ dataItemStreamFactory, dataItemSizeFactory, signal, }) {
90
+ uploadSignedDataItem({ dataItemStreamFactory, dataItemSizeFactory, signal, events, }) {
91
91
  return this.uploadService.uploadSignedDataItem({
92
92
  dataItemStreamFactory,
93
93
  dataItemSizeFactory,
94
94
  signal,
95
+ events,
95
96
  });
96
97
  }
97
98
  /**
@@ -142,18 +143,19 @@ class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
142
143
  /**
143
144
  * Signs and uploads raw data to the Turbo Upload Service.
144
145
  */
145
- upload({ data, dataItemOpts, signal, }) {
146
- return this.uploadService.upload({ data, dataItemOpts, signal });
146
+ upload({ data, dataItemOpts, signal, events, }) {
147
+ return this.uploadService.upload({ data, dataItemOpts, signal, events });
147
148
  }
148
149
  /**
149
150
  * Signs and uploads raw file data to the Turbo Upload Service.
150
151
  */
151
- uploadFile({ fileStreamFactory, fileSizeFactory, signal, dataItemOpts, }) {
152
+ uploadFile({ fileStreamFactory, fileSizeFactory, signal, dataItemOpts, events, }) {
152
153
  return this.uploadService.uploadFile({
153
154
  fileStreamFactory,
154
155
  fileSizeFactory,
155
156
  signal,
156
157
  dataItemOpts,
158
+ events,
157
159
  });
158
160
  }
159
161
  uploadFolder(p) {