@atproto/lex-schema 0.0.11 → 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 (238) hide show
  1. package/CHANGELOG.md +19 -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 +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 +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 +9 -24
  38. package/dist/helpers.d.ts.map +1 -1
  39. package/dist/helpers.js.map +1 -1
  40. package/dist/schema/array.d.ts +45 -0
  41. package/dist/schema/array.d.ts.map +1 -1
  42. package/dist/schema/array.js +14 -0
  43. package/dist/schema/array.js.map +1 -1
  44. package/dist/schema/blob.d.ts +46 -0
  45. package/dist/schema/blob.d.ts.map +1 -1
  46. package/dist/schema/blob.js +39 -0
  47. package/dist/schema/blob.js.map +1 -1
  48. package/dist/schema/boolean.d.ts +28 -0
  49. package/dist/schema/boolean.d.ts.map +1 -1
  50. package/dist/schema/boolean.js +28 -0
  51. package/dist/schema/boolean.js.map +1 -1
  52. package/dist/schema/bytes.d.ts +38 -0
  53. package/dist/schema/bytes.d.ts.map +1 -1
  54. package/dist/schema/bytes.js +32 -0
  55. package/dist/schema/bytes.js.map +1 -1
  56. package/dist/schema/cid.d.ts +38 -0
  57. package/dist/schema/cid.d.ts.map +1 -1
  58. package/dist/schema/cid.js +33 -0
  59. package/dist/schema/cid.js.map +1 -1
  60. package/dist/schema/custom.d.ts +66 -1
  61. package/dist/schema/custom.d.ts.map +1 -1
  62. package/dist/schema/custom.js +54 -0
  63. package/dist/schema/custom.js.map +1 -1
  64. package/dist/schema/dict.d.ts +44 -0
  65. package/dist/schema/dict.d.ts.map +1 -1
  66. package/dist/schema/dict.js +44 -0
  67. package/dist/schema/dict.js.map +1 -1
  68. package/dist/schema/discriminated-union.d.ts +58 -0
  69. package/dist/schema/discriminated-union.d.ts.map +1 -1
  70. package/dist/schema/discriminated-union.js +45 -0
  71. package/dist/schema/discriminated-union.js.map +1 -1
  72. package/dist/schema/enum.d.ts +48 -0
  73. package/dist/schema/enum.d.ts.map +1 -1
  74. package/dist/schema/enum.js +48 -0
  75. package/dist/schema/enum.js.map +1 -1
  76. package/dist/schema/integer.d.ts +42 -0
  77. package/dist/schema/integer.d.ts.map +1 -1
  78. package/dist/schema/integer.js +36 -0
  79. package/dist/schema/integer.js.map +1 -1
  80. package/dist/schema/intersection.d.ts +54 -0
  81. package/dist/schema/intersection.d.ts.map +1 -1
  82. package/dist/schema/intersection.js +49 -0
  83. package/dist/schema/intersection.js.map +1 -1
  84. package/dist/schema/literal.d.ts +44 -0
  85. package/dist/schema/literal.d.ts.map +1 -1
  86. package/dist/schema/literal.js +44 -0
  87. package/dist/schema/literal.js.map +1 -1
  88. package/dist/schema/never.d.ts +42 -0
  89. package/dist/schema/never.d.ts.map +1 -1
  90. package/dist/schema/never.js +42 -0
  91. package/dist/schema/never.js.map +1 -1
  92. package/dist/schema/null.d.ts +29 -0
  93. package/dist/schema/null.d.ts.map +1 -1
  94. package/dist/schema/null.js +29 -0
  95. package/dist/schema/null.js.map +1 -1
  96. package/dist/schema/nullable.d.ts +41 -0
  97. package/dist/schema/nullable.d.ts.map +1 -1
  98. package/dist/schema/nullable.js +41 -0
  99. package/dist/schema/nullable.js.map +1 -1
  100. package/dist/schema/object.d.ts +56 -0
  101. package/dist/schema/object.d.ts.map +1 -1
  102. package/dist/schema/object.js +51 -0
  103. package/dist/schema/object.js.map +1 -1
  104. package/dist/schema/optional.d.ts +42 -0
  105. package/dist/schema/optional.d.ts.map +1 -1
  106. package/dist/schema/optional.js +42 -0
  107. package/dist/schema/optional.js.map +1 -1
  108. package/dist/schema/params.d.ts +90 -10
  109. package/dist/schema/params.d.ts.map +1 -1
  110. package/dist/schema/params.js +84 -10
  111. package/dist/schema/params.js.map +1 -1
  112. package/dist/schema/payload.d.ts +111 -15
  113. package/dist/schema/payload.d.ts.map +1 -1
  114. package/dist/schema/payload.js +70 -0
  115. package/dist/schema/payload.js.map +1 -1
  116. package/dist/schema/permission-set.d.ts +58 -0
  117. package/dist/schema/permission-set.d.ts.map +1 -1
  118. package/dist/schema/permission-set.js +50 -0
  119. package/dist/schema/permission-set.js.map +1 -1
  120. package/dist/schema/permission.d.ts +42 -0
  121. package/dist/schema/permission.d.ts.map +1 -1
  122. package/dist/schema/permission.js +39 -0
  123. package/dist/schema/permission.js.map +1 -1
  124. package/dist/schema/procedure.d.ts +64 -0
  125. package/dist/schema/procedure.d.ts.map +1 -1
  126. package/dist/schema/procedure.js +64 -0
  127. package/dist/schema/procedure.js.map +1 -1
  128. package/dist/schema/query.d.ts +55 -0
  129. package/dist/schema/query.d.ts.map +1 -1
  130. package/dist/schema/query.js +55 -0
  131. package/dist/schema/query.js.map +1 -1
  132. package/dist/schema/record.d.ts +63 -8
  133. package/dist/schema/record.d.ts.map +1 -1
  134. package/dist/schema/record.js +20 -0
  135. package/dist/schema/record.js.map +1 -1
  136. package/dist/schema/ref.d.ts +50 -0
  137. package/dist/schema/ref.d.ts.map +1 -1
  138. package/dist/schema/ref.js +17 -0
  139. package/dist/schema/ref.js.map +1 -1
  140. package/dist/schema/refine.d.ts +58 -9
  141. package/dist/schema/refine.d.ts.map +1 -1
  142. package/dist/schema/refine.js.map +1 -1
  143. package/dist/schema/regexp.d.ts +44 -0
  144. package/dist/schema/regexp.d.ts.map +1 -1
  145. package/dist/schema/regexp.js +44 -0
  146. package/dist/schema/regexp.js.map +1 -1
  147. package/dist/schema/string.d.ts +50 -0
  148. package/dist/schema/string.d.ts.map +1 -1
  149. package/dist/schema/string.js +41 -0
  150. package/dist/schema/string.js.map +1 -1
  151. package/dist/schema/subscription.d.ts +72 -2
  152. package/dist/schema/subscription.d.ts.map +1 -1
  153. package/dist/schema/subscription.js +59 -0
  154. package/dist/schema/subscription.js.map +1 -1
  155. package/dist/schema/token.d.ts +47 -0
  156. package/dist/schema/token.d.ts.map +1 -1
  157. package/dist/schema/token.js +47 -0
  158. package/dist/schema/token.js.map +1 -1
  159. package/dist/schema/typed-object.d.ts +62 -8
  160. package/dist/schema/typed-object.d.ts.map +1 -1
  161. package/dist/schema/typed-object.js +18 -0
  162. package/dist/schema/typed-object.js.map +1 -1
  163. package/dist/schema/typed-ref.d.ts +53 -0
  164. package/dist/schema/typed-ref.d.ts.map +1 -1
  165. package/dist/schema/typed-ref.js +15 -0
  166. package/dist/schema/typed-ref.js.map +1 -1
  167. package/dist/schema/typed-union.d.ts +50 -1
  168. package/dist/schema/typed-union.d.ts.map +1 -1
  169. package/dist/schema/typed-union.js +50 -1
  170. package/dist/schema/typed-union.js.map +1 -1
  171. package/dist/schema/union.d.ts +45 -0
  172. package/dist/schema/union.d.ts.map +1 -1
  173. package/dist/schema/union.js +40 -0
  174. package/dist/schema/union.js.map +1 -1
  175. package/dist/schema/unknown-object.d.ts +34 -0
  176. package/dist/schema/unknown-object.d.ts.map +1 -1
  177. package/dist/schema/unknown-object.js +31 -0
  178. package/dist/schema/unknown-object.js.map +1 -1
  179. package/dist/schema/unknown.d.ts +33 -0
  180. package/dist/schema/unknown.d.ts.map +1 -1
  181. package/dist/schema/unknown.js +33 -0
  182. package/dist/schema/unknown.js.map +1 -1
  183. package/dist/schema/with-default.d.ts +44 -0
  184. package/dist/schema/with-default.d.ts.map +1 -1
  185. package/dist/schema/with-default.js +44 -0
  186. package/dist/schema/with-default.js.map +1 -1
  187. package/package.json +3 -3
  188. package/src/core/$type.ts +150 -18
  189. package/src/core/record-key.ts +44 -0
  190. package/src/core/result.ts +86 -4
  191. package/src/core/schema.ts +236 -7
  192. package/src/core/string-format.ts +259 -13
  193. package/src/core/types.ts +91 -3
  194. package/src/core/validation-error.ts +60 -0
  195. package/src/core/validation-issue.ts +65 -0
  196. package/src/core/validator.ts +351 -10
  197. package/src/helpers.test.ts +110 -29
  198. package/src/helpers.ts +9 -14
  199. package/src/schema/array.test.ts +94 -79
  200. package/src/schema/array.ts +45 -0
  201. package/src/schema/blob.ts +46 -0
  202. package/src/schema/boolean.ts +28 -0
  203. package/src/schema/bytes.ts +38 -0
  204. package/src/schema/cid.ts +38 -0
  205. package/src/schema/custom.ts +66 -1
  206. package/src/schema/dict.ts +44 -0
  207. package/src/schema/discriminated-union.ts +58 -0
  208. package/src/schema/enum.ts +48 -0
  209. package/src/schema/integer.ts +42 -0
  210. package/src/schema/intersection.ts +54 -0
  211. package/src/schema/literal.ts +44 -0
  212. package/src/schema/never.ts +42 -0
  213. package/src/schema/null.ts +29 -0
  214. package/src/schema/nullable.ts +41 -0
  215. package/src/schema/object.ts +56 -0
  216. package/src/schema/optional.ts +42 -0
  217. package/src/schema/params.test.ts +58 -2
  218. package/src/schema/params.ts +125 -19
  219. package/src/schema/payload.test.ts +3 -3
  220. package/src/schema/payload.ts +142 -38
  221. package/src/schema/permission-set.ts +58 -0
  222. package/src/schema/permission.ts +42 -0
  223. package/src/schema/procedure.ts +64 -0
  224. package/src/schema/query.ts +55 -0
  225. package/src/schema/record.ts +63 -8
  226. package/src/schema/ref.ts +50 -0
  227. package/src/schema/refine.ts +58 -9
  228. package/src/schema/regexp.ts +44 -0
  229. package/src/schema/string.ts +50 -0
  230. package/src/schema/subscription.ts +72 -2
  231. package/src/schema/token.ts +47 -0
  232. package/src/schema/typed-object.ts +62 -8
  233. package/src/schema/typed-ref.ts +53 -0
  234. package/src/schema/typed-union.ts +55 -2
  235. package/src/schema/union.ts +45 -0
  236. package/src/schema/unknown-object.ts +34 -0
  237. package/src/schema/unknown.ts +33 -0
  238. package/src/schema/with-default.ts +44 -0
