···1# prototypey
23+## 0.3.8
4+5+### Patch Changes
6+7+- 7a19f90: releast changes from dep updates and #66
8+9+## 0.3.7
10+11+### Patch Changes
12+13+- e75de54: update docs
14+15+## 0.3.6
16+17+### Patch Changes
18+19+- 2b55317: fix exported type bug
20+21+## 0.3.5
22+23+### Patch Changes
24+25+- abb4b31: updated docs
26+27+## 0.3.4
28+29+### Patch Changes
30+31+- 3329654: fix for type of record key and description hint
32+33+## 0.3.3
34+35+### Patch Changes
36+37+- e7a7497: documentation update
38+39+## 0.3.2
40+41+### Patch Changes
42+43+- 6a6cae5: update deps
44+45## 0.3.1
4647### Patch Changes
+14-12
packages/prototypey/README.md
···23A fully-featured sdk for developing lexicons with typescript.
40000000000005## Installation
67```bash
···207npm run lexicon:import
208```
209210-## State of the Project
211-212-**Done:**
213-214-- Full atproto spec lexicon authoring with in IDE docs & hints for each attribute (ts => json)
215-- CLI generates json from ts definitions
216-- CLI generates ts from json definitions
217-- Inferrance of valid type from full lexicon definition
218- - the really cool part of this is that it fills in the refs from the defs all at the type level
219-- `lx.lexicon(...).validate(data)` for validating data using `@atproto/lexicon` and your lexicon definitions
220-- `fromJSON()` helper for creating lexicons directly from JSON objects with full type inference
221222Please give any and all feedback. I've not really written many lexicons much myself yet, so this project is at a point of "well I think this makes sense". Both the [issues page](https://github.com/tylersayshi/prototypey/issues) and [discussions](https://github.com/tylersayshi/prototypey/discussions) are open and ready for y'all ๐.
223224**Call For Contribution:**
225226-- We need library art! Please reach out if you'd be willing to contribute some drawings or anything :)
···23A fully-featured sdk for developing lexicons with typescript.
45+Below this is the docs and features of the library. If you'd like the story for why prototypey exists and what it's good for: [that's published here](https://notes.tylur.dev/3m5a3do4eus2w)
6+7+## Features
8+9+- atproto spec lexicon authoring with in IDE docs & hints for each attribute (ts => json)
10+- CLI to generate json from ts definitions
11+- CLI to generate ts from json definitions
12+- inference of usage type from full lexicon definition
13+ - the really cool part of this is that it fills in the refs from the defs all at the type level
14+- `lx.lexicon(...).validate(data)` for validating data using `@atproto/lexicon`
15+- `fromJSON()` helper for creating lexicons directly from JSON objects with full type inference
16+17## Installation
1819```bash
···219npm run lexicon:import
220```
221222+---
0000000000223224Please give any and all feedback. I've not really written many lexicons much myself yet, so this project is at a point of "well I think this makes sense". Both the [issues page](https://github.com/tylersayshi/prototypey/issues) and [discussions](https://github.com/tylersayshi/prototypey/discussions) are open and ready for y'all ๐.
225226**Call For Contribution:**
227228+We need library art! Please reach out if you'd be willing to contribute some drawings or anything :)
+68-49
packages/prototypey/core/lib.ts
···33 * Common options available for lexicon items.
34 * @see https://atproto.com/specs/lexicon#string-formats
35 */
36-interface LexiconItemCommonOptions {
37 /** Indicates this field must be provided */
38 required?: boolean;
39 /** Indicates this field can be explicitly set to null */
40 nullable?: boolean;
41-}
004243/**
44 * Base interface for all lexicon items.
45 * @see https://atproto.com/specs/lexicon#overview-of-types
46 */
47-interface LexiconItem extends LexiconItemCommonOptions {
48 type: LexiconType;
49-}
5051/**
52 * Definition in a lexicon namespace.
53 * @see https://atproto.com/specs/lexicon#lexicon-document
54 */
55-interface Def {
56 type: LexiconType;
57-}
5859/**
60 * Lexicon namespace document structure.
61 * @see https://atproto.com/specs/lexicon#lexicon-document
62 */
63-interface LexiconNamespace {
64 /** Namespaced identifier (NSID) for this lexicon */
65 id: string;
66 /** Named definitions within this namespace */
67 defs: Record<string, Def>;
68-}
6970/**
71 * String type options.
72 * @see https://atproto.com/specs/lexicon#string
73 */
74-interface StringOptions extends LexiconItemCommonOptions {
75 /**
76 * Semantic string format constraint.
77 * @see https://atproto.com/specs/lexicon#string-formats
···104 default?: string;
105 /** Fixed, unchangeable value */
106 const?: string;
107-}
108109/**
110 * Boolean type options.
111 * @see https://atproto.com/specs/lexicon#boolean
112 */
113-interface BooleanOptions extends LexiconItemCommonOptions {
114 /** Default value if not provided */
115 default?: boolean;
116 /** Fixed, unchangeable value */
117 const?: boolean;
118-}
119120/**
121 * Integer type options.
122 * @see https://atproto.com/specs/lexicon#integer
123 */
124-interface IntegerOptions extends LexiconItemCommonOptions {
125 /** Minimum allowed value (inclusive) */
126 minimum?: number;
127 /** Maximum allowed value (inclusive) */
···132 default?: number;
133 /** Fixed, unchangeable value */
134 const?: number;
135-}
136137/**
138 * Bytes type options for arbitrary byte arrays.
139 * @see https://atproto.com/specs/lexicon#bytes
140 */
141-interface BytesOptions extends LexiconItemCommonOptions {
142 /** Minimum byte array length */
143 minLength?: number;
144 /** Maximum byte array length */
145 maxLength?: number;
146-}
147148/**
149 * Blob type options for binary data with MIME types.
150 * @see https://atproto.com/specs/lexicon#blob
151 */
152-interface BlobOptions extends LexiconItemCommonOptions {
153 /** Allowed MIME types (e.g., ["image/png", "image/jpeg"]) */
154 accept?: string[];
155 /** Maximum blob size in bytes */
156 maxSize?: number;
157-}
158159/**
160 * Array type options.
161 * @see https://atproto.com/specs/lexicon#array
162 */
163-interface ArrayOptions extends LexiconItemCommonOptions {
164 /** Minimum array length */
165 minLength?: number;
166 /** Maximum array length */
167 maxLength?: number;
168-}
169170/**
171 * Record type options for repository records.
172 * @see https://atproto.com/specs/lexicon#record
173 */
174-interface RecordOptions {
175- /** Record key strategy: "self" for self-describing or "tid" for timestamp IDs */
176- key: "self" | "tid";
000177 /** Object schema defining the record structure */
178 record: { type: "object" };
179 /** Human-readable description */
180 description?: string;
181-}
182183/**
184 * Union type options for multiple possible types.
185 * @see https://atproto.com/specs/lexicon#union
186 */
187-interface UnionOptions extends LexiconItemCommonOptions {
188 /** If true, only listed refs are allowed; if false, additional types may be added */
189 closed?: boolean;
190-}
191192/**
193 * Map of property names to their lexicon item definitions.
···200 }
201>;
202000000000203type RequiredKeys<T> = {
204 [K in keyof T]: T[K] extends { required: true } ? K : never;
205}[keyof T];
···212 * Resulting object schema with required and nullable fields extracted.
213 * @see https://atproto.com/specs/lexicon#object
214 */
215-type ObjectResult<T extends ObjectProperties> = {
216 type: "object";
217 /** Property definitions */
218 properties: {
···225 : { required: UnionToTuple<RequiredKeys<T>> }) &
226 ([NullableKeys<T>] extends [never]
227 ? {}
228- : { nullable: UnionToTuple<NullableKeys<T>> });
0229230/**
231 * Map of parameter names to their lexicon item definitions.
···251 * HTTP request or response body schema.
252 * @see https://atproto.com/specs/lexicon#http-endpoints
253 */
254-interface BodySchema {
255 /** MIME type encoding (typically "application/json") */
256 encoding: "application/json" | (string & {});
257 /** Human-readable description */
258 description?: string;
259 /** Object schema defining the body structure */
260 schema?: ObjectResult<ObjectProperties>;
261-}
262263/**
264 * Error definition for HTTP endpoints.
265 * @see https://atproto.com/specs/lexicon#http-endpoints
266 */
267-interface ErrorDef {
268 /** Error name/code */
269 name: string;
270 /** Human-readable error description */
271 description?: string;
272-}
273274/**
275 * Query endpoint options (HTTP GET).
276 * @see https://atproto.com/specs/lexicon#query
277 */
278-interface QueryOptions {
279 /** Human-readable description */
280 description?: string;
281 /** Query string parameters */
···284 output?: BodySchema;
285 /** Possible error responses */
286 errors?: ErrorDef[];
287-}
288289/**
290 * Procedure endpoint options (HTTP POST).
291 * @see https://atproto.com/specs/lexicon#procedure
292 */
293-interface ProcedureOptions {
294 /** Human-readable description */
295 description?: string;
296 /** Query string parameters */
···301 output?: BodySchema;
302 /** Possible error responses */
303 errors?: ErrorDef[];
304-}
305306/**
307 * WebSocket message schema for subscriptions.
308 * @see https://atproto.com/specs/lexicon#subscription
309 */
310-interface MessageSchema {
311 /** Human-readable description */
312 description?: string;
313 /** Union of possible message types */
314 schema: { type: "union"; refs: readonly string[] };
315-}
316317/**
318 * Subscription endpoint options (WebSocket).
319 * @see https://atproto.com/specs/lexicon#subscription
320 */
321-interface SubscriptionOptions {
322 /** Human-readable description */
323 description?: string;
324 /** Query string parameters */
···327 message?: MessageSchema;
328 /** Possible error responses */
329 errors?: ErrorDef[];
330-}
331332/**
333 * Public interface for Lexicon to avoid exposing private implementation details
334 */
335-export interface LexiconSchema<T extends LexiconNamespace> {
336 json: T;
337 "~infer": Infer<{ json: T }>;
338 validate(
339 data: unknown,
340 def?: keyof T["defs"],
341 ): ValidationResult<Infer<{ json: T }>>;
342-}
343344class Lexicon<T extends LexiconNamespace> implements LexiconSchema<T> {
345 public json: T;
···524 * Creates an object type with defined properties.
525 * @see https://atproto.com/specs/lexicon#object
526 */
527- object<T extends ObjectProperties>(options: T): ObjectResult<T> {
528- const required = Object.keys(options).filter(
529- (key) => "required" in options[key] && options[key].required,
000530 );
531- const nullable = Object.keys(options).filter(
532- (key) => "nullable" in options[key] && options[key].nullable,
533 );
534 const result: Record<string, unknown> = {
535 type: "object",
536- properties: options,
0537 };
538 if (required.length > 0) {
539 result.required = required;
···541 if (nullable.length > 0) {
542 result.nullable = nullable;
543 }
544- return result as ObjectResult<T>;
545 },
546 /**
547 * Creates a params type for query string parameters.
···33 * Common options available for lexicon items.
34 * @see https://atproto.com/specs/lexicon#string-formats
35 */
36+type LexiconItemCommonOptions = {
37 /** Indicates this field must be provided */
38 required?: boolean;
39 /** Indicates this field can be explicitly set to null */
40 nullable?: boolean;
41+ /** Human-readable description */
42+ description?: string;
43+};
4445/**
46 * Base interface for all lexicon items.
47 * @see https://atproto.com/specs/lexicon#overview-of-types
48 */
49+type LexiconItem = LexiconItemCommonOptions & {
50 type: LexiconType;
51+};
5253/**
54 * Definition in a lexicon namespace.
55 * @see https://atproto.com/specs/lexicon#lexicon-document
56 */
57+type Def = {
58 type: LexiconType;
59+};
6061/**
62 * Lexicon namespace document structure.
63 * @see https://atproto.com/specs/lexicon#lexicon-document
64 */
65+type LexiconNamespace = {
66 /** Namespaced identifier (NSID) for this lexicon */
67 id: string;
68 /** Named definitions within this namespace */
69 defs: Record<string, Def>;
70+};
7172/**
73 * String type options.
74 * @see https://atproto.com/specs/lexicon#string
75 */
76+type StringOptions = LexiconItemCommonOptions & {
77 /**
78 * Semantic string format constraint.
79 * @see https://atproto.com/specs/lexicon#string-formats
···106 default?: string;
107 /** Fixed, unchangeable value */
108 const?: string;
109+};
110111/**
112 * Boolean type options.
113 * @see https://atproto.com/specs/lexicon#boolean
114 */
115+type BooleanOptions = LexiconItemCommonOptions & {
116 /** Default value if not provided */
117 default?: boolean;
118 /** Fixed, unchangeable value */
119 const?: boolean;
120+};
121122/**
123 * Integer type options.
124 * @see https://atproto.com/specs/lexicon#integer
125 */
126+type IntegerOptions = LexiconItemCommonOptions & {
127 /** Minimum allowed value (inclusive) */
128 minimum?: number;
129 /** Maximum allowed value (inclusive) */
···134 default?: number;
135 /** Fixed, unchangeable value */
136 const?: number;
137+};
138139/**
140 * Bytes type options for arbitrary byte arrays.
141 * @see https://atproto.com/specs/lexicon#bytes
142 */
143+type BytesOptions = LexiconItemCommonOptions & {
144 /** Minimum byte array length */
145 minLength?: number;
146 /** Maximum byte array length */
147 maxLength?: number;
148+};
149150/**
151 * Blob type options for binary data with MIME types.
152 * @see https://atproto.com/specs/lexicon#blob
153 */
154+type BlobOptions = LexiconItemCommonOptions & {
155 /** Allowed MIME types (e.g., ["image/png", "image/jpeg"]) */
156 accept?: string[];
157 /** Maximum blob size in bytes */
158 maxSize?: number;
159+};
160161/**
162 * Array type options.
163 * @see https://atproto.com/specs/lexicon#array
164 */
165+type ArrayOptions = LexiconItemCommonOptions & {
166 /** Minimum array length */
167 minLength?: number;
168 /** Maximum array length */
169 maxLength?: number;
170+};
171172/**
173 * Record type options for repository records.
174 * @see https://atproto.com/specs/lexicon#record
175 */
176+type RecordOptions = {
177+ /**
178+ * Record key strategy: "self" for self-describing or "tid" for timestamp IDs
179+ * @see https://atproto.com/specs/record-key
180+ */
181+ key: string;
182 /** Object schema defining the record structure */
183 record: { type: "object" };
184 /** Human-readable description */
185 description?: string;
186+};
187188/**
189 * Union type options for multiple possible types.
190 * @see https://atproto.com/specs/lexicon#union
191 */
192+type UnionOptions = LexiconItemCommonOptions & {
193 /** If true, only listed refs are allowed; if false, additional types may be added */
194 closed?: boolean;
195+};
196197/**
198 * Map of property names to their lexicon item definitions.
···205 }
206>;
207208+/**
209+ * Object-level options (not property-level).
210+ * @see https://atproto.com/specs/lexicon#object
211+ */
212+type ObjectOptions = {
213+ /** Human-readable description of the object */
214+ description?: string;
215+};
216+217type RequiredKeys<T> = {
218 [K in keyof T]: T[K] extends { required: true } ? K : never;
219}[keyof T];
···226 * Resulting object schema with required and nullable fields extracted.
227 * @see https://atproto.com/specs/lexicon#object
228 */
229+type ObjectResult<T extends ObjectProperties, O extends ObjectOptions = {}> = {
230 type: "object";
231 /** Property definitions */
232 properties: {
···239 : { required: UnionToTuple<RequiredKeys<T>> }) &
240 ([NullableKeys<T>] extends [never]
241 ? {}
242+ : { nullable: UnionToTuple<NullableKeys<T>> }) &
243+ O;
244245/**
246 * Map of parameter names to their lexicon item definitions.
···266 * HTTP request or response body schema.
267 * @see https://atproto.com/specs/lexicon#http-endpoints
268 */
269+type BodySchema = {
270 /** MIME type encoding (typically "application/json") */
271 encoding: "application/json" | (string & {});
272 /** Human-readable description */
273 description?: string;
274 /** Object schema defining the body structure */
275 schema?: ObjectResult<ObjectProperties>;
276+};
277278/**
279 * Error definition for HTTP endpoints.
280 * @see https://atproto.com/specs/lexicon#http-endpoints
281 */
282+type ErrorDef = {
283 /** Error name/code */
284 name: string;
285 /** Human-readable error description */
286 description?: string;
287+};
288289/**
290 * Query endpoint options (HTTP GET).
291 * @see https://atproto.com/specs/lexicon#query
292 */
293+type QueryOptions = {
294 /** Human-readable description */
295 description?: string;
296 /** Query string parameters */
···299 output?: BodySchema;
300 /** Possible error responses */
301 errors?: ErrorDef[];
302+};
303304/**
305 * Procedure endpoint options (HTTP POST).
306 * @see https://atproto.com/specs/lexicon#procedure
307 */
308+type ProcedureOptions = {
309 /** Human-readable description */
310 description?: string;
311 /** Query string parameters */
···316 output?: BodySchema;
317 /** Possible error responses */
318 errors?: ErrorDef[];
319+};
320321/**
322 * WebSocket message schema for subscriptions.
323 * @see https://atproto.com/specs/lexicon#subscription
324 */
325+type MessageSchema = {
326 /** Human-readable description */
327 description?: string;
328 /** Union of possible message types */
329 schema: { type: "union"; refs: readonly string[] };
330+};
331332/**
333 * Subscription endpoint options (WebSocket).
334 * @see https://atproto.com/specs/lexicon#subscription
335 */
336+type SubscriptionOptions = {
337 /** Human-readable description */
338 description?: string;
339 /** Query string parameters */
···342 message?: MessageSchema;
343 /** Possible error responses */
344 errors?: ErrorDef[];
345+};
346347/**
348 * Public interface for Lexicon to avoid exposing private implementation details
349 */
350+export type LexiconSchema<T extends LexiconNamespace> = {
351 json: T;
352 "~infer": Infer<{ json: T }>;
353 validate(
354 data: unknown,
355 def?: keyof T["defs"],
356 ): ValidationResult<Infer<{ json: T }>>;
357+};
358359class Lexicon<T extends LexiconNamespace> implements LexiconSchema<T> {
360 public json: T;
···539 * Creates an object type with defined properties.
540 * @see https://atproto.com/specs/lexicon#object
541 */
542+ object<T extends ObjectProperties, O extends ObjectOptions>(
543+ properties: T,
544+ options?: O,
545+ ): ObjectResult<T, O> {
546+ const required = Object.keys(properties).filter(
547+ (key) => "required" in properties[key] && properties[key].required,
548 );
549+ const nullable = Object.keys(properties).filter(
550+ (key) => "nullable" in properties[key] && properties[key].nullable,
551 );
552 const result: Record<string, unknown> = {
553 type: "object",
554+ properties,
555+ ...options,
556 };
557 if (required.length > 0) {
558 result.required = required;
···560 if (nullable.length > 0) {
561 result.nullable = nullable;
562 }
563+ return result as ObjectResult<T, O>;
564 },
565 /**
566 * Creates a params type for query string parameters.
+1
packages/prototypey/core/main.ts
···1export { lx, fromJSON } from "./lib.ts";
2export { type Infer } from "./infer.ts";
0
···1export { lx, fromJSON } from "./lib.ts";
2export { type Infer } from "./infer.ts";
3+export type * from "@atproto/lexicon";
···183 });
184});
1850000000000000000000000000000000000000000000000000000000000000000000000000186test("lx.token() with interaction event", () => {
187 const result = lx.token(
188 "Request that less content like the given feed item be shown in the feed",