@atproto/lex-schema 0.0.10 → 0.0.12

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 (239) hide show
  1. package/CHANGELOG.md +26 -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 +229 -2
  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 +61 -1
  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 +51 -0
  32. package/dist/core/validation-issue.js.map +1 -1
  33. package/dist/core/validator.d.ts +347 -10
  34. package/dist/core/validator.d.ts.map +1 -1
  35. package/dist/core/validator.js +184 -3
  36. package/dist/core/validator.js.map +1 -1
  37. package/dist/helpers.d.ts +13 -25
  38. package/dist/helpers.d.ts.map +1 -1
  39. package/dist/helpers.js +2 -2
  40. package/dist/helpers.js.map +1 -1
  41. package/dist/schema/array.d.ts +45 -0
  42. package/dist/schema/array.d.ts.map +1 -1
  43. package/dist/schema/array.js +14 -0
  44. package/dist/schema/array.js.map +1 -1
  45. package/dist/schema/blob.d.ts +46 -0
  46. package/dist/schema/blob.d.ts.map +1 -1
  47. package/dist/schema/blob.js +39 -0
  48. package/dist/schema/blob.js.map +1 -1
  49. package/dist/schema/boolean.d.ts +28 -0
  50. package/dist/schema/boolean.d.ts.map +1 -1
  51. package/dist/schema/boolean.js +28 -0
  52. package/dist/schema/boolean.js.map +1 -1
  53. package/dist/schema/bytes.d.ts +38 -0
  54. package/dist/schema/bytes.d.ts.map +1 -1
  55. package/dist/schema/bytes.js +32 -0
  56. package/dist/schema/bytes.js.map +1 -1
  57. package/dist/schema/cid.d.ts +38 -0
  58. package/dist/schema/cid.d.ts.map +1 -1
  59. package/dist/schema/cid.js +33 -0
  60. package/dist/schema/cid.js.map +1 -1
  61. package/dist/schema/custom.d.ts +66 -1
  62. package/dist/schema/custom.d.ts.map +1 -1
  63. package/dist/schema/custom.js +54 -0
  64. package/dist/schema/custom.js.map +1 -1
  65. package/dist/schema/dict.d.ts +44 -0
  66. package/dist/schema/dict.d.ts.map +1 -1
  67. package/dist/schema/dict.js +44 -0
  68. package/dist/schema/dict.js.map +1 -1
  69. package/dist/schema/discriminated-union.d.ts +58 -0
  70. package/dist/schema/discriminated-union.d.ts.map +1 -1
  71. package/dist/schema/discriminated-union.js +45 -0
  72. package/dist/schema/discriminated-union.js.map +1 -1
  73. package/dist/schema/enum.d.ts +48 -0
  74. package/dist/schema/enum.d.ts.map +1 -1
  75. package/dist/schema/enum.js +48 -0
  76. package/dist/schema/enum.js.map +1 -1
  77. package/dist/schema/integer.d.ts +42 -0
  78. package/dist/schema/integer.d.ts.map +1 -1
  79. package/dist/schema/integer.js +36 -0
  80. package/dist/schema/integer.js.map +1 -1
  81. package/dist/schema/intersection.d.ts +54 -0
  82. package/dist/schema/intersection.d.ts.map +1 -1
  83. package/dist/schema/intersection.js +49 -0
  84. package/dist/schema/intersection.js.map +1 -1
  85. package/dist/schema/literal.d.ts +44 -0
  86. package/dist/schema/literal.d.ts.map +1 -1
  87. package/dist/schema/literal.js +44 -0
  88. package/dist/schema/literal.js.map +1 -1
  89. package/dist/schema/never.d.ts +42 -0
  90. package/dist/schema/never.d.ts.map +1 -1
  91. package/dist/schema/never.js +42 -0
  92. package/dist/schema/never.js.map +1 -1
  93. package/dist/schema/null.d.ts +29 -0
  94. package/dist/schema/null.d.ts.map +1 -1
  95. package/dist/schema/null.js +29 -0
  96. package/dist/schema/null.js.map +1 -1
  97. package/dist/schema/nullable.d.ts +41 -0
  98. package/dist/schema/nullable.d.ts.map +1 -1
  99. package/dist/schema/nullable.js +41 -0
  100. package/dist/schema/nullable.js.map +1 -1
  101. package/dist/schema/object.d.ts +56 -0
  102. package/dist/schema/object.d.ts.map +1 -1
  103. package/dist/schema/object.js +51 -0
  104. package/dist/schema/object.js.map +1 -1
  105. package/dist/schema/optional.d.ts +42 -0
  106. package/dist/schema/optional.d.ts.map +1 -1
  107. package/dist/schema/optional.js +42 -0
  108. package/dist/schema/optional.js.map +1 -1
  109. package/dist/schema/params.d.ts +89 -7
  110. package/dist/schema/params.d.ts.map +1 -1
  111. package/dist/schema/params.js +84 -10
  112. package/dist/schema/params.js.map +1 -1
  113. package/dist/schema/payload.d.ts +111 -15
  114. package/dist/schema/payload.d.ts.map +1 -1
  115. package/dist/schema/payload.js +70 -0
  116. package/dist/schema/payload.js.map +1 -1
  117. package/dist/schema/permission-set.d.ts +58 -0
  118. package/dist/schema/permission-set.d.ts.map +1 -1
  119. package/dist/schema/permission-set.js +50 -0
  120. package/dist/schema/permission-set.js.map +1 -1
  121. package/dist/schema/permission.d.ts +42 -0
  122. package/dist/schema/permission.d.ts.map +1 -1
  123. package/dist/schema/permission.js +39 -0
  124. package/dist/schema/permission.js.map +1 -1
  125. package/dist/schema/procedure.d.ts +64 -0
  126. package/dist/schema/procedure.d.ts.map +1 -1
  127. package/dist/schema/procedure.js +64 -0
  128. package/dist/schema/procedure.js.map +1 -1
  129. package/dist/schema/query.d.ts +55 -0
  130. package/dist/schema/query.d.ts.map +1 -1
  131. package/dist/schema/query.js +55 -0
  132. package/dist/schema/query.js.map +1 -1
  133. package/dist/schema/record.d.ts +63 -8
  134. package/dist/schema/record.d.ts.map +1 -1
  135. package/dist/schema/record.js +20 -0
  136. package/dist/schema/record.js.map +1 -1
  137. package/dist/schema/ref.d.ts +50 -0
  138. package/dist/schema/ref.d.ts.map +1 -1
  139. package/dist/schema/ref.js +17 -0
  140. package/dist/schema/ref.js.map +1 -1
  141. package/dist/schema/refine.d.ts +58 -9
  142. package/dist/schema/refine.d.ts.map +1 -1
  143. package/dist/schema/refine.js.map +1 -1
  144. package/dist/schema/regexp.d.ts +44 -0
  145. package/dist/schema/regexp.d.ts.map +1 -1
  146. package/dist/schema/regexp.js +44 -0
  147. package/dist/schema/regexp.js.map +1 -1
  148. package/dist/schema/string.d.ts +50 -0
  149. package/dist/schema/string.d.ts.map +1 -1
  150. package/dist/schema/string.js +41 -0
  151. package/dist/schema/string.js.map +1 -1
  152. package/dist/schema/subscription.d.ts +72 -2
  153. package/dist/schema/subscription.d.ts.map +1 -1
  154. package/dist/schema/subscription.js +59 -0
  155. package/dist/schema/subscription.js.map +1 -1
  156. package/dist/schema/token.d.ts +47 -0
  157. package/dist/schema/token.d.ts.map +1 -1
  158. package/dist/schema/token.js +47 -0
  159. package/dist/schema/token.js.map +1 -1
  160. package/dist/schema/typed-object.d.ts +62 -8
  161. package/dist/schema/typed-object.d.ts.map +1 -1
  162. package/dist/schema/typed-object.js +18 -0
  163. package/dist/schema/typed-object.js.map +1 -1
  164. package/dist/schema/typed-ref.d.ts +53 -0
  165. package/dist/schema/typed-ref.d.ts.map +1 -1
  166. package/dist/schema/typed-ref.js +15 -0
  167. package/dist/schema/typed-ref.js.map +1 -1
  168. package/dist/schema/typed-union.d.ts +50 -1
  169. package/dist/schema/typed-union.d.ts.map +1 -1
  170. package/dist/schema/typed-union.js +50 -1
  171. package/dist/schema/typed-union.js.map +1 -1
  172. package/dist/schema/union.d.ts +45 -0
  173. package/dist/schema/union.d.ts.map +1 -1
  174. package/dist/schema/union.js +40 -0
  175. package/dist/schema/union.js.map +1 -1
  176. package/dist/schema/unknown-object.d.ts +34 -0
  177. package/dist/schema/unknown-object.d.ts.map +1 -1
  178. package/dist/schema/unknown-object.js +31 -0
  179. package/dist/schema/unknown-object.js.map +1 -1
  180. package/dist/schema/unknown.d.ts +33 -0
  181. package/dist/schema/unknown.d.ts.map +1 -1
  182. package/dist/schema/unknown.js +33 -0
  183. package/dist/schema/unknown.js.map +1 -1
  184. package/dist/schema/with-default.d.ts +44 -0
  185. package/dist/schema/with-default.d.ts.map +1 -1
  186. package/dist/schema/with-default.js +44 -0
  187. package/dist/schema/with-default.js.map +1 -1
  188. package/package.json +4 -4
  189. package/src/core/$type.ts +150 -18
  190. package/src/core/record-key.ts +44 -0
  191. package/src/core/result.ts +86 -4
  192. package/src/core/schema.ts +236 -7
  193. package/src/core/string-format.ts +259 -13
  194. package/src/core/types.ts +91 -3
  195. package/src/core/validation-error.ts +60 -0
  196. package/src/core/validation-issue.ts +65 -0
  197. package/src/core/validator.ts +351 -10
  198. package/src/helpers.test.ts +110 -29
  199. package/src/helpers.ts +14 -14
  200. package/src/schema/array.test.ts +94 -79
  201. package/src/schema/array.ts +45 -0
  202. package/src/schema/blob.ts +46 -0
  203. package/src/schema/boolean.ts +28 -0
  204. package/src/schema/bytes.ts +38 -0
  205. package/src/schema/cid.ts +38 -0
  206. package/src/schema/custom.ts +66 -1
  207. package/src/schema/dict.ts +44 -0
  208. package/src/schema/discriminated-union.ts +58 -0
  209. package/src/schema/enum.ts +48 -0
  210. package/src/schema/integer.ts +42 -0
  211. package/src/schema/intersection.ts +54 -0
  212. package/src/schema/literal.ts +44 -0
  213. package/src/schema/never.ts +42 -0
  214. package/src/schema/null.ts +29 -0
  215. package/src/schema/nullable.ts +41 -0
  216. package/src/schema/object.ts +56 -0
  217. package/src/schema/optional.ts +42 -0
  218. package/src/schema/params.test.ts +58 -2
  219. package/src/schema/params.ts +124 -16
  220. package/src/schema/payload.test.ts +3 -3
  221. package/src/schema/payload.ts +142 -38
  222. package/src/schema/permission-set.ts +58 -0
  223. package/src/schema/permission.ts +42 -0
  224. package/src/schema/procedure.ts +64 -0
  225. package/src/schema/query.ts +55 -0
  226. package/src/schema/record.ts +63 -8
  227. package/src/schema/ref.ts +50 -0
  228. package/src/schema/refine.ts +58 -9
  229. package/src/schema/regexp.ts +44 -0
  230. package/src/schema/string.ts +50 -0
  231. package/src/schema/subscription.ts +72 -2
  232. package/src/schema/token.ts +47 -0
  233. package/src/schema/typed-object.ts +62 -8
  234. package/src/schema/typed-ref.ts +53 -0
  235. package/src/schema/typed-union.ts +55 -2
  236. package/src/schema/union.ts +45 -0
  237. package/src/schema/unknown-object.ts +34 -0
  238. package/src/schema/unknown.ts +33 -0
  239. package/src/schema/with-default.ts +44 -0
