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