@simplysm/storage 13.0.100 → 14.0.4

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @simplysm/storage
2
2
 
3
- Simplysm Package - Storage Module (node). Provides FTP, FTPS, and SFTP storage client implementations with a factory for managed connections.
3
+ Storage client library for FTP, FTPS, and SFTP file operations.
4
+
5
+ Platform: Node.js.
4
6
 
5
7
  ## Installation
6
8
 
@@ -12,131 +14,121 @@ npm install @simplysm/storage
12
14
 
13
15
  ### Types
14
16
 
15
- | API | Type | Description |
16
- |-----|------|-------------|
17
- | `StorageConnConfig` | interface | Storage connection configuration |
18
- | `FileInfo` | interface | File/directory entry information |
19
- | `StorageClient` | interface | Storage client interface |
20
- | `StorageProtocol` | type | Protocol type (`"ftp" \| "ftps" \| "sftp"`) |
17
+ #### StorageConnConfig
21
18
 
22
- ### Clients
19
+ Connection configuration for storage clients.
23
20
 
24
- | API | Type | Description |
25
- |-----|------|-------------|
26
- | `FtpStorageClient` | class | FTP/FTPS storage client (uses basic-ftp) |
27
- | `SftpStorageClient` | class | SFTP storage client (uses ssh2-sftp-client) |
21
+ | Field | Type | Description |
22
+ |---|---|---|
23
+ | `host` | `string` | Server hostname or IP address |
24
+ | `port?` | `number` | Server port (protocol default if omitted) |
25
+ | `user?` | `string` | Username |
26
+ | `password?` | `string` | Password |
28
27
 
29
- ### Factory
28
+ #### FileInfo
30
29
 
31
- | API | Type | Description |
32
- |-----|------|-------------|
33
- | `StorageFactory` | class | Storage client factory with managed connections |
30
+ File/directory entry returned by `list()`.
34
31
 
35
- ---
32
+ | Field | Type | Description |
33
+ |---|---|---|
34
+ | `name` | `string` | File or directory name |
35
+ | `isFile` | `boolean` | `true` if this entry is a file, `false` if it is a directory |
36
36
 
37
- ### `StorageProtocol`
37
+ #### StorageProtocol
38
38
 
39
39
  ```typescript
40
- type StorageProtocol = "ftp" | "ftps" | "sftp"
40
+ type StorageProtocol = "ftp" | "ftps" | "sftp";
41
41
  ```
42
42
 
43
- ### `StorageConnConfig`
43
+ #### StorageClient (interface)
44
44
 
45
- | Field | Type | Description |
46
- |-------|------|-------------|
47
- | `host` | `string` | Server hostname |
48
- | `port` | `number?` | Server port |
49
- | `user` | `string?` | Username |
50
- | `password` | `string?` | Password (SFTP: if omitted, uses SSH agent + key file) |
45
+ Common interface implemented by all storage clients.
51
46
 
52
- ### `FileInfo`
47
+ | Method | Signature | Description |
48
+ |---|---|---|
49
+ | `connect` | `(config: StorageConnConfig) => Promise<void>` | Connect to the server |
50
+ | `mkdir` | `(dirPath: string) => Promise<void>` | Create directory (recursive) |
51
+ | `rename` | `(fromPath: string, toPath: string) => Promise<void>` | Rename a file or directory |
52
+ | `list` | `(dirPath: string) => Promise<FileInfo[]>` | List directory contents |
53
+ | `readFile` | `(filePath: string) => Promise<Bytes>` | Read file contents |
54
+ | `exists` | `(filePath: string) => Promise<boolean>` | Check if file or directory exists |
55
+ | `put` | `(localPathOrBuffer: string \| Bytes, storageFilePath: string) => Promise<void>` | Upload a local file path or byte data to remote path |
56
+ | `uploadDir` | `(fromPath: string, toPath: string) => Promise<void>` | Upload entire local directory to remote path |
57
+ | `remove` | `(filePath: string) => Promise<void>` | Remove a file |
58
+ | `close` | `() => Promise<void>` | Close the connection. Safe to call multiple times. |
53
59
 
