@atproto/lex-schema 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE.txt +1 -1
  3. package/dist/core/$type.d.ts +11 -0
  4. package/dist/core/$type.d.ts.map +1 -1
  5. package/dist/core/$type.js +4 -0
  6. package/dist/core/$type.js.map +1 -1
  7. package/dist/core/schema.d.ts +31 -24
  8. package/dist/core/schema.d.ts.map +1 -1
  9. package/dist/core/schema.js +38 -8
  10. package/dist/core/schema.js.map +1 -1
  11. package/dist/core/string-format.d.ts +35 -35
  12. package/dist/core/string-format.d.ts.map +1 -1
  13. package/dist/core/string-format.js +49 -91
  14. package/dist/core/string-format.js.map +1 -1
  15. package/dist/core/validation-issue.js +1 -1
  16. package/dist/core/validation-issue.js.map +1 -1
  17. package/dist/core/validator.d.ts +53 -32
  18. package/dist/core/validator.d.ts.map +1 -1
  19. package/dist/core/validator.js +18 -22
  20. package/dist/core/validator.js.map +1 -1
  21. package/dist/external.d.ts +0 -85
  22. package/dist/external.d.ts.map +1 -1
  23. package/dist/external.js +0 -164
  24. package/dist/external.js.map +1 -1
  25. package/dist/helpers.d.ts +10 -5
  26. package/dist/helpers.d.ts.map +1 -1
  27. package/dist/helpers.js +3 -3
  28. package/dist/helpers.js.map +1 -1
  29. package/dist/schema/array.d.ts +9 -5
  30. package/dist/schema/array.d.ts.map +1 -1
  31. package/dist/schema/array.js +14 -5
  32. package/dist/schema/array.js.map +1 -1
  33. package/dist/schema/blob.d.ts +9 -7
  34. package/dist/schema/blob.d.ts.map +1 -1
  35. package/dist/schema/blob.js +9 -5
  36. package/dist/schema/blob.js.map +1 -1
  37. package/dist/schema/boolean.d.ts +3 -7
  38. package/dist/schema/boolean.d.ts.map +1 -1
  39. package/dist/schema/boolean.js +6 -7
  40. package/dist/schema/boolean.js.map +1 -1
  41. package/dist/schema/bytes.d.ts +3 -2
  42. package/dist/schema/bytes.d.ts.map +1 -1
  43. package/dist/schema/bytes.js +7 -3
  44. package/dist/schema/bytes.js.map +1 -1
  45. package/dist/schema/cid.d.ts +7 -7
  46. package/dist/schema/cid.d.ts.map +1 -1
  47. package/dist/schema/cid.js +5 -1
  48. package/dist/schema/cid.js.map +1 -1
  49. package/dist/schema/custom.d.ts +6 -5
  50. package/dist/schema/custom.d.ts.map +1 -1
  51. package/dist/schema/custom.js +10 -4
  52. package/dist/schema/custom.js.map +1 -1
  53. package/dist/schema/dict.d.ts +8 -8
  54. package/dist/schema/dict.d.ts.map +1 -1
  55. package/dist/schema/dict.js +11 -2
  56. package/dist/schema/dict.js.map +1 -1
  57. package/dist/schema/discriminated-union.d.ts +21 -14
  58. package/dist/schema/discriminated-union.d.ts.map +1 -1
  59. package/dist/schema/discriminated-union.js +7 -0
  60. package/dist/schema/discriminated-union.js.map +1 -1
  61. package/dist/schema/enum.d.ts +7 -9
  62. package/dist/schema/enum.d.ts.map +1 -1
  63. package/dist/schema/enum.js +8 -4
  64. package/dist/schema/enum.js.map +1 -1
  65. package/dist/schema/integer.d.ts +5 -5
  66. package/dist/schema/integer.d.ts.map +1 -1
  67. package/dist/schema/integer.js +9 -5
  68. package/dist/schema/integer.js.map +1 -1
  69. package/dist/schema/intersection.d.ts +4 -4
  70. package/dist/schema/intersection.d.ts.map +1 -1
  71. package/dist/schema/intersection.js +5 -0
  72. package/dist/schema/intersection.js.map +1 -1
  73. package/dist/schema/literal.d.ts +6 -9
  74. package/dist/schema/literal.d.ts.map +1 -1
  75. package/dist/schema/literal.js +7 -4
  76. package/dist/schema/literal.js.map +1 -1
  77. package/dist/schema/never.d.ts +3 -2
  78. package/dist/schema/never.d.ts.map +1 -1
  79. package/dist/schema/never.js +5 -1
  80. package/dist/schema/never.js.map +1 -1
  81. package/dist/schema/null.d.ts +4 -3
  82. package/dist/schema/null.d.ts.map +1 -1
  83. package/dist/schema/null.js +6 -4
  84. package/dist/schema/null.js.map +1 -1
  85. package/dist/schema/nullable.d.ts +6 -5
  86. package/dist/schema/nullable.d.ts.map +1 -1
  87. package/dist/schema/nullable.js +9 -5
  88. package/dist/schema/nullable.js.map +1 -1
  89. package/dist/schema/object.d.ts +10 -8
  90. package/dist/schema/object.d.ts.map +1 -1
  91. package/dist/schema/object.js +11 -3
  92. package/dist/schema/object.js.map +1 -1
  93. package/dist/schema/optional.d.ts +7 -5
  94. package/dist/schema/optional.d.ts.map +1 -1
  95. package/dist/schema/optional.js +14 -6
  96. package/dist/schema/optional.js.map +1 -1
  97. package/dist/schema/params.d.ts +24 -13
  98. package/dist/schema/params.d.ts.map +1 -1
  99. package/dist/schema/params.js +47 -25
  100. package/dist/schema/params.js.map +1 -1
  101. package/dist/schema/payload.d.ts +12 -9
  102. package/dist/schema/payload.d.ts.map +1 -1
  103. package/dist/schema/payload.js +11 -0
  104. package/dist/schema/payload.js.map +1 -1
  105. package/dist/schema/permission-set.d.ts +1 -0
  106. package/dist/schema/permission-set.d.ts.map +1 -1
  107. package/dist/schema/permission-set.js +5 -0
  108. package/dist/schema/permission-set.js.map +1 -1
  109. package/dist/schema/permission.d.ts +6 -5
  110. package/dist/schema/permission.d.ts.map +1 -1
  111. package/dist/schema/permission.js +5 -0
  112. package/dist/schema/permission.js.map +1 -1
  113. package/dist/schema/procedure.d.ts +2 -1
  114. package/dist/schema/procedure.d.ts.map +1 -1
  115. package/dist/schema/procedure.js +5 -0
  116. package/dist/schema/procedure.js.map +1 -1
  117. package/dist/schema/query.d.ts +2 -1
  118. package/dist/schema/query.d.ts.map +1 -1
  119. package/dist/schema/query.js +5 -0
  120. package/dist/schema/query.js.map +1 -1
  121. package/dist/schema/record.d.ts +48 -30
  122. package/dist/schema/record.d.ts.map +1 -1
  123. package/dist/schema/record.js +12 -9
  124. package/dist/schema/record.js.map +1 -1
  125. package/dist/schema/ref.d.ts +9 -6
  126. package/dist/schema/ref.d.ts.map +1 -1
  127. package/dist/schema/ref.js +9 -16
  128. package/dist/schema/ref.js.map +1 -1
  129. package/dist/schema/refine.d.ts +4 -4
  130. package/dist/schema/refine.d.ts.map +1 -1
  131. package/dist/schema/refine.js.map +1 -1
  132. package/dist/schema/regexp.d.ts +4 -3
  133. package/dist/schema/regexp.d.ts.map +1 -1
  134. package/dist/schema/regexp.js +5 -0
  135. package/dist/schema/regexp.js.map +1 -1
  136. package/dist/schema/string.d.ts +7 -8
  137. package/dist/schema/string.d.ts.map +1 -1
  138. package/dist/schema/string.js +13 -19
  139. package/dist/schema/string.js.map +1 -1
  140. package/dist/schema/subscription.d.ts +2 -1
  141. package/dist/schema/subscription.d.ts.map +1 -1
  142. package/dist/schema/subscription.js +5 -0
  143. package/dist/schema/subscription.js.map +1 -1
  144. package/dist/schema/token.d.ts +6 -5
  145. package/dist/schema/token.d.ts.map +1 -1
  146. package/dist/schema/token.js +5 -0
  147. package/dist/schema/token.js.map +1 -1
  148. package/dist/schema/typed-object.d.ts +43 -26
  149. package/dist/schema/typed-object.d.ts.map +1 -1
  150. package/dist/schema/typed-object.js +6 -3
  151. package/dist/schema/typed-object.js.map +1 -1
  152. package/dist/schema/typed-ref.d.ts +16 -25
  153. package/dist/schema/typed-ref.d.ts.map +1 -1
  154. package/dist/schema/typed-ref.js +7 -17
  155. package/dist/schema/typed-ref.js.map +1 -1
  156. package/dist/schema/typed-union.d.ts +9 -21
  157. package/dist/schema/typed-union.d.ts.map +1 -1
  158. package/dist/schema/typed-union.js +15 -11
  159. package/dist/schema/typed-union.js.map +1 -1
  160. package/dist/schema/union.d.ts +6 -6
  161. package/dist/schema/union.d.ts.map +1 -1
  162. package/dist/schema/union.js +7 -5
  163. package/dist/schema/union.js.map +1 -1
  164. package/dist/schema/unknown-object.d.ts +5 -4
  165. package/dist/schema/unknown-object.d.ts.map +1 -1
  166. package/dist/schema/unknown-object.js +5 -1
  167. package/dist/schema/unknown-object.js.map +1 -1
  168. package/dist/schema/unknown.d.ts +3 -2
  169. package/dist/schema/unknown.d.ts.map +1 -1
  170. package/dist/schema/unknown.js +5 -1
  171. package/dist/schema/unknown.js.map +1 -1
  172. package/dist/schema/with-default.d.ts +9 -0
  173. package/dist/schema/with-default.d.ts.map +1 -0
  174. package/dist/schema/with-default.js +27 -0
  175. package/dist/schema/with-default.js.map +1 -0
  176. package/dist/schema.d.ts +2 -2
  177. package/dist/schema.d.ts.map +1 -1
  178. package/dist/schema.js +2 -4
  179. package/dist/schema.js.map +1 -1
  180. package/dist/util/assertion-util.d.ts +0 -6
  181. package/dist/util/assertion-util.d.ts.map +1 -1
  182. package/dist/util/assertion-util.js +0 -28
  183. package/dist/util/assertion-util.js.map +1 -1
  184. package/dist/util/memoize.d.ts +2 -2
  185. package/dist/util/memoize.d.ts.map +1 -1
  186. package/dist/util/memoize.js +23 -39
  187. package/dist/util/memoize.js.map +1 -1
  188. package/package.json +3 -3
  189. package/src/core/$type.test.ts +20 -0
  190. package/src/core/$type.ts +30 -0
  191. package/src/core/schema.ts +86 -38
  192. package/src/core/string-format.ts +119 -158
  193. package/src/core/validation-issue.ts +1 -1
  194. package/src/core/validator.ts +93 -53
  195. package/src/external.ts +0 -404
  196. package/src/helpers.test.ts +22 -21
  197. package/src/helpers.ts +14 -14
  198. package/src/schema/array.test.ts +38 -40
  199. package/src/schema/array.ts +35 -13
  200. package/src/schema/blob.test.ts +21 -21
  201. package/src/schema/blob.ts +19 -17
  202. package/src/schema/boolean.test.ts +9 -8
  203. package/src/schema/boolean.ts +7 -13
  204. package/src/schema/bytes.test.ts +13 -13
  205. package/src/schema/bytes.ts +13 -8
  206. package/src/schema/cid.test.ts +3 -3
  207. package/src/schema/cid.ts +13 -12
  208. package/src/schema/custom.test.ts +26 -26
  209. package/src/schema/custom.ts +20 -13
  210. package/src/schema/dict.test.ts +21 -39
  211. package/src/schema/dict.ts +28 -19
  212. package/src/schema/discriminated-union.test.ts +128 -128
  213. package/src/schema/discriminated-union.ts +45 -26
  214. package/src/schema/enum.test.ts +17 -16
  215. package/src/schema/enum.ts +16 -16
  216. package/src/schema/integer.test.ts +22 -21
  217. package/src/schema/integer.ts +12 -9
  218. package/src/schema/intersection.test.ts +10 -10
  219. package/src/schema/intersection.ts +17 -14
  220. package/src/schema/literal.test.ts +35 -34
  221. package/src/schema/literal.ts +12 -15
  222. package/src/schema/never.test.ts +5 -5
  223. package/src/schema/never.ts +7 -2
  224. package/src/schema/null.test.ts +3 -3
  225. package/src/schema/null.ts +9 -9
  226. package/src/schema/nullable.test.ts +31 -42
  227. package/src/schema/nullable.ts +17 -9
  228. package/src/schema/object.test.ts +10 -12
  229. package/src/schema/object.ts +27 -18
  230. package/src/schema/optional.test.ts +21 -28
  231. package/src/schema/optional.ts +27 -10
  232. package/src/schema/params.test.ts +471 -47
  233. package/src/schema/params.ts +72 -38
  234. package/src/schema/payload.test.ts +150 -156
  235. package/src/schema/payload.ts +35 -19
  236. package/src/schema/permission-set.test.ts +206 -273
  237. package/src/schema/permission-set.ts +8 -0
  238. package/src/schema/permission.test.ts +177 -177
  239. package/src/schema/permission.ts +13 -5
  240. package/src/schema/procedure.test.ts +183 -242
  241. package/src/schema/procedure.ts +18 -5
  242. package/src/schema/query.test.ts +186 -200
  243. package/src/schema/query.ts +16 -4
  244. package/src/schema/record.test.ts +121 -101
  245. package/src/schema/record.ts +74 -40
  246. package/src/schema/ref.test.ts +101 -118
  247. package/src/schema/ref.ts +33 -28
  248. package/src/schema/refine.test.ts +28 -28
  249. package/src/schema/refine.ts +23 -20
  250. package/src/schema/regexp.test.ts +29 -33
  251. package/src/schema/regexp.ts +11 -7
  252. package/src/schema/string.test.ts +35 -35
  253. package/src/schema/string.ts +24 -33
  254. package/src/schema/subscription.test.ts +259 -387
  255. package/src/schema/subscription.ts +16 -4
  256. package/src/schema/token.test.ts +47 -324
  257. package/src/schema/token.ts +14 -7
  258. package/src/schema/typed-object.test.ts +98 -81
  259. package/src/schema/typed-object.ts +68 -33
  260. package/src/schema/typed-ref.test.ts +206 -234
  261. package/src/schema/typed-ref.ts +40 -42
  262. package/src/schema/typed-union.test.ts +40 -64
  263. package/src/schema/typed-union.ts +36 -58
  264. package/src/schema/union.test.ts +17 -27
  265. package/src/schema/union.ts +20 -16
  266. package/src/schema/unknown-object.test.ts +8 -8
  267. package/src/schema/unknown-object.ts +9 -7
  268. package/src/schema/unknown.test.ts +4 -4
  269. package/src/schema/unknown.ts +7 -5
  270. package/src/schema/with-default.ts +35 -0
  271. package/src/schema.ts +2 -6
  272. package/src/util/assertion-util.ts +0 -39
  273. package/src/util/memoize.ts +26 -46
  274. package/dist/schema/_parameters.d.ts +0 -17
  275. package/dist/schema/_parameters.d.ts.map +0 -1
  276. package/dist/schema/_parameters.js +0 -20
  277. package/dist/schema/_parameters.js.map +0 -1
  278. package/src/schema/_parameters.test.ts +0 -417
  279. package/src/schema/_parameters.ts +0 -26
