@wener/common 1.0.1
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/LICENSE +21 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +180 -0
- package/lib/jsonschema/JsonSchema.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 +3 -0
- package/lib/jsonschema/types.d.js.map +1 -0
- package/lib/meta/defineInit.js +42 -0
- package/lib/meta/defineInit.js.map +1 -0
- package/lib/meta/defineMetadata.js +30 -0
- package/lib/meta/defineMetadata.js.map +1 -0
- package/lib/meta/index.js +3 -0
- package/lib/meta/index.js.map +1 -0
- package/lib/normalizePagination.js +14 -0
- package/lib/normalizePagination.js.map +1 -0
- package/lib/parseSort.js +91 -0
- package/lib/parseSort.js.map +1 -0
- package/lib/password/PHC.js +200 -0
- package/lib/password/PHC.js.map +1 -0
- package/lib/password/Password.js +83 -0
- package/lib/password/Password.js.map +1 -0
- package/lib/password/createArgon2PasswordAlgorithm.js +53 -0
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBase64PasswordAlgorithm.js +14 -0
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBcryptPasswordAlgorithm.js +20 -0
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/createPBKDF2PasswordAlgorithm.js +54 -0
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createScryptPasswordAlgorithm.js +66 -0
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/index.js +6 -0
- package/lib/password/index.js.map +1 -0
- package/lib/password/server/index.js +2 -0
- package/lib/password/server/index.js.map +1 -0
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js +85 -0
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -0
- package/package.json +56 -0
- package/src/index.ts +2 -0
- package/src/jsonschema/JsonSchema.test.ts +27 -0
- package/src/jsonschema/JsonSchema.ts +197 -0
- package/src/jsonschema/index.ts +2 -0
- package/src/jsonschema/types.d.ts +173 -0
- package/src/meta/defineInit.ts +68 -0
- package/src/meta/defineMetadata.test.ts +15 -0
- package/src/meta/defineMetadata.ts +57 -0
- package/src/meta/index.ts +3 -0
- package/src/normalizePagination.ts +25 -0
- package/src/parseSort.test.ts +41 -0
- package/src/parseSort.ts +115 -0
- package/src/password/PHC.test.ts +317 -0
- package/src/password/PHC.ts +247 -0
- package/src/password/Password.test.ts +58 -0
- package/src/password/Password.ts +113 -0
- package/src/password/createArgon2PasswordAlgorithm.ts +80 -0
- package/src/password/createBase64PasswordAlgorithm.ts +14 -0
- package/src/password/createBcryptPasswordAlgorithm.ts +30 -0
- package/src/password/createPBKDF2PasswordAlgorithm.ts +73 -0
- package/src/password/createScryptPasswordAlgorithm.ts +72 -0
- package/src/password/index.ts +5 -0
- package/src/password/server/index.ts +1 -0
- package/src/tools/renderJsonSchemaToMarkdownDoc.ts +93 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export function renderJsonSchemaToMarkdownDoc(rootSchema) {
|
|
2
|
+
// markdown
|
|
3
|
+
const out = [];
|
|
4
|
+
const all = new Set();
|
|
5
|
+
const pending = [];
|
|
6
|
+
const addObject = (o)=>{
|
|
7
|
+
if (all.has(o.$id)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
all.add(o.$id);
|
|
11
|
+
pending.push(o);
|
|
12
|
+
};
|
|
13
|
+
const writeObjectProps = (T, { prefix = '' } = {})=>{
|
|
14
|
+
if (!T?.properties) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const [name, schema] of Object.entries(T.properties)){
|
|
18
|
+
let typ = schema.$id || schema.type;
|
|
19
|
+
if (typ === 'array') {
|
|
20
|
+
typ = `${schema.items.$id || schema.items.type}[]`;
|
|
21
|
+
if (schema.items.$id) {
|
|
22
|
+
addObject(schema.items);
|
|
23
|
+
}
|
|
24
|
+
} else if (schema.$id) {
|
|
25
|
+
addObject(schema);
|
|
26
|
+
}
|
|
27
|
+
if (!typ) {
|
|
28
|
+
if ('anyOf' in schema) {
|
|
29
|
+
typ = 'enum';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
out.push(`| ${prefix}${name} | ${typ} | ${schema.title || schema.description || ''} |`);
|
|
33
|
+
if (typ === 'object') {
|
|
34
|
+
writeObjectProps(schema, {
|
|
35
|
+
prefix: `${prefix}${name}.`
|
|
36
|
+
});
|
|
37
|
+
} else if (schema.type === 'array') {
|
|
38
|
+
if (schema.items.type === 'object' && !schema.items.$id) {
|
|
39
|
+
writeObjectProps(schema.items, {
|
|
40
|
+
prefix: `${prefix}${name}[].`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const writeObject = (T)=>{
|
|
47
|
+
out.push(`### ${T.title || T.$id}`);
|
|
48
|
+
out.push(`
|
|
49
|
+
| $id | 名字 |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| ${T.$id || ''} | ${T.title || ''} |
|
|
52
|
+
`);
|
|
53
|
+
if (T.description) {
|
|
54
|
+
out.push('');
|
|
55
|
+
out.push(`> ${T.description}`);
|
|
56
|
+
out.push('');
|
|
57
|
+
}
|
|
58
|
+
out.push(`| 名字 | 类型 | 说明 |`);
|
|
59
|
+
out.push(`| --- | --- | --- |`);
|
|
60
|
+
writeObjectProps(T);
|
|
61
|
+
out.push('');
|
|
62
|
+
};
|
|
63
|
+
writeObject(rootSchema);
|
|
64
|
+
for (const schema of pending){
|
|
65
|
+
if (schema.type === 'object') {
|
|
66
|
+
writeObject(schema);
|
|
67
|
+
} else if ('anyOf' in schema) {
|
|
68
|
+
out.push(`### ${schema.$id || schema.title}`);
|
|
69
|
+
out.push(`
|
|
70
|
+
| $id | 名字 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| ${schema.$id || ''} | ${schema.title || ''} |
|
|
73
|
+
`);
|
|
74
|
+
out.push(`| 值 | 说明 |`);
|
|
75
|
+
out.push(`| --- | --- |`);
|
|
76
|
+
for (const item of schema.anyOf){
|
|
77
|
+
out.push(`| ${item.const} | ${item.title || item.description || ''} |`);
|
|
78
|
+
}
|
|
79
|
+
out.push('');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//# sourceMappingURL=renderJsonSchemaToMarkdownDoc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/renderJsonSchemaToMarkdownDoc.ts"],"sourcesContent":["import type { TObject, TSchema } from '@sinclair/typebox';\n\nexport function renderJsonSchemaToMarkdownDoc(rootSchema: any) {\n // markdown\n const out: string[] = [];\n const all = new Set();\n const pending: TSchema[] = [];\n\n const addObject = (o: TSchema) => {\n if (all.has(o.$id)) {\n return;\n }\n all.add(o.$id);\n pending.push(o);\n };\n\n const writeObjectProps = (T: TObject, { prefix = '' }: { prefix?: string } = {}) => {\n if (!T?.properties) {\n return;\n }\n for (const [name, schema] of Object.entries(T.properties)) {\n let typ = schema.$id || schema.type;\n\n if (typ === 'array') {\n typ = `${schema.items.$id || schema.items.type}[]`;\n if (schema.items.$id) {\n addObject(schema.items);\n }\n } else if (schema.$id) {\n addObject(schema);\n }\n if (!typ) {\n if ('anyOf' in schema) {\n typ = 'enum';\n }\n }\n\n out.push(`| ${prefix}${name} | ${typ} | ${schema.title || schema.description || ''} |`);\n\n if (typ === 'object') {\n writeObjectProps(schema as TObject, { prefix: `${prefix}${name}.` });\n } else if (schema.type === 'array') {\n if (schema.items.type === 'object' && !schema.items.$id) {\n writeObjectProps(schema.items as TObject, { prefix: `${prefix}${name}[].` });\n }\n }\n }\n };\n const writeObject = (T: TObject) => {\n out.push(`### ${T.title || T.$id}`);\n out.push(`\n| $id | 名字 |\n| --- | --- |\n| ${T.$id || ''} | ${T.title || ''} | \n `);\n if (T.description) {\n out.push('');\n out.push(`> ${T.description}`);\n out.push('');\n }\n\n out.push(`| 名字 | 类型 | 说明 |`);\n out.push(`| --- | --- | --- |`);\n\n writeObjectProps(T);\n\n out.push('');\n };\n\n writeObject(rootSchema);\n\n for (const schema of pending) {\n if (schema.type === 'object') {\n writeObject(schema as TObject);\n } else if ('anyOf' in schema) {\n out.push(`### ${schema.$id || schema.title}`);\n out.push(`\n| $id | 名字 |\n| --- | --- |\n| ${schema.$id || ''} | ${schema.title || ''} | \n `);\n\n out.push(`| 值 | 说明 |`);\n out.push(`| --- | --- |`);\n for (const item of schema.anyOf) {\n out.push(`| ${item.const} | ${item.title || item.description || ''} |`);\n }\n out.push('');\n }\n }\n\n return out.join('\\n');\n}\n"],"names":["renderJsonSchemaToMarkdownDoc","rootSchema","out","all","Set","pending","addObject","o","has","$id","add","push","writeObjectProps","T","prefix","properties","name","schema","Object","entries","typ","type","items","title","description","writeObject","item","anyOf","const","join"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wener/common",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./jsonschema": {
|
|
14
|
+
"types": "./src/jsonschema/index.ts",
|
|
15
|
+
"default": "./lib/jsonschema/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./meta": {
|
|
18
|
+
"types": "./src/meta/index.ts",
|
|
19
|
+
"default": "./lib/meta/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./password": {
|
|
22
|
+
"types": "./src/password/index.ts",
|
|
23
|
+
"default": "./lib/password/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib",
|
|
29
|
+
"lib.d.ts",
|
|
30
|
+
"libs",
|
|
31
|
+
"src"
|
|
32
|
+
],
|
|
33
|
+
"keywords": [],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"es-toolkit": "^1.27.0",
|
|
36
|
+
"ts-pattern": "^5.5.0",
|
|
37
|
+
"@wener/utils": "1.1.51"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@sinclair/typebox": "^0.34.9",
|
|
41
|
+
"@types/argon2-browser": "^1.18.4",
|
|
42
|
+
"@types/bcrypt": "^5.0.2",
|
|
43
|
+
"@types/bcryptjs": "^2.4.6",
|
|
44
|
+
"ajv": "^8.17.1",
|
|
45
|
+
"ajv-formats": "^3.0.1",
|
|
46
|
+
"ajv-i18n": "^4.2.0",
|
|
47
|
+
"ajv-keywords": "^5.1.0",
|
|
48
|
+
"argon2": "^0.41.1",
|
|
49
|
+
"argon2-browser": "^1.18.0",
|
|
50
|
+
"bcrypt": "^5.1.1",
|
|
51
|
+
"bcryptjs": "^2.4.3"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { JsonSchema } from './JsonSchema';
|
|
3
|
+
|
|
4
|
+
describe('jsonschema', () => {
|
|
5
|
+
it('should create from schema', () => {
|
|
6
|
+
for (const [a, b] of [
|
|
7
|
+
[{ type: 'string' }, ''],
|
|
8
|
+
[{ type: 'number' }, 0],
|
|
9
|
+
[{ type: 'number', default: 1 }, 1],
|
|
10
|
+
[{ type: 'integer' }, 0],
|
|
11
|
+
[{ type: 'array' }, []],
|
|
12
|
+
[{ type: 'object' }, {}],
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
a: { type: 'string' },
|
|
18
|
+
},
|
|
19
|
+
required: ['a'],
|
|
20
|
+
},
|
|
21
|
+
{ a: '' },
|
|
22
|
+
],
|
|
23
|
+
]) {
|
|
24
|
+
expect(JsonSchema.create(a)).toEqual(b);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { Static, TSchema } from '@sinclair/typebox';
|
|
2
|
+
import Ajv, { type ErrorObject, type Options } from 'ajv';
|
|
3
|
+
import addFormats from 'ajv-formats';
|
|
4
|
+
import localize from 'ajv-i18n/localize/zh';
|
|
5
|
+
import addKeywords from 'ajv-keywords';
|
|
6
|
+
import { isNil } from 'es-toolkit';
|
|
7
|
+
import { match, P } from 'ts-pattern';
|
|
8
|
+
import type { JsonSchemaDef } from './types';
|
|
9
|
+
|
|
10
|
+
function _createAjv(opt: Options) {
|
|
11
|
+
const ajv = new Ajv(opt);
|
|
12
|
+
addKeywords(ajv);
|
|
13
|
+
addFormats(ajv);
|
|
14
|
+
return ajv;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ValidateOptions = {
|
|
18
|
+
mutate?: boolean;
|
|
19
|
+
clone?: boolean;
|
|
20
|
+
ajv?: Ajv;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type ValidateResult<T> =
|
|
24
|
+
| {
|
|
25
|
+
data: T;
|
|
26
|
+
success: true;
|
|
27
|
+
message: undefined;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
data: undefined;
|
|
31
|
+
success: false;
|
|
32
|
+
message: string;
|
|
33
|
+
errors: ErrorObject[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function validate({
|
|
37
|
+
schema,
|
|
38
|
+
data,
|
|
39
|
+
mutate,
|
|
40
|
+
clone,
|
|
41
|
+
ajv,
|
|
42
|
+
}: ValidateOptions & {
|
|
43
|
+
schema: any;
|
|
44
|
+
data: any;
|
|
45
|
+
}) {
|
|
46
|
+
let opts: Options = {
|
|
47
|
+
// strict: 'log',
|
|
48
|
+
strict: true,
|
|
49
|
+
strictSchema: 'log', // skip unknown keywords in schema
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (mutate) {
|
|
53
|
+
Object.assign(opts, {
|
|
54
|
+
removeAdditional: true,
|
|
55
|
+
useDefaults: true,
|
|
56
|
+
coerceTypes: true,
|
|
57
|
+
allErrors: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (clone) {
|
|
62
|
+
data = structuredClone(data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!ajv) {
|
|
66
|
+
ajv = JsonSchema.createAjv(opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const validate = ajv.compile(schema);
|
|
70
|
+
|
|
71
|
+
// consider reusing validate instance
|
|
72
|
+
|
|
73
|
+
const valid = validate(data);
|
|
74
|
+
const errors = validate.errors;
|
|
75
|
+
localize(errors);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
data,
|
|
79
|
+
success: valid,
|
|
80
|
+
message: ajv.errorsText(errors),
|
|
81
|
+
errors: errors,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type TypeOfSchema<S> = S extends TSchema ? Static<S> : any;
|
|
86
|
+
|
|
87
|
+
export namespace JsonSchema {
|
|
88
|
+
export let schemas: JsonSchemaDef[] = [];
|
|
89
|
+
|
|
90
|
+
export const createAjv = _createAjv;
|
|
91
|
+
|
|
92
|
+
export function addSchema(
|
|
93
|
+
schema: JsonSchemaDef,
|
|
94
|
+
{
|
|
95
|
+
onConflict = 'throw',
|
|
96
|
+
}: {
|
|
97
|
+
onConflict?: 'throw' | 'ignore' | 'replace' | ((old: JsonSchemaDef, neo: JsonSchemaDef) => JsonSchemaDef);
|
|
98
|
+
} = {},
|
|
99
|
+
) {
|
|
100
|
+
if (!schema.$id) throw new Error('Schema must have $id');
|
|
101
|
+
switch (onConflict) {
|
|
102
|
+
case 'ignore':
|
|
103
|
+
onConflict = (old) => old;
|
|
104
|
+
break;
|
|
105
|
+
case 'replace':
|
|
106
|
+
onConflict = (_, neo) => neo;
|
|
107
|
+
break;
|
|
108
|
+
case 'throw':
|
|
109
|
+
onConflict = (old, neo) => {
|
|
110
|
+
throw new Error(`Schema ${neo.$id} already exists`);
|
|
111
|
+
};
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
let idx = schemas.findIndex((s) => s.$id === schema.$id);
|
|
115
|
+
if (idx >= 0) {
|
|
116
|
+
schemas[idx] = onConflict(schemas[idx], schema);
|
|
117
|
+
} else {
|
|
118
|
+
schemas.push(schema);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check data is valid, will not use default
|
|
124
|
+
*/
|
|
125
|
+
export function check<S>(schema: S, data: any): ValidateResult<TypeOfSchema<S>> {
|
|
126
|
+
return validate({ schema, data, mutate: false, clone: true }) as any;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse data with default value and coerceTypes
|
|
131
|
+
*/
|
|
132
|
+
export function safeParse<S>(schema: S, data: any): ValidateResult<TypeOfSchema<S>> {
|
|
133
|
+
return validate({ schema, data, mutate: true, clone: true }) as any;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function parse<S>(schema: S, data: any): TypeOfSchema<S> {
|
|
137
|
+
const { data: out, message, errors } = validate({ schema, data, mutate: true, clone: true });
|
|
138
|
+
if (errors) {
|
|
139
|
+
throw new Error(message);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function create<S>(schema: S, data?: any): TypeOfSchema<S> {
|
|
145
|
+
// will not ensure value match the rule
|
|
146
|
+
return match(schema as JsonSchemaDef)
|
|
147
|
+
.returnType<any>()
|
|
148
|
+
.with({ const: P.select() }, (v) => v)
|
|
149
|
+
.with({ default: P.select() }, (v) => v)
|
|
150
|
+
.with({ anyOf: P.nonNullable }, (schema) => {
|
|
151
|
+
return create(schema.anyOf[0]);
|
|
152
|
+
})
|
|
153
|
+
.with({ oneOf: P.nonNullable }, (schema) => {
|
|
154
|
+
return create(schema.oneOf[0]);
|
|
155
|
+
})
|
|
156
|
+
.with({ type: 'string' }, (schema) => '')
|
|
157
|
+
.with({ type: P.union('number', 'integer') }, (schema) => 0)
|
|
158
|
+
.with({ type: 'object' }, (schema) => {
|
|
159
|
+
let out = validate({ schema, data: data ?? {}, mutate: true });
|
|
160
|
+
if (!out.success) {
|
|
161
|
+
// fallback
|
|
162
|
+
let obj = data || {};
|
|
163
|
+
schema.required?.forEach((key) => {
|
|
164
|
+
if (!(key in obj)) {
|
|
165
|
+
let last = obj[key];
|
|
166
|
+
let prop = schema.properties?.[key];
|
|
167
|
+
if (prop && isNil(last)) obj[key] = JsonSchema.create(prop, last);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
out = validate({ schema, data: obj, mutate: true });
|
|
171
|
+
if (!out.success) {
|
|
172
|
+
console.warn(`Failed to create object with schema: ${out.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out.data;
|
|
176
|
+
})
|
|
177
|
+
.with({ type: 'null' }, () => null)
|
|
178
|
+
.with({ type: 'boolean' }, (schema) => false)
|
|
179
|
+
.with({ type: 'array' }, (schema) => [])
|
|
180
|
+
.otherwise(() => {
|
|
181
|
+
return undefined;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function isPrimitiveType(schema: any): boolean {
|
|
186
|
+
return match(schema as JsonSchemaDef)
|
|
187
|
+
.returnType<boolean>()
|
|
188
|
+
.with({ type: P.union('number', 'integer', 'string', 'boolean') }, () => true)
|
|
189
|
+
.with({ anyOf: P.nonNullable }, (schema) => {
|
|
190
|
+
return isPrimitiveType(schema.anyOf[0]);
|
|
191
|
+
})
|
|
192
|
+
.with({ oneOf: P.nonNullable }, (schema) => {
|
|
193
|
+
return isPrimitiveType(schema.oneOf[0]);
|
|
194
|
+
})
|
|
195
|
+
.otherwise(() => false);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
type JsonSchemaTypeName =
|
|
2
|
+
| 'string' //
|
|
3
|
+
| 'number'
|
|
4
|
+
| 'integer'
|
|
5
|
+
| 'boolean'
|
|
6
|
+
| 'object'
|
|
7
|
+
| 'array'
|
|
8
|
+
| 'null';
|
|
9
|
+
|
|
10
|
+
type JsonValue =
|
|
11
|
+
| string //
|
|
12
|
+
| number
|
|
13
|
+
| boolean
|
|
14
|
+
| {
|
|
15
|
+
[key: string]: JsonValue;
|
|
16
|
+
}
|
|
17
|
+
| JsonValue[]
|
|
18
|
+
| null;
|
|
19
|
+
|
|
20
|
+
type JsonSchemaVersion =
|
|
21
|
+
| 'http://json-schema.org/schema' // latest
|
|
22
|
+
| 'https://json-schema.org/draft/2020-12/schema'
|
|
23
|
+
// draft-07
|
|
24
|
+
| 'http://json-schema.org/draft-07/schema#' //
|
|
25
|
+
| 'http://json-schema.org/draft-07/hyper-schema#';
|
|
26
|
+
|
|
27
|
+
type JsonSchemaFormatName =
|
|
28
|
+
//
|
|
29
|
+
| 'date-time'
|
|
30
|
+
| 'date'
|
|
31
|
+
| 'time'
|
|
32
|
+
| 'duration'
|
|
33
|
+
//
|
|
34
|
+
| 'email'
|
|
35
|
+
| 'idn-email'
|
|
36
|
+
//
|
|
37
|
+
| 'hostname'
|
|
38
|
+
| 'idn-hostname'
|
|
39
|
+
| 'ipv4'
|
|
40
|
+
| 'ipv6'
|
|
41
|
+
//
|
|
42
|
+
| 'uri'
|
|
43
|
+
| 'uri-reference'
|
|
44
|
+
| 'iri'
|
|
45
|
+
| 'iri-reference'
|
|
46
|
+
| 'uuid'
|
|
47
|
+
| 'uri-template'
|
|
48
|
+
| 'regex'
|
|
49
|
+
| 'json-pointer'
|
|
50
|
+
| 'relative-json-pointer';
|
|
51
|
+
|
|
52
|
+
export type JsonSchemaDef = {
|
|
53
|
+
$id?: string;
|
|
54
|
+
$ref?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Meta schema
|
|
57
|
+
*
|
|
58
|
+
* Recommended values:
|
|
59
|
+
* - 'http://json-schema.org/schema#'
|
|
60
|
+
* - 'http://json-schema.org/hyper-schema#'
|
|
61
|
+
* - 'http://json-schema.org/draft-07/schema#'
|
|
62
|
+
* - 'http://json-schema.org/draft-07/hyper-schema#'
|
|
63
|
+
*
|
|
64
|
+
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-5
|
|
65
|
+
*/
|
|
66
|
+
$schema?: JsonSchemaVersion | string;
|
|
67
|
+
$comment?: string;
|
|
68
|
+
|
|
69
|
+
$defs?: {
|
|
70
|
+
[key: string]: JsonSchemaDef;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type?: JsonSchemaTypeName | JsonSchemaTypeName[];
|
|
74
|
+
enum?: JsonValue[];
|
|
75
|
+
const?: JsonValue;
|
|
76
|
+
|
|
77
|
+
//region Numeric Validation
|
|
78
|
+
|
|
79
|
+
multipleOf?: number;
|
|
80
|
+
maximum?: number;
|
|
81
|
+
exclusiveMaximum?: number;
|
|
82
|
+
minimum?: number;
|
|
83
|
+
exclusiveMinimum?: number;
|
|
84
|
+
|
|
85
|
+
//endregion
|
|
86
|
+
|
|
87
|
+
//region String Validation
|
|
88
|
+
|
|
89
|
+
maxLength?: number;
|
|
90
|
+
minLength?: number;
|
|
91
|
+
pattern?: string;
|
|
92
|
+
|
|
93
|
+
//endregion
|
|
94
|
+
|
|
95
|
+
//region Array Validation
|
|
96
|
+
|
|
97
|
+
items?: JsonSchemaDef | JsonSchemaDef[];
|
|
98
|
+
additionalItems?: JsonSchemaDef;
|
|
99
|
+
maxItems?: number;
|
|
100
|
+
minItems?: number;
|
|
101
|
+
uniqueItems?: boolean;
|
|
102
|
+
contains?: JsonSchemaDef;
|
|
103
|
+
//endregion
|
|
104
|
+
|
|
105
|
+
//region Object Validation
|
|
106
|
+
|
|
107
|
+
maxProperties?: number;
|
|
108
|
+
minProperties?: number;
|
|
109
|
+
required?: string[];
|
|
110
|
+
properties?: {
|
|
111
|
+
[key: string]: JsonSchemaDef;
|
|
112
|
+
};
|
|
113
|
+
patternProperties?: {
|
|
114
|
+
[key: string]: JsonSchemaDef;
|
|
115
|
+
};
|
|
116
|
+
additionalProperties?: JsonSchemaDef;
|
|
117
|
+
dependencies?: {
|
|
118
|
+
[key: string]: JsonSchemaDef | string[];
|
|
119
|
+
};
|
|
120
|
+
propertyNames?: JsonSchemaDef;
|
|
121
|
+
//endregion
|
|
122
|
+
|
|
123
|
+
//region Conditional
|
|
124
|
+
|
|
125
|
+
if?: JsonSchemaDef;
|
|
126
|
+
then?: JsonSchemaDef;
|
|
127
|
+
else?: JsonSchemaDef;
|
|
128
|
+
|
|
129
|
+
//endregion
|
|
130
|
+
|
|
131
|
+
//region Boolean Logic
|
|
132
|
+
|
|
133
|
+
allOf?: JsonSchemaDef[];
|
|
134
|
+
anyOf?: JsonSchemaDef[];
|
|
135
|
+
oneOf?: JsonSchemaDef[];
|
|
136
|
+
not?: JsonSchemaDef;
|
|
137
|
+
//endregion
|
|
138
|
+
|
|
139
|
+
//region Semantic
|
|
140
|
+
|
|
141
|
+
format?: JsonSchemaFormatName | string;
|
|
142
|
+
|
|
143
|
+
//endregion
|
|
144
|
+
|
|
145
|
+
//region String-Encoding Non-JSON Data
|
|
146
|
+
|
|
147
|
+
contentMediaType?: string;
|
|
148
|
+
contentEncoding?: string;
|
|
149
|
+
contentSchema?: JsonSchemaDef;
|
|
150
|
+
//endregion
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* use {@link $defs} instead
|
|
154
|
+
*/
|
|
155
|
+
definitions?: Record<string, JsonSchemaDef>;
|
|
156
|
+
|
|
157
|
+
//region Meta-Data Annotations
|
|
158
|
+
/**
|
|
159
|
+
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-10
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
title?: string;
|
|
163
|
+
description?: string;
|
|
164
|
+
default?: JsonValue;
|
|
165
|
+
readOnly?: boolean;
|
|
166
|
+
writeOnly?: boolean;
|
|
167
|
+
examples?: JsonValue;
|
|
168
|
+
deprecated?: boolean;
|
|
169
|
+
|
|
170
|
+
//endregion
|
|
171
|
+
|
|
172
|
+
nullable?: boolean;
|
|
173
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { MaybePromise } from '@wener/utils';
|
|
2
|
+
import { startCase } from 'es-toolkit';
|
|
3
|
+
|
|
4
|
+
type DefineInitOptions = {
|
|
5
|
+
name: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
onInit?: () => any;
|
|
8
|
+
metadata?: Record<string, any>;
|
|
9
|
+
};
|
|
10
|
+
export type InitDef = {
|
|
11
|
+
name: string;
|
|
12
|
+
title: string;
|
|
13
|
+
onInit?: () => any;
|
|
14
|
+
metadata: Record<string, any>;
|
|
15
|
+
};
|
|
16
|
+
let _all: InitDef[] = [];
|
|
17
|
+
|
|
18
|
+
export function defineInit(o: DefineInitOptions) {
|
|
19
|
+
const def = {
|
|
20
|
+
title: startCase(o.name),
|
|
21
|
+
metadata: {},
|
|
22
|
+
...o,
|
|
23
|
+
};
|
|
24
|
+
let idx = _all.findIndex((v) => v.name === def.name);
|
|
25
|
+
if (idx >= 0) {
|
|
26
|
+
console.warn(`skip redefined init: ${def.name}`);
|
|
27
|
+
} else {
|
|
28
|
+
_all.push(def);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return def;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let result = new Map<any, MaybePromise<InitResult>>();
|
|
35
|
+
|
|
36
|
+
type InitResult = {
|
|
37
|
+
name: string;
|
|
38
|
+
success: boolean;
|
|
39
|
+
error?: any;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function runInit(inits = _all) {
|
|
43
|
+
for (let init of inits) {
|
|
44
|
+
if (result.get(init)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
result.set(
|
|
48
|
+
init,
|
|
49
|
+
Promise.resolve().then(async () => {
|
|
50
|
+
let out: InitResult = {
|
|
51
|
+
name: init.name,
|
|
52
|
+
success: true,
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
await init.onInit?.();
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(`Failed to init ${init.name}`, e);
|
|
58
|
+
out.success = false;
|
|
59
|
+
out.error = e;
|
|
60
|
+
}
|
|
61
|
+
result.set(init, out);
|
|
62
|
+
return out;
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Promise.all(result.values());
|
|
68
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import { createMetadataKey, defineMetadata, getMetadata } from './defineMetadata';
|
|
3
|
+
|
|
4
|
+
test('defineMetadata', () => {
|
|
5
|
+
const key = createMetadataKey<string>('name');
|
|
6
|
+
|
|
7
|
+
const user = {
|
|
8
|
+
metadata: {},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
defineMetadata(user, key, 'wener');
|
|
12
|
+
|
|
13
|
+
expect(user.metadata).toEqual({ name: 'wener' });
|
|
14
|
+
expect(getMetadata(user, key)).toEqual('wener');
|
|
15
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { startCase } from 'es-toolkit';
|
|
2
|
+
import { get, set } from 'es-toolkit/compat';
|
|
3
|
+
|
|
4
|
+
type HasMetadata = {
|
|
5
|
+
metadata?: Record<string, any>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type MetadataKey<T> = {
|
|
9
|
+
key: string;
|
|
10
|
+
type: T;
|
|
11
|
+
title: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface CreateMetadataKeyOptions {
|
|
16
|
+
key: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createMetadataKey<T = never>(key: string, opts?: Omit<CreateMetadataKeyOptions, 'key'>): MetadataKey<T>;
|
|
22
|
+
export function createMetadataKey<T = never>(opts: CreateMetadataKeyOptions): MetadataKey<T>;
|
|
23
|
+
export function createMetadataKey<T = never>(a: any, b?: any): MetadataKey<T> {
|
|
24
|
+
const opts: CreateMetadataKeyOptions =
|
|
25
|
+
typeof a === 'string'
|
|
26
|
+
? {
|
|
27
|
+
key: a,
|
|
28
|
+
...b,
|
|
29
|
+
}
|
|
30
|
+
: a;
|
|
31
|
+
|
|
32
|
+
const { key } = opts;
|
|
33
|
+
opts.title ||= startCase(key);
|
|
34
|
+
|
|
35
|
+
const k: MetadataKey<T> = {
|
|
36
|
+
...opts,
|
|
37
|
+
type: null as any,
|
|
38
|
+
} as MetadataKey<T>;
|
|
39
|
+
|
|
40
|
+
if ('toStringTag' in Symbol && typeof Symbol.toStringTag === 'symbol') {
|
|
41
|
+
Object.defineProperty(k, Symbol.toStringTag, {
|
|
42
|
+
value: key,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return k;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function defineMetadata<T>(res: HasMetadata, key: MetadataKey<T>, opts: T) {
|
|
50
|
+
res.metadata = res.metadata || {};
|
|
51
|
+
set(res.metadata, key.key, opts);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getMetadata<T>(res: HasMetadata | undefined | null, key: MetadataKey<T>): T | undefined {
|
|
55
|
+
if (!res?.metadata) return undefined;
|
|
56
|
+
return get(res.metadata, key.key);
|
|
57
|
+
}
|