Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

Select the types of activity you want to include in your feed.

add readme, agents.md

+158 -37
+2
.github/workflows/check.yml
··· 21 21 uses: denoland/setup-deno@v2 22 22 with: 23 23 cache: true 24 + deno-version: 2.x 25 + 24 26 - name: Lint 25 27 run: deno lint 26 28
+1
.github/workflows/publish.yml
··· 21 21 uses: denoland/setup-deno@v2 22 22 with: 23 23 cache: true 24 + deno-version: 2.x 24 25 25 26 - name: Lint 26 27 run: deno lint
+21
AGENTS.md
··· 1 + # Agent Guidelines for ATP Monorepo 2 + 3 + ## Build & Test Commands 4 + - Run all tests: `deno test -P` 5 + - Run single test file: `deno test -P path/to/file_test.ts` 6 + - Run specific test: `deno test -P --filter "test name" path/to/file_test.ts` 7 + - Format code: `deno fmt` 8 + - Lint code: `deno lint` 9 + - Check code: `deno check` 10 + 11 + ## Code Style 12 + - **NO COMMENTS** unless explicitly requested 13 + - Use JSDoc only for exported types/functions with `@prop`, `@param`, `@returns` tags 14 + - Test files: `*_test.ts` pattern (e.g., `car_test.ts`), use Deno.test(), imports from `@std/assert` 15 + - Types: Explicit types for function parameters/returns, prefer `interface` over `type` for objects 16 + - Error handling: Use custom error classes extending base errors, include `ErrorOptions` with `cause` 17 + - Imports: Use JSR/npm imports from deno.json, absolute imports (e.g., `@atp/crypto`, `@std/assert`) 18 + - Naming: camelCase for vars/functions, PascalCase for classes/types, UPPER_CASE for constants 19 + - Exports: Use `export` directly, re-export from `mod.ts` for public API 20 + - Async: Prefer async/await over promises, use AsyncGenerator for streams 21 + - Formatting: 2 spaces indent, semicolons, trailing commas, 80 char soft limit
+77
README.md
··· 1 + # ATP 2 + 3 + [![JSR @atp](https://jsr.io/badges/@atp)](https://jsr.io/@atp) 4 + 5 + A suite of TypeScript libraries for the AT Protocol, built on web standards. 6 + 7 + ## Overview 8 + 9 + This monorepo provides modular, standards-based TypeScript implementations of core AT Protocol components, based on the @atproto NPM packages. 10 + 11 + Each package is designed to work across JavaScript runtimes (Deno, Node.js, Bun, Cloudflare Workers) and can be used independently or together. 12 + 13 + ## Packages 14 + 15 + ### [@atp/xrpc-server](./xrpc-server) 16 + 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. 17 + 18 + ### [@atp/xrpc](./xrpc) 19 + XRPC client library for calling AT Protocol services with lexicon schema validation. 20 + 21 + 22 + ### [@atp/sync](./sync) 23 + Tools for syncing data from AT Protocol, including firehose (relay) subscriptions with authentication and filtering. 24 + 25 + ### [@atp/lex-cli](./lex-cli) 26 + Command-line tool for generating documentation, servers, and clients from AT Protocol lexicon files. 27 + 28 + ### [@atp/crypto](./crypto) 29 + 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. 30 + 31 + ### [@atp/identity](./identity) 32 + Decentralized identity resolution for DIDs and handles. Resolves handles to DIDs, DIDs to DID documents, and provides caching and verification methods. 33 + 34 + ### [@atp/lexicon](./lexicon) 35 + Validation utilities for AT Protocol lexicons. Validates records, XRPC parameters, inputs, and outputs against lexicon schemas. 36 + 37 + ### [@atp/repo](./repo) 38 + Repository utilities including the Merkle Search Tree (MST) implementation. Handles signed key/value stores with CBOR-encoded data records, CAR files, and repo synchronization. 39 + 40 + ### [@atp/syntax](./syntax) 41 + Validation and parsing for AT Protocol string formats including DIDs, handles, NSIDs, AT URIs, TIDs, record keys, and datetimes. 42 + 43 + ### [@atp/common](./common) 44 + Shared utilities for server-oriented applications, including IPLD handling, streams, async helpers, obfuscation, retry logic, and TID generation. 45 + 46 + ### [@atp/bytes](./bytes) 47 + Simple `Uint8Array` utilities including allocation, comparison, concatenation, string conversion (with multibase encoding support), and XOR operations. Based on the uint8arrays npm package. 48 + 49 + ## Installation 50 + 51 + Each package can be installed independently from JSR: 52 + 53 + ```bash 54 + # deno 55 + deno add jsr:@atp/crypto jsr:@atp/xrpc-server 56 + 57 + # pnpm 10.9+ 58 + pnpm add jsr:@atp/crypto jsr:@atp/identity jsr:@atp/xrpc 59 + 60 + # yarn 4.9+ 61 + yarn add jsr:@atp/crypto jsr:@atp/identity jsr:@atp/xrpc 62 + 63 + # npm, bun, and older versions of yarn or pnpm 64 + npx jsr add @atp/xrpc-server # replace npx with any of yarn dlx, pnpm dlx, or bunx 65 + ``` 66 + 67 + ## Development 68 + 69 + Ensure you have the latest version of [Deno](https://deno.com/) installed. 70 + 71 + ```bash 72 + deno test 73 + ``` 74 + 75 + ## License 76 + 77 + MIT
-13
crypto/types.ts
··· 1 - /** 2 - * Creates signatures for messages. 3 - * 4 - * @prop jwtAlg - The JWT algorithm used for signing. 5 - * @prop sign - Returns a signature for the given message bytes. 6 - */ 7 1 export interface Signer { 8 2 jwtAlg: string; 9 3 sign(msg: Uint8Array): Uint8Array; 10 4 } 11 5 12 - /** 13 - * Can create DID keys. 14 - * @prop did - Returns a DID key. 15 - */ 16 6 export interface Didable { 17 7 did(): string; 18 8 } 19 9 20 - /** 21 - * Combines a {@linkcode Signer} and {@linkcode Didable} into a Keypair. 22 - */ 23 10 export interface Keypair extends Signer, Didable {} 24 11 25 12 /**
+25 -1
lexicon/lexicons.ts
··· 19 19 import { object as validateObject } from "./validation/complex.ts"; 20 20 21 21 /** 22 - * A collection of compiled lexicons. 22 + * A collection of compiled lexicon docs with methods for adding, 23 + * removing, validating, and iterating over them. 24 + * 25 + * @example Add and validate a lexicon 26 + * ```typescript 27 + * import { Lexicon } from "@atp/lexicon"; 28 + * 29 + * // create your lexicons collection 30 + * const lex = new Lexicons() 31 + * 32 + * // add your lexicons 33 + * lex.add({ 34 + * lex: 1, 35 + * id: 'com.example.post', 36 + * defs: { 37 + * // ... 38 + * } 39 + * }) 40 + * 41 + * // validate 42 + * lex.assertValidRecord('com.example.record', {$type: 'com.example.record', ...}) 43 + * lex.assertValidXrpcParams('com.example.query', {...}) 44 + * lex.assertValidXrpcInput('com.example.procedure', {...}) 45 + * lex.assertValidXrpcOutput('com.example.query', {...}) 46 + * ``` 23 47 */ 24 48 export class Lexicons implements Iterable<LexiconDoc> { 25 49 docs: Map<string, LexiconDoc> = new Map();
+1
lexicon/serialize.ts
··· 14 14 | Array<LexValue> 15 15 | { [key: string]: LexValue }; 16 16 17 + /** Record of LexValues */ 17 18 export type RepoRecord = Record<string, LexValue>; 18 19 19 20 // @NOTE avoiding use of check.is() here only because it makes
+22
repo/util.ts
··· 22 22 } from "./types.ts"; 23 23 import type { CID } from "multiformats/basics"; 24 24 25 + /** 26 + * Converts a DataDiff of a repo three arrays of RecordWriteDescripts, 27 + * for creates, updates, and deletes in the diff. 28 + */ 25 29 export const diffToWriteDescripts = ( 26 30 diff: DataDiff, 27 31 ): Promise<RecordWriteDescript[]> => { ··· 57 61 ]); 58 62 }; 59 63 64 + /** 65 + * Ensures that all write operations given are create actions. 66 + * @throws If any write operation is not a create action. 67 + */ 60 68 export const ensureCreates = ( 61 69 descripts: RecordWriteDescript[], 62 70 ): RecordCreateDescript[] => { ··· 85 93 return a.did === b.did && a.version === b.version; 86 94 }; 87 95 96 + /** 97 + * Generates a signature for an unsigned commit using the provided keypair. 98 + */ 88 99 export const signCommit = ( 89 100 unsigned: UnsignedCommit, 90 101 keypair: Keypair, ··· 97 108 }; 98 109 }; 99 110 111 + /** 112 + * Ensures a commit is authenticated by verifying its signature 113 + * against the provided DID key. 114 + */ 100 115 export const verifyCommitSig = ( 101 116 commit: Commit, 102 117 didKey: string, ··· 106 121 return crypto.verifySignature(didKey, encoded, sig as Uint8Array); 107 122 }; 108 123 124 + /** 125 + * Converts CBOR-encoded bytes to a LexValue using {@linkcode ipldToLex}. 126 + */ 109 127 export const cborToLex = (val: Uint8Array): LexValue => { 110 128 return ipldToLex(cborDecode(val)); 111 129 }; 112 130 131 + /** 132 + * Converts CBOR-encoded record bytes to a RepoRecord using {@linkcode cborToLex}. 133 + * Validates that the parsed value is a valid RepoRecord. 134 + */ 113 135 export const cborToLexRecord = (val: Uint8Array): RepoRecord => { 114 136 const parsed = cborToLex(val); 115 137 if (!check.is(parsed, schema.map)) {
+5 -9
xrpc-server/mod.ts
··· 1 1 /** 2 - * XRPC Server implementation for atproto services. 2 + * # XRPC Server implementation for atproto services. 3 3 * 4 4 * This module provides a Hono-based server implementation for atproto's XRPC protocol, 5 5 * with support for Lexicon schema validation, authentication, rate limiting, and streaming. ··· 15 15 * - Comprehensive error handling with XRPC error types 16 16 * - TypeScript-first with complete type definitions 17 17 * 18 - * ## Runtime Compatibility 19 - * Works with Deno, Node.js, Bun, Cloudflare Workers, and other JavaScript runtimes 20 - * supported by Hono. 21 - * 22 - * Note: Subscription (WebSocket) endpoints are currently supported only in Deno 23 - * and Cloudflare Workers. On Node.js and Bun, query/procedure routes function 24 - * normally, but subscription endpoints are not yet available (WebSocket upgrades 25 - * will be rejected or unmounted). 18 + * NOTE: streamMethods (WebSocket streaming endpoints) are currently 19 + * only supported in Deno and Cloudflare Workers. This doesn't 20 + * include the client-side StreamConnection function, which can be 21 + * used in any runtime or browser. 26 22 * 27 23 * @example Basic server setup with a simple endpoint 28 24 * ```ts
+4 -14
xrpc-server/tests/subscriptions_test.ts
··· 451 451 for await (const msg of sub) { 452 452 const typedMsg = msg as { count: number }; 453 453 messagesReceived++; 454 - assertEquals(typedMsg.count >= 0, true); // Ensure valid count 454 + assertEquals(typedMsg.count >= 0, true); 455 455 456 - // Abort early to avoid lingering sockets/heartbeats; this simulates a reconnect trigger. 457 456 if (messagesReceived === 2) { 458 457 ac.abort(new Error("test-abort")); 459 458 break; 460 459 } 461 460 } 462 461 463 - // Allow internal subscription cleanup (heartbeat interval & socket close) to settle 464 - // so Deno's leak detector doesn't flag the pending timer from the generator's wait(0). 465 - await new Promise((r) => setTimeout(r, 0)); 462 + await wait(50); 466 463 467 - // Ensure we actually received the expected early messages 468 464 assertEquals(messagesReceived >= 2, true); 469 465 } finally { 470 466 await closeServer(httpServer); ··· 499 495 messages.push(typedMsg); 500 496 if (typedMsg.count <= 6 && !disconnected) { 501 497 disconnected = true; 502 - // Abort and immediately break to ensure iterator finalizer runs, 503 - // preventing lingering heartbeat intervals / WebSocket reads. 504 498 abortController.abort(new Error("Oops!")); 505 499 break; 506 500 } 507 501 } 508 502 } catch (err) { 509 503 error = err; 510 - } finally { 511 - // Give the subscription cleanup a microtask + tick to run. 512 - await new Promise((r) => setTimeout(r, 0)); 513 504 } 514 505 515 - // The subscription may terminate cleanly or throw - either is acceptable 506 + await wait(50); 507 + 516 508 if (error) { 517 509 assertEquals(error instanceof Error, true); 518 510 assertEquals((error as Error).message, "Oops!"); 519 511 } 520 - // Ensure abort actually happened 521 512 assertEquals(abortController.signal.aborted, true); 522 - // Ensure we received at least one message before abort 523 513 assertEquals(messages.length > 0, true); 524 514 } finally { 525 515 await closeServer(httpServer);