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.

THE (documented) RAPTURE

+306 -37
+1 -2
bytes/mod.ts
··· 1 1 /** 2 2 * @module 3 3 * 4 - * `Uint8Array`s bring memory-efficient(ish) byte handling to browsers - they are similar to Node.js `Buffer`s but lack a lot of the utility methods present on that class. 5 - * This module exports a number of function that let you do common operations - joining Uint8Arrays together, seeing if they have the same contents etc. 4 + * Simple `Uint8Array` utilities for AT Protocol. 6 5 * 7 6 * ## alloc(size) 8 7 *
+6
common/mod.ts
··· 1 + /** 2 + * # AT Protocol Common Utilities 3 + * 4 + * Shared TypeScript code for other @atproto/* packages. 5 + * This package is oriented towards writing servers. 6 + */ 1 7 export * as check from "./check.ts"; 2 8 export * as util from "./util.ts"; 3 9
+43
crypto/mod.ts
··· 1 + /** 2 + * # AT Protocol Cryptographic Utilities 3 + * 4 + * This module provides cryptographic helpers for AT Protocol. 5 + * 6 + * 2 cryptographic systems are currently supported: 7 + * - P-256 elliptic curve: aka "NIST P-256", aka secp256r1 (note the r), aka prime256v1 8 + * - K-256 elliptic curve: aka "NIST K-256", aka secp256k1 (note the k) 9 + * 10 + * The details of cryptography in atproto are described in {@link https://atproto.com/specs/cryptography | the specification.} 11 + * This includes string encodings, validity of "low-S" signatures, byte representation 12 + * "compression", hashing, and more. 13 + * 14 + * @example 15 + * ```typescript 16 + * import { verifySignature, Secp256k1Keypair, P256Keypair } from '@atp/crypto' 17 + * 18 + * // generate a new random K-256 private key 19 + * const keypair = await Secp256k1Keypair.create({ exportable: true }) 20 + * 21 + * // sign binary data, resulting signature bytes. 22 + * // SHA-256 hash of data is what actually gets signed. 23 + * // signature output is often base64-encoded. 24 + * const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) 25 + * const sig = await keypair.sign(data) 26 + * 27 + * // serialize the public key as a did:key string, which includes key type metadata 28 + * const pubDidKey = keypair.did() 29 + * console.log(pubDidKey) 30 + * 31 + * // output would look something like: 'did:key:zQ3shVRtgqTRHC7Lj4DYScoDgReNpsDp3HBnuKBKt1FSXKQ38' 32 + * 33 + * // verify signature using public key 34 + * const ok = verifySignature(pubDidKey, data, sig) 35 + * if (!ok) { 36 + * throw new Error('Uh oh, something is fishy') 37 + * } else { 38 + * console.log('Success') 39 + * } 40 + * ``` 41 + * 42 + * @module 43 + */ 1 44 export * from "./const.ts"; 2 45 export * from "./did.ts"; 3 46 export * from "./multibase.ts";
+31 -2
lex-cli/mod.ts
··· 1 - #!/usr/bin/env node 2 - 1 + /** 2 + * # AT Protocol Lexicon CLI 3 + * 4 + * A command-line interface for generating docs, servers, and clients 5 + * from AT Protocol lexicon files. 6 + * 7 + * Turn lexicon files into: 8 + * - Markdown documentation 9 + * - Server implementation 10 + * - TypeScript objects 11 + * - Client implementation 12 + * 13 + * ## Installation 14 + * ```bash 15 + * deno install -g jsr:@atp/lex-cli@latest --name lex-cli 16 + * ``` 17 + * Alternatively, you can use it without installation by referring to 18 + * it as `jsr:@atp/lex-cli` instead of `lex-cli`. 19 + * 20 + * @example Generate Server 21 + * ```bash 22 + * lex-cli gen-server -i <path/to/lexicon/dir> -o <output/path> 23 + * ``` 24 + * 25 + * @example Generate Client 26 + * ```bash 27 + * lex-cli gen-api -i <path/to/lexicon/dir> -o <output/path> 28 + * ``` 29 + * 30 + * @module 31 + */ 3 32 import { Command } from "@cliffy/command"; 4 33 import { genApi, genMd, genServer, genTsObj } from "./cmd/index.ts"; 5 34 import process from "node:process";
+31
lexicon/mod.ts
··· 1 + /** 2 + * # AT Protocol Lexicon Validation Utility 3 + * 4 + * This module provides utilities for validating and working with AT Protocol lexicons 5 + * in TypeScript. 6 + * 7 + * @example Validate a lexicon 8 + * ```typescript 9 + * import { Lexicon } from "@atp/lexicon"; 10 + * 11 + * // create your lexicons collection 12 + * const lex = new Lexicons() 13 + * 14 + * // add your lexicons 15 + * lex.add({ 16 + * lex: 1, 17 + * id: 'com.example.post', 18 + * defs: { 19 + * // ... 20 + * } 21 + * }) 22 + * 23 + * // validate 24 + * lex.assertValidRecord('com.example.record', {$type: 'com.example.record', ...}) 25 + * lex.assertValidXrpcParams('com.example.query', {...}) 26 + * lex.assertValidXrpcInput('com.example.procedure', {...}) 27 + * lex.assertValidXrpcOutput('com.example.query', {...}) 28 + * ``` 29 + * 30 + * @module 31 + */ 1 32 export * from "./types.ts"; 2 33 export * from "./lexicons.ts"; 3 34 export * from "./blob-refs.ts";
+15
syntax/aturi.ts
··· 6 6 // --path----- --query-- --hash-- 7 7 const RELATIVE_REGEX = /^(\/[^?#\s]*)?(\?[^#\s]+)?(#[^\s]+)?$/i; 8 8 9 + /** 10 + * AT URI Validation and parsing class 11 + * 12 + * @example AT URIs 13 + * ```typescript 14 + * import { AtUri } from '@atp/syntax' 15 + * 16 + * const uri = new AtUri('at://bob.com/com.example.post/1234') 17 + * uri.protocol // => 'at:' 18 + * uri.origin // => 'at://bob.com' 19 + * uri.hostname // => 'bob.com' 20 + * uri.collection // => 'com.example.post' 21 + * uri.rkey // => '1234' 22 + * ``` 23 + */ 9 24 export class AtUri { 10 25 hash: string; 11 26 host: string;
+49 -20
syntax/handle.ts
··· 18 18 // "should" "never" actually resolve and get registered in production 19 19 ]; 20 20 21 - // Handle constraints, in English: 22 - // - must be a possible domain name 23 - // - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696, 24 - // section 2. and RFC-3986, section 3. can now have leading numbers (eg, 25 - // 4chan.org) 26 - // - "labels" (sub-names) are made of ASCII letters, digits, hyphens 27 - // - can not start or end with a hyphen 28 - // - TLD (last component) should not start with a digit 29 - // - can't end with a hyphen (can end with digit) 30 - // - each segment must be between 1 and 63 characters (not including any periods) 31 - // - overall length can't be more than 253 characters 32 - // - separated by (ASCII) periods; does not start or end with period 33 - // - case insensitive 34 - // - domains (handles) are equal if they are the same lower-case 35 - // - punycode allowed for internationalization 36 - // - no whitespace, null bytes, joining chars, etc 37 - // - does not validate whether domain or TLD exists, or is a reserved or 38 - // special TLD (eg, .onion or .local) 39 - // - does not validate punycode 21 + /** 22 + * Ensure a handle is valid 23 + * @throws If handle is invalid 24 + * 25 + * Handle constraints, in English: 26 + * - must be a possible domain name 27 + * - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696, 28 + * section 2. and RFC-3986, section 3. can now have leading numbers (eg, 29 + * 4chan.org) 30 + * - "labels" (sub-names) are made of ASCII letters, digits, hyphens 31 + * - can not start or end with a hyphen 32 + * - TLD (last component) should not start with a digit 33 + * - can't end with a hyphen (can end with digit) 34 + * - each segment must be between 1 and 63 characters (not including any periods) 35 + * - overall length can't be more than 253 characters 36 + * - separated by (ASCII) periods; does not start or end with period 37 + * - case insensitive 38 + * - domains (handles) are equal if they are the same lower-case 39 + * - punycode allowed for internationalization 40 + * - no whitespace, null bytes, joining chars, etc 41 + * - does not validate whether domain or TLD exists, or is a reserved or 42 + * special TLD (eg, .onion or .local) 43 + * - does not validate punycode 44 + */ 40 45 export const ensureValidHandle = (handle: string): void => { 41 46 // check that all chars are boring ASCII 42 47 if (!/^[a-zA-Z0-9.-]*$/.test(handle)) { ··· 73 78 } 74 79 }; 75 80 76 - // simple regex translation of above constraints 81 + /** 82 + * Ensure a handle is valid using a regex pattern. 83 + * @throws If handle is invalid 84 + */ 77 85 export const ensureValidHandleRegex = (handle: string): void => { 78 86 if ( 79 87 !/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/ ··· 88 96 } 89 97 }; 90 98 99 + /** 100 + * Converts a handle to lowercase. 101 + */ 91 102 export const normalizeHandle = (handle: string): string => { 92 103 return handle.toLowerCase(); 93 104 }; 94 105 106 + /** 107 + * Converts a handle to lowercase and ensures it is valid. 108 + * @returns The normalized handle if it is valid 109 + * @throws If handle is invalid 110 + */ 95 111 export const normalizeAndEnsureValidHandle = (handle: string): string => { 96 112 const normalized = normalizeHandle(handle); 97 113 ensureValidHandle(normalized); 98 114 return normalized; 99 115 }; 100 116 117 + /** 118 + * Checks if a handle is valid and returns a boolean. 119 + * 120 + * @returns True if handle is valid 121 + */ 101 122 export const isValidHandle = (handle: string): boolean => { 102 123 try { 103 124 ensureValidHandle(handle); ··· 111 132 return true; 112 133 }; 113 134 135 + /** 136 + * Check if a TLD is valid. 137 + * 138 + * Disallowed TLDs: {@linkcode DISALLOWED_TLDS} 139 + */ 114 140 export const isValidTld = (handle: string): boolean => { 115 141 return !DISALLOWED_TLDS.some((domain) => handle.endsWith(domain)); 116 142 }; 117 143 144 + /** 145 + * Thrown when a handle is invalid. 146 + */ 118 147 export class InvalidHandleError extends Error {} 119 148 /** @deprecated Never used */ 120 149 export class ReservedHandleError extends Error {}
+58
syntax/mod.ts
··· 1 + /** 2 + * # AT Protocol Syntax Validation 3 + * 4 + * Validation utilities for AT Protocol strings: 5 + * - DIDs 6 + * - Handles 7 + * - NSIDs 8 + * - AT URIs 9 + * - TIDs 10 + * - Record Keys 11 + * - Datetimes 12 + * 13 + * @example Handles 14 + * ```typescript 15 + * import { isValidHandle, ensureValidHandle, isValidDid } from '@atp/syntax' 16 + * 17 + * isValidHandle('alice.test') // returns true 18 + * ensureValidHandle('alice.test') // returns void 19 + * 20 + * isValidHandle('al!ce.test') // returns false 21 + * ensureValidHandle('al!ce.test') // throws an error 22 + * ``` 23 + * 24 + * @example NSIDs 25 + * ```typescript 26 + * import { NSID } from '@atp/syntax' 27 + * 28 + * const id1 = NSID.parse('com.example.foo') 29 + * id1.authority // => 'example.com' 30 + * id1.name // => 'foo' 31 + * id1.toString() // => 'com.example.foo' 32 + * 33 + * const id2 = NSID.create('example.com', 'foo') 34 + * id2.authority // => 'example.com' 35 + * id2.name // => 'foo' 36 + * id2.toString() // => 'com.example.foo' 37 + * 38 + * NSID.isValid('com.example.foo') // => true 39 + * NSID.isValid('com.example.someThing') // => true 40 + * NSID.isValid('example.com/foo') // => false 41 + * NSID.isValid('foo') // => false 42 + * ``` 43 + * 44 + * @example AT URIs 45 + * ```typescript 46 + * import { AtUri } from '@atp/syntax' 47 + * 48 + * const uri = new AtUri('at://bob.com/com.example.post/1234') 49 + * uri.protocol // => 'at:' 50 + * uri.origin // => 'at://bob.com' 51 + * uri.hostname // => 'bob.com' 52 + * uri.collection // => 'com.example.post' 53 + * uri.rkey // => '1234' 54 + * ``` 55 + * 56 + * @module 57 + */ 58 + 1 59 export * from "./handle.ts"; 2 60 export * from "./did.ts"; 3 61 export * from "./nsid.ts";
+26 -13
syntax/nsid.ts
··· 1 - /* 2 - Grammar: 3 - 4 - alpha = "a" / "b" / "c" / "d" / "e" / "f" / "g" / "h" / "i" / "j" / "k" / "l" / "m" / "n" / "o" / "p" / "q" / "r" / "s" / "t" / "u" / "v" / "w" / "x" / "y" / "z" / "A" / "B" / "C" / "D" / "E" / "F" / "G" / "H" / "I" / "J" / "K" / "L" / "M" / "N" / "O" / "P" / "Q" / "R" / "S" / "T" / "U" / "V" / "W" / "X" / "Y" / "Z" 5 - number = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" 6 - delim = "." 7 - segment = alpha *( alpha / number / "-" ) 8 - authority = segment *( delim segment ) 9 - name = alpha *( alpha / number ) 10 - nsid = authority delim name 11 - 12 - */ 13 - 1 + /** 2 + * NameSpaced Identifier class 3 + * 4 + * Validation and parsing based on the NSID specification: 5 + * https://atproto.com/specs/nsid 6 + * 7 + * @example NSIDs 8 + * ```typescript 9 + * import { NSID } from '@atp/syntax' 10 + * 11 + * const id1 = NSID.parse('com.example.foo') 12 + * id1.authority // => 'example.com' 13 + * id1.name // => 'foo' 14 + * id1.toString() // => 'com.example.foo' 15 + * 16 + * const id2 = NSID.create('example.com', 'foo') 17 + * id2.authority // => 'example.com' 18 + * id2.name // => 'foo' 19 + * id2.toString() // => 'com.example.foo' 20 + * 21 + * NSID.isValid('com.example.foo') // => true 22 + * NSID.isValid('com.example.someThing') // => true 23 + * NSID.isValid('example.com/foo') // => false 24 + * NSID.isValid('foo') // => false 25 + * ``` 26 + */ 14 27 export class NSID { 15 28 readonly segments: readonly string[]; 16 29
+46
xrpc/mod.ts
··· 1 + /** 2 + * # XRPC Client 3 + * 4 + * TypeScript client library for talking to AT Protocol services, 5 + * with Lexicon schema validation. 6 + * 7 + * @example Fetching an XRPC endpoint 8 + * ```typescript 9 + * import { LexiconDoc } from '@atproto/lexicon' 10 + * import { XrpcClient } from '@atproto/xrpc' 11 + * 12 + * const pingLexicon = { 13 + * lexicon: 1, 14 + * id: 'io.example.ping', 15 + * defs: { 16 + * main: { 17 + * type: 'query', 18 + * description: 'Ping the server', 19 + * parameters: { 20 + * type: 'params', 21 + * properties: { message: { type: 'string' } }, 22 + * }, 23 + * output: { 24 + * encoding: 'application/json', 25 + * schema: { 26 + * type: 'object', 27 + * required: ['message'], 28 + * properties: { message: { type: 'string' } }, 29 + * }, 30 + * }, 31 + * }, 32 + * }, 33 + * } satisfies LexiconDoc 34 + * 35 + * const xrpc = new XrpcClient('https://ping.example.com', [ 36 + * // Any number of lexicon here 37 + * pingLexicon, 38 + * ]) 39 + * 40 + * const res1 = await xrpc.call('io.example.ping', { 41 + * message: 'hello world', 42 + * }) 43 + * res1.encoding // => 'application/json' 44 + * res1.body // => {message: 'hello world'} 45 + * ``` 46 + */ 1 47 export * from "./client.ts"; 2 48 export * from "./fetch-handler.ts"; 3 49 export * from "./types.ts";