@atproto/lex-schema 0.0.11 → 0.0.13

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 (261) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/core/$type.d.ts +149 -0
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js +44 -0
  5. package/dist/core/$type.js.map +1 -1
  6. package/dist/core/record-key.d.ts +44 -0
  7. package/dist/core/record-key.d.ts.map +1 -1
  8. package/dist/core/record-key.js +30 -0
  9. package/dist/core/record-key.js.map +1 -1
  10. package/dist/core/result.d.ts +85 -4
  11. package/dist/core/result.d.ts.map +1 -1
  12. package/dist/core/result.js +60 -4
  13. package/dist/core/result.js.map +1 -1
  14. package/dist/core/schema.d.ts +232 -5
  15. package/dist/core/schema.d.ts.map +1 -1
  16. package/dist/core/schema.js +197 -4
  17. package/dist/core/schema.js.map +1 -1
  18. package/dist/core/string-format.d.ts +244 -11
  19. package/dist/core/string-format.d.ts.map +1 -1
  20. package/dist/core/string-format.js +150 -0
  21. package/dist/core/string-format.js.map +1 -1
  22. package/dist/core/types.d.ts +90 -3
  23. package/dist/core/types.d.ts.map +1 -1
  24. package/dist/core/types.js.map +1 -1
  25. package/dist/core/validation-error.d.ts +60 -0
  26. package/dist/core/validation-error.d.ts.map +1 -1
  27. package/dist/core/validation-error.js +60 -0
  28. package/dist/core/validation-error.js.map +1 -1
  29. package/dist/core/validation-issue.d.ts +61 -0
  30. package/dist/core/validation-issue.d.ts.map +1 -1
  31. package/dist/core/validation-issue.js +54 -1
  32. package/dist/core/validation-issue.js.map +1 -1
  33. package/dist/core/validator.d.ts +356 -11
  34. package/dist/core/validator.d.ts.map +1 -1
  35. package/dist/core/validator.js +203 -4
  36. package/dist/core/validator.js.map +1 -1
  37. package/dist/helpers.d.ts +12 -28
  38. package/dist/helpers.d.ts.map +1 -1
  39. package/dist/helpers.js.map +1 -1
  40. package/dist/schema/array.d.ts +46 -0
  41. package/dist/schema/array.d.ts.map +1 -1
  42. package/dist/schema/array.js +16 -1
  43. package/dist/schema/array.js.map +1 -1
  44. package/dist/schema/blob.d.ts +50 -2
  45. package/dist/schema/blob.d.ts.map +1 -1
  46. package/dist/schema/blob.js +44 -2
  47. package/dist/schema/blob.js.map +1 -1
  48. package/dist/schema/boolean.d.ts +29 -0
  49. package/dist/schema/boolean.d.ts.map +1 -1
  50. package/dist/schema/boolean.js +30 -1
  51. package/dist/schema/boolean.js.map +1 -1
  52. package/dist/schema/bytes.d.ts +39 -0
  53. package/dist/schema/bytes.d.ts.map +1 -1
  54. package/dist/schema/bytes.js +34 -1
  55. package/dist/schema/bytes.js.map +1 -1
  56. package/dist/schema/cid.d.ts +39 -0
  57. package/dist/schema/cid.d.ts.map +1 -1
  58. package/dist/schema/cid.js +35 -1
  59. package/dist/schema/cid.js.map +1 -1
  60. package/dist/schema/custom.d.ts +67 -1
  61. package/dist/schema/custom.d.ts.map +1 -1
  62. package/dist/schema/custom.js +55 -0
  63. package/dist/schema/custom.js.map +1 -1
  64. package/dist/schema/dict.d.ts +45 -0
  65. package/dist/schema/dict.d.ts.map +1 -1
  66. package/dist/schema/dict.js +46 -1
  67. package/dist/schema/dict.js.map +1 -1
  68. package/dist/schema/discriminated-union.d.ts +59 -0
  69. package/dist/schema/discriminated-union.d.ts.map +1 -1
  70. package/dist/schema/discriminated-union.js +47 -1
  71. package/dist/schema/discriminated-union.js.map +1 -1
  72. package/dist/schema/enum.d.ts +49 -0
  73. package/dist/schema/enum.d.ts.map +1 -1
  74. package/dist/schema/enum.js +49 -0
  75. package/dist/schema/enum.js.map +1 -1
  76. package/dist/schema/integer.d.ts +43 -0
  77. package/dist/schema/integer.d.ts.map +1 -1
  78. package/dist/schema/integer.js +38 -1
  79. package/dist/schema/integer.js.map +1 -1
  80. package/dist/schema/intersection.d.ts +55 -0
  81. package/dist/schema/intersection.d.ts.map +1 -1
  82. package/dist/schema/intersection.js +50 -0
  83. package/dist/schema/intersection.js.map +1 -1
  84. package/dist/schema/lex-map.d.ts +37 -0
  85. package/dist/schema/lex-map.d.ts.map +1 -0
  86. package/dist/schema/lex-map.js +60 -0
  87. package/dist/schema/lex-map.js.map +1 -0
  88. package/dist/schema/lex-value.d.ts +35 -0
  89. package/dist/schema/lex-value.d.ts.map +1 -0
  90. package/dist/schema/lex-value.js +87 -0
  91. package/dist/schema/lex-value.js.map +1 -0
  92. package/dist/schema/literal.d.ts +45 -0
  93. package/dist/schema/literal.d.ts.map +1 -1
  94. package/dist/schema/literal.js +45 -0
  95. package/dist/schema/literal.js.map +1 -1
  96. package/dist/schema/never.d.ts +43 -0
  97. package/dist/schema/never.d.ts.map +1 -1
  98. package/dist/schema/never.js +44 -1
  99. package/dist/schema/never.js.map +1 -1
  100. package/dist/schema/null.d.ts +30 -0
  101. package/dist/schema/null.d.ts.map +1 -1
  102. package/dist/schema/null.js +31 -1
  103. package/dist/schema/null.js.map +1 -1
  104. package/dist/schema/nullable.d.ts +42 -0
  105. package/dist/schema/nullable.d.ts.map +1 -1
  106. package/dist/schema/nullable.js +42 -0
  107. package/dist/schema/nullable.js.map +1 -1
  108. package/dist/schema/object.d.ts +57 -0
  109. package/dist/schema/object.d.ts.map +1 -1
  110. package/dist/schema/object.js +53 -1
  111. package/dist/schema/object.js.map +1 -1
  112. package/dist/schema/optional.d.ts +43 -0
  113. package/dist/schema/optional.d.ts.map +1 -1
  114. package/dist/schema/optional.js +43 -0
  115. package/dist/schema/optional.js.map +1 -1
  116. package/dist/schema/params.d.ts +96 -12
  117. package/dist/schema/params.d.ts.map +1 -1
  118. package/dist/schema/params.js +155 -21
  119. package/dist/schema/params.js.map +1 -1
  120. package/dist/schema/payload.d.ts +111 -15
  121. package/dist/schema/payload.d.ts.map +1 -1
  122. package/dist/schema/payload.js +73 -3
  123. package/dist/schema/payload.js.map +1 -1
  124. package/dist/schema/permission-set.d.ts +58 -0
  125. package/dist/schema/permission-set.d.ts.map +1 -1
  126. package/dist/schema/permission-set.js +50 -0
  127. package/dist/schema/permission-set.js.map +1 -1
  128. package/dist/schema/permission.d.ts +42 -0
  129. package/dist/schema/permission.d.ts.map +1 -1
  130. package/dist/schema/permission.js +39 -0
  131. package/dist/schema/permission.js.map +1 -1
  132. package/dist/schema/procedure.d.ts +64 -0
  133. package/dist/schema/procedure.d.ts.map +1 -1
  134. package/dist/schema/procedure.js +64 -0
  135. package/dist/schema/procedure.js.map +1 -1
  136. package/dist/schema/query.d.ts +55 -0
  137. package/dist/schema/query.d.ts.map +1 -1
  138. package/dist/schema/query.js +55 -0
  139. package/dist/schema/query.js.map +1 -1
  140. package/dist/schema/record.d.ts +76 -25
  141. package/dist/schema/record.d.ts.map +1 -1
  142. package/dist/schema/record.js +21 -0
  143. package/dist/schema/record.js.map +1 -1
  144. package/dist/schema/ref.d.ts +51 -0
  145. package/dist/schema/ref.d.ts.map +1 -1
  146. package/dist/schema/ref.js +18 -0
  147. package/dist/schema/ref.js.map +1 -1
  148. package/dist/schema/refine.d.ts +58 -9
  149. package/dist/schema/refine.d.ts.map +1 -1
  150. package/dist/schema/refine.js.map +1 -1
  151. package/dist/schema/regexp.d.ts +45 -0
  152. package/dist/schema/regexp.d.ts.map +1 -1
  153. package/dist/schema/regexp.js +46 -1
  154. package/dist/schema/regexp.js.map +1 -1
  155. package/dist/schema/string.d.ts +72 -6
  156. package/dist/schema/string.d.ts.map +1 -1
  157. package/dist/schema/string.js +56 -8
  158. package/dist/schema/string.js.map +1 -1
  159. package/dist/schema/subscription.d.ts +72 -2
  160. package/dist/schema/subscription.d.ts.map +1 -1
  161. package/dist/schema/subscription.js +59 -0
  162. package/dist/schema/subscription.js.map +1 -1
  163. package/dist/schema/token.d.ts +48 -0
  164. package/dist/schema/token.d.ts.map +1 -1
  165. package/dist/schema/token.js +49 -1
  166. package/dist/schema/token.js.map +1 -1
  167. package/dist/schema/typed-object.d.ts +73 -23
  168. package/dist/schema/typed-object.d.ts.map +1 -1
  169. package/dist/schema/typed-object.js +20 -1
  170. package/dist/schema/typed-object.js.map +1 -1
  171. package/dist/schema/typed-ref.d.ts +54 -0
  172. package/dist/schema/typed-ref.d.ts.map +1 -1
  173. package/dist/schema/typed-ref.js +16 -0
  174. package/dist/schema/typed-ref.js.map +1 -1
  175. package/dist/schema/typed-union.d.ts +51 -1
  176. package/dist/schema/typed-union.d.ts.map +1 -1
  177. package/dist/schema/typed-union.js +52 -2
  178. package/dist/schema/typed-union.js.map +1 -1
  179. package/dist/schema/union.d.ts +46 -0
  180. package/dist/schema/union.d.ts.map +1 -1
  181. package/dist/schema/union.js +41 -0
  182. package/dist/schema/union.js.map +1 -1
  183. package/dist/schema/unknown.d.ts +34 -0
  184. package/dist/schema/unknown.d.ts.map +1 -1
  185. package/dist/schema/unknown.js +34 -0
  186. package/dist/schema/unknown.js.map +1 -1
  187. package/dist/schema/with-default.d.ts +45 -0
  188. package/dist/schema/with-default.d.ts.map +1 -1
  189. package/dist/schema/with-default.js +45 -0
  190. package/dist/schema/with-default.js.map +1 -1
  191. package/dist/schema.d.ts +2 -1
  192. package/dist/schema.d.ts.map +1 -1
  193. package/dist/schema.js +2 -1
  194. package/dist/schema.js.map +1 -1
  195. package/dist/util/if-any.d.ts +2 -0
  196. package/dist/util/if-any.d.ts.map +1 -0
  197. package/dist/util/if-any.js +3 -0
  198. package/dist/util/if-any.js.map +1 -0
  199. package/package.json +3 -3
  200. package/src/core/$type.ts +150 -18
  201. package/src/core/record-key.ts +44 -0
  202. package/src/core/result.ts +86 -4
  203. package/src/core/schema.ts +244 -9
  204. package/src/core/string-format.ts +259 -13
  205. package/src/core/types.ts +91 -3
  206. package/src/core/validation-error.ts +60 -0
  207. package/src/core/validation-issue.ts +68 -2
  208. package/src/core/validator.ts +373 -12
  209. package/src/helpers.test.ts +110 -29
  210. package/src/helpers.ts +54 -25
  211. package/src/schema/array.test.ts +94 -79
  212. package/src/schema/array.ts +48 -1
  213. package/src/schema/blob.ts +50 -1
  214. package/src/schema/boolean.ts +31 -1
  215. package/src/schema/bytes.ts +41 -1
  216. package/src/schema/cid.ts +41 -1
  217. package/src/schema/custom.ts +68 -1
  218. package/src/schema/dict.ts +47 -1
  219. package/src/schema/discriminated-union.ts +61 -1
  220. package/src/schema/enum.ts +50 -0
  221. package/src/schema/integer.ts +45 -1
  222. package/src/schema/intersection.ts +56 -0
  223. package/src/schema/{unknown-object.test.ts → lex-map.test.ts} +9 -9
  224. package/src/schema/lex-map.ts +63 -0
  225. package/src/schema/lex-value.test.ts +81 -0
  226. package/src/schema/lex-value.ts +86 -0
  227. package/src/schema/literal.ts +46 -0
  228. package/src/schema/never.ts +45 -1
  229. package/src/schema/null.ts +32 -1
  230. package/src/schema/nullable.ts +43 -0
  231. package/src/schema/object.ts +59 -1
  232. package/src/schema/optional.ts +44 -0
  233. package/src/schema/params.test.ts +133 -38
  234. package/src/schema/params.ts +237 -37
  235. package/src/schema/payload.test.ts +3 -3
  236. package/src/schema/payload.ts +145 -42
  237. package/src/schema/permission-set.ts +58 -0
  238. package/src/schema/permission.ts +42 -0
  239. package/src/schema/procedure.ts +64 -0
  240. package/src/schema/query.ts +55 -0
  241. package/src/schema/record.ts +82 -16
  242. package/src/schema/ref.ts +52 -0
  243. package/src/schema/refine.ts +58 -9
  244. package/src/schema/regexp.ts +47 -1
  245. package/src/schema/string.test.ts +99 -2
  246. package/src/schema/string.ts +108 -15
  247. package/src/schema/subscription.ts +72 -2
  248. package/src/schema/token.ts +50 -1
  249. package/src/schema/typed-object.ts +81 -16
  250. package/src/schema/typed-ref.ts +55 -0
  251. package/src/schema/typed-union.ts +58 -3
  252. package/src/schema/union.ts +47 -0
  253. package/src/schema/unknown.ts +35 -0
  254. package/src/schema/with-default.ts +46 -0
  255. package/src/schema.ts +2 -1
  256. package/src/util/if-any.ts +3 -0
  257. package/dist/schema/unknown-object.d.ts +0 -8
  258. package/dist/schema/unknown-object.d.ts.map +0 -1
  259. package/dist/schema/unknown-object.js +0 -19
  260. package/dist/schema/unknown-object.js.map +0 -1
  261. package/src/schema/unknown-object.ts +0 -19
