@tsed/schema 8.17.3 → 8.18.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.
Files changed (50) hide show
  1. package/functional-api-typing-plan.md +207 -0
  2. package/lib/esm/decorators/common/schema.js +2 -2
  3. package/lib/esm/domain/JsonSchema.js +105 -91
  4. package/lib/esm/domain/types.js +1 -0
  5. package/lib/esm/fn/allOf.js +2 -0
  6. package/lib/esm/fn/any.js +0 -5
  7. package/lib/esm/fn/anyOf.js +2 -1
  8. package/lib/esm/fn/boolean.js +3 -1
  9. package/lib/esm/fn/collection.js +13 -45
  10. package/lib/esm/fn/date.js +6 -0
  11. package/lib/esm/fn/email.js +2 -0
  12. package/lib/esm/fn/enums.js +2 -2
  13. package/lib/esm/fn/from.js +0 -11
  14. package/lib/esm/fn/index.js +10 -1
  15. package/lib/esm/fn/integer.js +2 -2
  16. package/lib/esm/fn/lazyRef.js +0 -3
  17. package/lib/esm/fn/number.js +2 -0
  18. package/lib/esm/fn/object.js +2 -1
  19. package/lib/esm/fn/string.js +2 -0
  20. package/lib/esm/fn/url.js +2 -0
  21. package/lib/esm/index.js +1 -0
  22. package/lib/types/decorators/common/default.d.ts +2 -2
  23. package/lib/types/decorators/common/schema.d.ts +3 -3
  24. package/lib/types/domain/JsonLazyRef.d.ts +1 -1
  25. package/lib/types/domain/JsonMedia.d.ts +1 -1
  26. package/lib/types/domain/JsonParameter.d.ts +1 -1
  27. package/lib/types/domain/JsonParameterStore.d.ts +1 -1
  28. package/lib/types/domain/JsonSchema.d.ts +58 -31
  29. package/lib/types/domain/types.d.ts +7 -0
  30. package/lib/types/fn/allOf.d.ts +5 -2
  31. package/lib/types/fn/any.d.ts +7 -1
  32. package/lib/types/fn/anyOf.d.ts +5 -3
  33. package/lib/types/fn/boolean.d.ts +4 -3
  34. package/lib/types/fn/collection.d.ts +33 -20
  35. package/lib/types/fn/date.d.ts +9 -4
  36. package/lib/types/fn/email.d.ts +3 -2
  37. package/lib/types/fn/enums.d.ts +11 -1
  38. package/lib/types/fn/from.d.ts +3 -1
  39. package/lib/types/fn/index.d.ts +14 -1
  40. package/lib/types/fn/integer.d.ts +3 -4
  41. package/lib/types/fn/lazyRef.d.ts +4 -2
  42. package/lib/types/fn/number.d.ts +3 -1
  43. package/lib/types/fn/object.d.ts +3 -4
  44. package/lib/types/fn/oneOf.d.ts +3 -2
  45. package/lib/types/fn/string.d.ts +3 -2
  46. package/lib/types/fn/uri.d.ts +1 -2
  47. package/lib/types/fn/url.d.ts +3 -2
  48. package/lib/types/index.d.ts +1 -0
  49. package/lib/types/registries/enumRegistries.d.ts +1 -1
  50. package/package.json +7 -7
