service status on atproto
at main 3.6 kB view raw
1import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons"; 2import { schemas as BarometerSchemas } from "barometer-lexicon"; 3import { config } from "./config"; 4import { ok as clientOk } from "@atcute/client"; 5import { atpClient } from "."; 6import { now as generateTid } from "@atcute/tid"; 7import type { AtprotoDid } from "@atcute/lexicons/syntax"; 8 9export type CollectionUri<Nsid extends string> = 10 `at://${AtprotoDid}/${Nsid}/${RecordKey}`; 11export type StateUri = CollectionUri<"systems.gaze.barometer.state">; 12export type CheckUri = CollectionUri<"systems.gaze.barometer.check">; 13export type ServiceUri = CollectionUri<"systems.gaze.barometer.service">; 14 15export const getUri = < 16 Collection extends 17 | "systems.gaze.barometer.state" 18 | "systems.gaze.barometer.check" 19 | "systems.gaze.barometer.service", 20>( 21 collection: Collection, 22 rkey: RecordKey, 23): CollectionUri<Collection> => { 24 return `at://${config.repoDid}/${collection}/${rkey}`; 25}; 26 27export type Result<T, E> = 28 | { 29 ok: true; 30 value: T; 31 } 32 | { 33 ok: false; 34 error: E; 35 }; 36 37export const ok = <T, E>(value: T): Result<T, E> => { 38 return { ok: true, value }; 39}; 40export const err = <T, E>(error: E): Result<T, E> => { 41 return { ok: false, error }; 42}; 43 44export const expect = <T, E>( 45 v: Result<T, E>, 46 msg: string = "expected result to not be error", 47) => { 48 if (v.ok) { 49 return v.value; 50 } 51 throw msg; 52}; 53 54export const getRecord = async < 55 Collection extends keyof typeof BarometerSchemas, 56>( 57 collection: Collection, 58 rkey: RecordKey, 59): Promise< 60 Result<InferOutput<(typeof BarometerSchemas)[Collection]>, string> 61> => { 62 let maybeRecord = await atpClient.get("com.atproto.repo.getRecord", { 63 params: { 64 collection, 65 repo: config.repoDid, 66 rkey, 67 }, 68 }); 69 if (!maybeRecord.ok) 70 return err(maybeRecord.data.message ?? maybeRecord.data.error); 71 const maybeTyped = safeParse( 72 BarometerSchemas[collection], 73 maybeRecord.data.value, 74 ); 75 if (!maybeTyped.ok) return err(maybeTyped.message); 76 return maybeTyped; 77}; 78 79export const putRecord = async < 80 Collection extends keyof typeof BarometerSchemas, 81>( 82 record: InferOutput<(typeof BarometerSchemas)[Collection]>, 83 rkey: RecordKey | null = null, 84) => { 85 return await clientOk( 86 atpClient.post("com.atproto.repo.putRecord", { 87 input: { 88 collection: record["$type"], 89 repo: config.repoDid, 90 record, 91 rkey: rkey ?? generateTid(), 92 }, 93 }), 94 ); 95}; 96 97export const log = { 98 info: console.log, 99 warn: console.warn, 100 error: console.error, 101}; 102 103export type Middleware = ( 104 req: Bun.BunRequest, 105) => Promise<Bun.BunRequest | Response>; 106 107export const applyMiddleware = 108 <T extends string>( 109 fns: Middleware[], 110 route: Bun.RouterTypes.RouteHandler<T>, 111 ): Bun.RouterTypes.RouteHandler<T> => 112 async (req, srv) => { 113 for (const fn of fns) { 114 const result = await fn(req); 115 if (result instanceof Response) return result; 116 else req = result; 117 } 118 return route(req, srv); 119 }; 120 121type Routes = Record< 122 string, 123 Record<string, Bun.RouterTypes.RouteHandler<string>> 124>; 125export const applyMiddlewareAll = ( 126 fns: Middleware[], 127 routes: Routes, 128): Routes => { 129 return Object.fromEntries( 130 Object.entries(routes).map(([path, route]) => { 131 return [ 132 path, 133 Object.fromEntries( 134 Object.entries(route).map(([method, handler]) => { 135 return [method, applyMiddleware(fns, handler)]; 136 }), 137 ), 138 ]; 139 }), 140 ); 141};