@atproto/lex-schema 0.0.16 → 0.0.18

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 (55) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/core/schema.d.ts +5 -11
  3. package/dist/core/schema.d.ts.map +1 -1
  4. package/dist/core/schema.js +10 -16
  5. package/dist/core/schema.js.map +1 -1
  6. package/dist/core/validation-error.d.ts +2 -2
  7. package/dist/core/validation-error.d.ts.map +1 -1
  8. package/dist/core/validation-error.js +1 -1
  9. package/dist/core/validation-error.js.map +1 -1
  10. package/dist/core/validation-issue.d.ts.map +1 -1
  11. package/dist/core/validation-issue.js +19 -38
  12. package/dist/core/validation-issue.js.map +1 -1
  13. package/dist/helpers.d.ts +4 -3
  14. package/dist/helpers.d.ts.map +1 -1
  15. package/dist/helpers.js +6 -1
  16. package/dist/helpers.js.map +1 -1
  17. package/dist/schema/blob.d.ts +6 -20
  18. package/dist/schema/blob.d.ts.map +1 -1
  19. package/dist/schema/blob.js +23 -28
  20. package/dist/schema/blob.js.map +1 -1
  21. package/dist/schema/payload.d.ts.map +1 -1
  22. package/dist/schema/payload.js +2 -3
  23. package/dist/schema/payload.js.map +1 -1
  24. package/dist/schema/record.d.ts +6 -8
  25. package/dist/schema/record.d.ts.map +1 -1
  26. package/dist/schema/record.js +1 -1
  27. package/dist/schema/record.js.map +1 -1
  28. package/dist/schema/regexp.d.ts +3 -2
  29. package/dist/schema/regexp.d.ts.map +1 -1
  30. package/dist/schema/regexp.js +6 -4
  31. package/dist/schema/regexp.js.map +1 -1
  32. package/dist/schema/string.d.ts.map +1 -1
  33. package/dist/schema/string.js +9 -2
  34. package/dist/schema/string.js.map +1 -1
  35. package/dist/schema/typed-object.d.ts +5 -7
  36. package/dist/schema/typed-object.d.ts.map +1 -1
  37. package/dist/schema/typed-object.js +1 -1
  38. package/dist/schema/typed-object.js.map +1 -1
  39. package/package.json +3 -3
  40. package/src/core/$type.test.ts +9 -5
  41. package/src/core/schema.ts +19 -16
  42. package/src/core/validation-error.ts +2 -2
  43. package/src/core/validation-issue.ts +20 -36
  44. package/src/helpers.ts +7 -1
  45. package/src/schema/array.test.ts +1 -1
  46. package/src/schema/blob.test.ts +223 -263
  47. package/src/schema/blob.ts +27 -46
  48. package/src/schema/params.test.ts +2 -2
  49. package/src/schema/payload.ts +2 -3
  50. package/src/schema/record.test.ts +135 -17
  51. package/src/schema/record.ts +14 -9
  52. package/src/schema/regexp.ts +14 -4
  53. package/src/schema/string.ts +8 -2
  54. package/src/schema/typed-object.test.ts +77 -0
  55. package/src/schema/typed-object.ts +11 -10
