@milaboratories/pl-drivers 1.10.9 → 1.10.10
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.
- package/dist/clients/download.cjs +1 -0
- package/dist/clients/download.cjs.map +1 -1
- package/dist/clients/download.d.ts +4 -3
- package/dist/clients/download.d.ts.map +1 -1
- package/dist/clients/download.js +1 -0
- package/dist/clients/download.js.map +1 -1
- package/dist/drivers/download_blob/download_blob.cjs +38 -12
- package/dist/drivers/download_blob/download_blob.cjs.map +1 -1
- package/dist/drivers/download_blob/download_blob.d.ts +8 -1
- package/dist/drivers/download_blob/download_blob.d.ts.map +1 -1
- package/dist/drivers/download_blob/download_blob.js +39 -13
- package/dist/drivers/download_blob/download_blob.js.map +1 -1
- package/dist/drivers/download_blob/sparse_cache/ranges.d.ts.map +1 -1
- package/dist/drivers/helpers/helpers.cjs.map +1 -1
- package/dist/drivers/helpers/helpers.d.ts.map +1 -1
- package/dist/drivers/helpers/helpers.js.map +1 -1
- package/dist/drivers/helpers/read_file.cjs +19 -11
- package/dist/drivers/helpers/read_file.cjs.map +1 -1
- package/dist/drivers/helpers/read_file.d.ts +6 -1
- package/dist/drivers/helpers/read_file.d.ts.map +1 -1
- package/dist/drivers/helpers/read_file.js +18 -11
- package/dist/drivers/helpers/read_file.js.map +1 -1
- package/dist/helpers/download.cjs +3 -0
- package/dist/helpers/download.cjs.map +1 -1
- package/dist/helpers/download.d.ts +2 -6
- package/dist/helpers/download.d.ts.map +1 -1
- package/dist/helpers/download.js +3 -0
- package/dist/helpers/download.js.map +1 -1
- package/package.json +8 -8
- package/src/clients/download.ts +5 -4
- package/src/drivers/download_blob/download_blob.ts +68 -19
- package/src/drivers/helpers/helpers.ts +0 -9
- package/src/drivers/helpers/read_file.ts +29 -11
- package/src/helpers/download.ts +7 -7
|
@@ -12,6 +12,8 @@ import { resourceIdToString, stringifyWithResourceId } from '@milaboratories/pl-
|
|
|
12
12
|
import type {
|
|
13
13
|
AnyLogHandle,
|
|
14
14
|
BlobDriver,
|
|
15
|
+
ContentHandler,
|
|
16
|
+
GetContentOptions,
|
|
15
17
|
LocalBlobHandle,
|
|
16
18
|
LocalBlobHandleAndSize,
|
|
17
19
|
ReadyLogHandle,
|
|
@@ -39,11 +41,11 @@ import * as fsp from 'node:fs/promises';
|
|
|
39
41
|
import * as os from 'node:os';
|
|
40
42
|
import * as path from 'node:path';
|
|
41
43
|
import * as readline from 'node:readline/promises';
|
|
42
|
-
import { Writable } from 'node:stream';
|
|
43
44
|
import { buffer } from 'node:stream/consumers';
|
|
45
|
+
import { Readable } from 'node:stream';
|
|
44
46
|
import type { ClientDownload } from '../../clients/download';
|
|
45
47
|
import type { ClientLogs } from '../../clients/logs';
|
|
46
|
-
import {
|
|
48
|
+
import { withFileContent } from '../helpers/read_file';
|
|
47
49
|
import {
|
|
48
50
|
isLocalBlobHandle,
|
|
49
51
|
newLocalHandle,
|
|
@@ -293,13 +295,55 @@ export class DownloadDriver implements BlobDriver {
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
/** Gets a content of a blob by a handle. */
|
|
296
|
-
public async getContent(handle: LocalBlobHandle | RemoteBlobHandle
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
public async getContent(handle: LocalBlobHandle | RemoteBlobHandle): Promise<Uint8Array>;
|
|
299
|
+
public async getContent(
|
|
300
|
+
handle: LocalBlobHandle | RemoteBlobHandle,
|
|
301
|
+
options?: GetContentOptions,
|
|
302
|
+
): Promise<Uint8Array>;
|
|
303
|
+
/** @deprecated Use {@link getContent} with {@link GetContentOptions} instead */
|
|
304
|
+
public async getContent(
|
|
305
|
+
handle: LocalBlobHandle | RemoteBlobHandle,
|
|
306
|
+
range?: RangeBytes,
|
|
307
|
+
): Promise<Uint8Array>;
|
|
308
|
+
public async getContent(
|
|
309
|
+
handle: LocalBlobHandle | RemoteBlobHandle,
|
|
310
|
+
optionsOrRange?: GetContentOptions | RangeBytes,
|
|
311
|
+
): Promise<Uint8Array> {
|
|
312
|
+
let options: GetContentOptions = {};
|
|
313
|
+
if (typeof optionsOrRange === 'object' && optionsOrRange !== null) {
|
|
314
|
+
if ('range' in optionsOrRange) {
|
|
315
|
+
options = optionsOrRange;
|
|
316
|
+
} else {
|
|
317
|
+
const range = optionsOrRange as RangeBytes;
|
|
318
|
+
validateRangeBytes(range, `getContent`);
|
|
319
|
+
options = { range };
|
|
320
|
+
}
|
|
299
321
|
}
|
|
300
322
|
|
|
323
|
+
return await this.withContent(handle, {
|
|
324
|
+
...options,
|
|
325
|
+
handler: async (content) => {
|
|
326
|
+
const chunks: Uint8Array[] = [];
|
|
327
|
+
for await (const chunk of content) {
|
|
328
|
+
options.signal?.throwIfAborted();
|
|
329
|
+
chunks.push(chunk);
|
|
330
|
+
}
|
|
331
|
+
return Buffer.concat(chunks);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Gets a content stream of a blob by a handle and calls handler with it. */
|
|
337
|
+
public async withContent<T>(
|
|
338
|
+
handle: LocalBlobHandle | RemoteBlobHandle,
|
|
339
|
+
options: GetContentOptions & {
|
|
340
|
+
handler: ContentHandler<T>;
|
|
341
|
+
},
|
|
342
|
+
): Promise<T> {
|
|
343
|
+
const { range, signal, handler } = options;
|
|
344
|
+
|
|
301
345
|
if (isLocalBlobHandle(handle)) {
|
|
302
|
-
return await
|
|
346
|
+
return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });
|
|
303
347
|
}
|
|
304
348
|
|
|
305
349
|
if (isRemoteBlobHandle(handle)) {
|
|
@@ -307,19 +351,24 @@ export class DownloadDriver implements BlobDriver {
|
|
|
307
351
|
|
|
308
352
|
const key = blobKey(result.info.id);
|
|
309
353
|
const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
async (content) =>
|
|
354
|
+
signal?.throwIfAborted();
|
|
355
|
+
|
|
356
|
+
if (filePath) return await withFileContent({ path: filePath, range, signal, handler });
|
|
357
|
+
|
|
358
|
+
return await this.clientDownload.withBlobContent(
|
|
359
|
+
result.info,
|
|
360
|
+
{ signal },
|
|
361
|
+
options,
|
|
362
|
+
async (content, size) => {
|
|
363
|
+
const [handlerStream, cacheStream] = content.tee();
|
|
364
|
+
|
|
365
|
+
const handlerPromise = handler(handlerStream, size);
|
|
366
|
+
const _cachePromise = buffer(cacheStream)
|
|
367
|
+
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data));
|
|
368
|
+
|
|
369
|
+
return await handlerPromise;
|
|
370
|
+
}
|
|
319
371
|
);
|
|
320
|
-
await this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data);
|
|
321
|
-
|
|
322
|
-
return data;
|
|
323
372
|
}
|
|
324
373
|
|
|
325
374
|
throw new Error('Malformed remote handle');
|
|
@@ -339,7 +388,7 @@ export class DownloadDriver implements BlobDriver {
|
|
|
339
388
|
|
|
340
389
|
return Computable.make((ctx) =>
|
|
341
390
|
this.getDownloadedBlob(res, ctx), {
|
|
342
|
-
postprocessValue: (v) => v ? this.getContent(v.handle, range) : undefined
|
|
391
|
+
postprocessValue: (v) => v ? this.getContent(v.handle, { range }) : undefined
|
|
343
392
|
}
|
|
344
393
|
).withStableType()
|
|
345
394
|
}
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BasicResourceData,
|
|
3
|
-
getField,
|
|
4
|
-
isNullResourceId,
|
|
5
|
-
PlClient,
|
|
6
|
-
ResourceId,
|
|
7
|
-
valErr,
|
|
8
|
-
} from '@milaboratories/pl-client';
|
|
9
|
-
|
|
10
1
|
/** Throws when a driver gets a resource with a wrong resource type. */
|
|
11
2
|
export class WrongResourceTypeError extends Error {
|
|
12
3
|
name = 'WrongResourceTypeError';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ConcurrencyLimitingExecutor } from '@milaboratories/ts-helpers';
|
|
2
2
|
import type { RangeBytes } from '@milaboratories/pl-model-common';
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
|
-
import
|
|
4
|
+
import * as fsp from 'node:fs/promises';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
5
6
|
|
|
6
7
|
// Global concurrency limiter for file reads - limit to 32 parallel reads
|
|
7
8
|
const fileReadLimiter = new ConcurrencyLimitingExecutor(32);
|
|
@@ -10,20 +11,37 @@ const fileReadLimiter = new ConcurrencyLimitingExecutor(32);
|
|
|
10
11
|
* Reads file content with concurrency limiting and proper error handling.
|
|
11
12
|
* Ensures file descriptors are properly cleaned up even in error cases.
|
|
12
13
|
*/
|
|
13
|
-
export async function
|
|
14
|
+
export async function withFileContent<T>({
|
|
15
|
+
path,
|
|
16
|
+
range,
|
|
17
|
+
signal,
|
|
18
|
+
handler,
|
|
19
|
+
}: {
|
|
20
|
+
path: string;
|
|
21
|
+
range?: RangeBytes;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
handler: (content: ReadableStream, size: number) => Promise<T>;
|
|
24
|
+
}): Promise<T> {
|
|
14
25
|
return await fileReadLimiter.run(async () => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
26
|
+
const readOps = {
|
|
27
|
+
start: range?.from,
|
|
28
|
+
end: range?.to !== undefined ? range.to - 1 : undefined,
|
|
29
|
+
signal: signal,
|
|
30
|
+
};
|
|
21
31
|
let stream: fs.ReadStream | undefined;
|
|
32
|
+
let handlerSuccess = false;
|
|
33
|
+
|
|
22
34
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
const stat = await fsp.stat(path);
|
|
36
|
+
stream = fs.createReadStream(path, readOps);
|
|
37
|
+
const webStream = Readable.toWeb(stream);
|
|
38
|
+
|
|
39
|
+
const result = await handler(webStream, stat.size);
|
|
40
|
+
handlerSuccess = true;
|
|
41
|
+
return result;
|
|
25
42
|
} catch (error) {
|
|
26
|
-
|
|
43
|
+
// Cleanup on error (including handler errors)
|
|
44
|
+
if (!handlerSuccess && stream && !stream.destroyed) {
|
|
27
45
|
stream.destroy();
|
|
28
46
|
}
|
|
29
47
|
throw error;
|
package/src/helpers/download.ts
CHANGED
|
@@ -5,12 +5,7 @@ import { request } from 'undici';
|
|
|
5
5
|
import { Readable } from 'node:stream';
|
|
6
6
|
import type { ReadableStream } from 'node:stream/web';
|
|
7
7
|
import { text } from 'node:stream/consumers';
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
export interface DownloadOps {
|
|
11
|
-
signal?: AbortSignal;
|
|
12
|
-
range?: RangeBytes;
|
|
13
|
-
}
|
|
8
|
+
import type { GetContentOptions } from '@milaboratories/pl-model-common';
|
|
14
9
|
|
|
15
10
|
export type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;
|
|
16
11
|
|
|
@@ -25,7 +20,7 @@ export class RemoteFileDownloader {
|
|
|
25
20
|
async withContent<T>(
|
|
26
21
|
url: string,
|
|
27
22
|
reqHeaders: Record<string, string>,
|
|
28
|
-
ops:
|
|
23
|
+
ops: GetContentOptions,
|
|
29
24
|
handler: ContentHandler<T>,
|
|
30
25
|
): Promise<T> {
|
|
31
26
|
const headers = { ...reqHeaders };
|
|
@@ -40,14 +35,19 @@ export class RemoteFileDownloader {
|
|
|
40
35
|
headers,
|
|
41
36
|
signal: ops.signal,
|
|
42
37
|
});
|
|
38
|
+
ops.signal?.throwIfAborted();
|
|
43
39
|
|
|
44
40
|
const webBody = Readable.toWeb(body);
|
|
45
41
|
let handlerSuccess = false;
|
|
46
42
|
|
|
47
43
|
try {
|
|
48
44
|
await checkStatusCodeOk(statusCode, webBody, url);
|
|
45
|
+
ops.signal?.throwIfAborted();
|
|
46
|
+
|
|
49
47
|
const size = Number(responseHeaders['content-length']);
|
|
50
48
|
const result = await handler(webBody, size);
|
|
49
|
+
ops.signal?.throwIfAborted();
|
|
50
|
+
|
|
51
51
|
handlerSuccess = true;
|
|
52
52
|
return result;
|
|
53
53
|
} catch (error) {
|