@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.
Files changed (142) hide show
  1. package/README.md +47 -178
  2. package/dist/index.d.ts +11 -4
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +11 -4
  5. package/dist/src/concurrency/AsyncChannel.d.ts +9 -0
  6. package/dist/src/concurrency/AsyncChannel.d.ts.map +1 -0
  7. package/dist/src/concurrency/AsyncChannel.js +48 -0
  8. package/dist/src/{ConcurrencyLimiter.d.ts → concurrency/ConcurrencyLimiter.d.ts} +1 -12
  9. package/dist/src/concurrency/ConcurrencyLimiter.d.ts.map +1 -0
  10. package/dist/src/concurrency/ConcurrencyLimiter.js +64 -0
  11. package/dist/src/concurrency/mapAsyncIterableConcurrently.d.ts +13 -0
  12. package/dist/src/concurrency/mapAsyncIterableConcurrently.d.ts.map +1 -0
  13. package/dist/src/concurrency/mapAsyncIterableConcurrently.js +61 -0
  14. package/dist/src/decorators/locallyCached.d.ts +9 -10
  15. package/dist/src/decorators/locallyCached.d.ts.map +1 -1
  16. package/dist/src/decorators/locallyCached.js +23 -16
  17. package/dist/src/decorators/seekable.d.ts +4 -4
  18. package/dist/src/decorators/seekable.d.ts.map +1 -1
  19. package/dist/src/decorators/seekable.js +2 -2
  20. package/dist/src/registry/ProviderRegistry.d.ts +52 -0
  21. package/dist/src/registry/ProviderRegistry.d.ts.map +1 -0
  22. package/dist/src/registry/ProviderRegistry.js +148 -0
  23. package/dist/src/registry/detectProtocol.d.ts +8 -0
  24. package/dist/src/registry/detectProtocol.d.ts.map +1 -0
  25. package/dist/src/registry/detectProtocol.js +15 -0
  26. package/dist/src/registry/negotiateTransfer.d.ts +29 -0
  27. package/dist/src/registry/negotiateTransfer.d.ts.map +1 -0
  28. package/dist/src/registry/negotiateTransfer.js +69 -0
  29. package/dist/src/retry/RetryOptions.d.ts +19 -0
  30. package/dist/src/retry/RetryOptions.d.ts.map +1 -0
  31. package/dist/src/retry/RetryOptions.js +9 -0
  32. package/dist/src/{withRetry.d.ts → retry/withRetry.d.ts} +1 -5
  33. package/dist/src/retry/withRetry.d.ts.map +1 -0
  34. package/dist/src/{withRetry.js → retry/withRetry.js} +2 -5
  35. package/dist/src/transfer/EntryContext.d.ts +16 -0
  36. package/dist/src/transfer/EntryContext.d.ts.map +1 -0
  37. package/dist/src/transfer/EntryContext.js +1 -0
  38. package/dist/src/transfer/TransferOptions.d.ts +45 -0
  39. package/dist/src/transfer/TransferOptions.d.ts.map +1 -0
  40. package/dist/src/transfer/TransferOptions.js +1 -0
  41. package/dist/src/transfer/TransferResult.d.ts +9 -0
  42. package/dist/src/transfer/TransferResult.d.ts.map +1 -0
  43. package/dist/src/transfer/TransferResult.js +1 -0
  44. package/dist/src/transfer/aggregateResults.d.ts +4 -0
  45. package/dist/src/transfer/aggregateResults.d.ts.map +1 -0
  46. package/dist/src/transfer/aggregateResults.js +9 -0
  47. package/dist/src/transfer/copyMove.d.ts +23 -0
  48. package/dist/src/transfer/copyMove.d.ts.map +1 -0
  49. package/dist/src/transfer/copyMove.js +68 -0
  50. package/dist/src/transfer/leaseTransfer.d.ts +14 -0
  51. package/dist/src/transfer/leaseTransfer.d.ts.map +1 -0
  52. package/dist/src/transfer/leaseTransfer.js +74 -0
  53. package/dist/src/transfer/multipartTransfer.d.ts +23 -0
  54. package/dist/src/transfer/multipartTransfer.d.ts.map +1 -0
  55. package/dist/src/transfer/multipartTransfer.js +94 -0
  56. package/dist/src/transfer/negotiatePartSize.d.ts +10 -0
  57. package/dist/src/transfer/negotiatePartSize.d.ts.map +1 -0
  58. package/dist/src/transfer/negotiatePartSize.js +27 -0
  59. package/dist/src/transfer/patternTransfer.d.ts +12 -0
  60. package/dist/src/transfer/patternTransfer.d.ts.map +1 -0
  61. package/dist/src/transfer/patternTransfer.js +61 -0
  62. package/dist/src/transfer/rangeReadableMultipartReader.d.ts +8 -0
  63. package/dist/src/transfer/rangeReadableMultipartReader.d.ts.map +1 -0
  64. package/dist/src/transfer/rangeReadableMultipartReader.js +19 -0
  65. package/dist/src/transfer/recursiveTransfer.d.ts +13 -0
  66. package/dist/src/transfer/recursiveTransfer.d.ts.map +1 -0
  67. package/dist/src/transfer/recursiveTransfer.js +112 -0
  68. package/dist/src/transfer/streamTransfer.d.ts +32 -0
  69. package/dist/src/transfer/streamTransfer.d.ts.map +1 -0
  70. package/dist/src/transfer/streamTransfer.js +186 -0
  71. package/dist/src/transfer/transferEntry.d.ts +25 -0
  72. package/dist/src/transfer/transferEntry.d.ts.map +1 -0
  73. package/dist/src/transfer/transferEntry.js +136 -0
  74. package/dist/src/util/abort.d.ts +12 -0
  75. package/dist/src/util/abort.d.ts.map +1 -0
  76. package/dist/src/util/abort.js +39 -0
  77. package/dist/src/util/applyPayloadConverter.d.ts +9 -0
  78. package/dist/src/util/applyPayloadConverter.d.ts.map +1 -0
  79. package/dist/src/util/applyPayloadConverter.js +26 -0
  80. package/dist/src/util/describePath.d.ts +6 -0
  81. package/dist/src/util/describePath.d.ts.map +1 -0
  82. package/dist/src/util/describePath.js +9 -0
  83. package/dist/src/util/globToRegex.d.ts +8 -0
  84. package/dist/src/util/globToRegex.d.ts.map +1 -0
  85. package/dist/src/util/globToRegex.js +35 -0
  86. package/dist/src/util/itemLength.d.ts +4 -0
  87. package/dist/src/util/itemLength.d.ts.map +1 -0
  88. package/dist/src/util/itemLength.js +5 -0
  89. package/dist/src/util/keys.d.ts +8 -0
  90. package/dist/src/util/keys.d.ts.map +1 -0
  91. package/dist/src/util/keys.js +19 -0
  92. package/dist/src/util/withParentOperationId.d.ts +4 -0
  93. package/dist/src/util/withParentOperationId.d.ts.map +1 -0
  94. package/dist/src/util/withParentOperationId.js +12 -0
  95. package/index.ts +11 -4
  96. package/package.json +4 -3
  97. package/src/concurrency/AsyncChannel.ts +52 -0
  98. package/src/concurrency/ConcurrencyLimiter.ts +72 -0
  99. package/src/concurrency/mapAsyncIterableConcurrently.ts +66 -0
  100. package/src/decorators/locallyCached.ts +30 -22
  101. package/src/decorators/seekable.ts +7 -7
  102. package/src/registry/ProviderRegistry.ts +241 -0
  103. package/src/registry/detectProtocol.ts +16 -0
  104. package/src/registry/negotiateTransfer.ts +121 -0
  105. package/src/retry/RetryOptions.ts +26 -0
  106. package/src/{withRetry.ts → retry/withRetry.ts} +2 -12
  107. package/src/transfer/EntryContext.ts +17 -0
  108. package/src/transfer/TransferOptions.ts +45 -0
  109. package/src/transfer/TransferResult.ts +8 -0
  110. package/src/transfer/aggregateResults.ts +15 -0
  111. package/src/transfer/copyMove.ts +113 -0
  112. package/src/transfer/leaseTransfer.ts +93 -0
  113. package/src/transfer/multipartTransfer.ts +119 -0
  114. package/src/transfer/negotiatePartSize.ts +40 -0
  115. package/src/transfer/patternTransfer.ts +84 -0
  116. package/src/transfer/rangeReadableMultipartReader.ts +30 -0
  117. package/src/transfer/recursiveTransfer.ts +147 -0
  118. package/src/transfer/streamTransfer.ts +214 -0
  119. package/src/transfer/transferEntry.ts +185 -0
  120. package/src/util/abort.ts +42 -0
  121. package/src/util/applyPayloadConverter.ts +37 -0
  122. package/src/util/describePath.ts +12 -0
  123. package/src/util/globToRegex.ts +32 -0
  124. package/src/util/itemLength.ts +6 -0
  125. package/src/util/keys.ts +21 -0
  126. package/src/util/withParentOperationId.ts +17 -0
  127. package/dist/src/ConcurrencyLimiter.d.ts.map +0 -1
  128. package/dist/src/ConcurrencyLimiter.js +0 -161
  129. package/dist/src/ProviderRegistry.d.ts +0 -14
  130. package/dist/src/ProviderRegistry.d.ts.map +0 -1
  131. package/dist/src/ProviderRegistry.js +0 -22
  132. package/dist/src/chunkLength.d.ts +0 -3
  133. package/dist/src/chunkLength.d.ts.map +0 -1
  134. package/dist/src/chunkLength.js +0 -4
  135. package/dist/src/copyMove.d.ts +0 -46
  136. package/dist/src/copyMove.d.ts.map +0 -1
  137. package/dist/src/copyMove.js +0 -280
  138. package/dist/src/withRetry.d.ts.map +0 -1
  139. package/src/ConcurrencyLimiter.ts +0 -178
  140. package/src/ProviderRegistry.ts +0 -32
  141. package/src/chunkLength.ts +0 -5
  142. package/src/copyMove.ts +0 -458
