@flowscripter/pluggable-io-framework 1.2.6 → 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 +6 -5
- 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,42 @@
|
|
|
1
|
+
/** The error a transfer rejects with when cancelled through `TransferOptions.signal`. */
|
|
2
|
+
export function createAbortError(): DOMException {
|
|
3
|
+
return new DOMException("The transfer was aborted", "AbortError");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
7
|
+
if (signal?.aborted) {
|
|
8
|
+
throw createAbortError();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves with `"aborted"` or `"stopped"` as soon as `signal` or `stop`
|
|
14
|
+
* fires, and never otherwise. `dispose` removes the listeners.
|
|
15
|
+
*/
|
|
16
|
+
export function watchSignals(
|
|
17
|
+
signal: AbortSignal | undefined,
|
|
18
|
+
stop: AbortSignal | undefined,
|
|
19
|
+
): { readonly fired: Promise<"aborted" | "stopped">; dispose(): void } {
|
|
20
|
+
const cleanups: (() => void)[] = [];
|
|
21
|
+
const fired = new Promise<"aborted" | "stopped">((resolve) => {
|
|
22
|
+
for (const [target, outcome] of [
|
|
23
|
+
[signal, "aborted"],
|
|
24
|
+
[stop, "stopped"],
|
|
25
|
+
] as const) {
|
|
26
|
+
if (!target) continue;
|
|
27
|
+
if (target.aborted) {
|
|
28
|
+
resolve(outcome);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const listener = () => resolve(outcome);
|
|
32
|
+
target.addEventListener("abort", listener, { once: true });
|
|
33
|
+
cleanups.push(() => target.removeEventListener("abort", listener));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
fired,
|
|
38
|
+
dispose() {
|
|
39
|
+
for (const cleanup of cleanups) cleanup();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adaptReadableStream,
|
|
3
|
+
type Item,
|
|
4
|
+
type PayloadConverter,
|
|
5
|
+
type PayloadKind,
|
|
6
|
+
} from "@flowscripter/pluggable-io-framework-api";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts every item of `stream` for a sink of `toKind`. With a converter,
|
|
10
|
+
* each item goes through `converter.convert`, including when the kinds match
|
|
11
|
+
* and only the memory domain differs. Without one, a kind mismatch fails on
|
|
12
|
+
* the first item.
|
|
13
|
+
*/
|
|
14
|
+
export function applyPayloadConverter(
|
|
15
|
+
stream: ReadableStream<Item>,
|
|
16
|
+
fromKind: PayloadKind,
|
|
17
|
+
toKind: PayloadKind,
|
|
18
|
+
converter: PayloadConverter | undefined,
|
|
19
|
+
): ReadableStream<Item> {
|
|
20
|
+
if (!converter) {
|
|
21
|
+
return adaptReadableStream(stream, fromKind, toKind);
|
|
22
|
+
}
|
|
23
|
+
const reader = stream.getReader();
|
|
24
|
+
return new ReadableStream<Item>({
|
|
25
|
+
async pull(controller) {
|
|
26
|
+
const { done, value } = await reader.read();
|
|
27
|
+
if (done) {
|
|
28
|
+
controller.close();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
controller.enqueue(converter.convert(value));
|
|
32
|
+
},
|
|
33
|
+
cancel(reason) {
|
|
34
|
+
return reader.cancel(reason);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PayloadConverter, PayloadKind } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
|
|
3
|
+
/** `kind` or `kind[domain]`. */
|
|
4
|
+
export function describeKind(kind: PayloadKind, domain?: string): string {
|
|
5
|
+
return domain === undefined ? kind : `${kind}[${domain}]`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** ` via from->to (zero-copy)` or ` via from->to (copy)`. */
|
|
9
|
+
export function describeConverter(converter: PayloadConverter): string {
|
|
10
|
+
const cost = converter.cost === 0 ? "zero-copy" : "copy";
|
|
11
|
+
return ` via ${describeKind(converter.from.kind, converter.from.domain)}->${describeKind(converter.to.kind, converter.to.domain)} (${cost})`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates a glob into an anchored regular expression matched against a
|
|
3
|
+
* single entry name: `*` matches any run of characters except `/`, `?`
|
|
4
|
+
* matches one such character, and `[...]` is a character class (`[!...]`
|
|
5
|
+
* negates it). Everything else matches literally.
|
|
6
|
+
*/
|
|
7
|
+
export function globToRegex(glob: string): RegExp {
|
|
8
|
+
let source = "";
|
|
9
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
10
|
+
const char = glob[i] as string;
|
|
11
|
+
if (char === "*") {
|
|
12
|
+
source += "[^/]*";
|
|
13
|
+
} else if (char === "?") {
|
|
14
|
+
source += "[^/]";
|
|
15
|
+
} else if (char === "[") {
|
|
16
|
+
const close = glob.indexOf("]", i + 2);
|
|
17
|
+
if (close === -1) {
|
|
18
|
+
source += "\\[";
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
let body = glob.slice(i + 1, close);
|
|
22
|
+
if (body.startsWith("!")) {
|
|
23
|
+
body = `^${body.slice(1)}`;
|
|
24
|
+
}
|
|
25
|
+
source += `[${body.replace(/\\/g, "\\\\")}]`;
|
|
26
|
+
i = close;
|
|
27
|
+
} else {
|
|
28
|
+
source += char.replace(/[.+^${}()|\\/\]]/g, "\\$&");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return new RegExp(`^${source}$`);
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Item, PayloadKind } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
|
|
3
|
+
/** The payload size of an item in bytes. */
|
|
4
|
+
export function itemLength(item: Item): number {
|
|
5
|
+
return item.payload.kind === PayloadKind.Js ? item.payload.data.byteLength : item.payload.length;
|
|
6
|
+
}
|
package/src/util/keys.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IOProvider } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
|
|
3
|
+
/** Joins with `/` - the default when a provider has no `joinKey`. */
|
|
4
|
+
export function defaultJoinKey(containerKey: string, name: string): string {
|
|
5
|
+
if (name === "") return containerKey;
|
|
6
|
+
if (containerKey === "") return name;
|
|
7
|
+
return `${containerKey.replace(/\/+$/, "")}/${name}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Joins a child name onto a container key using the provider's own rule. */
|
|
11
|
+
export function joinKey(provider: IOProvider, containerKey: string, name: string): string {
|
|
12
|
+
return provider.joinKey
|
|
13
|
+
? provider.joinKey(containerKey, name)
|
|
14
|
+
: defaultJoinKey(containerKey, name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The last segment of a key, treating both `/` and `\` as separators. */
|
|
18
|
+
export function baseName(key: string): string {
|
|
19
|
+
const segments = key.split(/[\\/]+/).filter((segment) => segment !== "");
|
|
20
|
+
return segments.at(-1) ?? key;
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TelemetryHooks } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
+
|
|
3
|
+
/** Wraps `hooks` so any `onProgress` event passing through gets `parentOperationId` filled in, unless the emitter already set one (nested hierarchy). */
|
|
4
|
+
export function withParentOperationId(
|
|
5
|
+
hooks: TelemetryHooks | undefined,
|
|
6
|
+
parentOperationId: string | undefined,
|
|
7
|
+
): TelemetryHooks {
|
|
8
|
+
if (!hooks || parentOperationId === undefined) {
|
|
9
|
+
return hooks ?? {};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
...hooks,
|
|
13
|
+
onProgress: hooks.onProgress
|
|
14
|
+
? (event) => hooks.onProgress?.({ parentOperationId, ...event })
|
|
15
|
+
: undefined,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ConcurrencyLimiter.d.ts","sourceRoot":"","sources":["../../src/ConcurrencyLimiter.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,kBAAkB;;IAK7B,YAAmB,cAAc,EAAE,MAAM,EAGxC;IAED,IAAW,cAAc,IAAI,MAAM,CAElC;IAEM,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAIrD;IAEY,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQpD;CA0BF;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,oBAAkD,CAAC;AAuDzF;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,EAAE,CAAC,EAC/C,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,EACxB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,kBAAkB,GAC1B,aAAa,CAAC,CAAC,CAAC,CAqClB"}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
const DEFAULT_MAX_CONCURRENCY = 4;
|
|
2
|
-
/**
|
|
3
|
-
* A semaphore bounding how many `run()` callbacks execute at once. A single
|
|
4
|
-
* shared instance (see {@link defaultConcurrencyLimiter}) enforces its cap
|
|
5
|
-
* *globally* across concurrently invoked `copy()`/`move()` calls, not just
|
|
6
|
-
* within one call - two folder copies started at the same time and sharing
|
|
7
|
-
* a limiter will never together exceed its `maxConcurrency`.
|
|
8
|
-
*/
|
|
9
|
-
export class ConcurrencyLimiter {
|
|
10
|
-
#maxConcurrency;
|
|
11
|
-
#active = 0;
|
|
12
|
-
#queue = [];
|
|
13
|
-
constructor(maxConcurrency) {
|
|
14
|
-
ConcurrencyLimiter.#validate(maxConcurrency);
|
|
15
|
-
this.#maxConcurrency = maxConcurrency;
|
|
16
|
-
}
|
|
17
|
-
get maxConcurrency() {
|
|
18
|
-
return this.#maxConcurrency;
|
|
19
|
-
}
|
|
20
|
-
setMaxConcurrency(maxConcurrency) {
|
|
21
|
-
ConcurrencyLimiter.#validate(maxConcurrency);
|
|
22
|
-
this.#maxConcurrency = maxConcurrency;
|
|
23
|
-
this.#drain();
|
|
24
|
-
}
|
|
25
|
-
async run(fn) {
|
|
26
|
-
await this.#acquire();
|
|
27
|
-
try {
|
|
28
|
-
return await fn();
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
this.#active -= 1;
|
|
32
|
-
this.#drain();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
#acquire() {
|
|
36
|
-
if (this.#active < this.#maxConcurrency) {
|
|
37
|
-
this.#active += 1;
|
|
38
|
-
return Promise.resolve();
|
|
39
|
-
}
|
|
40
|
-
return new Promise((resolve) => {
|
|
41
|
-
this.#queue.push(() => {
|
|
42
|
-
this.#active += 1;
|
|
43
|
-
resolve();
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
#drain() {
|
|
48
|
-
while (this.#active < this.#maxConcurrency && this.#queue.length > 0) {
|
|
49
|
-
this.#queue.shift()?.();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
static #validate(maxConcurrency) {
|
|
53
|
-
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
54
|
-
throw new Error(`maxConcurrency must be a positive integer, got ${maxConcurrency}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Process-wide default limiter, used by `copy()`/`move()` whenever
|
|
60
|
-
* `TransferOptions.concurrencyLimiter` is omitted - so any two calls in the
|
|
61
|
-
* same process share one cap unless a caller explicitly opts out with its
|
|
62
|
-
* own instance (e.g. for test isolation).
|
|
63
|
-
*/
|
|
64
|
-
export const defaultConcurrencyLimiter = new ConcurrencyLimiter(DEFAULT_MAX_CONCURRENCY);
|
|
65
|
-
/** Minimal single-consumer async FIFO queue backing {@link mapAsyncIterableConcurrently}. */
|
|
66
|
-
class AsyncChannel {
|
|
67
|
-
#items = [];
|
|
68
|
-
#waiters = [];
|
|
69
|
-
#closed = false;
|
|
70
|
-
#hasError = false;
|
|
71
|
-
#error;
|
|
72
|
-
push(item) {
|
|
73
|
-
const waiter = this.#waiters.shift();
|
|
74
|
-
if (waiter) {
|
|
75
|
-
waiter.resolve({ value: item, done: false });
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
this.#items.push(item);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
close() {
|
|
82
|
-
this.#closed = true;
|
|
83
|
-
for (const waiter of this.#waiters.splice(0)) {
|
|
84
|
-
if (this.#hasError) {
|
|
85
|
-
waiter.reject(this.#error);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
waiter.resolve({ value: undefined, done: true });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
fail(error) {
|
|
93
|
-
this.#hasError = true;
|
|
94
|
-
this.#error = error;
|
|
95
|
-
this.close();
|
|
96
|
-
}
|
|
97
|
-
async #next() {
|
|
98
|
-
if (this.#items.length > 0) {
|
|
99
|
-
return { value: this.#items.shift(), done: false };
|
|
100
|
-
}
|
|
101
|
-
if (this.#closed) {
|
|
102
|
-
if (this.#hasError) {
|
|
103
|
-
throw this.#error;
|
|
104
|
-
}
|
|
105
|
-
return { value: undefined, done: true };
|
|
106
|
-
}
|
|
107
|
-
return new Promise((resolve, reject) => this.#waiters.push({ resolve, reject }));
|
|
108
|
-
}
|
|
109
|
-
[Symbol.asyncIterator]() {
|
|
110
|
-
return { next: () => this.#next() };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Pulls items from `source` and runs `fn` on each, bounded by `limiter`:
|
|
115
|
-
* exactly `limiter.maxConcurrency` worker loops each pull-then-process one
|
|
116
|
-
* item at a time through `limiter.run`, so at most that many items are ever
|
|
117
|
-
* pulled/in-flight at once (important for sources like a multipart reader
|
|
118
|
-
* where pulling an item eagerly opens a file/network handle). Results are
|
|
119
|
-
* yielded in completion order, not source order. If `fn` throws, no new
|
|
120
|
-
* items are pulled but already-in-flight ones are allowed to finish before
|
|
121
|
-
* the error is rethrown from the returned iterable.
|
|
122
|
-
*/
|
|
123
|
-
export function mapAsyncIterableConcurrently(source, fn, limiter) {
|
|
124
|
-
const channel = new AsyncChannel();
|
|
125
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
126
|
-
let stopped = false;
|
|
127
|
-
let firstError;
|
|
128
|
-
let errorRecorded = false;
|
|
129
|
-
async function worker() {
|
|
130
|
-
for (;;) {
|
|
131
|
-
if (stopped)
|
|
132
|
-
return;
|
|
133
|
-
const { done, value } = await iterator.next();
|
|
134
|
-
if (done)
|
|
135
|
-
return;
|
|
136
|
-
try {
|
|
137
|
-
const result = await limiter.run(() => fn(value));
|
|
138
|
-
channel.push(result);
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
stopped = true;
|
|
142
|
-
if (!errorRecorded) {
|
|
143
|
-
errorRecorded = true;
|
|
144
|
-
firstError = error;
|
|
145
|
-
}
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
void (async () => {
|
|
151
|
-
const workerCount = Math.max(1, limiter.maxConcurrency);
|
|
152
|
-
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
153
|
-
if (errorRecorded) {
|
|
154
|
-
channel.fail(firstError);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
channel.close();
|
|
158
|
-
}
|
|
159
|
-
})();
|
|
160
|
-
return channel;
|
|
161
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { PluginManager } from "@flowscripter/dynamic-plugin-framework";
|
|
2
|
-
import { type IOProvider } from "@flowscripter/pluggable-io-framework-api";
|
|
3
|
-
/**
|
|
4
|
-
* Wraps a `dynamic-plugin-framework` {@link PluginManager} to discover and
|
|
5
|
-
* instantiate pluggable-io-framework source/sink provider plugins.
|
|
6
|
-
*/
|
|
7
|
-
export declare class ProviderRegistry {
|
|
8
|
-
private readonly pluginManager;
|
|
9
|
-
constructor(pluginManager: PluginManager);
|
|
10
|
-
discover(): Promise<void>;
|
|
11
|
-
listAvailableProviders(): Promise<readonly import("@flowscripter/dynamic-plugin-framework").ExtensionInfo[]>;
|
|
12
|
-
createProvider(extensionHandle: string, config: unknown): Promise<IOProvider>;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=ProviderRegistry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../src/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,0CAA0C,CAAC;AAElD;;;GAGG;AACH,qBAAa,gBAAgB;IACR,OAAO,CAAC,QAAQ,CAAC,aAAa;IAAjD,YAAoC,aAAa,EAAE,aAAa,EAAI;IAEvD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrC;IAEY,sBAAsB,uFAIlC;IAEY,cAAc,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAIzF;CACF"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
-
/**
|
|
3
|
-
* Wraps a `dynamic-plugin-framework` {@link PluginManager} to discover and
|
|
4
|
-
* instantiate pluggable-io-framework source/sink provider plugins.
|
|
5
|
-
*/
|
|
6
|
-
export class ProviderRegistry {
|
|
7
|
-
pluginManager;
|
|
8
|
-
constructor(pluginManager) {
|
|
9
|
-
this.pluginManager = pluginManager;
|
|
10
|
-
}
|
|
11
|
-
async discover() {
|
|
12
|
-
await this.pluginManager.registerExtensions(PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT);
|
|
13
|
-
}
|
|
14
|
-
async listAvailableProviders() {
|
|
15
|
-
return this.pluginManager.getRegisteredExtensions(PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT);
|
|
16
|
-
}
|
|
17
|
-
async createProvider(extensionHandle, config) {
|
|
18
|
-
const factory = (await this.pluginManager.instantiate(extensionHandle));
|
|
19
|
-
const validatedConfig = factory.configSchema.parse(config);
|
|
20
|
-
return factory.createProvider(validatedConfig);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chunkLength.d.ts","sourceRoot":"","sources":["../../src/chunkLength.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,0CAA0C,CAAC;AAEpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAEnD"}
|
package/dist/src/chunkLength.js
DELETED
package/dist/src/copyMove.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type ChunkConverter, type IOProvider, type TelemetryHooks } from "@flowscripter/pluggable-io-framework-api";
|
|
2
|
-
import { type ConcurrencyLimiter } from "./ConcurrencyLimiter.ts";
|
|
3
|
-
import { type RetryOptions } from "./withRetry.ts";
|
|
4
|
-
export interface TransferOptions {
|
|
5
|
-
readonly telemetry?: TelemetryHooks;
|
|
6
|
-
/** Minimum file size (bytes) before multipart transfer is attempted over plain streaming. */
|
|
7
|
-
readonly multipartThreshold?: number;
|
|
8
|
-
/**
|
|
9
|
-
* Required only when `source.kind !== sink.kind` - pure-TS code can only
|
|
10
|
-
* convert chunks already of the target kind (see
|
|
11
|
-
* `identityChunkConverter`); a real js<->native conversion needs an
|
|
12
|
-
* FFI-capable converter supplied by a runtime-specific package.
|
|
13
|
-
*/
|
|
14
|
-
readonly chunkConverter?: ChunkConverter;
|
|
15
|
-
/**
|
|
16
|
-
* Bounds concurrent multipart parts and recursive-copy/move entries.
|
|
17
|
-
* Defaults to the shared {@link defaultConcurrencyLimiter} - so two
|
|
18
|
-
* `copy()`/`move()` calls in the same process share one cap unless a
|
|
19
|
-
* caller passes its own isolated instance.
|
|
20
|
-
*/
|
|
21
|
-
readonly concurrencyLimiter?: ConcurrencyLimiter;
|
|
22
|
-
/** Retry policy for the non-direct transfer path. Defaults to `{ maxRetries: 3 }`. */
|
|
23
|
-
readonly retry?: RetryOptions;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Copies `sourcePath` on `source` to `destPath` on `sink`. When `sourcePath`
|
|
27
|
-
* is a folder, recurses: uses a folder-aware `directCopy` when the source
|
|
28
|
-
* declares `supportsRecursiveDirectTransfer`, otherwise lists the folder and
|
|
29
|
-
* transfers each entry (bounded by `options.concurrencyLimiter`), following
|
|
30
|
-
* `cp -r` target semantics (see {@link resolveRecursiveDestRoot}).
|
|
31
|
-
*
|
|
32
|
-
* For a single file: uses `source.directCopy` when
|
|
33
|
-
* `source.canDirectTransfer?.(sink)` reports eligibility. Otherwise falls
|
|
34
|
-
* back to multipart transfer (when both sides support it, size crosses
|
|
35
|
-
* `options.multipartThreshold`, and part-size negotiation succeeds) or plain
|
|
36
|
-
* streaming - retried per `options.retry` on `TransientIOError`.
|
|
37
|
-
*/
|
|
38
|
-
export declare function copy(source: IOProvider, sourcePath: string, sink: IOProvider, destPath: string, options?: TransferOptions): Promise<void>;
|
|
39
|
-
/**
|
|
40
|
-
* Moves `sourcePath` on `source` to `destPath` on `sink`. Folder handling
|
|
41
|
-
* mirrors {@link copy}. For a single file, uses `source.directMove` when
|
|
42
|
-
* eligible, otherwise performs a {@link copy} followed by deleting the
|
|
43
|
-
* source item (each retried independently per `options.retry`).
|
|
44
|
-
*/
|
|
45
|
-
export declare function move(source: IOProvider, sourcePath: string, sink: IOProvider, destPath: string, options?: TransferOptions): Promise<void>;
|
|
46
|
-
//# sourceMappingURL=copyMove.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"copyMove.d.ts","sourceRoot":"","sources":["../../src/copyMove.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,UAAU,EAIf,KAAK,cAAc,EACpB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EACL,KAAK,kBAAkB,EAGxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC;IACpC,6FAA6F;IAC7F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;CAC/B;AAmXD;;;;;;;;;;;;GAYG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;GAKG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAOf"}
|