54
- | Field | Type | Description |
55
- |-------|------|-------------|
56
- | `name` | `string` | File or directory name |
57
- | `isFile` | `boolean` | Whether the entry is a file |
60
+ ### Clients
58
61
 
59
- ### `StorageClient`
62
+ #### FtpStorageClient
60
63
 
61
- Interface implemented by both `FtpStorageClient` and `SftpStorageClient`.
64
+ FTP/FTPS client. Implements `StorageClient`. Uses `basic-ftp` internally.
62
65
 
63
- | Method | Signature | Description |
64
- |--------|-----------|-------------|
65
- | `connect` | `(config: StorageConnConfig) => Promise<void>` | Connect to server |
66
- | `mkdir` | `(dirPath: string) => Promise<void>` | Create directory (recursive) |
67
- | `rename` | `(fromPath: string, toPath: string) => Promise<void>` | Rename file/directory |
68
- | `list` | `(dirPath: string) => Promise<FileInfo[]>` | List directory contents |
69
- | `readFile` | `(filePath: string) => Promise<Bytes>` | Read file |
70
- | `exists` | `(filePath: string) => Promise<boolean>` | Check existence |
71
- | `put` | `(localPathOrBuffer: string \| Bytes, storageFilePath: string) => Promise<void>` | Upload file (local path or byte data) |
72
- | `uploadDir` | `(fromPath: string, toPath: string) => Promise<void>` | Upload entire directory |
73
- | `remove` | `(filePath: string) => Promise<void>` | Delete file |
74
- | `close` | `() => Promise<void>` | Close connection |
66
+ ```typescript
67
+ constructor(secure?: boolean)
68
+ ```
75
69
 
76
- ### `FtpStorageClient`
70
+ | Parameter | Type | Default | Description |
71
+ |---|---|---|---|
72
+ | `secure` | `boolean` | `false` | Set `true` for FTPS |
77
73
 
78
- Implements `StorageClient` using FTP/FTPS protocol via the `basic-ftp` library.
74
+ All methods from `StorageClient` are implemented. Prefer using `StorageFactory.connect` over direct instantiation.
79
75
 
80
- | Method | Signature | Description |
81
- |--------|-----------|-------------|
82
- | `constructor` | `(secure?: boolean)` | Create client (`true` for FTPS, `false` for FTP) |
76
+ #### SftpStorageClient
83
77
 
84
- All `StorageClient` methods are implemented. Using `StorageFactory.connect` is recommended over direct usage.
78
+ SFTP client. Implements `StorageClient`. Uses `ssh2-sftp-client` internally.
85
79
 
86
- ### `SftpStorageClient`
80
+ ```typescript
81
+ constructor()
82
+ ```
87
83
 
88
- Implements `StorageClient` using SFTP protocol via the `ssh2-sftp-client` library. Supports password authentication and SSH agent + key file authentication.
84
+ Authentication priority when `password` is not provided:
85
+ 1. SSH private key (`~/.ssh/id_ed25519`) + SSH agent
86
+ 2. SSH agent only (fallback when key parsing fails)
89
87
 
90
- All `StorageClient` methods are implemented. Using `StorageFactory.connect` is recommended over direct usage.
88
+ All methods from `StorageClient` are implemented. Prefer using `StorageFactory.connect` over direct instantiation.
91
89
 
92
- ### `StorageFactory`
90
+ ### Factory
91
+
92
+ #### StorageFactory
93
+
94
+ Static factory class for creating and managing storage connections.
95
+
96
+ ##### Static Methods
93
97
 
94
98
  | Method | Signature | Description |
95
- |--------|-----------|-------------|
96
- | `connect` | `<R>(type: StorageProtocol, config: StorageConnConfig, fn: (storage: StorageClient) => R \| Promise<R>) => Promise<R>` | Connect, execute callback, auto-close |
99
+ |---|---|---|
100
+ | `connect` | `<R>(type: StorageProtocol, config: StorageConnConfig, fn: (storage: StorageClient) => R \| Promise<R>) => Promise<R>` | Connect to storage, execute callback, and automatically close the connection. The connection is closed even if the callback throws. |
97
101
 
