@atproto/lex-schema 0.0.1 → 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 +8 -4
  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 +13 -56
  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 +23 -134
  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,694 @@
1
+ import { ObjectSchema } from './object.js'
2
+ import { RecordSchema } from './record.js'
3
+ import { StringSchema } from './string.js'
4
+
5
+ describe('RecordSchema', () => {
6
+ describe('basic validation', () => {
7
+ const schema = new RecordSchema(
8
+ 'any',
9
+ 'app.bsky.feed.post',
10
+ new ObjectSchema({
11
+ $type: new StringSchema({}),
12
+ text: new StringSchema({}),
13
+ }),
14
+ )
15
+
16
+ it('validates record with correct $type', () => {
17
+ const result = schema.safeParse({
18
+ $type: 'app.bsky.feed.post',
19
+ text: 'Hello world',
20
+ })
21
+ expect(result.success).toBe(true)
22
+ if (result.success) {
23
+ expect(result.value.$type).toBe('app.bsky.feed.post')
24
+ expect(result.value.text).toBe('Hello world')
25
+ }
26
+ })
27
+
28
+ it('rejects record with incorrect $type', () => {
29
+ const result = schema.safeParse({
30
+ $type: 'app.bsky.feed.like',
31
+ text: 'Hello world',
32
+ })
33
+ expect(result.success).toBe(false)
34
+ })
35
+
36
+ it('rejects record missing $type', () => {
37
+ const result = schema.safeParse({
38
+ text: 'Hello world',
39
+ })
40
+ expect(result.success).toBe(false)
41
+ })
42
+
43
+ it('rejects non-objects', () => {
44
+ const result = schema.safeParse('not an object')
45
+ expect(result.success).toBe(false)
46
+ })
47
+
48
+ it('rejects null', () => {
49
+ const result = schema.safeParse(null)
50
+ expect(result.success).toBe(false)
51
+ })
52
+
53
+ it('rejects undefined', () => {
54
+ const result = schema.safeParse(undefined)
55
+ expect(result.success).toBe(false)
56
+ })
57
+
58
+ it('rejects arrays', () => {
59
+ const result = schema.safeParse([{ $type: 'app.bsky.feed.post' }])
60
+ expect(result.success).toBe(false)
61
+ })
62
+ })
63
+
64
+ describe('isTypeOf method', () => {
65
+ const schema = new RecordSchema(
66
+ 'any',
67
+ 'app.bsky.feed.post',
68
+ new ObjectSchema({
69
+ $type: new StringSchema({}),
70
+ text: new StringSchema({}),
71
+ }),
72
+ )
73
+
74
+ it('returns true for matching $type', () => {
75
+ const result = schema.isTypeOf({ $type: 'app.bsky.feed.post' })
76
+ expect(result).toBe(true)
77
+ })
78
+
79
+ it('returns false for non-matching $type', () => {
80
+ const result = schema.isTypeOf({ $type: 'app.bsky.feed.like' })
81
+ expect(result).toBe(false)
82
+ })
83
+
84
+ it('returns false for missing $type', () => {
85
+ const result = schema.isTypeOf({})
86
+ expect(result).toBe(false)
87
+ })
88
+
89
+ it('returns false for undefined $type', () => {
90
+ const result = schema.isTypeOf({ $type: undefined })
91
+ expect(result).toBe(false)
92
+ })
93
+
94
+ it('returns false for null $type', () => {
95
+ const result = schema.isTypeOf({ $type: null })
96
+ expect(result).toBe(false)
97
+ })
98
+ })
99
+
100
+ describe('build method', () => {
101
+ const schema = new RecordSchema(
102
+ 'any',
103
+ 'app.bsky.feed.post',
104
+ new ObjectSchema({
105
+ $type: new StringSchema({}),
106
+ text: new StringSchema({}),
107
+ }),
108
+ )
109
+
110
+ it('adds correct $type to input', () => {
111
+ const result = schema.build({ text: 'Hello world' })
112
+ expect(result.$type).toBe('app.bsky.feed.post')
113
+ expect(result.text).toBe('Hello world')
114
+ })
115
+
116
+ it('preserves existing properties', () => {
117
+ const result = schema.build({
118
+ text: 'Hello world',
119
+ extra: 'value',
120
+ })
121
+ expect(result.$type).toBe('app.bsky.feed.post')
122
+ expect(result.text).toBe('Hello world')
123
+ expect(result.extra).toBe('value')
124
+ })
125
+
126
+ it('overwrites existing $type', () => {
127
+ const result = schema.build({
128
+ $type: 'wrong.type',
129
+ text: 'Hello world',
130
+ })
131
+ expect(result.$type).toBe('app.bsky.feed.post')
132
+ })
133
+ })
134
+
135
+ describe('key type: any', () => {
136
+ const schema = new RecordSchema(
137
+ 'any',
138
+ 'app.bsky.feed.post',
139
+ new ObjectSchema({
140
+ $type: new StringSchema({}),
141
+ text: new StringSchema({}),
142
+ }),
143
+ )
144
+
145
+ it('validates record keys', () => {
146
+ const result = schema.keySchema.safeParse('anyStringKey')
147
+ expect(result.success).toBe(true)
148
+ })
149
+
150
+ it('validates alphanumeric keys', () => {
151
+ const result = schema.keySchema.safeParse('key123')
152
+ expect(result.success).toBe(true)
153
+ })
154
+
155
+ it('validates keys with special characters', () => {
156
+ const result = schema.keySchema.safeParse('key-with-dashes')
157
+ expect(result.success).toBe(true)
158
+ })
159
+
160
+ it('rejects empty strings', () => {
161
+ const result = schema.keySchema.safeParse('')
162
+ expect(result.success).toBe(false)
163
+ })
164
+
165
+ it('rejects non-strings', () => {
166
+ const result = schema.keySchema.safeParse(123)
167
+ expect(result.success).toBe(false)
168
+ })
169
+ })
170
+
171
+ describe('key type: tid', () => {
172
+ const schema = new RecordSchema(
173
+ 'tid',
174
+ 'app.bsky.feed.post',
175
+ new ObjectSchema({
176
+ $type: new StringSchema({}),
177
+ text: new StringSchema({}),
178
+ }),
179
+ )
180
+
181
+ it('validates valid TID', () => {
182
+ const result = schema.keySchema.safeParse('3jzfcijpj2z2a')
183
+ expect(result.success).toBe(true)
184
+ })
185
+
186
+ it('rejects invalid TID format', () => {
187
+ const result = schema.keySchema.safeParse('not-a-tid')
188
+ expect(result.success).toBe(false)
189
+ })
190
+
191
+ it('rejects TID with invalid characters', () => {
192
+ const result = schema.keySchema.safeParse('3jzfcijpj2z2!')
193
+ expect(result.success).toBe(false)
194
+ })
195
+
196
+ it('rejects empty strings', () => {
197
+ const result = schema.keySchema.safeParse('')
198
+ expect(result.success).toBe(false)
199
+ })
200
+
201
+ it('rejects regular strings', () => {
202
+ const result = schema.keySchema.safeParse('regularString')
203
+ expect(result.success).toBe(false)
204
+ })
205
+ })
206
+
207
+ describe('key type: nsid', () => {
208
+ const schema = new RecordSchema(
209
+ 'nsid',
210
+ 'app.bsky.feed.post',
211
+ new ObjectSchema({
212
+ $type: new StringSchema({}),
213
+ text: new StringSchema({}),
214
+ }),
215
+ )
216
+
217
+ it('validates valid NSID', () => {
218
+ const result = schema.keySchema.safeParse('app.bsky.feed.post')
219
+ expect(result.success).toBe(true)
220
+ })
221
+
222
+ it('validates NSID with multiple segments', () => {
223
+ const result = schema.keySchema.safeParse(
224
+ 'com.example.app.feature.action',
225
+ )
226
+ expect(result.success).toBe(true)
227
+ })
228
+
229
+ it('rejects invalid NSID format', () => {
230
+ const result = schema.keySchema.safeParse('not-an-nsid')
231
+ expect(result.success).toBe(false)
232
+ })
233
+
234
+ it('rejects NSID with invalid characters', () => {
235
+ const result = schema.keySchema.safeParse('app.bsky.feed!')
236
+ expect(result.success).toBe(false)
237
+ })
238
+
239
+ it('rejects empty strings', () => {
240
+ const result = schema.keySchema.safeParse('')
241
+ expect(result.success).toBe(false)
242
+ })
243
+ })
244
+
245
+ describe('key type: literal', () => {
246
+ describe('literal:self', () => {
247
+ const schema = new RecordSchema(
248
+ 'literal:self',
249
+ 'app.bsky.feed.post',
250
+ new ObjectSchema({
251
+ $type: new StringSchema({}),
252
+ text: new StringSchema({}),
253
+ }),
254
+ )
255
+
256
+ it('validates exact literal "self"', () => {
257
+ const result = schema.keySchema.safeParse('self')
258
+ expect(result.success).toBe(true)
259
+ })
260
+
261
+ it('rejects non-matching strings', () => {
262
+ const result = schema.keySchema.safeParse('other')
263
+ expect(result.success).toBe(false)
264
+ })
265
+
266
+ it('rejects case variations', () => {
267
+ const result = schema.keySchema.safeParse('Self')
268
+ expect(result.success).toBe(false)
269
+ })
270
+
271
+ it('rejects empty strings', () => {
272
+ const result = schema.keySchema.safeParse('')
273
+ expect(result.success).toBe(false)
274
+ })
275
+ })
276
+
277
+ describe('literal:customKey', () => {
278
+ const schema = new RecordSchema(
279
+ 'literal:customKey',
280
+ 'app.bsky.feed.post',
281
+ new ObjectSchema({
282
+ $type: new StringSchema({}),
283
+ text: new StringSchema({}),
284
+ }),
285
+ )
286
+
287
+ it('validates exact literal match', () => {
288
+ const result = schema.keySchema.safeParse('customKey')
289
+ expect(result.success).toBe(true)
290
+ })
291
+
292
+ it('rejects non-matching strings', () => {
293
+ const result = schema.keySchema.safeParse('otherKey')
294
+ expect(result.success).toBe(false)
295
+ })
296
+
297
+ it('rejects partial matches', () => {
298
+ const result = schema.keySchema.safeParse('custom')
299
+ expect(result.success).toBe(false)
300
+ })
301
+ })
302
+ })
303
+
304
+ describe('$type with hash fragment', () => {
305
+ const schema = new RecordSchema(
306
+ 'any',
307
+ 'app.bsky.feed.post#main',
308
+ new ObjectSchema({
309
+ $type: new StringSchema({}),
310
+ text: new StringSchema({}),
311
+ }),
312
+ )
313
+
314
+ it('validates record with correct $type including hash', () => {
315
+ const result = schema.safeParse({
316
+ $type: 'app.bsky.feed.post#main',
317
+ text: 'Hello world',
318
+ })
319
+ expect(result.success).toBe(true)
320
+ })
321
+
322
+ it('rejects record with $type without hash', () => {
323
+ const result = schema.safeParse({
324
+ $type: 'app.bsky.feed.post',
325
+ text: 'Hello world',
326
+ })
327
+ expect(result.success).toBe(false)
328
+ })
329
+
330
+ it('rejects record with different hash fragment', () => {
331
+ const result = schema.safeParse({
332
+ $type: 'app.bsky.feed.post#other',
333
+ text: 'Hello world',
334
+ })
335
+ expect(result.success).toBe(false)
336
+ })
337
+ })
338
+
339
+ describe('complex nested schema', () => {
340
+ const schema = new RecordSchema(
341
+ 'any',
342
+ 'app.bsky.feed.post',
343
+ new ObjectSchema({
344
+ $type: new StringSchema({}),
345
+ text: new StringSchema({ maxLength: 300 }),
346
+ createdAt: new StringSchema({ format: 'datetime' }),
347
+ }),
348
+ )
349
+
350
+ it('validates complex record with all constraints', () => {
351
+ const result = schema.safeParse({
352
+ $type: 'app.bsky.feed.post',
353
+ text: 'Hello world',
354
+ createdAt: '2023-12-25T12:00:00Z',
355
+ })
356
+ expect(result.success).toBe(true)
357
+ })
358
+
359
+ it('rejects when nested field violates constraints', () => {
360
+ const result = schema.safeParse({
361
+ $type: 'app.bsky.feed.post',
362
+ text: 'a'.repeat(301),
363
+ createdAt: '2023-12-25T12:00:00Z',
364
+ })
365
+ expect(result.success).toBe(false)
366
+ })
367
+
368
+ it('rejects when datetime format is invalid', () => {
369
+ const result = schema.safeParse({
370
+ $type: 'app.bsky.feed.post',
371
+ text: 'Hello world',
372
+ createdAt: 'not-a-date',
373
+ })
374
+ expect(result.success).toBe(false)
375
+ })
376
+ })
377
+
378
+ describe('edge cases', () => {
379
+ const schema = new RecordSchema(
380
+ 'any',
381
+ 'app.bsky.feed.post',
382
+ new ObjectSchema({
383
+ $type: new StringSchema({}),
384
+ text: new StringSchema({}),
385
+ }),
386
+ )
387
+
388
+ it('handles $type as number', () => {
389
+ const result = schema.safeParse({
390
+ $type: 123,
391
+ text: 'Hello world',
392
+ })
393
+ expect(result.success).toBe(false)
394
+ })
395
+
396
+ it('handles $type as boolean', () => {
397
+ const result = schema.safeParse({
398
+ $type: true,
399
+ text: 'Hello world',
400
+ })
401
+ expect(result.success).toBe(false)
402
+ })
403
+
404
+ it('handles $type as object', () => {
405
+ const result = schema.safeParse({
406
+ $type: { value: 'app.bsky.feed.post' },
407
+ text: 'Hello world',
408
+ })
409
+ expect(result.success).toBe(false)
410
+ })
411
+
412
+ it('handles $type as array', () => {
413
+ const result = schema.safeParse({
414
+ $type: ['app.bsky.feed.post'],
415
+ text: 'Hello world',
416
+ })
417
+ expect(result.success).toBe(false)
418
+ })
419
+
420
+ it('preserves extra properties not in schema', () => {
421
+ const input = {
422
+ $type: 'app.bsky.feed.post',
423
+ text: 'Hello world',
424
+ extra: 'value',
425
+ another: 123,
426
+ }
427
+
428
+ const result = schema.safeParse(input)
429
+ expect(result.success).toBe(true)
430
+ if (result.success) {
431
+ // @ts-expect-error
432
+ expect(result.value.extra).toBe('value')
433
+ // @ts-expect-error
434
+ expect(result.value.another).toBe(123)
435
+ }
436
+ })
437
+
438
+ it('handles empty object', () => {
439
+ const result = schema.safeParse({})
440
+ expect(result.success).toBe(false)
441
+ })
442
+
443
+ it('handles deeply nested structures', () => {
444
+ const complexSchema = new RecordSchema(
445
+ 'any',
446
+ 'app.bsky.complex',
447
+ new ObjectSchema({
448
+ $type: new StringSchema({}),
449
+ nested: new ObjectSchema({
450
+ deep: new ObjectSchema({
451
+ value: new StringSchema({}),
452
+ }),
453
+ }),
454
+ }),
455
+ )
456
+
457
+ const result = complexSchema.safeParse({
458
+ $type: 'app.bsky.complex',
459
+ nested: {
460
+ deep: {
461
+ value: 'test',
462
+ },
463
+ },
464
+ })
465
+ expect(result.success).toBe(true)
466
+ })
467
+ })
468
+
469
+ describe('$isTypeOf method', () => {
470
+ const schema = new RecordSchema(
471
+ 'any',
472
+ 'app.bsky.feed.post',
473
+ new ObjectSchema({
474
+ $type: new StringSchema({}),
475
+ text: new StringSchema({}),
476
+ }),
477
+ )
478
+
479
+ it('returns true for matching $type', () => {
480
+ const result = schema.$isTypeOf({ $type: 'app.bsky.feed.post' })
481
+ expect(result).toBe(true)
482
+ })
483
+
484
+ it('returns false for non-matching $type', () => {
485
+ const result = schema.$isTypeOf({ $type: 'app.bsky.feed.like' })
486
+ expect(result).toBe(false)
487
+ })
488
+ })
489
+
490
+ describe('$build method', () => {
491
+ const schema = new RecordSchema(
492
+ 'any',
493
+ 'app.bsky.feed.post',
494
+ new ObjectSchema({
495
+ $type: new StringSchema({}),
496
+ text: new StringSchema({}),
497
+ }),
498
+ )
499
+
500
+ it('adds correct $type to input', () => {
501
+ const result = schema.$build({ text: 'Hello world' })
502
+ expect(result.$type).toBe('app.bsky.feed.post')
503
+ expect(result.text).toBe('Hello world')
504
+ })
505
+ })
506
+
507
+ describe('validation with missing required fields', () => {
508
+ const schema = new RecordSchema(
509
+ 'any',
510
+ 'app.bsky.feed.post',
511
+ new ObjectSchema({
512
+ $type: new StringSchema({}),
513
+ text: new StringSchema({}),
514
+ author: new StringSchema({}),
515
+ }),
516
+ )
517
+
518
+ it('rejects when required field is missing', () => {
519
+ const result = schema.safeParse({
520
+ $type: 'app.bsky.feed.post',
521
+ text: 'Hello world',
522
+ })
523
+ expect(result.success).toBe(false)
524
+ })
525
+
526
+ it('validates when all required fields are present', () => {
527
+ const result = schema.safeParse({
528
+ $type: 'app.bsky.feed.post',
529
+ text: 'Hello world',
530
+ author: 'did:plc:123',
531
+ })
532
+ expect(result.success).toBe(true)
533
+ })
534
+ })
535
+
536
+ describe('different record key types', () => {
537
+ it('constructs with key type "any"', () => {
538
+ const schema = new RecordSchema(
539
+ 'any',
540
+ 'app.bsky.test',
541
+ new ObjectSchema({ $type: new StringSchema({}) }),
542
+ )
543
+ expect(schema.key).toBe('any')
544
+ expect(schema.keySchema).toBeDefined()
545
+ })
546
+
547
+ it('constructs with key type "tid"', () => {
548
+ const schema = new RecordSchema(
549
+ 'tid',
550
+ 'app.bsky.test',
551
+ new ObjectSchema({ $type: new StringSchema({}) }),
552
+ )
553
+ expect(schema.key).toBe('tid')
554
+ expect(schema.keySchema).toBeDefined()
555
+ })
556
+
557
+ it('constructs with key type "nsid"', () => {
558
+ const schema = new RecordSchema(
559
+ 'nsid',
560
+ 'app.bsky.test',
561
+ new ObjectSchema({ $type: new StringSchema({}) }),
562
+ )
563
+ expect(schema.key).toBe('nsid')
564
+ expect(schema.keySchema).toBeDefined()
565
+ })
566
+
567
+ it('constructs with literal key type', () => {
568
+ const schema = new RecordSchema(
569
+ 'literal:custom',
570
+ 'app.bsky.test',
571
+ new ObjectSchema({ $type: new StringSchema({}) }),
572
+ )
573
+ expect(schema.key).toBe('literal:custom')
574
+ expect(schema.keySchema).toBeDefined()
575
+ })
576
+ })
577
+
578
+ describe('validation with undefined vs missing fields', () => {
579
+ const schema = new RecordSchema(
580
+ 'any',
581
+ 'app.bsky.feed.post',
582
+ new ObjectSchema({
583
+ $type: new StringSchema({}),
584
+ text: new StringSchema({}),
585
+ }),
586
+ )
587
+
588
+ it('rejects when required field is explicitly undefined', () => {
589
+ const result = schema.safeParse({
590
+ $type: 'app.bsky.feed.post',
591
+ text: undefined,
592
+ })
593
+ expect(result.success).toBe(false)
594
+ })
595
+
596
+ it('rejects when $type is explicitly undefined', () => {
597
+ const result = schema.safeParse({
598
+ $type: undefined,
599
+ text: 'Hello world',
600
+ })
601
+ expect(result.success).toBe(false)
602
+ })
603
+
604
+ it('rejects when $type is null', () => {
605
+ const result = schema.safeParse({
606
+ $type: null,
607
+ text: 'Hello world',
608
+ })
609
+ expect(result.success).toBe(false)
610
+ })
611
+ })
612
+
613
+ describe('record with empty $type string', () => {
614
+ it('rejects empty $type string', () => {
615
+ const schema = new RecordSchema(
616
+ 'any',
617
+ 'app.bsky.feed.post',
618
+ new ObjectSchema({
619
+ $type: new StringSchema({}),
620
+ text: new StringSchema({}),
621
+ }),
622
+ )
623
+
624
+ const result = schema.safeParse({
625
+ $type: '',
626
+ text: 'Hello world',
627
+ })
628
+ expect(result.success).toBe(false)
629
+ })
630
+ })
631
+
632
+ describe('special characters in $type', () => {
633
+ it('validates $type with dots', () => {
634
+ const schema = new RecordSchema(
635
+ 'any',
636
+ 'app.bsky.feed.post',
637
+ new ObjectSchema({
638
+ $type: new StringSchema({}),
639
+ text: new StringSchema({}),
640
+ }),
641
+ )
642
+
643
+ const result = schema.safeParse({
644
+ $type: 'app.bsky.feed.post',
645
+ text: 'Hello world',
646
+ })
647
+ expect(result.success).toBe(true)
648
+ })
649
+
650
+ it('validates $type with hash and alphanumeric fragment', () => {
651
+ const schema = new RecordSchema(
652
+ 'any',
653
+ 'app.bsky.feed.post#reply123',
654
+ new ObjectSchema({
655
+ $type: new StringSchema({}),
656
+ text: new StringSchema({}),
657
+ }),
658
+ )
659
+
660
+ const result = schema.safeParse({
661
+ $type: 'app.bsky.feed.post#reply123',
662
+ text: 'Hello world',
663
+ })
664
+ expect(result.success).toBe(true)
665
+ })
666
+ })
667
+
668
+ describe('case sensitivity', () => {
669
+ const schema = new RecordSchema(
670
+ 'any',
671
+ 'app.bsky.feed.post',
672
+ new ObjectSchema({
673
+ $type: new StringSchema({}),
674
+ text: new StringSchema({}),
675
+ }),
676
+ )
677
+
678
+ it('rejects $type with different case', () => {
679
+ const result = schema.safeParse({
680
+ $type: 'App.Bsky.Feed.Post',
681
+ text: 'Hello world',
682
+ })
683
+ expect(result.success).toBe(false)
684
+ })
685
+
686
+ it('rejects $type with uppercase', () => {
687
+ const result = schema.safeParse({
688
+ $type: 'APP.BSKY.FEED.POST',
689
+ text: 'Hello world',
690
+ })
691
+ expect(result.success).toBe(false)
692
+ })
693
+ })
694
+ })