@@ -1,8 +1,25 @@
1
1
  import { $type, NsidString, Schema, ValidationContext } from '../core.js'
2
2
 
3
+ /**
4
+ * Schema for Lexicon token values.
5
+ *
6
+ * Tokens are named constants in Lexicon, identified by their NSID and hash.
7
+ * They validate to their string value (e.g., 'app.bsky.feed.defs#requestLess').
8
+ * TokenSchema instances can also be used as values themselves.
9
+ *
10
+ * @template TValue - The token string literal type
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const schema = new TokenSchema('app.bsky.feed.defs#requestLess')
15
+ * schema.validate('app.bsky.feed.defs#requestLess') // success
16
+ * ```
17
+ */
3
18
  export class TokenSchema<
4
19
  const TValue extends string = string,
5
20
  > extends Schema<TValue> {
21
+ readonly type = 'token' as const
22
+
6
23
  constructor(readonly value: TValue) {
7
24
  super()
8
25
  }
@@ -19,7 +36,7 @@ export class TokenSchema<
19
36
  }
20
37
 
21
38
  if (typeof input !== 'string') {
22
- return ctx.issueInvalidType(input, 'token')
39
+ return ctx.issueUnexpectedType(input, 'token')
23
40
  }
24
41
 
25
42
  return ctx.issueInvalidValue(input, [this.value])
