@atproto/lex-schema 0.0.9 → 0.0.10

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 (279) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/core/$type.d.ts +11 -0
  4. package/dist/core/$type.d.ts.map +1 -1
  5. package/dist/core/$type.js +4 -0
  6. package/dist/core/$type.js.map +1 -1
  7. package/dist/core/schema.d.ts +31 -24
  8. package/dist/core/schema.d.ts.map +1 -1
  9. package/dist/core/schema.js +38 -8
  10. package/dist/core/schema.js.map +1 -1
  11. package/dist/core/string-format.d.ts +35 -35
  12. package/dist/core/string-format.d.ts.map +1 -1
  13. package/dist/core/string-format.js +49 -91
  14. package/dist/core/string-format.js.map +1 -1
  15. package/dist/core/validation-issue.js +1 -1
  16. package/dist/core/validation-issue.js.map +1 -1
  17. package/dist/core/validator.d.ts +53 -32
  18. package/dist/core/validator.d.ts.map +1 -1
  19. package/dist/core/validator.js +18 -22
  20. package/dist/core/validator.js.map +1 -1
  21. package/dist/external.d.ts +0 -85
  22. package/dist/external.d.ts.map +1 -1
  23. package/dist/external.js +0 -164
  24. package/dist/external.js.map +1 -1
  25. package/dist/helpers.d.ts +10 -5
  26. package/dist/helpers.d.ts.map +1 -1
  27. package/dist/helpers.js +3 -3
  28. package/dist/helpers.js.map +1 -1
  29. package/dist/schema/array.d.ts +9 -5
  30. package/dist/schema/array.d.ts.map +1 -1
  31. package/dist/schema/array.js +14 -5
  32. package/dist/schema/array.js.map +1 -1
  33. package/dist/schema/blob.d.ts +9 -7
  34. package/dist/schema/blob.d.ts.map +1 -1
  35. package/dist/schema/blob.js +9 -5
  36. package/dist/schema/blob.js.map +1 -1
  37. package/dist/schema/boolean.d.ts +3 -7
  38. package/dist/schema/boolean.d.ts.map +1 -1
  39. package/dist/schema/boolean.js +6 -7
  40. package/dist/schema/boolean.js.map +1 -1
  41. package/dist/schema/bytes.d.ts +3 -2
  42. package/dist/schema/bytes.d.ts.map +1 -1
  43. package/dist/schema/bytes.js +7 -3
  44. package/dist/schema/bytes.js.map +1 -1
  45. package/dist/schema/cid.d.ts +7 -7
  46. package/dist/schema/cid.d.ts.map +1 -1
  47. package/dist/schema/cid.js +5 -1
  48. package/dist/schema/cid.js.map +1 -1
  49. package/dist/schema/custom.d.ts +6 -5
  50. package/dist/schema/custom.d.ts.map +1 -1
  51. package/dist/schema/custom.js +10 -4
  52. package/dist/schema/custom.js.map +1 -1
  53. package/dist/schema/dict.d.ts +8 -8
  54. package/dist/schema/dict.d.ts.map +1 -1
  55. package/dist/schema/dict.js +11 -2
  56. package/dist/schema/dict.js.map +1 -1
  57. package/dist/schema/discriminated-union.d.ts +21 -14
  58. package/dist/schema/discriminated-union.d.ts.map +1 -1
  59. package/dist/schema/discriminated-union.js +7 -0
  60. package/dist/schema/discriminated-union.js.map +1 -1
  61. package/dist/schema/enum.d.ts +7 -9
  62. package/dist/schema/enum.d.ts.map +1 -1
  63. package/dist/schema/enum.js +8 -4
  64. package/dist/schema/enum.js.map +1 -1
  65. package/dist/schema/integer.d.ts +5 -5
  66. package/dist/schema/integer.d.ts.map +1 -1
  67. package/dist/schema/integer.js +9 -5
  68. package/dist/schema/integer.js.map +1 -1
  69. package/dist/schema/intersection.d.ts +4 -4
  70. package/dist/schema/intersection.d.ts.map +1 -1
  71. package/dist/schema/intersection.js +5 -0
  72. package/dist/schema/intersection.js.map +1 -1
  73. package/dist/schema/literal.d.ts +6 -9
  74. package/dist/schema/literal.d.ts.map +1 -1
  75. package/dist/schema/literal.js +7 -4
  76. package/dist/schema/literal.js.map +1 -1
  77. package/dist/schema/never.d.ts +3 -2
  78. package/dist/schema/never.d.ts.map +1 -1
  79. package/dist/schema/never.js +5 -1
  80. package/dist/schema/never.js.map +1 -1
  81. package/dist/schema/null.d.ts +4 -3
  82. package/dist/schema/null.d.ts.map +1 -1
  83. package/dist/schema/null.js +6 -4
  84. package/dist/schema/null.js.map +1 -1
  85. package/dist/schema/nullable.d.ts +6 -5
  86. package/dist/schema/nullable.d.ts.map +1 -1
  87. package/dist/schema/nullable.js +9 -5
  88. package/dist/schema/nullable.js.map +1 -1
  89. package/dist/schema/object.d.ts +10 -8
  90. package/dist/schema/object.d.ts.map +1 -1
  91. package/dist/schema/object.js +11 -3
  92. package/dist/schema/object.js.map +1 -1
  93. package/dist/schema/optional.d.ts +7 -5
  94. package/dist/schema/optional.d.ts.map +1 -1
  95. package/dist/schema/optional.js +14 -6
  96. package/dist/schema/optional.js.map +1 -1
  97. package/dist/schema/params.d.ts +24 -13
  98. package/dist/schema/params.d.ts.map +1 -1
  99. package/dist/schema/params.js +47 -25
  100. package/dist/schema/params.js.map +1 -1
  101. package/dist/schema/payload.d.ts +12 -9
  102. package/dist/schema/payload.d.ts.map +1 -1
  103. package/dist/schema/payload.js +11 -0
  104. package/dist/schema/payload.js.map +1 -1
  105. package/dist/schema/permission-set.d.ts +1 -0
  106. package/dist/schema/permission-set.d.ts.map +1 -1
  107. package/dist/schema/permission-set.js +5 -0
  108. package/dist/schema/permission-set.js.map +1 -1
  109. package/dist/schema/permission.d.ts +6 -5
  110. package/dist/schema/permission.d.ts.map +1 -1
  111. package/dist/schema/permission.js +5 -0
  112. package/dist/schema/permission.js.map +1 -1
  113. package/dist/schema/procedure.d.ts +2 -1
  114. package/dist/schema/procedure.d.ts.map +1 -1
  115. package/dist/schema/procedure.js +5 -0
  116. package/dist/schema/procedure.js.map +1 -1
  117. package/dist/schema/query.d.ts +2 -1
  118. package/dist/schema/query.d.ts.map +1 -1
  119. package/dist/schema/query.js +5 -0
  120. package/dist/schema/query.js.map +1 -1
  121. package/dist/schema/record.d.ts +48 -30
  122. package/dist/schema/record.d.ts.map +1 -1
  123. package/dist/schema/record.js +12 -9
  124. package/dist/schema/record.js.map +1 -1
  125. package/dist/schema/ref.d.ts +9 -6
  126. package/dist/schema/ref.d.ts.map +1 -1
  127. package/dist/schema/ref.js +9 -16
  128. package/dist/schema/ref.js.map +1 -1
  129. package/dist/schema/refine.d.ts +4 -4
  130. package/dist/schema/refine.d.ts.map +1 -1
  131. package/dist/schema/refine.js.map +1 -1
  132. package/dist/schema/regexp.d.ts +4 -3
  133. package/dist/schema/regexp.d.ts.map +1 -1
  134. package/dist/schema/regexp.js +5 -0
  135. package/dist/schema/regexp.js.map +1 -1
  136. package/dist/schema/string.d.ts +7 -8
  137. package/dist/schema/string.d.ts.map +1 -1
  138. package/dist/schema/string.js +13 -19
  139. package/dist/schema/string.js.map +1 -1
  140. package/dist/schema/subscription.d.ts +2 -1
  141. package/dist/schema/subscription.d.ts.map +1 -1
  142. package/dist/schema/subscription.js +5 -0
  143. package/dist/schema/subscription.js.map +1 -1
  144. package/dist/schema/token.d.ts +6 -5
  145. package/dist/schema/token.d.ts.map +1 -1
  146. package/dist/schema/token.js +5 -0
  147. package/dist/schema/token.js.map +1 -1
  148. package/dist/schema/typed-object.d.ts +43 -26
  149. package/dist/schema/typed-object.d.ts.map +1 -1
  150. package/dist/schema/typed-object.js +6 -3
  151. package/dist/schema/typed-object.js.map +1 -1
  152. package/dist/schema/typed-ref.d.ts +16 -25
  153. package/dist/schema/typed-ref.d.ts.map +1 -1
  154. package/dist/schema/typed-ref.js +7 -17
  155. package/dist/schema/typed-ref.js.map +1 -1
  156. package/dist/schema/typed-union.d.ts +9 -21
  157. package/dist/schema/typed-union.d.ts.map +1 -1
  158. package/dist/schema/typed-union.js +15 -11
  159. package/dist/schema/typed-union.js.map +1 -1
  160. package/dist/schema/union.d.ts +6 -6
  161. package/dist/schema/union.d.ts.map +1 -1
  162. package/dist/schema/union.js +7 -5
  163. package/dist/schema/union.js.map +1 -1
  164. package/dist/schema/unknown-object.d.ts +5 -4
  165. package/dist/schema/unknown-object.d.ts.map +1 -1
  166. package/dist/schema/unknown-object.js +5 -1
  167. package/dist/schema/unknown-object.js.map +1 -1
  168. package/dist/schema/unknown.d.ts +3 -2
  169. package/dist/schema/unknown.d.ts.map +1 -1
  170. package/dist/schema/unknown.js +5 -1
  171. package/dist/schema/unknown.js.map +1 -1
  172. package/dist/schema/with-default.d.ts +9 -0
  173. package/dist/schema/with-default.d.ts.map +1 -0
  174. package/dist/schema/with-default.js +27 -0
  175. package/dist/schema/with-default.js.map +1 -0
  176. package/dist/schema.d.ts +2 -2
  177. package/dist/schema.d.ts.map +1 -1
  178. package/dist/schema.js +2 -4
  179. package/dist/schema.js.map +1 -1
  180. package/dist/util/assertion-util.d.ts +0 -6
  181. package/dist/util/assertion-util.d.ts.map +1 -1
  182. package/dist/util/assertion-util.js +0 -28
  183. package/dist/util/assertion-util.js.map +1 -1
  184. package/dist/util/memoize.d.ts +2 -2
  185. package/dist/util/memoize.d.ts.map +1 -1
  186. package/dist/util/memoize.js +23 -39
  187. package/dist/util/memoize.js.map +1 -1
  188. package/package.json +3 -3
  189. package/src/core/$type.test.ts +20 -0
  190. package/src/core/$type.ts +30 -0
  191. package/src/core/schema.ts +86 -38
  192. package/src/core/string-format.ts +119 -158
  193. package/src/core/validation-issue.ts +1 -1
  194. package/src/core/validator.ts +93 -53
  195. package/src/external.ts +0 -404
  196. package/src/helpers.test.ts +22 -21
  197. package/src/helpers.ts +14 -14
  198. package/src/schema/array.test.ts +38 -40
  199. package/src/schema/array.ts +35 -13
  200. package/src/schema/blob.test.ts +21 -21
  201. package/src/schema/blob.ts +19 -17
  202. package/src/schema/boolean.test.ts +9 -8
  203. package/src/schema/boolean.ts +7 -13
  204. package/src/schema/bytes.test.ts +13 -13
  205. package/src/schema/bytes.ts +13 -8
  206. package/src/schema/cid.test.ts +3 -3
  207. package/src/schema/cid.ts +13 -12
  208. package/src/schema/custom.test.ts +26 -26
  209. package/src/schema/custom.ts +20 -13
  210. package/src/schema/dict.test.ts +21 -39
  211. package/src/schema/dict.ts +28 -19
  212. package/src/schema/discriminated-union.test.ts +128 -128
  213. package/src/schema/discriminated-union.ts +45 -26
  214. package/src/schema/enum.test.ts +17 -16
  215. package/src/schema/enum.ts +16 -16
  216. package/src/schema/integer.test.ts +22 -21
  217. package/src/schema/integer.ts +12 -9
  218. package/src/schema/intersection.test.ts +10 -10
  219. package/src/schema/intersection.ts +17 -14
  220. package/src/schema/literal.test.ts +35 -34
  221. package/src/schema/literal.ts +12 -15
  222. package/src/schema/never.test.ts +5 -5
  223. package/src/schema/never.ts +7 -2
  224. package/src/schema/null.test.ts +3 -3
  225. package/src/schema/null.ts +9 -9
  226. package/src/schema/nullable.test.ts +31 -42
  227. package/src/schema/nullable.ts +17 -9
  228. package/src/schema/object.test.ts +10 -12
  229. package/src/schema/object.ts +27 -18
  230. package/src/schema/optional.test.ts +21 -28
  231. package/src/schema/optional.ts +27 -10
  232. package/src/schema/params.test.ts +471 -47
  233. package/src/schema/params.ts +72 -38
  234. package/src/schema/payload.test.ts +150 -156
  235. package/src/schema/payload.ts +35 -19
  236. package/src/schema/permission-set.test.ts +206 -273
  237. package/src/schema/permission-set.ts +8 -0
  238. package/src/schema/permission.test.ts +177 -177
  239. package/src/schema/permission.ts +13 -5
  240. package/src/schema/procedure.test.ts +183 -242
  241. package/src/schema/procedure.ts +18 -5
  242. package/src/schema/query.test.ts +186 -200
  243. package/src/schema/query.ts +16 -4
  244. package/src/schema/record.test.ts +121 -101
  245. package/src/schema/record.ts +74 -40
  246. package/src/schema/ref.test.ts +101 -118
  247. package/src/schema/ref.ts +33 -28
  248. package/src/schema/refine.test.ts +28 -28
  249. package/src/schema/refine.ts +23 -20
  250. package/src/schema/regexp.test.ts +29 -33
  251. package/src/schema/regexp.ts +11 -7
  252. package/src/schema/string.test.ts +35 -35
  253. package/src/schema/string.ts +24 -33
  254. package/src/schema/subscription.test.ts +259 -387
  255. package/src/schema/subscription.ts +16 -4
  256. package/src/schema/token.test.ts +47 -324
  257. package/src/schema/token.ts +14 -7
  258. package/src/schema/typed-object.test.ts +98 -81
  259. package/src/schema/typed-object.ts +68 -33
  260. package/src/schema/typed-ref.test.ts +206 -234
  261. package/src/schema/typed-ref.ts +40 -42
  262. package/src/schema/typed-union.test.ts +40 -64
  263. package/src/schema/typed-union.ts +36 -58
  264. package/src/schema/union.test.ts +17 -27
  265. package/src/schema/union.ts +20 -16
  266. package/src/schema/unknown-object.test.ts +8 -8
  267. package/src/schema/unknown-object.ts +9 -7
  268. package/src/schema/unknown.test.ts +4 -4
  269. package/src/schema/unknown.ts +7 -5
  270. package/src/schema/with-default.ts +35 -0
  271. package/src/schema.ts +2 -6
  272. package/src/util/assertion-util.ts +0 -39
  273. package/src/util/memoize.ts +26 -46
  274. package/dist/schema/_parameters.d.ts +0 -17
  275. package/dist/schema/_parameters.d.ts.map +0 -1
  276. package/dist/schema/_parameters.js +0 -20
  277. package/dist/schema/_parameters.js.map +0 -1
  278. package/src/schema/_parameters.test.ts +0 -417
  279. package/src/schema/_parameters.ts +0 -26
