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.
- package/.github/workflows/npm-publish.yml +41 -0
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/docker-compose.yml +24 -0
- package/package.json +45 -0
- package/quick-test.js +36 -0
- package/samples/fs-minio-test.js +135 -0
- package/samples/memfs-sample.js +44 -0
- package/samples/minio-connection-test.js +66 -0
- package/samples/minio-sample.js +64 -0
- package/src/index.d.ts +98 -0
- package/src/index.js +17 -0
- package/src/lib/ErrorHandler.js +149 -0
- package/src/lib/FsMinioClient.js +480 -0
- package/src/lib/PathConverter.js +209 -0
- package/src/lib/StreamConverter.js +281 -0
- package/test-package.json +9 -0
- package/tests/unit/ErrorHandler.test.js +117 -0
- package/tests/unit/PathConverter.test.js +224 -0
- package/tests/unit/StreamConverter.test.js +267 -0
- package/unit-tests.js +101 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// MinIO接続テスト用サンプル
|
|
2
|
+
import * as Minio from 'minio';
|
|
3
|
+
|
|
4
|
+
console.log('=== MinIO接続テスト ===');
|
|
5
|
+
|
|
6
|
+
const minioClient = new Minio.Client({
|
|
7
|
+
endPoint: 'localhost',
|
|
8
|
+
port: 9000,
|
|
9
|
+
useSSL: false,
|
|
10
|
+
accessKey: 'minioadmin',
|
|
11
|
+
secretKey: 'minioadmin123'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
async function testMinIOConnection() {
|
|
15
|
+
try {
|
|
16
|
+
console.log('\n1. MinIOサーバーへの接続テスト');
|
|
17
|
+
|
|
18
|
+
// バケット一覧の取得テスト
|
|
19
|
+
const buckets = await minioClient.listBuckets();
|
|
20
|
+
console.log('✅ 接続成功!現在のバケット数:', buckets.length);
|
|
21
|
+
|
|
22
|
+
// テスト用バケットの作成
|
|
23
|
+
const testBucket = 'fs-storage-test';
|
|
24
|
+
const bucketExists = await minioClient.bucketExists(testBucket);
|
|
25
|
+
|
|
26
|
+
if (!bucketExists) {
|
|
27
|
+
await minioClient.makeBucket(testBucket);
|
|
28
|
+
console.log(`✅ テストバケット '${testBucket}' を作成したのだ`);
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`✅ テストバケット '${testBucket}' は既に存在するのだ`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// テストファイルの書き込み
|
|
34
|
+
const testData = 'Hello MinIO from Node.js!';
|
|
35
|
+
const testFileName = 'test-file.txt';
|
|
36
|
+
|
|
37
|
+
await minioClient.putObject(testBucket, testFileName, testData);
|
|
38
|
+
console.log(`✅ テストファイル '${testFileName}' を書き込んだのだ`);
|
|
39
|
+
|
|
40
|
+
// テストファイルの読み込み
|
|
41
|
+
const dataStream = await minioClient.getObject(testBucket, testFileName);
|
|
42
|
+
let result = '';
|
|
43
|
+
|
|
44
|
+
dataStream.on('data', (chunk) => {
|
|
45
|
+
result += chunk;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
dataStream.on('end', () => {
|
|
49
|
+
console.log(`✅ テストファイル読み込み結果: ${result}`);
|
|
50
|
+
console.log('\n🎉 MinIO環境は正常に動作しているのだ!');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
dataStream.on('error', (err) => {
|
|
54
|
+
console.error('❌ ファイル読み込みエラー:', err);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('❌ MinIO接続エラー:', error.message);
|
|
59
|
+
console.log('\n💡 MinIOサーバーが起動しているか確認するのだ:');
|
|
60
|
+
console.log(' - docker compose ps');
|
|
61
|
+
console.log(' - http://localhost:9001 でWebコンソールにアクセス');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 接続テスト実行
|
|
66
|
+
testMinIOConnection();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// MinIOライブラリのAPI調査用サンプル(接続なし確認)
|
|
2
|
+
import * as Minio from 'minio';
|
|
3
|
+
|
|
4
|
+
console.log('=== MinIO API調査 ===');
|
|
5
|
+
|
|
6
|
+
// 1. MinIOクライアントの基本的なAPI確認
|
|
7
|
+
console.log('\n1. MinIOクライアントの利用可能なメソッド:');
|
|
8
|
+
const client = new Minio.Client({
|
|
9
|
+
endPoint: 'localhost',
|
|
10
|
+
port: 9000,
|
|
11
|
+
useSSL: false,
|
|
12
|
+
accessKey: 'dummy',
|
|
13
|
+
secretKey: 'dummy'
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const clientMethods = Object.getOwnPropertyNames(Object.getPrototypeOf(client))
|
|
17
|
+
.filter(name => typeof client[name] === 'function' && !name.startsWith('_'));
|
|
18
|
+
console.log('主要メソッド:', clientMethods);
|
|
19
|
+
|
|
20
|
+
// 2. 重要なメソッドの詳細確認
|
|
21
|
+
console.log('\n2. 重要なメソッドの詳細:');
|
|
22
|
+
const importantMethods = [
|
|
23
|
+
'getObject',
|
|
24
|
+
'putObject',
|
|
25
|
+
'removeObject',
|
|
26
|
+
'statObject',
|
|
27
|
+
'listObjects',
|
|
28
|
+
'bucketExists',
|
|
29
|
+
'makeBucket'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
importantMethods.forEach(method => {
|
|
33
|
+
if (typeof client[method] === 'function') {
|
|
34
|
+
console.log(`✓ ${method}: 利用可能`);
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`✗ ${method}: 利用不可`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 3. fs互換APIとの対応関係の分析
|
|
41
|
+
console.log('\n3. fs互換APIとMinIO APIの対応関係:');
|
|
42
|
+
const fsToMinioMapping = {
|
|
43
|
+
'fs.readFile': 'client.getObject',
|
|
44
|
+
'fs.writeFile': 'client.putObject',
|
|
45
|
+
'fs.exists': 'client.statObject (catchでfalse)',
|
|
46
|
+
'fs.stat': 'client.statObject',
|
|
47
|
+
'fs.unlink': 'client.removeObject',
|
|
48
|
+
'fs.readdir': 'client.listObjects'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Object.entries(fsToMinioMapping).forEach(([fsMethod, minioMethod]) => {
|
|
52
|
+
console.log(`${fsMethod} → ${minioMethod}`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 4. 設定可能なオプションの確認
|
|
56
|
+
console.log('\n4. 主要設定項目:');
|
|
57
|
+
console.log('- endPoint: MinIOサーバーのホスト');
|
|
58
|
+
console.log('- port: ポート番号');
|
|
59
|
+
console.log('- useSSL: SSL使用有無');
|
|
60
|
+
console.log('- accessKey: アクセスキー');
|
|
61
|
+
console.log('- secretKey: シークレットキー');
|
|
62
|
+
console.log('- region: リージョン(オプション)');
|
|
63
|
+
|
|
64
|
+
console.log('\n=== MinIO調査完了 ===');
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Readable, Writable } from 'stream';
|
|
2
|
+
|
|
3
|
+
export interface MinioConfig {
|
|
4
|
+
endPoint: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
useSSL?: boolean;
|
|
7
|
+
accessKey: string;
|
|
8
|
+
secretKey: string;
|
|
9
|
+
region?: string;
|
|
10
|
+
sessionToken?: string;
|
|
11
|
+
partSize?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StatResult {
|
|
15
|
+
size: number;
|
|
16
|
+
mtime: Date;
|
|
17
|
+
isFile(): boolean;
|
|
18
|
+
isDirectory(): boolean;
|
|
19
|
+
isBlockDevice(): boolean;
|
|
20
|
+
isCharacterDevice(): boolean;
|
|
21
|
+
isSymbolicLink(): boolean;
|
|
22
|
+
isFIFO(): boolean;
|
|
23
|
+
isSocket(): boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WriteFileOptions {
|
|
27
|
+
encoding?: BufferEncoding;
|
|
28
|
+
mode?: number;
|
|
29
|
+
flag?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MkdirOptions {
|
|
33
|
+
recursive?: boolean;
|
|
34
|
+
mode?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FileSystemError extends Error {
|
|
38
|
+
code: string;
|
|
39
|
+
errno: number;
|
|
40
|
+
path?: string;
|
|
41
|
+
syscall?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default class FsMinioClient {
|
|
45
|
+
constructor(config: MinioConfig);
|
|
46
|
+
|
|
47
|
+
// File operations
|
|
48
|
+
readFile(path: string): Promise<Buffer>;
|
|
49
|
+
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
50
|
+
readFile(path: string, encoding?: BufferEncoding): Promise<Buffer | string>;
|
|
51
|
+
|
|
52
|
+
writeFile(path: string, data: string | Buffer | Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
53
|
+
|
|
54
|
+
exists(path: string): Promise<boolean>;
|
|
55
|
+
|
|
56
|
+
stat(path: string): Promise<StatResult>;
|
|
57
|
+
|
|
58
|
+
unlink(path: string): Promise<void>;
|
|
59
|
+
|
|
60
|
+
copyFile(src: string, dest: string): Promise<void>;
|
|
61
|
+
|
|
62
|
+
// Directory operations
|
|
63
|
+
readdir(path: string): Promise<string[]>;
|
|
64
|
+
|
|
65
|
+
mkdir(path: string, options?: MkdirOptions): Promise<void>;
|
|
66
|
+
|
|
67
|
+
rmdir(path: string): Promise<void>;
|
|
68
|
+
|
|
69
|
+
// Stream operations
|
|
70
|
+
createReadStream(path: string): Promise<Readable>;
|
|
71
|
+
|
|
72
|
+
createWriteStream(path: string): Promise<Writable>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class ErrorHandler {
|
|
76
|
+
static convertMinioError(error: Error, path?: string): FileSystemError;
|
|
77
|
+
static createFileSystemError(code: string, path?: string, syscall?: string): FileSystemError;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class PathConverter {
|
|
81
|
+
static splitPath(path: string): { bucket: string; key: string };
|
|
82
|
+
static joinPath(bucket: string, key: string): string;
|
|
83
|
+
static normalizePath(path: string): string;
|
|
84
|
+
static isDirectory(path: string): boolean;
|
|
85
|
+
static getParentPath(path: string): string;
|
|
86
|
+
static getBasename(path: string): string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class StreamConverter {
|
|
90
|
+
static bufferToString(buffer: Buffer, encoding?: BufferEncoding): string;
|
|
91
|
+
static stringToBuffer(str: string, encoding?: BufferEncoding): Buffer;
|
|
92
|
+
static streamToBuffer(stream: Readable): Promise<Buffer>;
|
|
93
|
+
static bufferToStream(buffer: Buffer): Readable;
|
|
94
|
+
static createPassThroughStream(): { stream: Writable; promise: Promise<Buffer> };
|
|
95
|
+
static normalizeData(data: any): Buffer;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default FsMinioClient;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// fs-minio - fs-compatible API for MinIO/S3 object storage
|
|
2
|
+
// Main entry point
|
|
3
|
+
|
|
4
|
+
import FsMinioClient from './lib/FsMinioClient.js';
|
|
5
|
+
import PathConverter from './lib/PathConverter.js';
|
|
6
|
+
import StreamConverter from './lib/StreamConverter.js';
|
|
7
|
+
import ErrorHandler from './lib/ErrorHandler.js';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
FsMinioClient,
|
|
11
|
+
PathConverter,
|
|
12
|
+
StreamConverter,
|
|
13
|
+
ErrorHandler
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Default export for convenience
|
|
17
|
+
export default FsMinioClient;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// ErrorHandler.js - MinIO errors to fs-compatible errors conversion
|
|
2
|
+
|
|
3
|
+
class ErrorHandler {
|
|
4
|
+
static errorMapping = {
|
|
5
|
+
// MinIO/S3 specific errors
|
|
6
|
+
'NoSuchKey': { code: 'ENOENT', errno: -2, message: 'no such file or directory' },
|
|
7
|
+
'NoSuchBucket': { code: 'ENOENT', errno: -2, message: 'no such file or directory' },
|
|
8
|
+
'BucketNotFound': { code: 'ENOENT', errno: -2, message: 'no such file or directory' },
|
|
9
|
+
'AccessDenied': { code: 'EACCES', errno: -13, message: 'permission denied' },
|
|
10
|
+
'InvalidBucketName': { code: 'EINVAL', errno: -22, message: 'invalid argument' },
|
|
11
|
+
'BucketAlreadyExists': { code: 'EEXIST', errno: -17, message: 'file already exists' },
|
|
12
|
+
'KeyTooLong': { code: 'ENAMETOOLONG', errno: -36, message: 'file name too long' },
|
|
13
|
+
|
|
14
|
+
// Network/Connection errors
|
|
15
|
+
'ENOTFOUND': { code: 'ENOTFOUND', errno: -3008, message: 'getaddrinfo ENOTFOUND' },
|
|
16
|
+
'ECONNREFUSED': { code: 'ECONNREFUSED', errno: -61, message: 'connect ECONNREFUSED' },
|
|
17
|
+
'ETIMEDOUT': { code: 'ETIMEDOUT', errno: -60, message: 'operation timed out' },
|
|
18
|
+
|
|
19
|
+
// Default fallback
|
|
20
|
+
'Unknown': { code: 'EIO', errno: -5, message: 'input/output error' }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert MinIO error to fs-compatible error
|
|
25
|
+
* @param {Error} minioError - Original MinIO error
|
|
26
|
+
* @param {string} path - File path for context
|
|
27
|
+
* @param {string} operation - Operation that failed (e.g., 'open', 'read', 'write')
|
|
28
|
+
* @returns {Error} fs-compatible error
|
|
29
|
+
*/
|
|
30
|
+
static convertError(minioError, path = null, operation = null) {
|
|
31
|
+
// If it's already an fs-style error, return as-is
|
|
32
|
+
if (minioError.code && minioError.errno) {
|
|
33
|
+
return minioError;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let errorInfo;
|
|
37
|
+
|
|
38
|
+
// Try to identify error by MinIO error code
|
|
39
|
+
if (minioError.code && this.errorMapping[minioError.code]) {
|
|
40
|
+
errorInfo = this.errorMapping[minioError.code];
|
|
41
|
+
} // Try to identify by error message patterns
|
|
42
|
+
else if (minioError.message) {
|
|
43
|
+
if (minioError.message.includes('key does not exist') ||
|
|
44
|
+
minioError.message.includes('NoSuchKey') ||
|
|
45
|
+
minioError.message.includes('Not Found')) {
|
|
46
|
+
errorInfo = this.errorMapping['NoSuchKey'];
|
|
47
|
+
} else if (minioError.message.includes('bucket does not exist') ||
|
|
48
|
+
minioError.message.includes('NoSuchBucket')) {
|
|
49
|
+
errorInfo = this.errorMapping['NoSuchBucket'];
|
|
50
|
+
} else if (minioError.message.includes('access denied') ||
|
|
51
|
+
minioError.message.includes('AccessDenied')) {
|
|
52
|
+
errorInfo = this.errorMapping['AccessDenied'];
|
|
53
|
+
} else if (minioError.message.includes('ENOTFOUND')) {
|
|
54
|
+
errorInfo = this.errorMapping['ENOTFOUND'];
|
|
55
|
+
} else if (minioError.message.includes('ECONNREFUSED')) {
|
|
56
|
+
errorInfo = this.errorMapping['ECONNREFUSED'];
|
|
57
|
+
} else if (minioError.message.includes('timeout') ||
|
|
58
|
+
minioError.message.includes('ETIMEDOUT')) {
|
|
59
|
+
errorInfo = this.errorMapping['ETIMEDOUT'];
|
|
60
|
+
} else {
|
|
61
|
+
errorInfo = this.errorMapping['Unknown'];
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
errorInfo = this.errorMapping['Unknown'];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Create fs-style error
|
|
68
|
+
const error = new Error();
|
|
69
|
+
error.code = errorInfo.code;
|
|
70
|
+
error.errno = errorInfo.errno;
|
|
71
|
+
error.syscall = operation || 'open';
|
|
72
|
+
error.path = path;
|
|
73
|
+
|
|
74
|
+
// Create message in fs style: "ENOENT: no such file or directory, open '/path/to/file'"
|
|
75
|
+
if (path) {
|
|
76
|
+
error.message = `${errorInfo.code}: ${errorInfo.message}, ${error.syscall} '${path}'`;
|
|
77
|
+
} else {
|
|
78
|
+
error.message = `${errorInfo.code}: ${errorInfo.message}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Preserve original error information
|
|
82
|
+
error.originalError = minioError;
|
|
83
|
+
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create custom fs-style error
|
|
89
|
+
* @param {string} code - Error code (e.g., 'ENOENT')
|
|
90
|
+
* @param {string} path - File path
|
|
91
|
+
* @param {string} operation - Operation name
|
|
92
|
+
* @returns {Error} fs-compatible error
|
|
93
|
+
*/
|
|
94
|
+
static createError(code, path = null, operation = 'open') {
|
|
95
|
+
// ENOENT, EACCES, EEXIST, EINVAL, ENAMETOOLONG など標準エラーコードにも対応
|
|
96
|
+
let errorInfo = this.errorMapping[code];
|
|
97
|
+
if (!errorInfo) {
|
|
98
|
+
// 標準的なfsエラーコードをサポート
|
|
99
|
+
switch (code) {
|
|
100
|
+
case 'ENOENT': errorInfo = { code: 'ENOENT', errno: -2, message: 'no such file or directory' }; break;
|
|
101
|
+
case 'EACCES': errorInfo = { code: 'EACCES', errno: -13, message: 'permission denied' }; break;
|
|
102
|
+
case 'EEXIST': errorInfo = { code: 'EEXIST', errno: -17, message: 'file already exists' }; break;
|
|
103
|
+
case 'EINVAL': errorInfo = { code: 'EINVAL', errno: -22, message: 'invalid argument' }; break;
|
|
104
|
+
case 'ENAMETOOLONG': errorInfo = { code: 'ENAMETOOLONG', errno: -36, message: 'file name too long' }; break;
|
|
105
|
+
default: errorInfo = this.errorMapping['Unknown'];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const error = new Error();
|
|
109
|
+
error.code = code;
|
|
110
|
+
error.errno = errorInfo.errno;
|
|
111
|
+
error.syscall = operation;
|
|
112
|
+
error.path = path;
|
|
113
|
+
if (path) {
|
|
114
|
+
error.message = `${code}: ${errorInfo.message}, ${operation} '${path}'`;
|
|
115
|
+
} else {
|
|
116
|
+
error.message = `${code}: ${errorInfo.message}`;
|
|
117
|
+
}
|
|
118
|
+
return error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if error indicates file not found
|
|
123
|
+
* @param {Error} error - Error to check
|
|
124
|
+
* @returns {boolean} True if file not found error
|
|
125
|
+
*/
|
|
126
|
+
static isNotFoundError(error) {
|
|
127
|
+
return error && error.code === 'ENOENT';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if error indicates access denied
|
|
132
|
+
* @param {Error} error - Error to check
|
|
133
|
+
* @returns {boolean} True if access denied error
|
|
134
|
+
*/
|
|
135
|
+
static isAccessDeniedError(error) {
|
|
136
|
+
return error && error.code === 'EACCES';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if error indicates file already exists
|
|
141
|
+
* @param {Error} error - Error to check
|
|
142
|
+
* @returns {boolean} True if file exists error
|
|
143
|
+
*/
|
|
144
|
+
static isExistsError(error) {
|
|
145
|
+
return error && error.code === 'EEXIST';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default ErrorHandler;
|