@@ -37,6 +54,38 @@ export class TokenSchema<
37
54
  }
38
55
  }
39
56
 
57
+ /**
58
+ * Creates a token schema for Lexicon named constants.
59
+ *
60
+ * Tokens are used in Lexicon as named constants or enum-like values.
61
+ * The token instance can be used both as a schema validator and as
62
+ * the token value itself (it serializes to its string value).
63
+ *
64
+ * @param nsid - The NSID part of the token
65
+ * @param hash - The hash part of the token (defaults to 'main')
66
+ * @returns A new {@link TokenSchema} instance
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Define tokens
71
+ * const requestLess = l.token('app.bsky.feed.defs', 'requestLess')
72
+ * const requestMore = l.token('app.bsky.feed.defs', 'requestMore')
73
+ *
74
+ * // Use as a value
75
+ * console.log(requestLess.toString()) // 'app.bsky.feed.defs#requestLess'
76
+ *
77
+ * // Use in union for validation
78
+ * const feedbackSchema = l.union([requestLess, requestMore])
79
+ *
80
+ * // Validate
81
+ * feedbackSchema.parse('app.bsky.feed.defs#requestLess') // success
82
+ *
83
+ * // Token instances can be used as values in other schemas
84
+ * const feedbackRequest = l.object({
85
+ * feedback: requestLess, // Accepts the token value
86
+ * })
87
+ * ```
88
+ */
40
89
  /*@__NO_SIDE_EFFECTS__*/