@@ -8,18 +8,40 @@ import {
8
8
  } from '../core.js'
9
9
  import { CustomAssertionContext } from './custom.js'
10
10
 
11
+ /**
12
+ * Configuration for a refinement check that validates a condition.
13
+ *
14
+ * @template T - The type being validated
15
+ * @property check - Function that returns true if the value passes the check
16
+ * @property message - Error message when the check fails
17
+ * @property path - Optional path to associate with the error
18
+ */
11
19
  export type RefinementCheck<T> = {
12
20
  check: (value: T, ctx: CustomAssertionContext) => boolean
13
21
  message: string
14
22
  path?: PropertyKey | readonly PropertyKey[]
15
23
  }
16
24
 
25
+ /**
26
+ * Configuration for a refinement assertion that narrows the type.
27
+ *
28
+ * @template T - The input type being validated
29
+ * @template Out - The narrowed output type
30
+ * @property check - Type guard function that narrows the type
31
+ * @property message - Error message when the assertion fails
32
+ * @property path - Optional path to associate with the error
33
+ */
17
34
  export type RefinementAssertion<T, Out extends T> = {
18
35
  check: (this: null, value: T, ctx: CustomAssertionContext) => value is Out
19
36
  message: string
20
37
  path?: PropertyKey | readonly PropertyKey[]
21
38
  }