package/src/copyMove.ts DELETED
@@ -1,458 +0,0 @@
1
- import {
2
- adaptReadableStream,
3
- identityChunkConverter,
4
- type ChunkConverter,
5
- type ChunkRef,
6
- type IOProvider,
7
- type ItemProperties,
8
- type Part,
9
- type PartSizeConstraints,
10
- type TelemetryHooks,
11
- } from "@flowscripter/pluggable-io-framework-api";
12
- import { chunkLength } from "./chunkLength.ts";
13
- import {
14
- type ConcurrencyLimiter,
15
- defaultConcurrencyLimiter,
16
- mapAsyncIterableConcurrently,
17
- } from "./ConcurrencyLimiter.ts";
18
- import { withRetry, type RetryOptions } from "./withRetry.ts";
19
-
20
- export interface TransferOptions {
21
- readonly telemetry?: TelemetryHooks;
22
- /** Minimum file size (bytes) before multipart transfer is attempted over plain streaming. */
23
- readonly multipartThreshold?: number;
24
- /**
25
- * Required only when `source.kind !== sink.kind` - pure-TS code can only
26
- * convert chunks already of the target kind (see
27
- * `identityChunkConverter`); a real js<->native conversion needs an
28
- * FFI-capable converter supplied by a runtime-specific package.
29
- */
30
- readonly chunkConverter?: ChunkConverter;
31
- /**
32
- * Bounds concurrent multipart parts and recursive-copy/move entries.
33
- * Defaults to the shared {@link defaultConcurrencyLimiter} - so two
34
- * `copy()`/`move()` calls in the same process share one cap unless a
35
- * caller passes its own isolated instance.
36
- */
37
- readonly concurrencyLimiter?: ConcurrencyLimiter;
38
- /** Retry policy for the non-direct transfer path. Defaults to `{ maxRetries: 3 }`. */
39
- readonly retry?: RetryOptions;
40
- }
41
-
42
- const DEFAULT_MULTIPART_THRESHOLD = 64 * 1024 * 1024;
43
- const DEFAULT_PART_SIZE = 8 * 1024 * 1024;
44
- const DEFAULT_RETRY: RetryOptions = { maxRetries: 3 };
45
-
46
- const UNCONSTRAINED_PART_SIZE: PartSizeConstraints = {
47
- minPartSize: 0,
48
- maxPartSize: Infinity,
49
- maxParts: Infinity,
50
- defaultPartSize: DEFAULT_PART_SIZE,
51
- };
52
-
53
- /** Wraps `hooks` so any `onProgress` event passing through gets `parentOperationId` filled in, unless the emitter already set one (nested hierarchy). */
54
- function withParentOperationId(
55
- hooks: TelemetryHooks | undefined,
56
- parentOperationId: string | undefined,
57
- ): TelemetryHooks {
58
- if (!hooks || parentOperationId === undefined) {
59
- return hooks ?? {};
60
- }
61
- return {
62
- ...hooks,
63
- onProgress: hooks.onProgress
64
- ? (event) => hooks.onProgress?.({ parentOperationId, ...event })
65
- : undefined,
66
- };
67
- }
68
-
69
- function joinPath(base: string, relative: string): string {
70
- if (relative === "") return base;
71
- if (base === "") return relative;
72
- return `${base.replace(/\/+$/, "")}/${relative}`;
73
- }
74
-
75
- /**
76
- * Reconciles `source`'s and `sink`'s {@link PartSizeConstraints} for a
77
- * transfer of `totalSize` bytes into a single part size satisfying both.
78
- * Returns `undefined` when the bounds are mutually infeasible (source's
79
- * minimum exceeds sink's maximum) - the caller should fall back to plain
80
- * streaming rather than throwing.
81
- */
82
- function negotiatePartSize(
83
- source: IOProvider,
84
- sink: IOProvider,
85
- totalSize: number,
86
- ): number | undefined {
87
- const sourceConstraints = source.getPartSizeConstraints?.(totalSize) ?? UNCONSTRAINED_PART_SIZE;
88
- const sinkConstraints = sink.getPartSizeConstraints?.(totalSize) ?? UNCONSTRAINED_PART_SIZE;
89
- const minPartSize = Math.max(sourceConstraints.minPartSize, sinkConstraints.minPartSize);
90
- const maxPartSize = Math.min(sourceConstraints.maxPartSize, sinkConstraints.maxPartSize);
91
- const maxParts = Math.min(sourceConstraints.maxParts, sinkConstraints.maxParts);
92
- if (minPartSize > maxPartSize) {
93
- return undefined;
94
- }
95
- const byMaxParts = Math.ceil(totalSize / maxParts);
96
- const candidate = Math.max(
97
- minPartSize,
98
- sourceConstraints.defaultPartSize,
99
- sinkConstraints.defaultPartSize,
100
- byMaxParts,
101
- );
102
- return Math.min(candidate, maxPartSize);
103
- }
104
-
105
- function canUseMultipart(source: IOProvider, sink: IOProvider): boolean {
106
- return (
107
- typeof source.getMultipartReader === "function" && typeof sink.getMultipartWriter === "function"
108
- );
109
- }
110
-
111
- async function streamingTransfer(
112
- source: IOProvider,
113
- sourcePath: string,
114
- sink: IOProvider,
115
- destPath: string,
116
- operationId: string,
117
- parentOperationId: string | undefined,
118
- options: TransferOptions,
119
- totalBytes: number | undefined,
120
- ): Promise<void> {
121
- const hooks = withParentOperationId(options.telemetry, parentOperationId);
122
- const readable = await source.getReadableStream(sourcePath);
123
- const writable = await sink.getWritableStream(destPath);
124
- // The source/sink kind mismatch is decided ONCE here, not per chunk.
125
- const adaptedStream = adaptReadableStream(
126
- readable.stream as ReadableStream<ChunkRef>,
127
- readable.kind,
128
- writable.kind,
129
- options.chunkConverter ?? identityChunkConverter,
130
- );
131
- const reader = adaptedStream.getReader();
132
- const writer = (writable.stream as WritableStream<ChunkRef>).getWriter();
133
- let bytesProcessed = 0;
134
- try {
135
- for (;;) {
136
- const { done, value } = await reader.read();
137
- if (done) break;
138
- await writer.write(value);
139
- bytesProcessed += chunkLength(value);
140
- hooks.onProgress?.({ operationId, type: "copy", bytesProcessed, totalBytes });
141
- }
142
- await writer.close();
143
- } catch (error) {
144
- await writer.abort(error);
145
- throw error;
146
- }
147
- }
148
-
149
- async function multipartTransfer(
150
- source: IOProvider,
151
- sourcePath: string,
152
- sink: IOProvider,
153
- destPath: string,
154
- operationId: string,
155
- parentOperationId: string | undefined,
156
- partSize: number,
157
- limiter: ConcurrencyLimiter,
158
- options: TransferOptions,
159
- totalBytes: number | undefined,
160
- ): Promise<void> {
161
- const hooks = withParentOperationId(options.telemetry, parentOperationId);
162
- let bytesProcessed = 0;
163
-
164
- async function transferPart(sourcePart: Part): Promise<Part> {
165
- const partOperationId = crypto.randomUUID();
166
- // Kind mismatch decided once per part, not once per chunk within it.
167
- const adaptedStream = adaptReadableStream(
168
- sourcePart.stream as ReadableStream<ChunkRef>,
169
- sourcePart.kind,
170
- sink.kind,
171
- options.chunkConverter ?? identityChunkConverter,
172
- );
173
- const reader = adaptedStream.getReader();
174
- const chunks: ChunkRef[] = [];
175
- let partBytesProcessed = 0;
176
- for (;;) {
177
- const { done, value } = await reader.read();
178
- if (done) break;
179
- chunks.push(value);
180
- const length = chunkLength(value);
181
- partBytesProcessed += length;
182
- bytesProcessed += length;
183
- hooks.onProgress?.({
184
- operationId: partOperationId,
185
- parentOperationId: operationId,
186
- type: "copy",
187
- bytesProcessed: partBytesProcessed,
188
- });
189
- hooks.onProgress?.({ operationId, type: "copy", bytesProcessed, totalBytes });
190
- }
191
- return {
192
- index: sourcePart.index,
193
- offset: sourcePart.offset,
194
- kind: sink.kind,
195
- stream: new ReadableStream<ChunkRef>({
196
- start(controller) {
197
- for (const chunk of chunks) controller.enqueue(chunk);
198
- controller.close();
199
- },
200
- }),
201
- complete: () => sourcePart.complete(),
202
- };
203
- }
204
-
205
- const writer = sink.getMultipartWriter(destPath, partSize);
206
- const transferredParts = mapAsyncIterableConcurrently(
207
- source.getMultipartReader(sourcePath, partSize),
208
- transferPart,
209
- limiter,
210
- );
211
- await writer.write(transferredParts);
212
- }
213
-
214
- async function copySingle(
215
- source: IOProvider,
216
- sourcePath: string,
217
- sink: IOProvider,
218
- destPath: string,
219
- options: TransferOptions,
220
- parentOperationId: string | undefined,
221
- properties: ItemProperties,
222
- ): Promise<void> {
223
- const operationId = crypto.randomUUID();
224
-
225
- if (source.canDirectTransfer?.(sink) && source.directCopy) {
226
- await source.directCopy(sourcePath, destPath, {
227
- operationId,
228
- hooks: withParentOperationId(options.telemetry, parentOperationId),
229
- });
230
- return;
231
- }
232
-
233
- const threshold = options.multipartThreshold ?? DEFAULT_MULTIPART_THRESHOLD;
234
- const size = properties.size;
235
- const partSize =
236
- size !== undefined && size >= threshold && canUseMultipart(source, sink)
237
- ? negotiatePartSize(source, sink, size)
238
- : undefined;
239
- const limiter = options.concurrencyLimiter ?? defaultConcurrencyLimiter;
240
- const retryOptions = options.retry ?? DEFAULT_RETRY;
241
-
242
- await withRetry(async () => {
243
- if (partSize !== undefined) {
244
- await multipartTransfer(
245
- source,
246
- sourcePath,
247
- sink,
248
- destPath,
249
- operationId,
250
- parentOperationId,
251
- partSize,
252
- limiter,
253
- options,
254
- size,
255
- );
256
- } else {
257
- await streamingTransfer(
258
- source,
259
- sourcePath,
260
- sink,
261
- destPath,
262
- operationId,
263
- parentOperationId,
264
- options,
265
- size,
266
- );
267
- }
268
- }, retryOptions);
269
- }
270
-
271
- async function moveSingle(
272
- source: IOProvider,
273
- sourcePath: string,
274
- sink: IOProvider,
275
- destPath: string,
276
- options: TransferOptions,
277
- parentOperationId: string | undefined,
278
- properties: ItemProperties,
279
- ): Promise<void> {
280
- if (source.canDirectTransfer?.(sink) && source.directMove) {
281
- const operationId = crypto.randomUUID();
282
- await source.directMove(sourcePath, destPath, {
283
- operationId,
284
- hooks: withParentOperationId(options.telemetry, parentOperationId),
285
- });
286
- return;
287
- }
288
- await copySingle(source, sourcePath, sink, destPath, options, parentOperationId, properties);
289
- await withRetry(() => source.delete(sourcePath), options.retry ?? DEFAULT_RETRY);
290
- }
291
-
292
- /**
293
- * Resolves the effective destination root for a recursive transfer,
294
- * following `cp -r`/`mv` semantics: a destination that doesn't exist becomes
295
- * the copy itself; an existing folder gets the source nested inside it as
296
- * `dest/<source-basename>`; an existing file is rejected.
297
- */
298
- async function resolveRecursiveDestRoot(
299
- sourcePath: string,
300
- sink: IOProvider,
301
- destPath: string,
302
- ): Promise<string> {
303
- let destProperties: ItemProperties | undefined;
304
- try {
305
- destProperties = await sink.getProperties(destPath);
306
- } catch {
307
- destProperties = undefined;
308
- }
309
- if (destProperties === undefined) {
310
- return destPath;
311
- }
312
- if (!destProperties.isFolder) {
313
- throw new Error(`Cannot copy/move folder "${sourcePath}" onto existing file "${destPath}"`);
314
- }
315
- const baseName = sourcePath.replace(/\/+$/, "").split("/").pop() || sourcePath;
316
- return joinPath(destPath, baseName);
317
- }
318
-
319
- async function recursiveTransfer(
320
- source: IOProvider,
321
- sourcePath: string,
322
- sink: IOProvider,
323
- destPath: string,
324
- options: TransferOptions,
325
- mode: "copy" | "move",
326
- ): Promise<void> {
327
- const resolvedDestRoot = await resolveRecursiveDestRoot(sourcePath, sink, destPath);
328
-
329
- if (source.canDirectTransfer?.(sink) && source.supportsRecursiveDirectTransfer) {
330
- const telemetry = { operationId: crypto.randomUUID(), hooks: options.telemetry ?? {} };
331
- if (mode === "copy" && source.directCopy) {
332
- await source.directCopy(sourcePath, resolvedDestRoot, telemetry);
333
- return;
334
- }
335
- if (mode === "move" && source.directMove) {
336
- await source.directMove(sourcePath, resolvedDestRoot, telemetry);
337
- return;
338
- }
339
- }
340
-
341
- const recursiveOperationId = crypto.randomUUID();
342
- const limiter = options.concurrencyLimiter ?? defaultConcurrencyLimiter;
343
-
344
- let discovered = 0;
345
- let completed = 0;
346
- const reportProgress = (): void => {
347
- options.telemetry?.onProgress?.({
348
- operationId: recursiveOperationId,
349
- type: mode,
350
- bytesProcessed: 0,
351
- itemsProcessed: completed,
352
- totalItems: discovered,
353
- });
354
- };
355
-
356
- async function processEntry(entry: { path: string; properties: ItemProperties }): Promise<void> {
357
- discovered += 1;
358
- reportProgress();
359
- const entrySourcePath = joinPath(sourcePath, entry.path);
360
- const entryDestPath = joinPath(resolvedDestRoot, entry.path);
361
- if (entry.properties.isFolder) {
362
- await sink.createFolder?.(entryDestPath);
363
- } else if (mode === "copy") {
364
- await copySingle(
365
- source,
366
- entrySourcePath,
367
- sink,
368
- entryDestPath,
369
- options,
370
- recursiveOperationId,
371
- entry.properties,
372
- );
373
- } else if (source.canDirectTransfer?.(sink) && source.directMove) {
374
- // A per-file directMove deletes its source atomically as part of the
375
- // provider's own operation - safe to do immediately. The non-direct
376
- // case below must NOT delete per entry: only after every entry
377
- // succeeds does the caller delete the whole source root once.
378
- const operationId = crypto.randomUUID();
379
- await source.directMove(entrySourcePath, entryDestPath, {
380
- operationId,
381
- hooks: withParentOperationId(options.telemetry, recursiveOperationId),
382
- });
383
- } else {
384
- await copySingle(
385
- source,
386
- entrySourcePath,
387
- sink,
388
- entryDestPath,
389
- options,
390
- recursiveOperationId,
391
- entry.properties,
392
- );
393
- }
394
- completed += 1;
395
- reportProgress();
396
- }
397
-
398
- for await (const _entry of mapAsyncIterableConcurrently(
399
- source.list(sourcePath, { recursive: true }),
400
- processEntry,
401
- limiter,
402
- )) {
403
- // work happens inside processEntry - nothing to do with the (void) result.
404
- }
405
-
406
- if (mode === "move") {
407
- await withRetry(() => source.delete(sourcePath), options.retry ?? DEFAULT_RETRY);
408
- }
409
- }
410
-
411
- /**
412
- * Copies `sourcePath` on `source` to `destPath` on `sink`. When `sourcePath`
413
- * is a folder, recurses: uses a folder-aware `directCopy` when the source
414
- * declares `supportsRecursiveDirectTransfer`, otherwise lists the folder and
415
- * transfers each entry (bounded by `options.concurrencyLimiter`), following
416
- * `cp -r` target semantics (see {@link resolveRecursiveDestRoot}).
417
- *
418
- * For a single file: uses `source.directCopy` when
419
- * `source.canDirectTransfer?.(sink)` reports eligibility. Otherwise falls
420
- * back to multipart transfer (when both sides support it, size crosses
421
- * `options.multipartThreshold`, and part-size negotiation succeeds) or plain
422
- * streaming - retried per `options.retry` on `TransientIOError`.
423
- */
424
- export async function copy(
425
- source: IOProvider,
426
- sourcePath: string,
427
- sink: IOProvider,
428
- destPath: string,
429
- options: TransferOptions = {},
430
- ): Promise<void> {
431
- const properties = await source.getProperties(sourcePath);
432
- if (properties.isFolder) {
433
- await recursiveTransfer(source, sourcePath, sink, destPath, options, "copy");
434
- return;
435
- }
436
- await copySingle(source, sourcePath, sink, destPath, options, undefined, properties);
437
- }
438
-
439
- /**
440
- * Moves `sourcePath` on `source` to `destPath` on `sink`. Folder handling
441
- * mirrors {@link copy}. For a single file, uses `source.directMove` when
442
- * eligible, otherwise performs a {@link copy} followed by deleting the
443
- * source item (each retried independently per `options.retry`).
444
- */
445
- export async function move(
446
- source: IOProvider,
447
- sourcePath: string,
448
- sink: IOProvider,
449
- destPath: string,
450
- options: TransferOptions = {},
451
- ): Promise<void> {
452
- const properties = await source.getProperties(sourcePath);
453
- if (properties.isFolder) {
454
- await recursiveTransfer(source, sourcePath, sink, destPath, options, "move");
455
- return;
456
- }
457
- await moveSingle(source, sourcePath, sink, destPath, options, undefined, properties);
458
- }