@zero-transfer/s3 0.1.6 → 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/README.md +1 -1
- package/dist/index.cjs +237 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +121 -6
- package/dist/index.d.ts +121 -6
- package/dist/index.mjs +241 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -439,7 +439,7 @@ function createPinnedHostKeyError(value) {
|
|
|
439
439
|
function createSshAlgorithmsError(value) {
|
|
440
440
|
return new ConfigurationError({
|
|
441
441
|
details: { algorithms: value },
|
|
442
|
-
message: "Connection profile ssh.algorithms must use
|
|
442
|
+
message: "Connection profile ssh.algorithms must use SSH-compatible non-empty algorithm lists",
|
|
443
443
|
retryable: false
|
|
444
444
|
});
|
|
445
445
|
}
|
|
@@ -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 {
|
|
@@ -4605,6 +4785,17 @@ function isModifiedAtDifferent2(source, destination, toleranceMs) {
|
|
|
4605
4785
|
return Math.abs(sourceTime - destinationTime) > toleranceMs;
|
|
4606
4786
|
}
|
|
4607
4787
|
|
|
4788
|
+
// src/providers/web/S3Provider.ts
|
|
4789
|
+
import { createHash as createHash2 } from "crypto";
|
|
4790
|
+
import {
|
|
4791
|
+
mkdir as fsMkdir,
|
|
4792
|
+
readFile as fsReadFile,
|
|
4793
|
+
rename as fsRename,
|
|
4794
|
+
unlink as fsUnlink,
|
|
4795
|
+
writeFile as fsWriteFile
|
|
4796
|
+
} from "fs/promises";
|
|
4797
|
+
import { join as joinPath } from "path";
|
|
4798
|
+
|
|
4608
4799
|
// src/providers/web/awsSigv4.ts
|
|
4609
4800
|
import { createHash, createHmac as createHmac2 } from "crypto";
|
|
4610
4801
|
function signSigV4(input) {
|
|
@@ -4775,6 +4966,48 @@ function createMemoryS3MultipartResumeStore() {
|
|
|
4775
4966
|
}
|
|
4776
4967
|
};
|
|
4777
4968
|
}
|
|
4969
|
+
function createFileSystemS3MultipartResumeStore(options) {
|
|
4970
|
+
const directory = options.directory;
|
|
4971
|
+
if (typeof directory !== "string" || directory.length === 0) {
|
|
4972
|
+
throw new ConfigurationError({
|
|
4973
|
+
message: "createFileSystemS3MultipartResumeStore requires a non-empty directory option",
|
|
4974
|
+
retryable: false
|
|
4975
|
+
});
|
|
4976
|
+
}
|
|
4977
|
+
const fileFor = (key) => {
|
|
4978
|
+
const hash = createHash2("sha256").update(`${key.bucket}\0${key.jobId}\0${key.path}`).digest("hex");
|
|
4979
|
+
return joinPath(directory, `${hash}.json`);
|
|
4980
|
+
};
|
|
4981
|
+
return {
|
|
4982
|
+
async clear(key) {
|
|
4983
|
+
try {
|
|
4984
|
+
await fsUnlink(fileFor(key));
|
|
4985
|
+
} catch (error) {
|
|
4986
|
+
if (error.code !== "ENOENT") throw error;
|
|
4987
|
+
}
|
|
4988
|
+
},
|
|
4989
|
+
async load(key) {
|
|
4990
|
+
try {
|
|
4991
|
+
const text = await fsReadFile(fileFor(key), "utf8");
|
|
4992
|
+
const parsed = JSON.parse(text);
|
|
4993
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.uploadId !== "string" || !Array.isArray(parsed.parts)) {
|
|
4994
|
+
return void 0;
|
|
4995
|
+
}
|
|
4996
|
+
return parsed;
|
|
4997
|
+
} catch (error) {
|
|
4998
|
+
if (error.code === "ENOENT") return void 0;
|
|
4999
|
+
throw error;
|
|
5000
|
+
}
|
|
5001
|
+
},
|
|
5002
|
+
async save(key, checkpoint) {
|
|
5003
|
+
await fsMkdir(directory, { recursive: true });
|
|
5004
|
+
const target = fileFor(key);
|
|
5005
|
+
const tmp = `${target}.${String(process.pid)}.${String(Date.now())}.tmp`;
|
|
5006
|
+
await fsWriteFile(tmp, JSON.stringify(checkpoint), { encoding: "utf8", mode: 384 });
|
|
5007
|
+
await fsRename(tmp, target);
|
|
5008
|
+
}
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
4778
5011
|
var DEFAULT_MULTIPART_PART_SIZE = 8 * 1024 * 1024;
|
|
4779
5012
|
var DEFAULT_MULTIPART_THRESHOLD = 8 * 1024 * 1024;
|
|
4780
5013
|
var S3_CHECKSUM_CAPABILITIES = ["etag"];
|
|
@@ -4802,7 +5035,7 @@ function createS3ProviderFactory(options = {}) {
|
|
|
4802
5035
|
retryable: false
|
|
4803
5036
|
});
|
|
4804
5037
|
}
|
|
4805
|
-
const multipartEnabled = options.multipart?.enabled ??
|
|
5038
|
+
const multipartEnabled = options.multipart?.enabled ?? true;
|
|
4806
5039
|
const multipart = {
|
|
4807
5040
|
enabled: multipartEnabled,
|
|
4808
5041
|
partSizeBytes: options.multipart?.partSizeBytes ?? DEFAULT_MULTIPART_PART_SIZE,
|
|
@@ -4819,9 +5052,11 @@ function createS3ProviderFactory(options = {}) {
|
|
|
4819
5052
|
maxConcurrency: 16,
|
|
4820
5053
|
metadata: ["modifiedAt", "mimeType", "uniqueId"],
|
|
4821
5054
|
notes: multipartEnabled ? [
|
|
4822
|
-
`S3 multipart upload enabled (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B)
|
|
5055
|
+
`S3 multipart upload enabled by default (partSize=${String(multipart.partSizeBytes)}B, threshold=${String(multipart.thresholdBytes)}B).`,
|
|
5056
|
+
"Payloads at or below the threshold automatically fall back to single-shot PUT.",
|
|
5057
|
+
"Pass `multipart: { enabled: false }` to force the legacy single-shot behaviour."
|
|
4823
5058
|
] : [
|
|
4824
|
-
"S3 provider performs single-shot PUT uploads;
|
|
5059
|
+
"S3 provider performs single-shot PUT uploads; entire object is buffered in memory before transmission."
|
|
4825
5060
|
],
|
|
4826
5061
|
provider: id,
|
|
4827
5062
|
readStream: true,
|
|
@@ -5427,10 +5662,12 @@ export {
|
|
|
5427
5662
|
copyBetween,
|
|
5428
5663
|
createAtomicDeployPlan,
|
|
5429
5664
|
createBandwidthThrottle,
|
|
5665
|
+
createFileSystemS3MultipartResumeStore,
|
|
5430
5666
|
createLocalProviderFactory,
|
|
5431
5667
|
createMemoryProviderFactory,
|
|
5432
5668
|
createMemoryS3MultipartResumeStore,
|
|
5433
5669
|
createOAuthTokenSecretSource,
|
|
5670
|
+
createPooledTransferClient,
|
|
5434
5671
|
createProgressEvent,
|
|
5435
5672
|
createProviderTransferExecutor,
|
|
5436
5673
|
createRemoteBrowser,
|