41
90
  export function token<
42
91
  const N extends NsidString,
@@ -15,6 +15,31 @@ import {
15
15
  Validator,
16
16
  } from '../core.js'
17
17
 
18
+ export type MaybeTypedObject<
19
+ TType extends $Type,
20
+ TValue extends { $type?: unknown } = { $type?: unknown },
21
+ > = TValue extends { $type?: TType }
22
+ ? TValue
23
+ : $TypedMaybe<Exclude<TValue, Unknown$TypedObject>, TType>
24
+
25
+ /**
26
+ * Schema for typed objects in Lexicon unions.
27
+ *
28
+ * Typed objects have a `$type` field that identifies which variant they are
29
+ * in a union. The `$type` can be omitted in input (it's implicit), but if
30
+ * present, it must match the expected value.
31
+ *
32
+ * @template TType - The $type string literal type
33
+ * @template TShape - The validator type for the object's shape
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const schema = new TypedObjectSchema(
38
+ * 'app.bsky.embed.images#view',
39
+ * l.object({ images: l.array(imageSchema) })
40
+ * )
41
+ * ```
42
+ */
18
43
  export class TypedObjectSchema<
19
44
  const TType extends $Type = $Type,
20
45
  const TShape extends Validator<{ [k: string]: unknown }> = any,
@@ -22,6 +47,8 @@ export class TypedObjectSchema<
22
47
  $TypedMaybe<InferInput<TShape>, TType>,
23
48
  $TypedMaybe<InferOutput<TShape>, TType>