@@ -9,10 +9,15 @@ import { DictSchema } from './dict.js'
9
9
  import { ObjectSchema } from './object.js'
10
10
 
11
11
  /**
12
+ * Type utility for computing the intersection of two object types.
13
+ *
12
14
  * Allows to more accurately represent the intersection of two object types
13
15
  * where both types may share some keys, and one of them uses an index
14
16
  * signature.
15
17
  *
18
+ * @template A - First object type (typically from ObjectSchema)
19
+ * @template B - Second object type (typically from DictSchema)
20
+ *
16
21
  * @see {@link https://www.typescriptlang.org/play/?#code/C4TwDgpgBAglC8UDeUBmB7dAuKByARgIYBOuUAvlAGTJQDaA+lAJYB2UAzsMWwOYC6OVgFcAtvgjEKAKGkATCAGMANiWiL0rLlEI4YsjVuBQA1hBA4uPVrwRQARBnT2Dm7QDdCy4dESE6ZiD8UAD0IVAi4pJQABQcABbowspyUBIORMT2AJSyEAAeYOjExqCQUACSrMCSHErAzJoAPNJQsFAFNaxyHFAASkrFck1WfAA0UMKsJqzoAO6sAHxjrVAAQh35XT39g8TDozYTUzPzSyuLdqtwVKttMYHoqO00j88bnRDdvawQ7pJ3NpQAD860BbRwSHBQLadAA0ix2G91oJ1vDggAfWABcxPF5QOH8aFtci5aRlaAwVDMfIQVKIKo1Yh1RQNZq0Jw4AgkMjkCYoRiIzjcPioyISKTkRayBQqNRQQzaQgAMRpdL01NpclcRignm8EFVWrsKrVchxQVC4XF0SxmSAA Playground link}
17
22
  */