98
- ## Usage Examples
102
+ ## Usage
99
103
 
100
- ### Using StorageFactory (recommended)
104
+ ### Recommended: StorageFactory (auto-managed connection)
101
105
 
102
106
  ```typescript
103
107
  import { StorageFactory } from "@simplysm/storage";
104
108
 
105
- await StorageFactory.connect("sftp", {
109
+ const files = await StorageFactory.connect("sftp", {
106
110
  host: "example.com",
107
111
  user: "deploy",
108
- password: "secret",
109
- }, async (storage) => {
110
- await storage.uploadDir("./dist", "/var/www/app");
111
- const files = await storage.list("/var/www/app");
112
+ }, async (client) => {
113
+ await client.mkdir("/remote/path");
114
+ await client.put("/local/file.txt", "/remote/file.txt");
115
+ return client.list("/remote/path");
112
116
  });
113
117
  ```
114
118
 
115
- ### Using client directly
119
+ ### Direct client usage
116
120
 
117
121
  ```typescript
118
- import { FtpStorageClient } from "@simplysm/storage";
122
+ import { SftpStorageClient } from "@simplysm/storage";
119
123
 
120
- const client = new FtpStorageClient(true); // FTPS
121
- await client.connect({ host: "ftp.example.com", user: "admin", password: "pass" });
124
+ const client = new SftpStorageClient();
125
+ await client.connect({ host: "example.com", user: "deploy" });
122
126
  try {
123
- await client.put("./build.zip", "/releases/build.zip");
124
- const exists = await client.exists("/releases/build.zip");
127
+ const exists = await client.exists("/remote/file.txt");
128
+ if (exists) {
129
+ const data = await client.readFile("/remote/file.txt");
130
+ }
125
131
  } finally {
126
132
  await client.close();
127
133
  }
128
134
  ```
129
-
130
- ### SFTP with SSH key authentication
131
-
132
- ```typescript
133
- import { StorageFactory } from "@simplysm/storage";
134
-
135
- // Omit password to use SSH agent + ~/.ssh/id_ed25519
136
- await StorageFactory.connect("sftp", {
137
- host: "example.com",
138
- user: "deploy",
139
- }, async (storage) => {
140
- await storage.put(fileBytes, "/data/upload.bin");
141
- });
142
- ```
@@ -2,54 +2,54 @@ import type { Bytes } from "@simplysm/core-common";
2
2
  import type { StorageClient, FileInfo } from "../types/storage";
3
3
  import type { StorageConnConfig } from "../types/storage-conn-config";
4
4
  /**
5
- * Storage client using FTP/FTPS protocol.
5
+ * FTP/FTPS 프로토콜을 사용하는 스토리지 클라이언트.
6
6
  *
7
7
  * @remarks
8
- * The `secure` constructor parameter configures whether to use FTPS.
9
- * Using {@link StorageFactory.connect} is recommended over direct usage.
8
+ * `secure` 생성자 매개변수로 FTPS 사용 여부를 설정합니다.
9
+ * 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
10
10
  */
