Skip to content

Commit c60c08f

Browse files
authored
Merge pull request #481 from traversable/json-schema-refs
feat(json-schema,json-schema-types): adds support for recursive refs
2 parents 2c13032 + f3cad78 commit c60c08f

36 files changed

+4810
-1029
lines changed

.changeset/olive-comics-tease.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@traversable/json-schema-types": patch
3+
"@traversable/json-schema-test": patch
4+
"@traversable/json-schema": patch
5+
---
6+
7+
feat(json-schema,json-schema-types): adds support for recursive refs for `JsonSchema.toType` and `JsonSchema.check`

.changeset/olive-pens-happen.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@traversable/json-schema-types": patch
3+
"@traversable/json-schema": patch
4+
---
5+
6+
break(json-schema,json-schema-types): renames `canonicalizeRefName` to `canonizeRefName`

.changeset/spicy-falcons-study.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@traversable/json-schema-types": patch
3+
"@traversable/json-schema-test": patch
4+
"@traversable/arktype-test": patch
5+
"@traversable/json-schema": patch
6+
"@traversable/arktype": patch
7+
---
8+
9+
feat(json-schema,json-schema-types): adds `JsonSchema.deepClone` support for $ref nodes

.changeset/thick-sheep-do.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@traversable/json-schema-types": patch
3+
"@traversable/json-schema-test": patch
4+
"@traversable/arktype-test": patch
5+
"@traversable/json-schema": patch
6+
"@traversable/arktype": patch
7+
---
8+
9+
break(json-schema,json-schema-test,json-schema-types): renames `JsonSchema.Union` to `JsonSchema.AnyOf` and `JsonSchema.Intersection` to `JsonSchema.AllOf`
10+
11+
feat(json-schema,json-schema-test,json-schema-types): adds `JsonSchema.OneOf`

.changeset/yummy-geckos-allow.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@traversable/json-schema-types": patch
3+
"@traversable/json-schema-test": patch
4+
"@traversable/json-schema": patch
5+
---
6+
7+
feat(json-schema,json-schema-types): adds `JsonSchema.deepEqual` support for $ref nodes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@
5757
"typescript": "catalog:",
5858
"vitest": "catalog:"
5959
},
60-
"packageManager": "pnpm@10.14.0"
60+
"packageManager": "pnpm@10.15.1"
6161
}