24
49
  > {
50
+ readonly type = 'typedObject' as const
51
+
25
52
  constructor(
26
53
  readonly $type: TType,
27
54
  readonly schema: TShape,
@@ -29,11 +56,9 @@ export class TypedObjectSchema<
29
56
  super()
30
57
  }
31
58
 
32
- isTypeOf<X extends Record<string, unknown>>(
33
- value: X,
34
- ): value is X extends { $type?: TType }
35
- ? X
36
- : $TypedMaybe<Exclude<X, Unknown$TypedObject>, TType> {
59
+ isTypeOf<TValue extends Record<string, unknown>>(
60
+ value: TValue,
61
+ ): value is MaybeTypedObject<TType, TValue> {
37
62
  return value.$type === undefined || value.$type === this.$type
38
63
  }
39
64
 
@@ -46,17 +71,21 @@ export class TypedObjectSchema<
46
71
  >
47
72
  }
48
73
 
49
- $isTypeOf<X extends Record<string, unknown>>(value: X) {
74
+ $isTypeOf<TValue extends Record<string, unknown>>(
75
+ value: TValue,
76
+ ): value is MaybeTypedObject<TType, TValue> {
50
77
  return this.isTypeOf(value)
51
78
  }
52
79
 
53
- $build(input: Omit<InferInput<this>, '$type'>) {
80
+ $build(
81
+ input: Omit<InferInput<this>, '$type'>,
82
+ ): $Typed<InferOutput<this>, TType> {
54
83
  return this.build(input)
55
84
  }
56
85
 
57
86
  validateInContext(input: unknown, ctx: ValidationContext) {
58
87
  if (!isPlainObject(input)) {
59
- return ctx.issueInvalidType(input, 'object')
88
+ return ctx.issueUnexpectedType(input, 'object')
60
89
  }
61
90
 
62
91
  if (
@@ -72,15 +101,51 @@ export class TypedObjectSchema<
72
101
  }
73
102
 
74
103
  /**
104
+ * Creates a typed object schema for use in Lexicon unions.
105
+ *
106
+ * Typed objects are identified by their `$type` field, which combines an NSID
107
+ * and a hash (e.g., 'app.bsky.embed.images#view'). Used for union variants.
108
+ *
75
109
  * This function offers two overloads:
76
- * - One that allows creating a {@link TypedObjectSchema}, and infer the output
77
- * type from the provided arguments, without requiring to specify any of the
78
- * generics. This is useful when you want to define a record without
79
- * explicitly defining its interface. This version does not support circular
80
- * references, as TypeScript cannot infer types in such cases.
81
- * - One allows creating a {@link TypedObjectSchema} with an explicitly defined
82
- * interface. This will typically be used by codegen (`lex build`) to generate
83
- * schemas that work even if they contain circular references.
110
+ * - One that infers the type from arguments (no circular reference support)
111
+ * - One with explicit interface for codegen with circular references
112
+ *
113
+ * @param nsid - The NSID part of the type (e.g., 'app.bsky.embed.images')
114
+ * @param hash - The hash part of the type (e.g., 'view'), defaults to 'main'
115
+ * @param validator - Schema for validating the object properties
116
+ * @returns A new {@link TypedObjectSchema} instance
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * // Image embed view
121
+ * const imageViewSchema = l.typedObject(
122
+ * 'app.bsky.embed.images',
123
+ * 'view',
124
+ * l.object({
125
+ * images: l.array(l.object({
126
+ * thumb: l.string(),
127
+ * fullsize: l.string(),
128
+ * alt: l.string(),
129
+ * })),
130
+ * })
131
+ * )
132
+ *
133
+ * // Main type (hash defaults to 'main')
134
+ * const postViewSchema = l.typedObject(
135
+ * 'app.bsky.feed.defs',
136
+ * 'postView',
137
+ * l.object({ uri: l.string(), cid: l.string(), author: authorSchema })
138
+ * )
139
+ *
140
+ * // Use $isTypeOf to narrow union types
141
+ * if (imageViewSchema.$isTypeOf(embed)) {
142
+ * // embed is narrowed to image view type
143
+ * }
144
+ *
145
+ * // Use $build to construct typed objects
146
+ * const view = imageViewSchema.$build({ images: [...] })
147
+ * // view.$type === 'app.bsky.embed.images#view'
148
+ * ```
84
149
  */
85
150
  export function typedObject<
86
151
  const N extends NsidString,
@@ -7,6 +7,14 @@ import {
7
7
  Validator,
8
8
  } from '../core.js'
9
9
 
10
+ /**
11
+ * Interface for validators that have a $type property.
12
+ *
13
+ * Used by typed objects and records to identify their type in unions.
14
+ *
15
+ * @template TInput - The input type (with optional $type)
16
+ * @template TOutput - The output type (with non-optional $type)
17
+ */
10
18
  export interface TypedObjectValidator<
11
19
  TInput extends { $type?: string } = { $type?: string },
12
20
  TOutput extends TInput = TInput,
@@ -14,15 +22,37 @@ export interface TypedObjectValidator<
14
22
  $type: NonNullable<TOutput['$type']>
15
23
  }
16
24
 
25
+ /**
26
+ * Function type that returns a typed object validator, used for lazy resolution.
27
+ *
28
+ * @template TValidator - The typed object validator type
29
+ */
17
30
  export type TypedRefGetter<out TValidator extends TypedObjectValidator> =
18
31
  () => TValidator
19
32
 
33
+ /**
34
+ * Schema for referencing typed objects with lazy resolution.
35
+ *
36
+ * Used in typed unions to reference typed object schemas. Requires the
37
+ * `$type` field to be present and match the referenced schema's type.
38
+ * The referenced schema is resolved lazily to support circular references.
39
+ *
40
+ * @template TValidator - The referenced typed object validator type
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const ref = new TypedRefSchema(() => imageViewSchema)
45
+ * // ref.$type === 'app.bsky.embed.images#view'
46
+ * ```
47
+ */
20
48
  export class TypedRefSchema<
21
49
  const TValidator extends TypedObjectValidator = TypedObjectValidator,
22
50
  > extends Schema<
23
51
  $Typed<InferInput<TValidator>>,
24
52
  $Typed<InferOutput<TValidator>>
25
53
  > {
54
+ readonly type = 'typedRef' as const
55
+
26
56
  #getter: TypedRefGetter<TValidator>
27
57
 
28
58
  constructor(getter: TypedRefGetter<TValidator>) {
@@ -54,6 +84,31 @@ export class TypedRefSchema<
54
84
  }
55
85
  }
56
86
 
87
+ /**
88
+ * Creates a reference to a typed object schema for use in typed unions.
89
+ *
90
+ * Unlike regular `ref()`, this requires the referenced schema to have a
91
+ * `$type` property, and validates that the input's `$type` matches.
92
+ *
93
+ * @param get - Function that returns the typed object validator
94
+ * @returns A new {@link TypedRefSchema} instance
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // Reference to image embed view
99
+ * const imageRef = l.typedRef(() => imageViewSchema)
100
+ *
101
+ * // Use in a typed union
102
+ * const embedUnion = l.typedUnion([
103
+ * l.typedRef(() => imageViewSchema),
104
+ * l.typedRef(() => videoViewSchema),
105
+ * l.typedRef(() => externalViewSchema),
106
+ * ], true) // closed union
107
+ *
108
+ * // The $type is accessible on the ref
109
+ * console.log(imageRef.$type) // 'app.bsky.embed.images#view'
110
+ * ```
111
+ */
57
112
  /*@__NO_SIDE_EFFECTS__*/
58
113
  export function typedRef<const TValidator extends TypedObjectValidator>(
59
114
  get: TypedRefGetter<TValidator>,
@@ -7,10 +7,32 @@ import {
7
7
  ValidationContext,
8
8
  } from '../core.js'
9
9
  import { lazyProperty } from '../util/lazy-property.js'
10
+ import { TypedObjectSchema } from './typed-object.js'
10
11
  import { TypedRefSchema } from './typed-ref.js'
11
12
 
13
+ /**
14
+ * Schema for Lexicon typed unions (unions discriminated by $type).
15
+ *
16
+ * Typed unions are collections of typed objects identified by their `$type`
17
+ * field. Can be "open" (accept unknown types) or "closed" (only accept
18
+ * known types).
19
+ *
20
+ * @template TValidators - Tuple of {@link TypedRefSchema} or {@link TypedObjectSchema} instances
21
+ * @template TClosed - Whether the union is closed (rejects unknown $types)
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const embedUnion = new TypedUnionSchema([
26
+ * l.typedRef(() => imageSchema),
27
+ * l.typedRef(() => videoSchema),
28
+ * ], true) // closed - only accepts images and videos
29
+ * ```
30
+ */
12
31
  export class TypedUnionSchema<
13
- const TValidators extends readonly TypedRefSchema[] = [],
32
+ const TValidators extends readonly (
33
+ | TypedRefSchema
34
+ | TypedObjectSchema
35
+ )[] = [],
14
36
  const TClosed extends boolean = boolean,
15
37
  > extends Schema<
16
38
  TClosed extends true
@@ -20,13 +42,16 @@ export class TypedUnionSchema<
20
42
  ? InferOutput<TValidators[number]>
21
43
  : InferOutput<TValidators[number]> | Unknown$TypedObject
22
44
  > {
45
+ readonly type = 'typedUnion' as const
46
+
23
47
  constructor(
24
48
  protected readonly validators: TValidators,
25
49
  public readonly closed: TClosed,
26
50
  ) {
27
51
  // @NOTE In order to avoid circular dependency issues, we don't access the
28
52
  // refs's schema (or $type) here. Instead, we access them lazily when first
29
- // needed.
53
+ // needed. The biggest issue with this strategy is that we can't throw
54
+ // early if the refs contain multiple refs with the same $type.
30
55
 
31
56
  super()
32
57
  }
@@ -44,7 +69,7 @@ export class TypedUnionSchema<
44
69
 
45
70
  validateInContext(input: unknown, ctx: ValidationContext) {
46
71
  if (!isPlainObject(input) || !('$type' in input)) {
47
- return ctx.issueInvalidType(input, '$typed')
72
+ return ctx.issueUnexpectedType(input, '$typed')
48
73
  }
49
74
 
50
75
  const { $type } = input
@@ -66,6 +91,36 @@ export class TypedUnionSchema<
66
91
  }
67
92
  }
68
93
 
94
+ /**
95
+ * Creates a typed union schema for Lexicon unions.
96
+ *
97
+ * Typed unions discriminate variants by their `$type` field. Can be open
98
+ * (accepts unknown types, useful for extensibility) or closed (strict).
99
+ *
100
+ * @param refs - Array of typed refs for the union variants
101
+ * @param closed - Whether to reject unknown $type values
102
+ * @returns A new {@link TypedUnionSchema} instance
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // Closed union - only accepts known types
107
+ * const embedSchema = l.typedUnion([
108
+ * l.typedRef(() => imageViewSchema),
109
+ * l.typedRef(() => videoViewSchema),
110
+ * l.typedRef(() => externalViewSchema),
111
+ * ], true)
112
+ *
113
+ * // Open union - accepts unknown types for forward compatibility
114
+ * const feedItemSchema = l.typedUnion([
115
+ * l.typedRef(() => postSchema),
116
+ * l.typedRef(() => repostSchema),
117
+ * ], false) // unknown types pass through
118
+ *
119
+ * // Get all known $types
120
+ * console.log(embedSchema.$types)
121
+ * // ['app.bsky.embed.images#view', 'app.bsky.embed.video#view', ...]
122
+ * ```
123
+ */
69
124
  /*@__NO_SIDE_EFFECTS__*/
70
125
  export function typedUnion<
71
126
  const R extends readonly TypedRefSchema[],
@@ -8,14 +8,37 @@ import {
8
8
  Validator,
9
9
  } from '../core.js'
10
10
 
11
+ /**
12
+ * Type representing a non-empty tuple of validators for union schemas.
13
+ *
14
+ * Requires at least one validator in the tuple.
15
+ */
11
16
  export type UnionSchemaValidators = readonly [Validator, ...Validator[]]
12
17
 
18
+ /**
19
+ * Schema for validating values that match one of several possible schemas.
20
+ *
21
+ * Tries each validator in order until one succeeds. If all validators fail,
22
+ * returns a combined error from all attempts.
23
+ *
24
+ * @template TValidators - Tuple type of the validators in the union
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const schema = new UnionSchema([l.string(), l.integer()])
29
+ * schema.validate('hello') // success
30
+ * schema.validate(42) // success
31
+ * schema.validate(true) // fails
32
+ * ```
33
+ */
13
34
  export class UnionSchema<
14
35
  const TValidators extends UnionSchemaValidators = any,
15
36
  > extends Schema<
16
37
  InferInput<TValidators[number]>,
17
38
  InferOutput<TValidators[number]>
18
39
  > {
40
+ readonly type = 'union' as const
41
+
19
42
  constructor(protected readonly validators: TValidators) {
20
43
  super()
21
44
  }
@@ -34,6 +57,30 @@ export class UnionSchema<
34
57
  }
35
58
  }
36
59
 
60
+ /**
61
+ * Creates a union schema that accepts values matching any of the provided schemas.
62
+ *
63
+ * Validators are tried in order. Use `discriminatedUnion()` for better
64
+ * performance when discriminating on a known property.
65
+ *
66
+ * @param validators - Non-empty array of validators to try
67
+ * @returns A new {@link UnionSchema} instance
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // String or number
72
+ * const stringOrNumber = l.union([l.string(), l.integer()])
73
+ *
74
+ * // Nullable value
75
+ * const nullableString = l.union([l.string(), l.null()])
76
+ *
77
+ * // Multiple object types
78
+ * const mediaSchema = l.union([
79
+ * l.object({ type: l.literal('image'), url: l.string() }),
80
+ * l.object({ type: l.literal('video'), url: l.string(), duration: l.integer() }),
81
+ * ])
82
+ * ```
83
+ */
37
84
  /*@__NO_SIDE_EFFECTS__*/
38
85
  export function union<const TValidators extends UnionSchemaValidators>(
39
86
  validators: TValidators,
@@ -1,12 +1,47 @@
1
1
  import { Schema, ValidationContext } from '../core.js'
2
2
  import { memoizedOptions } from '../util/memoize.js'
3
3
 
4
+ /**
5
+ * Schema that accepts any value without validation.
6
+ *
7
+ * Passes through any input unchanged. Use sparingly as it bypasses
8
+ * type safety. Useful for dynamic data or when the schema is not
9
+ * known at compile time.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const schema = new UnknownSchema()
14
+ * schema.validate(anything) // always succeeds
15
+ * ```
16
+ */
4
17
  export class UnknownSchema extends Schema<unknown> {
18
+ readonly type = 'unknown' as const
19
+
5
20
  validateInContext(input: unknown, ctx: ValidationContext) {
6
21
  return ctx.success(input)
7
22
  }
8
23
  }
9
24
 
25
+ /**
26
+ * Creates an unknown schema that accepts any value.
27
+ *
28
+ * The value passes through without any validation or transformation.
29
+ * Use this when you need to accept arbitrary data.
30
+ *
31
+ * @returns A new {@link UnknownSchema} instance
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * // Accept any value
36
+ * const anyDataSchema = l.unknown()
37
+ *
38
+ * // In an object with a dynamic field
39
+ * const flexibleSchema = l.object({
40
+ * type: l.string(),
41
+ * data: l.unknown(),
42
+ * })
43
+ * ```
44
+ */
10
45
  export const unknown = /*#__PURE__*/ memoizedOptions(function () {
11
46
  return new UnknownSchema()
12
47
  })
@@ -6,9 +6,27 @@ import {
6
6
  Validator,
7
7
  } from '../core.js'
8
8
 
9
+ /**
10
+ * Schema wrapper that provides a default value when the input is undefined.
11
+ *
12
+ * In parse mode, when the input is `undefined`, the default value is used
13
+ * instead. In validate mode, undefined values pass through unchanged (the
14
+ * default is not applied).
15
+ *
16
+ * @template TValidator - The wrapped validator type
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const schema = new WithDefaultSchema(l.integer(), 0)
21
+ * schema.parse(undefined) // 0
22
+ * schema.parse(42) // 42
23
+ * ```
24
+ */
9
25
  export class WithDefaultSchema<
10
26
  const TValidator extends Validator,
11
27
  > extends Schema<InferInput<TValidator>, InferOutput<TValidator>> {
28
+ readonly type = 'withDefault' as const
29
+
12
30
  constructor(
13
31
  readonly validator: TValidator,
14
32
  readonly defaultValue: InferInput<TValidator>,
@@ -27,6 +45,34 @@ export class WithDefaultSchema<
27
45
  }
28
46
  }
29
47
 
48
+ /**
49
+ * Creates a schema that applies a default value when the input is undefined.
50
+ *
51
+ * Commonly used with `optional()` to provide fallback values for missing
52
+ * properties. The default value is validated against the schema.
53
+ *
54
+ * @param validator - The validator for the value
55
+ * @param defaultValue - The default value to use when input is undefined
56
+ * @returns A new {@link WithDefaultSchema} instance
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // Integer with default
61
+ * const countSchema = l.withDefault(l.integer(), 0)
62
+ * countSchema.parse(undefined) // 0
63
+ * countSchema.parse(5) // 5
64
+ *
65
+ * // Commonly combined with optional in objects
66
+ * const settingsSchema = l.object({
67
+ * theme: l.optional(l.withDefault(l.string(), 'light')),
68
+ * pageSize: l.optional(l.withDefault(l.integer(), 25)),
69
+ * })
70
+ * settingsSchema.parse({}) // { theme: 'light', pageSize: 25 }
71
+ *
72
+ * // Boolean with default
73
+ * const enabledSchema = l.withDefault(l.boolean(), false)
74
+ * ```
75
+ */
30
76
  export function withDefault<const TValidator extends Validator>(
31
77
  validator: TValidator,
32
78
  defaultValue: InferInput<TValidator>,
package/src/schema.ts CHANGED
@@ -7,13 +7,14 @@ export * from './schema/cid.js'
7
7
  export * from './schema/dict.js'
8
8
  export * from './schema/enum.js'
9
9
  export * from './schema/integer.js'
10
+ export * from './schema/lex-map.js'
11
+ export * from './schema/lex-value.js'
10
12
  export * from './schema/literal.js'
11
13
  export * from './schema/never.js'
12
14
  export * from './schema/null.js'
13
15
  export * from './schema/object.js'
14
16
  export * from './schema/regexp.js'
15
17
  export * from './schema/string.js'
16
- export * from './schema/unknown-object.js'
17
18
  export * from './schema/unknown.js'
18
19
 
19
20
  // Composite Types
@@ -0,0 +1,3 @@
1
+ export type IfAny<T, TrueValue, FalseValue> = 0 extends 1 & T
2
+ ? TrueValue
3
+ : FalseValue
@@ -1,8 +0,0 @@
1
- import { LexMap } from '@atproto/lex-data';
2
- import { Schema, ValidationContext } from '../core.js';
3
- export type UnknownObject = LexMap;
4
- export declare class UnknownObjectSchema extends Schema<UnknownObject> {
5
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationResult<LexMap>;
6
- }
7
- export declare const unknownObject: () => UnknownObjectSchema;
8
- //# sourceMappingURL=unknown-object.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unknown-object.d.ts","sourceRoot":"","sources":["../../src/schema/unknown-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGtD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAA;AAElC,qBAAa,mBAAoB,SAAQ,MAAM,CAAC,aAAa,CAAC;IAC5D,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;CAOzD;AAED,eAAO,MAAM,aAAa,2BAExB,CAAA"}
@@ -1,19 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unknownObject = exports.UnknownObjectSchema = void 0;
4
- const lex_data_1 = require("@atproto/lex-data");
5
- const core_js_1 = require("../core.js");
6
- const memoize_js_1 = require("../util/memoize.js");
7
- class UnknownObjectSchema extends core_js_1.Schema {
8
- validateInContext(input, ctx) {
9
- if ((0, lex_data_1.isLexMap)(input)) {
10
- return ctx.success(input);
11
- }
12
- return ctx.issueInvalidType(input, 'unknown');
13
- }
14
- }
15
- exports.UnknownObjectSchema = UnknownObjectSchema;
16
- exports.unknownObject = (0, memoize_js_1.memoizedOptions)(function () {
17
- return new UnknownObjectSchema();
18
- });
19
- //# sourceMappingURL=unknown-object.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"unknown-object.js","sourceRoot":"","sources":["../../src/schema/unknown-object.ts"],"names":[],"mappings":";;;AAAA,gDAAoD;AACpD,wCAAsD;AACtD,mDAAoD;AAIpD,MAAa,mBAAoB,SAAQ,gBAAqB;IAC5D,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,IAAA,mBAAQ,EAAC,KAAK,CAAC,EAAE,CAAC;YACpB,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;QAED,OAAO,GAAG,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IAC/C,CAAC;CACF;AARD,kDAQC;AAEY,QAAA,aAAa,GAAiB,IAAA,4BAAe,EAAC;IACzD,OAAO,IAAI,mBAAmB,EAAE,CAAA;AAClC,CAAC,CAAC,CAAA","sourcesContent":["import { LexMap, isLexMap } from '@atproto/lex-data'\nimport { Schema, ValidationContext } from '../core.js'\nimport { memoizedOptions } from '../util/memoize.js'\n\nexport type UnknownObject = LexMap\n\nexport class UnknownObjectSchema extends Schema<UnknownObject> {\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (isLexMap(input)) {\n return ctx.success(input)\n }\n\n return ctx.issueInvalidType(input, 'unknown')\n }\n}\n\nexport const unknownObject = /*#__PURE__*/ memoizedOptions(function () {\n return new UnknownObjectSchema()\n})\n"]}
@@ -1,19 +0,0 @@
1
- import { LexMap, isLexMap } from '@atproto/lex-data'
2
- import { Schema, ValidationContext } from '../core.js'
3
- import { memoizedOptions } from '../util/memoize.js'
4
-
5
- export type UnknownObject = LexMap
6
-
7
- export class UnknownObjectSchema extends Schema<UnknownObject> {
8
- validateInContext(input: unknown, ctx: ValidationContext) {
9
- if (isLexMap(input)) {
10
- return ctx.success(input)
11
- }
12
-
13
- return ctx.issueInvalidType(input, 'unknown')
14
- }
15
- }
16
-
17
- export const unknownObject = /*#__PURE__*/ memoizedOptions(function () {
18
- return new UnknownObjectSchema()
19
- })