22
39
 
40
+ /**
41
+ * Infers the input type from a refinement configuration.
42
+ *
43
+ * @template R - The refinement type
44
+ */
23
45
  export type InferRefinement<R> =
24
46
  R extends RefinementCheck<infer T>
25
47
  ? T
@@ -27,25 +49,52 @@ export type InferRefinement<R> =
27
49
  ? T
28
50
  : never
29
51
 
52
+ /**
53
+ * Union type of refinement check or assertion.
54
+ *
55
+ * @template T - The input type being validated
56
+ * @template Out - The output type (same as T for checks, narrowed for assertions)
57
+ */
30
58
  export type Refinement<T = any, Out extends T = T> =
31
59
  | RefinementCheck<T>
32
60
  | RefinementAssertion<T, Out>
33
61
 
34
62
  /**
35
- * Create a refined schema based on an existing schema and a refinement check.
63
+ * Creates a refined schema by adding additional validation constraints.
36
64
  *
37
- * @param schema - The base schema to refine.
38
- * @param refinement - The refinement check to apply.
39
- * @returns A new schema that includes the refinement.
40
- * @example
65
+ * Wraps an existing schema with an additional check function. The base schema
66
+ * is validated first, then the refinement check is applied to the result.
41
67
  *
68
+ * @param schema - The base schema to refine
69
+ * @param refinement - The refinement check or assertion to apply
70
+ * @returns A new schema that includes the refinement
71
+ *
72
+ * @example
42
73
  * ```ts
43
- * const PositiveInt = refine(l.integer(), {
74
+ * // Simple check refinement
75
+ * const positiveInt = l.refine(l.integer(), {
44
76
  * check: (value) => value > 0,
45
- * message: 'Value must be a positive integer',
77
+ * message: 'Value must be positive',
46
78
  * })
47
- * const result = PositiveInt.validate(-5)
48
- * // result.success === false
79
+ *
80
+ * positiveInt.parse(5) // 5
81
+ * positiveInt.parse(-1) // throws
82
+ *
83
+ * // Type-narrowing assertion
84
+ * const nonEmptyString = l.refine(l.string(), {
85
+ * check: (value): value is string & { length: number } => value.length > 0,
86
+ * message: 'String must not be empty',
87
+ * })
88
+ *
89
+ * // With custom path for nested errors
90
+ * const validDateRange = l.refine(
91
+ * l.object({ start: l.string(), end: l.string() }),
92
+ * {
93
+ * check: (v) => new Date(v.start) < new Date(v.end),
94
+ * message: 'Start date must be before end date',
95
+ * path: ['end'],
96
+ * }
97
+ * )
49
98
  * ```
50
99
  */
