@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
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { parseCid } from '@atproto/lex-data'
3
+ import { lexValue } from './lex-value.js'
4
+
5
+ const schema = lexValue()
6
+
7
+ describe(lexValue, () => {
8
+ describe('valid values', () => {
9
+ for (const { note, value } of [
10
+ { note: 'string', value: 'hello' },
11
+ { note: 'boolean true', value: true },
12
+ { note: 'boolean false', value: false },
13
+ { note: 'null', value: null },
14
+ { note: 'integer', value: 42 },
15
+ { note: 'negative integer', value: -1 },
16
+ { note: 'zero', value: 0 },
17
+ { note: 'Uint8Array', value: new Uint8Array([1, 2, 3]) },
18
+ {
19
+ note: 'Cid',
20
+ value: parseCid(
21
+ 'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
22
+ ),
23
+ },
24
+ { note: 'empty plain object', value: {} },
25
+ {
26
+ note: 'object with Lex values',
27
+ value: {
28
+ a: 123,
29
+ b: 'blah',
30
+ c: true,
31
+ d: null,
32
+ e: new Uint8Array([1, 2, 3]),
33
+ f: { nested: 'value' },
34
+ g: [1, 2, 3],
35
+ },
36
+ },
37
+ { note: 'empty array', value: [] },
38
+ {
39
+ note: 'array with Lex values',
40
+ value: [
41
+ 123,
42
+ 'blah',
43
+ true,
44
+ null,
45
+ new Uint8Array([1, 2, 3]),
46
+ { nested: 'value' },
47
+ [1, 2, 3],
48
+ ],
49
+ },
50
+ ]) {
51
+ test(note, () => {
52
+ const result = schema.safeParse(value)
53
+ expect(result.success).toBe(true)
54
+ })
55
+ }
56
+ })
57
+
58
+ describe('invalid values', () => {
59
+ for (const { note, value } of [
60
+ { note: 'float', value: 42.5 },
61
+ { note: 'undefined', value: undefined },
62
+ { note: 'function', value: () => {} },
63
+ { note: 'Date object', value: new Date() },
64
+ { note: 'Map object', value: new Map() },
65
+ { note: 'Set object', value: new Set() },
66
+ { note: 'class instance', value: new (class A {})() },
67
+ { note: 'object with function value', value: { a: 123, b: () => {} } },
68
+ {
69
+ note: 'object with undefined value',
70
+ value: { a: 123, b: undefined },
71
+ },
72
+ { note: 'array with function', value: [123, 'blah', () => {}] },
73
+ { note: 'array with undefined', value: [123, 'blah', undefined] },
74
+ ]) {
75
+ test(note, () => {
76
+ const result = schema.safeParse(value)
77
+ expect(result.success).toBe(false)
78
+ })
79
+ }
80
+ })
81
+ })
@@ -0,0 +1,86 @@
1
+ import { LexValue, isLexScalar, isPlainObject } from '@atproto/lex-data'
2
+ import { Schema, ValidationContext } from '../core.js'
3
+ import { memoizedOptions } from '../util/memoize.js'
4
+
5
+ export type { LexValue }
6
+
7
+ const EXPECTED_TYPES = Object.freeze([
8
+ // Scalar types
9
+ 'null',
10
+ 'boolean',
11
+ 'integer',
12
+ 'string',
13
+ 'cid',
14
+ 'bytes',
15
+ // Recursive types
16
+ 'array',
17
+ 'object',
18
+ ] as const)
19
+
20
+ /**
21
+ * AT Protocol lexicon values are any valid AT Protocol data types: string,
22
+ * integer, boolean, null, bytes, cid, array, or object.
23
+ */
24
+ export class LexValueSchema extends Schema<LexValue> {
25
+ readonly type = 'lexValue' as const
26
+
27
+ validateInContext(input: unknown, ctx: ValidationContext) {
28
+ // @NOTE We are *not* using "isLexValue" here to allow for more specific
29
+ // error messages about the path and type of the invalid value. The
30
+ // "isLexValue" check is effectively performed by the recursive validation
31
+ // of child properties below.
32
+
33
+ // @NOTE There are two limitations to the fact that we are not using
34
+ // "isLexValue" here:
35
+ // 1. We cannot detect circular references in objects or arrays, which would
36
+ // cause infinite recursion. However, circular references are not valid
37
+ // AT Protocol data types, so this is not a concern for valid input. This
38
+ // could easily be addressed in the "validateChild" method by keeping
39
+ // track of "parent" objects.
40
+ // 2. We are limited in the recursion depth we can validate due to potential
41
+ // recursion depth limits in JavaScript. However, this is also not a
42
+ // concern for most valid input, as extremely deep nesting is unlikely in
43
+ // typical use cases.
44
+ if (isPlainObject(input)) {
45
+ for (const key of Object.keys(input)) {
46
+ const r = ctx.validateChild(input, key, this) // recursively validate all properties
47
+ if (!r.success) return r
48
+ }
49
+ } else if (Array.isArray(input)) {
50
+ for (let i = 0; i < input.length; i++) {
51
+ const r = ctx.validateChild(input, i, this) // recursively validate all array items
52
+ if (!r.success) return r
53
+ }
54
+ } else if (!isLexScalar(input)) {
55
+ return ctx.issueInvalidType(input, EXPECTED_TYPES)
56
+ }
57
+
58
+ return ctx.success(input)
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Creates a schema that accepts any valid AT Protocol data type: string,
64
+ * integer, boolean, null, bytes, cid, array, or plain object. Arrays and
65
+ * objects are recursively validated to ensure all nested values are also valid
66
+ * AT Protocol data types.
67
+ *
68
+ * @see {@link LexValue} from `@atproto/lex-data` for the type definition of valid AT Protocol data types
69
+ * @returns A new {@link LexValueSchema} instance
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const schema = l.lexValue()
74
+ *
75
+ * schema.validate('hello') // success
76
+ * schema.validate(42) // success
77
+ * schema.validate(null) // success
78
+ * schema.validate([1, 'two', null]) // success
79
+ * schema.validate({ any: 'props' }) // success
80
+ * schema.validate(new Date()) // fails - Date is not a valid LexValue
81
+ * schema.validate({ foo: 1.2 }) // fails - 1.2 is not a valid LexValue (not an integer)
82
+ * ```
83
+ */
84
+ export const lexValue = /*#__PURE__*/ memoizedOptions(function () {
85
+ return new LexValueSchema()
86
+ })
@@ -1,8 +1,25 @@
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> {
21
+ readonly type = 'literal' as const
22
+
6
23
  constructor(readonly value: TValue) {
7
24
  super()
8
25
  }
@@ -16,6 +33,35 @@ export class LiteralSchema<
16
33
  }
17
34
  }
18
35
 
36
+ /**
37
+ * Creates a literal schema that only accepts the exact specified value.
38
+ *
39
+ * Useful for discriminator fields in unions, constant values, or type narrowing.
40
+ *
41
+ * @param value - The exact value that must be matched
42
+ * @returns A new {@link LiteralSchema} instance
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // String literal
47
+ * const roleSchema = l.literal('admin')
48
+ *
49
+ * // Number literal
50
+ * const versionSchema = l.literal(1)
51
+ *
52
+ * // Boolean literal
53
+ * const enabledSchema = l.literal(true)
54
+ *
55
+ * // Null literal
56
+ * const nullSchema = l.literal(null)
57
+ *
58
+ * // In discriminated unions
59
+ * const actionSchema = l.discriminatedUnion('type', [
60
+ * l.object({ type: l.literal('create'), data: l.unknown() }),
61
+ * l.object({ type: l.literal('delete'), id: l.string() }),
62
+ * ])
63
+ * ```
64
+ */
19
65
  /*@__NO_SIDE_EFFECTS__*/
20
66
  export function literal<const V extends null | string | number | boolean>(
21
67
  value: V,
@@ -1,12 +1,56 @@
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> {
17
+ readonly type = 'never' as const
18
+
5
19
  validateInContext(input: unknown, ctx: ValidationContext) {
6
- return ctx.issueInvalidType(input, 'never')
20
+ return ctx.issueUnexpectedType(input, 'never')
7
21
  }
8
22
  }
9
23
 
24
+ /**
25
+ * Creates a never schema that always fails validation.
26
+ *
27
+ * Useful for exhaustiveness checking in TypeScript or marking impossible
28
+ * code paths.
29
+ *
30
+ * @returns A new {@link NeverSchema} instance
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * // Exhaustiveness checking
35
+ * type Status = 'active' | 'inactive'
36
+ *
37
+ * function handleStatus(status: Status) {
38
+ * switch (status) {
39
+ * case 'active': return 'Active'
40
+ * case 'inactive': return 'Inactive'
41
+ * default:
42
+ * // TypeScript will error if we miss a case
43
+ * l.never().parse(status)
44
+ * }
45
+ * }
46
+ *
47
+ * // In impossible union branches
48
+ * const schema = l.object({
49
+ * type: l.literal('fixed'),
50
+ * dynamic: l.never(), // This property can never exist
51
+ * })
52
+ * ```
53
+ */
10
54
  export const never = /*#__PURE__*/ memoizedOptions(function () {
11
55
  return new NeverSchema()
12
56
  })
@@ -1,16 +1,47 @@
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> {
18
+ readonly type = 'null' as const
19
+
5
20
  validateInContext(input: unknown, ctx: ValidationContext) {
6
21
  if (input !== null) {
7
- return ctx.issueInvalidType(input, 'null')
22
+ return ctx.issueUnexpectedType(input, 'null')
8
23
  }
9
24
 
10
25
  return ctx.success(null)
11
26
  }
12
27
  }
13
28
 
29
+ /**
30
+ * Creates a null schema that only accepts the null value.
31
+ *
32
+ * Useful for explicitly representing null in union types or optional fields.
33
+ *
34
+ * @returns A new {@link NullSchema} instance
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // Explicit null
39
+ * const nullOnlySchema = l.null()
40
+ *
41
+ * // Nullable string (string or null)
42
+ * const nullableStringSchema = l.union([l.string(), l.null()])
43
+ * ```
44
+ */
14
45
  export const nullSchema = /*#__PURE__*/ memoizedOptions(function () {
15
46
  return new NullSchema()
16
47
  })
@@ -7,10 +7,27 @@ 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
13
28
  > {
29
+ readonly type = 'nullable' as const
30
+
14
31
  constructor(readonly validator: TValidator) {
15
32
  super()
16
33
  }
@@ -24,6 +41,32 @@ export class NullableSchema<const TValidator extends Validator> extends Schema<
24
41
  }
25
42
  }
26
43
 
44
+ /**
45
+ * Creates a nullable schema that accepts null in addition to the wrapped type.
46
+ *
47
+ * Wraps another schema to allow null values. Different from `optional()` which
48
+ * allows undefined.
49
+ *
50
+ * @param validator - The validator to make nullable
51
+ * @returns A new {@link NullableSchema} instance
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Nullable string
56
+ * const nullableString = l.nullable(l.string())
57
+ * nullableString.parse(null) // null
58
+ * nullableString.parse('hello') // 'hello'
59
+ *
60
+ * // In an object
61
+ * const userSchema = l.object({
62
+ * name: l.string(),
63
+ * deletedAt: l.nullable(l.string({ format: 'datetime' })),
64
+ * })
65
+ *
66
+ * // Combine with optional for null or undefined
67
+ * const maybeString = l.optional(l.nullable(l.string()))
68
+ * ```
69
+ */
27
70
  export const nullable = /*#__PURE__*/ memoizedTransformer(function <
28
71
  const TValidator extends Validator,
29
72
  >(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<
@@ -21,6 +43,8 @@ export class ObjectSchema<
21
43
  [K in keyof TShape]: InferOutput<TShape[K]>
22
44
  }>
23
45
  > {
46
+ readonly type = 'object' as const
47
+
24
48
  constructor(readonly shape: TShape) {
25
49
  super()
26
50
  }
@@ -33,7 +57,7 @@ export class ObjectSchema<
33
57
 
34
58
  validateInContext(input: unknown, ctx: ValidationContext) {
35
59
  if (!isPlainObject(input)) {
36
- return ctx.issueInvalidType(input, 'object')
60
+ return ctx.issueUnexpectedType(input, 'object')
37
61
  }
38
62
 
39
63
  // Lazily copy value
@@ -70,6 +94,40 @@ export class ObjectSchema<
70
94
  }
71
95
  }
72
96
 
97
+ /**
98
+ * Creates an object schema with the specified property validators.
99
+ *
100
+ * Validates that the input is a plain object and each property matches
101
+ * its corresponding schema. Properties wrapped in `optional()` are not required.
102
+ *
103
+ * @param properties - Object mapping property names to their validators
104
+ * @returns A new {@link ObjectSchema} instance
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // Basic object
109
+ * const userSchema = l.object({
110
+ * name: l.string(),
111
+ * email: l.string({ format: 'uri' }),
112
+ * })
113
+ *
114
+ * // With optional properties
115
+ * const profileSchema = l.object({
116
+ * displayName: l.string(),
117
+ * bio: l.optional(l.string({ maxLength: 256 })),
118
+ * avatar: l.optional(l.blob({ accept: ['image/*'] })),
119
+ * })
120
+ *
121
+ * // Nested objects
122
+ * const postSchema = l.object({
123
+ * text: l.string(),
124
+ * author: l.object({
125
+ * did: l.string({ format: 'did' }),
126
+ * handle: l.string({ format: 'handle' }),
127
+ * }),
128
+ * })
129
+ * ```
130
+ */
73
131
  /*@__NO_SIDE_EFFECTS__*/
74
132
  export function object<const TShape extends ObjectSchemaShape>(
75
133
  properties: TShape,
@@ -9,12 +9,30 @@ 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>
15
31
  ? InferOutput<TValidator>
16
32
  : InferOutput<TValidator> | undefined
17
33
  > {
34
+ readonly type = 'optional' as const
35
+
18
36
  constructor(readonly validator: TValidator) {
19
37
  super()
20
38
  }
@@ -41,6 +59,32 @@ export class OptionalSchema<TValidator extends Validator> extends Schema<
41
59
  }
42
60
  }
43
61
 
62
+ /**
63
+ * Creates an optional schema that allows undefined values.
64
+ *
65
+ * Wraps another schema to make it optional. When used in an object schema,
66
+ * properties with optional schemas are not required.
67
+ *
68
+ * @param validator - The validator to make optional
69
+ * @returns A new {@link OptionalSchema} instance
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * // Optional string
74
+ * const optionalBio = l.optional(l.string())
75
+ *
76
+ * // In an object - property is not required
77
+ * const userSchema = l.object({
78
+ * name: l.string(),
79
+ * bio: l.optional(l.string()),
80
+ * })
81
+ * userSchema.parse({ name: 'Alice' }) // Valid, bio is undefined
82
+ *
83
+ * // With default value
84
+ * const countSchema = l.optional(l.withDefault(l.integer(), 0))
85
+ * countSchema.parse(undefined) // Returns 0
86
+ * ```
87
+ */
44
88
  export const optional = /*#__PURE__*/ memoizedTransformer(function <
45
89
  const TValidator extends Validator,
46
90
  >(validator: TValidator) {