@wener/common 2.0.2 → 2.0.3
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/index.js +2 -0
- package/lib/ai/qwen3vl/index.js.map +1 -0
- package/lib/ai/qwen3vl/utils.js +31 -0
- package/lib/ai/qwen3vl/utils.js.map +1 -0
- package/lib/ai/vision/DocLayoutElementTypeSchema.js +28 -0
- package/lib/ai/vision/DocLayoutElementTypeSchema.js.map +1 -0
- package/lib/ai/vision/ImageAnnotationSchema.js +50 -0
- package/lib/ai/vision/ImageAnnotationSchema.js.map +1 -0
- package/lib/ai/vision/index.js +3 -0
- package/lib/ai/vision/index.js.map +1 -0
- package/lib/ai/vision/resolveImageAnnotation.js +105 -0
- package/lib/ai/vision/resolveImageAnnotation.js.map +1 -0
- package/lib/cn/ChineseResidentIdNo.js +21 -14
- package/lib/cn/ChineseResidentIdNo.js.map +1 -0
- package/lib/cn/ChineseResidentIdNo.test.js +1 -1
- package/lib/cn/DivisionCode.js +30 -25
- package/lib/cn/DivisionCode.js.map +1 -0
- package/lib/cn/DivisionCode.test.js +1 -1
- package/lib/cn/Mod11.js +38 -81
- package/lib/cn/Mod11.js.map +1 -0
- package/lib/cn/Mod31.js +41 -90
- package/lib/cn/Mod31.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.js +43 -34
- package/lib/cn/UnifiedSocialCreditCode.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.test.js +1 -1
- package/lib/cn/formatChineseAmount.js +77 -0
- package/lib/cn/formatChineseAmount.js.map +1 -0
- package/lib/cn/index.js +7 -1
- package/lib/cn/index.js.map +1 -0
- package/lib/cn/parseChineseNumber.js +94 -0
- package/lib/cn/parseChineseNumber.js.map +1 -0
- package/lib/cn/parseChineseNumber.test.js +278 -0
- package/lib/cn/pinyin/cartesianProduct.js +22 -0
- package/lib/cn/pinyin/cartesianProduct.js.map +1 -0
- package/lib/cn/pinyin/cartesianProduct.test.js +179 -0
- package/lib/cn/pinyin/data.json +23573 -0
- package/lib/cn/pinyin/loader.js +14 -0
- package/lib/cn/pinyin/loader.js.map +1 -0
- package/lib/cn/pinyin/preload.js +3 -0
- package/lib/cn/pinyin/preload.js.map +1 -0
- package/lib/cn/pinyin/toPinyin.test.js +167 -0
- package/lib/cn/pinyin/toPinyinPure.js +33 -0
- package/lib/cn/pinyin/toPinyinPure.js.map +1 -0
- package/lib/cn/pinyin/transform.js +14 -0
- package/lib/cn/pinyin/transform.js.map +1 -0
- package/lib/cn/types.d.js +2 -0
- package/lib/cn/types.d.js.map +1 -0
- package/lib/consola/createStandardConsolaReporter.js +6 -6
- package/lib/consola/createStandardConsolaReporter.js.map +1 -0
- package/lib/consola/formatLogObject.js +65 -145
- package/lib/consola/formatLogObject.js.map +1 -0
- package/lib/consola/formatLogObject.test.js +184 -0
- package/lib/consola/index.js +1 -0
- package/lib/consola/index.js.map +1 -0
- package/lib/data/formatSort.js +6 -5
- package/lib/data/formatSort.js.map +1 -0
- package/lib/data/index.js +1 -0
- package/lib/data/index.js.map +1 -0
- package/lib/data/maybeNumber.js +5 -7
- package/lib/data/maybeNumber.js.map +1 -0
- package/lib/data/parseSort.js +22 -28
- package/lib/data/parseSort.js.map +1 -0
- package/lib/data/resolvePagination.js +13 -17
- package/lib/data/resolvePagination.js.map +1 -0
- package/lib/data/types.d.js +2 -0
- package/lib/data/types.d.js.map +1 -0
- package/lib/dayjs/dayjs.js +21 -19
- package/lib/dayjs/dayjs.js.map +1 -0
- package/lib/dayjs/formatDuration.js +15 -14
- package/lib/dayjs/formatDuration.js.map +1 -0
- package/lib/dayjs/index.js +2 -0
- package/lib/dayjs/index.js.map +1 -0
- package/lib/dayjs/parseDuration.js +5 -8
- package/lib/dayjs/parseDuration.js.map +1 -0
- package/lib/dayjs/parseRelativeTime.js +90 -0
- package/lib/dayjs/parseRelativeTime.js.map +1 -0
- package/lib/dayjs/parseRelativeTime.test.js +247 -0
- package/lib/dayjs/resolveRelativeTime.js +158 -0
- package/lib/dayjs/resolveRelativeTime.js.map +1 -0
- package/lib/dayjs/resolveRelativeTime.test.js +310 -0
- package/lib/decimal/index.js +1 -0
- package/lib/decimal/index.js.map +1 -0
- package/lib/decimal/parseDecimal.js +3 -1
- package/lib/decimal/parseDecimal.js.map +1 -0
- package/lib/emittery/emitter.js +10 -0
- package/lib/emittery/emitter.js.map +1 -0
- package/lib/emittery/index.js +2 -0
- package/lib/emittery/index.js.map +1 -0
- package/lib/foundation/schema/SexType.js +5 -3
- package/lib/foundation/schema/SexType.js.map +1 -0
- package/lib/foundation/schema/index.js +1 -0
- package/lib/foundation/schema/index.js.map +1 -0
- package/lib/foundation/schema/parseSexType.js +1 -0
- package/lib/foundation/schema/parseSexType.js.map +1 -0
- package/lib/foundation/schema/types.js +4 -2
- package/lib/foundation/schema/types.js.map +1 -0
- package/lib/fs/FileSystemError.js +23 -0
- package/lib/fs/FileSystemError.js.map +1 -0
- package/lib/fs/IFileSystem.d.js +3 -0
- package/lib/fs/IFileSystem.d.js.map +1 -0
- package/lib/fs/MemoryFileSystem.test.js +188 -0
- package/lib/fs/createBrowserFileSystem.js +248 -0
- package/lib/fs/createBrowserFileSystem.js.map +1 -0
- package/lib/fs/createMemoryFileSystem.js +516 -0
- package/lib/fs/createMemoryFileSystem.js.map +1 -0
- package/lib/fs/createSandboxFileSystem.js +108 -0
- package/lib/fs/createSandboxFileSystem.js.map +1 -0
- package/lib/fs/createWebDavFileSystem.js +137 -0
- package/lib/fs/createWebDavFileSystem.js.map +1 -0
- package/lib/fs/findMimeType.js +17 -0
- package/lib/fs/findMimeType.js.map +1 -0
- package/lib/fs/index.js +8 -0
- package/lib/fs/index.js.map +1 -0
- package/lib/fs/orpc/FileSystemContract.js +93 -0
- package/lib/fs/orpc/FileSystemContract.js.map +1 -0
- package/lib/fs/orpc/createContractClientFileSystem.js +93 -0
- package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -0
- package/lib/fs/orpc/index.js +3 -0
- package/lib/fs/orpc/index.js.map +1 -0
- package/lib/fs/orpc/server/createFileSystemContractImpl.js +63 -0
- package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -0
- package/lib/fs/orpc/server/index.js +2 -0
- package/lib/fs/orpc/server/index.js.map +1 -0
- package/lib/fs/s3/createS3MiniFileSystem.js +705 -0
- package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -0
- package/lib/fs/s3/index.js +2 -0
- package/lib/fs/s3/index.js.map +1 -0
- package/lib/fs/s3/s3mini.test.js +584 -0
- package/lib/fs/scandir.js +59 -0
- package/lib/fs/scandir.js.map +1 -0
- package/lib/fs/server/createDatabaseFileSystem.js +750 -0
- package/lib/fs/server/createDatabaseFileSystem.js.map +1 -0
- package/lib/fs/server/createNodeFileSystem.js +401 -0
- package/lib/fs/server/createNodeFileSystem.js.map +1 -0
- package/lib/fs/server/dbfs.test.js +221 -0
- package/lib/fs/server/index.js +2 -0
- package/lib/fs/server/index.js.map +1 -0
- package/lib/fs/server/loadTestDatabase.js +127 -0
- package/lib/fs/server/loadTestDatabase.js.map +1 -0
- package/lib/fs/tests/runFileSystemTest.js +318 -0
- package/lib/fs/tests/runFileSystemTest.js.map +1 -0
- package/lib/fs/types.js +27 -0
- package/lib/fs/types.js.map +1 -0
- package/lib/fs/utils/getFileUrl.js +35 -0
- package/lib/fs/utils/getFileUrl.js.map +1 -0
- package/lib/fs/utils.js +22 -0
- package/lib/fs/utils.js.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +146 -172
- package/lib/jsonschema/JsonSchema.js.map +1 -0
- package/lib/jsonschema/forEachJsonSchema.js +44 -0
- package/lib/jsonschema/forEachJsonSchema.js.map +1 -0
- package/lib/jsonschema/index.js +2 -0
- package/lib/jsonschema/index.js.map +1 -0
- package/lib/jsonschema/types.d.js +2 -0
- package/lib/jsonschema/types.d.js.map +1 -0
- package/lib/meta/defineFileType.js +20 -103
- package/lib/meta/defineFileType.js.map +1 -0
- package/lib/meta/defineInit.js +31 -250
- package/lib/meta/defineInit.js.map +1 -0
- package/lib/meta/defineMetadata.js +24 -140
- package/lib/meta/defineMetadata.js.map +1 -0
- package/lib/meta/index.js +1 -0
- package/lib/meta/index.js.map +1 -0
- package/lib/orpc/createOpenApiContractClient.js +27 -0
- package/lib/orpc/createOpenApiContractClient.js.map +1 -0
- package/lib/orpc/createRpcContractClient.js +34 -0
- package/lib/orpc/createRpcContractClient.js.map +1 -0
- package/lib/orpc/index.js +3 -0
- package/lib/orpc/index.js.map +1 -0
- package/lib/orpc/resolveLinkPlugins.js +28 -0
- package/lib/orpc/resolveLinkPlugins.js.map +1 -0
- package/lib/password/PHC.js +63 -87
- package/lib/password/PHC.js.map +1 -0
- package/lib/password/PHC.test.js +11 -3
- package/lib/password/Password.js +29 -294
- package/lib/password/Password.js.map +1 -0
- package/lib/password/Password.test.js +35 -22
- package/lib/password/createArgon2PasswordAlgorithm.js +35 -191
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBase64PasswordAlgorithm.js +8 -141
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBcryptPasswordAlgorithm.js +13 -168
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/createPBKDF2PasswordAlgorithm.js +46 -228
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createScryptPasswordAlgorithm.js +55 -211
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/index.js +1 -0
- package/lib/password/index.js.map +1 -0
- package/lib/password/server/index.js +1 -0
- package/lib/password/server/index.js.map +1 -0
- package/lib/resource/Identifiable.js +2 -0
- package/lib/resource/Identifiable.js.map +1 -0
- package/lib/resource/ListQuery.js +21 -93
- package/lib/resource/ListQuery.js.map +1 -0
- package/lib/resource/getTitleOfResource.js +3 -5
- package/lib/resource/getTitleOfResource.js.map +1 -0
- package/lib/resource/index.js +1 -0
- package/lib/resource/index.js.map +1 -0
- package/lib/resource/schema/AnyResourceSchema.js +2 -1
- package/lib/resource/schema/AnyResourceSchema.js.map +1 -0
- package/lib/resource/schema/BaseResourceSchema.js +2 -1
- package/lib/resource/schema/BaseResourceSchema.js.map +1 -0
- package/lib/resource/schema/ResourceActionType.js +6 -4
- package/lib/resource/schema/ResourceActionType.js.map +1 -0
- package/lib/resource/schema/ResourceStatus.js +5 -3
- package/lib/resource/schema/ResourceStatus.js.map +1 -0
- package/lib/resource/schema/ResourceType.js +5 -3
- package/lib/resource/schema/ResourceType.js.map +1 -0
- package/lib/resource/schema/index.js +1 -0
- package/lib/resource/schema/index.js.map +1 -0
- package/lib/resource/schema/types.js +16 -20
- package/lib/resource/schema/types.js.map +1 -0
- package/lib/s3/formatS3Url.js +65 -0
- package/lib/s3/formatS3Url.js.map +1 -0
- package/lib/s3/formatS3Url.test.js +262 -0
- package/lib/s3/index.js +3 -0
- package/lib/s3/index.js.map +1 -0
- package/lib/s3/parseS3Url.js +65 -0
- package/lib/s3/parseS3Url.js.map +1 -0
- package/lib/s3/parseS3Url.test.js +270 -0
- package/lib/schema/SchemaRegistry.js +38 -38
- package/lib/schema/SchemaRegistry.js.map +1 -0
- package/lib/schema/TypeSchema.d.js +2 -0
- package/lib/schema/TypeSchema.d.js.map +1 -0
- package/lib/schema/createSchemaData.js +26 -125
- package/lib/schema/createSchemaData.js.map +1 -0
- package/lib/schema/findJsonSchemaByPath.js +13 -36
- package/lib/schema/findJsonSchemaByPath.js.map +1 -0
- package/lib/schema/formatZodError.js +140 -0
- package/lib/schema/formatZodError.js.map +1 -0
- package/lib/schema/formatZodError.test.js +196 -0
- package/lib/schema/getSchemaCache.js +5 -5
- package/lib/schema/getSchemaCache.js.map +1 -0
- package/lib/schema/getSchemaOptions.js +8 -11
- package/lib/schema/getSchemaOptions.js.map +1 -0
- package/lib/schema/index.js +2 -1
- package/lib/schema/index.js.map +1 -0
- package/lib/schema/toJsonSchema.js +47 -290
- package/lib/schema/toJsonSchema.js.map +1 -0
- package/lib/schema/validate.js +33 -45
- package/lib/schema/validate.js.map +1 -0
- package/lib/tools/generateSchema.js +39 -197
- package/lib/tools/generateSchema.js.map +1 -0
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js +55 -143
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -0
- package/lib/utils/buildBaseUrl.js +13 -0
- package/lib/utils/buildBaseUrl.js.map +1 -0
- package/lib/utils/buildRedactorFormSchema.js +59 -0
- package/lib/utils/buildRedactorFormSchema.js.map +1 -0
- package/lib/utils/getEstimateProcessTime.js +12 -11
- package/lib/utils/getEstimateProcessTime.js.map +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/resolveFeatureOptions.js +12 -0
- package/lib/utils/resolveFeatureOptions.js.map +1 -0
- package/package.json +61 -13
- package/src/ai/qwen3vl/index.ts +1 -0
- package/src/ai/qwen3vl/utils.ts +36 -0
- package/src/ai/vision/DocLayoutElementTypeSchema.ts +30 -0
- package/src/ai/vision/ImageAnnotationSchema.ts +60 -0
- package/src/ai/vision/index.ts +2 -0
- package/src/ai/vision/resolveImageAnnotation.ts +135 -0
- package/src/cn/ChineseResidentIdNo.test.ts +1 -1
- package/src/cn/ChineseResidentIdNo.ts +8 -0
- package/src/cn/DivisionCode.test.ts +1 -1
- package/src/cn/DivisionCode.ts +8 -0
- package/src/cn/UnifiedSocialCreditCode.test.ts +1 -1
- package/src/cn/UnifiedSocialCreditCode.ts +15 -0
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +23 -0
- package/src/cn/formatChineseAmount.ts +61 -0
- package/src/cn/index.ts +7 -1
- package/src/cn/parseChineseNumber.test.ts +159 -0
- package/src/cn/parseChineseNumber.ts +97 -0
- package/src/cn/pinyin/cartesianProduct.test.ts +64 -0
- package/src/cn/pinyin/cartesianProduct.ts +24 -0
- package/src/cn/pinyin/data.json +23573 -0
- package/src/cn/pinyin/loader.ts +12 -0
- package/src/cn/pinyin/preload.ts +3 -0
- package/src/cn/pinyin/toPinyin.test.ts +12 -0
- package/src/cn/pinyin/toPinyinPure.ts +43 -0
- package/src/cn/pinyin/transform.ts +12 -0
- package/src/consola/formatLogObject.test.ts +27 -0
- package/src/consola/formatLogObject.ts +34 -6
- package/src/dayjs/dayjs.ts +18 -18
- package/src/dayjs/index.ts +3 -1
- package/src/dayjs/parseRelativeTime.test.ts +185 -0
- package/src/dayjs/parseRelativeTime.ts +115 -0
- package/src/dayjs/resolveRelativeTime.test.ts +357 -0
- package/src/dayjs/resolveRelativeTime.ts +167 -0
- package/src/emittery/emitter.ts +9 -0
- package/src/emittery/index.ts +1 -0
- package/src/fs/FileSystemError.ts +26 -0
- package/src/fs/IFileSystem.d.ts +102 -0
- package/src/fs/MemoryFileSystem.test.ts +37 -0
- package/src/fs/createBrowserFileSystem.ts +291 -0
- package/src/fs/createMemoryFileSystem.ts +604 -0
- package/src/fs/createSandboxFileSystem.ts +136 -0
- package/src/fs/createWebDavFileSystem.ts +172 -0
- package/src/fs/findMimeType.ts +23 -0
- package/src/fs/index.ts +8 -0
- package/src/fs/orpc/FileSystemContract.ts +92 -0
- package/src/fs/orpc/createContractClientFileSystem.ts +115 -0
- package/src/fs/orpc/index.ts +2 -0
- package/src/fs/orpc/server/createFileSystemContractImpl.ts +64 -0
- package/src/fs/orpc/server/index.ts +1 -0
- package/src/fs/s3/createS3MiniFileSystem.ts +830 -0
- package/src/fs/s3/index.ts +1 -0
- package/src/fs/s3/s3mini.test.ts +264 -0
- package/src/fs/scandir.ts +75 -0
- package/src/fs/server/createDatabaseFileSystem.ts +668 -0
- package/src/fs/server/createNodeFileSystem.ts +499 -0
- package/src/fs/server/dbfs.test.ts +47 -0
- package/src/fs/server/index.ts +1 -0
- package/src/fs/server/loadTestDatabase.ts +131 -0
- package/src/fs/tests/runFileSystemTest.ts +288 -0
- package/src/fs/types.ts +29 -0
- package/src/fs/utils/getFileUrl.ts +44 -0
- package/src/fs/utils.ts +23 -0
- package/src/jsonschema/JsonSchema.ts +118 -110
- package/src/jsonschema/forEachJsonSchema.ts +50 -0
- package/src/jsonschema/index.ts +1 -0
- package/src/orpc/createOpenApiContractClient.ts +52 -0
- package/src/orpc/createRpcContractClient.ts +50 -0
- package/src/orpc/index.ts +2 -0
- package/src/orpc/resolveLinkPlugins.ts +29 -0
- package/src/password/PHC.ts +3 -3
- package/src/password/Password.test.ts +1 -1
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/resource/schema/AnyResourceSchema.ts +16 -2
- package/src/s3/formatS3Url.test.ts +254 -0
- package/src/s3/formatS3Url.ts +84 -0
- package/src/s3/index.ts +2 -0
- package/src/s3/parseS3Url.test.ts +258 -0
- package/src/s3/parseS3Url.ts +88 -0
- package/src/schema/SchemaRegistry.ts +35 -33
- package/src/schema/formatZodError.test.ts +196 -0
- package/src/schema/formatZodError.ts +151 -0
- package/src/schema/getSchemaOptions.ts +2 -2
- package/src/schema/index.ts +1 -1
- package/src/utils/buildBaseUrl.ts +12 -0
- package/src/utils/buildRedactorFormSchema.ts +85 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/resolveFeatureOptions.ts +14 -0
- package/src/cn/ChineseResidentIdNo.mod.ts +0 -7
- package/src/cn/DivisionCode.mod.ts +0 -7
- package/src/cn/UnifiedSocialCreditCode.mod.ts +0 -7
- package/src/cn/mod.ts +0 -3
- package/src/schema/SchemaRegistry.mod.ts +0 -1
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import type { Readable, Writable } from 'node:stream';
|
|
2
|
+
import { computeIfAbsent } from '@wener/utils';
|
|
3
|
+
import { basename, dirname, normalize } from 'pathe';
|
|
4
|
+
import { findMimeType } from './findMimeType';
|
|
5
|
+
import type {
|
|
6
|
+
CopyOptions,
|
|
7
|
+
CreateReadStreamOptions,
|
|
8
|
+
CreateWriteStreamOptions,
|
|
9
|
+
IFileStat,
|
|
10
|
+
IFileSystem,
|
|
11
|
+
MkdirOptions,
|
|
12
|
+
ReadFileOptions,
|
|
13
|
+
RenameOptions,
|
|
14
|
+
RmOptions,
|
|
15
|
+
WriteFileOptions,
|
|
16
|
+
} from './IFileSystem';
|
|
17
|
+
|
|
18
|
+
type MemoryNode = MemoryFile | MemoryDirectory;
|
|
19
|
+
|
|
20
|
+
type MemoryFile = IFileStat & {
|
|
21
|
+
kind: 'file';
|
|
22
|
+
content: string | Buffer;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type MemoryDirectory = IFileStat & {
|
|
26
|
+
kind: 'directory';
|
|
27
|
+
children: MemoryNode[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function createMemoryFileSystem(
|
|
31
|
+
options: {
|
|
32
|
+
root?: MemoryDirectory;
|
|
33
|
+
} = {},
|
|
34
|
+
): IFileSystem {
|
|
35
|
+
return new MemFS(options);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class MemoryFileSystemError extends Error {
|
|
39
|
+
code?: string;
|
|
40
|
+
|
|
41
|
+
constructor(message: string, code?: string) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'MemoryFileSystemError';
|
|
44
|
+
this.code = code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Cache = {
|
|
49
|
+
url?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
class MemFS implements IFileSystem {
|
|
53
|
+
private readonly root: MemoryDirectory;
|
|
54
|
+
|
|
55
|
+
private readonly cache = new WeakMap<MemoryNode, Cache>();
|
|
56
|
+
|
|
57
|
+
constructor({
|
|
58
|
+
root,
|
|
59
|
+
}: {
|
|
60
|
+
root?: MemoryDirectory;
|
|
61
|
+
} = {}) {
|
|
62
|
+
this.root = root || {
|
|
63
|
+
name: '',
|
|
64
|
+
kind: 'directory',
|
|
65
|
+
path: '/',
|
|
66
|
+
directory: '',
|
|
67
|
+
children: [],
|
|
68
|
+
size: 0,
|
|
69
|
+
mtime: Date.now(),
|
|
70
|
+
meta: {},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 核心辅助方法:通过路径查找节点
|
|
76
|
+
* @returns A tuple: [foundNode, parentNode, finalName]
|
|
77
|
+
*/
|
|
78
|
+
private _getNodeByPath(path: string): [MemoryNode | null, MemoryDirectory | null, string] {
|
|
79
|
+
if (!path || typeof path !== 'string') {
|
|
80
|
+
throw new MemoryFileSystemError('Invalid path: path must be a non-empty string', 'EINVAL');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const normalized = normalize(path);
|
|
84
|
+
if (normalized === '/') {
|
|
85
|
+
// the parent of root is itself
|
|
86
|
+
return [this.root, this.root, ''];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parts = normalized.split('/').filter((p) => p);
|
|
90
|
+
if (parts.length === 0) {
|
|
91
|
+
return [this.root, this.root, ''];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const finalName = parts.pop()!;
|
|
95
|
+
|
|
96
|
+
let current: MemoryDirectory = this.root;
|
|
97
|
+
for (const part of parts) {
|
|
98
|
+
if (!part) continue; // skip empty parts
|
|
99
|
+
|
|
100
|
+
const found = current.children.find((child) => child.name === part);
|
|
101
|
+
if (!found || found.kind !== 'directory') {
|
|
102
|
+
return [null, null, finalName];
|
|
103
|
+
}
|
|
104
|
+
current = found;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const node = current.children.find((child) => child.name === finalName) ?? null;
|
|
108
|
+
return [node, current, finalName];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 核心辅助方法:查找或创建目录
|
|
113
|
+
*/
|
|
114
|
+
private _findOrCreateDirectory(path: string): MemoryDirectory {
|
|
115
|
+
if (!path || typeof path !== 'string') {
|
|
116
|
+
throw new MemoryFileSystemError('Invalid path: path must be a non-empty string', 'EINVAL');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const normalized = normalize(path);
|
|
120
|
+
if (normalized === '/') return this.root;
|
|
121
|
+
|
|
122
|
+
const parts = normalized.split('/').filter((p) => p);
|
|
123
|
+
if (parts.length === 0) return this.root;
|
|
124
|
+
|
|
125
|
+
let current: MemoryDirectory = this.root;
|
|
126
|
+
let currentPath = '';
|
|
127
|
+
|
|
128
|
+
for (const part of parts) {
|
|
129
|
+
if (!part) continue; // skip empty parts
|
|
130
|
+
|
|
131
|
+
currentPath = `${currentPath}/${part}`;
|
|
132
|
+
let found = current.children.find((child) => child.name === part);
|
|
133
|
+
if (!found) {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const newDir: MemoryDirectory = {
|
|
136
|
+
name: part,
|
|
137
|
+
kind: 'directory',
|
|
138
|
+
path: currentPath,
|
|
139
|
+
directory: dirname(currentPath),
|
|
140
|
+
children: [],
|
|
141
|
+
size: 0,
|
|
142
|
+
mtime: now,
|
|
143
|
+
meta: {},
|
|
144
|
+
};
|
|
145
|
+
current.children.push(newDir);
|
|
146
|
+
current = newDir;
|
|
147
|
+
} else if (found.kind !== 'directory') {
|
|
148
|
+
throw new MemoryFileSystemError(`Path conflict: ${currentPath} is a file`, 'ENOTDIR');
|
|
149
|
+
} else {
|
|
150
|
+
current = found;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return current;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async stat(path: string, options?: { signal?: AbortSignal }): Promise<IFileStat> {
|
|
157
|
+
if (options?.signal?.aborted) {
|
|
158
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const [node] = this._getNodeByPath(path);
|
|
162
|
+
if (!node) {
|
|
163
|
+
throw new MemoryFileSystemError(`File not found: ${path}`, 'ENOENT');
|
|
164
|
+
}
|
|
165
|
+
return { ...node };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async exists(path: string): Promise<boolean> {
|
|
169
|
+
const [node] = this._getNodeByPath(path);
|
|
170
|
+
return !!node;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async readdir(path: string, options?: { signal?: AbortSignal }): Promise<IFileStat[]> {
|
|
174
|
+
if (options?.signal?.aborted) {
|
|
175
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const [node] = this._getNodeByPath(path);
|
|
179
|
+
if (!node) {
|
|
180
|
+
throw new MemoryFileSystemError(`Directory not found: ${path}`, 'ENOENT');
|
|
181
|
+
}
|
|
182
|
+
if (node.kind !== 'directory') {
|
|
183
|
+
throw new MemoryFileSystemError(`Not a directory: ${path}`, 'ENOTDIR');
|
|
184
|
+
}
|
|
185
|
+
// 返回副本
|
|
186
|
+
return node.children.map((child) => ({ ...child }));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async mkdir(path: string, options?: MkdirOptions): Promise<void> {
|
|
190
|
+
if (options?.signal?.aborted) {
|
|
191
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const [node] = this._getNodeByPath(path);
|
|
195
|
+
if (node) {
|
|
196
|
+
if (node.kind === 'file') throw new MemoryFileSystemError(`File already exists: ${path}`, 'EEXIST');
|
|
197
|
+
return; // 目录已存在
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (options?.recursive) {
|
|
201
|
+
this._findOrCreateDirectory(path);
|
|
202
|
+
} else {
|
|
203
|
+
const parentPath = dirname(path);
|
|
204
|
+
const [, parent] = this._getNodeByPath(parentPath);
|
|
205
|
+
if (!parent) {
|
|
206
|
+
throw new MemoryFileSystemError(`Parent directory does not exist: ${parentPath}`, 'ENOENT');
|
|
207
|
+
}
|
|
208
|
+
this._findOrCreateDirectory(path);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
readFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;
|
|
213
|
+
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
214
|
+
async readFile(path: string, options?: ReadFileOptions): Promise<string | Uint8Array> {
|
|
215
|
+
if (options?.signal?.aborted) {
|
|
216
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const [node] = this._getNodeByPath(path);
|
|
220
|
+
if (!node) {
|
|
221
|
+
throw new MemoryFileSystemError(`File not found: ${path}`, 'ENOENT');
|
|
222
|
+
}
|
|
223
|
+
if (node.kind !== 'file') {
|
|
224
|
+
throw new MemoryFileSystemError(`Is a directory: ${path}`, 'EISDIR');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const content = 'content' in node ? node.content : '';
|
|
228
|
+
return options?.encoding === 'text' ? content.toString() : Buffer.from(content);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async writeFile(path: string, data: string | Buffer, options: WriteFileOptions = {}): Promise<void> {
|
|
232
|
+
if (options?.signal?.aborted) {
|
|
233
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!path || typeof path !== 'string') {
|
|
237
|
+
throw new MemoryFileSystemError('Invalid path: path must be a non-empty string', 'EINVAL');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (data === null || data === undefined) {
|
|
241
|
+
throw new MemoryFileSystemError('Invalid data: data cannot be null or undefined', 'EINVAL');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const { overwrite = true } = options;
|
|
245
|
+
const parentPath = dirname(path);
|
|
246
|
+
const filename = basename(path);
|
|
247
|
+
|
|
248
|
+
if (!filename) {
|
|
249
|
+
throw new MemoryFileSystemError('Invalid path: filename cannot be empty', 'EINVAL');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const parent = this._findOrCreateDirectory(parentPath);
|
|
253
|
+
const nodeIndex = parent.children.findIndex((child) => child.name === filename);
|
|
254
|
+
const existingNode = nodeIndex !== -1 ? parent.children[nodeIndex] : null;
|
|
255
|
+
|
|
256
|
+
if (existingNode) {
|
|
257
|
+
if (!overwrite) throw new MemoryFileSystemError(`File already exists: ${path}`, 'EEXIST');
|
|
258
|
+
if (existingNode.kind === 'directory')
|
|
259
|
+
throw new MemoryFileSystemError(`Cannot overwrite a directory: ${path}`, 'EISDIR');
|
|
260
|
+
|
|
261
|
+
// 更新文件
|
|
262
|
+
existingNode.content = data;
|
|
263
|
+
existingNode.size = data.length;
|
|
264
|
+
existingNode.mtime = Date.now();
|
|
265
|
+
} else {
|
|
266
|
+
// 创建新文件
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
const newFile: MemoryFile = {
|
|
269
|
+
name: filename,
|
|
270
|
+
kind: 'file',
|
|
271
|
+
path: path,
|
|
272
|
+
directory: parentPath,
|
|
273
|
+
content: data,
|
|
274
|
+
size: data.length,
|
|
275
|
+
mtime: now,
|
|
276
|
+
meta: {},
|
|
277
|
+
};
|
|
278
|
+
parent.children.push(newFile);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async rm(path: string, options: RmOptions = {}): Promise<void> {
|
|
283
|
+
if (options?.signal?.aborted) {
|
|
284
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { recursive = false, force = false } = options;
|
|
288
|
+
const [node, parent] = this._getNodeByPath(path);
|
|
289
|
+
|
|
290
|
+
if (!node) {
|
|
291
|
+
if (force) return;
|
|
292
|
+
throw new MemoryFileSystemError(`File not found: ${path}`, 'ENOENT');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (node.kind === 'directory' && node.children.length > 0 && !recursive) {
|
|
296
|
+
throw new MemoryFileSystemError(`Directory not empty: ${path}`, 'ENOTEMPTY');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (parent) {
|
|
300
|
+
const index = parent.children.findIndex((child) => child.name === node.name);
|
|
301
|
+
if (index !== -1) {
|
|
302
|
+
parent.children.splice(index, 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async rename(oldPath: string, newPath: string, options?: RenameOptions): Promise<void> {
|
|
308
|
+
if (options?.signal?.aborted) {
|
|
309
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const [oldNode, oldParent, oldName] = this._getNodeByPath(oldPath);
|
|
313
|
+
if (!oldNode || !oldParent) throw new MemoryFileSystemError(`Source not found: ${oldPath}`, 'ENOENT');
|
|
314
|
+
|
|
315
|
+
const oldNodeIndex = oldParent.children.findIndex((c) => c.name === oldName);
|
|
316
|
+
oldParent.children.splice(oldNodeIndex, 1);
|
|
317
|
+
|
|
318
|
+
const newParentPath = dirname(newPath);
|
|
319
|
+
const newName = basename(newPath);
|
|
320
|
+
const newParent = this._findOrCreateDirectory(newParentPath);
|
|
321
|
+
|
|
322
|
+
const existingNodeIndex = newParent.children.findIndex((c) => c.name === newName);
|
|
323
|
+
if (existingNodeIndex !== -1) {
|
|
324
|
+
if (!options?.overwrite) throw new MemoryFileSystemError(`Destination exists: ${newPath}`, 'EEXIST');
|
|
325
|
+
newParent.children.splice(existingNodeIndex, 1);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
oldNode.name = newName;
|
|
329
|
+
oldNode.path = newPath;
|
|
330
|
+
oldNode.directory = newParentPath;
|
|
331
|
+
oldNode.mtime = Date.now();
|
|
332
|
+
newParent.children.push(oldNode);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async copy(srcPath: string, destPath: string, options?: CopyOptions): Promise<void> {
|
|
336
|
+
if (options?.signal?.aborted) {
|
|
337
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const [srcNode] = this._getNodeByPath(srcPath);
|
|
341
|
+
if (!srcNode) throw new MemoryFileSystemError(`Source not found: ${srcPath}`, 'ENOENT');
|
|
342
|
+
|
|
343
|
+
const deepClone = <T extends MemoryNode>(node: T): T => JSON.parse(JSON.stringify(node));
|
|
344
|
+
const newNode = deepClone(srcNode);
|
|
345
|
+
|
|
346
|
+
const newParentPath = dirname(destPath);
|
|
347
|
+
const newName = basename(destPath);
|
|
348
|
+
const newParent = this._findOrCreateDirectory(newParentPath);
|
|
349
|
+
|
|
350
|
+
newNode.name = newName;
|
|
351
|
+
newNode.path = destPath;
|
|
352
|
+
newNode.directory = newParentPath;
|
|
353
|
+
|
|
354
|
+
const existingNodeIndex = newParent.children.findIndex((c) => c.name === newName);
|
|
355
|
+
if (existingNodeIndex !== -1) {
|
|
356
|
+
if (!options?.overwrite) throw new MemoryFileSystemError(`Destination exists: ${destPath}`, 'EEXIST');
|
|
357
|
+
newParent.children[existingNodeIndex] = newNode;
|
|
358
|
+
} else {
|
|
359
|
+
newParent.children.push(newNode);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
createReadStream(path: string, options?: CreateReadStreamOptions): Readable {
|
|
364
|
+
const { signal, range } = options || {};
|
|
365
|
+
|
|
366
|
+
if (signal?.aborted) {
|
|
367
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const [node] = this._getNodeByPath(path);
|
|
371
|
+
if (!node) {
|
|
372
|
+
throw new MemoryFileSystemError(`File not found: ${path}`, 'ENOENT');
|
|
373
|
+
}
|
|
374
|
+
if (node.kind !== 'file') {
|
|
375
|
+
throw new MemoryFileSystemError(`Is a directory: ${path}`, 'EISDIR');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const content = Buffer.from('content' in node ? node.content : '');
|
|
379
|
+
const { Readable } = require('node:stream');
|
|
380
|
+
|
|
381
|
+
let data = content;
|
|
382
|
+
if (range) {
|
|
383
|
+
const start = range.start || 0;
|
|
384
|
+
const end = range.end || content.length - 1;
|
|
385
|
+
data = content.subarray(start, end + 1);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const stream = Readable.from([data]);
|
|
389
|
+
|
|
390
|
+
if (signal) {
|
|
391
|
+
signal.addEventListener('abort', () => {
|
|
392
|
+
stream.destroy(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return stream;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
createWriteStream(path: string, options?: CreateWriteStreamOptions): Writable {
|
|
400
|
+
const { signal, overwrite = true } = options || {};
|
|
401
|
+
|
|
402
|
+
if (signal?.aborted) {
|
|
403
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const { Writable } = require('node:stream');
|
|
407
|
+
const chunks: Buffer[] = [];
|
|
408
|
+
|
|
409
|
+
const self = this;
|
|
410
|
+
const stream = new Writable({
|
|
411
|
+
write(chunk: Buffer, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
|
|
412
|
+
if (signal?.aborted) {
|
|
413
|
+
callback(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
chunks.push(chunk);
|
|
417
|
+
callback();
|
|
418
|
+
},
|
|
419
|
+
final(callback: (error?: Error | null) => void) {
|
|
420
|
+
if (signal?.aborted) {
|
|
421
|
+
callback(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const content = Buffer.concat(chunks);
|
|
427
|
+
const parentPath = dirname(path);
|
|
428
|
+
const filename = basename(path);
|
|
429
|
+
const parent = self._findOrCreateDirectory(parentPath);
|
|
430
|
+
|
|
431
|
+
const nodeIndex = parent.children.findIndex((child) => child.name === filename);
|
|
432
|
+
const existingNode = nodeIndex !== -1 ? parent.children[nodeIndex] : null;
|
|
433
|
+
|
|
434
|
+
if (existingNode) {
|
|
435
|
+
if (!overwrite) {
|
|
436
|
+
callback(new MemoryFileSystemError(`File already exists: ${path}`, 'EEXIST'));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (existingNode.kind === 'directory') {
|
|
440
|
+
callback(new MemoryFileSystemError(`Cannot overwrite a directory: ${path}`, 'EISDIR'));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
existingNode.content = content;
|
|
445
|
+
existingNode.size = content.length;
|
|
446
|
+
existingNode.mtime = Date.now();
|
|
447
|
+
} else {
|
|
448
|
+
const now = Date.now();
|
|
449
|
+
const newFile: MemoryFile = {
|
|
450
|
+
name: filename,
|
|
451
|
+
kind: 'file',
|
|
452
|
+
path: path,
|
|
453
|
+
directory: parentPath,
|
|
454
|
+
content: content,
|
|
455
|
+
size: content.length,
|
|
456
|
+
mtime: now,
|
|
457
|
+
meta: {},
|
|
458
|
+
};
|
|
459
|
+
parent.children.push(newFile);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
callback();
|
|
463
|
+
} catch (error) {
|
|
464
|
+
callback(error instanceof Error ? error : new MemoryFileSystemError('Unknown error', 'UNKNOWN'));
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (signal) {
|
|
470
|
+
signal.addEventListener('abort', () => {
|
|
471
|
+
stream.destroy(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return stream;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
getUrl(file: IFileStat | string) {
|
|
479
|
+
let node: IFileStat | null;
|
|
480
|
+
if (typeof file === 'string') {
|
|
481
|
+
[node] = this._getNodeByPath(file);
|
|
482
|
+
} else {
|
|
483
|
+
node = file;
|
|
484
|
+
}
|
|
485
|
+
let mf = node as MemoryFile;
|
|
486
|
+
if (mf.content) {
|
|
487
|
+
let c = computeIfAbsent(this.cache, mf, () => ({}) as Cache);
|
|
488
|
+
if (!c.url) {
|
|
489
|
+
let mime = findMimeType(mf.name) || 'application/octet-stream';
|
|
490
|
+
c.url = URL.createObjectURL(new Blob([mf.content], { type: mime }));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return c.url;
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
createReadableStream(path: string, options?: CreateReadStreamOptions): ReadableStream {
|
|
499
|
+
const { signal, range } = options || {};
|
|
500
|
+
|
|
501
|
+
if (signal?.aborted) {
|
|
502
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const [node] = this._getNodeByPath(path);
|
|
506
|
+
if (!node) {
|
|
507
|
+
throw new MemoryFileSystemError(`File not found: ${path}`, 'ENOENT');
|
|
508
|
+
}
|
|
509
|
+
if (node.kind !== 'file') {
|
|
510
|
+
throw new MemoryFileSystemError(`Is a directory: ${path}`, 'EISDIR');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const content = Buffer.from('content' in node ? node.content : '');
|
|
514
|
+
|
|
515
|
+
let data = content;
|
|
516
|
+
if (range) {
|
|
517
|
+
const start = range.start || 0;
|
|
518
|
+
const end = range.end || content.length - 1;
|
|
519
|
+
data = content.subarray(start, end + 1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const stream = new ReadableStream({
|
|
523
|
+
start(controller) {
|
|
524
|
+
controller.enqueue(data);
|
|
525
|
+
controller.close();
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
if (signal) {
|
|
530
|
+
signal.addEventListener('abort', () => {
|
|
531
|
+
stream.cancel(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return stream;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
createWritableStream(path: string, options?: CreateWriteStreamOptions): WritableStream {
|
|
539
|
+
const { signal, overwrite = true } = options || {};
|
|
540
|
+
|
|
541
|
+
if (signal?.aborted) {
|
|
542
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const chunks: Buffer[] = [];
|
|
546
|
+
|
|
547
|
+
const self = this;
|
|
548
|
+
const stream = new WritableStream({
|
|
549
|
+
write(chunk: Buffer) {
|
|
550
|
+
if (signal?.aborted) {
|
|
551
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
552
|
+
}
|
|
553
|
+
chunks.push(chunk);
|
|
554
|
+
},
|
|
555
|
+
close() {
|
|
556
|
+
if (signal?.aborted) {
|
|
557
|
+
throw new MemoryFileSystemError('Operation aborted', 'ABORT_ERR');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const content = Buffer.concat(chunks);
|
|
561
|
+
const parentPath = dirname(path);
|
|
562
|
+
const filename = basename(path);
|
|
563
|
+
const parent = self._findOrCreateDirectory(parentPath);
|
|
564
|
+
|
|
565
|
+
const nodeIndex = parent.children.findIndex((child) => child.name === filename);
|
|
566
|
+
const existingNode = nodeIndex !== -1 ? parent.children[nodeIndex] : null;
|
|
567
|
+
|
|
568
|
+
if (existingNode) {
|
|
569
|
+
if (!overwrite) {
|
|
570
|
+
throw new MemoryFileSystemError(`File already exists: ${path}`, 'EEXIST');
|
|
571
|
+
}
|
|
572
|
+
if (existingNode.kind === 'directory') {
|
|
573
|
+
throw new MemoryFileSystemError(`Cannot overwrite a directory: ${path}`, 'EISDIR');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
existingNode.content = content;
|
|
577
|
+
existingNode.size = content.length;
|
|
578
|
+
existingNode.mtime = Date.now();
|
|
579
|
+
} else {
|
|
580
|
+
const now = Date.now();
|
|
581
|
+
const newFile: MemoryFile = {
|
|
582
|
+
name: filename,
|
|
583
|
+
kind: 'file',
|
|
584
|
+
path: path,
|
|
585
|
+
directory: parentPath,
|
|
586
|
+
content: content,
|
|
587
|
+
size: content.length,
|
|
588
|
+
mtime: now,
|
|
589
|
+
meta: {},
|
|
590
|
+
};
|
|
591
|
+
parent.children.push(newFile);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
if (signal) {
|
|
597
|
+
signal.addEventListener('abort', () => {
|
|
598
|
+
stream.abort(new MemoryFileSystemError('Operation aborted', 'ABORT_ERR'));
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return stream;
|
|
603
|
+
}
|
|
604
|
+
}
|