51
100
  export function refine<
@@ -1,5 +1,20 @@
1
1
  import { Schema, ValidationContext } from '../core.js'
2
2
 
3
+ /**
4
+ * Schema for validating strings against a regular expression pattern.
5
+ *
6
+ * Validates that the input is a string and matches the provided pattern.
7
+ * The pattern is tested using RegExp.test().
8
+ *
9
+ * @template TValue - The string type (can be narrowed with branded types)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const schema = new RegexpSchema(/^[a-z]+$/)
14
+ * schema.validate('hello') // success
15
+ * schema.validate('Hello') // fails - uppercase not allowed
16
+ * ```
17
+ */
3
18
  export class RegexpSchema<
4
19
  TValue extends string = string,
5
20
  > extends Schema<TValue> {
@@ -20,6 +35,35 @@ export class RegexpSchema<
20
35
  }
21
36
  }
22
37
 
38
+ /**
39
+ * Creates a regexp schema that validates strings against a pattern.
40
+ *
41
+ * Useful for custom string formats not covered by the built-in format
42
+ * validators.
43
+ *
44
+ * @param pattern - Regular expression pattern to match against
45
+ * @returns A new {@link RegexpSchema} instance
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * // Simple pattern
50
+ * const slugSchema = l.regexp(/^[a-z0-9-]+$/)
51
+ *
52
+ * // With anchors for exact match
53
+ * const uuidSchema = l.regexp(
54
+ * /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
55
+ * )
56
+ *
57
+ * // Semantic versioning
58
+ * const semverSchema = l.regexp(/^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/)
59
+ *
60
+ * // Use in object
61
+ * const configSchema = l.object({
62
+ * name: l.regexp(/^[a-z][a-z0-9-]*$/), // kebab-case identifier
63
+ * version: semverSchema,
64
+ * })
65
+ * ```
66
+ */
23
67
  /*@__NO_SIDE_EFFECTS__*/
