A plain JavaScript validator for AT Protocol lexicon schemas

Comparison with @atproto/lexicon#

This document describes the behavioral differences between lexicon.js and @atproto/lexicon (the official TypeScript implementation).

Summary#

When comparing against @atproto/lexicon, there are ~44 behavioral differences. These fall into several categories.


Category 1: Schema Constraint Validation#

Our behavior: Validate that schema constraints are logically valid. @atproto behavior: Does not perform these checks.

Test What we check
string-invalid-length-constraints minLength <= maxLength
string-invalid-negative-minLength minLength >= 0
string-invalid-negative-maxLength maxLength >= 0
string-invalid-negative-minGraphemes minGraphemes >= 0
string-invalid-negative-maxGraphemes maxGraphemes >= 0
integer-invalid-range-constraints minimum <= maximum
array-invalid-length-constraints minLength <= maxLength
bytes-invalid-max-less-than-min minLength <= maxLength
bytes-invalid-negative-min minLength >= 0
bytes-invalid-negative-max maxLength >= 0
blob-invalid-mime-no-slash accept MIME types have /
blob-invalid-partial-wildcard accept patterns are valid
blob-invalid-zero-maxsize maxSize > 0
object-invalid-nullable-not-in-properties nullable fields exist in properties

Rationale: Catching invalid schemas early prevents runtime confusion.


Category 2: Inline Type Definitions#

Our behavior: Allow inline object/union type definitions in schemas. @atproto behavior: Requires all complex types to be defined as named refs.

Test What we allow
object-valid-nested { type: 'object', properties: { inner: { type: 'object', ... } } }
array-valid-object-items { type: 'array', items: { type: 'object', ... } }
array-valid-nested-array { type: 'array', items: { type: 'array', ... } }
union-valid-open-empty-refs { type: 'union', refs: [] }
union-valid-with-refs Union with local refs
union-valid-closed-with-refs Closed union with refs
ref-valid-local Local ref #defName
params-valid-* (7 tests) Various params configurations
subscription-valid-* (3 tests) Various subscription configurations

Rationale: The Lexicon spec explicitly allows object properties to contain "any Lexicon field type" including nested objects, arrays, refs, and unions.


Category 3: Blob Data Constraint Validation#

Our behavior: Validate blob constraints at data validation time. @atproto behavior: Does not validate blob constraints.

Test What we validate
blob-data-invalid-unaccepted-mime Blob MIME type matches schema accept
blob-data-invalid-exceeds-maxSize Blob size <= schema maxSize

Rationale: Schema constraints should be enforced at validation time.


Category 4: Token Type Support#

Our behavior: Support token type. @atproto behavior: Throws "Unexpected lexicon type: token".

Test
token-data-valid-simple-string
token-data-valid-local-ref

Rationale: Token is a valid lexicon type per the spec.


Category 5: Unknown Type Validation#

Our behavior: Reject structured types (arrays, blob objects, bytes objects) for unknown type. @atproto behavior: Accepts these values.

Test What we reject
unknown-data-invalid-array Arrays for unknown type
unknown-data-invalid-bytes-object { $bytes: ... } objects
unknown-data-invalid-blob-object { $type: 'blob', ... } objects

Rationale: Unknown should be primitive JSON values, not complex IPLD types.


Category 6: Format Validation - Handles#

Our behavior: Reject reserved TLDs (.local, .localhost). @atproto behavior: Accepts these handles.

Test Handle value
handle-invalid-local-tld user.local
handle-invalid-localhost-tld app.localhost

Rationale: The AT Protocol Handle spec explicitly lists .local and .localhost as disallowed TLDs.


Category 7: Format Validation - CID#

Our behavior: Reject CIDv0, require CIDv1. @atproto behavior: Accepts CIDv0.

Test CID value
cid-invalid-qmb-prefix QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR (CIDv0)

Rationale: The AT Protocol Data Model spec lists only CIDv1 as a "blessed" format.


Category 8: Ref Type in Data Validation#

Our behavior: Support ref resolution in data validation. @atproto behavior: Throws "Unexpected lexicon type: ref".

Test
ref-data-valid-nested-chain

Rationale: Refs should be resolvable during data validation.


Design Philosophy#

lexicon.js prioritizes:

  1. Spec compliance - We follow the AT Protocol specs for handles, CIDs, and other formats
  2. Strict schema validation - We catch invalid schemas early (constraint validation)
  3. Flexible schema authoring - We allow inline types as the spec permits
  4. Full type support - We support token and ref types that @atproto/lexicon doesn't handle