@flowscripter/pluggable-io-framework 1.2.7 → 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.
- package/README.md +47 -178
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/src/concurrency/AsyncChannel.d.ts +9 -0
- package/dist/src/concurrency/AsyncChannel.d.ts.map +1 -0
- package/dist/src/concurrency/AsyncChannel.js +48 -0
- package/dist/src/{ConcurrencyLimiter.d.ts → concurrency/ConcurrencyLimiter.d.ts} +1 -12
- package/dist/src/concurrency/ConcurrencyLimiter.d.ts.map +1 -0
- package/dist/src/concurrency/ConcurrencyLimiter.js +64 -0
- package/dist/src/concurrency/mapAsyncIterableConcurrently.d.ts +13 -0
- package/dist/src/concurrency/mapAsyncIterableConcurrently.d.ts.map +1 -0
- package/dist/src/concurrency/mapAsyncIterableConcurrently.js +61 -0
- package/dist/src/decorators/locallyCached.d.ts +9 -10
- package/dist/src/decorators/locallyCached.d.ts.map +1 -1
- package/dist/src/decorators/locallyCached.js +23 -16
- package/dist/src/decorators/seekable.d.ts +4 -4
- package/dist/src/decorators/seekable.d.ts.map +1 -1
- package/dist/src/decorators/seekable.js +2 -2
- package/dist/src/registry/ProviderRegistry.d.ts +52 -0
- package/dist/src/registry/ProviderRegistry.d.ts.map +1 -0
- package/dist/src/registry/ProviderRegistry.js +148 -0
- package/dist/src/registry/detectProtocol.d.ts +8 -0
- package/dist/src/registry/detectProtocol.d.ts.map +1 -0
- package/dist/src/registry/detectProtocol.js +15 -0
- package/dist/src/registry/negotiateTransfer.d.ts +29 -0
- package/dist/src/registry/negotiateTransfer.d.ts.map +1 -0
- package/dist/src/registry/negotiateTransfer.js +69 -0
- package/dist/src/retry/RetryOptions.d.ts +19 -0
- package/dist/src/retry/RetryOptions.d.ts.map +1 -0
- package/dist/src/retry/RetryOptions.js +9 -0
- package/dist/src/{withRetry.d.ts → retry/withRetry.d.ts} +1 -5
- package/dist/src/retry/withRetry.d.ts.map +1 -0
- package/dist/src/{withRetry.js → retry/withRetry.js} +2 -5
- package/dist/src/transfer/EntryContext.d.ts +16 -0
- package/dist/src/transfer/EntryContext.d.ts.map +1 -0
- package/dist/src/transfer/EntryContext.js +1 -0
- package/dist/src/transfer/TransferOptions.d.ts +45 -0
- package/dist/src/transfer/TransferOptions.d.ts.map +1 -0
- package/dist/src/transfer/TransferOptions.js +1 -0
- package/dist/src/transfer/TransferResult.d.ts +9 -0
- package/dist/src/transfer/TransferResult.d.ts.map +1 -0
- package/dist/src/transfer/TransferResult.js +1 -0
- package/dist/src/transfer/aggregateResults.d.ts +4 -0
- package/dist/src/transfer/aggregateResults.d.ts.map +1 -0
- package/dist/src/transfer/aggregateResults.js +9 -0
- package/dist/src/transfer/copyMove.d.ts +23 -0
- package/dist/src/transfer/copyMove.d.ts.map +1 -0
- package/dist/src/transfer/copyMove.js +68 -0
- package/dist/src/transfer/leaseTransfer.d.ts +14 -0
- package/dist/src/transfer/leaseTransfer.d.ts.map +1 -0
- package/dist/src/transfer/leaseTransfer.js +74 -0
- package/dist/src/transfer/multipartTransfer.d.ts +23 -0
- package/dist/src/transfer/multipartTransfer.d.ts.map +1 -0
- package/dist/src/transfer/multipartTransfer.js +94 -0
- package/dist/src/transfer/negotiatePartSize.d.ts +10 -0
- package/dist/src/transfer/negotiatePartSize.d.ts.map +1 -0
- package/dist/src/transfer/negotiatePartSize.js +27 -0
- package/dist/src/transfer/patternTransfer.d.ts +12 -0
- package/dist/src/transfer/patternTransfer.d.ts.map +1 -0
- package/dist/src/transfer/patternTransfer.js +61 -0
- package/dist/src/transfer/rangeReadableMultipartReader.d.ts +8 -0
- package/dist/src/transfer/rangeReadableMultipartReader.d.ts.map +1 -0
- package/dist/src/transfer/rangeReadableMultipartReader.js +19 -0
- package/dist/src/transfer/recursiveTransfer.d.ts +13 -0
- package/dist/src/transfer/recursiveTransfer.d.ts.map +1 -0
- package/dist/src/transfer/recursiveTransfer.js +112 -0
- package/dist/src/transfer/streamTransfer.d.ts +32 -0
- package/dist/src/transfer/streamTransfer.d.ts.map +1 -0
- package/dist/src/transfer/streamTransfer.js +186 -0
- package/dist/src/transfer/transferEntry.d.ts +25 -0
- package/dist/src/transfer/transferEntry.d.ts.map +1 -0
- package/dist/src/transfer/transferEntry.js +136 -0
- package/dist/src/util/abort.d.ts +12 -0
- package/dist/src/util/abort.d.ts.map +1 -0
- package/dist/src/util/abort.js +39 -0
- package/dist/src/util/applyPayloadConverter.d.ts +9 -0
- package/dist/src/util/applyPayloadConverter.d.ts.map +1 -0
- package/dist/src/util/applyPayloadConverter.js +26 -0
- package/dist/src/util/describePath.d.ts +6 -0
- package/dist/src/util/describePath.d.ts.map +1 -0
- package/dist/src/util/describePath.js +9 -0
- package/dist/src/util/globToRegex.d.ts +8 -0
- package/dist/src/util/globToRegex.d.ts.map +1 -0
- package/dist/src/util/globToRegex.js +35 -0
- package/dist/src/util/itemLength.d.ts +4 -0
- package/dist/src/util/itemLength.d.ts.map +1 -0
- package/dist/src/util/itemLength.js +5 -0
- package/dist/src/util/keys.d.ts +8 -0
- package/dist/src/util/keys.d.ts.map +1 -0
- package/dist/src/util/keys.js +19 -0
- package/dist/src/util/withParentOperationId.d.ts +4 -0
- package/dist/src/util/withParentOperationId.d.ts.map +1 -0
- package/dist/src/util/withParentOperationId.js +12 -0
- package/index.ts +11 -4
- package/package.json +4 -3
- package/src/concurrency/AsyncChannel.ts +52 -0
- package/src/concurrency/ConcurrencyLimiter.ts +72 -0
- package/src/concurrency/mapAsyncIterableConcurrently.ts +66 -0
- package/src/decorators/locallyCached.ts +30 -22
- package/src/decorators/seekable.ts +7 -7
- package/src/registry/ProviderRegistry.ts +241 -0
- package/src/registry/detectProtocol.ts +16 -0
- package/src/registry/negotiateTransfer.ts +121 -0
- package/src/retry/RetryOptions.ts +26 -0
- package/src/{withRetry.ts → retry/withRetry.ts} +2 -12
- package/src/transfer/EntryContext.ts +17 -0
- package/src/transfer/TransferOptions.ts +45 -0
- package/src/transfer/TransferResult.ts +8 -0
- package/src/transfer/aggregateResults.ts +15 -0
- package/src/transfer/copyMove.ts +113 -0
- package/src/transfer/leaseTransfer.ts +93 -0
- package/src/transfer/multipartTransfer.ts +119 -0
- package/src/transfer/negotiatePartSize.ts +40 -0
- package/src/transfer/patternTransfer.ts +84 -0
- package/src/transfer/rangeReadableMultipartReader.ts +30 -0
- package/src/transfer/recursiveTransfer.ts +147 -0
- package/src/transfer/streamTransfer.ts +214 -0
- package/src/transfer/transferEntry.ts +185 -0
- package/src/util/abort.ts +42 -0
- package/src/util/applyPayloadConverter.ts +37 -0
- package/src/util/describePath.ts +12 -0
- package/src/util/globToRegex.ts +32 -0
- package/src/util/itemLength.ts +6 -0
- package/src/util/keys.ts +21 -0
- package/src/util/withParentOperationId.ts +17 -0
- package/dist/src/ConcurrencyLimiter.d.ts.map +0 -1
- package/dist/src/ConcurrencyLimiter.js +0 -161
- package/dist/src/ProviderRegistry.d.ts +0 -14
- package/dist/src/ProviderRegistry.d.ts.map +0 -1
- package/dist/src/ProviderRegistry.js +0 -22
- package/dist/src/chunkLength.d.ts +0 -3
- package/dist/src/chunkLength.d.ts.map +0 -1
- package/dist/src/chunkLength.js +0 -4
- package/dist/src/copyMove.d.ts +0 -46
- package/dist/src/copyMove.d.ts.map +0 -1
- package/dist/src/copyMove.js +0 -280
- package/dist/src/withRetry.d.ts.map +0 -1
- package/src/ConcurrencyLimiter.ts +0 -178
- package/src/ProviderRegistry.ts +0 -32
- package/src/chunkLength.ts +0 -5
- package/src/copyMove.ts +0 -458
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PermanentIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import { baseName, joinKey } from "../util/keys.js";
|
|
3
|
+
import { patternTransfer } from "./patternTransfer.js";
|
|
4
|
+
import { recursiveTransfer } from "./recursiveTransfer.js";
|
|
5
|
+
import { transferEntry } from "./transferEntry.js";
|
|
6
|
+
async function checkSourceVariant(source, key, expectContainer, variant) {
|
|
7
|
+
const properties = await source.getProperties(key);
|
|
8
|
+
if (properties.isContainer !== expectContainer) {
|
|
9
|
+
const actual = properties.isContainer ? "a container" : "an entry";
|
|
10
|
+
throw new PermanentIOError(`Source "${key}" is declared as ${variant} but is ${actual}`);
|
|
11
|
+
}
|
|
12
|
+
return properties;
|
|
13
|
+
}
|
|
14
|
+
async function transfer(source, sourceTarget, sink, destTarget, options, type) {
|
|
15
|
+
if (destTarget.kind === "pattern") {
|
|
16
|
+
throw new PermanentIOError("A destination cannot be a pattern");
|
|
17
|
+
}
|
|
18
|
+
if (sourceTarget.kind === "entry") {
|
|
19
|
+
const properties = await checkSourceVariant(source, sourceTarget.key, false, "an entry");
|
|
20
|
+
const destKey = destTarget.kind === "entry"
|
|
21
|
+
? destTarget.key
|
|
22
|
+
: joinKey(sink, destTarget.key, baseName(sourceTarget.key));
|
|
23
|
+
return transferEntry({
|
|
24
|
+
source,
|
|
25
|
+
sourceKey: sourceTarget.key,
|
|
26
|
+
sink,
|
|
27
|
+
destKey,
|
|
28
|
+
properties,
|
|
29
|
+
options,
|
|
30
|
+
type,
|
|
31
|
+
deleteSource: type === "move",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (destTarget.kind === "entry") {
|
|
35
|
+
throw new PermanentIOError(sourceTarget.kind === "container"
|
|
36
|
+
? "Cannot transfer a container onto an entry"
|
|
37
|
+
: "Cannot transfer a pattern of entries onto a single entry");
|
|
38
|
+
}
|
|
39
|
+
if (sourceTarget.kind === "container") {
|
|
40
|
+
await checkSourceVariant(source, sourceTarget.key, true, "a container");
|
|
41
|
+
return recursiveTransfer(source, sourceTarget.key, sink, destTarget.key, options, type);
|
|
42
|
+
}
|
|
43
|
+
await checkSourceVariant(source, sourceTarget.containerKey, true, "a pattern container");
|
|
44
|
+
return patternTransfer(source, sourceTarget.containerKey, sourceTarget.pattern, sink, destTarget.key, options, type);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Copies `sourceTarget` on `source` to `destTarget` on `sink`:
|
|
48
|
+
* - entry -> entry writes exactly the destination key;
|
|
49
|
+
* - entry -> container writes `<container>/<source basename>`;
|
|
50
|
+
* - container -> container copies recursively with `cp -r` semantics;
|
|
51
|
+
* - pattern -> container copies each matching entry;
|
|
52
|
+
* - container or pattern -> entry, and any pattern destination, are rejected.
|
|
53
|
+
*
|
|
54
|
+
* Each entry uses `directCopy` when eligible (and `options.directTransfer`
|
|
55
|
+
* is not `false`), otherwise multipart, the lease path or plain streaming.
|
|
56
|
+
*/
|
|
57
|
+
export function copy(source, sourceTarget, sink, destTarget, options = {}) {
|
|
58
|
+
return transfer(source, sourceTarget, sink, destTarget, options, "copy");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Moves `sourceTarget` on `source` to `destTarget` on `sink`, with the same
|
|
62
|
+
* target rules as {@link copy}. Non-direct moves copy and then delete the
|
|
63
|
+
* source. Unbounded sources are rejected, and a stopped move never deletes
|
|
64
|
+
* its source.
|
|
65
|
+
*/
|
|
66
|
+
export function move(source, sourceTarget, sink, destTarget, options = {}) {
|
|
67
|
+
return transfer(source, sourceTarget, sink, destTarget, options, "move");
|
|
68
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BufferProvider, FillReadable, StreamHandle } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import type { EntryContext, EntryOutcome } from "./EntryContext.ts";
|
|
3
|
+
export declare const DEFAULT_LEASE_DEPTH = 2;
|
|
4
|
+
/** The number of leases the engine keeps outstanding for `writable`. */
|
|
5
|
+
export declare function leaseDepth(writable: BufferProvider, requested: number | undefined): number;
|
|
6
|
+
/**
|
|
7
|
+
* Moves data by having the source fill buffers handed out by the sink:
|
|
8
|
+
* `acquire -> readInto -> commit`, with up to `depth` leases outstanding so
|
|
9
|
+
* the next read overlaps earlier commits. `readInto` calls are serialised
|
|
10
|
+
* and commits happen in acquisition order. Every lease not yet committed is
|
|
11
|
+
* released on abort, error or end of stream.
|
|
12
|
+
*/
|
|
13
|
+
export declare function leaseTransfer(readable: StreamHandle & FillReadable, writable: StreamHandle & BufferProvider, depth: number, context: EntryContext): Promise<EntryOutcome>;
|
|
14
|
+
//# sourceMappingURL=leaseTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaseTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/leaseTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EACd,YAAY,EAEZ,YAAY,EACb,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEpE,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK1F;AAID;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,YAAY,GAAG,YAAY,EACrC,QAAQ,EAAE,YAAY,GAAG,cAAc,EACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,YAAY,CAAC,CA0DvB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createAbortError } from "../util/abort.js";
|
|
2
|
+
export const DEFAULT_LEASE_DEPTH = 2;
|
|
3
|
+
/** The number of leases the engine keeps outstanding for `writable`. */
|
|
4
|
+
export function leaseDepth(writable, requested) {
|
|
5
|
+
return Math.max(1, Math.min(requested ?? DEFAULT_LEASE_DEPTH, writable.maxOutstanding ?? Infinity));
|
|
6
|
+
}
|
|
7
|
+
function ignore() { }
|
|
8
|
+
/**
|
|
9
|
+
* Moves data by having the source fill buffers handed out by the sink:
|
|
10
|
+
* `acquire -> readInto -> commit`, with up to `depth` leases outstanding so
|
|
11
|
+
* the next read overlaps earlier commits. `readInto` calls are serialised
|
|
12
|
+
* and commits happen in acquisition order. Every lease not yet committed is
|
|
13
|
+
* released on abort, error or end of stream.
|
|
14
|
+
*/
|
|
15
|
+
export async function leaseTransfer(readable, writable, depth, context) {
|
|
16
|
+
const { options, hooks, operationId, type } = context;
|
|
17
|
+
const outstanding = new Set();
|
|
18
|
+
const pending = [];
|
|
19
|
+
let commitChain = Promise.resolve();
|
|
20
|
+
let bytes = 0;
|
|
21
|
+
let items = 0;
|
|
22
|
+
let stopped = false;
|
|
23
|
+
const writer = writable.stream.getWriter();
|
|
24
|
+
try {
|
|
25
|
+
for (;;) {
|
|
26
|
+
if (options.signal?.aborted)
|
|
27
|
+
throw createAbortError();
|
|
28
|
+
if (options.stop?.aborted) {
|
|
29
|
+
stopped = true;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
while (pending.length >= depth) {
|
|
33
|
+
await pending.shift();
|
|
34
|
+
}
|
|
35
|
+
const lease = await writable.acquire();
|
|
36
|
+
outstanding.add(lease);
|
|
37
|
+
const length = await readable.readInto(lease);
|
|
38
|
+
if (length === null) {
|
|
39
|
+
outstanding.delete(lease);
|
|
40
|
+
lease.release();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
bytes += length;
|
|
44
|
+
items += 1;
|
|
45
|
+
const committed = commitChain.then(async () => {
|
|
46
|
+
await lease.commit(length);
|
|
47
|
+
outstanding.delete(lease);
|
|
48
|
+
});
|
|
49
|
+
committed.catch(ignore);
|
|
50
|
+
commitChain = committed;
|
|
51
|
+
pending.push(committed);
|
|
52
|
+
hooks.onProgress?.({
|
|
53
|
+
operationId,
|
|
54
|
+
type,
|
|
55
|
+
bytesProcessed: bytes,
|
|
56
|
+
totalBytes: undefined,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
await commitChain;
|
|
60
|
+
await writer.close();
|
|
61
|
+
if (!stopped) {
|
|
62
|
+
await readable.stream.cancel().catch(ignore);
|
|
63
|
+
}
|
|
64
|
+
return { bytes, items, stopped };
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
for (const lease of outstanding) {
|
|
68
|
+
lease.release();
|
|
69
|
+
}
|
|
70
|
+
await writer.abort(error).catch(ignore);
|
|
71
|
+
await readable.stream.cancel(error).catch(ignore);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IOProvider, type Part, type RangeReadable, type StreamHandle } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import type { ConcurrencyLimiter } from "../concurrency/ConcurrencyLimiter.ts";
|
|
3
|
+
import type { EntryContext, EntryOutcome } from "./EntryContext.ts";
|
|
4
|
+
export interface MultipartTransferInput {
|
|
5
|
+
readonly readable: StreamHandle & RangeReadable;
|
|
6
|
+
readonly sink: IOProvider;
|
|
7
|
+
readonly writer: {
|
|
8
|
+
write(parts: AsyncIterable<Part>): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
readonly totalBytes: number;
|
|
11
|
+
readonly partSize: number;
|
|
12
|
+
readonly limiter: ConcurrencyLimiter;
|
|
13
|
+
readonly context: EntryContext;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads parts with `readRange` and hands them to the sink's multipart
|
|
17
|
+
* writer, bounded by `limiter`. A part whose read fails with a
|
|
18
|
+
* `TransientIOError` is re-read on its own, up to `retry.maxRetries` times,
|
|
19
|
+
* within the same upload. `stop` ends the transfer after the parts already
|
|
20
|
+
* started; `signal` aborts it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function multipartTransfer(input: MultipartTransferInput): Promise<EntryOutcome>;
|
|
23
|
+
//# sourceMappingURL=multipartTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipartTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/multipartTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,IAAI,EACT,KAAK,aAAa,EAClB,KAAK,YAAY,EAElB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAM/E,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGpE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,aAAa,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoF5F"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { TransientIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import { mapAsyncIterableConcurrently } from "../concurrency/mapAsyncIterableConcurrently.js";
|
|
3
|
+
import { backoff, DEFAULT_RETRY } from "../retry/RetryOptions.js";
|
|
4
|
+
import { createAbortError } from "../util/abort.js";
|
|
5
|
+
import { applyPayloadConverter } from "../util/applyPayloadConverter.js";
|
|
6
|
+
import { itemLength } from "../util/itemLength.js";
|
|
7
|
+
import { rangeReadableMultipartReader } from "./rangeReadableMultipartReader.js";
|
|
8
|
+
/**
|
|
9
|
+
* Reads parts with `readRange` and hands them to the sink's multipart
|
|
10
|
+
* writer, bounded by `limiter`. A part whose read fails with a
|
|
11
|
+
* `TransientIOError` is re-read on its own, up to `retry.maxRetries` times,
|
|
12
|
+
* within the same upload. `stop` ends the transfer after the parts already
|
|
13
|
+
* started; `signal` aborts it.
|
|
14
|
+
*/
|
|
15
|
+
export async function multipartTransfer(input) {
|
|
16
|
+
const { readable, sink, writer, totalBytes, partSize, limiter, context } = input;
|
|
17
|
+
const { options, hooks, operationId, type } = context;
|
|
18
|
+
const retry = options.retry ?? DEFAULT_RETRY;
|
|
19
|
+
let bytes = 0;
|
|
20
|
+
let items = 0;
|
|
21
|
+
let stopped = false;
|
|
22
|
+
async function readPart(sourcePart) {
|
|
23
|
+
const partOperationId = crypto.randomUUID();
|
|
24
|
+
const end = Math.min(sourcePart.offset + partSize, totalBytes);
|
|
25
|
+
let stream = sourcePart.stream;
|
|
26
|
+
let attempt = 0;
|
|
27
|
+
for (;;) {
|
|
28
|
+
const reader = applyPayloadConverter(stream, readable.kind, sink.kind, options.converter).getReader();
|
|
29
|
+
const partItems = [];
|
|
30
|
+
let partBytes = 0;
|
|
31
|
+
try {
|
|
32
|
+
for (;;) {
|
|
33
|
+
if (options.signal?.aborted) {
|
|
34
|
+
await reader.cancel().catch(() => { });
|
|
35
|
+
throw createAbortError();
|
|
36
|
+
}
|
|
37
|
+
const { done, value } = await reader.read();
|
|
38
|
+
if (done)
|
|
39
|
+
break;
|
|
40
|
+
partItems.push(value);
|
|
41
|
+
partBytes += itemLength(value);
|
|
42
|
+
hooks.onProgress?.({
|
|
43
|
+
operationId: partOperationId,
|
|
44
|
+
parentOperationId: operationId,
|
|
45
|
+
type,
|
|
46
|
+
bytesProcessed: partBytes,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
bytes += partBytes;
|
|
50
|
+
items += partItems.length;
|
|
51
|
+
hooks.onProgress?.({ operationId, type, bytesProcessed: bytes, totalBytes });
|
|
52
|
+
return partItems;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (!(error instanceof TransientIOError) || attempt >= retry.maxRetries) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
attempt += 1;
|
|
59
|
+
await backoff(retry, attempt);
|
|
60
|
+
stream = (await readable.readRange(sourcePart.offset, end));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function transferPart(sourcePart) {
|
|
65
|
+
const partItems = await readPart(sourcePart);
|
|
66
|
+
return {
|
|
67
|
+
index: sourcePart.index,
|
|
68
|
+
offset: sourcePart.offset,
|
|
69
|
+
kind: sink.kind,
|
|
70
|
+
stream: new ReadableStream({
|
|
71
|
+
start(controller) {
|
|
72
|
+
for (const item of partItems)
|
|
73
|
+
controller.enqueue(item);
|
|
74
|
+
controller.close();
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
complete: () => sourcePart.complete(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function* sourceParts() {
|
|
81
|
+
for await (const part of rangeReadableMultipartReader(readable, totalBytes, partSize)) {
|
|
82
|
+
if (options.signal?.aborted)
|
|
83
|
+
throw createAbortError();
|
|
84
|
+
if (options.stop?.aborted) {
|
|
85
|
+
stopped = true;
|
|
86
|
+
await part.stream.cancel().catch(() => { });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
yield part;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await writer.write(mapAsyncIterableConcurrently(sourceParts(), transferPart, limiter));
|
|
93
|
+
return { bytes, items, stopped };
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IOProvider } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
/**
|
|
3
|
+
* Reconciles `source`'s and `sink`'s {@link PartSizeConstraints} for a
|
|
4
|
+
* transfer of `totalSize` bytes into a single part size satisfying both.
|
|
5
|
+
* Returns `undefined` when the bounds are mutually infeasible (source's
|
|
6
|
+
* minimum exceeds sink's maximum) - the caller should fall back to plain
|
|
7
|
+
* streaming rather than throwing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function negotiatePartSize(source: IOProvider, sink: IOProvider, totalSize: number): number | undefined;
|
|
10
|
+
//# sourceMappingURL=negotiatePartSize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiatePartSize.d.ts","sourceRoot":"","sources":["../../../src/transfer/negotiatePartSize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAuB,MAAM,0CAA0C,CAAC;AAWhG;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAiBpB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const DEFAULT_PART_SIZE = 8 * 1024 * 1024;
|
|
2
|
+
const UNCONSTRAINED_PART_SIZE = {
|
|
3
|
+
minPartSize: 0,
|
|
4
|
+
maxPartSize: Infinity,
|
|
5
|
+
maxParts: Infinity,
|
|
6
|
+
defaultPartSize: DEFAULT_PART_SIZE,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Reconciles `source`'s and `sink`'s {@link PartSizeConstraints} for a
|
|
10
|
+
* transfer of `totalSize` bytes into a single part size satisfying both.
|
|
11
|
+
* Returns `undefined` when the bounds are mutually infeasible (source's
|
|
12
|
+
* minimum exceeds sink's maximum) - the caller should fall back to plain
|
|
13
|
+
* streaming rather than throwing.
|
|
14
|
+
*/
|
|
15
|
+
export function negotiatePartSize(source, sink, totalSize) {
|
|
16
|
+
const sourceConstraints = source.getPartSizeConstraints?.(totalSize) ?? UNCONSTRAINED_PART_SIZE;
|
|
17
|
+
const sinkConstraints = sink.getPartSizeConstraints?.(totalSize) ?? UNCONSTRAINED_PART_SIZE;
|
|
18
|
+
const minPartSize = Math.max(sourceConstraints.minPartSize, sinkConstraints.minPartSize);
|
|
19
|
+
const maxPartSize = Math.min(sourceConstraints.maxPartSize, sinkConstraints.maxPartSize);
|
|
20
|
+
const maxParts = Math.min(sourceConstraints.maxParts, sinkConstraints.maxParts);
|
|
21
|
+
if (minPartSize > maxPartSize) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const byMaxParts = Math.ceil(totalSize / maxParts);
|
|
25
|
+
const candidate = Math.max(minPartSize, sourceConstraints.defaultPartSize, sinkConstraints.defaultPartSize, byMaxParts);
|
|
26
|
+
return Math.min(candidate, maxPartSize);
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IOProvider } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import type { TransferOptions } from "./TransferOptions.ts";
|
|
3
|
+
import type { TransferResult } from "./TransferResult.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Transfers every entry directly inside `containerKey` whose name matches
|
|
6
|
+
* the glob `pattern` into the destination container, one single-entry
|
|
7
|
+
* transfer per match, bounded by the concurrency limiter. Matching
|
|
8
|
+
* containers are skipped. A move deletes each source entry after it is
|
|
9
|
+
* transferred.
|
|
10
|
+
*/
|
|
11
|
+
export declare function patternTransfer(source: IOProvider, containerKey: string, pattern: string, sink: IOProvider, destContainerKey: string, options: TransferOptions, type: "copy" | "move"): Promise<TransferResult>;
|
|
12
|
+
//# sourceMappingURL=patternTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patternTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/patternTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,0CAA0C,CAAC;AAQlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,EAChB,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,GAAG,MAAM,GACpB,OAAO,CAAC,cAAc,CAAC,CAqDzB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PermanentIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import { defaultConcurrencyLimiter } from "../concurrency/ConcurrencyLimiter.js";
|
|
3
|
+
import { mapAsyncIterableConcurrently } from "../concurrency/mapAsyncIterableConcurrently.js";
|
|
4
|
+
import { throwIfAborted } from "../util/abort.js";
|
|
5
|
+
import { globToRegex } from "../util/globToRegex.js";
|
|
6
|
+
import { joinKey } from "../util/keys.js";
|
|
7
|
+
import { aggregateResults } from "./aggregateResults.js";
|
|
8
|
+
import { basePath, transferEntry } from "./transferEntry.js";
|
|
9
|
+
/**
|
|
10
|
+
* Transfers every entry directly inside `containerKey` whose name matches
|
|
11
|
+
* the glob `pattern` into the destination container, one single-entry
|
|
12
|
+
* transfer per match, bounded by the concurrency limiter. Matching
|
|
13
|
+
* containers are skipped. A move deletes each source entry after it is
|
|
14
|
+
* transferred.
|
|
15
|
+
*/
|
|
16
|
+
export async function patternTransfer(source, containerKey, pattern, sink, destContainerKey, options, type) {
|
|
17
|
+
if (typeof source.list !== "function") {
|
|
18
|
+
throw new PermanentIOError("The source provider does not support listing containers");
|
|
19
|
+
}
|
|
20
|
+
const operationId = crypto.randomUUID();
|
|
21
|
+
const limiter = options.concurrencyLimiter ?? defaultConcurrencyLimiter;
|
|
22
|
+
let discovered = 0;
|
|
23
|
+
let completed = 0;
|
|
24
|
+
const reportProgress = () => {
|
|
25
|
+
options.telemetry?.onProgress?.({
|
|
26
|
+
operationId,
|
|
27
|
+
type,
|
|
28
|
+
bytesProcessed: 0,
|
|
29
|
+
entriesProcessed: completed,
|
|
30
|
+
totalEntries: discovered,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
async function processEntry(entry) {
|
|
34
|
+
throwIfAborted(options.signal);
|
|
35
|
+
if (options.stop?.aborted || entry.properties.isContainer) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
discovered += 1;
|
|
39
|
+
reportProgress();
|
|
40
|
+
const result = await transferEntry({
|
|
41
|
+
source,
|
|
42
|
+
sourceKey: joinKey(source, containerKey, entry.path),
|
|
43
|
+
sink,
|
|
44
|
+
destKey: joinKey(sink, destContainerKey, entry.path),
|
|
45
|
+
properties: entry.properties,
|
|
46
|
+
options,
|
|
47
|
+
parentOperationId: operationId,
|
|
48
|
+
type,
|
|
49
|
+
deleteSource: type === "move",
|
|
50
|
+
});
|
|
51
|
+
completed += 1;
|
|
52
|
+
reportProgress();
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
const results = [];
|
|
56
|
+
for await (const result of mapAsyncIterableConcurrently(source.list(containerKey, { recursive: false, regex: globToRegex(pattern) }), processEntry, limiter)) {
|
|
57
|
+
if (result)
|
|
58
|
+
results.push(result);
|
|
59
|
+
}
|
|
60
|
+
return aggregateResults(results, basePath(source, sink, options), options.stop?.aborted ?? false);
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Part, PayloadKind, RangeReadable, StreamHandle } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
/**
|
|
3
|
+
* Splits a `RangeReadable` handle into parts of `partSize` bytes covering
|
|
4
|
+
* `totalSize`. Each part's stream is opened with `readRange(start, end)`
|
|
5
|
+
* (`end` exclusive) when the part is pulled.
|
|
6
|
+
*/
|
|
7
|
+
export declare function rangeReadableMultipartReader<K extends PayloadKind>(handle: StreamHandle<K> & RangeReadable<K>, totalSize: number, partSize: number): AsyncGenerator<Part<K>>;
|
|
8
|
+
//# sourceMappingURL=rangeReadableMultipartReader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rangeReadableMultipartReader.d.ts","sourceRoot":"","sources":["../../../src/transfer/rangeReadableMultipartReader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,WAAW,EACX,aAAa,EACb,YAAY,EACb,MAAM,0CAA0C,CAAC;AAElD;;;;GAIG;AACH,wBAAuB,4BAA4B,CAAC,CAAC,SAAS,WAAW,EACvE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,EAC1C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAazB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a `RangeReadable` handle into parts of `partSize` bytes covering
|
|
3
|
+
* `totalSize`. Each part's stream is opened with `readRange(start, end)`
|
|
4
|
+
* (`end` exclusive) when the part is pulled.
|
|
5
|
+
*/
|
|
6
|
+
export async function* rangeReadableMultipartReader(handle, totalSize, partSize) {
|
|
7
|
+
const partCount = Math.max(1, Math.ceil(totalSize / partSize));
|
|
8
|
+
for (let index = 0; index < partCount; index += 1) {
|
|
9
|
+
const start = index * partSize;
|
|
10
|
+
const end = Math.min(start + partSize, totalSize);
|
|
11
|
+
yield {
|
|
12
|
+
index,
|
|
13
|
+
offset: start,
|
|
14
|
+
kind: handle.kind,
|
|
15
|
+
stream: await handle.readRange(start, end),
|
|
16
|
+
complete: async () => { },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type IOProvider } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import type { TransferOptions } from "./TransferOptions.ts";
|
|
3
|
+
import type { TransferResult } from "./TransferResult.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Transfers a whole container. Uses a container-aware
|
|
6
|
+
* `directCopy`/`directMove` when the source declares
|
|
7
|
+
* `supportsRecursiveDirectTransfer`; otherwise lists the container and
|
|
8
|
+
* transfers each entry, bounded by the concurrency limiter. A non-direct
|
|
9
|
+
* move deletes the source container once, after every entry succeeded and
|
|
10
|
+
* only if the transfer was not stopped.
|
|
11
|
+
*/
|
|
12
|
+
export declare function recursiveTransfer(source: IOProvider, sourceKey: string, sink: IOProvider, destKey: string, options: TransferOptions, type: "copy" | "move"): Promise<TransferResult>;
|
|
13
|
+
//# sourceMappingURL=recursiveTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursiveTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/recursiveTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,0CAA0C,CAAC;AASlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA8B1D;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,GAAG,MAAM,GACpB,OAAO,CAAC,cAAc,CAAC,CAuFzB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { PermanentIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import { defaultConcurrencyLimiter } from "../concurrency/ConcurrencyLimiter.js";
|
|
3
|
+
import { mapAsyncIterableConcurrently } from "../concurrency/mapAsyncIterableConcurrently.js";
|
|
4
|
+
import { DEFAULT_RETRY } from "../retry/RetryOptions.js";
|
|
5
|
+
import { withRetry } from "../retry/withRetry.js";
|
|
6
|
+
import { throwIfAborted } from "../util/abort.js";
|
|
7
|
+
import { baseName, joinKey } from "../util/keys.js";
|
|
8
|
+
import { aggregateResults } from "./aggregateResults.js";
|
|
9
|
+
import { basePath, transferEntry } from "./transferEntry.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the effective destination root for a recursive transfer,
|
|
12
|
+
* following `cp -r`/`mv` semantics: a destination that doesn't exist becomes
|
|
13
|
+
* the copy itself; an existing container gets the source nested inside it
|
|
14
|
+
* as `dest/<source-basename>`; an existing entry is rejected.
|
|
15
|
+
*/
|
|
16
|
+
async function resolveRecursiveDestRoot(sourceKey, sink, destKey) {
|
|
17
|
+
let destProperties;
|
|
18
|
+
try {
|
|
19
|
+
destProperties = await sink.getProperties(destKey);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
destProperties = undefined;
|
|
23
|
+
}
|
|
24
|
+
if (destProperties === undefined) {
|
|
25
|
+
return destKey;
|
|
26
|
+
}
|
|
27
|
+
if (!destProperties.isContainer) {
|
|
28
|
+
throw new PermanentIOError(`Cannot copy/move container "${sourceKey}" onto existing entry "${destKey}"`);
|
|
29
|
+
}
|
|
30
|
+
return joinKey(sink, destKey, baseName(sourceKey));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Transfers a whole container. Uses a container-aware
|
|
34
|
+
* `directCopy`/`directMove` when the source declares
|
|
35
|
+
* `supportsRecursiveDirectTransfer`; otherwise lists the container and
|
|
36
|
+
* transfers each entry, bounded by the concurrency limiter. A non-direct
|
|
37
|
+
* move deletes the source container once, after every entry succeeded and
|
|
38
|
+
* only if the transfer was not stopped.
|
|
39
|
+
*/
|
|
40
|
+
export async function recursiveTransfer(source, sourceKey, sink, destKey, options, type) {
|
|
41
|
+
const path = basePath(source, sink, options);
|
|
42
|
+
const resolvedDestRoot = await resolveRecursiveDestRoot(sourceKey, sink, destKey);
|
|
43
|
+
if (options.directTransfer !== false &&
|
|
44
|
+
source.canDirectTransfer?.(sink) &&
|
|
45
|
+
source.supportsRecursiveDirectTransfer) {
|
|
46
|
+
const direct = type === "move" ? source.directMove : source.directCopy;
|
|
47
|
+
if (direct) {
|
|
48
|
+
throwIfAborted(options.signal);
|
|
49
|
+
const telemetry = { operationId: crypto.randomUUID(), hooks: options.telemetry ?? {} };
|
|
50
|
+
await direct.call(source, sourceKey, resolvedDestRoot, telemetry);
|
|
51
|
+
return { stopped: false, bytes: 0, items: 0, path: `${path}, direct` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeof source.list !== "function") {
|
|
55
|
+
throw new PermanentIOError("The source provider does not support listing containers");
|
|
56
|
+
}
|
|
57
|
+
if (type === "move" && typeof source.delete !== "function") {
|
|
58
|
+
throw new PermanentIOError("The source provider does not support delete, so it cannot move");
|
|
59
|
+
}
|
|
60
|
+
const recursiveOperationId = crypto.randomUUID();
|
|
61
|
+
const limiter = options.concurrencyLimiter ?? defaultConcurrencyLimiter;
|
|
62
|
+
let discovered = 0;
|
|
63
|
+
let completed = 0;
|
|
64
|
+
const reportProgress = () => {
|
|
65
|
+
options.telemetry?.onProgress?.({
|
|
66
|
+
operationId: recursiveOperationId,
|
|
67
|
+
type,
|
|
68
|
+
bytesProcessed: 0,
|
|
69
|
+
entriesProcessed: completed,
|
|
70
|
+
totalEntries: discovered,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
async function processEntry(entry) {
|
|
74
|
+
throwIfAborted(options.signal);
|
|
75
|
+
if (options.stop?.aborted) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
discovered += 1;
|
|
79
|
+
reportProgress();
|
|
80
|
+
const entryDestKey = joinKey(sink, resolvedDestRoot, entry.path);
|
|
81
|
+
let result;
|
|
82
|
+
if (entry.properties.isContainer) {
|
|
83
|
+
await sink.createContainer?.(entryDestKey);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
result = await transferEntry({
|
|
87
|
+
source,
|
|
88
|
+
sourceKey: joinKey(source, sourceKey, entry.path),
|
|
89
|
+
sink,
|
|
90
|
+
destKey: entryDestKey,
|
|
91
|
+
properties: entry.properties,
|
|
92
|
+
options,
|
|
93
|
+
parentOperationId: recursiveOperationId,
|
|
94
|
+
type,
|
|
95
|
+
deleteSource: false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
completed += 1;
|
|
99
|
+
reportProgress();
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
const results = [];
|
|
103
|
+
for await (const result of mapAsyncIterableConcurrently(source.list(sourceKey, { recursive: true }), processEntry, limiter)) {
|
|
104
|
+
if (result)
|
|
105
|
+
results.push(result);
|
|
106
|
+
}
|
|
107
|
+
const outcome = aggregateResults(results, path, options.stop?.aborted ?? false);
|
|
108
|
+
if (type === "move" && !outcome.stopped) {
|
|
109
|
+
await withRetry(() => source.delete.call(source, sourceKey), options.retry ?? DEFAULT_RETRY);
|
|
110
|
+
}
|
|
111
|
+
return outcome;
|
|
112
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type IOProvider, type StreamHandle } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
import type { EntryContext, EntryOutcome } from "./EntryContext.ts";
|
|
3
|
+
export interface StreamTransferInput {
|
|
4
|
+
readonly source: IOProvider;
|
|
5
|
+
readonly sourceKey: string;
|
|
6
|
+
readonly readable: StreamHandle;
|
|
7
|
+
readonly sink: IOProvider;
|
|
8
|
+
readonly destKey: string;
|
|
9
|
+
readonly writable: StreamHandle;
|
|
10
|
+
/** Total bytes for a bounded source, when known. */
|
|
11
|
+
readonly totalBytes?: number;
|
|
12
|
+
readonly context: EntryContext;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Pipes a readable handle into a writable handle item by item, honouring
|
|
16
|
+
* `signal` (abort) and `stop` (graceful close).
|
|
17
|
+
*
|
|
18
|
+
* On a `TransientIOError`:
|
|
19
|
+
* - bounded source with `RangeReadable`, and a writable exposing a resume
|
|
20
|
+
* token: the sink is reopened with the token and the source re-read from
|
|
21
|
+
* the sink's `startOffset`;
|
|
22
|
+
* - other bounded transfers restart from scratch;
|
|
23
|
+
* - unbounded source failure: the source is reopened, the sink kept open,
|
|
24
|
+
* the first new item flagged with `discontinuity`, and `onGap` reported;
|
|
25
|
+
* - unbounded sink failure: the sink is reopened with its resume token, or
|
|
26
|
+
* the transfer fails.
|
|
27
|
+
*
|
|
28
|
+
* `retry.maxRetries` counts consecutive failures and resets once an item is
|
|
29
|
+
* written.
|
|
30
|
+
*/
|
|
31
|
+
export declare function streamTransfer(input: StreamTransferInput): Promise<EntryOutcome>;
|
|
32
|
+
//# sourceMappingURL=streamTransfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/streamTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAIf,KAAK,YAAY,EAElB,MAAM,0CAA0C,CAAC;AAKlD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYpE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8JtF"}
|