service status on atproto

Compare changes

Choose any two refs to compare.

+34
.gitignore
···
+
# dependencies (bun install)
+
node_modules
+
+
# output
+
out
+
dist
+
*.tgz
+
+
# code coverage
+
coverage
+
*.lcov
+
+
# logs
+
logs
+
_.log
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+
# dotenv environment variable files
+
.env
+
.env.development.local
+
.env.test.local
+
.env.production.local
+
.env.local
+
+
# caches
+
.eslintcache
+
.cache
+
*.tsbuildinfo
+
+
# IntelliJ based IDEs
+
.idea
+
+
# Finder (MacOS) folder config
+
.DS_Store
+30
lexicon/check.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.check",
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "any",
+
"record": {
+
"type": "object",
+
"required": ["name", "forService"],
+
"properties": {
+
"name": {
+
"type": "string"
+
},
+
"description": {
+
"type": "string"
+
},
+
"forService": {
+
"type": "string",
+
"format": "at-uri"
+
},
+
"currentState": {
+
"type": "string",
+
"format": "at-uri"
+
}
+
}
+
}
+
}
+
}
+
}
+25
lexicon/host.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.host",
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "any",
+
"record": {
+
"type": "object",
+
"required": ["name", "os"],
+
"properties": {
+
"name": {
+
"type": "string"
+
},
+
"description": {
+
"type": "string"
+
},
+
"os": {
+
"type": "string"
+
}
+
}
+
}
+
}
+
}
+
}
+34
lexicon/service.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.service",
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "any",
+
"record": {
+
"type": "object",
+
"required": ["name"],
+
"properties": {
+
"name": {
+
"type": "string"
+
},
+
"description": {
+
"type": "string"
+
},
+
"appUri": {
+
"type": "string",
+
"format": "uri"
+
},
+
"hostedBy": {
+
"type": "string",
+
"format": "at-uri"
+
},
+
"currentState": {
+
"type": "string",
+
"format": "at-uri"
+
}
+
}
+
}
+
}
+
}
+
}
+42
lexicon/state.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.state",
+
"defs": {
+
"main": {
+
"type": "record",
+
"key": "tid",
+
"record": {
+
"type": "object",
+
"required": ["state", "changedAt", "forService"],
+
"properties": {
+
"state": {
+
"type": "string",
+
"enum": [
+
"systems.gaze.barometer.status.healthy",
+
"systems.gaze.barometer.status.degraded"
+
]
+
},
+
"reason": {
+
"type": "string"
+
},
+
"changedAt": {
+
"type": "string",
+
"format": "datetime"
+
},
+
"forService": {
+
"type": "string",
+
"format": "at-uri"
+
},
+
"generatedBy": {
+
"type": "string",
+
"format": "at-uri"
+
},
+
"previous": {
+
"type": "string",
+
"format": "at-uri"
+
}
+
}
+
}
+
}
+
}
+
}
+10
lexicon/status/degraded.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.status.degraded",
+
"defs": {
+
"main": {
+
"type": "token",
+
"description": "represents that a service / check is not working as it should"
+
}
+
}
+
}
+10
lexicon/status/healthy.json
···
+
{
+
"lexicon": 1,
+
"id": "systems.gaze.barometer.status.healthy",
+
"defs": {
+
"main": {
+
"type": "token",
+
"description": "represents that a service / check is working properly"
+
}
+
}
+
}
+34
lib/.gitignore
···
+
# dependencies (bun install)
+
node_modules
+
+
# output
+
out
+
dist
+
*.tgz
+
+
# code coverage
+
coverage
+
*.lcov
+
+
# logs
+
logs
+
_.log
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+
# dotenv environment variable files
+
.env
+
.env.development.local
+
.env.test.local
+
.env.production.local
+
.env.local
+
+
# caches
+
.eslintcache
+
.cache
+
*.tsbuildinfo
+
+
# IntelliJ based IDEs
+
.idea
+
+
# Finder (MacOS) folder config
+
.DS_Store
+49
lib/bun.lock
···
+
{
+
"lockfileVersion": 1,
+
"workspaces": {
+
"": {
+
"name": "barometer-lexicon",
+
"dependencies": {
+
"@atcute/lexicons": "^1.1.0",
+
},
+
"devDependencies": {
+
"@atcute/lex-cli": "^2.1.1",
+
"@types/bun": "latest",
+
},
+
"peerDependencies": {
+
"typescript": "^5",
+
},
+
},
+
},
+
"packages": {
+
"@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=="],
+
+
"@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="],
+
+
"@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="],
+
+
"@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="],
+
+
"@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="],
+
+
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
+
+
"@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
+
+
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
+
+
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
+
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
+
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
+
+
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
+
}
+
}
+6
lib/lex.config.js
···
+
import { defineLexiconConfig } from "@atcute/lex-cli";
+
+
export default defineLexiconConfig({
+
files: ["../lexicon/**/*.json"],
+
outdir: "src/lexicons/",
+
});
+27
lib/package.json
···
+
{
+
"name": "barometer-lexicon",
+
"type": "module",
+
"files": [
+
"dist/",
+
"src/"
+
],
+
"exports": {
+
".": "./dist/index.js",
+
"./types/*": "./dist/lexicons/types/com/atproto/*.js"
+
},
+
"scripts": {
+
"build": "tsc",
+
"generate": "rm -rf ./src/lexicons/; lex-cli generate -c ./lex.config.js",
+
"prepublish": "rm -rf dist; pnpm run build"
+
},
+
"devDependencies": {
+
"@atcute/lex-cli": "^2.1.1",
+
"@types/bun": "latest"
+
},
+
"peerDependencies": {
+
"typescript": "^5"
+
},
+
"dependencies": {
+
"@atcute/lexicons": "^1.1.0"
+
}
+
}
+26
lib/src/index.ts
···
+
import {
+
SystemsGazeBarometerCheck,
+
SystemsGazeBarometerHost,
+
SystemsGazeBarometerService,
+
SystemsGazeBarometerState,
+
} from "./lexicons/index.js";
+
+
export * from "./lexicons/index.js";
+
+
export const schemas = {
+
"systems.gaze.barometer.host": SystemsGazeBarometerHost.mainSchema,
+
"systems.gaze.barometer.service": SystemsGazeBarometerService.mainSchema,
+
"systems.gaze.barometer.check": SystemsGazeBarometerCheck.mainSchema,
+
"systems.gaze.barometer.state": SystemsGazeBarometerState.mainSchema,
+
};
+
export const nsid = <
+
T extends
+
| typeof SystemsGazeBarometerHost
+
| typeof SystemsGazeBarometerService
+
| typeof SystemsGazeBarometerCheck
+
| typeof SystemsGazeBarometerState,
+
>(
+
lex: T,
+
): (typeof schemas)[typeof lex.mainSchema.object.shape.$type.expected] => {
+
return schemas[lex.mainSchema.object.shape.$type.expected];
+
};
+6
lib/src/lexicons/index.ts
···
+
export * as SystemsGazeBarometerCheck from "./types/systems/gaze/barometer/check.js";
+
export * as SystemsGazeBarometerHost from "./types/systems/gaze/barometer/host.js";
+
export * as SystemsGazeBarometerService from "./types/systems/gaze/barometer/service.js";
+
export * as SystemsGazeBarometerState from "./types/systems/gaze/barometer/state.js";
+
export * as SystemsGazeBarometerStatusDegraded from "./types/systems/gaze/barometer/status/degraded.js";
+
export * as SystemsGazeBarometerStatusHealthy from "./types/systems/gaze/barometer/status/healthy.js";
+28
lib/src/lexicons/types/systems/gaze/barometer/check.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
import type {} from "@atcute/lexicons/ambient";
+
+
const _mainSchema = /*#__PURE__*/ v.record(
+
/*#__PURE__*/ v.string(),
+
/*#__PURE__*/ v.object({
+
$type: /*#__PURE__*/ v.literal("systems.gaze.barometer.check"),
+
currentState: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()),
+
description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
+
forService: /*#__PURE__*/ v.resourceUriString(),
+
name: /*#__PURE__*/ v.string(),
+
}),
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+
+
declare module "@atcute/lexicons/ambient" {
+
interface Records {
+
"systems.gaze.barometer.check": mainSchema;
+
}
+
}
+27
lib/src/lexicons/types/systems/gaze/barometer/host.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
import type {} from "@atcute/lexicons/ambient";
+
+
const _mainSchema = /*#__PURE__*/ v.record(
+
/*#__PURE__*/ v.string(),
+
/*#__PURE__*/ v.object({
+
$type: /*#__PURE__*/ v.literal("systems.gaze.barometer.host"),
+
description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
+
name: /*#__PURE__*/ v.string(),
+
os: /*#__PURE__*/ v.string(),
+
}),
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+
+
declare module "@atcute/lexicons/ambient" {
+
interface Records {
+
"systems.gaze.barometer.host": mainSchema;
+
}
+
}
+29
lib/src/lexicons/types/systems/gaze/barometer/service.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
import type {} from "@atcute/lexicons/ambient";
+
+
const _mainSchema = /*#__PURE__*/ v.record(
+
/*#__PURE__*/ v.string(),
+
/*#__PURE__*/ v.object({
+
$type: /*#__PURE__*/ v.literal("systems.gaze.barometer.service"),
+
appUri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()),
+
currentState: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()),
+
description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
+
hostedBy: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()),
+
name: /*#__PURE__*/ v.string(),
+
}),
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+
+
declare module "@atcute/lexicons/ambient" {
+
interface Records {
+
"systems.gaze.barometer.service": mainSchema;
+
}
+
}
+33
lib/src/lexicons/types/systems/gaze/barometer/state.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
import type {} from "@atcute/lexicons/ambient";
+
+
const _mainSchema = /*#__PURE__*/ v.record(
+
/*#__PURE__*/ v.tidString(),
+
/*#__PURE__*/ v.object({
+
$type: /*#__PURE__*/ v.literal("systems.gaze.barometer.state"),
+
changedAt: /*#__PURE__*/ v.datetimeString(),
+
forService: /*#__PURE__*/ v.resourceUriString(),
+
generatedBy: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()),
+
previous: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()),
+
reason: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()),
+
state: /*#__PURE__*/ v.literalEnum([
+
"systems.gaze.barometer.status.degraded",
+
"systems.gaze.barometer.status.healthy",
+
]),
+
}),
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export interface Main extends v.InferInput<typeof mainSchema> {}
+
+
declare module "@atcute/lexicons/ambient" {
+
interface Records {
+
"systems.gaze.barometer.state": mainSchema;
+
}
+
}
+14
lib/src/lexicons/types/systems/gaze/barometer/status/degraded.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
+
const _mainSchema = /*#__PURE__*/ v.literal(
+
"systems.gaze.barometer.status.degraded",
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export type Main = v.InferInput<typeof mainSchema>;
+14
lib/src/lexicons/types/systems/gaze/barometer/status/healthy.ts
···
+
import type {} from "@atcute/lexicons";
+
import * as v from "@atcute/lexicons/validations";
+
+
const _mainSchema = /*#__PURE__*/ v.literal(
+
"systems.gaze.barometer.status.healthy",
+
);
+
+
type main$schematype = typeof _mainSchema;
+
+
export interface mainSchema extends main$schematype {}
+
+
export const mainSchema = _mainSchema as mainSchema;
+
+
export type Main = v.InferInput<typeof mainSchema>;
+22
lib/tsconfig.json
···
+
{
+
"compilerOptions": {
+
"outDir": "dist/",
+
"esModuleInterop": true,
+
"skipLibCheck": true,
+
"target": "ESNext",
+
"allowJs": true,
+
"resolveJsonModule": true,
+
"moduleDetection": "force",
+
"isolatedModules": true,
+
"verbatimModuleSyntax": true,
+
"strict": true,
+
"noImplicitOverride": true,
+
"noUnusedLocals": true,
+
"noUnusedParameters": true,
+
"noFallthroughCasesInSwitch": true,
+
"module": "NodeNext",
+
"sourceMap": true,
+
"declaration": true
+
},
+
"include": ["src"]
+
}
+145
proxy/bun.lock
···
+
{
+
"lockfileVersion": 1,
+
"workspaces": {
+
"": {
+
"name": "barometer",
+
"dependencies": {
+
"@atcute/atproto": "^3.1.1",
+
"@atcute/client": "^4.0.3",
+
"@atcute/identity": "^1.0.3",
+
"@atcute/identity-resolver": "^1.1.3",
+
"@atcute/jetstream": "^1.0.2",
+
"@atcute/lexicons": "^1.1.0",
+
"@atcute/tid": "^1.0.2",
+
"barometer-lexicon": "file:../lib",
+
"nanoid": "^5.1.5",
+
"parsimmon": "^1.18.1",
+
},
+
"devDependencies": {
+
"@types/bun": "latest",
+
"@types/parsimmon": "^1.10.9",
+
"concurrently": "^9.2.0",
+
},
+
"peerDependencies": {
+
"typescript": "^5",
+
},
+
},
+
},
+
"packages": {
+
"@atcute/atproto": ["@atcute/atproto@3.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.1.0" } }, "sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg=="],
+
+
"@atcute/client": ["@atcute/client@4.0.3", "", { "dependencies": { "@atcute/identity": "^1.0.2", "@atcute/lexicons": "^1.0.3" } }, "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw=="],
+
+
"@atcute/identity": ["@atcute/identity@1.0.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@badrap/valita": "^0.4.5" } }, "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw=="],
+
+
"@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=="],
+
+
"@atcute/jetstream": ["@atcute/jetstream@1.0.2", "", { "dependencies": { "@atcute/lexicons": "^1.0.2", "@badrap/valita": "^0.4.2", "@mary-ext/event-iterator": "^1.0.0", "@mary-ext/simple-event-emitter": "^1.0.0", "partysocket": "^1.1.4", "type-fest": "^4.41.0", "yocto-queue": "^1.2.1" } }, "sha512-ZtdNNxl4zq9cgUpXSL9F+AsXUZt0Zuyj0V7974D7LxdMxfTItPnMZ9dRG8GoFkkGz3+pszdsG888Ix8C0F2+mA=="],
+
+
"@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=="],
+
+
"@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="],
+
+
"@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="],
+
+
"@atcute/tid": ["@atcute/tid@1.0.2", "", {}, "sha512-ahmjroNyeDPJhtuf3+HTJropaH04HmJ8fhntDu73Gpz/RkAF7+nkz6kcP2QTgfvMCgMPAJUdskAAP82GPDTY9w=="],
+
+
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow=="],
+
+
"@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="],
+
+
"@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="],
+
+
"@mary-ext/event-iterator": ["@mary-ext/event-iterator@1.0.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ=="],
+
+
"@mary-ext/simple-event-emitter": ["@mary-ext/simple-event-emitter@1.0.0", "", {}, "sha512-meA/zJZKIN1RVBNEYIbjufkUrW7/tRjHH60FjolpG1ixJKo76TB208qefQLNdOVDA7uIG0CGEDuhmMirtHKLAg=="],
+
+
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
+
+
"@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
+
+
"@types/parsimmon": ["@types/parsimmon@1.10.9", "", {}, "sha512-O2M2x1w+m7gWLen8i5DOy6tWRnbRcsW6Pke3j3HAsJUrPb4g0MgjksIUm2aqUtCYxy7Qjr3CzjjwQBzhiGn46A=="],
+
+
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
+
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+
"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" } }],
+
+
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
+
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+
"concurrently": ["concurrently@9.2.0", "", { "dependencies": { "chalk": "^4.1.2", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" }, "bin": { "concurrently": "dist/bin/concurrently.js", "conc": "dist/bin/concurrently.js" } }, "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ=="],
+
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
+
+
"event-target-polyfill": ["event-target-polyfill@0.0.4", "", {}, "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="],
+
+
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+
+
"nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
+
+
"parsimmon": ["parsimmon@1.18.1", "", {}, "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw=="],
+
+
"partysocket": ["partysocket@1.1.4", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-jXP7PFj2h5/v4UjDS8P7MZy6NJUQ7sspiFyxL4uc/+oKOL+KdtXzHnTV8INPGxBrLTXgalyG3kd12Qm7WrYc3A=="],
+
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
+
+
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
+
+
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
+
+
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
+
+
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+
"yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="],
+
+
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
}
+
}
+42
proxy/gen-routes.ts
···
+
#!/usr/bin/env bun
+
// build-routes.ts - Run this at build time
+
+
import { Glob } from "bun";
+
import { writeFileSync } from "fs";
+
+
const routeImports: string[] = [];
+
const routeMap: string[] = [];
+
+
// Use Bun.Glob to find all route files
+
const glob = new Glob("*.ts");
+
+
for await (const file of glob.scan("./src/routes")) {
+
// Skip index.ts
+
if (file === "index.ts") continue;
+
+
const routeName = file.replace(".ts", "");
+
const routePath = `/${routeName}`;
+
+
// Generate import statement
+
routeImports.push(`import * as ${routeName}Route from "./${routeName}";`);
+
+
// Generate route map entry
+
routeMap.push(` "${routePath}": ${routeName}Route`);
+
}
+
+
// Generate the complete index.ts content
+
const indexContent = `// Auto-generated route index
+
${routeImports.join("\n")}
+
+
export const routes: Record<
+
string,
+
Record<string, Bun.RouterTypes.RouteHandler<string>>
+
> = {
+
${routeMap.join(",\n")}
+
};
+
+
export default routes;
+
`;
+
+
// Write the generated index.ts file
+
writeFileSync("./src/routes/index.ts", indexContent);
+30
proxy/package.json
···
+
{
+
"name": "barometer",
+
"module": "index.ts",
+
"type": "module",
+
"private": true,
+
"scripts": {
+
"build:routes": "bun run gen-routes.ts",
+
"dev": "bun run build:routes && concurrently 'bun --watch gen-routes.ts' 'bun --watch src/index.ts'"
+
},
+
"devDependencies": {
+
"@types/bun": "latest",
+
"@types/parsimmon": "^1.10.9",
+
"concurrently": "^9.2.0"
+
},
+
"peerDependencies": {
+
"typescript": "^5"
+
},
+
"dependencies": {
+
"@atcute/atproto": "^3.1.1",
+
"@atcute/client": "^4.0.3",
+
"@atcute/identity": "^1.0.3",
+
"@atcute/identity-resolver": "^1.1.3",
+
"@atcute/jetstream": "^1.0.2",
+
"@atcute/lexicons": "^1.1.0",
+
"@atcute/tid": "^1.0.2",
+
"barometer-lexicon": "file:../lib",
+
"nanoid": "^5.1.5",
+
"parsimmon": "^1.18.1"
+
}
+
}
+30
proxy/src/config.ts
···
+
import { isDid, type AtprotoDid } from "@atcute/lexicons/syntax";
+
import { env } from "process";
+
+
interface Config {
+
repoDid: AtprotoDid;
+
appPass: string;
+
hostName?: string;
+
hostDescription?: string;
+
}
+
+
const required = (value: unknown) => typeof value !== "undefined";
+
+
const getConfig = (prefix: string): Config => {
+
const get = <Value>(
+
name: string,
+
check: (value: unknown) => boolean = () => true,
+
): Value => {
+
const value = env[`${prefix}${name}`];
+
if (check(value)) return value as Value;
+
throw `config key ${name} is invalid`;
+
};
+
return {
+
repoDid: get("REPO_DID", isDid),
+
appPass: get("APP_PASSWORD", required),
+
hostName: get("HOST_NAME"),
+
hostDescription: get("HOST_DESCRIPTION"),
+
};
+
};
+
+
export const config = getConfig("BAROMETER_");
+78
proxy/src/index.ts
···
+
import os from "os";
+
import { Client, CredentialManager } from "@atcute/client";
+
import { getPdsEndpoint } from "@atcute/identity";
+
import {
+
CompositeDidDocumentResolver,
+
PlcDidDocumentResolver,
+
WebDidDocumentResolver,
+
} from "@atcute/identity-resolver";
+
import { config } from "./config";
+
import type {} from "@atcute/atproto";
+
import {
+
applyMiddleware,
+
applyMiddlewareAll,
+
getRecord,
+
log,
+
ok,
+
putRecord,
+
type Middleware,
+
type Result,
+
} from "./utils";
+
import store from "./store";
+
import routes from "./routes";
+
import { handleEvents } from "./jetstream";
+
+
const docResolver = new CompositeDidDocumentResolver({
+
methods: {
+
plc: new PlcDidDocumentResolver(),
+
web: new WebDidDocumentResolver(),
+
},
+
});
+
+
const pdsUrl = getPdsEndpoint(await docResolver.resolve(config.repoDid));
+
if (pdsUrl === undefined) {
+
throw `no pds found`;
+
}
+
console.info(`pds is ${pdsUrl}`);
+
+
const creds = new CredentialManager({ service: pdsUrl });
+
const session = await creds.login({
+
identifier: config.repoDid,
+
password: config.appPass,
+
});
+
export const atpClient = new Client({ handler: creds });
+
+
// fetch host record for this host
+
const maybeRecord = await getRecord(
+
"systems.gaze.barometer.host",
+
store.hostname,
+
);
+
if (maybeRecord.ok) {
+
store.host = maybeRecord.value;
+
}
+
+
// if it doesnt exist we make a new one
+
if (store.host === null) {
+
await putRecord(
+
{
+
$type: "systems.gaze.barometer.host",
+
name: config.hostName ?? store.hostname,
+
description: config.hostDescription,
+
os: os.platform(),
+
},
+
store.hostname,
+
);
+
}
+
+
const traceRequest: Middleware = async (req) => {
+
const url = new URL(req.url);
+
log.info(`${req.method} ${url.pathname}`);
+
return req;
+
};
+
const server = Bun.serve({
+
routes: applyMiddlewareAll([traceRequest], routes),
+
});
+
+
console.log(`server running on http://localhost:${server.port}`);
+
+
await handleEvents();
+126
proxy/src/jetstream.ts
···
+
import { JetstreamSubscription } from "@atcute/jetstream";
+
import {
+
is,
+
parse,
+
parseCanonicalResourceUri,
+
type RecordKey,
+
} from "@atcute/lexicons";
+
import {
+
SystemsGazeBarometerService,
+
SystemsGazeBarometerCheck,
+
} from "barometer-lexicon";
+
import { config } from "./config";
+
import store, { type Service } from "./store";
+
import { expect, getRecord, getUri, log, type ServiceUri } from "./utils";
+
+
const subscription = new JetstreamSubscription({
+
url: "wss://jetstream2.us-east.bsky.network",
+
wantedCollections: [
+
"systems.gaze.barometer.service",
+
"systems.gaze.barometer.check",
+
"systems.gaze.barometer.state",
+
],
+
wantedDids: [config.repoDid],
+
});
+
+
const handleService = async (
+
record: Record<string, unknown>,
+
rkey: RecordKey,
+
) => {
+
const collection = "systems.gaze.barometer.service";
+
const serviceRecord = parse(SystemsGazeBarometerService.mainSchema, record);
+
// we dont care if its a dangling service
+
if (!serviceRecord.hostedBy) return true;
+
const hostAtUri = expect(parseCanonicalResourceUri(serviceRecord.hostedBy));
+
// not our host
+
if (hostAtUri.rkey !== store.hostname) return true;
+
const serviceUri = getUri(collection, rkey);
+
const service: Service = store.services.get(serviceUri) ?? {
+
record: serviceRecord,
+
checks: new Set(),
+
rkey,
+
};
+
store.services.set(serviceUri, {
+
...service,
+
record: serviceRecord,
+
});
+
return false;
+
};
+
+
const handleCheck = async (
+
record: Record<string, unknown>,
+
rkey: RecordKey,
+
) => {
+
const collection = "systems.gaze.barometer.check";
+
const checkRecord = parse(SystemsGazeBarometerCheck.mainSchema, record);
+
const checkUri = getUri(collection, rkey);
+
const serviceUri = checkRecord.forService as ServiceUri;
+
const maybeService = await store.getOrFetch(serviceUri);
+
if (!maybeService.ok) {
+
log.error(
+
`can't fetch service record (${serviceUri}) for check record (${checkUri})`,
+
);
+
return true;
+
}
+
const service = maybeService.value;
+
service.checks.add(rkey);
+
store.checks.set(checkUri, {
+
record: checkRecord,
+
rkey,
+
});
+
store.services.set(serviceUri, service);
+
return false;
+
};
+
+
export const handleEvents = async () => {
+
for await (const event of subscription) {
+
if (event.kind !== "commit") continue;
+
const { operation, collection, rkey } = event.commit;
+
// log.info(`${operation} at://${event.did}/${collection}/${rkey}`);
+
if (operation === "create" || operation === "update") {
+
const record = event.commit.record;
+
switch (collection) {
+
case "systems.gaze.barometer.service": {
+
if (await handleService(record, rkey)) continue;
+
break;
+
}
+
case "systems.gaze.barometer.check": {
+
if (await handleCheck(record, rkey)) continue;
+
break;
+
}
+
}
+
} else {
+
switch (collection) {
+
case "systems.gaze.barometer.service": {
+
const serviceUri = getUri(collection, rkey);
+
const service = store.services.get(serviceUri);
+
if (!service) continue;
+
for (const checkRkey of service.checks) {
+
store.checks.delete(
+
getUri("systems.gaze.barometer.check", checkRkey),
+
);
+
}
+
store.services.delete(serviceUri);
+
break;
+
}
+
case "systems.gaze.barometer.check": {
+
const checkUri = getUri(collection, rkey);
+
const check = store.checks.get(checkUri);
+
if (!check) continue;
+
const serviceUri = check.record.forService as ServiceUri;
+
const service = store.services.get(serviceUri);
+
if (service) {
+
service.checks.delete(rkey);
+
store.services.set(serviceUri, service);
+
}
+
store.checks.delete(checkUri);
+
break;
+
}
+
case "systems.gaze.barometer.state": {
+
store.states.delete(getUri(collection, rkey));
+
break;
+
}
+
}
+
}
+
}
+
};
+3
proxy/src/routes/_health.ts
···
+
export const GET = async (req: Bun.BunRequest) => {
+
return new Response(JSON.stringify({ version: "0.1.0" }));
+
};
+13
proxy/src/routes/index.ts
···
+
// Auto-generated route index
+
import * as _healthRoute from "./_health";
+
import * as pushRoute from "./push";
+
+
export const routes: Record<
+
string,
+
Record<string, Bun.RouterTypes.RouteHandler<string>>
+
> = {
+
"/_health": _healthRoute,
+
"/push": pushRoute
+
};
+
+
export default routes;
+171
proxy/src/routes/push.ts
···
+
import {
+
err,
+
expect,
+
getRecord,
+
getUri,
+
ok,
+
putRecord,
+
type CheckUri,
+
type CollectionUri,
+
type Result,
+
type ServiceUri,
+
type StateUri,
+
} from "../utils";
+
import {
+
parseCanonicalResourceUri,
+
safeParse,
+
type CanonicalResourceUri,
+
type ParsedCanonicalResourceUri,
+
type ResourceUri,
+
} from "@atcute/lexicons";
+
import store, { type Check, type Service, type State } from "../store";
+
import { systemctlShow } from "../systemd";
+
import { config } from "../config";
+
import { now as generateTid } from "@atcute/tid";
+
import * as v from "@atcute/lexicons/validations";
+
import type { SystemsGazeBarometerService } from "barometer-lexicon";
+
import type { SystemsGazeBarometerState } from "barometer-lexicon";
+
+
// this is hacky but we want to make forService be optional so its okay
+
const StateSchemaSubset = v.record(
+
v.tidString(),
+
v.object({
+
changedAt: v.optional(v.datetimeString()),
+
forService: v.optional(v.resourceUriString()),
+
generatedBy: v.optional(v.resourceUriString()),
+
reason: v.optional(v.string()),
+
state: v.literalEnum([
+
"systems.gaze.barometer.status.degraded",
+
"systems.gaze.barometer.status.healthy",
+
]),
+
}),
+
);
+
+
interface PushRequest {
+
serviceName?: string; // service manager service name
+
state: v.InferOutput<typeof StateSchemaSubset>;
+
}
+
+
const parsePushRequest = (json: unknown): Result<PushRequest, string> => {
+
if (typeof json !== "object" || json === null) {
+
return err("invalid request");
+
}
+
if ("serviceName" in json && typeof json.serviceName !== "string") {
+
return err("serviceName is not a string");
+
}
+
if ("state" in json) {
+
const parsed = safeParse(StateSchemaSubset, json.state);
+
if (!parsed.ok) {
+
return err(`state is invalid: ${parsed.message}`);
+
}
+
} else {
+
return err("state not found");
+
}
+
return ok(json as PushRequest);
+
};
+
+
const error = <Error extends { msg: string }>(
+
error: Error,
+
status: number = 400,
+
) => {
+
return new Response(JSON.stringify(error), { status });
+
};
+
export const POST = async (req: Bun.BunRequest) => {
+
const maybeData = parsePushRequest(await req.json());
+
if (!maybeData.ok) {
+
return error({
+
msg: `invalid request: ${maybeData.error}`,
+
});
+
}
+
const data = maybeData.value;
+
+
let service: Service | undefined = undefined;
+
let serviceAtUri: ServiceUri | undefined;
+
if (data.state.forService) {
+
serviceAtUri = data.state.forService as ServiceUri;
+
const maybeService = await store.getOrFetch(serviceAtUri);
+
if (!maybeService.ok)
+
return error({
+
msg: `could not fetch service: ${maybeService.error}`,
+
});
+
service = maybeService.value;
+
} else if (data.serviceName) {
+
const maybeService = await store.getServiceFromSystemd(data.serviceName);
+
if (!maybeService.ok)
+
return error({
+
msg: `could not fetch service from systemd: ${maybeService.error}`,
+
});
+
const [uri, srv] = maybeService.value;
+
serviceAtUri = uri;
+
service = srv;
+
} else {
+
return error({
+
msg: `either 'state.forService' or 'serviceName' must be provided`,
+
});
+
}
+
+
let check: Check | undefined = undefined;
+
if (data.state.generatedBy) {
+
const maybeCheck = await store.getOrFetch(
+
data.state.generatedBy as CheckUri,
+
);
+
if (!maybeCheck.ok) return error({ msg: maybeCheck.error });
+
check = maybeCheck.value;
+
if (check.record.forService !== serviceAtUri)
+
return error({
+
msg: `check record does not point to the same service as the state record service`,
+
});
+
// update services with check
+
service.checks.add(check.rkey);
+
store.services.set(serviceAtUri, service);
+
}
+
+
// get current state uri
+
const currentStateUri =
+
check && check.record.currentState
+
? check.record.currentState
+
: service.record.currentState;
+
+
if (currentStateUri) {
+
// fetch current state
+
const record = await store.getOrFetch(currentStateUri as StateUri);
+
if (!record.ok) return error({ msg: record.error });
+
const currentState = record.value;
+
+
// check if the state has changed
+
if (currentState.record.state === data.state.state)
+
return error(
+
{
+
msg: `state can't be the same as the latest state`,
+
},
+
208,
+
);
+
}
+
+
const stateRecord: SystemsGazeBarometerState.Main = {
+
$type: "systems.gaze.barometer.state",
+
...data.state,
+
forService: serviceAtUri,
+
changedAt: data.state.changedAt ?? new Date().toISOString(),
+
previous: currentStateUri,
+
};
+
const rkey = generateTid();
+
const result = await putRecord(stateRecord, rkey);
+
+
// store committed state in "cache"
+
store.states.set(result.uri as StateUri, { record: stateRecord, rkey });
+
+
// update check with new state url
+
if (check) {
+
check.record.currentState = result.uri;
+
store.checks.set(getUri("systems.gaze.barometer.check", check.rkey), check);
+
await putRecord(check.record, check.rkey);
+
} else {
+
// update service with new state url
+
service.record.currentState = result.uri;
+
store.services.set(serviceAtUri, service);
+
await putRecord(service.record, service.rkey);
+
}
+
+
return new Response(JSON.stringify({ cid: result.cid, uri: result.uri }));
+
};
+130
proxy/src/store.ts
···
+
import os from "os";
+
import { parseCanonicalResourceUri, type RecordKey } from "@atcute/lexicons";
+
import type {
+
SystemsGazeBarometerCheck,
+
SystemsGazeBarometerHost,
+
SystemsGazeBarometerService,
+
SystemsGazeBarometerState,
+
} from "barometer-lexicon";
+
import {
+
err,
+
expect,
+
getRecord,
+
ok,
+
putRecord,
+
type CheckUri,
+
type CollectionUri,
+
type Result,
+
type ServiceUri,
+
type StateUri,
+
} from "./utils";
+
import { systemctlShow } from "./systemd";
+
import { config } from "./config";
+
import { now as generateTid } from "@atcute/tid";
+
+
export interface State {
+
rkey: RecordKey;
+
record: SystemsGazeBarometerState.Main;
+
}
+
export interface Check {
+
rkey: RecordKey;
+
record: SystemsGazeBarometerCheck.Main;
+
}
+
export interface Service {
+
checks: Set<RecordKey>;
+
rkey: RecordKey;
+
record: SystemsGazeBarometerService.Main;
+
}
+
+
class Store {
+
services: Map<ServiceUri, Service>;
+
checks: Map<CheckUri, Check>;
+
states: Map<StateUri, State>;
+
host: SystemsGazeBarometerHost.Main | null;
+
hostname: string;
+
+
constructor() {
+
this.services = new Map();
+
this.checks = new Map();
+
this.states = new Map();
+
this.host = null;
+
this.hostname = os.hostname();
+
}
+
+
getOrFetch = async <
+
Nsid extends
+
| "systems.gaze.barometer.state"
+
| "systems.gaze.barometer.check"
+
| "systems.gaze.barometer.service",
+
Uri extends CollectionUri<Nsid>,
+
>(
+
uri: Uri,
+
): Promise<
+
Result<
+
Uri extends StateUri
+
? State
+
: Uri extends CheckUri
+
? Check
+
: Uri extends ServiceUri
+
? Service
+
: never,
+
string
+
>
+
> => {
+
const parsedUri = expect(parseCanonicalResourceUri(uri));
+
const nsid = parsedUri.collection;
+
const record = await getRecord(nsid as Nsid, parsedUri.rkey);
+
if (!record.ok)
+
return err(`record not found or is invalid: ${record.error}`);
+
const data = {
+
record: record.value,
+
rkey: parsedUri.rkey,
+
};
+
+
switch (nsid) {
+
case "systems.gaze.barometer.state":
+
this.states.set(uri as StateUri, data as State);
+
return ok(data as any);
+
case "systems.gaze.barometer.check":
+
this.checks.set(uri as CheckUri, data as Check);
+
return ok(data as any);
+
case "systems.gaze.barometer.service":
+
this.services.set(
+
uri as ServiceUri,
+
{ checks: new Set(), ...data } as Service,
+
);
+
return ok(data as any);
+
default:
+
throw new Error(`unsupported namespace: ${nsid}`);
+
}
+
};
+
+
getServiceFromSystemd = async (
+
serviceName: string,
+
): Promise<Result<[ServiceUri, Service], string>> => {
+
const serviceInfo = await systemctlShow(serviceName);
+
if (serviceInfo.ok) {
+
const record: SystemsGazeBarometerService.Main = {
+
$type: "systems.gaze.barometer.service",
+
name: serviceName,
+
description: serviceInfo.value.description,
+
hostedBy: `at://${config.repoDid}/systems.gaze.barometer.host/${store.hostname}`,
+
};
+
const rkey = generateTid();
+
const putAt = await putRecord(record, rkey);
+
const serviceUri = putAt.uri as ServiceUri;
+
const service: Service = {
+
record,
+
checks: new Set(),
+
rkey,
+
};
+
store.services.set(serviceUri, service);
+
return ok([serviceUri, service]);
+
} else {
+
return err(`could not fetch service from systemd: ${serviceInfo.error}`);
+
}
+
};
+
}
+
+
const store = new Store();
+
export default store;
+92
proxy/src/systemd.ts
···
+
import { spawn } from "bun";
+
import P from "parsimmon";
+
import { err, ok, type Result } from "./utils";
+
+
interface SystemctlShowOutput {
+
[key: string]: string;
+
}
+
+
// Parsimmon parsers for systemctl output
+
const newline = P.string("\n");
+
const equals = P.string("=");
+
+
// Key: anything except = and newline
+
const key = P.regexp(/[^=\n]+/).map((s) => s.trim());
+
+
// Single line value: everything until newline (or end of input)
+
const singleLineValue = P.regexp(/[^\n]*/);
+
+
// Continuation line: newline followed by whitespace and content
+
const continuationLine = P.seq(
+
newline,
+
P.regexp(/[ \t]*/), // optional whitespace
+
P.regexp(/[^\n]*/), // content
+
).map(([, , content]) => "\n" + content);
+
+
// Multi-line value: first line + any continuation lines
+
const multiLineValue = P.seq(singleLineValue, continuationLine.many()).map(
+
([first, continuations]) => (first + continuations.join("")).trim(),
+
);
+
+
// Key-value pair: key = value
+
const keyValuePair = P.seq(key, equals, multiLineValue).map(([k, , v]) => ({
+
key: k,
+
value: v,
+
}));
+
+
// Empty line (just whitespace)
+
const emptyLine = P.regexp(/[ \t]*/).result(null);
+
+
// A line is either a key-value pair or empty line
+
const line = P.alt(keyValuePair, emptyLine);
+
+
// Complete systemctl output: lines separated by newlines, ending with optional newline
+
const systemctlOutput = P.seq(line.sepBy(newline), P.alt(newline, P.eof)).map(
+
([lines]) =>
+
lines.filter((l): l is { key: string; value: string } => l !== null),
+
);
+
+
const parseSystemctlOutput = (
+
output: string,
+
): Result<SystemctlShowOutput, string> => {
+
const result = systemctlOutput.parse(output);
+
+
if (!result.status) {
+
return err(
+
`Parse error at position ${result.index.offset}: ${result.expected.join(", ")}`,
+
);
+
}
+
+
const kvMap: SystemctlShowOutput = {};
+
+
for (const { key, value } of result.value) {
+
if (value.length > 0) {
+
kvMap[key.toLowerCase()] = value;
+
}
+
}
+
+
return ok(kvMap);
+
};
+
+
export const systemctlShow = async (
+
serviceName: string,
+
): Promise<Result<SystemctlShowOutput, string>> => {
+
try {
+
const proc = spawn(["systemctl", "show", `${serviceName}.service`], {
+
stdout: "pipe",
+
stderr: "pipe",
+
});
+
+
const output = await new Response(proc.stdout).text();
+
const exitCode = await proc.exited;
+
+
if (exitCode !== 0) {
+
const error = await new Response(proc.stderr).text();
+
return err(`systemctl show failed with exit code ${exitCode}: ${error}`);
+
}
+
+
return parseSystemctlOutput(output);
+
} catch (error) {
+
return err(`failed to execute systemctl show: ${error}`);
+
}
+
};
+141
proxy/src/utils.ts
···
+
import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons";
+
import { schemas as BarometerSchemas } from "barometer-lexicon";
+
import { config } from "./config";
+
import { ok as clientOk } from "@atcute/client";
+
import { atpClient } from ".";
+
import { now as generateTid } from "@atcute/tid";
+
import type { AtprotoDid } from "@atcute/lexicons/syntax";
+
+
export type CollectionUri<Nsid extends string> =
+
`at://${AtprotoDid}/${Nsid}/${RecordKey}`;
+
export type StateUri = CollectionUri<"systems.gaze.barometer.state">;
+
export type CheckUri = CollectionUri<"systems.gaze.barometer.check">;
+
export type ServiceUri = CollectionUri<"systems.gaze.barometer.service">;
+
+
export const getUri = <
+
Collection extends
+
| "systems.gaze.barometer.state"
+
| "systems.gaze.barometer.check"
+
| "systems.gaze.barometer.service",
+
>(
+
collection: Collection,
+
rkey: RecordKey,
+
): CollectionUri<Collection> => {
+
return `at://${config.repoDid}/${collection}/${rkey}`;
+
};
+
+
export type Result<T, E> =
+
| {
+
ok: true;
+
value: T;
+
}
+
| {
+
ok: false;
+
error: E;
+
};
+
+
export const ok = <T, E>(value: T): Result<T, E> => {
+
return { ok: true, value };
+
};
+
export const err = <T, E>(error: E): Result<T, E> => {
+
return { ok: false, error };
+
};
+
+
export const expect = <T, E>(
+
v: Result<T, E>,
+
msg: string = "expected result to not be error",
+
) => {
+
if (v.ok) {
+
return v.value;
+
}
+
throw msg;
+
};
+
+
export const getRecord = async <
+
Collection extends keyof typeof BarometerSchemas,
+
>(
+
collection: Collection,
+
rkey: RecordKey,
+
): Promise<
+
Result<InferOutput<(typeof BarometerSchemas)[Collection]>, string>
+
> => {
+
let maybeRecord = await atpClient.get("com.atproto.repo.getRecord", {
+
params: {
+
collection,
+
repo: config.repoDid,
+
rkey,
+
},
+
});
+
if (!maybeRecord.ok)
+
return err(maybeRecord.data.message ?? maybeRecord.data.error);
+
const maybeTyped = safeParse(
+
BarometerSchemas[collection],
+
maybeRecord.data.value,
+
);
+
if (!maybeTyped.ok) return err(maybeTyped.message);
+
return maybeTyped;
+
};
+
+
export const putRecord = async <
+
Collection extends keyof typeof BarometerSchemas,
+
>(
+
record: InferOutput<(typeof BarometerSchemas)[Collection]>,
+
rkey: RecordKey | null = null,
+
) => {
+
return await clientOk(
+
atpClient.post("com.atproto.repo.putRecord", {
+
input: {
+
collection: record["$type"],
+
repo: config.repoDid,
+
record,
+
rkey: rkey ?? generateTid(),
+
},
+
}),
+
);
+
};
+
+
export const log = {
+
info: console.log,
+
warn: console.warn,
+
error: console.error,
+
};
+
+
export type Middleware = (
+
req: Bun.BunRequest,
+
) => Promise<Bun.BunRequest | Response>;
+
+
export const applyMiddleware =
+
<T extends string>(
+
fns: Middleware[],
+
route: Bun.RouterTypes.RouteHandler<T>,
+
): Bun.RouterTypes.RouteHandler<T> =>
+
async (req, srv) => {
+
for (const fn of fns) {
+
const result = await fn(req);
+
if (result instanceof Response) return result;
+
else req = result;
+
}
+
return route(req, srv);
+
};
+
+
type Routes = Record<
+
string,
+
Record<string, Bun.RouterTypes.RouteHandler<string>>
+
>;
+
export const applyMiddlewareAll = (
+
fns: Middleware[],
+
routes: Routes,
+
): Routes => {
+
return Object.fromEntries(
+
Object.entries(routes).map(([path, route]) => {
+
return [
+
path,
+
Object.fromEntries(
+
Object.entries(route).map(([method, handler]) => {
+
return [method, applyMiddleware(fns, handler)];
+
}),
+
),
+
];
+
}),
+
);
+
};
+30
proxy/tsconfig.json
···
+
{
+
"compilerOptions": {
+
// Environment setup & latest features
+
"lib": ["ESNext"],
+
"target": "ESNext",
+
"module": "Preserve",
+
"moduleDetection": "force",
+
"jsx": "react-jsx",
+
"allowJs": true,
+
+
// Bundler mode
+
"moduleResolution": "bundler",
+
"allowImportingTsExtensions": true,
+
"verbatimModuleSyntax": true,
+
"noEmit": true,
+
+
// Best practices
+
"strict": true,
+
"skipLibCheck": true,
+
"noFallthroughCasesInSwitch": true,
+
"noUncheckedIndexedAccess": true,
+
"noImplicitOverride": true,
+
+
// Some stricter flags (disabled by default)
+
"noUnusedLocals": false,
+
"noUnusedParameters": false,
+
"noPropertyAccessFromIndexSignature": false
+
},
+
"include": ["src/**/*.ts"]
+
}