@t4h.framework/sftp 0.8.5 → 0.9.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.
@@ -1,324 +1,101 @@
1
1
  ---
2
2
  name: framework-sftp
3
3
  description: >-
4
- Guides correct use of @t4h.framework/sftp in T4H Framework workflows: SFTP
5
- activities, SftpClaim runtime dependency, the reconciler-aware Sftp client,
6
- Binary file payloads, and testing with TestWorkflowEnvironment. Use when
7
- implementing SFTP upload/download/list in workflows, wiring SftpClaim providers,
8
- mocking SftpClaim in workflow tests, working in packages/sftp, or integrating
9
- remote file transfer (sftp:// connection strings, put/get, mkdir, stat, exists).
4
+ Guides the use of @t4h.framework/sftp to read and write files on an SFTP
5
+ server from a T4H Framework workflow: the Sftp client (get, put, append,
6
+ list, exists, stat, mkdir, rmdir, rename, copy, delete, chmod, downloadDir),
7
+ sftp:// connection strings as a Secret, the Binary returned by get and its
8
+ SftpBinary slice, and how to test with TestWorkflowEnvironment and mockClaim
9
+ of SftpClaim. Use when a workflow must download, upload, list, move or
10
+ delete remote files, or when someone asks for ssh2, ssh2-sftp-client or an
11
+ SFTP connection in a workflow.
10
12
  ---
11
13
 
12
14
  # @t4h.framework/sftp
13
15
 
14
- SFTP **activities**, **`SftpClaim`**, and a high-level **`Sftp`** client for replayable remote file operations in workflows. Package path: `framework/packages/sftp`. Peer dependency: `@t4h.framework/core` (`Binary`, `SyncActivity`, `Claim`, `History`).
16
+ ## Use the client
15
17
 
16
- Import surface (from `src/sftp.ts` only — do not reference unexported modules):
18
+ 1. Declare the connection string as a `Secret` in the `EnvDesign` in
19
+ `src/constants/environments.ts`. The URL (`sftp://user:pass@host:22`)
20
+ carries the password.
21
+ 2. Create one `Sftp` client and call its methods in the workflow body. Each
22
+ call is one durable step.
17
23
 
18
24
  ```typescript
19
- import {
20
- Sftp,
21
- SftpClaim,
22
- SftpAppendActivity,
23
- SftpChmodActivity,
24
- SftpCopyActivity,
25
- SftpCwdActivity,
26
- SftpDeleteActivity,
27
- SftpDownloadDirActivity,
28
- SftpExistsActivity,
29
- SftpGetActivity,
30
- SftpListActivity,
31
- SftpMkdirActivity,
32
- SftpPutActivity,
33
- SftpRealPathActivity,
34
- SftpRenameActivity,
35
- SftpRmdirActivity,
36
- SftpStatActivity,
37
- type SftpAppendActivityInput,
38
- type SftpChmodActivityInput,
39
- type SftpCopyActivityInput,
40
- type SftpCwdActivityInput,
41
- type SftpDeleteActivityInput,
42
- type SftpDownloadDirActivityInput,
43
- type SftpExistsActivityInput,
44
- type SftpExistsResult,
45
- type SftpGetActivityInput,
46
- type SftpFileInfo,
47
- type SftpListActivityInput,
48
- type SftpMkdirActivityInput,
49
- type SftpPutActivityInput,
50
- type SftpRealPathActivityInput,
51
- type SftpRenameActivityInput,
52
- type SftpRmdirActivityInput,
53
- type SftpStatActivityInput,
54
- type SftpStat,
55
- } from '@t4h.framework/sftp'
25
+ // src/constants/environments.ts
26
+ import { EnvDesign, Secret } from '@t4h.framework/core'
56
27
 
57
- import type { Binary } from '@t4h.framework/core'
58
- ```
59
-
60
- Node `>=22`. See [README.md](../../../README.md).
61
-
62
- ---
63
-
64
- ## Architecture
65
-
66
- `Workflow` → **`Sftp`** (`History.bind` per activity) → shipped `Sftp*Activity` → **`SftpClaim.<method>(input)`** (runtime impl).
67
-
68
- From a workflow you call the **`Sftp`** client; it forwards each method to the
69
- bound, replay-safe activity for you. The claim method name matches the client
70
- method (`get`, `put`, `delete`, …) and the runtime supplies the `SftpClaim`
71
- implementation.
72
-
73
- ---
74
-
75
- ## Claims
76
-
77
- ### SftpClaim (required)
78
-
79
- Runtime must register a concrete implementation:
80
-
81
- ```typescript
82
- { provide: SftpClaim, value: new MySftpClaimImpl() }
83
- ```
84
-
85
- Abstract contract (each method accepts the corresponding `*ActivityInput` type):
86
-
87
- | Method | Input type | Return |
88
- |--------|------------|--------|
89
- | `append` | `SftpAppendActivityInput` | `void` |
90
- | `chmod` | `SftpChmodActivityInput` | `void` |
91
- | `copy` | `SftpCopyActivityInput` | `void` |
92
- | `cwd` | `SftpCwdActivityInput` | `string` |
93
- | `delete` | `SftpDeleteActivityInput` | `void` |
94
- | `downloadDir` | `SftpDownloadDirActivityInput` | `Binary` |
95
- | `exists` | `SftpExistsActivityInput` | `SftpExistsResult` |
96
- | `get` | `SftpGetActivityInput` | `Binary` |
97
- | `list` | `SftpListActivityInput` | `SftpFileInfo[]` |
98
- | `mkdir` | `SftpMkdirActivityInput` | `void` |
99
- | `put` | `SftpPutActivityInput` | `void` |
100
- | `realPath` | `SftpRealPathActivityInput` | `string` |
101
- | `rename` | `SftpRenameActivityInput` | `void` |
102
- | `rmdir` | `SftpRmdirActivityInput` | `void` |
103
- | `stat` | `SftpStatActivityInput` | `SftpStat` |
104
-
105
- Methods may return sync values or `Promise`s.
106
-
107
- ---
108
-
109
- ## Sftp client (preferred in workflows)
110
-
111
- Instantiate with the `Secret` an `EnvDesign` entry declared `type: Secret` hands the workflow (the URL `sftp://user:pass@host:22` carries the password, so a string is a `tsc` error; the host reads the value where each activity runs):
112
-
113
- ```typescript
114
- import { Sftp } from '@t4h.framework/sftp'
115
- import { environments } from '../constants/environments.js'
116
-
117
- const sftp = new Sftp(environments.SFTP_URL) // Secret
118
- ```
119
-
120
- All calls run inside a workflow (the runtime, or `TestWorkflowEnvironment` in tests, provides the reconciler context).
121
-
122
- ### Methods
123
-
124
- | Method | Args | Returns |
125
- |--------|------|---------|
126
- | `append` | `filepath`, `content: Binary` | `void` |
127
- | `chmod` | `path`, `mode: string \| number` | `void` |
128
- | `copy` | `srcPath`, `destPath` | `void` |
129
- | `cwd` | — | `string` |
130
- | `delete` | `path`, `noErrorOK?: boolean` | `void` |
131
- | `downloadDir` | `path` | `Binary` |
132
- | `exists` | `path` | `SftpExistsResult` |
133
- | `get` | `filepath` | `Binary` |
134
- | `list` | `path` | `SftpFileInfo[]` |
135
- | `mkdir` | `path`, `recursive?: boolean` | `void` |
136
- | `put` | `filepath`, `content: Binary` | `void` |
137
- | `realPath` | `path` | `string` |
138
- | `rename` | `srcPath`, `destPath` | `void` |
139
- | `rmdir` | `path`, `recursive?: boolean` | `void` |
140
- | `stat` | `path` | `SftpStat` |
141
-
142
- **Flags:**
143
-
144
- - **`delete(path, true)`** — `noErrorOK`: suppress error when file missing.
145
- - **`mkdir(path, true)`** — `recursive`: create intermediate directories.
146
- - **`rmdir(path, true)`** — `recursive`: remove directory tree.
147
-
148
- **`exists`:** `false` if not found; otherwise `'d'` (directory), `'-'` (file), `'l'` (symlink).
149
-
150
- ### Example workflow usage
151
-
152
- ```typescript
153
- import { Workflow } from '@t4h.framework/core'
154
- import { Sftp } from '@t4h.framework/sftp'
155
-
156
- const sftp = new Sftp(process.env.SFTP_URL!)
157
-
158
- export const ingestRemoteFile = new Workflow(async () => {
159
- const remotePath = '/incoming/report.csv'
160
-
161
- if ((await sftp.exists(remotePath)) === false) {
162
- return { ok: false, reason: 'missing' }
163
- }
164
-
165
- const file = await sftp.get(remotePath)
166
- await sftp.rename(remotePath, '/processed/report.csv')
167
-
168
- return { ok: true, size: file /* use Binary APIs from core */ }
28
+ export const environments = EnvDesign({
29
+ SFTP_URL: { type: Secret },
169
30
  })
170
31
  ```
171
32
 
172
- Use **`Binary`** from `@t4h.framework/core` for upload/download content (`put`, `get`, `append`, `downloadDir`).
173
-
174
- ---
175
-
176
- ## Using SFTP in a workflow via `History.reconciler`
177
-
178
- Prefer the **`Sftp`** client. When you need the exported activity directly,
179
- drive the **shipped** `Sftp*Activity` class through `History.reconciler` — this
180
- is a ready, replay-safe helper. **Never define or extend your own activity**;
181
- only pass the activity classes this package exports.
182
-
183
- ```typescript
184
- import { History } from '@t4h.framework/core'
185
- import { Sftp, SftpGetActivity } from '@t4h.framework/sftp'
186
-
187
- const sftp = new Sftp(environments.SFTP_URL) // Secret
188
- const content = await History.reconciler(SftpGetActivity, sftp, '/remote/file.txt')
189
- ```
190
-
191
- Activity ↔ client mapping:
192
-
193
- | Activity | Client method |
194
- |----------|---------------|
195
- | `SftpAppendActivity` | `append` |
196
- | `SftpChmodActivity` | `chmod` |
197
- | `SftpCopyActivity` | `copy` |
198
- | `SftpCwdActivity` | `cwd` |
199
- | `SftpDeleteActivity` | `delete` |
200
- | `SftpDownloadDirActivity` | `downloadDir` |
201
- | `SftpExistsActivity` | `exists` |
202
- | `SftpGetActivity` | `get` |
203
- | `SftpListActivity` | `list` |
204
- | `SftpMkdirActivity` | `mkdir` |
205
- | `SftpPutActivity` | `put` |
206
- | `SftpRealPathActivity` | `realPath` |
207
- | `SftpRenameActivity` | `rename` |
208
- | `SftpRmdirActivity` | `rmdir` |
209
- | `SftpStatActivity` | `stat` |
210
-
211
- ---
212
-
213
- ## Activity input shapes
214
-
215
- All inputs include **`connectionString: Secret`** (the name of the env entry; the value is read on the host). Path field names differ by operation:
216
-
217
- | Input type | Fields (beyond `connectionString`) |
218
- |------------|-----------------------------------|
219
- | `SftpAppendActivityInput` | `filepath`, `content: Binary` |
220
- | `SftpChmodActivityInput` | `path`, `mode: string \| number` |
221
- | `SftpCopyActivityInput` | `srcPath`, `destPath` |
222
- | `SftpCwdActivityInput` | _(none)_ |
223
- | `SftpDeleteActivityInput` | `path`, `noErrorOK?: boolean` |
224
- | `SftpDownloadDirActivityInput` | `path` |
225
- | `SftpExistsActivityInput` | `path` |
226
- | `SftpGetActivityInput` | `filepath` |
227
- | `SftpListActivityInput` | `path` |
228
- | `SftpMkdirActivityInput` | `path`, `recursive?: boolean` |
229
- | `SftpPutActivityInput` | `filepath`, `content: Binary` |
230
- | `SftpRealPathActivityInput` | `path` |
231
- | `SftpRenameActivityInput` | `srcPath`, `destPath` |
232
- | `SftpRmdirActivityInput` | `path`, `recursive?: boolean` |
233
- | `SftpStatActivityInput` | `path` |
234
-
235
- ---
236
-
237
- ## Result types
238
-
239
- ### `SftpExistsResult`
240
-
241
33
  ```typescript
242
- type SftpExistsResult = false | 'd' | '-' | 'l'
243
- ```
34
+ import { Sftp } from '@t4h.framework/sftp'
244
35
 
245
- ### `SftpFileInfo` (`list`)
36
+ import { environments } from '../constants/environments.js'
246
37
 
247
- ```typescript
248
- type SftpFileInfo = {
249
- name: string
250
- type: 'd' | '-' | 'l'
251
- size: number
252
- modifyTime: number
253
- accessTime: number
254
- rights: { user: string; group: string; other: string }
255
- owner: number
256
- group: number
257
- longname: string
258
- }
259
- ```
38
+ const sftp = new Sftp(environments.SFTP_URL)
260
39
 
261
- ### `SftpStat` (`stat`)
40
+ for (const entry of await sftp.list('/inbound')) {
41
+ if (entry.type !== '-') continue
262
42
 
263
- ```typescript
264
- type SftpStat = {
265
- mode: number
266
- uid: number
267
- gid: number
268
- size: number
269
- accessTime: number
270
- modifyTime: number
271
- isDirectory: boolean
272
- isFile: boolean
273
- isBlockDevice: boolean
274
- isCharacterDevice: boolean
275
- isSymbolicLink: boolean
276
- isFIFO: boolean
277
- isSocket: boolean
43
+ const file = await sftp.get(`/inbound/${entry.name}`)
44
+ await sftp.put(`/archive/${entry.name}`, file)
45
+ await sftp.delete(`/inbound/${entry.name}`, true)
278
46
  }
279
47
  ```
280
48
 
281
- ---
282
-
283
- ## Testing
284
-
285
- Test the **workflow** with `TestWorkflowEnvironment` from
286
- `@t4h.framework/core/testing`, mocking `SftpClaim` — the single claim every
287
- `Sftp*Activity` resolves. Provide only the methods your workflow triggers
288
- (`get`, `put`, `delete`, …). See the **framework-workflow-testing** skill for the
289
- harness API.
49
+ ## Traps
50
+
51
+ - `new Sftp(...)` takes a `Secret`. A string literal or a `String` env entry is
52
+ a type error.
53
+ - `get` returns a `Binary`, a reference to the remote file. Pass it to the
54
+ next step (`put`, `split`, `unzip`). Do not buffer it in the workflow. To
55
+ read a header or a trailer, use `read` from `@t4h.framework/fs` with a
56
+ `range`; see the `framework-fs` skill.
57
+ - `put` and `append` take a `Binary`, not a string or a `Buffer`. Make one with
58
+ `write` from `@t4h.framework/fs`. Never build a `Binary` by hand.
59
+ - `exists` returns `false` or a type character (`'d'`, `'-'`, `'l'`), not a
60
+ boolean. `if (await sftp.exists(p))` works; `=== true` never matches.
61
+ - The boolean second arguments are positional: `delete(path, true)` gives no
62
+ error when the file is missing, `mkdir(path, true)` and `rmdir(path, true)`
63
+ are recursive.
64
+ - `SftpBinary.slice(range)`: `range` is relative to the binary you slice, `end`
65
+ is exclusive, and it throws a `RangeError` outside the binary.
66
+ - `@t4h.framework/core/no-io-in-workflow` rejects `ssh2`, `ssh2-sftp-client`
67
+ and `basic-ftp` in a workflow. Use `Sftp`.
68
+ - Do not `new` or `extend` an `Sftp*Activity`
69
+ (`@t4h.framework/core/no-framework-activity-in-app`). Use the client.
70
+ - The runtime must provide `SftpClaim`.
71
+
72
+ ## Test
73
+
74
+ Mock `SftpClaim` with only the methods the workflow calls. Each claim method
75
+ has the name of the client method and receives an input object with
76
+ `connectionString` and the call arguments. The path field is `filepath` for
77
+ `get`, `put` and `append`, `srcPath` and `destPath` for `copy` and `rename`,
78
+ and `path` for the others.
290
79
 
291
80
  ```typescript
292
81
  import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
293
82
  import { SftpClaim } from '@t4h.framework/sftp'
294
83
 
295
- const mockGet = vi.fn().mockImplementation(async () => someBinary)
84
+ const get = vi.fn().mockResolvedValue(binary)
296
85
 
297
86
  const env = TestWorkflowEnvironment.create({ now: 0 })
298
87
 
299
- const output = await env.execute(workflow, input, {
300
- claims: [mockClaim(SftpClaim, { get: mockGet })],
88
+ await env.execute(workflow, input, {
89
+ claims: [mockClaim(SftpClaim, { get, put: vi.fn() })],
90
+ env: { SFTP_URL: 'sftp://user:pass@sftp.example.com:22' },
301
91
  })
302
92
 
303
- // each SftpClaim method receives the activity's serializable input
304
- expect(mockGet.mock.calls[0][0]).toMatchObject({ filepath: '/remote/file.txt' })
93
+ expect(get.mock.calls[0][0]).toMatchObject({ filepath: '/inbound/a.csv' })
305
94
  ```
306
95
 
307
- An unconfigured claim method throws a clear error, so mock every method the
308
- workflow reaches.
309
-
310
- ---
311
-
312
- ## Checklist for agents
313
-
314
- 1. Prefer **`new Sftp(connectionString)`** in workflow code — not raw SSH/SFTP libraries.
315
- 2. Never perform non-replayable SFTP I/O inside workflows — use **`Sftp`** or **`History.reconciler(Sftp*Activity, ...)`**.
316
- 3. Ensure runtime provides **`SftpClaim`** (or pass a mock in tests).
317
- 4. Pass **`Binary`** for `put` / `append` / `get` / `downloadDir` content — from `@t4h.framework/core`.
318
- 5. Use **`filepath`** for get/put/append; **`path`** for most other operations; **`srcPath`/`destPath`** for copy/rename.
319
- 6. Use **`exists`** before destructive ops when idempotency matters; use **`delete(..., true)`** for tolerant deletes.
320
- 7. Use **`mkdir(..., true)`** / **`rmdir(..., true)`** only when recursive tree ops are intended.
321
- 8. Test workflows with **`TestWorkflowEnvironment`** + **`mockClaim(SftpClaim, ...)`**; assert on the mock's `mock.calls`.
322
- 9. Do not document or import APIs not exported from `@t4h.framework/sftp`.
96
+ ## Reference
323
97
 
324
- See also: `packages/sftp/README.md`; **framework-workflow-testing** for the test harness.
98
+ - Guide: https://tech4humans-brasil.github.io/framework/sftp/
99
+ - Secrets: https://tech4humans-brasil.github.io/framework/core/environment
100
+ - Workflow testing: https://tech4humans-brasil.github.io/framework/core/testing
101
+ - Types: `node_modules/@t4h.framework/sftp/dist/sftp.d.ts`
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @t4h.framework/sftp
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#246](https://github.com/tech4humans-brasil/framework/pull/246) [`b99b41f`](https://github.com/tech4humans-brasil/framework/commit/b99b41f60d69b3037187e9fcdc27ab0d47ff18a2) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds byte windows to `SftpBinary`. `binary.slice(range)` returns a view of a byte window of the remote file that reuses the stream of `binary`. The new `range` field is absolute in the file, with an exclusive `end`, and `contentLength` is `range.end - range.start`. A slice of a slice composes. `toJSON()` includes `range` on a slice only, so the JSON of a whole file does not change. `slice` throws a `RangeError` when the window is not inside the binary. In `@t4h.framework/fs`, the docs no longer give an SFTP reference as an example of a binary that cannot be sliced.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#257](https://github.com/tech4humans-brasil/framework/pull/257) [`586bfb4`](https://github.com/tech4humans-brasil/framework/commit/586bfb4663f4a478527ee0c8db360603f034d615) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The sftp README is now short and links to the site. The docs page now has the testing, slice and runtime sections, and the skill keeps only the traps that the types do not show.
12
+
13
+ ## 0.8.6
14
+
15
+ ### Patch Changes
16
+
17
+ - [#241](https://github.com/tech4humans-brasil/framework/pull/241) [`f6b8454`](https://github.com/tech4humans-brasil/framework/commit/f6b845484d22e7c8e7240fe2e113f1cdea7c8ce3) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds seven rules to `@t4h.framework/core/eslint` and turns them on in `configs.recommended`:
18
+ - `no-io-in-workflow` reports `fetch` and calls to network, file, database and mail modules in a workflow and in the `constructor`, `toInput` and `toOutput` of an activity. It also reports `zlib` and `stream` calls there, because they work on the content in memory on each replay.
19
+ - `no-binary-subclass` reports a class that extends `Binary`.
20
+ - `no-framework-activity-in-app` reports `new` and `extends` on an activity of a framework package other than the core.
21
+ - `no-testing-import-in-source` reports an import of a `@t4h.framework/<package>/testing` entry out of a test file.
22
+ - `require-workflow-id` reports a `Workflow` with no `id`.
23
+ - `no-secret-to-string` reports a `Secret` that becomes a string.
24
+ - `no-literal-secret-in-authorization` reports a string literal in the `secret` or `clientSecret` field of an `Authorization`.
25
+
26
+ The package skills now name these rules in place of the text that the rules enforce. The docs and skills give each `Workflow` an `id`, read the HMAC secret from `EnvDesign`, and do not document a custom HTTP activity.
27
+
28
+ - [#243](https://github.com/tech4humans-brasil/framework/pull/243) [`0ffd663`](https://github.com/tech4humans-brasil/framework/commit/0ffd663ce8369edd7b54206cb3216568ad1de471) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The README examples read each value from `environments`, an `EnvDesign` in `src/constants/environments.ts`, and pass each credential as a `Secret`. They no longer use `process.env`, the raw `env` from the core or a literal in a `Secret` field. The jwks README describes the real context fields (`signature`, `rawPayload`) and says that a token that `jwt.verify` rejects throws.
29
+
30
+ - [#240](https://github.com/tech4humans-brasil/framework/pull/240) [`0606115`](https://github.com/tech4humans-brasil/framework/commit/0606115e2e0baa994e9f0ed3559ee54ecac88e44) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The skills do not repeat a rule that a lint rule, `tsc` or a runtime error already enforces. Each such rule is now one line that names the gate. The examples that broke a gate (`process.env`, `as any`, an inline secret, an `EnvDesign` in a workflow file, `ref.output` before `wait()`) are correct now.
31
+
32
+ - [#228](https://github.com/tech4humans-brasil/framework/pull/228) [`d29e1ab`](https://github.com/tech4humans-brasil/framework/commit/d29e1ab29a039b13c3e4b075472bd615566ae75e) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Uses exact types and `unknown` in place of `any`, and checks external data with guards.
33
+ - harness: the fix-issues prompt lists each sensor as `- checkTypes: PASS`. Before, it printed `- 0: [object Object]`.
34
+ - hmac: an algorithm name such as `constructor` throws `Unsupported algorithm`.
35
+ - oidc: a discovery response with no `introspection_endpoint`, or a null introspection body, throws a clear error.
36
+ - `HttpRequestActivity.toOutput` takes `HttpRequestActivityOutput` in place of `any`.
37
+
3
38
  ## 0.8.5
4
39
 
5
40
  ## 0.8.4
package/README.md CHANGED
@@ -1,186 +1,46 @@
1
1
  # @t4h.framework/sftp
2
2
 
3
- SFTP activities, claim, and a high-level client for file operations within workflows.
3
+ Reads and writes files on an SFTP server from a T4H Framework workflow. Each
4
+ operation (`get`, `put`, `list`, `rename` and more) is a durable step.
4
5
 
5
- ## Claim
6
+ ## Install
6
7
 
7
- ### SftpClaim
8
-
9
- Single abstract dependency that backs all SFTP activities. The runtime must provide a concrete implementation.
10
-
11
- ```typescript
12
- import { SftpClaim } from '@t4h.framework/sftp'
13
-
14
- // the runtime provides this:
15
- // { provide: SftpClaim, value: new MySftpClaimImpl() }
16
- ```
17
-
18
- ## Sftp
19
-
20
- High-level SFTP client that wraps every activity behind a simple API. Instantiated with a connection string.
21
-
22
- ```typescript
23
- import { Sftp } from '@t4h.framework/sftp'
24
- import type { Binary } from '@t4h.framework/core'
25
-
26
- const sftp = new Sftp('sftp://user:pass@sftp.example.com:22')
27
- ```
28
-
29
- ### append
30
-
31
- Appends content to an existing remote file.
32
-
33
- ```typescript
34
- await sftp.append('/remote/path/file.txt', myBinaryContent) // void
35
- ```
36
-
37
- ### chmod
38
-
39
- Changes the permissions of a remote file or directory.
40
-
41
- ```typescript
42
- await sftp.chmod('/remote/path/file.txt', 0o755) // void
43
- ```
44
-
45
- ### copy
46
-
47
- Copies a remote file from one path to another.
48
-
49
- ```typescript
50
- await sftp.copy('/remote/src.txt', '/remote/dest.txt') // void
8
+ ```bash
9
+ npm install @t4h.framework/sftp
51
10
  ```
52
11
 
53
- ### cwd
54
-
55
- Returns the current working directory on the remote server.
56
-
57
- ```typescript
58
- const dir = await sftp.cwd() // string
59
- ```
12
+ Peer dependency: `@t4h.framework/core`.
60
13
 
61
- ### delete
14
+ ## Usage
62
15
 
63
- Deletes a remote file. Pass `true` as the second argument to suppress errors when the file does not exist.
16
+ The connection string carries the password, so declare it with
17
+ `type: Secret` in the `EnvDesign` in `src/constants/environments.ts`:
64
18
 
65
19
  ```typescript
66
- await sftp.delete('/remote/path/file.txt') // void
67
- await sftp.delete('/remote/path/file.txt', true) // void, no error if missing
68
- ```
69
-
70
- ### downloadDir
71
-
72
- Downloads a remote directory as binary content.
20
+ // src/constants/environments.ts
21
+ import { EnvDesign, Secret } from '@t4h.framework/core'
73
22
 
74
- ```typescript
75
- const content = await sftp.downloadDir('/remote/path') // Binary
23
+ export const environments = EnvDesign({
24
+ SFTP_URL: { type: Secret },
25
+ })
76
26
  ```
77
27
 
78
- ### exists
79
-
80
- Checks whether a remote path exists. Returns `false` if not found, or its type character (`'d'` directory, `'-'` file, `'l'` symlink).
81
-
82
28
  ```typescript
83
- const result = await sftp.exists('/remote/path') // false | 'd' | '-' | 'l'
84
- ```
85
-
86
- ### get
87
-
88
- Downloads a single remote file.
89
-
90
- ```typescript
91
- const content = await sftp.get('/remote/path/file.txt') // Binary
92
- ```
93
-
94
- ### list
95
-
96
- Lists the contents of a remote directory.
97
-
98
- ```typescript
99
- const files = await sftp.list('/remote/path') // SftpFileInfo[]
100
- ```
101
-
102
- `SftpFileInfo`:
103
-
104
- ```typescript
105
- type SftpFileInfo = {
106
- name: string
107
- type: 'd' | '-' | 'l'
108
- size: number
109
- modifyTime: number
110
- accessTime: number
111
- rights: { user: string; group: string; other: string }
112
- owner: number
113
- group: number
114
- longname: string
115
- }
116
- ```
117
-
118
- ### mkdir
119
-
120
- Creates a remote directory. Pass `true` as the second argument to create intermediate directories recursively.
121
-
122
- ```typescript
123
- await sftp.mkdir('/remote/path/new-dir') // void
124
- await sftp.mkdir('/remote/path/a/b/c', true) // void, recursive
125
- ```
126
-
127
- ### put
128
-
129
- Uploads content to a remote file.
130
-
131
- ```typescript
132
- await sftp.put('/remote/path/file.txt', myBinaryContent) // void
133
- ```
134
-
135
- ### realPath
136
-
137
- Resolves the absolute path of a remote path (expanding symlinks, `.`, `..`, etc.).
138
-
139
- ```typescript
140
- const abs = await sftp.realPath('/remote/./path/../file.txt') // string
141
- ```
142
-
143
- ### rename
144
-
145
- Renames (moves) a remote file or directory.
146
-
147
- ```typescript
148
- await sftp.rename('/remote/old-name.txt', '/remote/new-name.txt') // void
149
- ```
150
-
151
- ### rmdir
29
+ import { Workflow } from '@t4h.framework/core'
30
+ import { Sftp } from '@t4h.framework/sftp'
152
31
 
153
- Removes a remote directory. Pass `true` as the second argument to remove recursively.
32
+ import { environments } from './constants/environments.js'
154
33
 
155
- ```typescript
156
- await sftp.rmdir('/remote/path/empty-dir') // void
157
- await sftp.rmdir('/remote/path/full-dir', true) // void, recursive
158
- ```
34
+ const sftp = new Sftp(environments.SFTP_URL)
159
35
 
160
- ### stat
36
+ const ingest = new Workflow({ id: 'ingest' }, async () => {
37
+ const file = await sftp.get('/inbound/orders.csv')
161
38
 
162
- Returns detailed metadata about a remote path.
163
-
164
- ```typescript
165
- const info = await sftp.stat('/remote/path/file.txt') // SftpStat
39
+ await sftp.put('/archive/orders.csv', file)
40
+ await sftp.delete('/inbound/orders.csv')
41
+ })
166
42
  ```
167
43
 
168
- `SftpStat`:
44
+ ## Documentation
169
45
 
170
- ```typescript
171
- type SftpStat = {
172
- mode: number
173
- uid: number
174
- gid: number
175
- size: number
176
- accessTime: number
177
- modifyTime: number
178
- isDirectory: boolean
179
- isFile: boolean
180
- isBlockDevice: boolean
181
- isCharacterDevice: boolean
182
- isSymbolicLink: boolean
183
- isFIFO: boolean
184
- isSocket: boolean
185
- }
186
- ```
46
+ https://tech4humans-brasil.github.io/framework/sftp/
@@ -1,6 +1,6 @@
1
1
  import type { Readable } from 'node:stream';
2
2
  import { Binary, type Secret } from '@t4h.framework/core';
3
- interface SftpBinaryJSON {
3
+ export interface SftpBinaryJSON {
4
4
  filepath: string;
5
5
  contentType: string;
6
6
  contentLength: number;
@@ -11,16 +11,26 @@ export declare class SftpBinary extends Binary {
11
11
  readonly contentType: string;
12
12
  readonly contentLength: number;
13
13
  readonly stream: (this: SftpBinary) => Readable | Promise<Readable>;
14
- protected constructor(connectionString: Secret, filepath: string, contentType: string, contentLength: number, stream: (this: SftpBinary) => Readable | Promise<Readable>);
14
+ readonly range?: {
15
+ start: number;
16
+ end: number;
17
+ } | undefined;
18
+ protected constructor(connectionString: Secret, filepath: string, contentType: string, contentLength: number, stream: (this: SftpBinary) => Readable | Promise<Readable>, range?: {
19
+ start: number;
20
+ end: number;
21
+ } | undefined);
15
22
  static from(connectionString: Secret, file: SftpBinaryJSON, stream: (self: SftpBinary) => Readable | Promise<Readable>): SftpBinary;
23
+ slice(range: {
24
+ start: number;
25
+ end: number;
26
+ }): SftpBinary;
16
27
  toJSON(): {
17
28
  connectionString: Secret;
18
- file: {
19
- filepath: string;
20
- contentType: string;
21
- contentLength: number;
29
+ file: SftpBinaryJSON;
30
+ range?: {
31
+ start: number;
32
+ end: number;
22
33
  };
23
34
  };
24
35
  }
25
- export {};
26
36
  //# sourceMappingURL=SftpBinary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SftpBinary.d.ts","sourceRoot":"","sources":["../../src/models/SftpBinary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEzD,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,UAAW,SAAQ,MAAM;aAElB,gBAAgB,EAAE,MAAM;aACxB,QAAQ,EAAE,MAAM;aAChB,WAAW,EAAE,MAAM;aACnB,aAAa,EAAE,MAAM;aACrB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAL5E,SAAS,aACS,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;WAK9D,IAAI,CAChB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAarD,MAAM;;;;;;;;CAUd"}
1
+ {"version":3,"file":"SftpBinary.d.ts","sourceRoot":"","sources":["../../src/models/SftpBinary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,UAAW,SAAQ,MAAM;aAElB,gBAAgB,EAAE,MAAM;aACxB,QAAQ,EAAE,MAAM;aAChB,WAAW,EAAE,MAAM;aACnB,aAAa,EAAE,MAAM;aACrB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;aAC1D,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE;IANxD,SAAS,aACS,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAC1D,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,YAAA;WAK1C,IAAI,CAChB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAarD,KAAK,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU;IAmBxD,MAAM,IAAI;QACf,gBAAgB,EAAE,MAAM,CAAA;QACxB,IAAI,EAAE,cAAc,CAAA;QACpB,KAAK,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAA;KACvC;CAcF"}
@@ -5,21 +5,31 @@ export class SftpBinary extends Binary {
5
5
  contentType;
6
6
  contentLength;
7
7
  stream;
8
- constructor(connectionString, filepath, contentType, contentLength, stream) {
8
+ range;
9
+ constructor(connectionString, filepath, contentType, contentLength, stream, range) {
9
10
  super();
10
11
  this.connectionString = connectionString;
11
12
  this.filepath = filepath;
12
13
  this.contentType = contentType;
13
14
  this.contentLength = contentLength;
14
15
  this.stream = stream;
16
+ this.range = range;
15
17
  }
16
18
  static from(connectionString, file, stream) {
17
19
  return new this(connectionString, file.filepath, file.contentType, file.contentLength, function () {
18
20
  return stream(this);
19
21
  });
20
22
  }
23
+ slice(range) {
24
+ if (range.start < 0 || range.end < range.start)
25
+ throw new RangeError('Slice range must be a non-negative byte window');
26
+ if (range.end > this.contentLength)
27
+ throw new RangeError('Slice range must not exceed the binary length');
28
+ const base = this.range?.start ?? 0;
29
+ return new SftpBinary(this.connectionString, this.filepath, this.contentType, range.end - range.start, this.stream, { start: base + range.start, end: base + range.end });
30
+ }
21
31
  toJSON() {
22
- return {
32
+ const json = {
23
33
  connectionString: this.connectionString,
24
34
  file: {
25
35
  filepath: this.filepath,
@@ -27,6 +37,9 @@ export class SftpBinary extends Binary {
27
37
  contentLength: this.contentLength,
28
38
  },
29
39
  };
40
+ if (!this.range)
41
+ return json;
42
+ return { ...json, range: { ...this.range } };
30
43
  }
31
44
  }
32
45
  //# sourceMappingURL=SftpBinary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SftpBinary.js","sourceRoot":"","sources":["../../src/models/SftpBinary.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AAQzD,MAAM,OAAO,UAAW,SAAQ,MAAM;IAElB;IACA;IACA;IACA;IACA;IALlB,YACkB,gBAAwB,EACxB,QAAgB,EAChB,WAAmB,EACnB,aAAqB,EACrB,MAA0D;QAE1E,KAAK,EAAE,CAAA;QANS,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,gBAAW,GAAX,WAAW,CAAQ;QACnB,kBAAa,GAAb,aAAa,CAAQ;QACrB,WAAM,GAAN,MAAM,CAAoD;IAG5E,CAAC;IAEM,MAAM,CAAC,IAAI,CAChB,gBAAwB,EACxB,IAAoB,EACpB,MAA0D;QAE1D,OAAO,IAAI,IAAI,CACb,gBAAgB,EAChB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,aAAa,EAClB;YACE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC,CACF,CAAA;IACH,CAAC;IAEM,MAAM;QACX,OAAO;YACL,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC;SACF,CAAA;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"SftpBinary.js","sourceRoot":"","sources":["../../src/models/SftpBinary.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AAQzD,MAAM,OAAO,UAAW,SAAQ,MAAM;IAElB;IACA;IACA;IACA;IACA;IACA;IANlB,YACkB,gBAAwB,EACxB,QAAgB,EAChB,WAAmB,EACnB,aAAqB,EACrB,MAA0D,EAC1D,KAAsC;QAEtD,KAAK,EAAE,CAAA;QAPS,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,gBAAW,GAAX,WAAW,CAAQ;QACnB,kBAAa,GAAb,aAAa,CAAQ;QACrB,WAAM,GAAN,MAAM,CAAoD;QAC1D,UAAK,GAAL,KAAK,CAAiC;IAGxD,CAAC;IAEM,MAAM,CAAC,IAAI,CAChB,gBAAwB,EACxB,IAAoB,EACpB,MAA0D;QAE1D,OAAO,IAAI,IAAI,CACb,gBAAgB,EAChB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,aAAa,EAClB;YACE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC,CACF,CAAA;IACH,CAAC;IAEM,KAAK,CAAC,KAAqC;QAChD,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK;YAC5C,MAAM,IAAI,UAAU,CAAC,gDAAgD,CAAC,CAAA;QAExE,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,aAAa;YAChC,MAAM,IAAI,UAAU,CAAC,+CAA+C,CAAC,CAAA;QAEvE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,CAAA;QAEnC,OAAO,IAAI,UAAU,CACnB,IAAI,CAAC,gBAAgB,EACrB,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,WAAW,EAChB,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,EACvB,IAAI,CAAC,MAAM,EACX,EAAE,KAAK,EAAE,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,CACrD,CAAA;IACH,CAAC;IAEM,MAAM;QAKX,MAAM,IAAI,GAAG;YACX,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC;SACF,CAAA;QAED,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAE5B,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,CAAA;IAC9C,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t4h.framework/sftp",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "SFTP module for the T4H Framework",
5
5
  "homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/sftp",
6
6
  "bugs": "https://github.com/tech4humans-brasil/framework/issues",
@@ -26,12 +26,14 @@
26
26
  "scripts": {
27
27
  "build": "tsc --project tsconfig.build.json",
28
28
  "check-types": "tsc --noEmit",
29
+ "check-package": "publint --strict && attw --pack . --profile esm-only",
30
+ "lint": "eslint . --max-warnings 0",
29
31
  "prepublishOnly": "yarn build",
30
32
  "test": "vitest run",
31
33
  "test:watch": "vitest"
32
34
  },
33
35
  "devDependencies": {
34
- "@t4h.framework/core": "^0.19.0",
36
+ "@t4h.framework/core": "^0.21.0",
35
37
  "typescript": "^5.9.3",
36
38
  "vitest": "^4.0.18"
37
39
  },