@atproto/lex-schema 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/core/$type.d.ts +2 -2
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js.map +1 -1
  5. package/dist/core/record-key.d.ts +1 -1
  6. package/dist/core/record-key.d.ts.map +1 -1
  7. package/dist/core/record-key.js.map +1 -1
  8. package/dist/core/schema.d.ts +3 -2
  9. package/dist/core/schema.d.ts.map +1 -1
  10. package/dist/core/schema.js +1 -1
  11. package/dist/core/schema.js.map +1 -1
  12. package/dist/core/standard-schema.d.ts +2 -2
  13. package/dist/core/standard-schema.d.ts.map +1 -1
  14. package/dist/core/standard-schema.js.map +1 -1
  15. package/dist/core/string-format.d.ts +2 -2
  16. package/dist/core/string-format.d.ts.map +1 -1
  17. package/dist/core/string-format.js.map +1 -1
  18. package/dist/core/validation-error.d.ts +1 -1
  19. package/dist/core/validation-error.d.ts.map +1 -1
  20. package/dist/core/validation-error.js +1 -1
  21. package/dist/core/validation-error.js.map +1 -1
  22. package/dist/core/validator.d.ts +1 -1
  23. package/dist/core/validator.d.ts.map +1 -1
  24. package/dist/core/validator.js +1 -1
  25. package/dist/core/validator.js.map +1 -1
  26. package/dist/helpers.d.ts +2 -2
  27. package/dist/helpers.d.ts.map +1 -1
  28. package/dist/helpers.js +2 -2
  29. package/dist/helpers.js.map +1 -1
  30. package/dist/schema/array.d.ts +1 -1
  31. package/dist/schema/array.d.ts.map +1 -1
  32. package/dist/schema/array.js +1 -1
  33. package/dist/schema/array.js.map +1 -1
  34. package/dist/schema/blob.d.ts +1 -1
  35. package/dist/schema/blob.d.ts.map +1 -1
  36. package/dist/schema/blob.js +2 -2
  37. package/dist/schema/blob.js.map +1 -1
  38. package/dist/schema/boolean.js +1 -1
  39. package/dist/schema/boolean.js.map +1 -1
  40. package/dist/schema/bytes.js +1 -1
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +1 -1
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +3 -3
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.js +1 -1
  47. package/dist/schema/custom.js.map +1 -1
  48. package/dist/schema/dict.d.ts +1 -1
  49. package/dist/schema/dict.d.ts.map +1 -1
  50. package/dist/schema/dict.js +1 -1
  51. package/dist/schema/dict.js.map +1 -1
  52. package/dist/schema/discriminated-union.d.ts +1 -1
  53. package/dist/schema/discriminated-union.d.ts.map +1 -1
  54. package/dist/schema/discriminated-union.js +2 -1
  55. package/dist/schema/discriminated-union.js.map +1 -1
  56. package/dist/schema/enum.js +1 -1
  57. package/dist/schema/enum.js.map +1 -1
  58. package/dist/schema/integer.js +1 -1
  59. package/dist/schema/integer.js.map +1 -1
  60. package/dist/schema/intersection.d.ts +1 -1
  61. package/dist/schema/intersection.d.ts.map +1 -1
  62. package/dist/schema/intersection.js +3 -1
  63. package/dist/schema/intersection.js.map +1 -1
  64. package/dist/schema/lex-map.d.ts +1 -1
  65. package/dist/schema/lex-map.d.ts.map +1 -1
  66. package/dist/schema/lex-map.js +1 -1
  67. package/dist/schema/lex-map.js.map +1 -1
  68. package/dist/schema/lex-value.d.ts +1 -1
  69. package/dist/schema/lex-value.d.ts.map +1 -1
  70. package/dist/schema/lex-value.js +1 -1
  71. package/dist/schema/lex-value.js.map +1 -1
  72. package/dist/schema/literal.js +1 -1
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.js +1 -1
  75. package/dist/schema/never.js.map +1 -1
  76. package/dist/schema/null.js +1 -1
  77. package/dist/schema/null.js.map +1 -1
  78. package/dist/schema/nullable.d.ts +1 -1
  79. package/dist/schema/nullable.d.ts.map +1 -1
  80. package/dist/schema/nullable.js +1 -1
  81. package/dist/schema/nullable.js.map +1 -1
  82. package/dist/schema/object.d.ts +2 -1
  83. package/dist/schema/object.d.ts.map +1 -1
  84. package/dist/schema/object.js +1 -1
  85. package/dist/schema/object.js.map +1 -1
  86. package/dist/schema/optional.d.ts +2 -1
  87. package/dist/schema/optional.d.ts.map +1 -1
  88. package/dist/schema/optional.js +2 -1
  89. package/dist/schema/optional.js.map +1 -1
  90. package/dist/schema/params.d.ts +1 -1
  91. package/dist/schema/params.d.ts.map +1 -1
  92. package/dist/schema/params.js +1 -1
  93. package/dist/schema/params.js.map +1 -1
  94. package/dist/schema/payload.d.ts +3 -2
  95. package/dist/schema/payload.d.ts.map +1 -1
  96. package/dist/schema/payload.js +2 -1
  97. package/dist/schema/payload.js.map +1 -1
  98. package/dist/schema/permission-set.d.ts +1 -1
  99. package/dist/schema/permission-set.d.ts.map +1 -1
  100. package/dist/schema/permission-set.js +1 -0
  101. package/dist/schema/permission-set.js.map +1 -1
  102. package/dist/schema/permission.d.ts +1 -1
  103. package/dist/schema/permission.d.ts.map +1 -1
  104. package/dist/schema/permission.js.map +1 -1
  105. package/dist/schema/procedure.d.ts +1 -1
  106. package/dist/schema/procedure.d.ts.map +1 -1
  107. package/dist/schema/procedure.js +2 -0
  108. package/dist/schema/procedure.js.map +1 -1
  109. package/dist/schema/query.d.ts +1 -1
  110. package/dist/schema/query.d.ts.map +1 -1
  111. package/dist/schema/query.js +2 -0
  112. package/dist/schema/query.js.map +1 -1
  113. package/dist/schema/record.d.ts +2 -2
  114. package/dist/schema/record.d.ts.map +1 -1
  115. package/dist/schema/record.js +1 -1
  116. package/dist/schema/record.js.map +1 -1
  117. package/dist/schema/ref.d.ts +1 -1
  118. package/dist/schema/ref.d.ts.map +1 -1
  119. package/dist/schema/ref.js +1 -1
  120. package/dist/schema/ref.js.map +1 -1
  121. package/dist/schema/refine.d.ts +2 -2
  122. package/dist/schema/refine.d.ts.map +1 -1
  123. package/dist/schema/refine.js +1 -1
  124. package/dist/schema/refine.js.map +1 -1
  125. package/dist/schema/regexp.js +1 -1
  126. package/dist/schema/regexp.js.map +1 -1
  127. package/dist/schema/string.d.ts +2 -2
  128. package/dist/schema/string.d.ts.map +1 -1
  129. package/dist/schema/string.js +1 -1
  130. package/dist/schema/string.js.map +1 -1
  131. package/dist/schema/subscription.d.ts +3 -2
  132. package/dist/schema/subscription.d.ts.map +1 -1
  133. package/dist/schema/subscription.js +2 -0
  134. package/dist/schema/subscription.js.map +1 -1
  135. package/dist/schema/token.d.ts +1 -1
  136. package/dist/schema/token.d.ts.map +1 -1
  137. package/dist/schema/token.js +1 -1
  138. package/dist/schema/token.js.map +1 -1
  139. package/dist/schema/typed-object.d.ts +2 -2
  140. package/dist/schema/typed-object.d.ts.map +1 -1
  141. package/dist/schema/typed-object.js +1 -1
  142. package/dist/schema/typed-object.js.map +1 -1
  143. package/dist/schema/typed-ref.d.ts +1 -1
  144. package/dist/schema/typed-ref.d.ts.map +1 -1
  145. package/dist/schema/typed-ref.js +1 -1
  146. package/dist/schema/typed-ref.js.map +1 -1
  147. package/dist/schema/typed-union.d.ts +1 -1
  148. package/dist/schema/typed-union.d.ts.map +1 -1
  149. package/dist/schema/typed-union.js +3 -1
  150. package/dist/schema/typed-union.js.map +1 -1
  151. package/dist/schema/union.d.ts +1 -1
  152. package/dist/schema/union.d.ts.map +1 -1
  153. package/dist/schema/union.js +1 -1
  154. package/dist/schema/union.js.map +1 -1
  155. package/dist/schema/unknown.js +1 -1
  156. package/dist/schema/unknown.js.map +1 -1
  157. package/dist/schema/with-default.d.ts +1 -1
  158. package/dist/schema/with-default.d.ts.map +1 -1
  159. package/dist/schema/with-default.js +1 -1
  160. package/dist/schema/with-default.js.map +1 -1
  161. package/package.json +6 -10
  162. package/src/core/$type.test.ts +0 -24
  163. package/src/core/$type.ts +0 -199
  164. package/src/core/record-key.ts +0 -85
  165. package/src/core/result.ts +0 -15
  166. package/src/core/schema.ts +0 -412
  167. package/src/core/standard-schema.test.ts +0 -124
  168. package/src/core/standard-schema.ts +0 -31
  169. package/src/core/string-format.ts +0 -411
  170. package/src/core/types.ts +0 -120
  171. package/src/core/validation-error.ts +0 -134
  172. package/src/core/validation-issue.ts +0 -340
  173. package/src/core/validator.ts +0 -636
  174. package/src/core.ts +0 -9
  175. package/src/external.ts +0 -3
  176. package/src/helpers.test.ts +0 -694
  177. package/src/helpers.ts +0 -222
  178. package/src/index.ts +0 -3
  179. package/src/schema/array.test.ts +0 -251
  180. package/src/schema/array.ts +0 -126
  181. package/src/schema/blob.test.ts +0 -733
  182. package/src/schema/blob.ts +0 -150
  183. package/src/schema/boolean.test.ts +0 -118
  184. package/src/schema/boolean.ts +0 -46
  185. package/src/schema/bytes.test.ts +0 -227
  186. package/src/schema/bytes.ts +0 -81
  187. package/src/schema/cid.test.ts +0 -125
  188. package/src/schema/cid.ts +0 -69
  189. package/src/schema/custom.test.ts +0 -414
  190. package/src/schema/custom.ts +0 -106
  191. package/src/schema/dict.test.ts +0 -181
  192. package/src/schema/dict.ts +0 -122
  193. package/src/schema/discriminated-union.test.ts +0 -676
  194. package/src/schema/discriminated-union.ts +0 -196
  195. package/src/schema/enum.test.ts +0 -398
  196. package/src/schema/enum.ts +0 -77
  197. package/src/schema/integer.test.ts +0 -314
  198. package/src/schema/integer.ts +0 -86
  199. package/src/schema/intersection.test.ts +0 -33
  200. package/src/schema/intersection.ts +0 -113
  201. package/src/schema/lex-map.test.ts +0 -593
  202. package/src/schema/lex-map.ts +0 -63
  203. package/src/schema/lex-value.test.ts +0 -81
  204. package/src/schema/lex-value.ts +0 -86
  205. package/src/schema/literal.test.ts +0 -533
  206. package/src/schema/literal.ts +0 -70
  207. package/src/schema/never.test.ts +0 -175
  208. package/src/schema/never.ts +0 -56
  209. package/src/schema/null.test.ts +0 -80
  210. package/src/schema/null.ts +0 -49
  211. package/src/schema/nullable.test.ts +0 -470
  212. package/src/schema/nullable.ts +0 -74
  213. package/src/schema/object.test.ts +0 -69
  214. package/src/schema/object.ts +0 -136
  215. package/src/schema/optional.test.ts +0 -479
  216. package/src/schema/optional.ts +0 -92
  217. package/src/schema/params.test.ts +0 -1118
  218. package/src/schema/params.ts +0 -371
  219. package/src/schema/payload.test.ts +0 -340
  220. package/src/schema/payload.ts +0 -204
  221. package/src/schema/permission-set.test.ts +0 -613
  222. package/src/schema/permission-set.ts +0 -86
  223. package/src/schema/permission.test.ts +0 -537
  224. package/src/schema/permission.ts +0 -63
  225. package/src/schema/procedure.test.ts +0 -324
  226. package/src/schema/procedure.ts +0 -98
  227. package/src/schema/query.test.ts +0 -348
  228. package/src/schema/query.ts +0 -86
  229. package/src/schema/record.test.ts +0 -812
  230. package/src/schema/record.ts +0 -217
  231. package/src/schema/ref.test.ts +0 -349
  232. package/src/schema/ref.ts +0 -103
  233. package/src/schema/refine.test.ts +0 -579
  234. package/src/schema/refine.ts +0 -153
  235. package/src/schema/regexp.test.ts +0 -577
  236. package/src/schema/regexp.ts +0 -82
  237. package/src/schema/string.test.ts +0 -773
  238. package/src/schema/string.ts +0 -229
  239. package/src/schema/subscription.test.ts +0 -499
  240. package/src/schema/subscription.ts +0 -108
  241. package/src/schema/token.test.ts +0 -152
  242. package/src/schema/token.ts +0 -103
  243. package/src/schema/typed-object.test.ts +0 -745
  244. package/src/schema/typed-object.ts +0 -181
  245. package/src/schema/typed-ref.test.ts +0 -796
  246. package/src/schema/typed-ref.ts +0 -126
  247. package/src/schema/typed-union.test.ts +0 -355
  248. package/src/schema/typed-union.ts +0 -130
  249. package/src/schema/union.test.ts +0 -191
  250. package/src/schema/union.ts +0 -89
  251. package/src/schema/unknown.test.ts +0 -313
  252. package/src/schema/unknown.ts +0 -47
  253. package/src/schema/with-default.ts +0 -81
  254. package/src/schema.ts +0 -43
  255. package/src/util/array-agg.test.ts +0 -42
  256. package/src/util/array-agg.ts +0 -44
  257. package/src/util/assertion-util.ts +0 -1
  258. package/src/util/if-any.ts +0 -3
  259. package/src/util/lazy-property.ts +0 -14
  260. package/src/util/memoize.ts +0 -37
  261. package/tsconfig.build.json +0 -12
  262. package/tsconfig.json +0 -7
  263. package/tsconfig.tests.json +0 -8
