@wener/common 2.0.1 → 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 +48 -0
- package/lib/cn/ChineseResidentIdNo.js.map +1 -0
- package/lib/cn/ChineseResidentIdNo.mod.js +1 -0
- package/lib/cn/{ResidentIdentityCardNumber.test.js → ChineseResidentIdNo.test.js} +7 -6
- package/lib/cn/DivisionCode.js +217 -301
- package/lib/cn/DivisionCode.js.map +1 -0
- package/lib/cn/DivisionCode.mod.js +1 -0
- package/lib/cn/DivisionCode.test.js +9 -15
- package/lib/cn/Mod11.js +43 -0
- package/lib/cn/Mod11.js.map +1 -0
- package/lib/cn/Mod31.js +49 -0
- package/lib/cn/Mod31.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.js +137 -113
- package/lib/cn/UnifiedSocialCreditCode.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.mod.js +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 +6 -2
- package/lib/cn/index.js.map +1 -0
- package/lib/cn/mod.js +6 -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 +67 -135
- 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 +40 -0
- package/lib/dayjs/dayjs.js.map +1 -0
- package/lib/dayjs/formatDuration.js +59 -0
- package/lib/dayjs/formatDuration.js.map +1 -0
- package/lib/dayjs/formatDuration.test.js +90 -0
- package/lib/dayjs/index.js +5 -0
- package/lib/dayjs/index.js.map +1 -0
- package/lib/dayjs/parseDuration.js +29 -0
- 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 +2 -0
- package/lib/decimal/index.js.map +1 -0
- package/lib/decimal/parseDecimal.js +15 -0
- 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 +16 -0
- package/lib/foundation/schema/SexType.js.map +1 -0
- package/lib/foundation/schema/index.js +2 -0
- package/lib/foundation/schema/index.js.map +1 -0
- package/lib/foundation/schema/parseSexType.js +19 -0
- package/lib/foundation/schema/parseSexType.js.map +1 -0
- package/lib/foundation/schema/types.js +7 -0
- 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 +30 -292
- 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 +47 -0
- 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 +2 -0
- package/lib/resource/index.js.map +1 -0
- package/lib/resource/schema/AnyResourceSchema.js +3 -2
- 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 +6 -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 +45 -0
- package/lib/schema/SchemaRegistry.js.map +1 -0
- package/lib/schema/SchemaRegistry.mod.js +2 -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 -0
- 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 +21 -0
- package/lib/utils/getEstimateProcessTime.js.map +1 -0
- package/lib/utils/index.js +4 -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 +77 -20
- 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 +18 -0
- package/src/cn/ChineseResidentIdNo.ts +74 -0
- package/src/cn/DivisionCode.test.ts +3 -13
- package/src/cn/DivisionCode.ts +138 -193
- package/src/cn/{Mod11Checksum.ts → Mod11.ts} +3 -1
- package/src/cn/{Mod31Checksum.ts → Mod31.ts} +2 -0
- package/src/cn/UnifiedSocialCreditCode.test.ts +2 -2
- package/src/cn/UnifiedSocialCreditCode.ts +119 -124
- package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +14 -0
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +41 -12
- package/src/cn/formatChineseAmount.ts +61 -0
- package/src/cn/index.ts +6 -2
- 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 +46 -10
- package/src/dayjs/dayjs.ts +40 -0
- package/src/dayjs/formatDuration.test.ts +14 -0
- package/src/dayjs/formatDuration.ts +86 -0
- package/src/dayjs/index.ts +5 -0
- package/src/dayjs/parseDuration.ts +40 -0
- 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/decimal/index.ts +1 -0
- package/src/decimal/parseDecimal.ts +16 -0
- package/src/emittery/emitter.ts +9 -0
- package/src/emittery/index.ts +1 -0
- package/src/foundation/schema/SexType.ts +21 -0
- package/src/foundation/schema/index.ts +1 -0
- package/src/foundation/schema/parseSexType.ts +19 -0
- package/src/foundation/schema/types.ts +8 -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/Password.ts +2 -2
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/resource/ListQuery.ts +53 -0
- package/src/resource/index.ts +1 -0
- package/src/resource/schema/AnyResourceSchema.ts +17 -3
- package/src/resource/schema/index.ts +5 -0
- 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 +48 -0
- package/src/schema/createSchemaData.ts +1 -1
- package/src/schema/formatZodError.test.ts +196 -0
- package/src/schema/formatZodError.ts +151 -0
- package/src/schema/getSchemaOptions.ts +3 -3
- package/src/schema/index.ts +1 -0
- package/src/utils/buildBaseUrl.ts +12 -0
- package/src/utils/buildRedactorFormSchema.ts +85 -0
- package/src/utils/getEstimateProcessTime.ts +36 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/resolveFeatureOptions.ts +14 -0
- package/lib/cn/Mod11Checksum.js +0 -85
- package/lib/cn/Mod31Checksum.js +0 -97
- package/lib/cn/ResidentIdentityCardNumber.js +0 -50
- package/lib/cn/formatDate.js +0 -13
- package/lib/cn/parseSex.js +0 -20
- package/lib/resource/schema/SchemaRegistry.js +0 -38
- package/lib/resource/schema/SexType.js +0 -10
- package/lib/search/AdvanceSearch.js +0 -9
- package/lib/search/AdvanceSearch.test.js +0 -435
- package/lib/search/formatAdvanceSearch.js +0 -78
- package/lib/search/index.js +0 -1
- package/lib/search/optimizeAdvanceSearch.js +0 -143
- package/lib/search/parseAdvanceSearch.js +0 -20
- package/lib/search/parser.d.js +0 -1
- package/lib/search/parser.js +0 -3088
- package/lib/search/types.d.js +0 -1
- package/src/cn/ResidentIdentityCardNumber.test.ts +0 -17
- package/src/cn/ResidentIdentityCardNumber.ts +0 -96
- package/src/cn/__snapshots__/ResidentIdentityCardNumber.test.ts.snap +0 -15
- package/src/cn/formatDate.ts +0 -12
- package/src/cn/parseSex.ts +0 -13
- package/src/resource/schema/SchemaRegistry.ts +0 -42
- package/src/resource/schema/SexType.ts +0 -13
- package/src/search/AdvanceSearch.test.ts +0 -149
- package/src/search/AdvanceSearch.ts +0 -14
- package/src/search/Makefile +0 -2
- package/src/search/__snapshots__/AdvanceSearch.test.ts.snap +0 -675
- package/src/search/formatAdvanceSearch.ts +0 -52
- package/src/search/index.ts +0 -1
- package/src/search/optimizeAdvanceSearch.ts +0 -77
- package/src/search/parseAdvanceSearch.ts +0 -23
- package/src/search/parser.d.ts +0 -8
- package/src/search/parser.js +0 -2794
- package/src/search/parser.peggy +0 -237
- package/src/search/types.d.ts +0 -45
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { basename, dirname, join, normalize } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
Cascade,
|
|
5
|
+
Collection,
|
|
6
|
+
Entity,
|
|
7
|
+
ManyToOne,
|
|
8
|
+
OneToMany,
|
|
9
|
+
OneToOne,
|
|
10
|
+
Property,
|
|
11
|
+
types,
|
|
12
|
+
Unique,
|
|
13
|
+
type Opt,
|
|
14
|
+
type Rel,
|
|
15
|
+
} from '@mikro-orm/core';
|
|
16
|
+
import type { EntityManager } from '@mikro-orm/knex';
|
|
17
|
+
import { TenantBaseEntity } from '@wener/server/entity';
|
|
18
|
+
import { getEntityManager } from '@wener/server/mikro-orm';
|
|
19
|
+
import type { CopyOptions } from 'fs-extra';
|
|
20
|
+
import { FileSystemError, FileSystemErrorCode } from '../FileSystemError';
|
|
21
|
+
import type {
|
|
22
|
+
CreateReadStreamOptions,
|
|
23
|
+
CreateWriteStreamOptions,
|
|
24
|
+
IFileStat,
|
|
25
|
+
IFileSystem,
|
|
26
|
+
MkdirOptions,
|
|
27
|
+
ReaddirOptions,
|
|
28
|
+
ReadFileOptions,
|
|
29
|
+
RenameOptions,
|
|
30
|
+
RmOptions,
|
|
31
|
+
StatOptions,
|
|
32
|
+
WriteFileOptions,
|
|
33
|
+
} from '../IFileSystem';
|
|
34
|
+
import { FileKind } from '../types';
|
|
35
|
+
|
|
36
|
+
export function createDatabaseFileSystem(options: Partial<IDatabaseFileSystemOptions> = {}): IDatabaseFileSystem {
|
|
37
|
+
return new DBFS(options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type IDatabaseFileSystem = IFileSystem & {
|
|
41
|
+
ensureRootNode(): Promise<FileNodeMetaEntity>;
|
|
42
|
+
};
|
|
43
|
+
type IDatabaseFileSystemOptions = {
|
|
44
|
+
getEntityManager: () => EntityManager;
|
|
45
|
+
smallFileThreshold?: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
class DBFS implements IFileSystem {
|
|
49
|
+
options: IDatabaseFileSystemOptions;
|
|
50
|
+
|
|
51
|
+
constructor(options: Partial<IDatabaseFileSystemOptions> = {}) {
|
|
52
|
+
this.options = {
|
|
53
|
+
getEntityManager: () => getEntityManager<EntityManager>().fork(),
|
|
54
|
+
smallFileThreshold: 512,
|
|
55
|
+
...options,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get em() {
|
|
60
|
+
return this.options.getEntityManager();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Ensure root node exists, create it if it doesn't exist
|
|
65
|
+
* @returns The root FileNodeMetaEntity
|
|
66
|
+
*/
|
|
67
|
+
async ensureRootNode(): Promise<FileNodeMetaEntity> {
|
|
68
|
+
const em = this.em;
|
|
69
|
+
const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
70
|
+
rootQb.where({ parent: null });
|
|
71
|
+
const rootNode = await rootQb.getSingleResult();
|
|
72
|
+
|
|
73
|
+
if (rootNode) {
|
|
74
|
+
return rootNode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create root directory (parent is null, filename is empty string)
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const rootDir = em.create(FileNodeMetaEntity, {
|
|
80
|
+
filename: '',
|
|
81
|
+
parent: null,
|
|
82
|
+
kind: FileKind.directory,
|
|
83
|
+
size: 0,
|
|
84
|
+
atime: now,
|
|
85
|
+
btime: now,
|
|
86
|
+
ctime: now,
|
|
87
|
+
mtime: now,
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
await em.persistAndFlush(rootDir);
|
|
91
|
+
return rootDir;
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
// If root already exists (race condition), fetch and return it
|
|
94
|
+
if (error.message?.includes('UNIQUE constraint') || error.message?.includes('duplicate')) {
|
|
95
|
+
const existingRootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
96
|
+
existingRootQb.where({ parent: null });
|
|
97
|
+
const existingRoot = await existingRootQb.getSingleResult();
|
|
98
|
+
if (existingRoot) {
|
|
99
|
+
return existingRoot;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async stat(path: string, options?: StatOptions): Promise<IFileStat> {
|
|
107
|
+
// Validate input
|
|
108
|
+
if (!path || typeof path !== 'string') {
|
|
109
|
+
throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const em = this.em;
|
|
113
|
+
const node = await this._getNodeByPath(path, em);
|
|
114
|
+
if (!node) {
|
|
115
|
+
throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
|
|
116
|
+
}
|
|
117
|
+
return this._toFileStat(node, path);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async exists(path: string): Promise<boolean> {
|
|
121
|
+
return !!(await this._getNodeByPath(path, this.em));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async readdir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]> {
|
|
125
|
+
const em = this.em;
|
|
126
|
+
const parentNode = await this._getNodeByPath(dir, em);
|
|
127
|
+
|
|
128
|
+
if (!parentNode) {
|
|
129
|
+
throw new FileSystemError(`Directory not found: ${dir}`, FileSystemErrorCode.ENOENT);
|
|
130
|
+
}
|
|
131
|
+
if (parentNode.kind !== FileKind.directory) {
|
|
132
|
+
throw new FileSystemError(`Path is not a directory: ${dir}`, FileSystemErrorCode.ENOTDIR);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Use QueryBuilder to avoid automatic relationship loading
|
|
136
|
+
const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
137
|
+
qb.where({ parent: parentNode });
|
|
138
|
+
const children = await qb.getResult();
|
|
139
|
+
|
|
140
|
+
return children.map((child) => this._toFileStat(child, join(dir, child.filename)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {
|
|
144
|
+
await this._mkdirInTransaction(path, options, this.em);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private async _mkdirInTransaction(path: string, options: MkdirOptions, em: EntityManager): Promise<void> {
|
|
148
|
+
const normalized = normalize(path);
|
|
149
|
+
|
|
150
|
+
// Special handling for root directory
|
|
151
|
+
if (normalized === '/') {
|
|
152
|
+
const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
153
|
+
rootQb.where({ parent: null });
|
|
154
|
+
const rootNode = await rootQb.getSingleResult();
|
|
155
|
+
if (rootNode) {
|
|
156
|
+
// Root directory already exists
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Create root directory (parent is null, filename is empty string)
|
|
160
|
+
const now = new Date();
|
|
161
|
+
const rootDir = em.create(FileNodeMetaEntity, {
|
|
162
|
+
filename: '',
|
|
163
|
+
parent: null,
|
|
164
|
+
kind: FileKind.directory,
|
|
165
|
+
size: 0,
|
|
166
|
+
atime: now,
|
|
167
|
+
btime: now,
|
|
168
|
+
ctime: now,
|
|
169
|
+
mtime: now,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
await em.persistAndFlush(rootDir);
|
|
173
|
+
} catch (error: any) {
|
|
174
|
+
// If root already exists (race condition), ignore the error
|
|
175
|
+
if (!error.message?.includes('UNIQUE constraint') && !error.message?.includes('duplicate')) {
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const parentPath = dirname(normalized);
|
|
183
|
+
const newDirName = basename(normalized);
|
|
184
|
+
|
|
185
|
+
if (!newDirName) throw new FileSystemError('Cannot create directory with empty name', FileSystemErrorCode.EINVAL);
|
|
186
|
+
|
|
187
|
+
let parentNode = await this._getNodeByPath(parentPath, em);
|
|
188
|
+
|
|
189
|
+
if (!parentNode) {
|
|
190
|
+
if (options.recursive) {
|
|
191
|
+
// 递归创建父目录
|
|
192
|
+
await this._mkdirInTransaction(parentPath, options, em);
|
|
193
|
+
parentNode = await this._getNodeByPath(parentPath, em);
|
|
194
|
+
} else {
|
|
195
|
+
throw new FileSystemError(`Parent directory not found: ${parentPath}`, FileSystemErrorCode.ENOENT);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!parentNode)
|
|
200
|
+
throw new FileSystemError('Failed to create parent directory structure', FileSystemErrorCode.EINVAL);
|
|
201
|
+
|
|
202
|
+
// Check if directory already exists using QueryBuilder
|
|
203
|
+
const existingQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
204
|
+
existingQb.where({ parent: parentNode, filename: newDirName });
|
|
205
|
+
const existing = await existingQb.getSingleResult();
|
|
206
|
+
|
|
207
|
+
if (existing) {
|
|
208
|
+
if (existing.kind === FileKind.directory) {
|
|
209
|
+
// Directory already exists, return silently
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
throw new FileSystemError(`A file with the same name already exists: ${path}`, FileSystemErrorCode.EEXIST);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Create directory using EntityManager
|
|
216
|
+
const now = new Date();
|
|
217
|
+
const newDir = em.create(FileNodeMetaEntity, {
|
|
218
|
+
tid: parentNode.tid,
|
|
219
|
+
parent: parentNode,
|
|
220
|
+
filename: newDirName,
|
|
221
|
+
kind: FileKind.directory,
|
|
222
|
+
size: 0,
|
|
223
|
+
atime: now,
|
|
224
|
+
btime: now,
|
|
225
|
+
ctime: now,
|
|
226
|
+
mtime: now,
|
|
227
|
+
});
|
|
228
|
+
await em.persistAndFlush(newDir);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
readFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;
|
|
232
|
+
|
|
233
|
+
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
234
|
+
|
|
235
|
+
async readFile(
|
|
236
|
+
path: string,
|
|
237
|
+
options?: ReadFileOptions & {
|
|
238
|
+
encoding?: 'text' | 'binary' | string;
|
|
239
|
+
},
|
|
240
|
+
): Promise<string | Uint8Array> {
|
|
241
|
+
const em = this.em;
|
|
242
|
+
const node = await this._getNodeByPath(path, em);
|
|
243
|
+
|
|
244
|
+
if (!node) throw new FileSystemError(`File not found: ${path}`, FileSystemErrorCode.ENOENT);
|
|
245
|
+
if (node.kind !== FileKind.file)
|
|
246
|
+
throw new FileSystemError(`Path is not a file: ${path}`, FileSystemErrorCode.EISDIR);
|
|
247
|
+
|
|
248
|
+
let buffer: Buffer;
|
|
249
|
+
if (node.content) {
|
|
250
|
+
// 小文件优化 - content is already loaded
|
|
251
|
+
buffer = node.content;
|
|
252
|
+
} else {
|
|
253
|
+
// Large file: load from file_node_content table using QueryBuilder
|
|
254
|
+
const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
|
|
255
|
+
fileContentQb.where({ node: node });
|
|
256
|
+
const fileContent = await fileContentQb.getSingleResult();
|
|
257
|
+
if (!fileContent) throw new FileSystemError('File content is missing', FileSystemErrorCode.ENOENT);
|
|
258
|
+
buffer = fileContent.content;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return options?.encoding === 'text' ? buffer.toString('utf-8') : buffer;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async writeFile(path: string, data: string | Buffer, options: WriteFileOptions = {}): Promise<void> {
|
|
265
|
+
// Validate input
|
|
266
|
+
if (!path || typeof path !== 'string') {
|
|
267
|
+
throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
|
|
268
|
+
}
|
|
269
|
+
if (data === null || data === undefined) {
|
|
270
|
+
throw new FileSystemError('Invalid data', FileSystemErrorCode.EINVAL);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await this.em.transactional(async (em) => {
|
|
274
|
+
const { overwrite = true } = options;
|
|
275
|
+
const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf-8');
|
|
276
|
+
const size = bufferData.length;
|
|
277
|
+
|
|
278
|
+
const parentPath = dirname(path);
|
|
279
|
+
const filename = basename(path);
|
|
280
|
+
|
|
281
|
+
// Validate filename
|
|
282
|
+
if (!filename) {
|
|
283
|
+
throw new FileSystemError('filename cannot be empty', FileSystemErrorCode.EINVAL);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 确保父目录存在 - create it within the transaction
|
|
287
|
+
await this._mkdirInTransaction(parentPath, { recursive: true }, em);
|
|
288
|
+
const parentNode = await this._getNodeByPath(parentPath, em);
|
|
289
|
+
if (!parentNode) throw new FileSystemError('Failed to establish parent directory', FileSystemErrorCode.EINVAL);
|
|
290
|
+
|
|
291
|
+
// Find existing node using QueryBuilder
|
|
292
|
+
const nodeQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
293
|
+
nodeQb.where({ parent: parentNode, filename });
|
|
294
|
+
let node = await nodeQb.getSingleResult();
|
|
295
|
+
|
|
296
|
+
if (node) {
|
|
297
|
+
// 文件已存在
|
|
298
|
+
if (!overwrite) throw new FileSystemError(`File already exists: ${path}`, FileSystemErrorCode.EEXIST);
|
|
299
|
+
if (node.kind === FileKind.directory)
|
|
300
|
+
throw new FileSystemError(`Cannot overwrite a directory with a file: ${path}`, FileSystemErrorCode.EISDIR);
|
|
301
|
+
|
|
302
|
+
// 更新节点
|
|
303
|
+
node.size = size;
|
|
304
|
+
node.mtime = new Date();
|
|
305
|
+
// ... 其他时间戳
|
|
306
|
+
} else {
|
|
307
|
+
// 新建文件
|
|
308
|
+
const now = new Date();
|
|
309
|
+
node = em.create(FileNodeMetaEntity, {
|
|
310
|
+
tid: parentNode.tid,
|
|
311
|
+
filename,
|
|
312
|
+
parent: parentNode,
|
|
313
|
+
kind: FileKind.file,
|
|
314
|
+
size,
|
|
315
|
+
atime: now,
|
|
316
|
+
btime: now,
|
|
317
|
+
ctime: now,
|
|
318
|
+
mtime: now,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 处理文件内容
|
|
323
|
+
if (size <= this.options.smallFileThreshold!) {
|
|
324
|
+
// Small file: store in file_node_meta.content
|
|
325
|
+
node.content = bufferData;
|
|
326
|
+
// If there was large file content, delete it
|
|
327
|
+
// Use QueryBuilder to avoid relationship issues
|
|
328
|
+
const existingContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
|
|
329
|
+
existingContentQb.where({ node: node });
|
|
330
|
+
const existingContent = await existingContentQb.getSingleResult();
|
|
331
|
+
if (existingContent) {
|
|
332
|
+
await em.removeAndFlush(existingContent);
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
// Large file: store in file_node_content table
|
|
336
|
+
node.content = undefined; // Clear small file content
|
|
337
|
+
const md5 = crypto.createHash('md5').update(bufferData).digest('hex');
|
|
338
|
+
const sha256 = crypto.createHash('sha256').update(bufferData).digest('hex');
|
|
339
|
+
|
|
340
|
+
// Check if fileContent already exists using QueryBuilder
|
|
341
|
+
const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
|
|
342
|
+
fileContentQb.where({ node: node });
|
|
343
|
+
let fileContent = await fileContentQb.getSingleResult();
|
|
344
|
+
|
|
345
|
+
if (fileContent) {
|
|
346
|
+
// Update existing content
|
|
347
|
+
fileContent.content = bufferData;
|
|
348
|
+
fileContent.size = size;
|
|
349
|
+
fileContent.md5 = md5;
|
|
350
|
+
fileContent.sha256 = sha256;
|
|
351
|
+
} else {
|
|
352
|
+
// Create new content entity
|
|
353
|
+
fileContent = em.create(FileNodeContentEntity, {
|
|
354
|
+
node: node, // Use node relationship as primary key
|
|
355
|
+
tid: node.tid,
|
|
356
|
+
content: bufferData,
|
|
357
|
+
size: size,
|
|
358
|
+
md5: md5,
|
|
359
|
+
sha256: sha256,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await em.persistAndFlush(node);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async rm(path: string, options: RmOptions = {}): Promise<void> {
|
|
369
|
+
await this.em.transactional(async (em) => {
|
|
370
|
+
const node = await this._getNodeByPath(path, em);
|
|
371
|
+
if (!node) {
|
|
372
|
+
if (options.force) return; // force=true, 不存在也算成功
|
|
373
|
+
throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (node.kind === FileKind.directory && !options.recursive) {
|
|
377
|
+
// Check if directory has children using QueryBuilder
|
|
378
|
+
const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
379
|
+
childrenQb.where({ parent: node });
|
|
380
|
+
childrenQb.select('id');
|
|
381
|
+
const children = await childrenQb.getResult();
|
|
382
|
+
if (children.length > 0) {
|
|
383
|
+
throw new FileSystemError(`Directory not empty: ${path}`, FileSystemErrorCode.ENOTEMPTY);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// 使用 orphanRemoval: true, ORM会自动处理子节点和内容的删除
|
|
388
|
+
await em.removeAndFlush(node);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async rename(oldPath: string, newPath: string, options: RenameOptions = {}): Promise<void> {
|
|
393
|
+
await this.em.transactional(async (em) => {
|
|
394
|
+
const node = await this._getNodeByPath(oldPath, em);
|
|
395
|
+
if (!node) throw new FileSystemError(`Source path not found: ${oldPath}`, FileSystemErrorCode.ENOENT);
|
|
396
|
+
|
|
397
|
+
const newParentPath = dirname(newPath);
|
|
398
|
+
const newFilename = basename(newPath);
|
|
399
|
+
|
|
400
|
+
const newParentNode = await this._getNodeByPath(newParentPath, em);
|
|
401
|
+
if (!newParentNode)
|
|
402
|
+
throw new FileSystemError(`Destination directory not found: ${newParentPath}`, FileSystemErrorCode.ENOENT);
|
|
403
|
+
|
|
404
|
+
const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
405
|
+
existingDestQb.where({ parent: newParentNode, filename: newFilename });
|
|
406
|
+
const existingDest = await existingDestQb.getSingleResult();
|
|
407
|
+
if (existingDest) {
|
|
408
|
+
if (!options.overwrite)
|
|
409
|
+
throw new FileSystemError(`Destination path already exists: ${newPath}`, FileSystemErrorCode.EEXIST);
|
|
410
|
+
if (node.id === existingDest.id) return; // 移动到原位置,什么都不做
|
|
411
|
+
await em.removeAndFlush(existingDest);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
node.parent = newParentNode;
|
|
415
|
+
node.filename = newFilename;
|
|
416
|
+
node.mtime = new Date();
|
|
417
|
+
|
|
418
|
+
await em.flush();
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async copy(srcPath: string, destPath: string, options: CopyOptions = {}): Promise<void> {
|
|
423
|
+
await this.em.transactional(async (em) => {
|
|
424
|
+
const srcNode = await this._getNodeByPath(srcPath, em);
|
|
425
|
+
if (!srcNode) throw new FileSystemError(`Source path not found: ${srcPath}`, FileSystemErrorCode.ENOENT);
|
|
426
|
+
|
|
427
|
+
const destParentPath = dirname(destPath);
|
|
428
|
+
const destFilename = basename(destPath);
|
|
429
|
+
|
|
430
|
+
const destParentNode = await this._getNodeByPath(destParentPath, em);
|
|
431
|
+
if (!destParentNode)
|
|
432
|
+
throw new FileSystemError(`Destination directory not found: ${destParentPath}`, FileSystemErrorCode.ENOENT);
|
|
433
|
+
|
|
434
|
+
const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
435
|
+
existingDestQb.where({ parent: destParentNode, filename: destFilename });
|
|
436
|
+
const existingDest = await existingDestQb.getSingleResult();
|
|
437
|
+
if (existingDest) {
|
|
438
|
+
if (!options.overwrite)
|
|
439
|
+
throw new FileSystemError(`Destination path already exists: ${destPath}`, FileSystemErrorCode.EEXIST);
|
|
440
|
+
// Delete existing destination within the same transaction
|
|
441
|
+
await em.removeAndFlush(existingDest);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
await this._copyNode(srcNode, destParentNode, destFilename, em);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
createReadStream(path: string, options?: CreateReadStreamOptions): never {
|
|
449
|
+
throw new Error('Streaming read is not supported by DBFS yet.');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
createWriteStream(path: string, options?: CreateWriteStreamOptions): never {
|
|
453
|
+
throw new Error('Streaming write is not supported by DBFS yet.');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
createReadableStream(path: string, options?: CreateReadStreamOptions): ReadableStream {
|
|
457
|
+
throw new Error('ReadableStream is not supported by DBFS yet.');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
createWritableStream(path: string, options?: CreateWriteStreamOptions): WritableStream {
|
|
461
|
+
throw new Error('WritableStream is not supported by DBFS yet.');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 将路径字符串解析为数据库中的节点。这是大部分操作的基础。
|
|
466
|
+
* @param pathStr 绝对路径, e.g., /home/user/file.txt
|
|
467
|
+
* @param em EntityManager 实例
|
|
468
|
+
* @returns 找到的节点或 null
|
|
469
|
+
*/
|
|
470
|
+
private async _getNodeByPath(pathStr: string, em: EntityManager): Promise<FileNodeMetaEntity | null> {
|
|
471
|
+
const normalized = normalize(pathStr);
|
|
472
|
+
|
|
473
|
+
if (normalized === '/') {
|
|
474
|
+
// Use QueryBuilder to avoid automatic relationship loading
|
|
475
|
+
const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
476
|
+
qb.where({ parent: null });
|
|
477
|
+
const rootNode = await qb.getSingleResult();
|
|
478
|
+
return rootNode || null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const parts = normalized.split('/').filter((p) => p);
|
|
482
|
+
|
|
483
|
+
// Get root node
|
|
484
|
+
const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
485
|
+
rootQb.where({ parent: null });
|
|
486
|
+
let currentNode = await rootQb.getSingleResult();
|
|
487
|
+
if (!currentNode) return null;
|
|
488
|
+
|
|
489
|
+
// Traverse path parts
|
|
490
|
+
for (const part of parts) {
|
|
491
|
+
const childQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
492
|
+
childQb.where({ parent: currentNode, filename: part });
|
|
493
|
+
const child = await childQb.getSingleResult();
|
|
494
|
+
if (!child) return null;
|
|
495
|
+
currentNode = child;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return currentNode;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* 将数据库实体转换为 IFileStat 接口
|
|
503
|
+
*/
|
|
504
|
+
private _toFileStat(node: FileNodeMetaEntity, path: string): IFileStat {
|
|
505
|
+
// Handle mtime - it might be a Date object or a string from raw query
|
|
506
|
+
let mtime: number;
|
|
507
|
+
if (node.mtime instanceof Date) {
|
|
508
|
+
mtime = node.mtime.getTime();
|
|
509
|
+
} else if (typeof node.mtime === 'string') {
|
|
510
|
+
mtime = new Date(node.mtime).getTime();
|
|
511
|
+
} else {
|
|
512
|
+
mtime = Date.now();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Handle size - convert bigint to number if needed
|
|
516
|
+
let size: number;
|
|
517
|
+
if (typeof node.size === 'bigint') {
|
|
518
|
+
size = Number(node.size);
|
|
519
|
+
} else {
|
|
520
|
+
size = node.size;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
path: path,
|
|
525
|
+
name: node.filename,
|
|
526
|
+
kind: node.kind,
|
|
527
|
+
size: size,
|
|
528
|
+
mtime: mtime,
|
|
529
|
+
meta: node.metadata || {},
|
|
530
|
+
// `directory` 字段可以根据 path 动态计算
|
|
531
|
+
directory: dirname(path),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private async _copyNode(
|
|
536
|
+
srcNode: FileNodeMetaEntity,
|
|
537
|
+
destParent: FileNodeMetaEntity,
|
|
538
|
+
newName: string,
|
|
539
|
+
em: EntityManager,
|
|
540
|
+
): Promise<void> {
|
|
541
|
+
// 1. 复制节点本身
|
|
542
|
+
const now = new Date();
|
|
543
|
+
const newNode = em.create(FileNodeMetaEntity, {
|
|
544
|
+
tid: srcNode.tid,
|
|
545
|
+
filename: newName,
|
|
546
|
+
parent: destParent,
|
|
547
|
+
kind: srcNode.kind,
|
|
548
|
+
size: srcNode.size,
|
|
549
|
+
metadata: srcNode.metadata, // deep copy metadata
|
|
550
|
+
content: srcNode.content, // copy small file content
|
|
551
|
+
atime: now,
|
|
552
|
+
btime: now,
|
|
553
|
+
ctime: now,
|
|
554
|
+
mtime: now,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// 2. 复制大文件内容 (如果存在) - use QueryBuilder to avoid relationship loading
|
|
558
|
+
if (!srcNode.content) {
|
|
559
|
+
const srcFileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
|
|
560
|
+
srcFileContentQb.where({ node: srcNode });
|
|
561
|
+
const srcFileContent = await srcFileContentQb.getSingleResult();
|
|
562
|
+
if (srcFileContent) {
|
|
563
|
+
const newContent = em.create(FileNodeContentEntity, {
|
|
564
|
+
node: newNode, // Use node relationship as primary key
|
|
565
|
+
tid: srcNode.tid,
|
|
566
|
+
content: srcFileContent.content,
|
|
567
|
+
size: srcFileContent.size,
|
|
568
|
+
md5: srcFileContent.md5,
|
|
569
|
+
sha256: srcFileContent.sha256,
|
|
570
|
+
mimeType: srcFileContent.mimeType,
|
|
571
|
+
metadata: srcFileContent.metadata,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
await em.persistAndFlush(newNode);
|
|
577
|
+
|
|
578
|
+
// 3. 如果是目录,递归复制子节点
|
|
579
|
+
if (srcNode.kind === FileKind.directory) {
|
|
580
|
+
const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
|
|
581
|
+
childrenQb.where({ parent: srcNode });
|
|
582
|
+
const children = await childrenQb.getResult();
|
|
583
|
+
for (const child of children) {
|
|
584
|
+
await this._copyNode(child, newNode, child.filename, em);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
@Entity({ tableName: 'file_node_meta' })
|
|
591
|
+
@Unique({ properties: ['tid', 'parent', 'filename'] })
|
|
592
|
+
export class FileNodeMetaEntity extends TenantBaseEntity {
|
|
593
|
+
@Property({ type: types.string, nullable: false, comment: '文件名' })
|
|
594
|
+
filename!: string;
|
|
595
|
+
@Property({ type: types.bigint, nullable: false, default: 0, comment: '文件大小' })
|
|
596
|
+
size!: number & Opt;
|
|
597
|
+
@Property({ type: types.string, nullable: false, comment: '文件类型' })
|
|
598
|
+
kind!: FileKind;
|
|
599
|
+
|
|
600
|
+
@Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
|
|
601
|
+
atime!: Date & Opt;
|
|
602
|
+
@Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
|
|
603
|
+
btime!: Date & Opt;
|
|
604
|
+
@Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
|
|
605
|
+
ctime!: Date & Opt;
|
|
606
|
+
@Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
|
|
607
|
+
mtime!: Date & Opt;
|
|
608
|
+
|
|
609
|
+
@Property({ type: types.json, nullable: false, defaultRaw: '{}' })
|
|
610
|
+
metadata!: Record<string, any>;
|
|
611
|
+
|
|
612
|
+
@ManyToOne(() => FileNodeMetaEntity, { nullable: true, cascade: [] })
|
|
613
|
+
parent?: Rel<FileNodeMetaEntity>;
|
|
614
|
+
|
|
615
|
+
@OneToMany({ entity: () => FileNodeMetaEntity, mappedBy: 'parent', orphanRemoval: true })
|
|
616
|
+
children = new Collection<FileNodeMetaEntity>(this);
|
|
617
|
+
|
|
618
|
+
@OneToOne({
|
|
619
|
+
entity: () => FileNodeContentEntity,
|
|
620
|
+
mappedBy: 'node',
|
|
621
|
+
orphanRemoval: true,
|
|
622
|
+
nullable: true,
|
|
623
|
+
cascade: [Cascade.ALL],
|
|
624
|
+
})
|
|
625
|
+
fileContent?: Rel<FileNodeContentEntity>;
|
|
626
|
+
@Property({ type: types.blob, nullable: true, comment: '文件内容' })
|
|
627
|
+
content?: Buffer; // for small file, e.g. < 64k
|
|
628
|
+
|
|
629
|
+
//region content
|
|
630
|
+
|
|
631
|
+
get parentId() {
|
|
632
|
+
return this.parent?.id as Opt<string | undefined>;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
//endregion
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
@Entity({ tableName: 'file_node_content' })
|
|
639
|
+
export class FileNodeContentEntity extends TenantBaseEntity {
|
|
640
|
+
@OneToOne({ entity: () => FileNodeMetaEntity, owner: true, joinColumn: 'node_id' })
|
|
641
|
+
node!: Rel<FileNodeMetaEntity>;
|
|
642
|
+
|
|
643
|
+
@Property({ type: types.integer, nullable: false })
|
|
644
|
+
size!: number; // 保留 size 方便查询分析
|
|
645
|
+
|
|
646
|
+
@Property({ type: types.blob, lazy: true, comment: '文件内容' })
|
|
647
|
+
content!: Buffer;
|
|
648
|
+
|
|
649
|
+
@Property({ type: types.string, nullable: true })
|
|
650
|
+
mimeType?: string;
|
|
651
|
+
|
|
652
|
+
@Property({ type: types.string, nullable: false })
|
|
653
|
+
md5?: string;
|
|
654
|
+
@Property({ type: types.string, nullable: false })
|
|
655
|
+
sha256?: string;
|
|
656
|
+
|
|
657
|
+
@Property({ type: types.string, nullable: true })
|
|
658
|
+
text?: string;
|
|
659
|
+
@Property({ type: types.integer, nullable: true })
|
|
660
|
+
width?: number;
|
|
661
|
+
@Property({ type: types.integer, nullable: true })
|
|
662
|
+
height?: number;
|
|
663
|
+
// @Property({ type: types.integer, nullable: true })
|
|
664
|
+
// length?: number;
|
|
665
|
+
|
|
666
|
+
@Property({ type: types.json, nullable: false, defaultRaw: '{}' })
|
|
667
|
+
metadata!: Record<string, any>;
|
|
668
|
+
}
|