@milaboratories/pl-drivers 1.2.16

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 (117) hide show
  1. package/README.md +18 -0
  2. package/dist/clients/download.d.ts +30 -0
  3. package/dist/clients/download.d.ts.map +1 -0
  4. package/dist/clients/helpers.d.ts +14 -0
  5. package/dist/clients/helpers.d.ts.map +1 -0
  6. package/dist/clients/logs.d.ts +26 -0
  7. package/dist/clients/logs.d.ts.map +1 -0
  8. package/dist/clients/ls_api.d.ts +13 -0
  9. package/dist/clients/ls_api.d.ts.map +1 -0
  10. package/dist/clients/progress.d.ts +25 -0
  11. package/dist/clients/progress.d.ts.map +1 -0
  12. package/dist/clients/upload.d.ts +38 -0
  13. package/dist/clients/upload.d.ts.map +1 -0
  14. package/dist/drivers/download_and_logs_blob.d.ts +106 -0
  15. package/dist/drivers/download_and_logs_blob.d.ts.map +1 -0
  16. package/dist/drivers/download_url.d.ts +70 -0
  17. package/dist/drivers/download_url.d.ts.map +1 -0
  18. package/dist/drivers/helpers/files_cache.d.ts +28 -0
  19. package/dist/drivers/helpers/files_cache.d.ts.map +1 -0
  20. package/dist/drivers/helpers/helpers.d.ts +34 -0
  21. package/dist/drivers/helpers/helpers.d.ts.map +1 -0
  22. package/dist/drivers/helpers/ls_list_entry.d.ts +49 -0
  23. package/dist/drivers/helpers/ls_list_entry.d.ts.map +1 -0
  24. package/dist/drivers/helpers/ls_storage_entry.d.ts +25 -0
  25. package/dist/drivers/helpers/ls_storage_entry.d.ts.map +1 -0
  26. package/dist/drivers/helpers/polling_ops.d.ts +8 -0
  27. package/dist/drivers/helpers/polling_ops.d.ts.map +1 -0
  28. package/dist/drivers/helpers/test_helpers.d.ts +2 -0
  29. package/dist/drivers/helpers/test_helpers.d.ts.map +1 -0
  30. package/dist/drivers/logs.d.ts +29 -0
  31. package/dist/drivers/logs.d.ts.map +1 -0
  32. package/dist/drivers/logs_stream.d.ts +50 -0
  33. package/dist/drivers/logs_stream.d.ts.map +1 -0
  34. package/dist/drivers/ls.d.ts +30 -0
  35. package/dist/drivers/ls.d.ts.map +1 -0
  36. package/dist/drivers/upload.d.ts +87 -0
  37. package/dist/drivers/upload.d.ts.map +1 -0
  38. package/dist/helpers/download.d.ts +15 -0
  39. package/dist/helpers/download.d.ts.map +1 -0
  40. package/dist/index.cjs +2 -0
  41. package/dist/index.cjs.map +1 -0
  42. package/dist/index.d.ts +15 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +4627 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts +36 -0
  47. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts.map +1 -0
  48. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts +103 -0
  49. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts.map +1 -0
  50. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts +42 -0
  51. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.d.ts.map +1 -0
  52. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts +165 -0
  53. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts.map +1 -0
  54. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts +44 -0
  55. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts.map +1 -0
  56. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts +171 -0
  57. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts.map +1 -0
  58. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts +122 -0
  59. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts.map +1 -0
  60. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts +315 -0
  61. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts.map +1 -0
  62. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts +98 -0
  63. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.d.ts.map +1 -0
  64. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts +337 -0
  65. package/dist/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts.map +1 -0
  66. package/dist/proto/google/api/http.d.ts +451 -0
  67. package/dist/proto/google/api/http.d.ts.map +1 -0
  68. package/dist/proto/google/protobuf/descriptor.d.ts +1646 -0
  69. package/dist/proto/google/protobuf/descriptor.d.ts.map +1 -0
  70. package/dist/proto/google/protobuf/duration.d.ts +106 -0
  71. package/dist/proto/google/protobuf/duration.d.ts.map +1 -0
  72. package/dist/proto/google/protobuf/timestamp.d.ts +151 -0
  73. package/dist/proto/google/protobuf/timestamp.d.ts.map +1 -0
  74. package/package.json +47 -0
  75. package/src/clients/download.test.ts +45 -0
  76. package/src/clients/download.ts +106 -0
  77. package/src/clients/helpers.ts +84 -0
  78. package/src/clients/logs.ts +68 -0
  79. package/src/clients/ls_api.ts +34 -0
  80. package/src/clients/progress.ts +86 -0
  81. package/src/clients/upload.test.ts +30 -0
  82. package/src/clients/upload.ts +199 -0
  83. package/src/drivers/download_and_logs_blob.ts +801 -0
  84. package/src/drivers/download_blob.test.ts +223 -0
  85. package/src/drivers/download_url.test.ts +90 -0
  86. package/src/drivers/download_url.ts +314 -0
  87. package/src/drivers/helpers/files_cache.test.ts +79 -0
  88. package/src/drivers/helpers/files_cache.ts +74 -0
  89. package/src/drivers/helpers/helpers.ts +136 -0
  90. package/src/drivers/helpers/ls_list_entry.test.ts +57 -0
  91. package/src/drivers/helpers/ls_list_entry.ts +152 -0
  92. package/src/drivers/helpers/ls_storage_entry.ts +135 -0
  93. package/src/drivers/helpers/polling_ops.ts +7 -0
  94. package/src/drivers/helpers/test_helpers.ts +5 -0
  95. package/src/drivers/logs.test.ts +337 -0
  96. package/src/drivers/logs.ts +214 -0
  97. package/src/drivers/logs_stream.ts +399 -0
  98. package/src/drivers/ls.test.ts +90 -0
  99. package/src/drivers/ls.ts +147 -0
  100. package/src/drivers/upload.test.ts +454 -0
  101. package/src/drivers/upload.ts +499 -0
  102. package/src/helpers/download.ts +43 -0
  103. package/src/index.ts +15 -0
  104. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +60 -0
  105. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +442 -0
  106. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +63 -0
  107. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +503 -0
  108. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +84 -0
  109. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +697 -0
  110. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +212 -0
  111. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +1036 -0
  112. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts +170 -0
  113. package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +1201 -0
  114. package/src/proto/google/api/http.ts +838 -0
  115. package/src/proto/google/protobuf/descriptor.ts +5173 -0
  116. package/src/proto/google/protobuf/duration.ts +272 -0
  117. package/src/proto/google/protobuf/timestamp.ts +354 -0
