@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,745 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { Infer, Unknown$Type, Unknown$TypedObject } from '../core.js'
3
- import { enumSchema } from './enum.js'
4
- import { integer } from './integer.js'
5
- import { nullable } from './nullable.js'
6
- import { object } from './object.js'
7
- import { optional } from './optional.js'
8
- import { string } from './string.js'
9
- import { typedObject } from './typed-object.js'
10
-
11
- describe('TypedObjectSchema', () => {
12
- const schema = typedObject(
13
- 'app.bsky.feed.post',
14
- 'main',
15
- object({
16
- text: string(),
17
- likes: optional(integer()),
18
- }),
19
- )
20
- type Schema = Infer<typeof schema>
21
-
22
- describe('basic validation', () => {
23
- it('validates plain objects without $type', () => {
24
- const result = schema.safeParse({
25
- text: 'Hello world',
26
- likes: 5,
27
- })
28
- expect(result.success).toBe(true)
29
- })
30
-
31
- it('validates plain objects with matching $type', () => {
32
- const result = schema.safeParse({
33
- $type: 'app.bsky.feed.post',
34
- text: 'Hello world',
35
- likes: 5,
36
- })
37
- expect(result.success).toBe(true)
38
- })
39
-
40
- it('rejects objects with non-matching $type', () => {
41
- const result = schema.safeParse({
42
- $type: 'app.bsky.feed.like',
43
- text: 'Hello world',
44
- likes: 5,
45
- })
46
- expect(result.success).toBe(false)
47
- })
48
-
49
- it('rejects non-objects', () => {
50
- const result = schema.safeParse('not an object')
51
- expect(result.success).toBe(false)
52
- })
53
-
54
- it('rejects null', () => {
55
- const result = schema.safeParse(null)
56
- expect(result.success).toBe(false)
57
- })
58
-
59
- it('rejects undefined', () => {
60
- const result = schema.safeParse(undefined)
61
- expect(result.success).toBe(false)
62
- })
63
-
64
- it('rejects arrays', () => {
65
- const result = schema.safeParse(['text', 5])
66
- expect(result.success).toBe(false)
67
- })
68
-
69
- it('rejects numbers', () => {
70
- const result = schema.safeParse(123)
71
- expect(result.success).toBe(false)
72
- })
73
-
74
- it('rejects booleans', () => {
75
- const result = schema.safeParse(true)
76
- expect(result.success).toBe(false)
77
- })
78
- })
79
-
80
- describe('property validation', () => {
81
- it('rejects missing required properties', () => {
82
- const result = schema.safeParse({
83
- likes: 5,
84
- })
85
- expect(result.success).toBe(false)
86
- })
87
-
88
- it('validates optional properties', () => {
89
- const result = schema.safeParse({
90
- text: 'Hello world',
91
- })
92
- expect(result.success).toBe(true)
93
- })
94
-
95
- it('rejects invalid property types', () => {
96
- const result = schema.safeParse({
97
- text: 'Hello world',
98
- likes: 'five',
99
- })
100
- expect(result.success).toBe(false)
101
- })
102
-
103
- it('rejects invalid required property types', () => {
104
- const result = schema.safeParse({
105
- text: 123,
106
- likes: 5,
107
- })
108
- expect(result.success).toBe(false)
109
- })
110
-
111
- it('ignores extra properties', () => {
112
- const result = schema.safeParse({
113
- text: 'Hello world',
114
- likes: 5,
115
- extra: 'value',
116
- })
117
- expect(result.success).toBe(true)
118
- })
119
- })
120
-
121
- describe('$type validation', () => {
122
- it('treats undefined $type as valid', () => {
123
- const result = schema.safeParse({
124
- $type: undefined,
125
- text: 'Hello world',
126
- })
127
- expect(result.success).toBe(true)
128
- })
129
-
130
- it('rejects empty string $type', () => {
131
- const result = schema.safeParse({
132
- $type: '',
133
- text: 'Hello world',
134
- })
135
- expect(result.success).toBe(false)
136
- })
137
-
138
- it('rejects numeric $type', () => {
139
- const result = schema.safeParse({
140
- $type: 123,
141
- text: 'Hello world',
142
- })
143
- expect(result.success).toBe(false)
144
- })
145
-
146
- it('rejects object $type', () => {
147
- const result = schema.safeParse({
148
- $type: { type: 'app.bsky.feed.post' },
149
- text: 'Hello world',
150
- })
151
- expect(result.success).toBe(false)
152
- })
153
-
154
- it('rejects non-normalized $type', () => {
155
- const result = schema.safeParse({
156
- $type: 'app.bsky.feed.post#main',
157
- text: 'Hello world',
158
- })
159
- expect(result.success).toBe(false)
160
- })
161
-
162
- it('rejects $type with extra characters', () => {
163
- const result = schema.safeParse({
164
- $type: 'app.bsky.feed.post#main-extra',
165
- text: 'Hello world',
166
- })
167
- expect(result.success).toBe(false)
168
- })
169
-
170
- it('rejects case-mismatched $type', () => {
171
- const result = schema.safeParse({
172
- $type: 'APP.BSKY.FEED.POST#MAIN',
173
- text: 'Hello world',
174
- })
175
- expect(result.success).toBe(false)
176
- })
177
- })
178
-
179
- describe('isTypeOf method', () => {
180
- it('returns true for objects without $type', () => {
181
- const obj = { text: 'Hello' }
182
- expect(schema.isTypeOf(obj)).toBe(true)
183
- })
184
-
185
- it('returns true for objects with undefined $type', () => {
186
- const obj = { $type: undefined, text: 'Hello' }
187
- expect(schema.isTypeOf(obj)).toBe(true)
188
- })
189
-
190
- it('returns true for objects with matching $type', () => {
191
- const obj = { $type: 'app.bsky.feed.post', text: 'Hello' }
192
- expect(schema.isTypeOf(obj)).toBe(true)
193
- })
194
-
195
- it('returns false for objects with non-matching $type', () => {
196
- const obj = { $type: 'app.bsky.feed.like', text: 'Hello' }
197
- expect(schema.isTypeOf(obj)).toBe(false)
198
- })
199
-
200
- it('returns false for objects with empty $type', () => {
201
- const obj = { $type: '', text: 'Hello' }
202
- expect(schema.isTypeOf(obj)).toBe(false)
203
- })
204
-
205
- it('returns false for objects with numeric $type', () => {
206
- const obj = { $type: 123, text: 'Hello' }
207
- expect(schema.isTypeOf(obj)).toBe(false)
208
- })
209
-
210
- it('properly discriminates Unknown$TypeObject', () => {
211
- function foo(value: Unknown$TypedObject | Schema) {
212
- if (schema.isTypeOf(value)) {
213
- void value.text
214
- } else {
215
- // @ts-expect-error
216
- void value.text
217
- }
218
- }
219
-
220
- foo({
221
- $type: 'app.bsky.feed.post',
222
- text: 'aze',
223
- // @ts-expect-error
224
- unknownProperty: 'should not be allowed !',
225
- })
226
-
227
- foo({
228
- $type: 'blah' as Unknown$Type,
229
- // @ts-expect-error
230
- unknownProperty: 'should not be allowed !',
231
- })
232
- })
233
- })
234
-
235
- describe('$isTypeOf method', () => {
236
- it('returns true for objects without $type', () => {
237
- const obj = { text: 'Hello' }
238
- expect(schema.$isTypeOf(obj)).toBe(true)
239
- })
240
-
241
- it('returns true for objects with matching $type', () => {
242
- const obj = { $type: 'app.bsky.feed.post', text: 'Hello' }
243
- expect(schema.$isTypeOf(obj)).toBe(true)
244
- })
245
-
246
- it('returns false for objects with non-matching $type', () => {
247
- const obj = { $type: 'app.bsky.feed.like', text: 'Hello' }
248
- expect(schema.$isTypeOf(obj)).toBe(false)
249
- })
250
-
251
- it('behaves identically to isTypeOf', () => {
252
- const obj1 = { text: 'Hello' }
253
- const obj2 = { $type: 'app.bsky.feed.post', text: 'Hello' }
254
- const obj3 = { $type: 'app.bsky.feed.like', text: 'Hello' }
255
-
256
- expect(schema.$isTypeOf(obj1)).toBe(schema.isTypeOf(obj1))
257
- expect(schema.$isTypeOf(obj2)).toBe(schema.isTypeOf(obj2))
258
- expect(schema.$isTypeOf(obj3)).toBe(schema.isTypeOf(obj3))
259
- })
260
- })
261
-
262
- describe('build method', () => {
263
- it('adds $type to object without $type', () => {
264
- const input = { text: 'Hello world', likes: 5 }
265
- const result = schema.build(input)
266
- expect(result).toEqual({
267
- text: 'Hello world',
268
- likes: 5,
269
- $type: 'app.bsky.feed.post',
270
- })
271
- })
272
-
273
- it('adds $type to object with only required properties', () => {
274
- const input = { text: 'Hello world' }
275
- const result = schema.build(input)
276
- expect(result).toEqual({
277
- text: 'Hello world',
278
- $type: 'app.bsky.feed.post',
279
- })
280
- })
281
-
282
- it('preserves existing properties', () => {
283
- const input = { text: 'Hello', likes: 10, extra: 'value' } as any
284
- const result = schema.build(input)
285
- expect(result).toEqual({
286
- text: 'Hello',
287
- likes: 10,
288
- extra: 'value',
289
- $type: 'app.bsky.feed.post',
290
- })
291
- })
292
-
293
- it('does not mutate the input object', () => {
294
- const input = { text: 'Hello world', likes: 5 }
295
- const inputCopy = { ...input }
296
- schema.build(input)
297
- expect(input).toEqual(inputCopy)
298
- })
299
-
300
- it('adds $type to empty object', () => {
301
- const emptySchema = typedObject('app.bsky.test', 'main', object({}))
302
- const input = {}
303
- const result = emptySchema.build(input)
304
- expect(result).toEqual({ $type: 'app.bsky.test' })
305
- })
306
-
307
- describe('build() does not validate', () => {
308
- const validationSchema = typedObject(
309
- 'app.bsky.test',
310
- 'validation',
311
- object({
312
- actor: string({ format: 'did' }),
313
- count: integer(),
314
- }),
315
- )
316
-
317
- it('does not throw for invalid data', () => {
318
- const result = validationSchema.build({
319
- // @ts-expect-error
320
- actor: 'not-a-did',
321
- count: 123,
322
- })
323
-
324
- expect(result.$type).toBe('app.bsky.test#validation')
325
- expect(result.actor).toBe('not-a-did')
326
- expect(result.count).toBe(123)
327
- })
328
-
329
- it('does not throw for invalid types', () => {
330
- const result = validationSchema.build({
331
- actor: 'did:plc:abc123',
332
- // @ts-expect-error
333
- count: 'not-a-number',
334
- })
335
-
336
- expect(result.$type).toBe('app.bsky.test#validation')
337
- expect(result.count).toBe('not-a-number')
338
- })
339
-
340
- it('does not throw for missing required fields', () => {
341
- // @ts-expect-error
342
- const result = validationSchema.build({})
343
-
344
- expect(result.$type).toBe('app.bsky.test#validation')
345
- expect(result.actor).toBeUndefined()
346
- expect(result.count).toBeUndefined()
347
- })
348
-
349
- it('does not throw for extra fields', () => {
350
- const result = validationSchema.build({
351
- actor: 'did:plc:abc123',
352
- count: 42,
353
- // @ts-expect-error
354
- extra: 'unexpected',
355
- })
356
-
357
- expect(result.$type).toBe('app.bsky.test#validation')
358
- // @ts-expect-error
359
- expect(result.extra).toBe('unexpected')
360
- })
361
-
362
- it('parse() still validates after build()', () => {
363
- const built = validationSchema.build({
364
- // @ts-expect-error
365
- actor: 'not-a-did',
366
- count: 123,
367
- })
368
-
369
- expect(() => validationSchema.parse(built)).toThrow('Invalid DID')
370
- })
371
-
372
- it('safeParse() can detect validation errors after build()', () => {
373
- const built = validationSchema.build({
374
- // @ts-expect-error
375
- actor: 'not-a-did',
376
- count: 123,
377
- })
378
-
379
- const result = validationSchema.safeParse(built)
380
- expect(result.success).toBe(false)
381
- })
382
- })
383
- })
384
-
385
- describe('$build method', () => {
386
- it('adds $type to object without $type', () => {
387
- const input = { text: 'Hello world', likes: 5 }
388
- const result = schema.$build(input)
389
- expect(result).toEqual({
390
- text: 'Hello world',
391
- likes: 5,
392
- $type: 'app.bsky.feed.post',
393
- })
394
- })
395
-
396
- it('behaves identically to build', () => {
397
- const input1 = { text: 'Hello world', likes: 5 }
398
- const input2 = { text: 'Another post' }
399
-
400
- expect(schema.$build(input1)).toEqual(schema.build(input1))
401
- expect(schema.$build(input2)).toEqual(schema.build(input2))
402
- })
403
-
404
- it('does not mutate the input object', () => {
405
- const input = { text: 'Hello world' }
406
- const inputCopy = { ...input }
407
- schema.$build(input)
408
- expect(input).toEqual(inputCopy)
409
- })
410
- })
411
-
412
- describe('bound $ methods', () => {
413
- it('$build can be used as a detached function', () => {
414
- const { $build } = schema
415
- const result = $build({ text: 'Hello' })
416
- expect(result).toEqual({
417
- text: 'Hello',
418
- $type: 'app.bsky.feed.post',
419
- })
420
- })
421
-
422
- it('$isTypeOf can be used as a detached function', () => {
423
- const { $isTypeOf } = schema
424
- expect($isTypeOf({ text: 'Hello' })).toBe(true)
425
- expect($isTypeOf({ $type: 'app.bsky.feed.post', text: 'Hello' })).toBe(
426
- true,
427
- )
428
- expect($isTypeOf({ $type: 'other.type', text: 'Hello' })).toBe(false)
429
- })
430
-
431
- it('$parse can be used as a detached function', () => {
432
- const { $parse } = schema
433
- const result = $parse({ text: 'Hello' })
434
- expect(result).toEqual({ text: 'Hello' })
435
- })
436
-
437
- it('$matches can be used as a detached function', () => {
438
- const { $matches } = schema
439
- expect($matches({ text: 'Hello' })).toBe(true)
440
- expect($matches(42)).toBe(false)
441
- })
442
-
443
- it('lazy property returns the same function on repeated access', () => {
444
- const fn1 = schema.$build
445
- const fn2 = schema.$build
446
- expect(fn1).toBe(fn2)
447
- })
448
- })
449
-
450
- describe('with complex nested schemas', () => {
451
- const complexSchema = typedObject(
452
- 'app.bsky.actor.profile',
453
- 'main',
454
- object({
455
- displayName: string(),
456
- bio: optional(string({ maxLength: 256 })),
457
- followerCount: optional(integer({ minimum: 0 })),
458
- verified: optional(nullable(enumSchema([true, false]))),
459
- }),
460
- )
461
-
462
- it('validates complex nested structure', () => {
463
- const result = complexSchema.safeParse({
464
- displayName: 'John Doe',
465
- bio: 'Software developer',
466
- followerCount: 1000,
467
- verified: true,
468
- })
469
- expect(result.success).toBe(true)
470
- })
471
-
472
- it('validates with nullable property set to null', () => {
473
- const result = complexSchema.safeParse({
474
- displayName: 'John Doe',
475
- verified: null,
476
- })
477
- expect(result.success).toBe(true)
478
- })
479
-
480
- it('rejects when nested constraint is violated', () => {
481
- const result = complexSchema.safeParse({
482
- displayName: 'John Doe',
483
- followerCount: -1,
484
- })
485
- expect(result.success).toBe(false)
486
- })
487
-
488
- it('rejects when string exceeds maxLength', () => {
489
- const result = complexSchema.safeParse({
490
- displayName: 'John Doe',
491
- bio: 'x'.repeat(257),
492
- })
493
- expect(result.success).toBe(false)
494
- })
495
-
496
- it('validates with matching $type', () => {
497
- const result = complexSchema.safeParse({
498
- $type: 'app.bsky.actor.profile',
499
- displayName: 'John Doe',
500
- })
501
- expect(result.success).toBe(true)
502
- })
503
-
504
- it('rejects with non-matching $type', () => {
505
- const result = complexSchema.safeParse({
506
- $type: 'app.bsky.feed.post',
507
- displayName: 'John Doe',
508
- })
509
- expect(result.success).toBe(false)
510
- })
511
- })
512
-
513
- describe('with different $type formats', () => {
514
- it('validates with main type', () => {
515
- const mainSchema = typedObject(
516
- 'app.bsky.feed.post',
517
- 'main',
518
- object({ text: string() }),
519
- )
520
- const result = mainSchema.safeParse({ text: 'Hello' })
521
- expect(result.success).toBe(true)
522
- })
523
-
524
- it('validates with custom fragment', () => {
525
- const fragmentSchema = typedObject(
526
- 'app.bsky.feed.post',
527
- 'reply',
528
- object({ text: string() }),
529
- )
530
- const result = fragmentSchema.safeParse({
531
- $type: 'app.bsky.feed.post#reply',
532
- text: 'Hello',
533
- })
534
- expect(result.success).toBe(true)
535
- })
536
-
537
- it('distinguishes between different fragments', () => {
538
- const replySchema = typedObject(
539
- 'app.bsky.feed.post',
540
- 'reply',
541
- object({ text: string() }),
542
- )
543
- const result = replySchema.safeParse({
544
- $type: 'app.bsky.feed.post#quote',
545
- text: 'Hello',
546
- })
547
- expect(result.success).toBe(false)
548
- })
549
-
550
- it('validates with long NSID', () => {
551
- const longSchema = typedObject(
552
- 'com.example.app.feature.action.detail',
553
- 'variant',
554
- object({ value: string() }),
555
- )
556
- const result = longSchema.safeParse({
557
- $type: 'com.example.app.feature.action.detail#variant',
558
- value: 'test',
559
- })
560
- expect(result.success).toBe(true)
561
- })
562
- })
563
-
564
- describe('edge cases', () => {
565
- it('validates object with only extra properties', () => {
566
- const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
567
- const result = minimalSchema.safeParse({
568
- extra1: 'value1',
569
- extra2: 'value2',
570
- })
571
- expect(result.success).toBe(true)
572
- })
573
-
574
- it('validates empty object with no required properties', () => {
575
- const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
576
- const result = minimalSchema.safeParse({})
577
- expect(result.success).toBe(true)
578
- })
579
-
580
- it('validates with $type as only property', () => {
581
- const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
582
- const result = minimalSchema.safeParse({
583
- $type: 'app.bsky.test',
584
- })
585
- expect(result.success).toBe(true)
586
- })
587
-
588
- it('rejects object with prototype properties', () => {
589
- const obj = Object.create({ inherited: 'value' })
590
- obj.text = 'Hello world'
591
- const result = schema.safeParse(obj)
592
- expect(result.success).toBe(false)
593
- })
594
-
595
- it('rejects Date objects', () => {
596
- const result = schema.safeParse(new Date())
597
- expect(result.success).toBe(false)
598
- })
599
-
600
- it('rejects RegExp objects', () => {
601
- const result = schema.safeParse(/pattern/)
602
- expect(result.success).toBe(false)
603
- })
604
-
605
- it('rejects Error objects', () => {
606
- const result = schema.safeParse(new Error('test'))
607
- expect(result.success).toBe(false)
608
- })
609
-
610
- it('rejects Map objects', () => {
611
- const result = schema.safeParse(new Map())
612
- expect(result.success).toBe(false)
613
- })
614
-
615
- it('rejects Set objects', () => {
616
- const result = schema.safeParse(new Set())
617
- expect(result.success).toBe(false)
618
- })
619
-
620
- it('rejects class instances', () => {
621
- class CustomClass {
622
- text = 'Hello'
623
- }
624
- const result = schema.safeParse(new CustomClass())
625
- expect(result.success).toBe(false)
626
- })
627
- })
628
-
629
- describe('integration with all property types', () => {
630
- const fullSchema = typedObject(
631
- 'app.bsky.test',
632
- 'full',
633
- object({
634
- required: string(),
635
- optional: optional(string()),
636
- nullable: nullable(string()),
637
- optionalNullable: optional(nullable(string())),
638
- }),
639
- )
640
-
641
- it('validates with all properties present', () => {
642
- const result = fullSchema.safeParse({
643
- required: 'value',
644
- optional: 'value',
645
- nullable: 'value',
646
- optionalNullable: 'value',
647
- })
648
- expect(result.success).toBe(true)
649
- })
650
-
651
- it('validates with only required property and nullable', () => {
652
- const result = fullSchema.safeParse({
653
- required: 'value',
654
- nullable: 'value',
655
- })
656
- expect(result.success).toBe(true)
657
- })
658
-
659
- it('validates with nullable property set to null', () => {
660
- const result = fullSchema.safeParse({
661
- required: 'value',
662
- nullable: null,
663
- })
664
- expect(result.success).toBe(true)
665
- })
666
-
667
- it('validates with required nullable and optional nullable set to null', () => {
668
- const result = fullSchema.safeParse({
669
- required: 'value',
670
- nullable: 'value',
671
- optionalNullable: null,
672
- })
673
- expect(result.success).toBe(true)
674
- })
675
-
676
- it('rejects when required property is missing', () => {
677
- const result = fullSchema.safeParse({
678
- optional: 'value',
679
- nullable: 'value',
680
- })
681
- expect(result.success).toBe(false)
682
- })
683
-
684
- it('rejects when required property is null', () => {
685
- const result = fullSchema.safeParse({
686
- required: null,
687
- })
688
- expect(result.success).toBe(false)
689
- })
690
-
691
- it('rejects when required property is undefined', () => {
692
- const result = fullSchema.safeParse({
693
- required: undefined,
694
- })
695
- expect(result.success).toBe(false)
696
- })
697
-
698
- it('validates with $type and all properties', () => {
699
- const result = fullSchema.safeParse({
700
- $type: 'app.bsky.test#full',
701
- required: 'value',
702
- optional: 'value',
703
- nullable: null,
704
- optionalNullable: 'value',
705
- })
706
- expect(result.success).toBe(true)
707
- })
708
- })
709
-
710
- describe('comparison with plain ObjectSchema', () => {
711
- const plainSchema = object({
712
- text: string(),
713
- likes: optional(integer()),
714
- })
715
-
716
- it('typed schema accepts same input as plain schema', () => {
717
- const input = { text: 'Hello', likes: 5 }
718
- const typedResult = schema.safeParse(input)
719
- const plainResult = plainSchema.safeParse(input)
720
- expect(typedResult.success).toBe(plainResult.success)
721
- })
722
-
723
- it('typed schema adds $type enforcement', () => {
724
- const input = { $type: 'wrong.type', text: 'Hello' }
725
- const typedResult = schema.safeParse(input)
726
- const plainResult = plainSchema.safeParse(input)
727
- expect(typedResult.success).toBe(false)
728
- expect(plainResult.success).toBe(true)
729
- })
730
-
731
- it('both schemas reject invalid types', () => {
732
- const input = { text: 123 }
733
- const typedResult = schema.safeParse(input)
734
- const plainResult = plainSchema.safeParse(input)
735
- expect(typedResult.success).toBe(false)
736
- expect(plainResult.success).toBe(false)
737
- })
738
-
739
- it('typed schema accepts matching $type', () => {
740
- const input = { $type: 'app.bsky.feed.post', text: 'Hello' }
741
- const typedResult = schema.safeParse(input)
742
- expect(typedResult.success).toBe(true)
743
- })
744
- })
745
- })