@ontemper/sdk 0.3.5 → 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/Sftp.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { Context, Effect, Layer, Redacted, Schema, type Scope } from 'effect';
2
+ import { type SftpEntry, type SftpFileType, type SftpStats, type SftpTransport } from './SftpTransport.js';
3
+ export type { SftpChannel, SftpEntry, SftpFileType, SftpStats, SftpTransferOptions, SftpTransport, SftpTransportOptions, } from './SftpTransport.js';
4
+ declare const SftpError_base: Schema.Class<SftpError, Schema.TaggedStruct<"SftpError", {
5
+ readonly host: Schema.String;
6
+ readonly operation: Schema.String;
7
+ readonly path: Schema.optional<Schema.String>;
8
+ readonly message: Schema.String;
9
+ readonly cause: Schema.optional<Schema.Defect>;
10
+ }>, import("effect/Cause").YieldableError>;
11
+ /** An SFTP operation failed; its `SFTP <operation> failed.` line carries the cause. */
12
+ export declare class SftpError extends SftpError_base {
13
+ }
14
+ declare const SftpHostKeyError_base: Schema.Class<SftpHostKeyError, Schema.TaggedStruct<"SftpHostKeyError", {
15
+ readonly host: Schema.String;
16
+ /** The OpenSSH `SHA256:` fingerprint the server offered, when it offered one. */
17
+ readonly offered: Schema.optional<Schema.String>;
18
+ readonly message: Schema.String;
19
+ }>, import("effect/Cause").YieldableError>;
20
+ /** The connection was refused before any credential left: no pins, an unreadable pin, or an unpinned host key. */
21
+ export declare class SftpHostKeyError extends SftpHostKeyError_base {
22
+ }
23
+ export interface SftpConnectOptions {
24
+ readonly host: string;
25
+ /** Default 22. */
26
+ readonly port?: number | undefined;
27
+ readonly username: string;
28
+ readonly password?: Redacted.Redacted<string> | undefined;
29
+ /** PEM or OpenSSH text; literal `\\n` sequences from one-line variables become newlines. */
30
+ readonly privateKey?: Redacted.Redacted<string> | undefined;
31
+ readonly passphrase?: Redacted.Redacted<string> | undefined;
32
+ /**
33
+ * Accepted server host keys as OpenSSH `SHA256:<base64>` fingerprints (`ssh-keygen -lf`). Required:
34
+ * with none, or with a key not in the list, the connection fails before authentication.
35
+ */
36
+ readonly hostKeyFingerprints: ReadonlyArray<string>;
37
+ /** SSH handshake timeout in milliseconds. Default 20000. */
38
+ readonly readyTimeoutMs?: number | undefined;
39
+ /** Bytes per pipelined request, 32 KiB to 1 MiB. Default 32 KiB. */
40
+ readonly chunkBytes?: number | undefined;
41
+ /** Pipelined requests in flight per transfer, 1 to 64. Default 16. */
42
+ readonly concurrency?: number | undefined;
43
+ /** Largest file `get` buffers in memory. Default 256 MiB; use `download` for larger files. */
44
+ readonly maxBufferBytes?: number | undefined;
45
+ }
46
+ /** One connected SFTP mailbox. Paths are remote unless the name says `local`. */
47
+ export interface SftpSession {
48
+ readonly host: string;
49
+ readonly list: (path: string) => Effect.Effect<ReadonlyArray<SftpEntry>, SftpError>;
50
+ readonly stat: (path: string) => Effect.Effect<SftpStats, SftpError>;
51
+ /** The entry's type without following a link, or `false` when nothing exists at the path. */
52
+ readonly exists: (path: string) => Effect.Effect<false | SftpFileType, SftpError>;
53
+ /** Reads the whole file into memory with pipelined requests. */
54
+ readonly get: (path: string) => Effect.Effect<Buffer, SftpError>;
55
+ /** Replaces the file with `data` using pipelined requests. */
56
+ readonly put: (data: Uint8Array, path: string) => Effect.Effect<void, SftpError>;
57
+ /** Streams the remote file to a local file with pipelined requests, never holding it in memory. */
58
+ readonly download: (path: string, localPath: string) => Effect.Effect<void, SftpError>;
59
+ /** Streams a local file to the remote path with pipelined requests. */
60
+ readonly upload: (localPath: string, path: string) => Effect.Effect<void, SftpError>;
61
+ readonly rename: (from: string, to: string) => Effect.Effect<void, SftpError>;
62
+ readonly remove: (path: string) => Effect.Effect<void, SftpError>;
63
+ readonly mkdir: (path: string) => Effect.Effect<void, SftpError>;
64
+ }
65
+ export interface SftpConnector {
66
+ /**
67
+ * Connects for the life of the enclosing scope: closing the scope, including on the Run deadline or an
68
+ * interrupt, closes the connection and logs `SFTP close`.
69
+ */
70
+ readonly connect: (options: SftpConnectOptions) => Effect.Effect<SftpSession, SftpError | SftpHostKeyError, Scope.Scope>;
71
+ }
72
+ /** The OpenSSH `SHA256:` fingerprint of a raw public host key, without base64 padding. */
73
+ export declare const hostKeyFingerprint: (key: Uint8Array) => string;
74
+ declare const Sftp_base: Context.ServiceClass<Sftp, "@temper/workflow/Sftp", SftpConnector>;
75
+ /** First-party SFTP: `yield* Sftp`, then `connect` inside the Run's scope. */
76
+ export declare class Sftp extends Sftp_base {
77
+ /** SFTP over `ssh2`. */
78
+ static readonly layer: Layer.Layer<Sftp, never, never>;
79
+ /** The same client over another transport, such as `memorySftp` from `@temper/workflow/sftp/testing`. */
80
+ static readonly layerWith: (transport: SftpTransport) => Layer.Layer<Sftp, never, never>;
81
+ }
82
+ //# sourceMappingURL=Sftp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sftp.d.ts","sourceRoot":"","sources":["../src/Sftp.ts"],"names":[],"mappings":"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"}
package/dist/Sftp.js ADDED
@@ -0,0 +1,127 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Context, Effect, Layer, Redacted, Schema } from 'effect';
3
+ import { withSpanLines } from './LogContext.js';
4
+ import { registerSecrets } from './Redaction.js';
5
+ import { ssh2Transport, } from './SftpTransport.js';
6
+ /** An SFTP operation failed; its `SFTP <operation> failed.` line carries the cause. */
7
+ export class SftpError extends Schema.TaggedError()('SftpError', {
8
+ host: Schema.String,
9
+ operation: Schema.String,
10
+ path: Schema.optional(Schema.String),
11
+ message: Schema.String,
12
+ cause: Schema.optional(Schema.Defect()),
13
+ }) {
14
+ }
15
+ /** The connection was refused before any credential left: no pins, an unreadable pin, or an unpinned host key. */
16
+ export class SftpHostKeyError extends Schema.TaggedError()('SftpHostKeyError', {
17
+ host: Schema.String,
18
+ /** The OpenSSH `SHA256:` fingerprint the server offered, when it offered one. */
19
+ offered: Schema.optional(Schema.String),
20
+ message: Schema.String,
21
+ }) {
22
+ }
23
+ const DEFAULTS = { port: 22, readyTimeoutMs: 20_000, chunkBytes: 32 * 1024, concurrency: 16 };
24
+ const DEFAULT_MAX_BUFFER_BYTES = 256 * 1024 * 1024;
25
+ const FINGERPRINT = /^SHA256:[A-Za-z0-9+/]{43}$/;
26
+ /** The OpenSSH `SHA256:` fingerprint of a raw public host key, without base64 padding. */
27
+ export const hostKeyFingerprint = (key) => `SHA256:${createHash('sha256').update(key).digest('base64').replace(/=+$/u, '')}`;
28
+ const inRange = (value, fallback, minimum, maximum) => value === undefined || (Number.isInteger(value) && value >= minimum && value <= maximum)
29
+ ? (value ?? fallback)
30
+ : undefined;
31
+ /** `SFTP <operation>` lines on the SDK logger and one tracer span; failures carry the raw cause as `err`. */
32
+ const traced = (host, operation, fields = () => ({})) => (effect) => effect.pipe(withSpanLines(`SFTP ${operation}`, (outcome) => ({ type: 'sftp', sftp: { host, operation, ...fields(outcome) } }), {
33
+ err: ({ error }) => (error instanceof SftpError && error.cause instanceof Error ? error.cause : undefined),
34
+ }), Effect.withSpan(`SFTP ${operation}`, { attributes: { 'sftp.host': host, 'sftp.operation': operation } }));
35
+ const attempt = (host, operation, path, run) => Effect.tryPromise({
36
+ try: run,
37
+ catch: (cause) => new SftpError({
38
+ host,
39
+ operation,
40
+ path,
41
+ message: `SFTP ${operation}${path === undefined ? '' : ` ${path}`} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
42
+ cause,
43
+ }),
44
+ });
45
+ const sessionOf = (host, channel, transfer, maxBytes) => {
46
+ const run = (operation, path, go, fields = () => ({})) => attempt(host, operation, path, go).pipe(traced(host, operation, (outcome) => ({ path, ...fields(outcome) })));
47
+ return {
48
+ host,
49
+ list: (path) => run('list', path, () => channel.list(path), (outcome) => ({ files: outcome?.result?.map((entry) => entry.name) })),
50
+ stat: (path) => run('stat', path, () => channel.stat(path)),
51
+ exists: (path) => run('exists', path, async () => (await channel.lstat(path))?.type ?? false, (outcome) => ({ exists: outcome?.result })),
52
+ // File contents never reach a log line: only the byte count does.
53
+ get: (path) => run('get', path, () => channel.read(path, { ...transfer, maxBytes }), (outcome) => ({ bytes: outcome?.result?.length })),
54
+ put: (data, path) => run('put', path, () => channel.write(path, data, transfer), () => ({ bytes: data.byteLength })),
55
+ download: (path, localPath) => run('download', path, () => channel.download(path, localPath, transfer)),
56
+ upload: (localPath, path) => run('upload', path, () => channel.upload(localPath, path, transfer)),
57
+ rename: (from, to) => run('rename', from, () => channel.rename(from, to), () => ({ destinationPath: to })),
58
+ remove: (path) => run('remove', path, () => channel.remove(path)),
59
+ mkdir: (path) => run('mkdir', path, () => channel.mkdir(path)),
60
+ };
61
+ };
62
+ const makeSftp = (transport) => ({
63
+ connect: (options) => {
64
+ const { host } = options;
65
+ const refuse = (message, offered) => new SftpHostKeyError({ host, offered, message });
66
+ const pins = new Set(options.hostKeyFingerprints.map((pin) => pin.trim().replace(/=+$/u, '')));
67
+ const privateKey = options.privateKey && Redacted.value(options.privateKey).split('\\n').join('\n');
68
+ const secrets = [options.password, options.privateKey, options.passphrase].flatMap((secret) => secret === undefined ? [] : [Redacted.value(secret)]);
69
+ const port = options.port ?? DEFAULTS.port;
70
+ const chunkBytes = inRange(options.chunkBytes, DEFAULTS.chunkBytes, 32 * 1024, 1024 * 1024);
71
+ const concurrency = inRange(options.concurrency, DEFAULTS.concurrency, 1, 64);
72
+ const maxBytes = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
73
+ let offered;
74
+ const open = Effect.gen(function* () {
75
+ offered = undefined;
76
+ if (pins.size === 0)
77
+ return yield* refuse('SFTP host-key pinning is required: set hostKeyFingerprints.');
78
+ const malformed = [...pins].find((pin) => !FINGERPRINT.test(pin));
79
+ if (malformed !== undefined)
80
+ return yield* refuse(`Host-key pin ${malformed} is not an OpenSSH SHA256 fingerprint.`);
81
+ if (chunkBytes === undefined || concurrency === undefined || !Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
82
+ return yield* new SftpError({ host, operation: 'connect', message: 'SFTP transfer options are out of range.' });
83
+ }
84
+ return yield* Effect.tryPromise({
85
+ try: (signal) => transport.connect({
86
+ host,
87
+ port,
88
+ username: options.username,
89
+ password: options.password && Redacted.value(options.password),
90
+ privateKey,
91
+ passphrase: options.passphrase && Redacted.value(options.passphrase),
92
+ readyTimeoutMs: options.readyTimeoutMs ?? DEFAULTS.readyTimeoutMs,
93
+ verifyHostKey: (key) => {
94
+ const fingerprint = hostKeyFingerprint(key);
95
+ if (pins.has(fingerprint))
96
+ return true;
97
+ offered = fingerprint;
98
+ return false;
99
+ },
100
+ }, signal),
101
+ catch: (cause) => offered === undefined
102
+ ? new SftpError({
103
+ host,
104
+ operation: 'connect',
105
+ message: `SFTP connect failed: ${cause instanceof Error ? cause.message : String(cause)}`,
106
+ cause,
107
+ })
108
+ : refuse(`SFTP server ${host} offered untrusted host-key fingerprint ${offered}.`, offered),
109
+ }).pipe(Effect.map((channel) => ({ channel, chunkBytes, concurrency })));
110
+ }).pipe(traced(host, 'connect'));
111
+ const close = (channel) => Effect.promise(() => channel.end()).pipe(withSpanLines('SFTP close', { type: 'sftp', sftp: { host, operation: 'close' } }));
112
+ return Effect.gen(function* () {
113
+ yield* registerSecrets([...secrets, ...(privateKey === undefined ? [] : [privateKey])]);
114
+ // The connect attempt stays interruptible; once it succeeds, its close is registered before anything else runs.
115
+ const opened = yield* Effect.uninterruptibleMask((restore) => Effect.tap(restore(open), ({ channel }) => Effect.addFinalizer(() => close(channel))));
116
+ return sessionOf(host, opened.channel, opened, maxBytes);
117
+ });
118
+ },
119
+ });
120
+ /** First-party SFTP: `yield* Sftp`, then `connect` inside the Run's scope. */
121
+ export class Sftp extends Context.Service()('@temper/workflow/Sftp') {
122
+ /** SFTP over `ssh2`. */
123
+ static layer = Layer.succeed(Sftp, makeSftp(ssh2Transport));
124
+ /** The same client over another transport, such as `memorySftp` from `@temper/workflow/sftp/testing`. */
125
+ static layerWith = (transport) => Layer.succeed(Sftp, makeSftp(transport));
126
+ }
127
+ //# sourceMappingURL=Sftp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sftp.js","sourceRoot":"","sources":["../src/Sftp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Layer } from 'effect';
2
+ import { Sftp } from './Sftp.js';
3
+ import type { SftpTransport, SftpTransportOptions } from './SftpTransport.js';
4
+ export interface MemorySftpOptions {
5
+ /** Host name → path → bytes; the test reads and seeds them directly. A host that is absent refuses the connection. */
6
+ readonly hosts: Record<string, Map<string, Buffer>>;
7
+ /** Every file's modification time, epoch milliseconds. Default 0. */
8
+ readonly modifyTime?: number;
9
+ /** The host key every memory host presents. Pin `MemorySftp.fingerprint`. */
10
+ readonly hostKey?: Uint8Array;
11
+ }
12
+ export interface MemorySftp {
13
+ /** Provide in place of `Sftp.layer`. */
14
+ readonly layer: Layer.Layer<Sftp>;
15
+ readonly transport: SftpTransport;
16
+ /** The OpenSSH fingerprint of the presented host key. */
17
+ readonly fingerprint: string;
18
+ /** Every connection the client completed, in order, with the options it sent. */
19
+ readonly connections: Array<Omit<SftpTransportOptions, 'verifyHostKey'>>;
20
+ /** Connections not yet closed. */
21
+ readonly open: () => number;
22
+ }
23
+ /**
24
+ * An in-memory SFTP transport for workflow tests: the SDK client runs unchanged on top of it, so host-key
25
+ * pinning, span lines, redaction, and scope release behave as in production. Directories are implicit.
26
+ */
27
+ export declare const memorySftp: (options: MemorySftpOptions) => MemorySftp;
28
+ //# sourceMappingURL=SftpTesting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SftpTesting.d.ts","sourceRoot":"","sources":["../src/SftpTesting.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,IAAI,EAAsB,MAAM,WAAW,CAAC;AACrD,OAAO,KAAK,EAAqC,aAAa,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEjH,MAAM,WAAW,iBAAiB;IAChC,sHAAsH;IACtH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,qEAAqE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC,CAAC,CAAC;IACzE,kCAAkC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC;CAC7B;AAKD;;;GAGG;AACH,eAAO,MAAM,UAAU,YAAa,iBAAiB,KAAG,UA8EvD,CAAC"}
@@ -0,0 +1,91 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { Sftp, hostKeyFingerprint } from './Sftp.js';
3
+ const notFound = (operation, path) => Object.assign(new Error(`${operation}: No such file ${path}`), { code: 2 });
4
+ /**
5
+ * An in-memory SFTP transport for workflow tests: the SDK client runs unchanged on top of it, so host-key
6
+ * pinning, span lines, redaction, and scope release behave as in production. Directories are implicit.
7
+ */
8
+ export const memorySftp = (options) => {
9
+ const hostKey = options.hostKey ?? Buffer.from('temper-memory-sftp-host-key');
10
+ const connections = [];
11
+ let open = 0;
12
+ const statsOf = (data) => ({
13
+ type: '-',
14
+ size: data.length,
15
+ modifyTime: options.modifyTime ?? 0,
16
+ accessTime: options.modifyTime ?? 0,
17
+ });
18
+ const transport = {
19
+ connect: async ({ verifyHostKey, ...sent }) => {
20
+ const files = options.hosts[sent.host];
21
+ if (files === undefined)
22
+ throw Object.assign(new Error(`getaddrinfo ENOTFOUND ${sent.host}`), { code: 'ENOTFOUND' });
23
+ if (!verifyHostKey(hostKey))
24
+ throw new Error('Handshake failed: host key verification failed');
25
+ connections.push(sent);
26
+ open++;
27
+ let ended = false;
28
+ const file = (operation, path) => {
29
+ const found = files.get(path);
30
+ if (found === undefined)
31
+ throw notFound(operation, path);
32
+ return found;
33
+ };
34
+ const channel = {
35
+ list: async (dir) => {
36
+ const prefix = dir.endsWith('/') ? dir : `${dir}/`;
37
+ return [...files.entries()]
38
+ .filter(([path]) => path.startsWith(prefix) && !path.slice(prefix.length).includes('/'))
39
+ .map(([path, entry]) => ({ name: path.slice(prefix.length), ...statsOf(entry) }));
40
+ },
41
+ stat: async (path) => statsOf(file('stat', path)),
42
+ lstat: async (path) => {
43
+ const found = files.get(path);
44
+ return found === undefined ? undefined : statsOf(found);
45
+ },
46
+ read: async (path, { maxBytes }) => {
47
+ const data = file('get', path);
48
+ if (data.length > maxBytes)
49
+ throw new RangeError(`${path} has ${data.length} bytes; get buffers at most ${maxBytes}.`);
50
+ return Buffer.from(data);
51
+ },
52
+ write: async (path, data) => {
53
+ files.set(path, Buffer.from(data));
54
+ },
55
+ download: async (path, localPath) => {
56
+ await writeFile(localPath, file('download', path));
57
+ },
58
+ upload: async (localPath, path) => {
59
+ files.set(path, await readFile(localPath));
60
+ },
61
+ rename: async (from, to) => {
62
+ const moved = file('rename', from);
63
+ if (files.has(to))
64
+ throw Object.assign(new Error(`rename: ${to} exists`), { code: 4 });
65
+ files.delete(from);
66
+ files.set(to, moved);
67
+ },
68
+ remove: async (path) => {
69
+ file('remove', path);
70
+ files.delete(path);
71
+ },
72
+ mkdir: async () => undefined,
73
+ end: async () => {
74
+ if (ended)
75
+ return;
76
+ ended = true;
77
+ open--;
78
+ },
79
+ };
80
+ return channel;
81
+ },
82
+ };
83
+ return {
84
+ layer: Sftp.layerWith(transport),
85
+ transport,
86
+ fingerprint: hostKeyFingerprint(hostKey),
87
+ connections,
88
+ open: () => open,
89
+ };
90
+ };
91
+ //# sourceMappingURL=SftpTesting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SftpTesting.js","sourceRoot":"","sources":["../src/SftpTesting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /** `d` directory, `-` regular file, `l` symbolic link, `?` anything else; the `ls -l` letters. */
2
+ export type SftpFileType = 'd' | '-' | 'l' | '?';
3
+ export interface SftpStats {
4
+ readonly type: SftpFileType;
5
+ readonly size: number;
6
+ /** Epoch milliseconds. */
7
+ readonly modifyTime: number;
8
+ /** Epoch milliseconds. */
9
+ readonly accessTime: number;
10
+ }
11
+ export interface SftpEntry extends SftpStats {
12
+ readonly name: string;
13
+ }
14
+ /** How one pipelined transfer splits a file: request size and requests in flight. */
15
+ export interface SftpTransferOptions {
16
+ readonly chunkBytes: number;
17
+ readonly concurrency: number;
18
+ }
19
+ /** What a transport receives to open one SSH connection with one SFTP channel. */
20
+ export interface SftpTransportOptions {
21
+ readonly host: string;
22
+ readonly port: number;
23
+ readonly username: string;
24
+ readonly password: string | undefined;
25
+ readonly privateKey: string | undefined;
26
+ readonly passphrase: string | undefined;
27
+ readonly readyTimeoutMs: number;
28
+ /** Decides on the server's raw public host key before any credential is sent; `false` aborts. */
29
+ readonly verifyHostKey: (key: Uint8Array) => boolean;
30
+ }
31
+ /** One open SFTP channel. Every path is remote unless the name says `local`. */
32
+ export interface SftpChannel {
33
+ readonly list: (path: string) => Promise<ReadonlyArray<SftpEntry>>;
34
+ readonly stat: (path: string) => Promise<SftpStats>;
35
+ /** `undefined` when nothing exists at the path; a link is reported as a link. */
36
+ readonly lstat: (path: string) => Promise<SftpStats | undefined>;
37
+ readonly read: (path: string, options: SftpTransferOptions & {
38
+ readonly maxBytes: number;
39
+ }) => Promise<Buffer>;
40
+ readonly write: (path: string, data: Uint8Array, options: SftpTransferOptions) => Promise<void>;
41
+ readonly download: (path: string, localPath: string, options: SftpTransferOptions) => Promise<void>;
42
+ readonly upload: (localPath: string, path: string, options: SftpTransferOptions) => Promise<void>;
43
+ readonly rename: (from: string, to: string) => Promise<void>;
44
+ readonly remove: (path: string) => Promise<void>;
45
+ readonly mkdir: (path: string) => Promise<void>;
46
+ /** Closes the connection; resolves once the socket closed or the close timed out. */
47
+ readonly end: () => Promise<void>;
48
+ }
49
+ /** Opens SFTP channels; `signal` aborts a connection attempt that has not finished. */
50
+ export interface SftpTransport {
51
+ readonly connect: (options: SftpTransportOptions, signal: AbortSignal) => Promise<SftpChannel>;
52
+ }
53
+ /** SFTP over `ssh2`: one SSH connection and one SFTP channel per `connect`. */
54
+ export declare const ssh2Transport: SftpTransport;
55
+ //# sourceMappingURL=SftpTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SftpTransport.d.ts","sourceRoot":"","sources":["../src/SftpTransport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,183 @@
1
+ import { Client } from 'ssh2';
2
+ const NO_SUCH_FILE = 2;
3
+ const EOF = 1;
4
+ const CLOSE_TIMEOUT_MS = 1_000;
5
+ const typeOf = (mode) => {
6
+ switch (mode & 0o170000) {
7
+ case 0o040000:
8
+ return 'd';
9
+ case 0o100000:
10
+ return '-';
11
+ case 0o120000:
12
+ return 'l';
13
+ default:
14
+ return '?';
15
+ }
16
+ };
17
+ const statsOf = (stats) => ({
18
+ type: typeOf(stats.mode),
19
+ size: stats.size,
20
+ modifyTime: stats.mtime * 1000,
21
+ accessTime: stats.atime * 1000,
22
+ });
23
+ /** ssh2 puts the SFTP status code of a failed request on the error. */
24
+ const hasStatus = (error, status) => error !== null && error !== undefined && 'code' in error && error.code === status;
25
+ const call = (start) => new Promise((resolve, reject) => start((error, value) => (error ? reject(error) : resolve(value))));
26
+ const settle = (start) => new Promise((resolve, reject) => start((error) => (error ? reject(error) : resolve(undefined))));
27
+ /** Runs `work` for every index with at most `concurrency` in flight; the first failure rejects. */
28
+ const pool = async (count, concurrency, work) => {
29
+ let next = 0;
30
+ const worker = async () => {
31
+ while (next < count)
32
+ await work(next++);
33
+ };
34
+ await Promise.all(Array.from({ length: Math.min(concurrency, count) }, worker));
35
+ };
36
+ const readAt = (sftp, handle, target, offset, length) => new Promise((resolve, reject) => sftp.read(handle, target, offset, length, offset, (error, bytesRead) => {
37
+ if (error && !hasStatus(error, EOF))
38
+ reject(error);
39
+ else
40
+ resolve(error ? 0 : bytesRead);
41
+ }));
42
+ /** Fills `target[start, end)` from the same file offsets; a short read asks again for the rest. */
43
+ const readRange = async (sftp, handle, target, start, end) => {
44
+ for (let offset = start; offset < end;) {
45
+ const bytes = await readAt(sftp, handle, target, offset, end - offset);
46
+ if (bytes === 0)
47
+ throw new Error(`SFTP read ended at byte ${offset} of ${target.length}: the file shrank.`);
48
+ offset += bytes;
49
+ }
50
+ };
51
+ /**
52
+ * Pipelined read: `concurrency` chunk requests in flight on one channel, each written to its own offset,
53
+ * then sequential reads past the reported size until EOF, so a file that grew is never cut short.
54
+ */
55
+ const readFile = async (sftp, path, options) => {
56
+ const handle = await call((done) => sftp.open(path, 'r', done));
57
+ try {
58
+ const { size } = await call((done) => sftp.fstat(handle, done));
59
+ if (size > options.maxBytes)
60
+ throw new RangeError(`${path} has ${size} bytes; get buffers at most ${options.maxBytes}.`);
61
+ const head = Buffer.alloc(size);
62
+ const chunks = Math.ceil(size / options.chunkBytes);
63
+ await pool(chunks, options.concurrency, (index) => readRange(sftp, handle, head, index * options.chunkBytes, Math.min(size, (index + 1) * options.chunkBytes)));
64
+ const tail = [];
65
+ for (let offset = size;;) {
66
+ const chunk = Buffer.alloc(options.chunkBytes);
67
+ const bytes = await new Promise((resolve, reject) => sftp.read(handle, chunk, 0, chunk.length, offset, (error, read) => {
68
+ if (error && !hasStatus(error, EOF))
69
+ reject(error);
70
+ else
71
+ resolve(error ? 0 : read);
72
+ }));
73
+ if (bytes === 0)
74
+ break;
75
+ offset += bytes;
76
+ if (offset > options.maxBytes)
77
+ throw new RangeError(`${path} grew past ${options.maxBytes} bytes during get.`);
78
+ tail.push(chunk.subarray(0, bytes));
79
+ }
80
+ return tail.length === 0 ? head : Buffer.concat([head, ...tail]);
81
+ }
82
+ finally {
83
+ await settle((done) => sftp.close(handle, done)).catch(() => undefined);
84
+ }
85
+ };
86
+ /** Pipelined write: the file is truncated, then `concurrency` chunk writes stay in flight at their offsets. */
87
+ const writeFile = async (sftp, path, data, options) => {
88
+ const handle = await call((done) => sftp.open(path, 'w', done));
89
+ let closed = false;
90
+ try {
91
+ const bytes = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
92
+ const chunks = Math.ceil(bytes.length / options.chunkBytes);
93
+ await pool(chunks, options.concurrency, (index) => {
94
+ const offset = index * options.chunkBytes;
95
+ const length = Math.min(options.chunkBytes, bytes.length - offset);
96
+ return settle((done) => sftp.write(handle, bytes, offset, length, offset, done));
97
+ });
98
+ closed = true;
99
+ // The server may report a failed flush only on close, so a failed close fails the write.
100
+ await settle((done) => sftp.close(handle, done));
101
+ }
102
+ finally {
103
+ if (!closed)
104
+ await settle((done) => sftp.close(handle, done)).catch(() => undefined);
105
+ }
106
+ };
107
+ const channelOf = (client, sftp) => {
108
+ let closed = false;
109
+ client.once('close', () => {
110
+ closed = true;
111
+ });
112
+ return {
113
+ list: async (path) => (await call((done) => sftp.readdir(path, done)))
114
+ .filter((entry) => entry.filename !== '.' && entry.filename !== '..')
115
+ .map((entry) => ({ name: entry.filename, ...statsOf(entry.attrs) })),
116
+ stat: async (path) => statsOf(await call((done) => sftp.stat(path, done))),
117
+ lstat: (path) => new Promise((resolve, reject) => sftp.lstat(path, (error, stats) => {
118
+ if (!error)
119
+ resolve(statsOf(stats));
120
+ else if (hasStatus(error, NO_SUCH_FILE))
121
+ resolve(undefined);
122
+ else
123
+ reject(error);
124
+ })),
125
+ read: (path, options) => readFile(sftp, path, options),
126
+ write: (path, data, options) => writeFile(sftp, path, data, options),
127
+ download: (path, localPath, { chunkBytes, concurrency }) => settle((done) => sftp.fastGet(path, localPath, { chunkSize: chunkBytes, concurrency }, done)),
128
+ upload: (localPath, path, { chunkBytes, concurrency }) => settle((done) => sftp.fastPut(localPath, path, { chunkSize: chunkBytes, concurrency }, done)),
129
+ rename: (from, to) => settle((done) => sftp.rename(from, to, done)),
130
+ remove: (path) => settle((done) => sftp.unlink(path, done)),
131
+ mkdir: (path) => settle((done) => sftp.mkdir(path, done)),
132
+ end: () => new Promise((resolve) => {
133
+ if (closed)
134
+ return resolve();
135
+ const timer = setTimeout(() => {
136
+ client.destroy();
137
+ resolve();
138
+ }, CLOSE_TIMEOUT_MS);
139
+ client.once('close', () => {
140
+ clearTimeout(timer);
141
+ resolve();
142
+ });
143
+ client.end();
144
+ }),
145
+ };
146
+ };
147
+ /** SFTP over `ssh2`: one SSH connection and one SFTP channel per `connect`. */
148
+ export const ssh2Transport = {
149
+ connect: (options, signal) => new Promise((resolve, reject) => {
150
+ const client = new Client();
151
+ const fail = (error) => {
152
+ signal.removeEventListener('abort', abort);
153
+ client.destroy();
154
+ reject(error);
155
+ };
156
+ const abort = () => fail(new Error('SFTP connect cancelled.', { cause: signal.reason }));
157
+ if (signal.aborted)
158
+ return abort();
159
+ signal.addEventListener('abort', abort, { once: true });
160
+ client.once('error', fail);
161
+ client.once('ready', () => client.sftp((error, sftp) => {
162
+ if (error)
163
+ return fail(error);
164
+ signal.removeEventListener('abort', abort);
165
+ client.removeListener('error', fail);
166
+ // A later socket error surfaces on the pending SFTP request; unhandled, it would crash the process.
167
+ client.on('error', () => undefined);
168
+ resolve(channelOf(client, sftp));
169
+ }));
170
+ client.connect({
171
+ host: options.host,
172
+ port: options.port,
173
+ username: options.username,
174
+ password: options.password,
175
+ privateKey: options.privateKey,
176
+ passphrase: options.passphrase,
177
+ readyTimeout: options.readyTimeoutMs,
178
+ ident: 'temper',
179
+ hostVerifier: (key) => options.verifyHostKey(key),
180
+ });
181
+ }),
182
+ };
183
+ //# sourceMappingURL=SftpTransport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SftpTransport.js","sourceRoot":"","sources":["../src/SftpTransport.ts"],"names":[],"mappings":"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"}