A plain JavaScript validator for AT Protocol lexicon schemas
1# Comparison with @atproto/lexicon
2
3This document describes the behavioral differences between lexicon.js and @atproto/lexicon (the official TypeScript implementation).
4
5## Summary
6
7When comparing against @atproto/lexicon, there are ~44 behavioral differences. These fall into several categories.
8
9---
10
11## Category 1: Schema Constraint Validation
12
13**Our behavior:** Validate that schema constraints are logically valid.
14**@atproto behavior:** Does not perform these checks.
15
16| Test | What we check |
17|------|---------------|
18| `string-invalid-length-constraints` | minLength <= maxLength |
19| `string-invalid-negative-minLength` | minLength >= 0 |
20| `string-invalid-negative-maxLength` | maxLength >= 0 |
21| `string-invalid-negative-minGraphemes` | minGraphemes >= 0 |
22| `string-invalid-negative-maxGraphemes` | maxGraphemes >= 0 |
23| `integer-invalid-range-constraints` | minimum <= maximum |
24| `array-invalid-length-constraints` | minLength <= maxLength |
25| `bytes-invalid-max-less-than-min` | minLength <= maxLength |
26| `bytes-invalid-negative-min` | minLength >= 0 |
27| `bytes-invalid-negative-max` | maxLength >= 0 |
28| `blob-invalid-mime-no-slash` | accept MIME types have / |
29| `blob-invalid-partial-wildcard` | accept patterns are valid |
30| `blob-invalid-zero-maxsize` | maxSize > 0 |
31| `object-invalid-nullable-not-in-properties` | nullable fields exist in properties |
32
33**Rationale:** Catching invalid schemas early prevents runtime confusion.
34
35---
36
37## Category 2: Inline Type Definitions
38
39**Our behavior:** Allow inline object/union type definitions in schemas.
40**@atproto behavior:** Requires all complex types to be defined as named refs.
41
42| Test | What we allow |
43|------|---------------|
44| `object-valid-nested` | `{ type: 'object', properties: { inner: { type: 'object', ... } } }` |
45| `array-valid-object-items` | `{ type: 'array', items: { type: 'object', ... } }` |
46| `array-valid-nested-array` | `{ type: 'array', items: { type: 'array', ... } }` |
47| `union-valid-open-empty-refs` | `{ type: 'union', refs: [] }` |
48| `union-valid-with-refs` | Union with local refs |
49| `union-valid-closed-with-refs` | Closed union with refs |
50| `ref-valid-local` | Local ref `#defName` |
51| `params-valid-*` (7 tests) | Various params configurations |
52| `subscription-valid-*` (3 tests) | Various subscription configurations |
53
54**Rationale:** The [Lexicon spec](https://atproto.com/specs/lexicon) explicitly allows object properties to contain "any Lexicon field type" including nested objects, arrays, refs, and unions.
55
56---
57
58## Category 3: Blob Data Constraint Validation
59
60**Our behavior:** Validate blob constraints at data validation time.
61**@atproto behavior:** Does not validate blob constraints.
62
63| Test | What we validate |
64|------|------------------|
65| `blob-data-invalid-unaccepted-mime` | Blob MIME type matches schema `accept` |
66| `blob-data-invalid-exceeds-maxSize` | Blob size <= schema `maxSize` |
67
68**Rationale:** Schema constraints should be enforced at validation time.
69
70---
71
72## Category 4: Token Type Support
73
74**Our behavior:** Support token type.
75**@atproto behavior:** Throws "Unexpected lexicon type: token".
76
77| Test |
78|------|
79| `token-data-valid-simple-string` |
80| `token-data-valid-local-ref` |
81
82**Rationale:** Token is a valid lexicon type per the spec.
83
84---
85
86## Category 5: Unknown Type Validation
87
88**Our behavior:** Reject structured types (arrays, blob objects, bytes objects) for unknown type.
89**@atproto behavior:** Accepts these values.
90
91| Test | What we reject |
92|------|----------------|
93| `unknown-data-invalid-array` | Arrays for unknown type |
94| `unknown-data-invalid-bytes-object` | `{ $bytes: ... }` objects |
95| `unknown-data-invalid-blob-object` | `{ $type: 'blob', ... }` objects |
96
97**Rationale:** Unknown should be primitive JSON values, not complex IPLD types.
98
99---
100
101## Category 6: Format Validation - Handles
102
103**Our behavior:** Reject reserved TLDs (.local, .localhost).
104**@atproto behavior:** Accepts these handles.
105
106| Test | Handle value |
107|------|--------------|
108| `handle-invalid-local-tld` | `user.local` |
109| `handle-invalid-localhost-tld` | `app.localhost` |
110
111**Rationale:** The [AT Protocol Handle spec](https://atproto.com/specs/handle) explicitly lists `.local` and `.localhost` as disallowed TLDs.
112
113---
114
115## Category 7: Format Validation - CID
116
117**Our behavior:** Reject CIDv0, require CIDv1.
118**@atproto behavior:** Accepts CIDv0.
119
120| Test | CID value |
121|------|-----------|
122| `cid-invalid-qmb-prefix` | `QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR` (CIDv0) |
123
124**Rationale:** The [AT Protocol Data Model spec](https://atproto.com/specs/data-model) lists only CIDv1 as a "blessed" format.
125
126---
127
128## Category 8: Ref Type in Data Validation
129
130**Our behavior:** Support ref resolution in data validation.
131**@atproto behavior:** Throws "Unexpected lexicon type: ref".
132
133| Test |
134|------|
135| `ref-data-valid-nested-chain` |
136
137**Rationale:** Refs should be resolvable during data validation.
138
139---
140
141## Design Philosophy
142
143lexicon.js prioritizes:
144
1451. **Spec compliance** - We follow the AT Protocol specs for handles, CIDs, and other formats
1462. **Strict schema validation** - We catch invalid schemas early (constraint validation)
1473. **Flexible schema authoring** - We allow inline types as the spec permits
1484. **Full type support** - We support token and ref types that @atproto/lexicon doesn't handle