packages/arktype-test/src/generator-options.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export type Constraints = {
8585
date?: {}
8686
enum?: {}
8787
// int?: { min: undefined | number, max: undefined | number, multipleOf?: number } & fc.IntegerConstraints
88-
intersection?: {}
88+
allOf?: {}
8989
literal?: {}
9090
never?: {}
9191
null?: {}
@@ -97,7 +97,8 @@ export type Constraints = {
9797
symbol?: {}
9898
tuple?: fc.ArrayConstraints
9999
undefined?: {}
100-
union?: fc.ArrayConstraints
100+
anyOf?: fc.ArrayConstraints
101+
oneOf?: fc.ArrayConstraints
101102
unknown?: {}
102103
['*']?: fc.OneOfConstraints
103104
}
@@ -143,7 +144,7 @@ export const defaultConstraints = {
143144
// max: undefined,
144145
// multipleOf: Number.NaN,
145146
// },
146-
intersection: {},
147+
allOf: {},
147148
literal: {},
148149
never: {},
149150
null: {},
@@ -179,7 +180,13 @@ export const defaultConstraints = {
179180
depthIdentifier: fc.createDepthIdentifier(),
180181
} satisfies fc.ArrayConstraints,
181182
undefined: {},
182-
union: {
183+
anyOf: {
184+
depthIdentifier: fc.createDepthIdentifier(),
185+
minLength: 1,
186+
maxLength: 3,
187+
size: 'xsmall',
188+
} satisfies fc.ArrayConstraints,
189+
oneOf: {
183190
depthIdentifier: fc.createDepthIdentifier(),
184191
minLength: 1,
185192
maxLength: 3,
@@ -236,7 +243,7 @@ export function parseOptions(options: Options<any> = defaults as never): Config
236243
// min: intMin,
237244
// // ...INT
238245
// } = defaultConstraints.int,
239-
intersection = defaultConstraints.intersection,
246+
allOf = defaultConstraints.allOf,
240247
literal = defaultConstraints.literal,
241248
never = defaultConstraints.never,
242249
null: null_ = defaultConstraints.null,
@@ -267,12 +274,18 @@ export function parseOptions(options: Options<any> = defaults as never): Config
267274
...TUPLE
268275
} = defaultConstraints.tuple,
269276
undefined: undefined_ = defaultConstraints.undefined,
270-
union: {
271-
minLength: unionMinLength = defaultConstraints.union.minLength,
272-
maxLength: unionMaxLength = defaultConstraints.union.maxLength,
273-
size: unionSize = defaultConstraints.union.size,
274-
...UNION
275-
} = defaultConstraints.union,
277+
anyOf: {
278+
minLength: anyOfMinLength = defaultConstraints.anyOf.minLength,
279+
maxLength: anyOfMaxLength = defaultConstraints.anyOf.maxLength,
280+
size: anyOfSize = defaultConstraints.anyOf.size,
281+
...ANY_OF
282+
} = defaultConstraints.anyOf,
283+
oneOf: {
284+
minLength: oneOfMinLength = defaultConstraints.oneOf.minLength,
285+
maxLength: oneOfMaxLength = defaultConstraints.oneOf.maxLength,
286+
size: oneOfSize = defaultConstraints.oneOf.size,
287+
...ONE_OF
288+
} = defaultConstraints.oneOf,
276289
unknown = defaultConstraints.unknown,
277290
object: {
278291
maxKeys: objectMaxKeys = defaultConstraints.object.maxKeys,
@@ -321,7 +334,7 @@ export function parseOptions(options: Options<any> = defaults as never): Config
321334
// max: intMax,
322335
// min: intMin,
323336
// },
324-
intersection,
337+
allOf,
325338
literal,
326339
never,
327340
null: null_,
@@ -351,11 +364,17 @@ export function parseOptions(options: Options<any> = defaults as never): Config
351364
maxLength: tupleMaxLength,
352365
},
353366
undefined: undefined_,
354-
union: {
355-
...UNION,
356-
minLength: unionMinLength,
357-
maxLength: unionMaxLength,
358-
size: unionSize,
367+
anyOf: {
368+
...ANY_OF,
369+
minLength: anyOfMinLength,
370+
maxLength: anyOfMaxLength,
371+
size: anyOfSize,
372+
},
373+
oneOf: {
374+
...ONE_OF,
375+
minLength: oneOfMinLength,
376+
maxLength: oneOfMaxLength,
377+
size: oneOfSize,
359378
},
360379
unknown,
361380
}

packages/arktype-test/src/generator-seed.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ export const byTag = {
2323
literal: 550,
2424
array: 1000,
2525
optional: 2500,
26-
intersection: 6000,
26+
allOf: 6000,
2727
record: 7000,
2828
object: 7500,
2929
tuple: 8000,
30-
union: 8500,
30+
anyOf: 8500,
31+
oneOf: 9000,
3132
} as const satisfies Record<AnyTypeName, number>
3233

3334
export function invert<T extends Record<keyof any, keyof any>>(x: T): { [K in keyof T as T[K]]: K }
@@ -62,9 +63,9 @@ export declare namespace Seed {
6263
| Seed.Record<T>
6364
| Seed.Object<T>
6465
| Seed.Tuple<T>
65-
| Seed.Union<T>
66-
// | Seed.Optional<T>
67-
| Seed.Intersection<T>
66+
| Seed.AnyOf<T>
67+
| Seed.OneOf<T>
68+
| Seed.AllOf<T>
6869

6970
interface Free extends T.HKT { [-1]: Seed.F<this[0]> }
7071
////////////////
@@ -115,9 +116,9 @@ export declare namespace Seed {
115116
record: Seed.Record<T>
116117
object: Seed.Object<T>
117118
tuple: Seed.Tuple<T>
118-
union: Seed.Union<T>
119-
// optional: Seed.Optional<T>
120-
intersection: Seed.Intersection<T>
119+
anyOf: Seed.AnyOf<T>
120+
oneOf: Seed.OneOf<T>
121+
allOf: Seed.AllOf<T>
121122
}
122123
type Composite =
123124
| Seed.Array
@@ -139,12 +140,13 @@ export declare namespace Seed {
139140
////////////////
140141
/// applicative
141142
interface Object<T = unknown> extends newtype<[seed: byTag['object'], def: [K: string, V: T][]]> {}
142-
interface Union<T = unknown> extends newtype<[seed: byTag['union'], def: T[]]> {}
143+
interface AnyOf<T = unknown> extends newtype<[seed: byTag['anyOf'], def: T[]]> {}
144+
interface OneOf<T = unknown> extends newtype<[seed: byTag['oneOf'], def: T[]]> {}
143145
interface Tuple<T = unknown> extends newtype<[seed: byTag['tuple'], def: T[]]> {}
144146
////////////////
145147
/// binary
146148
interface Record<T = unknown> extends newtype<[seed: byTag['record'], def: T]> {}
147-
interface Intersection<T = unknown> extends newtype<[seed: byTag['intersection'], def: [A: T, B: T]]> {}
149+
interface AllOf<T = unknown> extends newtype<[seed: byTag['allOf'], def: [A: T, B: T]]> {}
148150
}
149151

150152
export const Functor: T.Functor.Ix<boolean, Seed.Free, Seed.F<unknown>> = {
@@ -166,11 +168,12 @@ export const Functor: T.Functor.Ix<boolean, Seed.Free, Seed.F<unknown>> = {
166168
case x[0] === byTag.literal: return x
167169
case x[0] === byTag.array: return [x[0], f(x[1]), x[2]]
168170
// case x[0] === byTag.optional: return [x[0], f(x[1])]
169-
case x[0] === byTag.intersection: return [x[0], [f(x[1][0]), f(x[1][1])]]
171+
case x[0] === byTag.allOf: return [x[0], [f(x[1][0]), f(x[1][1])]]
170172
case x[0] === byTag.record: return [x[0], f(x[1])]
171173
case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v)] satisfies [any, any])]
172174
case x[0] === byTag.tuple: return [x[0], x[1].map(f)]
173-
case x[0] === byTag.union: return [x[0], x[1].map(f)]
175+
case x[0] === byTag.anyOf: return [x[0], x[1].map(f)]
176+
case x[0] === byTag.oneOf: return [x[0], x[1].map(f)]
174177
}
175178
}
176179
},
@@ -192,9 +195,10 @@ export const Functor: T.Functor.Ix<boolean, Seed.Free, Seed.F<unknown>> = {
192195
case x[0] === byTag.literal: return x
193196
case x[0] === byTag.array: return [x[0], f(x[1], false, x), x[2]]
194197
// case x[0] === byTag.optional: return [x[0], f(x[1], isProperty, x)]
195-
case x[0] === byTag.intersection: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]]
198+
case x[0] === byTag.allOf: return [x[0], [f(x[1][0], false, x), f(x[1][1], false, x)]]
196199
case x[0] === byTag.record: return [x[0], f(x[1], false, x)]
197-
case x[0] === byTag.union: return [x[0], x[1].map((_) => f(_, false, x))]
200+
case x[0] === byTag.anyOf: return [x[0], x[1].map((_) => f(_, false, x))]
201+
case x[0] === byTag.oneOf: return [x[0], x[1].map((_) => f(_, false, x))]
198202
case x[0] === byTag.tuple: return [x[0], x[1].map((_) => f(_, true, x))]
199203
case x[0] === byTag.object: return [x[0], x[1].map(([k, v]) => [k, f(v, true, x)] satisfies [any, any])]
200204
}

