@flowscripter/pluggable-io-framework 2.0.1 → 2.1.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 +6 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/src/registry/ProviderRegistry.d.ts +5 -3
- package/dist/src/registry/ProviderRegistry.d.ts.map +1 -1
- package/dist/src/registry/ProviderRegistry.js +10 -5
- package/dist/src/registry/StructuredLocation.d.ts +11 -0
- package/dist/src/registry/StructuredLocation.d.ts.map +1 -0
- package/dist/src/registry/StructuredLocation.js +1 -0
- package/dist/src/transfer/multipartTransfer.d.ts +6 -5
- package/dist/src/transfer/multipartTransfer.d.ts.map +1 -1
- package/dist/src/transfer/multipartTransfer.js +5 -3
- package/dist/src/transfer/rangeReadableMultipartReader.d.ts +3 -2
- package/dist/src/transfer/rangeReadableMultipartReader.d.ts.map +1 -1
- package/dist/src/transfer/rangeReadableMultipartReader.js +4 -3
- package/dist/src/transfer/transferEntry.d.ts.map +1 -1
- package/dist/src/transfer/transferEntry.js +19 -10
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/registry/ProviderRegistry.ts +18 -10
- package/src/registry/StructuredLocation.ts +10 -0
- package/src/transfer/multipartTransfer.ts +14 -4
- package/src/transfer/rangeReadableMultipartReader.ts +4 -2
- package/src/transfer/transferEntry.ts +20 -13
package/README.md
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
## Key Features
|
|
12
12
|
|
|
13
|
-
- Protocol-aware locations
|
|
14
|
-
`https://host/a
|
|
15
|
-
|
|
13
|
+
- Protocol-aware locations, given as a string (`file:///data/a.txt`,
|
|
14
|
+
`s3://bucket/key`, `https://host/a`, composite schemes such as
|
|
15
|
+
`tams+https:`; bare paths default to `file`) or as a structured
|
|
16
|
+
`{ protocol, location }` object. Either form selects the installed provider
|
|
17
|
+
plugin for that protocol. See
|
|
18
|
+
[String and Structured Locations](README/key-concepts.md#string-and-structured-locations).
|
|
16
19
|
- A `ProviderRegistry` keyed by protocol and payload kind, with more than one
|
|
17
20
|
implementation of a protocol installed side by side (e.g. JS and native
|
|
18
21
|
`file` providers).
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./src/decorators/locallyCached.ts";
|
|
|
4
4
|
export * from "./src/decorators/seekable.ts";
|
|
5
5
|
export * from "./src/registry/detectProtocol.ts";
|
|
6
6
|
export * from "./src/registry/ProviderRegistry.ts";
|
|
7
|
+
export * from "./src/registry/StructuredLocation.ts";
|
|
7
8
|
export * from "./src/retry/RetryOptions.ts";
|
|
8
9
|
export * from "./src/retry/withRetry.ts";
|
|
9
10
|
export * from "./src/transfer/copyMove.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,yCAAyC,CAAC;AACxD,cAAc,mDAAmD,CAAC;AAClE,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,yCAAyC,CAAC;AACxD,cAAc,mDAAmD,CAAC;AAClE,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,oCAAoC,CAAC;AACnD,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./src/decorators/locallyCached.js";
|
|
|
4
4
|
export * from "./src/decorators/seekable.js";
|
|
5
5
|
export * from "./src/registry/detectProtocol.js";
|
|
6
6
|
export * from "./src/registry/ProviderRegistry.js";
|
|
7
|
+
export * from "./src/registry/StructuredLocation.js";
|
|
7
8
|
export * from "./src/retry/RetryOptions.js";
|
|
8
9
|
export * from "./src/retry/withRetry.js";
|
|
9
10
|
export * from "./src/transfer/copyMove.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PluginManager } from "@flowscripter/dynamic-plugin-framework";
|
|
2
2
|
import { type IOProvider, type IOProviderFactory, type LocationTarget, PayloadKind, type PayloadConverter, type ProviderResolver } from "@flowscripter/pluggable-io-framework-api";
|
|
3
3
|
import type { TransferOptions } from "../transfer/TransferOptions.ts";
|
|
4
|
+
import type { StructuredLocation } from "./StructuredLocation.ts";
|
|
4
5
|
/** Nested provider resolutions allowed before resolution fails. */
|
|
5
6
|
export declare const MAX_RESOLUTION_DEPTH = 4;
|
|
6
7
|
export interface ResolvedProvider {
|
|
@@ -19,7 +20,8 @@ export interface TransferProviders {
|
|
|
19
20
|
/**
|
|
20
21
|
* Discovers provider factories and payload converters through a
|
|
21
22
|
* `dynamic-plugin-framework` `PluginManager`, keyed by protocol and
|
|
22
|
-
* payload kind, and creates providers for location strings
|
|
23
|
+
* payload kind, and creates providers for location strings or
|
|
24
|
+
* {@link StructuredLocation}s. It is also the
|
|
23
25
|
* `ProviderResolver` handed to every provider it creates.
|
|
24
26
|
*/
|
|
25
27
|
export declare class ProviderRegistry implements ProviderResolver {
|
|
@@ -37,7 +39,7 @@ export declare class ProviderRegistry implements ProviderResolver {
|
|
|
37
39
|
/** The factory for `protocol` and `kind`; without `kind`, the `js` one, else the only one. */
|
|
38
40
|
getFactory(protocol: string, kind?: PayloadKind): IOProviderFactory | undefined;
|
|
39
41
|
getConverters(): readonly PayloadConverter[];
|
|
40
|
-
createProviderForLocation(location: string, options?: {
|
|
42
|
+
createProviderForLocation(location: string | StructuredLocation, options?: {
|
|
41
43
|
kind?: PayloadKind;
|
|
42
44
|
domain?: string;
|
|
43
45
|
}): Promise<ResolvedProvider>;
|
|
@@ -45,7 +47,7 @@ export declare class ProviderRegistry implements ProviderResolver {
|
|
|
45
47
|
* Creates providers for both sides of a transfer, negotiating a common
|
|
46
48
|
* (kind, domain) and payload type, or a converter.
|
|
47
49
|
*/
|
|
48
|
-
createProvidersForTransfer(source: string, dest: string, options?: {
|
|
50
|
+
createProvidersForTransfer(source: string | StructuredLocation, dest: string | StructuredLocation, options?: {
|
|
49
51
|
kind?: PayloadKind;
|
|
50
52
|
}): Promise<TransferProviders>;
|
|
51
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../../src/registry/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,WAAW,EACX,KAAK,gBAAgB,EAIrB,KAAK,gBAAgB,EACtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../../src/registry/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,WAAW,EACX,KAAK,gBAAgB,EAIrB,KAAK,gBAAgB,EACtB,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACnC;AAWD;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,gBAAgB;;IAIpC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAAjD,YAAoC,aAAa,EAAE,aAAa,EAAI;IAEpE;;;;OAIG;IACU,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAqCrC;IAEM,YAAY,IAAI,MAAM,EAAE,CAE9B;IAEM,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CAE/C;IAED,8FAA8F;IACvF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,iBAAiB,GAAG,SAAS,CAKrF;IAEM,aAAa,IAAI,SAAS,gBAAgB,EAAE,CAElD;IAEM,yBAAyB,CAC9B,QAAQ,EAAE,MAAM,GAAG,kBAAkB,EACrC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,OAAO,CAAC,gBAAgB,CAAC,CAE3B;IAED;;;OAGG;IACU,0BAA0B,CACrC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EACnC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EACjC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAA;KAAE,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwC5B;CAuEF"}
|
|
@@ -3,6 +3,9 @@ import { detectProtocol } from "./detectProtocol.js";
|
|
|
3
3
|
import { DEFAULT_NATIVE_DOMAINS, negotiateTransfer } from "./negotiateTransfer.js";
|
|
4
4
|
/** Nested provider resolutions allowed before resolution fails. */
|
|
5
5
|
export const MAX_RESOLUTION_DEPTH = 4;
|
|
6
|
+
function protocolOf(location) {
|
|
7
|
+
return typeof location === "string" ? detectProtocol(location) : location.protocol;
|
|
8
|
+
}
|
|
6
9
|
function locationFieldNames(factory) {
|
|
7
10
|
const shape = factory.locationSchema.shape;
|
|
8
11
|
return Object.keys(shape ?? {}).sort();
|
|
@@ -10,7 +13,8 @@ function locationFieldNames(factory) {
|
|
|
10
13
|
/**
|
|
11
14
|
* Discovers provider factories and payload converters through a
|
|
12
15
|
* `dynamic-plugin-framework` `PluginManager`, keyed by protocol and
|
|
13
|
-
* payload kind, and creates providers for location strings
|
|
16
|
+
* payload kind, and creates providers for location strings or
|
|
17
|
+
* {@link StructuredLocation}s. It is also the
|
|
14
18
|
* `ProviderResolver` handed to every provider it creates.
|
|
15
19
|
*/
|
|
16
20
|
export class ProviderRegistry {
|
|
@@ -77,8 +81,8 @@ export class ProviderRegistry {
|
|
|
77
81
|
* (kind, domain) and payload type, or a converter.
|
|
78
82
|
*/
|
|
79
83
|
async createProvidersForTransfer(source, dest, options) {
|
|
80
|
-
const sourceProtocol =
|
|
81
|
-
const destProtocol =
|
|
84
|
+
const sourceProtocol = protocolOf(source);
|
|
85
|
+
const destProtocol = protocolOf(dest);
|
|
82
86
|
const sourceFactories = this.#factoriesFor(sourceProtocol, options?.kind);
|
|
83
87
|
const destFactories = this.#factoriesFor(destProtocol, options?.kind);
|
|
84
88
|
const negotiation = negotiateTransfer(sourceProtocol, sourceFactories, destProtocol, destFactories, this.#converters, options?.kind);
|
|
@@ -120,7 +124,7 @@ export class ProviderRegistry {
|
|
|
120
124
|
if (depth > MAX_RESOLUTION_DEPTH) {
|
|
121
125
|
throw new PermanentIOError("provider resolution too deep");
|
|
122
126
|
}
|
|
123
|
-
const protocol =
|
|
127
|
+
const protocol = protocolOf(location);
|
|
124
128
|
this.#factoriesFor(protocol, options?.kind);
|
|
125
129
|
const factory = this.getFactory(protocol, options?.kind);
|
|
126
130
|
let domain;
|
|
@@ -134,7 +138,8 @@ export class ProviderRegistry {
|
|
|
134
138
|
return this.#instantiate(factory, location, domain, depth);
|
|
135
139
|
}
|
|
136
140
|
async #instantiate(factory, location, domain, depth) {
|
|
137
|
-
const
|
|
141
|
+
const raw = typeof location === "string" ? factory.parseLocationString(location) : location.location;
|
|
142
|
+
const parsed = factory.locationSchema.parse(raw);
|
|
138
143
|
const { config, target } = factory.toProviderInputs(parsed);
|
|
139
144
|
const resolver = {
|
|
140
145
|
createProviderForLocation: (inner, opts) => this.#resolve(inner, { kind: opts?.kind ?? factory.kind, domain: opts?.domain ?? domain }, depth + 1),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A location given as its protocol plus the raw location object that the
|
|
3
|
+
* protocol's factory `locationSchema` validates, instead of a location
|
|
4
|
+
* string. It can carry fields a string cannot, such as `filename`,
|
|
5
|
+
* `pattern` or credentials.
|
|
6
|
+
*/
|
|
7
|
+
export interface StructuredLocation {
|
|
8
|
+
readonly protocol: string;
|
|
9
|
+
readonly location: unknown;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=StructuredLocation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StructuredLocation.d.ts","sourceRoot":"","sources":["../../../src/registry/StructuredLocation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { type IOProvider, type
|
|
1
|
+
import { type IOProvider, type MultipartWriter, type RangeReadable, type StreamHandle } from "@flowscripter/pluggable-io-framework-api";
|
|
2
2
|
import type { ConcurrencyLimiter } from "../concurrency/ConcurrencyLimiter.ts";
|
|
3
3
|
import type { EntryContext, EntryOutcome } from "./EntryContext.ts";
|
|
4
4
|
export interface MultipartTransferInput {
|
|
5
5
|
readonly readable: StreamHandle & RangeReadable;
|
|
6
6
|
readonly sink: IOProvider;
|
|
7
|
-
readonly writer:
|
|
8
|
-
write(parts: AsyncIterable<Part>): Promise<void>;
|
|
9
|
-
};
|
|
7
|
+
readonly writer: MultipartWriter;
|
|
10
8
|
readonly totalBytes: number;
|
|
11
9
|
readonly partSize: number;
|
|
10
|
+
/** Where a resumed upload continues: parts start at the part containing this offset. */
|
|
11
|
+
readonly startOffset?: number;
|
|
12
12
|
readonly limiter: ConcurrencyLimiter;
|
|
13
13
|
readonly context: EntryContext;
|
|
14
14
|
}
|
|
@@ -17,7 +17,8 @@ export interface MultipartTransferInput {
|
|
|
17
17
|
* writer, bounded by `limiter`. A part whose read fails with a
|
|
18
18
|
* `TransientIOError` is re-read on its own, up to `retry.maxRetries` times,
|
|
19
19
|
* within the same upload. `stop` ends the transfer after the parts already
|
|
20
|
-
* started; `signal` aborts it.
|
|
20
|
+
* started; `signal` aborts it. Reported bytes include the parts before
|
|
21
|
+
* `startOffset`.
|
|
21
22
|
*/
|
|
22
23
|
export declare function multipartTransfer(input: MultipartTransferInput): Promise<EntryOutcome>;
|
|
23
24
|
//# sourceMappingURL=multipartTransfer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipartTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/multipartTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,
|
|
1
|
+
{"version":3,"file":"multipartTransfer.d.ts","sourceRoot":"","sources":["../../../src/transfer/multipartTransfer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,eAAe,EAEpB,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,eAAe,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CA0F5F"}
|
|
@@ -10,13 +10,15 @@ import { rangeReadableMultipartReader } from "./rangeReadableMultipartReader.js"
|
|
|
10
10
|
* writer, bounded by `limiter`. A part whose read fails with a
|
|
11
11
|
* `TransientIOError` is re-read on its own, up to `retry.maxRetries` times,
|
|
12
12
|
* within the same upload. `stop` ends the transfer after the parts already
|
|
13
|
-
* started; `signal` aborts it.
|
|
13
|
+
* started; `signal` aborts it. Reported bytes include the parts before
|
|
14
|
+
* `startOffset`.
|
|
14
15
|
*/
|
|
15
16
|
export async function multipartTransfer(input) {
|
|
16
17
|
const { readable, sink, writer, totalBytes, partSize, limiter, context } = input;
|
|
17
18
|
const { options, hooks, operationId, type } = context;
|
|
18
19
|
const retry = options.retry ?? DEFAULT_RETRY;
|
|
19
|
-
|
|
20
|
+
const firstOffset = Math.floor((input.startOffset ?? 0) / partSize) * partSize;
|
|
21
|
+
let bytes = firstOffset;
|
|
20
22
|
let items = 0;
|
|
21
23
|
let stopped = false;
|
|
22
24
|
async function readPart(sourcePart) {
|
|
@@ -78,7 +80,7 @@ export async function multipartTransfer(input) {
|
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
async function* sourceParts() {
|
|
81
|
-
for await (const part of rangeReadableMultipartReader(readable, totalBytes, partSize)) {
|
|
83
|
+
for await (const part of rangeReadableMultipartReader(readable, totalBytes, partSize, firstOffset)) {
|
|
82
84
|
if (options.signal?.aborted)
|
|
83
85
|
throw createAbortError();
|
|
84
86
|
if (options.stop?.aborted) {
|
|
@@ -2,7 +2,8 @@ import type { Part, PayloadKind, RangeReadable, StreamHandle } from "@flowscript
|
|
|
2
2
|
/**
|
|
3
3
|
* Splits a `RangeReadable` handle into parts of `partSize` bytes covering
|
|
4
4
|
* `totalSize`. Each part's stream is opened with `readRange(start, end)`
|
|
5
|
-
* (`end` exclusive) when the part is pulled.
|
|
5
|
+
* (`end` exclusive) when the part is pulled. With `startOffset`, parts begin
|
|
6
|
+
* at the part containing that offset.
|
|
6
7
|
*/
|
|
7
|
-
export declare function rangeReadableMultipartReader<K extends PayloadKind>(handle: StreamHandle<K> & RangeReadable<K>, totalSize: number, partSize: number): AsyncGenerator<Part<K>>;
|
|
8
|
+
export declare function rangeReadableMultipartReader<K extends PayloadKind>(handle: StreamHandle<K> & RangeReadable<K>, totalSize: number, partSize: number, startOffset?: number): AsyncGenerator<Part<K>>;
|
|
8
9
|
//# sourceMappingURL=rangeReadableMultipartReader.d.ts.map
|
|
@@ -1 +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
|
|
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;;;;;GAKG;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,EAChB,WAAW,SAAI,GACd,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAazB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Splits a `RangeReadable` handle into parts of `partSize` bytes covering
|
|
3
3
|
* `totalSize`. Each part's stream is opened with `readRange(start, end)`
|
|
4
|
-
* (`end` exclusive) when the part is pulled.
|
|
4
|
+
* (`end` exclusive) when the part is pulled. With `startOffset`, parts begin
|
|
5
|
+
* at the part containing that offset.
|
|
5
6
|
*/
|
|
6
|
-
export async function* rangeReadableMultipartReader(handle, totalSize, partSize) {
|
|
7
|
+
export async function* rangeReadableMultipartReader(handle, totalSize, partSize, startOffset = 0) {
|
|
7
8
|
const partCount = Math.max(1, Math.ceil(totalSize / partSize));
|
|
8
|
-
for (let index =
|
|
9
|
+
for (let index = Math.floor(startOffset / partSize); index < partCount; index += 1) {
|
|
9
10
|
const start = index * partSize;
|
|
10
11
|
const end = Math.min(start + partSize, totalSize);
|
|
11
12
|
yield {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transferEntry.d.ts","sourceRoot":"","sources":["../../../src/transfer/transferEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"transferEntry.d.ts","sourceRoot":"","sources":["../../../src/transfer/transferEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,UAAU,EAShB,MAAM,0CAA0C,CAAC;AAYlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAChC;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAM/F;AAgBD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAwHtF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BYTES_PAYLOAD_TYPE, isBufferProvider, isFillReadable, isRangeReadable, PermanentIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
1
|
+
import { BYTES_PAYLOAD_TYPE, isBufferProvider, isFillReadable, isRangeReadable, isResumableWritable, PermanentIOError, } from "@flowscripter/pluggable-io-framework-api";
|
|
2
2
|
import { defaultConcurrencyLimiter } from "../concurrency/ConcurrencyLimiter.js";
|
|
3
3
|
import { DEFAULT_RETRY } from "../retry/RetryOptions.js";
|
|
4
4
|
import { withRetry } from "../retry/withRetry.js";
|
|
@@ -83,15 +83,24 @@ export async function transferEntry(input) {
|
|
|
83
83
|
isRangeReadable(readable)) {
|
|
84
84
|
await cancelStream(readable);
|
|
85
85
|
const rangeReadable = readable;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
writer
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
let writer;
|
|
87
|
+
outcome = await withRetry(() => {
|
|
88
|
+
// A retry continues the failed writer's upload when it has a resume token.
|
|
89
|
+
const token = writer && isResumableWritable(writer) ? writer.resumeToken() : undefined;
|
|
90
|
+
writer = token
|
|
91
|
+
? multipartWriter(destKey, partSize, { resume: token })
|
|
92
|
+
: multipartWriter(destKey, partSize);
|
|
93
|
+
return multipartTransfer({
|
|
94
|
+
readable: rangeReadable,
|
|
95
|
+
sink,
|
|
96
|
+
writer,
|
|
97
|
+
totalBytes: size,
|
|
98
|
+
partSize,
|
|
99
|
+
startOffset: token?.offset,
|
|
100
|
+
limiter: options.concurrencyLimiter ?? defaultConcurrencyLimiter,
|
|
101
|
+
context,
|
|
102
|
+
});
|
|
103
|
+
}, retry);
|
|
95
104
|
strategy = "multipart";
|
|
96
105
|
}
|
|
97
106
|
else {
|
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./src/decorators/locallyCached.ts";
|
|
|
4
4
|
export * from "./src/decorators/seekable.ts";
|
|
5
5
|
export * from "./src/registry/detectProtocol.ts";
|
|
6
6
|
export * from "./src/registry/ProviderRegistry.ts";
|
|
7
|
+
export * from "./src/registry/StructuredLocation.ts";
|
|
7
8
|
export * from "./src/retry/RetryOptions.ts";
|
|
8
9
|
export * from "./src/retry/withRetry.ts";
|
|
9
10
|
export * from "./src/transfer/copyMove.ts";
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@flowscripter/pluggable-io-framework-api";
|
|
14
14
|
import type { TransferOptions } from "../transfer/TransferOptions.ts";
|
|
15
15
|
import { detectProtocol } from "./detectProtocol.ts";
|
|
16
|
+
import type { StructuredLocation } from "./StructuredLocation.ts";
|
|
16
17
|
import { DEFAULT_NATIVE_DOMAINS, negotiateTransfer } from "./negotiateTransfer.ts";
|
|
17
18
|
|
|
18
19
|
/** Nested provider resolutions allowed before resolution fails. */
|
|
@@ -33,6 +34,10 @@ export interface TransferProviders {
|
|
|
33
34
|
readonly options: TransferOptions;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function protocolOf(location: string | StructuredLocation): string {
|
|
38
|
+
return typeof location === "string" ? detectProtocol(location) : location.protocol;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
function locationFieldNames(factory: IOProviderFactory): string[] {
|
|
37
42
|
const shape = (factory.locationSchema as { shape?: Record<string, unknown> }).shape;
|
|
38
43
|
return Object.keys(shape ?? {}).sort();
|
|
@@ -41,7 +46,8 @@ function locationFieldNames(factory: IOProviderFactory): string[] {
|
|
|
41
46
|
/**
|
|
42
47
|
* Discovers provider factories and payload converters through a
|
|
43
48
|
* `dynamic-plugin-framework` `PluginManager`, keyed by protocol and
|
|
44
|
-
* payload kind, and creates providers for location strings
|
|
49
|
+
* payload kind, and creates providers for location strings or
|
|
50
|
+
* {@link StructuredLocation}s. It is also the
|
|
45
51
|
* `ProviderResolver` handed to every provider it creates.
|
|
46
52
|
*/
|
|
47
53
|
export class ProviderRegistry implements ProviderResolver {
|
|
@@ -115,7 +121,7 @@ export class ProviderRegistry implements ProviderResolver {
|
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
public createProviderForLocation(
|
|
118
|
-
location: string,
|
|
124
|
+
location: string | StructuredLocation,
|
|
119
125
|
options?: { kind?: PayloadKind; domain?: string },
|
|
120
126
|
): Promise<ResolvedProvider> {
|
|
121
127
|
return this.#resolve(location, options, 0);
|
|
@@ -126,12 +132,12 @@ export class ProviderRegistry implements ProviderResolver {
|
|
|
126
132
|
* (kind, domain) and payload type, or a converter.
|
|
127
133
|
*/
|
|
128
134
|
public async createProvidersForTransfer(
|
|
129
|
-
source: string,
|
|
130
|
-
dest: string,
|
|
135
|
+
source: string | StructuredLocation,
|
|
136
|
+
dest: string | StructuredLocation,
|
|
131
137
|
options?: { kind?: PayloadKind },
|
|
132
138
|
): Promise<TransferProviders> {
|
|
133
|
-
const sourceProtocol =
|
|
134
|
-
const destProtocol =
|
|
139
|
+
const sourceProtocol = protocolOf(source);
|
|
140
|
+
const destProtocol = protocolOf(dest);
|
|
135
141
|
const sourceFactories = this.#factoriesFor(sourceProtocol, options?.kind);
|
|
136
142
|
const destFactories = this.#factoriesFor(destProtocol, options?.kind);
|
|
137
143
|
const negotiation = negotiateTransfer(
|
|
@@ -193,14 +199,14 @@ export class ProviderRegistry implements ProviderResolver {
|
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
async #resolve(
|
|
196
|
-
location: string,
|
|
202
|
+
location: string | StructuredLocation,
|
|
197
203
|
options: { kind?: PayloadKind; domain?: string } | undefined,
|
|
198
204
|
depth: number,
|
|
199
205
|
): Promise<ResolvedProvider> {
|
|
200
206
|
if (depth > MAX_RESOLUTION_DEPTH) {
|
|
201
207
|
throw new PermanentIOError("provider resolution too deep");
|
|
202
208
|
}
|
|
203
|
-
const protocol =
|
|
209
|
+
const protocol = protocolOf(location);
|
|
204
210
|
this.#factoriesFor(protocol, options?.kind);
|
|
205
211
|
const factory = this.getFactory(protocol, options?.kind) as IOProviderFactory;
|
|
206
212
|
let domain: string | undefined;
|
|
@@ -218,11 +224,13 @@ export class ProviderRegistry implements ProviderResolver {
|
|
|
218
224
|
|
|
219
225
|
async #instantiate(
|
|
220
226
|
factory: IOProviderFactory,
|
|
221
|
-
location: string,
|
|
227
|
+
location: string | StructuredLocation,
|
|
222
228
|
domain: string | undefined,
|
|
223
229
|
depth: number,
|
|
224
230
|
): Promise<ResolvedProvider> {
|
|
225
|
-
const
|
|
231
|
+
const raw =
|
|
232
|
+
typeof location === "string" ? factory.parseLocationString(location) : location.location;
|
|
233
|
+
const parsed = factory.locationSchema.parse(raw);
|
|
226
234
|
const { config, target } = factory.toProviderInputs(parsed);
|
|
227
235
|
const resolver: ProviderResolver = {
|
|
228
236
|
createProviderForLocation: (inner, opts) =>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A location given as its protocol plus the raw location object that the
|
|
3
|
+
* protocol's factory `locationSchema` validates, instead of a location
|
|
4
|
+
* string. It can carry fields a string cannot, such as `filename`,
|
|
5
|
+
* `pattern` or credentials.
|
|
6
|
+
*/
|
|
7
|
+
export interface StructuredLocation {
|
|
8
|
+
readonly protocol: string;
|
|
9
|
+
readonly location: unknown;
|
|
10
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type IOProvider,
|
|
3
3
|
type Item,
|
|
4
|
+
type MultipartWriter,
|
|
4
5
|
type Part,
|
|
5
6
|
type RangeReadable,
|
|
6
7
|
type StreamHandle,
|
|
@@ -18,9 +19,11 @@ import { rangeReadableMultipartReader } from "./rangeReadableMultipartReader.ts"
|
|
|
18
19
|
export interface MultipartTransferInput {
|
|
19
20
|
readonly readable: StreamHandle & RangeReadable;
|
|
20
21
|
readonly sink: IOProvider;
|
|
21
|
-
readonly writer:
|
|
22
|
+
readonly writer: MultipartWriter;
|
|
22
23
|
readonly totalBytes: number;
|
|
23
24
|
readonly partSize: number;
|
|
25
|
+
/** Where a resumed upload continues: parts start at the part containing this offset. */
|
|
26
|
+
readonly startOffset?: number;
|
|
24
27
|
readonly limiter: ConcurrencyLimiter;
|
|
25
28
|
readonly context: EntryContext;
|
|
26
29
|
}
|
|
@@ -30,13 +33,15 @@ export interface MultipartTransferInput {
|
|
|
30
33
|
* writer, bounded by `limiter`. A part whose read fails with a
|
|
31
34
|
* `TransientIOError` is re-read on its own, up to `retry.maxRetries` times,
|
|
32
35
|
* within the same upload. `stop` ends the transfer after the parts already
|
|
33
|
-
* started; `signal` aborts it.
|
|
36
|
+
* started; `signal` aborts it. Reported bytes include the parts before
|
|
37
|
+
* `startOffset`.
|
|
34
38
|
*/
|
|
35
39
|
export async function multipartTransfer(input: MultipartTransferInput): Promise<EntryOutcome> {
|
|
36
40
|
const { readable, sink, writer, totalBytes, partSize, limiter, context } = input;
|
|
37
41
|
const { options, hooks, operationId, type } = context;
|
|
38
42
|
const retry = options.retry ?? DEFAULT_RETRY;
|
|
39
|
-
|
|
43
|
+
const firstOffset = Math.floor((input.startOffset ?? 0) / partSize) * partSize;
|
|
44
|
+
let bytes = firstOffset;
|
|
40
45
|
let items = 0;
|
|
41
46
|
let stopped = false;
|
|
42
47
|
|
|
@@ -103,7 +108,12 @@ export async function multipartTransfer(input: MultipartTransferInput): Promise<
|
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
async function* sourceParts(): AsyncGenerator<Part> {
|
|
106
|
-
for await (const part of rangeReadableMultipartReader(
|
|
111
|
+
for await (const part of rangeReadableMultipartReader(
|
|
112
|
+
readable,
|
|
113
|
+
totalBytes,
|
|
114
|
+
partSize,
|
|
115
|
+
firstOffset,
|
|
116
|
+
)) {
|
|
107
117
|
if (options.signal?.aborted) throw createAbortError();
|
|
108
118
|
if (options.stop?.aborted) {
|
|
109
119
|
stopped = true;
|
|
@@ -8,15 +8,17 @@ import type {
|
|
|
8
8
|
/**
|
|
9
9
|
* Splits a `RangeReadable` handle into parts of `partSize` bytes covering
|
|
10
10
|
* `totalSize`. Each part's stream is opened with `readRange(start, end)`
|
|
11
|
-
* (`end` exclusive) when the part is pulled.
|
|
11
|
+
* (`end` exclusive) when the part is pulled. With `startOffset`, parts begin
|
|
12
|
+
* at the part containing that offset.
|
|
12
13
|
*/
|
|
13
14
|
export async function* rangeReadableMultipartReader<K extends PayloadKind>(
|
|
14
15
|
handle: StreamHandle<K> & RangeReadable<K>,
|
|
15
16
|
totalSize: number,
|
|
16
17
|
partSize: number,
|
|
18
|
+
startOffset = 0,
|
|
17
19
|
): AsyncGenerator<Part<K>> {
|
|
18
20
|
const partCount = Math.max(1, Math.ceil(totalSize / partSize));
|
|
19
|
-
for (let index =
|
|
21
|
+
for (let index = Math.floor(startOffset / partSize); index < partCount; index += 1) {
|
|
20
22
|
const start = index * partSize;
|
|
21
23
|
const end = Math.min(start + partSize, totalSize);
|
|
22
24
|
yield {
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
isBufferProvider,
|
|
6
6
|
isFillReadable,
|
|
7
7
|
isRangeReadable,
|
|
8
|
+
isResumableWritable,
|
|
8
9
|
type Item,
|
|
10
|
+
type MultipartWriter,
|
|
9
11
|
PermanentIOError,
|
|
10
12
|
type StreamHandle,
|
|
11
13
|
} from "@flowscripter/pluggable-io-framework-api";
|
|
@@ -124,19 +126,24 @@ export async function transferEntry(input: TransferEntryInput): Promise<Transfer
|
|
|
124
126
|
) {
|
|
125
127
|
await cancelStream(readable);
|
|
126
128
|
const rangeReadable = readable;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
129
|
+
let writer: MultipartWriter | undefined;
|
|
130
|
+
outcome = await withRetry(() => {
|
|
131
|
+
// A retry continues the failed writer's upload when it has a resume token.
|
|
132
|
+
const token = writer && isResumableWritable(writer) ? writer.resumeToken() : undefined;
|
|
133
|
+
writer = token
|
|
134
|
+
? multipartWriter(destKey, partSize, { resume: token })
|
|
135
|
+
: multipartWriter(destKey, partSize);
|
|
136
|
+
return multipartTransfer({
|
|
137
|
+
readable: rangeReadable,
|
|
138
|
+
sink,
|
|
139
|
+
writer,
|
|
140
|
+
totalBytes: size,
|
|
141
|
+
partSize,
|
|
142
|
+
startOffset: token?.offset,
|
|
143
|
+
limiter: options.concurrencyLimiter ?? defaultConcurrencyLimiter,
|
|
144
|
+
context,
|
|
145
|
+
});
|
|
146
|
+
}, retry);
|
|
140
147
|
strategy = "multipart";
|
|
141
148
|
} else {
|
|
142
149
|
let writable = await withRetry(() => sink.getWritableStream(destKey), retry);
|