fs-object-storage 1.0.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.
@@ -0,0 +1,41 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+ push:
10
+ branches:
11
+ - main
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [16.x]
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - name: Use Node.js ${{ matrix.node-version }}
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: ${{ matrix.node-version }}
26
+ - run: npm ci
27
+ - run: npm test
28
+
29
+ publish-npm:
30
+ needs: build
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-node@v4
35
+ with:
36
+ node-version: '16.x'
37
+ registry-url: https://registry.npmjs.org/
38
+ - run: npm ci
39
+ - run: npm publish
40
+ env:
41
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nojaja
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # fs-object-storage
2
+
3
+ Node.js fs互換APIでオブジェクトストレージ(MinIO/S3)を操作するライブラリです。
4
+
5
+ 既存のファイルシステムコードを最小限の変更でオブジェクトストレージに対応させることができます。
6
+
7
+ ## 🚀 特徴
8
+
9
+ - **fs互換API**: 標準のNode.js fsモジュールと同じインターフェース
10
+ - **MinIO/S3対応**: MinIOおよびAmazon S3と互換性
11
+ - **型安全性**: TypeScriptサポート(型定義含む)
12
+ - **ES Modules**: モダンなES Modules形式
13
+ - **ストリーミング**: 大容量ファイルのストリーミング処理対応
14
+ - **エラーハンドリング**: fs互換のエラーコード変換
15
+
16
+ ## 📦 インストール
17
+
18
+ ```bash
19
+ npm install fs-object-storage
20
+ ```
21
+
22
+ ## 🏃‍♂️ クイックスタート
23
+
24
+ ### 基本的な使用方法
25
+
26
+ ```javascript
27
+ import { FsMinioClient } from 'fs-object-storage';
28
+
29
+ // MinIO/S3クライアントの設定
30
+ const client = new FsMinioClient({
31
+ endPoint: 'localhost',
32
+ port: 9000,
33
+ useSSL: false,
34
+ accessKey: 'minioadmin',
35
+ secretKey: 'minioadmin123'
36
+ });
37
+
38
+ // ファイル操作(fs互換)
39
+ try {
40
+ // ファイル書き込み
41
+ await client.writeFile('/mybucket/path/to/file.txt', 'Hello, World!');
42
+
43
+ // ファイル読み込み
44
+ const data = await client.readFile('/mybucket/path/to/file.txt', 'utf8');
45
+ console.log(data); // "Hello, World!"
46
+
47
+ // ファイル存在確認
48
+ const exists = await client.exists('/mybucket/path/to/file.txt');
49
+ console.log(exists); // true
50
+
51
+ // ディレクトリ一覧
52
+ const files = await client.readdir('/mybucket/path');
53
+ console.log(files); // ['to/']
54
+
55
+ } catch (error) {
56
+ console.error('エラー:', error.message);
57
+ }
58
+ ```
59
+
60
+ ### ストリーミング操作
61
+
62
+ ```javascript
63
+ import fs from 'fs';
64
+
65
+ // 大容量ファイルのアップロード
66
+ const readStream = fs.createReadStream('./large-file.zip');
67
+ const writeStream = await client.createWriteStream('/mybucket/uploads/large-file.zip');
68
+ readStream.pipe(writeStream);
69
+
70
+ // ダウンロードストリーム
71
+ const downloadStream = await client.createReadStream('/mybucket/uploads/large-file.zip');
72
+ const localWriteStream = fs.createWriteStream('./downloaded-file.zip');
73
+ downloadStream.pipe(localWriteStream);
74
+ ```
75
+
76
+ ## 🔧 開発環境のセットアップ
77
+
78
+ ### MinIO開発環境(Docker)
79
+
80
+ ```bash
81
+ # リポジトリをクローン
82
+ git clone <repository-url>
83
+ cd fs-object-storage
84
+
85
+ # 依存関係のインストール
86
+ npm install
87
+
88
+ # MinIO開発環境の起動
89
+ docker-compose up -d
90
+
91
+ # MinIO管理画面: http://localhost:9001
92
+ # ユーザー名: minioadmin
93
+ # パスワード: minioadmin123
94
+ ```
95
+
96
+ ### テスト実行
97
+
98
+ ```bash
99
+ # 単体テスト
100
+ npm run test
101
+
102
+ # 統合テスト(MinIO必須)
103
+ npm run test:integration
104
+
105
+ # 全テスト
106
+ npm run test:all
107
+ ```
108
+
109
+ ## 📚 API リファレンス
110
+
111
+ ### コンストラクタ
112
+
113
+ ```javascript
114
+ new FsMinioClient(options)
115
+ ```
116
+
117
+ **options**:
118
+ - `endPoint`: MinIO/S3エンドポイント
119
+ - `port`: ポート番号
120
+ - `useSSL`: SSL使用フラグ
121
+ - `accessKey`: アクセスキー
122
+ - `secretKey`: シークレットキー
123
+
124
+ ### ファイル操作メソッド
125
+
126
+ #### `readFile(path, encoding?)`
127
+ ファイルを読み込みます。
128
+
129
+ ```javascript
130
+ const data = await client.readFile('/bucket/file.txt', 'utf8');
131
+ ```
132
+
133
+ #### `writeFile(path, data, options?)`
134
+ ファイルを書き込みます。
135
+
136
+ ```javascript
137
+ await client.writeFile('/bucket/file.txt', 'データ');
138
+ ```
139
+
140
+ #### `exists(path)`
141
+ ファイル/ディレクトリの存在を確認します。
142
+
143
+ ```javascript
144
+ const exists = await client.exists('/bucket/file.txt');
145
+ ```
146
+
147
+ #### `stat(path)`
148
+ ファイル/ディレクトリの統計情報を取得します。
149
+
150
+ ```javascript
151
+ const stats = await client.stat('/bucket/file.txt');
152
+ console.log(stats.size, stats.isFile(), stats.isDirectory());
153
+ ```
154
+
155
+ #### `unlink(path)`
156
+ ファイルを削除します。
157
+
158
+ ```javascript
159
+ await client.unlink('/bucket/file.txt');
160
+ ```
161
+
162
+ #### `copyFile(src, dest)`
163
+ ファイルをコピーします。
164
+
165
+ ```javascript
166
+ await client.copyFile('/bucket/src.txt', '/bucket/dest.txt');
167
+ ```
168
+
169
+ ### ディレクトリ操作メソッド
170
+
171
+ #### `readdir(path)`
172
+ ディレクトリの内容を一覧します。
173
+
174
+ ```javascript
175
+ const files = await client.readdir('/bucket/directory');
176
+ ```
177
+
178
+ #### `mkdir(path, options?)`
179
+ ディレクトリを作成します。
180
+
181
+ ```javascript
182
+ await client.mkdir('/bucket/new-directory', { recursive: true });
183
+ ```
184
+
185
+ #### `rmdir(path)`
186
+ 空のディレクトリを削除します。
187
+
188
+ ```javascript
189
+ await client.rmdir('/bucket/empty-directory');
190
+ ```
191
+
192
+ ### ストリーム操作メソッド
193
+
194
+ #### `createReadStream(path)`
195
+ 読み込みストリームを作成します。
196
+
197
+ ```javascript
198
+ const stream = await client.createReadStream('/bucket/file.txt');
199
+ ```
200
+
201
+ #### `createWriteStream(path)`
202
+ 書き込みストリームを作成します。
203
+
204
+ ```javascript
205
+ const stream = await client.createWriteStream('/bucket/file.txt');
206
+ ```
207
+
208
+ ## 🗺️ パス形式
209
+
210
+ ファイルパスは `/bucket/path/to/file.txt` 形式で指定します:
211
+
212
+ - 先頭の `/` は必須
213
+ - 最初のセグメントがバケット名
214
+ - それ以降がオブジェクトキー
215
+
216
+ 例:
217
+ - `/mybucket/documents/report.pdf` → バケット: `mybucket`, キー: `documents/report.pdf`
218
+ - `/photos/2023/vacation.jpg` → バケット: `photos`, キー: `2023/vacation.jpg`
219
+
220
+ ## 🚨 エラーハンドリング
221
+
222
+ MinIOのエラーは自動的にfs互換のエラーコードに変換されます:
223
+
224
+ ```javascript
225
+ try {
226
+ await client.readFile('/bucket/nonexistent.txt');
227
+ } catch (error) {
228
+ console.log(error.code); // 'ENOENT'
229
+ console.log(error.errno); // -2
230
+ console.log(error.path); // '/bucket/nonexistent.txt'
231
+ }
232
+ ```
233
+
234
+ ## 📋 対応エラーコード
235
+
236
+ | MinIOエラー | fsエラーコード | 説明 |
237
+ |-------------|----------------|------|
238
+ | NoSuchKey | ENOENT | ファイルが存在しない |
239
+ | NoSuchBucket | ENOENT | バケットが存在しない |
240
+ | AccessDenied | EACCES | アクセス権限がない |
241
+ | BucketAlreadyExists | EEXIST | バケットが既に存在 |
242
+
243
+ ## 🔍 使用例
244
+
245
+ 詳細な使用例は以下のファイルを参照してください:
246
+
247
+ - [`docs/technical/usage-examples.md`](./docs/technical/usage-examples.md) - 包括的な使用例
248
+ - [`samples/fs-minio-test.js`](./samples/fs-minio-test.js) - 統合テストサンプル
249
+ - [`quick-test.js`](./quick-test.js) - 基本動作確認
250
+
251
+ ## 🏗️ アーキテクチャ
252
+
253
+ 本ライブラリは以下のコンポーネントで構成されています:
254
+
255
+ - **FsMinioClient**: メインのfs互換クライアント
256
+ - **ErrorHandler**: MinIO→fs エラー変換
257
+ - **PathConverter**: ファイルパス⇔バケット/キー変換
258
+ - **StreamConverter**: ストリーム/データ形式変換
259
+
260
+ 詳細は [`docs/technical/architecture.md`](./docs/technical/architecture.md) を参照してください。
261
+
262
+ ## 🧪 テスト
263
+
264
+ ```bash
265
+ # 単体テスト(3つのコンポーネント)
266
+ npm run test
267
+
268
+ # 統合テスト(MinIO接続必須)
269
+ npm run test:integration
270
+
271
+ # 全テスト実行
272
+ npm run test:all
273
+ ```
274
+
275
+ ## 📄 ライセンス
276
+
277
+ MIT
278
+
279
+ ## 🤝 コントリビューション
280
+
281
+ バグ報告や機能提案は Issue からお願いします。プルリクエストも歓迎します。
282
+
283
+ ## 📞 サポート
284
+
285
+ - **ドキュメント**: [`docs/`](./docs/) フォルダ
286
+ - **GitHub Issues**: バグ報告・機能要求
287
+ - **サンプルコード**: [`samples/`](./samples/) フォルダ"
@@ -0,0 +1,24 @@
1
+ version: '3.8'
2
+
3
+ services:
4
+ minio:
5
+ image: minio/minio:latest
6
+ container_name: fs-object-storage-minio
7
+ ports:
8
+ - "9000:9000"
9
+ - "9001:9001"
10
+ environment:
11
+ MINIO_ROOT_USER: minioadmin
12
+ MINIO_ROOT_PASSWORD: minioadmin123
13
+ command: server /data --console-address ":9001"
14
+ volumes:
15
+ - minio_data:/data
16
+ healthcheck:
17
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
18
+ interval: 30s
19
+ timeout: 20s
20
+ retries: 3
21
+
22
+ volumes:
23
+ minio_data:
24
+ driver: local
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "fs-object-storage",
3
+ "version": "1.0.0",
4
+ "description": "Node.js fs compatible API for object storage (MinIO/S3)",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "type": "module", "scripts": {
8
+ "test": "node --test unit-tests.js",
9
+ "test:integration": "node samples/fs-minio-test.js",
10
+ "test:all": "npm run test && npm run test:integration",
11
+ "dev": "node src/index.js",
12
+ "build": "webpack --mode production"
13
+ },"keywords": [
14
+ "filesystem",
15
+ "fs",
16
+ "object-storage",
17
+ "minio",
18
+ "s3",
19
+ "compatible",
20
+ "node",
21
+ "typescript"
22
+ ],
23
+ "author": "nojaja <free.riccia@gmail.com> (https://github.com/nojaja)",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/nojaja/fs-object-storage.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/nojaja/fs-object-storage/issues"
31
+ },
32
+ "homepage": "https://github.com/nojaja/fs-object-storage#readme",
33
+ "engines": {
34
+ "node": ">=16.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "jest": "^28.1.3",
38
+ "webpack": "^5.99.8",
39
+ "webpack-cli": "^5.1.4"
40
+ },
41
+ "dependencies": {
42
+ "memfs": "^4.17.2",
43
+ "minio": "^8.0.5"
44
+ }
45
+ }
package/quick-test.js ADDED
@@ -0,0 +1,36 @@
1
+ // quick-test.js - Quick test of fs-minio library
2
+
3
+ console.log('Starting fs-minio quick test...');
4
+
5
+ import { FsMinioClient } from './src/index.js';
6
+
7
+ async function quickTest() {
8
+ try {
9
+ console.log('Creating client...'); const client = new FsMinioClient({
10
+ endpoint: 'localhost:9000',
11
+ accessKey: 'minioadmin',
12
+ secretKey: 'minioadmin123',
13
+ bucket: 'fs-minio-test',
14
+ useSSL: false
15
+ });
16
+
17
+ console.log('Initializing client...');
18
+ await client.initialize();
19
+ console.log('Client initialized!');
20
+
21
+ console.log('Writing test file...');
22
+ await client.writeFile('/test.txt', 'Hello World!', 'utf8');
23
+ console.log('File written!');
24
+
25
+ console.log('Reading test file...');
26
+ const content = await client.readFile('/test.txt', 'utf8');
27
+ console.log('File content:', content);
28
+
29
+ console.log('Test completed successfully!');
30
+ } catch (error) {
31
+ console.error('Test failed:', error.message);
32
+ console.error('Stack:', error.stack);
33
+ }
34
+ }
35
+
36
+ quickTest();
@@ -0,0 +1,135 @@
1
+ // fs-minio-test.js - Test the fs-minio library implementation
2
+
3
+ import { FsMinioClient } from '../src/index.js';
4
+ import path from 'path';
5
+
6
+ async function testFsMinioLibrary() {
7
+ console.log('🧪 Testing fs-minio library implementation...\n');
8
+
9
+ // Create client instance
10
+ const client = new FsMinioClient({
11
+ endpoint: 'localhost:9000',
12
+ accessKey: 'minioadmin',
13
+ secretKey: 'minioadmin123',
14
+ bucket: 'fs-minio-test',
15
+ useSSL: false,
16
+ prefix: 'test'
17
+ });
18
+
19
+ try {
20
+ console.log('📝 Initializing client...');
21
+ await client.initialize();
22
+ console.log('✅ Client initialized successfully\n');
23
+
24
+ // Test 1: Write file
25
+ console.log('📝 Test 1: writeFile()');
26
+ const testContent = 'Hello from fs-minio library!\nThis is a test file.\n今日は良い天気ですね。';
27
+ await client.writeFile('/data/hello.txt', testContent, 'utf8');
28
+ console.log('✅ File written successfully\n');
29
+
30
+ // Test 2: Check if file exists
31
+ console.log('📝 Test 2: exists()');
32
+ const fileExists = await client.exists('/data/hello.txt');
33
+ console.log(`✅ File exists: ${fileExists}\n`);
34
+
35
+ // Test 3: Read file
36
+ console.log('📝 Test 3: readFile()');
37
+ const readContent = await client.readFile('/data/hello.txt', 'utf8');
38
+ console.log(`✅ File content: "${readContent}"\n`);
39
+
40
+ // Verify content matches
41
+ if (readContent === testContent) {
42
+ console.log('✅ Content matches original\n');
43
+ } else {
44
+ console.log('❌ Content does not match!\n');
45
+ }
46
+
47
+ // Test 4: Get file stats
48
+ console.log('📝 Test 4: stat()');
49
+ const stats = await client.stat('/data/hello.txt');
50
+ console.log('✅ File stats:');
51
+ console.log(` Size: ${stats.size} bytes`);
52
+ console.log(` Modified: ${stats.mtime}`);
53
+ console.log(` Is file: ${stats.isFile()}`);
54
+ console.log(` Is directory: ${stats.isDirectory()}\n`);
55
+
56
+ // Test 5: Write binary file
57
+ console.log('📝 Test 5: writeFile() with binary data');
58
+ const binaryData = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); // PNG header
59
+ await client.writeFile('/images/test.png', binaryData);
60
+ console.log('✅ Binary file written successfully\n');
61
+
62
+ // Test 6: Read binary file
63
+ console.log('📝 Test 6: readFile() binary data');
64
+ const readBinary = await client.readFile('/images/test.png');
65
+ console.log(`✅ Binary data read: ${readBinary.length} bytes`);
66
+ console.log(` First 8 bytes: ${Array.from(readBinary.slice(0, 8)).map(b => '0x' + b.toString(16).padStart(2, '0')).join(', ')}\n`);
67
+
68
+ // Test 7: Create directory
69
+ console.log('📝 Test 7: mkdir()');
70
+ await client.mkdir('/documents/projects', { recursive: true });
71
+ console.log('✅ Directory created successfully\n');
72
+
73
+ // Test 8: List files in root
74
+ console.log('📝 Test 8: readdir()');
75
+ const rootFiles = await client.readdir('/');
76
+ console.log(`✅ Root directory contents: ${JSON.stringify(rootFiles)}\n`);
77
+
78
+ // Test 9: Test error handling (non-existent file)
79
+ console.log('📝 Test 9: Error handling');
80
+ try {
81
+ await client.readFile('/nonexistent/file.txt');
82
+ console.log('❌ Should have thrown error');
83
+ } catch (error) {
84
+ console.log(`✅ Correctly threw error: ${error.code} - ${error.message}\n`);
85
+ }
86
+
87
+ // Test 10: Delete file
88
+ console.log('📝 Test 10: unlink()');
89
+ await client.unlink('/data/hello.txt');
90
+ console.log('✅ File deleted successfully\n');
91
+
92
+ // Test 11: Verify file is deleted
93
+ console.log('📝 Test 11: Verify deletion');
94
+ const fileExistsAfterDelete = await client.exists('/data/hello.txt');
95
+ console.log(`✅ File exists after delete: ${fileExistsAfterDelete}\n`);
96
+
97
+ // Test 12: Test streams
98
+ console.log('📝 Test 12: createReadStream()');
99
+ const streamContent = 'This is stream test content\nLine 2\nLine 3';
100
+ await client.writeFile('/streams/test.txt', streamContent);
101
+
102
+ const readStream = await client.createReadStream('/streams/test.txt');
103
+ const chunks = [];
104
+
105
+ readStream.on('data', (chunk) => {
106
+ chunks.push(chunk);
107
+ });
108
+
109
+ await new Promise((resolve, reject) => {
110
+ readStream.on('end', () => {
111
+ const streamData = Buffer.concat(chunks).toString('utf8');
112
+ console.log(`✅ Stream content: "${streamData}"`);
113
+ if (streamData === streamContent) {
114
+ console.log('✅ Stream content matches original\n');
115
+ } else {
116
+ console.log('❌ Stream content does not match!\n');
117
+ }
118
+ resolve();
119
+ });
120
+ readStream.on('error', reject);
121
+ });
122
+
123
+ console.log('🎉 All tests completed successfully!');
124
+
125
+ } catch (error) {
126
+ console.error('❌ Test failed:', error.message);
127
+ if (error.originalError) {
128
+ console.error('Original error:', error.originalError.message);
129
+ }
130
+ console.error('Stack:', error.stack);
131
+ }
132
+ }
133
+
134
+ // Run tests
135
+ testFsMinioLibrary().catch(console.error);
@@ -0,0 +1,44 @@
1
+ // memfsライブラリのAPI調査用サンプル
2
+ import { fs } from 'memfs';
3
+
4
+ console.log('=== memfs API調査 ===');
5
+
6
+ // 1. memfsの基本的なfs互換API確認
7
+ console.log('\n1. 利用可能なメソッド:');
8
+ const fsMethods = Object.getOwnPropertyNames(fs).filter(name => typeof fs[name] === 'function');
9
+ console.log(fsMethods.slice(0, 20)); // 最初の20個のメソッドを表示
10
+
11
+ // 2. fs.writeFileの動作確認
12
+ try {
13
+ fs.writeFileSync('/test.txt', 'Hello memfs!');
14
+ console.log('\n2. writeFileSync成功');
15
+
16
+ // 3. fs.readFileの動作確認
17
+ const content = fs.readFileSync('/test.txt', 'utf8');
18
+ console.log('3. readFileSync結果:', content);
19
+
20
+ // 4. fs.existsSyncの動作確認
21
+ const exists = fs.existsSync('/test.txt');
22
+ console.log('4. existsSync結果:', exists);
23
+
24
+ // 5. fs.statSyncの動作確認
25
+ const stats = fs.statSync('/test.txt');
26
+ console.log('5. statSync結果:', {
27
+ size: stats.size,
28
+ isFile: stats.isFile(),
29
+ isDirectory: stats.isDirectory()
30
+ });
31
+
32
+ // 6. Promise版APIの確認
33
+ console.log('\n6. Promise版API確認:');
34
+ console.log('fs.promises利用可能:', typeof fs.promises === 'object');
35
+ if (fs.promises) {
36
+ const promiseMethods = Object.getOwnPropertyNames(fs.promises).filter(name => typeof fs.promises[name] === 'function');
37
+ console.log('Promise版メソッド例:', promiseMethods.slice(0, 10));
38
+ }
39
+
40
+ } catch (error) {
41
+ console.error('エラーが発生したのだ:', error.message);
42
+ }
43
+
44
+ console.log('\n=== memfs調査完了 ===');