@@ -1 +1 @@
1
- {"version":3,"file":"typed-object.d.ts","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"typed-object.d.ts","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":"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"}
@@ -44,7 +44,7 @@ class TypedObjectSchema extends core_js_1.Schema {
44
44
  return ctx.validate(input, this.schema);
45
45
  }
46
46
  build(input) {
47
- return this.parse((0, core_js_1.$typed)(input, this.$type));
47
+ return (0, core_js_1.$typed)(input, this.$type);
48
48
  }
49
49
  isTypeOf(value) {
50
50
  return value.$type === undefined || value.$type === this.$type;
@@ -1 +1 @@
1
- {"version":3,"file":"typed-object.js","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":";;;AA6KA,kCAMC;AAnLD,gDAAiD;AACjD,wCAcmB;AACnB,+DAAuD;AASvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,iBAGX,SAAQ,gBAGT;IAIY;IACA;IAJF,IAAI,GAAG,aAAsB,CAAA;IAEtC,YACW,KAAY,EACZ,MAAc;QAEvB,KAAK,EAAE,CAAA;QAHE,UAAK,GAAL,KAAK,CAAO;QACZ,WAAM,GAAN,MAAM,CAAQ;IAGzB,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,CAAC,IAAA,wBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QACjD,CAAC;QAED,IACE,OAAO,IAAI,KAAK;YAChB,KAAK,CAAC,KAAK,KAAK,SAAS;YACzB,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAC1B,CAAC;YACD,OAAO,GAAG,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACpE,CAAC;QAED,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,CACH,KAAsC;QAEtC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,gBAAM,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAG1C,CAAA;IACH,CAAC;IAED,QAAQ,CACN,KAAa;QAEb,OAAO,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA;IAChE,CAAC;IAED;;;OAGG;IACH,IAAI,MAAM;QACR,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,CAAC;CACF;AA9DD,8CA8DC;AAmED,wBAAwB;AACxB,SAAgB,WAAW,CAIzB,IAAO,EAAE,IAAO,EAAE,SAAY;IAC9B,OAAO,IAAI,iBAAiB,CAAiB,IAAA,eAAK,EAAC,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["import { isPlainObject } from '@atproto/lex-data'\nimport {\n $Type,\n $TypeOf,\n $Typed,\n $TypedMaybe,\n $type,\n $typed,\n InferInput,\n InferOutput,\n NsidString,\n Schema,\n Unknown$TypedObject,\n ValidationContext,\n Validator,\n} from '../core.js'\nimport { lazyProperty } from '../util/lazy-property.js'\n\nexport type MaybeTypedObject<\n TType extends $Type,\n TValue extends { $type?: unknown } = { $type?: unknown },\n> = TValue extends { $type?: TType }\n ? TValue\n : $TypedMaybe<Exclude<TValue, Unknown$TypedObject>, TType>\n\n/**\n * Schema for typed objects in Lexicon unions.\n *\n * Typed objects have a `$type` field that identifies which variant they are\n * in a union. The `$type` can be omitted in input (it's implicit), but if\n * present, it must match the expected value.\n *\n * @template TType - The $type string literal type\n * @template TShape - The validator type for the object's shape\n *\n * @example\n * ```ts\n * const schema = new TypedObjectSchema(\n * 'app.bsky.embed.images#view',\n * l.object({ images: l.array(imageSchema) })\n * )\n * ```\n */\nexport class TypedObjectSchema<\n const TType extends $Type = $Type,\n const TShape extends Validator<{ [k: string]: unknown }> = any,\n> extends Schema<\n $TypedMaybe<InferInput<TShape>, TType>,\n $TypedMaybe<InferOutput<TShape>, TType>\n> {\n readonly type = 'typedObject' as const\n\n constructor(\n readonly $type: TType,\n readonly schema: TShape,\n ) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (!isPlainObject(input)) {\n return ctx.issueUnexpectedType(input, 'object')\n }\n\n if (\n '$type' in input &&\n input.$type !== undefined &&\n input.$type !== this.$type\n ) {\n return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])\n }\n\n return ctx.validate(input, this.schema)\n }\n\n build(\n input: Omit<InferInput<this>, '$type'>,\n ): $Typed<InferOutput<this>, TType> {\n return this.parse($typed(input, this.$type)) as $Typed<\n InferOutput<this>,\n TType\n >\n }\n\n isTypeOf<TValue extends Record<string, unknown>>(\n value: TValue,\n ): value is MaybeTypedObject<TType, TValue> {\n return value.$type === undefined || value.$type === this.$type\n }\n\n /**\n * Bound alias for {@link build} for compatibility with generated utilities.\n * @see {@link build}\n */\n get $build(): typeof this.build {\n return lazyProperty(this, '$build', this.build.bind(this))\n }\n\n /**\n * Bound alias for {@link isTypeOf} for compatibility with generated utilities.\n * @see {@link isTypeOf}\n */\n get $isTypeOf(): typeof this.isTypeOf {\n return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))\n }\n}\n\n/**\n * Creates a typed object schema for use in Lexicon unions.\n *\n * Typed objects are identified by their `$type` field, which combines an NSID\n * and a hash (e.g., 'app.bsky.embed.images#view'). Used for union variants.\n *\n * This function offers two overloads:\n * - One that infers the type from arguments (no circular reference support)\n * - One with explicit interface for codegen with circular references\n *\n * @param nsid - The NSID part of the type (e.g., 'app.bsky.embed.images')\n * @param hash - The hash part of the type (e.g., 'view'), defaults to 'main'\n * @param validator - Schema for validating the object properties\n * @returns A new {@link TypedObjectSchema} instance\n *\n * @example\n * ```ts\n * // Image embed view\n * const imageViewSchema = l.typedObject(\n * 'app.bsky.embed.images',\n * 'view',\n * l.object({\n * images: l.array(l.object({\n * thumb: l.string(),\n * fullsize: l.string(),\n * alt: l.string(),\n * })),\n * })\n * )\n *\n * // Main type (hash defaults to 'main')\n * const postViewSchema = l.typedObject(\n * 'app.bsky.feed.defs',\n * 'postView',\n * l.object({ uri: l.string(), cid: l.string(), author: authorSchema })\n * )\n *\n * // Use $isTypeOf to narrow union types\n * if (imageViewSchema.$isTypeOf(embed)) {\n * // embed is narrowed to image view type\n * }\n *\n * // Use $build to construct typed objects\n * const view = imageViewSchema.$build({ images: [...] })\n * // view.$type === 'app.bsky.embed.images#view'\n * ```\n */\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<{ [k: string]: unknown }>,\n>(nsid: N, hash: H, validator: S): TypedObjectSchema<$Type<N, H>, S>\nexport function typedObject<V extends { $type?: $Type }>(\n nsid: V extends { $type?: infer T extends string }\n ? T extends `${infer N}#${string}`\n ? N\n : T // (T is a \"main\" type, so already an NSID)\n : never,\n hash: V extends { $type?: infer T extends string }\n ? T extends `${string}#${infer H}`\n ? H\n : 'main'\n : never,\n validator: Validator<Omit<V, '$type'>>,\n): TypedObjectSchema<$TypeOf<V>, Validator<V>>\n/*@__NO_SIDE_EFFECTS__*/\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<{ [k: string]: unknown }>,\n>(nsid: N, hash: H, validator: S) {\n return new TypedObjectSchema<$Type<N, H>, S>($type(nsid, hash), validator)\n}\n"]}
1
+ {"version":3,"file":"typed-object.js","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":";;;AA8KA,kCAMC;AApLD,gDAAyD;AACzD,wCAcmB;AACnB,+DAAuD;AASvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,iBAGX,SAAQ,gBAGT;IAIY;IACA;IAJF,IAAI,GAAG,aAAsB,CAAA;IAEtC,YACW,KAAY,EACZ,MAAc;QAEvB,KAAK,EAAE,CAAA;QAHE,UAAK,GAAL,KAAK,CAAO;QACZ,WAAM,GAAN,MAAM,CAAQ;IAGzB,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,CAAC,IAAA,wBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QACjD,CAAC;QAED,IACE,OAAO,IAAI,KAAK;YAChB,KAAK,CAAC,KAAK,KAAK,SAAS;YACzB,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAC1B,CAAC;YACD,OAAO,GAAG,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACpE,CAAC;QAED,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACzC,CAAC;IAQD,KAAK,CAAC,KAA8B;QAClC,OAAO,IAAA,gBAAM,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,QAAQ,CACN,KAAa;QAEb,OAAO,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA;IAChE,CAAC;IAED;;;OAGG;IACH,IAAI,MAAM;QACR,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,CAAC;CACF;AA/DD,8CA+DC;AAmED,wBAAwB;AACxB,SAAgB,WAAW,CAIzB,IAAO,EAAE,IAAO,EAAE,SAAY;IAC9B,OAAO,IAAI,iBAAiB,CAAiB,IAAA,eAAK,EAAC,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["import { LexMap, isPlainObject } from '@atproto/lex-data'\nimport {\n $Type,\n $TypeOf,\n $Typed,\n $TypedMaybe,\n $type,\n $typed,\n InferInput,\n InferOutput,\n NsidString,\n Schema,\n Unknown$TypedObject,\n ValidationContext,\n Validator,\n} from '../core.js'\nimport { lazyProperty } from '../util/lazy-property.js'\n\nexport type MaybeTypedObject<\n TType extends $Type,\n TValue extends { $type?: unknown } = { $type?: unknown },\n> = TValue extends { $type?: TType }\n ? TValue\n : $TypedMaybe<Exclude<TValue, Unknown$TypedObject>, TType>\n\n/**\n * Schema for typed objects in Lexicon unions.\n *\n * Typed objects have a `$type` field that identifies which variant they are\n * in a union. The `$type` can be omitted in input (it's implicit), but if\n * present, it must match the expected value.\n *\n * @template TType - The $type string literal type\n * @template TShape - The validator type for the object's shape\n *\n * @example\n * ```ts\n * const schema = new TypedObjectSchema(\n * 'app.bsky.embed.images#view',\n * l.object({ images: l.array(imageSchema) })\n * )\n * ```\n */\nexport class TypedObjectSchema<\n const TType extends $Type = $Type,\n const TShape extends Validator<LexMap> = Validator<LexMap>,\n> extends Schema<\n $TypedMaybe<InferInput<TShape>, TType>,\n $TypedMaybe<InferOutput<TShape>, TType>\n> {\n readonly type = 'typedObject' as const\n\n constructor(\n readonly $type: TType,\n readonly schema: TShape,\n ) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (!isPlainObject(input)) {\n return ctx.issueUnexpectedType(input, 'object')\n }\n\n if (\n '$type' in input &&\n input.$type !== undefined &&\n input.$type !== this.$type\n ) {\n return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])\n }\n\n return ctx.validate(input, this.schema)\n }\n\n build(\n input: Omit<InferOutput<TShape>, '$type'>,\n ): $Typed<InferOutput<TShape>, TType>\n build(\n input: Omit<InferInput<TShape>, '$type'>,\n ): $Typed<InferInput<TShape>, TType>\n build(input: Record<string, unknown>) {\n return $typed(input, this.$type)\n }\n\n isTypeOf<TValue extends Record<string, unknown>>(\n value: TValue,\n ): value is MaybeTypedObject<TType, TValue> {\n return value.$type === undefined || value.$type === this.$type\n }\n\n /**\n * Bound alias for {@link build} for compatibility with generated utilities.\n * @see {@link build}\n */\n get $build(): typeof this.build {\n return lazyProperty(this, '$build', this.build.bind(this))\n }\n\n /**\n * Bound alias for {@link isTypeOf} for compatibility with generated utilities.\n * @see {@link isTypeOf}\n */\n get $isTypeOf(): typeof this.isTypeOf {\n return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))\n }\n}\n\n/**\n * Creates a typed object schema for use in Lexicon unions.\n *\n * Typed objects are identified by their `$type` field, which combines an NSID\n * and a hash (e.g., 'app.bsky.embed.images#view'). Used for union variants.\n *\n * This function offers two overloads:\n * - One that infers the type from arguments (no circular reference support)\n * - One with explicit interface for codegen with circular references\n *\n * @param nsid - The NSID part of the type (e.g., 'app.bsky.embed.images')\n * @param hash - The hash part of the type (e.g., 'view'), defaults to 'main'\n * @param validator - Schema for validating the object properties\n * @returns A new {@link TypedObjectSchema} instance\n *\n * @example\n * ```ts\n * // Image embed view\n * const imageViewSchema = l.typedObject(\n * 'app.bsky.embed.images',\n * 'view',\n * l.object({\n * images: l.array(l.object({\n * thumb: l.string(),\n * fullsize: l.string(),\n * alt: l.string(),\n * })),\n * })\n * )\n *\n * // Main type (hash defaults to 'main')\n * const postViewSchema = l.typedObject(\n * 'app.bsky.feed.defs',\n * 'postView',\n * l.object({ uri: l.string(), cid: l.string(), author: authorSchema })\n * )\n *\n * // Use $isTypeOf to narrow union types\n * if (imageViewSchema.$isTypeOf(embed)) {\n * // embed is narrowed to image view type\n * }\n *\n * // Use $build to construct typed objects\n * const view = imageViewSchema.$build({ images: [...] })\n * // view.$type === 'app.bsky.embed.images#view'\n * ```\n */\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<LexMap>,\n>(nsid: N, hash: H, validator: S): TypedObjectSchema<$Type<N, H>, S>\nexport function typedObject<V extends { $type?: $Type }>(\n nsid: V extends { $type?: infer T extends string }\n ? T extends `${infer N}#${string}`\n ? N\n : T // (T is a \"main\" type, so already an NSID)\n : never,\n hash: V extends { $type?: infer T extends string }\n ? T extends `${string}#${infer H}`\n ? H\n : 'main'\n : never,\n validator: Validator<Omit<V, '$type'>>,\n): TypedObjectSchema<$TypeOf<V>, Validator<V>>\n/*@__NO_SIDE_EFFECTS__*/\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<LexMap>,\n>(nsid: N, hash: H, validator: S) {\n return new TypedObjectSchema<$Type<N, H>, S>($type(nsid, hash), validator)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-schema",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "license": "MIT",
5
5
  "description": "Lexicon schema system for AT Lexicons",
6
6
  "keywords": [
@@ -38,8 +38,8 @@
38
38
  "@standard-schema/spec": "^1.1.0",
39
39
  "iso-datestring-validator": "^2.2.2",
40
40
  "tslib": "^2.8.1",
41
- "@atproto/syntax": "^0.5.1",
42
- "@atproto/lex-data": "^0.0.14"
41
+ "@atproto/syntax": "^0.5.3",
42
+ "@atproto/lex-data": "^0.0.15"
43
43
  },
44
44
  "devDependencies": {
45
45
  "vitest": "^4.0.16"
@@ -1,14 +1,18 @@
1
- import { describe, it } from 'vitest'
1
+ import { describe, expectTypeOf, it } from 'vitest'
2
2
  import { LexMap } from '@atproto/lex-data'
3
- import { Unknown$TypedObject } from './$type.js'
3
+ import { Unknown$Type, Unknown$TypedObject } from './$type.js'
4
4
 
5
5
  describe('Unknown$TypedObject', () => {
6
6
  it('allows assigning Unknown$TypedObject to LexMap', () => {
7
7
  function expectLexMap(_value: LexMap) {}
8
8
 
9
- const someObject = {
10
- $type: 'some-type',
11
- } as Unknown$TypedObject
9
+ const someObject: Unknown$TypedObject = {
10
+ $type: 'some-type' as Unknown$Type,
11
+ // @ts-expect-error should not allow arbitrary properties
12
+ foo: 'bar',
13
+ }
14
+
15
+ expectTypeOf(someObject).toEqualTypeOf<Unknown$TypedObject>()
12
16
 
13
17
  expectLexMap(someObject)
14
18
 
@@ -120,8 +120,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
120
120
  * will typically arise in generic contexts, where the narrowed type is not
121
121
  * needed.
122
122
  */
123
- assert(input: unknown): asserts input is InferInput<this> {
124
- const result = ValidationContext.validate(input, this)
123
+ assert(
124
+ input: unknown,
125
+ options?: ValidateOptions,
126
+ ): asserts input is InferInput<this> {
127
+ const result = this.safeValidate(input, options)
125
128
  if (!result.success) throw result.reason
126
129
  }
127
130
 
@@ -131,8 +134,8 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
131
134
  * every name in the call target to be declared with an explicit type
132
135
  * annotation. ts(2775)_" errors.
133
136
  */
134
- check(input: unknown): void {
135
- this.assert(input)
137
+ check(input: unknown, options?: ValidateOptions): void {
138
+ this.assert(input, options)
136
139
  }
137
140
 
138
141
  /**
@@ -140,8 +143,8 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
140
143
  * schema, otherwise throws. This is the same as calling {@link parse}() with
141
144
  * `mode: "validate"`.
142
145
  */
143
- cast<I>(input: I): I & InferInput<this> {
144
- const result = ValidationContext.validate(input, this)
146
+ cast<I>(input: I, options?: ValidateOptions): I & InferInput<this> {
147
+ const result = this.safeValidate(input, options)
145
148
  if (result.success) return result.value
146
149
  throw result.reason
147
150
  }
@@ -149,9 +152,6 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
149
152
  /**
150
153
  * Type guard that checks if the input matches this schema.
151
154
  *
152
- * @param input - The value to check
153
- * @returns `true` if the input is valid according to this schema
154
- *
155
155
  * @example
156
156
  * ```typescript
157
157
  * if (schema.matches(data)) {
@@ -160,8 +160,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
160
160
  * }
161
161
  * ```
162
162
  */
163
- matches<I>(input: I): input is I & InferInput<this> {
164
- const result = ValidationContext.validate(input, this)
163
+ matches<I>(
164
+ input: I,
165
+ options?: ValidateOptions,
166
+ ): input is I & InferInput<this> {
167
+ const result = this.safeValidate(input, options)
165
168
  return result.success
166
169
  }
167
170
 
@@ -171,9 +174,6 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
171
174
  * This is useful for optional filtering operations where you want to
172
175
  * conditionally extract values that match a schema.
173
176
  *
174
- * @param input - The value to check
175
- * @returns The input value with narrowed type if valid, otherwise `undefined`
176
- *
177
177
  * @example
178
178
  * ```typescript
179
179
  * const validData = schema.ifMatches(data)
@@ -183,8 +183,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
183
183
  * }
184
184
  * ```
185
185
  */
186
- ifMatches<I>(input: I): (I & InferInput<this>) | undefined {
187
- return this.matches(input) ? input : undefined
186
+ ifMatches<I>(
187
+ input: I,
188
+ options?: ValidateOptions,
189
+ ): (I & InferInput<this>) | undefined {
190
+ return this.matches(input, options) ? input : undefined
188
191
  }
189
192
 
190
193
  /**
@@ -23,7 +23,7 @@ import {
23
23
  * new IssueInvalidType(['user', 'age'], 'hello', ['number'])
24
24
  * ])
25
25
  * console.log(error.message)
26
- * // "Expected number value type at $.user.age (got string)"
26
+ * // "Expected integer value type (got "some-string") at $.user.age"
27
27
  *
28
28
  * console.log(error.issues.length) // 1
29
29
  * console.log(error.toJSON())
@@ -46,7 +46,7 @@ export class LexValidationError
46
46
  * Issues are aggregated when possible (e.g., multiple invalid type issues
47
47
  * at the same path are combined into a single issue listing all expected types).
48
48
  */
49
- readonly issues: Issue[]
49
+ readonly issues: readonly Issue[]
50
50
 
51
51
  /**
52
52
  * Creates a new validation error from a list of issues.
@@ -1,5 +1,8 @@
1
1
  import { ifCid, isLegacyBlobRef, isPlainObject } from '@atproto/lex-data'
2
2
 
3
+ const STRING_PREVIEW_MAX_LENGTH = 48
4
+ const STRING_PREVIEW_TRUNCATED_SUFFIX = '…'
5
+
3
6
  /**
4
7
  * Abstract base class for all validation issues.
5
8
  *
@@ -120,7 +123,7 @@ export class IssueInvalidType extends Issue {
120
123
  }
121
124
 
122
125
  override get message(): string {
123
- return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type (got ${stringifyType(this.input)})`
126
+ return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type (got ${stringifyValue(this.input)})`
124
127
  }
125
128
 
126
129
  toJSON() {
@@ -289,55 +292,36 @@ function oneOf(arr: readonly string[]): string {
289
292
  return `one of ${arr.slice(0, -1).join(', ')} or ${arr.at(-1)}`
290
293
  }
291
294
 
292
- function stringifyType(value: unknown): string {
293
- switch (typeof value) {
294
- case 'object':
295
- if (value === null) return 'null'
296
- if (Array.isArray(value)) return 'array'
297
- if (ifCid(value)) return 'cid'
298
- if (isLegacyBlobRef(value)) return 'legacy-blob'
299
- if (value instanceof Date) return 'date'
300
- if (value instanceof RegExp) return 'regexp'
301
- if (value instanceof Map) return 'map'
302
- if (value instanceof Set) return 'set'
303
- return 'object'
304
- case 'number':
305
- if (Number.isInteger(value) && Number.isSafeInteger(value)) {
306
- return 'integer'
307
- }
308
- if (Number.isNaN(value)) {
309
- return 'NaN'
310
- }
311
- if (value === Infinity) {
312
- return 'Infinity'
313
- }
314
- if (value === -Infinity) {
315
- return '-Infinity'
316
- }
317
- return 'float'
318
- default:
319
- return typeof value
320
- }
321
- }
322
-
323
295
  function stringifyValue(value: unknown): string {
324
296
  switch (typeof value) {
325
297
  case 'bigint':
326
298
  return `${value}n`
327
299
  case 'number':
328
- case 'string':
329
300
  case 'boolean':
330
- return JSON.stringify(value)
301
+ return String(value)
302
+ case 'string':
303
+ return JSON.stringify(
304
+ value.length < STRING_PREVIEW_MAX_LENGTH
305
+ ? value
306
+ : `${value.slice(0, STRING_PREVIEW_MAX_LENGTH - STRING_PREVIEW_TRUNCATED_SUFFIX.length)}${STRING_PREVIEW_TRUNCATED_SUFFIX}`,
307
+ )
331
308
  case 'object':
309
+ if (value === null) return 'null'
332
310
  if (Array.isArray(value)) {
333
311
  return `[${stringifyArray(value, stringifyValue)}]`
334
312
  }
335
313
  if (isPlainObject(value)) {
336
314
  return `{${stringifyArray(Object.entries(value), stringifyObjectEntry)}}`
337
315
  }
338
- // fallthrough
316
+ if (ifCid(value)) return 'cid'
317
+ if (isLegacyBlobRef(value)) return 'legacy-blob'
318
+ if (value instanceof Date) return 'date'
319
+ if (value instanceof RegExp) return 'regexp'
320
+ if (value instanceof Map) return 'map'
321
+ if (value instanceof Set) return 'set'
322
+ return 'object'
339
323
  default:
340
- return stringifyType(value)
324
+ return typeof value
341
325
  }
342
326
  }
343
327
 
package/src/helpers.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  Subscription,
10
10
  object,
11
11
  optional,
12
+ regexp,
12
13
  string,
13
14
  } from './schema.js'
14
15
 
@@ -103,7 +104,12 @@ export type InferMethodError<
103
104
  M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
104
105
  > = M extends { errors: readonly (infer E extends string)[] } ? E : never
105
106
 
107
+ /**
108
+ * @see {@link https://atproto.com/specs/xrpc#error-responses}
109
+ */
106
110
  export const lexErrorDataSchema = object({
107
- error: string({ minLength: 1 }),
111
+ // type name of the error (generic ASCII constant, no whitespace)
112
+ error: regexp(/^[\w_-]+$/, 'Expected ASCII constant with no whitespace'),
113
+ // description of the error, appropriate for display to humans
108
114
  message: optional(string()),
109
115
  }) satisfies Schema<LexErrorData>
@@ -81,7 +81,7 @@ describe('ArraySchema', () => {
81
81
  success: false,
82
82
  reason: expect.objectContaining({
83
83
  message: expect.stringContaining(
84
- 'Expected array value type (got integer) at $',
84
+ 'Expected array value type (got 3) at $',
85
85
  ),
86
86
  }),
87
87
  })