@atproto/lex-schema 0.0.2 → 0.0.3

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 (289) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/core/$type.d.ts +6 -3
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js +1 -0
  5. package/dist/core/$type.js.map +1 -1
  6. package/dist/core/record-key.d.ts +3 -3
  7. package/dist/core/record-key.d.ts.map +1 -1
  8. package/dist/core/record-key.js +12 -6
  9. package/dist/core/record-key.js.map +1 -1
  10. package/dist/core/result.d.ts.map +1 -1
  11. package/dist/core/result.js +6 -0
  12. package/dist/core/result.js.map +1 -1
  13. package/dist/core/string-format.d.ts +30 -27
  14. package/dist/core/string-format.d.ts.map +1 -1
  15. package/dist/core/string-format.js +56 -42
  16. package/dist/core/string-format.js.map +1 -1
  17. package/dist/core/types.d.ts +9 -1
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/external.d.ts +31 -28
  21. package/dist/external.d.ts.map +1 -1
  22. package/dist/external.js +33 -17
  23. package/dist/external.js.map +1 -1
  24. package/dist/schema/_parameters.d.ts +2 -2
  25. package/dist/schema/_parameters.d.ts.map +1 -1
  26. package/dist/schema/array.d.ts +5 -6
  27. package/dist/schema/array.d.ts.map +1 -1
  28. package/dist/schema/array.js +5 -6
  29. package/dist/schema/array.js.map +1 -1
  30. package/dist/schema/blob.d.ts +2 -3
  31. package/dist/schema/blob.d.ts.map +1 -1
  32. package/dist/schema/blob.js +1 -2
  33. package/dist/schema/blob.js.map +1 -1
  34. package/dist/schema/boolean.d.ts +4 -5
  35. package/dist/schema/boolean.d.ts.map +1 -1
  36. package/dist/schema/boolean.js +2 -3
  37. package/dist/schema/boolean.js.map +1 -1
  38. package/dist/schema/bytes.d.ts +3 -4
  39. package/dist/schema/bytes.d.ts.map +1 -1
  40. package/dist/schema/bytes.js +2 -3
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +13 -6
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +2 -4
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.d.ts +3 -4
  47. package/dist/schema/custom.d.ts.map +1 -1
  48. package/dist/schema/custom.js +4 -3
  49. package/dist/schema/custom.js.map +1 -1
  50. package/dist/schema/dict.d.ts +3 -3
  51. package/dist/schema/dict.d.ts.map +1 -1
  52. package/dist/schema/dict.js +1 -1
  53. package/dist/schema/dict.js.map +1 -1
  54. package/dist/schema/discriminated-union.d.ts +15 -24
  55. package/dist/schema/discriminated-union.d.ts.map +1 -1
  56. package/dist/schema/discriminated-union.js +40 -64
  57. package/dist/schema/discriminated-union.js.map +1 -1
  58. package/dist/schema/enum.d.ts +8 -4
  59. package/dist/schema/enum.d.ts.map +1 -1
  60. package/dist/schema/enum.js +5 -3
  61. package/dist/schema/enum.js.map +1 -1
  62. package/dist/schema/integer.d.ts +3 -4
  63. package/dist/schema/integer.d.ts.map +1 -1
  64. package/dist/schema/integer.js +3 -4
  65. package/dist/schema/integer.js.map +1 -1
  66. package/dist/schema/intersection.d.ts +22 -14
  67. package/dist/schema/intersection.d.ts.map +1 -1
  68. package/dist/schema/intersection.js +12 -22
  69. package/dist/schema/intersection.js.map +1 -1
  70. package/dist/schema/literal.d.ts +7 -3
  71. package/dist/schema/literal.d.ts.map +1 -1
  72. package/dist/schema/literal.js +5 -3
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.d.ts +2 -2
  75. package/dist/schema/never.d.ts.map +1 -1
  76. package/dist/schema/never.js +1 -1
  77. package/dist/schema/never.js.map +1 -1
  78. package/dist/schema/null.d.ts +2 -3
  79. package/dist/schema/null.d.ts.map +1 -1
  80. package/dist/schema/null.js +1 -2
  81. package/dist/schema/null.js.map +1 -1
  82. package/dist/schema/nullable.d.ts +7 -0
  83. package/dist/schema/nullable.d.ts.map +1 -0
  84. package/dist/schema/nullable.js +19 -0
  85. package/dist/schema/nullable.js.map +1 -0
  86. package/dist/schema/object.d.ts +10 -44
  87. package/dist/schema/object.d.ts.map +1 -1
  88. package/dist/schema/object.js +10 -46
  89. package/dist/schema/object.js.map +1 -1
  90. package/dist/schema/optional.d.ts +7 -0
  91. package/dist/schema/optional.d.ts.map +1 -0
  92. package/dist/schema/optional.js +25 -0
  93. package/dist/schema/optional.js.map +1 -0
  94. package/dist/schema/params.d.ts +14 -19
  95. package/dist/schema/params.d.ts.map +1 -1
  96. package/dist/schema/params.js +10 -24
  97. package/dist/schema/params.js.map +1 -1
  98. package/dist/schema/payload.d.ts +4 -4
  99. package/dist/schema/payload.d.ts.map +1 -1
  100. package/dist/schema/payload.js.map +1 -1
  101. package/dist/schema/permission-set.d.ts +6 -6
  102. package/dist/schema/permission-set.d.ts.map +1 -1
  103. package/dist/schema/permission-set.js +1 -2
  104. package/dist/schema/permission-set.js.map +1 -1
  105. package/dist/schema/permission.d.ts +0 -1
  106. package/dist/schema/permission.d.ts.map +1 -1
  107. package/dist/schema/permission.js +0 -1
  108. package/dist/schema/permission.js.map +1 -1
  109. package/dist/schema/procedure.d.ts +8 -9
  110. package/dist/schema/procedure.d.ts.map +1 -1
  111. package/dist/schema/procedure.js +0 -1
  112. package/dist/schema/procedure.js.map +1 -1
  113. package/dist/schema/query.d.ts +7 -8
  114. package/dist/schema/query.d.ts.map +1 -1
  115. package/dist/schema/query.js +0 -1
  116. package/dist/schema/query.js.map +1 -1
  117. package/dist/schema/record.d.ts +34 -28
  118. package/dist/schema/record.d.ts.map +1 -1
  119. package/dist/schema/record.js +1 -2
  120. package/dist/schema/record.js.map +1 -1
  121. package/dist/schema/ref.d.ts +2 -3
  122. package/dist/schema/ref.d.ts.map +1 -1
  123. package/dist/schema/ref.js +1 -2
  124. package/dist/schema/ref.js.map +1 -1
  125. package/dist/schema/refine.d.ts +18 -0
  126. package/dist/schema/refine.d.ts.map +1 -0
  127. package/dist/schema/refine.js +33 -0
  128. package/dist/schema/refine.js.map +1 -0
  129. package/dist/schema/regexp.d.ts +7 -0
  130. package/dist/schema/regexp.d.ts.map +1 -0
  131. package/dist/schema/regexp.js +22 -0
  132. package/dist/schema/regexp.js.map +1 -0
  133. package/dist/schema/string.d.ts +4 -8
  134. package/dist/schema/string.d.ts.map +1 -1
  135. package/dist/schema/string.js +6 -3
  136. package/dist/schema/string.js.map +1 -1
  137. package/dist/schema/subscription.d.ts +7 -6
  138. package/dist/schema/subscription.d.ts.map +1 -1
  139. package/dist/schema/subscription.js.map +1 -1
  140. package/dist/schema/token.d.ts +2 -3
  141. package/dist/schema/token.d.ts.map +1 -1
  142. package/dist/schema/token.js +1 -2
  143. package/dist/schema/token.js.map +1 -1
  144. package/dist/schema/typed-object.d.ts +29 -27
  145. package/dist/schema/typed-object.d.ts.map +1 -1
  146. package/dist/schema/typed-object.js +1 -2
  147. package/dist/schema/typed-object.js.map +1 -1
  148. package/dist/schema/typed-ref.d.ts +2 -2
  149. package/dist/schema/typed-ref.d.ts.map +1 -1
  150. package/dist/schema/typed-ref.js +1 -1
  151. package/dist/schema/typed-ref.js.map +1 -1
  152. package/dist/schema/typed-union.d.ts +3 -4
  153. package/dist/schema/typed-union.d.ts.map +1 -1
  154. package/dist/schema/typed-union.js +3 -10
  155. package/dist/schema/typed-union.js.map +1 -1
  156. package/dist/schema/union.d.ts +2 -2
  157. package/dist/schema/union.d.ts.map +1 -1
  158. package/dist/schema/union.js +1 -1
  159. package/dist/schema/union.js.map +1 -1
  160. package/dist/schema/unknown-object.d.ts +2 -3
  161. package/dist/schema/unknown-object.d.ts.map +1 -1
  162. package/dist/schema/unknown-object.js +1 -2
  163. package/dist/schema/unknown-object.js.map +1 -1
  164. package/dist/schema/unknown.d.ts +2 -2
  165. package/dist/schema/unknown.d.ts.map +1 -1
  166. package/dist/schema/unknown.js +1 -1
  167. package/dist/schema/unknown.js.map +1 -1
  168. package/dist/schema.d.ts +4 -0
  169. package/dist/schema.d.ts.map +1 -1
  170. package/dist/schema.js +6 -1
  171. package/dist/schema.js.map +1 -1
  172. package/dist/util/array-agg.d.ts.map +1 -1
  173. package/dist/util/array-agg.js +1 -0
  174. package/dist/util/array-agg.js.map +1 -1
  175. package/dist/util/lazy-property.d.ts +2 -0
  176. package/dist/util/lazy-property.d.ts.map +1 -0
  177. package/dist/util/lazy-property.js +14 -0
  178. package/dist/util/lazy-property.js.map +1 -0
  179. package/dist/validation/schema.d.ts +24 -0
  180. package/dist/validation/schema.d.ts.map +1 -0
  181. package/dist/validation/schema.js +57 -0
  182. package/dist/validation/schema.js.map +1 -0
  183. package/dist/validation/validation-error.d.ts +3 -3
  184. package/dist/validation/validation-error.d.ts.map +1 -1
  185. package/dist/validation/validation-error.js +32 -4
  186. package/dist/validation/validation-error.js.map +1 -1
  187. package/dist/validation/validation-issue.d.ts +32 -24
  188. package/dist/validation/validation-issue.d.ts.map +1 -1
  189. package/dist/validation/validation-issue.js +136 -92
  190. package/dist/validation/validation-issue.js.map +1 -1
  191. package/dist/validation/validator.d.ts +20 -50
  192. package/dist/validation/validator.d.ts.map +1 -1
  193. package/dist/validation/validator.js +40 -134
  194. package/dist/validation/validator.js.map +1 -1
  195. package/dist/validation.d.ts +1 -0
  196. package/dist/validation.d.ts.map +1 -1
  197. package/dist/validation.js +1 -0
  198. package/dist/validation.js.map +1 -1
  199. package/package.json +8 -4
  200. package/src/core/$type.ts +7 -4
  201. package/src/core/record-key.ts +12 -5
  202. package/src/core/result.ts +6 -0
  203. package/src/core/string-format.ts +97 -61
  204. package/src/core/types.ts +12 -6
  205. package/src/external.ts +92 -70
  206. package/src/schema/_parameters.test.ts +416 -0
  207. package/src/schema/array.test.ts +237 -0
  208. package/src/schema/array.ts +17 -11
  209. package/src/schema/blob.test.ts +506 -0
  210. package/src/schema/blob.ts +3 -5
  211. package/src/schema/boolean.test.ts +116 -0
  212. package/src/schema/boolean.ts +5 -7
  213. package/src/schema/bytes.test.ts +226 -0
  214. package/src/schema/bytes.ts +4 -6
  215. package/src/schema/cid.test.ts +155 -0
  216. package/src/schema/cid.ts +14 -8
  217. package/src/schema/custom.test.ts +413 -0
  218. package/src/schema/custom.ts +10 -8
  219. package/src/schema/dict.test.ts +198 -0
  220. package/src/schema/dict.ts +6 -8
  221. package/src/schema/discriminated-union.test.ts +675 -0
  222. package/src/schema/discriminated-union.ts +68 -95
  223. package/src/schema/enum.test.ts +396 -0
  224. package/src/schema/enum.ts +12 -5
  225. package/src/schema/integer.test.ts +312 -0
  226. package/src/schema/integer.ts +5 -7
  227. package/src/schema/intersection.test.ts +32 -0
  228. package/src/schema/intersection.ts +37 -40
  229. package/src/schema/literal.test.ts +531 -0
  230. package/src/schema/literal.ts +12 -5
  231. package/src/schema/never.test.ts +174 -0
  232. package/src/schema/never.ts +3 -10
  233. package/src/schema/null.test.ts +79 -0
  234. package/src/schema/null.ts +3 -5
  235. package/src/schema/nullable.test.ts +480 -0
  236. package/src/schema/nullable.ts +23 -0
  237. package/src/schema/object.test.ts +47 -115
  238. package/src/schema/object.ts +19 -123
  239. package/src/schema/optional.test.ts +485 -0
  240. package/src/schema/optional.ts +31 -0
  241. package/src/schema/params.test.ts +582 -0
  242. package/src/schema/params.ts +37 -55
  243. package/src/schema/payload.test.ts +345 -0
  244. package/src/schema/payload.ts +5 -5
  245. package/src/schema/permission-set.test.ts +679 -0
  246. package/src/schema/permission-set.ts +6 -8
  247. package/src/schema/permission.test.ts +536 -0
  248. package/src/schema/permission.ts +0 -2
  249. package/src/schema/procedure.test.ts +443 -0
  250. package/src/schema/procedure.ts +11 -13
  251. package/src/schema/query.test.ts +408 -0
  252. package/src/schema/query.ts +9 -11
  253. package/src/schema/record.test.ts +694 -0
  254. package/src/schema/record.ts +38 -36
  255. package/src/schema/ref.test.ts +365 -0
  256. package/src/schema/ref.ts +8 -5
  257. package/src/schema/refine.test.ts +578 -0
  258. package/src/schema/refine.ts +85 -0
  259. package/src/schema/regexp.test.ts +580 -0
  260. package/src/schema/regexp.ts +22 -0
  261. package/src/schema/string.test.ts +612 -0
  262. package/src/schema/string.ts +11 -17
  263. package/src/schema/subscription.test.ts +689 -0
  264. package/src/schema/subscription.ts +13 -8
  265. package/src/schema/token.test.ts +428 -0
  266. package/src/schema/token.ts +3 -5
  267. package/src/schema/typed-object.test.ts +612 -0
  268. package/src/schema/typed-object.ts +23 -20
  269. package/src/schema/typed-ref.test.ts +823 -0
  270. package/src/schema/typed-ref.ts +10 -5
  271. package/src/schema/typed-union.test.ts +378 -0
  272. package/src/schema/typed-union.ts +6 -15
  273. package/src/schema/union.test.ts +200 -0
  274. package/src/schema/union.ts +5 -4
  275. package/src/schema/unknown-object.test.ts +592 -0
  276. package/src/schema/unknown-object.ts +3 -5
  277. package/src/schema/unknown.test.ts +312 -0
  278. package/src/schema/unknown.ts +3 -3
  279. package/src/schema.ts +7 -1
  280. package/src/util/array-agg.ts +1 -0
  281. package/src/util/lazy-property.ts +14 -0
  282. package/src/validation/schema.ts +92 -0
  283. package/src/validation/validation-error.ts +60 -9
  284. package/src/validation/validation-issue.ts +141 -144
  285. package/src/validation/validator.ts +67 -206
  286. package/src/validation.ts +1 -0
  287. package/tsconfig.build.json +12 -0
  288. package/tsconfig.json +7 -0
  289. package/tsconfig.tests.json +9 -0
