@atproto/lex-schema 0.0.12 → 0.0.14

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 (210) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/core/schema.d.ts +27 -36
  3. package/dist/core/schema.d.ts.map +1 -1
  4. package/dist/core/schema.js +68 -54
  5. package/dist/core/schema.js.map +1 -1
  6. package/dist/core/string-format.d.ts +1 -14
  7. package/dist/core/string-format.d.ts.map +1 -1
  8. package/dist/core/string-format.js +12 -9
  9. package/dist/core/string-format.js.map +1 -1
  10. package/dist/core/validation-error.d.ts +5 -5
  11. package/dist/core/validation-error.d.ts.map +1 -1
  12. package/dist/core/validation-error.js +8 -8
  13. package/dist/core/validation-error.js.map +1 -1
  14. package/dist/core/validation-issue.js +3 -1
  15. package/dist/core/validation-issue.js.map +1 -1
  16. package/dist/core/validator.d.ts +16 -8
  17. package/dist/core/validator.d.ts.map +1 -1
  18. package/dist/core/validator.js +24 -6
  19. package/dist/core/validator.js.map +1 -1
  20. package/dist/helpers.d.ts +10 -11
  21. package/dist/helpers.d.ts.map +1 -1
  22. package/dist/helpers.js.map +1 -1
  23. package/dist/schema/array.d.ts +1 -0
  24. package/dist/schema/array.d.ts.map +1 -1
  25. package/dist/schema/array.js +2 -1
  26. package/dist/schema/array.js.map +1 -1
  27. package/dist/schema/blob.d.ts +4 -2
  28. package/dist/schema/blob.d.ts.map +1 -1
  29. package/dist/schema/blob.js +5 -2
  30. package/dist/schema/blob.js.map +1 -1
  31. package/dist/schema/boolean.d.ts +1 -0
  32. package/dist/schema/boolean.d.ts.map +1 -1
  33. package/dist/schema/boolean.js +2 -1
  34. package/dist/schema/boolean.js.map +1 -1
  35. package/dist/schema/bytes.d.ts +1 -0
  36. package/dist/schema/bytes.d.ts.map +1 -1
  37. package/dist/schema/bytes.js +2 -1
  38. package/dist/schema/bytes.js.map +1 -1
  39. package/dist/schema/cid.d.ts +1 -0
  40. package/dist/schema/cid.d.ts.map +1 -1
  41. package/dist/schema/cid.js +2 -1
  42. package/dist/schema/cid.js.map +1 -1
  43. package/dist/schema/custom.d.ts +1 -0
  44. package/dist/schema/custom.d.ts.map +1 -1
  45. package/dist/schema/custom.js +1 -0
  46. package/dist/schema/custom.js.map +1 -1
  47. package/dist/schema/dict.d.ts +1 -0
  48. package/dist/schema/dict.d.ts.map +1 -1
  49. package/dist/schema/dict.js +2 -1
  50. package/dist/schema/dict.js.map +1 -1
  51. package/dist/schema/discriminated-union.d.ts +1 -0
  52. package/dist/schema/discriminated-union.d.ts.map +1 -1
  53. package/dist/schema/discriminated-union.js +2 -1
  54. package/dist/schema/discriminated-union.js.map +1 -1
  55. package/dist/schema/enum.d.ts +1 -0
  56. package/dist/schema/enum.d.ts.map +1 -1
  57. package/dist/schema/enum.js +1 -0
  58. package/dist/schema/enum.js.map +1 -1
  59. package/dist/schema/integer.d.ts +1 -0
  60. package/dist/schema/integer.d.ts.map +1 -1
  61. package/dist/schema/integer.js +2 -1
  62. package/dist/schema/integer.js.map +1 -1
  63. package/dist/schema/intersection.d.ts +1 -0
  64. package/dist/schema/intersection.d.ts.map +1 -1
  65. package/dist/schema/intersection.js +1 -0
  66. package/dist/schema/intersection.js.map +1 -1
  67. package/dist/schema/lex-map.d.ts +37 -0
  68. package/dist/schema/lex-map.d.ts.map +1 -0
  69. package/dist/schema/lex-map.js +60 -0
  70. package/dist/schema/lex-map.js.map +1 -0
  71. package/dist/schema/lex-value.d.ts +35 -0
  72. package/dist/schema/lex-value.d.ts.map +1 -0
  73. package/dist/schema/lex-value.js +87 -0
  74. package/dist/schema/lex-value.js.map +1 -0
  75. package/dist/schema/literal.d.ts +1 -0
  76. package/dist/schema/literal.d.ts.map +1 -1
  77. package/dist/schema/literal.js +1 -0
  78. package/dist/schema/literal.js.map +1 -1
  79. package/dist/schema/never.d.ts +1 -0
  80. package/dist/schema/never.d.ts.map +1 -1
  81. package/dist/schema/never.js +2 -1
  82. package/dist/schema/never.js.map +1 -1
  83. package/dist/schema/null.d.ts +1 -0
  84. package/dist/schema/null.d.ts.map +1 -1
  85. package/dist/schema/null.js +2 -1
  86. package/dist/schema/null.js.map +1 -1
  87. package/dist/schema/nullable.d.ts +1 -0
  88. package/dist/schema/nullable.d.ts.map +1 -1
  89. package/dist/schema/nullable.js +1 -0
  90. package/dist/schema/nullable.js.map +1 -1
  91. package/dist/schema/object.d.ts +1 -0
  92. package/dist/schema/object.d.ts.map +1 -1
  93. package/dist/schema/object.js +2 -1
  94. package/dist/schema/object.js.map +1 -1
  95. package/dist/schema/optional.d.ts +1 -0
  96. package/dist/schema/optional.d.ts.map +1 -1
  97. package/dist/schema/optional.js +1 -0
  98. package/dist/schema/optional.js.map +1 -1
  99. package/dist/schema/params.d.ts +14 -10
  100. package/dist/schema/params.d.ts.map +1 -1
  101. package/dist/schema/params.js +87 -24
  102. package/dist/schema/params.js.map +1 -1
  103. package/dist/schema/payload.d.ts.map +1 -1
  104. package/dist/schema/payload.js +3 -3
  105. package/dist/schema/payload.js.map +1 -1
  106. package/dist/schema/record.d.ts +21 -19
  107. package/dist/schema/record.d.ts.map +1 -1
  108. package/dist/schema/record.js +22 -12
  109. package/dist/schema/record.js.map +1 -1
  110. package/dist/schema/ref.d.ts +1 -0
  111. package/dist/schema/ref.d.ts.map +1 -1
  112. package/dist/schema/ref.js +1 -0
  113. package/dist/schema/ref.js.map +1 -1
  114. package/dist/schema/regexp.d.ts +1 -0
  115. package/dist/schema/regexp.d.ts.map +1 -1
  116. package/dist/schema/regexp.js +2 -1
  117. package/dist/schema/regexp.js.map +1 -1
  118. package/dist/schema/string.d.ts +22 -6
  119. package/dist/schema/string.d.ts.map +1 -1
  120. package/dist/schema/string.js +16 -9
  121. package/dist/schema/string.js.map +1 -1
  122. package/dist/schema/token.d.ts +1 -0
  123. package/dist/schema/token.d.ts.map +1 -1
  124. package/dist/schema/token.js +2 -1
  125. package/dist/schema/token.js.map +1 -1
  126. package/dist/schema/typed-object.d.ts +20 -16
  127. package/dist/schema/typed-object.d.ts.map +1 -1
  128. package/dist/schema/typed-object.js +23 -13
  129. package/dist/schema/typed-object.js.map +1 -1
  130. package/dist/schema/typed-ref.d.ts +1 -0
  131. package/dist/schema/typed-ref.d.ts.map +1 -1
  132. package/dist/schema/typed-ref.js +1 -0
  133. package/dist/schema/typed-ref.js.map +1 -1
  134. package/dist/schema/typed-union.d.ts +1 -0
  135. package/dist/schema/typed-union.d.ts.map +1 -1
  136. package/dist/schema/typed-union.js +2 -1
  137. package/dist/schema/typed-union.js.map +1 -1
  138. package/dist/schema/union.d.ts +1 -0
  139. package/dist/schema/union.d.ts.map +1 -1
  140. package/dist/schema/union.js +2 -1
  141. package/dist/schema/union.js.map +1 -1
  142. package/dist/schema/unknown.d.ts +1 -0
  143. package/dist/schema/unknown.d.ts.map +1 -1
  144. package/dist/schema/unknown.js +1 -0
  145. package/dist/schema/unknown.js.map +1 -1
  146. package/dist/schema/with-default.d.ts +1 -0
  147. package/dist/schema/with-default.d.ts.map +1 -1
  148. package/dist/schema/with-default.js +1 -0
  149. package/dist/schema/with-default.js.map +1 -1
  150. package/dist/schema.d.ts +2 -1
  151. package/dist/schema.d.ts.map +1 -1
  152. package/dist/schema.js +2 -1
  153. package/dist/schema.js.map +1 -1
  154. package/dist/util/if-any.d.ts +2 -0
  155. package/dist/util/if-any.d.ts.map +1 -0
  156. package/dist/util/if-any.js +3 -0
  157. package/dist/util/if-any.js.map +1 -0
  158. package/package.json +3 -3
  159. package/src/core/schema.ts +76 -62
  160. package/src/core/string-format.ts +14 -17
  161. package/src/core/validation-error.ts +10 -10
  162. package/src/core/validation-issue.ts +3 -2
  163. package/src/core/validator.ts +32 -12
  164. package/src/helpers.test.ts +1 -1
  165. package/src/helpers.ts +53 -19
  166. package/src/schema/array.ts +3 -1
  167. package/src/schema/blob.ts +4 -1
  168. package/src/schema/boolean.ts +3 -1
  169. package/src/schema/bytes.ts +3 -1
  170. package/src/schema/cid.ts +3 -1
  171. package/src/schema/custom.ts +2 -0
  172. package/src/schema/dict.ts +3 -1
  173. package/src/schema/discriminated-union.ts +3 -1
  174. package/src/schema/enum.ts +2 -0
  175. package/src/schema/integer.ts +3 -1
  176. package/src/schema/intersection.ts +2 -0
  177. package/src/schema/{unknown-object.test.ts → lex-map.test.ts} +9 -9
  178. package/src/schema/lex-map.ts +63 -0
  179. package/src/schema/lex-value.test.ts +81 -0
  180. package/src/schema/lex-value.ts +86 -0
  181. package/src/schema/literal.ts +2 -0
  182. package/src/schema/never.ts +3 -1
  183. package/src/schema/null.ts +3 -1
  184. package/src/schema/nullable.ts +2 -0
  185. package/src/schema/object.ts +3 -1
  186. package/src/schema/optional.ts +2 -0
  187. package/src/schema/params.test.ts +98 -43
  188. package/src/schema/params.ts +136 -39
  189. package/src/schema/payload.test.ts +2 -2
  190. package/src/schema/payload.ts +3 -4
  191. package/src/schema/record.ts +38 -22
  192. package/src/schema/ref.ts +2 -0
  193. package/src/schema/regexp.ts +3 -1
  194. package/src/schema/string.test.ts +99 -2
  195. package/src/schema/string.ts +58 -15
  196. package/src/schema/token.ts +3 -1
  197. package/src/schema/typed-object.test.ts +38 -0
  198. package/src/schema/typed-object.ts +40 -24
  199. package/src/schema/typed-ref.ts +2 -0
  200. package/src/schema/typed-union.ts +3 -1
  201. package/src/schema/union.ts +4 -2
  202. package/src/schema/unknown.ts +2 -0
  203. package/src/schema/with-default.ts +2 -0
  204. package/src/schema.ts +2 -1
  205. package/src/util/if-any.ts +3 -0
  206. package/dist/schema/unknown-object.d.ts +0 -42
  207. package/dist/schema/unknown-object.d.ts.map +0 -1
  208. package/dist/schema/unknown-object.js +0 -50
  209. package/dist/schema/unknown-object.js.map +0 -1
  210. package/src/schema/unknown-object.ts +0 -53
