@atproto/lex-schema 0.0.13 → 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 (42) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/core/schema.d.ts +23 -32
  3. package/dist/core/schema.d.ts.map +1 -1
  4. package/dist/core/schema.js +67 -53
  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/validator.d.ts +6 -6
  15. package/dist/core/validator.d.ts.map +1 -1
  16. package/dist/core/validator.js +3 -3
  17. package/dist/core/validator.js.map +1 -1
  18. package/dist/schema/params.d.ts.map +1 -1
  19. package/dist/schema/params.js +4 -1
  20. package/dist/schema/params.js.map +1 -1
  21. package/dist/schema/record.d.ts +12 -6
  22. package/dist/schema/record.d.ts.map +1 -1
  23. package/dist/schema/record.js +21 -12
  24. package/dist/schema/record.js.map +1 -1
  25. package/dist/schema/typed-object.d.ts +12 -4
  26. package/dist/schema/typed-object.d.ts.map +1 -1
  27. package/dist/schema/typed-object.js +21 -12
  28. package/dist/schema/typed-object.js.map +1 -1
  29. package/dist/schema/union.d.ts.map +1 -1
  30. package/dist/schema/union.js +1 -1
  31. package/dist/schema/union.js.map +1 -1
  32. package/package.json +3 -3
  33. package/src/core/schema.ts +67 -59
  34. package/src/core/string-format.ts +14 -17
  35. package/src/core/validation-error.ts +10 -10
  36. package/src/core/validator.ts +9 -9
  37. package/src/schema/params.test.ts +16 -0
  38. package/src/schema/params.ts +5 -2
  39. package/src/schema/record.ts +27 -22
  40. package/src/schema/typed-object.test.ts +38 -0
  41. package/src/schema/typed-object.ts +29 -24
  42. package/src/schema/union.ts +2 -2
@@ -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
  */
@@ -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
  }
