@atproto/lex-schema 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/record-key.d.ts +10 -0
  3. package/dist/core/record-key.d.ts.map +1 -1
  4. package/dist/core/record-key.js.map +1 -1
  5. package/dist/core/result.d.ts +1 -125
  6. package/dist/core/result.d.ts.map +1 -1
  7. package/dist/core/result.js +1 -128
  8. package/dist/core/result.js.map +1 -1
  9. package/dist/core/validation-error.d.ts +0 -18
  10. package/dist/core/validation-error.d.ts.map +1 -1
  11. package/dist/core/validation-error.js +0 -30
  12. package/dist/core/validation-error.js.map +1 -1
  13. package/dist/core/validator.d.ts +8 -15
  14. package/dist/core/validator.d.ts.map +1 -1
  15. package/dist/core/validator.js +3 -14
  16. package/dist/core/validator.js.map +1 -1
  17. package/dist/helpers.d.ts +35 -2
  18. package/dist/helpers.d.ts.map +1 -1
  19. package/dist/helpers.js +33 -0
  20. package/dist/helpers.js.map +1 -1
  21. package/dist/schema/array.d.ts +1 -1
  22. package/dist/schema/blob.d.ts +1 -1
  23. package/dist/schema/boolean.d.ts +1 -1
  24. package/dist/schema/bytes.d.ts +1 -1
  25. package/dist/schema/cid.d.ts +1 -1
  26. package/dist/schema/custom.d.ts +1 -1
  27. package/dist/schema/dict.d.ts +1 -1
  28. package/dist/schema/enum.d.ts +1 -1
  29. package/dist/schema/integer.d.ts +1 -1
  30. package/dist/schema/intersection.d.ts +1 -1
  31. package/dist/schema/lex-map.d.ts +1 -1
  32. package/dist/schema/lex-value.d.ts +1 -1
  33. package/dist/schema/literal.d.ts +1 -1
  34. package/dist/schema/null.d.ts +1 -1
  35. package/dist/schema/nullable.d.ts +1 -1
  36. package/dist/schema/object.d.ts +1 -1
  37. package/dist/schema/optional.d.ts +1 -1
  38. package/dist/schema/params.d.ts +2 -2
  39. package/dist/schema/params.d.ts.map +1 -1
  40. package/dist/schema/record.d.ts +4 -4
  41. package/dist/schema/record.d.ts.map +1 -1
  42. package/dist/schema/record.js +4 -3
  43. package/dist/schema/record.js.map +1 -1
  44. package/dist/schema/regexp.d.ts +1 -1
  45. package/dist/schema/string.d.ts +1 -1
  46. package/dist/schema/token.d.ts +2 -1
  47. package/dist/schema/token.d.ts.map +1 -1
  48. package/dist/schema/token.js +6 -1
  49. package/dist/schema/token.js.map +1 -1
  50. package/dist/schema/typed-union.d.ts +1 -1
  51. package/dist/schema/union.d.ts.map +1 -1
  52. package/dist/schema/union.js +3 -3
  53. package/dist/schema/union.js.map +1 -1
  54. package/dist/schema/unknown.d.ts +1 -1
  55. package/package.json +2 -2
  56. package/src/core/record-key.ts +20 -1
  57. package/src/core/result.ts +8 -155
  58. package/src/core/validation-error.ts +1 -33
  59. package/src/core/validator.ts +10 -21
  60. package/src/helpers.test.ts +126 -1
  61. package/src/helpers.ts +108 -1
  62. package/src/schema/record.test.ts +9 -30
  63. package/src/schema/record.ts +9 -16
  64. package/src/schema/token.ts +9 -1
  65. package/src/schema/union.ts +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/schema/record.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexMap } from '@atproto/lex-data'\nimport {\n $Typed,\n $typed,\n InferInput,\n InferOutput,\n LexiconRecordKey,\n NsidString,\n Schema,\n TidString,\n Unknown$TypedObject,\n ValidationContext,\n Validator,\n} from '../core.js'\nimport { lazyProperty } from '../util/lazy-property.js'\nimport { literal } from './literal.js'\nimport { string } from './string.js'\n\n/**\n * Infers the record key type from a RecordSchema.\n *\n * @template R - The RecordSchema type\n */\nexport type InferRecordKey<R extends RecordSchema> =\n R extends RecordSchema<infer TKey> ? RecordKeySchemaOutput<TKey> : never\n\nexport type TypedRecord<\n TType extends NsidString,\n TValue extends { $type?: unknown } = { $type?: unknown },\n> = TValue extends { $type: TType }\n ? TValue\n : $Typed<Exclude<TValue, Unknown$TypedObject>, TType>\n\n/**\n * Schema for AT Protocol records with a type identifier and key constraints.\n *\n * Records are the primary data unit in AT Protocol. Each record has a `$type`\n * field identifying its Lexicon schema, and is stored at a specific key\n * (TID, NSID, or other format) in a repository.\n *\n * @template TKey - The record key type ('tid', 'nsid', 'any', or 'literal:...')\n * @template TType - The NSID string identifying this record type\n * @template TShape - The validator type for the record's data shape\n *\n * @example\n * ```ts\n * const postSchema = new RecordSchema(\n * 'tid',\n * 'app.bsky.feed.post',\n * l.object({ text: l.string(), createdAt: l.string() })\n * )\n * ```\n */\nexport class RecordSchema<\n const TKey extends LexiconRecordKey = LexiconRecordKey,\n const TType extends NsidString = NsidString,\n const TShape extends Validator<LexMap> = Validator<LexMap>,\n> extends Schema<\n $Typed<InferInput<TShape>, TType>,\n $Typed<InferOutput<TShape>, TType>\n> {\n readonly type = 'record' as const\n\n keySchema: RecordKeySchema<TKey>\n\n constructor(\n readonly key: TKey,\n readonly $type: TType,\n readonly schema: TShape,\n ) {\n super()\n this.keySchema = recordKey(key)\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const result = ctx.validate(input, this.schema)\n\n if (!result.success) {\n return result\n }\n\n if (result.value.$type !== this.$type) {\n return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])\n }\n\n return result\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 { $type?: unknown }>(\n value: TValue,\n ): value is TypedRecord<TType, TValue> {\n return 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\nexport type RecordKeySchemaOutput<Key extends LexiconRecordKey> =\n Key extends 'any'\n ? string\n : Key extends 'tid'\n ? TidString\n : Key extends 'nsid'\n ? NsidString\n : Key extends `literal:${infer L extends string}`\n ? L\n : never\n\nexport type RecordKeySchema<Key extends LexiconRecordKey> = Schema<\n RecordKeySchemaOutput<Key>\n>\n\nconst keySchema = string({ minLength: 1 })\nconst tidSchema = string({ format: 'tid' })\nconst nsidSchema = string({ format: 'nsid' })\nconst selfLiteralSchema = literal('self')\n\nfunction recordKey<Key extends LexiconRecordKey>(\n key: Key,\n): RecordKeySchema<Key> {\n // @NOTE Use cached instances for common schemas\n if (key === 'any') return keySchema as any\n if (key === 'tid') return tidSchema as any\n if (key === 'nsid') return nsidSchema as any\n if (key.startsWith('literal:')) {\n const value = key.slice(8) as RecordKeySchemaOutput<Key>\n if (value === 'self') return selfLiteralSchema as any\n return literal(value)\n }\n\n throw new Error(`Unsupported record key type: ${key}`)\n}\n\n/**\n * Ensures that a `$type` used in a record is a valid NSID (i.e. no fragment).\n */\ntype AsNsid<T> = T extends `${string}#${string}` ? never : T\n\n/**\n * Creates a record schema for AT Protocol records.\n *\n * Records are the primary data unit in AT Protocol repositories. They have\n * a `$type` field identifying their Lexicon schema, and are stored at keys\n * following a specific format (TID, NSID, etc.).\n *\n * This function offers two overloads:\n * - One that infers the output type from the provided arguments (does not\n * support circular references)\n * - One with an explicitly defined interface for use with codegen and\n * circular references\n *\n * @param key - The record key type: 'tid', 'nsid', 'any', or 'literal:value'\n * @param type - The NSID identifying this record type (e.g., 'app.bsky.feed.post')\n * @param validator - Schema validator for the record's properties\n * @returns A new {@link RecordSchema} instance\n *\n * @example\n * ```ts\n * // Post record with TID key\n * const postSchema = l.record('tid', 'app.bsky.feed.post', l.object({\n * text: l.string({ maxGraphemes: 300 }),\n * createdAt: l.string({ format: 'datetime' }),\n * reply: l.optional(l.object({\n * root: l.ref(() => strongRefSchema),\n * parent: l.ref(() => strongRefSchema),\n * })),\n * }))\n *\n * // Profile record with literal 'self' key\n * const profileSchema = l.record('literal:self', 'app.bsky.actor.profile', l.object({\n * displayName: l.optional(l.string({ maxGraphemes: 64 })),\n * description: l.optional(l.string({ maxGraphemes: 256 })),\n * avatar: l.optional(l.blob({ accept: ['image/*'] })),\n * }))\n *\n * // Build a record with automatic $type injection\n * const post = postSchema.build({ text: 'Hello!', createdAt: new Date().toISOString() })\n * ```\n */\nexport function record<\n const K extends LexiconRecordKey,\n const T extends NsidString,\n const S extends Validator<LexMap>,\n>(key: K, type: AsNsid<T>, validator: S): RecordSchema<K, T, S>\nexport function record<\n const K extends LexiconRecordKey,\n const V extends LexMap & { $type: NsidString },\n>(\n key: K,\n type: AsNsid<V['$type']>,\n validator: Validator<Omit<V, '$type'>>,\n): RecordSchema<K, V['$type'], Validator<Omit<V, '$type'>>>\n/*@__NO_SIDE_EFFECTS__*/\nexport function record<\n const K extends LexiconRecordKey,\n const T extends NsidString,\n const S extends Validator<LexMap>,\n>(key: K, type: T, validator: S) {\n return new RecordSchema<K, T, S>(key, type, validator)\n}\n"]}
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/schema/record.ts"],"names":[],"mappings":"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","sourcesContent":["import { LexMap } from '@atproto/lex-data'\nimport {\n $Typed,\n $typed,\n InferInput,\n InferOutput,\n LexiconRecordKey,\n NsidString,\n RecordKeyValue,\n Schema,\n Unknown$TypedObject,\n ValidationContext,\n Validator,\n} from '../core.js'\nimport { lazyProperty } from '../util/lazy-property.js'\nimport { literal } from './literal.js'\nimport { string } from './string.js'\nimport { withDefault } from './with-default.js'\n\n/**\n * Infers the record key type from a RecordSchema.\n *\n * @template R - The RecordSchema type\n */\nexport type InferRecordKey<R extends RecordSchema> =\n R extends RecordSchema<infer TKey> ? RecordKeyValue<TKey> : never\n\nexport type TypedRecord<\n TType extends NsidString,\n TValue extends { $type?: unknown } = { $type?: unknown },\n> = TValue extends { $type: TType }\n ? TValue\n : $Typed<Exclude<TValue, Unknown$TypedObject>, TType>\n\n/**\n * Schema for AT Protocol records with a type identifier and key constraints.\n *\n * Records are the primary data unit in AT Protocol. Each record has a `$type`\n * field identifying its Lexicon schema, and is stored at a specific key\n * (TID, NSID, or other format) in a repository.\n *\n * @template TKey - The record key type ('tid', 'nsid', 'any', or 'literal:...')\n * @template TType - The NSID string identifying this record type\n * @template TShape - The validator type for the record's data shape\n *\n * @example\n * ```ts\n * const postSchema = new RecordSchema(\n * 'tid',\n * 'app.bsky.feed.post',\n * l.object({ text: l.string(), createdAt: l.string() })\n * )\n * ```\n */\nexport class RecordSchema<\n const TKey extends LexiconRecordKey = LexiconRecordKey,\n const TType extends NsidString = NsidString,\n const TShape extends Validator<LexMap> = Validator<LexMap>,\n> extends Schema<\n $Typed<InferInput<TShape>, TType>,\n $Typed<InferOutput<TShape>, TType>\n> {\n readonly type = 'record' as const\n\n keySchema: RecordKeySchema<TKey>\n\n constructor(\n readonly key: TKey,\n readonly $type: TType,\n readonly schema: TShape,\n ) {\n super()\n this.keySchema = recordKey(key)\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const result = ctx.validate(input, this.schema)\n\n if (!result.success) {\n return result\n }\n\n if (result.value.$type !== this.$type) {\n return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])\n }\n\n return result\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 { $type?: unknown }>(\n value: TValue,\n ): value is TypedRecord<TType, TValue> {\n return 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\nexport type RecordKeySchemaOutput<Key extends LexiconRecordKey> =\n RecordKeyValue<Key>\n\nexport type RecordKeySchema<Key extends LexiconRecordKey> = Schema<\n RecordKeyValue<Key>\n>\n\nconst keySchema = string({ format: 'record-key' })\nconst tidSchema = string({ format: 'tid' })\nconst nsidSchema = string({ format: 'nsid' })\nconst selfLiteralSchema = withDefault(literal('self'), 'self')\n\nfunction recordKey<Key extends LexiconRecordKey>(\n key: Key,\n): RecordKeySchema<Key> {\n // @NOTE Use cached instances for common schemas\n if (key === 'any') return keySchema as any\n if (key === 'tid') return tidSchema as any\n if (key === 'nsid') return nsidSchema as any\n if (key.startsWith('literal:')) {\n const value = key.slice(8) as RecordKeyValue<Key>\n if (value === 'self') return selfLiteralSchema as any\n return withDefault(literal(value), value)\n }\n\n throw new Error(`Unsupported record key type: ${key}`)\n}\n\n/**\n * Ensures that a `$type` used in a record is a valid NSID (i.e. no fragment).\n */\ntype AsNsid<T> = T extends `${string}#${string}` ? never : T\n\n/**\n * Creates a record schema for AT Protocol records.\n *\n * Records are the primary data unit in AT Protocol repositories. They have\n * a `$type` field identifying their Lexicon schema, and are stored at keys\n * following a specific format (TID, NSID, etc.).\n *\n * This function offers two overloads:\n * - One that infers the output type from the provided arguments (does not\n * support circular references)\n * - One with an explicitly defined interface for use with codegen and\n * circular references\n *\n * @param key - The record key type: 'tid', 'nsid', 'any', or 'literal:value'\n * @param type - The NSID identifying this record type (e.g., 'app.bsky.feed.post')\n * @param validator - Schema validator for the record's properties\n * @returns A new {@link RecordSchema} instance\n *\n * @example\n * ```ts\n * // Post record with TID key\n * const postSchema = l.record('tid', 'app.bsky.feed.post', l.object({\n * text: l.string({ maxGraphemes: 300 }),\n * createdAt: l.string({ format: 'datetime' }),\n * reply: l.optional(l.object({\n * root: l.ref(() => strongRefSchema),\n * parent: l.ref(() => strongRefSchema),\n * })),\n * }))\n *\n * // Profile record with literal 'self' key\n * const profileSchema = l.record('literal:self', 'app.bsky.actor.profile', l.object({\n * displayName: l.optional(l.string({ maxGraphemes: 64 })),\n * description: l.optional(l.string({ maxGraphemes: 256 })),\n * avatar: l.optional(l.blob({ accept: ['image/*'] })),\n * }))\n *\n * // Build a record with automatic $type injection\n * const post = postSchema.build({ text: 'Hello!', createdAt: new Date().toISOString() })\n * ```\n */\nexport function record<\n const K extends LexiconRecordKey,\n const T extends NsidString,\n const S extends Validator<LexMap>,\n>(key: K, type: AsNsid<T>, validator: S): RecordSchema<K, T, S>\nexport function record<\n const K extends LexiconRecordKey,\n const V extends LexMap & { $type: NsidString },\n>(\n key: K,\n type: AsNsid<V['$type']>,\n validator: Validator<Omit<V, '$type'>>,\n): RecordSchema<K, V['$type'], Validator<Omit<V, '$type'>>>\n/*@__NO_SIDE_EFFECTS__*/\nexport function record<\n const K extends LexiconRecordKey,\n const T extends NsidString,\n const S extends Validator<LexMap>,\n>(key: K, type: T, validator: S) {\n return new RecordSchema<K, T, S>(key, type, validator)\n}\n"]}
@@ -19,7 +19,7 @@ export declare class RegexpSchema<TValue extends string = string> extends Schema
19
19
  readonly message?: string | undefined;
20
20
  readonly type: "regexp";
21
21
  constructor(pattern: RegExp, message?: string | undefined);
22
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationResult<TValue>;
22
+ validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").LexValidationError | import("../core.js").ValidationSuccess<TValue>;
23
23
  }
24
24
  /**
25
25
  * Creates a regexp schema that validates strings against a pattern.
@@ -40,7 +40,7 @@ export declare class StringSchema<const TOptions extends StringSchemaOptions = S
40
40
  readonly type: "string";
41
41
  readonly options: StringSchemaOptions;
42
42
  constructor(options: TOptions);
43
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationResult<string>;
43
+ validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").LexValidationError | import("../core.js").ValidationSuccess<string>;
44
44
  }
45
45
  export declare function coerceToString(input: unknown): string | null;
46
46
  declare function _string(): StringSchema<NonNullable<unknown>>;
@@ -18,7 +18,8 @@ export declare class TokenSchema<const TValue extends string = string> extends S
18
18
  readonly value: TValue;
19
19
  readonly type: "token";
20
20
  constructor(value: TValue);
21
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationResult<TValue>;
21
+ get $token(): TValue;
22
+ validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").LexValidationError | import("../core.js").ValidationSuccess<TValue>;
22
23
  toJSON(): string;
23
24
  toString(): string;
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/schema/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEzE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAW,CACtB,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,CACpC,SAAQ,MAAM,CAAC,MAAM,CAAC;IAGV,QAAQ,CAAC,KAAK,EAAE,MAAM;IAFlC,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIlC,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;IAqBxD,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,MAAM;CAGnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,wBAAgB,KAAK,CACnB,KAAK,CAAC,CAAC,SAAS,UAAU,EAC1B,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC/B,IAAI,EAAE,CAAC,EAAE,IAAI,GAAE,CAAe,iDAE/B"}
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/schema/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEzE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAW,CACtB,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,CACpC,SAAQ,MAAM,CAAC,MAAM,CAAC;IAGV,QAAQ,CAAC,KAAK,EAAE,MAAM;IAFlC,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAEX,KAAK,EAAE,MAAM;IAIlC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;IAyBxD,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,MAAM;CAGnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,wBAAgB,KAAK,CACnB,KAAK,CAAC,CAAC,SAAS,UAAU,EAC1B,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC/B,IAAI,EAAE,CAAC,EAAE,IAAI,GAAE,CAAe,iDAE/B"}
@@ -20,13 +20,18 @@ export class TokenSchema extends Schema {
20
20
  this.value = value;
21
21
  this.type = 'token';
22
22
  }
23
+ get $token() {
24
+ return this.value;
25
+ }
23
26
  validateInContext(input, ctx) {
24
27
  if (input === this.value) {
25
28
  return ctx.success(this.value);
26
29
  }
27
30
  // @NOTE: allow using the token instance itself (but convert to the actual
28
31
  // token value)
29
- if (input instanceof TokenSchema && input.value === this.value) {
32
+ if (ctx.options.mode === 'parse' &&
33
+ input instanceof TokenSchema &&
34
+ input.value === this.value) {
30
35
  return ctx.success(this.value);
31
36
  }
32
37
  if (typeof input !== 'string') {
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/schema/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAc,MAAM,EAAqB,MAAM,YAAY,CAAA;AAEzE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,WAEX,SAAQ,MAAc;IAGtB,YAAqB,KAAa;QAChC,KAAK,EAAE,CAAA;QADY,UAAK,GAAL,KAAK,CAAQ;QAFzB,SAAI,GAAG,OAAgB,CAAA;IAIhC,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACzB,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;QAED,0EAA0E;QAC1E,eAAe;QACf,IAAI,KAAK,YAAY,WAAW,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/D,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC;QAED,OAAO,GAAG,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACnD,CAAC;IAED,yEAAyE;IACzE,cAAc;IAEd,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAwB;AACxB,MAAM,UAAU,KAAK,CAGnB,IAAO,EAAE,OAAU,MAAW;IAC9B,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAC3C,CAAC","sourcesContent":["import { $type, NsidString, Schema, ValidationContext } from '../core.js'\n\n/**\n * Schema for Lexicon token values.\n *\n * Tokens are named constants in Lexicon, identified by their NSID and hash.\n * They validate to their string value (e.g., 'app.bsky.feed.defs#requestLess').\n * TokenSchema instances can also be used as values themselves.\n *\n * @template TValue - The token string literal type\n *\n * @example\n * ```ts\n * const schema = new TokenSchema('app.bsky.feed.defs#requestLess')\n * schema.validate('app.bsky.feed.defs#requestLess') // success\n * ```\n */\nexport class TokenSchema<\n const TValue extends string = string,\n> extends Schema<TValue> {\n readonly type = 'token' as const\n\n constructor(readonly value: TValue) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (input === this.value) {\n return ctx.success(this.value)\n }\n\n // @NOTE: allow using the token instance itself (but convert to the actual\n // token value)\n if (input instanceof TokenSchema && input.value === this.value) {\n return ctx.success(this.value)\n }\n\n if (typeof input !== 'string') {\n return ctx.issueUnexpectedType(input, 'token')\n }\n\n return ctx.issueInvalidValue(input, [this.value])\n }\n\n // When using the TokenSchema instance as data, let's serialize it to the\n // token value\n\n toJSON(): string {\n return this.value\n }\n\n toString(): string {\n return this.value\n }\n}\n\n/**\n * Creates a token schema for Lexicon named constants.\n *\n * Tokens are used in Lexicon as named constants or enum-like values.\n * The token instance can be used both as a schema validator and as\n * the token value itself (it serializes to its string value).\n *\n * @param nsid - The NSID part of the token\n * @param hash - The hash part of the token (defaults to 'main')\n * @returns A new {@link TokenSchema} instance\n *\n * @example\n * ```ts\n * // Define tokens\n * const requestLess = l.token('app.bsky.feed.defs', 'requestLess')\n * const requestMore = l.token('app.bsky.feed.defs', 'requestMore')\n *\n * // Use as a value\n * console.log(requestLess.toString()) // 'app.bsky.feed.defs#requestLess'\n *\n * // Use in union for validation\n * const feedbackSchema = l.union([requestLess, requestMore])\n *\n * // Validate\n * feedbackSchema.parse('app.bsky.feed.defs#requestLess') // success\n *\n * // Token instances can be used as values in other schemas\n * const feedbackRequest = l.object({\n * feedback: requestLess, // Accepts the token value\n * })\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function token<\n const N extends NsidString,\n const H extends string = 'main',\n>(nsid: N, hash: H = 'main' as H) {\n return new TokenSchema($type(nsid, hash))\n}\n"]}
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/schema/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAc,MAAM,EAAqB,MAAM,YAAY,CAAA;AAEzE;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,WAEX,SAAQ,MAAc;IAGtB,YAAqB,KAAa;QAChC,KAAK,EAAE,CAAA;QADY,UAAK,GAAL,KAAK,CAAQ;QAFzB,SAAI,GAAG,OAAgB,CAAA;IAIhC,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACzB,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;QAED,0EAA0E;QAC1E,eAAe;QACf,IACE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO;YAC5B,KAAK,YAAY,WAAW;YAC5B,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAC1B,CAAC;YACD,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC;QAED,OAAO,GAAG,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACnD,CAAC;IAED,yEAAyE;IACzE,cAAc;IAEd,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAwB;AACxB,MAAM,UAAU,KAAK,CAGnB,IAAO,EAAE,OAAU,MAAW;IAC9B,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAC3C,CAAC","sourcesContent":["import { $type, NsidString, Schema, ValidationContext } from '../core.js'\n\n/**\n * Schema for Lexicon token values.\n *\n * Tokens are named constants in Lexicon, identified by their NSID and hash.\n * They validate to their string value (e.g., 'app.bsky.feed.defs#requestLess').\n * TokenSchema instances can also be used as values themselves.\n *\n * @template TValue - The token string literal type\n *\n * @example\n * ```ts\n * const schema = new TokenSchema('app.bsky.feed.defs#requestLess')\n * schema.validate('app.bsky.feed.defs#requestLess') // success\n * ```\n */\nexport class TokenSchema<\n const TValue extends string = string,\n> extends Schema<TValue> {\n readonly type = 'token' as const\n\n constructor(readonly value: TValue) {\n super()\n }\n\n get $token(): TValue {\n return this.value\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (input === this.value) {\n return ctx.success(this.value)\n }\n\n // @NOTE: allow using the token instance itself (but convert to the actual\n // token value)\n if (\n ctx.options.mode === 'parse' &&\n input instanceof TokenSchema &&\n input.value === this.value\n ) {\n return ctx.success(this.value)\n }\n\n if (typeof input !== 'string') {\n return ctx.issueUnexpectedType(input, 'token')\n }\n\n return ctx.issueInvalidValue(input, [this.value])\n }\n\n // When using the TokenSchema instance as data, let's serialize it to the\n // token value\n\n toJSON(): string {\n return this.value\n }\n\n toString(): string {\n return this.value\n }\n}\n\n/**\n * Creates a token schema for Lexicon named constants.\n *\n * Tokens are used in Lexicon as named constants or enum-like values.\n * The token instance can be used both as a schema validator and as\n * the token value itself (it serializes to its string value).\n *\n * @param nsid - The NSID part of the token\n * @param hash - The hash part of the token (defaults to 'main')\n * @returns A new {@link TokenSchema} instance\n *\n * @example\n * ```ts\n * // Define tokens\n * const requestLess = l.token('app.bsky.feed.defs', 'requestLess')\n * const requestMore = l.token('app.bsky.feed.defs', 'requestMore')\n *\n * // Use as a value\n * console.log(requestLess.toString()) // 'app.bsky.feed.defs#requestLess'\n *\n * // Use in union for validation\n * const feedbackSchema = l.union([requestLess, requestMore])\n *\n * // Validate\n * feedbackSchema.parse('app.bsky.feed.defs#requestLess') // success\n *\n * // Token instances can be used as values in other schemas\n * const feedbackRequest = l.object({\n * feedback: requestLess, // Accepts the token value\n * })\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function token<\n const N extends NsidString,\n const H extends string = 'main',\n>(nsid: N, hash: H = 'main' as H) {\n return new TokenSchema($type(nsid, hash))\n}\n"]}
@@ -26,7 +26,7 @@ export declare class TypedUnionSchema<const TValidators extends readonly (TypedR
26
26
  constructor(validators: TValidators, closed: TClosed);
27
27
  get validatorsMap(): Map<unknown, TValidators[number]>;
28
28
  get $types(): unknown[];
29
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").LexValidationError | import("../core.js").ValidationSuccess<Record<string, unknown>> | import("../core.js").ValidationSuccess<InferInput<TValidators[number]>>;
29
+ validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationSuccess<Record<string, unknown>> | import("../core.js").ValidationResult<InferInput<TValidators[number]>>;
30
30
  }
31
31
  /**
32
32
  * Creates a typed union schema for Lexicon unions.
@@ -1 +1 @@
1
- {"version":3,"file":"union.d.ts","sourceRoot":"","sources":["../../src/schema/union.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,MAAM,EACN,iBAAiB,EAEjB,SAAS,EACV,MAAM,YAAY,CAAA;AAEnB;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,SAAS,EAAE,GAAG,SAAS,EAAE,CAAC,CAAA;AAExE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW,CACtB,KAAK,CAAC,WAAW,SAAS,qBAAqB,GAAG,GAAG,CACrD,SAAQ,MAAM,CACd,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAC/B,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CACjC;IAGa,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,WAAW;IAFtD,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAED,UAAU,EAAE,WAAW;IAItD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;CAYzD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,wBAAgB,KAAK,CAAC,KAAK,CAAC,WAAW,SAAS,qBAAqB,EACnE,UAAU,EAAE,WAAW,4BAGxB"}
1
+ {"version":3,"file":"union.d.ts","sourceRoot":"","sources":["../../src/schema/union.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,WAAW,EAEX,kBAAkB,EAClB,MAAM,EACN,iBAAiB,EACjB,SAAS,EACV,MAAM,YAAY,CAAA;AAEnB;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,SAAS,EAAE,GAAG,SAAS,EAAE,CAAC,CAAA;AAExE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW,CACtB,KAAK,CAAC,WAAW,SAAS,qBAAqB,GAAG,GAAG,CACrD,SAAQ,MAAM,CACd,UAAU,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAC/B,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CACjC;IAGa,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,WAAW;IAFtD,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAED,UAAU,EAAE,WAAW;IAItD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;CAYzD;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,wBAAgB,KAAK,CAAC,KAAK,CAAC,WAAW,SAAS,qBAAqB,EACnE,UAAU,EAAE,WAAW,4BAGxB"}
@@ -22,14 +22,14 @@ export class UnionSchema extends Schema {
22
22
  this.type = 'union';
23
23
  }
24
24
  validateInContext(input, ctx) {
25
- const failures = [];
25
+ const issues = [];
26
26
  for (const validator of this.validators) {
27
27
  const result = ctx.validate(input, validator);
28
28
  if (result.success)
29
29
  return result;
30
- failures.push(result);
30
+ issues.push(...result.issues);
31
31
  }
32
- return ctx.failure(LexValidationError.fromFailures(failures));
32
+ return new LexValidationError(issues);
33
33
  }
34
34
  }
35
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"union.js","sourceRoot":"","sources":["../../src/schema/union.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,EAClB,MAAM,GAIP,MAAM,YAAY,CAAA;AASnB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,WAEX,SAAQ,MAGT;IAGC,YAA+B,UAAuB;QACpD,KAAK,EAAE,CAAA;QADsB,eAAU,GAAV,UAAU,CAAa;QAF7C,SAAI,GAAG,OAAgB,CAAA;IAIhC,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,MAAM,QAAQ,GAAwB,EAAE,CAAA;QAExC,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;YAC7C,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,MAAM,CAAA;YAEjC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACvB,CAAC;QAED,OAAO,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC/D,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAwB;AACxB,MAAM,UAAU,KAAK,CACnB,UAAuB;IAEvB,OAAO,IAAI,WAAW,CAAc,UAAU,CAAC,CAAA;AACjD,CAAC","sourcesContent":["import {\n InferInput,\n InferOutput,\n LexValidationError,\n Schema,\n ValidationContext,\n ValidationFailure,\n Validator,\n} from '../core.js'\n\n/**\n * Type representing a non-empty tuple of validators for union schemas.\n *\n * Requires at least one validator in the tuple.\n */\nexport type UnionSchemaValidators = readonly [Validator, ...Validator[]]\n\n/**\n * Schema for validating values that match one of several possible schemas.\n *\n * Tries each validator in order until one succeeds. If all validators fail,\n * returns a combined error from all attempts.\n *\n * @template TValidators - Tuple type of the validators in the union\n *\n * @example\n * ```ts\n * const schema = new UnionSchema([l.string(), l.integer()])\n * schema.validate('hello') // success\n * schema.validate(42) // success\n * schema.validate(true) // fails\n * ```\n */\nexport class UnionSchema<\n const TValidators extends UnionSchemaValidators = any,\n> extends Schema<\n InferInput<TValidators[number]>,\n InferOutput<TValidators[number]>\n> {\n readonly type = 'union' as const\n\n constructor(protected readonly validators: TValidators) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const failures: ValidationFailure[] = []\n\n for (const validator of this.validators) {\n const result = ctx.validate(input, validator)\n if (result.success) return result\n\n failures.push(result)\n }\n\n return ctx.failure(LexValidationError.fromFailures(failures))\n }\n}\n\n/**\n * Creates a union schema that accepts values matching any of the provided schemas.\n *\n * Validators are tried in order. Use `discriminatedUnion()` for better\n * performance when discriminating on a known property.\n *\n * @param validators - Non-empty array of validators to try\n * @returns A new {@link UnionSchema} instance\n *\n * @example\n * ```ts\n * // String or number\n * const stringOrNumber = l.union([l.string(), l.integer()])\n *\n * // Nullable value\n * const nullableString = l.union([l.string(), l.null()])\n *\n * // Multiple object types\n * const mediaSchema = l.union([\n * l.object({ type: l.literal('image'), url: l.string() }),\n * l.object({ type: l.literal('video'), url: l.string(), duration: l.integer() }),\n * ])\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function union<const TValidators extends UnionSchemaValidators>(\n validators: TValidators,\n) {\n return new UnionSchema<TValidators>(validators)\n}\n"]}
1
+ {"version":3,"file":"union.js","sourceRoot":"","sources":["../../src/schema/union.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAClB,MAAM,GAGP,MAAM,YAAY,CAAA;AASnB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,WAEX,SAAQ,MAGT;IAGC,YAA+B,UAAuB;QACpD,KAAK,EAAE,CAAA;QADsB,eAAU,GAAV,UAAU,CAAa;QAF7C,SAAI,GAAG,OAAgB,CAAA;IAIhC,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,MAAM,MAAM,GAAY,EAAE,CAAA;QAE1B,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;YAC7C,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,MAAM,CAAA;YAEjC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAA;QAC/B,CAAC;QAED,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACvC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAwB;AACxB,MAAM,UAAU,KAAK,CACnB,UAAuB;IAEvB,OAAO,IAAI,WAAW,CAAc,UAAU,CAAC,CAAA;AACjD,CAAC","sourcesContent":["import {\n InferInput,\n InferOutput,\n Issue,\n LexValidationError,\n Schema,\n ValidationContext,\n Validator,\n} from '../core.js'\n\n/**\n * Type representing a non-empty tuple of validators for union schemas.\n *\n * Requires at least one validator in the tuple.\n */\nexport type UnionSchemaValidators = readonly [Validator, ...Validator[]]\n\n/**\n * Schema for validating values that match one of several possible schemas.\n *\n * Tries each validator in order until one succeeds. If all validators fail,\n * returns a combined error from all attempts.\n *\n * @template TValidators - Tuple type of the validators in the union\n *\n * @example\n * ```ts\n * const schema = new UnionSchema([l.string(), l.integer()])\n * schema.validate('hello') // success\n * schema.validate(42) // success\n * schema.validate(true) // fails\n * ```\n */\nexport class UnionSchema<\n const TValidators extends UnionSchemaValidators = any,\n> extends Schema<\n InferInput<TValidators[number]>,\n InferOutput<TValidators[number]>\n> {\n readonly type = 'union' as const\n\n constructor(protected readonly validators: TValidators) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const issues: Issue[] = []\n\n for (const validator of this.validators) {\n const result = ctx.validate(input, validator)\n if (result.success) return result\n\n issues.push(...result.issues)\n }\n\n return new LexValidationError(issues)\n }\n}\n\n/**\n * Creates a union schema that accepts values matching any of the provided schemas.\n *\n * Validators are tried in order. Use `discriminatedUnion()` for better\n * performance when discriminating on a known property.\n *\n * @param validators - Non-empty array of validators to try\n * @returns A new {@link UnionSchema} instance\n *\n * @example\n * ```ts\n * // String or number\n * const stringOrNumber = l.union([l.string(), l.integer()])\n *\n * // Nullable value\n * const nullableString = l.union([l.string(), l.null()])\n *\n * // Multiple object types\n * const mediaSchema = l.union([\n * l.object({ type: l.literal('image'), url: l.string() }),\n * l.object({ type: l.literal('video'), url: l.string(), duration: l.integer() }),\n * ])\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function union<const TValidators extends UnionSchemaValidators>(\n validators: TValidators,\n) {\n return new UnionSchema<TValidators>(validators)\n}\n"]}
@@ -14,7 +14,7 @@ import { Schema, ValidationContext } from '../core.js';
14
14
  */
15
15
  export declare class UnknownSchema extends Schema<unknown> {
16
16
  readonly type: "unknown";
17
- validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationResult<unknown>;
17
+ validateInContext(input: unknown, ctx: ValidationContext): import("../core.js").ValidationSuccess<unknown>;
18
18
  }
19
19
  /**
20
20
  * Creates an unknown schema that accepts any value.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-schema",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -37,7 +37,7 @@
37
37
  "@standard-schema/spec": "^1.1.0",
38
38
  "tslib": "^2.8.1",
39
39
  "@atproto/syntax": "^0.6.1",
40
- "@atproto/lex-data": "^0.1.0"
40
+ "@atproto/lex-data": "^0.1.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "vitest": "^4.0.16"
@@ -1,4 +1,4 @@
1
- import { isValidRecordKey } from '@atproto/syntax'
1
+ import { NsidString, TidString, isValidRecordKey } from '@atproto/syntax'
2
2
 
3
3
  /**
4
4
  * The valid record key constraint types in a lexicon definition.
@@ -64,3 +64,22 @@ export function asLexiconRecordKey(key: unknown): LexiconRecordKey {
64
64
  if (isLexiconRecordKey(key)) return key
65
65
  throw new Error(`Invalid record key: ${String(key)}`)
66
66
  }
67
+
68
+ /**
69
+ * Maps a lexicon record key definition to its corresponding string subtype.
70
+ *
71
+ * - `'any'` maps to `string`
72
+ * - `'nsid'` maps to `NsidString`
73
+ * - `'tid'` maps to `TidString`
74
+ * - `'literal:...'` maps to the literal string value
75
+ */
76
+ export type RecordKeyValue<Key extends LexiconRecordKey = LexiconRecordKey> =
77
+ Key extends 'any'
78
+ ? string
79
+ : Key extends 'tid'
80
+ ? TidString
81
+ : Key extends 'nsid'
82
+ ? NsidString
83
+ : Key extends `literal:${infer L extends string}`
84
+ ? L
85
+ : never
@@ -1,162 +1,15 @@
1
- export type ResultSuccess<V = any> = { success: true; value: V }
2
-
3
- /**
4
- * Represents a failed result containing an error reason.
5
- *
6
- * @typeParam E - The type of the error reason
7
- */
8
- export type ResultFailure<E = Error> = { success: false; reason: E }
9
-
10
- /**
11
- * A discriminated union type representing either a success or failure outcome.
12
- *
13
- * Check the `success` property to determine the outcome and access the
14
- * appropriate property (`value` for success, `reason` for failure).
15
- *
16
- * @typeParam V - The type of the success value
17
- * @typeParam E - The type of the error reason
18
- *
19
- * @example
20
- * ```typescript
21
- * function parseJson(text: string): Result<unknown, SyntaxError> {
22
- * try {
23
- * return success(JSON.parse(text))
24
- * } catch (e) {
25
- * return failure(e as SyntaxError)
26
- * }
27
- * }
28
- * ```
29
- */
30
- export type Result<V = any, E = Error> = ResultSuccess<V> | ResultFailure<E>
31
-
32
- /**
33
- * Creates a successful result wrapping the given value.
34
- *
35
- * @typeParam V - The type of the value
36
- * @param value - The success value to wrap
37
- * @returns {ResultSuccess} A success result containing the value
38
- *
39
- * @example
40
- * ```typescript
41
- * const result = success(42)
42
- * console.log(result.success) // true
43
- * console.log(result.value) // 42
44
- * ```
45
- */
46
- /*@__NO_SIDE_EFFECTS__*/
47
- export function success<V>(value: V): ResultSuccess<V> {
48
- return { success: true, value }
1
+ export type ResultSuccess<V = any> = {
2
+ success: true
3
+ value: V
4
+ reason?: undefined
49
5
  }
50
6
 
51
7
  /**
52
- * Creates a failed result wrapping the given error reason.
8
+ * Represents a failed result containing an error reason.
53
9
  *
54
10
  * @typeParam E - The type of the error reason
55
- * @param reason - The error reason to wrap
56
- * @returns {ResultFailure} A failure result containing the error
57
- *
58
- * @example
59
- * ```typescript
60
- * const result = failure(new Error('Something went wrong'))
61
- * console.log(result.success) // false
62
- * console.log(result.reason.message) // "Something went wrong"
63
- * ```
64
- */
65
- /*@__NO_SIDE_EFFECTS__*/
66
- export function failure<E>(reason: E): ResultFailure<E> {
67
- return { success: false, reason }
68
- }
69
-
70
- /**
71
- * Extracts the error reason from a failure result.
72
- *
73
- * @typeParam T - The type of the error reason
74
- * @param result - A failure result
75
- * @returns {T} The error reason
76
- *
77
- * @example
78
- * ```typescript
79
- * const result = failure(new Error('oops'))
80
- * const error = failureReason(result)
81
- * console.log(error.message) // "oops"
82
- * ```
83
- */
84
- /*@__NO_SIDE_EFFECTS__*/
85
- export function failureReason<T>(result: ResultFailure<T>): T {
86
- return result.reason
87
- }
88
-
89
- /**
90
- * Extracts the value from a success result.
91
- *
92
- * @typeParam T - The type of the success value
93
- * @param result - A success result
94
- * @returns {T} The success value
95
- *
96
- * @example
97
- * ```typescript
98
- * const result = success(42)
99
- * const value = successValue(result)
100
- * console.log(value) // 42
101
- * ```
102
- */
103
- /*@__NO_SIDE_EFFECTS__*/
104
- export function successValue<T>(result: ResultSuccess<T>): T {
105
- return result.value
106
- }
107
-
108
- /**
109
- * Catches any error and wraps it in a {@link ResultFailure<Error>}.
110
- *
111
- * @param err - The error to catch.
112
- * @returns {ResultFailure} A failure result containing the error.
113
- * @example
114
- *
115
- * ```ts
116
- * declare function someFunction(): Promise<string>
117
- *
118
- * const result = await someFunction().then(success, catchall)
119
- * if (result.success) {
120
- * console.log(result.value) // string
121
- * } else {
122
- * console.error(result.reason instanceof Error) // true
123
- * console.error(result.reason.message) // string
124
- * }
125
- * ```
126
- */
127
- /*@__NO_SIDE_EFFECTS__*/
128
- export function catchall(err: unknown): ResultFailure<Error> {
129
- if (err instanceof Error) return failure(err)
130
- return failure(new Error('Unknown error', { cause: err }))
131
- }
132
-
133
- /**
134
- * Creates a catcher function for the given constructor that wraps caught errors
135
- * in a {@link ResultFailure}.
136
- *
137
- * @example
138
- *
139
- * ```ts
140
- * class FooError extends Error {}
141
- * class BarError extends Error {}
142
- *
143
- * declare function someFunction(): Promise<string>
144
- *
145
- * const result = await someFunction()
146
- * .then(success)
147
- * .catch(createCatcher(FooError))
148
- * .catch(createCatcher(BarError))
149
- *
150
- * if (result.success) {
151
- * console.log(result.value) // string
152
- * } else {
153
- * console.error(result.reason) // FooError | BarError
154
- * }
155
11
  */
156
- /*@__NO_SIDE_EFFECTS__*/
157
- export function createCatcher<T>(Ctor: new (...args: any[]) => T) {
158
- return (err: unknown): ResultFailure<T> => {
159
- if (err instanceof Ctor) return failure(err)
160
- throw err
161
- }
12
+ export type ResultFailure<E = Error> = {
13
+ success: false
14
+ reason: E
162
15
  }
@@ -1,6 +1,6 @@
1
1
  import { LexError } from '@atproto/lex-data'
2
2
  import { arrayAgg } from '../util/array-agg.js'
3
- import { ResultFailure, failureReason } from './result.js'
3
+ import { ResultFailure } from './result.js'
4
4
  import {
5
5
  Issue,
6
6
  IssueInvalidType,
@@ -82,38 +82,6 @@ export class LexValidationError
82
82
  issues: this.issues.map((issue) => issue.toJSON()),
83
83
  }
84
84
  }
85
-
86
- /**
87
- * Creates a validation error by combining multiple validation failures.
88
- *
89
- * This is useful when validating against multiple possible schemas (e.g., unions)
90
- * and all branches fail. The resulting error contains issues from all failures.
91
- *
92
- * @param failures - The validation failures to combine
93
- * @returns A single validation error containing all issues from the failures
94
- *
95
- * @example
96
- * ```typescript
97
- * const failures = schemas.map(s => s.safeValidate(data)).filter(r => !r.success)
98
- * if (failures.length === schemas.length) {
99
- * throw LexValidationError.fromFailures(failures)
100
- * }
101
- * ```
102
- */
103
- static fromFailures(
104
- failures: readonly ResultFailure<LexValidationError>[],
105
- ): LexValidationError {
106
- if (failures.length === 1) return failureReason(failures[0])
107
- const issues = failures.flatMap(extractFailureIssues)
108
- return new LexValidationError(issues, {
109
- // Keep the original errors as the cause chain
110
- cause: failures.map(failureReason),
111
- })
112
- }
113
- }
114
-
115
- function extractFailureIssues(result: ResultFailure<LexValidationError>) {
116
- return result.reason.issues
117
85
  }
118
86
 
119
87
  function aggregateIssues(issues: Issue[]): Issue[] {
@@ -1,5 +1,4 @@
1
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2
- import { ResultFailure, ResultSuccess, success } from './result.js'
1
+ import type * as Result from './result.js'
3
2
  import { LexValidationError } from './validation-error.js'
4
3
  import {
5
4
  Issue,
@@ -15,16 +14,16 @@ import {
15
14
  /**
16
15
  * Represents a successful validation result.
17
16
  *
18
- * @typeParam Value - The type of the validated value
17
+ * @typeParam TValue - The type of the validated value
18
+ * @extends Result.ResultSuccess<TValue>
19
19
  */
20
- export type ValidationSuccess<Value = unknown> = ResultSuccess<Value>
20
+ export type ValidationSuccess<TValue = unknown> = Result.ResultSuccess<TValue>
21
21
 
22
22
  /**
23
23
  * Represents a failed validation result containing a {@link LexValidationError}.
24
24
  *
25
- * @extends ResultFailure<LexValidationError>
26
- * @see {@link ResultFailure}
27
25
  * @see {@link LexValidationError}
26
+ * @extends Result.ResultFailure<LexValidationError>
28
27
  */
29
28
  export type ValidationFailure = LexValidationError
30
29
 
@@ -429,24 +428,14 @@ export class ValidationContext {
429
428
  }
430
429
 
431
430
  /**
432
- * Creates a successful validation result with the given value.
431
+ * Helper method to create a successful validation result.
433
432
  *
434
433
  * @typeParam V - The value type
435
434
  * @param value - The validated value
436
435
  * @returns A successful validation result
437
436
  */
438
- success<V>(value: V): ValidationResult<V> {
439
- return success(value)
440
- }
441
-
442
- /**
443
- * Creates a failed validation result with the given error.
444
- *
445
- * @param reason - The validation error
446
- * @returns A failed validation result
447
- */
448
- failure(reason: LexValidationError): ValidationFailure {
449
- return reason
437
+ success<V>(value: V): ValidationSuccess<V> {
438
+ return { success: true, value }
450
439
  }
451
440
 
452
441
  /**
@@ -457,8 +446,8 @@ export class ValidationContext {
457
446
  * @param issue - The validation issue that caused the failure
458
447
  * @returns A failed validation result
459
448
  */
460
- issue(issue: Issue) {
461
- return this.failure(new LexValidationError([...this.issues, issue]))
449
+ issue(issue: Issue): ValidationFailure {
450
+ return new LexValidationError([...this.issues, issue])
462
451
  }
463
452
 
464
453
  /**