11
11
  export declare class FtpStorageClient implements StorageClient {
12
12
  private readonly _secure;
13
13
  private _client;
14
14
  constructor(_secure?: boolean);
15
15
  /**
16
- * Connect to the FTP server.
16
+ * FTP 서버에 연결합니다.
17
17
  *
18
18
  * @remarks
19
- * - Must close the connection with {@link close} after use.
20
- * - Do not call multiple times on the same instance (connection leak).
21
- * - Use {@link StorageFactory.connect} for automatic connection/close management (recommended).
19
+ * - 사용 {@link close} 연결을 종료해야 합니다.
20
+ * - 동일 인스턴스에서 여러 호출하지 마세요 (연결 누수).
21
+ * - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
22
22
  */
23
23
  connect(config: StorageConnConfig): Promise<void>;
24
24
  private _requireClient;
25
- /** Create a directory. Creates parent directories if they do not exist. */
25
+ /** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
26
26
  mkdir(dirPath: string): Promise<void>;
27
27
  rename(fromPath: string, toPath: string): Promise<void>;
28
28
  list(dirPath: string): Promise<FileInfo[]>;
29
29
  readFile(filePath: string): Promise<Bytes>;
30
30
  /**
31
- * Check whether a file or directory exists.
31
+ * 파일 또는 디렉토리의 존재 여부를 확인합니다.
32
32
  *
33
33
  * @remarks
34
- * For files, uses the size() command for O(1) performance.
35
- * For directories, queries the parent directory listing, so performance may degrade with many entries.
34
+ * 파일의 경우 size() 명령으로 O(1) 성능을 제공합니다.
35
+ * 디렉토리의 경우 부모 디렉토리 목록을 조회하므로 항목이 많으면 성능이 저하될 있습니다.
36
36
  *
37
- * Paths without slashes (e.g. `file.txt`) are searched in the root directory (`/`).
37
+ * 슬래시가 없는 경로(예: `file.txt`) 루트 디렉토리(`/`)에서 검색합니다.
38
38
  *
39
- * Returns false even if the parent directory does not exist.
40
- * Returns false for all exceptions including network errors and permission errors.
39
+ * 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
40
+ * 네트워크 오류, 권한 오류 모든 예외에 대해 false를 반환합니다.
41
41
  */
42
42
  exists(filePath: string): Promise<boolean>;
43
43
  remove(filePath: string): Promise<void>;
44
- /** Upload a local file path or byte data to the remote path. */
44
+ /** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
45
45
  put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>;
46
46
  uploadDir(fromPath: string, toPath: string): Promise<void>;
47
47
  /**
48
- * Close the connection.
48
+ * 연결을 종료합니다.
49
49
  *
50
50
  * @remarks
51
- * Safe to call when already closed (no error thrown).
52
- * After closing, you can reconnect by calling {@link connect} again on the same instance.
51
+ * 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
52
+ * 종료 동일 인스턴스에서 {@link connect} 다시 호출하여 재연결할 있습니다.
53
53
  */
54
54
  close(): Promise<void>;
55
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ftp-storage-client.d.ts","sourceRoot":"","sources":["..\\..\\src\\clients\\ftp-storage-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEtE;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAGxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,OAAO,CAAyB;gBAEX,OAAO,GAAE,OAAe;IAErD;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvD,OAAO,CAAC,cAAc;IAOtB,2EAA2E;IACrE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAWhD;;;;;;;;;;;OAWG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB1C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,gEAAgE;IAC1D,GAAG,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU9E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CASvB"}
1
+ {"version":3,"file":"ftp-storage-client.d.ts","sourceRoot":"","sources":["..\\..\\src\\clients\\ftp-storage-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAInD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEtE;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAGxC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,OAAO,CAAyB;gBAEX,OAAO,GAAE,OAAe;IAErD;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvD,OAAO,CAAC,cAAc;IAOtB,0CAA0C;IACpC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAK1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAWhD;;;;;;;;;;;OAWG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmB1C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,0CAA0C;IACpC,GAAG,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU9E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CASvB"}
@@ -1,126 +1,137 @@
1
1
  import { bytes, SdError } from "@simplysm/core-common";
2
2
  import ftp from "basic-ftp";
3
3
  import { PassThrough, Readable } from "stream";
4
- class FtpStorageClient {
5
- constructor(_secure = false) {
6
- this._secure = _secure;
7
- }
8
- _client;
9
- /**
10
- * Connect to the FTP server.
11
- *
12
- * @remarks
13
- * - Must close the connection with {@link close} after use.
14
- * - Do not call multiple times on the same instance (connection leak).
15
- * - Use {@link StorageFactory.connect} for automatic connection/close management (recommended).
16
- */
17
- async connect(config) {
18
- if (this._client !== void 0) {
19
- throw new SdError("FTP server is already connected. Please call close() first.");
4
+ /**
5
+ * FTP/FTPS 프로토콜을 사용하는 스토리지 클라이언트.
6
+ *
7
+ * @remarks
8
+ * `secure` 생성자 매개변수로 FTPS 사용 여부를 설정합니다.
9
+ * 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
10
+ */
11
+ export class FtpStorageClient {
12
+ _secure;
13
+ _client;
14
+ constructor(_secure = false) {
15
+ this._secure = _secure;
20
16
  }
21
- const client = new ftp.Client();
22
- try {
23
- await client.access({
24
- host: config.host,
25
- port: config.port,
26
- user: config.user,
27
- password: config.password,
28
- secure: this._secure
29
- });
30
- this._client = client;
31
- } catch (err) {
32
- client.close();
33
- throw err;
17
+ /**
18
+ * FTP 서버에 연결합니다.
19
+ *
20
+ * @remarks
21
+ * - 사용 후 {@link close}로 연결을 종료해야 합니다.
22
+ * - 동일 인스턴스에서 여러 번 호출하지 마세요 (연결 누수).
23
+ * - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
24
+ */
25
+ async connect(config) {
26
+ if (this._client !== undefined) {
27
+ throw new SdError("FTP 서버에 이미 연결되어 있습니다. 먼저 close() 호출해 주세요.");
28
+ }
29
+ const client = new ftp.Client();
30
+ try {
31
+ await client.access({
32
+ host: config.host,
33
+ port: config.port,
34
+ user: config.user,
35
+ password: config.password,
36
+ secure: this._secure,
37
+ });
38
+ this._client = client;
39
+ }
40
+ catch (err) {
41
+ client.close();
42
+ throw err;
43
+ }
34
44
  }
35
- }
36
- _requireClient() {
37
- if (this._client === void 0) {
38
- throw new SdError("Not connected to FTP server.");
45
+ _requireClient() {
46
+ if (this._client === undefined) {
47
+ throw new SdError("FTP 서버에 연결되어 있지 않습니다.");
48
+ }
49
+ return this._client;
39
50
  }
40
- return this._client;
41
- }
42
- /** Create a directory. Creates parent directories if they do not exist. */
43
- async mkdir(dirPath) {
44
- await this._requireClient().ensureDir(dirPath);
45
- }
46
- async rename(fromPath, toPath) {
47
- await this._requireClient().rename(fromPath, toPath);
48
- }
49
- async list(dirPath) {
50
- const fileInfos = await this._requireClient().list(dirPath);
51
- return fileInfos.map((item) => ({ name: item.name, isFile: item.isFile }));
52
- }
53
- async readFile(filePath) {
54
- const client = this._requireClient();
55
- const chunks = [];
56
- const writable = new PassThrough();
57
- writable.on("data", (chunk) => {
58
- chunks.push(chunk);
59
- });
60
- await client.downloadTo(writable, filePath);
61
- return bytes.concat(chunks);
62
- }
63
- /**
64
- * Check whether a file or directory exists.
65
- *
66
- * @remarks
67
- * For files, uses the size() command for O(1) performance.
68
- * For directories, queries the parent directory listing, so performance may degrade with many entries.
69
- *
70
- * Paths without slashes (e.g. `file.txt`) are searched in the root directory (`/`).
71
- *
72
- * Returns false even if the parent directory does not exist.
73
- * Returns false for all exceptions including network errors and permission errors.
74
- */
75
- async exists(filePath) {
76
- try {
77
- await this._requireClient().size(filePath);
78
- return true;
79
- } catch {
80
- try {
81
- const lastSlash = filePath.lastIndexOf("/");
82
- const dirPath = lastSlash > 0 ? filePath.substring(0, lastSlash) : "/";
83
- const fileName = filePath.substring(lastSlash + 1);
84
- const list = await this._requireClient().list(dirPath);
85
- return list.some((item) => item.name === fileName);
86
- } catch {
87
- return false;
88
- }
51
+ /** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
52
+ async mkdir(dirPath) {
53
+ await this._requireClient().ensureDir(dirPath);
89
54
  }
90
- }
91
- async remove(filePath) {
92
- await this._requireClient().remove(filePath);
93
- }
94
- /** Upload a local file path or byte data to the remote path. */
95
- async put(localPathOrBuffer, storageFilePath) {
96
- let param;
97
- if (typeof localPathOrBuffer === "string") {
98
- param = localPathOrBuffer;
99
- } else {
100
- param = Readable.from(localPathOrBuffer);
55
+ async rename(fromPath, toPath) {
56
+ await this._requireClient().rename(fromPath, toPath);
101
57
  }
102
- await this._requireClient().uploadFrom(param, storageFilePath);
103
- }
104
- async uploadDir(fromPath, toPath) {
105
- await this._requireClient().uploadFromDir(fromPath, toPath);
106
- }
107
- /**
108
- * Close the connection.
109
- *
110
- * @remarks
111
- * Safe to call when already closed (no error thrown).
112
- * After closing, you can reconnect by calling {@link connect} again on the same instance.
113
- */
114
- close() {
115
- if (this._client === void 0) {
116
- return Promise.resolve();
58
+ async list(dirPath) {
59
+ const fileInfos = await this._requireClient().list(dirPath);
60
+ return fileInfos.map((item) => ({ name: item.name, isFile: item.isFile }));
61
+ }
62
+ async readFile(filePath) {
63
+ const client = this._requireClient();
64
+ const chunks = [];
65
+ const writable = new PassThrough();
66
+ writable.on("data", (chunk) => {
67
+ chunks.push(chunk);
68
+ });
69
+ await client.downloadTo(writable, filePath);
70
+ return bytes.concat(chunks);
71
+ }
72
+ /**
73
+ * 파일 또는 디렉토리의 존재 여부를 확인합니다.
74
+ *
75
+ * @remarks
76
+ * 파일의 경우 size() 명령으로 O(1) 성능을 제공합니다.
77
+ * 디렉토리의 경우 부모 디렉토리 목록을 조회하므로 항목이 많으면 성능이 저하될 수 있습니다.
78
+ *
79
+ * 슬래시가 없는 경로(예: `file.txt`)는 루트 디렉토리(`/`)에서 검색합니다.
80
+ *
81
+ * 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
82
+ * 네트워크 오류, 권한 오류 등 모든 예외에 대해 false를 반환합니다.
83
+ */
84
+ async exists(filePath) {
85
+ try {
86
+ // size()로 파일 존재 여부 빠른 확인 (O(1))
87
+ await this._requireClient().size(filePath);
88
+ return true;
89
+ }
90
+ catch {
91
+ // size() 실패 시 디렉토리일 수 있으므로 list()로 확인
92
+ try {
93
+ const lastSlash = filePath.lastIndexOf("/");
94
+ const dirPath = lastSlash > 0 ? filePath.substring(0, lastSlash) : "/";
95
+ const fileName = filePath.substring(lastSlash + 1);
96
+ const list = await this._requireClient().list(dirPath);
97
+ return list.some((item) => item.name === fileName);
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ }
104
+ async remove(filePath) {
105
+ await this._requireClient().remove(filePath);
106
+ }
107
+ /** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
108
+ async put(localPathOrBuffer, storageFilePath) {
109
+ let param;
110
+ if (typeof localPathOrBuffer === "string") {
111
+ param = localPathOrBuffer;
112
+ }
113
+ else {
114
+ param = Readable.from(localPathOrBuffer);
115
+ }
116
+ await this._requireClient().uploadFrom(param, storageFilePath);
117
+ }
118
+ async uploadDir(fromPath, toPath) {
119
+ await this._requireClient().uploadFromDir(fromPath, toPath);
120
+ }
121
+ /**
122
+ * 연결을 종료합니다.
123
+ *
124
+ * @remarks
125
+ * 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
126
+ * 종료 후 동일 인스턴스에서 {@link connect}를 다시 호출하여 재연결할 수 있습니다.
127
+ */
128
+ close() {
129
+ if (this._client === undefined) {
130
+ return Promise.resolve();
131
+ }
132
+ this._client.close();
133
+ this._client = undefined;
134
+ return Promise.resolve();
117
135
  }
118
- this._client.close();
119
- this._client = void 0;
120
- return Promise.resolve();
121
- }
122
136
  }
123
- export {
124
- FtpStorageClient
125
- };
126
- //# sourceMappingURL=ftp-storage-client.js.map
137
+ //# sourceMappingURL=ftp-storage-client.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/clients/ftp-storage-client.ts"],
4
- "mappings": "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",
5
- "names": []
6
- }
1
+ {"version":3,"file":"ftp-storage-client.js","sourceRoot":"","sources":["..\\..\\src\\clients\\ftp-storage-client.ts"],"names":[],"mappings":"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"}
@@ -2,46 +2,46 @@ import type { Bytes } from "@simplysm/core-common";
2
2
  import type { StorageClient, FileInfo } from "../types/storage";
3
3
  import type { StorageConnConfig } from "../types/storage-conn-config";
4
4
  /**
5
- * Storage client using SFTP protocol.
5
+ * SFTP 프로토콜을 사용하는 스토리지 클라이언트.
6
6
  *
7
7
  * @remarks
8
- * Using {@link StorageFactory.connect} is recommended over direct usage.
8
+ * 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
9
9
  */
10
10
  export declare class SftpStorageClient implements StorageClient {
11
11
  private _client;
12
12
  /**
13
- * Connect to the SFTP server.
13
+ * SFTP 서버에 연결합니다.
14
14
  *
15
15
  * @remarks
16
- * - Must close the connection with {@link close} after use.
17
- * - Do not call multiple times on the same instance (connection leak).
18
- * - Use {@link StorageFactory.connect} for automatic connection/close management (recommended).
16
+ * - 사용 {@link close} 연결을 종료해야 합니다.
17
+ * - 동일 인스턴스에서 여러 호출하지 마세요 (연결 누수).
18
+ * - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
19
19
  */
20
20
  connect(config: StorageConnConfig): Promise<void>;
21
21
  private _requireClient;
22
- /** Create a directory. Creates parent directories if they do not exist. */
22
+ /** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
23
23
  mkdir(dirPath: string): Promise<void>;
24
24
  rename(fromPath: string, toPath: string): Promise<void>;
25
25
  /**
26
- * Check whether a file or directory exists.
26
+ * 파일 또는 디렉토리의 존재 여부를 확인합니다.
27
27
  *
28
28
  * @remarks
29
- * Returns false even if the parent directory does not exist.
30
- * Returns false for all exceptions including network errors and permission errors.
29
+ * 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
30
+ * 네트워크 오류, 권한 오류 모든 예외에 대해 false를 반환합니다.
31
31
  */
32
32
  exists(filePath: string): Promise<boolean>;
33
33
  list(dirPath: string): Promise<FileInfo[]>;
34
34
  readFile(filePath: string): Promise<Bytes>;
35
35
  remove(filePath: string): Promise<void>;
36
- /** Upload a local file path or byte data to the remote path. */
36
+ /** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
37
37
  put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>;
38
38
  uploadDir(fromPath: string, toPath: string): Promise<void>;
39
39
  /**
40
- * Close the connection.
40
+ * 연결을 종료합니다.
41
41
  *
42
42
  * @remarks
43
- * Safe to call when already closed (no error thrown).
44
- * After closing, you can reconnect by calling {@link connect} again on the same instance.
43
+ * 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
44
+ * 종료 동일 인스턴스에서 {@link connect} 다시 호출하여 재연결할 있습니다.
45
45
  */
46
46
  close(): Promise<void>;
47
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sftp-storage-client.d.ts","sourceRoot":"","sources":["..\\..\\src\\clients\\sftp-storage-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKtE;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,OAAO,CAAyB;IAExC;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CvD,OAAO,CAAC,cAAc;IAOtB,2EAA2E;IACrE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;OAMG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW1C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAQ1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAc1C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,gEAAgE;IAC1D,GAAG,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
1
+ {"version":3,"file":"sftp-storage-client.d.ts","sourceRoot":"","sources":["..\\..\\src\\clients\\sftp-storage-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAMnD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKtE;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,OAAO,CAAyB;IAExC;;;;;;;OAOG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0CvD,OAAO,CAAC,cAAc;IAOtB,0CAA0C;IACpC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7D;;;;;;OAMG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW1C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAQ1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAc1C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,0CAA0C;IACpC,GAAG,CAAC,iBAAiB,EAAE,MAAM,GAAG,KAAK,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE;;;;;;OAMG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}