@@ -0,0 +1,506 @@
1
+ import { parseCid } from '@atproto/lex-data'
2
+ import { BlobSchema } from './blob.js'
3
+
4
+ // await cidForRawBytes(Buffer.from('Hello, World!'))
5
+ const blobCid = parseCid(
6
+ 'bafkreig77vqcdozl2wyk6z3cscaj5q5fggi53aoh64fewkdiri3cdauyn4',
7
+ )
8
+ // await cidForLex(Buffer.from('Hello, World!'))
9
+ const lexCid = parseCid(
10
+ 'bafyreic52vzks7wdklat4evp3vimohl55i2unzqpshz2ytka5omzr7exdy',
11
+ )
12
+
13
+ describe('BlobSchema', () => {
14
+ describe('basic validation', () => {
15
+ const schema = new BlobSchema({})
16
+
17
+ it('validates valid blob references', () => {
18
+ const result = schema.safeParse({
19
+ $type: 'blob',
20
+ ref: blobCid,
21
+ mimeType: 'image/jpeg',
22
+ size: 10000,
23
+ })
24
+ expect(result.success).toBe(true)
25
+ if (result.success) {
26
+ expect(result.value.$type).toBe('blob')
27
+ expect(result.value.mimeType).toBe('image/jpeg')
28
+ expect(result.value.size).toBe(10000)
29
+ }
30
+ })
31
+
32
+ it('validates blob with different mime types', () => {
33
+ const result = schema.safeParse({
34
+ $type: 'blob',
35
+ ref: blobCid,
36
+ mimeType: 'image/png',
37
+ size: 5000,
38
+ })
39
+ expect(result.success).toBe(true)
40
+ })
41
+
42
+ it('validates blob with size 0', () => {
43
+ const result = schema.safeParse({
44
+ $type: 'blob',
45
+ ref: blobCid,
46
+ mimeType: 'text/plain',
47
+ size: 0,
48
+ })
49
+ expect(result.success).toBe(true)
50
+ })
51
+
52
+ it('rejects non-objects', () => {
53
+ const result = schema.safeParse('not an object')
54
+ expect(result.success).toBe(false)
55
+ })
56
+
57
+ it('rejects null', () => {
58
+ const result = schema.safeParse(null)
59
+ expect(result.success).toBe(false)
60
+ })
61
+
62
+ it('rejects undefined', () => {
63
+ const result = schema.safeParse(undefined)
64
+ expect(result.success).toBe(false)
65
+ })
66
+
67
+ it('rejects arrays', () => {
68
+ const result = schema.safeParse([])
69
+ expect(result.success).toBe(false)
70
+ })
71
+
72
+ it('rejects numbers', () => {
73
+ const result = schema.safeParse(123)
74
+ expect(result.success).toBe(false)
75
+ })
76
+
77
+ it('rejects booleans', () => {
78
+ const result = schema.safeParse(true)
79
+ expect(result.success).toBe(false)
80
+ })
81
+ })
82
+
83
+ describe('BlobRef validation', () => {
84
+ const schema = new BlobSchema({})
85
+
86
+ it('rejects blob without $type', () => {
87
+ const result = schema.safeParse({
88
+ ref: blobCid,
89
+ mimeType: 'image/jpeg',
90
+ size: 10000,
91
+ })
92
+ expect(result.success).toBe(false)
93
+ })
94
+
95
+ it('rejects blob with wrong $type', () => {
96
+ const result = schema.safeParse({
97
+ $type: 'notblob',
98
+ ref: blobCid,
99
+ mimeType: 'image/jpeg',
100
+ size: 10000,
101
+ })
102
+ expect(result.success).toBe(false)
103
+ })
104
+
105
+ it('rejects blob without ref', () => {
106
+ const result = schema.safeParse({
107
+ $type: 'blob',
108
+ mimeType: 'image/jpeg',
109
+ size: 10000,
110
+ })
111
+ expect(result.success).toBe(false)
112
+ })
113
+
114
+ it('rejects blob without mimeType', () => {
115
+ const result = schema.safeParse({
116
+ $type: 'blob',
117
+ ref: blobCid,
118
+ size: 10000,
119
+ })
120
+ expect(result.success).toBe(false)
121
+ })
122
+
123
+ it('rejects blob without size', () => {
124
+ const result = schema.safeParse({
125
+ $type: 'blob',
126
+ ref: blobCid,
127
+ mimeType: 'image/jpeg',
128
+ })
129
+ expect(result.success).toBe(false)
130
+ })
131
+
132
+ it('rejects blob with invalid ref type', () => {
133
+ const result = schema.safeParse({
134
+ $type: 'blob',
135
+ ref: 'not a cid',
136
+ mimeType: 'image/jpeg',
137
+ size: 10000,
138
+ })
139
+ expect(result.success).toBe(false)
140
+ })
141
+
142
+ it('rejects blob with invalid mimeType type', () => {
143
+ const result = schema.safeParse({
144
+ $type: 'blob',
145
+ ref: blobCid,
146
+ mimeType: 123,
147
+ size: 10000,
148
+ })
149
+ expect(result.success).toBe(false)
150
+ })
151
+
152
+ it('rejects blob with invalid size type', () => {
153
+ const result = schema.safeParse({
154
+ $type: 'blob',
155
+ ref: blobCid,
156
+ mimeType: 'image/jpeg',
157
+ size: '10000',
158
+ })
159
+ expect(result.success).toBe(false)
160
+ })
161
+
162
+ it('rejects blob with negative size', () => {
163
+ const result = schema.safeParse({
164
+ $type: 'blob',
165
+ ref: blobCid,
166
+ mimeType: 'image/jpeg',
167
+ size: -1,
168
+ })
169
+ expect(result.success).toBe(false)
170
+ })
171
+
172
+ it('rejects blob with decimal size', () => {
173
+ const result = schema.safeParse({
174
+ $type: 'blob',
175
+ ref: blobCid,
176
+ mimeType: 'image/jpeg',
177
+ size: 10000.5,
178
+ })
179
+ expect(result.success).toBe(false)
180
+ })
181
+
182
+ it('rejects blob with extra properties', () => {
183
+ const result = schema.safeParse({
184
+ $type: 'blob',
185
+ ref: blobCid,
186
+ mimeType: 'image/jpeg',
187
+ size: 10000,
188
+ extra: 'not allowed',
189
+ })
190
+ expect(result.success).toBe(false)
191
+ })
192
+
193
+ it('rejects blob with $link format for ref', () => {
194
+ const result = schema.safeParse({
195
+ $type: 'blob',
196
+ ref: { $link: blobCid.toString() },
197
+ mimeType: 'image/jpeg',
198
+ size: 10000,
199
+ })
200
+ expect(result.success).toBe(false)
201
+ })
202
+
203
+ it('rejects blob with unknown properties', () => {
204
+ const result = schema.safeParse({
205
+ $type: 'blob',
206
+ ref: blobCid,
207
+ mimeType: 'image/jpeg',
208
+ size: 10000,
209
+ unknownProp: 42,
210
+ })
211
+ expect(result.success).toBe(false)
212
+ })
213
+ })
214
+
215
+ describe('strict validation', () => {
216
+ const strictSchema = new BlobSchema({ strict: true })
217
+
218
+ it('accepts valid raw CID in strict mode', () => {
219
+ const result = strictSchema.safeParse({
220
+ $type: 'blob',
221
+ ref: blobCid,
222
+ mimeType: 'image/jpeg',
223
+ size: 10000,
224
+ })
225
+ expect(result.success).toBe(true)
226
+ })
227
+
228
+ it('rejects non-raw CID in strict mode', () => {
229
+ const result = strictSchema.safeParse({
230
+ $type: 'blob',
231
+ ref: lexCid,
232
+ mimeType: 'image/jpeg',
233
+ size: 10000,
234
+ })
235
+ expect(result.success).toBe(false)
236
+ })
237
+
238
+ it('accepts non-raw CID in non-strict mode', () => {
239
+ const nonStrictSchema = new BlobSchema({ strict: false })
240
+ const result = nonStrictSchema.safeParse({
241
+ $type: 'blob',
242
+ ref: lexCid,
243
+ mimeType: 'image/jpeg',
244
+ size: 10000,
245
+ })
246
+ expect(result.success).toBe(true)
247
+ })
248
+ })
249
+
250
+ describe('legacy blob format', () => {
251
+ it('rejects legacy format by default', () => {
252
+ const schema = new BlobSchema({})
253
+ const result = schema.safeParse({
254
+ cid: blobCid.toString(),
255
+ mimeType: 'image/jpeg',
256
+ })
257
+ expect(result.success).toBe(false)
258
+ })
259
+
260
+ it('accepts legacy format when allowLegacy is true', () => {
261
+ const schema = new BlobSchema({ allowLegacy: true })
262
+ const result = schema.safeParse({
263
+ cid: blobCid.toString(),
264
+ mimeType: 'image/jpeg',
265
+ })
266
+ expect(result.success).toBe(true)
267
+ if (result.success) {
268
+ expect('cid' in result.value && result.value.cid).toBe(
269
+ blobCid.toString(),
270
+ )
271
+ expect(result.value.mimeType).toBe('image/jpeg')
272
+ }
273
+ })
274
+
275
+ it('accepts legacy format with lexCid when allowLegacy is true', () => {
276
+ const schema = new BlobSchema({ allowLegacy: true })
277
+ const result = schema.safeParse({
278
+ cid: lexCid.toString(),
279
+ mimeType: 'image/png',
280
+ })
281
+ expect(result.success).toBe(true)
282
+ })
283
+
284
+ it('rejects legacy format without cid', () => {
285
+ const schema = new BlobSchema({ allowLegacy: true })
286
+ const result = schema.safeParse({
287
+ mimeType: 'image/jpeg',
288
+ })
289
+ expect(result.success).toBe(false)
290
+ })
291
+
292
+ it('rejects legacy format without mimeType', () => {
293
+ const schema = new BlobSchema({ allowLegacy: true })
294
+ const result = schema.safeParse({
295
+ cid: blobCid.toString(),
296
+ })
297
+ expect(result.success).toBe(false)
298
+ })
299
+
300
+ it('rejects legacy format with invalid cid', () => {
301
+ const schema = new BlobSchema({ allowLegacy: true })
302
+ const result = schema.safeParse({
303
+ cid: 'invalid-cid',
304
+ mimeType: 'image/jpeg',
305
+ })
306
+ expect(result.success).toBe(false)
307
+ })
308
+
309
+ it('rejects legacy format with numeric cid', () => {
310
+ const schema = new BlobSchema({ allowLegacy: true })
311
+ const result = schema.safeParse({
312
+ cid: 123,
313
+ mimeType: 'image/jpeg',
314
+ })
315
+ expect(result.success).toBe(false)
316
+ })
317
+
318
+ it('rejects legacy format with extra properties', () => {
319
+ const schema = new BlobSchema({ allowLegacy: true })
320
+ const result = schema.safeParse({
321
+ cid: blobCid.toString(),
322
+ mimeType: 'image/jpeg',
323
+ extra: 'not allowed',
324
+ })
325
+ expect(result.success).toBe(false)
326
+ })
327
+
328
+ it('accepts both BlobRef and LegacyBlobRef formats when allowLegacy is true', () => {
329
+ const schema = new BlobSchema({ allowLegacy: true })
330
+
331
+ const blobRefResult = schema.safeParse({
332
+ $type: 'blob',
333
+ ref: blobCid,
334
+ mimeType: 'image/jpeg',
335
+ size: 10000,
336
+ })
337
+ expect(blobRefResult.success).toBe(true)
338
+
339
+ const legacyResult = schema.safeParse({
340
+ cid: blobCid.toString(),
341
+ mimeType: 'image/jpeg',
342
+ })
343
+ expect(legacyResult.success).toBe(true)
344
+ })
345
+ })
346
+
347
+ describe('accept and maxSize options', () => {
348
+ it('accepts blob with accept option (not enforced)', () => {
349
+ const schema = new BlobSchema({ accept: ['image/jpeg', 'image/png'] })
350
+ const result = schema.safeParse({
351
+ $type: 'blob',
352
+ ref: blobCid,
353
+ mimeType: 'image/gif',
354
+ size: 10000,
355
+ })
356
+ // Accept constraints are not enforced (see comment in source)
357
+ expect(result.success).toBe(true)
358
+ })
359
+
360
+ it('accepts blob with maxSize option (not enforced)', () => {
361
+ const schema = new BlobSchema({ maxSize: 1000 })
362
+ const result = schema.safeParse({
363
+ $type: 'blob',
364
+ ref: blobCid,
365
+ mimeType: 'image/jpeg',
366
+ size: 10000,
367
+ })
368
+ // MaxSize constraints are not enforced (see comment in source)
369
+ expect(result.success).toBe(true)
370
+ })
371
+
372
+ it('accepts blob matching accept constraint', () => {
373
+ const schema = new BlobSchema({ accept: ['image/jpeg', 'image/png'] })
374
+ const result = schema.safeParse({
375
+ $type: 'blob',
376
+ ref: blobCid,
377
+ mimeType: 'image/jpeg',
378
+ size: 10000,
379
+ })
380
+ expect(result.success).toBe(true)
381
+ })
382
+
383
+ it('accepts blob matching maxSize constraint', () => {
384
+ const schema = new BlobSchema({ maxSize: 20000 })
385
+ const result = schema.safeParse({
386
+ $type: 'blob',
387
+ ref: blobCid,
388
+ mimeType: 'image/jpeg',
389
+ size: 10000,
390
+ })
391
+ expect(result.success).toBe(true)
392
+ })
393
+ })
394
+
395
+ describe('edge cases', () => {
396
+ const schema = new BlobSchema({})
397
+
398
+ it('validates blob with large size', () => {
399
+ const result = schema.safeParse({
400
+ $type: 'blob',
401
+ ref: blobCid,
402
+ mimeType: 'video/mp4',
403
+ size: Number.MAX_SAFE_INTEGER,
404
+ })
405
+ expect(result.success).toBe(true)
406
+ })
407
+
408
+ it('rejects empty object', () => {
409
+ const result = schema.safeParse({})
410
+ expect(result.success).toBe(false)
411
+ })
412
+
413
+ it('rejects object with only $type', () => {
414
+ const result = schema.safeParse({ $type: 'blob' })
415
+ expect(result.success).toBe(false)
416
+ })
417
+
418
+ it('rejects blob with empty mimeType', () => {
419
+ const result = schema.safeParse({
420
+ $type: 'blob',
421
+ ref: blobCid,
422
+ mimeType: '',
423
+ size: 10000,
424
+ })
425
+ expect(result.success).toBe(false)
426
+ })
427
+
428
+ it('rejects blob with null ref', () => {
429
+ const result = schema.safeParse({
430
+ $type: 'blob',
431
+ ref: null,
432
+ mimeType: 'image/jpeg',
433
+ size: 10000,
434
+ })
435
+ expect(result.success).toBe(false)
436
+ })
437
+
438
+ it('rejects blob with null mimeType', () => {
439
+ const result = schema.safeParse({
440
+ $type: 'blob',
441
+ ref: blobCid,
442
+ mimeType: null,
443
+ size: 10000,
444
+ })
445
+ expect(result.success).toBe(false)
446
+ })
447
+
448
+ it('rejects blob with null size', () => {
449
+ const result = schema.safeParse({
450
+ $type: 'blob',
451
+ ref: blobCid,
452
+ mimeType: 'image/jpeg',
453
+ size: null,
454
+ })
455
+ expect(result.success).toBe(false)
456
+ })
457
+ })
458
+
459
+ describe('combined options', () => {
460
+ it('validates with strict and allowLegacy both true', () => {
461
+ const schema = new BlobSchema({ strict: true, allowLegacy: true })
462
+
463
+ // Should accept strict BlobRef
464
+ const blobRefResult = schema.safeParse({
465
+ $type: 'blob',
466
+ ref: blobCid,
467
+ mimeType: 'image/jpeg',
468
+ size: 10000,
469
+ })
470
+ expect(blobRefResult.success).toBe(true)
471
+
472
+ // Should accept LegacyBlobRef
473
+ const legacyResult = schema.safeParse({
474
+ cid: blobCid.toString(),
475
+ mimeType: 'image/jpeg',
476
+ })
477
+ expect(legacyResult.success).toBe(true)
478
+
479
+ // Should reject non-raw CID in BlobRef format
480
+ const nonRawResult = schema.safeParse({
481
+ $type: 'blob',
482
+ ref: lexCid,
483
+ mimeType: 'image/jpeg',
484
+ size: 10000,
485
+ })
486
+ expect(nonRawResult.success).toBe(false)
487
+ })
488
+
489
+ it('validates with all options combined', () => {
490
+ const schema = new BlobSchema({
491
+ strict: true,
492
+ allowLegacy: true,
493
+ accept: ['image/jpeg'],
494
+ maxSize: 20000,
495
+ })
496
+
497
+ const result = schema.safeParse({
498
+ $type: 'blob',
499
+ ref: blobCid,
500
+ mimeType: 'image/jpeg',
501
+ size: 10000,
502
+ })
503
+ expect(result.success).toBe(true)
504
+ })
505
+ })
506
+ })
@@ -4,7 +4,7 @@ import {
4
4
  isBlobRef,
5
5
  isLegacyBlobRef,
6
6
  } from '@atproto/lex-data'