24
68
  export function regexp<TInput extends string = string>(pattern: RegExp) {
25
69
  return new RegexpSchema<TInput>(pattern)
@@ -9,6 +9,15 @@ import {
9
9
  import { memoizedOptions } from '../util/memoize.js'
10
10
  import { TokenSchema } from './token.js'
11
11
 
12
+ /**
13
+ * Configuration options for string schema validation.
14
+ *
15
+ * @property format - Expected string format (e.g., 'datetime', 'uri', 'at-uri', 'did', 'handle', 'nsid', 'cid', 'tid', 'record-key', 'at-identifier', 'language')
16
+ * @property minLength - Minimum length in UTF-8 bytes
17
+ * @property maxLength - Maximum length in UTF-8 bytes
18
+ * @property minGraphemes - Minimum number of grapheme clusters
19
+ * @property maxGraphemes - Maximum number of grapheme clusters
20
+ */
12
21
  export type StringSchemaOptions = {
13
22
  format?: StringFormat
14
23
  minLength?: number
@@ -17,6 +26,20 @@ export type StringSchemaOptions = {
17
26
  maxGraphemes?: number
18
27
  }
19
28
 
29
+ /**
30
+ * Schema for validating string values with optional format and length constraints.
31
+ *
32
+ * Supports various string formats defined in the Lexicon specification, as well as
33
+ * length constraints measured in UTF-8 bytes or grapheme clusters.
34
+ *
35
+ * @template TOptions - The configuration options type
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const schema = new StringSchema({ format: 'datetime', maxLength: 64 })
40
+ * const result = schema.validate('2024-01-15T10:30:00Z')
41
+ * ```
42
+ */
20
43
  export class StringSchema<
21
44
  const TOptions extends StringSchemaOptions = StringSchemaOptions,
22
45
  > extends Schema<
@@ -123,6 +146,33 @@ export function coerceToString(input: unknown): string | null {
123
146
  }
124
147
  }
125
148
 
149
+ /**
150
+ * Creates a string schema with optional format and length constraints.
151
+ *
152
+ * Strings can be validated against various formats (datetime, uri, did, handle, etc.)
153
+ * and constrained by length in UTF-8 bytes or grapheme clusters.
154
+ *
155
+ * @param options - Optional configuration for format and length constraints
156
+ * @returns A new {@link StringSchema} instance
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * // Basic string
161
+ * const nameSchema = l.string()
162
+ *
163
+ * // With format validation
164
+ * const dateSchema = l.string({ format: 'datetime' })
165
+ *
166
+ * // With length constraints (UTF-8 bytes)
167
+ * const bioSchema = l.string({ maxLength: 256 })
168
+ *
169
+ * // With grapheme constraints (user-perceived characters)
170
+ * const displayNameSchema = l.string({ maxGraphemes: 64 })
171
+ *
172
+ * // Combining constraints
173
+ * const handleSchema = l.string({ format: 'handle', minLength: 3, maxLength: 253 })
174
+ * ```
175
+ */
126
176
  export const string = /*#__PURE__*/ memoizedOptions(function <
127
177
  const O extends StringSchemaOptions = NonNullable<unknown>,
128
178
  >(options?: StringSchemaOptions & O) {
@@ -1,18 +1,51 @@
1
+ import { LexValue } from '@atproto/lex-data'
1
2
  import { Infer, NsidString, Schema } from '../core.js'
2
3
  import { ParamsSchema } from './params.js'
3
4
 
5
+ /**
6
+ * Infers the parameters type from a Subscription definition.
7
+ *
8
+ * @template S - The Subscription type
9
+ */
4
10
  export type InferSubscriptionParameters<S extends Subscription> = Infer<
5
11
  S['parameters']
6
12
  >
7
13
 
14
+ /**
15
+ * Infers the message type from a Subscription definition.
16
+ *
17
+ * @template S - The Subscription type
18
+ */
8
19
  export type InferSubscriptionMessage<S extends Subscription> = Infer<
9
20
  S['message']
10
21
  >
11
22
 
23
+ /**
24
+ * Represents a Lexicon subscription (WebSocket) endpoint definition.
25
+ *
26
+ * Subscriptions are real-time event streams delivered over WebSocket.
27
+ * They have parameters for initializing the connection and a message
28
+ * schema for validating incoming events.
29
+ *
30
+ * @template TNsid - The NSID identifying this subscription
31
+ * @template TParameters - The connection parameters schema type
32
+ * @template TMessage - The message schema type
33
+ * @template TErrors - Array of error type strings, or undefined
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const firehose = new Subscription(
38
+ * 'com.atproto.sync.subscribeRepos',
39
+ * l.params({ cursor: l.optional(l.integer()) }),
40
+ * repoEventSchema,
41
+ * ['FutureCursor']
42
+ * )
43
+ * ```
44
+ */
12
45
  export class Subscription<
13
46
  const TNsid extends NsidString = NsidString,
14
47
  const TParameters extends ParamsSchema = ParamsSchema,
15
- const TMessage extends Schema = Schema,
48
+ const TMessage extends Schema<LexValue> = Schema<LexValue>,
16
49
  const TErrors extends undefined | readonly string[] =
17
50
  | undefined
18
51
  | readonly string[],
@@ -27,11 +60,48 @@ export class Subscription<
27
60
  ) {}
28
61
  }
29
62
 
63
+ /**
64
+ * Creates a subscription definition for a Lexicon WebSocket endpoint.
65
+ *
66
+ * Subscriptions enable real-time event streaming. The connection is
67
+ * initialized with parameters, and the server sends messages matching
68
+ * the message schema.
69
+ *
70
+ * @param nsid - The NSID identifying this subscription endpoint
71
+ * @param parameters - Schema for connection parameters
72
+ * @param message - Schema for validating incoming messages
73
+ * @param errors - Optional array of error type strings
74
+ * @returns A new {@link Subscription} instance
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // Repository event stream
79
+ * const subscribeRepos = l.subscription(
80
+ * 'com.atproto.sync.subscribeRepos',
81
+ * l.params({
82
+ * cursor: l.optional(l.integer()),
83
+ * }),
84
+ * l.typedUnion([
85
+ * l.typedRef(() => commitEventSchema),
86
+ * l.typedRef(() => handleEventSchema),
87
+ * l.typedRef(() => identityEventSchema),
88
+ * ], false),
89
+ * ['FutureCursor', 'ConsumerTooSlow'],
90
+ * )
91
+ *
92
+ * // Label stream
93
+ * const subscribeLabels = l.subscription(
94
+ * 'com.atproto.label.subscribeLabels',
95
+ * l.params({ cursor: l.optional(l.integer()) }),
96
+ * labelEventSchema,
97
+ * )
98
+ * ```
99
+ */
30
100
  /*@__NO_SIDE_EFFECTS__*/
31
101
  export function subscription<
32
102
  const N extends NsidString,
33
103
  const P extends ParamsSchema,
34
- const M extends Schema,
104
+ const M extends Schema<LexValue>,
35
105
  const E extends undefined | readonly string[] = undefined,
36
106
  >(nsid: N, parameters: P, message: M, errors: E = undefined as E) {
37
107
  return new Subscription<N, P, M, E>(nsid, parameters, message, errors)
@@ -1,5 +1,20 @@
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> {
@@ -37,6 +52,38 @@ export class TokenSchema<
37
52
  }
38
53
  }
39
54
 
55
+ /**
56
+ * Creates a token schema for Lexicon named constants.
57
+ *
58
+ * Tokens are used in Lexicon as named constants or enum-like values.
59
+ * The token instance can be used both as a schema validator and as
60
+ * the token value itself (it serializes to its string value).
61
+ *
62
+ * @param nsid - The NSID part of the token
63
+ * @param hash - The hash part of the token (defaults to 'main')
64
+ * @returns A new {@link TokenSchema} instance
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // Define tokens
69
+ * const requestLess = l.token('app.bsky.feed.defs', 'requestLess')
70
+ * const requestMore = l.token('app.bsky.feed.defs', 'requestMore')
71
+ *
72
+ * // Use as a value
73
+ * console.log(requestLess.toString()) // 'app.bsky.feed.defs#requestLess'
74
+ *
75
+ * // Use in union for validation
76
+ * const feedbackSchema = l.union([requestLess, requestMore])
77
+ *
78
+ * // Validate
79
+ * feedbackSchema.parse('app.bsky.feed.defs#requestLess') // success
80
+ *
81
+ * // Token instances can be used as values in other schemas
82
+ * const feedbackRequest = l.object({
83
+ * feedback: requestLess, // Accepts the token value
84
+ * })
85
+ * ```
86
+ */
40
87
  /*@__NO_SIDE_EFFECTS__*/
41
88
  export function token<
42
89
  const N extends NsidString,
@@ -15,6 +15,24 @@ import {
15
15
  Validator,
16
16
  } from '../core.js'
17
17
 
18
+ /**
19
+ * Schema for typed objects in Lexicon unions.
20
+ *
21
+ * Typed objects have a `$type` field that identifies which variant they are
22
+ * in a union. The `$type` can be omitted in input (it's implicit), but if
23
+ * present, it must match the expected value.
24
+ *
25
+ * @template TType - The $type string literal type
26
+ * @template TShape - The validator type for the object's shape
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const schema = new TypedObjectSchema(
31
+ * 'app.bsky.embed.images#view',
32
+ * l.object({ images: l.array(imageSchema) })
33
+ * )
34
+ * ```
35
+ */
18
36
  export class TypedObjectSchema<
19
37
  const TType extends $Type = $Type,
20
38
  const TShape extends Validator<{ [k: string]: unknown }> = any,
@@ -72,15 +90,51 @@ export class TypedObjectSchema<
72
90
  }
73
91
 
74
92
  /**
93
+ * Creates a typed object schema for use in Lexicon unions.
94
+ *
95
+ * Typed objects are identified by their `$type` field, which combines an NSID
96
+ * and a hash (e.g., 'app.bsky.embed.images#view'). Used for union variants.
97
+ *
75
98
  * 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.
99
+ * - One that infers the type from arguments (no circular reference support)
100
+ * - One with explicit interface for codegen with circular references
101
+ *
102
+ * @param nsid - The NSID part of the type (e.g., 'app.bsky.embed.images')
103
+ * @param hash - The hash part of the type (e.g., 'view'), defaults to 'main'
104
+ * @param validator - Schema for validating the object properties
105
+ * @returns A new {@link TypedObjectSchema} instance
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * // Image embed view
110
+ * const imageViewSchema = l.typedObject(
111
+ * 'app.bsky.embed.images',
112
+ * 'view',
113
+ * l.object({
114
+ * images: l.array(l.object({
115
+ * thumb: l.string(),
116
+ * fullsize: l.string(),
117
+ * alt: l.string(),
118
+ * })),
119
+ * })
120
+ * )
121
+ *
122
+ * // Main type (hash defaults to 'main')
123
+ * const postViewSchema = l.typedObject(
124
+ * 'app.bsky.feed.defs',
125
+ * 'postView',
126
+ * l.object({ uri: l.string(), cid: l.string(), author: authorSchema })
127
+ * )
128
+ *
129
+ * // Use $isTypeOf to narrow union types
130
+ * if (imageViewSchema.$isTypeOf(embed)) {
131
+ * // embed is narrowed to image view type
132
+ * }
133
+ *
134
+ * // Use $build to construct typed objects
135
+ * const view = imageViewSchema.$build({ images: [...] })
136
+ * // view.$type === 'app.bsky.embed.images#view'
137
+ * ```
84
138
  */
85
139
  export function typedObject<
86
140
  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,9 +22,29 @@ 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<
@@ -54,6 +82,31 @@ export class TypedRefSchema<
54
82
  }
