@simplysm/storage 14.0.47 → 14.0.49

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.
Files changed (2) hide show
  1. package/README.md +301 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # @simplysm/storage
2
+
3
+ FTP/FTPS/SFTP 파일 저장소 클라이언트 라이브러리 (Node.js 전용). `StorageClient` 인터페이스로 프로토콜을 통일하고, `StorageFactory`로 연결 생명주기를 관리한다.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @simplysm/storage
9
+ ```
10
+
11
+ ## API Overview
12
+
13
+ ### Types
14
+
15
+ | API | Type | Description |
16
+ |-----|------|-------------|
17
+ | `StorageProtocol` | type | 지원 프로토콜 유니온 타입 (`"ftp"` \| `"ftps"` \| `"sftp"`) |
18
+ | `StorageConnConfig` | interface | 스토리지 연결 설정 |
19
+ | `FileInfo` | interface | 파일/디렉토리 정보 |
20
+ | `StorageClient` | interface | 스토리지 클라이언트 공통 인터페이스 |
21
+
22
+ #### `StorageProtocol`
23
+
24
+ ```typescript
25
+ type StorageProtocol = "ftp" | "ftps" | "sftp";
26
+ ```
27
+
28
+ 지원하는 프로토콜을 나타내는 유니온 타입:
29
+ - `"ftp"`: 일반 FTP 프로토콜
30
+ - `"ftps"`: TLS/SSL 암호화 FTP 프로토콜
31
+ - `"sftp"`: SSH 기반 SFTP 프로토콜
32
+
33
+ #### `StorageConnConfig`
34
+
35
+ ```typescript
36
+ interface StorageConnConfig {
37
+ host: string;
38
+ port?: number;
39
+ user?: string;
40
+ password?: string;
41
+ }
42
+ ```
43
+
44
+ | Field | Type | Description |
45
+ |-------|------|-------------|
46
+ | `host` | `string` | 서버 호스트 주소 |
47
+ | `port` | `number \| undefined` | 포트 번호. 생략 시 프로토콜 기본값 사용 (FTP: 21, FTPS: 21, SFTP: 22) |
48
+ | `user` | `string \| undefined` | 사용자 이름 |
49
+ | `password` | `string \| undefined` | 비밀번호. SFTP에서 생략하면 SSH agent (`SSH_AUTH_SOCK` 환경변수) + `~/.ssh/id_ed25519` 키 파일 인증을 순서대로 시도 |
50
+
51
+ #### `FileInfo`
52
+
53
+ ```typescript
54
+ interface FileInfo {
55
+ name: string;
56
+ isFile: boolean;
57
+ }
58
+ ```
59
+
60
+ | Field | Type | Description |
61
+ |-------|------|-------------|
62
+ | `name` | `string` | 파일 또는 디렉토리의 이름 |
63
+ | `isFile` | `boolean` | `true`이면 파일, `false`이면 디렉토리 |
64
+
65
+ #### `StorageClient`
66
+
67
+ 스토리지 클라이언트 공통 인터페이스. `FtpStorageClient`와 `SftpStorageClient`가 구현한다.
68
+
69
+ ```typescript
70
+ interface StorageClient {
71
+ connect(config: StorageConnConfig): Promise<void>;
72
+ mkdir(dirPath: string): Promise<void>;
73
+ rename(fromPath: string, toPath: string): Promise<void>;
74
+ list(dirPath: string): Promise<FileInfo[]>;
75
+ readFile(filePath: string): Promise<Bytes>;
76
+ exists(filePath: string): Promise<boolean>;
77
+ put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>;
78
+ uploadDir(fromPath: string, toPath: string): Promise<void>;
79
+ remove(filePath: string): Promise<void>;
80
+ close(): Promise<void>;
81
+ }
82
+ ```
83
+
84
+ | Method | Parameters | Return | Description |
85
+ |--------|-----------|--------|-------------|
86
+ | `connect` | `config: StorageConnConfig` | `Promise<void>` | 스토리지 서버에 연결. 연결 해제 전 한 번만 호출 가능 |
87
+ | `mkdir` | `dirPath: string` | `Promise<void>` | 디렉토리 생성. 부모 디렉토리가 없으면 함께 생성 |
88
+ | `rename` | `fromPath: string, toPath: string` | `Promise<void>` | 파일/디렉토리 이름 변경 또는 이동 |
89
+ | `list` | `dirPath: string` | `Promise<FileInfo[]>` | 디렉토리 내 파일/디렉토리 목록 조회 |
90
+ | `readFile` | `filePath: string` | `Promise<Bytes>` | 파일 내용을 `Bytes`(`Uint8Array`)로 읽기 |
91
+ | `exists` | `filePath: string` | `Promise<boolean>` | 파일/디렉토리 존재 여부 확인. 모든 예외는 `false` 반환 |
92
+ | `put` | `localPathOrBuffer: string \| Bytes, storageFilePath: string` | `Promise<void>` | 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드 |
93
+ | `uploadDir` | `fromPath: string, toPath: string` | `Promise<void>` | 로컬 디렉토리 전체를 원격 경로에 업로드 |
94
+ | `remove` | `filePath: string` | `Promise<void>` | 파일 삭제 |
95
+ | `close` | 없음 | `Promise<void>` | 연결 종료. 이미 종료된 상태에서 호출해도 안전 |
96
+
97
+ ### Clients
98
+
99
+ | API | Type | Description |
100
+ |-----|------|-------------|
101
+ | `FtpStorageClient` | class | FTP/FTPS 프로토콜 스토리지 클라이언트 (`basic-ftp` 라이브러리 기반) |
102
+ | `SftpStorageClient` | class | SFTP 프로토콜 스토리지 클라이언트 (`ssh2-sftp-client` 라이브러리 기반) |
103
+
104
+ #### `FtpStorageClient`
105
+
106
+ FTP/FTPS 프로토콜을 사용하는 스토리지 클라이언트. `StorageClient` 인터페이스를 구현한다.
107
+
108
+ ```typescript
109
+ class FtpStorageClient implements StorageClient {
110
+ constructor(private readonly _secure: boolean = false);
111
+ }
112
+ ```
113
+
114
+ **생성자**:
115
+ - `_secure`: `true`이면 FTPS (TLS/SSL 암호화), `false`이면 FTP (기본값)
116
+
117
+ **특징**:
118
+ - `connect()`로 연결 후 메서드 호출. 미연결 상태에서 호출하면 `SdError` 발생
119
+ - `exists()`는 먼저 `size()` 명령으로 파일을 O(1) 성능으로 확인하고, 실패 시 부모 디렉토리 목록을 조회하여 디렉토리 존재 여부 확인. 모든 예외는 `false` 반환
120
+ - 슬래시가 없는 경로(예: `file.txt`)는 루트 디렉토리(`/`)에서 검색
121
+ - `close()`는 이미 종료된 상태에서 호출해도 안전 (오류 미발생)
122
+
123
+ #### `SftpStorageClient`
124
+
125
+ SFTP 프로토콜을 사용하는 스토리지 클라이언트. `StorageClient` 인터페이스를 구현한다.
126
+
127
+ ```typescript
128
+ class SftpStorageClient implements StorageClient {
129
+ constructor();
130
+ }
131
+ ```
132
+
133
+ **인증 메커니즘**:
134
+ - `password`가 있으면 패스워드 인증 사용
135
+ - `password`가 없으면 다음 순서대로 시도:
136
+ 1. SSH agent (`SSH_AUTH_SOCK` 환경변수 설정 시)
137
+ 2. `~/.ssh/id_ed25519` 개인키 파일 인증
138
+ 3. privateKey 파싱 실패 시 (암호화된 키 등) agent만으로 재시도
139
+
140
+ **특징**:
141
+ - `connect()`로 연결 후 메서드 호출. 미연결 상태에서 호출하면 `SdError` 발생
142
+ - `exists()`는 `ssh2-sftp-client`의 `exists()` 반환값 (`false | 'd' | '-' | 'l'`)을 검사하여 존재 여부 판단. 모든 예외는 `false` 반환
143
+ - `close()`는 이미 종료된 상태에서 호출해도 안전 (오류 미발생)
144
+
145
+ ### Factory
146
+
147
+ | API | Type | Description |
148
+ |-----|------|-------------|
149
+ | `StorageFactory` | class | 프로토콜별 클라이언트 생성 및 연결 생명주기 자동 관리 |
150
+
151
+ #### `StorageFactory`
152
+
153
+ 스토리지 클라이언트 팩토리. 콜백 패턴으로 연결/종료를 자동 관리한다.
154
+
155
+ ```typescript
156
+ class StorageFactory {
157
+ static async connect<R>(
158
+ type: StorageProtocol,
159
+ config: StorageConnConfig,
160
+ fn: (storage: StorageClient) => R | Promise<R>,
161
+ ): Promise<R>;
162
+ }
163
+ ```
164
+
165
+ **메서드**:
166
+ - `StorageFactory.connect<R>()` (정적 메서드)
167
+
168
+ **파라미터**:
169
+
170
+ | Parameter | Type | Description |
171
+ |-----------|------|-------------|
172
+ | `type` | `StorageProtocol` | 프로토콜 타입 (`"ftp"`, `"ftps"`, `"sftp"`) |
173
+ | `config` | `StorageConnConfig` | 연결 설정 |
174
+ | `fn` | `(storage: StorageClient) => R \| Promise<R>` | 연결된 클라이언트를 받아 작업을 수행하는 콜백. 반환값은 `Promise<R>` 형태로 래핑되어 반환됨 |
175
+
176
+ **동작**:
177
+ - `fn` 콜백이 완료되거나 예외를 발생시키면 연결이 자동으로 종료됨
178
+ - `fn`에서 발생한 예외는 그대로 전파됨
179
+ - 반환값: 콜백의 반환값
180
+
181
+ ## Usage Examples
182
+
183
+ ### SFTP로 파일 업로드 (StorageFactory 사용 권장)
184
+
185
+ ```typescript
186
+ import { StorageFactory } from "@simplysm/storage";
187
+
188
+ await StorageFactory.connect(
189
+ "sftp",
190
+ { host: "sftp.example.com", user: "user", password: "pass" },
191
+ async (storage) => {
192
+ await storage.mkdir("/remote/dir");
193
+ await storage.put("/local/file.txt", "/remote/dir/file.txt");
194
+ },
195
+ );
196
+ ```
197
+
198
+ 콜백이 완료되거나 예외가 발생하면 자동으로 연결이 종료된다.
199
+
200
+ ### FTP로 파일 목록 조회 및 다운로드
201
+
202
+ ```typescript
203
+ import { StorageFactory } from "@simplysm/storage";
204
+
205
+ const files = await StorageFactory.connect(
206
+ "ftp",
207
+ { host: "ftp.example.com", port: 21, user: "user", password: "pass" },
208
+ async (storage) => {
209
+ const list = await storage.list("/data");
210
+ for (const file of list.filter((f) => f.isFile)) {
211
+ const content = await storage.readFile(`/data/${file.name}`);
212
+ // content는 Bytes (Uint8Array)
213
+ console.log(`Read ${file.name}: ${content.length} bytes`);
214
+ }
215
+ return list;
216
+ },
217
+ );
218
+ ```
219
+
220
+ ### FTPS로 파일 이름 변경
221
+
222
+ ```typescript
223
+ import { StorageFactory } from "@simplysm/storage";
224
+
225
+ await StorageFactory.connect(
226
+ "ftps",
227
+ { host: "ftps.example.com", user: "user", password: "pass" },
228
+ async (storage) => {
229
+ const exists = await storage.exists("/remote/file.txt");
230
+ if (exists) {
231
+ await storage.rename("/remote/file.txt", "/remote/backup.txt");
232
+ }
233
+ },
234
+ );
235
+ ```
236
+
237
+ ### 클라이언트 직접 사용 (수동 생명주기 관리)
238
+
239
+ 클라이언트를 직접 인스턴스화하여 수동으로 연결 생명주기를 관리할 수 있다. 하지만 연결 누수 위험이 있으므로 `StorageFactory.connect` 사용을 권장한다.
240
+
241
+ ```typescript
242
+ import { FtpStorageClient } from "@simplysm/storage";
243
+
244
+ const client = new FtpStorageClient(true); // FTPS 사용
245
+ try {
246
+ await client.connect({ host: "ftps.example.com", user: "user", password: "pass" });
247
+ const exists = await client.exists("/remote/file.txt");
248
+ if (exists) {
249
+ await client.rename("/remote/file.txt", "/remote/backup.txt");
250
+ }
251
+ } finally {
252
+ await client.close();
253
+ }
254
+ ```
255
+
256
+ ### SSH 키 인증으로 SFTP 연결 (비밀번호 생략)
257
+
258
+ `password`를 생략하면 SSH agent와 `~/.ssh/id_ed25519` 키 파일을 사용하여 인증한다.
259
+
260
+ ```typescript
261
+ import { StorageFactory } from "@simplysm/storage";
262
+
263
+ await StorageFactory.connect(
264
+ "sftp",
265
+ { host: "sftp.example.com", user: "user" }, // password 생략
266
+ async (storage) => {
267
+ const list = await storage.list("/home/user");
268
+ console.log(`Files: ${list.map((f) => f.name).join(", ")}`);
269
+ },
270
+ );
271
+ ```
272
+
273
+ ### 바이트 데이터로 파일 업로드
274
+
275
+ ```typescript
276
+ import { StorageFactory } from "@simplysm/storage";
277
+
278
+ const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"
279
+
280
+ await StorageFactory.connect(
281
+ "sftp",
282
+ { host: "sftp.example.com", user: "user", password: "pass" },
283
+ async (storage) => {
284
+ await storage.put(data, "/remote/file.bin");
285
+ },
286
+ );
287
+ ```
288
+
289
+ ### 로컬 디렉토리 전체 업로드
290
+
291
+ ```typescript
292
+ import { StorageFactory } from "@simplysm/storage";
293
+
294
+ await StorageFactory.connect(
295
+ "ftp",
296
+ { host: "ftp.example.com", user: "user", password: "pass" },
297
+ async (storage) => {
298
+ await storage.uploadDir("/local/folder", "/remote/backup");
299
+ },
300
+ );
301
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/storage",
3
- "version": "14.0.47",
3
+ "version": "14.0.49",
4
4
  "description": "심플리즘 패키지 - 저장소 (node)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "basic-ftp": "^5.3.0",
22
22
  "ssh2-sftp-client": "^12.1.1",
23
- "@simplysm/core-common": "14.0.47"
23
+ "@simplysm/core-common": "14.0.49"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/ssh2-sftp-client": "^9.0.6"