@@ -1,20 +1,23 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { EnumSchema } from './enum.js'
3
- import { IntegerSchema } from './integer.js'
4
- import { NullableSchema } from './nullable.js'
5
- import { ObjectSchema } from './object.js'
6
- import { OptionalSchema } from './optional.js'
7
- import { StringSchema } from './string.js'
8
- import { TypedObjectSchema } from './typed-object.js'
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'
9
10
 
10
11
  describe('TypedObjectSchema', () => {
11
- const schema = new TypedObjectSchema(
12
- 'app.bsky.feed.post#main',
13
- new ObjectSchema({
14
- text: new StringSchema({}),
15
- likes: new OptionalSchema(new IntegerSchema({})),
12
+ const schema = typedObject(
13
+ 'app.bsky.feed.post',
14
+ 'main',
15
+ object({
16
+ text: string(),
17
+ likes: optional(integer()),
16
18
  }),
17
19
  )
20
+ type Schema = Infer<typeof schema>
18
21
 
19
22
  describe('basic validation', () => {
20
23
  it('validates plain objects without $type', () => {
@@ -27,7 +30,7 @@ describe('TypedObjectSchema', () => {
27
30
 
28
31
  it('validates plain objects with matching $type', () => {
29
32
  const result = schema.safeParse({
30
- $type: 'app.bsky.feed.post#main',
33
+ $type: 'app.bsky.feed.post',
31
34
  text: 'Hello world',
32
35
  likes: 5,
33
36
  })
@@ -36,7 +39,7 @@ describe('TypedObjectSchema', () => {
36
39
 
37
40
  it('rejects objects with non-matching $type', () => {
38
41
  const result = schema.safeParse({
39
- $type: 'app.bsky.feed.like#main',
42
+ $type: 'app.bsky.feed.like',
40
43
  text: 'Hello world',
41
44
  likes: 5,
42
45
  })
@@ -142,15 +145,15 @@ describe('TypedObjectSchema', () => {
142
145
 
143
146
  it('rejects object $type', () => {
144
147
  const result = schema.safeParse({
145
- $type: { type: 'app.bsky.feed.post#main' },
148
+ $type: { type: 'app.bsky.feed.post' },
146
149
  text: 'Hello world',
147
150
  })
148
151
  expect(result.success).toBe(false)
149
152
  })
150
153
 
151
- it('rejects partial match $type', () => {
154
+ it('rejects non-normalized $type', () => {
152
155
  const result = schema.safeParse({
153
- $type: 'app.bsky.feed.post',
156
+ $type: 'app.bsky.feed.post#main',
154
157
  text: 'Hello world',
155
158
  })
156
159
  expect(result.success).toBe(false)
@@ -185,12 +188,12 @@ describe('TypedObjectSchema', () => {
185
188
  })
186
189
 
187
190
  it('returns true for objects with matching $type', () => {
188
- const obj = { $type: 'app.bsky.feed.post#main', text: 'Hello' }
191
+ const obj = { $type: 'app.bsky.feed.post', text: 'Hello' }
189
192
  expect(schema.isTypeOf(obj)).toBe(true)
190
193
  })
191
194
 
192
195
  it('returns false for objects with non-matching $type', () => {
193
- const obj = { $type: 'app.bsky.feed.like#main', text: 'Hello' }
196
+ const obj = { $type: 'app.bsky.feed.like', text: 'Hello' }
194
197
  expect(schema.isTypeOf(obj)).toBe(false)
195
198
  })
196
199
 
@@ -203,6 +206,30 @@ describe('TypedObjectSchema', () => {
203
206
  const obj = { $type: 123, text: 'Hello' }
204
207
  expect(schema.isTypeOf(obj)).toBe(false)
205
208
  })
209
+
210
+ it('properly discriminates Unknown$TypeObject', () => {
211
+ function foo(value: Unknown$TypedObject | Schema) {
212
+ if (schema.isTypeOf(value)) {
213
+ value.text
214
+ } else {
215
+ // @ts-expect-error
216
+ 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
+ })
206
233
  })
207
234
 
208
235
  describe('$isTypeOf method', () => {
@@ -212,19 +239,19 @@ describe('TypedObjectSchema', () => {
212
239
  })
213
240
 
214
241
  it('returns true for objects with matching $type', () => {
215
- const obj = { $type: 'app.bsky.feed.post#main', text: 'Hello' }
242
+ const obj = { $type: 'app.bsky.feed.post', text: 'Hello' }
216
243
  expect(schema.$isTypeOf(obj)).toBe(true)
217
244
  })
218
245
 
219
246
  it('returns false for objects with non-matching $type', () => {
220
- const obj = { $type: 'app.bsky.feed.like#main', text: 'Hello' }
247
+ const obj = { $type: 'app.bsky.feed.like', text: 'Hello' }
221
248
  expect(schema.$isTypeOf(obj)).toBe(false)
222
249
  })
223
250
 
224
251
  it('behaves identically to isTypeOf', () => {
225
252
  const obj1 = { text: 'Hello' }
226
- const obj2 = { $type: 'app.bsky.feed.post#main', text: 'Hello' }
227
- const obj3 = { $type: 'app.bsky.feed.like#main', text: 'Hello' }
253
+ const obj2 = { $type: 'app.bsky.feed.post', text: 'Hello' }
254
+ const obj3 = { $type: 'app.bsky.feed.like', text: 'Hello' }
228
255
 
229
256
  expect(schema.$isTypeOf(obj1)).toBe(schema.isTypeOf(obj1))
230
257
  expect(schema.$isTypeOf(obj2)).toBe(schema.isTypeOf(obj2))
@@ -239,7 +266,7 @@ describe('TypedObjectSchema', () => {
239
266
  expect(result).toEqual({
240
267
  text: 'Hello world',
241
268
  likes: 5,
242
- $type: 'app.bsky.feed.post#main',
269
+ $type: 'app.bsky.feed.post',
243
270
  })
244
271
  })
245
272
 
@@ -248,7 +275,7 @@ describe('TypedObjectSchema', () => {
248
275
  const result = schema.build(input)
249
276
  expect(result).toEqual({
250
277
  text: 'Hello world',
251
- $type: 'app.bsky.feed.post#main',
278
+ $type: 'app.bsky.feed.post',
252
279
  })
253
280
  })
254
281
 
@@ -259,7 +286,7 @@ describe('TypedObjectSchema', () => {
259
286
  text: 'Hello',
260
287
  likes: 10,
261
288
  extra: 'value',
262
- $type: 'app.bsky.feed.post#main',
289
+ $type: 'app.bsky.feed.post',
263
290
  })
264
291
  })
265
292
 
@@ -271,13 +298,10 @@ describe('TypedObjectSchema', () => {
271
298
  })
272
299
 
273
300
  it('adds $type to empty object', () => {
274
- const emptySchema = new TypedObjectSchema(
275
- 'app.bsky.test#main',
276
- new ObjectSchema({}),
277
- )
301
+ const emptySchema = typedObject('app.bsky.test', 'main', object({}))
278
302
  const input = {}
279
303
  const result = emptySchema.build(input)
280
- expect(result).toEqual({ $type: 'app.bsky.test#main' })
304
+ expect(result).toEqual({ $type: 'app.bsky.test' })
281
305
  })
282
306
  })
283
307
 
@@ -288,7 +312,7 @@ describe('TypedObjectSchema', () => {
288
312
  expect(result).toEqual({
289
313
  text: 'Hello world',
290
314
  likes: 5,
291
- $type: 'app.bsky.feed.post#main',
315
+ $type: 'app.bsky.feed.post',
292
316
  })
293
317
  })
294
318
 
@@ -309,15 +333,14 @@ describe('TypedObjectSchema', () => {
309
333
  })
310
334
 
311
335
  describe('with complex nested schemas', () => {
312
- const complexSchema = new TypedObjectSchema(
313
- 'app.bsky.actor.profile#main',
314
- new ObjectSchema({
315
- displayName: new StringSchema({}),
316
- bio: new OptionalSchema(new StringSchema({ maxLength: 256 })),
317
- followerCount: new OptionalSchema(new IntegerSchema({ minimum: 0 })),
318
- verified: new OptionalSchema(
319
- new NullableSchema(new EnumSchema([true, false])),
320
- ),
336
+ const complexSchema = typedObject(
337
+ 'app.bsky.actor.profile',
338
+ 'main',
339
+ object({
340
+ displayName: string(),
341
+ bio: optional(string({ maxLength: 256 })),
342
+ followerCount: optional(integer({ minimum: 0 })),
343
+ verified: optional(nullable(enumSchema([true, false]))),
321
344
  }),
322
345
  )
323
346
 
@@ -357,7 +380,7 @@ describe('TypedObjectSchema', () => {
357
380
 
358
381
  it('validates with matching $type', () => {
359
382
  const result = complexSchema.safeParse({
360
- $type: 'app.bsky.actor.profile#main',
383
+ $type: 'app.bsky.actor.profile',
361
384
  displayName: 'John Doe',
362
385
  })
363
386
  expect(result.success).toBe(true)
@@ -365,7 +388,7 @@ describe('TypedObjectSchema', () => {
365
388
 
366
389
  it('rejects with non-matching $type', () => {
367
390
  const result = complexSchema.safeParse({
368
- $type: 'app.bsky.feed.post#main',
391
+ $type: 'app.bsky.feed.post',
369
392
  displayName: 'John Doe',
370
393
  })
371
394
  expect(result.success).toBe(false)
@@ -374,18 +397,20 @@ describe('TypedObjectSchema', () => {
374
397
 
375
398
  describe('with different $type formats', () => {
376
399
  it('validates with main type', () => {
377
- const mainSchema = new TypedObjectSchema(
378
- 'app.bsky.feed.post#main',
379
- new ObjectSchema({ text: new StringSchema({}) }),
400
+ const mainSchema = typedObject(
401
+ 'app.bsky.feed.post',
402
+ 'main',
403
+ object({ text: string() }),
380
404
  )
381
405
  const result = mainSchema.safeParse({ text: 'Hello' })
382
406
  expect(result.success).toBe(true)
383
407
  })
384
408
 
385
409
  it('validates with custom fragment', () => {
386
- const fragmentSchema = new TypedObjectSchema(
387
- 'app.bsky.feed.post#reply',
388
- new ObjectSchema({ text: new StringSchema({}) }),
410
+ const fragmentSchema = typedObject(
411
+ 'app.bsky.feed.post',
412
+ 'reply',
413
+ object({ text: string() }),
389
414
  )
390
415
  const result = fragmentSchema.safeParse({
391
416
  $type: 'app.bsky.feed.post#reply',
@@ -395,9 +420,10 @@ describe('TypedObjectSchema', () => {
395
420
  })
396
421
 
397
422
  it('distinguishes between different fragments', () => {
398
- const replySchema = new TypedObjectSchema(
399
- 'app.bsky.feed.post#reply',
400
- new ObjectSchema({ text: new StringSchema({}) }),
423
+ const replySchema = typedObject(
424
+ 'app.bsky.feed.post',
425
+ 'reply',
426
+ object({ text: string() }),
401
427
  )
402
428
  const result = replySchema.safeParse({
403
429
  $type: 'app.bsky.feed.post#quote',
@@ -407,9 +433,10 @@ describe('TypedObjectSchema', () => {
407
433
  })
408
434
 
409
435
  it('validates with long NSID', () => {
410
- const longSchema = new TypedObjectSchema(
411
- 'com.example.app.feature.action.detail#variant',
412
- new ObjectSchema({ value: new StringSchema({}) }),
436
+ const longSchema = typedObject(
437
+ 'com.example.app.feature.action.detail',
438
+ 'variant',
439
+ object({ value: string() }),
413
440
  )
414
441
  const result = longSchema.safeParse({
415
442
  $type: 'com.example.app.feature.action.detail#variant',
@@ -421,10 +448,7 @@ describe('TypedObjectSchema', () => {
421
448
 
422
449
  describe('edge cases', () => {
423
450
  it('validates object with only extra properties', () => {
424
- const minimalSchema = new TypedObjectSchema(
425
- 'app.bsky.test#main',
426
- new ObjectSchema({}),
427
- )
451
+ const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
428
452
  const result = minimalSchema.safeParse({
429
453
  extra1: 'value1',
430
454
  extra2: 'value2',
@@ -433,21 +457,15 @@ describe('TypedObjectSchema', () => {
433
457
  })
434
458
 
435
459
  it('validates empty object with no required properties', () => {
436
- const minimalSchema = new TypedObjectSchema(
437
- 'app.bsky.test#main',
438
- new ObjectSchema({}),
439
- )
460
+ const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
440
461
  const result = minimalSchema.safeParse({})
441
462
  expect(result.success).toBe(true)
442
463
  })
443
464
 
444
465
  it('validates with $type as only property', () => {
445
- const minimalSchema = new TypedObjectSchema(
446
- 'app.bsky.test#main',
447
- new ObjectSchema({}),
448
- )
466
+ const minimalSchema = typedObject('app.bsky.test', 'main', object({}))
449
467
  const result = minimalSchema.safeParse({
450
- $type: 'app.bsky.test#main',
468
+ $type: 'app.bsky.test',
451
469
  })
452
470
  expect(result.success).toBe(true)
453
471
  })
@@ -494,15 +512,14 @@ describe('TypedObjectSchema', () => {
494
512
  })
495
513
 
496
514
  describe('integration with all property types', () => {
497
- const fullSchema = new TypedObjectSchema(
498
- 'app.bsky.test#full',
499
- new ObjectSchema({
500
- required: new StringSchema({}),
501
- optional: new OptionalSchema(new StringSchema({})),
502
- nullable: new NullableSchema(new StringSchema({})),
503
- optionalNullable: new OptionalSchema(
504
- new NullableSchema(new StringSchema({})),
505
- ),
515
+ const fullSchema = typedObject(
516
+ 'app.bsky.test',
517
+ 'full',
518
+ object({
519
+ required: string(),
520
+ optional: optional(string()),
521
+ nullable: nullable(string()),
522
+ optionalNullable: optional(nullable(string())),
506
523
  }),
507
524
  )
508
525
 
@@ -576,9 +593,9 @@ describe('TypedObjectSchema', () => {
576
593
  })
577
594
 
578
595
  describe('comparison with plain ObjectSchema', () => {
579
- const plainSchema = new ObjectSchema({
580
- text: new StringSchema({}),
581
- likes: new OptionalSchema(new IntegerSchema({})),
596
+ const plainSchema = object({
597
+ text: string(),
598
+ likes: optional(integer()),
582
599
  })
583
600
 
584
601
  it('typed schema accepts same input as plain schema', () => {
@@ -605,7 +622,7 @@ describe('TypedObjectSchema', () => {
605
622
  })
606
623
 
607
624
  it('typed schema accepts matching $type', () => {
608
- const input = { $type: 'app.bsky.feed.post#main', text: 'Hello' }
625
+ const input = { $type: 'app.bsky.feed.post', text: 'Hello' }
609
626
  const typedResult = schema.safeParse(input)
610
627
  expect(typedResult.success).toBe(true)
611
628
  })
@@ -1,61 +1,60 @@
1
1
  import { isPlainObject } from '@atproto/lex-data'
2
2
  import {
3
3
  $Type,
4
+ $TypeOf,
4
5
  $Typed,
5
6
  $TypedMaybe,
6
- Infer,
7
+ $type,
8
+ $typed,
9
+ InferInput,
10
+ InferOutput,
11
+ NsidString,
7
12
  Schema,
8
- ValidationResult,
13
+ Unknown$TypedObject,
14
+ ValidationContext,
9
15
  Validator,
10
- ValidatorContext,
11
16
  } from '../core.js'
12
- import { TypedObject } from './typed-union.js'
13
-
14
- export type TypedObjectSchemaOutput<
15
- T extends $Type,
16
- S extends Validator<{ [k: string]: unknown }>,
17
- > = $TypedMaybe<Infer<S>, T>
18
17
 
19
18
  export class TypedObjectSchema<
20
- const T extends $Type = any,
21
- const S extends Validator<{ [k: string]: unknown }> = any,
22
- > extends Schema<TypedObjectSchemaOutput<T, S>> {
19
+ const TType extends $Type = $Type,
20
+ const TShape extends Validator<{ [k: string]: unknown }> = any,
21
+ > extends Schema<
22
+ $TypedMaybe<InferInput<TShape>, TType>,
23
+ $TypedMaybe<InferOutput<TShape>, TType>
24
+ > {
23
25
  constructor(
24
- readonly $type: T,
25
- readonly schema: S,
26
+ readonly $type: TType,
27
+ readonly schema: TShape,
26
28
  ) {
27
29
  super()
28
30
  }
29
31
 
30
32
  isTypeOf<X extends Record<string, unknown>>(
31
33
  value: X,
32
- ): value is Exclude<
33
- X extends { $type?: T } ? X : $TypedMaybe<X, T>,
34
- TypedObject
35
- > {
34
+ ): value is X extends { $type?: TType }
35
+ ? X
36
+ : $TypedMaybe<Exclude<X, Unknown$TypedObject>, TType> {
36
37
  return value.$type === undefined || value.$type === this.$type
37
38
  }
38
39
 
39
- build<X extends Omit<Infer<S>, '$type'>>(
40
- input: X,
41
- ): $Typed<Omit<X, '$type'>, T> {
42
- return input.$type === this.$type
43
- ? (input as $Typed<X, T>)
44
- : { ...input, $type: this.$type }
40
+ build(
41
+ input: Omit<InferInput<this>, '$type'>,
42
+ ): $Typed<InferOutput<this>, TType> {
43
+ return this.parse($typed(input, this.$type)) as $Typed<
44
+ InferOutput<this>,
45
+ TType
46
+ >
45
47
  }
46
48
 
47
49
  $isTypeOf<X extends Record<string, unknown>>(value: X) {
48
50
  return this.isTypeOf(value)
49
51
  }
50
52
 
51
- $build<X extends Omit<Infer<S>, '$type'>>(input: X) {
52
- return this.build<X>(input)
53
+ $build(input: Omit<InferInput<this>, '$type'>) {
54
+ return this.build(input)
53
55
  }
54
56
 
55
- validateInContext(
56
- input: unknown,
57
- ctx: ValidatorContext,
58
- ): ValidationResult<TypedObjectSchemaOutput<T, S>> {
57
+ validateInContext(input: unknown, ctx: ValidationContext) {
59
58
  if (!isPlainObject(input)) {
60
59
  return ctx.issueInvalidType(input, 'object')
61
60
  }
@@ -68,8 +67,44 @@ export class TypedObjectSchema<
68
67
  return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])
69
68
  }
70
69
 
71
- return ctx.validate(input, this.schema) as ValidationResult<
72
- TypedObjectSchemaOutput<T, S>
73
- >
70
+ return ctx.validate(input, this.schema)
74
71
  }
75
72
  }
73
+
74
+ /**
75
+ * This function offers two overloads:
76
+ * - One that allows creating a {@link TypedObjectSchema}, and infer the output
77
+ * type from the provided arguments, without requiring to specify any of the
78
+ * generics. This is useful when you want to define a record without
79
+ * explicitly defining its interface. This version does not support circular
80
+ * references, as TypeScript cannot infer types in such cases.
81
+ * - One allows creating a {@link TypedObjectSchema} with an explicitly defined
82
+ * interface. This will typically be used by codegen (`lex build`) to generate
83
+ * schemas that work even if they contain circular references.
84
+ */
85
+ export function typedObject<
86
+ const N extends NsidString,
87
+ const H extends string,
88
+ const S extends Validator<{ [k: string]: unknown }>,
89
+ >(nsid: N, hash: H, validator: S): TypedObjectSchema<$Type<N, H>, S>
90
+ export function typedObject<V extends { $type?: $Type }>(
91
+ nsid: V extends { $type?: infer T extends string }
92
+ ? T extends `${infer N}#${string}`
93
+ ? N
94
+ : T // (T is a "main" type, so already an NSID)
95
+ : never,
96
+ hash: V extends { $type?: infer T extends string }
97
+ ? T extends `${string}#${infer H}`
98
+ ? H
99
+ : 'main'
100
+ : never,
101
+ validator: Validator<Omit<V, '$type'>>,
102
+ ): TypedObjectSchema<$TypeOf<V>, Validator<V>>
103
+ /*@__NO_SIDE_EFFECTS__*/
104
+ export function typedObject<
105
+ const N extends NsidString,
106
+ const H extends string,
107
+ const S extends Validator<{ [k: string]: unknown }>,
108
+ >(nsid: N, hash: H, validator: S) {
109
+ return new TypedObjectSchema<$Type<N, H>, S>($type(nsid, hash), validator)
110
+ }