@wener/common 2.0.3 → 2.0.5
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/lib/ai/qwen3vl/utils.js.map +1 -1
- package/lib/cn/ChineseResidentIdNo.js +1 -1
- package/lib/cn/ChineseResidentIdNo.js.map +1 -1
- package/lib/cn/Mod11.js +1 -1
- package/lib/cn/Mod11.js.map +1 -1
- package/lib/consola/formatLogObject.js +5 -5
- package/lib/consola/formatLogObject.js.map +1 -1
- package/lib/data/maybeNumber.js +1 -1
- package/lib/data/maybeNumber.js.map +1 -1
- package/lib/data/types.d.js.map +1 -1
- package/lib/dayjs/formatDuration.js.map +1 -1
- package/lib/dayjs/resolveRelativeTime.js +9 -80
- package/lib/dayjs/resolveRelativeTime.js.map +1 -1
- package/lib/drain3/Drain.js +356 -0
- package/lib/drain3/Drain.js.map +1 -0
- package/lib/drain3/LogCluster.js +38 -0
- package/lib/drain3/LogCluster.js.map +1 -0
- package/lib/drain3/Node.js +39 -0
- package/lib/drain3/Node.js.map +1 -0
- package/lib/drain3/TemplateMiner.js +204 -0
- package/lib/drain3/TemplateMiner.js.map +1 -0
- package/lib/drain3/index.js +31 -0
- package/lib/drain3/index.js.map +1 -0
- package/lib/drain3/persistence/FilePersistence.js +24 -0
- package/lib/drain3/persistence/FilePersistence.js.map +1 -0
- package/lib/drain3/persistence/MemoryPersistence.js +18 -0
- package/lib/drain3/persistence/MemoryPersistence.js.map +1 -0
- package/lib/drain3/persistence/PersistenceHandler.js +5 -0
- package/lib/drain3/persistence/PersistenceHandler.js.map +1 -0
- package/lib/drain3/types.js +7 -0
- package/lib/drain3/types.js.map +1 -0
- package/lib/fs/IFileSystem.d.js.map +1 -1
- package/lib/fs/createBrowserFileSystem.js +4 -2
- package/lib/fs/createBrowserFileSystem.js.map +1 -1
- package/lib/fs/createMemoryFileSystem.js +7 -6
- package/lib/fs/createMemoryFileSystem.js.map +1 -1
- package/lib/fs/createSandboxFileSystem.js.map +1 -1
- package/lib/fs/createWebDavFileSystem.js +22 -5
- package/lib/fs/createWebDavFileSystem.js.map +1 -1
- package/lib/fs/createWebFileSystem.js +225 -0
- package/lib/fs/createWebFileSystem.js.map +1 -0
- package/lib/fs/findMimeType.js +1 -1
- package/lib/fs/findMimeType.js.map +1 -1
- package/lib/fs/index.js +1 -1
- package/lib/fs/index.js.map +1 -1
- package/lib/fs/minio/createMinioFileSystem.js +974 -0
- package/lib/fs/minio/createMinioFileSystem.js.map +1 -0
- package/lib/fs/minio/index.js +2 -0
- package/lib/fs/minio/index.js.map +1 -0
- package/lib/fs/orpc/createContractClientFileSystem.js +3 -3
- package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -1
- package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -1
- package/lib/fs/s3/createS3MiniFileSystem.js +116 -68
- package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -1
- package/lib/fs/server/createDatabaseFileSystem.js +7 -7
- package/lib/fs/server/createDatabaseFileSystem.js.map +1 -1
- package/lib/fs/server/createNodeFileSystem.js +30 -5
- package/lib/fs/server/createNodeFileSystem.js.map +1 -1
- package/lib/fs/tests/runFileSystemTest.js +27 -26
- package/lib/fs/tests/runFileSystemTest.js.map +1 -1
- package/lib/fs/utils.js.map +1 -1
- package/lib/fs/webdav/index.js +2 -0
- package/lib/fs/webdav/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +5 -5
- package/lib/jsonschema/JsonSchema.js.map +1 -1
- package/lib/jsonschema/forEachJsonSchema.js +1 -1
- package/lib/jsonschema/forEachJsonSchema.js.map +1 -1
- package/lib/jsonschema/types.d.js.map +1 -1
- package/lib/meta/defineMetadata.js.map +1 -1
- package/lib/orpc/createOpenApiContractClient.js.map +1 -1
- package/lib/password/PHC.js +2 -2
- package/lib/password/PHC.js.map +1 -1
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js +1 -1
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js +1 -1
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
- package/lib/password/createScryptPasswordAlgorithm.js +3 -3
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
- package/lib/resource/ListQuery.js.map +1 -1
- package/lib/resource/index.js.map +1 -1
- package/lib/s3/formatS3Url.js +2 -2
- package/lib/s3/formatS3Url.js.map +1 -1
- package/lib/s3/parseS3Url.js +1 -1
- package/lib/s3/parseS3Url.js.map +1 -1
- package/lib/schema/SchemaRegistry.js.map +1 -1
- package/lib/schema/TypeSchema.d.js.map +1 -1
- package/lib/schema/createSchemaData.js +4 -4
- package/lib/schema/createSchemaData.js.map +1 -1
- package/lib/schema/findJsonSchemaByPath.js.map +1 -1
- package/lib/schema/formatZodError.js +42 -44
- package/lib/schema/formatZodError.js.map +1 -1
- package/lib/schema/toJsonSchema.js +4 -4
- package/lib/schema/toJsonSchema.js.map +1 -1
- package/lib/schema/validate.js +1 -1
- package/lib/schema/validate.js.map +1 -1
- package/lib/utils/buildRedactorFormSchema.js +1 -1
- package/lib/utils/buildRedactorFormSchema.js.map +1 -1
- package/package.json +32 -13
- package/src/ai/qwen3vl/utils.ts +1 -1
- package/src/cn/ChineseResidentIdNo.ts +1 -1
- package/src/cn/Mod11.ts +1 -1
- package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +1 -1
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +0 -23
- package/src/cn/parseChineseNumber.test.ts +4 -4
- package/src/consola/formatLogObject.ts +6 -6
- package/src/data/maybeNumber.ts +1 -1
- package/src/data/parseSort.test.ts +0 -1
- package/src/data/types.d.ts +2 -2
- package/src/dayjs/formatDuration.ts +2 -2
- package/src/dayjs/resolveRelativeTime.ts +11 -14
- package/src/drain3/Drain.test.ts +378 -0
- package/src/drain3/Drain.ts +394 -0
- package/src/drain3/LogCluster.ts +46 -0
- package/src/drain3/Node.ts +53 -0
- package/src/drain3/TemplateMiner.ts +246 -0
- package/src/drain3/index.ts +36 -0
- package/src/drain3/persistence/FilePersistence.ts +24 -0
- package/src/drain3/persistence/MemoryPersistence.ts +23 -0
- package/src/drain3/persistence/PersistenceHandler.ts +19 -0
- package/src/drain3/types.ts +75 -0
- package/src/fs/IFileSystem.d.ts +1 -2
- package/src/fs/createBrowserFileSystem.ts +7 -5
- package/src/fs/createMemoryFileSystem.ts +9 -13
- package/src/fs/createSandboxFileSystem.ts +1 -1
- package/src/fs/createWebDavFileSystem.ts +28 -10
- package/src/fs/createWebFileSystem.ts +242 -0
- package/src/fs/findMimeType.ts +1 -4
- package/src/fs/index.ts +1 -1
- package/src/fs/minio/createMinioFileSystem.ts +1148 -0
- package/src/fs/minio/index.ts +1 -0
- package/src/fs/orpc/createContractClientFileSystem.ts +5 -5
- package/src/fs/orpc/server/createFileSystemContractImpl.ts +1 -1
- package/src/fs/s3/createS3MiniFileSystem.ts +119 -78
- package/src/fs/s3/s3fs.test.ts +441 -0
- package/src/fs/s3/s3mini.test.ts +2 -2
- package/src/fs/server/createDatabaseFileSystem.ts +7 -7
- package/src/fs/server/createNodeFileSystem.ts +32 -13
- package/src/fs/server/dbfs.test.ts +2 -1
- package/src/fs/tests/runFileSystemTest.ts +29 -28
- package/src/fs/utils.ts +1 -1
- package/src/fs/webdav/index.ts +1 -0
- package/src/jsonschema/JsonSchema.ts +5 -5
- package/src/jsonschema/forEachJsonSchema.ts +1 -1
- package/src/jsonschema/types.d.ts +1 -1
- package/src/meta/defineMetadata.ts +1 -1
- package/src/orpc/createOpenApiContractClient.ts +2 -2
- package/src/password/PHC.ts +3 -3
- package/src/password/createArgon2PasswordAlgorithm.ts +1 -1
- package/src/password/createBase64PasswordAlgorithm.ts +2 -2
- package/src/password/createBcryptPasswordAlgorithm.ts +4 -2
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/password/createScryptPasswordAlgorithm.ts +4 -4
- package/src/resource/ListQuery.ts +4 -1
- package/src/resource/index.ts +2 -2
- package/src/s3/formatS3Url.test.ts +1 -1
- package/src/s3/formatS3Url.ts +2 -2
- package/src/s3/parseS3Url.ts +1 -1
- package/src/schema/SchemaRegistry.ts +1 -1
- package/src/schema/TypeSchema.d.ts +6 -6
- package/src/schema/createSchemaData.ts +4 -4
- package/src/schema/findJsonSchemaByPath.ts +4 -4
- package/src/schema/formatZodError.test.ts +2 -1
- package/src/schema/formatZodError.ts +50 -62
- package/src/schema/toJsonSchema.ts +6 -6
- package/src/schema/validate.ts +1 -1
- package/src/utils/buildRedactorFormSchema.ts +3 -3
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
+
import { createMinioFileSystem } from '../minio/createMinioFileSystem';
|
|
3
|
+
|
|
4
|
+
const S3_URL = process.env.S3_URL;
|
|
5
|
+
|
|
6
|
+
// Use a test prefix to avoid conflicts with real data
|
|
7
|
+
const TEST_PREFIX = `test-${Date.now()}`;
|
|
8
|
+
|
|
9
|
+
describe.skipIf(!S3_URL)('MinioFileSystem', () => {
|
|
10
|
+
let fs: ReturnType<typeof createMinioFileSystem>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
fs = createMinioFileSystem({
|
|
14
|
+
url: S3_URL!,
|
|
15
|
+
prefix: TEST_PREFIX,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Cleanup: remove all test files after all tests
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
if (S3_URL && fs) {
|
|
22
|
+
try {
|
|
23
|
+
await fs.rm('/', { recursive: true, force: true });
|
|
24
|
+
} catch {
|
|
25
|
+
// Ignore cleanup errors
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('readdir', () => {
|
|
31
|
+
test('should list root directory', async () => {
|
|
32
|
+
const entries = await fs.readdir('/');
|
|
33
|
+
expect(Array.isArray(entries)).toBe(true);
|
|
34
|
+
entries.forEach((entry) => {
|
|
35
|
+
expect(entry).toHaveProperty('path');
|
|
36
|
+
expect(entry).toHaveProperty('name');
|
|
37
|
+
expect(entry).toHaveProperty('kind');
|
|
38
|
+
expect(entry).toHaveProperty('size');
|
|
39
|
+
expect(entry).toHaveProperty('mtime');
|
|
40
|
+
expect(['file', 'directory']).toContain(entry.kind);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should list directory with recursive option', async () => {
|
|
45
|
+
// Create test structure
|
|
46
|
+
await fs.writeFile('/test-file.txt', 'test');
|
|
47
|
+
await fs.mkdir('/test-dir');
|
|
48
|
+
await fs.writeFile('/test-dir/nested.txt', 'nested');
|
|
49
|
+
|
|
50
|
+
const entries = await fs.readdir('/', { recursive: true });
|
|
51
|
+
const fileNames = entries.map((e) => e.name);
|
|
52
|
+
const paths = entries.map((e) => e.path);
|
|
53
|
+
expect(fileNames).toContain('test-file.txt');
|
|
54
|
+
expect(fileNames).toContain('test-dir');
|
|
55
|
+
// In recursive mode, nested files should appear with their relative path
|
|
56
|
+
expect(paths.some((p) => p.includes('nested.txt'))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should filter by kind', async () => {
|
|
60
|
+
await fs.writeFile('/filter-file.txt', 'test');
|
|
61
|
+
await fs.mkdir('/filter-dir');
|
|
62
|
+
|
|
63
|
+
const files = await fs.readdir('/', { kind: 'file' });
|
|
64
|
+
expect(files.every((f) => f.kind === 'file')).toBe(true);
|
|
65
|
+
|
|
66
|
+
const dirs = await fs.readdir('/', { kind: 'directory' });
|
|
67
|
+
expect(dirs.every((d) => d.kind === 'directory')).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should filter hidden files', async () => {
|
|
71
|
+
await fs.writeFile('/.hidden', 'hidden');
|
|
72
|
+
await fs.writeFile('/visible.txt', 'visible');
|
|
73
|
+
|
|
74
|
+
const entries = await fs.readdir('/', { hidden: false });
|
|
75
|
+
const names = entries.map((e) => e.name);
|
|
76
|
+
expect(names).not.toContain('.hidden');
|
|
77
|
+
expect(names).toContain('visible.txt');
|
|
78
|
+
|
|
79
|
+
const allEntries = await fs.readdir('/', { hidden: true });
|
|
80
|
+
const allNames = allEntries.map((e) => e.name);
|
|
81
|
+
expect(allNames).toContain('.hidden');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should support depth option', async () => {
|
|
85
|
+
await fs.mkdir('/depth1', { recursive: true });
|
|
86
|
+
await fs.mkdir('/depth1/depth2', { recursive: true });
|
|
87
|
+
await fs.writeFile('/depth1/depth2/file.txt', 'test');
|
|
88
|
+
|
|
89
|
+
const depth1 = await fs.readdir('/', { depth: 1 });
|
|
90
|
+
expect(depth1.some((e) => e.name === 'depth1')).toBe(true);
|
|
91
|
+
expect(depth1.some((e) => e.name === 'file.txt')).toBe(false);
|
|
92
|
+
|
|
93
|
+
const depth2 = await fs.readdir('/', { depth: 2 });
|
|
94
|
+
expect(depth2.some((e) => e.name === 'file.txt')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('stat', () => {
|
|
99
|
+
test('should stat root directory', async () => {
|
|
100
|
+
const stat = await fs.stat('/');
|
|
101
|
+
expect(stat.kind).toBe('directory');
|
|
102
|
+
expect(stat.path).toBe('/');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should stat a file', async () => {
|
|
106
|
+
await fs.writeFile('/stat-test.txt', 'test content');
|
|
107
|
+
const stat = await fs.stat('/stat-test.txt');
|
|
108
|
+
expect(stat.kind).toBe('file');
|
|
109
|
+
expect(stat.size).toBe(12); // "test content" is 12 bytes
|
|
110
|
+
expect(stat.name).toBe('stat-test.txt');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('should stat a directory', async () => {
|
|
114
|
+
await fs.mkdir('/stat-dir');
|
|
115
|
+
const stat = await fs.stat('/stat-dir');
|
|
116
|
+
expect(stat.kind).toBe('directory');
|
|
117
|
+
expect(stat.name).toBe('stat-dir');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should throw error for non-existent file', async () => {
|
|
121
|
+
await expect(fs.stat('/nonexistent.txt')).rejects.toThrow('File not found');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('readFile and writeFile', () => {
|
|
126
|
+
test('should write and read text file', async () => {
|
|
127
|
+
const content = 'Hello, World!';
|
|
128
|
+
await fs.writeFile('/hello.txt', content);
|
|
129
|
+
const read = await fs.readFile('/hello.txt', { encoding: 'text' });
|
|
130
|
+
expect(read).toBe(content);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('should write and read binary file', async () => {
|
|
134
|
+
const content = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
|
|
135
|
+
await fs.writeFile('/binary.bin', content);
|
|
136
|
+
const read = await fs.readFile('/binary.bin');
|
|
137
|
+
expect(Buffer.from(read)).toEqual(content);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should overwrite file by default', async () => {
|
|
141
|
+
await fs.writeFile('/overwrite.txt', 'original');
|
|
142
|
+
await fs.writeFile('/overwrite.txt', 'updated');
|
|
143
|
+
const content = await fs.readFile('/overwrite.txt', { encoding: 'text' });
|
|
144
|
+
expect(content).toBe('updated');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should prevent overwrite when overwrite=false', async () => {
|
|
148
|
+
await fs.writeFile('/no-overwrite.txt', 'original');
|
|
149
|
+
await expect(fs.writeFile('/no-overwrite.txt', 'new', { overwrite: false })).rejects.toThrow(
|
|
150
|
+
'File already exists',
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should handle large files', async () => {
|
|
155
|
+
const largeContent = 'x'.repeat(10000);
|
|
156
|
+
await fs.writeFile('/large.txt', largeContent);
|
|
157
|
+
const read = await fs.readFile('/large.txt', { encoding: 'text' });
|
|
158
|
+
expect(read).toBe(largeContent);
|
|
159
|
+
expect(read.length).toBe(10000);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should throw error when reading non-existent file', async () => {
|
|
163
|
+
await expect(fs.readFile('/nonexistent.txt')).rejects.toThrow('File not found');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('mkdir', () => {
|
|
168
|
+
test('should create directory', async () => {
|
|
169
|
+
await fs.mkdir('/new-dir');
|
|
170
|
+
expect(await fs.exists('/new-dir')).toBe(true);
|
|
171
|
+
const stat = await fs.stat('/new-dir');
|
|
172
|
+
expect(stat.kind).toBe('directory');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should create nested directories recursively', async () => {
|
|
176
|
+
await fs.mkdir('/nested/deep/dir', { recursive: true });
|
|
177
|
+
expect(await fs.exists('/nested/deep/dir')).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('should handle creating root directory', async () => {
|
|
181
|
+
await expect(fs.mkdir('/')).resolves.not.toThrow();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('rm', () => {
|
|
186
|
+
test('should remove a file', async () => {
|
|
187
|
+
await fs.writeFile('/to-remove.txt', 'content');
|
|
188
|
+
expect(await fs.exists('/to-remove.txt')).toBe(true);
|
|
189
|
+
|
|
190
|
+
await fs.rm('/to-remove.txt');
|
|
191
|
+
expect(await fs.exists('/to-remove.txt')).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should remove directory recursively', async () => {
|
|
195
|
+
await fs.mkdir('/rm-dir', { recursive: true });
|
|
196
|
+
await fs.writeFile('/rm-dir/file1.txt', 'file1');
|
|
197
|
+
await fs.writeFile('/rm-dir/file2.txt', 'file2');
|
|
198
|
+
await fs.mkdir('/rm-dir/subdir', { recursive: true });
|
|
199
|
+
await fs.writeFile('/rm-dir/subdir/file3.txt', 'file3');
|
|
200
|
+
|
|
201
|
+
await fs.rm('/rm-dir', { recursive: true });
|
|
202
|
+
expect(await fs.exists('/rm-dir')).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('should handle force option', async () => {
|
|
206
|
+
await expect(fs.rm('/nonexistent.txt')).rejects.toThrow();
|
|
207
|
+
await expect(fs.rm('/nonexistent.txt', { force: true })).resolves.not.toThrow();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('rename', () => {
|
|
212
|
+
test('should rename a file', async () => {
|
|
213
|
+
await fs.writeFile('/old-name.txt', 'content');
|
|
214
|
+
await fs.rename('/old-name.txt', '/new-name.txt');
|
|
215
|
+
|
|
216
|
+
expect(await fs.exists('/old-name.txt')).toBe(false);
|
|
217
|
+
expect(await fs.exists('/new-name.txt')).toBe(true);
|
|
218
|
+
const content = await fs.readFile('/new-name.txt', { encoding: 'text' });
|
|
219
|
+
expect(content).toBe('content');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should rename a directory', async () => {
|
|
223
|
+
await fs.mkdir('/old-dir', { recursive: true });
|
|
224
|
+
await fs.writeFile('/old-dir/file.txt', 'content');
|
|
225
|
+
|
|
226
|
+
// Clean up any existing new-dir first
|
|
227
|
+
try {
|
|
228
|
+
await fs.rm('/new-dir', { recursive: true, force: true });
|
|
229
|
+
} catch {
|
|
230
|
+
// Ignore if doesn't exist
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await fs.rename('/old-dir', '/new-dir');
|
|
234
|
+
|
|
235
|
+
expect(await fs.exists('/old-dir')).toBe(false);
|
|
236
|
+
expect(await fs.exists('/new-dir')).toBe(true);
|
|
237
|
+
expect(await fs.exists('/new-dir/file.txt')).toBe(true);
|
|
238
|
+
const content = await fs.readFile('/new-dir/file.txt', { encoding: 'text' });
|
|
239
|
+
expect(content).toBe('content');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('should prevent overwrite when overwrite=false', async () => {
|
|
243
|
+
await fs.writeFile('/source.txt', 'source');
|
|
244
|
+
await fs.writeFile('/target.txt', 'target');
|
|
245
|
+
|
|
246
|
+
await expect(fs.rename('/source.txt', '/target.txt', { overwrite: false })).rejects.toThrow(
|
|
247
|
+
'Destination already exists',
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('should allow overwrite when overwrite=true', async () => {
|
|
252
|
+
await fs.writeFile('/source.txt', 'source');
|
|
253
|
+
await fs.writeFile('/target.txt', 'target');
|
|
254
|
+
|
|
255
|
+
await fs.rename('/source.txt', '/target.txt', { overwrite: true });
|
|
256
|
+
expect(await fs.exists('/source.txt')).toBe(false);
|
|
257
|
+
const content = await fs.readFile('/target.txt', { encoding: 'text' });
|
|
258
|
+
expect(content).toBe('source');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('copy', () => {
|
|
263
|
+
test('should copy a file', async () => {
|
|
264
|
+
await fs.writeFile('/source.txt', 'source content');
|
|
265
|
+
await fs.copy('/source.txt', '/dest.txt');
|
|
266
|
+
|
|
267
|
+
expect(await fs.exists('/source.txt')).toBe(true);
|
|
268
|
+
expect(await fs.exists('/dest.txt')).toBe(true);
|
|
269
|
+
const source = await fs.readFile('/source.txt', { encoding: 'text' });
|
|
270
|
+
const dest = await fs.readFile('/dest.txt', { encoding: 'text' });
|
|
271
|
+
expect(source).toBe(dest);
|
|
272
|
+
expect(dest).toBe('source content');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should copy a directory recursively', async () => {
|
|
276
|
+
await fs.mkdir('/copy-source', { recursive: true });
|
|
277
|
+
await fs.writeFile('/copy-source/file1.txt', 'file1');
|
|
278
|
+
await fs.writeFile('/copy-source/file2.txt', 'file2');
|
|
279
|
+
await fs.mkdir('/copy-source/subdir', { recursive: true });
|
|
280
|
+
await fs.writeFile('/copy-source/subdir/file3.txt', 'file3');
|
|
281
|
+
|
|
282
|
+
await fs.copy('/copy-source', '/copy-dest');
|
|
283
|
+
|
|
284
|
+
expect(await fs.exists('/copy-dest/file1.txt')).toBe(true);
|
|
285
|
+
expect(await fs.exists('/copy-dest/file2.txt')).toBe(true);
|
|
286
|
+
expect(await fs.exists('/copy-dest/subdir/file3.txt')).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('should prevent overwrite when overwrite=false', async () => {
|
|
290
|
+
await fs.writeFile('/copy-src.txt', 'source');
|
|
291
|
+
await fs.writeFile('/copy-dst.txt', 'target');
|
|
292
|
+
|
|
293
|
+
await expect(fs.copy('/copy-src.txt', '/copy-dst.txt', { overwrite: false })).rejects.toThrow(
|
|
294
|
+
'Destination already exists',
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('should allow overwrite when overwrite=true', async () => {
|
|
299
|
+
await fs.writeFile('/copy-src.txt', 'source');
|
|
300
|
+
await fs.writeFile('/copy-dst.txt', 'target');
|
|
301
|
+
|
|
302
|
+
await fs.copy('/copy-src.txt', '/copy-dst.txt', { overwrite: true });
|
|
303
|
+
const content = await fs.readFile('/copy-dst.txt', { encoding: 'text' });
|
|
304
|
+
expect(content).toBe('source');
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('exists', () => {
|
|
309
|
+
test('should return true for existing file', async () => {
|
|
310
|
+
await fs.writeFile('/exists-file.txt', 'test');
|
|
311
|
+
expect(await fs.exists('/exists-file.txt')).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('should return true for existing directory', async () => {
|
|
315
|
+
await fs.mkdir('/exists-dir');
|
|
316
|
+
expect(await fs.exists('/exists-dir')).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('should return true for root directory', async () => {
|
|
320
|
+
expect(await fs.exists('/')).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('should return false for non-existent path', async () => {
|
|
324
|
+
expect(await fs.exists('/nonexistent')).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('streams', () => {
|
|
329
|
+
test('should support createReadStream', async () => {
|
|
330
|
+
await fs.writeFile('/stream-read.txt', 'Stream content');
|
|
331
|
+
const stream = fs.createReadStream('/stream-read.txt');
|
|
332
|
+
expect(stream).toBeDefined();
|
|
333
|
+
|
|
334
|
+
const chunks: Buffer[] = [];
|
|
335
|
+
// Use stream events instead of async iteration for compatibility
|
|
336
|
+
await new Promise<void>((resolve, reject) => {
|
|
337
|
+
stream.on('data', (chunk) => {
|
|
338
|
+
chunks.push(chunk);
|
|
339
|
+
});
|
|
340
|
+
stream.on('end', () => {
|
|
341
|
+
resolve();
|
|
342
|
+
});
|
|
343
|
+
stream.on('error', reject);
|
|
344
|
+
});
|
|
345
|
+
expect(Buffer.concat(chunks).toString()).toBe('Stream content');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('should support createReadStream with range', async () => {
|
|
349
|
+
await fs.writeFile('/stream-range.txt', 'Hello, World!');
|
|
350
|
+
const stream = fs.createReadStream('/stream-range.txt', { range: { start: 0, end: 4 } });
|
|
351
|
+
|
|
352
|
+
const chunks: Buffer[] = [];
|
|
353
|
+
// Use stream events instead of async iteration for compatibility
|
|
354
|
+
await new Promise<void>((resolve, reject) => {
|
|
355
|
+
stream.on('data', (chunk) => {
|
|
356
|
+
chunks.push(chunk);
|
|
357
|
+
});
|
|
358
|
+
stream.on('end', () => {
|
|
359
|
+
resolve();
|
|
360
|
+
});
|
|
361
|
+
stream.on('error', reject);
|
|
362
|
+
});
|
|
363
|
+
expect(Buffer.concat(chunks).toString()).toBe('Hello');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('should support createReadableStream', async () => {
|
|
367
|
+
await fs.writeFile('/readable-stream.txt', 'Readable stream content');
|
|
368
|
+
const stream = fs.createReadableStream('/readable-stream.txt');
|
|
369
|
+
|
|
370
|
+
const reader = stream.getReader();
|
|
371
|
+
const chunks: Uint8Array[] = [];
|
|
372
|
+
while (true) {
|
|
373
|
+
const { done, value } = await reader.read();
|
|
374
|
+
if (done) break;
|
|
375
|
+
if (value) chunks.push(value);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const content = Buffer.concat(chunks.map((c) => Buffer.from(c))).toString();
|
|
379
|
+
expect(content).toBe('Readable stream content');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('should support createWritableStream', async () => {
|
|
383
|
+
const stream = fs.createWritableStream('/writable-stream.txt');
|
|
384
|
+
const writer = stream.getWriter();
|
|
385
|
+
|
|
386
|
+
await writer.write(new TextEncoder().encode('Part 1'));
|
|
387
|
+
await writer.write(new TextEncoder().encode('Part 2'));
|
|
388
|
+
await writer.close();
|
|
389
|
+
|
|
390
|
+
const content = await fs.readFile('/writable-stream.txt', { encoding: 'text' });
|
|
391
|
+
expect(content).toBe('Part 1Part 2');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe('edge cases', () => {
|
|
396
|
+
test('should handle empty file', async () => {
|
|
397
|
+
await fs.writeFile('/empty.txt', '');
|
|
398
|
+
const stat = await fs.stat('/empty.txt');
|
|
399
|
+
expect(stat.size).toBe(0);
|
|
400
|
+
const content = await fs.readFile('/empty.txt', { encoding: 'text' });
|
|
401
|
+
expect(content).toBe('');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('should handle special characters in filename', async () => {
|
|
405
|
+
await fs.writeFile('/file with spaces.txt', 'content');
|
|
406
|
+
expect(await fs.exists('/file with spaces.txt')).toBe(true);
|
|
407
|
+
const content = await fs.readFile('/file with spaces.txt', { encoding: 'text' });
|
|
408
|
+
expect(content).toBe('content');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('should handle unicode content', async () => {
|
|
412
|
+
const unicode = '你好世界 🌍';
|
|
413
|
+
await fs.writeFile('/unicode.txt', unicode);
|
|
414
|
+
const content = await fs.readFile('/unicode.txt', { encoding: 'text' });
|
|
415
|
+
expect(content).toBe(unicode);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('should handle path normalization', async () => {
|
|
419
|
+
await fs.writeFile('/normalize.txt', 'test');
|
|
420
|
+
expect(await fs.exists('/normalize.txt')).toBe(true);
|
|
421
|
+
expect(await fs.exists('/./normalize.txt')).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe('prefix support', () => {
|
|
426
|
+
test('should scope operations to prefix', async () => {
|
|
427
|
+
const prefixedFs = createMinioFileSystem({
|
|
428
|
+
url: S3_URL!,
|
|
429
|
+
prefix: `${TEST_PREFIX}/prefixed`,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await prefixedFs.writeFile('/test.txt', 'prefixed content');
|
|
433
|
+
expect(await prefixedFs.exists('/test.txt')).toBe(true);
|
|
434
|
+
const content = await prefixedFs.readFile('/test.txt', { encoding: 'text' });
|
|
435
|
+
expect(content).toBe('prefixed content');
|
|
436
|
+
|
|
437
|
+
// Cleanup
|
|
438
|
+
await prefixedFs.rm('/', { recursive: true, force: true });
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|
package/src/fs/s3/s3mini.test.ts
CHANGED
|
@@ -249,8 +249,8 @@ describe('s3mini listObjects behavior', () => {
|
|
|
249
249
|
const normalizedPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
250
250
|
|
|
251
251
|
const stripPrefix = (key: string): string => {
|
|
252
|
-
if (!normalizedPrefix || !key.startsWith(normalizedPrefix
|
|
253
|
-
return key.startsWith('/') ? key :
|
|
252
|
+
if (!normalizedPrefix || !key.startsWith(`${normalizedPrefix}/`)) {
|
|
253
|
+
return key.startsWith('/') ? key : `/${key}`;
|
|
254
254
|
}
|
|
255
255
|
const withoutPrefix = key.slice(normalizedPrefix.length);
|
|
256
256
|
return withoutPrefix || '/';
|
|
@@ -103,7 +103,7 @@ class DBFS implements IFileSystem {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
async stat(path: string,
|
|
106
|
+
async stat(path: string, _options?: StatOptions): Promise<IFileStat> {
|
|
107
107
|
// Validate input
|
|
108
108
|
if (!path || typeof path !== 'string') {
|
|
109
109
|
throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
|
|
@@ -121,7 +121,7 @@ class DBFS implements IFileSystem {
|
|
|
121
121
|
return !!(await this._getNodeByPath(path, this.em));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
async readdir(dir: string,
|
|
124
|
+
async readdir(dir: string, _options?: ReaddirOptions): Promise<IFileStat[]> {
|
|
125
125
|
const em = this.em;
|
|
126
126
|
const parentNode = await this._getNodeByPath(dir, em);
|
|
127
127
|
|
|
@@ -445,19 +445,19 @@ class DBFS implements IFileSystem {
|
|
|
445
445
|
});
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
createReadStream(
|
|
448
|
+
createReadStream(_path: string, _options?: CreateReadStreamOptions): never {
|
|
449
449
|
throw new Error('Streaming read is not supported by DBFS yet.');
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
createWriteStream(
|
|
452
|
+
createWriteStream(_path: string, _options?: CreateWriteStreamOptions): never {
|
|
453
453
|
throw new Error('Streaming write is not supported by DBFS yet.');
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
createReadableStream(
|
|
456
|
+
createReadableStream(_path: string, _options?: CreateReadStreamOptions): ReadableStream {
|
|
457
457
|
throw new Error('ReadableStream is not supported by DBFS yet.');
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
createWritableStream(
|
|
460
|
+
createWritableStream(_path: string, _options?: CreateWriteStreamOptions): WritableStream {
|
|
461
461
|
throw new Error('WritableStream is not supported by DBFS yet.');
|
|
462
462
|
}
|
|
463
463
|
|
|
@@ -560,7 +560,7 @@ class DBFS implements IFileSystem {
|
|
|
560
560
|
srcFileContentQb.where({ node: srcNode });
|
|
561
561
|
const srcFileContent = await srcFileContentQb.getSingleResult();
|
|
562
562
|
if (srcFileContent) {
|
|
563
|
-
const
|
|
563
|
+
const _newContent = em.create(FileNodeContentEntity, {
|
|
564
564
|
node: newNode, // Use node relationship as primary key
|
|
565
565
|
tid: srcNode.tid,
|
|
566
566
|
content: srcFileContent.content,
|
|
@@ -12,17 +12,18 @@ import type {
|
|
|
12
12
|
CreateReadStreamOptions,
|
|
13
13
|
CreateWriteStreamOptions,
|
|
14
14
|
IFileStat,
|
|
15
|
-
|
|
15
|
+
IServerFileSystem,
|
|
16
16
|
MkdirOptions,
|
|
17
17
|
ReaddirOptions,
|
|
18
18
|
ReadFileOptions,
|
|
19
19
|
RenameOptions,
|
|
20
20
|
RmOptions,
|
|
21
21
|
StatOptions,
|
|
22
|
+
WritableData,
|
|
22
23
|
WriteFileOptions,
|
|
23
24
|
} from '../IFileSystem';
|
|
24
25
|
|
|
25
|
-
export type INodeFileSystem =
|
|
26
|
+
export type INodeFileSystem = IServerFileSystem & {
|
|
26
27
|
readonly root: string;
|
|
27
28
|
resolvePath(filePath: string): string;
|
|
28
29
|
};
|
|
@@ -38,7 +39,7 @@ export function createNodeFileSystem(options: { root?: string } = {}): INodeFile
|
|
|
38
39
|
|
|
39
40
|
type IFS = typeof import('fs/promises');
|
|
40
41
|
|
|
41
|
-
class NodeFs implements
|
|
42
|
+
class NodeFs implements IServerFileSystem, INodeFileSystem {
|
|
42
43
|
readonly root: string;
|
|
43
44
|
private readonly fs: IFS;
|
|
44
45
|
|
|
@@ -113,7 +114,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
if (!relativePath.startsWith('/') && !relativePath.startsWith('\\')) {
|
|
116
|
-
relativePath =
|
|
117
|
+
relativePath = `/${relativePath}`;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// Normalize to ensure consistent path separators
|
|
@@ -183,7 +184,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
|
|
|
183
184
|
const maxDepth = recursive ? Infinity : depth - 1;
|
|
184
185
|
if (maxDepth > 0) {
|
|
185
186
|
// Need to convert the path back to a full path for the recursive call
|
|
186
|
-
const
|
|
187
|
+
const _subdirFullPath = this.resolvePath(subdir.path);
|
|
187
188
|
|
|
188
189
|
const subEntries = await this.readdir(subdir.path, {
|
|
189
190
|
...options,
|
|
@@ -295,11 +296,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
|
|
|
295
296
|
}
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
async writeFile(
|
|
299
|
-
path: string,
|
|
300
|
-
data: string | Buffer | ArrayBuffer | Readable,
|
|
301
|
-
options: WriteFileOptions = {},
|
|
302
|
-
): Promise<void> {
|
|
299
|
+
async writeFile(path: string, data: WritableData, options: WriteFileOptions = {}): Promise<void> {
|
|
303
300
|
const { fs } = this;
|
|
304
301
|
|
|
305
302
|
const { signal, overwrite = true, onUploadProgress } = options;
|
|
@@ -354,12 +351,34 @@ class NodeFs implements IFileSystem, INodeFileSystem {
|
|
|
354
351
|
});
|
|
355
352
|
}
|
|
356
353
|
});
|
|
354
|
+
} else if (data instanceof ReadableStream) {
|
|
355
|
+
// Handle web ReadableStream
|
|
356
|
+
const reader = data.getReader();
|
|
357
|
+
const chunks: Uint8Array[] = [];
|
|
358
|
+
let loaded = 0;
|
|
359
|
+
while (true) {
|
|
360
|
+
const { done, value } = await reader.read();
|
|
361
|
+
if (done) break;
|
|
362
|
+
if (value) {
|
|
363
|
+
chunks.push(value);
|
|
364
|
+
loaded += value.length;
|
|
365
|
+
if (onUploadProgress) {
|
|
366
|
+
onUploadProgress({ loaded, total: -1 });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
await fs.writeFile(resolvedPath, Buffer.concat(chunks));
|
|
357
371
|
} else {
|
|
358
|
-
// Convert ArrayBuffer to Buffer if necessary
|
|
372
|
+
// Convert ArrayBuffer/ArrayBufferView to Buffer if necessary
|
|
373
|
+
let writeData: string | Buffer;
|
|
359
374
|
if (data instanceof ArrayBuffer) {
|
|
360
|
-
|
|
375
|
+
writeData = Buffer.from(data);
|
|
376
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
377
|
+
writeData = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
378
|
+
} else {
|
|
379
|
+
writeData = data;
|
|
361
380
|
}
|
|
362
|
-
await fs.writeFile(resolvedPath,
|
|
381
|
+
await fs.writeFile(resolvedPath, writeData);
|
|
363
382
|
}
|
|
364
383
|
}
|
|
365
384
|
|
|
@@ -4,7 +4,8 @@ import { runFileSystemTest } from '../tests/runFileSystemTest';
|
|
|
4
4
|
import { createDatabaseFileSystem, FileNodeMetaEntity } from './createDatabaseFileSystem';
|
|
5
5
|
import { loadTestDatabase } from './loadTestDatabase';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Skip: createDatabaseFileSystem imports from @wener/server which is not a dependency of wener-common
|
|
8
|
+
describe.skip('DatabaseFileSystem', () => {
|
|
8
9
|
let fs: ReturnType<typeof createDatabaseFileSystem>;
|
|
9
10
|
let em: EntityManager;
|
|
10
11
|
|