18
23
  export type Intersect<A, B> = B[keyof B] extends never
@@ -24,6 +29,26 @@ export type Intersect<A, B> = B[keyof B] extends never
24
29
  // index signature could return a value from either A or B
25
30
  A & { [K in keyof B]: B[K] | A[keyof A & K] }
26
31
 
32
+ /**
33
+ * Schema for combining an object schema with a dictionary schema.
34
+ *
35
+ * Validates that the input matches both the fixed object shape and allows
36
+ * additional properties that match the dictionary schema. Properties defined
37
+ * in the object schema are validated by the object, and remaining properties
38
+ * are validated by the dictionary.
39
+ *
40
+ * @template Left - The ObjectSchema type for fixed properties
41
+ * @template Right - The DictSchema type for additional properties
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const schema = new IntersectionSchema(
46
+ * l.object({ name: l.string() }),
47
+ * l.dict(l.string(), l.integer())
48
+ * )
49
+ * // Validates: { name: 'test', score: 100, count: 5 }
50
+ * ```
51
+ */
27
52
  export class IntersectionSchema<
28
53
  const Left extends ObjectSchema = any,
29
54
  const Right extends DictSchema = any,
@@ -48,6 +73,35 @@ export class IntersectionSchema<
48
73
  }
49
74
  }
