@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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/lib/index.js +3 -0
  3. package/lib/index.js.map +1 -0
  4. package/lib/jsonschema/JsonSchema.js +180 -0
  5. package/lib/jsonschema/JsonSchema.js.map +1 -0
  6. package/lib/jsonschema/index.js +2 -0
  7. package/lib/jsonschema/index.js.map +1 -0
  8. package/lib/jsonschema/types.d.js +3 -0
  9. package/lib/jsonschema/types.d.js.map +1 -0
  10. package/lib/meta/defineInit.js +42 -0
  11. package/lib/meta/defineInit.js.map +1 -0
  12. package/lib/meta/defineMetadata.js +30 -0
  13. package/lib/meta/defineMetadata.js.map +1 -0
  14. package/lib/meta/index.js +3 -0
  15. package/lib/meta/index.js.map +1 -0
  16. package/lib/normalizePagination.js +14 -0
  17. package/lib/normalizePagination.js.map +1 -0
  18. package/lib/parseSort.js +91 -0
  19. package/lib/parseSort.js.map +1 -0
  20. package/lib/password/PHC.js +200 -0
  21. package/lib/password/PHC.js.map +1 -0
  22. package/lib/password/Password.js +83 -0
  23. package/lib/password/Password.js.map +1 -0
  24. package/lib/password/createArgon2PasswordAlgorithm.js +53 -0
  25. package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -0
  26. package/lib/password/createBase64PasswordAlgorithm.js +14 -0
  27. package/lib/password/createBase64PasswordAlgorithm.js.map +1 -0
  28. package/lib/password/createBcryptPasswordAlgorithm.js +20 -0
  29. package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -0
  30. package/lib/password/createPBKDF2PasswordAlgorithm.js +54 -0
  31. package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -0
  32. package/lib/password/createScryptPasswordAlgorithm.js +66 -0
  33. package/lib/password/createScryptPasswordAlgorithm.js.map +1 -0
  34. package/lib/password/index.js +6 -0
  35. package/lib/password/index.js.map +1 -0
  36. package/lib/password/server/index.js +2 -0
  37. package/lib/password/server/index.js.map +1 -0
  38. package/lib/tools/renderJsonSchemaToMarkdownDoc.js +85 -0
  39. package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -0
  40. package/package.json +56 -0
  41. package/src/index.ts +2 -0
  42. package/src/jsonschema/JsonSchema.test.ts +27 -0
  43. package/src/jsonschema/JsonSchema.ts +197 -0
  44. package/src/jsonschema/index.ts +2 -0
  45. package/src/jsonschema/types.d.ts +173 -0
  46. package/src/meta/defineInit.ts +68 -0
  47. package/src/meta/defineMetadata.test.ts +15 -0
  48. package/src/meta/defineMetadata.ts +57 -0
  49. package/src/meta/index.ts +3 -0
  50. package/src/normalizePagination.ts +25 -0
  51. package/src/parseSort.test.ts +41 -0
  52. package/src/parseSort.ts +115 -0
  53. package/src/password/PHC.test.ts +317 -0
  54. package/src/password/PHC.ts +247 -0
  55. package/src/password/Password.test.ts +58 -0
  56. package/src/password/Password.ts +113 -0
  57. package/src/password/createArgon2PasswordAlgorithm.ts +80 -0
  58. package/src/password/createBase64PasswordAlgorithm.ts +14 -0
  59. package/src/password/createBcryptPasswordAlgorithm.ts +30 -0
  60. package/src/password/createPBKDF2PasswordAlgorithm.ts +73 -0
  61. package/src/password/createScryptPasswordAlgorithm.ts +72 -0
  62. package/src/password/index.ts +5 -0
  63. package/src/password/server/index.ts +1 -0
  64. 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,2 @@
1
+ export { normalizePagination } from './normalizePagination';
2
+ export { parseSort, type SortRule } from './parseSort';
@@ -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,2 @@
1
+ export { JsonSchema } from './JsonSchema';
2
+ export type { JsonSchemaDef } from './types';
@@ -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
+ }