@atproto/lex-schema 0.1.5 → 0.1.6

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 (263) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/core/$type.d.ts +2 -2
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js.map +1 -1
  5. package/dist/core/record-key.d.ts +1 -1
  6. package/dist/core/record-key.d.ts.map +1 -1
  7. package/dist/core/record-key.js.map +1 -1
  8. package/dist/core/schema.d.ts +3 -2
  9. package/dist/core/schema.d.ts.map +1 -1
  10. package/dist/core/schema.js +1 -1
  11. package/dist/core/schema.js.map +1 -1
  12. package/dist/core/standard-schema.d.ts +2 -2
  13. package/dist/core/standard-schema.d.ts.map +1 -1
  14. package/dist/core/standard-schema.js.map +1 -1
  15. package/dist/core/string-format.d.ts +2 -2
  16. package/dist/core/string-format.d.ts.map +1 -1
  17. package/dist/core/string-format.js.map +1 -1
  18. package/dist/core/validation-error.d.ts +1 -1
  19. package/dist/core/validation-error.d.ts.map +1 -1
  20. package/dist/core/validation-error.js +1 -1
  21. package/dist/core/validation-error.js.map +1 -1
  22. package/dist/core/validator.d.ts +1 -1
  23. package/dist/core/validator.d.ts.map +1 -1
  24. package/dist/core/validator.js +1 -1
  25. package/dist/core/validator.js.map +1 -1
  26. package/dist/helpers.d.ts +2 -2
  27. package/dist/helpers.d.ts.map +1 -1
  28. package/dist/helpers.js +2 -2
  29. package/dist/helpers.js.map +1 -1
  30. package/dist/schema/array.d.ts +1 -1
  31. package/dist/schema/array.d.ts.map +1 -1
  32. package/dist/schema/array.js +1 -1
  33. package/dist/schema/array.js.map +1 -1
  34. package/dist/schema/blob.d.ts +1 -1
  35. package/dist/schema/blob.d.ts.map +1 -1
  36. package/dist/schema/blob.js +2 -2
  37. package/dist/schema/blob.js.map +1 -1
  38. package/dist/schema/boolean.js +1 -1
  39. package/dist/schema/boolean.js.map +1 -1
  40. package/dist/schema/bytes.js +1 -1
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +1 -1
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +3 -3
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.js +1 -1
  47. package/dist/schema/custom.js.map +1 -1
  48. package/dist/schema/dict.d.ts +1 -1
  49. package/dist/schema/dict.d.ts.map +1 -1
  50. package/dist/schema/dict.js +1 -1
  51. package/dist/schema/dict.js.map +1 -1
  52. package/dist/schema/discriminated-union.d.ts +1 -1
  53. package/dist/schema/discriminated-union.d.ts.map +1 -1
  54. package/dist/schema/discriminated-union.js +2 -1
  55. package/dist/schema/discriminated-union.js.map +1 -1
  56. package/dist/schema/enum.js +1 -1
  57. package/dist/schema/enum.js.map +1 -1
  58. package/dist/schema/integer.js +1 -1
  59. package/dist/schema/integer.js.map +1 -1
  60. package/dist/schema/intersection.d.ts +1 -1
  61. package/dist/schema/intersection.d.ts.map +1 -1
  62. package/dist/schema/intersection.js +3 -1
  63. package/dist/schema/intersection.js.map +1 -1
  64. package/dist/schema/lex-map.d.ts +1 -1
  65. package/dist/schema/lex-map.d.ts.map +1 -1
  66. package/dist/schema/lex-map.js +1 -1
  67. package/dist/schema/lex-map.js.map +1 -1
  68. package/dist/schema/lex-value.d.ts +1 -1
  69. package/dist/schema/lex-value.d.ts.map +1 -1
  70. package/dist/schema/lex-value.js +1 -1
  71. package/dist/schema/lex-value.js.map +1 -1
  72. package/dist/schema/literal.js +1 -1
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.js +1 -1
  75. package/dist/schema/never.js.map +1 -1
  76. package/dist/schema/null.js +1 -1
  77. package/dist/schema/null.js.map +1 -1
  78. package/dist/schema/nullable.d.ts +1 -1
  79. package/dist/schema/nullable.d.ts.map +1 -1
  80. package/dist/schema/nullable.js +1 -1
  81. package/dist/schema/nullable.js.map +1 -1
  82. package/dist/schema/object.d.ts +2 -1
  83. package/dist/schema/object.d.ts.map +1 -1
  84. package/dist/schema/object.js +1 -1
  85. package/dist/schema/object.js.map +1 -1
  86. package/dist/schema/optional.d.ts +2 -1
  87. package/dist/schema/optional.d.ts.map +1 -1
  88. package/dist/schema/optional.js +2 -1
  89. package/dist/schema/optional.js.map +1 -1
  90. package/dist/schema/params.d.ts +1 -1
  91. package/dist/schema/params.d.ts.map +1 -1
  92. package/dist/schema/params.js +1 -1
  93. package/dist/schema/params.js.map +1 -1
  94. package/dist/schema/payload.d.ts +3 -2
  95. package/dist/schema/payload.d.ts.map +1 -1
  96. package/dist/schema/payload.js +2 -1
  97. package/dist/schema/payload.js.map +1 -1
  98. package/dist/schema/permission-set.d.ts +1 -1
  99. package/dist/schema/permission-set.d.ts.map +1 -1
  100. package/dist/schema/permission-set.js +1 -0
  101. package/dist/schema/permission-set.js.map +1 -1
  102. package/dist/schema/permission.d.ts +1 -1
  103. package/dist/schema/permission.d.ts.map +1 -1
  104. package/dist/schema/permission.js.map +1 -1
  105. package/dist/schema/procedure.d.ts +1 -1
  106. package/dist/schema/procedure.d.ts.map +1 -1
  107. package/dist/schema/procedure.js +2 -0
  108. package/dist/schema/procedure.js.map +1 -1
  109. package/dist/schema/query.d.ts +1 -1
  110. package/dist/schema/query.d.ts.map +1 -1
  111. package/dist/schema/query.js +2 -0
  112. package/dist/schema/query.js.map +1 -1
  113. package/dist/schema/record.d.ts +2 -2
  114. package/dist/schema/record.d.ts.map +1 -1
  115. package/dist/schema/record.js +1 -1
  116. package/dist/schema/record.js.map +1 -1
  117. package/dist/schema/ref.d.ts +1 -1
  118. package/dist/schema/ref.d.ts.map +1 -1
  119. package/dist/schema/ref.js +1 -1
  120. package/dist/schema/ref.js.map +1 -1
  121. package/dist/schema/refine.d.ts +2 -2
  122. package/dist/schema/refine.d.ts.map +1 -1
  123. package/dist/schema/refine.js +1 -1
  124. package/dist/schema/refine.js.map +1 -1
  125. package/dist/schema/regexp.js +1 -1
  126. package/dist/schema/regexp.js.map +1 -1
  127. package/dist/schema/string.d.ts +2 -2
  128. package/dist/schema/string.d.ts.map +1 -1
  129. package/dist/schema/string.js +1 -1
  130. package/dist/schema/string.js.map +1 -1
  131. package/dist/schema/subscription.d.ts +3 -2
  132. package/dist/schema/subscription.d.ts.map +1 -1
  133. package/dist/schema/subscription.js +2 -0
  134. package/dist/schema/subscription.js.map +1 -1
  135. package/dist/schema/token.d.ts +1 -1
  136. package/dist/schema/token.d.ts.map +1 -1
  137. package/dist/schema/token.js +1 -1
  138. package/dist/schema/token.js.map +1 -1
  139. package/dist/schema/typed-object.d.ts +2 -2
  140. package/dist/schema/typed-object.d.ts.map +1 -1
  141. package/dist/schema/typed-object.js +1 -1
  142. package/dist/schema/typed-object.js.map +1 -1
  143. package/dist/schema/typed-ref.d.ts +1 -1
  144. package/dist/schema/typed-ref.d.ts.map +1 -1
  145. package/dist/schema/typed-ref.js +1 -1
  146. package/dist/schema/typed-ref.js.map +1 -1
  147. package/dist/schema/typed-union.d.ts +1 -1
  148. package/dist/schema/typed-union.d.ts.map +1 -1
  149. package/dist/schema/typed-union.js +3 -1
  150. package/dist/schema/typed-union.js.map +1 -1
  151. package/dist/schema/union.d.ts +1 -1
  152. package/dist/schema/union.d.ts.map +1 -1
  153. package/dist/schema/union.js +1 -1
  154. package/dist/schema/union.js.map +1 -1
  155. package/dist/schema/unknown.js +1 -1
  156. package/dist/schema/unknown.js.map +1 -1
  157. package/dist/schema/with-default.d.ts +1 -1
  158. package/dist/schema/with-default.d.ts.map +1 -1
  159. package/dist/schema/with-default.js +1 -1
  160. package/dist/schema/with-default.js.map +1 -1
  161. package/package.json +6 -10
  162. package/src/core/$type.test.ts +0 -24
  163. package/src/core/$type.ts +0 -199
  164. package/src/core/record-key.ts +0 -85
  165. package/src/core/result.ts +0 -15
  166. package/src/core/schema.ts +0 -412
  167. package/src/core/standard-schema.test.ts +0 -124
  168. package/src/core/standard-schema.ts +0 -31
  169. package/src/core/string-format.ts +0 -411
  170. package/src/core/types.ts +0 -120
  171. package/src/core/validation-error.ts +0 -134
  172. package/src/core/validation-issue.ts +0 -340
  173. package/src/core/validator.ts +0 -636
  174. package/src/core.ts +0 -9
  175. package/src/external.ts +0 -3
  176. package/src/helpers.test.ts +0 -694
  177. package/src/helpers.ts +0 -222
  178. package/src/index.ts +0 -3
  179. package/src/schema/array.test.ts +0 -251
  180. package/src/schema/array.ts +0 -126
  181. package/src/schema/blob.test.ts +0 -733
  182. package/src/schema/blob.ts +0 -150
  183. package/src/schema/boolean.test.ts +0 -118
  184. package/src/schema/boolean.ts +0 -46
  185. package/src/schema/bytes.test.ts +0 -227
  186. package/src/schema/bytes.ts +0 -81
  187. package/src/schema/cid.test.ts +0 -125
  188. package/src/schema/cid.ts +0 -69
  189. package/src/schema/custom.test.ts +0 -414
  190. package/src/schema/custom.ts +0 -106
  191. package/src/schema/dict.test.ts +0 -181
  192. package/src/schema/dict.ts +0 -122
  193. package/src/schema/discriminated-union.test.ts +0 -676
  194. package/src/schema/discriminated-union.ts +0 -196
  195. package/src/schema/enum.test.ts +0 -398
  196. package/src/schema/enum.ts +0 -77
  197. package/src/schema/integer.test.ts +0 -314
  198. package/src/schema/integer.ts +0 -86
  199. package/src/schema/intersection.test.ts +0 -33
  200. package/src/schema/intersection.ts +0 -113
  201. package/src/schema/lex-map.test.ts +0 -593
  202. package/src/schema/lex-map.ts +0 -63
  203. package/src/schema/lex-value.test.ts +0 -81
  204. package/src/schema/lex-value.ts +0 -86
  205. package/src/schema/literal.test.ts +0 -533
  206. package/src/schema/literal.ts +0 -70
  207. package/src/schema/never.test.ts +0 -175
  208. package/src/schema/never.ts +0 -56
  209. package/src/schema/null.test.ts +0 -80
  210. package/src/schema/null.ts +0 -49
  211. package/src/schema/nullable.test.ts +0 -470
  212. package/src/schema/nullable.ts +0 -74
  213. package/src/schema/object.test.ts +0 -69
  214. package/src/schema/object.ts +0 -136
  215. package/src/schema/optional.test.ts +0 -479
  216. package/src/schema/optional.ts +0 -92
  217. package/src/schema/params.test.ts +0 -1118
  218. package/src/schema/params.ts +0 -371
  219. package/src/schema/payload.test.ts +0 -340
  220. package/src/schema/payload.ts +0 -204
  221. package/src/schema/permission-set.test.ts +0 -613
  222. package/src/schema/permission-set.ts +0 -86
  223. package/src/schema/permission.test.ts +0 -537
  224. package/src/schema/permission.ts +0 -63
  225. package/src/schema/procedure.test.ts +0 -324
  226. package/src/schema/procedure.ts +0 -98
  227. package/src/schema/query.test.ts +0 -348
  228. package/src/schema/query.ts +0 -86
  229. package/src/schema/record.test.ts +0 -812
  230. package/src/schema/record.ts +0 -217
  231. package/src/schema/ref.test.ts +0 -349
  232. package/src/schema/ref.ts +0 -103
  233. package/src/schema/refine.test.ts +0 -579
  234. package/src/schema/refine.ts +0 -153
  235. package/src/schema/regexp.test.ts +0 -577
  236. package/src/schema/regexp.ts +0 -82
  237. package/src/schema/string.test.ts +0 -773
  238. package/src/schema/string.ts +0 -229
  239. package/src/schema/subscription.test.ts +0 -499
  240. package/src/schema/subscription.ts +0 -108
  241. package/src/schema/token.test.ts +0 -152
  242. package/src/schema/token.ts +0 -103
  243. package/src/schema/typed-object.test.ts +0 -745
  244. package/src/schema/typed-object.ts +0 -181
  245. package/src/schema/typed-ref.test.ts +0 -796
  246. package/src/schema/typed-ref.ts +0 -126
  247. package/src/schema/typed-union.test.ts +0 -355
  248. package/src/schema/typed-union.ts +0 -130
  249. package/src/schema/union.test.ts +0 -191
  250. package/src/schema/union.ts +0 -89
  251. package/src/schema/unknown.test.ts +0 -313
  252. package/src/schema/unknown.ts +0 -47
  253. package/src/schema/with-default.ts +0 -81
  254. package/src/schema.ts +0 -43
  255. package/src/util/array-agg.test.ts +0 -42
  256. package/src/util/array-agg.ts +0 -44
  257. package/src/util/assertion-util.ts +0 -1
  258. package/src/util/if-any.ts +0 -3
  259. package/src/util/lazy-property.ts +0 -14
  260. package/src/util/memoize.ts +0 -37
  261. package/tsconfig.build.json +0 -12
  262. package/tsconfig.json +0 -7
  263. package/tsconfig.tests.json +0 -8
