@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,288 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
import { FileSystemError, FileSystemErrorCode } from '../FileSystemError';
|
|
3
|
+
import type { IFileSystem } from '../IFileSystem';
|
|
4
|
+
|
|
5
|
+
export type RunFileSystemTestOptions = {
|
|
6
|
+
writableStream?: boolean;
|
|
7
|
+
readableStream?: boolean;
|
|
8
|
+
readStream?: boolean;
|
|
9
|
+
writeStream?: boolean;
|
|
10
|
+
abort?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function runFileSystemTest(fs: IFileSystem, options: RunFileSystemTestOptions = {}) {
|
|
14
|
+
const { writableStream = true, readableStream = true, readStream = true, writeStream = true, abort = true } = options;
|
|
15
|
+
// should be implemented
|
|
16
|
+
const stat = await fs.stat('/README.txt');
|
|
17
|
+
expect(stat).toMatchObject({
|
|
18
|
+
path: '/README.txt',
|
|
19
|
+
directory: '/',
|
|
20
|
+
name: 'README.txt',
|
|
21
|
+
kind: 'file',
|
|
22
|
+
size: 5,
|
|
23
|
+
});
|
|
24
|
+
await fs.mkdir('/');
|
|
25
|
+
const entries = await fs.readdir('/');
|
|
26
|
+
expect(entries.some((e) => e.name === 'README.txt')).toBe(true);
|
|
27
|
+
await fs.mkdir('/test');
|
|
28
|
+
|
|
29
|
+
if (fs.getUrl) {
|
|
30
|
+
expect(fs.getUrl(await fs.stat('/README.txt'))).toBeTypeOf('string');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// should handle AbortSignal
|
|
34
|
+
if (abort) {
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
controller.abort();
|
|
37
|
+
|
|
38
|
+
await expect(fs.stat('/README.txt', { signal: controller.signal })).rejects.toThrow(Error);
|
|
39
|
+
await expect(fs.readdir('/', { signal: controller.signal })).rejects.toThrow(Error);
|
|
40
|
+
await expect(fs.mkdir('/test', { signal: controller.signal })).rejects.toThrow(Error);
|
|
41
|
+
await expect(fs.readFile('/README.txt', { signal: controller.signal })).rejects.toThrow(Error);
|
|
42
|
+
await expect(fs.writeFile('/test.txt', 'test', { signal: controller.signal })).rejects.toThrow(Error);
|
|
43
|
+
await expect(fs.rm('/README.txt', { signal: controller.signal })).rejects.toThrow(Error);
|
|
44
|
+
await expect(fs.rename('/README.txt', '/new.txt', { signal: controller.signal })).rejects.toThrow(Error);
|
|
45
|
+
await expect(fs.copy('/README.txt', '/copy.txt', { signal: controller.signal })).rejects.toThrow(Error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// should validate input parameters
|
|
49
|
+
await expect(fs.stat('')).rejects.toThrow(FileSystemError);
|
|
50
|
+
await expect(fs.stat('')).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
51
|
+
await expect(fs.stat(null as any)).rejects.toThrow(FileSystemError);
|
|
52
|
+
await expect(fs.stat(null as any)).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
53
|
+
await expect(fs.stat(undefined as any)).rejects.toThrow(FileSystemError);
|
|
54
|
+
await expect(fs.stat(undefined as any)).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
55
|
+
await expect(fs.stat(123 as any)).rejects.toThrow(FileSystemError);
|
|
56
|
+
await expect(fs.stat(123 as any)).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
57
|
+
|
|
58
|
+
await expect(fs.writeFile('', 'test')).rejects.toThrow(FileSystemError);
|
|
59
|
+
await expect(fs.writeFile('', 'test')).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
60
|
+
await expect(fs.writeFile('/test', null as any)).rejects.toThrow(FileSystemError);
|
|
61
|
+
await expect(fs.writeFile('/test', null as any)).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
62
|
+
await expect(fs.writeFile('/test', undefined as any)).rejects.toThrow(FileSystemError);
|
|
63
|
+
await expect(fs.writeFile('/test', undefined as any)).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
64
|
+
await expect(fs.writeFile('/', 'test')).rejects.toThrow(FileSystemError);
|
|
65
|
+
await expect(fs.writeFile('/', 'test')).rejects.toMatchObject({ code: FileSystemErrorCode.EINVAL });
|
|
66
|
+
|
|
67
|
+
// should support streaming operations
|
|
68
|
+
if (readStream && fs.createReadStream) {
|
|
69
|
+
await fs.writeFile('/stream.txt', 'Hello, World!');
|
|
70
|
+
|
|
71
|
+
// Test createReadStream
|
|
72
|
+
const readStream = fs.createReadStream('/stream.txt');
|
|
73
|
+
expect(readStream).toBeDefined();
|
|
74
|
+
|
|
75
|
+
const chunks: Buffer[] = [];
|
|
76
|
+
for await (const chunk of readStream) {
|
|
77
|
+
chunks.push(chunk);
|
|
78
|
+
}
|
|
79
|
+
expect(Buffer.concat(chunks).toString()).toBe('Hello, World!');
|
|
80
|
+
|
|
81
|
+
// Test createReadStream with range
|
|
82
|
+
const rangeStream = fs.createReadStream('/stream.txt', { range: { start: 0, end: 4 } });
|
|
83
|
+
const rangeChunks: Buffer[] = [];
|
|
84
|
+
for await (const chunk of rangeStream) {
|
|
85
|
+
rangeChunks.push(chunk);
|
|
86
|
+
}
|
|
87
|
+
expect(Buffer.concat(rangeChunks).toString()).toBe('Hello');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (writeStream && fs.createWriteStream) {
|
|
91
|
+
// Test createWriteStream
|
|
92
|
+
const writeStream = fs.createWriteStream('/write-test.txt');
|
|
93
|
+
expect(writeStream).toBeDefined();
|
|
94
|
+
|
|
95
|
+
writeStream.write('Test content');
|
|
96
|
+
writeStream.end();
|
|
97
|
+
|
|
98
|
+
await new Promise((resolve) => writeStream.on('finish', resolve));
|
|
99
|
+
expect(await fs.readFile('/write-test.txt', { encoding: 'text' })).toBe('Test content');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// should support Web Streams API
|
|
103
|
+
if (readableStream) {
|
|
104
|
+
await fs.writeFile('/webstream.txt', 'Web Stream Test');
|
|
105
|
+
|
|
106
|
+
// Test createReadableStream
|
|
107
|
+
const readableStream = fs.createReadableStream('/webstream.txt');
|
|
108
|
+
expect(readableStream).toBeDefined();
|
|
109
|
+
|
|
110
|
+
const reader = readableStream.getReader();
|
|
111
|
+
const { value } = await reader.read();
|
|
112
|
+
expect(value?.toString()).toBe('Web Stream Test');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (writableStream) {
|
|
116
|
+
// Test createWritableStream
|
|
117
|
+
const writableStream = fs.createWritableStream('/web-write-test.txt');
|
|
118
|
+
expect(writableStream).toBeDefined();
|
|
119
|
+
|
|
120
|
+
const writer = writableStream.getWriter();
|
|
121
|
+
await writer.write(Buffer.from('Web Stream Write Test'));
|
|
122
|
+
await writer.close();
|
|
123
|
+
|
|
124
|
+
expect(await fs.readFile('/web-write-test.txt', { encoding: 'text' })).toBe('Web Stream Write Test');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// should handle streaming errors
|
|
128
|
+
if (abort && readStream && fs.createReadStream) {
|
|
129
|
+
const controller2 = new AbortController();
|
|
130
|
+
|
|
131
|
+
// Test read stream with abort
|
|
132
|
+
const readStream = fs.createReadStream('/README.txt', { signal: controller2.signal });
|
|
133
|
+
controller2.abort();
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
new Promise((_, reject) => {
|
|
137
|
+
readStream.on('error', reject);
|
|
138
|
+
}),
|
|
139
|
+
).rejects.toThrow(Error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (abort && writeStream && fs.createWriteStream) {
|
|
143
|
+
// Test write stream with abort
|
|
144
|
+
const controller3 = new AbortController();
|
|
145
|
+
const writeStream = fs.createWriteStream('/abort-test.txt', { signal: controller3.signal });
|
|
146
|
+
controller3.abort();
|
|
147
|
+
|
|
148
|
+
await expect(
|
|
149
|
+
new Promise((_, reject) => {
|
|
150
|
+
writeStream.on('error', reject);
|
|
151
|
+
writeStream.write('test');
|
|
152
|
+
}),
|
|
153
|
+
).rejects.toThrow(Error);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// should handle file operations correctly
|
|
157
|
+
// Test file creation
|
|
158
|
+
await fs.writeFile('/newfile.txt', 'New content');
|
|
159
|
+
expect(await fs.exists('/newfile.txt')).toBe(true);
|
|
160
|
+
|
|
161
|
+
const stat2 = await fs.stat('/newfile.txt');
|
|
162
|
+
expect(stat2.kind).toBe('file');
|
|
163
|
+
expect(stat2.size).toBe(11); // "New content" is 11 bytes
|
|
164
|
+
|
|
165
|
+
// Test file reading
|
|
166
|
+
const content = await fs.readFile('/newfile.txt', { encoding: 'text' });
|
|
167
|
+
expect(content).toBe('New content');
|
|
168
|
+
|
|
169
|
+
// Test file reading as binary
|
|
170
|
+
const binary = await fs.readFile('/newfile.txt');
|
|
171
|
+
expect(Buffer.from(binary).toString()).toBe('New content');
|
|
172
|
+
|
|
173
|
+
// Test file overwrite
|
|
174
|
+
await fs.writeFile('/newfile.txt', 'Updated content');
|
|
175
|
+
expect(await fs.readFile('/newfile.txt', { encoding: 'text' })).toBe('Updated content');
|
|
176
|
+
|
|
177
|
+
// Test file overwrite protection
|
|
178
|
+
await expect(fs.writeFile('/newfile.txt', 'Should fail', { overwrite: false })).rejects.toThrow(FileSystemError);
|
|
179
|
+
await expect(fs.writeFile('/newfile.txt', 'Should fail', { overwrite: false })).rejects.toMatchObject({
|
|
180
|
+
code: FileSystemErrorCode.EEXIST,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Test large file write (should use file_node_content table)
|
|
184
|
+
// Create ~1KB data to test large file handling
|
|
185
|
+
const largeData = 'x'.repeat(1024); // 1KB of data
|
|
186
|
+
await fs.writeFile('/largefile.txt', largeData);
|
|
187
|
+
const largeFileStat = await fs.stat('/largefile.txt');
|
|
188
|
+
expect(largeFileStat.size).toBe(1024);
|
|
189
|
+
const largeFileContent = await fs.readFile('/largefile.txt', { encoding: 'text' });
|
|
190
|
+
expect(largeFileContent).toBe(largeData);
|
|
191
|
+
expect(largeFileContent.length).toBe(1024);
|
|
192
|
+
|
|
193
|
+
// should handle directory operations correctly
|
|
194
|
+
// Test directory creation
|
|
195
|
+
await fs.mkdir('/newdir');
|
|
196
|
+
expect(await fs.exists('/newdir')).toBe(true);
|
|
197
|
+
|
|
198
|
+
const stat3 = await fs.stat('/newdir');
|
|
199
|
+
expect(stat3.kind).toBe('directory');
|
|
200
|
+
|
|
201
|
+
// Test recursive directory creation
|
|
202
|
+
await fs.mkdir('/deep/nested/dir', { recursive: true });
|
|
203
|
+
expect(await fs.exists('/deep/nested/dir')).toBe(true);
|
|
204
|
+
|
|
205
|
+
// Test non-recursive directory creation failure
|
|
206
|
+
await expect(fs.mkdir('/another/deep/dir')).rejects.toThrow(FileSystemError);
|
|
207
|
+
await expect(fs.mkdir('/another/deep/dir')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
208
|
+
|
|
209
|
+
// Test directory listing
|
|
210
|
+
await fs.writeFile('/newdir/file1.txt', 'File 1');
|
|
211
|
+
await fs.writeFile('/newdir/file2.txt', 'File 2');
|
|
212
|
+
|
|
213
|
+
const contents = await fs.readdir('/newdir');
|
|
214
|
+
expect(contents).toHaveLength(2);
|
|
215
|
+
expect(contents.map((f) => f.name)).toContain('file1.txt');
|
|
216
|
+
expect(contents.map((f) => f.name)).toContain('file2.txt');
|
|
217
|
+
|
|
218
|
+
// should handle file removal correctly
|
|
219
|
+
await fs.writeFile('/toremove.txt', 'Remove me');
|
|
220
|
+
expect(await fs.exists('/toremove.txt')).toBe(true);
|
|
221
|
+
|
|
222
|
+
await fs.rm('/toremove.txt');
|
|
223
|
+
expect(await fs.exists('/toremove.txt')).toBe(false);
|
|
224
|
+
|
|
225
|
+
// Test force removal
|
|
226
|
+
await expect(fs.rm('/nonexistent.txt')).rejects.toThrow(FileSystemError);
|
|
227
|
+
await expect(fs.rm('/nonexistent.txt')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
228
|
+
await fs.rm('/nonexistent.txt', { force: true }); // Should not throw
|
|
229
|
+
|
|
230
|
+
// Test recursive removal
|
|
231
|
+
await fs.mkdir('/dirwithfiles', { recursive: true });
|
|
232
|
+
await fs.writeFile('/dirwithfiles/file.txt', 'content');
|
|
233
|
+
await expect(fs.rm('/dirwithfiles')).rejects.toThrow(FileSystemError);
|
|
234
|
+
await expect(fs.rm('/dirwithfiles')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOTEMPTY });
|
|
235
|
+
await fs.rm('/dirwithfiles', { recursive: true });
|
|
236
|
+
expect(await fs.exists('/dirwithfiles')).toBe(false);
|
|
237
|
+
|
|
238
|
+
// should handle file rename correctly
|
|
239
|
+
await fs.writeFile('/rename.txt', 'Original content');
|
|
240
|
+
expect(await fs.exists('/rename.txt')).toBe(true);
|
|
241
|
+
|
|
242
|
+
await fs.rename('/rename.txt', '/renamed.txt');
|
|
243
|
+
expect(await fs.exists('/rename.txt')).toBe(false);
|
|
244
|
+
expect(await fs.exists('/renamed.txt')).toBe(true);
|
|
245
|
+
expect(await fs.readFile('/renamed.txt', { encoding: 'text' })).toBe('Original content');
|
|
246
|
+
|
|
247
|
+
// should handle file copy correctly
|
|
248
|
+
await fs.writeFile('/copy.txt', 'Copy me');
|
|
249
|
+
expect(await fs.exists('/copy.txt')).toBe(true);
|
|
250
|
+
|
|
251
|
+
await fs.copy('/copy.txt', '/copied.txt');
|
|
252
|
+
expect(await fs.exists('/copy.txt')).toBe(true);
|
|
253
|
+
expect(await fs.exists('/copied.txt')).toBe(true);
|
|
254
|
+
expect(await fs.readFile('/copied.txt', { encoding: 'text' })).toBe('Copy me');
|
|
255
|
+
|
|
256
|
+
// Test copy overwrite
|
|
257
|
+
await fs.writeFile('/target2.txt', 'Target content');
|
|
258
|
+
await expect(fs.copy('/copy.txt', '/target2.txt')).rejects.toThrow(FileSystemError);
|
|
259
|
+
await expect(fs.copy('/copy.txt', '/target2.txt')).rejects.toMatchObject({ code: FileSystemErrorCode.EEXIST });
|
|
260
|
+
await fs.copy('/copy.txt', '/target2.txt', { overwrite: true });
|
|
261
|
+
expect(await fs.readFile('/target2.txt', { encoding: 'text' })).toBe('Copy me');
|
|
262
|
+
|
|
263
|
+
// should handle edge cases correctly
|
|
264
|
+
// Test root directory operations
|
|
265
|
+
await expect(fs.stat('/')).resolves.toBeDefined();
|
|
266
|
+
await expect(fs.readdir('/')).resolves.toBeDefined();
|
|
267
|
+
await fs.mkdir('/'); // Should not throw
|
|
268
|
+
|
|
269
|
+
// Test path normalization
|
|
270
|
+
await fs.writeFile('/normalize.txt', 'test');
|
|
271
|
+
expect(await fs.exists('/normalize.txt')).toBe(true);
|
|
272
|
+
expect(await fs.exists('/./normalize.txt')).toBe(true);
|
|
273
|
+
// Note: the exists method may return true for files with trailing slashes due to normalization
|
|
274
|
+
|
|
275
|
+
// Test error cases
|
|
276
|
+
await expect(fs.stat('/nonexistent')).rejects.toThrow(FileSystemError);
|
|
277
|
+
await expect(fs.stat('/nonexistent')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
278
|
+
await expect(fs.readdir('/nonexistent')).rejects.toThrow(FileSystemError);
|
|
279
|
+
await expect(fs.readdir('/nonexistent')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
280
|
+
await expect(fs.readFile('/nonexistent')).rejects.toThrow(FileSystemError);
|
|
281
|
+
await expect(fs.readFile('/nonexistent')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
282
|
+
await expect(fs.rm('/nonexistent')).rejects.toThrow(FileSystemError);
|
|
283
|
+
await expect(fs.rm('/nonexistent')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
284
|
+
await expect(fs.rename('/nonexistent', '/new')).rejects.toThrow(FileSystemError);
|
|
285
|
+
await expect(fs.rename('/nonexistent', '/new')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
286
|
+
await expect(fs.copy('/nonexistent', '/new')).rejects.toThrow(FileSystemError);
|
|
287
|
+
await expect(fs.copy('/nonexistent', '/new')).rejects.toMatchObject({ code: FileSystemErrorCode.ENOENT });
|
|
288
|
+
}
|
package/src/fs/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
type EnumValues<T> = T[Exclude<keyof T, '__proto__'>];
|
|
4
|
+
export const FileKind = Object.freeze({
|
|
5
|
+
__proto__: null,
|
|
6
|
+
directory: 'directory',
|
|
7
|
+
file: 'file',
|
|
8
|
+
});
|
|
9
|
+
export type FileKind = EnumValues<typeof FileKind>;
|
|
10
|
+
export const FileKindSchema = z.enum(['directory', 'file']);
|
|
11
|
+
|
|
12
|
+
export type ReaddirOptions = z.infer<typeof ReaddirOptionsSchema>;
|
|
13
|
+
export const ReaddirOptionsSchema = z.object({
|
|
14
|
+
recursive: z.boolean().optional(),
|
|
15
|
+
depth: z.number().optional(),
|
|
16
|
+
glob: z.string().optional(),
|
|
17
|
+
kind: FileKindSchema.optional(),
|
|
18
|
+
cursor: z.string().optional(),
|
|
19
|
+
hidden: z.boolean().default(false),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type FileUrlOptions = z.infer<typeof FileUrlOptionsSchema>;
|
|
23
|
+
export const FileUrlOptionsSchema = z.object({
|
|
24
|
+
size: z.coerce.number().optional(),
|
|
25
|
+
width: z.number().optional(),
|
|
26
|
+
height: z.number().optional(),
|
|
27
|
+
quality: z.number().optional(),
|
|
28
|
+
thumbnail: z.boolean().optional(),
|
|
29
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { IFileStat, IFileSystem } from '../IFileSystem';
|
|
2
|
+
import type { FileUrlOptions } from '../types';
|
|
3
|
+
|
|
4
|
+
export function getFileUrl(
|
|
5
|
+
fs: IFileSystem,
|
|
6
|
+
needle?: IFileStat | string,
|
|
7
|
+
{
|
|
8
|
+
...options
|
|
9
|
+
}: FileUrlOptions & {
|
|
10
|
+
fs?: IFileSystem;
|
|
11
|
+
} = {},
|
|
12
|
+
) {
|
|
13
|
+
const [file, path] = resolveFilePath(needle);
|
|
14
|
+
if (!path || !needle) return;
|
|
15
|
+
|
|
16
|
+
let out: string | undefined;
|
|
17
|
+
if (fs?.getUrl) {
|
|
18
|
+
try {
|
|
19
|
+
out = fs.getUrl(needle, options);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error(`failed to get file url`, needle, e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!out && file) {
|
|
25
|
+
out = file.meta?.url || (file as any).url;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveFilePath(needle: IFileStat): [IFileStat, string];
|
|
31
|
+
export function resolveFilePath(needle: string): [undefined, string];
|
|
32
|
+
export function resolveFilePath(needle?: IFileStat | string): [IFileStat | undefined, string | undefined];
|
|
33
|
+
export function resolveFilePath(needle?: IFileStat | string): [IFileStat | undefined, string | undefined] {
|
|
34
|
+
if (!needle) {
|
|
35
|
+
return [undefined, undefined];
|
|
36
|
+
}
|
|
37
|
+
let file = typeof needle === 'string' ? undefined : needle;
|
|
38
|
+
let path = typeof needle === 'string' ? needle : needle.path;
|
|
39
|
+
|
|
40
|
+
if (file && !path) {
|
|
41
|
+
path = file.path;
|
|
42
|
+
}
|
|
43
|
+
return [file, path];
|
|
44
|
+
}
|
package/src/fs/utils.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ArrayBuffers, classOf } from '@wener/utils';
|
|
2
|
+
import type { IFileStat, WritableData } from './IFileSystem';
|
|
3
|
+
|
|
4
|
+
export function resolveData(data: WritableData) {
|
|
5
|
+
let buf: Uint8Array;
|
|
6
|
+
if (typeof data === 'string') {
|
|
7
|
+
buf = ArrayBuffers.toUint8Array(ArrayBuffers.from(data, 'utf8'));
|
|
8
|
+
} else if (data instanceof ArrayBuffer) {
|
|
9
|
+
buf = new Uint8Array(data);
|
|
10
|
+
} else if (data instanceof Uint8Array) {
|
|
11
|
+
buf = data;
|
|
12
|
+
} else {
|
|
13
|
+
throw new Error(`unable to resolve data: ${typeof data} ${classOf(data)}`);
|
|
14
|
+
}
|
|
15
|
+
return buf;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getPath(f: IFileStat | string) {
|
|
19
|
+
if (typeof f === 'string') {
|
|
20
|
+
return f;
|
|
21
|
+
}
|
|
22
|
+
return f.path;
|
|
23
|
+
}
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import type { Static, TSchema } from '@sinclair/typebox';
|
|
2
|
+
import { isNil } from '@wener/utils';
|
|
2
3
|
import Ajv, { type ErrorObject, type Options } from 'ajv';
|
|
3
4
|
import addFormats from 'ajv-formats';
|
|
4
|
-
// import localize from 'ajv-i18n/localize/zh';
|
|
5
5
|
import addKeywords from 'ajv-keywords';
|
|
6
|
-
import { isNil } from 'es-toolkit';
|
|
7
6
|
import { match, P } from 'ts-pattern';
|
|
8
7
|
import type { JsonSchemaDef } from './types';
|
|
9
8
|
|
|
10
|
-
function
|
|
9
|
+
function _createValidator(opt: Options) {
|
|
11
10
|
const ajv = new Ajv(opt);
|
|
12
11
|
addKeywords(ajv);
|
|
13
12
|
addFormats(ajv);
|
|
14
13
|
return ajv;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
type ValidateOptions = { mutate?: boolean; clone?: boolean;
|
|
16
|
+
type ValidateOptions = { mutate?: boolean; clone?: boolean; validator?: Ajv };
|
|
18
17
|
|
|
19
18
|
type ValidateResult<T> =
|
|
20
19
|
| { data: T; success: true; message: undefined }
|
|
21
20
|
| { data: undefined; success: false; message: string; errors: ErrorObject[] };
|
|
22
21
|
|
|
23
|
-
function validate({ schema, data, mutate, clone,
|
|
22
|
+
function validate({ schema, data, mutate, clone, validator }: ValidateOptions & { schema: any; data: any }) {
|
|
24
23
|
let opts: Options = {
|
|
25
24
|
// strict: 'log',
|
|
26
25
|
strict: true,
|
|
@@ -35,11 +34,11 @@ function validate({ schema, data, mutate, clone, ajv }: ValidateOptions & { sche
|
|
|
35
34
|
data = structuredClone(data);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
if (!
|
|
39
|
-
|
|
37
|
+
if (!validator) {
|
|
38
|
+
validator = JsonSchema.createValidator(opts);
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
const validate =
|
|
41
|
+
const validate = validator.compile(schema);
|
|
43
42
|
|
|
44
43
|
// consider reusing validate instance
|
|
45
44
|
|
|
@@ -47,119 +46,128 @@ function validate({ schema, data, mutate, clone, ajv }: ValidateOptions & { sche
|
|
|
47
46
|
const errors = validate.errors;
|
|
48
47
|
// localize(errors);
|
|
49
48
|
|
|
50
|
-
return { data, success: valid, message:
|
|
49
|
+
return { data, success: valid, message: validator.errorsText(errors), errors: errors };
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
type TypeOfSchema<S> = S extends TSchema ? Static<S> : any;
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
let idx = schemas.findIndex((s) => s.$id === schema.$id);
|
|
83
|
-
if (idx >= 0) {
|
|
84
|
-
schemas[idx] = onConflict(schemas[idx], schema);
|
|
85
|
-
} else {
|
|
86
|
-
schemas.push(schema);
|
|
87
|
-
}
|
|
54
|
+
let schemas: JsonSchemaDef[] = [];
|
|
55
|
+
|
|
56
|
+
let createValidator = _createValidator;
|
|
57
|
+
|
|
58
|
+
function addSchema(
|
|
59
|
+
schema: JsonSchemaDef,
|
|
60
|
+
{
|
|
61
|
+
onConflict = 'throw',
|
|
62
|
+
}: {
|
|
63
|
+
onConflict?: 'throw' | 'ignore' | 'replace' | ((old: JsonSchemaDef, neo: JsonSchemaDef) => JsonSchemaDef);
|
|
64
|
+
} = {},
|
|
65
|
+
) {
|
|
66
|
+
if (!schema.$id) throw new Error('Schema must have $id');
|
|
67
|
+
switch (onConflict) {
|
|
68
|
+
case 'ignore':
|
|
69
|
+
onConflict = (old) => old;
|
|
70
|
+
break;
|
|
71
|
+
case 'replace':
|
|
72
|
+
onConflict = (_, neo) => neo;
|
|
73
|
+
break;
|
|
74
|
+
case 'throw':
|
|
75
|
+
onConflict = (old, neo) => {
|
|
76
|
+
throw new Error(`Schema ${neo.$id} already exists`);
|
|
77
|
+
};
|
|
78
|
+
break;
|
|
88
79
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return validate({ schema, data, mutate: false, clone: true }) as any;
|
|
80
|
+
let idx = schemas.findIndex((s) => s.$id === schema.$id);
|
|
81
|
+
if (idx >= 0) {
|
|
82
|
+
schemas[idx] = onConflict(schemas[idx], schema);
|
|
83
|
+
} else {
|
|
84
|
+
schemas.push(schema);
|
|
95
85
|
}
|
|
86
|
+
}
|
|
96
87
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Check data is valid, will not use default
|
|
90
|
+
*/
|
|
91
|
+
function check<S>(schema: S, data: any): ValidateResult<TypeOfSchema<S>> {
|
|
92
|
+
return validate({ schema, data, mutate: false, clone: true }) as any;
|
|
93
|
+
}
|
|
103
94
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Parse data with default value and coerceTypes
|
|
97
|
+
*/
|
|
98
|
+
function safeParse<S>(schema: S, data: any): ValidateResult<TypeOfSchema<S>> {
|
|
99
|
+
return validate({ schema, data, mutate: true, clone: true }) as any;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parse<S>(schema: S, data: any): TypeOfSchema<S> {
|
|
103
|
+
const { data: out, message, errors } = validate({ schema, data, mutate: true, clone: true });
|
|
104
|
+
if (errors) {
|
|
105
|
+
throw new Error(message);
|
|
110
106
|
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
out = validate({ schema, data: obj, mutate: true });
|
|
139
|
-
if (!out.success) {
|
|
140
|
-
console.warn(`Failed to create object with schema: ${out.message}`);
|
|
110
|
+
function create<S>(schema: S, data?: any): TypeOfSchema<S> {
|
|
111
|
+
// will not ensure value match the rule
|
|
112
|
+
return match(schema as JsonSchemaDef)
|
|
113
|
+
.returnType<any>()
|
|
114
|
+
.with({ const: P.nonNullable }, (v) => v)
|
|
115
|
+
.with({ default: P.select() }, (v) => v)
|
|
116
|
+
.with({ anyOf: P.nonNullable }, (schema) => {
|
|
117
|
+
return create(schema.anyOf[0]);
|
|
118
|
+
})
|
|
119
|
+
.with({ oneOf: P.nonNullable }, (schema) => {
|
|
120
|
+
return create(schema.oneOf[0]);
|
|
121
|
+
})
|
|
122
|
+
.with({ type: 'string' }, (schema) => '')
|
|
123
|
+
.with({ type: P.union('number', 'integer') }, (schema) => 0)
|
|
124
|
+
.with({ type: 'object' }, (schema: JsonSchemaDef) => {
|
|
125
|
+
let out = validate({ schema, data: data ?? {}, mutate: true });
|
|
126
|
+
if (!out.success) {
|
|
127
|
+
// fallback
|
|
128
|
+
let obj = data || {};
|
|
129
|
+
schema.required?.forEach((key) => {
|
|
130
|
+
if (!(key in obj)) {
|
|
131
|
+
let last = obj[key];
|
|
132
|
+
let prop = schema.properties?.[key];
|
|
133
|
+
if (prop && isNil(last)) obj[key] = JsonSchema.create(prop, last);
|
|
141
134
|
}
|
|
135
|
+
});
|
|
136
|
+
out = validate({ schema, data: obj, mutate: true });
|
|
137
|
+
if (!out.success) {
|
|
138
|
+
console.warn(`Failed to create object with schema: ${out.message}`);
|
|
142
139
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
}
|
|
141
|
+
return out.data;
|
|
142
|
+
})
|
|
143
|
+
.with({ type: 'null' }, () => null)
|
|
144
|
+
.with({ type: 'boolean' }, (schema) => false)
|
|
145
|
+
.with({ type: 'array' }, (schema) => [])
|
|
146
|
+
.otherwise(() => {
|
|
147
|
+
return undefined;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
152
150
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
151
|
+
function isPrimitiveType(schema: any): boolean {
|
|
152
|
+
return match(schema as JsonSchemaDef)
|
|
153
|
+
.returnType<boolean>()
|
|
154
|
+
.with({ type: P.union('number', 'integer', 'string', 'boolean') }, () => true)
|
|
155
|
+
.with({ anyOf: P.nonNullable }, (schema) => {
|
|
156
|
+
return isPrimitiveType(schema.anyOf[0]);
|
|
157
|
+
})
|
|
158
|
+
.with({ oneOf: P.nonNullable }, (schema) => {
|
|
159
|
+
return isPrimitiveType(schema.oneOf[0]);
|
|
160
|
+
})
|
|
161
|
+
.otherwise(() => false);
|
|
165
162
|
}
|
|
163
|
+
|
|
164
|
+
export const JsonSchema = {
|
|
165
|
+
createValidator,
|
|
166
|
+
addSchema,
|
|
167
|
+
schemas,
|
|
168
|
+
check,
|
|
169
|
+
safeParse,
|
|
170
|
+
parse,
|
|
171
|
+
create,
|
|
172
|
+
isPrimitiveType,
|
|
173
|
+
} as const;
|