@zero-transfer/webdav 0.3.1 → 0.4.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/dist/index.cjs +264 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +78 -1
- package/dist/index.d.ts +78 -1
- package/dist/index.mjs +263 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,7 @@ __export(webdav_exports, {
|
|
|
63
63
|
createLocalProviderFactory: () => createLocalProviderFactory,
|
|
64
64
|
createMemoryProviderFactory: () => createMemoryProviderFactory,
|
|
65
65
|
createOAuthTokenSecretSource: () => createOAuthTokenSecretSource,
|
|
66
|
+
createPooledTransferClient: () => createPooledTransferClient,
|
|
66
67
|
createProgressEvent: () => createProgressEvent,
|
|
67
68
|
createProviderTransferExecutor: () => createProviderTransferExecutor,
|
|
68
69
|
createRemoteBrowser: () => createRemoteBrowser,
|
|
@@ -1882,6 +1883,186 @@ function summarizeDiagnosticError(error) {
|
|
|
1882
1883
|
return { message: String(error) };
|
|
1883
1884
|
}
|
|
1884
1885
|
|
|
1886
|
+
// src/core/ConnectionPool.ts
|
|
1887
|
+
var DEFAULT_MAX_IDLE_PER_KEY = 4;
|
|
1888
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
|
|
1889
|
+
function createPooledTransferClient(inner, options = {}) {
|
|
1890
|
+
const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
|
|
1891
|
+
const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
|
|
1892
|
+
const keyOf = options.keyOf ?? defaultKeyOf;
|
|
1893
|
+
const state = {
|
|
1894
|
+
drained: false,
|
|
1895
|
+
idle: /* @__PURE__ */ new Map()
|
|
1896
|
+
};
|
|
1897
|
+
const release = (key, session, tainted) => {
|
|
1898
|
+
if (tainted || state.drained) {
|
|
1899
|
+
return safelyDisconnect(session);
|
|
1900
|
+
}
|
|
1901
|
+
let bucket = state.idle.get(key);
|
|
1902
|
+
if (bucket === void 0) {
|
|
1903
|
+
bucket = [];
|
|
1904
|
+
state.idle.set(key, bucket);
|
|
1905
|
+
}
|
|
1906
|
+
const entry = { session };
|
|
1907
|
+
if (idleTimeoutMs > 0) {
|
|
1908
|
+
entry.idleTimer = setTimeout(() => {
|
|
1909
|
+
evictEntry(state, key, entry);
|
|
1910
|
+
}, idleTimeoutMs);
|
|
1911
|
+
const timer = entry.idleTimer;
|
|
1912
|
+
if (timer !== void 0 && typeof timer.unref === "function") {
|
|
1913
|
+
timer.unref();
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
bucket.push(entry);
|
|
1917
|
+
while (bucket.length > maxIdlePerKey) {
|
|
1918
|
+
const dropped = bucket.shift();
|
|
1919
|
+
if (dropped !== void 0) {
|
|
1920
|
+
clearEntryTimer(dropped);
|
|
1921
|
+
void safelyDisconnect(dropped.session);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return Promise.resolve();
|
|
1925
|
+
};
|
|
1926
|
+
const acquire = async (profile) => {
|
|
1927
|
+
const key = keyOf(profile);
|
|
1928
|
+
const bucket = state.idle.get(key);
|
|
1929
|
+
if (bucket !== void 0 && bucket.length > 0) {
|
|
1930
|
+
const entry = bucket.pop();
|
|
1931
|
+
if (entry !== void 0) {
|
|
1932
|
+
clearEntryTimer(entry);
|
|
1933
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1934
|
+
return { key, session: entry.session };
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
const session = await inner.connect(profile);
|
|
1938
|
+
return { key, session };
|
|
1939
|
+
};
|
|
1940
|
+
return {
|
|
1941
|
+
connect: async (profile) => {
|
|
1942
|
+
const { key, session } = await acquire(profile);
|
|
1943
|
+
return wrapPooledSession(session, key, release);
|
|
1944
|
+
},
|
|
1945
|
+
drainPool: async () => {
|
|
1946
|
+
state.drained = true;
|
|
1947
|
+
const entries = [];
|
|
1948
|
+
for (const bucket of state.idle.values()) {
|
|
1949
|
+
for (const entry of bucket) {
|
|
1950
|
+
clearEntryTimer(entry);
|
|
1951
|
+
entries.push(entry);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
state.idle.clear();
|
|
1955
|
+
await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
|
|
1956
|
+
},
|
|
1957
|
+
getCapabilities: ((providerId) => {
|
|
1958
|
+
if (providerId === void 0) return inner.getCapabilities();
|
|
1959
|
+
return inner.getCapabilities(providerId);
|
|
1960
|
+
}),
|
|
1961
|
+
hasProvider: (providerId) => inner.hasProvider(providerId),
|
|
1962
|
+
poolSize: () => {
|
|
1963
|
+
let total = 0;
|
|
1964
|
+
for (const bucket of state.idle.values()) total += bucket.length;
|
|
1965
|
+
return total;
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function defaultKeyOf(profile) {
|
|
1970
|
+
const provider = profile.provider ?? profile.protocol ?? "unknown";
|
|
1971
|
+
const host = profile.host ?? "";
|
|
1972
|
+
const port = profile.port ?? "";
|
|
1973
|
+
const username = typeof profile.username === "string" ? profile.username : "";
|
|
1974
|
+
return `${provider}|${host}|${String(port)}|${username}`;
|
|
1975
|
+
}
|
|
1976
|
+
function evictEntry(state, key, entry) {
|
|
1977
|
+
const bucket = state.idle.get(key);
|
|
1978
|
+
if (bucket === void 0) return;
|
|
1979
|
+
const index = bucket.indexOf(entry);
|
|
1980
|
+
if (index < 0) return;
|
|
1981
|
+
bucket.splice(index, 1);
|
|
1982
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1983
|
+
clearEntryTimer(entry);
|
|
1984
|
+
void safelyDisconnect(entry.session);
|
|
1985
|
+
}
|
|
1986
|
+
function clearEntryTimer(entry) {
|
|
1987
|
+
if (entry.idleTimer !== void 0) {
|
|
1988
|
+
clearTimeout(entry.idleTimer);
|
|
1989
|
+
delete entry.idleTimer;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
async function safelyDisconnect(session) {
|
|
1993
|
+
try {
|
|
1994
|
+
await session.disconnect();
|
|
1995
|
+
} catch {
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function isTaintingError(error) {
|
|
1999
|
+
return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
|
|
2000
|
+
}
|
|
2001
|
+
function wrapPooledSession(session, key, release) {
|
|
2002
|
+
let tainted = false;
|
|
2003
|
+
let released = false;
|
|
2004
|
+
const guard = (fn) => {
|
|
2005
|
+
let promise;
|
|
2006
|
+
try {
|
|
2007
|
+
promise = fn();
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
if (isTaintingError(error)) tainted = true;
|
|
2010
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
2011
|
+
}
|
|
2012
|
+
return promise.catch((error) => {
|
|
2013
|
+
if (isTaintingError(error)) tainted = true;
|
|
2014
|
+
throw error;
|
|
2015
|
+
});
|
|
2016
|
+
};
|
|
2017
|
+
const fs = wrapFs(session.fs, guard);
|
|
2018
|
+
const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
|
|
2019
|
+
const wrapped = {
|
|
2020
|
+
capabilities: session.capabilities,
|
|
2021
|
+
disconnect: async () => {
|
|
2022
|
+
if (released) return;
|
|
2023
|
+
released = true;
|
|
2024
|
+
await release(key, session, tainted);
|
|
2025
|
+
},
|
|
2026
|
+
fs,
|
|
2027
|
+
provider: session.provider,
|
|
2028
|
+
...transfers !== void 0 ? { transfers } : {}
|
|
2029
|
+
};
|
|
2030
|
+
if (typeof session.raw === "function") {
|
|
2031
|
+
const rawFn = session.raw.bind(session);
|
|
2032
|
+
wrapped.raw = () => rawFn();
|
|
2033
|
+
}
|
|
2034
|
+
return wrapped;
|
|
2035
|
+
}
|
|
2036
|
+
function wrapFs(fs, guard) {
|
|
2037
|
+
const wrapped = {
|
|
2038
|
+
list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
|
|
2039
|
+
stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
|
|
2040
|
+
};
|
|
2041
|
+
if (typeof fs.remove === "function") {
|
|
2042
|
+
const remove = fs.remove.bind(fs);
|
|
2043
|
+
wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
|
|
2044
|
+
}
|
|
2045
|
+
if (typeof fs.rename === "function") {
|
|
2046
|
+
const rename2 = fs.rename.bind(fs);
|
|
2047
|
+
wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
|
|
2048
|
+
}
|
|
2049
|
+
if (typeof fs.mkdir === "function") {
|
|
2050
|
+
const mkdir2 = fs.mkdir.bind(fs);
|
|
2051
|
+
wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
|
|
2052
|
+
}
|
|
2053
|
+
if (typeof fs.rmdir === "function") {
|
|
2054
|
+
const rmdir = fs.rmdir.bind(fs);
|
|
2055
|
+
wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
|
|
2056
|
+
}
|
|
2057
|
+
return wrapped;
|
|
2058
|
+
}
|
|
2059
|
+
function wrapTransfers(transfers, guard) {
|
|
2060
|
+
return {
|
|
2061
|
+
read: (request) => guard(() => Promise.resolve(transfers.read(request))),
|
|
2062
|
+
write: (request) => guard(() => Promise.resolve(transfers.write(request)))
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1885
2066
|
// src/providers/local/LocalProvider.ts
|
|
1886
2067
|
var import_node_fs = require("fs");
|
|
1887
2068
|
var import_promises2 = require("fs/promises");
|
|
@@ -4861,12 +5042,14 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4861
5042
|
const secure = options.secure ?? false;
|
|
4862
5043
|
const basePath = options.basePath ?? "";
|
|
4863
5044
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
5045
|
+
const uploadStreaming = options.uploadStreaming ?? "when-known-size";
|
|
4864
5046
|
if (typeof fetchImpl !== "function") {
|
|
4865
5047
|
throw new ConfigurationError({
|
|
4866
5048
|
message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
|
|
4867
5049
|
retryable: false
|
|
4868
5050
|
});
|
|
4869
5051
|
}
|
|
5052
|
+
const streamingNote = uploadStreaming === "always" ? "PUT bodies are always streamed (chunked when size is unknown)." : uploadStreaming === "never" ? "PUT bodies are buffered in memory (uploadStreaming: 'never')." : "PUT bodies stream when totalBytes is known; otherwise buffered in memory.";
|
|
4870
5053
|
const capabilities = {
|
|
4871
5054
|
atomicRename: false,
|
|
4872
5055
|
authentication: ["anonymous", "password", "token"],
|
|
@@ -4876,7 +5059,7 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4876
5059
|
list: true,
|
|
4877
5060
|
maxConcurrency: 8,
|
|
4878
5061
|
metadata: ["modifiedAt", "mimeType", "uniqueId"],
|
|
4879
|
-
notes: [
|
|
5062
|
+
notes: [streamingNote],
|
|
4880
5063
|
provider: id,
|
|
4881
5064
|
readStream: true,
|
|
4882
5065
|
resumeDownload: true,
|
|
@@ -4895,7 +5078,8 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4895
5078
|
defaultHeaders: { ...options.defaultHeaders ?? {} },
|
|
4896
5079
|
fetch: fetchImpl,
|
|
4897
5080
|
id,
|
|
4898
|
-
secure
|
|
5081
|
+
secure,
|
|
5082
|
+
uploadStreaming
|
|
4899
5083
|
}),
|
|
4900
5084
|
id
|
|
4901
5085
|
};
|
|
@@ -4929,7 +5113,8 @@ var WebDavProvider = class {
|
|
|
4929
5113
|
capabilities: this.internals.capabilities,
|
|
4930
5114
|
fetch: this.internals.fetch,
|
|
4931
5115
|
headers,
|
|
4932
|
-
id: this.internals.id
|
|
5116
|
+
id: this.internals.id,
|
|
5117
|
+
uploadStreaming: this.internals.uploadStreaming
|
|
4933
5118
|
};
|
|
4934
5119
|
if (profile.timeoutMs !== void 0) sessionOptions.timeoutMs = profile.timeoutMs;
|
|
4935
5120
|
return new WebDavSession(sessionOptions);
|
|
@@ -5054,13 +5239,53 @@ var WebDavTransferOperations = class {
|
|
|
5054
5239
|
}
|
|
5055
5240
|
const normalized = normalizeRemotePath(request.endpoint.path);
|
|
5056
5241
|
const url = resolveUrl(this.options.baseUrl, normalized);
|
|
5057
|
-
const
|
|
5242
|
+
const totalBytes = request.totalBytes;
|
|
5243
|
+
const policy = this.options.uploadStreaming;
|
|
5244
|
+
const shouldStream = policy === "always" || policy === "when-known-size" && typeof totalBytes === "number" && totalBytes >= 0;
|
|
5245
|
+
if (!shouldStream) {
|
|
5246
|
+
const buffered = await collectChunks(request.content);
|
|
5247
|
+
const headers2 = {
|
|
5248
|
+
"Content-Length": String(buffered.byteLength),
|
|
5249
|
+
"Content-Type": "application/octet-stream"
|
|
5250
|
+
};
|
|
5251
|
+
const init2 = {
|
|
5252
|
+
body: buffered,
|
|
5253
|
+
headers: headers2,
|
|
5254
|
+
method: "PUT"
|
|
5255
|
+
};
|
|
5256
|
+
if (request.signal !== void 0) init2.signal = request.signal;
|
|
5257
|
+
const response2 = await dispatchRequest(this.options, url, init2);
|
|
5258
|
+
if (!response2.ok) {
|
|
5259
|
+
throw mapResponseError(response2, normalized);
|
|
5260
|
+
}
|
|
5261
|
+
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
5262
|
+
const result2 = {
|
|
5263
|
+
bytesTransferred: buffered.byteLength,
|
|
5264
|
+
totalBytes: buffered.byteLength
|
|
5265
|
+
};
|
|
5266
|
+
const etag2 = response2.headers.get("etag");
|
|
5267
|
+
if (etag2 !== null) result2.checksum = etag2;
|
|
5268
|
+
return result2;
|
|
5269
|
+
}
|
|
5270
|
+
let bytesTransferred = 0;
|
|
5271
|
+
const knownTotal = typeof totalBytes === "number" ? totalBytes : void 0;
|
|
5272
|
+
const stream = asyncIterableToReadableStream(request.content, (chunk) => {
|
|
5273
|
+
bytesTransferred += chunk.byteLength;
|
|
5274
|
+
if (knownTotal !== void 0) {
|
|
5275
|
+
request.reportProgress(bytesTransferred, knownTotal);
|
|
5276
|
+
} else {
|
|
5277
|
+
request.reportProgress(bytesTransferred);
|
|
5278
|
+
}
|
|
5279
|
+
});
|
|
5058
5280
|
const headers = {
|
|
5059
|
-
"Content-Length": String(buffered.byteLength),
|
|
5060
5281
|
"Content-Type": "application/octet-stream"
|
|
5061
5282
|
};
|
|
5283
|
+
if (knownTotal !== void 0) {
|
|
5284
|
+
headers["Content-Length"] = String(knownTotal);
|
|
5285
|
+
}
|
|
5062
5286
|
const init = {
|
|
5063
|
-
body:
|
|
5287
|
+
body: stream,
|
|
5288
|
+
duplex: "half",
|
|
5064
5289
|
headers,
|
|
5065
5290
|
method: "PUT"
|
|
5066
5291
|
};
|
|
@@ -5069,10 +5294,9 @@ var WebDavTransferOperations = class {
|
|
|
5069
5294
|
if (!response.ok) {
|
|
5070
5295
|
throw mapResponseError(response, normalized);
|
|
5071
5296
|
}
|
|
5072
|
-
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
5073
5297
|
const result = {
|
|
5074
|
-
bytesTransferred
|
|
5075
|
-
totalBytes:
|
|
5298
|
+
bytesTransferred,
|
|
5299
|
+
...knownTotal !== void 0 ? { totalBytes: knownTotal } : { totalBytes: bytesTransferred }
|
|
5076
5300
|
};
|
|
5077
5301
|
const etag = response.headers.get("etag");
|
|
5078
5302
|
if (etag !== null) result.checksum = etag;
|
|
@@ -5094,6 +5318,36 @@ async function collectChunks(source) {
|
|
|
5094
5318
|
}
|
|
5095
5319
|
return out;
|
|
5096
5320
|
}
|
|
5321
|
+
function asyncIterableToReadableStream(source, onChunk) {
|
|
5322
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
5323
|
+
return new ReadableStream({
|
|
5324
|
+
async pull(controller) {
|
|
5325
|
+
try {
|
|
5326
|
+
const next = await iterator.next();
|
|
5327
|
+
if (next.done === true) {
|
|
5328
|
+
controller.close();
|
|
5329
|
+
return;
|
|
5330
|
+
}
|
|
5331
|
+
const chunk = next.value;
|
|
5332
|
+
if (chunk.byteLength === 0) {
|
|
5333
|
+
return;
|
|
5334
|
+
}
|
|
5335
|
+
controller.enqueue(chunk);
|
|
5336
|
+
onChunk(chunk);
|
|
5337
|
+
} catch (error) {
|
|
5338
|
+
controller.error(error);
|
|
5339
|
+
}
|
|
5340
|
+
},
|
|
5341
|
+
async cancel(reason) {
|
|
5342
|
+
if (typeof iterator.return === "function") {
|
|
5343
|
+
try {
|
|
5344
|
+
await iterator.return(reason);
|
|
5345
|
+
} catch {
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5097
5351
|
function parsePropfindResponses(xml, baseUrl) {
|
|
5098
5352
|
const entries = [];
|
|
5099
5353
|
const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
|
|
@@ -5207,6 +5461,7 @@ function parentOf(path2) {
|
|
|
5207
5461
|
createLocalProviderFactory,
|
|
5208
5462
|
createMemoryProviderFactory,
|
|
5209
5463
|
createOAuthTokenSecretSource,
|
|
5464
|
+
createPooledTransferClient,
|
|
5210
5465
|
createProgressEvent,
|
|
5211
5466
|
createProviderTransferExecutor,
|
|
5212
5467
|
createRemoteBrowser,
|