@@ -0,0 +1,207 @@
1
+ # Ts.ED Schema Functional API — Typed Inference Plan (Zod-like)
2
+
3
+ Status: Proposal/Plan
4
+ Owner: @tsed/schema
5
+ Last updated: 2025-10-12
6
+
7
+ ## Goals
8
+
9
+ - Provide strong TypeScript inference for the Functional API under `@tsed/schema/src/fn`, similar to Zod.
10
+ - Preserve current runtime behavior and JSON Schema generation.
11
+ - Keep backward compatibility for existing call sites (decorators and functional builders).
12
+ - Offer a simple type-level utility to derive a TypeScript type from a schema (like `z.infer`).
13
+
14
+ ## Non-Goals
15
+
16
+ - No changes to runtime logic of `JsonSchema` and mappers unless strictly needed for typing.
17
+ - No breaking changes to decorator-based API.
18
+
19
+ ---
20
+
21
+ ## High-level Design
22
+
23
+ Leverage the new generic `JsonSchema<T>` as the single source of truth for type inference. We no longer need a separate phantom wrapper type. All functional builders will return `JsonSchema<T>` directly and chaining methods will transform `T` via their generic signatures.
24
+
25
+ ### 1) JsonSchema<T> as carrier of the inferred type
26
+
27
+ - `JsonSchema` is now generic: `class JsonSchema<T = any> { ... }`. The generic `T` carries the inferred TypeScript type across builder chains.
28
+ - No runtime changes are necessary; the generic only exists at compile time.
29
+
30
+ Rationale: By placing the generic on `JsonSchema` itself, we simplify the type surface, avoid wrapper aliases, and reduce conflicts with existing decorators like `@Schema()`.
31
+
32
+ ### 2) Infer Utility (s.infer)
33
+
34
+ - Provide an `infer<S>` helper that extracts the `T` from `JsonSchema<T>`.
35
+ - Expose it under the `s` namespace as `s.infer<...>` using declaration merging. Also export from the package root for convenience.
36
+
37
+ ```ts
38
+ // Helper to extract the generic parameter
39
+ export type Infer<S> = S extends JsonSchema<infer T> ? T : never;
40
+
41
+ // Re-export as s.infer via namespace merging
42
+ export namespace s {
43
+ export type infer<S> = Infer<S>;
44
+ }
45
+ ```
46
+
47
+ Usage:
48
+
49
+ ```ts
50
+ const user = s.object({name: s.string(), age: s.number().minimum(0)});
51
+ type User = s.infer<typeof user>; // { name: string; age: number }
52
+ ```
53
+
54
+ ### 3) Typed Builder Signatures
55
+
56
+ Note on time(): time-of-day values are mapped to Date by default (not string) to align with @tsed/json-mapper’s default behavior. This can be made configurable in a future phase.
57
+
58
+ Update the type signatures of the functional builders to return `JsonSchema<T>` directly. Key builders:
59
+
60
+ - Primitives: `string() -> JsonSchema<string>`, `number() -> JsonSchema<number>`, `integer() -> JsonSchema<number>`, `boolean() -> JsonSchema<boolean>`
61
+ - Dates: `date() -> JsonSchema<Date>`, `datetime() -> JsonSchema<Date>`, `time() -> JsonSchema<Date>` (inferred as Date by default to align with @tsed/json-mapper)
62
+ - Collections: `array(item: JsonSchema<I>) -> JsonSchema<I[]>`, `set(item: JsonSchema<I>) -> JsonSchema<Set<I>>`, `map(value: JsonSchema<V>) -> JsonSchema<Map<string, V>>`
63
+ - Object: `object(props: { [K in string]: JsonSchema<any> }) -> JsonSchema<{ [K in keyof props]: Infer<props[K]> }>`
64
+ - Enums: `enums(["A","B"]) -> JsonSchema<"A" | "B">` and `enums(enumObj) -> JsonSchema<EnumType>`
65
+ - Unions: `oneOf([S1, S2]) -> JsonSchema<Infer<S1> | Infer<S2>>`, `anyOf` similarly
66
+ - Intersections: `allOf([S1, S2]) -> JsonSchema<Infer<S1> & Infer<S2>>`
67
+ - Lazy refs: `lazyRef(() => Class) -> JsonSchema<InstanceType<typeof Class>>`
68
+
69
+ Notes:
70
+
71
+ - All definitions remain runtime-compatible and internally still build `JsonSchema`.
72
+ - Overloads may be necessary to support both array-of-schemas and variadic signatures.
73
+
74
+ ### 4) Typed Method Chaining on JsonSchema
75
+
76
+ Many instance methods exist on `JsonSchema<T>` (e.g., `optional`, `nullable`, `default`, `minLength`, etc.). We will type the subset that affects the resulting inferred type via generic method signatures on `JsonSchema<T>` itself:
77
+
78
+ - `optional(): JsonSchema<T | undefined>` — marks property as optional (required=false) and adds `undefined` to `T`.
79
+ - `nullable(value?: boolean): JsonSchema<T | null>` — sets `nullable=true` and adds `null` to `T`.
80
+ - `default(value: T | (() => T)): JsonSchema<T>` — documentation-only; DOES NOT change the type `T`.
81
+ - `required(): JsonSchema<Exclude<T, undefined>>` and `required(false): JsonSchema<T | undefined>` — toggles required at type level accordingly.
82
+
83
+ Implementation approach:
84
+
85
+ - Since `JsonSchema` is generic, its instance methods can be declared with generic-aware return types. No wrapper interface is needed.
86
+ - Non-type-affecting methods (format, minLength, etc.) keep returning `JsonSchema<T>`.
87
+
88
+ ### 5) Object Properties Typing
89
+
90
+ - `object({...})` should accept `JsonSchema` values and infer the resulting TypeScript type.
91
+ - Add a helper `PropsToShape<P>` mapping from builder map to a TS type:
92
+
93
+ ```ts
94
+ type PropsToShape<P extends Record<string, JsonSchema<any>>> = {
95
+ [K in keyof P]: Infer<P[K]>;
96
+ };
97
+ ```
98
+
99
+ - For optional properties via `.optional()`, `Infer` already carries `| undefined` which object typing will reflect.
100
+
101
+ ### 6) Narrowing via JSON Keywords (Optional Phase II)
102
+
103
+ - Certain keywords could refine types, but we will keep Phase I simple:
104
+ - `minLength`, `maxLength`: do not narrow `string`.
105
+ - `minimum`, `maximum`, `multipleOf`: do not narrow `number`.
106
+ - Leave a roadmap for literal/default inference improvements (e.g., `.const("x")` -> type `"x"`).
107
+
108
+ ### 7) Types for `from()`
109
+
110
+ - `from(Ctor)` should produce `JsonSchema<InstanceType<Ctor>>` when `Ctor` is a class.
111
+ - For primitives passed (String, Number, Boolean), map to their primitive types.
112
+
113
+ ### 8) Public Exports and `s` Namespace
114
+
115
+ - Extend `s` in `fn/index.ts` with a declaration-merged namespace to expose `type infer<S>`; also export `Infer` from the package root (`src/index.ts`). No separate `SchemaShape` export is required anymore.
116
+
117
+ ### 9) Type Tests (no runtime)
118
+
119
+ Add `.dts.spec.ts` style tests using Vitest’s type tests (or `@tsd` style) to validate inference without executing code:
120
+
121
+ - `object` with required/optional/nullable/default.
122
+ - `array/map/set` element typing (ensure `set()` infers `Set<T>`).
123
+ - `oneOf/anyOf/allOf` unions and intersections.
124
+ - `enums` literal unions.
125
+ - `from(Class)` yields instance type.
126
+
127
+ Use `// @ts-expect-error` to ensure invalid compositions are caught.
128
+
129
+ ### 10) Docs
130
+
131
+ - Update docs with a new page: “Functional API with Type Inference”.
132
+ - Show side-by-side examples vs Zod (`s.string().optional()` etc.).
133
+ - Document `s.infer` (lowercase) and ensure discoverability from `s` namespace.
134
+
135
+ ### 11) Migration & Compatibility
136
+
137
+ - The change is additive: existing code using `JsonSchema` continues to work.
138
+ - Builders now return `JsonSchema<T>` directly; the inferred type is carried by the generic parameter.
139
+ - No change in emitted JSON Schema or OpenAPI.
140
+
141
+ ### 12) Implementation Steps
142
+
143
+ Additional builder: any()
144
+
145
+ - Behavior: `any()` with no arguments => `JsonSchema<any>`; `any(S1, S2, ...)` where each Si is a `JsonSchema` => `JsonSchema<[Infer<S1>, Infer<S2>, ...]>` (tuple of provided types). It is also exposed on `s.any`.
146
+ - Rationale: matches Ts.ED’s current JSON mapper use-cases for a variadic any() that describes a fixed tuple of allowed types at the type level while preserving runtime behavior.
147
+
148
+ 1. Ensure `Infer` type extracts the generic from `JsonSchema<T>` and expose it as `s.infer` via namespace merging.
149
+ 2. Verify each builder’s TypeScript signature returns `JsonSchema<T>` with correct generic, leveraging the new `JsonSchema<T>`.
150
+ 3. Ensure `JsonSchema` chainers (`optional`, `nullable`, `default`, `required`) have generic-aware return types that transform `T` as specified; `default()` remains doc-only.
151
+ 4. Keep `object()` typings using `PropsToShape` to infer property shapes from `JsonSchema` props.
152
+ 5. Confirm `from()` typings map classes and primitives to the correct `T`.
153
+ 6. Public exports: expose `Infer` and `s.infer`; no separate wrapper type export needed.
154
+ 7. Maintain and extend type-level tests in `packages/specs/schema/src/fn/typing.spec.ts` to cover all scenarios.
155
+ 8. Update docs and examples to reflect `JsonSchema<T>` as the carrier type and `s.infer` usage.
156
+
157
+ ### 13) Decisions Applied (resolving previous open questions)
158
+
159
+ - `set()` MUST infer `Set<T>`.
160
+ - `.default()` is documentation-only for Swagger/JSON Schema and DOES NOT affect runtime behavior or TypeScript types.
161
+ - `date()` and `datetime()` infer `Date` by default because `@tsed/json-mapper` performs conversion. We will document an extension point to let devs override the inferred type (e.g., Moment) in a future phase; not part of MVP.
162
+
163
+ ### 14) Axes d'amélioration (Roadmap Phase II/III)
164
+
165
+ - Date type override (configurable): Allow a global/project-level override for the inferred date-like type (e.g., Moment, Dayjs) while keeping `Date` as the default. Possible approaches: module augmentation hook, DI configuration token, or generic parameter like `SchemaDate<TDate = Date>` applied via a central helper. The runtime stays unchanged; only typing varies.
166
+ - Literal narrowing enhancements: Improve `.const()` and `.enum()` so that the inferred type preserves literal values ("A", 42) where possible. Keep `.default()` as documentation-only (no type impact) but explore an opt-in helper (e.g., `.asConstDefault()`) for literal default-based narrowing without changing runtime.
167
+ - Branded primitives: Provide branded types for format-based strings (email/uri/url/uuid/cuid) via intersection branding (e.g., `string & { __brand: "email" }`) to improve static safety without affecting runtime.
168
+ - Deep helpers on object schemas: Add helpers like `.partial()`, `.requiredAll()`, `.pick<K>()`, `.omit<K>()`, `.merge()`, and `.deepPartial()` that transform `T` while preserving JSON Schema equivalence or providing documented transformation semantics.
169
+ - `additionalProperties` typing ergonomics: Offer a configuration or helper to choose between `Record<string, V>` and `Map<string, V>` for object maps when using `additionalProperties`. Today `map()` is `Map<string, V>`; consider an alternate builder `record()` to infer `Record<string, V>`.
170
+ - `patternProperties` key typing: Explore a light refinement to represent known regex buckets at the type level (e.g., via branded keys or mapped types) while remaining sound. Likely an opt-in helper.
171
+ - Union/intersection expressiveness: Improve `oneOf/anyOf/allOf` inference for complex arrays (distributive unions, exclusion of `null`/`undefined` where appropriate) and add stricter oneOf semantics checks (type-level) when feasible.
172
+ - `any()` ergonomics: Evaluate an additional signature that yields union of provided types instead of tuple when appropriate, e.g., `anyUnion(S1, S2, ...) -> Infer<S1> | Infer<S2> | ...`, keeping existing behavior for backward compatibility.
173
+ - Schema generics and parameterization: Provide a pattern for defining generic schemas (e.g., `makePaged<T>(item: JsonSchema<T>)`) with strong typing, including inference for nested generics.
174
+ - Type emission performance: Keep types shallow where possible to reduce editor latency (avoid excessive conditional types and recursive mapped types). Provide internal utility types to cap recursion depth for `.deepPartial()`.
175
+ - DX and error messages: Add typed helpers that surface clearer compile-time errors (e.g., invalid enum inputs, inconsistent `oneOf` array forms). Consider labeled helpers like `s.name("User")` that brand the type for better diagnostics without changing `T`.
176
+ - Interop and adapters: Provide type-safe adapters/importers from Zod/Valibot/Yup where feasible (type-only). Ensure the inferred `T` matches adapters’ expectations.
177
+ - Testing strategy: Augment type-level tests using `@tsd` or `vitest` type assertions to cover the above features; include negative tests (`@ts-expect-error`) and perf sentinels for slow types.
178
+ - Documentation and recipes: Add a dedicated page with advanced recipes (branded types, deep helpers, generics), guidance on date type override, and migration notes. Provide copy‑paste snippets and playground links.
179
+ - Tooling guardrails: Offer optional ESLint rules or codemods to discourage unsafe `any()` usage in favor of precise builders (array/map/set/object) or `anyUnion` when introduced.
180
+
181
+ ---
182
+
183
+ ## Example
184
+
185
+ ```ts
186
+ import {s} from "@tsed/schema";
187
+
188
+ const UserSchema = s.object({
189
+ id: s.string().uuid().required(),
190
+ email: s.email().optional(),
191
+ age: s.number().minimum(0).optional(),
192
+ roles: s.array(s.string()).default([]),
193
+ profile: s.object({
194
+ firstName: s.string(),
195
+ lastName: s.string().optional()
196
+ })
197
+ });
198
+
199
+ type User = s.infer<typeof UserSchema>;
200
+ // type User = {
201
+ // id: string;
202
+ // email?: string | undefined;
203
+ // age?: number | undefined;
204
+ // roles: string[];
205
+ // profile: { firstName: string; lastName?: string | undefined };
206
+ // }
207
+ ```
@@ -4,7 +4,7 @@ import { JsonEntityFn } from "./jsonEntityFn.js";
4
4
  *