packages/arktype-test/src/generator.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import * as fc from 'fast-check'
44
import type { newtype, inline } from '@traversable/registry'
55
import {
66
Array_isArray,
7-
escape,
87
fn,
98
getRandomElementOf,
109
isKeyOf,
@@ -101,8 +100,9 @@ const UnaryMap = {
101100
fc.uniqueArray(fc.tuple(identifier, tie('*')), $)
102101
),
103102
tuple: (tie, $) => fc.tuple(fc.constant(byTag.tuple), fc.array(tie('*'), $)),
104-
union: (tie, $) => fc.tuple(fc.constant(byTag.union), fc.array(tie('*'), $)),
105-
intersection: (tie) => entries(tie('*'), { minLength: 2 }).map(fn.flow(
103+
anyOf: (tie, $) => fc.tuple(fc.constant(byTag.anyOf), fc.array(tie('*'), $)),
104+
oneOf: (tie, $) => fc.tuple(fc.constant(byTag.oneOf), fc.array(tie('*'), $)),
105+
allOf: (tie) => entries(tie('*'), { minLength: 2 }).map(fn.flow(
106106
(xs) => pair(
107107
xs.slice(0, Math.ceil(xs.length / 2)),
108108
xs.slice(Math.ceil(xs.length / 2)),
@@ -111,7 +111,7 @@ const UnaryMap = {
111111
pair(byTag.object, l),
112112
pair(byTag.object, r),
113113
),
114-
(both) => pair(byTag.intersection, both),
114+
(both) => pair(byTag.allOf, both),
115115
)),
116116
} satisfies { [K in keyof Seed.UnaryMap<never>]: SeedBuilder<K> }
117117

@@ -327,9 +327,10 @@ const GeneratorByTag = {
327327
// optional: (x, _$, isProperty) => isProperty ? x[1] : fc.option(x[1], { nil: undefined }),
328328
record: (x) => fc.dictionary(fc.string(), x[1]),
329329
tuple: (x) => fc.tuple(...x[1]),
330-
union: (x) => fc.oneof(...(x[1] || [fc.constant(void 0 as never)])),
330+
anyOf: (x) => fc.oneof(...(x[1] || [fc.constant(void 0 as never)])),
331+
oneOf: (x) => fc.oneof(...(x[1] || [fc.constant(void 0 as never)])),
331332
object: (x) => fc.record(Object_fromEntries(x[1]), { requiredKeys: x[1].filter(([k]) => !k.endsWith('?')).map(([k]) => k) }),
332-
intersection: (x) => fc.tuple(...x[1]).map(([x, y]) => intersect(x, y)),
333+
allOf: (x) => fc.tuple(...x[1]).map(([x, y]) => intersect(x, y)),
333334
} satisfies {
334335
[K in keyof Seed]: (x: Seed<fc.Arbitrary<unknown>>[K], $: Config<never>, isProperty: boolean) => fc.Arbitrary<unknown>
335336
}
@@ -565,10 +566,11 @@ export function seedToSchema<T>(seed: Seed.F<T>) {
565566
case x[0] === byTag.enum: return type.enumerated(...Object_values(x[1]))
566567
case x[0] === byTag.array: return x[1].array()
567568
// case x[0] === byTag.optional: return x[1]
568-
case x[0] === byTag.intersection: return x[1].reduce((l, r) => l.and(r))
569+
case x[0] === byTag.allOf: return x[1].reduce((l, r) => l.and(r))
569570
case x[0] === byTag.record: return type.Record(type.string, x[1])
570571
case x[0] === byTag.tuple: return type(x[1] as [])
571-
case x[0] === byTag.union: return x[1].reduce((l, r) => l.or(r))
572+
case x[0] === byTag.anyOf: return x[1].reduce((l, r) => l.or(r))
573+
case x[0] === byTag.oneOf: return x[1].reduce((l, r) => l.or(r))
572574
case x[0] === byTag.object: return type(Object_fromEntries(x[1]))
573575
case x[0] === byTag.literal: return type(
574576
// typeof x[1] === 'string' ? `"${escape(x[1])}"` : (typeof x[1] === 'bigint' ? `${x[1]}n` : `${x[1]}`) as '""'

packages/arktype-test/src/typename.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ export const TypeName = {
1414
record: 'record',
1515
unknown: 'unknown',
1616
never: 'never',
17-
intersection: 'intersection',
18-
union: 'union',
17+
allOf: 'allOf',
18+
anyOf: 'anyOf',
19+
oneOf: 'oneOf',
1920
enum: 'enum',
2021
literal: 'literal',
2122
// bigint: 'bigint',

0 commit comments

Comments
 (0)