55
83
  }
56
84
 
85
+ /**
86
+ * Creates a reference to a typed object schema for use in typed unions.
87
+ *
88
+ * Unlike regular `ref()`, this requires the referenced schema to have a
89
+ * `$type` property, and validates that the input's `$type` matches.
90
+ *
91
+ * @param get - Function that returns the typed object validator
92
+ * @returns A new {@link TypedRefSchema} instance
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * // Reference to image embed view
97
+ * const imageRef = l.typedRef(() => imageViewSchema)
98
+ *
99
+ * // Use in a typed union
100
+ * const embedUnion = l.typedUnion([
101
+ * l.typedRef(() => imageViewSchema),
102
+ * l.typedRef(() => videoViewSchema),
103
+ * l.typedRef(() => externalViewSchema),
104
+ * ], true) // closed union
105
+ *
106
+ * // The $type is accessible on the ref
107
+ * console.log(imageRef.$type) // 'app.bsky.embed.images#view'
108
+ * ```
109
+ */
57
110
  /*@__NO_SIDE_EFFECTS__*/
58
111
  export function typedRef<const TValidator extends TypedObjectValidator>(
59
112
  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
@@ -26,7 +48,8 @@ export class TypedUnionSchema<
26
48
  ) {
27
49
  // @NOTE In order to avoid circular dependency issues, we don't access the
28
50
  // refs's schema (or $type) here. Instead, we access them lazily when first
29
- // needed.
51
+ // needed. The biggest issue with this strategy is that we can't throw
52
+ // early if the refs contain multiple refs with the same $type.
30
53
 
31
54
  super()
32
55
  }