5
5
  * ## Example
6
6
  *
7
- * ```typescript
7
+ * ```ts
8
8
  * @Schema({title: "test"})
9
9
  * class Model {
10
10
  * @Schema({formatMinimum: "1987-10-24"})
@@ -43,7 +43,7 @@ export function Schema(partialSchema) {
43
43
  });
44
44
  }
45
45
  /**
46
- * Apply specific schema depending on the spec version
46
+ * Apply a specific schema depending on the spec version
47
47
  * @param specType
48
48
  * @param schema
49
49
  * @decorator
@@ -10,67 +10,12 @@ import { serializeEnumValues } from "../utils/serializeEnumValues.js";
10
10
  import { toJsonRegex } from "../utils/toJsonRegex.js";
11
11
  import { Discriminator } from "./JsonDiscriminator.js";
12
12
  import { JsonEntityStore } from "./JsonEntityStore.js";
13
- function mapProperties(properties) {
14
- // istanbul ignore next
15
- if (properties instanceof JsonSchema) {
16
- return properties;
17
- }
18
- return Object.entries(properties).reduce((properties, [key, schema]) => {
19
- properties[toJsonRegex(key)] = mapToJsonSchema(schema);
20
- if (schema instanceof JsonSchema) {
21
- schema.propertyKey(toJsonRegex(key));
22
- }
23
- return properties;
24
- }, {});
25
- }
26
- function mapToJsonSchema(item) {
27
- if (isArray(item)) {
28
- return item.map(mapToJsonSchema);
29
- }
30
- if (item && (item.isStore || item.$isJsonDocument || item.isLazyRef)) {
31
- return item;
32
- }
33
- if (item && classOf(item) !== Object && isClass(item)) {
34
- return JsonEntityStore.from(item).schema;
35
- }
36
- if (isObject(item)) {
37
- return JsonSchema.from(item);
38
- }
39
- if (isPrimitiveClass(item) || item === Date || item === null) {
40
- return JsonSchema.from({ type: item });
41
- }
42
- return item;
43
- }
44
13
  function isEnum(type) {
45
14
  return isObject(type) && !("toJSON" in type);
46
15
  }
47
- function mapGenerics(jsonSchema, generics) {
48
- const genericLabels = jsonSchema.getGenericLabels();
49
- if (!genericLabels) {
50
- return {};
51
- }
52
- const [types, ...nextTypes] = generics;
53
- return types.reduce((mapping, type, index) => {
54
- const label = genericLabels[index];
55
- if (label) {
56
- if (isEnum(type)) {
57
- mapping[label] = [JsonSchema.from({ type: "string", enum: Object.values(type) })];
58
- }
59
- else if (nextTypes.length) {
60
- const nextSchema = mapToJsonSchema(type);
61
- mapping[label] = [nextSchema, mapGenerics(nextSchema, nextTypes)];
62
- }
63
- else {
64
- mapping[label] = [mapToJsonSchema(type)];
65
- }
66
- }
67
- return mapping;
68
- }, {});
69
- }
70
16
  export class JsonSchema extends Map {
71
17
  /**
72
18
  * All required fields are stored here
73
- * @private
74
19
  */