@@ -1,28 +1,31 @@
1
1
  import {
2
+ $Typed,
3
+ InferInput,
4
+ InferOutput,
2
5
  Schema,
3
- ValidationResult,
6
+ ValidationContext,
4
7
  Validator,
5
- ValidatorContext,
6
8
  } from '../core.js'
7
9
 
8
- // Basically a RecordSchema or TypedObjectSchema
9
- export type TypedRefSchemaValidator<V extends { $type?: string } = any> =
10
- V extends { $type?: infer T extends string }
11
- ? { $type: T } & Validator<V & { $type?: T }>
12
- : never
13
-
14
- export type TypedRefGetter<V extends { $type?: string } = any> =
15
- () => TypedRefSchemaValidator<V>
10
+ export interface TypedObjectValidator<
11
+ TInput extends { $type?: string } = { $type?: string },
12
+ TOutput extends TInput = TInput,
13
+ > extends Validator<TInput, TOutput> {
14
+ $type: NonNullable<TOutput['$type']>
15
+ }
16
16
 
17
- export type TypedRefSchemaOutput<V extends { $type?: string } = any> =
18
- V extends { $type?: infer T extends string } ? V & { $type: T } : never
17
+ export type TypedRefGetter<out TValidator extends TypedObjectValidator> =
18
+ () => TValidator
19
19
 
