@atproto/lex-schema 0.0.2 → 0.0.3

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 (289) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/core/$type.d.ts +6 -3
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js +1 -0
  5. package/dist/core/$type.js.map +1 -1
  6. package/dist/core/record-key.d.ts +3 -3
  7. package/dist/core/record-key.d.ts.map +1 -1
  8. package/dist/core/record-key.js +12 -6
  9. package/dist/core/record-key.js.map +1 -1
  10. package/dist/core/result.d.ts.map +1 -1
  11. package/dist/core/result.js +6 -0
  12. package/dist/core/result.js.map +1 -1
  13. package/dist/core/string-format.d.ts +30 -27
  14. package/dist/core/string-format.d.ts.map +1 -1
  15. package/dist/core/string-format.js +56 -42
  16. package/dist/core/string-format.js.map +1 -1
  17. package/dist/core/types.d.ts +9 -1
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/external.d.ts +31 -28
  21. package/dist/external.d.ts.map +1 -1
  22. package/dist/external.js +33 -17
  23. package/dist/external.js.map +1 -1
  24. package/dist/schema/_parameters.d.ts +2 -2
  25. package/dist/schema/_parameters.d.ts.map +1 -1
  26. package/dist/schema/array.d.ts +5 -6
  27. package/dist/schema/array.d.ts.map +1 -1
  28. package/dist/schema/array.js +5 -6
  29. package/dist/schema/array.js.map +1 -1
  30. package/dist/schema/blob.d.ts +2 -3
  31. package/dist/schema/blob.d.ts.map +1 -1
  32. package/dist/schema/blob.js +1 -2
  33. package/dist/schema/blob.js.map +1 -1
  34. package/dist/schema/boolean.d.ts +4 -5
  35. package/dist/schema/boolean.d.ts.map +1 -1
  36. package/dist/schema/boolean.js +2 -3
  37. package/dist/schema/boolean.js.map +1 -1
  38. package/dist/schema/bytes.d.ts +3 -4
  39. package/dist/schema/bytes.d.ts.map +1 -1
  40. package/dist/schema/bytes.js +2 -3
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +13 -6
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +2 -4
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.d.ts +3 -4
  47. package/dist/schema/custom.d.ts.map +1 -1
  48. package/dist/schema/custom.js +4 -3
  49. package/dist/schema/custom.js.map +1 -1
  50. package/dist/schema/dict.d.ts +3 -3
  51. package/dist/schema/dict.d.ts.map +1 -1
  52. package/dist/schema/dict.js +1 -1
  53. package/dist/schema/dict.js.map +1 -1
  54. package/dist/schema/discriminated-union.d.ts +15 -24
  55. package/dist/schema/discriminated-union.d.ts.map +1 -1
  56. package/dist/schema/discriminated-union.js +40 -64
  57. package/dist/schema/discriminated-union.js.map +1 -1
  58. package/dist/schema/enum.d.ts +8 -4
  59. package/dist/schema/enum.d.ts.map +1 -1
  60. package/dist/schema/enum.js +5 -3
  61. package/dist/schema/enum.js.map +1 -1
  62. package/dist/schema/integer.d.ts +3 -4
  63. package/dist/schema/integer.d.ts.map +1 -1
  64. package/dist/schema/integer.js +3 -4
  65. package/dist/schema/integer.js.map +1 -1
  66. package/dist/schema/intersection.d.ts +22 -14
  67. package/dist/schema/intersection.d.ts.map +1 -1
  68. package/dist/schema/intersection.js +12 -22
  69. package/dist/schema/intersection.js.map +1 -1
  70. package/dist/schema/literal.d.ts +7 -3
  71. package/dist/schema/literal.d.ts.map +1 -1
  72. package/dist/schema/literal.js +5 -3
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.d.ts +2 -2
  75. package/dist/schema/never.d.ts.map +1 -1
  76. package/dist/schema/never.js +1 -1
  77. package/dist/schema/never.js.map +1 -1
  78. package/dist/schema/null.d.ts +2 -3
  79. package/dist/schema/null.d.ts.map +1 -1
  80. package/dist/schema/null.js +1 -2
  81. package/dist/schema/null.js.map +1 -1
  82. package/dist/schema/nullable.d.ts +7 -0
  83. package/dist/schema/nullable.d.ts.map +1 -0
  84. package/dist/schema/nullable.js +19 -0
  85. package/dist/schema/nullable.js.map +1 -0
  86. package/dist/schema/object.d.ts +10 -44
  87. package/dist/schema/object.d.ts.map +1 -1
  88. package/dist/schema/object.js +10 -46
  89. package/dist/schema/object.js.map +1 -1
  90. package/dist/schema/optional.d.ts +7 -0
  91. package/dist/schema/optional.d.ts.map +1 -0
  92. package/dist/schema/optional.js +25 -0
  93. package/dist/schema/optional.js.map +1 -0
  94. package/dist/schema/params.d.ts +14 -19
  95. package/dist/schema/params.d.ts.map +1 -1
  96. package/dist/schema/params.js +10 -24
  97. package/dist/schema/params.js.map +1 -1
  98. package/dist/schema/payload.d.ts +4 -4
  99. package/dist/schema/payload.d.ts.map +1 -1
  100. package/dist/schema/payload.js.map +1 -1
  101. package/dist/schema/permission-set.d.ts +6 -6
  102. package/dist/schema/permission-set.d.ts.map +1 -1
  103. package/dist/schema/permission-set.js +1 -2
  104. package/dist/schema/permission-set.js.map +1 -1
  105. package/dist/schema/permission.d.ts +0 -1
  106. package/dist/schema/permission.d.ts.map +1 -1
  107. package/dist/schema/permission.js +0 -1
  108. package/dist/schema/permission.js.map +1 -1
  109. package/dist/schema/procedure.d.ts +8 -9
  110. package/dist/schema/procedure.d.ts.map +1 -1
  111. package/dist/schema/procedure.js +0 -1
  112. package/dist/schema/procedure.js.map +1 -1
  113. package/dist/schema/query.d.ts +7 -8
  114. package/dist/schema/query.d.ts.map +1 -1
  115. package/dist/schema/query.js +0 -1
  116. package/dist/schema/query.js.map +1 -1
  117. package/dist/schema/record.d.ts +34 -28
  118. package/dist/schema/record.d.ts.map +1 -1
  119. package/dist/schema/record.js +1 -2
  120. package/dist/schema/record.js.map +1 -1
  121. package/dist/schema/ref.d.ts +2 -3
  122. package/dist/schema/ref.d.ts.map +1 -1
  123. package/dist/schema/ref.js +1 -2
  124. package/dist/schema/ref.js.map +1 -1
  125. package/dist/schema/refine.d.ts +18 -0
  126. package/dist/schema/refine.d.ts.map +1 -0
  127. package/dist/schema/refine.js +33 -0
  128. package/dist/schema/refine.js.map +1 -0
  129. package/dist/schema/regexp.d.ts +7 -0
  130. package/dist/schema/regexp.d.ts.map +1 -0
  131. package/dist/schema/regexp.js +22 -0
  132. package/dist/schema/regexp.js.map +1 -0
  133. package/dist/schema/string.d.ts +4 -8
  134. package/dist/schema/string.d.ts.map +1 -1
  135. package/dist/schema/string.js +6 -3
  136. package/dist/schema/string.js.map +1 -1
  137. package/dist/schema/subscription.d.ts +7 -6
  138. package/dist/schema/subscription.d.ts.map +1 -1
  139. package/dist/schema/subscription.js.map +1 -1
  140. package/dist/schema/token.d.ts +2 -3
  141. package/dist/schema/token.d.ts.map +1 -1
  142. package/dist/schema/token.js +1 -2
  143. package/dist/schema/token.js.map +1 -1
  144. package/dist/schema/typed-object.d.ts +29 -27
  145. package/dist/schema/typed-object.d.ts.map +1 -1
  146. package/dist/schema/typed-object.js +1 -2
  147. package/dist/schema/typed-object.js.map +1 -1
  148. package/dist/schema/typed-ref.d.ts +2 -2
  149. package/dist/schema/typed-ref.d.ts.map +1 -1
  150. package/dist/schema/typed-ref.js +1 -1
  151. package/dist/schema/typed-ref.js.map +1 -1
  152. package/dist/schema/typed-union.d.ts +3 -4
  153. package/dist/schema/typed-union.d.ts.map +1 -1
  154. package/dist/schema/typed-union.js +3 -10
  155. package/dist/schema/typed-union.js.map +1 -1
  156. package/dist/schema/union.d.ts +2 -2
  157. package/dist/schema/union.d.ts.map +1 -1
  158. package/dist/schema/union.js +1 -1
  159. package/dist/schema/union.js.map +1 -1
  160. package/dist/schema/unknown-object.d.ts +2 -3
  161. package/dist/schema/unknown-object.d.ts.map +1 -1
  162. package/dist/schema/unknown-object.js +1 -2
  163. package/dist/schema/unknown-object.js.map +1 -1
  164. package/dist/schema/unknown.d.ts +2 -2
  165. package/dist/schema/unknown.d.ts.map +1 -1
  166. package/dist/schema/unknown.js +1 -1
  167. package/dist/schema/unknown.js.map +1 -1
  168. package/dist/schema.d.ts +4 -0
  169. package/dist/schema.d.ts.map +1 -1
  170. package/dist/schema.js +6 -1
  171. package/dist/schema.js.map +1 -1
  172. package/dist/util/array-agg.d.ts.map +1 -1
  173. package/dist/util/array-agg.js +1 -0
  174. package/dist/util/array-agg.js.map +1 -1
  175. package/dist/util/lazy-property.d.ts +2 -0
  176. package/dist/util/lazy-property.d.ts.map +1 -0
  177. package/dist/util/lazy-property.js +14 -0
  178. package/dist/util/lazy-property.js.map +1 -0
  179. package/dist/validation/schema.d.ts +24 -0
  180. package/dist/validation/schema.d.ts.map +1 -0
  181. package/dist/validation/schema.js +57 -0
  182. package/dist/validation/schema.js.map +1 -0
  183. package/dist/validation/validation-error.d.ts +3 -3
  184. package/dist/validation/validation-error.d.ts.map +1 -1
  185. package/dist/validation/validation-error.js +32 -4
  186. package/dist/validation/validation-error.js.map +1 -1
  187. package/dist/validation/validation-issue.d.ts +32 -24
  188. package/dist/validation/validation-issue.d.ts.map +1 -1
  189. package/dist/validation/validation-issue.js +136 -92
  190. package/dist/validation/validation-issue.js.map +1 -1
  191. package/dist/validation/validator.d.ts +20 -50
  192. package/dist/validation/validator.d.ts.map +1 -1
  193. package/dist/validation/validator.js +40 -134
  194. package/dist/validation/validator.js.map +1 -1
  195. package/dist/validation.d.ts +1 -0
  196. package/dist/validation.d.ts.map +1 -1
  197. package/dist/validation.js +1 -0
  198. package/dist/validation.js.map +1 -1
  199. package/package.json +8 -4
  200. package/src/core/$type.ts +7 -4
  201. package/src/core/record-key.ts +12 -5
  202. package/src/core/result.ts +6 -0
  203. package/src/core/string-format.ts +97 -61
  204. package/src/core/types.ts +12 -6
  205. package/src/external.ts +92 -70
  206. package/src/schema/_parameters.test.ts +416 -0
  207. package/src/schema/array.test.ts +237 -0
  208. package/src/schema/array.ts +17 -11
  209. package/src/schema/blob.test.ts +506 -0
  210. package/src/schema/blob.ts +3 -5
  211. package/src/schema/boolean.test.ts +116 -0
  212. package/src/schema/boolean.ts +5 -7
  213. package/src/schema/bytes.test.ts +226 -0
  214. package/src/schema/bytes.ts +4 -6
  215. package/src/schema/cid.test.ts +155 -0
  216. package/src/schema/cid.ts +14 -8
  217. package/src/schema/custom.test.ts +413 -0
  218. package/src/schema/custom.ts +10 -8
  219. package/src/schema/dict.test.ts +198 -0
  220. package/src/schema/dict.ts +6 -8
  221. package/src/schema/discriminated-union.test.ts +675 -0
  222. package/src/schema/discriminated-union.ts +68 -95
  223. package/src/schema/enum.test.ts +396 -0
  224. package/src/schema/enum.ts +12 -5
  225. package/src/schema/integer.test.ts +312 -0
  226. package/src/schema/integer.ts +5 -7
  227. package/src/schema/intersection.test.ts +32 -0
  228. package/src/schema/intersection.ts +37 -40
  229. package/src/schema/literal.test.ts +531 -0
  230. package/src/schema/literal.ts +12 -5
  231. package/src/schema/never.test.ts +174 -0
  232. package/src/schema/never.ts +3 -10
  233. package/src/schema/null.test.ts +79 -0
  234. package/src/schema/null.ts +3 -5
  235. package/src/schema/nullable.test.ts +480 -0
  236. package/src/schema/nullable.ts +23 -0
  237. package/src/schema/object.test.ts +47 -115
  238. package/src/schema/object.ts +19 -123
  239. package/src/schema/optional.test.ts +485 -0
  240. package/src/schema/optional.ts +31 -0
  241. package/src/schema/params.test.ts +582 -0
  242. package/src/schema/params.ts +37 -55
  243. package/src/schema/payload.test.ts +345 -0
  244. package/src/schema/payload.ts +5 -5
  245. package/src/schema/permission-set.test.ts +679 -0
  246. package/src/schema/permission-set.ts +6 -8
  247. package/src/schema/permission.test.ts +536 -0
  248. package/src/schema/permission.ts +0 -2
  249. package/src/schema/procedure.test.ts +443 -0
  250. package/src/schema/procedure.ts +11 -13
  251. package/src/schema/query.test.ts +408 -0
  252. package/src/schema/query.ts +9 -11
  253. package/src/schema/record.test.ts +694 -0
  254. package/src/schema/record.ts +38 -36
  255. package/src/schema/ref.test.ts +365 -0
  256. package/src/schema/ref.ts +8 -5
  257. package/src/schema/refine.test.ts +578 -0
  258. package/src/schema/refine.ts +85 -0
  259. package/src/schema/regexp.test.ts +580 -0
  260. package/src/schema/regexp.ts +22 -0
  261. package/src/schema/string.test.ts +612 -0
  262. package/src/schema/string.ts +11 -17
  263. package/src/schema/subscription.test.ts +689 -0
  264. package/src/schema/subscription.ts +13 -8
  265. package/src/schema/token.test.ts +428 -0
  266. package/src/schema/token.ts +3 -5
  267. package/src/schema/typed-object.test.ts +612 -0
  268. package/src/schema/typed-object.ts +23 -20
  269. package/src/schema/typed-ref.test.ts +823 -0
  270. package/src/schema/typed-ref.ts +10 -5
  271. package/src/schema/typed-union.test.ts +378 -0
  272. package/src/schema/typed-union.ts +6 -15
  273. package/src/schema/union.test.ts +200 -0
  274. package/src/schema/union.ts +5 -4
  275. package/src/schema/unknown-object.test.ts +592 -0
  276. package/src/schema/unknown-object.ts +3 -5
  277. package/src/schema/unknown.test.ts +312 -0
  278. package/src/schema/unknown.ts +3 -3
  279. package/src/schema.ts +7 -1
  280. package/src/util/array-agg.ts +1 -0
  281. package/src/util/lazy-property.ts +14 -0
  282. package/src/validation/schema.ts +92 -0
  283. package/src/validation/validation-error.ts +60 -9
  284. package/src/validation/validation-issue.ts +141 -144
  285. package/src/validation/validator.ts +67 -206
  286. package/src/validation.ts +1 -0
  287. package/tsconfig.build.json +12 -0
  288. package/tsconfig.json +7 -0
  289. package/tsconfig.tests.json +9 -0