50
75
 
76
+ /**
77
+ * Creates an intersection schema combining fixed object properties with dynamic dictionary properties.
78
+ *
79
+ * Useful for objects that have a known set of properties plus additional
80
+ * arbitrary properties that follow a pattern.
81
+ *
82
+ * @param left - Object schema defining the fixed, known properties
83
+ * @param right - Dictionary schema for validating additional properties
84
+ * @returns A new {@link IntersectionSchema} instance
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * // Object with fixed and dynamic properties
89
+ * const configSchema = l.intersection(
90
+ * l.object({
91
+ * version: l.integer(),
92
+ * name: l.string(),
93
+ * }),
94
+ * l.dict(l.string(), l.string()) // Additional string properties
95
+ * )
96
+ *
97
+ * configSchema.parse({
98
+ * version: 1,
99
+ * name: 'my-config',
100
+ * customField: 'value',
101
+ * anotherField: 'another',
102
+ * })
103
+ * ```
104
+ */
51
105
  /*@__NO_SIDE_EFFECTS__*/
52
106
  export function intersection<
53
107
  const Left extends ObjectSchema,
@@ -1,5 +1,20 @@
1
1
  import { Schema, ValidationContext } from '../core.js'
2
2
 
3
+ /**
4
+ * Schema that only accepts a specific literal value.
5
+ *
6
+ * Validates that the input is exactly equal to the specified value using
7
+ * strict equality (===).
8
+ *
9
+ * @template TValue - The literal type (null, string, number, or boolean)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const schema = new LiteralSchema('admin')
14
+ * schema.validate('admin') // success
15
+ * schema.validate('user') // fails
16
+ * ```
17
+ */
3
18
  export class LiteralSchema<
