service status on atproto

feat: implement proxying state records

ptr.pet 982d5ceb 8974b29d

verified
+188 -70
+19 -2
bun.lock proxy/bun.lock
··· 4 4 "": { 5 5 "name": "barometer", 6 6 "dependencies": { 7 + "@atcute/atproto": "^3.1.1", 8 + "@atcute/client": "^4.0.3", 9 + "@atcute/identity": "^1.0.3", 10 + "@atcute/identity-resolver": "^1.1.3", 7 11 "@atcute/lexicons": "^1.1.0", 8 - "barometer-lexicon": "file:lib", 12 + "@atcute/tid": "^1.0.2", 13 + "barometer-lexicon": "file:../lib", 9 14 }, 10 15 "devDependencies": { 11 16 "@types/bun": "latest", ··· 16 21 }, 17 22 }, 18 23 "packages": { 24 + "@atcute/atproto": ["@atcute/atproto@3.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.1.0" } }, "sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg=="], 25 + 26 + "@atcute/client": ["@atcute/client@4.0.3", "", { "dependencies": { "@atcute/identity": "^1.0.2", "@atcute/lexicons": "^1.0.3" } }, "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw=="], 27 + 28 + "@atcute/identity": ["@atcute/identity@1.0.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@badrap/valita": "^0.4.5" } }, "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw=="], 29 + 30 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@atcute/util-fetch": "^1.0.1", "@badrap/valita": "^0.4.4" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-KZgGgg99CWaV7Df3+h3X/WMrDzTPQVfsaoIVbTNLx2B56BvCL2EmaxPSVw/7BFUJMZHlVU4rtoEB4lyvNyMswA=="], 31 + 19 32 "@atcute/lex-cli": ["@atcute/lex-cli@2.1.1", "", { "dependencies": { "@atcute/lexicon-doc": "^1.0.2", "@badrap/valita": "^0.4.5", "@externdefs/collider": "^0.3.0", "picocolors": "^1.1.1", "prettier": "^3.5.3" }, "bin": { "lex-cli": "cli.mjs" } }, "sha512-QaR0sOP8Z24opGHKsSfleDbP/ahUb6HECkVaOqSwG7ORZzbLK1w0265o1BRjCVr2dT6FxlsMUa2Ge85JMA9bxg=="], 20 33 21 34 "@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="], 22 35 23 36 "@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="], 24 37 38 + "@atcute/tid": ["@atcute/tid@1.0.2", "", {}, "sha512-ahmjroNyeDPJhtuf3+HTJropaH04HmJ8fhntDu73Gpz/RkAF7+nkz6kcP2QTgfvMCgMPAJUdskAAP82GPDTY9w=="], 39 + 40 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow=="], 41 + 25 42 "@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="], 26 43 27 44 "@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="], ··· 32 49 33 50 "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], 34 51 35 - "barometer-lexicon": ["barometer-lexicon@file:lib", { "dependencies": { "@atcute/lexicons": "^1.1.0" }, "devDependencies": { "@atcute/lex-cli": "^2.1.1", "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], 52 + "barometer-lexicon": ["barometer-lexicon@file:../lib", { "dependencies": { "@atcute/lexicons": "^1.1.0" }, "devDependencies": { "@atcute/lex-cli": "^2.1.1", "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], 36 53 37 54 "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], 38 55
+1 -1
lib/src/index.ts
··· 1 - export * from "./lexicons/index.ts"; 1 + export * from "./lexicons/index.js";
+12 -20
lib/tsconfig.json
··· 1 1 { 2 2 "compilerOptions": { 3 - // Environment setup & latest features 4 - "lib": ["ESNext"], 3 + "outDir": "dist/", 4 + "esModuleInterop": true, 5 + "skipLibCheck": true, 5 6 "target": "ESNext", 6 - "module": "Preserve", 7 - "moduleDetection": "force", 8 - "jsx": "react-jsx", 9 7 "allowJs": true, 10 - 11 - // Bundler mode 12 - "moduleResolution": "bundler", 13 - "allowImportingTsExtensions": true, 8 + "resolveJsonModule": true, 9 + "moduleDetection": "force", 10 + "isolatedModules": true, 14 11 "verbatimModuleSyntax": true, 15 - "noEmit": true, 16 - 17 - // Best practices 18 12 "strict": true, 19 - "skipLibCheck": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "noUncheckedIndexedAccess": true, 22 13 "noImplicitOverride": true, 23 - 24 - // Some stricter flags (disabled by default) 25 - "noUnusedLocals": false, 26 - "noUnusedParameters": false, 27 - "noPropertyAccessFromIndexSignature": false 14 + "noUnusedLocals": true, 15 + "noUnusedParameters": true, 16 + "noFallthroughCasesInSwitch": true, 17 + "module": "NodeNext", 18 + "sourceMap": true, 19 + "declaration": true 28 20 }, 29 21 "include": ["src"] 30 22 }
-16
package.json
··· 1 - { 2 - "name": "barometer", 3 - "module": "index.ts", 4 - "type": "module", 5 - "private": true, 6 - "devDependencies": { 7 - "@types/bun": "latest" 8 - }, 9 - "peerDependencies": { 10 - "typescript": "^5" 11 - }, 12 - "dependencies": { 13 - "@atcute/lexicons": "^1.1.0", 14 - "barometer-lexicon": "file:lib" 15 - } 16 - }
+24
proxy/package.json
··· 1 + { 2 + "name": "barometer", 3 + "module": "index.ts", 4 + "type": "module", 5 + "private": true, 6 + "scripts": { 7 + "dev": "bun --watch src/index.ts" 8 + }, 9 + "devDependencies": { 10 + "@types/bun": "latest" 11 + }, 12 + "peerDependencies": { 13 + "typescript": "^5" 14 + }, 15 + "dependencies": { 16 + "@atcute/atproto": "^3.1.1", 17 + "@atcute/client": "^4.0.3", 18 + "@atcute/identity": "^1.0.3", 19 + "@atcute/identity-resolver": "^1.1.3", 20 + "@atcute/lexicons": "^1.1.0", 21 + "@atcute/tid": "^1.0.2", 22 + "barometer-lexicon": "file:../lib" 23 + } 24 + }
+102
proxy/src/index.ts
··· 1 + import { 2 + Client, 3 + CredentialManager, 4 + ok, 5 + simpleFetchHandler, 6 + } from "@atcute/client"; 7 + import { getPdsEndpoint } from "@atcute/identity"; 8 + import { 9 + CompositeDidDocumentResolver, 10 + PlcDidDocumentResolver, 11 + WebDidDocumentResolver, 12 + } from "@atcute/identity-resolver"; 13 + import { isDid, type AtprotoDid } from "@atcute/lexicons/syntax"; 14 + import { env } from "process"; 15 + import type {} from "@atcute/atproto"; 16 + import {} from "barometer-lexicon"; 17 + import { SystemsGazeBarometerState } from "barometer-lexicon"; 18 + import { now as generateTid } from "@atcute/tid"; 19 + import { is, safeParse } from "@atcute/lexicons"; 20 + 21 + interface Config { 22 + repoDid: AtprotoDid; 23 + appPass: string; 24 + } 25 + 26 + const getConfig = (prefix: string): Config => { 27 + const get = <Value>( 28 + name: string, 29 + check: (value: unknown) => boolean = (value) => 30 + typeof value !== "undefined", 31 + ): Value => { 32 + const value = env[`${prefix}${name}`]; 33 + if (check(value)) { 34 + return value as Value; 35 + } 36 + throw `config key ${name} is invalid`; 37 + }; 38 + return { 39 + repoDid: get("REPO_DID", isDid), 40 + appPass: get("APP_PASSWORD"), 41 + }; 42 + }; 43 + 44 + const config = getConfig("BAROMETER_"); 45 + 46 + const docResolver = new CompositeDidDocumentResolver({ 47 + methods: { 48 + plc: new PlcDidDocumentResolver(), 49 + web: new WebDidDocumentResolver(), 50 + }, 51 + }); 52 + 53 + const pdsUrl = getPdsEndpoint(await docResolver.resolve(config.repoDid)); 54 + if (pdsUrl === undefined) { 55 + throw `no pds found`; 56 + } 57 + console.info(`pds is ${pdsUrl}`); 58 + 59 + const creds = new CredentialManager({ service: pdsUrl }); 60 + const session = await creds.login({ 61 + identifier: config.repoDid, 62 + password: config.appPass, 63 + }); 64 + const atpClient = new Client({ handler: creds }); 65 + 66 + const server = Bun.serve({ 67 + routes: { 68 + "/push": { 69 + POST: async (req) => { 70 + const maybeState = safeParse( 71 + SystemsGazeBarometerState.mainSchema, 72 + await req.json(), 73 + ); 74 + if (!maybeState.ok) { 75 + return new Response( 76 + JSON.stringify({ 77 + msg: `invalid state: ${maybeState.message}`, 78 + issues: maybeState.issues, 79 + }), 80 + { status: 400 }, 81 + ); 82 + } 83 + const state = maybeState.value; 84 + const result = await ok( 85 + atpClient.post("com.atproto.repo.putRecord", { 86 + input: { 87 + collection: state.$type, 88 + record: state, 89 + repo: config.repoDid, 90 + rkey: generateTid(), 91 + }, 92 + }), 93 + ); 94 + return new Response( 95 + JSON.stringify({ cid: result.cid, uri: result.uri }), 96 + ); 97 + }, 98 + }, 99 + }, 100 + }); 101 + 102 + console.log(`server running on http://localhost:${server.port}`);
+30
proxy/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + // Environment setup & latest features 4 + "lib": ["ESNext"], 5 + "target": "ESNext", 6 + "module": "Preserve", 7 + "moduleDetection": "force", 8 + "jsx": "react-jsx", 9 + "allowJs": true, 10 + 11 + // Bundler mode 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true, 14 + "verbatimModuleSyntax": true, 15 + "noEmit": true, 16 + 17 + // Best practices 18 + "strict": true, 19 + "skipLibCheck": true, 20 + "noFallthroughCasesInSwitch": true, 21 + "noUncheckedIndexedAccess": true, 22 + "noImplicitOverride": true, 23 + 24 + // Some stricter flags (disabled by default) 25 + "noUnusedLocals": false, 26 + "noUnusedParameters": false, 27 + "noPropertyAccessFromIndexSignature": false 28 + }, 29 + "include": ["src/**/*.ts"] 30 + }
-1
src/index.ts
··· 1 - console.log("Hello via Bun!");
-30
tsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - // Environment setup & latest features 4 - "lib": ["ESNext"], 5 - "target": "ESNext", 6 - "module": "Preserve", 7 - "moduleDetection": "force", 8 - "jsx": "react-jsx", 9 - "allowJs": true, 10 - 11 - // Bundler mode 12 - "moduleResolution": "bundler", 13 - "allowImportingTsExtensions": true, 14 - "verbatimModuleSyntax": true, 15 - "noEmit": true, 16 - 17 - // Best practices 18 - "strict": true, 19 - "skipLibCheck": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "noUncheckedIndexedAccess": true, 22 - "noImplicitOverride": true, 23 - 24 - // Some stricter flags (disabled by default) 25 - "noUnusedLocals": false, 26 - "noUnusedParameters": false, 27 - "noPropertyAccessFromIndexSignature": false 28 - }, 29 - "include": ["src/**/*.ts"] 30 - }