···11+export const SECOND = 1000;
22+export const MINUTE = SECOND * 60;
33+export const HOUR = MINUTE * 60;
44+export const DAY = HOUR * 24;
55+66+export const lessThanAgoMs = (time: Date, range: number) => {
77+ return Date.now() < time.getTime() + range;
88+};
99+1010+export const addHoursToDate = (hours: number, startingDate?: Date): Date => {
1111+ // When date is passed, clone before calling `setHours()` so that we are not mutating the original date
1212+ const currentDate = startingDate ? new Date(startingDate) : new Date();
1313+ currentDate.setHours(currentDate.getHours() + hours);
1414+ return currentDate;
1515+};
1616+1717+function isValidISODateString(dateString: string): boolean {
1818+ // Basic format check: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ssZ
1919+ const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
2020+ if (!isoRegex.test(dateString)) {
2121+ return false;
2222+ }
2323+2424+ // Check if the date is valid and matches the original string when parsed and converted back
2525+ const date = new Date(dateString);
2626+ return !isNaN(date.getTime()) && date.toISOString() === dateString;
2727+}
2828+2929+export function toSimplifiedISOSafe(dateStr: string) {
3030+ const date = new Date(dateStr);
3131+ if (isNaN(date.getTime())) {
3232+ return new Date(0).toISOString();
3333+ }
3434+ const iso = date.toISOString();
3535+ if (!isValidISODateString(iso)) {
3636+ // Occurs in rare cases, e.g. where resulting UTC year is negative. These also don't preserve lexical sort.
3737+ return new Date(0).toISOString();
3838+ }
3939+ return iso; // YYYY-MM-DDTHH:mm:ss.sssZ
4040+}
+2-1
common/mod.ts
···11-export * as util from "./util.ts";
21export * as check from "./check.ts";
3243export * from "./env.ts";
···1211export * from "./tid.ts";
1312export * from "./strings.ts";
1413export * from "./logger.ts";
1414+export * from "./dates.ts";
1515+export * from "./util.ts";
-9
common/util.ts
···158158 ) as ArrayBuffer;
159159}
160160161161-export function toSimplifiedISOSafe(dateStr: string) {
162162- const date = new Date(dateStr);
163163- if (isNaN(date.getTime())) {
164164- return new Date(0).toISOString();
165165- }
166166- const iso = date.toISOString();
167167- return iso; // YYYY-MM-DDTHH:mm:ss.sssZ
168168-}
169169-170161export type RetryOptions = {
171162 maxRetries?: number;
172163 getWaitMs?: (n: number) => number | null;
···11-import { cidForCbor } from "@atproto/common";
11+import { cidForCbor } from "@atp/common";
22import { randomBytes } from "@atproto/crypto";
33import type { LexiconDoc } from "@atproto/lexicon";
44import { ResponseType, XrpcClient, XRPCError } from "@atproto/xrpc";
+1-1
xrpc-server/tests/rate-limiter_test.ts
···11-import { MINUTE } from "@atproto/common";
11+import { MINUTE } from "@atp/common";
22import type { LexiconDoc } from "@atproto/lexicon";
33import { XrpcClient } from "@atproto/xrpc";
44import * as xrpcServer from "../mod.ts";
+1-1
xrpc-server/tests/responses_test.ts
···11-import { byteIterableToStream } from "@atproto/common";
11+import { byteIterableToStream } from "@atp/common";
22import type { LexiconDoc } from "@atproto/lexicon";
33import { XrpcClient } from "@atproto/xrpc";
44import * as xrpcServer from "../mod.ts";
+1-1
xrpc-server/tests/subscriptions_test.ts
···11import { WebSocket, type WebSocketServer } from "ws";
22-import { wait } from "@atproto/common";
22+import { wait } from "@atp/common";
33import type { LexiconDoc } from "@atproto/lexicon";
44import {
55 byFrame,
+13-5
xrpc-server/types.ts
···11+import type { Context, HonoRequest, Next } from "hono";
12import { z } from "zod";
23import type { ErrorResult, XRPCError } from "./errors.ts";
34import type { CalcKeyFn, CalcPointsFn } from "./rate-limiter.ts";
···11121213/**
1314 * Handler function for catching all unmatched routes.
1414- * @param req - The HTTP request object
1515- * @returns A promise that resolves to a Response
1515+ * @param c - The Hono context object
1616+ * @param next - The next middleware function
1717+ * @returns A promise that resolves to void or a Response
1618 */
1719export type CatchallHandler = (
1818- req: Request,
1919- res: Response,
2020-) => Promise<Response>;
2020+ c: Context,
2121+ next: Next,
2222+) => Promise<void | Response>;
21232224/**
2325 * Configuration options for the XRPC server.
···5254 */
5355 errorParser?: (err: unknown) => XRPCError;
5456};
5757+5858+/**
5959+ * Raw query parameters from the HTTP request before type conversion.
6060+ */
6161+export type UndecodedParams = HonoRequest["query"];
55625663/**
5764 * Basic primitive types supported in XRPC parameters.
···163170 | ((ctx: C) => Awaitable<A | ErrorResult>)
164171 | ((ctx: C) => Awaitable<A>);
165172173173+// Handler context that combines Hono Context with XRPC-specific properties
166174/**
167175 * Context object provided to XRPC method handlers containing request data and utilities.
168176 * @template A - Authentication type