@@ -1,196 +0,0 @@
1
- import { isPlainObject } from '@atproto/lex-data'
2
- import {
3
- InferInput,
4
- InferOutput,
5
- Schema,
6
- ValidationContext,
7
- ValidationResult,
8
- Validator,
9
- } from '../core.js'
10
- import { EnumSchema } from './enum.js'
11
- import { LiteralSchema } from './literal.js'
12
- import { ObjectSchema } from './object.js'
13
-
14
- /**
15
- * Type representing a single variant in a discriminated union.
16
- *
17
- * Must be an ObjectSchema with the discriminator property using either
18
- * a LiteralSchema or EnumSchema.
19
- *
20
- * @template Discriminator - The discriminator property name
21
- */
22
- export type DiscriminatedUnionVariant<Discriminator extends string = string> =
23
- ObjectSchema<Record<Discriminator, EnumSchema<any> | LiteralSchema<any>>>
24
-
25
- /**
26
- * Type representing a non-empty tuple of discriminated union variants.
27
- *
28
- * @template TDiscriminator - The discriminator property name
29
- */
30
- export type DiscriminatedUnionVariants<TDiscriminator extends string> =
31
- readonly [
32
- DiscriminatedUnionVariant<TDiscriminator>,
33
- ...DiscriminatedUnionVariant<TDiscriminator>[],
34
- ]
35
-
36
- type DiscriminatedUnionSchemaInput<TVariants extends readonly Validator[]> =
37
- TVariants extends readonly [
38
- infer TValidator extends Validator,
39
- ...infer TRest extends readonly Validator[],
40
- ]
41
- ? InferInput<TValidator> | DiscriminatedUnionSchemaInput<TRest>
42
- : never
43
-
44
- type DiscriminatedUnionSchemaOutput<TVariants extends readonly Validator[]> =
45
- TVariants extends readonly [
46
- infer TValidator extends Validator,
47
- ...infer TRest extends readonly Validator[],
48
- ]
49
- ? InferOutput<TValidator> | DiscriminatedUnionSchemaOutput<TRest>
50
- : never
51
-
52
- /**
53
- * Schema for validating discriminated unions of objects.
54
- *
55
- * More efficient than regular union schemas when discriminating on a known
56
- * property. Looks up the correct variant schema directly based on the
57
- * discriminator value instead of trying each variant in sequence.
58
- *
59
- * @note There is no discriminated union in Lexicon schemas. This is a custom
60
- * extension to allow optimized validation of union of objects when using the
61
- * lex library programmatically (i.e. not code generated from a lexicon schema).
62
- *
63
- * @template TDiscriminator - The discriminator property name
64
- * @template TVariants - Tuple type of the variant schemas
65
- *
66
- * @example
67
- * ```ts
68
- * const schema = new DiscriminatedUnionSchema('type', [
69
- * l.object({ type: l.literal('text'), content: l.string() }),
70
- * l.object({ type: l.literal('image'), url: l.string() }),
71
- * ])
72
- * ```
73
- */
74
- export class DiscriminatedUnionSchema<
75
- const TDiscriminator extends string,
76
- const TVariants extends DiscriminatedUnionVariants<TDiscriminator>,
77
- > extends Schema<
78
- DiscriminatedUnionSchemaInput<TVariants>,
79
- DiscriminatedUnionSchemaOutput<TVariants>
80
- > {
81
- readonly type = 'discriminatedUnion' as const
82
-
83
- readonly variantsMap: Map<unknown, DiscriminatedUnionVariant<TDiscriminator>>
84
-
85
- constructor(
86
- readonly discriminator: TDiscriminator,
87
- readonly variants: TVariants,
88
- ) {
89
- super()
90
-
91
- // Although we usually try to avoid initialization work in constructors,
92
- // here it is necessary to ensure that invalid discriminated throw from the
93
- // place of construction, rather than later during validation.
94
- this.variantsMap = buildVariantsMap(discriminator, variants)
95
- }
96
-
97
- validateInContext(input: unknown, ctx: ValidationContext) {
98
- if (!isPlainObject(input)) {
99
- return ctx.issueUnexpectedType(input, 'object')
100
- }
101
-
102
- const { discriminator } = this
103
-
104
- if (!Object.hasOwn(input, discriminator)) {
105
- return ctx.issueRequiredKey(input, discriminator)
106
- }
107
-
108
- const discriminatorValue = input[discriminator]
109
-
110
- const variant = this.variantsMap.get(discriminatorValue)
111
- if (variant) {
112
- return ctx.validate(input, variant) as ValidationResult<
113
- DiscriminatedUnionSchemaInput<TVariants>
114
- >
115
- }
116
-
117
- return ctx.issueInvalidPropertyValue(input, discriminator, [
118
- ...this.variantsMap.keys(),
119
- ])
120
- }
121
- }
122
-
123
- function buildVariantsMap<Discriminator extends string>(
124
- discriminator: Discriminator,
125
- variants: DiscriminatedUnionVariants<Discriminator>,
126
- ) {
127
- const variantsMap = new Map<
128
- unknown,
129
- DiscriminatedUnionVariant<Discriminator>
130
- >()
131
-
132
- for (const variant of variants) {
133
- const schema = variant.shape[discriminator]
134
- if (schema instanceof LiteralSchema) {
135
- if (variantsMap.has(schema.value)) {
136
- throw new TypeError(`Overlapping discriminator value: ${schema.value}`)
137
- }
138
- variantsMap.set(schema.value, variant)
139
- } else if (schema instanceof EnumSchema) {
140
- for (const val of schema.values) {
141
- if (variantsMap.has(val)) {
142
- throw new TypeError(`Overlapping discriminator value: ${val}`)
143
- }
144
- variantsMap.set(val, variant)
145
- }
146
- } else {
147
- // Only enumerable discriminator schemas are supported
148
-
149
- // Should never happen if types are used correctly
150
- throw new TypeError(
151
- `Discriminator schema must be a LiteralSchema or EnumSchema`,
152
- )
153
- }
154
- }
155
-
156
- return variantsMap
157
- }
158
-
159
- /**
160
- * Creates a discriminated union schema for efficient object type switching.
161
- *
162
- * Unlike regular `union()`, this schema uses a discriminator property to
163
- * directly look up the correct variant, providing O(1) validation instead
164
- * of trying each variant sequentially.
165
- *
166
- * @param discriminator - Property name to discriminate on
167
- * @param variants - Non-empty array of object schemas with the discriminator property
168
- * @returns A new {@link DiscriminatedUnionSchema} instance
169
- *
170
- * @example
171
- * ```ts
172
- * // Message types discriminated by 'kind'
173
- * const messageSchema = l.discriminatedUnion('kind', [
174
- * l.object({ kind: l.literal('text'), text: l.string() }),
175
- * l.object({ kind: l.literal('image'), url: l.string(), alt: l.optional(l.string()) }),
176
- * l.object({ kind: l.literal('video'), url: l.string(), duration: l.integer() }),
177
- * ])
178
- *
179
- * // Using enums for multiple values mapping to same variant
180
- * const statusSchema = l.discriminatedUnion('status', [
181
- * l.object({ status: l.enum(['pending', 'processing']), startedAt: l.string() }),
182
- * l.object({ status: l.literal('completed'), completedAt: l.string() }),
183
- * l.object({ status: l.literal('failed'), error: l.string() }),
184
- * ])
185
- * ```
186
- */
187
- /*@__NO_SIDE_EFFECTS__*/
188
- export function discriminatedUnion<
189
- const Discriminator extends string,
190
- const Options extends DiscriminatedUnionVariants<Discriminator>,
191
- >(discriminator: Discriminator, variants: Options) {
192
- return new DiscriminatedUnionSchema<Discriminator, Options>(
193
- discriminator,
194
- variants,
195
- )
196
- }
@@ -1,398 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { enumSchema } from './enum.js'
3
- import { withDefault } from './with-default.js'
4
-
5
- describe('EnumSchema', () => {
6
- describe('with string values', () => {
7
- const schema = enumSchema(['male', 'female', 'other'])
8
-
9
- it('validates matching string values', () => {
10
- const result = schema.safeParse('male')
11
- expect(result.success).toBe(true)
12
- })
13
-
14
- it('validates all enum values', () => {
15
- expect(schema.safeParse('male').success).toBe(true)
16
- expect(schema.safeParse('female').success).toBe(true)
17
- expect(schema.safeParse('other').success).toBe(true)
18
- })
19
-
20
- it('rejects non-matching string values', () => {
21
- const result = schema.safeParse('unknown')
22
- expect(result.success).toBe(false)
23
- })
24
-
25
- it('rejects null', () => {
26
- const result = schema.safeParse(null)
27
- expect(result.success).toBe(false)
28
- })
29
-
30
- it('rejects undefined', () => {
31
- const result = schema.safeParse(undefined)
32
- expect(result.success).toBe(false)
33
- })
34
-
35
- it('rejects numbers', () => {
36
- const result = schema.safeParse(123)
37
- expect(result.success).toBe(false)
38
- })
39
-
40
- it('rejects booleans', () => {
41
- const result = schema.safeParse(true)
42
- expect(result.success).toBe(false)
43
- })
44
-
45
- it('rejects objects', () => {
46
- const result = schema.safeParse({ value: 'male' })
47
- expect(result.success).toBe(false)
48
- })
49
-
50
- it('rejects arrays', () => {
51
- const result = schema.safeParse(['male'])
52
- expect(result.success).toBe(false)
53
- })
54
-
55
- it('rejects empty string when not in enum', () => {
56
- const result = schema.safeParse('')
57
- expect(result.success).toBe(false)
58
- })
59
- })
60
-
61
- describe('with number values', () => {
62
- const schema = enumSchema([1, 2, 3])
63
-
64
- it('validates matching number values', () => {
65
- const result = schema.safeParse(1)
66
- expect(result.success).toBe(true)
67
- })
68
-
69
- it('validates all enum values', () => {
70
- expect(schema.safeParse(1).success).toBe(true)
71
- expect(schema.safeParse(2).success).toBe(true)
72
- expect(schema.safeParse(3).success).toBe(true)
73
- })
74
-
75
- it('rejects non-matching number values', () => {
76
- const result = schema.safeParse(4)
77
- expect(result.success).toBe(false)
78
- })
79
-
80
- it('rejects string numbers', () => {
81
- const result = schema.safeParse('1')
82
- expect(result.success).toBe(false)
83
- })
84
-
85
- it('rejects zero when not in enum', () => {
86
- const result = schema.safeParse(0)
87
- expect(result.success).toBe(false)
88
- })
89
-
90
- it('rejects negative numbers when not in enum', () => {
91
- const result = schema.safeParse(-1)
92
- expect(result.success).toBe(false)
93
- })
94
- })
95
-
96
- describe('with boolean values', () => {
97
- const schema = enumSchema([true, false])
98
-
99
- it('validates true', () => {
100
- const result = schema.safeParse(true)
101
- expect(result.success).toBe(true)
102
- })
103
-
104
- it('validates false', () => {
105
- const result = schema.safeParse(false)
106
- expect(result.success).toBe(true)
107
- })
108
-
109
- it('rejects string booleans', () => {
110
- expect(schema.safeParse('true').success).toBe(false)
111
- expect(schema.safeParse('false').success).toBe(false)
112
- })
113
-
114
- it('rejects number booleans', () => {
115
- expect(schema.safeParse(1).success).toBe(false)
116
- expect(schema.safeParse(0).success).toBe(false)
117
- })
118
- })
119
-
120
- describe('with single boolean value', () => {
121
- const schema = enumSchema([true])
122
-
123
- it('validates true', () => {
124
- const result = schema.safeParse(true)
125
- expect(result.success).toBe(true)
126
- })
127
-
128
- it('rejects false when not in enum', () => {
129
- const result = schema.safeParse(false)
130
- expect(result.success).toBe(false)
131
- })
132
- })
133
-
134
- describe('with null value', () => {
135
- const schema = enumSchema([null, 'value'])
136
-
137
- it('validates null', () => {
138
- const result = schema.safeParse(null)
139
- expect(result.success).toBe(true)
140
- })
141
-
142
- it('validates other enum values', () => {
143
- const result = schema.safeParse('value')
144
- expect(result.success).toBe(true)
145
- })
146
-
147
- it('rejects undefined', () => {
148
- const result = schema.safeParse(undefined)
149
- expect(result.success).toBe(false)
150
- })
151
- })
152
-
153
- describe('with mixed type values', () => {
154
- const schema = enumSchema(['string', 123, true, null])
155
-
156
- it('validates string value', () => {
157
- const result = schema.safeParse('string')
158
- expect(result.success).toBe(true)
159
- })
160
-
161
- it('validates number value', () => {
162
- const result = schema.safeParse(123)
163
- expect(result.success).toBe(true)
164
- })
165
-
166
- it('validates boolean value', () => {
167
- const result = schema.safeParse(true)
168
- expect(result.success).toBe(true)
169
- })
170
-
171
- it('validates null value', () => {
172
- const result = schema.safeParse(null)
173
- expect(result.success).toBe(true)
174
- })
175
-
176
- it('rejects non-matching values', () => {
177
- expect(schema.safeParse('other').success).toBe(false)
178
- expect(schema.safeParse(456).success).toBe(false)
179
- expect(schema.safeParse(false).success).toBe(false)
180
- expect(schema.safeParse(undefined).success).toBe(false)
181
- })
182
- })
183
-
184
- describe('with default option', () => {
185
- const schema = withDefault(enumSchema(['red', 'green', 'blue']), 'red')
186
-
187
- it('validates matching values', () => {
188
- const result = schema.safeParse('green')
189
- expect(result.success).toBe(true)
190
- })
191
-
192
- it('uses default when input is undefined', () => {
193
- const result = schema.safeParse(undefined)
194
- expect(result.success).toBe(true)
195
- if (result.success) {
196
- expect(result.value).toBe('red')
197
- }
198
- })
199
-
200
- it('uses default when no argument is passed', () => {
201
- const result = schema.safeParse(undefined)
202
- expect(result.success).toBe(true)
203
- if (result.success) {
204
- expect(result.value).toBe('red')
205
- }
206
- })
207
-
208
- it('does not use default for null', () => {
209
- const result = schema.safeParse(null)
210
- expect(result.success).toBe(false)
211
- })
212
-
213
- it('does not use default for invalid values', () => {
214
- const result = schema.safeParse('yellow')
215
- expect(result.success).toBe(false)
216
- })
217
-
218
- it('does not use default for empty string', () => {
219
- const result = schema.safeParse('')
220
- expect(result.success).toBe(false)
221
- })
222
- })
223
-
224
- describe('with default option as number', () => {
225
- const schema = withDefault(enumSchema([1, 2, 3]), 1)
226
-
227
- it('uses default when input is undefined', () => {
228
- const result = schema.safeParse(undefined)
229
- expect(result.success).toBe(true)
230
- if (result.success) {
231
- expect(result.value).toBe(1)
232
- }
233
- })
234
-
235
- it('does not use default for zero', () => {
236
- const result = schema.safeParse(0)
237
- expect(result.success).toBe(false)
238
- })
239
- })
240
-
241
- describe('with default option as boolean', () => {
242
- const schema = withDefault(enumSchema([true, false]), false)
243
-
244
- it('uses default when input is undefined', () => {
245
- const result = schema.safeParse(undefined)
246
- expect(result.success).toBe(true)
247
- if (result.success) {
248
- expect(result.value).toBe(false)
249
- }
250
- })
251
-
252
- it('validates true even when default is false', () => {
253
- const result = schema.safeParse(true)
254
- expect(result.success).toBe(true)
255
- if (result.success) {
256
- expect(result.value).toBe(true)
257
- }
258
- })
259
- })
260
-
261
- describe('with default option as null', () => {
262
- const schema = withDefault(enumSchema([null, 'value']), null)
263
-
264
- it('uses default when input is undefined', () => {
265
- const result = schema.safeParse(undefined)
266
- expect(result.success).toBe(true)
267
- if (result.success) {
268
- expect(result.value).toBe(null)
269
- }
270
- })
271
-
272
- it('validates explicit null', () => {
273
- const result = schema.safeParse(null)
274
- expect(result.success).toBe(true)
275
- if (result.success) {
276
- expect(result.value).toBe(null)
277
- }
278
- })
279
- })
280
-
281
- describe('with single value', () => {
282
- const schema = enumSchema(['only'])
283
-
284
- it('validates the single value', () => {
285
- const result = schema.safeParse('only')
286
- expect(result.success).toBe(true)
287
- })
288
-
289
- it('rejects any other value', () => {
290
- expect(schema.safeParse('other').success).toBe(false)
291
- expect(schema.safeParse('').success).toBe(false)
292
- expect(schema.safeParse(null).success).toBe(false)
293
- expect(schema.safeParse(undefined).success).toBe(false)
294
- })
295
- })
296
-
297
- describe('with empty string value', () => {
298
- const schema = enumSchema(['', 'value'])
299
-
300
- it('validates empty string', () => {
301
- const result = schema.safeParse('')
302
- expect(result.success).toBe(true)
303
- })
304
-
305
- it('validates other values', () => {
306
- const result = schema.safeParse('value')
307
- expect(result.success).toBe(true)
308
- })
309
-
310
- it('rejects non-matching values', () => {
311
- const result = schema.safeParse('other')
312
- expect(result.success).toBe(false)
313
- })
314
- })
315
-
316
- describe('with zero value', () => {
317
- const schema = enumSchema([0, 1, 2])
318
-
319
- it('validates zero', () => {
320
- const result = schema.safeParse(0)
321
- expect(result.success).toBe(true)
322
- })
323
-
324
- it('validates other values', () => {
325
- expect(schema.safeParse(1).success).toBe(true)
326
- expect(schema.safeParse(2).success).toBe(true)
327
- })
328
-
329
- it('rejects false even though zero is in enum', () => {
330
- const result = schema.safeParse(false)
331
- expect(result.success).toBe(false)
332
- })
333
- })
334
-
335
- describe('case sensitivity', () => {
336
- const schema = enumSchema(['Value', 'VALUE', 'value'])
337
-
338
- it('validates exact case matches', () => {
339
- expect(schema.safeParse('Value').success).toBe(true)
340
- expect(schema.safeParse('VALUE').success).toBe(true)
341
- expect(schema.safeParse('value').success).toBe(true)
342
- })
343
-
344
- it('rejects case mismatches', () => {
345
- expect(schema.safeParse('vaLue').success).toBe(false)
346
- expect(schema.safeParse('VaLuE').success).toBe(false)
347
- })
348
- })
349
-
350
- describe('with special string values', () => {
351
- const schema = enumSchema([
352
- 'with space',
353
- 'with\ttab',
354
- 'with\nnewline',
355
- '123',
356
- 'true',
357
- 'null',
358
- 'undefined',
359
- ])
360
-
361
- it('validates strings with spaces', () => {
362
- const result = schema.safeParse('with space')
363
- expect(result.success).toBe(true)
364
- })
365
-
366
- it('validates strings with tabs', () => {
367
- const result = schema.safeParse('with\ttab')
368
- expect(result.success).toBe(true)
369
- })
370
-
371
- it('validates strings with newlines', () => {
372
- const result = schema.safeParse('with\nnewline')
373
- expect(result.success).toBe(true)
374
- })
375
-
376
- it('validates number-like strings', () => {
377
- const result = schema.safeParse('123')
378
- expect(result.success).toBe(true)
379
- })
380
-
381
- it('rejects actual numbers for number-like strings', () => {
382
- const result = schema.safeParse(123)
383
- expect(result.success).toBe(false)
384
- })
385
-
386
- it('validates keyword strings', () => {
387
- expect(schema.safeParse('true').success).toBe(true)
388
- expect(schema.safeParse('null').success).toBe(true)
389
- expect(schema.safeParse('undefined').success).toBe(true)
390
- })
391
-
392
- it('rejects actual boolean/null/undefined for keyword strings', () => {
393
- expect(schema.safeParse(true).success).toBe(false)
394
- expect(schema.safeParse(null).success).toBe(false)
395
- expect(schema.safeParse(undefined).success).toBe(false)
396
- })
397
- })
398
- })
@@ -1,77 +0,0 @@
1
- import { Schema, ValidationContext } from '../core.js'
2
-
3
- /**
4
- * Schema that accepts one of several specific literal values.
5
- *
6
- * Validates that the input matches one of the allowed values using strict
7
- * equality. Similar to TypeScript union of literals.
8
- *
9
- * @template TValue - The union of literal types
10
- *
11
- * @example
12
- * ```ts
13
- * const schema = new EnumSchema(['pending', 'active', 'completed'])
14
- * schema.validate('active') // success
15
- * schema.validate('invalid') // fails
16
- * ```
17
- */
18
- export class EnumSchema<
19
- const TValue extends null | string | number | boolean,
20
- > extends Schema<TValue> {
21
- readonly type = 'enum' as const
22
-
23
- constructor(readonly values: readonly TValue[]) {
24
- super()
25
- }
26
-
27
- validateInContext(input: unknown, ctx: ValidationContext) {
28
- if (!(this.values as readonly unknown[]).includes(input)) {
29
- return ctx.issueInvalidValue(input, this.values)
30
- }
31
-
32
- return ctx.success(input as TValue)
33
- }
34
- }
35
-
36
- /**
37
- * Creates an enum schema that accepts one of the specified values.
38
- *
39
- * Similar to TypeScript's union of string literals. Use `l.enum()` for
40
- * the namespace-friendly alias.
41
- *
42
- * @param value - Array of allowed values
43
- * @returns A new {@link EnumSchema} instance
44
- *
45
- * @example
46
- * ```ts
47
- * // String enum
48
- * const statusSchema = l.enum(['pending', 'active', 'completed', 'failed'])
49
- *
50
- * // Number enum
51
- * const prioritySchema = l.enum([1, 2, 3, 4, 5])
52
- *
53
- * // Mixed types
54
- * const mixedSchema = l.enum(['auto', 0, 1, true])
55
- *
56
- * // Use in objects
57
- * const taskSchema = l.object({
58
- * title: l.string(),
59
- * status: l.enum(['todo', 'in-progress', 'done']),
60
- * })
61
- *
62
- * // In discriminated unions
63
- * const resultSchema = l.discriminatedUnion('status', [
64
- * l.object({ status: l.enum(['pending', 'processing']), progress: l.integer() }),
65
- * l.object({ status: l.literal('completed'), result: l.unknown() }),
66
- * ])
67
- * ```
68
- */
69
- /*@__NO_SIDE_EFFECTS__*/
70
- export function enumSchema<const V extends null | string | number | boolean>(
71
- value: readonly V[],
72
- ) {
73
- return new EnumSchema<V>(value)
74
- }
75
-
76
- // @NOTE "enum" is a reserved keyword in JS/TS
77
- export { enumSchema as enum }