@@ -1,6 +1,7 @@
1
- import { Nsid, RecordKey, Simplify } from '../core.js'
1
+ import { LexiconRecordKey, NsidString, Simplify, TidString } from '../core.js'
2
2
  import {
3
3
  Infer,
4
+ Schema,
4
5
  ValidationResult,
5
6
  Validator,
6
7
  ValidatorContext,
@@ -9,26 +10,24 @@ import { LiteralSchema } from './literal.js'
9
10
  import { StringSchema } from './string.js'
10
11
 
11
12
  export type InferRecordKey<R extends RecordSchema> =
12
- R extends RecordSchema<infer K, any, any, any>
13
- ? RecordKeySchemaOutput<K>
14
- : never
13
+ R extends RecordSchema<infer K> ? RecordKeySchemaOutput<K> : never
15
14
 
16
- export class RecordSchema<
17
- Key extends RecordKey = any,
18
- Type extends Nsid = any,
19
- Schema extends Validator<object> = any,
20
- Output extends Infer<Schema> & { $type: Type } = Infer<Schema> & {
21
- $type: Type
22
- },
23
- > extends Validator<Output> {
24
- readonly lexiconType = 'record' as const
15
+ export type RecordSchemaOutput<
16
+ T extends NsidString,
17
+ S extends Validator<{ [_ in string]?: unknown }>,
18
+ > = Simplify<Omit<Infer<S>, '$type'> & { $type: T }>
25
19
 
26
- keySchema: RecordKeySchema<Key>
20
+ export class RecordSchema<
21
+ K extends LexiconRecordKey = any,
22
+ T extends NsidString = any,
23
+ S extends Validator<{ [_ in string]?: unknown }> = any,
24
+ > extends Schema<RecordSchemaOutput<T, S>> {
25
+ keySchema: RecordKeySchema<K>
27
26
 
28
27
  constructor(
29
- readonly key: Key,
30
- readonly $type: Type,
31
- readonly schema: Schema,
28
+ readonly key: K,
29
+ readonly $type: T,
30
+ readonly schema: S,
32
31
  ) {
33
32
  super()
34
33
  this.keySchema = recordKey(key)
@@ -36,13 +35,13 @@ export class RecordSchema<
36
35
 
37
36
  isTypeOf<X extends { $type?: unknown }>(
38
37
  value: X,
39
- ): value is X extends { $type: Type } ? X : never {
38
+ ): value is X extends { $type: T } ? X : X & { $type: T } {
40
39
  return value.$type === this.$type
41
40
  }
42
41
 
43
- build<X extends Omit<Output, '$type'>>(
42
+ build<X extends Omit<Infer<S>, '$type'>>(
44
43
  input: X,
45
- ): Simplify<Omit<X, '$type'> & { $type: Type }> {
44
+ ): Simplify<Omit<X, '$type'> & { $type: T }> {
46
45
  return { ...input, $type: this.$type }
47
46
  }
48
47
 
@@ -50,15 +49,15 @@ export class RecordSchema<
50
49
  return this.isTypeOf<X>(value)
51
50
  }
52
51
 
53
- $build<X extends Omit<Output, '$type'>>(input: X) {
52
+ $build<X extends Omit<Infer<S>, '$type'>>(input: X) {
54
53
  return this.build<X>(input)
55
54
  }
56
55
 
57
- override validateInContext(
56
+ validateInContext(
58
57
  input: unknown,
59
58
  ctx: ValidatorContext,
60
- ): ValidationResult<Output> {
61
- const result = ctx.validate(input, this.schema) as ValidationResult<Output>
59
+ ): ValidationResult<RecordSchemaOutput<T, S>> {
60
+ const result = ctx.validate(input, this.schema)
62
61
 
63
62
  if (!result.success) {
64
63
  return result
@@ -68,21 +67,22 @@ export class RecordSchema<
68
67
  return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
69
68
  }
70
69
 
71
- return result
70
+ return result as ValidationResult<RecordSchemaOutput<T, S>>
72
71
  }
73
72
  }
74
73
 
75
- export type RecordKeySchemaOutput<Key extends RecordKey> = Key extends 'any'
76
- ? string
77
- : Key extends 'tid'
74
+ export type RecordKeySchemaOutput<Key extends LexiconRecordKey> =
75
+ Key extends 'any'
78
76
  ? string
79
- : Key extends 'nsid'
80
- ? Nsid
81
- : Key extends `literal:${infer L extends string}`
82
- ? L
83
- : never
84
-
85
- export type RecordKeySchema<Key extends RecordKey> = Validator<
77
+ : Key extends 'tid'
78
+ ? TidString
79
+ : Key extends 'nsid'
80
+ ? NsidString
81
+ : Key extends `literal:${infer L extends string}`
82
+ ? L
83
+ : never
84
+
85
+ export type RecordKeySchema<Key extends LexiconRecordKey> = Schema<
86
86
  RecordKeySchemaOutput<Key>
87
87
  >
88
88
 
@@ -91,7 +91,9 @@ const tidSchema = new StringSchema({ format: 'tid' })
91
91
  const nsidSchema = new StringSchema({ format: 'nsid' })
92
92
  const selfLiteralSchema = new LiteralSchema('self')
93
93
 
94
- function recordKey<Key extends RecordKey>(key: Key): RecordKeySchema<Key> {
94
+ function recordKey<Key extends LexiconRecordKey>(
95
+ key: Key,
96
+ ): RecordKeySchema<Key> {
95
97
  // @NOTE Use cached instances for common schemas
96
98
  if (key === 'any') return keySchema as any
97
99
  if (key === 'tid') return tidSchema as any
@@ -0,0 +1,365 @@
1
+ import { Schema } from '../validation.js'
2
+ import { IntegerSchema } from './integer.js'
3
+ import { ObjectSchema } from './object.js'
4
+ import { OptionalSchema } from './optional.js'
5
+ import { RefSchema } from './ref.js'
6
+ import { StringSchema } from './string.js'
7
+
8
+ describe('RefSchema', () => {
9
+ describe('basic validation', () => {
10
+ it('validates through a simple string reference', () => {
11
+ const schema = new RefSchema(() => new StringSchema({}))
12
+ const result = schema.safeParse('hello')
13
+ expect(result.success).toBe(true)
14
+ })
15
+
16
+ it('validates through an integer reference', () => {
17
+ const schema = new RefSchema(() => new IntegerSchema({}))
18
+ const result = schema.safeParse(42)
19
+ expect(result.success).toBe(true)
20
+ })
21
+
22
+ it('rejects invalid input through reference', () => {
23
+ const schema = new RefSchema(() => new StringSchema({}))
24
+ const result = schema.safeParse(123)
25
+ expect(result.success).toBe(false)
26
+ })
27
+
28
+ it('validates null rejection through reference', () => {
29
+ const schema = new RefSchema(() => new StringSchema({}))
30
+ const result = schema.safeParse(null)
31
+ expect(result.success).toBe(false)
32
+ })
33
+
34
+ it('validates undefined rejection through reference', () => {
35
+ const schema = new RefSchema(() => new IntegerSchema({}))
36
+ const result = schema.safeParse(undefined)
37
+ expect(result.success).toBe(false)
38
+ })
39
+ })
40
+
41
+ describe('lazy schema resolution', () => {
42
+ it('does not call getter until first validation', () => {
43
+ let getterCalled = false
44
+ const schema = new RefSchema(() => {
45
+ getterCalled = true
46
+ return new StringSchema({})
47
+ })
48
+ expect(getterCalled).toBe(false)
49
+
50
+ schema.safeParse('test')
51
+ expect(getterCalled).toBe(true)
52
+ })
53
+
54
+ it('caches the resolved schema', () => {
55
+ let callCount = 0
56
+ const schema = new RefSchema(() => {
57
+ callCount++
58
+ return new StringSchema({})
59
+ })
60
+
61
+ schema.safeParse('first')
62
+ schema.safeParse('second')
63
+ schema.safeParse('third')
64
+
65
+ expect(callCount).toBe(1)
66
+ })
67
+
68
+ it('throws error if getter is called multiple times', () => {
69
+ const schema = new RefSchema(() => new StringSchema({}))
70
+
71
+ // Access schema property to resolve it
72
+ schema.schema
73
+
74
+ // Try to access the original getter again (which should throw)
75
+ // This is internal behavior, but we're testing the protection mechanism
76
+ expect(() => {
77
+ // Force access to the cached schema property
78
+ const schemaValue = schema.schema
79
+ // This should work fine as it's now cached
80
+ expect(schemaValue).toBeDefined()
81
+ }).not.toThrow()
82
+ })
83
+ })
84
+
85
+ describe('with object schemas', () => {
86
+ it('validates objects through reference', () => {
87
+ const schema = new RefSchema(
88
+ () =>
89
+ new ObjectSchema({
90
+ name: new StringSchema({}),
91
+ age: new IntegerSchema({}),
92
+ }),
93
+ )
94
+ const result = schema.safeParse({
95
+ name: 'Alice',
96
+ age: 30,
97
+ })
98
+ expect(result.success).toBe(true)
99
+ })
100
+
101
+ it('rejects invalid objects through reference', () => {
102
+ const schema = new RefSchema(
103
+ () =>
104
+ new ObjectSchema({
105
+ name: new StringSchema({}),
106
+ age: new IntegerSchema({}),
107
+ }),
108
+ )
109
+ const result = schema.safeParse({
110
+ name: 'Alice',
111
+ age: 'thirty',
112
+ })
113
+ expect(result.success).toBe(false)
114
+ })
115
+
116
+ it('rejects objects with missing properties through reference', () => {
117
+ const schema = new RefSchema(
118
+ () =>
119
+ new ObjectSchema({
120
+ name: new StringSchema({}),
121
+ age: new IntegerSchema({}),
122
+ }),
123
+ )
124
+ const result = schema.safeParse({
125
+ name: 'Alice',
126
+ })
127
+ expect(result.success).toBe(false)
128
+ })
129
+ })
130
+
131
+ describe('with constrained schemas', () => {
132
+ it('validates string with minLength constraint through reference', () => {
133
+ const schema = new RefSchema(() => new StringSchema({ minLength: 5 }))
134
+ const result = schema.safeParse('hello')
135
+ expect(result.success).toBe(true)
136
+ })
137
+
138
+ it('rejects string violating minLength through reference', () => {
139
+ const schema = new RefSchema(() => new StringSchema({ minLength: 5 }))
140
+ const result = schema.safeParse('hi')
141
+ expect(result.success).toBe(false)
142
+ })
143
+
144
+ it('validates integer with range constraints through reference', () => {
145
+ const schema = new RefSchema(
146
+ () => new IntegerSchema({ minimum: 0, maximum: 100 }),
147
+ )
148
+ const result = schema.safeParse(50)
149
+ expect(result.success).toBe(true)
150
+ })
151
+
152
+ it('rejects integer violating constraints through reference', () => {
153
+ const schema = new RefSchema(
154
+ () => new IntegerSchema({ minimum: 0, maximum: 100 }),
155
+ )
156
+ const result = schema.safeParse(150)
157
+ expect(result.success).toBe(false)
158
+ })
159
+ })
160
+
161
+ describe('circular references', () => {
162
+ it('prevents recursive getter calls', () => {
163
+ // Create a schema that would cause infinite recursion if not protected
164
+ let schema: RefSchema<any>
165
+
166
+ // eslint-disable-next-line prefer-const
167
+ schema = new RefSchema(() => {
168
+ // This would normally cause infinite recursion
169
+ // but the getter protection should prevent it
170
+ return schema.schema
171
+ })
172
+
173
+ // The first access causes stack overflow before the protection can kick in
174
+ expect(() => {
175
+ schema.safeParse('test')
176
+ }).toThrow()
177
+ })
178
+
179
+ it('supports indirect circular references', () => {
180
+ // Create two schemas that reference each other
181
+ // This demonstrates forward references are possible
182
+
183
+ type A = { value: string; ref?: B }
184
+ type B = { value: number; ref?: A }
185
+
186
+ const schemaA: Schema<A> = new ObjectSchema({
187
+ value: new StringSchema({}),
188
+ ref: new OptionalSchema(new RefSchema<B>((() => schemaB) as any)),
189
+ })
190
+
191
+ const schemaB: Schema<B> = new ObjectSchema({
192
+ value: new IntegerSchema({}),
193
+ ref: new OptionalSchema(new RefSchema<A>((() => schemaA) as any)),
194
+ })
195
+
196
+ expect(
197
+ schemaB.matches({
198
+ value: 42,
199
+ ref: {
200
+ value: 'hello',
201
+ ref: {
202
+ value: 3,
203
+ },
204
+ },
205
+ }),
206
+ ).toBe(true)
207
+
208
+ expect(
209
+ schemaA.matches({
210
+ value: 'hello',
211
+ ref: {
212
+ value: 3,
213
+ ref: {
214
+ value: 'world',
215
+ },
216
+ },
217
+ }),
218
+ ).toBe(true)
219
+ })
220
+ })
221
+
222
+ describe('multiple validations', () => {
223
+ it('validates multiple inputs correctly', () => {
224
+ const schema = new RefSchema(() => new StringSchema({ minLength: 3 }))
225
+
226
+ const result1 = schema.safeParse('hello')
227
+ expect(result1.success).toBe(true)
228
+
229
+ const result2 = schema.safeParse('hi')
230
+ expect(result2.success).toBe(false)
231
+
232
+ const result3 = schema.safeParse('world')
233
+ expect(result3.success).toBe(true)
234
+
235
+ const result4 = schema.safeParse('no')
236
+ expect(result4.success).toBe(false)
237
+ })
238
+
239
+ it('handles different types of validation failures', () => {
240
+ const schema = new RefSchema(
241
+ () =>
242
+ new ObjectSchema({
243
+ name: new StringSchema({ minLength: 2 }),
244
+ age: new IntegerSchema({ minimum: 0 }),
245
+ }),
246
+ )
247
+
248
+ const result1 = schema.safeParse({ name: 'A', age: 25 })
249
+ expect(result1.success).toBe(false)
250
+
251
+ const result2 = schema.safeParse({ name: 'Alice', age: -5 })
252
+ expect(result2.success).toBe(false)
253
+
254
+ const result3 = schema.safeParse({ name: 'Alice', age: 25 })
255
+ expect(result3.success).toBe(true)
256
+ })
257
+ })
258
+
259
+ describe('edge cases', () => {
260
+ it('handles empty string validation', () => {
261
+ const schema = new RefSchema(() => new StringSchema({}))
262
+ const result = schema.safeParse('')
263
+ expect(result.success).toBe(true)
264
+ })
265
+
266
+ it('handles zero validation', () => {
267
+ const schema = new RefSchema(() => new IntegerSchema({}))
268
+ const result = schema.safeParse(0)
269
+ expect(result.success).toBe(true)
270
+ })
271
+
272
+ it('rejects NaN through integer reference', () => {
273
+ const schema = new RefSchema(() => new IntegerSchema({}))
274
+ const result = schema.safeParse(NaN)
275
+ expect(result.success).toBe(false)
276
+ })
277
+
278
+ it('rejects Infinity through integer reference', () => {
279
+ const schema = new RefSchema(() => new IntegerSchema({}))
280
+ const result = schema.safeParse(Infinity)
281
+ expect(result.success).toBe(false)
282
+ })
283
+
284
+ it('rejects arrays when expecting string', () => {
285
+ const schema = new RefSchema(() => new StringSchema({}))
286
+ const result = schema.safeParse(['array'])
287
+ expect(result.success).toBe(false)
288
+ })
289
+
290
+ it('rejects objects when expecting string', () => {
291
+ const schema = new RefSchema(() => new StringSchema({}))
292
+ const result = schema.safeParse({ key: 'value' })
293
+ expect(result.success).toBe(false)
294
+ })
295
+
296
+ it('rejects booleans when expecting string', () => {
297
+ const schema = new RefSchema(() => new StringSchema({}))
298
+ const result = schema.safeParse(true)
299
+ expect(result.success).toBe(false)
300
+ })
301
+ })
302
+
303
+ describe('nested references', () => {
304
+ it('validates through nested RefSchema', () => {
305
+ const innerRef = new RefSchema(() => new StringSchema({ minLength: 3 }))
306
+ const outerRef = new RefSchema(() => innerRef)
307
+
308
+ const result = outerRef.safeParse('hello')
309
+ expect(result.success).toBe(true)
310
+ })
311
+
312
+ it('rejects invalid input through nested RefSchema', () => {
313
+ const innerRef = new RefSchema(() => new StringSchema({ minLength: 3 }))
314
+ const outerRef = new RefSchema(() => innerRef)
315
+
316
+ const result = outerRef.safeParse('hi')
317
+ expect(result.success).toBe(false)
318
+ })
319
+
320
+ it('validates with deeply nested references', () => {
321
+ const level3 = new RefSchema(() => new IntegerSchema({ minimum: 0 }))
322
+ const level2 = new RefSchema(() => level3)
323
+ const level1 = new RefSchema(() => level2)
324
+
325
+ const result = level1.safeParse(42)
326
+ expect(result.success).toBe(true)
327
+ })
328
+ })
329
+
330
+ describe('schema property access', () => {
331
+ it('allows direct access to resolved schema', () => {
332
+ const innerSchema = new StringSchema({ minLength: 5 })
333
+ const refSchema = new RefSchema(() => innerSchema)
334
+
335
+ const resolved = refSchema.schema
336
+ expect(resolved).toBe(innerSchema)
337
+ })
338
+
339
+ it('returns same instance on multiple schema property accesses', () => {
340
+ const innerSchema = new StringSchema({})
341
+ const refSchema = new RefSchema(() => innerSchema)
342
+
343
+ const first = refSchema.schema
344
+ const second = refSchema.schema
345
+ const third = refSchema.schema
346
+
347
+ expect(first).toBe(second)
348
+ expect(second).toBe(third)
349
+ })
350
+
351
+ it('resolves schema before validation', () => {
352
+ let resolved = false
353
+ const refSchema = new RefSchema(() => {
354
+ resolved = true
355
+ return new StringSchema({})
356
+ })
357
+
358
+ expect(resolved).toBe(false)
359
+
360
+ const schemaValue = refSchema.schema
361
+ expect(resolved).toBe(true)
362
+ expect(schemaValue).toBeDefined()
363
+ })
364
+ })
365
+ })
package/src/schema/ref.ts CHANGED
@@ -1,10 +1,13 @@
1
- import { ValidationResult, Validator, ValidatorContext } from '../validation.js'
1
+ import {
2
+ Schema,
3
+ ValidationResult,
4
+ Validator,
5
+ ValidatorContext,
6
+ } from '../validation.js'
2
7
 
3
8
  export type RefSchemaGetter<V> = () => Validator<V>
4
9
 
5
- export class RefSchema<V = any> extends Validator<V> {
6
- readonly lexiconType = 'ref' as const
7
-
10
+ export class RefSchema<V = any> extends Schema<V> {
8
11
  #getter: RefSchemaGetter<V>
9
12
 
10
13
  constructor(getter: RefSchemaGetter<V>) {
@@ -34,7 +37,7 @@ export class RefSchema<V = any> extends Validator<V> {
34
37
  return value
35
38
  }
36
39
 
37
- override validateInContext(
40
+ validateInContext(
38
41
  input: unknown,
39
42
  ctx: ValidatorContext,
40
43
  ): ValidationResult<V> {