@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.
- package/CHANGELOG.md +53 -0
- package/dist/core/schema.d.ts +27 -36
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +68 -54
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +1 -14
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +12 -9
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +5 -5
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +8 -8
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.js +3 -1
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +16 -8
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +24 -6
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +10 -11
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +1 -0
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +2 -1
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +4 -2
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +5 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +1 -0
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +2 -1
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +1 -0
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +2 -1
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +1 -0
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +2 -1
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +1 -0
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +1 -0
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +1 -0
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +2 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +1 -0
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +2 -1
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +1 -0
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +1 -0
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +1 -0
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +2 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +1 -0
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +1 -0
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/lex-map.d.ts +37 -0
- package/dist/schema/lex-map.d.ts.map +1 -0
- package/dist/schema/lex-map.js +60 -0
- package/dist/schema/lex-map.js.map +1 -0
- package/dist/schema/lex-value.d.ts +35 -0
- package/dist/schema/lex-value.d.ts.map +1 -0
- package/dist/schema/lex-value.js +87 -0
- package/dist/schema/lex-value.js.map +1 -0
- package/dist/schema/literal.d.ts +1 -0
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +1 -0
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +1 -0
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +2 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +1 -0
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +2 -1
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +1 -0
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +1 -0
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +1 -0
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +2 -1
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +1 -0
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +1 -0
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +14 -10
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +87 -24
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +3 -3
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +21 -19
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +22 -12
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +1 -0
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -0
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/regexp.d.ts +1 -0
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +2 -1
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +22 -6
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +16 -9
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/token.d.ts +1 -0
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +2 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +20 -16
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +23 -13
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +1 -0
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +1 -0
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +1 -0
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +2 -1
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +1 -0
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +2 -1
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown.d.ts +1 -0
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +1 -0
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +1 -0
- package/dist/schema/with-default.d.ts.map +1 -1
- package/dist/schema/with-default.js +1 -0
- package/dist/schema/with-default.js.map +1 -1
- package/dist/schema.d.ts +2 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/if-any.d.ts +2 -0
- package/dist/util/if-any.d.ts.map +1 -0
- package/dist/util/if-any.js +3 -0
- package/dist/util/if-any.js.map +1 -0
- package/package.json +3 -3
- package/src/core/schema.ts +76 -62
- package/src/core/string-format.ts +14 -17
- package/src/core/validation-error.ts +10 -10
- package/src/core/validation-issue.ts +3 -2
- package/src/core/validator.ts +32 -12
- package/src/helpers.test.ts +1 -1
- package/src/helpers.ts +53 -19
- package/src/schema/array.ts +3 -1
- package/src/schema/blob.ts +4 -1
- package/src/schema/boolean.ts +3 -1
- package/src/schema/bytes.ts +3 -1
- package/src/schema/cid.ts +3 -1
- package/src/schema/custom.ts +2 -0
- package/src/schema/dict.ts +3 -1
- package/src/schema/discriminated-union.ts +3 -1
- package/src/schema/enum.ts +2 -0
- package/src/schema/integer.ts +3 -1
- package/src/schema/intersection.ts +2 -0
- package/src/schema/{unknown-object.test.ts → lex-map.test.ts} +9 -9
- package/src/schema/lex-map.ts +63 -0
- package/src/schema/lex-value.test.ts +81 -0
- package/src/schema/lex-value.ts +86 -0
- package/src/schema/literal.ts +2 -0
- package/src/schema/never.ts +3 -1
- package/src/schema/null.ts +3 -1
- package/src/schema/nullable.ts +2 -0
- package/src/schema/object.ts +3 -1
- package/src/schema/optional.ts +2 -0
- package/src/schema/params.test.ts +98 -43
- package/src/schema/params.ts +136 -39
- package/src/schema/payload.test.ts +2 -2
- package/src/schema/payload.ts +3 -4
- package/src/schema/record.ts +38 -22
- package/src/schema/ref.ts +2 -0
- package/src/schema/regexp.ts +3 -1
- package/src/schema/string.test.ts +99 -2
- package/src/schema/string.ts +58 -15
- package/src/schema/token.ts +3 -1
- package/src/schema/typed-object.test.ts +38 -0
- package/src/schema/typed-object.ts +40 -24
- package/src/schema/typed-ref.ts +2 -0
- package/src/schema/typed-union.ts +3 -1
- package/src/schema/union.ts +4 -2
- package/src/schema/unknown.ts +2 -0
- package/src/schema/with-default.ts +2 -0
- package/src/schema.ts +2 -1
- package/src/util/if-any.ts +3 -0
- package/dist/schema/unknown-object.d.ts +0 -42
- package/dist/schema/unknown-object.d.ts.map +0 -1
- package/dist/schema/unknown-object.js +0 -50
- package/dist/schema/unknown-object.js.map +0 -1
- package/src/schema/unknown-object.ts +0 -53
package/src/core/schema.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
295
|
-
// the schema's methods without the need to specify ".main."
|
|
296
|
-
// namespace. This
|
|
297
|
-
// like "app.bsky.feed.post.<utility>()" instead of
|
|
298
|
-
// "app.bsky.feed.post.main.<utility>()".
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
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
|
-
//
|
|
306
|
-
//
|
|
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
|
-
*
|
|
310
|
-
*
|
|
338
|
+
* Bound alias for {@link assert} for compatibility with generated utilities.
|
|
311
339
|
* @see {@link assert}
|
|
312
340
|
*/
|
|
313
|
-
$assert(
|
|
314
|
-
return this.assert(
|
|
341
|
+
get $assert(): typeof this.assert {
|
|
342
|
+
return lazyProperty(this, '$assert', this.assert.bind(this))
|
|
315
343
|
}
|
|
316
344
|
|
|
317
345
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
346
|
+
* Bound alias for {@link check} for compatibility with generated utilities.
|
|
320
347
|
* @see {@link check}
|
|
321
348
|
*/
|
|
322
|
-
$check(
|
|
323
|
-
return this.check(
|
|
349
|
+
get $check(): typeof this.check {
|
|
350
|
+
return lazyProperty(this, '$check', this.check.bind(this))
|
|
324
351
|
}
|
|
325
352
|
|
|
326
353
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
354
|
+
* Bound alias for {@link cast} for compatibility with generated utilities.
|
|
329
355
|
* @see {@link cast}
|
|
330
356
|
*/
|
|
331
|
-
$cast
|
|
332
|
-
return this.cast(
|
|
357
|
+
get $cast(): typeof this.cast {
|
|
358
|
+
return lazyProperty(this, '$cast', this.cast.bind(this))
|
|
333
359
|
}
|
|
334
360
|
|
|
335
361
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
362
|
+
* Bound alias for {@link matches} for compatibility with generated utilities.
|
|
338
363
|
* @see {@link matches}
|
|
339
364
|
*/
|
|
340
|
-
$matches(
|
|
341
|
-
return this.matches(
|
|
365
|
+
get $matches(): typeof this.matches {
|
|
366
|
+
return lazyProperty(this, '$matches', this.matches.bind(this))
|
|
342
367
|
}
|
|
343
368
|
|
|
344
369
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
370
|
+
* Bound alias for {@link ifMatches} for compatibility with generated utilities.
|
|
347
371
|
* @see {@link ifMatches}
|
|
348
372
|
*/
|
|
349
|
-
$ifMatches
|
|
350
|
-
return this.ifMatches(
|
|
373
|
+
get $ifMatches(): typeof this.ifMatches {
|
|
374
|
+
return lazyProperty(this, '$ifMatches', this.ifMatches.bind(this))
|
|
351
375
|
}
|
|
352
376
|
|
|
353
377
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
378
|
+
* Bound alias for {@link parse} for compatibility with generated utilities.
|
|
356
379
|
* @see {@link parse}
|
|
357
380
|
*/
|
|
358
|
-
$parse(
|
|
359
|
-
return this.parse(
|
|
381
|
+
get $parse(): typeof this.parse {
|
|
382
|
+
return lazyProperty(this, '$parse', this.parse.bind(this))
|
|
360
383
|
}
|
|
361
384
|
|
|
362
385
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
386
|
+
* Bound alias for {@link safeParse} for compatibility with generated utilities.
|
|
365
387
|
* @see {@link safeParse}
|
|
366
388
|
*/
|
|
367
|
-
$safeParse(
|
|
368
|
-
|
|
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
|
-
*
|
|
376
|
-
*
|
|
394
|
+
* Bound alias for {@link validate} for compatibility with generated utilities.
|
|
377
395
|
* @see {@link validate}
|
|
378
396
|
*/
|
|
379
|
-
$validate
|
|
380
|
-
return this.validate(
|
|
397
|
+
get $validate(): typeof this.validate {
|
|
398
|
+
return lazyProperty(this, '$validate', this.validate.bind(this))
|
|
381
399
|
}
|
|
382
400
|
|
|
383
401
|
/**
|
|
384
|
-
*
|
|
385
|
-
*
|
|
402
|
+
* Bound alias for {@link safeValidate} for compatibility with generated utilities.
|
|
386
403
|
* @see {@link safeValidate}
|
|
387
404
|
*/
|
|
388
|
-
$safeValidate
|
|
389
|
-
|
|
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
|
|
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
|
|
34
|
-
name = '
|
|
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
|
|
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
|
|
84
|
+
* throw LexValidationError.fromFailures(failures)
|
|
85
85
|
* }
|
|
86
86
|
* ```
|
|
87
87
|
*/
|
|
88
88
|
static fromFailures(
|
|
89
|
-
failures: ResultFailure<
|
|
90
|
-
):
|
|
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
|
|
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<
|
|
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
|
|
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'
|
package/src/core/validator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PropertyKey } from './property-key.js'
|
|
2
2
|
import { ResultFailure, ResultSuccess, failure, success } from './result.js'
|
|
3
|
-
import {
|
|
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
|
|
23
|
+
* Represents a failed validation result containing a {@link LexValidationError}.
|
|
24
24
|
*/
|
|
25
|
-
export type ValidationFailure = ResultFailure<
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
454
|
-
return this.
|
|
473
|
+
issueUnexpectedType(input: unknown, expected: string) {
|
|
474
|
+
return this.issueInvalidType(input, [expected])
|
|
455
475
|
}
|
|
456
476
|
|
|
457
477
|
/**
|
package/src/helpers.test.ts
CHANGED
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<
|
|
32
|
-
|
|
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
|
-
> =
|
|
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
|
-
> =
|
|
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
|
-
> =
|
|
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
|
-
> =
|
|
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
|
-
> =
|
|
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
|
-
> =
|
|
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
|
-
|
|
65
|
-
|
|
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({
|
package/src/schema/array.ts
CHANGED
|
@@ -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.
|
|
50
|
+
return ctx.issueUnexpectedType(input, 'array')
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const { minLength, maxLength } = this.options
|
package/src/schema/blob.ts
CHANGED
|
@@ -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.
|
|
74
|
+
return ctx.issueUnexpectedType(input, 'blob')
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
const accept = this.options?.accept
|