@zero-transfer/webdav 0.3.1 → 0.4.2
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 +356 -6
- package/dist/index.d.ts +356 -6
- package/dist/index.mjs +263 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1767,6 +1767,186 @@ function summarizeDiagnosticError(error) {
|
|
|
1767
1767
|
return { message: String(error) };
|
|
1768
1768
|
}
|
|
1769
1769
|
|
|
1770
|
+
// src/core/ConnectionPool.ts
|
|
1771
|
+
var DEFAULT_MAX_IDLE_PER_KEY = 4;
|
|
1772
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
|
|
1773
|
+
function createPooledTransferClient(inner, options = {}) {
|
|
1774
|
+
const maxIdlePerKey = Math.max(1, options.maxIdlePerKey ?? DEFAULT_MAX_IDLE_PER_KEY);
|
|
1775
|
+
const idleTimeoutMs = Math.max(0, options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS);
|
|
1776
|
+
const keyOf = options.keyOf ?? defaultKeyOf;
|
|
1777
|
+
const state = {
|
|
1778
|
+
drained: false,
|
|
1779
|
+
idle: /* @__PURE__ */ new Map()
|
|
1780
|
+
};
|
|
1781
|
+
const release = (key, session, tainted) => {
|
|
1782
|
+
if (tainted || state.drained) {
|
|
1783
|
+
return safelyDisconnect(session);
|
|
1784
|
+
}
|
|
1785
|
+
let bucket = state.idle.get(key);
|
|
1786
|
+
if (bucket === void 0) {
|
|
1787
|
+
bucket = [];
|
|
1788
|
+
state.idle.set(key, bucket);
|
|
1789
|
+
}
|
|
1790
|
+
const entry = { session };
|
|
1791
|
+
if (idleTimeoutMs > 0) {
|
|
1792
|
+
entry.idleTimer = setTimeout(() => {
|
|
1793
|
+
evictEntry(state, key, entry);
|
|
1794
|
+
}, idleTimeoutMs);
|
|
1795
|
+
const timer = entry.idleTimer;
|
|
1796
|
+
if (timer !== void 0 && typeof timer.unref === "function") {
|
|
1797
|
+
timer.unref();
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
bucket.push(entry);
|
|
1801
|
+
while (bucket.length > maxIdlePerKey) {
|
|
1802
|
+
const dropped = bucket.shift();
|
|
1803
|
+
if (dropped !== void 0) {
|
|
1804
|
+
clearEntryTimer(dropped);
|
|
1805
|
+
void safelyDisconnect(dropped.session);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return Promise.resolve();
|
|
1809
|
+
};
|
|
1810
|
+
const acquire = async (profile) => {
|
|
1811
|
+
const key = keyOf(profile);
|
|
1812
|
+
const bucket = state.idle.get(key);
|
|
1813
|
+
if (bucket !== void 0 && bucket.length > 0) {
|
|
1814
|
+
const entry = bucket.pop();
|
|
1815
|
+
if (entry !== void 0) {
|
|
1816
|
+
clearEntryTimer(entry);
|
|
1817
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1818
|
+
return { key, session: entry.session };
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
const session = await inner.connect(profile);
|
|
1822
|
+
return { key, session };
|
|
1823
|
+
};
|
|
1824
|
+
return {
|
|
1825
|
+
connect: async (profile) => {
|
|
1826
|
+
const { key, session } = await acquire(profile);
|
|
1827
|
+
return wrapPooledSession(session, key, release);
|
|
1828
|
+
},
|
|
1829
|
+
drainPool: async () => {
|
|
1830
|
+
state.drained = true;
|
|
1831
|
+
const entries = [];
|
|
1832
|
+
for (const bucket of state.idle.values()) {
|
|
1833
|
+
for (const entry of bucket) {
|
|
1834
|
+
clearEntryTimer(entry);
|
|
1835
|
+
entries.push(entry);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
state.idle.clear();
|
|
1839
|
+
await Promise.all(entries.map((entry) => safelyDisconnect(entry.session)));
|
|
1840
|
+
},
|
|
1841
|
+
getCapabilities: ((providerId) => {
|
|
1842
|
+
if (providerId === void 0) return inner.getCapabilities();
|
|
1843
|
+
return inner.getCapabilities(providerId);
|
|
1844
|
+
}),
|
|
1845
|
+
hasProvider: (providerId) => inner.hasProvider(providerId),
|
|
1846
|
+
poolSize: () => {
|
|
1847
|
+
let total = 0;
|
|
1848
|
+
for (const bucket of state.idle.values()) total += bucket.length;
|
|
1849
|
+
return total;
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function defaultKeyOf(profile) {
|
|
1854
|
+
const provider = profile.provider ?? profile.protocol ?? "unknown";
|
|
1855
|
+
const host = profile.host ?? "";
|
|
1856
|
+
const port = profile.port ?? "";
|
|
1857
|
+
const username = typeof profile.username === "string" ? profile.username : "";
|
|
1858
|
+
return `${provider}|${host}|${String(port)}|${username}`;
|
|
1859
|
+
}
|
|
1860
|
+
function evictEntry(state, key, entry) {
|
|
1861
|
+
const bucket = state.idle.get(key);
|
|
1862
|
+
if (bucket === void 0) return;
|
|
1863
|
+
const index = bucket.indexOf(entry);
|
|
1864
|
+
if (index < 0) return;
|
|
1865
|
+
bucket.splice(index, 1);
|
|
1866
|
+
if (bucket.length === 0) state.idle.delete(key);
|
|
1867
|
+
clearEntryTimer(entry);
|
|
1868
|
+
void safelyDisconnect(entry.session);
|
|
1869
|
+
}
|
|
1870
|
+
function clearEntryTimer(entry) {
|
|
1871
|
+
if (entry.idleTimer !== void 0) {
|
|
1872
|
+
clearTimeout(entry.idleTimer);
|
|
1873
|
+
delete entry.idleTimer;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
async function safelyDisconnect(session) {
|
|
1877
|
+
try {
|
|
1878
|
+
await session.disconnect();
|
|
1879
|
+
} catch {
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function isTaintingError(error) {
|
|
1883
|
+
return error instanceof ConnectionError || error instanceof TimeoutError || error instanceof ProtocolError;
|
|
1884
|
+
}
|
|
1885
|
+
function wrapPooledSession(session, key, release) {
|
|
1886
|
+
let tainted = false;
|
|
1887
|
+
let released = false;
|
|
1888
|
+
const guard = (fn) => {
|
|
1889
|
+
let promise;
|
|
1890
|
+
try {
|
|
1891
|
+
promise = fn();
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
if (isTaintingError(error)) tainted = true;
|
|
1894
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
1895
|
+
}
|
|
1896
|
+
return promise.catch((error) => {
|
|
1897
|
+
if (isTaintingError(error)) tainted = true;
|
|
1898
|
+
throw error;
|
|
1899
|
+
});
|
|
1900
|
+
};
|
|
1901
|
+
const fs = wrapFs(session.fs, guard);
|
|
1902
|
+
const transfers = session.transfers === void 0 ? void 0 : wrapTransfers(session.transfers, guard);
|
|
1903
|
+
const wrapped = {
|
|
1904
|
+
capabilities: session.capabilities,
|
|
1905
|
+
disconnect: async () => {
|
|
1906
|
+
if (released) return;
|
|
1907
|
+
released = true;
|
|
1908
|
+
await release(key, session, tainted);
|
|
1909
|
+
},
|
|
1910
|
+
fs,
|
|
1911
|
+
provider: session.provider,
|
|
1912
|
+
...transfers !== void 0 ? { transfers } : {}
|
|
1913
|
+
};
|
|
1914
|
+
if (typeof session.raw === "function") {
|
|
1915
|
+
const rawFn = session.raw.bind(session);
|
|
1916
|
+
wrapped.raw = () => rawFn();
|
|
1917
|
+
}
|
|
1918
|
+
return wrapped;
|
|
1919
|
+
}
|
|
1920
|
+
function wrapFs(fs, guard) {
|
|
1921
|
+
const wrapped = {
|
|
1922
|
+
list: (path2, options) => guard(() => options !== void 0 ? fs.list(path2, options) : fs.list(path2)),
|
|
1923
|
+
stat: (path2, options) => guard(() => options !== void 0 ? fs.stat(path2, options) : fs.stat(path2))
|
|
1924
|
+
};
|
|
1925
|
+
if (typeof fs.remove === "function") {
|
|
1926
|
+
const remove = fs.remove.bind(fs);
|
|
1927
|
+
wrapped.remove = (path2, options) => guard(() => options !== void 0 ? remove(path2, options) : remove(path2));
|
|
1928
|
+
}
|
|
1929
|
+
if (typeof fs.rename === "function") {
|
|
1930
|
+
const rename2 = fs.rename.bind(fs);
|
|
1931
|
+
wrapped.rename = (from, to, options) => guard(() => options !== void 0 ? rename2(from, to, options) : rename2(from, to));
|
|
1932
|
+
}
|
|
1933
|
+
if (typeof fs.mkdir === "function") {
|
|
1934
|
+
const mkdir2 = fs.mkdir.bind(fs);
|
|
1935
|
+
wrapped.mkdir = (path2, options) => guard(() => options !== void 0 ? mkdir2(path2, options) : mkdir2(path2));
|
|
1936
|
+
}
|
|
1937
|
+
if (typeof fs.rmdir === "function") {
|
|
1938
|
+
const rmdir = fs.rmdir.bind(fs);
|
|
1939
|
+
wrapped.rmdir = (path2, options) => guard(() => options !== void 0 ? rmdir(path2, options) : rmdir(path2));
|
|
1940
|
+
}
|
|
1941
|
+
return wrapped;
|
|
1942
|
+
}
|
|
1943
|
+
function wrapTransfers(transfers, guard) {
|
|
1944
|
+
return {
|
|
1945
|
+
read: (request) => guard(() => Promise.resolve(transfers.read(request))),
|
|
1946
|
+
write: (request) => guard(() => Promise.resolve(transfers.write(request)))
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1770
1950
|
// src/providers/local/LocalProvider.ts
|
|
1771
1951
|
import { createReadStream } from "fs";
|
|
1772
1952
|
import {
|
|
@@ -4756,12 +4936,14 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4756
4936
|
const secure = options.secure ?? false;
|
|
4757
4937
|
const basePath = options.basePath ?? "";
|
|
4758
4938
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
4939
|
+
const uploadStreaming = options.uploadStreaming ?? "when-known-size";
|
|
4759
4940
|
if (typeof fetchImpl !== "function") {
|
|
4760
4941
|
throw new ConfigurationError({
|
|
4761
4942
|
message: "Global fetch is unavailable; supply WebDavProviderOptions.fetch explicitly",
|
|
4762
4943
|
retryable: false
|
|
4763
4944
|
});
|
|
4764
4945
|
}
|
|
4946
|
+
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.";
|
|
4765
4947
|
const capabilities = {
|
|
4766
4948
|
atomicRename: false,
|
|
4767
4949
|
authentication: ["anonymous", "password", "token"],
|
|
@@ -4771,7 +4953,7 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4771
4953
|
list: true,
|
|
4772
4954
|
maxConcurrency: 8,
|
|
4773
4955
|
metadata: ["modifiedAt", "mimeType", "uniqueId"],
|
|
4774
|
-
notes: [
|
|
4956
|
+
notes: [streamingNote],
|
|
4775
4957
|
provider: id,
|
|
4776
4958
|
readStream: true,
|
|
4777
4959
|
resumeDownload: true,
|
|
@@ -4790,7 +4972,8 @@ function createWebDavProviderFactory(options = {}) {
|
|
|
4790
4972
|
defaultHeaders: { ...options.defaultHeaders ?? {} },
|
|
4791
4973
|
fetch: fetchImpl,
|
|
4792
4974
|
id,
|
|
4793
|
-
secure
|
|
4975
|
+
secure,
|
|
4976
|
+
uploadStreaming
|
|
4794
4977
|
}),
|
|
4795
4978
|
id
|
|
4796
4979
|
};
|
|
@@ -4824,7 +5007,8 @@ var WebDavProvider = class {
|
|
|
4824
5007
|
capabilities: this.internals.capabilities,
|
|
4825
5008
|
fetch: this.internals.fetch,
|
|
4826
5009
|
headers,
|
|
4827
|
-
id: this.internals.id
|
|
5010
|
+
id: this.internals.id,
|
|
5011
|
+
uploadStreaming: this.internals.uploadStreaming
|
|
4828
5012
|
};
|
|
4829
5013
|
if (profile.timeoutMs !== void 0) sessionOptions.timeoutMs = profile.timeoutMs;
|
|
4830
5014
|
return new WebDavSession(sessionOptions);
|
|
@@ -4949,13 +5133,53 @@ var WebDavTransferOperations = class {
|
|
|
4949
5133
|
}
|
|
4950
5134
|
const normalized = normalizeRemotePath(request.endpoint.path);
|
|
4951
5135
|
const url = resolveUrl(this.options.baseUrl, normalized);
|
|
4952
|
-
const
|
|
5136
|
+
const totalBytes = request.totalBytes;
|
|
5137
|
+
const policy = this.options.uploadStreaming;
|
|
5138
|
+
const shouldStream = policy === "always" || policy === "when-known-size" && typeof totalBytes === "number" && totalBytes >= 0;
|
|
5139
|
+
if (!shouldStream) {
|
|
5140
|
+
const buffered = await collectChunks(request.content);
|
|
5141
|
+
const headers2 = {
|
|
5142
|
+
"Content-Length": String(buffered.byteLength),
|
|
5143
|
+
"Content-Type": "application/octet-stream"
|
|
5144
|
+
};
|
|
5145
|
+
const init2 = {
|
|
5146
|
+
body: buffered,
|
|
5147
|
+
headers: headers2,
|
|
5148
|
+
method: "PUT"
|
|
5149
|
+
};
|
|
5150
|
+
if (request.signal !== void 0) init2.signal = request.signal;
|
|
5151
|
+
const response2 = await dispatchRequest(this.options, url, init2);
|
|
5152
|
+
if (!response2.ok) {
|
|
5153
|
+
throw mapResponseError(response2, normalized);
|
|
5154
|
+
}
|
|
5155
|
+
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
5156
|
+
const result2 = {
|
|
5157
|
+
bytesTransferred: buffered.byteLength,
|
|
5158
|
+
totalBytes: buffered.byteLength
|
|
5159
|
+
};
|
|
5160
|
+
const etag2 = response2.headers.get("etag");
|
|
5161
|
+
if (etag2 !== null) result2.checksum = etag2;
|
|
5162
|
+
return result2;
|
|
5163
|
+
}
|
|
5164
|
+
let bytesTransferred = 0;
|
|
5165
|
+
const knownTotal = typeof totalBytes === "number" ? totalBytes : void 0;
|
|
5166
|
+
const stream = asyncIterableToReadableStream(request.content, (chunk) => {
|
|
5167
|
+
bytesTransferred += chunk.byteLength;
|
|
5168
|
+
if (knownTotal !== void 0) {
|
|
5169
|
+
request.reportProgress(bytesTransferred, knownTotal);
|
|
5170
|
+
} else {
|
|
5171
|
+
request.reportProgress(bytesTransferred);
|
|
5172
|
+
}
|
|
5173
|
+
});
|
|
4953
5174
|
const headers = {
|
|
4954
|
-
"Content-Length": String(buffered.byteLength),
|
|
4955
5175
|
"Content-Type": "application/octet-stream"
|
|
4956
5176
|
};
|
|
5177
|
+
if (knownTotal !== void 0) {
|
|
5178
|
+
headers["Content-Length"] = String(knownTotal);
|
|
5179
|
+
}
|
|
4957
5180
|
const init = {
|
|
4958
|
-
body:
|
|
5181
|
+
body: stream,
|
|
5182
|
+
duplex: "half",
|
|
4959
5183
|
headers,
|
|
4960
5184
|
method: "PUT"
|
|
4961
5185
|
};
|
|
@@ -4964,10 +5188,9 @@ var WebDavTransferOperations = class {
|
|
|
4964
5188
|
if (!response.ok) {
|
|
4965
5189
|
throw mapResponseError(response, normalized);
|
|
4966
5190
|
}
|
|
4967
|
-
request.reportProgress(buffered.byteLength, buffered.byteLength);
|
|
4968
5191
|
const result = {
|
|
4969
|
-
bytesTransferred
|
|
4970
|
-
totalBytes:
|
|
5192
|
+
bytesTransferred,
|
|
5193
|
+
...knownTotal !== void 0 ? { totalBytes: knownTotal } : { totalBytes: bytesTransferred }
|
|
4971
5194
|
};
|
|
4972
5195
|
const etag = response.headers.get("etag");
|
|
4973
5196
|
if (etag !== null) result.checksum = etag;
|
|
@@ -4989,6 +5212,36 @@ async function collectChunks(source) {
|
|
|
4989
5212
|
}
|
|
4990
5213
|
return out;
|
|
4991
5214
|
}
|
|
5215
|
+
function asyncIterableToReadableStream(source, onChunk) {
|
|
5216
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
5217
|
+
return new ReadableStream({
|
|
5218
|
+
async pull(controller) {
|
|
5219
|
+
try {
|
|
5220
|
+
const next = await iterator.next();
|
|
5221
|
+
if (next.done === true) {
|
|
5222
|
+
controller.close();
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5225
|
+
const chunk = next.value;
|
|
5226
|
+
if (chunk.byteLength === 0) {
|
|
5227
|
+
return;
|
|
5228
|
+
}
|
|
5229
|
+
controller.enqueue(chunk);
|
|
5230
|
+
onChunk(chunk);
|
|
5231
|
+
} catch (error) {
|
|
5232
|
+
controller.error(error);
|
|
5233
|
+
}
|
|
5234
|
+
},
|
|
5235
|
+
async cancel(reason) {
|
|
5236
|
+
if (typeof iterator.return === "function") {
|
|
5237
|
+
try {
|
|
5238
|
+
await iterator.return(reason);
|
|
5239
|
+
} catch {
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
});
|
|
5244
|
+
}
|
|
4992
5245
|
function parsePropfindResponses(xml, baseUrl) {
|
|
4993
5246
|
const entries = [];
|
|
4994
5247
|
const responseRegex = /<(?:[a-zA-Z0-9-]+:)?response\b[^>]*>([\s\S]*?)<\/(?:[a-zA-Z0-9-]+:)?response>/gi;
|
|
@@ -5101,6 +5354,7 @@ export {
|
|
|
5101
5354
|
createLocalProviderFactory,
|
|
5102
5355
|
createMemoryProviderFactory,
|
|
5103
5356
|
createOAuthTokenSecretSource,
|
|
5357
|
+
createPooledTransferClient,
|
|
5104
5358
|
createProgressEvent,
|
|
5105
5359
|
createProviderTransferExecutor,
|
|
5106
5360
|
createRemoteBrowser,
|