@atproto/lex-schema 0.1.5 → 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 +14 -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,126 +0,0 @@
1
- import {
2
- $Typed,
3
- InferInput,
4
- InferOutput,
5
- Schema,
6
- ValidationContext,
7
- Validator,
8
- } from '../core.js'
9
-
10
- /**
11
- * Interface for validators that have a $type property.
12
- *
13
- * Used by typed objects and records to identify their type in unions.
14
- *
15
- * @template TInput - The input type (with optional $type)
16
- * @template TOutput - The output type (with non-optional $type)
17
- */
18
- export interface TypedObjectValidator<
19
- TInput extends { $type?: string } = { $type?: string },
20
- TOutput extends TInput = TInput,
21
- > extends Validator<TInput, TOutput> {
22
- $type: NonNullable<TOutput['$type']>
23
- }
24
-
25
- /**
26
- * Function type that returns a typed object validator, used for lazy resolution.
27
- *
28
- * @template TValidator - The typed object validator type
29
- */
30
- export type TypedRefGetter<out TValidator extends TypedObjectValidator> =
31
- () => TValidator
32
-
33
- /**
34
- * Schema for referencing typed objects with lazy resolution.
35
- *
36
- * Used in typed unions to reference typed object schemas. Requires the
37
- * `$type` field to be present and match the referenced schema's type.
38
- * The referenced schema is resolved lazily to support circular references.
39
- *
40
- * @template TValidator - The referenced typed object validator type
41
- *
42
- * @example
43
- * ```ts
44
- * const ref = new TypedRefSchema(() => imageViewSchema)
45
- * // ref.$type === 'app.bsky.embed.images#view'
46
- * ```
47
- */
48
- export class TypedRefSchema<
49
- const TValidator extends TypedObjectValidator = TypedObjectValidator,
50
- > extends Schema<
51
- $Typed<InferInput<TValidator>>,
52
- $Typed<InferOutput<TValidator>>
53
- > {
54
- readonly type = 'typedRef' as const
55
-
56
- #getter: TypedRefGetter<TValidator>
57
-
58
- constructor(getter: TypedRefGetter<TValidator>) {
59
- // @NOTE In order to avoid circular dependency issues, we don't resolve
60
- // the schema here. Instead, we resolve it lazily when first accessed.
61
-
62
- super()
63
-
64
- this.#getter = getter
65
- }
66
-
67
- get validator(): TValidator {
68
- return this.#getter.call(null)
69
- }
70
-
71
- get $type(): TValidator['$type'] {
72
- return this.validator.$type
73
- }
74
-
75
- validateInContext(input: unknown, ctx: ValidationContext) {
76
- const result = ctx.validate(input, this.validator)
77
- if (!result.success) return result
78
-
79
- if (result.value.$type !== this.$type) {
80
- return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
81
- }
82
-
83
- return result
84
- }
85
- }
86
-
87
- /**
88
- * Creates a reference to a typed object schema for use in typed unions.
89
- *
90
- * Unlike regular `ref()`, this requires the referenced schema to have a
91
- * `$type` property, and validates that the input's `$type` matches.
92
- *
93
- * @param get - Function that returns the typed object validator
94
- * @returns A new {@link TypedRefSchema} instance
95
- *
96
- * @example
97
- * ```ts
98
- * // Reference to image embed view
99
- * const imageRef = l.typedRef(() => imageViewSchema)
100
- *
101
- * // Use in a typed union
102
- * const embedUnion = l.typedUnion([
103
- * l.typedRef(() => imageViewSchema),
104
- * l.typedRef(() => videoViewSchema),
105
- * l.typedRef(() => externalViewSchema),
106
- * ], true) // closed union
107
- *
108
- * // The $type is accessible on the ref
109
- * console.log(imageRef.$type) // 'app.bsky.embed.images#view'
110
- * ```
111
- */
112
- /*@__NO_SIDE_EFFECTS__*/
113
- export function typedRef<const TValidator extends TypedObjectValidator>(
114
- get: TypedRefGetter<TValidator>,
115
- ): TypedRefSchema<TValidator>
116
- export function typedRef<
117
- TInput extends { $type?: string },
118
- TOutput extends TInput = TInput,
119
- >(
120
- get: TypedRefGetter<TypedObjectValidator<TInput, TOutput>>,
121
- ): TypedRefSchema<TypedObjectValidator<TInput, TOutput>>
122
- export function typedRef<const TValidator extends TypedObjectValidator>(
123
- get: TypedRefGetter<TValidator>,
124
- ): TypedRefSchema<TValidator> {
125
- return new TypedRefSchema<TValidator>(get)
126
- }
@@ -1,355 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
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
-
9
- describe('TypedUnionSchema', () => {
10
- const personSchema = typedObject(
11
- 'app.bsky.actor.person',
12
- 'main',
13
- object({
14
- name: string(),
15
- age: integer(),
16
- }),
17
- )
18
-
19
- const postSchema = typedObject(
20
- 'app.bsky.feed.post',
21
- 'main',
22
- object({
23
- text: string(),
24
- createdAt: string(),
25
- }),
26
- )
27
-
28
- const commentSchema = typedObject(
29
- 'app.bsky.feed.comment',
30
- 'main',
31
- object({
32
- text: string(),
33
- parentUri: string(),
34
- }),
35
- )
36
-
37
- describe('closed union', () => {
38
- const schema = typedUnion(
39
- [typedRef(() => personSchema), typedRef(() => postSchema)],
40
- true,
41
- )
42
-
43
- it('validates first type in union', () => {
44
- const result = schema.safeParse({
45
- $type: 'app.bsky.actor.person',
46
- name: 'Alice',
47
- age: 30,
48
- })
49
- expect(result.success).toBe(true)
50
- })
51
-
52
- it('validates second type in union', () => {
53
- const result = schema.safeParse({
54
- $type: 'app.bsky.feed.post',
55
- text: 'Hello world',
56
- createdAt: '2023-01-01T00:00:00Z',
57
- })
58
- expect(result.success).toBe(true)
59
- })
60
-
61
- it('rejects unknown $type in closed union', () => {
62
- const result = schema.safeParse({
63
- $type: 'app.bsky.feed.like',
64
- subject: 'some-uri',
65
- })
66
- expect(result.success).toBe(false)
67
- })
68
-
69
- it('rejects object without $type', () => {
70
- const result = schema.safeParse({
71
- name: 'Alice',
72
- age: 30,
73
- })
74
- expect(result.success).toBe(false)
75
- })
76
-
77
- it('rejects object with invalid property for the $type', () => {
78
- const result = schema.safeParse({
79
- $type: 'app.bsky.actor.person',
80
- name: 'Alice',
81
- age: 'thirty',
82
- })
83
- expect(result.success).toBe(false)
84
- })
85
-
86
- it('rejects object missing required properties', () => {
87
- const result = schema.safeParse({
88
- $type: 'app.bsky.actor.person',
89
- name: 'Alice',
90
- })
91
- expect(result.success).toBe(false)
92
- })
93
-
94
- it('rejects non-object input', () => {
95
- const result = schema.safeParse('not an object')
96
- expect(result.success).toBe(false)
97
- })
98
-
99
- it('rejects null', () => {
100
- const result = schema.safeParse(null)
101
- expect(result.success).toBe(false)
102
- })
103
-
104
- it('rejects undefined', () => {
105
- const result = schema.safeParse(undefined)
106
- expect(result.success).toBe(false)
107
- })
108
-
109
- it('rejects array', () => {
110
- const result = schema.safeParse([])
111
- expect(result.success).toBe(false)
112
- })
113
-
114
- it('rejects number', () => {
115
- const result = schema.safeParse(42)
116
- expect(result.success).toBe(false)
117
- })
118
-
119
- it('rejects boolean', () => {
120
- const result = schema.safeParse(true)
121
- expect(result.success).toBe(false)
122
- })
123
- })
124
-
125
- describe('open union', () => {
126
- const schema = typedUnion(
127
- [typedRef(() => personSchema), typedRef(() => postSchema)],
128
- false,
129
- )
130
-
131
- it('validates known type', () => {
132
- const result = schema.safeParse({
133
- $type: 'app.bsky.actor.person',
134
- name: 'Alice',
135
- age: 30,
136
- })
137
- expect(result.success).toBe(true)
138
- })
139
-
140
- it('validates unknown $type with valid structure', () => {
141
- const result = schema.safeParse({
142
- $type: 'app.bsky.feed.like',
143
- subject: 'some-uri',
144
- createdAt: '2023-01-01T00:00:00Z',
145
- })
146
- expect(result.success).toBe(true)
147
- })
148
-
149
- it('accepts any properties for unknown $type', () => {
150
- const result = schema.safeParse({
151
- $type: 'unknown.nsid.type',
152
- anyProperty: 'any value',
153
- anotherProperty: 123,
154
- })
155
- expect(result.success).toBe(true)
156
- })
157
-
158
- it('rejects unknown $type with non-string $type', () => {
159
- const result = schema.safeParse({
160
- $type: 123,
161
- someProperty: 'value',
162
- })
163
- expect(result.success).toBe(false)
164
- })
165
-
166
- it('rejects unknown $type with null $type', () => {
167
- const result = schema.safeParse({
168
- $type: null,
169
- someProperty: 'value',
170
- })
171
- expect(result.success).toBe(false)
172
- })
173
-
174
- it('rejects object without $type', () => {
175
- const result = schema.safeParse({
176
- name: 'Alice',
177
- age: 30,
178
- })
179
- expect(result.success).toBe(false)
180
- })
181
-
182
- it('rejects non-object input', () => {
183
- const result = schema.safeParse('not an object')
184
- expect(result.success).toBe(false)
185
- })
186
-
187
- it('validates known type with extra properties', () => {
188
- const result = schema.safeParse({
189
- $type: 'app.bsky.actor.person',
190
- name: 'Alice',
191
- age: 30,
192
- extraProperty: 'extra',
193
- })
194
- expect(result.success).toBe(true)
195
- })
196
-
197
- it('rejects known type with invalid property types', () => {
198
- const result = schema.safeParse({
199
- $type: 'app.bsky.actor.person',
200
- name: 123,
201
- age: 30,
202
- })
203
- expect(result.success).toBe(false)
204
- })
205
- })
206
-
207
- describe('with three types', () => {
208
- const schema = typedUnion(
209
- [
210
- typedRef(() => personSchema),
211
- typedRef(() => postSchema),
212
- typedRef(() => commentSchema),
213
- ],
214
- true,
215
- )
216
-
217
- it('validates first type', () => {
218
- const result = schema.safeParse({
219
- $type: 'app.bsky.actor.person',
220
- name: 'Alice',
221
- age: 30,
222
- })
223
- expect(result.success).toBe(true)
224
- })
225
-
226
- it('validates second type', () => {
227
- const result = schema.safeParse({
228
- $type: 'app.bsky.feed.post',
229
- text: 'Hello',
230
- createdAt: '2023-01-01T00:00:00Z',
231
- })
232
- expect(result.success).toBe(true)
233
- })
234
-
235
- it('validates third type', () => {
236
- const result = schema.safeParse({
237
- $type: 'app.bsky.feed.comment',
238
- text: 'Nice post!',
239
- parentUri: 'at://did:plc:xyz/app.bsky.feed.post/123',
240
- })
241
- expect(result.success).toBe(true)
242
- })
243
-
244
- it('rejects unknown type', () => {
245
- const result = schema.safeParse({
246
- $type: 'app.bsky.feed.like',
247
- subject: 'some-uri',
248
- })
249
- expect(result.success).toBe(false)
250
- })
251
- })
252
-
253
- describe('with single type', () => {
254
- const schema = typedUnion([typedRef(() => personSchema)], true)
255
-
256
- it('validates the single type', () => {
257
- const result = schema.safeParse({
258
- $type: 'app.bsky.actor.person',
259
- name: 'Alice',
260
- age: 30,
261
- })
262
- expect(result.success).toBe(true)
263
- })
264
-
265
- it('rejects different type', () => {
266
- const result = schema.safeParse({
267
- $type: 'app.bsky.feed.post',
268
- text: 'Hello',
269
- createdAt: '2023-01-01T00:00:00Z',
270
- })
271
- expect(result.success).toBe(false)
272
- })
273
- })
274
-
275
- describe('$types getter', () => {
276
- const schema = typedUnion(
277
- [typedRef(() => personSchema), typedRef(() => postSchema)],
278
- true,
279
- )
280
-
281
- it('returns array of valid $type values', () => {
282
- const types = schema.$types
283
- expect(types).toContain('app.bsky.actor.person')
284
- expect(types).toContain('app.bsky.feed.post')
285
- expect(types.length).toBe(2)
286
- })
287
- })
288
-
289
- describe('refsMap getter', () => {
290
- const schema = typedUnion(
291
- [typedRef(() => personSchema), typedRef(() => postSchema)],
292
- true,
293
- )
294
-
295
- it('returns map of $type to ref schema', () => {
296
- const refsMap = schema.validatorsMap
297
- expect(refsMap.size).toBe(2)
298
- expect(refsMap.has('app.bsky.actor.person')).toBe(true)
299
- expect(refsMap.has('app.bsky.feed.post')).toBe(true)
300
- })
301
- })
302
-
303
- describe('edge cases', () => {
304
- const schema = typedUnion(
305
- [typedRef(() => personSchema), typedRef(() => postSchema)],
306
- true,
307
- )
308
-
309
- it('rejects object with $type as empty string in closed union', () => {
310
- const result = schema.safeParse({
311
- $type: '',
312
- name: 'Alice',
313
- })
314
- expect(result.success).toBe(false)
315
- })
316
-
317
- it('validates object with $type as empty string in open union', () => {
318
- const openSchema = typedUnion([typedRef(() => personSchema)], false)
319
- const result = openSchema.safeParse({
320
- $type: '',
321
- someProperty: 'value',
322
- })
323
- expect(result.success).toBe(true)
324
- })
325
-
326
- it('rejects plain object with only $type', () => {
327
- const result = schema.safeParse({
328
- $type: 'app.bsky.actor.person',
329
- })
330
- expect(result.success).toBe(false)
331
- })
332
-
333
- it('handles object with $type and undefined properties', () => {
334
- const result = schema.safeParse({
335
- $type: 'app.bsky.actor.person',
336
- name: 'Alice',
337
- age: 30,
338
- extra: undefined,
339
- })
340
- expect(result.success).toBe(true)
341
- })
342
- })
343
-
344
- describe('closed property', () => {
345
- it('exposes closed property as true', () => {
346
- const schema = typedUnion([typedRef(() => personSchema)], true)
347
- expect(schema.closed).toBe(true)
348
- })
349
-
350
- it('exposes closed property as false', () => {
351
- const schema = typedUnion([typedRef(() => personSchema)], false)
352
- expect(schema.closed).toBe(false)
353
- })
354
- })
355
- })
@@ -1,130 +0,0 @@
1
- import { isPlainObject } from '@atproto/lex-data'
2
- import {
3
- InferInput,
4
- InferOutput,
5
- Schema,
6
- Unknown$TypedObject,
7
- ValidationContext,
8
- } from '../core.js'
9
- import { lazyProperty } from '../util/lazy-property.js'
10
- import { TypedObjectSchema } from './typed-object.js'
11
- import { TypedRefSchema } from './typed-ref.js'
12
-
13
- /**
14
- * Schema for Lexicon typed unions (unions discriminated by $type).
15
- *
16
- * Typed unions are collections of typed objects identified by their `$type`
17
- * field. Can be "open" (accept unknown types) or "closed" (only accept
18
- * known types).
19
- *
20
- * @template TValidators - Tuple of {@link TypedRefSchema} or {@link TypedObjectSchema} instances
21
- * @template TClosed - Whether the union is closed (rejects unknown $types)
22
- *
23
- * @example
24
- * ```ts
25
- * const embedUnion = new TypedUnionSchema([
26
- * l.typedRef(() => imageSchema),
27
- * l.typedRef(() => videoSchema),
28
- * ], true) // closed - only accepts images and videos
29
- * ```
30
- */
31
- export class TypedUnionSchema<
32
- const TValidators extends readonly (
33
- | TypedRefSchema
34
- | TypedObjectSchema
35
- )[] = [],
36
- const TClosed extends boolean = boolean,
37
- > extends Schema<
38
- TClosed extends true
39
- ? InferInput<TValidators[number]>
40
- : InferInput<TValidators[number]> | Unknown$TypedObject,
41
- TClosed extends true
42
- ? InferOutput<TValidators[number]>
43
- : InferOutput<TValidators[number]> | Unknown$TypedObject
44
- > {
45
- readonly type = 'typedUnion' as const
46
-
47
- constructor(
48
- protected readonly validators: TValidators,
49
- public readonly closed: TClosed,
50
- ) {
51
- // @NOTE In order to avoid circular dependency issues, we don't access the
52
- // refs's schema (or $type) here. Instead, we access them lazily when first
53
- // needed. The biggest issue with this strategy is that we can't throw
54
- // early if the refs contain multiple refs with the same $type.
55
-
56
- super()
57
- }
58
-
59
- get validatorsMap(): Map<unknown, TValidators[number]> {
60
- const map = new Map<unknown, TValidators[number]>()
61
- for (const ref of this.validators) map.set(ref.$type, ref)
62
-
63
- return lazyProperty(this, 'validatorsMap', map)
64
- }
65
-
66
- get $types() {
67
- return Array.from(this.validatorsMap.keys())
68
- }
69
-
70
- validateInContext(input: unknown, ctx: ValidationContext) {
71
- if (!isPlainObject(input) || !('$type' in input)) {
72
- return ctx.issueUnexpectedType(input, '$typed')
73
- }
74
-
75
- const { $type } = input
76
-
77
- const validator = this.validatorsMap.get($type)
78
- if (validator) {
79
- return ctx.validate(input, validator)
80
- }
81
-
82
- if (this.closed) {
83
- return ctx.issueInvalidPropertyValue(input, '$type', this.$types)
84
- }
85
-
86
- if (typeof $type !== 'string') {
87
- return ctx.issueInvalidPropertyType(input, '$type', 'string')
88
- }
89
-
90
- return ctx.success(input)
91
- }
92
- }
93
-
94
- /**
95
- * Creates a typed union schema for Lexicon unions.
96
- *
97
- * Typed unions discriminate variants by their `$type` field. Can be open
98
- * (accepts unknown types, useful for extensibility) or closed (strict).
99
- *
100
- * @param refs - Array of typed refs for the union variants
101
- * @param closed - Whether to reject unknown $type values
102
- * @returns A new {@link TypedUnionSchema} instance
103
- *
104
- * @example
105
- * ```ts
106
- * // Closed union - only accepts known types
107
- * const embedSchema = l.typedUnion([
108
- * l.typedRef(() => imageViewSchema),
109
- * l.typedRef(() => videoViewSchema),
110
- * l.typedRef(() => externalViewSchema),
111
- * ], true)
112
- *
113
- * // Open union - accepts unknown types for forward compatibility
114
- * const feedItemSchema = l.typedUnion([
115
- * l.typedRef(() => postSchema),
116
- * l.typedRef(() => repostSchema),
117
- * ], false) // unknown types pass through
118
- *
119
- * // Get all known $types
120
- * console.log(embedSchema.$types)
121
- * // ['app.bsky.embed.images#view', 'app.bsky.embed.video#view', ...]
122
- * ```
123
- */
124
- /*@__NO_SIDE_EFFECTS__*/
125
- export function typedUnion<
126
- const R extends readonly TypedRefSchema[],
127
- const C extends boolean,
128
- >(refs: R, closed: C) {
129
- return new TypedUnionSchema<R, C>(refs, closed)
130
- }