@@ -66,6 +89,36 @@ export class TypedUnionSchema<
66
89
  }
67
90
  }
68
91
 
92
+ /**
93
+ * Creates a typed union schema for Lexicon unions.
94
+ *
95
+ * Typed unions discriminate variants by their `$type` field. Can be open
96
+ * (accepts unknown types, useful for extensibility) or closed (strict).
97
+ *
98
+ * @param refs - Array of typed refs for the union variants
99
+ * @param closed - Whether to reject unknown $type values
100
+ * @returns A new {@link TypedUnionSchema} instance
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // Closed union - only accepts known types
105
+ * const embedSchema = l.typedUnion([
106
+ * l.typedRef(() => imageViewSchema),
107
+ * l.typedRef(() => videoViewSchema),
108
+ * l.typedRef(() => externalViewSchema),
109
+ * ], true)
110
+ *
111
+ * // Open union - accepts unknown types for forward compatibility
112
+ * const feedItemSchema = l.typedUnion([
113
+ * l.typedRef(() => postSchema),
114
+ * l.typedRef(() => repostSchema),
115
+ * ], false) // unknown types pass through
116
+ *
117
+ * // Get all known $types
118
+ * console.log(embedSchema.$types)
119
+ * // ['app.bsky.embed.images#view', 'app.bsky.embed.video#view', ...]
120
+ * ```
121
+ */
69
122
  /*@__NO_SIDE_EFFECTS__*/
