@simplysm/storage 13.0.85 → 13.0.86
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 +154 -91
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
# @simplysm/storage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
FTP, FTPS, SFTP 원격 스토리지 통합 라이브러리. 팩토리 패턴으로 프로토콜에 독립적인 파일 전송을 제공한다.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 설치
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @simplysm/storage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**의존성:** `basic-ftp` (FTP/FTPS), `ssh2-sftp-client` (SFTP), `@simplysm/core-common`
|
|
12
|
+
|
|
13
|
+
## 빠른 시작
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { StorageFactory } from "@simplysm/storage";
|
|
17
|
+
|
|
18
|
+
// 팩토리 패턴: 연결 → 작업 → 자동 종료
|
|
19
|
+
await StorageFactory.connect("sftp", {
|
|
20
|
+
host: "example.com",
|
|
21
|
+
port: 22,
|
|
22
|
+
user: "deploy",
|
|
23
|
+
password: "secret",
|
|
24
|
+
}, async (storage) => {
|
|
25
|
+
await storage.put("/local/file.zip", "/remote/file.zip");
|
|
26
|
+
await storage.uploadDir("/local/dist", "/remote/www");
|
|
27
|
+
const files = await storage.list("/remote/www");
|
|
28
|
+
const exists = await storage.exists("/remote/file.zip");
|
|
29
|
+
});
|
|
30
|
+
// 콜백 종료 시 연결 자동 해제 (예외 발생해도 보장)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 타입 정의
|
|
8
34
|
|
|
9
35
|
### StorageProtocol
|
|
10
36
|
|
|
@@ -12,149 +38,186 @@ A unified storage client library for Node.js that provides a consistent API for
|
|
|
12
38
|
type StorageProtocol = "ftp" | "ftps" | "sftp";
|
|
13
39
|
```
|
|
14
40
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
| 값 | 설명 | 내부 클라이언트 |
|
|
42
|
+
|-----|------|----------------|
|
|
43
|
+
| `"ftp"` | File Transfer Protocol | `FtpStorageClient(secure=false)` |
|
|
44
|
+
| `"ftps"` | FTP over SSL/TLS | `FtpStorageClient(secure=true)` |
|
|
45
|
+
| `"sftp"` | SSH File Transfer Protocol | `SftpStorageClient` |
|
|
18
46
|
|
|
19
47
|
### StorageConnConfig
|
|
20
48
|
|
|
21
49
|
```typescript
|
|
22
50
|
interface StorageConnConfig {
|
|
23
51
|
host: string;
|
|
24
|
-
port?: number;
|
|
52
|
+
port?: number; // 미지정 시 프로토콜 기본값 사용
|
|
25
53
|
user?: string;
|
|
26
54
|
password?: string;
|
|
27
55
|
}
|
|
28
56
|
```
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
- **SFTP에서 `password` 미지정 시**: SSH 키 인증으로 전환
|
|
59
|
+
1. `~/.ssh/id_ed25519` 키 파일 자동 탐색
|
|
60
|
+
2. 키 파싱 실패 시 `SSH_AUTH_SOCK` 환경변수의 SSH 에이전트로 재시도
|
|
33
61
|
|
|
34
62
|
### FileInfo
|
|
35
63
|
|
|
36
64
|
```typescript
|
|
37
65
|
interface FileInfo {
|
|
38
|
-
name: string;
|
|
39
|
-
isFile: boolean;
|
|
66
|
+
name: string; // 파일/디렉토리 이름
|
|
67
|
+
isFile: boolean; // true: 파일, false: 디렉토리
|
|
40
68
|
}
|
|
41
69
|
```
|
|
42
70
|
|
|
43
|
-
|
|
71
|
+
### Bytes
|
|
44
72
|
|
|
45
|
-
|
|
73
|
+
`@simplysm/core-common`에서 정의한 바이트 배열 타입 (`Uint8Array` 기반).
|
|
46
74
|
|
|
47
|
-
|
|
75
|
+
## API 레퍼런스
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
interface StorageClient {
|
|
51
|
-
connect(config: StorageConnConfig): Promise<void>;
|
|
52
|
-
mkdir(dirPath: string): Promise<void>;
|
|
53
|
-
rename(fromPath: string, toPath: string): Promise<void>;
|
|
54
|
-
list(dirPath: string): Promise<FileInfo[]>;
|
|
55
|
-
readFile(filePath: string): Promise<Bytes>;
|
|
56
|
-
exists(filePath: string): Promise<boolean>;
|
|
57
|
-
put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>;
|
|
58
|
-
uploadDir(fromPath: string, toPath: string): Promise<void>;
|
|
59
|
-
remove(filePath: string): Promise<void>;
|
|
60
|
-
close(): Promise<void>;
|
|
61
|
-
}
|
|
62
|
-
```
|
|
77
|
+
### StorageFactory
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
프로토콜에 독립적으로 스토리지 클라이언트를 생성하고 연결을 관리하는 팩토리 클래스.
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|--------|-------------|
|
|
68
|
-
| `connect` | Open a connection to the remote server. |
|
|
69
|
-
| `mkdir` | Create a directory, including parent directories. |
|
|
70
|
-
| `rename` | Rename or move a remote file/directory. |
|
|
71
|
-
| `list` | List entries in a remote directory. |
|
|
72
|
-
| `readFile` | Read the contents of a remote file as `Bytes`. |
|
|
73
|
-
| `exists` | Check whether a remote file or directory exists. |
|
|
74
|
-
| `put` | Upload a local file path or byte data to a remote path. |
|
|
75
|
-
| `uploadDir` | Upload an entire local directory to a remote path. |
|
|
76
|
-
| `remove` | Delete a remote file. |
|
|
77
|
-
| `close` | Close the connection. Safe to call when already closed. |
|
|
81
|
+
#### `StorageFactory.connect<R>(type, config, fn): Promise<R>`
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
```typescript
|
|
84
|
+
static async connect<R>(
|
|
85
|
+
type: StorageProtocol,
|
|
86
|
+
config: StorageConnConfig,
|
|
87
|
+
fn: (storage: StorageClient) => R | Promise<R>,
|
|
88
|
+
): Promise<R>
|
|
89
|
+
```
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
- 연결 수립 → 콜백 실행 → 연결 종료를 자동 관리
|
|
92
|
+
- 콜백에서 예외가 발생해도 `finally`에서 연결이 안전하게 종료됨
|
|
93
|
+
- 콜백의 반환값을 그대로 반환 (제네릭 `R`)
|
|
82
94
|
|
|
83
95
|
```typescript
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
): Promise<R>;
|
|
90
|
-
}
|
|
96
|
+
// 반환값 활용 예시
|
|
97
|
+
const fileList = await StorageFactory.connect("ftp", config, async (storage) => {
|
|
98
|
+
return await storage.list("/data");
|
|
99
|
+
});
|
|
100
|
+
// fileList: FileInfo[]
|
|
91
101
|
```
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
### StorageClient (공통 인터페이스)
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
`FtpStorageClient`와 `SftpStorageClient`가 구현하는 공통 인터페이스. `StorageFactory.connect` 콜백의 `storage` 파라미터 타입이다.
|
|
106
|
+
|
|
107
|
+
| 메서드 | 시그니처 | 설명 |
|
|
108
|
+
|--------|---------|------|
|
|
109
|
+
| `connect` | `(config: StorageConnConfig) => Promise<void>` | 서버 연결. 이미 연결된 인스턴스에서 호출하면 에러 발생 |
|
|
110
|
+
| `close` | `() => Promise<void>` | 연결 종료. 이미 종료된 상태에서 호출해도 안전함 |
|
|
111
|
+
| `mkdir` | `(dirPath: string) => Promise<void>` | 디렉토리 생성 (부모 디렉토리 자동 생성) |
|
|
112
|
+
| `rename` | `(fromPath: string, toPath: string) => Promise<void>` | 파일/디렉토리 이름 변경 |
|
|
113
|
+
| `list` | `(dirPath: string) => Promise<FileInfo[]>` | 디렉토리 내 항목 목록 조회 |
|
|
114
|
+
| `readFile` | `(filePath: string) => Promise<Bytes>` | 원격 파일을 바이트 배열로 읽기 |
|
|
115
|
+
| `exists` | `(filePath: string) => Promise<boolean>` | 파일/디렉토리 존재 여부 확인 |
|
|
116
|
+
| `put` | `(localPathOrBuffer: string \| Bytes, remotePath: string) => Promise<void>` | 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드 |
|
|
117
|
+
| `uploadDir` | `(fromPath: string, toPath: string) => Promise<void>` | 로컬 디렉토리 전체를 원격 경로에 업로드 |
|
|
118
|
+
| `remove` | `(filePath: string) => Promise<void>` | 원격 파일 삭제 |
|
|
119
|
+
|
|
120
|
+
#### 메서드별 주의사항
|
|
121
|
+
|
|
122
|
+
**`exists`**: 부모 디렉토리가 존재하지 않아도 `false` 반환. 네트워크 오류/권한 오류 등 모든 예외에 대해서도 `false` 반환.
|
|
123
|
+
- FTP: `size()` 명령으로 파일 확인 (O(1)), 실패 시 부모 디렉토리 `list()`로 폴백
|
|
124
|
+
- SFTP: `exists()` 내장 메서드 사용 (반환값 `false | 'd' | '-' | 'l'`)
|
|
125
|
+
|
|
126
|
+
**`mkdir`**: 부모 디렉토리가 없으면 재귀적으로 생성한다.
|
|
127
|
+
|
|
128
|
+
**`put`**: 첫 번째 인자로 로컬 파일 경로(string) 또는 바이트 데이터(Bytes)를 받는다.
|
|
129
|
+
- SFTP는 파일 경로 전달 시 `fastPut` 사용 (병렬 전송으로 더 빠름)
|
|
130
|
+
|
|
131
|
+
**`connect`**: 이미 연결된 인스턴스에서 중복 호출하면 `SdError` 발생. 반드시 `close()` 후 재연결해야 한다.
|
|
96
132
|
|
|
97
133
|
### FtpStorageClient
|
|
98
134
|
|
|
135
|
+
FTP/FTPS 프로토콜 클라이언트. 내부적으로 `basic-ftp` 라이브러리를 사용한다.
|
|
136
|
+
|
|
99
137
|
```typescript
|
|
100
|
-
|
|
101
|
-
constructor(secure?: boolean);
|
|
102
|
-
}
|
|
103
|
-
```
|
|
138
|
+
import { FtpStorageClient } from "@simplysm/storage";
|
|
104
139
|
|
|
105
|
-
|
|
140
|
+
const client = new FtpStorageClient(secure?: boolean);
|
|
141
|
+
// secure=false: FTP (기본값)
|
|
142
|
+
// secure=true: FTPS
|
|
143
|
+
```
|
|
106
144
|
|
|
107
|
-
|
|
145
|
+
직접 사용 시 반드시 `connect` → 작업 → `close` 순서를 지켜야 한다. `StorageFactory.connect` 사용을 권장한다.
|
|
108
146
|
|
|
109
147
|
### SftpStorageClient
|
|
110
148
|
|
|
149
|
+
SFTP 프로토콜 클라이언트. 내부적으로 `ssh2-sftp-client` 라이브러리를 사용한다.
|
|
150
|
+
|
|
111
151
|
```typescript
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
```
|
|
152
|
+
import { SftpStorageClient } from "@simplysm/storage";
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
const client = new SftpStorageClient();
|
|
155
|
+
```
|
|
117
156
|
|
|
118
|
-
|
|
157
|
+
**인증 방식 (자동 선택):**
|
|
158
|
+
1. `password`가 있으면 비밀번호 인증
|
|
159
|
+
2. `password`가 없으면 키 기반 인증:
|
|
160
|
+
- `~/.ssh/id_ed25519` 키 파일 + SSH 에이전트(`SSH_AUTH_SOCK`) 시도
|
|
161
|
+
- 키 파싱 실패 시 SSH 에이전트만으로 재시도
|
|
119
162
|
|
|
120
|
-
##
|
|
163
|
+
## 사용 예제
|
|
121
164
|
|
|
122
|
-
###
|
|
165
|
+
### 파일 업로드/다운로드
|
|
123
166
|
|
|
124
167
|
```typescript
|
|
125
|
-
|
|
168
|
+
await StorageFactory.connect("sftp", config, async (storage) => {
|
|
169
|
+
// 로컬 파일 업로드
|
|
170
|
+
await storage.put("/local/path/file.txt", "/remote/path/file.txt");
|
|
126
171
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
await storage.
|
|
130
|
-
await storage.put("/local/path/file.txt", "/uploads/2024/file.txt");
|
|
131
|
-
});
|
|
172
|
+
// 바이트 데이터 직접 업로드
|
|
173
|
+
const data = new TextEncoder().encode("hello");
|
|
174
|
+
await storage.put(data, "/remote/path/hello.txt");
|
|
132
175
|
|
|
133
|
-
//
|
|
134
|
-
const
|
|
135
|
-
return await storage.list("/data");
|
|
176
|
+
// 파일 읽기
|
|
177
|
+
const content = await storage.readFile("/remote/path/file.txt");
|
|
136
178
|
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 디렉토리 관리
|
|
137
182
|
|
|
138
|
-
|
|
139
|
-
await StorageFactory.connect("
|
|
140
|
-
|
|
183
|
+
```typescript
|
|
184
|
+
await StorageFactory.connect("ftp", config, async (storage) => {
|
|
185
|
+
// 디렉토리 생성 (중첩 경로 자동 생성)
|
|
186
|
+
await storage.mkdir("/remote/a/b/c");
|
|
187
|
+
|
|
188
|
+
// 디렉토리 전체 업로드
|
|
189
|
+
await storage.uploadDir("/local/dist", "/remote/www");
|
|
190
|
+
|
|
191
|
+
// 디렉토리 목록 조회
|
|
192
|
+
const items = await storage.list("/remote/www");
|
|
193
|
+
for (const item of items) {
|
|
194
|
+
// item.name: 이름, item.isFile: 파일 여부
|
|
195
|
+
}
|
|
141
196
|
});
|
|
142
197
|
```
|
|
143
198
|
|
|
144
|
-
###
|
|
199
|
+
### 파일 존재 확인 및 삭제
|
|
145
200
|
|
|
146
201
|
```typescript
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
await client.connect({ host: "example.com", user: "deploy", password: "secret" });
|
|
151
|
-
try {
|
|
152
|
-
const exists = await client.exists("/remote/file.txt");
|
|
153
|
-
if (exists) {
|
|
154
|
-
const data = await client.readFile("/remote/file.txt");
|
|
202
|
+
await StorageFactory.connect("ftps", config, async (storage) => {
|
|
203
|
+
if (await storage.exists("/remote/old-file.txt")) {
|
|
204
|
+
await storage.remove("/remote/old-file.txt");
|
|
155
205
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
206
|
+
|
|
207
|
+
await storage.rename("/remote/temp.txt", "/remote/final.txt");
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### SSH 키 인증 (SFTP)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// password 미지정 시 자동으로 키 기반 인증
|
|
215
|
+
await StorageFactory.connect("sftp", {
|
|
216
|
+
host: "example.com",
|
|
217
|
+
port: 22,
|
|
218
|
+
user: "deploy",
|
|
219
|
+
// password 생략 → ~/.ssh/id_ed25519 + SSH_AUTH_SOCK 사용
|
|
220
|
+
}, async (storage) => {
|
|
221
|
+
await storage.uploadDir("/local/build", "/var/www/html");
|
|
222
|
+
});
|
|
160
223
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/storage",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.86",
|
|
4
4
|
"description": "Simplysm Package - Storage Module (node)",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"basic-ftp": "^5.2.0",
|
|
23
23
|
"ssh2-sftp-client": "^12.1.0",
|
|
24
|
-
"@simplysm/core-common": "13.0.
|
|
24
|
+
"@simplysm/core-common": "13.0.86"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/ssh2-sftp-client": "^9.0.6"
|