···11+# Agent Guidelines for ATP Monorepo
22+33+## Build & Test Commands
44+- Run all tests: `deno test -P`
55+- Run single test file: `deno test -P path/to/file_test.ts`
66+- Run specific test: `deno test -P --filter "test name" path/to/file_test.ts`
77+- Format code: `deno fmt`
88+- Lint code: `deno lint`
99+- Check code: `deno check`
1010+1111+## Code Style
1212+- **NO COMMENTS** unless explicitly requested
1313+- Use JSDoc only for exported types/functions with `@prop`, `@param`, `@returns` tags
1414+- Test files: `*_test.ts` pattern (e.g., `car_test.ts`), use Deno.test(), imports from `@std/assert`
1515+- Types: Explicit types for function parameters/returns, prefer `interface` over `type` for objects
1616+- Error handling: Use custom error classes extending base errors, include `ErrorOptions` with `cause`
1717+- Imports: Use JSR/npm imports from deno.json, absolute imports (e.g., `@atp/crypto`, `@std/assert`)
1818+- Naming: camelCase for vars/functions, PascalCase for classes/types, UPPER_CASE for constants
1919+- Exports: Use `export` directly, re-export from `mod.ts` for public API
2020+- Async: Prefer async/await over promises, use AsyncGenerator for streams
2121+- Formatting: 2 spaces indent, semicolons, trailing commas, 80 char soft limit
+77
README.md
···11+# ATP
22+33+[](https://jsr.io/@atp)
44+55+A suite of TypeScript libraries for the AT Protocol, built on web standards.
66+77+## Overview
88+99+This monorepo provides modular, standards-based TypeScript implementations of core AT Protocol components, based on the @atproto NPM packages.
1010+1111+Each package is designed to work across JavaScript runtimes (Deno, Node.js, Bun, Cloudflare Workers) and can be used independently or together.
1212+1313+## Packages
1414+1515+### [@atp/xrpc-server](./xrpc-server)
1616+Hono-based XRPC server implementation with lexicon validation, authentication, rate limiting, and WebSocket streaming support. Works across JavaScript runtimes with comprehensive error handling and type safety.
1717+1818+### [@atp/xrpc](./xrpc)
1919+XRPC client library for calling AT Protocol services with lexicon schema validation.
2020+2121+2222+### [@atp/sync](./sync)
2323+Tools for syncing data from AT Protocol, including firehose (relay) subscriptions with authentication and filtering.
2424+2525+### [@atp/lex-cli](./lex-cli)
2626+Command-line tool for generating documentation, servers, and clients from AT Protocol lexicon files.
2727+2828+### [@atp/crypto](./crypto)
2929+Cryptographic primitives for AT Protocol supporting P-256 and K-256 (secp256k1) elliptic curves. Includes key generation, signing, verification, DID key serialization, and hashing utilities.
3030+3131+### [@atp/identity](./identity)
3232+Decentralized identity resolution for DIDs and handles. Resolves handles to DIDs, DIDs to DID documents, and provides caching and verification methods.
3333+3434+### [@atp/lexicon](./lexicon)
3535+Validation utilities for AT Protocol lexicons. Validates records, XRPC parameters, inputs, and outputs against lexicon schemas.
3636+3737+### [@atp/repo](./repo)
3838+Repository utilities including the Merkle Search Tree (MST) implementation. Handles signed key/value stores with CBOR-encoded data records, CAR files, and repo synchronization.
3939+4040+### [@atp/syntax](./syntax)
4141+Validation and parsing for AT Protocol string formats including DIDs, handles, NSIDs, AT URIs, TIDs, record keys, and datetimes.
4242+4343+### [@atp/common](./common)
4444+Shared utilities for server-oriented applications, including IPLD handling, streams, async helpers, obfuscation, retry logic, and TID generation.
4545+4646+### [@atp/bytes](./bytes)
4747+Simple `Uint8Array` utilities including allocation, comparison, concatenation, string conversion (with multibase encoding support), and XOR operations. Based on the uint8arrays npm package.
4848+4949+## Installation
5050+5151+Each package can be installed independently from JSR:
5252+5353+```bash
5454+# deno
5555+deno add jsr:@atp/crypto jsr:@atp/xrpc-server
5656+5757+# pnpm 10.9+
5858+pnpm add jsr:@atp/crypto jsr:@atp/identity jsr:@atp/xrpc
5959+6060+# yarn 4.9+
6161+yarn add jsr:@atp/crypto jsr:@atp/identity jsr:@atp/xrpc
6262+6363+# npm, bun, and older versions of yarn or pnpm
6464+npx jsr add @atp/xrpc-server # replace npx with any of yarn dlx, pnpm dlx, or bunx
6565+```
6666+6767+## Development
6868+6969+Ensure you have the latest version of [Deno](https://deno.com/) installed.
7070+7171+```bash
7272+deno test
7373+```
7474+7575+## License
7676+7777+MIT
-13
crypto/types.ts
···11-/**
22- * Creates signatures for messages.
33- *
44- * @prop jwtAlg - The JWT algorithm used for signing.
55- * @prop sign - Returns a signature for the given message bytes.
66- */
71export interface Signer {
82 jwtAlg: string;
93 sign(msg: Uint8Array): Uint8Array;
104}
1151212-/**
1313- * Can create DID keys.
1414- * @prop did - Returns a DID key.
1515- */
166export interface Didable {
177 did(): string;
188}
1992020-/**
2121- * Combines a {@linkcode Signer} and {@linkcode Didable} into a Keypair.
2222- */
2310export interface Keypair extends Signer, Didable {}
24112512/**
+25-1
lexicon/lexicons.ts
···1919import { object as validateObject } from "./validation/complex.ts";
20202121/**
2222- * A collection of compiled lexicons.
2222+ * A collection of compiled lexicon docs with methods for adding,
2323+ * removing, validating, and iterating over them.
2424+ *
2525+ * @example Add and validate a lexicon
2626+ * ```typescript
2727+ * import { Lexicon } from "@atp/lexicon";
2828+ *
2929+ * // create your lexicons collection
3030+ * const lex = new Lexicons()
3131+ *
3232+ * // add your lexicons
3333+ * lex.add({
3434+ * lex: 1,
3535+ * id: 'com.example.post',
3636+ * defs: {
3737+ * // ...
3838+ * }
3939+ * })
4040+ *
4141+ * // validate
4242+ * lex.assertValidRecord('com.example.record', {$type: 'com.example.record', ...})
4343+ * lex.assertValidXrpcParams('com.example.query', {...})
4444+ * lex.assertValidXrpcInput('com.example.procedure', {...})
4545+ * lex.assertValidXrpcOutput('com.example.query', {...})
4646+ * ```
2347 */
2448export class Lexicons implements Iterable<LexiconDoc> {
2549 docs: Map<string, LexiconDoc> = new Map();
+1
lexicon/serialize.ts
···1414 | Array<LexValue>
1515 | { [key: string]: LexValue };
16161717+/** Record of LexValues */
1718export type RepoRecord = Record<string, LexValue>;
18191920// @NOTE avoiding use of check.is() here only because it makes
+22
repo/util.ts
···2222} from "./types.ts";
2323import type { CID } from "multiformats/basics";
24242525+/**
2626+ * Converts a DataDiff of a repo three arrays of RecordWriteDescripts,
2727+ * for creates, updates, and deletes in the diff.
2828+ */
2529export const diffToWriteDescripts = (
2630 diff: DataDiff,
2731): Promise<RecordWriteDescript[]> => {
···5761 ]);
5862};
59636464+/**
6565+ * Ensures that all write operations given are create actions.
6666+ * @throws If any write operation is not a create action.
6767+ */
6068export const ensureCreates = (
6169 descripts: RecordWriteDescript[],
6270): RecordCreateDescript[] => {
···8593 return a.did === b.did && a.version === b.version;
8694};
87959696+/**
9797+ * Generates a signature for an unsigned commit using the provided keypair.
9898+ */
8899export const signCommit = (
89100 unsigned: UnsignedCommit,
90101 keypair: Keypair,
···97108 };
98109};
99110111111+/**
112112+ * Ensures a commit is authenticated by verifying its signature
113113+ * against the provided DID key.
114114+ */
100115export const verifyCommitSig = (
101116 commit: Commit,
102117 didKey: string,
···106121 return crypto.verifySignature(didKey, encoded, sig as Uint8Array);
107122};
108123124124+/**
125125+ * Converts CBOR-encoded bytes to a LexValue using {@linkcode ipldToLex}.
126126+ */
109127export const cborToLex = (val: Uint8Array): LexValue => {
110128 return ipldToLex(cborDecode(val));
111129};
112130131131+/**
132132+ * Converts CBOR-encoded record bytes to a RepoRecord using {@linkcode cborToLex}.
133133+ * Validates that the parsed value is a valid RepoRecord.
134134+ */
113135export const cborToLexRecord = (val: Uint8Array): RepoRecord => {
114136 const parsed = cborToLex(val);
115137 if (!check.is(parsed, schema.map)) {
+5-9
xrpc-server/mod.ts
···11/**
22- * XRPC Server implementation for atproto services.
22+ * # XRPC Server implementation for atproto services.
33 *
44 * This module provides a Hono-based server implementation for atproto's XRPC protocol,
55 * with support for Lexicon schema validation, authentication, rate limiting, and streaming.
···1515 * - Comprehensive error handling with XRPC error types
1616 * - TypeScript-first with complete type definitions
1717 *
1818- * ## Runtime Compatibility
1919- * Works with Deno, Node.js, Bun, Cloudflare Workers, and other JavaScript runtimes
2020- * supported by Hono.
2121- *
2222- * Note: Subscription (WebSocket) endpoints are currently supported only in Deno
2323- * and Cloudflare Workers. On Node.js and Bun, query/procedure routes function
2424- * normally, but subscription endpoints are not yet available (WebSocket upgrades
2525- * will be rejected or unmounted).
1818+ * NOTE: streamMethods (WebSocket streaming endpoints) are currently
1919+ * only supported in Deno and Cloudflare Workers. This doesn't
2020+ * include the client-side StreamConnection function, which can be
2121+ * used in any runtime or browser.
2622 *
2723 * @example Basic server setup with a simple endpoint
2824 * ```ts
+4-14
xrpc-server/tests/subscriptions_test.ts
···451451 for await (const msg of sub) {
452452 const typedMsg = msg as { count: number };
453453 messagesReceived++;
454454- assertEquals(typedMsg.count >= 0, true); // Ensure valid count
454454+ assertEquals(typedMsg.count >= 0, true);
455455456456- // Abort early to avoid lingering sockets/heartbeats; this simulates a reconnect trigger.
457456 if (messagesReceived === 2) {
458457 ac.abort(new Error("test-abort"));
459458 break;
460459 }
461460 }
462461463463- // Allow internal subscription cleanup (heartbeat interval & socket close) to settle
464464- // so Deno's leak detector doesn't flag the pending timer from the generator's wait(0).
465465- await new Promise((r) => setTimeout(r, 0));
462462+ await wait(50);
466463467467- // Ensure we actually received the expected early messages
468464 assertEquals(messagesReceived >= 2, true);
469465 } finally {
470466 await closeServer(httpServer);
···499495 messages.push(typedMsg);
500496 if (typedMsg.count <= 6 && !disconnected) {
501497 disconnected = true;
502502- // Abort and immediately break to ensure iterator finalizer runs,
503503- // preventing lingering heartbeat intervals / WebSocket reads.
504498 abortController.abort(new Error("Oops!"));
505499 break;
506500 }
507501 }
508502 } catch (err) {
509503 error = err;
510510- } finally {
511511- // Give the subscription cleanup a microtask + tick to run.
512512- await new Promise((r) => setTimeout(r, 0));
513504 }
514505515515- // The subscription may terminate cleanly or throw - either is acceptable
506506+ await wait(50);
507507+516508 if (error) {
517509 assertEquals(error instanceof Error, true);
518510 assertEquals((error as Error).message, "Oops!");
519511 }
520520- // Ensure abort actually happened
521512 assertEquals(abortController.signal.aborted, true);
522522- // Ensure we received at least one message before abort
523513 assertEquals(messages.length > 0, true);
524514 } finally {
525515 await closeServer(httpServer);