70
123
  export function typedUnion<
71
124
  const R extends readonly TypedRefSchema[],
@@ -8,8 +8,29 @@ 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<
@@ -34,6 +55,30 @@ export class UnionSchema<
34
55
  }
35
56
  }
36
57
 
58
+ /**
59
+ * Creates a union schema that accepts values matching any of the provided schemas.
60
+ *
61
+ * Validators are tried in order. Use `discriminatedUnion()` for better
62
+ * performance when discriminating on a known property.
63
+ *
64
+ * @param validators - Non-empty array of validators to try
65
+ * @returns A new {@link UnionSchema} instance
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // String or number
70
+ * const stringOrNumber = l.union([l.string(), l.integer()])
71
+ *
72
+ * // Nullable value
73
+ * const nullableString = l.union([l.string(), l.null()])
74
+ *
75
+ * // Multiple object types
76
+ * const mediaSchema = l.union([
77
+ * l.object({ type: l.literal('image'), url: l.string() }),
78
+ * l.object({ type: l.literal('video'), url: l.string(), duration: l.integer() }),
79
+ * ])
80
+ * ```
81
+ */
37
82
  /*@__NO_SIDE_EFFECTS__*/
38
83
  export function union<const TValidators extends UnionSchemaValidators>(
39
84
  validators: TValidators,