@powerlines/schema 0.11.27 → 0.11.37
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/dist/bundle.cjs +34 -20
- package/dist/bundle.d.cts +3 -3
- package/dist/bundle.d.cts.map +1 -1
- package/dist/bundle.d.mts +3 -3
- package/dist/bundle.d.mts.map +1 -1
- package/dist/bundle.mjs +35 -21
- package/dist/bundle.mjs.map +1 -1
- package/dist/codegen.cjs +40 -36
- package/dist/codegen.d.cts +10 -22
- package/dist/codegen.d.cts.map +1 -1
- package/dist/codegen.d.mts +10 -22
- package/dist/codegen.d.mts.map +1 -1
- package/dist/codegen.mjs +39 -36
- package/dist/codegen.mjs.map +1 -1
- package/dist/constants.cjs +34 -11
- package/dist/constants.d.cts +9 -11
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.mts +9 -11
- package/dist/constants.d.mts.map +1 -1
- package/dist/constants.mjs +32 -11
- package/dist/constants.mjs.map +1 -1
- package/dist/extract.cjs +122 -92
- package/dist/extract.d.cts +34 -69
- package/dist/extract.d.cts.map +1 -1
- package/dist/extract.d.mts +34 -69
- package/dist/extract.d.mts.map +1 -1
- package/dist/extract.mjs +123 -93
- package/dist/extract.mjs.map +1 -1
- package/dist/helpers.cjs +45 -52
- package/dist/helpers.d.cts +28 -24
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.mts +28 -24
- package/dist/helpers.d.mts.map +1 -1
- package/dist/helpers.mjs +46 -52
- package/dist/helpers.mjs.map +1 -1
- package/dist/index.cjs +14 -7
- package/dist/index.d.cts +6 -6
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +5 -5
- package/dist/metadata.cjs +80 -0
- package/dist/metadata.d.cts +52 -0
- package/dist/metadata.d.cts.map +1 -0
- package/dist/metadata.d.mts +52 -0
- package/dist/metadata.d.mts.map +1 -0
- package/dist/metadata.mjs +76 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/persistence.cjs +1 -2
- package/dist/persistence.d.cts +5 -5
- package/dist/persistence.d.cts.map +1 -1
- package/dist/persistence.d.mts +5 -5
- package/dist/persistence.d.mts.map +1 -1
- package/dist/persistence.mjs +1 -1
- package/dist/persistence.mjs.map +1 -1
- package/dist/reflection.cjs +289 -303
- package/dist/reflection.d.cts +3 -16
- package/dist/reflection.d.cts.map +1 -1
- package/dist/reflection.d.mts +3 -16
- package/dist/reflection.d.mts.map +1 -1
- package/dist/reflection.mjs +290 -304
- package/dist/reflection.mjs.map +1 -1
- package/dist/resolve.cjs +7 -7
- package/dist/resolve.mjs +7 -7
- package/dist/resolve.mjs.map +1 -1
- package/dist/type-checks.cjs +122 -40
- package/dist/type-checks.d.cts +41 -33
- package/dist/type-checks.d.cts.map +1 -1
- package/dist/type-checks.d.mts +41 -33
- package/dist/type-checks.d.mts.map +1 -1
- package/dist/type-checks.mjs +120 -37
- package/dist/type-checks.mjs.map +1 -1
- package/dist/types.d.cts +225 -113
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +225 -113
- package/dist/types.d.mts.map +1 -1
- package/package.json +11 -7
- package/dist/jtd.cjs +0 -385
- package/dist/jtd.d.cts +0 -15
- package/dist/jtd.d.cts.map +0 -1
- package/dist/jtd.d.mts +0 -15
- package/dist/jtd.d.mts.map +0 -1
- package/dist/jtd.mjs +0 -384
- package/dist/jtd.mjs.map +0 -1
package/dist/reflection.mjs
CHANGED
|
@@ -1,237 +1,259 @@
|
|
|
1
|
+
import { isJsonSchema, isJsonSchemaObject, isNullOnlyJsonSchema } from "./type-checks.mjs";
|
|
1
2
|
import defu from "defu";
|
|
2
|
-
import { isSetArray, isSetObject, isSetString } from "@stryke/type-checks";
|
|
3
|
+
import { isSetArray, isSetObject, isSetString, isUndefined } from "@stryke/type-checks";
|
|
3
4
|
import { ReflectionClass, ReflectionKind, TypeNumberBrand } from "@powerlines/deepkit/vendor/type";
|
|
4
5
|
|
|
5
6
|
//#region src/reflection.ts
|
|
6
7
|
/**
|
|
7
|
-
* Maps a Deepkit numeric `brand` to
|
|
8
|
+
* Maps a Deepkit numeric `brand` to JSON Schema `type` and `format`.
|
|
8
9
|
*
|
|
9
|
-
* @
|
|
10
|
-
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* This function takes a `TypeNumberBrand` (which represents specific numeric types in Deepkit, such as `integer`, `float`, `int8`, etc.) and returns a corresponding JSON Schema fragment that includes the appropriate `type`, `format`, and any relevant keywords (like `multipleOf` for integers). If the brand is not recognized, it defaults to a generic JSON Schema for numbers.
|
|
12
|
+
*
|
|
13
|
+
* @param brand - The Deepkit numeric brand to convert.
|
|
14
|
+
* @return A JSON Schema fragment representing the numeric type corresponding to the provided brand.
|
|
11
15
|
*/
|
|
12
|
-
function
|
|
16
|
+
function numberBrandToJsonSchema(brand) {
|
|
13
17
|
switch (brand) {
|
|
14
|
-
case TypeNumberBrand.integer: return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
case TypeNumberBrand.
|
|
20
|
-
|
|
18
|
+
case TypeNumberBrand.integer: return {
|
|
19
|
+
type: "integer",
|
|
20
|
+
format: "int32",
|
|
21
|
+
multipleOf: 1
|
|
22
|
+
};
|
|
23
|
+
case TypeNumberBrand.int8: return {
|
|
24
|
+
type: "integer",
|
|
25
|
+
format: "int8",
|
|
26
|
+
multipleOf: 1
|
|
27
|
+
};
|
|
28
|
+
case TypeNumberBrand.uint8: return {
|
|
29
|
+
type: "integer",
|
|
30
|
+
format: "uint8",
|
|
31
|
+
multipleOf: 1
|
|
32
|
+
};
|
|
33
|
+
case TypeNumberBrand.int16: return {
|
|
34
|
+
type: "integer",
|
|
35
|
+
format: "int16",
|
|
36
|
+
multipleOf: 1
|
|
37
|
+
};
|
|
38
|
+
case TypeNumberBrand.uint16: return {
|
|
39
|
+
type: "integer",
|
|
40
|
+
format: "uint16",
|
|
41
|
+
multipleOf: 1
|
|
42
|
+
};
|
|
43
|
+
case TypeNumberBrand.int32: return {
|
|
44
|
+
type: "integer",
|
|
45
|
+
format: "int32",
|
|
46
|
+
multipleOf: 1
|
|
47
|
+
};
|
|
48
|
+
case TypeNumberBrand.uint32: return {
|
|
49
|
+
type: "integer",
|
|
50
|
+
format: "uint32",
|
|
51
|
+
multipleOf: 1
|
|
52
|
+
};
|
|
21
53
|
case TypeNumberBrand.float:
|
|
22
|
-
case TypeNumberBrand.float32: return
|
|
23
|
-
|
|
54
|
+
case TypeNumberBrand.float32: return {
|
|
55
|
+
type: "number",
|
|
56
|
+
format: "float"
|
|
57
|
+
};
|
|
58
|
+
case TypeNumberBrand.float64: return {
|
|
59
|
+
type: "number",
|
|
60
|
+
format: "double"
|
|
61
|
+
};
|
|
24
62
|
case void 0:
|
|
25
|
-
default: return "
|
|
63
|
+
default: return { type: "number" };
|
|
26
64
|
}
|
|
27
65
|
}
|
|
66
|
+
function withReflectionTags(reflection, schema) {
|
|
67
|
+
if (!isSetObject(reflection?.tags)) return schema;
|
|
68
|
+
const tags = reflection.tags;
|
|
69
|
+
if (isSetString(tags.title)) schema.title = tags.title;
|
|
70
|
+
if (isSetArray(tags.alias)) schema.alias = tags.alias;
|
|
71
|
+
if (!isUndefined(tags.hidden)) schema.hidden = tags.hidden;
|
|
72
|
+
if (!isUndefined(tags.ignore)) schema.ignore = tags.ignore;
|
|
73
|
+
if (!isUndefined(tags.internal)) schema.internal = tags.internal;
|
|
74
|
+
if (!isUndefined(tags.runtime)) schema.runtime = tags.runtime;
|
|
75
|
+
if (!isUndefined(tags.readonly)) schema.readOnly = tags.readonly;
|
|
76
|
+
return schema;
|
|
77
|
+
}
|
|
78
|
+
function withNullable(schema, nullable) {
|
|
79
|
+
if (!nullable) return schema;
|
|
80
|
+
const types = Array.isArray(schema.type) ? [...schema.type] : schema.type ? [schema.type] : [];
|
|
81
|
+
if (!types.includes("null")) types.push("null");
|
|
82
|
+
return {
|
|
83
|
+
...schema,
|
|
84
|
+
type: types.length === 1 ? types[0] : types
|
|
85
|
+
};
|
|
86
|
+
}
|
|
28
87
|
/**
|
|
29
|
-
* Converts a Deepkit type reflection into a JSON
|
|
30
|
-
*
|
|
31
|
-
* @remarks
|
|
32
|
-
* Some TypeScript constructs have no direct JTD equivalent and are handled with the closest available form:
|
|
33
|
-
*
|
|
34
|
-
* - `null` and `undefined` become the empty JTD form with `nullable: true`.
|
|
35
|
-
* - Unions of primitives that cannot be expressed as a JTD enum collapse to the empty form (which validates any value).
|
|
36
|
-
* - String/number/bigint literal unions are emitted as a JTD enum (non-string members are stringified, as JTD requires string enum members).
|
|
37
|
-
* - Tuples are emitted as a JTD elements form whose element schema is the single tuple member type, or the empty schema for mixed tuples.
|
|
38
|
-
* - `Date` is emitted as `{ type: "timestamp" }`.
|
|
39
|
-
* - Discriminated unions of object literals (a shared string-literal tag property) are emitted as a JTD discriminator form.
|
|
40
|
-
*
|
|
41
|
-
* @param reflection - The Deepkit type reflection to convert.
|
|
42
|
-
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
88
|
+
* Converts a Deepkit type reflection into a JSON Schema (draft-07) fragment.
|
|
43
89
|
*/
|
|
44
90
|
function reflectionToJsonSchema(reflection) {
|
|
45
|
-
return
|
|
91
|
+
return reflectionToJsonSchemaInner(reflection);
|
|
46
92
|
}
|
|
47
|
-
|
|
48
|
-
* Internal worker that performs the recursive Deepkit reflection → JTD conversion.
|
|
49
|
-
*
|
|
50
|
-
* @param reflection - The Deepkit type reflection to convert.
|
|
51
|
-
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
52
|
-
*/
|
|
53
|
-
function reflectionToJtd(reflection) {
|
|
54
|
-
const schema = {};
|
|
55
|
-
if (isSetObject(reflection?.tags)) {
|
|
56
|
-
const tags = reflection.tags;
|
|
57
|
-
schema.metadata = schema.metadata ?? {};
|
|
58
|
-
if (tags.readonly === true) schema.metadata.isReadonly = true;
|
|
59
|
-
if (tags.ignore === true) schema.metadata.isIgnored = true;
|
|
60
|
-
if (tags.internal === true) schema.metadata.isInternal = true;
|
|
61
|
-
if (tags.runtime === true) schema.metadata.isRuntime = true;
|
|
62
|
-
if (tags.hidden === true) schema.metadata.isHidden = true;
|
|
63
|
-
if (isSetArray(tags.alias)) schema.metadata.alias = tags.alias;
|
|
64
|
-
if (isSetString(tags.title)) schema.metadata.title = tags.title;
|
|
65
|
-
}
|
|
93
|
+
function reflectionToJsonSchemaInner(reflection) {
|
|
66
94
|
switch (reflection.kind) {
|
|
67
95
|
case ReflectionKind.any:
|
|
68
96
|
case ReflectionKind.unknown:
|
|
69
97
|
case ReflectionKind.void:
|
|
70
|
-
case ReflectionKind.object: return {};
|
|
98
|
+
case ReflectionKind.object: return withReflectionTags(reflection, { name: reflection.typeName });
|
|
71
99
|
case ReflectionKind.never: return;
|
|
72
100
|
case ReflectionKind.undefined:
|
|
73
|
-
case ReflectionKind.null: return {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
};
|
|
78
|
-
case ReflectionKind.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
82
|
-
case ReflectionKind.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
};
|
|
86
|
-
case ReflectionKind.
|
|
87
|
-
|
|
88
|
-
type: "
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
case ReflectionKind.null: return withReflectionTags(reflection, {
|
|
102
|
+
type: "null",
|
|
103
|
+
name: reflection.typeName,
|
|
104
|
+
nullable: true
|
|
105
|
+
});
|
|
106
|
+
case ReflectionKind.string: return withReflectionTags(reflection, {
|
|
107
|
+
type: "string",
|
|
108
|
+
name: reflection.typeName
|
|
109
|
+
});
|
|
110
|
+
case ReflectionKind.boolean: return withReflectionTags(reflection, {
|
|
111
|
+
type: "boolean",
|
|
112
|
+
name: reflection.typeName
|
|
113
|
+
});
|
|
114
|
+
case ReflectionKind.number: return withReflectionTags(reflection, numberBrandToJsonSchema(reflection.brand));
|
|
115
|
+
case ReflectionKind.bigint: return withReflectionTags(reflection, {
|
|
116
|
+
type: "integer",
|
|
117
|
+
name: reflection.typeName,
|
|
118
|
+
format: "int64",
|
|
119
|
+
multipleOf: 1
|
|
120
|
+
});
|
|
121
|
+
case ReflectionKind.regexp: return withReflectionTags(reflection, {
|
|
122
|
+
type: "string",
|
|
123
|
+
name: reflection.typeName,
|
|
124
|
+
format: "regex",
|
|
125
|
+
contentMediaType: "text/regex"
|
|
126
|
+
});
|
|
94
127
|
case ReflectionKind.literal: {
|
|
95
128
|
const { literal } = reflection;
|
|
96
|
-
if (typeof literal === "string") return {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
108
|
-
if (literal instanceof RegExp) return {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
if (typeof literal === "string" || typeof literal === "number" || typeof literal === "boolean") return withReflectionTags(reflection, {
|
|
130
|
+
type: typeof literal,
|
|
131
|
+
name: reflection.typeName,
|
|
132
|
+
const: literal
|
|
133
|
+
});
|
|
134
|
+
if (typeof literal === "bigint") return withReflectionTags(reflection, {
|
|
135
|
+
type: "integer",
|
|
136
|
+
name: reflection.typeName,
|
|
137
|
+
format: "int64",
|
|
138
|
+
multipleOf: 1,
|
|
139
|
+
const: String(literal)
|
|
140
|
+
});
|
|
141
|
+
if (literal instanceof RegExp) return withReflectionTags(reflection, {
|
|
142
|
+
type: "string",
|
|
143
|
+
name: reflection.typeName,
|
|
144
|
+
format: "regex",
|
|
145
|
+
const: literal.source
|
|
146
|
+
});
|
|
147
|
+
return withReflectionTags(reflection, { name: reflection.typeName });
|
|
113
148
|
}
|
|
114
|
-
case ReflectionKind.templateLiteral: return {
|
|
115
|
-
...schema,
|
|
116
|
-
type: "string"
|
|
117
|
-
};
|
|
149
|
+
case ReflectionKind.templateLiteral: return withReflectionTags(reflection, { type: "string" });
|
|
118
150
|
case ReflectionKind.enum: {
|
|
119
|
-
const values = reflection.values.filter((value) => typeof value === "string" || typeof value === "number"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
151
|
+
const values = reflection.values.filter((value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean");
|
|
152
|
+
if (values.length === 0) return withReflectionTags(reflection, {
|
|
153
|
+
name: reflection.typeName,
|
|
154
|
+
description: reflection.description
|
|
155
|
+
});
|
|
156
|
+
return withReflectionTags(reflection, {
|
|
157
|
+
name: reflection.typeName,
|
|
158
|
+
description: reflection.description,
|
|
159
|
+
enum: values
|
|
160
|
+
});
|
|
126
161
|
}
|
|
127
162
|
case ReflectionKind.array: {
|
|
128
|
-
const items =
|
|
129
|
-
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
const items = reflectionToJsonSchemaInner(reflection.type);
|
|
164
|
+
return withReflectionTags(reflection, {
|
|
165
|
+
type: "array",
|
|
166
|
+
name: reflection.typeName,
|
|
167
|
+
items: items ?? {}
|
|
168
|
+
});
|
|
133
169
|
}
|
|
134
170
|
case ReflectionKind.tuple: {
|
|
135
|
-
const items = reflection.types.map((member) =>
|
|
136
|
-
if (items.length
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
};
|
|
171
|
+
const items = reflection.types.map((member) => reflectionToJsonSchemaInner(member.type)).filter((item) => item !== void 0);
|
|
172
|
+
if (items.length <= 1) return withReflectionTags(reflection, {
|
|
173
|
+
type: "array",
|
|
174
|
+
name: reflection.typeName,
|
|
175
|
+
items: items[0] ?? {}
|
|
176
|
+
});
|
|
177
|
+
return withReflectionTags(reflection, {
|
|
178
|
+
type: "array",
|
|
179
|
+
name: reflection.typeName,
|
|
180
|
+
items,
|
|
181
|
+
minItems: items.length,
|
|
182
|
+
maxItems: items.length
|
|
183
|
+
});
|
|
148
184
|
}
|
|
149
185
|
case ReflectionKind.union: {
|
|
150
|
-
const branches = reflection.types.map((inner) =>
|
|
186
|
+
const branches = reflection.types.map((inner) => reflectionToJsonSchemaInner(inner)).filter(isJsonSchema);
|
|
151
187
|
const nullable = reflection.types.some((inner) => inner.kind === ReflectionKind.null || inner.kind === ReflectionKind.undefined);
|
|
152
|
-
const nonNull = branches.filter((
|
|
153
|
-
if (nonNull.length === 0) return {
|
|
154
|
-
|
|
188
|
+
const nonNull = branches.filter((branch) => branch.type !== "null" && !isNullOnlyJsonSchema(branch));
|
|
189
|
+
if (nonNull.length === 0) return withReflectionTags(reflection, {
|
|
190
|
+
type: "null",
|
|
155
191
|
nullable: true
|
|
156
|
-
};
|
|
157
|
-
if (nonNull.length === 1) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const merged = Array.from(new Set(nonNull.flatMap((b) => b.enum)));
|
|
167
|
-
const form = {
|
|
168
|
-
...schema,
|
|
169
|
-
enum: merged
|
|
170
|
-
};
|
|
171
|
-
if (nullable) form.nullable = true;
|
|
172
|
-
return {
|
|
173
|
-
...schema,
|
|
174
|
-
...form
|
|
175
|
-
};
|
|
176
|
-
}
|
|
192
|
+
});
|
|
193
|
+
if (nonNull.length === 1) return withNullable(withReflectionTags(reflection, {
|
|
194
|
+
name: reflection.typeName,
|
|
195
|
+
...nonNull[0]
|
|
196
|
+
}), nullable);
|
|
197
|
+
const enumValues = nonNull.map((branch) => branch.const).filter((value) => value !== void 0);
|
|
198
|
+
if (enumValues.length === nonNull.length) return withNullable(withReflectionTags(reflection, {
|
|
199
|
+
name: reflection.typeName,
|
|
200
|
+
enum: enumValues
|
|
201
|
+
}), nullable);
|
|
177
202
|
const discriminator = tryReflectionDiscriminator(reflection.types);
|
|
178
|
-
if (discriminator) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (nullable) fallback.nullable = true;
|
|
187
|
-
return {
|
|
188
|
-
...schema,
|
|
189
|
-
...fallback
|
|
190
|
-
};
|
|
203
|
+
if (discriminator) return withNullable(withReflectionTags(reflection, {
|
|
204
|
+
name: reflection.typeName,
|
|
205
|
+
...discriminator
|
|
206
|
+
}), nullable);
|
|
207
|
+
return withNullable(withReflectionTags(reflection, {
|
|
208
|
+
name: reflection.typeName,
|
|
209
|
+
anyOf: nonNull
|
|
210
|
+
}), nullable);
|
|
191
211
|
}
|
|
192
212
|
case ReflectionKind.intersection: {
|
|
193
|
-
const members = reflection.types.map((inner) =>
|
|
213
|
+
const members = reflection.types.map((inner) => reflectionToJsonSchemaInner(inner)).filter((item) => item !== void 0);
|
|
194
214
|
if (members.length === 0) return;
|
|
195
|
-
if (members.length === 1) return {
|
|
196
|
-
|
|
197
|
-
...members[0]
|
|
198
|
-
};
|
|
199
|
-
if (members.every((member) => member && isPropertiesForm(member))) return mergePropertiesForms(members);
|
|
200
|
-
return {
|
|
201
|
-
...schema,
|
|
215
|
+
if (members.length === 1) return withReflectionTags(reflection, {
|
|
216
|
+
name: reflection.typeName,
|
|
202
217
|
...members[0]
|
|
203
|
-
};
|
|
218
|
+
});
|
|
219
|
+
if (members.every(isJsonSchemaObject)) return withReflectionTags(reflection, {
|
|
220
|
+
name: reflection.typeName,
|
|
221
|
+
...mergeObjectSchemas(members)
|
|
222
|
+
});
|
|
223
|
+
return withReflectionTags(reflection, {
|
|
224
|
+
name: reflection.typeName,
|
|
225
|
+
allOf: members
|
|
226
|
+
});
|
|
204
227
|
}
|
|
205
|
-
case ReflectionKind.promise: return
|
|
206
|
-
case ReflectionKind.objectLiteral: return
|
|
228
|
+
case ReflectionKind.promise: return reflectionToJsonSchemaInner(reflection.type);
|
|
229
|
+
case ReflectionKind.objectLiteral: return objectReflectionToJsonSchema(reflection);
|
|
207
230
|
case ReflectionKind.class: switch (reflection.classType?.name) {
|
|
208
|
-
case "Date": return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
};
|
|
212
|
-
case "RegExp": return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
216
|
-
case "URL": return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
};
|
|
231
|
+
case "Date": return withReflectionTags(reflection, {
|
|
232
|
+
type: "string",
|
|
233
|
+
format: "date-time"
|
|
234
|
+
});
|
|
235
|
+
case "RegExp": return withReflectionTags(reflection, {
|
|
236
|
+
type: "string",
|
|
237
|
+
format: "regex"
|
|
238
|
+
});
|
|
239
|
+
case "URL": return withReflectionTags(reflection, {
|
|
240
|
+
type: "string",
|
|
241
|
+
format: "uri"
|
|
242
|
+
});
|
|
220
243
|
case "Set": {
|
|
221
244
|
const itemType = reflection.arguments?.[0];
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
};
|
|
245
|
+
return withReflectionTags(reflection, {
|
|
246
|
+
type: "array",
|
|
247
|
+
items: (itemType ? reflectionToJsonSchemaInner(itemType) : void 0) ?? {},
|
|
248
|
+
uniqueItems: true
|
|
249
|
+
});
|
|
227
250
|
}
|
|
228
251
|
case "Map": {
|
|
229
252
|
const valueType = reflection.arguments?.[1];
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
};
|
|
253
|
+
return withReflectionTags(reflection, {
|
|
254
|
+
type: "object",
|
|
255
|
+
additionalProperties: (valueType ? reflectionToJsonSchemaInner(valueType) : void 0) ?? true
|
|
256
|
+
});
|
|
235
257
|
}
|
|
236
258
|
case "Uint8Array":
|
|
237
259
|
case "Uint8ClampedArray":
|
|
@@ -243,12 +265,17 @@ function reflectionToJtd(reflection) {
|
|
|
243
265
|
case "Float32Array":
|
|
244
266
|
case "Float64Array":
|
|
245
267
|
case "BigInt64Array":
|
|
246
|
-
case "BigUint64Array": return {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
268
|
+
case "BigUint64Array": return withReflectionTags(reflection, {
|
|
269
|
+
type: "string",
|
|
270
|
+
format: "byte",
|
|
271
|
+
contentEncoding: "base64"
|
|
272
|
+
});
|
|
250
273
|
case void 0:
|
|
251
|
-
default: return
|
|
274
|
+
default: return withReflectionTags(reflection, {
|
|
275
|
+
name: reflection.typeName,
|
|
276
|
+
description: reflection.description,
|
|
277
|
+
...objectReflectionToJsonSchema(reflection)
|
|
278
|
+
});
|
|
252
279
|
}
|
|
253
280
|
case ReflectionKind.symbol:
|
|
254
281
|
case ReflectionKind.property:
|
|
@@ -267,72 +294,26 @@ function reflectionToJtd(reflection) {
|
|
|
267
294
|
default: return;
|
|
268
295
|
}
|
|
269
296
|
}
|
|
270
|
-
|
|
271
|
-
* Tests whether a JTD form is an enum form.
|
|
272
|
-
*
|
|
273
|
-
* @param form - The JTD form to inspect.
|
|
274
|
-
* @returns `true` if the form is a JTD enum form.
|
|
275
|
-
*/
|
|
276
|
-
function isEnumForm(form) {
|
|
277
|
-
return Array.isArray(form.enum);
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Tests whether a JTD form is a properties form (object).
|
|
281
|
-
*
|
|
282
|
-
* @param form - The JTD form to inspect.
|
|
283
|
-
* @returns `true` if the form is a JTD properties form.
|
|
284
|
-
*/
|
|
285
|
-
function isPropertiesForm(form) {
|
|
286
|
-
return "properties" in form || "optionalProperties" in form;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Tests whether a JTD form is the empty `{ nullable: true }` placeholder.
|
|
290
|
-
*
|
|
291
|
-
* @param form - The JTD form to inspect.
|
|
292
|
-
* @returns `true` if the form has no shape constraints beyond `nullable`.
|
|
293
|
-
*/
|
|
294
|
-
function isPureNullable(form) {
|
|
295
|
-
return Object.keys(form).filter((k) => k !== "nullable" && k !== "metadata").length === 0 && form.nullable === true;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Shallow-merges two JTD properties forms, unioning their `properties` and `optionalProperties` maps.
|
|
299
|
-
*
|
|
300
|
-
* @param forms - The JTD properties forms to merge.
|
|
301
|
-
* @returns The merged JTD properties form.
|
|
302
|
-
*/
|
|
303
|
-
function mergePropertiesForms(forms) {
|
|
297
|
+
function mergeObjectSchemas(schemas) {
|
|
304
298
|
const merged = {
|
|
299
|
+
type: "object",
|
|
305
300
|
properties: {},
|
|
306
|
-
|
|
301
|
+
required: []
|
|
307
302
|
};
|
|
308
|
-
for (const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
if (optionalProperties) defu(merged.optionalProperties, optionalProperties);
|
|
313
|
-
if (form.additionalProperties) merged.additionalProperties = true;
|
|
303
|
+
for (const schema of schemas) {
|
|
304
|
+
if (schema.properties) merged.properties = defu(merged.properties, schema.properties);
|
|
305
|
+
if (schema.required) merged.required = Array.from(new Set([...merged.required ?? [], ...schema.required]));
|
|
306
|
+
if (schema.additionalProperties !== void 0) merged.additionalProperties = schema.additionalProperties;
|
|
314
307
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const result = {};
|
|
318
|
-
if (hasProperties) result.properties = merged.properties;
|
|
319
|
-
else if (!hasOptional) result.properties = {};
|
|
320
|
-
if (hasOptional) result.optionalProperties = merged.optionalProperties;
|
|
321
|
-
if (merged.additionalProperties) result.additionalProperties = true;
|
|
322
|
-
return result;
|
|
308
|
+
if ((merged.required?.length ?? 0) === 0) delete merged.required;
|
|
309
|
+
return merged;
|
|
323
310
|
}
|
|
324
|
-
/**
|
|
325
|
-
* Detects whether a Deepkit union represents a tagged union and, when so, emits the corresponding JTD discriminator form.
|
|
326
|
-
*
|
|
327
|
-
* @param types - The Deepkit reflection types that make up the union branches.
|
|
328
|
-
* @returns A JTD discriminator form if every non-null branch is an object literal that shares a string-literal tag property, otherwise `undefined`.
|
|
329
|
-
*/
|
|
330
311
|
function tryReflectionDiscriminator(types) {
|
|
331
312
|
const nonNullTypes = types.filter((t) => t.kind !== ReflectionKind.null && t.kind !== ReflectionKind.undefined);
|
|
332
313
|
const objectBranches = nonNullTypes.filter((t) => t.kind === ReflectionKind.objectLiteral || t.kind === ReflectionKind.class);
|
|
333
314
|
if (objectBranches.length < 2 || objectBranches.length !== nonNullTypes.length) return;
|
|
334
315
|
let tagKey;
|
|
335
|
-
const
|
|
316
|
+
const branches = [];
|
|
336
317
|
for (const branch of objectBranches) {
|
|
337
318
|
const literalProps = [];
|
|
338
319
|
for (const member of branch.types) if ((member.kind === ReflectionKind.property || member.kind === ReflectionKind.propertySignature) && typeof member.name === "string" && member.type.kind === ReflectionKind.literal && typeof member.type.literal === "string") literalProps.push({
|
|
@@ -343,72 +324,77 @@ function tryReflectionDiscriminator(types) {
|
|
|
343
324
|
const first = literalProps[0];
|
|
344
325
|
if (!tagKey) tagKey = first.name;
|
|
345
326
|
else if (tagKey !== first.name) return;
|
|
346
|
-
const body =
|
|
327
|
+
const body = objectReflectionToJsonSchema({
|
|
347
328
|
...branch,
|
|
348
329
|
types: branch.types.filter((member) => !((member.kind === ReflectionKind.property || member.kind === ReflectionKind.propertySignature) && member.name === tagKey))
|
|
349
330
|
});
|
|
350
|
-
if (!body || !
|
|
351
|
-
|
|
331
|
+
if (!body || !isJsonSchemaObject(body)) return;
|
|
332
|
+
branches.push({
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
[tagKey]: { const: first.literal },
|
|
336
|
+
...body.properties ?? {}
|
|
337
|
+
},
|
|
338
|
+
required: [tagKey, ...body.required ?? []],
|
|
339
|
+
additionalProperties: body.additionalProperties ?? false
|
|
340
|
+
});
|
|
352
341
|
}
|
|
353
342
|
if (!tagKey) return;
|
|
354
343
|
return {
|
|
355
|
-
|
|
356
|
-
|
|
344
|
+
oneOf: branches,
|
|
345
|
+
discriminator: { propertyName: tagKey }
|
|
357
346
|
};
|
|
358
347
|
}
|
|
359
|
-
|
|
360
|
-
* Internal worker that produces a JTD properties form (or `values` form for index signatures alone) from a Deepkit object-like type.
|
|
361
|
-
*
|
|
362
|
-
* @param type - The class or object literal type whose members should be serialized.
|
|
363
|
-
* @returns A JTD properties or values form describing the type's members.
|
|
364
|
-
*/
|
|
365
|
-
function objectReflectionToJtd(type) {
|
|
348
|
+
function objectReflectionToJsonSchema(type) {
|
|
366
349
|
const reflection = ReflectionClass.from(type);
|
|
367
|
-
const schema = {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
350
|
+
const schema = {
|
|
351
|
+
type: "object",
|
|
352
|
+
name: reflection.getName(),
|
|
353
|
+
description: reflection.getDescription(),
|
|
354
|
+
properties: {},
|
|
355
|
+
required: [],
|
|
356
|
+
readOnly: reflection.isReadonly(),
|
|
357
|
+
ignore: reflection.isIgnored(),
|
|
358
|
+
internal: reflection.isInternal(),
|
|
359
|
+
runtime: reflection.isRuntime(),
|
|
360
|
+
hidden: reflection.isHidden(),
|
|
361
|
+
primaryKey: reflection.getPrimaries().map((primary) => primary.getNameAsString()),
|
|
362
|
+
...isSetString(reflection.databaseSchemaName) ? { databaseSchemaName: reflection.databaseSchemaName } : {},
|
|
363
|
+
...isSetString(reflection.getName()) ? { name: reflection.getName() } : {},
|
|
364
|
+
...isSetString(reflection.getDescription()) ? { description: reflection.getDescription() } : {},
|
|
365
|
+
...isSetArray(reflection.getAlias()) ? { alias: reflection.getAlias() } : {},
|
|
366
|
+
...isSetString(reflection.getTitle()) ? { title: reflection.getTitle() } : {}
|
|
367
|
+
};
|
|
368
|
+
for (const propertyReflection of reflection.getProperties()) {
|
|
369
|
+
if (propertyReflection.getKind() === ReflectionKind.indexSignature) {
|
|
370
|
+
schema.additionalProperties = reflectionToJsonSchemaInner(propertyReflection.type) ?? true;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
let property = reflectionToJsonSchemaInner(propertyReflection.type);
|
|
390
374
|
if (!property) continue;
|
|
391
|
-
property
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (propertyReflection.
|
|
407
|
-
|
|
375
|
+
property = {
|
|
376
|
+
...property,
|
|
377
|
+
name: propertyReflection.getNameAsString(),
|
|
378
|
+
description: propertyReflection.getDescription(),
|
|
379
|
+
readOnly: propertyReflection.isReadonly(),
|
|
380
|
+
ignore: propertyReflection.isIgnored(),
|
|
381
|
+
internal: propertyReflection.isInternal(),
|
|
382
|
+
runtime: propertyReflection.isRuntime(),
|
|
383
|
+
hidden: propertyReflection.isHidden(),
|
|
384
|
+
visibility: propertyReflection.isPublic() ? "public" : propertyReflection.isProtected() ? "protected" : propertyReflection.isPrivate() ? "private" : void 0,
|
|
385
|
+
...propertyReflection.hasDefault() ? { default: propertyReflection.getDefaultValue() } : {},
|
|
386
|
+
...isSetArray(propertyReflection.getGroups()) ? { tags: propertyReflection.getGroups() } : {},
|
|
387
|
+
...isSetArray(propertyReflection.getAlias()) ? { alias: propertyReflection.getAlias() } : {},
|
|
388
|
+
...isSetString(propertyReflection.getTitle()) ? { title: propertyReflection.getTitle() } : {}
|
|
389
|
+
};
|
|
390
|
+
if (propertyReflection.isNullable()) property = withNullable(property, true);
|
|
391
|
+
schema.properties ??= {};
|
|
392
|
+
schema.properties[propertyReflection.name] = property;
|
|
393
|
+
if (!propertyReflection.isOptional()) {
|
|
394
|
+
schema.required ??= [];
|
|
395
|
+
schema.required.push(propertyReflection.name);
|
|
396
|
+
}
|
|
408
397
|
}
|
|
409
|
-
if (Object.keys(properties).length > 0) schema.properties = properties;
|
|
410
|
-
else if (Object.keys(optionalProperties).length > 0) schema.optionalProperties = optionalProperties;
|
|
411
|
-
else schema.properties = {};
|
|
412
398
|
return schema;
|
|
413
399
|
}
|
|
414
400
|
|