20
- export class TypedRefSchema<V extends { $type?: string } = any> extends Schema<
21
- TypedRefSchemaOutput<V>
20
+ export class TypedRefSchema<
21
+ const TValidator extends TypedObjectValidator = TypedObjectValidator,
22
+ > extends Schema<
23
+ $Typed<InferInput<TValidator>>,
24
+ $Typed<InferOutput<TValidator>>
22
25
  > {
23
- #getter: TypedRefGetter<V>
26
+ #getter: TypedRefGetter<TValidator>
24
27
 
25
- constructor(getter: TypedRefGetter<V>) {
28
+ constructor(getter: TypedRefGetter<TValidator>) {
26
29
  // @NOTE In order to avoid circular dependency issues, we don't resolve
27
30
  // the schema here. Instead, we resolve it lazily when first accessed.
28
31
 
@@ -31,43 +34,38 @@ export class TypedRefSchema<V extends { $type?: string } = any> extends Schema<
31
34
  this.#getter = getter
32
35
  }
33
36
 
34
- get schema(): TypedRefSchemaValidator<V> {
35
- const value = this.#getter.call(null)
36
-
37
- // Prevents a getter from depending on itself recursively, also allows GC to
38
- // clean up the getter function.
39
- this.#getter = throwAlreadyCalled
40
-
41
- // Cache the resolved schema on the instance
42
- Object.defineProperty(this, 'schema', {
43
- value,
44
- writable: false,
45
- enumerable: false,
46
- configurable: true,
47
- })
48
-
49
- return value
37
+ get validator(): TValidator {
38
+ return this.#getter.call(null)
50
39
  }
51
40
 
52
- get $type(): TypedRefSchemaOutput<V>['$type'] {
53
- return this.schema.$type
41
+ get $type(): TValidator['$type'] {
42
+ return this.validator.$type
54
43
  }
55
44
 
56
- validateInContext(
57
- input: unknown,
58
- ctx: ValidatorContext,
59
- ): ValidationResult<TypedRefSchemaOutput<V>> {
60
- const result = ctx.validate(input, this.schema)
45
+ validateInContext(input: unknown, ctx: ValidationContext) {
46
+ const result = ctx.validate(input, this.validator)
61
47
  if (!result.success) return result
62
48
 
63
49
  if (result.value.$type !== this.$type) {
64
50
  return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
65
51
  }
66
52
 
67
- return result as ValidationResult<TypedRefSchemaOutput<V>>
53
+ return result
68
54
  }
69
55
  }
70
56
 
71
- function throwAlreadyCalled(): never {
72
- throw new Error('TypedRefSchema getter called multiple times')
57
+ /*@__NO_SIDE_EFFECTS__*/
58
+ export function typedRef<const TValidator extends TypedObjectValidator>(
59
+ get: TypedRefGetter<TValidator>,
60
+ ): TypedRefSchema<TValidator>
61
+ export function typedRef<
62
+ TInput extends { $type?: string },
63
+ TOutput extends TInput = TInput,
64
+ >(
65
+ get: TypedRefGetter<TypedObjectValidator<TInput, TOutput>>,
66
+ ): TypedRefSchema<TypedObjectValidator<TInput, TOutput>>
67
+ export function typedRef<const TValidator extends TypedObjectValidator>(
68
+ get: TypedRefGetter<TValidator>,
69
+ ): TypedRefSchema<TValidator> {
70
+ return new TypedRefSchema<TValidator>(get)
73
71
  }
@@ -1,42 +1,42 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { IntegerSchema } from './integer.js'
3
- import { ObjectSchema } from './object.js'
4
- import { StringSchema } from './string.js'
5
- import { TypedObjectSchema } from './typed-object.js'
6
- import { TypedRefSchema } from './typed-ref.js'
7
- import { TypedUnionSchema } from './typed-union.js'
2
+ import { integer } from './integer.js'
3
+ import { object } from './object.js'
4
+ import { string } from './string.js'
5
+ import { typedObject } from './typed-object.js'
6
+ import { typedRef } from './typed-ref.js'
7
+ import { typedUnion } from './typed-union.js'
8
8
 
9
9
  describe('TypedUnionSchema', () => {
10
- const personSchema = new TypedObjectSchema(
10
+ const personSchema = typedObject(
11
11
  'app.bsky.actor.person',
12
- new ObjectSchema({
13
- name: new StringSchema({}),
14
- age: new IntegerSchema({}),
12
+ 'main',
13
+ object({
14
+ name: string(),
15
+ age: integer(),
15
16
  }),
16
17
  )
17
18
 
18
- const postSchema = new TypedObjectSchema(
19
+ const postSchema = typedObject(
19
20
  'app.bsky.feed.post',
20
- new ObjectSchema({
21
- text: new StringSchema({}),
22
- createdAt: new StringSchema({}),
21
+ 'main',
22
+ object({
23
+ text: string(),
24
+ createdAt: string(),
23
25
  }),
24
26
  )
25
27
 
26
- const commentSchema = new TypedObjectSchema(
28
+ const commentSchema = typedObject(
27
29
  'app.bsky.feed.comment',
28
- new ObjectSchema({
29
- text: new StringSchema({}),
30
- parentUri: new StringSchema({}),
30
+ 'main',
31
+ object({
32
+ text: string(),
33
+ parentUri: string(),
31
34
  }),
32
35
  )
33
36
 
34
37
  describe('closed union', () => {
35
- const schema = new TypedUnionSchema(
36
- [
37
- new TypedRefSchema(() => personSchema),
38
- new TypedRefSchema(() => postSchema),
39
- ],
38
+ const schema = typedUnion(
39
+ [typedRef(() => personSchema), typedRef(() => postSchema)],
40
40
  true,
41
41
  )
42
42
 
@@ -123,11 +123,8 @@ describe('TypedUnionSchema', () => {
123
123
  })
124
124
 
125
125
  describe('open union', () => {
126
- const schema = new TypedUnionSchema(
127
- [
128
- new TypedRefSchema(() => personSchema),
129
- new TypedRefSchema(() => postSchema),
130
- ],
126
+ const schema = typedUnion(
127
+ [typedRef(() => personSchema), typedRef(() => postSchema)],
131
128
  false,
132
129
  )
133
130
 
@@ -208,11 +205,11 @@ describe('TypedUnionSchema', () => {
208
205
  })
209
206
 
210
207
  describe('with three types', () => {
211
- const schema = new TypedUnionSchema(
208
+ const schema = typedUnion(
212
209
  [
213
- new TypedRefSchema(() => personSchema),
214
- new TypedRefSchema(() => postSchema),
215
- new TypedRefSchema(() => commentSchema),
210
+ typedRef(() => personSchema),
211
+ typedRef(() => postSchema),
212
+ typedRef(() => commentSchema),
216
213
  ],
217
214
  true,
218
215
  )
@@ -254,10 +251,7 @@ describe('TypedUnionSchema', () => {
254
251
  })
255
252
 
256
253
  describe('with single type', () => {
257
- const schema = new TypedUnionSchema(
258
- [new TypedRefSchema(() => personSchema)],
259
- true,
260
- )
254
+ const schema = typedUnion([typedRef(() => personSchema)], true)
261
255
 
262
256
  it('validates the single type', () => {
263
257
  const result = schema.safeParse({
@@ -279,11 +273,8 @@ describe('TypedUnionSchema', () => {
279
273
  })
280
274
 
281
275
  describe('$types getter', () => {
282
- const schema = new TypedUnionSchema(
283
- [
284
- new TypedRefSchema(() => personSchema),
285
- new TypedRefSchema(() => postSchema),
286
- ],
276
+ const schema = typedUnion(
277
+ [typedRef(() => personSchema), typedRef(() => postSchema)],
287
278
  true,
288
279
  )
289
280
 
@@ -296,16 +287,13 @@ describe('TypedUnionSchema', () => {
296
287
  })
297
288
 
298
289
  describe('refsMap getter', () => {
299
- const schema = new TypedUnionSchema(
300
- [
301
- new TypedRefSchema(() => personSchema),
302
- new TypedRefSchema(() => postSchema),
303
- ],
290
+ const schema = typedUnion(
291
+ [typedRef(() => personSchema), typedRef(() => postSchema)],
304
292
  true,
305
293
  )
306
294
 
307
295
  it('returns map of $type to ref schema', () => {
308
- const refsMap = schema.refsMap
296
+ const refsMap = schema.validatorsMap
309
297
  expect(refsMap.size).toBe(2)
310
298
  expect(refsMap.has('app.bsky.actor.person')).toBe(true)
311
299
  expect(refsMap.has('app.bsky.feed.post')).toBe(true)
@@ -313,11 +301,8 @@ describe('TypedUnionSchema', () => {
313
301
  })
314
302
 
315
303
  describe('edge cases', () => {
316
- const schema = new TypedUnionSchema(
317
- [
318
- new TypedRefSchema(() => personSchema),
319
- new TypedRefSchema(() => postSchema),
320
- ],
304
+ const schema = typedUnion(
305
+ [typedRef(() => personSchema), typedRef(() => postSchema)],
321
306
  true,
322
307
  )
323
308
 
@@ -330,10 +315,7 @@ describe('TypedUnionSchema', () => {
330
315
  })
331
316
 
332
317
  it('validates object with $type as empty string in open union', () => {
333
- const openSchema = new TypedUnionSchema(
334
- [new TypedRefSchema(() => personSchema)],
335
- false,
336
- )
318
+ const openSchema = typedUnion([typedRef(() => personSchema)], false)
337
319
  const result = openSchema.safeParse({
338
320
  $type: '',
339
321
  someProperty: 'value',
@@ -361,18 +343,12 @@ describe('TypedUnionSchema', () => {
361
343
 
362
344
  describe('closed property', () => {
363
345
  it('exposes closed property as true', () => {
364
- const schema = new TypedUnionSchema(
365
- [new TypedRefSchema(() => personSchema)],
366
- true,
367
- )
346
+ const schema = typedUnion([typedRef(() => personSchema)], true)
368
347
  expect(schema.closed).toBe(true)
369
348
  })
370
349
 
371
350
  it('exposes closed property as false', () => {
372
- const schema = new TypedUnionSchema(
373
- [new TypedRefSchema(() => personSchema)],
374
- false,
375
- )
351
+ const schema = typedUnion([typedRef(() => personSchema)], false)
376
352
  expect(schema.closed).toBe(false)
377
353
  })
378
354
  })
@@ -1,53 +1,28 @@
1
1
  import { isPlainObject } from '@atproto/lex-data'
2
2
  import {
3
- Infer,
4
- Restricted,
3
+ InferInput,
4
+ InferOutput,
5
5
  Schema,
6
- ValidationResult,
7
- ValidatorContext,
6
+ Unknown$TypedObject,
7
+ ValidationContext,
8
8
  } from '../core.js'
9
9
  import { lazyProperty } from '../util/lazy-property.js'
10
- import { TypedRefSchema, TypedRefSchemaOutput } from './typed-ref.js'
11
-
12
- export type TypedRef<T extends { $type?: string }> = TypedRefSchemaOutput<T>
13
-
14
- export type TypedObject = { $type: string } & {
15
- // In order to prevent places that expect an open union from accepting an
16
- // invalid version of the known typed objects, we need to prevent any other
17
- // properties from being present.
18
- //
19
- // For example, if an open union expects:
20
- // ```ts
21
- // TypedObject | { $type: 'A'; a: number }
22
- // ```
23
- // we don't want it to accept:
24
- // ```ts
25
- // { $type: 'A' }
26
- // ```
27
- // Which would be the case as `{ $type: 'A' }` is a valid
28
- // `TypedObject`. By adding an index signature that forbids any
29
- // property, we ensure that only valid known typed objects can be used.
30
- [K in string]: Restricted<'Unknown property'>
31
- }
32
-
33
- type TypedRefSchemasToUnion<T extends readonly TypedRefSchema[]> = {
34
- [K in keyof T]: Infer<T[K]>
35
- }[number]
36
-
37
- export type TypedUnionSchemaOutput<
38
- TypedRefs extends readonly TypedRefSchema[],
39
- Closed extends boolean,
40
- > = Closed extends true
41
- ? TypedRefSchemasToUnion<TypedRefs>
42
- : TypedRefSchemasToUnion<TypedRefs> | TypedObject
10
+ import { TypedRefSchema } from './typed-ref.js'
43
11
 
44
12
  export class TypedUnionSchema<
45
- TypedRefs extends readonly TypedRefSchema[] = any,
46
- Closed extends boolean = any,
47
- > extends Schema<TypedUnionSchemaOutput<TypedRefs, Closed>> {
13
+ const TValidators extends readonly TypedRefSchema[] = [],
14
+ const TClosed extends boolean = boolean,
15
+ > extends Schema<
16
+ TClosed extends true
17
+ ? InferInput<TValidators[number]>
18
+ : InferInput<TValidators[number]> | Unknown$TypedObject,
19
+ TClosed extends true
20
+ ? InferOutput<TValidators[number]>
21
+ : InferOutput<TValidators[number]> | Unknown$TypedObject
22
+ > {
48
23
  constructor(
49
- protected readonly refs: TypedRefs,
50
- public readonly closed: Closed,
24
+ protected readonly validators: TValidators,
25
+ public readonly closed: TClosed,
51
26
  ) {
52
27
  // @NOTE In order to avoid circular dependency issues, we don't access the
53
28
  // refs's schema (or $type) here. Instead, we access them lazily when first
@@ -56,42 +31,45 @@ export class TypedUnionSchema<
56
31
  super()
57
32
  }
58
33
 
59
- get refsMap(): Map<unknown, TypedRefs[number]> {
60
- const map = new Map<unknown, TypedRefs[number]>()
61
- for (const ref of this.refs) map.set(ref.$type, ref)
34
+ get validatorsMap(): Map<unknown, TValidators[number]> {
35
+ const map = new Map<unknown, TValidators[number]>()
36
+ for (const ref of this.validators) map.set(ref.$type, ref)
62
37
 
63
- return lazyProperty(this, 'refsMap', map)
38
+ return lazyProperty(this, 'validatorsMap', map)
64
39
  }
65
40
 
66
41
  get $types() {
67
- return Array.from(this.refsMap.keys())
42
+ return Array.from(this.validatorsMap.keys())
68
43
  }
69
44
 
70
- validateInContext(
71
- input: unknown,
72
- ctx: ValidatorContext,
73
- ): ValidationResult<TypedUnionSchemaOutput<TypedRefs, Closed>> {
45
+ validateInContext(input: unknown, ctx: ValidationContext) {
74
46
  if (!isPlainObject(input) || !('$type' in input)) {
75
47
  return ctx.issueInvalidType(input, '$typed')
76
48
  }
77
49
 
78
50
  const { $type } = input
79
51
 
80
- const def = this.refsMap.get($type)
81
- if (def) {
82
- const result = ctx.validate(input, def)
83
- return result as ValidationResult<
84
- TypedUnionSchemaOutput<TypedRefs, Closed>
85
- >
52
+ const validator = this.validatorsMap.get($type)
53
+ if (validator) {
54
+ return ctx.validate(input, validator)
86
55
  }
87
56
 
88
57
  if (this.closed) {
89
58
  return ctx.issueInvalidPropertyValue(input, '$type', this.$types)
90
59
  }
60
+
91
61
  if (typeof $type !== 'string') {
92
62
  return ctx.issueInvalidPropertyType(input, '$type', 'string')
93
63
  }
94
64
 
95
- return ctx.success(input as TypedUnionSchemaOutput<TypedRefs, Closed>)
65
+ return ctx.success(input)
96
66
  }
97
67
  }
68
+
69
+ /*@__NO_SIDE_EFFECTS__*/
70
+ export function typedUnion<
71
+ const R extends readonly TypedRefSchema[],
72
+ const C extends boolean,
73
+ >(refs: R, closed: C) {
74
+ return new TypedUnionSchema<R, C>(refs, closed)
75
+ }
@@ -1,15 +1,12 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { BooleanSchema } from './boolean.js'
3
- import { IntegerSchema } from './integer.js'
4
- import { ObjectSchema } from './object.js'
5
- import { StringSchema } from './string.js'
6
- import { UnionSchema } from './union.js'
2
+ import { boolean } from './boolean.js'
3
+ import { integer } from './integer.js'
4
+ import { object } from './object.js'
5
+ import { string } from './string.js'
6
+ import { union } from './union.js'
7
7
 
8
8
  describe('UnionSchema', () => {
9
- const stringOrNumber = new UnionSchema([
10
- new StringSchema({}),
11
- new IntegerSchema({}),
12
- ])
9
+ const stringOrNumber = union([string(), integer()])
13
10
 
14
11
  it('validates string input', () => {
15
12
  const result = stringOrNumber.safeParse('hello')
@@ -47,14 +44,14 @@ describe('UnionSchema', () => {
47
44
  })
48
45
 
49
46
  describe('with object types', () => {
50
- const schema = new UnionSchema([
51
- new ObjectSchema({
52
- type: new StringSchema({}),
53
- name: new StringSchema({}),
47
+ const schema = union([
48
+ object({
49
+ type: string(),
50
+ name: string(),
54
51
  }),
55
- new ObjectSchema({
56
- type: new StringSchema({}),
57
- age: new IntegerSchema({}),
52
+ object({
53
+ type: string(),
54
+ age: integer(),
58
55
  }),
59
56
  ])
60
57
 
@@ -91,11 +88,7 @@ describe('UnionSchema', () => {
91
88
  })
92
89
 
93
90
  describe('with three types', () => {
94
- const schema = new UnionSchema([
95
- new StringSchema({}),
96
- new IntegerSchema({}),
97
- new BooleanSchema({}),
98
- ])
91
+ const schema = union([string(), integer(), boolean()])
99
92
 
100
93
  it('validates string input', () => {
101
94
  const result = schema.safeParse('text')
@@ -124,10 +117,7 @@ describe('UnionSchema', () => {
124
117
  })
125
118
 
126
119
  describe('with constrained types', () => {
127
- const schema = new UnionSchema([
128
- new StringSchema({ minLength: 5 }),
129
- new IntegerSchema({ minimum: 100 }),
130
- ])
120
+ const schema = union([string({ minLength: 5 }), integer({ minimum: 100 })])
131
121
 
132
122
  it('validates string meeting constraint', () => {
133
123
  const result = schema.safeParse('hello')
@@ -157,13 +147,13 @@ describe('UnionSchema', () => {
157
147
 
158
148
  describe('edge cases', () => {
159
149
  it('validates with single type in union', () => {
160
- const schema = new UnionSchema([new StringSchema({})])
150
+ const schema = union([string()])
161
151
  const result = schema.safeParse('test')
162
152
  expect(result.success).toBe(true)
163
153
  })
164
154
 
165
155
  it('rejects when single type in union does not match', () => {
166
- const schema = new UnionSchema([new StringSchema({})])
156
+ const schema = union([string()])
167
157
  const result = schema.safeParse(123)
168
158
  expect(result.success).toBe(false)
169
159
  })
@@ -1,38 +1,42 @@
1
1
  import {
2
- Infer,
2
+ InferInput,
3
+ InferOutput,
3
4
  Schema,
5
+ ValidationContext,
4
6
  ValidationError,
5
7
  ValidationFailure,
6
- ValidationResult,
7
8
  Validator,
8
- ValidatorContext,
9
9
  } from '../core.js'
10
10
 
11
11
  export type UnionSchemaValidators = readonly [Validator, ...Validator[]]
12
- export type UnionSchemaOutput<V extends readonly Validator[]> = Infer<V[number]>
13
12
 
14
- export class UnionSchema<V extends UnionSchemaValidators = any> extends Schema<
15
- UnionSchemaOutput<V>
13
+ export class UnionSchema<
14
+ const TValidators extends UnionSchemaValidators = any,
15
+ > extends Schema<
16
+ InferInput<TValidators[number]>,
17
+ InferOutput<TValidators[number]>
16
18
  > {
17
- constructor(protected readonly validators: V) {
19
+ constructor(protected readonly validators: TValidators) {
18
20
  super()
19
21
  }
20
22
 
21
- validateInContext(
22
- input: unknown,
23
- ctx: ValidatorContext,
24
- ): ValidationResult<UnionSchemaOutput<V>> {
23
+ validateInContext(input: unknown, ctx: ValidationContext) {
25
24
  const failures: ValidationFailure[] = []
26
25
 
27
26
  for (const validator of this.validators) {
28
27
  const result = ctx.validate(input, validator)
29
- if (result.success) {
30
- return result as ValidationResult<UnionSchemaOutput<V>>
31
- } else {
32
- failures.push(result)
33
- }
28
+ if (result.success) return result
29
+
30
+ failures.push(result)
34
31
  }
35
32
 
36
33
  return ctx.failure(ValidationError.fromFailures(failures))
37
34
  }
38
35
  }
36
+
37
+ /*@__NO_SIDE_EFFECTS__*/
38
+ export function union<const TValidators extends UnionSchemaValidators>(
39
+ validators: TValidators,
40
+ ) {
41
+ return new UnionSchema<TValidators>(validators)
42
+ }
@@ -1,9 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { UnknownObjectSchema } from './unknown-object.js'
2
+ import { unknownObject } from './unknown-object.js'
3
3
 
4
4
  describe('UnknownObjectSchema', () => {
5
5
  describe('basic validation', () => {
6
- const schema = new UnknownObjectSchema()
6
+ const schema = unknownObject()
7
7
 
8
8
  it('accepts empty plain objects', () => {
9
9
  const result = schema.safeParse({})
@@ -106,7 +106,7 @@ describe('UnknownObjectSchema', () => {
106
106
  })
107
107
 
108
108
  describe('rejects non-plain-objects', () => {
109
- const schema = new UnknownObjectSchema()
109
+ const schema = unknownObject()
110
110
 
111
111
  it('rejects strings', () => {
112
112
  const result = schema.safeParse('not an object')
@@ -208,7 +208,7 @@ describe('UnknownObjectSchema', () => {
208
208
  })
209
209
 
210
210
  describe('rejects invalid value types', () => {
211
- const schema = new UnknownObjectSchema()
211
+ const schema = unknownObject()
212
212
 
213
213
  it('rejects objects with floating point numbers', () => {
214
214
  const result = schema.safeParse({ value: 3.14 })
@@ -288,7 +288,7 @@ describe('UnknownObjectSchema', () => {
288
288
  })
289
289
 
290
290
  describe('rejects invalid nested values', () => {
291
- const schema = new UnknownObjectSchema()
291
+ const schema = unknownObject()
292
292
 
293
293
  it('rejects deeply nested invalid values', () => {
294
294
  const result = schema.safeParse({
@@ -335,7 +335,7 @@ describe('UnknownObjectSchema', () => {
335
335
  })
336
336
 
337
337
  describe('edge cases', () => {
338
- const schema = new UnknownObjectSchema()
338
+ const schema = unknownObject()
339
339
 
340
340
  it('accepts objects with numeric string keys', () => {
341
341
  const obj = { '0': 'zero', '1': 'one', '2': 'two' }
@@ -500,7 +500,7 @@ describe('UnknownObjectSchema', () => {
500
500
  })
501
501
 
502
502
  describe('large objects', () => {
503
- const schema = new UnknownObjectSchema()
503
+ const schema = unknownObject()
504
504
 
505
505
  it('accepts objects with many keys', () => {
506
506
  const obj: Record<string, number> = {}
@@ -538,7 +538,7 @@ describe('UnknownObjectSchema', () => {
538
538
  })
539
539
 
540
540
  describe('preservation of input', () => {
541
- const schema = new UnknownObjectSchema()
541
+ const schema = unknownObject()
542
542
 
543
543
  it('preserves the original object reference', () => {
544
544
  const input = { key: 'value', count: 42 }
@@ -1,13 +1,11 @@
1
1
  import { LexMap, isLexMap } from '@atproto/lex-data'
2
- import { Schema, ValidationResult, ValidatorContext } from '../core.js'
2
+ import { Schema, ValidationContext } from '../core.js'
3
+ import { memoizedOptions } from '../util/memoize.js'
3
4
 
4
- export type UnknownObjectOutput = LexMap
5
+ export type UnknownObject = LexMap
5
6
 
6
- export class UnknownObjectSchema extends Schema<UnknownObjectOutput> {
7
- validateInContext(
8
- input: unknown,
9
- ctx: ValidatorContext,
10
- ): ValidationResult<UnknownObjectOutput> {
7
+ export class UnknownObjectSchema extends Schema<UnknownObject> {
8
+ validateInContext(input: unknown, ctx: ValidationContext) {
11
9
  if (isLexMap(input)) {
12
10
  return ctx.success(input)
13
11
  }
@@ -15,3 +13,7 @@ export class UnknownObjectSchema extends Schema<UnknownObjectOutput> {
15
13
  return ctx.issueInvalidType(input, 'unknown')
16
14
  }
17
15
  }
16
+
17
+ export const unknownObject = /*#__PURE__*/ memoizedOptions(function () {
18
+ return new UnknownObjectSchema()
19
+ })