@@ -0,0 +1,499 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import {
3
+ ResourceId,
4
+ stringifyWithResourceId
5
+ } from '@milaboratories/pl-client';
6
+ import {
7
+ Watcher,
8
+ ChangeSource,
9
+ ComputableCtx,
10
+ Computable,
11
+ PollingComputableHooks
12
+ } from '@milaboratories/computable';
13
+ import {
14
+ MiLogger,
15
+ asyncPool,
16
+ TaskProcessor,
17
+ CallersCounter,
18
+ Signer
19
+ } from '@milaboratories/ts-helpers';
20
+ import * as sdk from '@milaboratories/pl-model-common';
21
+ import { ProgressStatus, ClientProgress } from '../clients/progress';
22
+ import {
23
+ ClientUpload,
24
+ MTimeError,
25
+ NoFileForUploading,
26
+ UnexpectedEOF
27
+ } from '../clients/upload';
28
+ import {
29
+ InferSnapshot,
30
+ isPlTreeEntry,
31
+ makeResourceSnapshot,
32
+ PlTreeEntry,
33
+ rsSchema
34
+ } from '@milaboratories/pl-tree';
35
+ import { scheduler } from 'node:timers/promises';
36
+ import { PollingOps } from './helpers/polling_ops';
37
+ import { z } from 'zod';
38
+
39
+ /** Options from BlobUpload resource that have to be passed to getProgress. */
40
+
41
+ const UploadOptsSchema = z.object({
42
+ localPath: z.string(),
43
+ pathSignature: z.string(),
44
+ modificationTime: z.string()
45
+ });
46
+
47
+ const ImportOptsSchema = z.union([UploadOptsSchema, z.object({})]);
48
+
49
+ export type UploadOpts = z.infer<typeof UploadOptsSchema>;
50
+
51
+ /** ResourceSnapshot that can be passed to GetProgressID */
52
+ export const UploadResourceSnapshot = rsSchema({
53
+ data: ImportOptsSchema,
54
+ fields: {
55
+ blob: false, // for BlobUpload
56
+ incarnation: false // for BlobIndex
57
+ }
58
+ });
59
+
60
+ export type UploadResourceSnapshot = InferSnapshot<
61
+ typeof UploadResourceSnapshot
62
+ >;
63
+
64
+ export type UploadDriverOps = PollingOps & {
65
+ /** How much parts of a file can be multipart-uploaded to S3 at once. */
66
+ nConcurrentPartUploads: number;
67
+ /** How much upload/indexing statuses of blobs can the driver ask
68
+ * from the platform gRPC at once. */
69
+ nConcurrentGetProgresses: number;
70
+ };
71
+
72
+ // TODO: add abort signal to Upload Tasks.
73
+
74
+ /** Uploads blobs in a queue and holds counters, so it can stop not-needed
75
+ * uploads.
76
+ * Handles both Index and Upload blobs,
77
+ * the client needs to pass concrete blobs from `handle` field. */
78
+ export class UploadDriver {
79
+ private readonly idToProgress: Map<ResourceId, ProgressUpdater> = new Map();
80
+
81
+ /** Holds a queue that upload blobs. */
82
+ private readonly uploadQueue: TaskProcessor;
83
+ private readonly hooks: PollingComputableHooks;
84
+
85
+ constructor(
86
+ private readonly logger: MiLogger,
87
+ private readonly signer: Signer,
88
+ private readonly clientBlob: ClientUpload,
89
+ private readonly clientProgress: ClientProgress,
90
+ private readonly opts: UploadDriverOps = {
91
+ nConcurrentPartUploads: 10,
92
+ nConcurrentGetProgresses: 10,
93
+ pollingInterval: 1000,
94
+ stopPollingDelay: 1000
95
+ }
96
+ ) {
97
+ this.uploadQueue = new TaskProcessor(this.logger, 1, {
98
+ type: 'exponentialWithMaxDelayBackoff',
99
+ initialDelay: 20,
100
+ maxDelay: 15000, // 15 seconds
101
+ backoffMultiplier: 1.5,
102
+ jitter: 0.5
103
+ });
104
+
105
+ this.hooks = new PollingComputableHooks(
106
+ () => this.startUpdating(),
107
+ () => this.stopUpdating(),
108
+ { stopDebounce: opts.stopPollingDelay },
109
+ (resolve, reject) => this.scheduleOnNextState(resolve, reject)
110
+ );
111
+ }
112
+
113
+ /** Returns a progress id and schedules an upload task if it's necessary. */
114
+ getProgressId(
115
+ res: UploadResourceSnapshot | PlTreeEntry
116
+ ): Computable<sdk.ImportProgress>;
117
+ getProgressId(
118
+ res: UploadResourceSnapshot | PlTreeEntry,
119
+ ctx: ComputableCtx
120
+ ): sdk.ImportProgress;
121
+ getProgressId(
122
+ res: UploadResourceSnapshot | PlTreeEntry,
123
+ ctx?: ComputableCtx
124
+ ): Computable<sdk.ImportProgress> | sdk.ImportProgress {
125
+ if (ctx == undefined)
126
+ return Computable.make((ctx) => this.getProgressId(res, ctx));
127
+
128
+ const rInfo: UploadResourceSnapshot = isPlTreeEntry(res)
129
+ ? makeResourceSnapshot(res, UploadResourceSnapshot, ctx)
130
+ : res;
131
+
132
+ const callerId = randomUUID();
133
+ ctx.attacheHooks(this.hooks);
134
+ ctx.addOnDestroy(() => this.release(rInfo.id, callerId));
135
+
136
+ const result = this.getProgressIdNoCtx(ctx.watcher, rInfo, callerId);
137
+ if (!isProgressStable(result)) {
138
+ ctx.markUnstable(
139
+ `upload/index progress was got, but it's not stable: ${result}`
140
+ );
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ private getProgressIdNoCtx(
147
+ w: Watcher,
148
+ res: UploadResourceSnapshot,
149
+ callerId: string
150
+ ): sdk.ImportProgress {
151
+ const blobExists =
152
+ res.fields.blob != undefined || res.fields.incarnation != undefined;
153
+
154
+ const value = this.idToProgress.get(res.id);
155
+
156
+ if (value != undefined) {
157
+ value.attach(w, callerId);
158
+ return value.mustGetProgress(blobExists);
159
+ }
160
+
161
+ const newValue = new ProgressUpdater(
162
+ this.logger,
163
+ this.clientBlob,
164
+ this.clientProgress,
165
+ this.opts.nConcurrentPartUploads,
166
+ this.signer,
167
+ res
168
+ );
169
+
170
+ this.idToProgress.set(res.id, newValue);
171
+ newValue.attach(w, callerId);
172
+
173
+ if (newValue.progress.isUpload && newValue.progress.isUploadSignMatch)
174
+ this.uploadQueue.push({
175
+ fn: () => newValue.uploadBlobTask(),
176
+ recoverableErrorPredicate: (e) => !nonRecoverableError(e)
177
+ });
178
+
179
+ return newValue.mustGetProgress(blobExists);
180
+ }
181
+
182
+ /** Decrement counters for the file and remove an uploading if counter == 0. */
183
+ private async release(id: ResourceId, callerId: string) {
184
+ const value = this.idToProgress.get(id);
185
+ if (value === undefined) return;
186
+
187
+ const deleted = value.decCounter(callerId);
188
+ if (deleted) this.idToProgress.delete(id);
189
+ }
190
+
191
+ /** Must be called when the driver is closing. */
192
+ public async releaseAll() {
193
+ this.uploadQueue.stop();
194
+ }
195
+
196
+ private scheduledOnNextState: ScheduledRefresh[] = [];
197
+
198
+ private scheduleOnNextState(
199
+ resolve: () => void,
200
+ reject: (err: any) => void
201
+ ): void {
202
+ this.scheduledOnNextState.push({ resolve, reject });
203
+ }
204
+
205
+ /** Called from observer */
206
+ private startUpdating(): void {
207
+ this.keepRunning = true;
208
+ if (this.currentLoop === undefined) this.currentLoop = this.mainLoop();
209
+ }
210
+
211
+ /** Called from observer */
212
+ private stopUpdating(): void {
213
+ this.keepRunning = false;
214
+ }
215
+
216
+ /** If true, main loop will continue polling pl state. */
217
+ private keepRunning = false;
218
+ /** Actual state of main loop. */
219
+ private currentLoop: Promise<void> | undefined = undefined;
220
+
221
+ private async mainLoop() {
222
+ while (this.keepRunning) {
223
+ const toNotify = this.scheduledOnNextState;
224
+ this.scheduledOnNextState = [];
225
+
226
+ try {
227
+ await asyncPool(
228
+ this.opts.nConcurrentGetProgresses,
229
+ this.getAllNotDoneProgresses().map(
230
+ (p) => async () => await p.updateStatus()
231
+ )
232
+ );
233
+
234
+ toNotify.forEach((n) => n.resolve());
235
+ } catch (e: any) {
236
+ console.error(e);
237
+ toNotify.forEach((n) => n.reject(e));
238
+ }
239
+
240
+ if (!this.keepRunning) break;
241
+ await scheduler.wait(this.opts.pollingInterval);
242
+ }
243
+
244
+ this.currentLoop = undefined;
245
+ }
246
+
247
+ private getAllNotDoneProgresses(): Array<ProgressUpdater> {
248
+ return Array.from(this.idToProgress.entries())
249
+ .filter(([_, p]) => !isProgressStable(p.progress))
250
+ .map(([_, p]) => p);
251
+ }
252
+ }
253
+
254
+ /** Holds all info needed to upload a file and a status of uploadong
255
+ * and indexing. Also, has a method to update a status of the progress.
256
+ * And holds a change source. */
257
+ class ProgressUpdater {
258
+ private readonly change: ChangeSource = new ChangeSource();
259
+ private readonly counter: CallersCounter = new CallersCounter();
260
+
261
+ public progress: sdk.ImportProgress;
262
+ private uploadOpts?: UploadOpts;
263
+ public uploadingTerminallyFailed?: boolean;
264
+
265
+ constructor(
266
+ private readonly logger: MiLogger,
267
+ private readonly clientBlob: ClientUpload,
268
+ private readonly clientProgress: ClientProgress,
269
+ private readonly nConcurrentPartsUpload: number,
270
+ signer: Signer,
271
+
272
+ public readonly res: UploadResourceSnapshot
273
+ ) {
274
+ const isUpload = res.type.name.startsWith('BlobUpload');
275
+ let isUploadSignMatch: boolean | undefined;
276
+ if (isUpload) {
277
+ this.uploadOpts = importToUploadOpts(res);
278
+ isUploadSignMatch = isSignMatch(
279
+ signer,
280
+ this.uploadOpts.localPath,
281
+ this.uploadOpts.pathSignature
282
+ );
283
+ }
284
+
285
+ this.progress = {
286
+ done: false,
287
+ status: undefined,
288
+ isUpload: isUpload,
289
+ isUploadSignMatch: isUploadSignMatch,
290
+ lastError: undefined
291
+ };
292
+ }
293
+
294
+ public mustGetProgress(blobExists: boolean) {
295
+ if (blobExists) {
296
+ this.setDone(blobExists);
297
+
298
+ return this.progress;
299
+ }
300
+
301
+ if (this.uploadingTerminallyFailed) {
302
+ this.logger.error(
303
+ `Uploading terminally failed: ${this.progress.lastError}`
304
+ );
305
+ throw new Error(this.progress.lastError);
306
+ }
307
+
308
+ return this.progress;
309
+ }
310
+
311
+ public attach(w: Watcher, callerId: string) {
312
+ this.change.attachWatcher(w);
313
+ this.counter.inc(callerId);
314
+ }
315
+
316
+ public decCounter(callerId: string) {
317
+ return this.counter.dec(callerId);
318
+ }
319
+
320
+ /** Uploads a blob if it's not BlobIndex. */
321
+ async uploadBlobTask() {
322
+ try {
323
+ await this.uploadBlob();
324
+ } catch (e: any) {
325
+ this.setLastError(e);
326
+
327
+ if (isResourceWasDeletedError(e)) {
328
+ this.logger.warn(`resource was deleted while uploading a blob: ${e}`);
329
+ this.change.markChanged();
330
+ this.setDone(true);
331
+
332
+ return;
333
+ }
334
+
335
+ this.logger.error(`error while uploading a blob: ${e}`);
336
+ this.change.markChanged();
337
+
338
+ if (nonRecoverableError(e)) this.terminateWithError(e);
339
+
340
+ throw e;
341
+ }
342
+ }
343
+
344
+ /** Uploads a blob using client. */
345
+ private async uploadBlob() {
346
+ if (this.counter.isZero()) return;
347
+ const parts = await this.clientBlob.initUpload(this.res);
348
+
349
+ this.logger.info(
350
+ `start to upload blob ${this.res.id}, parts count: ${parts.length}`
351
+ );
352
+
353
+ const partUploadFn = (part: bigint) => async () => {
354
+ if (this.counter.isZero()) return;
355
+ await this.clientBlob.partUpload(
356
+ this.res,
357
+ this.uploadOpts!.localPath,
358
+ part,
359
+ parts.length,
360
+ BigInt(this.uploadOpts!.modificationTime)
361
+ );
362
+ };
363
+
364
+ await asyncPool(this.nConcurrentPartsUpload, parts.map(partUploadFn));
365
+
366
+ if (this.counter.isZero()) return;
367
+ await this.clientBlob.finalizeUpload(this.res);
368
+
369
+ this.logger.info(`uploading of resource ${this.res.id} finished.`);
370
+ this.change.markChanged();
371
+ }
372
+
373
+ private terminateWithError(e: unknown) {
374
+ this.progress.lastError = String(e);
375
+ this.progress.done = false;
376
+ this.uploadingTerminallyFailed = true;
377
+ }
378
+
379
+ private setLastError(e: unknown) {
380
+ this.progress.lastError = String(e);
381
+ }
382
+
383
+ private setDone(done: boolean) {
384
+ this.progress.done = done;
385
+ }
386
+
387
+ async updateStatus() {
388
+ try {
389
+ const status = await this.clientProgress.getStatus(this.res);
390
+
391
+ const oldStatus = this.progress.status;
392
+ this.progress.status = protoToStatus(status);
393
+ this.setDone(status.done);
394
+
395
+ if (status.done || status.progress != oldStatus?.progress)
396
+ this.change.markChanged();
397
+ } catch (e: any) {
398
+ this.setLastError(e);
399
+
400
+ if (e.name == 'RpcError' && e.code == 'DEADLINE_EXCEEDED') {
401
+ this.logger.warn(
402
+ `deadline exceeded while getting a status of BlobImport`
403
+ );
404
+ return;
405
+ }
406
+
407
+ if (isResourceWasDeletedError(e)) {
408
+ this.logger.warn(
409
+ `resource was not found while updating a status of BlobImport: ${e}, ${stringifyWithResourceId(this.res)}`
410
+ );
411
+ this.change.markChanged();
412
+ this.setDone(true);
413
+ return;
414
+ }
415
+
416
+ this.logger.error(`error while updating a status of BlobImport: ${e}`);
417
+ this.change.markChanged();
418
+ this.terminateWithError(e);
419
+ }
420
+ }
421
+ }
422
+
423
+ function isProgressStable(p: sdk.ImportProgress) {
424
+ return (
425
+ p.done &&
426
+ p.status !== undefined &&
427
+ p.status !== null &&
428
+ p.status.progress >= 1.0
429
+ );
430
+ }
431
+
432
+ export function importToUploadOpts(res: UploadResourceSnapshot): UploadOpts {
433
+ if (res.data == undefined || !('modificationTime' in res.data)) {
434
+ throw new Error(
435
+ 'no upload options in BlobUpload resource data: ' +
436
+ stringifyWithResourceId(res.data)
437
+ );
438
+ }
439
+
440
+ const opts = res.data;
441
+ if (opts.modificationTime === undefined) {
442
+ throw new Error(
443
+ 'no modification time in data: ' + stringifyWithResourceId(res.data)
444
+ );
445
+ }
446
+ if (opts.localPath === undefined) {
447
+ throw new Error(
448
+ 'no local path in data: ' + stringifyWithResourceId(res.data)
449
+ );
450
+ }
451
+ if (opts.pathSignature === undefined) {
452
+ throw new Error(
453
+ 'no path signature in data: ' + stringifyWithResourceId(res.data)
454
+ );
455
+ }
456
+
457
+ return {
458
+ modificationTime: opts.modificationTime,
459
+ localPath: opts.localPath,
460
+ pathSignature: opts.pathSignature
461
+ };
462
+ }
463
+
464
+ function protoToStatus(proto: ProgressStatus): sdk.ImportStatus {
465
+ return {
466
+ progress: proto.progress ?? 0,
467
+ bytesProcessed: Number(proto.bytesProcessed),
468
+ bytesTotal: Number(proto.bytesTotal)
469
+ };
470
+ }
471
+
472
+ function isSignMatch(signer: Signer, path: string, signature: string): boolean {
473
+ try {
474
+ signer.verify(path, signature);
475
+ return true;
476
+ } catch (e) {
477
+ return false;
478
+ }
479
+ }
480
+
481
+ function nonRecoverableError(e: any) {
482
+ return (
483
+ e instanceof MTimeError ||
484
+ e instanceof UnexpectedEOF ||
485
+ e instanceof NoFileForUploading
486
+ );
487
+ }
488
+
489
+ function isResourceWasDeletedError(e: any) {
490
+ return (
491
+ e.name == 'RpcError' &&
492
+ (e.code == 'NOT_FOUND' || e.code == 'ABORTED' || e.code == 'ALREADY_EXISTS')
493
+ );
494
+ }
495
+
496
+ type ScheduledRefresh = {
497
+ resolve: () => void;
498
+ reject: (err: any) => void;
499
+ };
@@ -0,0 +1,43 @@
1
+ import { Dispatcher, request } from 'undici';
2
+ import { Readable } from 'node:stream';
3
+ import { ReadableStream } from 'node:stream/web';
4
+
5
+ export interface DownloadResponse {
6
+ content: ReadableStream;
7
+ size: number;
8
+ }
9
+
10
+ /** Throws when a status code of the downloading URL was in range [400, 500). */
11
+ export class NetworkError400 extends Error {}
12
+
13
+ export class DownloadHelper {
14
+ constructor(public readonly httpClient: Dispatcher) {}
15
+
16
+ async downloadRemoteFile(
17
+ url: string,
18
+ reqHeaders: Record<string, string>,
19
+ signal?: AbortSignal
20
+ ): Promise<DownloadResponse> {
21
+ const { statusCode, body, headers } = await request(url, {
22
+ dispatcher: this.httpClient,
23
+ headers: reqHeaders,
24
+ signal
25
+ });
26
+
27
+ if (400 <= statusCode && statusCode < 500) {
28
+ throw new NetworkError400(
29
+ `Http error: statusCode: ${statusCode} url: ${url.toString()}`
30
+ );
31
+ }
32
+ if (statusCode != 200) {
33
+ throw Error(
34
+ `Http error: statusCode: ${statusCode} url: ${url.toString()}`
35
+ );
36
+ }
37
+
38
+ return {
39
+ content: Readable.toWeb(body),
40
+ size: Number(headers['content-length'])
41
+ };
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export * from './clients/upload';
2
+ export * from './clients/progress';
3
+ export * from './clients/download';
4
+ export * from './clients/ls_api';
5
+ export * from './clients/logs';
6
+ export * from './clients/helpers';
7
+
8
+ export * from './drivers/download_and_logs_blob';
9
+ export * from './drivers/upload';
10
+ export * from './drivers/logs_stream';
11
+ export * from './drivers/logs';
12
+ export * from './drivers/download_url';
13
+ export * from './drivers/ls';
14
+ export * from './drivers/helpers/helpers';
15
+ export * from './drivers/helpers/polling_ops';
@@ -0,0 +1,60 @@
1
+ // @generated by protobuf-ts 2.9.4 with parameter client_generic,optimize_speed,generate_dependencies,force_server_none
2
+ // @generated from protobuf file "github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.proto" (package "MiLaboratories.Controller.Shared", syntax proto3)
3
+ // tslint:disable
4
+ import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
5
+ import type { ServiceInfo } from '@protobuf-ts/runtime-rpc';
6
+ import { Download } from './protocol';
7
+ import { stackIntercept } from '@protobuf-ts/runtime-rpc';
8
+ import type { DownloadAPI_GetDownloadURL_Response } from './protocol';
9
+ import type { DownloadAPI_GetDownloadURL_Request } from './protocol';
10
+ import type { UnaryCall } from '@protobuf-ts/runtime-rpc';
11
+ import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
12
+ /**
13
+ *
14
+ * Download provides access to any data, that can be downloaded via network.
15
+ *
16
+ *
17
+ * @generated from protobuf service MiLaboratories.Controller.Shared.Download
18
+ */
19
+ export interface IDownloadClient {
20
+ /**
21
+ * @generated from protobuf rpc: GetDownloadURL(MiLaboratories.Controller.Shared.DownloadAPI.GetDownloadURL.Request) returns (MiLaboratories.Controller.Shared.DownloadAPI.GetDownloadURL.Response);
22
+ */
23
+ getDownloadURL(
24
+ input: DownloadAPI_GetDownloadURL_Request,
25
+ options?: RpcOptions
26
+ ): UnaryCall<
27
+ DownloadAPI_GetDownloadURL_Request,
28
+ DownloadAPI_GetDownloadURL_Response
29
+ >;
30
+ }
31
+ /**
32
+ *
33
+ * Download provides access to any data, that can be downloaded via network.
34
+ *
35
+ *
36
+ * @generated from protobuf service MiLaboratories.Controller.Shared.Download
37
+ */
38
+ export class DownloadClient implements IDownloadClient, ServiceInfo {
39
+ typeName = Download.typeName;
40
+ methods = Download.methods;
41
+ options = Download.options;
42
+ constructor(private readonly _transport: RpcTransport) {}
43
+ /**
44
+ * @generated from protobuf rpc: GetDownloadURL(MiLaboratories.Controller.Shared.DownloadAPI.GetDownloadURL.Request) returns (MiLaboratories.Controller.Shared.DownloadAPI.GetDownloadURL.Response);
45
+ */
46
+ getDownloadURL(
47
+ input: DownloadAPI_GetDownloadURL_Request,
48
+ options?: RpcOptions
49
+ ): UnaryCall<
50
+ DownloadAPI_GetDownloadURL_Request,
51
+ DownloadAPI_GetDownloadURL_Response
52
+ > {
53
+ const method = this.methods[0],
54
+ opt = this._transport.mergeOptions(options);
55
+ return stackIntercept<
56
+ DownloadAPI_GetDownloadURL_Request,
57
+ DownloadAPI_GetDownloadURL_Response
58
+ >('unary', this._transport, method, opt, input);
59
+ }
60
+ }