@powerlines/schema 0.8.67 → 0.9.0
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/README.md +36 -22
- package/dist/bundle.d.cts +2 -2
- package/dist/bundle.d.cts.map +1 -1
- package/dist/bundle.d.mts +2 -2
- package/dist/bundle.d.mts.map +1 -1
- package/dist/bundle.mjs.map +1 -1
- package/dist/extract.cjs +200 -51
- package/dist/extract.d.cts +82 -16
- package/dist/extract.d.cts.map +1 -1
- package/dist/extract.d.mts +82 -16
- package/dist/extract.d.mts.map +1 -1
- package/dist/extract.mjs +198 -54
- package/dist/extract.mjs.map +1 -1
- package/dist/index.cjs +13 -2
- package/dist/index.d.cts +5 -4
- package/dist/index.d.mts +5 -4
- package/dist/index.mjs +4 -3
- package/dist/jtd.cjs +362 -0
- package/dist/jtd.d.cts +16 -0
- package/dist/jtd.d.cts.map +1 -0
- package/dist/jtd.d.mts +16 -0
- package/dist/jtd.d.mts.map +1 -0
- package/dist/jtd.mjs +361 -0
- package/dist/jtd.mjs.map +1 -0
- package/dist/reflection.cjs +229 -108
- package/dist/reflection.d.cts +17 -7
- package/dist/reflection.d.cts.map +1 -1
- package/dist/reflection.d.mts +17 -7
- package/dist/reflection.d.mts.map +1 -1
- package/dist/reflection.mjs +229 -108
- package/dist/reflection.mjs.map +1 -1
- package/dist/resolve.d.cts +5 -5
- package/dist/resolve.d.cts.map +1 -1
- package/dist/resolve.d.mts +5 -5
- package/dist/resolve.d.mts.map +1 -1
- package/dist/resolve.mjs.map +1 -1
- package/dist/type-checks.cjs +76 -0
- package/dist/type-checks.d.cts +43 -0
- package/dist/type-checks.d.cts.map +1 -0
- package/dist/type-checks.d.mts +43 -0
- package/dist/type-checks.d.mts.map +1 -0
- package/dist/type-checks.mjs +71 -0
- package/dist/type-checks.mjs.map +1 -0
- package/dist/types.d.cts +80 -24
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +80 -24
- package/dist/types.d.mts.map +1 -1
- package/package.json +21 -19
- package/dist/is-schema-definition.cjs +0 -18
- package/dist/is-schema-definition.d.cts +0 -13
- package/dist/is-schema-definition.d.cts.map +0 -1
- package/dist/is-schema-definition.d.mts +0 -13
- package/dist/is-schema-definition.d.mts.map +0 -1
- package/dist/is-schema-definition.mjs +0 -17
- package/dist/is-schema-definition.mjs.map +0 -1
package/dist/reflection.cjs
CHANGED
|
@@ -4,120 +4,146 @@ let _powerlines_deepkit_vendor_type = require("@powerlines/deepkit/vendor/type")
|
|
|
4
4
|
|
|
5
5
|
//#region src/reflection.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Maps a Deepkit numeric `brand` to the JTD numeric `type` keyword that best preserves the underlying width and signedness.
|
|
8
|
+
*
|
|
9
|
+
* @param brand - The Deepkit `TypeNumberBrand` of a numeric reflection, or `undefined` when no brand is set.
|
|
10
|
+
* @returns The JTD numeric `type` keyword to use.
|
|
11
|
+
*/
|
|
12
|
+
function numberBrandToJtdType(brand) {
|
|
13
|
+
switch (brand) {
|
|
14
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.integer: return "int32";
|
|
15
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.int8: return "int8";
|
|
16
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.uint8: return "uint8";
|
|
17
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.int16: return "int16";
|
|
18
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.uint16: return "uint16";
|
|
19
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.int32: return "int32";
|
|
20
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.uint32: return "uint32";
|
|
21
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.float:
|
|
22
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.float32: return "float32";
|
|
23
|
+
case _powerlines_deepkit_vendor_type.TypeNumberBrand.float64: return "float64";
|
|
24
|
+
case void 0:
|
|
25
|
+
default: return "float64";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Attaches a `description` annotation to a JTD form's `metadata` bag.
|
|
30
|
+
*
|
|
31
|
+
* @param form - The JTD form to decorate.
|
|
32
|
+
* @param description - The description text, or `undefined` to skip.
|
|
33
|
+
* @returns The same JTD form, decorated when a description was provided.
|
|
34
|
+
*/
|
|
35
|
+
function attachDescription(form, description) {
|
|
36
|
+
if (!description) return form;
|
|
37
|
+
const metadata = form.metadata ?? {};
|
|
38
|
+
metadata.description = description;
|
|
39
|
+
form.metadata = metadata;
|
|
40
|
+
return form;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Converts a Deepkit type reflection into a JSON Type Definition (RFC 8927) form suitable for AJV's JTD validator.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* Some TypeScript constructs have no direct JTD equivalent and are handled with the closest available form:
|
|
47
|
+
*
|
|
48
|
+
* - `null` and `undefined` become the empty JTD form with `nullable: true`.
|
|
49
|
+
* - Unions of primitives that cannot be expressed as a JTD enum collapse to the empty form (which validates any value).
|
|
50
|
+
* - String/number/bigint literal unions are emitted as a JTD enum (non-string members are stringified, as JTD requires string enum members).
|
|
51
|
+
* - Tuples are emitted as a JTD elements form whose element schema is the single tuple member type, or the empty schema for mixed tuples.
|
|
52
|
+
* - `Date` is emitted as `{ type: "timestamp" }`.
|
|
53
|
+
* - Discriminated unions of object literals (a shared string-literal tag property) are emitted as a JTD discriminator form.
|
|
8
54
|
*
|
|
9
55
|
* @param reflection - The Deepkit type reflection to convert.
|
|
10
|
-
* @returns The corresponding
|
|
56
|
+
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
11
57
|
*/
|
|
12
58
|
function reflectionToJsonSchema(reflection) {
|
|
59
|
+
return reflectionToJtd(reflection);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Internal worker that performs the recursive Deepkit reflection → JTD conversion.
|
|
63
|
+
*
|
|
64
|
+
* @param reflection - The Deepkit type reflection to convert.
|
|
65
|
+
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
66
|
+
*/
|
|
67
|
+
function reflectionToJtd(reflection) {
|
|
13
68
|
switch (reflection.kind) {
|
|
14
69
|
case _powerlines_deepkit_vendor_type.ReflectionKind.any:
|
|
15
70
|
case _powerlines_deepkit_vendor_type.ReflectionKind.unknown:
|
|
16
71
|
case _powerlines_deepkit_vendor_type.ReflectionKind.void:
|
|
17
72
|
case _powerlines_deepkit_vendor_type.ReflectionKind.object: return {};
|
|
18
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.never:
|
|
19
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.undefined:
|
|
20
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.null: return {
|
|
73
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.never: return;
|
|
74
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.undefined:
|
|
75
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.null: return { nullable: true };
|
|
21
76
|
case _powerlines_deepkit_vendor_type.ReflectionKind.string: return { type: "string" };
|
|
22
77
|
case _powerlines_deepkit_vendor_type.ReflectionKind.boolean: return { type: "boolean" };
|
|
23
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.number: {
|
|
24
|
-
|
|
25
|
-
return { type: brand !== void 0 && brand >= _powerlines_deepkit_vendor_type.TypeNumberBrand.integer && brand <= _powerlines_deepkit_vendor_type.TypeNumberBrand.uint32 ? "integer" : "number" };
|
|
26
|
-
}
|
|
27
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.bigint: return {
|
|
28
|
-
type: "integer",
|
|
29
|
-
format: "int64"
|
|
30
|
-
};
|
|
78
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.number: return { type: numberBrandToJtdType(reflection.brand) };
|
|
79
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.bigint: return { type: "float64" };
|
|
31
80
|
case _powerlines_deepkit_vendor_type.ReflectionKind.regexp: return { type: "string" };
|
|
32
81
|
case _powerlines_deepkit_vendor_type.ReflectionKind.literal: {
|
|
33
82
|
const { literal } = reflection;
|
|
34
|
-
if (typeof literal === "string") return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
38
|
-
if (typeof literal === "number") return {
|
|
39
|
-
type: Number.isInteger(literal) ? "integer" : "number",
|
|
40
|
-
const: literal
|
|
41
|
-
};
|
|
42
|
-
if (typeof literal === "boolean") return {
|
|
43
|
-
type: "boolean",
|
|
44
|
-
const: literal
|
|
45
|
-
};
|
|
46
|
-
if (typeof literal === "bigint") return {
|
|
47
|
-
type: "integer",
|
|
48
|
-
format: "int64"
|
|
49
|
-
};
|
|
50
|
-
if (literal instanceof RegExp) return {
|
|
51
|
-
type: "string",
|
|
52
|
-
pattern: literal.source
|
|
53
|
-
};
|
|
83
|
+
if (typeof literal === "string") return { enum: [literal] };
|
|
84
|
+
if (typeof literal === "number" || typeof literal === "bigint") return { enum: [String(literal)] };
|
|
85
|
+
if (typeof literal === "boolean") return { type: "boolean" };
|
|
86
|
+
if (literal instanceof RegExp) return { type: "string" };
|
|
54
87
|
return {};
|
|
55
88
|
}
|
|
56
89
|
case _powerlines_deepkit_vendor_type.ReflectionKind.templateLiteral: return { type: "string" };
|
|
57
90
|
case _powerlines_deepkit_vendor_type.ReflectionKind.enum: {
|
|
58
|
-
const values = reflection.values.filter((value) => typeof value === "string" || typeof value === "number");
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
type: allStrings ? "string" : allNumbers ? "number" : ["string", "number"],
|
|
63
|
-
enum: values
|
|
64
|
-
};
|
|
91
|
+
const values = reflection.values.filter((value) => typeof value === "string" || typeof value === "number").map((value) => String(value));
|
|
92
|
+
const unique = Array.from(new Set(values));
|
|
93
|
+
if (unique.length === 0) return {};
|
|
94
|
+
return { enum: unique };
|
|
65
95
|
}
|
|
66
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.array: {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.array: return { elements: reflectionToJtd(reflection.type) ?? {} };
|
|
97
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.tuple: {
|
|
98
|
+
const items = reflection.types.map((member) => reflectionToJtd(member.type)).filter((item) => item !== void 0);
|
|
99
|
+
if (items.length === 0) return { elements: {} };
|
|
100
|
+
if (items.length === 1) return { elements: items[0] };
|
|
101
|
+
return { elements: {} };
|
|
72
102
|
}
|
|
73
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.tuple: return {
|
|
74
|
-
type: "array",
|
|
75
|
-
items: reflection.types.map((member) => reflectionToJsonSchema(member.type)).filter((item) => item !== void 0),
|
|
76
|
-
minItems: reflection.types.filter((member) => !member.optional).length,
|
|
77
|
-
maxItems: reflection.types.length
|
|
78
|
-
};
|
|
79
103
|
case _powerlines_deepkit_vendor_type.ReflectionKind.union: {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return {
|
|
104
|
+
const branches = reflection.types.map((inner) => reflectionToJtd(inner)).filter((item) => item !== void 0);
|
|
105
|
+
const nullable = reflection.types.some((inner) => inner.kind === _powerlines_deepkit_vendor_type.ReflectionKind.null || inner.kind === _powerlines_deepkit_vendor_type.ReflectionKind.undefined);
|
|
106
|
+
const nonNull = branches.filter((b) => !isPureNullable(b));
|
|
107
|
+
if (nonNull.length === 0) return { nullable: true };
|
|
108
|
+
if (nonNull.length === 1) {
|
|
109
|
+
const only = nonNull[0];
|
|
110
|
+
if (nullable) only.nullable = true;
|
|
111
|
+
return only;
|
|
112
|
+
}
|
|
113
|
+
if (nonNull.every(isEnumForm)) {
|
|
114
|
+
const form = { enum: Array.from(new Set(nonNull.flatMap((b) => b.enum))) };
|
|
115
|
+
if (nullable) form.nullable = true;
|
|
116
|
+
return form;
|
|
117
|
+
}
|
|
118
|
+
const discriminator = tryReflectionDiscriminator(reflection.types);
|
|
119
|
+
if (discriminator) {
|
|
120
|
+
if (nullable) discriminator.nullable = true;
|
|
121
|
+
return discriminator;
|
|
122
|
+
}
|
|
123
|
+
const fallback = {};
|
|
124
|
+
if (nullable) fallback.nullable = true;
|
|
125
|
+
return fallback;
|
|
84
126
|
}
|
|
85
127
|
case _powerlines_deepkit_vendor_type.ReflectionKind.intersection: {
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
89
|
-
|
|
128
|
+
const members = reflection.types.map((inner) => reflectionToJtd(inner)).filter((item) => item !== void 0);
|
|
129
|
+
if (members.length === 0) return;
|
|
130
|
+
if (members.length === 1) return members[0];
|
|
131
|
+
if (members.every(isPropertiesForm)) return mergePropertiesForms(members);
|
|
132
|
+
return members[0];
|
|
90
133
|
}
|
|
91
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.promise: return
|
|
92
|
-
case _powerlines_deepkit_vendor_type.ReflectionKind.objectLiteral: return
|
|
134
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.promise: return reflectionToJtd(reflection.type);
|
|
135
|
+
case _powerlines_deepkit_vendor_type.ReflectionKind.objectLiteral: return objectReflectionToJtd(reflection);
|
|
93
136
|
case _powerlines_deepkit_vendor_type.ReflectionKind.class: switch (reflection.classType?.name) {
|
|
94
|
-
case "Date": return {
|
|
95
|
-
type: "string",
|
|
96
|
-
format: "date-time"
|
|
97
|
-
};
|
|
137
|
+
case "Date": return { type: "timestamp" };
|
|
98
138
|
case "RegExp": return { type: "string" };
|
|
99
|
-
case "URL": return {
|
|
100
|
-
type: "string",
|
|
101
|
-
format: "uri"
|
|
102
|
-
};
|
|
139
|
+
case "URL": return { type: "string" };
|
|
103
140
|
case "Set": {
|
|
104
141
|
const itemType = reflection.arguments?.[0];
|
|
105
|
-
|
|
106
|
-
return items ? {
|
|
107
|
-
type: "array",
|
|
108
|
-
uniqueItems: true,
|
|
109
|
-
items
|
|
110
|
-
} : {
|
|
111
|
-
type: "array",
|
|
112
|
-
uniqueItems: true
|
|
113
|
-
};
|
|
142
|
+
return { elements: (itemType ? reflectionToJtd(itemType) : void 0) ?? {} };
|
|
114
143
|
}
|
|
115
144
|
case "Map": {
|
|
116
145
|
const valueType = reflection.arguments?.[1];
|
|
117
|
-
|
|
118
|
-
const schema = { type: "object" };
|
|
119
|
-
if (additionalProperties) schema.additionalProperties = additionalProperties;
|
|
120
|
-
return schema;
|
|
146
|
+
return { values: (valueType ? reflectionToJtd(valueType) : void 0) ?? {} };
|
|
121
147
|
}
|
|
122
148
|
case "Uint8Array":
|
|
123
149
|
case "Uint8ClampedArray":
|
|
@@ -129,12 +155,9 @@ function reflectionToJsonSchema(reflection) {
|
|
|
129
155
|
case "Float32Array":
|
|
130
156
|
case "Float64Array":
|
|
131
157
|
case "BigInt64Array":
|
|
132
|
-
case "BigUint64Array": return {
|
|
133
|
-
type: "string",
|
|
134
|
-
contentEncoding: "base64"
|
|
135
|
-
};
|
|
158
|
+
case "BigUint64Array": return { type: "string" };
|
|
136
159
|
case void 0:
|
|
137
|
-
default: return
|
|
160
|
+
default: return objectReflectionToJtd(reflection);
|
|
138
161
|
}
|
|
139
162
|
case _powerlines_deepkit_vendor_type.ReflectionKind.symbol:
|
|
140
163
|
case _powerlines_deepkit_vendor_type.ReflectionKind.property:
|
|
@@ -154,36 +177,134 @@ function reflectionToJsonSchema(reflection) {
|
|
|
154
177
|
}
|
|
155
178
|
}
|
|
156
179
|
/**
|
|
157
|
-
*
|
|
180
|
+
* Tests whether a JTD form is an enum form.
|
|
181
|
+
*
|
|
182
|
+
* @param form - The JTD form to inspect.
|
|
183
|
+
* @returns `true` if the form is a JTD enum form.
|
|
184
|
+
*/
|
|
185
|
+
function isEnumForm(form) {
|
|
186
|
+
return Array.isArray(form.enum);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Tests whether a JTD form is a properties form (object).
|
|
190
|
+
*
|
|
191
|
+
* @param form - The JTD form to inspect.
|
|
192
|
+
* @returns `true` if the form is a JTD properties form.
|
|
193
|
+
*/
|
|
194
|
+
function isPropertiesForm(form) {
|
|
195
|
+
return "properties" in form || "optionalProperties" in form;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Tests whether a JTD form is the empty `{ nullable: true }` placeholder.
|
|
199
|
+
*
|
|
200
|
+
* @param form - The JTD form to inspect.
|
|
201
|
+
* @returns `true` if the form has no shape constraints beyond `nullable`.
|
|
202
|
+
*/
|
|
203
|
+
function isPureNullable(form) {
|
|
204
|
+
return Object.keys(form).filter((k) => k !== "nullable" && k !== "metadata").length === 0 && form.nullable === true;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Shallow-merges two JTD properties forms, unioning their `properties` and `optionalProperties` maps.
|
|
208
|
+
*
|
|
209
|
+
* @param forms - The JTD properties forms to merge.
|
|
210
|
+
* @returns The merged JTD properties form.
|
|
211
|
+
*/
|
|
212
|
+
function mergePropertiesForms(forms) {
|
|
213
|
+
const merged = {
|
|
214
|
+
properties: {},
|
|
215
|
+
optionalProperties: {}
|
|
216
|
+
};
|
|
217
|
+
for (const form of forms) {
|
|
218
|
+
const p = form.properties;
|
|
219
|
+
const o = form.optionalProperties;
|
|
220
|
+
if (p) Object.assign(merged.properties, p);
|
|
221
|
+
if (o) Object.assign(merged.optionalProperties, o);
|
|
222
|
+
if (form.additionalProperties) merged.additionalProperties = true;
|
|
223
|
+
}
|
|
224
|
+
const hasProperties = Object.keys(merged.properties).length > 0;
|
|
225
|
+
const hasOptional = Object.keys(merged.optionalProperties).length > 0;
|
|
226
|
+
const result = {};
|
|
227
|
+
if (hasProperties) result.properties = merged.properties;
|
|
228
|
+
else if (!hasOptional) result.properties = {};
|
|
229
|
+
if (hasOptional) result.optionalProperties = merged.optionalProperties;
|
|
230
|
+
if (merged.additionalProperties) result.additionalProperties = true;
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Detects whether a Deepkit union represents a tagged union and, when so, emits the corresponding JTD discriminator form.
|
|
235
|
+
*
|
|
236
|
+
* @param types - The Deepkit reflection types that make up the union branches.
|
|
237
|
+
* @returns A JTD discriminator form if every non-null branch is an object literal that shares a string-literal tag property, otherwise `undefined`.
|
|
238
|
+
*/
|
|
239
|
+
function tryReflectionDiscriminator(types) {
|
|
240
|
+
const nonNullTypes = types.filter((t) => t.kind !== _powerlines_deepkit_vendor_type.ReflectionKind.null && t.kind !== _powerlines_deepkit_vendor_type.ReflectionKind.undefined);
|
|
241
|
+
const objectBranches = nonNullTypes.filter((t) => t.kind === _powerlines_deepkit_vendor_type.ReflectionKind.objectLiteral || t.kind === _powerlines_deepkit_vendor_type.ReflectionKind.class);
|
|
242
|
+
if (objectBranches.length < 2 || objectBranches.length !== nonNullTypes.length) return;
|
|
243
|
+
let tagKey;
|
|
244
|
+
const mapping = {};
|
|
245
|
+
for (const branch of objectBranches) {
|
|
246
|
+
const literalProps = [];
|
|
247
|
+
for (const member of branch.types) if ((member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.property || member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.propertySignature) && typeof member.name === "string" && member.type.kind === _powerlines_deepkit_vendor_type.ReflectionKind.literal && typeof member.type.literal === "string") literalProps.push({
|
|
248
|
+
name: member.name,
|
|
249
|
+
literal: member.type.literal
|
|
250
|
+
});
|
|
251
|
+
if (literalProps.length === 0) return;
|
|
252
|
+
const first = literalProps[0];
|
|
253
|
+
if (!tagKey) tagKey = first.name;
|
|
254
|
+
else if (tagKey !== first.name) return;
|
|
255
|
+
const body = objectReflectionToJtd({
|
|
256
|
+
...branch,
|
|
257
|
+
types: branch.types.filter((member) => !((member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.property || member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.propertySignature) && member.name === tagKey))
|
|
258
|
+
});
|
|
259
|
+
if (!body || !isPropertiesForm(body)) return;
|
|
260
|
+
mapping[first.literal] = body;
|
|
261
|
+
}
|
|
262
|
+
if (!tagKey) return;
|
|
263
|
+
return {
|
|
264
|
+
discriminator: tagKey,
|
|
265
|
+
mapping
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Builds a JTD properties form from a Deepkit class or object literal type.
|
|
158
270
|
*
|
|
159
271
|
* @param type - The class or object literal type whose members should be serialized.
|
|
160
|
-
* @returns A
|
|
272
|
+
* @returns A JTD properties form describing the type's members.
|
|
161
273
|
*/
|
|
162
274
|
function objectReflectionToJsonSchema(type) {
|
|
275
|
+
return objectReflectionToJtd(type);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Internal worker that produces a JTD properties form (or `values` form for index signatures alone) from a Deepkit object-like type.
|
|
279
|
+
*
|
|
280
|
+
* @param type - The class or object literal type whose members should be serialized.
|
|
281
|
+
* @returns A JTD properties or values form describing the type's members.
|
|
282
|
+
*/
|
|
283
|
+
function objectReflectionToJtd(type) {
|
|
163
284
|
const properties = {};
|
|
164
|
-
const
|
|
165
|
-
let
|
|
285
|
+
const optionalProperties = {};
|
|
286
|
+
let indexValueSchema;
|
|
166
287
|
const description = "description" in type && typeof type.description === "string" ? type.description : void 0;
|
|
167
288
|
for (const member of type.types) if (member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.property || member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.propertySignature) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const propertySchema = reflectionToJsonSchema(property.type);
|
|
289
|
+
if (typeof member.name !== "string") continue;
|
|
290
|
+
const propertySchema = reflectionToJtd(member.type);
|
|
171
291
|
if (!propertySchema) continue;
|
|
172
|
-
if (typeof
|
|
173
|
-
|
|
174
|
-
|
|
292
|
+
if (typeof member.description === "string") attachDescription(propertySchema, member.description);
|
|
293
|
+
if (member.optional) optionalProperties[member.name] = propertySchema;
|
|
294
|
+
else properties[member.name] = propertySchema;
|
|
175
295
|
} else if (member.kind === _powerlines_deepkit_vendor_type.ReflectionKind.indexSignature) {
|
|
176
|
-
const valueSchema =
|
|
177
|
-
if (valueSchema)
|
|
296
|
+
const valueSchema = reflectionToJtd(member.type);
|
|
297
|
+
if (valueSchema) indexValueSchema = valueSchema;
|
|
178
298
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
};
|
|
183
|
-
if (
|
|
184
|
-
if (
|
|
185
|
-
if (
|
|
186
|
-
|
|
299
|
+
const hasProperties = Object.keys(properties).length > 0;
|
|
300
|
+
const hasOptional = Object.keys(optionalProperties).length > 0;
|
|
301
|
+
if (!hasProperties && !hasOptional && indexValueSchema) return attachDescription({ values: indexValueSchema }, description);
|
|
302
|
+
const form = {};
|
|
303
|
+
if (hasProperties) form.properties = properties;
|
|
304
|
+
else if (!hasOptional) form.properties = {};
|
|
305
|
+
if (hasOptional) form.optionalProperties = optionalProperties;
|
|
306
|
+
if (indexValueSchema) form.additionalProperties = true;
|
|
307
|
+
return attachDescription(form, description);
|
|
187
308
|
}
|
|
188
309
|
|
|
189
310
|
//#endregion
|
package/dist/reflection.d.cts
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { Type, TypeClass, TypeObjectLiteral } from "@powerlines/deepkit/vendor/type";
|
|
2
|
-
import {
|
|
2
|
+
import { JTDSchemaType } from "ajv/dist/types/jtd-schema.js";
|
|
3
3
|
|
|
4
4
|
//#region src/reflection.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Converts a Deepkit type reflection into a JSON
|
|
6
|
+
* Converts a Deepkit type reflection into a JSON Type Definition (RFC 8927) form suitable for AJV's JTD validator.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Some TypeScript constructs have no direct JTD equivalent and are handled with the closest available form:
|
|
10
|
+
*
|
|
11
|
+
* - `null` and `undefined` become the empty JTD form with `nullable: true`.
|
|
12
|
+
* - Unions of primitives that cannot be expressed as a JTD enum collapse to the empty form (which validates any value).
|
|
13
|
+
* - String/number/bigint literal unions are emitted as a JTD enum (non-string members are stringified, as JTD requires string enum members).
|
|
14
|
+
* - Tuples are emitted as a JTD elements form whose element schema is the single tuple member type, or the empty schema for mixed tuples.
|
|
15
|
+
* - `Date` is emitted as `{ type: "timestamp" }`.
|
|
16
|
+
* - Discriminated unions of object literals (a shared string-literal tag property) are emitted as a JTD discriminator form.
|
|
7
17
|
*
|
|
8
18
|
* @param reflection - The Deepkit type reflection to convert.
|
|
9
|
-
* @returns The corresponding
|
|
19
|
+
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
10
20
|
*/
|
|
11
|
-
declare function reflectionToJsonSchema(reflection: Type):
|
|
21
|
+
declare function reflectionToJsonSchema<T = unknown, D extends Record<string, unknown> = Record<string, unknown>>(reflection: Type): JTDSchemaType<T, D> | undefined;
|
|
12
22
|
/**
|
|
13
|
-
* Builds a
|
|
23
|
+
* Builds a JTD properties form from a Deepkit class or object literal type.
|
|
14
24
|
*
|
|
15
25
|
* @param type - The class or object literal type whose members should be serialized.
|
|
16
|
-
* @returns A
|
|
26
|
+
* @returns A JTD properties form describing the type's members.
|
|
17
27
|
*/
|
|
18
|
-
declare function objectReflectionToJsonSchema(type: TypeObjectLiteral | TypeClass):
|
|
28
|
+
declare function objectReflectionToJsonSchema<T = unknown, D extends Record<string, unknown> = Record<string, unknown>>(type: TypeObjectLiteral | TypeClass): JTDSchemaType<T, D>;
|
|
19
29
|
//#endregion
|
|
20
30
|
export { objectReflectionToJsonSchema, reflectionToJsonSchema };
|
|
21
31
|
//# sourceMappingURL=reflection.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection.d.cts","names":[],"sources":["../src/reflection.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"reflection.d.cts","names":[],"sources":["../src/reflection.ts"],"mappings":";;;;;AA0GA;;;;;;;;;;;;;;;iBAAgB,sBAAA,wBAEJ,MAAA,oBAA0B,MAAA,kBAAA,CACpC,UAAA,EAAY,IAAA,GAAO,aAAA,CAAc,CAAA,EAAG,CAAA;;;;;;;iBAwYtB,4BAAA,wBAEJ,MAAA,oBAA0B,MAAA,kBAAA,CACpC,IAAA,EAAM,iBAAA,GAAoB,SAAA,GAAY,aAAA,CAAc,CAAA,EAAG,CAAA"}
|
package/dist/reflection.d.mts
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { Type, TypeClass, TypeObjectLiteral } from "@powerlines/deepkit/vendor/type";
|
|
2
|
-
import {
|
|
2
|
+
import { JTDSchemaType } from "ajv/dist/types/jtd-schema.js";
|
|
3
3
|
|
|
4
4
|
//#region src/reflection.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Converts a Deepkit type reflection into a JSON
|
|
6
|
+
* Converts a Deepkit type reflection into a JSON Type Definition (RFC 8927) form suitable for AJV's JTD validator.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Some TypeScript constructs have no direct JTD equivalent and are handled with the closest available form:
|
|
10
|
+
*
|
|
11
|
+
* - `null` and `undefined` become the empty JTD form with `nullable: true`.
|
|
12
|
+
* - Unions of primitives that cannot be expressed as a JTD enum collapse to the empty form (which validates any value).
|
|
13
|
+
* - String/number/bigint literal unions are emitted as a JTD enum (non-string members are stringified, as JTD requires string enum members).
|
|
14
|
+
* - Tuples are emitted as a JTD elements form whose element schema is the single tuple member type, or the empty schema for mixed tuples.
|
|
15
|
+
* - `Date` is emitted as `{ type: "timestamp" }`.
|
|
16
|
+
* - Discriminated unions of object literals (a shared string-literal tag property) are emitted as a JTD discriminator form.
|
|
7
17
|
*
|
|
8
18
|
* @param reflection - The Deepkit type reflection to convert.
|
|
9
|
-
* @returns The corresponding
|
|
19
|
+
* @returns The corresponding JTD form, or `undefined` if the type cannot be represented.
|
|
10
20
|
*/
|
|
11
|
-
declare function reflectionToJsonSchema(reflection: Type):
|
|
21
|
+
declare function reflectionToJsonSchema<T = unknown, D extends Record<string, unknown> = Record<string, unknown>>(reflection: Type): JTDSchemaType<T, D> | undefined;
|
|
12
22
|
/**
|
|
13
|
-
* Builds a
|
|
23
|
+
* Builds a JTD properties form from a Deepkit class or object literal type.
|
|
14
24
|
*
|
|
15
25
|
* @param type - The class or object literal type whose members should be serialized.
|
|
16
|
-
* @returns A
|
|
26
|
+
* @returns A JTD properties form describing the type's members.
|
|
17
27
|
*/
|
|
18
|
-
declare function objectReflectionToJsonSchema(type: TypeObjectLiteral | TypeClass):
|
|
28
|
+
declare function objectReflectionToJsonSchema<T = unknown, D extends Record<string, unknown> = Record<string, unknown>>(type: TypeObjectLiteral | TypeClass): JTDSchemaType<T, D>;
|
|
19
29
|
//#endregion
|
|
20
30
|
export { objectReflectionToJsonSchema, reflectionToJsonSchema };
|
|
21
31
|
//# sourceMappingURL=reflection.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection.d.mts","names":[],"sources":["../src/reflection.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"reflection.d.mts","names":[],"sources":["../src/reflection.ts"],"mappings":";;;;;AA0GA;;;;;;;;;;;;;;;iBAAgB,sBAAA,wBAEJ,MAAA,oBAA0B,MAAA,kBAAA,CACpC,UAAA,EAAY,IAAA,GAAO,aAAA,CAAc,CAAA,EAAG,CAAA;;;;;;;iBAwYtB,4BAAA,wBAEJ,MAAA,oBAA0B,MAAA,kBAAA,CACpC,IAAA,EAAM,iBAAA,GAAoB,SAAA,GAAY,aAAA,CAAc,CAAA,EAAG,CAAA"}
|