public uptime monitoring + (soon) observability with events saved to PDS
1import { Client, ok, simpleFetchHandler } from '@atcute/client';
2import type {} from '@atcute/atproto';
3import type { ActorIdentifier, Did, Handle } from '@atcute/lexicons';
4import type { UptimeCheck, UptimeCheckRecord } from './types.ts';
5
6/**
7 * fetches uptime check records from a PDS for a given DID with cursor support
8 *
9 * @param pds the PDS URL
10 * @param did the DID or handle to fetch records for
11 * @param cursor optional cursor for pagination
12 * @returns object with records array and optional next cursor
13 */
14export async function fetchUptimeChecks(
15 pds: string,
16 did: ActorIdentifier,
17 cursor?: string,
18): Promise<{ records: UptimeCheckRecord[]; cursor?: string }> {
19 const handler = simpleFetchHandler({ service: pds });
20 const rpc = new Client({ handler });
21
22 // resolve handle to DID if needed
23 let resolvedDid: Did;
24 if (!did.startsWith('did:')) {
25 const handleData = await ok(
26 rpc.get('com.atproto.identity.resolveHandle', {
27 params: { handle: did as Handle },
28 }),
29 );
30 resolvedDid = handleData.did;
31 } else {
32 resolvedDid = did as Did;
33 }
34
35 // fetch uptime check records with cursor
36 const response = await ok(
37 rpc.get('com.atproto.repo.listRecords', {
38 params: {
39 repo: resolvedDid,
40 collection: 'pet.nkp.uptime.check',
41 limit: 100,
42 cursor,
43 },
44 }),
45 );
46
47 // transform records into a more usable format
48 const records = response.records.map((record) => ({
49 uri: record.uri,
50 cid: record.cid,
51 value: record.value as unknown as UptimeCheck,
52 indexedAt: new Date((record.value as unknown as UptimeCheck).checkedAt),
53 }));
54
55 return {
56 records,
57 cursor: response.cursor,
58 };
59}
60
61/**
62 * fetches up to 2000 uptime check records using cursor pagination with progress callback
63 * stops early if no more records are available
64 *
65 * @param pds the PDS URL
66 * @param did the DID or handle to fetch records for
67 * @param onProgress optional callback called with each batch of records
68 * @returns array of uptime check records (max 2000)
69 */
70export async function fetchInitialUptimeChecks(
71 pds: string,
72 did: ActorIdentifier,
73 onProgress?: (records: UptimeCheckRecord[]) => void,
74): Promise<UptimeCheckRecord[]> {
75 let allRecords: UptimeCheckRecord[] = [];
76 let cursor: string | undefined;
77 const maxRecords = 2000;
78 const batchSize = 100;
79 let totalFetched = 0;
80
81 do {
82 const result = await fetchUptimeChecks(pds, did, cursor);
83 const newRecords = result.records;
84 allRecords = allRecords.concat(newRecords);
85 totalFetched += newRecords.length;
86 cursor = result.cursor;
87
88 // Call progress callback with the accumulated records
89 if (onProgress) {
90 onProgress(allRecords);
91 }
92
93 // Stop early if we got fewer records than the batch size (indicating we've exhausted all records)
94 // or if no cursor is returned (also indicating no more records)
95 if (newRecords.length < batchSize || !cursor) {
96 break;
97 }
98
99 } while (cursor && totalFetched < maxRecords);
100
101 return allRecords;
102}