@@ -1,217 +0,0 @@
1
- import { LexMap } from '@atproto/lex-data'
2
- import {
3
- $Typed,
4
- $typed,
5
- InferInput,
6
- InferOutput,
7
- LexiconRecordKey,
8
- NsidString,
9
- RecordKeyValue,
10
- Schema,
11
- Unknown$TypedObject,
12
- ValidationContext,
13
- Validator,
14
- } from '../core.js'
15
- import { lazyProperty } from '../util/lazy-property.js'
16
- import { literal } from './literal.js'
17
- import { string } from './string.js'
18
- import { withDefault } from './with-default.js'
19
-
20
- /**
21
- * Infers the record key type from a RecordSchema.
22
- *
23
- * @template R - The RecordSchema type
24
- */
25
- export type InferRecordKey<R extends RecordSchema> =
26
- R extends RecordSchema<infer TKey> ? RecordKeyValue<TKey> : never
27
-
28
- export type TypedRecord<
29
- TType extends NsidString,
30
- TValue extends { $type?: unknown } = { $type?: unknown },
31
- > = TValue extends { $type: TType }
32
- ? TValue
33
- : $Typed<Exclude<TValue, Unknown$TypedObject>, TType>
34
-
35
- /**
36
- * Schema for AT Protocol records with a type identifier and key constraints.
37
- *
38
- * Records are the primary data unit in AT Protocol. Each record has a `$type`
39
- * field identifying its Lexicon schema, and is stored at a specific key
40
- * (TID, NSID, or other format) in a repository.
41
- *
42
- * @template TKey - The record key type ('tid', 'nsid', 'any', or 'literal:...')
43
- * @template TType - The NSID string identifying this record type
44
- * @template TShape - The validator type for the record's data shape
45
- *
46
- * @example
47
- * ```ts
48
- * const postSchema = new RecordSchema(
49
- * 'tid',
50
- * 'app.bsky.feed.post',
51
- * l.object({ text: l.string(), createdAt: l.string() })
52
- * )
53
- * ```
54
- */
55
- export class RecordSchema<
56
- const TKey extends LexiconRecordKey = LexiconRecordKey,
57
- const TType extends NsidString = NsidString,
58
- const TShape extends Validator<LexMap> = Validator<LexMap>,
59
- > extends Schema<
60
- $Typed<InferInput<TShape>, TType>,
61
- $Typed<InferOutput<TShape>, TType>
62
- > {
63
- readonly type = 'record' as const
64
-
65
- keySchema: RecordKeySchema<TKey>
66
-
67
- constructor(
68
- readonly key: TKey,
69
- readonly $type: TType,
70
- readonly schema: TShape,
71
- ) {
72
- super()
73
- this.keySchema = recordKey(key)
74
- }
75
-
76
- validateInContext(input: unknown, ctx: ValidationContext) {
77
- const result = ctx.validate(input, this.schema)
78
-
79
- if (!result.success) {
80
- return result
81
- }
82
-
83
- if (result.value.$type !== this.$type) {
84
- return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
85
- }
86
-
87
- return result
88
- }
89
-
90
- build(
91
- input: Omit<InferOutput<TShape>, '$type'>,
92
- ): $Typed<InferOutput<TShape>, TType>
93
- build(
94
- input: Omit<InferInput<TShape>, '$type'>,
95
- ): $Typed<InferInput<TShape>, TType>
96
- build(input: Record<string, unknown>) {
97
- return $typed(input, this.$type)
98
- }
99
-
100
- isTypeOf<TValue extends { $type?: unknown }>(
101
- value: TValue,
102
- ): value is TypedRecord<TType, TValue> {
103
- return value.$type === this.$type
104
- }
105
-
106
- /**
107
- * Bound alias for {@link build} for compatibility with generated utilities.
108
- * @see {@link build}
109
- */
110
- get $build(): typeof this.build {
111
- return lazyProperty(this, '$build', this.build.bind(this))
112
- }
113
-
114
- /**
115
- * Bound alias for {@link isTypeOf} for compatibility with generated utilities.
116
- * @see {@link isTypeOf}
117
- */
118
- get $isTypeOf(): typeof this.isTypeOf {
119
- return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
120
- }
121
- }
122
-
123
- export type RecordKeySchemaOutput<Key extends LexiconRecordKey> =
124
- RecordKeyValue<Key>
125
-
126
- export type RecordKeySchema<Key extends LexiconRecordKey> = Schema<
127
- RecordKeyValue<Key>
128
- >
129
-
130
- const keySchema = string({ format: 'record-key' })
131
- const tidSchema = string({ format: 'tid' })
132
- const nsidSchema = string({ format: 'nsid' })
133
- const selfLiteralSchema = withDefault(literal('self'), 'self')
134
-
135
- function recordKey<Key extends LexiconRecordKey>(
136
- key: Key,
137
- ): RecordKeySchema<Key> {
138
- // @NOTE Use cached instances for common schemas
139
- if (key === 'any') return keySchema as any
140
- if (key === 'tid') return tidSchema as any
141
- if (key === 'nsid') return nsidSchema as any
142
- if (key.startsWith('literal:')) {
143
- const value = key.slice(8) as RecordKeyValue<Key>
144
- if (value === 'self') return selfLiteralSchema as any
145
- return withDefault(literal(value), value)
146
- }
147
-
148
- throw new Error(`Unsupported record key type: ${key}`)
149
- }
150
-
151
- /**
152
- * Ensures that a `$type` used in a record is a valid NSID (i.e. no fragment).
153
- */
154
- type AsNsid<T> = T extends `${string}#${string}` ? never : T
155
-
156
- /**
157
- * Creates a record schema for AT Protocol records.
158
- *
159
- * Records are the primary data unit in AT Protocol repositories. They have
160
- * a `$type` field identifying their Lexicon schema, and are stored at keys
161
- * following a specific format (TID, NSID, etc.).
162
- *
163
- * This function offers two overloads:
164
- * - One that infers the output type from the provided arguments (does not
165
- * support circular references)
166
- * - One with an explicitly defined interface for use with codegen and
167
- * circular references
168
- *
169
- * @param key - The record key type: 'tid', 'nsid', 'any', or 'literal:value'
170
- * @param type - The NSID identifying this record type (e.g., 'app.bsky.feed.post')
171
- * @param validator - Schema validator for the record's properties
172
- * @returns A new {@link RecordSchema} instance
173
- *
174
- * @example
175
- * ```ts
176
- * // Post record with TID key
177
- * const postSchema = l.record('tid', 'app.bsky.feed.post', l.object({
178
- * text: l.string({ maxGraphemes: 300 }),
179
- * createdAt: l.string({ format: 'datetime' }),
180
- * reply: l.optional(l.object({
181
- * root: l.ref(() => strongRefSchema),
182
- * parent: l.ref(() => strongRefSchema),
183
- * })),
184
- * }))
185
- *
186
- * // Profile record with literal 'self' key
187
- * const profileSchema = l.record('literal:self', 'app.bsky.actor.profile', l.object({
188
- * displayName: l.optional(l.string({ maxGraphemes: 64 })),
189
- * description: l.optional(l.string({ maxGraphemes: 256 })),
190
- * avatar: l.optional(l.blob({ accept: ['image/*'] })),
191
- * }))
192
- *
193
- * // Build a record with automatic $type injection
194
- * const post = postSchema.build({ text: 'Hello!', createdAt: new Date().toISOString() })
195
- * ```
196
- */
197
- export function record<
198
- const K extends LexiconRecordKey,
199
- const T extends NsidString,
200
- const S extends Validator<LexMap>,
201
- >(key: K, type: AsNsid<T>, validator: S): RecordSchema<K, T, S>
202
- export function record<
203
- const K extends LexiconRecordKey,
204
- const V extends LexMap & { $type: NsidString },
205
- >(
206
- key: K,
207
- type: AsNsid<V['$type']>,
208
- validator: Validator<Omit<V, '$type'>>,
209
- ): RecordSchema<K, V['$type'], Validator<Omit<V, '$type'>>>
210
- /*@__NO_SIDE_EFFECTS__*/
211
- export function record<
212
- const K extends LexiconRecordKey,
213
- const T extends NsidString,
214
- const S extends Validator<LexMap>,
215
- >(key: K, type: T, validator: S) {
216
- return new RecordSchema<K, T, S>(key, type, validator)
217
- }
@@ -1,349 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { Schema, Validator } from '../core.js'
3
- import { integer } from './integer.js'
4
- import { object } from './object.js'
5
- import { optional } from './optional.js'
6
- import { ref } from './ref.js'
7
- import { string } from './string.js'
8
-
9
- describe('RefSchema', () => {
10
- describe('basic validation', () => {
11
- it('validates through a simple string reference', () => {
12
- const schema = ref(() => innerSchema)
13
- const innerSchema = string()
14
- const result = schema.safeParse('hello')
15
- expect(result.success).toBe(true)
16
- })
17
-
18
- it('validates through an integer reference', () => {
19
- const schema = ref(() => innerSchema)
20
- const innerSchema = integer()
21
- const result = schema.safeParse(42)
22
- expect(result.success).toBe(true)
23
- })
24
-
25
- it('rejects invalid input through reference', () => {
26
- const schema = ref(() => innerSchema)
27
- const innerSchema = string()
28
- const result = schema.safeParse(123)
29
- expect(result.success).toBe(false)
30
- })
31
-
32
- it('validates null rejection through reference', () => {
33
- const schema = ref(() => innerSchema)
34
- const innerSchema = string()
35
- const result = schema.safeParse(null)
36
- expect(result.success).toBe(false)
37
- })
38
-
39
- it('validates undefined rejection through reference', () => {
40
- const schema = ref(() => innerSchema)
41
- const innerSchema = integer()
42
- const result = schema.safeParse(undefined)
43
- expect(result.success).toBe(false)
44
- })
45
- })
46
-
47
- describe('lazy schema resolution', () => {
48
- it('does not call getter until first validation', () => {
49
- let getterCalled = false
50
- const schema = ref(() => {
51
- getterCalled = true
52
- return string()
53
- })
54
- expect(getterCalled).toBe(false)
55
-
56
- schema.safeParse('test')
57
- expect(getterCalled).toBe(true)
58
- })
59
-
60
- it('throws error if getter is called multiple times', () => {
61
- const schema = ref(() => innerSchema)
62
- const innerSchema = string()
63
-
64
- // Access schema property to resolve it
65
- void schema.validator
66
-
67
- // Try to access the original getter again (which should throw)
68
- // This is internal behavior, but we're testing the protection mechanism
69
- expect(() => {
70
- // Force access to the cached schema property
71
- const schemaValue = schema.validator
72
- // This should work fine as it's now cached
73
- expect(schemaValue).toBeDefined()
74
- }).not.toThrow()
75
- })
76
- })
77
-
78
- describe('with object schemas', () => {
79
- it('validates objects through reference', () => {
80
- const schema = ref(() => innerSchema)
81
- const innerSchema = object({
82
- name: string(),
83
- age: integer(),
84
- })
85
-
86
- expect(
87
- schema.safeValidate({
88
- name: 'Alice',
89
- age: 30,
90
- }),
91
- ).toMatchObject({
92
- success: true,
93
- })
94
- })
95
-
96
- it('rejects invalid objects through reference', () => {
97
- const schema = ref(() => innerSchema)
98
- const innerSchema = object({
99
- name: string(),
100
- age: integer(),
101
- })
102
- const result = schema.safeParse({
103
- name: 'Alice',
104
- age: 'thirty',
105
- })
106
- expect(result.success).toBe(false)
107
- })
108
-
109
- it('rejects objects with missing properties through reference', () => {
110
- const schema = ref(() => innerSchema)
111
- const innerSchema = object({
112
- name: string(),
113
- age: integer(),
114
- })
115
- const result = schema.safeParse({
116
- name: 'Alice',
117
- })
118
- expect(result.success).toBe(false)
119
- })
120
- })
121
-
122
- describe('with constrained schemas', () => {
123
- it('validates string with minLength constraint through reference', () => {
124
- const schema = ref(() => innerSchema)
125
- const innerSchema = string({ minLength: 5 })
126
- const result = schema.safeParse('hello')
127
- expect(result.success).toBe(true)
128
- })
129
-
130
- it('rejects string violating minLength through reference', () => {
131
- const schema = ref(() => innerSchema)
132
- const innerSchema = string({ minLength: 5 })
133
- const result = schema.safeParse('hi')
134
- expect(result.success).toBe(false)
135
- })
136
-
137
- it('validates integer with range constraints through reference', () => {
138
- const schema = ref(() => innerSchema)
139
- const innerSchema = integer({ minimum: 0, maximum: 100 })
140
- const result = schema.safeParse(50)
141
- expect(result.success).toBe(true)
142
- })
143
-
144
- it('rejects integer violating constraints through reference', () => {
145
- const schema = ref(() => innerSchema)
146
- const innerSchema = integer({ minimum: 0, maximum: 100 })
147
- const result = schema.safeParse(150)
148
- expect(result.success).toBe(false)
149
- })
150
- })
151
-
152
- describe('circular references', () => {
153
- it('supports indirect circular references', () => {
154
- // Create two schemas that reference each other
155
- // This demonstrates forward references are possible
156
-
157
- type A = { value: string; ref?: B }
158
- type B = { value: number; ref?: A }
159
-
160
- const schemaA: Schema<A> = object({
161
- value: string(),
162
- ref: optional(ref<Validator<B>>((() => schemaB) as any)),
163
- })
164
-
165
- const schemaB: Schema<B> = object({
166
- value: integer(),
167
- ref: optional(ref<Validator<A>>((() => schemaA) as any)),
168
- })
169
-
170
- expect(
171
- schemaB.matches({
172
- value: 42,
173
- ref: {
174
- value: 'hello',
175
- ref: {
176
- value: 3,
177
- },
178
- },
179
- }),
180
- ).toBe(true)
181
-
182
- expect(
183
- schemaA.matches({
184
- value: 'hello',
185
- ref: {
186
- value: 3,
187
- ref: {
188
- value: 'world',
189
- },
190
- },
191
- }),
192
- ).toBe(true)
193
- })
194
- })
195
-
196
- describe('multiple validations', () => {
197
- it('validates multiple inputs correctly', () => {
198
- const schema = ref(() => innerSchema)
199
- const innerSchema = string({ minLength: 3 })
200
-
201
- const result1 = schema.safeParse('hello')
202
- expect(result1.success).toBe(true)
203
-
204
- const result2 = schema.safeParse('hi')
205
- expect(result2.success).toBe(false)
206
-
207
- const result3 = schema.safeParse('world')
208
- expect(result3.success).toBe(true)
209
-
210
- const result4 = schema.safeParse('no')
211
- expect(result4.success).toBe(false)
212
- })
213
-
214
- it('handles different types of validation failures', () => {
215
- const schema = ref(() =>
216
- object({
217
- name: string({ minLength: 2 }),
218
- age: integer({ minimum: 0 }),
219
- }),
220
- )
221
-
222
- const result1 = schema.safeParse({ name: 'A', age: 25 })
223
- expect(result1.success).toBe(false)
224
-
225
- const result2 = schema.safeParse({ name: 'Alice', age: -5 })
226
- expect(result2.success).toBe(false)
227
-
228
- const result3 = schema.safeParse({ name: 'Alice', age: 25 })
229
- expect(result3.success).toBe(true)
230
- })
231
- })
232
-
233
- describe('edge cases', () => {
234
- it('handles empty string validation', () => {
235
- const schema = ref(() => innerSchema)
236
- const innerSchema = string()
237
- const result = schema.safeParse('')
238
- expect(result.success).toBe(true)
239
- })
240
-
241
- it('handles zero validation', () => {
242
- const schema = ref(() => innerSchema)
243
- const innerSchema = integer()
244
- const result = schema.safeParse(0)
245
- expect(result.success).toBe(true)
246
- })
247
-
248
- it('rejects NaN through integer reference', () => {
249
- const schema = ref(() => innerSchema)
250
- const innerSchema = integer()
251
- const result = schema.safeParse(NaN)
252
- expect(result.success).toBe(false)
253
- })
254
-
255
- it('rejects Infinity through integer reference', () => {
256
- const schema = ref(() => innerSchema)
257
- const innerSchema = integer()
258
- const result = schema.safeParse(Infinity)
259
- expect(result.success).toBe(false)
260
- })
261
-
262
- it('rejects arrays when expecting string', () => {
263
- const schema = ref(() => innerSchema)
264
- const innerSchema = string()
265
- const result = schema.safeParse(['array'])
266
- expect(result.success).toBe(false)
267
- })
268
-
269
- it('rejects objects when expecting string', () => {
270
- const schema = ref(() => innerSchema)
271
- const innerSchema = string()
272
- const result = schema.safeParse({ key: 'value' })
273
- expect(result.success).toBe(false)
274
- })
275
-
276
- it('rejects booleans when expecting string', () => {
277
- const schema = ref(() => innerSchema)
278
- const innerSchema = string()
279
- const result = schema.safeParse(true)
280
- expect(result.success).toBe(false)
281
- })
282
- })
283
-
284
- describe('nested references', () => {
285
- it('validates through nested RefSchema', () => {
286
- const innerRef = ref(() => innerSchema)
287
- const innerSchema = string({ minLength: 3 })
288
- const outerRef = ref(() => innerRef)
289
-
290
- const result = outerRef.safeParse('hello')
291
- expect(result.success).toBe(true)
292
- })
293
-
294
- it('rejects invalid input through nested RefSchema', () => {
295
- const innerRef = ref(() => innerSchema)
296
- const innerSchema = string({ minLength: 3 })
297
- const outerRef = ref(() => innerRef)
298
-
299
- const result = outerRef.safeParse('hi')
300
- expect(result.success).toBe(false)
301
- })
302
-
303
- it('validates with deeply nested references', () => {
304
- const level3 = ref(() => innerSchema)
305
- const innerSchema = integer({ minimum: 0 })
306
- const level2 = ref(() => level3)
307
- const level1 = ref(() => level2)
308
-
309
- const result = level1.safeParse(42)
310
- expect(result.success).toBe(true)
311
- })
312
- })
313
-
314
- describe('schema property access', () => {
315
- it('allows direct access to resolved schema', () => {
316
- const innerSchema = string({ minLength: 5 })
317
- const refSchema = ref(() => innerSchema)
318
-
319
- const resolved = refSchema.validator
320
- expect(resolved).toBe(innerSchema)
321
- })
322
-
323
- it('returns same instance on multiple schema property accesses', () => {
324
- const innerSchema = string()
325
- const refSchema = ref(() => innerSchema)
326
-
327
- const first = refSchema.validator
328
- const second = refSchema.validator
329
- const third = refSchema.validator
330
-
331
- expect(first).toBe(second)
332
- expect(second).toBe(third)
333
- })
334
-
335
- it('resolves schema before validation', () => {
336
- let resolved = false
337
- const refSchema = ref(() => {
338
- resolved = true
339
- return string()
340
- })
341
-
342
- expect(resolved).toBe(false)
343
-
344
- const schemaValue = refSchema.validator
345
- expect(resolved).toBe(true)
346
- expect(schemaValue).toBeDefined()
347
- })
348
- })
349
- })
package/src/schema/ref.ts DELETED
@@ -1,103 +0,0 @@
1
- import {
2
- InferInput,
3
- InferOutput,
4
- Schema,
5
- ValidationContext,
6
- Validator,
7
- WrappedValidator,
8
- } from '../core.js'
9
-
10
- /**
11
- * Function type that returns a validator, used for lazy schema resolution.
12
- *
13
- * @template TValidator - The validator type that will be returned
14
- */
15
- export type RefSchemaGetter<out TValidator extends Validator> = () => TValidator
16
-
17
- /**
18
- * Schema for creating references to other schemas with lazy resolution.
19
- *
20
- * Useful for handling circular references or breaking module dependency cycles.
21
- * The referenced schema is resolved lazily when first needed for validation.
22
- *
23
- * @template TValidator - The referenced validator type
24
- *
25
- * @example
26
- * ```ts
27
- * // Self-referential schema for tree structure
28
- * const nodeSchema = l.object({
29
- * value: l.string(),
30
- * children: l.array(l.ref(() => nodeSchema)),
31
- * })
32
- * ```
33
- */
34
- export class RefSchema<const TValidator extends Validator>
35
- extends Schema<InferInput<TValidator>, InferOutput<TValidator>>
36
- implements WrappedValidator<TValidator>
37
- {
38
- readonly type = 'ref' as const
39
-
40
- #getter: RefSchemaGetter<TValidator>
41
-
42
- constructor(getter: RefSchemaGetter<TValidator>) {
43
- // @NOTE In order to avoid circular dependency issues, we don't resolve
44
- // the schema here. Instead, we resolve it lazily when first accessed.
45
-
46
- super()
47
-
48
- this.#getter = getter
49
- }
50
-
51
- get validator(): TValidator {
52
- return this.#getter.call(null)
53
- }
54
-
55
- unwrap(): TValidator {
56
- return this.validator
57
- }
58
-
59
- validateInContext(input: unknown, ctx: ValidationContext) {
60
- return ctx.validate(input, this.validator)
61
- }
62
- }
63
-
64
- /**
65
- * Creates a reference schema with lazy resolution.
66
- *
67
- * Allows referencing schemas that may not be defined yet, enabling
68
- * circular references and breaking dependency cycles. The getter function
69
- * is called lazily when validation is first performed.
70
- *
71
- * @param get - Function that returns the referenced validator
72
- * @returns A new {@link RefSchema} instance
73
- *
74
- * @example
75
- * ```ts
76
- * // Circular reference - tree node that contains children of the same type
77
- * const treeNodeSchema = l.object({
78
- * name: l.string(),
79
- * children: l.optional(l.array(l.ref(() => treeNodeSchema))),
80
- * })
81
- *
82
- * // Cross-module reference
83
- * const commentSchema = l.object({
84
- * text: l.string(),
85
- * author: l.ref(() => userSchema), // userSchema defined elsewhere
86
- * })
87
- *
88
- * // Explicitly typed reference
89
- * const itemSchema = l.ref<Item>(() => complexItemSchema)
90
- * ```
91
- */
92
- /*@__NO_SIDE_EFFECTS__*/
93
- export function ref<const TValidator extends Validator>(
94
- get: RefSchemaGetter<TValidator>,
95
- ): RefSchema<TValidator>
96
- export function ref<TInput, TOutput extends TInput = TInput>(
97
- get: RefSchemaGetter<Validator<TInput, TOutput>>,
98
- ): RefSchema<Validator<TInput, TOutput>>
99
- export function ref<const TValidator extends Validator>(
100
- get: RefSchemaGetter<TValidator>,
101
- ) {
102
- return new RefSchema<TValidator>(get)
103
- }