@@ -425,7 +425,7 @@ export class ValidationContext {
425
425
  * @param reason - The validation error
426
426
  * @returns A failed validation result
427
427
  */
428
- failure(reason: ValidationError): ValidationFailure {
428
+ failure(reason: LexValidationError): ValidationFailure {
429
429
  return failure(reason)
430
430
  }
431
431
 
@@ -438,7 +438,7 @@ export class ValidationContext {
438
438
  * @returns A failed validation result
439
439
  */
440
440
  issue(issue: Issue) {
441
- return this.failure(new ValidationError([...this.issues, issue]))
441
+ return this.failure(new LexValidationError([...this.issues, issue]))
442
442
  }
443
443
 
444
444
  /**
@@ -433,6 +433,22 @@ describe('ParamsSchema', () => {
433
433
  ),
434
434
  ).toThrow('Expected boolean value type at $.foo.bar.bools (got string)')
435
435
  })
436
+
437
+ it('ignores empty string values', () => {
438
+ const result = schema.fromURLSearchParams([
439
+ ['name', 'Alice'],
440
+ ['extra', ''],
441
+ ])
442
+ expect(result).toEqual({ name: 'Alice' })
443
+ })
444
+
445
+ it('ignores empty string values for known parameters', () => {
446
+ const result = schema.fromURLSearchParams([
447
+ ['name', 'Alice'],
448
+ ['age', ''],
449
+ ])
450
+ expect(result).toEqual({ name: 'Alice' })
451
+ })
436
452
  })
437
453
 
438
454
  describe('toURLSearchParams', () => {
@@ -6,10 +6,10 @@ import {
6
6
  Issue,
7
7
  IssueInvalidType,
8
8
  IssueInvalidValue,
9
+ LexValidationError,
9
10
  ParseOptions,
10
11
  Schema,
11
12
  ValidationContext,
12
- ValidationError,
13
13
  Validator,
14
14
  WithOptionalProperties,
15
15
  } from '../core.js'
@@ -209,6 +209,9 @@ export class ParamsSchema<
209
209
  iterable instanceof URLSearchParams ? iterable.entries() : iterable
210
210
 
211
211
  for (const [name, value] of entries) {
212
+ // Ignore empty strings
213
+ if (!value) continue
214
+
212
215
  const validator = this.shapeValidators.get(name)
213
216
  const innerValidator = validator ? unwrapSchema(validator) : undefined
214
217
  const expectsArray = innerValidator instanceof ArraySchema
@@ -302,7 +305,7 @@ function coerceParam(
302
305
  // the index of the param in case of array params (e.g. "tags[1]"), which
303
306
  // could be helpful for debugging. The cost overhead is not worth it though
304
307
  // (IMO).
305
- throw new ValidationError([issue])
308
+ throw new LexValidationError([issue])
306
309
  }
307
310
 
308
311
  function paramPath(key: string, options?: ParseOptions) {
@@ -11,6 +11,7 @@ import {
11
11
  ValidationContext,
12
12
  Validator,
13
13
  } from '../core.js'
14
+ import { lazyProperty } from '../util/lazy-property.js'
14
15
  import { literal } from './literal.js'
15
16
  import { string } from './string.js'
16
17
 
@@ -70,10 +71,18 @@ export class RecordSchema<
70
71
  this.keySchema = recordKey(key)
71
72
  }
72
73
 
73
- isTypeOf<TValue extends { $type?: unknown }>(
74
- value: TValue,
75
- ): value is TypedRecord<TType, TValue> {
76
- return value.$type === this.$type
74
+ validateInContext(input: unknown, ctx: ValidationContext) {
75
+ const result = ctx.validate(input, this.schema)
76
+
77
+ if (!result.success) {
78
+ return result
79
+ }
80
+
81
+ if (result.value.$type !== this.$type) {
82
+ return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
83
+ }
84
+
85
+ return result
77
86
  }
78
87
 
79
88
  build(
@@ -82,30 +91,26 @@ export class RecordSchema<
82
91
  return this.parse($typed(input, this.$type))
83
92
  }
84
93
 
85
- $isTypeOf<TValue extends { $type?: unknown }>(
94
+ isTypeOf<TValue extends { $type?: unknown }>(
86
95
  value: TValue,
87
96
  ): value is TypedRecord<TType, TValue> {
88
- return this.isTypeOf<TValue>(value)
97
+ return value.$type === this.$type
89
98
  }
90
99
 
91
- $build(
92
- input: Omit<InferInput<this>, '$type'>,
93
- ): $Typed<InferOutput<this>, TType> {
94
- return this.build(input)
100
+ /**
101
+ * Bound alias for {@link build} for compatibility with generated utilities.
102
+ * @see {@link build}
103
+ */
104
+ get $build(): typeof this.build {
105
+ return lazyProperty(this, '$build', this.build.bind(this))
95
106
  }
96
107
 
97
- validateInContext(input: unknown, ctx: ValidationContext) {
98
- const result = ctx.validate(input, this.schema)
99
-
100
- if (!result.success) {
101
- return result
102
- }
103
-
104
- if (result.value.$type !== this.$type) {
105
- return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
106
- }
107
-
108
- return result
108
+ /**
109
+ * Bound alias for {@link isTypeOf} for compatibility with generated utilities.
110
+ * @see {@link isTypeOf}
111
+ */
112
+ get $isTypeOf(): typeof this.isTypeOf {
113
+ return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
109
114
  }
110
115
  }
111
116
 
@@ -332,6 +332,44 @@ describe('TypedObjectSchema', () => {
332
332
  })
333
333
  })
334
334
 
335
+ describe('bound $ methods', () => {
336
+ it('$build can be used as a detached function', () => {
337
+ const { $build } = schema
338
+ const result = $build({ text: 'Hello' })
339
+ expect(result).toEqual({
340
+ text: 'Hello',
341
+ $type: 'app.bsky.feed.post',
342
+ })
343
+ })
344
+
345
+ it('$isTypeOf can be used as a detached function', () => {
346
+ const { $isTypeOf } = schema
347
+ expect($isTypeOf({ text: 'Hello' })).toBe(true)
348
+ expect($isTypeOf({ $type: 'app.bsky.feed.post', text: 'Hello' })).toBe(
349
+ true,
350
+ )
351
+ expect($isTypeOf({ $type: 'other.type', text: 'Hello' })).toBe(false)
352
+ })
353
+
354
+ it('$parse can be used as a detached function', () => {
355
+ const { $parse } = schema
356
+ const result = $parse({ text: 'Hello' })
357
+ expect(result).toEqual({ text: 'Hello' })
358
+ })
359
+
360
+ it('$matches can be used as a detached function', () => {
361
+ const { $matches } = schema
362
+ expect($matches({ text: 'Hello' })).toBe(true)
363
+ expect($matches(42)).toBe(false)
364
+ })
365
+
366
+ it('lazy property returns the same function on repeated access', () => {
367
+ const fn1 = schema.$build
368
+ const fn2 = schema.$build
369
+ expect(fn1).toBe(fn2)
370
+ })
371
+ })
372
+
335
373
  describe('with complex nested schemas', () => {
336
374
  const complexSchema = typedObject(
337
375
  'app.bsky.actor.profile',
@@ -14,6 +14,7 @@ import {
14
14
  ValidationContext,
15
15
  Validator,
16
16
  } from '../core.js'
17
+ import { lazyProperty } from '../util/lazy-property.js'
17
18
 
18
19
  export type MaybeTypedObject<
19
20
  TType extends $Type,
@@ -56,10 +57,20 @@ export class TypedObjectSchema<
56
57
  super()
57
58
  }
58
59
 
59
- isTypeOf<TValue extends Record<string, unknown>>(
60
- value: TValue,
61
- ): value is MaybeTypedObject<TType, TValue> {
62
- return value.$type === undefined || value.$type === this.$type
60
+ validateInContext(input: unknown, ctx: ValidationContext) {
61
+ if (!isPlainObject(input)) {
62
+ return ctx.issueUnexpectedType(input, 'object')
63
+ }
64
+
65
+ if (
66
+ '$type' in input &&
67
+ input.$type !== undefined &&
68
+ input.$type !== this.$type
69
+ ) {
70
+ return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])
71
+ }
72
+
73
+ return ctx.validate(input, this.schema)
63
74
  }
64
75
 
65
76
  build(
@@ -71,32 +82,26 @@ export class TypedObjectSchema<
71
82
  >
72
83
  }
73
84
 
74
- $isTypeOf<TValue extends Record<string, unknown>>(
85
+ isTypeOf<TValue extends Record<string, unknown>>(
75
86
  value: TValue,
76
87
  ): value is MaybeTypedObject<TType, TValue> {
77
- return this.isTypeOf(value)
88
+ return value.$type === undefined || value.$type === this.$type
78
89
  }
79
90
 
80
- $build(
81
- input: Omit<InferInput<this>, '$type'>,
82
- ): $Typed<InferOutput<this>, TType> {
83
- return this.build(input)
91
+ /**
92
+ * Bound alias for {@link build} for compatibility with generated utilities.
93
+ * @see {@link build}
94
+ */
95
+ get $build(): typeof this.build {
96
+ return lazyProperty(this, '$build', this.build.bind(this))
84
97
  }
85
98
 
86
- validateInContext(input: unknown, ctx: ValidationContext) {
87
- if (!isPlainObject(input)) {
88
- return ctx.issueUnexpectedType(input, 'object')
89
- }
90
-
91
- if (
92
- '$type' in input &&
93
- input.$type !== undefined &&
94
- input.$type !== this.$type
95
- ) {
96
- return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])
97
- }
98
-
99
- return ctx.validate(input, this.schema)
99
+ /**
100
+ * Bound alias for {@link isTypeOf} for compatibility with generated utilities.
101
+ * @see {@link isTypeOf}
102
+ */
103
+ get $isTypeOf(): typeof this.isTypeOf {
104
+ return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
100
105
  }
101
106
  }
102
107
 
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  InferInput,
3
3
  InferOutput,
4
+ LexValidationError,
4
5
  Schema,
5
6
  ValidationContext,
6
- ValidationError,
7
7
  ValidationFailure,
8
8
  Validator,
9
9
  } from '../core.js'
@@ -53,7 +53,7 @@ export class UnionSchema<
53
53
  failures.push(result)
54
54
  }
55
55
 
56
- return ctx.failure(ValidationError.fromFailures(failures))
56
+ return ctx.failure(LexValidationError.fromFailures(failures))
57
57
  }
58
58
  }
59
59