@@ -1,3 +1,4 @@
1
+ import { lazyProperty } from '../util/lazy-property.js'
1
2
  import {
2
3
  InferInput,
3
4
  InferOutput,
@@ -11,13 +12,13 @@ import {
11
12
  * Options for parsing operations.
12
13
  * Excludes the `mode` option as it is implicitly set to `"parse"`.
13
14
  */
14
- type ParseOptions = Omit<ValidationOptions, 'mode'>
15
+ export type ParseOptions = Omit<ValidationOptions, 'mode'>
15
16
 
16
17
  /**
17
18
  * Options for validation operations.
18
19
  * Excludes the `mode` option as it is implicitly set to `"validate"`.
19
20
  */
20
- type ValidateOptions = Omit<ValidationOptions, 'mode'>
21
+ export type ValidateOptions = Omit<ValidationOptions, 'mode'>
21
22
 
22
23
  /**
23
24
  * Internal type structure for schema type inference.
@@ -58,7 +59,7 @@ export interface SchemaInternals<out TInput = unknown, out TOutput = TInput> {
58
59
  * class MySchema extends Schema<string> {
59
60
  * validateInContext(input: unknown, ctx: ValidationContext): ValidationResult {
60
61
  * if (typeof input !== 'string') {
61
- * return ctx.issueInvalidType(input, 'string')
62
+ * return ctx.issueUnexpectedType(input, 'string')
62
63
  * }
63
64
  * return ctx.success(input)
64
65
  * }
@@ -66,7 +67,7 @@ export interface SchemaInternals<out TInput = unknown, out TOutput = TInput> {
66
67
  *
67
68
  * const schema = new MySchema()
68
69
  * schema.assert('hello') // OK
69
- * schema.assert(123) // Throws ValidationError
70
+ * schema.assert(123) // Throws LexValidationError
70
71
  * schema.matches('hello') // true
71
72
  * schema.matches(123) // false
72
73
  * ```
@@ -88,6 +89,12 @@ export abstract class Schema<
88
89
  */
89
90
  declare readonly ['__lex']: TInternals
90
91
 
92
+ // Needed to discriminate multiple schema types when used in unions. Without
93
+ // this, Typescript could allow an EnumSchema<"foo" | "bar"> to be used where
94
+ // a StringSchema is expected, since they would both be structurally
95
+ // compatible.
96
+ abstract readonly type: string
97
+
91
98
  /**
92
99
  * Performs validation of the input value within a validation context.
93
100
  *
@@ -184,12 +191,12 @@ export abstract class Schema<
184
191
  *
185
192
  * Unlike {@link validate}, this method allows the schema to transform
186
193
  * the input value (e.g., applying default values, type coercion).
187
- * Throws a {@link ValidationError} if the input is invalid.
194
+ * Throws a {@link LexValidationError} if the input is invalid.
188
195
  *
189
196
  * @param input - The value to parse
190
197
  * @param options - Optional parsing configuration
191
198
  * @returns The parsed and potentially transformed value
192
- * @throws {ValidationError} If the input fails validation
199
+ * @throws {LexValidationError} If the input fails validation
193
200
  *
194
201
  * @example
195
202
  * ```typescript
@@ -238,13 +245,13 @@ export abstract class Schema<
238
245
  *
239
246
  * Unlike {@link parse}, this method requires the input to exactly match
240
247
  * the schema without any transformations (no defaults applied, no coercion).
241
- * Throws a {@link ValidationError} if the input is invalid or would require transformation.
248
+ * Throws a {@link LexValidationError} if the input is invalid or would require transformation.
242
249
  *
243
250
  * @typeParam I - The input type (preserved in the return type)
244
251
  * @param input - The value to validate
245
252
  * @param options - Optional validation configuration
246
253
  * @returns The validated input with narrowed type
247
- * @throws {ValidationError} If the input fails validation or requires transformation
254
+ * @throws {LexValidationError} If the input fails validation or requires transformation
248
255
  *
249
256
  * @example
250
257
  * ```typescript
@@ -291,104 +298,111 @@ export abstract class Schema<
291
298
 
292
299
  // @NOTE Dollar-prefixed aliases
293
300
  //
294
- // The built lexicons namespaces export utility functions that allow accessing
295
- // the schema's methods without the need to specify ".main." as part of the
296
- // namespace. This way, a utility for a particular record type can be called
297
- // like "app.bsky.feed.post.<utility>()" instead of
298
- // "app.bsky.feed.post.main.<utility>()". Because those utilities could
299
- // conflict with other schemas (e.g. if there is a lexicon definition at
300
- // "#<utility>"), those exported utilities will be prefixed with "$". In order
301
- // to be able to consistently call the utilities, when using the "main" and
302
- // non "main" definitions, we also expose the same methods with a "$" prefix.
303
- // Thanks to this, both of the following call will be possible:
301
+ // The `lex-builder` lib generates namespaced utility functions that allow
302
+ // accessing the schema's methods without the need to specify the ".main."
303
+ // part of the namespace. This allows utilities for a particular record type
304
+ // to be called like "app.bsky.feed.post.<utility>()" instead of
305
+ // "app.bsky.feed.post.main.<utility>()".
306
+ //
307
+ // Because those utilities could conflict with other schemas (e.g. if there is
308
+ // a lexicon definition with the same name as the "<utility>"), those exported
309
+ // utilities will be prefixed with "$".
310
+ //
311
+ // Similarly, since those utilities are defined as simple "const", they are
312
+ // also bound (using JS's .bind) to the schema instance, so that they can be
313
+ // used without worrying about the context (e.g. "app.bsky.feed.post.$parse()"
314
+ // will work regardless of how it is imported or called).
315
+ //
316
+ // In order to provide the same functionalities for non-main definitions, we
317
+ // also define those aliases directly on the schema instance, so that they can
318
+ // be used in the same way as the utilities generated by "lex-builder". For
319
+ // example, if there is a non-main definition "app.bsky.feed.defs.postView",
320
+ // it will also be possible to call "app.bsky.feed.defs.postView.$parse()".
321
+ //
322
+ // These methods are also "bound" to the instance so that they can be used
323
+ // exactly like the utilities generated by "lex-builder", without worrying
324
+ // about the context.
325
+ //
326
+ // There are two ways we could "bind" those methods to the instance:
327
+ // 1. Define them as getters that return the bound method (e.g. get $parse() {
328
+ // return this.parse.bind(this) })
329
+ // 2. Define them as properties that are initialized in the constructor (e.g.
330
+ // this.$parse = this.parse.bind(this))
304
331
  //
305
- // - "app.bsky.feed.post.$parse(...)" // calls a utility function created by "lex build"
306
- // - "app.bsky.feed.defs.postView.$parse(...)" // uses the alias defined below on the schema instance
332
+ // Since a **lot** of those methods would end-up being created in systems that
333
+ // contains many schemas (e.g. the appview), we choose the first approach
334
+ // (getters) in order to avoid the overhead of creating all those bound
335
+ // functions upfront when instantiating the schemas.
307
336
 
308
337
  /**
309
- * Alias for {@link assert} with `$` prefix for namespace compatibility.
310
- *
338
+ * Bound alias for {@link assert} for compatibility with generated utilities.
311
339
  * @see {@link assert}
312
340
  */
313
- $assert(input: unknown): asserts input is InferInput<this> {
314
- return this.assert(input)
341
+ get $assert(): typeof this.assert {
342
+ return lazyProperty(this, '$assert', this.assert.bind(this))
315
343
  }
316
344
 
317
345
  /**
318
- * Alias for {@link check} with `$` prefix for namespace compatibility.
319
- *
346
+ * Bound alias for {@link check} for compatibility with generated utilities.
320
347
  * @see {@link check}
321
348
  */
322
- $check(input: unknown): void {
323
- return this.check(input)
349
+ get $check(): typeof this.check {
350
+ return lazyProperty(this, '$check', this.check.bind(this))
324
351
  }
325
352
 
326
353
  /**
327
- * Alias for {@link cast} with `$` prefix for namespace compatibility.
328
- *
354
+ * Bound alias for {@link cast} for compatibility with generated utilities.
329
355
  * @see {@link cast}
330
356
  */
331
- $cast<I>(input: I): I & InferInput<this> {
332
- return this.cast(input)
357
+ get $cast(): typeof this.cast {
358
+ return lazyProperty(this, '$cast', this.cast.bind(this))
333
359
  }
334
360
 
335
361
  /**
336
- * Alias for {@link matches} with `$` prefix for namespace compatibility.
337
- *
362
+ * Bound alias for {@link matches} for compatibility with generated utilities.
338
363
  * @see {@link matches}
339
364
  */
340
- $matches(input: unknown): input is InferInput<this> {
341
- return this.matches(input)
365
+ get $matches(): typeof this.matches {
366
+ return lazyProperty(this, '$matches', this.matches.bind(this))
342
367
  }
343
368
 
344
369
  /**
345
- * Alias for {@link ifMatches} with `$` prefix for namespace compatibility.
346
- *
370
+ * Bound alias for {@link ifMatches} for compatibility with generated utilities.
347
371
  * @see {@link ifMatches}
348
372
  */
349
- $ifMatches<I>(input: I): (I & InferInput<this>) | undefined {
350
- return this.ifMatches(input)
373
+ get $ifMatches(): typeof this.ifMatches {
374
+ return lazyProperty(this, '$ifMatches', this.ifMatches.bind(this))
351
375
  }
352
376
 
353
377
  /**
354
- * Alias for {@link parse} with `$` prefix for namespace compatibility.
355
- *
378
+ * Bound alias for {@link parse} for compatibility with generated utilities.
356
379
  * @see {@link parse}
357
380
  */
358
- $parse(input: unknown, options?: ValidateOptions): InferOutput<this> {
359
- return this.parse(input, options)
381
+ get $parse(): typeof this.parse {
382
+ return lazyProperty(this, '$parse', this.parse.bind(this))
360
383
  }
361
384
 
362
385
  /**
363
- * Alias for {@link safeParse} with `$` prefix for namespace compatibility.
364
- *
386
+ * Bound alias for {@link safeParse} for compatibility with generated utilities.
365
387
  * @see {@link safeParse}
366
388
  */
367
- $safeParse(
368
- input: unknown,
369
- options?: ValidateOptions,
370
- ): ValidationResult<InferOutput<this>> {
371
- return this.safeParse(input, options)
389
+ get $safeParse(): typeof this.safeParse {
390
+ return lazyProperty(this, '$safeParse', this.safeParse.bind(this))
372
391
  }
373
392
 
374
393
  /**
375
- * Alias for {@link validate} with `$` prefix for namespace compatibility.
376
- *
394
+ * Bound alias for {@link validate} for compatibility with generated utilities.
377
395
  * @see {@link validate}
378
396
  */
379
- $validate<I>(input: I, options?: ValidateOptions): I & InferInput<this> {
380
- return this.validate(input, options)
397
+ get $validate(): typeof this.validate {
398
+ return lazyProperty(this, '$validate', this.validate.bind(this))
381
399
  }
382
400
 
383
401
  /**
384
- * Alias for {@link safeValidate} with `$` prefix for namespace compatibility.
385
- *
402
+ * Bound alias for {@link safeValidate} for compatibility with generated utilities.
386
403
  * @see {@link safeValidate}
387
404
  */
388
- $safeValidate<I>(
389
- input: I,
390
- options?: ValidateOptions,
391
- ): ValidationResult<I & InferInput<this>> {
392
- return this.safeValidate(input, options)
405
+ get $safeValidate(): typeof this.safeValidate {
406
+ return lazyProperty(this, '$safeValidate', this.safeValidate.bind(this))
393
407
  }
394
408
  }
@@ -9,9 +9,9 @@ import {
9
9
  RecordKeyString,
10
10
  TidString,
11
11
  UriString,
12
+ isDatetimeString,
12
13
  isValidAtIdentifier as isValidAtId,
13
14
  isValidAtUri,
14
- isValidDatetime,
15
15
  isValidDid,
16
16
  isValidHandle,
17
17
  isValidLanguage,
@@ -26,6 +26,19 @@ import { CheckFn } from '../util/assertion-util.js'
26
26
  // Individual string format types and type guards
27
27
  // -----------------------------------------------------------------------------
28
28
 
29
+ // Re-exporting from @atproto/syntax without modification to preserve types and
30
+ // documentation for types and utilities that are already well-defined there.
31
+ // @TODO rework other string formats in @atproto/syntax to follow this pattern
32
+ // and re-export here, e.g. language tags, NSIDs, record keys, etc.
33
+ export {
34
+ type DatetimeString,
35
+ asDatetimeString,
36
+ currentDatetimeString,
37
+ ifDatetimeString,
38
+ isDatetimeString,
39
+ toDatetimeString,
40
+ } from '@atproto/syntax'
41
+
29
42
  /**
30
43
  * Type guard that checks if a value is a valid AT identifier (DID or handle).
31
44
  *
@@ -74,22 +87,6 @@ export const isCidString = ((v) => validateCidString(v)) as CheckFn<CidString>
74
87
  */
75
88
  export type CidString = string
76
89
 
77
- /**
78
- * Type guard that checks if a value is a valid datetime string.
79
- *
80
- * @param value - The value to check
81
- * @returns `true` if the value is a valid datetime string
82
- */
83
- export const isDatetimeString: CheckFn<DatetimeString> = isValidDatetime
84
- export type {
85
- /**
86
- * An ISO 8601 datetime string.
87
- *
88
- * @example `"2024-01-15T12:30:00.000Z"`
89
- */
90
- DatetimeString,
91
- }
92
-
93
90
  /**
94
91
  * Type guard that checks if a value is a valid DID string.
95
92
  *
@@ -19,7 +19,7 @@ import {
19
19
  *
20
20
  * @example
21
21
  * ```typescript
22
- * const error = new ValidationError([
22
+ * const error = new LexValidationError([
23
23
  * new IssueInvalidType(['user', 'age'], 'hello', ['number'])
24
24
  * ])
25
25
  * console.log(error.message)
@@ -30,8 +30,8 @@ import {
30
30
  * // { error: 'InvalidRequest', message: '...', issues: [...] }
31
31
  * ```
32
32
  */
33
- export class ValidationError extends LexError {
34
- name = 'ValidationError'
33
+ export class LexValidationError extends LexError<'InvalidRequest'> {
34
+ name = 'LexValidationError'
35
35
 
36
36
  /**
37
37
  * The list of validation issues that caused this error.
@@ -59,9 +59,9 @@ export class ValidationError extends LexError {
59
59
  /**
60
60
  * Converts the error to a JSON-serializable object.
61
61
  *
62
- * @returns An object containing the error details and all issues in JSON format
62
+ * @returns An object containing the error details and issues details
63
63
  */
64
- toJSON() {
64
+ override toJSON() {
65
65
  return {
66
66
  ...super.toJSON(),
67
67
  issues: this.issues.map((issue) => issue.toJSON()),
@@ -81,23 +81,23 @@ export class ValidationError extends LexError {
81
81
  * ```typescript
82
82
  * const failures = schemas.map(s => s.safeValidate(data)).filter(r => !r.success)
83
83
  * if (failures.length === schemas.length) {
84
- * throw ValidationError.fromFailures(failures)
84
+ * throw LexValidationError.fromFailures(failures)
85
85
  * }
86
86
  * ```
87
87
  */
88
88
  static fromFailures(
89
- failures: ResultFailure<ValidationError>[],
90
- ): ValidationError {
89
+ failures: readonly ResultFailure<LexValidationError>[],
90
+ ): LexValidationError {
91
91
  if (failures.length === 1) return failureReason(failures[0])
92
92
  const issues = failures.flatMap(extractFailureIssues)
93
- return new ValidationError(issues, {
93
+ return new LexValidationError(issues, {
94
94
  // Keep the original errors as the cause chain
95
95
  cause: failures.map(failureReason),
96
96
  })
97
97
  }
98
98
  }
99
99
 
100
- function extractFailureIssues(result: ResultFailure<ValidationError>) {
100
+ function extractFailureIssues(result: ResultFailure<LexValidationError>) {
101
101
  return result.reason.issues
102
102
  }
103
103
 
@@ -1,4 +1,4 @@
1
- import { ifCid, isPlainObject } from '@atproto/lex-data'
1
+ import { ifCid, isLegacyBlobRef, isPlainObject } from '@atproto/lex-data'
2
2
  import { PropertyKey } from './property-key.js'
3
3
 
4
4
  /**
@@ -260,7 +260,7 @@ export class IssueTooSmall extends Issue {
260
260
 
261
261
  function stringifyExpectedType(expected: string): string {
262
262
  if (expected === '$typed') {
263
- return 'an object or record which includes a "$type" property'
263
+ return 'an object which includes the "$type" property'
264
264
  }
265
265
  return expected
266
266
  }
@@ -295,6 +295,7 @@ function stringifyType(value: unknown): string {
295
295
  if (value === null) return 'null'
296
296
  if (Array.isArray(value)) return 'array'
297
297
  if (ifCid(value)) return 'cid'
298
+ if (isLegacyBlobRef(value)) return 'legacy-blob'
298
299
  if (value instanceof Date) return 'date'
299
300
  if (value instanceof RegExp) return 'regexp'
300
301
  if (value instanceof Map) return 'map'
@@ -1,6 +1,6 @@
1
1
  import { PropertyKey } from './property-key.js'
2
2
  import { ResultFailure, ResultSuccess, failure, success } from './result.js'
3
- import { ValidationError } from './validation-error.js'
3
+ import { LexValidationError } from './validation-error.js'
4
4
  import {
5
5
  Issue,
6
6
  IssueInvalidFormat,
@@ -20,16 +20,16 @@ import {
20
20
  export type ValidationSuccess<Value = unknown> = ResultSuccess<Value>
21
21
 
22
22
  /**
23
- * Represents a failed validation result containing a {@link ValidationError}.
23
+ * Represents a failed validation result containing a {@link LexValidationError}.
24
24
  */
25
- export type ValidationFailure = ResultFailure<ValidationError>
25
+ export type ValidationFailure = ResultFailure<LexValidationError>
26
26
 
27
27
  /**
28
28
  * Discriminated union representing the outcome of a validation operation.
29
29
  *
30
30
  * Check the `success` property to determine if validation passed or failed:
31
31
  * - If `success` is `true`, the `value` property contains the validated data
32
- * - If `success` is `false`, the `reason` property contains the {@link ValidationError}
32
+ * - If `success` is `false`, the `reason` property contains the {@link LexValidationError}
33
33
  *
34
34
  * @typeParam Value - The type of the validated value on success
35
35
  *
@@ -39,7 +39,7 @@ export type ValidationFailure = ResultFailure<ValidationError>
39
39
  * if (result.success) {
40
40
  * // result.value is string
41
41
  * } else {
42
- * // result.reason is ValidationError
42
+ * // result.reason is LexValidationError
43
43
  * }
44
44
  * ```
45
45
  */
@@ -192,7 +192,7 @@ export type ValidationOptions = {
192
192
  * class MyValidator implements Validator {
193
193
  * validateInContext(input: unknown, ctx: ValidationContext): ValidationResult {
194
194
  * if (typeof input !== 'string') {
195
- * return ctx.issueInvalidType(input, 'string')
195
+ * return ctx.issueUnexpectedType(input, 'string')
196
196
  * }
197
197
  * return ctx.success(input)
198
198
  * }
@@ -326,7 +326,7 @@ export class ValidationContext {
326
326
  if (this.issues.length > 0) {
327
327
  // Validator returned a success but issues were added via the context.
328
328
  // This means the overall validation failed.
329
- return failure(new ValidationError(Array.from(this.issues)))
329
+ return failure(new LexValidationError(Array.from(this.issues)))
330
330
  }
331
331
 
332
332
  if (this.options.mode !== 'parse' && !Object.is(result.value, input)) {
@@ -341,7 +341,7 @@ export class ValidationContext {
341
341
  // another.
342
342
 
343
343
  // This if block comes before the next one because 'this.issues' will
344
- // end-up being appended to the returned ValidationError (see the
344
+ // end-up being appended to the returned LexValidationError (see the
345
345
  // "failure" method below), resulting in a more complete error report.
346
346
  return this.issueInvalidValue(input, [result.value])
347
347
  }
@@ -377,6 +377,15 @@ export class ValidationContext {
377
377
  K extends PropertyKey & keyof I,
378
378
  V extends Validator,
379
379
  >(input: I, key: K, validator: V): ValidationResult<InferInput<V>> {
380
+ // @NOTE we could add support for recursive schemas by keeping track of
381
+ // "parent" objects in the context and checking for circular references
382
+ // here. This would allow us to validate recursive structures without
383
+ // hitting maximum call stack errors, and would also allow us to provide
384
+ // better error messages for circular reference issues. However, this is not
385
+ // a priority at the moment as recursive structures are not supported in
386
+ // the context of AT Protocol lexicons, and we can always add this in the
387
+ // future if needed.
388
+
380
389
  // Instead of creating a new context, we just push/pop the path segment.
381
390
  this.currentPath.push(key)
382
391
  try {
@@ -416,7 +425,7 @@ export class ValidationContext {
416
425
  * @param reason - The validation error
417
426
  * @returns A failed validation result
418
427
  */
419
- failure(reason: ValidationError): ValidationFailure {
428
+ failure(reason: LexValidationError): ValidationFailure {
420
429
  return failure(reason)
421
430
  }
422
431
 
@@ -429,7 +438,7 @@ export class ValidationContext {
429
438
  * @returns A failed validation result
430
439
  */
431
440
  issue(issue: Issue) {
432
- return this.failure(new ValidationError([...this.issues, issue]))
441
+ return this.failure(new LexValidationError([...this.issues, issue]))
433
442
  }
434
443
 
435
444
  /**
@@ -443,6 +452,17 @@ export class ValidationContext {
443
452
  return this.issue(new IssueInvalidValue(this.path, input, values))
444
453
  }
445
454
 
455
+ /**
456
+ * Creates a failure for an invalid type.
457
+ *
458
+ * @param input - The actual value that was received
459
+ * @param expected - An array of expected type names
460
+ * @returns A failed validation result with an invalid type issue
461
+ */
462
+ issueInvalidType(input: unknown, expected: readonly string[]) {
463
+ return this.issue(new IssueInvalidType(this.path, input, expected))
464
+ }
465
+
446
466
  /**
447
467
  * Creates a failure for an invalid type.
448
468
  *
@@ -450,8 +470,8 @@ export class ValidationContext {
450
470
  * @param expected - The expected type name
451
471
  * @returns A failed validation result with an invalid type issue
452
472
  */
453
- issueInvalidType(input: unknown, expected: string) {
454
- return this.issue(new IssueInvalidType(this.path, input, [expected]))
473
+ issueUnexpectedType(input: unknown, expected: string) {
474
+ return this.issueInvalidType(input, [expected])
455
475
  }
456
476
 
457
477
  /**
@@ -61,7 +61,7 @@ describe('InferMethodParams', () => {
61
61
  l.params({
62
62
  cursor: l.optional(l.integer()),
63
63
  }),
64
- l.unknownObject(),
64
+ l.lexMap(),
65
65
  )
66
66
 
67
67
  type Params = l.InferMethodParams<typeof subscription>
package/src/helpers.ts CHANGED
@@ -26,47 +26,81 @@ export function getMain<T extends object>(ns: Main<T>): T {
26
26
  * `ReadableStream`, etc.). This type is a placeholder to represent binary data
27
27
  * when not explicitly provided.
28
28
  */
29
- type BinaryData = Restricted<'Binary data'>
29
+ export type BinaryData = Restricted<'Binary data'>
30
30
 
31
- export type InferMethodParams<M extends Procedure | Query | Subscription> =
32
- InferOutput<M['parameters']>
31
+ export type InferMethodParams<
32
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
33
+ > =
34
+ M extends Procedure<any, infer TParams, any, any, any>
35
+ ? InferOutput<TParams>
36
+ : M extends Query<any, infer TParams, any, any>
37
+ ? InferOutput<TParams>
38
+ : M extends Subscription<any, infer TParams, any, any>
39
+ ? InferOutput<TParams>
40
+ : never
33
41
 
34
42
  export type InferMethodInput<
35
- M extends Procedure | Query | Subscription,
43
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
36
44
  B = BinaryData,
37
- > = M extends Procedure ? InferPayload<M['input'], B> : undefined
45
+ > =
46
+ M extends Procedure<any, any, infer TInput, any, any>
47
+ ? InferPayload<TInput, B>
48
+ : undefined
38
49
 
39
50
  export type InferMethodInputBody<
40
- M extends Procedure | Query | Subscription,
51
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
41
52
  B = BinaryData,
42
- > = M extends Procedure ? InferPayloadBody<M['input'], B> : undefined
53
+ > =
54
+ M extends Procedure<any, any, infer TInput, any, any>
55
+ ? InferPayloadBody<TInput, B>
56
+ : undefined
43
57
 
44
58
  export type InferMethodInputEncoding<
45
- M extends Procedure | Query | Subscription,
46
- > = M extends Procedure ? InferPayloadEncoding<M['input']> : undefined
59
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
60
+ > =
61
+ M extends Procedure<any, any, infer TInput, any, any>
62
+ ? InferPayloadEncoding<TInput>
63
+ : undefined
47
64
 
48
65
  export type InferMethodOutput<
49
- M extends Procedure | Query | Subscription,
66
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
50
67
  B = BinaryData,
51
- > = M extends Procedure | Query ? InferPayload<M['output'], B> : undefined
68
+ > =
69
+ M extends Procedure<any, any, any, infer TOutput, any>
70
+ ? InferPayload<TOutput, B>
71
+ : M extends Query<any, any, infer TOutput, any>
72
+ ? InferPayload<TOutput, B>
73
+ : undefined
52
74
 
53
75
  export type InferMethodOutputBody<
54
- M extends Procedure | Query | Subscription,
76
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
55
77
  B = BinaryData,
56
- > = M extends Procedure | Query ? InferPayloadBody<M['output'], B> : undefined
78
+ > =
79
+ M extends Procedure<any, any, any, infer TOutput, any>
80
+ ? InferPayloadBody<TOutput, B>
81
+ : M extends Query<any, any, infer TOutput, any>
82
+ ? InferPayloadBody<TOutput, B>
83
+ : undefined
57
84
 
58
85
  export type InferMethodOutputEncoding<
59
- M extends Procedure | Query | Subscription,
60
- > = M extends Procedure | Query ? InferPayloadEncoding<M['output']> : undefined
86
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
87
+ > =
88
+ M extends Procedure<any, any, any, infer TOutput, any>
89
+ ? InferPayloadEncoding<TOutput>
90
+ : M extends Query<any, any, infer TOutput, any>
91
+ ? InferPayloadEncoding<TOutput>
92
+ : undefined
61
93
 
62
94
  export type InferMethodMessage<
63
- //
64
- M extends Subscription,
65
- > = M extends Subscription ? InferOutput<M['message']> : undefined
95
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
96
+ > =
97
+ M extends Subscription<any, any, infer TMessage, any>
98
+ ? InferOutput<TMessage>
99
+ : undefined
66
100
 
67
101
  export type InferMethodError<
68
102
  //
69
- M extends Procedure | Query | Subscription,
103
+ M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
70
104
  > = M extends { errors: readonly (infer E extends string)[] } ? E : never
71
105
 
72
106
  export const lexErrorDataSchema = object({
@@ -36,6 +36,8 @@ export class ArraySchema<const TItem extends Validator> extends Schema<
36
36
  Array<InferInput<TItem>>,
37
37
  Array<InferOutput<TItem>>
38
38
  > {
39
+ readonly type = 'array' as const
40
+
39
41
  constructor(
40
42
  readonly validator: TItem,
41
43
  readonly options: ArraySchemaOptions = {},
@@ -45,7 +47,7 @@ export class ArraySchema<const TItem extends Validator> extends Schema<
45
47
 
46
48
  validateInContext(input: unknown, ctx: ValidationContext) {
47
49
  if (!Array.isArray(input)) {
48
- return ctx.issueInvalidType(input, 'array')
50
+ return ctx.issueUnexpectedType(input, 'array')
49
51
  }
50
52
 
51
53
  const { minLength, maxLength } = this.options
@@ -32,6 +32,7 @@ export type BlobSchemaOptions = BlobRefCheckOptions & {
32
32
  }
33
33
 
34
34
  export type { BlobRef, LegacyBlobRef }
35
+ export { isBlobRef, isLegacyBlobRef }
35
36
 
36
37
  /**
37
38
  * Schema for validating blob references in AT Protocol.
@@ -53,6 +54,8 @@ export class BlobSchema<
53
54
  > extends Schema<
54
55
  TOptions extends { allowLegacy: true } ? BlobRef | LegacyBlobRef : BlobRef
55
56
  > {
57
+ readonly type = 'blob' as const
58
+
56
59
  constructor(readonly options?: TOptions) {
57
60
  super()
58
61
  }
@@ -68,7 +71,7 @@ export class BlobSchema<
68
71
  : null
69
72
 
70
73
  if (!blob) {
71
- return ctx.issueInvalidType(input, 'blob')
74
+ return ctx.issueUnexpectedType(input, 'blob')
72
75
  }
73
76
 
74
77
  const accept = this.options?.accept