4
19
  const TValue extends null | string | number | boolean,
5
20
  > extends Schema<TValue> {
@@ -16,6 +31,35 @@ export class LiteralSchema<
16
31
  }
17
32
  }
18
33
 
34
+ /**
35
+ * Creates a literal schema that only accepts the exact specified value.
36
+ *
37
+ * Useful for discriminator fields in unions, constant values, or type narrowing.
38
+ *
39
+ * @param value - The exact value that must be matched
40
+ * @returns A new {@link LiteralSchema} instance
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // String literal
45
+ * const roleSchema = l.literal('admin')
46
+ *
47
+ * // Number literal
48
+ * const versionSchema = l.literal(1)
49
+ *
50
+ * // Boolean literal
51
+ * const enabledSchema = l.literal(true)
52
+ *
53
+ * // Null literal
54
+ * const nullSchema = l.literal(null)
55
+ *
56
+ * // In discriminated unions
57
+ * const actionSchema = l.discriminatedUnion('type', [
58
+ * l.object({ type: l.literal('create'), data: l.unknown() }),
59
+ * l.object({ type: l.literal('delete'), id: l.string() }),
60
+ * ])
61
+ * ```
62
+ */
19
63
  /*@__NO_SIDE_EFFECTS__*/
20
64
  export function literal<const V extends null | string | number | boolean>(
21
65
  value: V,
@@ -1,12 +1,54 @@
1
1
  import { Schema, ValidationContext } from '../core.js'
2
2
  import { memoizedOptions } from '../util/memoize.js'
3
3
 
4
+ /**
5
+ * Schema that always fails validation.
6
+ *
7
+ * Represents an impossible type - no value can satisfy this schema.
8
+ * Useful for exhaustiveness checking or marking impossible branches.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const schema = new NeverSchema()
13
+ * schema.validate(anything) // always fails
14
+ * ```
15
+ */
4
16
  export class NeverSchema extends Schema<never> {
5
17
  validateInContext(input: unknown, ctx: ValidationContext) {
6
18
  return ctx.issueInvalidType(input, 'never')
7
19
  }
8
20
  }
9
21
 
22
+ /**
23
+ * Creates a never schema that always fails validation.
24
+ *
25
+ * Useful for exhaustiveness checking in TypeScript or marking impossible
26
+ * code paths.
27
+ *
28
+ * @returns A new {@link NeverSchema} instance
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Exhaustiveness checking
33
+ * type Status = 'active' | 'inactive'
34
+ *
35
+ * function handleStatus(status: Status) {
36
+ * switch (status) {
37
+ * case 'active': return 'Active'
38
+ * case 'inactive': return 'Inactive'
39
+ * default:
40
+ * // TypeScript will error if we miss a case
41
+ * l.never().parse(status)
42
+ * }
43
+ * }
44
+ *
45
+ * // In impossible union branches
46
+ * const schema = l.object({
47
+ * type: l.literal('fixed'),
48
+ * dynamic: l.never(), // This property can never exist
49
+ * })
50
+ * ```
51
+ */
10
52
  export const never = /*#__PURE__*/ memoizedOptions(function () {
11
53
  return new NeverSchema()
12
54
  })
@@ -1,6 +1,19 @@
1
1
  import { Schema, ValidationContext } from '../core.js'
2
2
  import { memoizedOptions } from '../util/memoize.js'
3
3
 
4
+ /**
5
+ * Schema for validating null values.
6
+ *
7
+ * Only accepts the JavaScript `null` value. Rejects `undefined` and all
8
+ * other values.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const schema = new NullSchema()
13
+ * schema.validate(null) // success
14
+ * schema.validate(undefined) // fails
15
+ * ```
16
+ */
4
17
  export class NullSchema extends Schema<null> {
5
18
  validateInContext(input: unknown, ctx: ValidationContext) {
6
19
  if (input !== null) {
@@ -11,6 +24,22 @@ export class NullSchema extends Schema<null> {
11
24
  }
12
25
  }
13
26
 
27
+ /**
28
+ * Creates a null schema that only accepts the null value.
29
+ *
30
+ * Useful for explicitly representing null in union types or optional fields.
31
+ *
32
+ * @returns A new {@link NullSchema} instance
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // Explicit null
37
+ * const nullOnlySchema = l.null()
38
+ *
39
+ * // Nullable string (string or null)
40
+ * const nullableStringSchema = l.union([l.string(), l.null()])
41
+ * ```
42
+ */
14
43
  export const nullSchema = /*#__PURE__*/ memoizedOptions(function () {
15
44
  return new NullSchema()
16
45
  })
@@ -7,6 +7,21 @@ import {
7
7
  } from '../core.js'
8
8
  import { memoizedTransformer } from '../util/memoize.js'
9
9
 
10
+ /**
11
+ * Schema wrapper that allows null values in addition to the wrapped schema.
12
+ *
13
+ * When the input is `null`, validation succeeds immediately. Otherwise,
14
+ * the input is validated against the wrapped schema.
15
+ *
16
+ * @template TValidator - The wrapped validator type
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const schema = new NullableSchema(l.string())
21
+ * schema.validate(null) // success
22
+ * schema.validate('hello') // success
23
+ * ```
24
+ */
10
25
  export class NullableSchema<const TValidator extends Validator> extends Schema<
11
26
  InferInput<TValidator> | null,
12
27
  InferOutput<TValidator> | null
@@ -24,6 +39,32 @@ export class NullableSchema<const TValidator extends Validator> extends Schema<
24
39
  }
25
40
  }
26
41
 
42
+ /**
43
+ * Creates a nullable schema that accepts null in addition to the wrapped type.
44
+ *
45
+ * Wraps another schema to allow null values. Different from `optional()` which
46
+ * allows undefined.
47
+ *
48
+ * @param validator - The validator to make nullable
49
+ * @returns A new {@link NullableSchema} instance
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * // Nullable string
54
+ * const nullableString = l.nullable(l.string())
55
+ * nullableString.parse(null) // null
56
+ * nullableString.parse('hello') // 'hello'
57
+ *
58
+ * // In an object
59
+ * const userSchema = l.object({
60
+ * name: l.string(),
61
+ * deletedAt: l.nullable(l.string({ format: 'datetime' })),
62
+ * })
63
+ *
64
+ * // Combine with optional for null or undefined
65
+ * const maybeString = l.optional(l.nullable(l.string()))
66
+ * ```
67
+ */
27
68
  export const nullable = /*#__PURE__*/ memoizedTransformer(function <
28
69
  const TValidator extends Validator,
29
70
  >(validator: TValidator) {
@@ -9,8 +9,30 @@ import {
9
9
  } from '../core.js'
10
10
  import { lazyProperty } from '../util/lazy-property.js'
11
11
 
12
+ /**
13
+ * Type representing the shape of an object schema.
14
+ *
15
+ * Maps property names to their corresponding validators.
16
+ */
12
17
  export type ObjectSchemaShape = Record<string, Validator>
13
18
 
19
+ /**
20
+ * Schema for validating objects with a defined shape.
21
+ *
22
+ * Each property in the shape is validated against its corresponding schema.
23
+ * Properties wrapped in `optional()` are not required.
24
+ *
25
+ * @template TShape - The object shape type mapping property names to validators
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const schema = new ObjectSchema({
30
+ * name: l.string(),
31
+ * age: l.optional(l.integer()),
32
+ * })
33
+ * const result = schema.validate({ name: 'Alice' })
34
+ * ```
35
+ */
14
36
  export class ObjectSchema<
15
37
  const TShape extends ObjectSchemaShape = any,
16
38
  > extends Schema<
@@ -70,6 +92,40 @@ export class ObjectSchema<
70
92
  }
71
93
  }
72
94
 
95
+ /**
96
+ * Creates an object schema with the specified property validators.
97
+ *
98
+ * Validates that the input is a plain object and each property matches
99
+ * its corresponding schema. Properties wrapped in `optional()` are not required.
100
+ *
101
+ * @param properties - Object mapping property names to their validators
102
+ * @returns A new {@link ObjectSchema} instance
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // Basic object
107
+ * const userSchema = l.object({
108
+ * name: l.string(),
109
+ * email: l.string({ format: 'uri' }),
110
+ * })
111
+ *
112
+ * // With optional properties
113
+ * const profileSchema = l.object({
114
+ * displayName: l.string(),
115
+ * bio: l.optional(l.string({ maxLength: 256 })),
116
+ * avatar: l.optional(l.blob({ accept: ['image/*'] })),
117
+ * })
118
+ *
119
+ * // Nested objects
120
+ * const postSchema = l.object({
121
+ * text: l.string(),
122
+ * author: l.object({
123
+ * did: l.string({ format: 'did' }),
124
+ * handle: l.string({ format: 'handle' }),
125
+ * }),
126
+ * })
127
+ * ```
128
+ */
73
129
  /*@__NO_SIDE_EFFECTS__*/
74
130
  export function object<const TShape extends ObjectSchemaShape>(
75
131
  properties: TShape,
@@ -9,6 +9,22 @@ import {
9
9
  import { memoizedTransformer } from '../util/memoize.js'
10
10
  import { WithDefaultSchema } from './with-default.js'
11
11
 
12
+ /**
13
+ * Schema wrapper that makes a value optional (allows undefined).
14
+ *
15
+ * When the input is `undefined`, validation succeeds without running the
16
+ * inner validator. If the inner validator has a default value (via `withDefault`),
17
+ * that default will be applied in parse mode.
18
+ *
19
+ * @template TValidator - The wrapped validator type
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const schema = new OptionalSchema(l.string())
24
+ * schema.validate(undefined) // success
25
+ * schema.validate('hello') // success
26
+ * ```
27
+ */
12
28
  export class OptionalSchema<TValidator extends Validator> extends Schema<
13
29
  InferInput<TValidator> | undefined,
14
30
  UnwrapValidator<TValidator> extends WithDefaultSchema<infer TValidator>
@@ -41,6 +57,32 @@ export class OptionalSchema<TValidator extends Validator> extends Schema<
41
57
  }
42
58
  }
43
59
 
60
+ /**
61
+ * Creates an optional schema that allows undefined values.
62
+ *
63
+ * Wraps another schema to make it optional. When used in an object schema,
64
+ * properties with optional schemas are not required.
65
+ *
66
+ * @param validator - The validator to make optional
67
+ * @returns A new {@link OptionalSchema} instance
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * // Optional string
72
+ * const optionalBio = l.optional(l.string())
73
+ *
74
+ * // In an object - property is not required
75
+ * const userSchema = l.object({
76
+ * name: l.string(),
77
+ * bio: l.optional(l.string()),
78
+ * })
79
+ * userSchema.parse({ name: 'Alice' }) // Valid, bio is undefined
80
+ *
81
+ * // With default value
82
+ * const countSchema = l.optional(l.withDefault(l.integer(), 0))
83
+ * countSchema.parse(undefined) // Returns 0
84
+ * ```
85
+ */
44
86
  export const optional = /*#__PURE__*/ memoizedTransformer(function <
45
87
  const TValidator extends Validator,
46
88
  >(validator: TValidator) {
@@ -265,6 +265,9 @@ describe('ParamsSchema', () => {
265
265
  name: string(),
266
266
  age: optional(integer()),
267
267
  active: optional(boolean()),
268
+ tags: optional(array(string())),
269
+ ids: optional(array(integer())),
270
+ bools: optional(array(boolean())),
268
271
  })
269
272
 
270
273
  it('parses string parameters', () => {
@@ -310,9 +313,9 @@ describe('ParamsSchema', () => {
310
313
  })
311
314
 
312
315
  it('parses multiple values as array', () => {
313
- const urlParams = new URLSearchParams('name=Alice&tag=one&tag=two')
316
+ const urlParams = new URLSearchParams('name=Alice&tags=one&tags=two')
314
317
  const result = schema.fromURLSearchParams(urlParams)
315
- expect(result).toEqual({ name: 'Alice', tag: ['one', 'two'] })
318
+ expect(result).toEqual({ name: 'Alice', tags: ['one', 'two'] })
316
319
  })
317
320
 
318
321
  it('coerces array values correctly', () => {
@@ -346,6 +349,59 @@ describe('ParamsSchema', () => {
346
349
  extra: 'value',
347
350
  })
348
351
  })
352
+
353
+ it('coerces single values into arrays in parse mode', () => {
354
+ expect(
355
+ schema.fromURLSearchParams([
356
+ ['name', 'Alice'],
357
+ ['tags', 'tag1'],
358
+ ]),
359
+ ).toEqual({ name: 'Alice', tags: ['tag1'] })
360
+
361
+ expect(
362
+ schema.fromURLSearchParams([
363
+ ['name', 'Alice'],
364
+ ['tags', 'true'],
365
+ ]),
366
+ ).toEqual({ name: 'Alice', tags: ['true'] })
367
+
368
+ expect(
369
+ schema.fromURLSearchParams([
370
+ ['name', 'Alice'],
371
+ ['tags', '1'],
372
+ ]),
373
+ ).toEqual({ name: 'Alice', tags: ['1'] })
374
+ })
375
+
376
+ it('coerces single boolean values into arrays in parse mode', () => {
377
+ expect(
378
+ schema.fromURLSearchParams([
379
+ ['name', 'Alice'],
380
+ ['bools', 'true'],
381
+ ]),
382
+ ).toEqual({ name: 'Alice', bools: [true] })
383
+
384
+ expect(
385
+ schema.fromURLSearchParams([
386
+ ['name', 'Alice'],
387
+ ['bools', 'false'],
388
+ ]),
389
+ ).toEqual({ name: 'Alice', bools: [false] })
390
+
391
+ expect(() =>
392
+ schema.fromURLSearchParams([
393
+ ['name', 'Alice'],
394
+ ['bools', 'notabool'],
395
+ ]),
396
+ ).toThrow('Expected boolean value type at $.bools[0] (got string)')
397
+
398
+ expect(() =>
399
+ schema.fromURLSearchParams([
400
+ ['name', 'Alice'],
401
+ ['bools', '2'],
402
+ ]),
403
+ ).toThrow('Expected boolean value type at $.bools[0] (got integer)')
404
+ })
349
405
  })
350
406
 
351
407
  describe('toURLSearchParams', () => {