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 one

+465 -119
+40
common/dates.ts
··· 1 + export const SECOND = 1000; 2 + export const MINUTE = SECOND * 60; 3 + export const HOUR = MINUTE * 60; 4 + export const DAY = HOUR * 24; 5 + 6 + export const lessThanAgoMs = (time: Date, range: number) => { 7 + return Date.now() < time.getTime() + range; 8 + }; 9 + 10 + export const addHoursToDate = (hours: number, startingDate?: Date): Date => { 11 + // When date is passed, clone before calling `setHours()` so that we are not mutating the original date 12 + const currentDate = startingDate ? new Date(startingDate) : new Date(); 13 + currentDate.setHours(currentDate.getHours() + hours); 14 + return currentDate; 15 + }; 16 + 17 + function isValidISODateString(dateString: string): boolean { 18 + // Basic format check: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ssZ 19 + const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/; 20 + if (!isoRegex.test(dateString)) { 21 + return false; 22 + } 23 + 24 + // Check if the date is valid and matches the original string when parsed and converted back 25 + const date = new Date(dateString); 26 + return !isNaN(date.getTime()) && date.toISOString() === dateString; 27 + } 28 + 29 + export function toSimplifiedISOSafe(dateStr: string) { 30 + const date = new Date(dateStr); 31 + if (isNaN(date.getTime())) { 32 + return new Date(0).toISOString(); 33 + } 34 + const iso = date.toISOString(); 35 + if (!isValidISODateString(iso)) { 36 + // Occurs in rare cases, e.g. where resulting UTC year is negative. These also don't preserve lexical sort. 37 + return new Date(0).toISOString(); 38 + } 39 + return iso; // YYYY-MM-DDTHH:mm:ss.sssZ 40 + }
+2 -1
common/mod.ts
··· 1 - export * as util from "./util.ts"; 2 1 export * as check from "./check.ts"; 3 2 4 3 export * from "./env.ts"; ··· 12 11 export * from "./tid.ts"; 13 12 export * from "./strings.ts"; 14 13 export * from "./logger.ts"; 14 + export * from "./dates.ts"; 15 + export * from "./util.ts";
-9
common/util.ts
··· 158 158 ) as ArrayBuffer; 159 159 } 160 160 161 - export function toSimplifiedISOSafe(dateStr: string) { 162 - const date = new Date(dateStr); 163 - if (isNaN(date.getTime())) { 164 - return new Date(0).toISOString(); 165 - } 166 - const iso = date.toISOString(); 167 - return iso; // YYYY-MM-DDTHH:mm:ss.sssZ 168 - } 169 - 170 161 export type RetryOptions = { 171 162 maxRetries?: number; 172 163 getWaitMs?: (n: number) => number | null;
+62 -26
deno.lock
··· 45 45 "npm:@atproto/xrpc@0.7": "0.7.4", 46 46 "npm:@ipld/dag-cbor@^9.2.5": "9.2.5", 47 47 "npm:@types/node@*": "24.2.0", 48 - "npm:http-errors@2": "2.0.0", 48 + "npm:cbor-x@*": "1.6.0", 49 49 "npm:jose@*": "6.1.0", 50 50 "npm:multiformats@^13.3.6": "13.4.0", 51 51 "npm:multiformats@^13.4.0": "13.4.0", ··· 228 228 "zod" 229 229 ] 230 230 }, 231 + "@cbor-extract/cbor-extract-darwin-arm64@2.2.0": { 232 + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", 233 + "os": ["darwin"], 234 + "cpu": ["arm64"] 235 + }, 236 + "@cbor-extract/cbor-extract-darwin-x64@2.2.0": { 237 + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", 238 + "os": ["darwin"], 239 + "cpu": ["x64"] 240 + }, 241 + "@cbor-extract/cbor-extract-linux-arm64@2.2.0": { 242 + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", 243 + "os": ["linux"], 244 + "cpu": ["arm64"] 245 + }, 246 + "@cbor-extract/cbor-extract-linux-arm@2.2.0": { 247 + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", 248 + "os": ["linux"], 249 + "cpu": ["arm"] 250 + }, 251 + "@cbor-extract/cbor-extract-linux-x64@2.2.0": { 252 + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", 253 + "os": ["linux"], 254 + "cpu": ["x64"] 255 + }, 256 + "@cbor-extract/cbor-extract-win32-x64@2.2.0": { 257 + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", 258 + "os": ["win32"], 259 + "cpu": ["x64"] 260 + }, 231 261 "@ipld/dag-cbor@9.2.5": { 232 262 "integrity": "sha512-84wSr4jv30biui7endhobYhXBQzQE4c/wdoWlFrKcfiwH+ofaPg8fwsM8okX9cOzkkrsAsNdDyH3ou+kiLquwQ==", 233 263 "dependencies": [ ··· 250 280 "undici-types" 251 281 ] 252 282 }, 283 + "cbor-extract@2.2.0": { 284 + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", 285 + "dependencies": [ 286 + "node-gyp-build-optional-packages" 287 + ], 288 + "optionalDependencies": [ 289 + "@cbor-extract/cbor-extract-darwin-arm64", 290 + "@cbor-extract/cbor-extract-darwin-x64", 291 + "@cbor-extract/cbor-extract-linux-arm", 292 + "@cbor-extract/cbor-extract-linux-arm64", 293 + "@cbor-extract/cbor-extract-linux-x64", 294 + "@cbor-extract/cbor-extract-win32-x64" 295 + ], 296 + "scripts": true, 297 + "bin": true 298 + }, 299 + "cbor-x@1.6.0": { 300 + "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", 301 + "optionalDependencies": [ 302 + "cbor-extract" 303 + ] 304 + }, 253 305 "cborg@4.2.15": { 254 306 "integrity": "sha512-T+YVPemWyXcBVQdp0k61lQp2hJniRNmul0lAwTj2DTS/6dI4eCq/MRMucGqqvFqMBfmnD8tJ9aFtPu5dEGAbgw==", 255 307 "bin": true 256 308 }, 257 - "depd@2.0.0": { 258 - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 309 + "detect-libc@2.0.4": { 310 + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" 259 311 }, 260 312 "graphemer@1.4.0": { 261 313 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 262 314 }, 263 - "http-errors@2.0.0": { 264 - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 265 - "dependencies": [ 266 - "depd", 267 - "inherits", 268 - "setprototypeof", 269 - "statuses", 270 - "toidentifier" 271 - ] 272 - }, 273 - "inherits@2.0.4": { 274 - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 275 - }, 276 315 "iso-datestring-validator@2.2.2": { 277 316 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==" 278 317 }, ··· 285 324 "multiformats@9.9.0": { 286 325 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 287 326 }, 327 + "node-gyp-build-optional-packages@5.1.1": { 328 + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", 329 + "dependencies": [ 330 + "detect-libc" 331 + ], 332 + "bin": true 333 + }, 288 334 "rate-limiter-flexible@2.4.2": { 289 335 "integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==" 290 - }, 291 - "setprototypeof@1.2.0": { 292 - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 293 - }, 294 - "statuses@2.0.1": { 295 - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 296 - }, 297 - "toidentifier@1.0.1": { 298 - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 299 336 }, 300 337 "uint8arrays@3.0.0": { 301 338 "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", ··· 349 386 "npm:@atproto/crypto@~0.4.4", 350 387 "npm:@atproto/lexicon@~0.4.11", 351 388 "npm:@atproto/xrpc@0.7", 352 - "npm:http-errors@2", 353 389 "npm:multiformats@^13.3.6", 354 390 "npm:rate-limiter-flexible@^2.4.1", 355 391 "npm:uint8arrays@3.0.0",
+3 -5
lex-cli/codegen/server.ts
··· 61 61 const extension = options?.useJsExtension ? ".js" : ".ts"; 62 62 //= import {createServer as createXrpcServer, Server as XrpcServer} from '@sprk/xrpc-server' 63 63 file.addImportDeclaration({ 64 - moduleSpecifier: "@sprk/xrpc-server", 64 + moduleSpecifier: "@atp/xrpc-server", 65 65 namedImports: [ 66 66 { name: "Auth", isTypeOnly: true }, 67 67 { name: "Options", alias: "XrpcOptions", isTypeOnly: true }, ··· 257 257 const methodType = isSubscription ? "streamMethod" : "method"; 258 258 method.setBodyText( 259 259 [ 260 - // Placing schema on separate line, since the following one was being formatted 261 - // into multiple lines and causing the ts-ignore to ignore the wrong line. 262 - `const nsid = '${userType.nsid}' // @ts-ignore - userType.nsid is dynamically generated and TypeScript can't infer its type`, 260 + `const nsid = '${userType.nsid}' // @ts-ignore - dynamically generated`, 263 261 `return this._server.xrpc.${methodType}(nsid, cfg)`, 264 262 ].join("\n"), 265 263 ); ··· 430 428 const def = lexicons.getDefOrThrow(lexUri, ["subscription"]); 431 429 432 430 file.addImportDeclaration({ 433 - moduleSpecifier: "@sprk/xrpc-server", 431 + moduleSpecifier: "@atp/xrpc-server", 434 432 namedImports: [{ name: "ErrorFrame" }], 435 433 }); 436 434
+265
xrpc-server/README.md
··· 1 + # XRPC Server for Deno 2 + 3 + A native Deno implementation of an XRPC server that can be used with `deno.serve()`. 4 + 5 + ## Features 6 + 7 + - Native Deno server implementation (no external HTTP framework dependencies) 8 + - XRPC protocol support with lexicon validation 9 + - Built-in rate limiting 10 + - Authentication support 11 + - Streaming subscriptions 12 + - Error handling and validation 13 + 14 + ## Basic Usage 15 + 16 + ```typescript 17 + import { createServer } from "./server.ts"; 18 + import type { HandlerContext, HandlerSuccess } from "./types.ts"; 19 + 20 + // Create a new XRPC server instance 21 + const server = createServer(); 22 + 23 + // Add a query method (GET request) 24 + server.method("com.example.getProfile", { 25 + handler: async (ctx: HandlerContext): Promise<HandlerSuccess> => { 26 + const { params } = ctx; 27 + 28 + return { 29 + encoding: "application/json", 30 + body: { 31 + id: params.id, 32 + name: "John Doe", 33 + email: "john@example.com", 34 + }, 35 + }; 36 + }, 37 + }); 38 + 39 + // Add a procedure method (POST request) 40 + server.method("com.example.createPost", { 41 + handler: async (ctx: HandlerContext): Promise<HandlerSuccess> => { 42 + const { input, auth } = ctx; 43 + 44 + // Validate authentication 45 + if (!auth) { 46 + throw new Error("Authentication required"); 47 + } 48 + 49 + return { 50 + encoding: "application/json", 51 + body: { 52 + success: true, 53 + postId: crypto.randomUUID(), 54 + content: input?.body?.text, 55 + }, 56 + }; 57 + }, 58 + }); 59 + 60 + // Start the server 61 + Deno.serve({ 62 + port: 8000, 63 + handler: server.handler, 64 + }); 65 + ``` 66 + 67 + ## Server Configuration 68 + 69 + ```typescript 70 + const server = createServer(lexicons, { 71 + // Custom error parser 72 + errorParser: (err) => { 73 + console.error('Server error:', err); 74 + return XRPCError.fromError(err); 75 + }, 76 + 77 + // Custom catchall handler for unregistered routes 78 + catchall: async (req) => { 79 + return new Response("Custom 404", { status: 404 }); 80 + }, 81 + 82 + // Rate limiting configuration 83 + rateLimits: { 84 + creator: (options) => new SomeRateLimiter(options), 85 + global: [ 86 + { 87 + name: "global", 88 + durationMs: 60000, // 1 minute 89 + points: 100, // 100 requests per minute 90 + } 91 + ], 92 + shared: [ 93 + { 94 + name: "auth", 95 + durationMs: 300000, // 5 minutes 96 + points: 30, // 30 requests per 5 minutes 97 + } 98 + ], 99 + }, 100 + }); 101 + ``` 102 + 103 + ## Method Configuration 104 + 105 + ### Query Methods (GET requests) 106 + 107 + ```typescript 108 + server.method("com.example.searchPosts", { 109 + handler: async ({ params }) => { 110 + const { q, limit = 10 } = params; 111 + 112 + // Search logic here 113 + const results = await searchPosts(q, limit); 114 + 115 + return { 116 + encoding: "application/json", 117 + body: { posts: results }, 118 + }; 119 + }, 120 + 121 + // Optional authentication 122 + auth: async ({ req, params, input }) => { 123 + const token = req.headers.get("authorization"); 124 + return await validateToken(token); 125 + }, 126 + 127 + // Optional rate limiting 128 + rateLimit: { 129 + durationMs: 60000, 130 + points: 20, 131 + }, 132 + }); 133 + ``` 134 + 135 + ### Procedure Methods (POST requests) 136 + 137 + ```typescript 138 + server.method("com.example.updateProfile", { 139 + handler: async ({ input, auth }) => { 140 + if (!auth?.user) { 141 + throw new Error("Authentication required"); 142 + } 143 + 144 + const updatedProfile = await updateUserProfile(auth.user.id, input.body); 145 + 146 + return { 147 + encoding: "application/json", 148 + body: updatedProfile, 149 + }; 150 + }, 151 + 152 + auth: async ({ req }) => { 153 + // Custom auth logic 154 + return await authenticateUser(req); 155 + }, 156 + }); 157 + ``` 158 + 159 + ## Streaming Methods 160 + 161 + ```typescript 162 + server.streamMethod("com.example.liveUpdates", { 163 + handler: async function* (req, signal) { 164 + while (!signal.aborted) { 165 + yield { 166 + type: "update", 167 + data: await getLiveData(), 168 + }; 169 + 170 + await new Promise(resolve => setTimeout(resolve, 1000)); 171 + } 172 + }, 173 + }); 174 + ``` 175 + 176 + ## Response Types 177 + 178 + The handler can return different response types: 179 + 180 + ```typescript 181 + // JSON response 182 + return { 183 + encoding: "application/json", 184 + body: { message: "Hello World" }, 185 + }; 186 + 187 + // Binary response 188 + return { 189 + encoding: "image/jpeg", 190 + buffer: imageBuffer, 191 + }; 192 + 193 + // Stream response 194 + return { 195 + encoding: "text/plain", 196 + stream: readableStream, 197 + }; 198 + 199 + // Custom headers 200 + return { 201 + encoding: "application/json", 202 + body: data, 203 + headers: { 204 + "Custom-Header": "value", 205 + }, 206 + }; 207 + ``` 208 + 209 + ## Error Handling 210 + 211 + ```typescript 212 + import { 213 + InvalidRequestError, 214 + MethodNotImplementedError, 215 + XRPCError 216 + } from "./errors.ts"; 217 + 218 + server.method("com.example.riskyOperation", { 219 + handler: async (ctx) => { 220 + if (!ctx.params.id) { 221 + throw new InvalidRequestError("Missing required parameter: id"); 222 + } 223 + 224 + try { 225 + const result = await performRiskyOperation(ctx.params.id); 226 + return { 227 + encoding: "application/json", 228 + body: result, 229 + }; 230 + } catch (err) { 231 + throw XRPCError.fromError(err); 232 + } 233 + }, 234 + }); 235 + ``` 236 + 237 + ## Running the Server 238 + 239 + ```bash 240 + # Basic server 241 + deno run --allow-net server.ts 242 + 243 + # With permissions for file access and environment variables 244 + deno run --allow-net --allow-read --allow-env server.ts 245 + 246 + # Production mode 247 + deno run --allow-net --allow-read --allow-env --no-check server.ts 248 + ``` 249 + 250 + ## Key Differences from Hono Version 251 + 252 + 1. **No external dependencies**: Uses native Deno server instead of Hono 253 + 2. **Direct handler**: The server's `handler` property is a function compatible with `deno.serve()` 254 + 3. **Simplified routing**: Routes are stored internally and matched directly 255 + 4. **Native Request/Response**: Uses standard Web API Request/Response objects 256 + 5. **Error handling**: Errors are converted to responses internally rather than thrown to middleware 257 + 258 + ## Migration from Hono 259 + 260 + If you're migrating from a Hono-based XRPC server: 261 + 262 + 1. Remove Hono dependency from your imports 263 + 2. Replace `server.app` or `server.handler` with `server.handler` 264 + 3. Use `deno.serve()` instead of Hono's serve method 265 + 4. Update any custom middleware to work with the native request handler
+6 -3
xrpc-server/auth.ts
··· 1 1 import * as ui8 from "uint8arrays"; 2 - import * as common from "@atproto/common"; 3 - import { MINUTE } from "@atproto/common"; 2 + import * as common from "@atp/common"; 3 + import { MINUTE } from "@atp/common"; 4 4 import * as crypto from "@atproto/crypto"; 5 5 import { AuthRequiredError } from "./errors.ts"; 6 6 ··· 152 152 jwtStr: string, 153 153 ownDid: string | null, 154 154 lxm: string | null, 155 - getSigningKey: (iss: string, forceRefresh: boolean) => Promise<string>, 155 + getSigningKey: ( 156 + iss: string, 157 + forceRefresh: boolean, 158 + ) => Promise<string> | string, 156 159 verifySignatureWithKey: VerifySignatureWithKeyFn = 157 160 cryptoVerifySignatureWithKey, 158 161 ): Promise<ServiceJwtPayload> => {
-1
xrpc-server/deno.json
··· 11 11 "@std/encoding": "jsr:@std/encoding@^1.0.10", 12 12 "zod": "jsr:@zod/zod@^4.0.17", 13 13 "hono": "jsr:@hono/hono@^4.7.10", 14 - "http-errors": "npm:http-errors@^2.0.0", 15 14 "multiformats": "npm:multiformats@^13.3.6", 16 15 "rate-limiter-flexible": "npm:rate-limiter-flexible@^2.4.1", 17 16 "uint8arrays": "npm:uint8arrays@3.0.0",
+42
xrpc-server/example.ts
··· 1 + import { createServer } from "./server.ts"; 2 + import type { HandlerContext, HandlerSuccess } from "./types.ts"; 3 + 4 + // Create a new XRPC server instance 5 + const server = createServer(); 6 + 7 + // Add a simple query method 8 + server.method("com.example.getStatus", { 9 + handler: async (_ctx: HandlerContext): Promise<HandlerSuccess> => { 10 + return { 11 + encoding: "application/json", 12 + body: { 13 + status: "ok", 14 + timestamp: new Date().toISOString(), 15 + }, 16 + }; 17 + }, 18 + }); 19 + 20 + // Add a simple procedure method 21 + server.method("com.example.createPost", { 22 + handler: (ctx: HandlerContext): HandlerSuccess => { 23 + const { input } = ctx; 24 + 25 + return { 26 + encoding: "application/json", 27 + body: { 28 + success: true, 29 + message: "Post created successfully", 30 + data: input, 31 + }, 32 + }; 33 + }, 34 + }); 35 + 36 + // Start the Deno server 37 + console.log("Starting XRPC server on port 8000..."); 38 + 39 + Deno.serve({ 40 + port: 8000, 41 + handler: server.handler, 42 + });
+4 -35
xrpc-server/server.ts
··· 54 54 WrappedRateLimiter, 55 55 type WrappedRateLimiterOptions, 56 56 } from "./rate-limiter.ts"; 57 - import type { CatchallHandler, HandlerInput } from "./types.ts"; 57 + import type { HandlerInput } from "./types.ts"; 58 58 import { assert } from "@std/assert"; 59 + import type { CatchallHandler } from "./types.ts"; 59 60 60 61 /** 61 62 * Creates a new XRPC server instance. ··· 403 404 resetRouteRateLimits: async () => {}, 404 405 }; 405 406 407 + // Apply rate limiting (route-specific, which includes global if configured) 406 408 if (routeLimiter) { 407 409 const result = await routeLimiter.consume(ctx); 408 410 if (result instanceof RateLimitExceededError) { ··· 417 419 418 420 if (isHandlerPipeThroughBuffer(output)) { 419 421 setHeaders(c, output.headers); 420 - return c.body(new Uint8Array(output.buffer), 200, { 422 + return c.body(output.buffer.buffer as ArrayBuffer, 200, { 421 423 "Content-Type": output.encoding, 422 424 }); 423 425 } else if (isHandlerPipeThroughStream(output)) { ··· 579 581 statusCode as 500, 580 582 ); 581 583 }; 582 - } 583 - 584 - /** 585 - * Type guard to check if an object is a Pino HTTP request object. 586 - * @param obj - The object to check 587 - * @returns True if the object has a req property 588 - * @private 589 - */ 590 - function _isPinoHttpRequest(obj: unknown): obj is { 591 - req: unknown; 592 - } { 593 - return ( 594 - !!obj && 595 - typeof obj === "object" && 596 - "req" in obj 597 - ); 598 - } 599 - 600 - /** 601 - * Converts an error to a simplified error-like object for logging. 602 - * @param err - The error to convert 603 - * @returns A simplified error object or the original value 604 - * @private 605 - */ 606 - function _toSimplifiedErrorLike(err: unknown) { 607 - if (err instanceof Error) { 608 - return { 609 - name: err.name, 610 - message: err.message, 611 - stack: err.stack, 612 - }; 613 - } 614 - return err; 615 584 } 616 585 617 586 /**
+1 -1
xrpc-server/stream/frames.ts
··· 1 1 import * as uint8arrays from "uint8arrays"; 2 - import { cborDecodeMulti, cborEncode } from "@atproto/common"; 2 + import { cborDecodeMulti, cborEncode } from "@atp/common"; 3 3 import type { 4 4 ErrorFrameBody, 5 5 ErrorFrameHeader,
+1 -1
xrpc-server/stream/logger.ts
··· 1 - import { subsystemLogger } from "@atproto/common"; 1 + import { subsystemLogger } from "@atp/common"; 2 2 3 3 /** 4 4 * Logger instance for XRPC streaming operations.
+3 -3
xrpc-server/stream/server.ts
··· 26 26 async (socket: WebSocket, req: Request) => { 27 27 socket.onerror = (ev: Event | ErrorEvent) => { 28 28 if (ev instanceof ErrorEvent) { 29 - logger.error(ev.error, "websocket error"); 29 + logger.error("websocket error", { error: ev.error }); 30 30 } else { 31 - logger.error(ev, "websocket error"); 31 + logger.error("websocket error", { ev }); 32 32 } 33 33 }; 34 34 try { ··· 58 58 if (err instanceof DisconnectError) { 59 59 return socket.close(err.wsCode, err.xrpcCode); 60 60 } else { 61 - logger.error({ err }, "websocket server error"); 61 + logger.error("websocket server error", { err }); 62 62 return socket.close(CloseCode.Abnormal); 63 63 } 64 64 }
+1 -1
xrpc-server/stream/websocket-keepalive.ts
··· 1 - import { SECOND, wait } from "@atproto/common"; 1 + import { SECOND, wait } from "@atp/common"; 2 2 import { CloseCode, DisconnectError, type WebSocketOptions } from "./types.ts"; 3 3 4 4 /**
-1
xrpc-server/tests/_util.ts
··· 68 68 params: xrpc.Params; 69 69 input: xrpc.Input; 70 70 req: Request; 71 - res: Response; 72 71 }) { 73 72 return verifyAuth(ctx.req.headers.get("authorization")); 74 73 };
+18 -23
xrpc-server/tests/auth_test.ts
··· 1 1 import * as jose from "npm:jose"; 2 - import { MINUTE } from "@atproto/common"; 2 + import { MINUTE } from "@atp/common"; 3 3 import { Secp256k1Keypair } from "@atproto/crypto"; 4 4 import type { LexiconDoc } from "@atproto/lexicon"; 5 5 import { XrpcClient, XRPCError } from "@atproto/xrpc"; ··· 58 58 original: string | undefined; 59 59 }; 60 60 61 - type AuthTestAuth = { 62 - credentials: { username: string }; 63 - artifacts: { original: string }; 64 - }; 65 - 66 61 server.method("io.example.authTest", { 67 62 auth: createBasicAuth({ username: "admin", password: "password" }), 68 63 handler: (ctx: xrpcServer.HandlerContext) => { ··· 90 85 client = new XrpcClient(`http://localhost:${port}`, LEXICONS); 91 86 92 87 // Tests 93 - await Deno.test("creates and validates service auth headers", async () => { 88 + Deno.test("creates and validates service auth headers", async () => { 94 89 const keypair = await Secp256k1Keypair.create(); 95 90 const iss = "did:example:alice"; 96 91 const aud = "did:example:bob"; ··· 104 99 token, 105 100 null, 106 101 null, 107 - async () => await keypair.did(), 102 + () => keypair.did(), 108 103 ); 109 104 assertEquals(validated.iss, iss); 110 105 assertEquals(validated.aud, aud); ··· 115 110 assert(validated.lxm === undefined); 116 111 }); 117 112 118 - await Deno.test("creates and validates service auth headers bound to a particular method", async () => { 113 + Deno.test("creates and validates service auth headers bound to a particular method", async () => { 119 114 const keypair = await Secp256k1Keypair.create(); 120 115 const iss = "did:example:alice"; 121 116 const aud = "did:example:bob"; ··· 130 125 token, 131 126 null, 132 127 lxm, 133 - async () => await keypair.did(), 128 + () => keypair.did(), 134 129 ); 135 130 assertEquals(validated.iss, iss); 136 131 assertEquals(validated.aud, aud); 137 132 assertEquals(validated.lxm, lxm); 138 133 }); 139 134 140 - await Deno.test("fails on bad auth before invalid request payload", async () => { 135 + Deno.test("fails on bad auth before invalid request payload", async () => { 141 136 try { 142 137 await client.call( 143 138 "io.example.authTest", ··· 160 155 } 161 156 }); 162 157 163 - await Deno.test("fails on invalid request payload after good auth", async () => { 158 + Deno.test("fails on invalid request payload after good auth", async () => { 164 159 try { 165 160 await client.call( 166 161 "io.example.authTest", ··· 183 178 } 184 179 }); 185 180 186 - await Deno.test("succeeds on good auth and payload", async () => { 181 + Deno.test("succeeds on good auth and payload", async () => { 187 182 const res = await client.call( 188 183 "io.example.authTest", 189 184 {}, ··· 202 197 }); 203 198 }); 204 199 205 - await Deno.test("verifyJwt tests", async (t) => { 200 + Deno.test("verifyJwt tests", async (t) => { 206 201 await t.step("fails on expired jwt", async () => { 207 202 const keypair = await Secp256k1Keypair.create(); 208 203 const jwt = await xrpcServer.createServiceJwt({ ··· 218 213 jwt, 219 214 "did:example:aud", 220 215 null, 221 - async () => await keypair.did(), 216 + () => keypair.did(), 222 217 ), 223 218 Error, 224 219 "jwt expired", ··· 239 234 jwt, 240 235 "did:example:aud2", 241 236 null, 242 - async () => await keypair.did(), 237 + () => keypair.did(), 243 238 ), 244 239 Error, 245 240 "jwt audience does not match service did", ··· 260 255 jwt, 261 256 "did:example:aud1", 262 257 "com.atproto.repo.putRecord", 263 - async () => await keypair.did(), 258 + () => keypair.did(), 264 259 ), 265 260 Error, 266 261 "bad jwt lexicon method", ··· 281 276 jwt, 282 277 "did:example:aud1", 283 278 "com.atproto.repo.putRecord", 284 - async () => await keypair.did(), 279 + () => keypair.did(), 285 280 ), 286 281 Error, 287 282 "missing jwt lexicon method", ··· 303 298 jwt, 304 299 "did:example:aud", 305 300 null, 306 - async (_did, forceRefresh) => { 301 + (_did, forceRefresh) => { 307 302 if (forceRefresh) { 308 303 usedKeypair2 = true; 309 - return await keypair2.did(); 304 + return keypair2.did(); 310 305 } else { 311 306 usedKeypair1 = true; 312 - return await keypair1.did(); 307 + return keypair1.did(); 313 308 } 314 309 }, 315 310 ); ··· 338 333 jwt, 339 334 "did:example:aud", 340 335 null, 341 - async () => { 342 - return await keypair.did(); 336 + () => { 337 + return keypair.did(); 343 338 }, 344 339 ); 345 340 assertEquals(tryVerify, payload);
+1 -1
xrpc-server/tests/bodies_test.ts
··· 1 - import { cidForCbor } from "@atproto/common"; 1 + import { cidForCbor } from "@atp/common"; 2 2 import { randomBytes } from "@atproto/crypto"; 3 3 import type { LexiconDoc } from "@atproto/lexicon"; 4 4 import { ResponseType, XrpcClient, XRPCError } from "@atproto/xrpc";
+1 -1
xrpc-server/tests/rate-limiter_test.ts
··· 1 - import { MINUTE } from "@atproto/common"; 1 + import { MINUTE } from "@atp/common"; 2 2 import type { LexiconDoc } from "@atproto/lexicon"; 3 3 import { XrpcClient } from "@atproto/xrpc"; 4 4 import * as xrpcServer from "../mod.ts";
+1 -1
xrpc-server/tests/responses_test.ts
··· 1 - import { byteIterableToStream } from "@atproto/common"; 1 + import { byteIterableToStream } from "@atp/common"; 2 2 import type { LexiconDoc } from "@atproto/lexicon"; 3 3 import { XrpcClient } from "@atproto/xrpc"; 4 4 import * as xrpcServer from "../mod.ts";
+1 -1
xrpc-server/tests/subscriptions_test.ts
··· 1 1 import { WebSocket, type WebSocketServer } from "ws"; 2 - import { wait } from "@atproto/common"; 2 + import { wait } from "@atp/common"; 3 3 import type { LexiconDoc } from "@atproto/lexicon"; 4 4 import { 5 5 byFrame,
+13 -5
xrpc-server/types.ts
··· 1 + import type { Context, HonoRequest, Next } from "hono"; 1 2 import { z } from "zod"; 2 3 import type { ErrorResult, XRPCError } from "./errors.ts"; 3 4 import type { CalcKeyFn, CalcPointsFn } from "./rate-limiter.ts"; ··· 11 12 12 13 /** 13 14 * Handler function for catching all unmatched routes. 14 - * @param req - The HTTP request object 15 - * @returns A promise that resolves to a Response 15 + * @param c - The Hono context object 16 + * @param next - The next middleware function 17 + * @returns A promise that resolves to void or a Response 16 18 */ 17 19 export type CatchallHandler = ( 18 - req: Request, 19 - res: Response, 20 - ) => Promise<Response>; 20 + c: Context, 21 + next: Next, 22 + ) => Promise<void | Response>; 21 23 22 24 /** 23 25 * Configuration options for the XRPC server. ··· 52 54 */ 53 55 errorParser?: (err: unknown) => XRPCError; 54 56 }; 57 + 58 + /** 59 + * Raw query parameters from the HTTP request before type conversion. 60 + */ 61 + export type UndecodedParams = HonoRequest["query"]; 55 62 56 63 /** 57 64 * Basic primitive types supported in XRPC parameters. ··· 163 170 | ((ctx: C) => Awaitable<A | ErrorResult>) 164 171 | ((ctx: C) => Awaitable<A>); 165 172 173 + // Handler context that combines Hono Context with XRPC-specific properties 166 174 /** 167 175 * Context object provided to XRPC method handlers containing request data and utilities. 168 176 * @template A - Authentication type