7
- import { ValidationResult, Validator, ValidatorContext } from '../validation.js'
7
+ import { Schema, ValidationResult, ValidatorContext } from '../validation.js'
8
8
 
9
9
  export type BlobSchemaOptions = {
10
10
  /**
@@ -32,16 +32,14 @@ export type BlobSchemaOutput<Options> = Options extends { allowLegacy: true }
32
32
  ? BlobRef | LegacyBlobRef
33
33
  : BlobRef
34
34
 
35
- export class BlobSchema<O extends BlobSchemaOptions> extends Validator<
35
+ export class BlobSchema<O extends BlobSchemaOptions> extends Schema<
36
36
  BlobSchemaOutput<O>
37
37
  > {
38
- readonly lexiconType = 'blob' as const
39
-
40
38
  constructor(readonly options: O) {
41
39
  super()
42
40
  }
43
41
 
44
- override validateInContext(
42
+ validateInContext(
45
43
  input: unknown,
46
44
  ctx: ValidatorContext,
47
45
  ): ValidationResult<BlobSchemaOutput<O>> {
@@ -0,0 +1,116 @@
1
+ import { BooleanSchema } from './boolean.js'
2
+
3
+ describe('BooleanSchema', () => {
4
+ describe('basic validation', () => {
5
+ const schema = new BooleanSchema()
6
+
7
+ it('validates true', () => {
8
+ const result = schema.safeParse(true)
9
+ expect(result.success).toBe(true)
10
+ if (result.success) {
11
+ expect(result.value).toBe(true)
12
+ }
13
+ })
14
+
15
+ it('validates false', () => {
16
+ const result = schema.safeParse(false)
17
+ expect(result.success).toBe(true)
18
+ if (result.success) {
19
+ expect(result.value).toBe(false)
20
+ }
21
+ })
22
+
23
+ it('rejects strings', () => {
24
+ const result = schema.safeParse('true')
25
+ expect(result.success).toBe(false)
26
+ })
27
+
28
+ it('rejects numbers', () => {
29
+ const result = schema.safeParse(1)
30
+ expect(result.success).toBe(false)
31
+ })
32
+
33
+ it('rejects null', () => {
34
+ const result = schema.safeParse(null)
35
+ expect(result.success).toBe(false)
36
+ })
37
+
38
+ it('rejects undefined', () => {
39
+ const result = schema.safeParse(undefined)
40
+ expect(result.success).toBe(false)
41
+ })
42
+
43
+ it('rejects objects', () => {
44
+ const result = schema.safeParse({})
45
+ expect(result.success).toBe(false)
46
+ })
47
+
48
+ it('rejects arrays', () => {
49
+ const result = schema.safeParse([])
50
+ expect(result.success).toBe(false)
51
+ })
52
+ })
53
+
54
+ describe('with default value', () => {
55
+ it('uses default value of true when input is undefined', () => {
56
+ const schema = new BooleanSchema({ default: true })
57
+ const result = schema.safeParse(undefined)
58
+ expect(result.success).toBe(true)
59
+ if (result.success) {
60
+ expect(result.value).toBe(true)
61
+ }
62
+ })
63
+
64
+ it('uses default value of false when input is undefined', () => {
65
+ const schema = new BooleanSchema({ default: false })
66
+ const result = schema.safeParse(undefined)
67
+ expect(result.success).toBe(true)
68
+ if (result.success) {
69
+ expect(result.value).toBe(false)
70
+ }
71
+ })
72
+
73
+ it('overrides default value with explicit true', () => {
74
+ const schema = new BooleanSchema({ default: false })
75
+ const result = schema.safeParse(true)
76
+ expect(result.success).toBe(true)
77
+ if (result.success) {
78
+ expect(result.value).toBe(true)
79
+ }
80
+ })
81
+
82
+ it('overrides default value with explicit false', () => {
83
+ const schema = new BooleanSchema({ default: true })
84
+ const result = schema.safeParse(false)
85
+ expect(result.success).toBe(true)
86
+ if (result.success) {
87
+ expect(result.value).toBe(false)
88
+ }
89
+ })
90
+
91
+ it('rejects invalid types even with default', () => {
92
+ const schema = new BooleanSchema({ default: true })
93
+ const result = schema.safeParse('not a boolean')
94
+ expect(result.success).toBe(false)
95
+ })
96
+ })
97
+
98
+ describe('edge cases', () => {
99
+ const schema = new BooleanSchema()
100
+
101
+ it('rejects Boolean object', () => {
102
+ const result = schema.safeParse(new Boolean(true))
103
+ expect(result.success).toBe(false)
104
+ })
105
+
106
+ it('rejects truthy values', () => {
107
+ const result = schema.safeParse('truthy')
108
+ expect(result.success).toBe(false)
109
+ })
110
+
111
+ it('rejects falsy values', () => {
112
+ const result = schema.safeParse(0)
113
+ expect(result.success).toBe(false)
114
+ })
115
+ })
116
+ })
@@ -1,18 +1,16 @@
1
- import { ValidationResult, Validator, ValidatorContext } from '../validation.js'
1
+ import { Schema, ValidationResult, ValidatorContext } from '../validation.js'
2
2
 
3
3
  export type BooleanSchemaOptions = {
4
4
  default?: boolean
5
5
  }
6
6
 
7
- export class BooleanSchema extends Validator<boolean> {
8
- readonly lexiconType = 'boolean' as const
9
-
10
- constructor(readonly options: BooleanSchemaOptions) {
7
+ export class BooleanSchema extends Schema<boolean> {
8
+ constructor(readonly options?: BooleanSchemaOptions) {
11
9
  super()
12
10
  }
13
11
 
14
- override validateInContext(
15
- input: unknown = this.options.default,
12
+ validateInContext(
13
+ input: unknown = this.options?.default,
16
14
  ctx: ValidatorContext,
17
15
  ): ValidationResult<boolean> {
18
16
  if (typeof input === 'boolean') {