75
20
  #required;
76
21
  /**
@@ -90,7 +35,7 @@ export class JsonSchema extends Map {
90
35
  #isCollection;
91
36
  #useRefLabel;
92
37
  #propertyKey;
93
- constructor(obj = {}) {
38
+ constructor(obj) {
94
39
  super();
95
40
  this.$kind = "schema";
96
41
  this.$isJsonDocument = true;
@@ -101,7 +46,6 @@ export class JsonSchema extends Map {
101
46
  this.isDiscriminator = false;
102
47
  /**
103
48
  * All required fields are stored here
104
- * @private
105
49
  */
106
50
  this.#required = new Set();
107
51
  /**
@@ -184,7 +128,7 @@ export class JsonSchema extends Map {
184
128
  get hasDiscriminator() {
185
129
  return !!this.#discriminator;
186
130
  }
187
- static from(item = {}) {
131
+ static from(item) {
188
132
  if (item instanceof JsonSchema) {
189
133
  return item;
190
134
  }
@@ -267,18 +211,18 @@ export class JsonSchema extends Map {
267
211
  * @param generics
268
212
  */
269
213
  genericOf(...generics) {
270
- const mapped = mapGenerics(this.#itemSchema || mapToJsonSchema(this.getTarget()), generics);
214
+ const mapped = this.mapGenerics(this.#itemSchema || this.mapToJsonSchema(this.getTarget()), generics);
271
215
  this.vendorKey(VendorKeys.GENERIC_OF, mapped);
272
216
  return this;
273
217
  }
274
- nullable(value) {
218
+ nullable(value = true) {
275
219
  if (!this.isNullable) {
276
220
  this.vendorKey(VendorKeys.NULLABLE, value);
277
221
  }
278
222
  return this;
279
223
  }
280
224
  itemSchema(obj = {}) {
281
- this.#itemSchema = this.#itemSchema || mapToJsonSchema(obj);
225
+ this.#itemSchema = this.#itemSchema || this.mapToJsonSchema(obj);
282
226
  if (isPlainObject(obj)) {
283
227
  this.#itemSchema.assign(obj);
284
228
  }
@@ -343,7 +287,7 @@ export class JsonSchema extends Map {
343
287
  return this;
344
288
  }
345
289
  /**
346
- * More readible form of a one-element "enum"
290
+ * More readable form of a one-element "enum"
347
291
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.24
348
292
  */
349
293
  const(value) {
@@ -351,7 +295,7 @@ export class JsonSchema extends Map {
351
295
  return this;
352
296
  }
353
297
  /**
354
- * This attribute is a string that provides a full description of the of purpose the instance property.
298
+ * This attribute is a string that provides a full description of the purpose of the instance property.
355
299
  *
356
300
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-7.2
357
301
  */
@@ -403,7 +347,7 @@ export class JsonSchema extends Map {
403
347
  return this;
404
348
  }
405
349
  /**
406
- * This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.
350
+ * This keyword determines how child instances validate for arrays and does not directly validate the immediate instance itself.
407
351
  * If "items" is an array of schemas, validation succeeds if every instance element
408
352
  * at a position greater than the size of "items" validates against "additionalItems".
409
353
  * Otherwise, "additionalItems" MUST be ignored, as the "items" schema
@@ -412,7 +356,7 @@ export class JsonSchema extends Map {
412
356
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.10
413
357
  */
414
358
  additionalItems(additionalItems) {
415
- super.set("additionalItems", mapToJsonSchema(additionalItems));
359
+ super.set("additionalItems", this.mapToJsonSchema(additionalItems));
416
360
  return this;
417
361
  }
418
362
  /**
@@ -420,7 +364,7 @@ export class JsonSchema extends Map {
420
364
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.14
421
365
  */
422
366
  contains(contains) {
423
- super.set("contains", mapToJsonSchema(contains));
367
+ super.set("contains", this.mapToJsonSchema(contains));
424
368
  return this;
425
369
  }
426
370
  /**
@@ -439,12 +383,16 @@ export class JsonSchema extends Map {
439
383
  return this.examples(examples);
440
384
  }
441
385
  /**
442
- * This keyword determines how child instances validate for arrays, and does not directly validate the immediate instance itself.
386
+ * This keyword determines how child instances validate for arrays and does not directly validate the immediate instance itself.
443
387
  * Omitting this keyword has the same behavior as an empty schema.
444
388
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.9
445
389
  */
390
+ /**
391
+ * Set the items' schema for array/set collections
392
+ * @param items The schema for array/set items
393
+ */
446
394
  items(items) {
447
- super.set("items", (this.#itemSchema = mapToJsonSchema(items)));
395
+ super.set("items", (this.#itemSchema = this.mapToJsonSchema(items)));
448
396
  return this;
449
397
  }
450
398
  $comment(comment) {
@@ -471,7 +419,7 @@ export class JsonSchema extends Map {
471
419
  return this;
472
420
  }
473
421
  /**
474
- * If this keyword has boolean value false, the instance validates successfully.
422
+ * If this keyword has a boolean value false, the instance validates successfully.
475
423
  * If it has boolean value true, the instance validates successfully if all of its elements are unique.
476
424
  * Omitting this keyword has the same behavior as a value of false.
477
425
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.13
@@ -523,13 +471,9 @@ export class JsonSchema extends Map {
523
471
  this.$allow.push(...allow);
524
472
  return this;
525
473
  }
526
- /**
527
- * Elements of this array must be unique.
528
- * An object instance is valid against this keyword if every item in the array is the name of a property in the instance.
529
- * Omitting this keyword has the same behavior as an empty array.
530
- *
531
- * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.17
532
- */
474
+ optional() {
475
+ return this.required(false);
476
+ }
533
477
  required(required = true) {
534
478
  if (isArray(required)) {
535
479
  this.#required.clear();
@@ -560,20 +504,20 @@ export class JsonSchema extends Map {
560
504
  return [...this.#required];
561
505
  }
562
506
  /**
563
- * This keyword determines how child instances validate for objects, and does not directly validate the immediate instance itself.
507
+ * This keyword determines how child instances validate for objects and does not directly validate the immediate instance itself.
564
508
  * Validation succeeds if, for each name that appears in both the instance and as a name within this keyword's value,
565
509
  * the child instance for that name successfully validates against the corresponding schema.
566
510
  * Omitting this keyword has the same behavior as an empty object.
567
511
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.18
568
512
  */
569
513
  properties(properties) {
570
- super.set("properties", mapProperties(properties));
514
+ super.set("properties", this.mapProperties(properties));
571
515
  return this;
572
516
  }
573
517
  addProperty(key, schema) {
574
518
  const properties = this.get("properties") || {};
575
519
  properties[key] = schema;
576
- super.set("properties", mapProperties(properties));
520
+ super.set("properties", this.mapProperties(properties));
577
521
  return this;
578
522
  }
579
523
  /**
@@ -585,25 +529,25 @@ export class JsonSchema extends Map {
585
529
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.19
586
530
  */
587
531
  patternProperties(patternProperties) {
588
- super.set("patternProperties", mapProperties(patternProperties));
532
+ super.set("patternProperties", this.mapProperties(patternProperties));
589
533
  return this;
590
534
  }
591
535
  /**
592
536
  * This attribute defines a schema for all properties that are not explicitly defined in an object type definition.
593
537
  * If specified, the value MUST be a schema or a boolean.
594
538
  * If false is provided, no additional properties are allowed beyond the properties defined in the schema.
595
- * The default value is an empty schema which allows any value for additional properties.
539
+ * The default value is an empty schema that allows any value for additional properties.
596
540
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.20
597
541
  */
598
542
  additionalProperties(additionalProperties) {
599
- super.set("additionalProperties", mapToJsonSchema(additionalProperties));
543
+ super.set("additionalProperties", this.mapToJsonSchema(additionalProperties));
600
544
  return this;
601
545
  }
602
546
  /**
603
547
  * This attribute defines a schema for all properties that are not explicitly defined in an object type definition.
604
548
  * If specified, the value MUST be a schema or a boolean.
605
549
  * If false is provided, no additional properties are allowed beyond the properties defined in the schema.
606
- * The default value is an empty schema which allows any value for additional properties.
550
+ * The default value is an empty schema that allows any value for additional properties.
607
551
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.20
608
552
  * @alias additionalProperties
609
553
  * @param unknown
@@ -619,7 +563,7 @@ export class JsonSchema extends Map {
619
563
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.21
620
564
  */
621
565
  dependencies(dependencies) {
622
- super.set("dependencies", mapProperties(dependencies));
566
+ super.set("dependencies", this.mapProperties(dependencies));
623
567
  return this;
624
568
  }
625
569
  /**
@@ -629,7 +573,7 @@ export class JsonSchema extends Map {
629
573
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.22
630
574
  */
631
575
  propertyNames(propertyNames) {
632
- super.set("propertyNames", mapToJsonSchema(propertyNames));
576
+ super.set("propertyNames", this.mapToJsonSchema(propertyNames));
633
577
  return this;
634
578
  }
635
579
  enum(enumValue, ...enumValues) {
@@ -654,7 +598,7 @@ export class JsonSchema extends Map {
654
598
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-7.1
655
599
  */
656
600
  definitions(definitions) {
657
- super.set("definitions", mapProperties(definitions));
601
+ super.set("definitions", this.mapProperties(definitions));
658
602
  return this;
659
603
  }
660
604
  /**
@@ -687,7 +631,7 @@ export class JsonSchema extends Map {
687
631
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.29
688
632
  */
689
633
  not(not) {
690
- super.set("not", mapToJsonSchema(not));
634
+ super.set("not", this.mapToJsonSchema(not));
691
635
  return this;
692
636
  }
693
637
  /**
@@ -699,6 +643,14 @@ export class JsonSchema extends Map {
699
643
  super.set("multipleOf", multipleOf);
700
644
  return this;
701
645
  }
646
+ /**
647
+ * Representing an inclusive upper limit for a numeric instance.
648
+ * This keyword validates only if the instance is less than or exactly equal to "maximum".
649
+ * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.2
650
+ */
651
+ max(maximum) {
652
+ return this.maximum(maximum);
653
+ }
702
654
  /**
703
655
  * Representing an inclusive upper limit for a numeric instance.
704
656
  * This keyword validates only if the instance is less than or exactly equal to "maximum".
@@ -717,6 +669,14 @@ export class JsonSchema extends Map {
717
669
  super.set("exclusiveMaximum", exclusiveMaximum);
718
670
  return this;
719
671
  }
672
+ /**
673
+ * Representing an inclusive lower limit for a numeric instance.
674
+ * This keyword validates only if the instance is greater than or exactly equal to "minimum".
675
+ * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.4
676
+ */
677
+ min(minimum) {
678
+ return this.minimum(minimum);
679
+ }
720
680
  /**
721
681
  * Representing an inclusive lower limit for a numeric instance.
722
682
  * This keyword validates only if the instance is greater than or exactly equal to "minimum".
@@ -890,7 +850,7 @@ export class JsonSchema extends Map {
890
850
  toJSON(options) {
891
851
  return execMapper("schema", [this], options);
892
852
  }
893
- assign(obj = {}) {
853
+ assign(obj) {
894
854
  const entries = obj instanceof JsonSchema ? [...obj.entries()] : Object.entries(obj);
895
855
  entries.forEach(([key, value]) => {
896
856
  this.set(key, value);
@@ -922,7 +882,7 @@ export class JsonSchema extends Map {
922
882
  return this;
923
883
  }
924
884
  /**
925
- * Return the Json type as string
885
+ * Return the JSON type as string
926
886
  */
927
887
  getJsonType() {
928
888
  if (this.get("anyOf")) {
@@ -1013,7 +973,7 @@ export class JsonSchema extends Map {
1013
973
  }
1014
974
  return true;
1015
975
  })
1016
- .map(mapToJsonSchema);
976
+ .map((item) => this.mapToJsonSchema(item));
1017
977
  if (resolved.length === 1 && !(value[0] instanceof JsonSchema) && !this.isNullable) {
1018
978
  if (!resolved[0].hasDiscriminator) {
1019
979
  return this.type(value[0]);
@@ -1022,7 +982,7 @@ export class JsonSchema extends Map {
1022
982
  if (!children.length) {
1023
983
  return this.type(value[0]);
1024
984
  }
1025
- resolved = children.map(mapToJsonSchema);
985
+ resolved = children.map((item) => this.mapToJsonSchema(item));
1026
986
  }
1027
987
  super.set(keyword, resolved);
1028
988
  const jsonSchema = resolved[0];
@@ -1042,4 +1002,58 @@ export class JsonSchema extends Map {
1042
1002
  this.#discriminator = discriminator;
1043
1003
  }
1044
1004
  }
1005
+ mapToJsonSchema(item) {
1006
+ if (isArray(item)) {
1007
+ return item.map((item) => this.mapToJsonSchema(item));
1008
+ }
1009
+ if (item && (item.isStore || item.$isJsonDocument || item.isLazyRef)) {
1010
+ return item;
1011
+ }
1012
+ if (item && classOf(item) !== Object && isClass(item)) {
1013
+ return JsonEntityStore.from(item).schema;
1014
+ }
1015
+ if (isObject(item)) {
1016
+ return JsonSchema.from(item);
1017
+ }
1018
+ if (isPrimitiveClass(item) || item === Date || item === null) {
1019
+ return JsonSchema.from({ type: item });
1020
+ }
1021
+ return item;
1022
+ }
1023
+ mapGenerics(jsonSchema, generics) {
1024
+ const genericLabels = jsonSchema.getGenericLabels();
1025
+ if (!genericLabels) {
1026
+ return {};
1027
+ }
1028
+ const [types, ...nextTypes] = generics;
1029
+ return types.reduce((mapping, type, index) => {
1030
+ const label = genericLabels[index];
1031
+ if (label) {
1032
+ if (isEnum(type)) {
1033
+ mapping[label] = [JsonSchema.from({ type: "string", enum: Object.values(type) })];
1034
+ }
1035
+ else if (nextTypes.length) {
1036
+ const nextSchema = this.mapToJsonSchema(type);
1037
+ mapping[label] = [nextSchema, this.mapGenerics(nextSchema, nextTypes)];
1038
+ }
1039
+ else {
1040
+ mapping[label] = [this.mapToJsonSchema(type)];
1041
+ }
1042
+ }
1043
+ return mapping;
1044
+ }, {});
1045
+ }
1046
+ mapProperties(properties) {
1047
+ // istanbul ignore next
1048
+ if (properties instanceof JsonSchema) {
1049
+ return properties;
1050
+ }
1051
+ return Object.entries(properties).reduce((properties, [key, schema]) => {
1052
+ properties[toJsonRegex(key)] = this.mapToJsonSchema(schema);
1053
+ if (schema instanceof JsonSchema) {
1054
+ schema.propertyKey(toJsonRegex(key));
1055
+ }
1056
+ return properties;
1057
+ }, {});
1058
+ }
1045
1059
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -5,6 +5,8 @@ import { from } from "./from.js";
5
5
  * * See @@JsonSchema@@ to discover available methods.
6
6
  *
7
7
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.26
8
+ *
9
+ * @schemaFunctional
8
10
  */
9
11
  export function allOf(...allOf) {
10
12
  return from().allOf(allOf);
package/lib/esm/fn/any.js CHANGED
@@ -1,9 +1,4 @@
1
1
  import { from } from "./from.js";
2
- /**
3
- * Declare a model with any type (By default: `integer`, `number`, `string`, `boolean`, `array`, `object`, `null`)
4
- *
5
- * See @@JsonSchema@@ to discover available methods.
6
- */
7
2
  export function any(...types) {
8
3
  return from().any(...types);
9
4
  }
@@ -2,9 +2,10 @@ import { from } from "./from.js";
2
2
  /**
3
3
  * See https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.27
4
4
  *
5
- * * See @@JsonSchema@@ to discover available methods.
5
+ * See @@JsonSchema@@ to discover available methods.
6
6
  *
7
7
  * @see https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.27
8
+ * @schemaFunctional
8
9
  */
9
10
  export function anyOf(...anyOf) {
10
11
  return from().anyOf(anyOf);