@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,499 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createReadStream as nodeCreateReadStream,
|
|
3
|
+
createWriteStream as nodeCreateWriteStream,
|
|
4
|
+
type Stats,
|
|
5
|
+
} from 'node:fs';
|
|
6
|
+
import fsp from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { Readable, type Writable } from 'node:stream';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import type {
|
|
11
|
+
CopyOptions,
|
|
12
|
+
CreateReadStreamOptions,
|
|
13
|
+
CreateWriteStreamOptions,
|
|
14
|
+
IFileStat,
|
|
15
|
+
IFileSystem,
|
|
16
|
+
MkdirOptions,
|
|
17
|
+
ReaddirOptions,
|
|
18
|
+
ReadFileOptions,
|
|
19
|
+
RenameOptions,
|
|
20
|
+
RmOptions,
|
|
21
|
+
StatOptions,
|
|
22
|
+
WriteFileOptions,
|
|
23
|
+
} from '../IFileSystem';
|
|
24
|
+
|
|
25
|
+
export type INodeFileSystem = IFileSystem & {
|
|
26
|
+
readonly root: string;
|
|
27
|
+
resolvePath(filePath: string): string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a Node.js filesystem adapter that implements the IFileSystem interface
|
|
32
|
+
* @param options Configuration options for the filesystem
|
|
33
|
+
* @param options.root Optional root directory to restrict all operations within
|
|
34
|
+
*/
|
|
35
|
+
export function createNodeFileSystem(options: { root?: string } = {}): INodeFileSystem {
|
|
36
|
+
return new NodeFs(options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type IFS = typeof import('fs/promises');
|
|
40
|
+
|
|
41
|
+
class NodeFs implements IFileSystem, INodeFileSystem {
|
|
42
|
+
readonly root: string;
|
|
43
|
+
private readonly fs: IFS;
|
|
44
|
+
|
|
45
|
+
constructor({
|
|
46
|
+
root,
|
|
47
|
+
fs = fsp,
|
|
48
|
+
}: {
|
|
49
|
+
root?: string;
|
|
50
|
+
fs?: IFS;
|
|
51
|
+
} = {}) {
|
|
52
|
+
// Normalize the root path if provided
|
|
53
|
+
if (root) {
|
|
54
|
+
this.root = path.resolve(root);
|
|
55
|
+
} else {
|
|
56
|
+
this.root = '';
|
|
57
|
+
}
|
|
58
|
+
this.fs = fs;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Helper method to handle aborted signals consistently
|
|
62
|
+
private checkAborted(signal?: AbortSignal): void {
|
|
63
|
+
if (signal?.aborted) {
|
|
64
|
+
throw new Error('The operation was aborted');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves and secures a path by:
|
|
70
|
+
* 1. Normalizing the path
|
|
71
|
+
* 2. Prepending the root directory if one is set
|
|
72
|
+
* 3. Verifying the resulting path is within the root directory
|
|
73
|
+
*
|
|
74
|
+
* This prevents path traversal attacks.
|
|
75
|
+
*/
|
|
76
|
+
resolvePath(filePath: string): string {
|
|
77
|
+
// Handle empty paths
|
|
78
|
+
if (!filePath) {
|
|
79
|
+
return this.root || '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Normalize to remove any '..' segments and handle slashes
|
|
83
|
+
const normalizedPath = path.normalize(filePath);
|
|
84
|
+
|
|
85
|
+
// If no root is set, just return the normalized path
|
|
86
|
+
if (!this.root) {
|
|
87
|
+
return normalizedPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Join with root and resolve to absolute path
|
|
91
|
+
const resolvedPath = path.resolve(path.join(this.root, normalizedPath));
|
|
92
|
+
|
|
93
|
+
// Security check: ensure the path is within the root directory
|
|
94
|
+
if (!resolvedPath.startsWith(this.root)) {
|
|
95
|
+
throw new Error(`Security violation: Path ${filePath} attempts to access outside of the root directory`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return resolvedPath;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes the root prefix from a path for external representation
|
|
103
|
+
*/
|
|
104
|
+
private stripRoot(fullPath: string): string {
|
|
105
|
+
if (!this.root || !fullPath.startsWith(this.root)) {
|
|
106
|
+
return fullPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Remove the root prefix and ensure there's a leading slash
|
|
110
|
+
let relativePath = fullPath.substring(this.root.length);
|
|
111
|
+
if (!relativePath) {
|
|
112
|
+
return '/';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!relativePath.startsWith('/') && !relativePath.startsWith('\\')) {
|
|
116
|
+
relativePath = '/' + relativePath;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Normalize to ensure consistent path separators
|
|
120
|
+
return path.normalize(relativePath).replace(/\\/g, '/');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Converts a system file stat to our IFileStat interface,
|
|
125
|
+
* stripping the root prefix from paths
|
|
126
|
+
*/
|
|
127
|
+
private toFileStat(fullPath: string, fsStats: Stats): IFileStat {
|
|
128
|
+
const normalizedPath = this.stripRoot(fullPath);
|
|
129
|
+
const directoryPath = path.dirname(normalizedPath);
|
|
130
|
+
const directory = directoryPath === '.' ? '/' : directoryPath.replace(/\\/g, '/');
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
directory,
|
|
134
|
+
path: normalizedPath,
|
|
135
|
+
name: path.basename(fullPath),
|
|
136
|
+
kind: fsStats.isDirectory() ? 'directory' : 'file',
|
|
137
|
+
mtime: fsStats.mtimeMs,
|
|
138
|
+
size: fsStats.size,
|
|
139
|
+
meta: {},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async readdir(dir: string, options: ReaddirOptions = {}): Promise<IFileStat[]> {
|
|
144
|
+
const { fs } = this;
|
|
145
|
+
const { glob, recursive, depth = 1, kind, hidden = true, signal } = options;
|
|
146
|
+
this.checkAborted(signal);
|
|
147
|
+
|
|
148
|
+
// Resolve the directory path with security checks
|
|
149
|
+
const resolvedDir = this.resolvePath(dir);
|
|
150
|
+
|
|
151
|
+
// Basic file listing
|
|
152
|
+
const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
|
|
153
|
+
let results = await Promise.all(
|
|
154
|
+
entries
|
|
155
|
+
.filter((entry) => hidden || !entry.name.startsWith('.'))
|
|
156
|
+
.filter((entry) => !kind || (kind === 'directory' ? entry.isDirectory() : entry.isFile()))
|
|
157
|
+
.map(async (entry) => {
|
|
158
|
+
this.checkAborted(signal);
|
|
159
|
+
|
|
160
|
+
const entryFullPath = path.join(resolvedDir, entry.name);
|
|
161
|
+
const stat = await fs.stat(entryFullPath);
|
|
162
|
+
|
|
163
|
+
// Convert to external representation
|
|
164
|
+
return {
|
|
165
|
+
directory: this.stripRoot(resolvedDir),
|
|
166
|
+
path: this.stripRoot(entryFullPath),
|
|
167
|
+
name: entry.name,
|
|
168
|
+
kind: entry.isDirectory() ? 'directory' : 'file',
|
|
169
|
+
mtime: stat.mtimeMs,
|
|
170
|
+
size: stat.size,
|
|
171
|
+
meta: {},
|
|
172
|
+
} as IFileStat;
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Handle recursive option
|
|
177
|
+
if (recursive || depth > 1) {
|
|
178
|
+
const subdirs = results.filter((entry) => entry.kind === 'directory');
|
|
179
|
+
|
|
180
|
+
for (const subdir of subdirs) {
|
|
181
|
+
this.checkAborted(signal);
|
|
182
|
+
|
|
183
|
+
const maxDepth = recursive ? Infinity : depth - 1;
|
|
184
|
+
if (maxDepth > 0) {
|
|
185
|
+
// Need to convert the path back to a full path for the recursive call
|
|
186
|
+
const subdirFullPath = this.resolvePath(subdir.path);
|
|
187
|
+
|
|
188
|
+
const subEntries = await this.readdir(subdir.path, {
|
|
189
|
+
...options,
|
|
190
|
+
depth: maxDepth,
|
|
191
|
+
});
|
|
192
|
+
results = [...results, ...subEntries];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle glob filtering
|
|
198
|
+
if (glob) {
|
|
199
|
+
const { matcher } = await import('micromatch');
|
|
200
|
+
const match = matcher(glob);
|
|
201
|
+
results = results.filter((entry) => match(entry.path));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async stat(filePath: string, options: StatOptions = {}): Promise<IFileStat> {
|
|
208
|
+
const { fs } = this;
|
|
209
|
+
|
|
210
|
+
const { signal } = options;
|
|
211
|
+
this.checkAborted(signal);
|
|
212
|
+
|
|
213
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const stat = await fs.stat(resolvedPath);
|
|
217
|
+
return this.toFileStat(resolvedPath, stat);
|
|
218
|
+
} catch (err: any) {
|
|
219
|
+
if (err.code === 'ENOENT') {
|
|
220
|
+
throw new Error(`File not found: ${filePath}`);
|
|
221
|
+
}
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async mkdir(dirPath: string, options: MkdirOptions = {}): Promise<void> {
|
|
227
|
+
const { fs } = this;
|
|
228
|
+
|
|
229
|
+
const { recursive = false, signal } = options;
|
|
230
|
+
this.checkAborted(signal);
|
|
231
|
+
|
|
232
|
+
const resolvedPath = this.resolvePath(dirPath);
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
await fs.mkdir(resolvedPath, { recursive });
|
|
236
|
+
} catch (err: any) {
|
|
237
|
+
if (err.code === 'EEXIST' && recursive) {
|
|
238
|
+
// Ignore if directory exists and recursive is true
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async readFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;
|
|
246
|
+
async readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
247
|
+
async readFile(path: string, options: ReadFileOptions = {}): Promise<string | Uint8Array> {
|
|
248
|
+
const { fs } = this;
|
|
249
|
+
|
|
250
|
+
const { encoding = 'binary', signal, onDownloadProgress } = options;
|
|
251
|
+
this.checkAborted(signal);
|
|
252
|
+
|
|
253
|
+
const resolvedPath = this.resolvePath(path);
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Handle progress reporting if needed
|
|
257
|
+
if (onDownloadProgress) {
|
|
258
|
+
const stat = await fs.stat(resolvedPath);
|
|
259
|
+
const stream = this.createReadStream(path, { signal });
|
|
260
|
+
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
const chunks: Buffer[] = [];
|
|
263
|
+
let loaded = 0;
|
|
264
|
+
|
|
265
|
+
stream.on('data', (chunk) => {
|
|
266
|
+
chunks.push(Buffer.from(chunk));
|
|
267
|
+
loaded += chunk.length;
|
|
268
|
+
onDownloadProgress({ loaded, total: stat.size });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
stream.on('end', () => {
|
|
272
|
+
const buffer = Buffer.concat(chunks);
|
|
273
|
+
if (encoding === 'text') {
|
|
274
|
+
resolve(buffer.toString('utf-8'));
|
|
275
|
+
} else {
|
|
276
|
+
resolve(buffer);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
stream.on('error', reject);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Standard file reading
|
|
285
|
+
if (encoding === 'text') {
|
|
286
|
+
return await fs.readFile(resolvedPath, { encoding: 'utf-8' });
|
|
287
|
+
} else {
|
|
288
|
+
return await fs.readFile(resolvedPath);
|
|
289
|
+
}
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
if (err.code === 'ENOENT') {
|
|
292
|
+
throw new Error(`File not found: ${path}`);
|
|
293
|
+
}
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async writeFile(
|
|
299
|
+
path: string,
|
|
300
|
+
data: string | Buffer | ArrayBuffer | Readable,
|
|
301
|
+
options: WriteFileOptions = {},
|
|
302
|
+
): Promise<void> {
|
|
303
|
+
const { fs } = this;
|
|
304
|
+
|
|
305
|
+
const { signal, overwrite = true, onUploadProgress } = options;
|
|
306
|
+
this.checkAborted(signal);
|
|
307
|
+
|
|
308
|
+
const resolvedPath = this.resolvePath(path);
|
|
309
|
+
|
|
310
|
+
// Check if file exists and overwrite is false
|
|
311
|
+
if (!overwrite) {
|
|
312
|
+
const exists = await this.exists(path);
|
|
313
|
+
if (exists) {
|
|
314
|
+
throw new Error(`File already exists: ${path}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Create parent directories if they don't exist
|
|
319
|
+
const directory = this.getDirectoryName(resolvedPath);
|
|
320
|
+
if (directory !== resolvedPath) {
|
|
321
|
+
try {
|
|
322
|
+
await this.mkdir(this.stripRoot(directory), { recursive: true });
|
|
323
|
+
} catch (err: any) {
|
|
324
|
+
// Ignore directory exists error
|
|
325
|
+
if (err?.code !== 'EEXIST') {
|
|
326
|
+
throw err;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (data instanceof Readable) {
|
|
332
|
+
let _data = data;
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
334
|
+
const writeStream = this.createWriteStream(path, options);
|
|
335
|
+
let totalBytes = 0;
|
|
336
|
+
|
|
337
|
+
if (onUploadProgress) {
|
|
338
|
+
_data.on('data', (chunk) => {
|
|
339
|
+
totalBytes += chunk.length;
|
|
340
|
+
onUploadProgress({ loaded: totalBytes, total: -1 });
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_data.pipe(writeStream);
|
|
345
|
+
|
|
346
|
+
writeStream.on('finish', () => resolve());
|
|
347
|
+
writeStream.on('error', reject);
|
|
348
|
+
|
|
349
|
+
if (signal) {
|
|
350
|
+
signal.addEventListener('abort', () => {
|
|
351
|
+
_data.destroy();
|
|
352
|
+
writeStream.destroy();
|
|
353
|
+
reject(new Error('The operation was aborted'));
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
} else {
|
|
358
|
+
// Convert ArrayBuffer to Buffer if necessary
|
|
359
|
+
if (data instanceof ArrayBuffer) {
|
|
360
|
+
data = Buffer.from(data);
|
|
361
|
+
}
|
|
362
|
+
await fs.writeFile(resolvedPath, data);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async rm(path: string, options: RmOptions = {}): Promise<void> {
|
|
367
|
+
const { fs } = this;
|
|
368
|
+
|
|
369
|
+
const { recursive = false, force = false, signal } = options;
|
|
370
|
+
this.checkAborted(signal);
|
|
371
|
+
|
|
372
|
+
const resolvedPath = this.resolvePath(path);
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
await fs.rm(resolvedPath, { recursive, force });
|
|
376
|
+
} catch (err: any) {
|
|
377
|
+
if (force && err.code === 'ENOENT') {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
throw err;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async rename(oldPath: string, newPath: string, options: RenameOptions = {}): Promise<void> {
|
|
385
|
+
const { fs } = this;
|
|
386
|
+
|
|
387
|
+
const { signal, overwrite = false } = options;
|
|
388
|
+
this.checkAborted(signal);
|
|
389
|
+
|
|
390
|
+
const resolvedOldPath = this.resolvePath(oldPath);
|
|
391
|
+
const resolvedNewPath = this.resolvePath(newPath);
|
|
392
|
+
|
|
393
|
+
// Check if target exists and overwrite is false
|
|
394
|
+
if (!overwrite) {
|
|
395
|
+
const exists = await this.exists(newPath);
|
|
396
|
+
if (exists) {
|
|
397
|
+
throw new Error(`Destination already exists: ${newPath}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
await fs.rename(resolvedOldPath, resolvedNewPath);
|
|
403
|
+
} catch (err: any) {
|
|
404
|
+
if (err.code === 'ENOENT') {
|
|
405
|
+
throw new Error(`Source file not found: ${oldPath}`);
|
|
406
|
+
}
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async exists(path: string): Promise<boolean> {
|
|
412
|
+
const { fs } = this;
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
await fs.access(this.resolvePath(path));
|
|
416
|
+
return true;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async copy(src: string, dest: string, options: CopyOptions = {}): Promise<void> {
|
|
423
|
+
const { fs } = this;
|
|
424
|
+
|
|
425
|
+
const { signal, overwrite = true, shallow = false } = options;
|
|
426
|
+
this.checkAborted(signal);
|
|
427
|
+
|
|
428
|
+
const resolvedSrc = this.resolvePath(src);
|
|
429
|
+
const resolvedDest = this.resolvePath(dest);
|
|
430
|
+
|
|
431
|
+
// Check if source exists
|
|
432
|
+
try {
|
|
433
|
+
const stat = await fs.stat(resolvedSrc);
|
|
434
|
+
|
|
435
|
+
// Check if destination exists and overwrite is false
|
|
436
|
+
if (!overwrite) {
|
|
437
|
+
const exists = await this.exists(dest);
|
|
438
|
+
if (exists) {
|
|
439
|
+
throw new Error(`Destination already exists: ${dest}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Create parent directory if it doesn't exist
|
|
444
|
+
const parentDir = this.getDirectoryName(resolvedDest);
|
|
445
|
+
await this.mkdir(this.stripRoot(parentDir), { recursive: true });
|
|
446
|
+
|
|
447
|
+
// Copy recursively or not based on shallow and if it's a directory
|
|
448
|
+
await fs.cp(resolvedSrc, resolvedDest, {
|
|
449
|
+
recursive: !shallow && stat.isDirectory(),
|
|
450
|
+
force: overwrite,
|
|
451
|
+
});
|
|
452
|
+
} catch (err: any) {
|
|
453
|
+
if (err.code === 'ENOENT') {
|
|
454
|
+
throw new Error(`Source file not found: ${src}`);
|
|
455
|
+
}
|
|
456
|
+
throw err;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
createReadStream(filePath: string, options: CreateReadStreamOptions = {}): Readable {
|
|
461
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
462
|
+
const { signal, range } = options;
|
|
463
|
+
const stream = nodeCreateReadStream(resolvedPath, {
|
|
464
|
+
start: range?.start,
|
|
465
|
+
end: range?.end,
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
signal?.addEventListener('abort', () => stream.destroy(new Error('The operation was aborted')));
|
|
469
|
+
|
|
470
|
+
return stream;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
createWriteStream(filePath: string, options: CreateWriteStreamOptions = {}): Writable {
|
|
474
|
+
const resolvedPath = this.resolvePath(filePath);
|
|
475
|
+
const { signal } = options;
|
|
476
|
+
const stream = nodeCreateWriteStream(resolvedPath, { flags: options.overwrite === false ? 'wx' : 'w' });
|
|
477
|
+
|
|
478
|
+
signal?.addEventListener('abort', () => stream.destroy(new Error('The operation was aborted')));
|
|
479
|
+
|
|
480
|
+
return stream;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Helper to extract directory name correctly
|
|
484
|
+
private getDirectoryName(filePath: string): string {
|
|
485
|
+
return path.dirname(filePath);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
getUrl(needle: IFileStat | string) {
|
|
489
|
+
if (typeof needle === 'object' && needle?.kind !== 'file') {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
let path = typeof needle === 'string' ? needle : needle.path;
|
|
493
|
+
if (!path) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
// file://
|
|
497
|
+
return pathToFileURL(this.resolvePath(path)).toString();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/knex';
|
|
2
|
+
import { beforeEach, describe, test } from 'vitest';
|
|
3
|
+
import { runFileSystemTest } from '../tests/runFileSystemTest';
|
|
4
|
+
import { createDatabaseFileSystem, FileNodeMetaEntity } from './createDatabaseFileSystem';
|
|
5
|
+
import { loadTestDatabase } from './loadTestDatabase';
|
|
6
|
+
|
|
7
|
+
describe('DatabaseFileSystem', () => {
|
|
8
|
+
let fs: ReturnType<typeof createDatabaseFileSystem>;
|
|
9
|
+
let em: EntityManager;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
const { em: entityManager } = await loadTestDatabase();
|
|
13
|
+
em = entityManager as any; // Type cast to match expected EntityManager type
|
|
14
|
+
|
|
15
|
+
fs = createDatabaseFileSystem({
|
|
16
|
+
getEntityManager: () => em as any,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Setup initial state: ensure root directory exists and create /README.txt
|
|
20
|
+
const rootNode = await fs.ensureRootNode();
|
|
21
|
+
|
|
22
|
+
// Create /README.txt file using EntityManager
|
|
23
|
+
const content = Buffer.from('Hello');
|
|
24
|
+
const readmeFile = em.create(FileNodeMetaEntity, {
|
|
25
|
+
filename: 'README.txt',
|
|
26
|
+
parent: rootNode,
|
|
27
|
+
kind: 'file',
|
|
28
|
+
size: content.length,
|
|
29
|
+
content: content,
|
|
30
|
+
atime: new Date(),
|
|
31
|
+
btime: new Date(),
|
|
32
|
+
ctime: new Date(),
|
|
33
|
+
mtime: new Date(),
|
|
34
|
+
});
|
|
35
|
+
await em.persistAndFlush(readmeFile);
|
|
36
|
+
}, 30000); // Increase timeout to 30 seconds
|
|
37
|
+
|
|
38
|
+
test('common tests', async () => {
|
|
39
|
+
await runFileSystemTest(fs, {
|
|
40
|
+
writableStream: false,
|
|
41
|
+
readableStream: false,
|
|
42
|
+
readStream: false,
|
|
43
|
+
writeStream: false,
|
|
44
|
+
abort: false,
|
|
45
|
+
});
|
|
46
|
+
}, 60000); // Increase timeout to 60 seconds
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNodeFileSystem } from './createNodeFileSystem';
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
import type { Options as BetterSqliteOptions } from '@mikro-orm/better-sqlite';
|
|
3
|
+
import { Errors, ulid } from '@wener/utils';
|
|
4
|
+
import type { Database } from 'better-sqlite3';
|
|
5
|
+
import { FileNodeContentEntity, FileNodeMetaEntity } from './createDatabaseFileSystem';
|
|
6
|
+
|
|
7
|
+
export async function loadTestDatabase({ options }: { options?: BetterSqliteOptions } = {}) {
|
|
8
|
+
const { MikroORM: SqliteMikroORM } = await import('@mikro-orm/better-sqlite');
|
|
9
|
+
|
|
10
|
+
const orm = await SqliteMikroORM.init({
|
|
11
|
+
dbName: ':memory:',
|
|
12
|
+
entities: [FileNodeContentEntity, FileNodeMetaEntity],
|
|
13
|
+
discovery: {
|
|
14
|
+
disableDynamicFileAccess: true,
|
|
15
|
+
requireEntitiesArray: true,
|
|
16
|
+
},
|
|
17
|
+
serialization: {
|
|
18
|
+
includePrimaryKeys: true,
|
|
19
|
+
forceObject: true,
|
|
20
|
+
},
|
|
21
|
+
findOneOrFailHandler(entityName, where) {
|
|
22
|
+
throw Errors.NotFound.asError(`未找到数据: ${entityName} ${inspect(where)}`);
|
|
23
|
+
},
|
|
24
|
+
findExactlyOneOrFailHandler(entityName, where) {
|
|
25
|
+
throw Errors.BadRequest.asError(`错误的数据数量: ${entityName} ${inspect(where)}`);
|
|
26
|
+
},
|
|
27
|
+
...options,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const knex = orm.em.getKnex();
|
|
31
|
+
{
|
|
32
|
+
const db: Database = await knex.client.acquireConnection();
|
|
33
|
+
db.function('ulid', () => ulid());
|
|
34
|
+
await knex.client.releaseConnection(db);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Execute schema creation
|
|
38
|
+
for (const stmt of FileSystemSchema.split(';')) {
|
|
39
|
+
const sql = stmt.trim();
|
|
40
|
+
if (sql) {
|
|
41
|
+
await knex.raw(sql);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
orm,
|
|
47
|
+
em: orm.em.fork(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const FileSystemSchema = `
|
|
52
|
+
CREATE TABLE IF NOT EXISTS "file_node_meta"
|
|
53
|
+
(
|
|
54
|
+
-- Base fields
|
|
55
|
+
"id" TEXT PRIMARY KEY NOT NULL DEFAULT (ulid()),
|
|
56
|
+
"tid" TEXT,
|
|
57
|
+
"uid" TEXT,
|
|
58
|
+
"eid" TEXT,
|
|
59
|
+
"created_at" TEXT NOT NULL DEFAULT (datetime('now')),
|
|
60
|
+
"updated_at" TEXT NOT NULL DEFAULT (datetime('now')),
|
|
61
|
+
"deleted_at" TEXT,
|
|
62
|
+
"attributes" TEXT,
|
|
63
|
+
"properties" TEXT,
|
|
64
|
+
"extensions" TEXT,
|
|
65
|
+
|
|
66
|
+
-- File identification
|
|
67
|
+
"filename" TEXT NOT NULL,
|
|
68
|
+
"size" INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
"kind" TEXT NOT NULL,
|
|
70
|
+
|
|
71
|
+
-- Timestamps
|
|
72
|
+
"atime" TEXT NOT NULL,
|
|
73
|
+
"btime" TEXT NOT NULL,
|
|
74
|
+
"ctime" TEXT NOT NULL,
|
|
75
|
+
"mtime" TEXT NOT NULL,
|
|
76
|
+
|
|
77
|
+
-- Metadata
|
|
78
|
+
"metadata" TEXT NOT NULL DEFAULT '{}',
|
|
79
|
+
|
|
80
|
+
-- Hierarchy
|
|
81
|
+
"parent_id" TEXT,
|
|
82
|
+
|
|
83
|
+
-- Small file content (for files < 64KB)
|
|
84
|
+
"content" BLOB,
|
|
85
|
+
|
|
86
|
+
FOREIGN KEY ("parent_id") REFERENCES "file_node_meta" ("id")
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
CREATE INDEX IF NOT EXISTS "idx_file_node_meta_filename" ON "file_node_meta" ("filename");
|
|
90
|
+
CREATE INDEX IF NOT EXISTS "idx_file_node_meta_parent_id" ON "file_node_meta" ("parent_id");
|
|
91
|
+
CREATE INDEX IF NOT EXISTS "idx_file_node_meta_tid_parent_filename" ON "file_node_meta" ("tid", "parent_id", "filename");
|
|
92
|
+
|
|
93
|
+
CREATE TABLE IF NOT EXISTS "file_node_content"
|
|
94
|
+
(
|
|
95
|
+
-- Base fields
|
|
96
|
+
"id" TEXT PRIMARY KEY NOT NULL DEFAULT (ulid()),
|
|
97
|
+
"node_id" TEXT NOT NULL UNIQUE,
|
|
98
|
+
"tid" TEXT,
|
|
99
|
+
"uid" TEXT,
|
|
100
|
+
"eid" TEXT,
|
|
101
|
+
"created_at" TEXT NOT NULL DEFAULT (datetime('now')),
|
|
102
|
+
"updated_at" TEXT NOT NULL DEFAULT (datetime('now')),
|
|
103
|
+
"deleted_at" TEXT,
|
|
104
|
+
"attributes" TEXT,
|
|
105
|
+
"properties" TEXT,
|
|
106
|
+
"extensions" TEXT,
|
|
107
|
+
|
|
108
|
+
-- Content information
|
|
109
|
+
"size" INTEGER NOT NULL,
|
|
110
|
+
"content" BLOB,
|
|
111
|
+
|
|
112
|
+
-- File properties
|
|
113
|
+
"mime_type" TEXT,
|
|
114
|
+
|
|
115
|
+
-- Checksums
|
|
116
|
+
"md5" TEXT,
|
|
117
|
+
"sha256" TEXT,
|
|
118
|
+
|
|
119
|
+
-- Content metadata
|
|
120
|
+
"text" TEXT,
|
|
121
|
+
"width" INTEGER,
|
|
122
|
+
"height" INTEGER,
|
|
123
|
+
|
|
124
|
+
-- Additional metadata
|
|
125
|
+
"metadata" TEXT NOT NULL DEFAULT '{}',
|
|
126
|
+
|
|
127
|
+
FOREIGN KEY ("node_id") REFERENCES "file_node_meta" ("id") ON DELETE CASCADE
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS "idx_file_node_content_node_id" ON "file_node_content" ("node_id");
|
|
131
|
+
`;
|