service status on atproto

Compare changes

Choose any two refs to compare.

-51
bun.lock
··· 1 - { 2 - "lockfileVersion": 1, 3 - "workspaces": { 4 - "": { 5 - "name": "barometer", 6 - "dependencies": { 7 - "@atcute/lexicons": "^1.1.0", 8 - "barometer-lexicon": "file:lib", 9 - }, 10 - "devDependencies": { 11 - "@types/bun": "latest", 12 - }, 13 - "peerDependencies": { 14 - "typescript": "^5", 15 - }, 16 - }, 17 - }, 18 - "packages": { 19 - "@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 - 21 - "@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="], 22 - 23 - "@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="], 24 - 25 - "@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="], 26 - 27 - "@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="], 28 - 29 - "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], 30 - 31 - "@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="], 32 - 33 - "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], 34 - 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" } }], 36 - 37 - "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], 38 - 39 - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 40 - 41 - "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 42 - 43 - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 44 - 45 - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], 46 - 47 - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 48 - 49 - "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], 50 - } 51 - }
+26 -22
lexicon/check.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.check", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "key": "tid", 8 - "record": { 9 - "type": "object", 10 - "required": ["name", "forService"], 11 - "properties": { 12 - "name": { 13 - "type": "string" 14 - }, 15 - "description": { 16 - "type": "string" 17 - }, 18 - "forService": { 19 - "type": "string", 20 - "format": "at-uri" 21 - } 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.check", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "any", 8 + "record": { 9 + "type": "object", 10 + "required": ["name", "forService"], 11 + "properties": { 12 + "name": { 13 + "type": "string" 14 + }, 15 + "description": { 16 + "type": "string" 17 + }, 18 + "forService": { 19 + "type": "string", 20 + "format": "at-uri" 21 + }, 22 + "currentState": { 23 + "type": "string", 24 + "format": "at-uri" 25 + } 26 + } 27 + } 22 28 } 23 - } 24 29 } 25 - } 26 30 }
+21 -21
lexicon/host.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.host", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "key": "any", 8 - "record": { 9 - "type": "object", 10 - "required": ["name", "os"], 11 - "properties": { 12 - "name": { 13 - "type": "string" 14 - }, 15 - "description": { 16 - "type": "string" 17 - }, 18 - "os": { 19 - "type": "string" 20 - } 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.host", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "any", 8 + "record": { 9 + "type": "object", 10 + "required": ["name", "os"], 11 + "properties": { 12 + "name": { 13 + "type": "string" 14 + }, 15 + "description": { 16 + "type": "string" 17 + }, 18 + "os": { 19 + "type": "string" 20 + } 21 + } 22 + } 21 23 } 22 - } 23 24 } 24 - } 25 25 }
+30 -26
lexicon/service.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.service", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "key": "tid", 8 - "record": { 9 - "type": "object", 10 - "required": ["name"], 11 - "properties": { 12 - "name": { 13 - "type": "string" 14 - }, 15 - "description": { 16 - "type": "string" 17 - }, 18 - "hostedBy": { 19 - "type": "string", 20 - "format": "at-uri" 21 - }, 22 - "appUri": { 23 - "type": "string", 24 - "format": "uri" 25 - } 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.service", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "any", 8 + "record": { 9 + "type": "object", 10 + "required": ["name"], 11 + "properties": { 12 + "name": { 13 + "type": "string" 14 + }, 15 + "description": { 16 + "type": "string" 17 + }, 18 + "appUri": { 19 + "type": "string", 20 + "format": "uri" 21 + }, 22 + "hostedBy": { 23 + "type": "string", 24 + "format": "at-uri" 25 + }, 26 + "currentState": { 27 + "type": "string", 28 + "format": "at-uri" 29 + } 30 + } 31 + } 26 32 } 27 - } 28 33 } 29 - } 30 34 }
+38 -42
lexicon/state.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.state", 4 - "defs": { 5 - "main": { 6 - "type": "record", 7 - "key": "tid", 8 - "record": { 9 - "type": "object", 10 - "required": ["from", "to", "changedAt", "forService"], 11 - "properties": { 12 - "from": { 13 - "type": "string", 14 - "enum": [ 15 - "systems.gaze.barometer.status.healthy", 16 - "systems.gaze.barometer.status.degraded", 17 - "systems.gaze.barometer.status.unknown" 18 - ] 19 - }, 20 - "to": { 21 - "type": "string", 22 - "enum": [ 23 - "systems.gaze.barometer.status.healthy", 24 - "systems.gaze.barometer.status.degraded" 25 - ] 26 - }, 27 - "reason": { 28 - "type": "string" 29 - }, 30 - "changedAt": { 31 - "type": "string", 32 - "format": "datetime" 33 - }, 34 - "forService": { 35 - "type": "string", 36 - "format": "at-uri" 37 - }, 38 - "generatedBy": { 39 - "type": "string", 40 - "format": "at-uri" 41 - } 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.state", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["state", "changedAt", "forService"], 11 + "properties": { 12 + "state": { 13 + "type": "string", 14 + "enum": [ 15 + "systems.gaze.barometer.status.healthy", 16 + "systems.gaze.barometer.status.degraded" 17 + ] 18 + }, 19 + "reason": { 20 + "type": "string" 21 + }, 22 + "changedAt": { 23 + "type": "string", 24 + "format": "datetime" 25 + }, 26 + "forService": { 27 + "type": "string", 28 + "format": "at-uri" 29 + }, 30 + "generatedBy": { 31 + "type": "string", 32 + "format": "at-uri" 33 + }, 34 + "previous": { 35 + "type": "string", 36 + "format": "at-uri" 37 + } 38 + } 39 + } 42 40 } 43 - } 44 41 } 45 - } 46 42 }
+7 -7
lexicon/status/degraded.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.status.degraded", 4 - "defs": { 5 - "main": { 6 - "type": "token", 7 - "description": "represents that a service / check is not working as it should" 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.status.degraded", 4 + "defs": { 5 + "main": { 6 + "type": "token", 7 + "description": "represents that a service / check is not working as it should" 8 + } 8 9 } 9 - } 10 10 }
+7 -7
lexicon/status/healthy.json
··· 1 1 { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.status.healthy", 4 - "defs": { 5 - "main": { 6 - "type": "token", 7 - "description": "represents that a service / check is working properly" 2 + "lexicon": 1, 3 + "id": "systems.gaze.barometer.status.healthy", 4 + "defs": { 5 + "main": { 6 + "type": "token", 7 + "description": "represents that a service / check is working properly" 8 + } 8 9 } 9 - } 10 10 }
-10
lexicon/status/unknown.json
··· 1 - { 2 - "lexicon": 1, 3 - "id": "systems.gaze.barometer.status.unknown", 4 - "defs": { 5 - "main": { 6 - "type": "token", 7 - "description": "represents that the state of the service / check is unknown (only used for first from field in a state record)" 8 - } 9 - } 10 - }
+26 -1
lib/src/index.ts
··· 1 - export * from "./lexicons/index.ts"; 1 + import { 2 + SystemsGazeBarometerCheck, 3 + SystemsGazeBarometerHost, 4 + SystemsGazeBarometerService, 5 + SystemsGazeBarometerState, 6 + } from "./lexicons/index.js"; 7 + 8 + export * from "./lexicons/index.js"; 9 + 10 + export const schemas = { 11 + "systems.gaze.barometer.host": SystemsGazeBarometerHost.mainSchema, 12 + "systems.gaze.barometer.service": SystemsGazeBarometerService.mainSchema, 13 + "systems.gaze.barometer.check": SystemsGazeBarometerCheck.mainSchema, 14 + "systems.gaze.barometer.state": SystemsGazeBarometerState.mainSchema, 15 + }; 16 + export const nsid = < 17 + T extends 18 + | typeof SystemsGazeBarometerHost 19 + | typeof SystemsGazeBarometerService 20 + | typeof SystemsGazeBarometerCheck 21 + | typeof SystemsGazeBarometerState, 22 + >( 23 + lex: T, 24 + ): (typeof schemas)[typeof lex.mainSchema.object.shape.$type.expected] => { 25 + return schemas[lex.mainSchema.object.shape.$type.expected]; 26 + };
-1
lib/src/lexicons/index.ts
··· 4 4 export * as SystemsGazeBarometerState from "./types/systems/gaze/barometer/state.js"; 5 5 export * as SystemsGazeBarometerStatusDegraded from "./types/systems/gaze/barometer/status/degraded.js"; 6 6 export * as SystemsGazeBarometerStatusHealthy from "./types/systems/gaze/barometer/status/healthy.js"; 7 - export * as SystemsGazeBarometerStatusUnknown from "./types/systems/gaze/barometer/status/unknown.js";
+2 -1
lib/src/lexicons/types/systems/gaze/barometer/check.ts
··· 3 3 import type {} from "@atcute/lexicons/ambient"; 4 4 5 5 const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 6 + /*#__PURE__*/ v.string(), 7 7 /*#__PURE__*/ v.object({ 8 8 $type: /*#__PURE__*/ v.literal("systems.gaze.barometer.check"), 9 + currentState: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 9 10 description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 10 11 forService: /*#__PURE__*/ v.resourceUriString(), 11 12 name: /*#__PURE__*/ v.string(),
+2 -1
lib/src/lexicons/types/systems/gaze/barometer/service.ts
··· 3 3 import type {} from "@atcute/lexicons/ambient"; 4 4 5 5 const _mainSchema = /*#__PURE__*/ v.record( 6 - /*#__PURE__*/ v.tidString(), 6 + /*#__PURE__*/ v.string(), 7 7 /*#__PURE__*/ v.object({ 8 8 $type: /*#__PURE__*/ v.literal("systems.gaze.barometer.service"), 9 9 appUri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), 10 + currentState: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 10 11 description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 11 12 hostedBy: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 12 13 name: /*#__PURE__*/ v.string(),
+2 -6
lib/src/lexicons/types/systems/gaze/barometer/state.ts
··· 8 8 $type: /*#__PURE__*/ v.literal("systems.gaze.barometer.state"), 9 9 changedAt: /*#__PURE__*/ v.datetimeString(), 10 10 forService: /*#__PURE__*/ v.resourceUriString(), 11 - from: /*#__PURE__*/ v.literalEnum([ 12 - "systems.gaze.barometer.status.degraded", 13 - "systems.gaze.barometer.status.healthy", 14 - "systems.gaze.barometer.status.unknown", 15 - ]), 16 11 generatedBy: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 12 + previous: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), 17 13 reason: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 18 - to: /*#__PURE__*/ v.literalEnum([ 14 + state: /*#__PURE__*/ v.literalEnum([ 19 15 "systems.gaze.barometer.status.degraded", 20 16 "systems.gaze.barometer.status.healthy", 21 17 ]),
-14
lib/src/lexicons/types/systems/gaze/barometer/status/unknown.ts
··· 1 - import type {} from "@atcute/lexicons"; 2 - import * as v from "@atcute/lexicons/validations"; 3 - 4 - const _mainSchema = /*#__PURE__*/ v.literal( 5 - "systems.gaze.barometer.status.unknown", 6 - ); 7 - 8 - type main$schematype = typeof _mainSchema; 9 - 10 - export interface mainSchema extends main$schematype {} 11 - 12 - export const mainSchema = _mainSchema as mainSchema; 13 - 14 - export type Main = v.InferInput<typeof mainSchema>;
+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 - }
+145
proxy/bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "workspaces": { 4 + "": { 5 + "name": "barometer", 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", 11 + "@atcute/jetstream": "^1.0.2", 12 + "@atcute/lexicons": "^1.1.0", 13 + "@atcute/tid": "^1.0.2", 14 + "barometer-lexicon": "file:../lib", 15 + "nanoid": "^5.1.5", 16 + "parsimmon": "^1.18.1", 17 + }, 18 + "devDependencies": { 19 + "@types/bun": "latest", 20 + "@types/parsimmon": "^1.10.9", 21 + "concurrently": "^9.2.0", 22 + }, 23 + "peerDependencies": { 24 + "typescript": "^5", 25 + }, 26 + }, 27 + }, 28 + "packages": { 29 + "@atcute/atproto": ["@atcute/atproto@3.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.1.0" } }, "sha512-D+RLTIPF0xLu7BPZY8KSewAPemJFh+3n3zeQ3ROsLxbTtCHbrTDMAmAFexaVRAPGcPYrwXaBUlv7yZjScJolMg=="], 30 + 31 + "@atcute/client": ["@atcute/client@4.0.3", "", { "dependencies": { "@atcute/identity": "^1.0.2", "@atcute/lexicons": "^1.0.3" } }, "sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw=="], 32 + 33 + "@atcute/identity": ["@atcute/identity@1.0.3", "", { "dependencies": { "@atcute/lexicons": "^1.0.4", "@badrap/valita": "^0.4.5" } }, "sha512-mNMxbKHFGys03A8JXKk0KfMBzdd0vrYMzZZWjpw1nYTs0+ea6bo5S1hwqVUZxHdo1gFHSe/t63jxQIF4yL9aKw=="], 34 + 35 + "@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=="], 36 + 37 + "@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=="], 38 + 39 + "@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=="], 40 + 41 + "@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.5" } }, "sha512-U7rinsTOwXGGcrF6/s7GzTXargcQpDr4BTrj5ci/XTK+POEK5jpcI+Ag1fF932pBX3k97em6y4TWwTSO8M/McQ=="], 42 + 43 + "@atcute/lexicons": ["@atcute/lexicons@1.1.0", "", { "dependencies": { "esm-env": "^1.2.2" } }, "sha512-LFqwnria78xLYb62Ri/+WwQpUTgZp2DuyolNGIIOV1dpiKhFFFh//nscHMA6IExFLQRqWDs3tTjy7zv0h3sf1Q=="], 44 + 45 + "@atcute/tid": ["@atcute/tid@1.0.2", "", {}, "sha512-ahmjroNyeDPJhtuf3+HTJropaH04HmJ8fhntDu73Gpz/RkAF7+nkz6kcP2QTgfvMCgMPAJUdskAAP82GPDTY9w=="], 46 + 47 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.1", "", { "dependencies": { "@badrap/valita": "^0.4.2" } }, "sha512-Clc0E/5ufyGBVfYBUwWNlHONlZCoblSr4Ho50l1LhmRPGB1Wu/AQ9Sz+rsBg7fdaW/auve8ulmwhRhnX2cGRow=="], 48 + 49 + "@badrap/valita": ["@badrap/valita@0.4.5", "", {}, "sha512-4QwGbuhh/JesHRQj79mO/l37PvJj4l/tlAu7+S1n4h47qwaNpZ0WDvIwUGLYUsdi9uQ5UPpiG9wb1Wm3XUFBUQ=="], 50 + 51 + "@externdefs/collider": ["@externdefs/collider@0.3.0", "", { "peerDependencies": { "@badrap/valita": "^0.4.4" } }, "sha512-x5CpeZ4c8n+1wMFthUMWSQKqCGcQo52/Qbda5ES+JFRRg/D8Ep6/JOvUUq5HExFuv/wW+6UYG2U/mXzw0IAd8Q=="], 52 + 53 + "@mary-ext/event-iterator": ["@mary-ext/event-iterator@1.0.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ=="], 54 + 55 + "@mary-ext/simple-event-emitter": ["@mary-ext/simple-event-emitter@1.0.0", "", {}, "sha512-meA/zJZKIN1RVBNEYIbjufkUrW7/tRjHH60FjolpG1ixJKo76TB208qefQLNdOVDA7uIG0CGEDuhmMirtHKLAg=="], 56 + 57 + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], 58 + 59 + "@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="], 60 + 61 + "@types/parsimmon": ["@types/parsimmon@1.10.9", "", {}, "sha512-O2M2x1w+m7gWLen8i5DOy6tWRnbRcsW6Pke3j3HAsJUrPb4g0MgjksIUm2aqUtCYxy7Qjr3CzjjwQBzhiGn46A=="], 62 + 63 + "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], 64 + 65 + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 66 + 67 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 68 + 69 + "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" } }], 70 + 71 + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], 72 + 73 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 74 + 75 + "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=="], 76 + 77 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 78 + 79 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 80 + 81 + "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=="], 82 + 83 + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 84 + 85 + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 86 + 87 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 88 + 89 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 90 + 91 + "event-target-polyfill": ["event-target-polyfill@0.0.4", "", {}, "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ=="], 92 + 93 + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 94 + 95 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 96 + 97 + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 98 + 99 + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], 100 + 101 + "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], 102 + 103 + "parsimmon": ["parsimmon@1.18.1", "", {}, "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw=="], 104 + 105 + "partysocket": ["partysocket@1.1.4", "", { "dependencies": { "event-target-polyfill": "^0.0.4" } }, "sha512-jXP7PFj2h5/v4UjDS8P7MZy6NJUQ7sspiFyxL4uc/+oKOL+KdtXzHnTV8INPGxBrLTXgalyG3kd12Qm7WrYc3A=="], 106 + 107 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 108 + 109 + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], 110 + 111 + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 112 + 113 + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], 114 + 115 + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], 116 + 117 + "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=="], 118 + 119 + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 120 + 121 + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], 122 + 123 + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], 124 + 125 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 126 + 127 + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], 128 + 129 + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 130 + 131 + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], 132 + 133 + "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=="], 134 + 135 + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 136 + 137 + "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=="], 138 + 139 + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 140 + 141 + "yocto-queue": ["yocto-queue@1.2.1", "", {}, "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg=="], 142 + 143 + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 144 + } 145 + }
+42
proxy/gen-routes.ts
··· 1 + #!/usr/bin/env bun 2 + // build-routes.ts - Run this at build time 3 + 4 + import { Glob } from "bun"; 5 + import { writeFileSync } from "fs"; 6 + 7 + const routeImports: string[] = []; 8 + const routeMap: string[] = []; 9 + 10 + // Use Bun.Glob to find all route files 11 + const glob = new Glob("*.ts"); 12 + 13 + for await (const file of glob.scan("./src/routes")) { 14 + // Skip index.ts 15 + if (file === "index.ts") continue; 16 + 17 + const routeName = file.replace(".ts", ""); 18 + const routePath = `/${routeName}`; 19 + 20 + // Generate import statement 21 + routeImports.push(`import * as ${routeName}Route from "./${routeName}";`); 22 + 23 + // Generate route map entry 24 + routeMap.push(` "${routePath}": ${routeName}Route`); 25 + } 26 + 27 + // Generate the complete index.ts content 28 + const indexContent = `// Auto-generated route index 29 + ${routeImports.join("\n")} 30 + 31 + export const routes: Record< 32 + string, 33 + Record<string, Bun.RouterTypes.RouteHandler<string>> 34 + > = { 35 + ${routeMap.join(",\n")} 36 + }; 37 + 38 + export default routes; 39 + `; 40 + 41 + // Write the generated index.ts file 42 + writeFileSync("./src/routes/index.ts", indexContent);
+30
proxy/package.json
··· 1 + { 2 + "name": "barometer", 3 + "module": "index.ts", 4 + "type": "module", 5 + "private": true, 6 + "scripts": { 7 + "build:routes": "bun run gen-routes.ts", 8 + "dev": "bun run build:routes && concurrently 'bun --watch gen-routes.ts' 'bun --watch src/index.ts'" 9 + }, 10 + "devDependencies": { 11 + "@types/bun": "latest", 12 + "@types/parsimmon": "^1.10.9", 13 + "concurrently": "^9.2.0" 14 + }, 15 + "peerDependencies": { 16 + "typescript": "^5" 17 + }, 18 + "dependencies": { 19 + "@atcute/atproto": "^3.1.1", 20 + "@atcute/client": "^4.0.3", 21 + "@atcute/identity": "^1.0.3", 22 + "@atcute/identity-resolver": "^1.1.3", 23 + "@atcute/jetstream": "^1.0.2", 24 + "@atcute/lexicons": "^1.1.0", 25 + "@atcute/tid": "^1.0.2", 26 + "barometer-lexicon": "file:../lib", 27 + "nanoid": "^5.1.5", 28 + "parsimmon": "^1.18.1" 29 + } 30 + }
+30
proxy/src/config.ts
··· 1 + import { isDid, type AtprotoDid } from "@atcute/lexicons/syntax"; 2 + import { env } from "process"; 3 + 4 + interface Config { 5 + repoDid: AtprotoDid; 6 + appPass: string; 7 + hostName?: string; 8 + hostDescription?: string; 9 + } 10 + 11 + const required = (value: unknown) => typeof value !== "undefined"; 12 + 13 + const getConfig = (prefix: string): Config => { 14 + const get = <Value>( 15 + name: string, 16 + check: (value: unknown) => boolean = () => true, 17 + ): Value => { 18 + const value = env[`${prefix}${name}`]; 19 + if (check(value)) return value as Value; 20 + throw `config key ${name} is invalid`; 21 + }; 22 + return { 23 + repoDid: get("REPO_DID", isDid), 24 + appPass: get("APP_PASSWORD", required), 25 + hostName: get("HOST_NAME"), 26 + hostDescription: get("HOST_DESCRIPTION"), 27 + }; 28 + }; 29 + 30 + export const config = getConfig("BAROMETER_");
+78
proxy/src/index.ts
··· 1 + import os from "os"; 2 + import { Client, CredentialManager } from "@atcute/client"; 3 + import { getPdsEndpoint } from "@atcute/identity"; 4 + import { 5 + CompositeDidDocumentResolver, 6 + PlcDidDocumentResolver, 7 + WebDidDocumentResolver, 8 + } from "@atcute/identity-resolver"; 9 + import { config } from "./config"; 10 + import type {} from "@atcute/atproto"; 11 + import { 12 + applyMiddleware, 13 + applyMiddlewareAll, 14 + getRecord, 15 + log, 16 + ok, 17 + putRecord, 18 + type Middleware, 19 + type Result, 20 + } from "./utils"; 21 + import store from "./store"; 22 + import routes from "./routes"; 23 + import { handleEvents } from "./jetstream"; 24 + 25 + const docResolver = new CompositeDidDocumentResolver({ 26 + methods: { 27 + plc: new PlcDidDocumentResolver(), 28 + web: new WebDidDocumentResolver(), 29 + }, 30 + }); 31 + 32 + const pdsUrl = getPdsEndpoint(await docResolver.resolve(config.repoDid)); 33 + if (pdsUrl === undefined) { 34 + throw `no pds found`; 35 + } 36 + console.info(`pds is ${pdsUrl}`); 37 + 38 + const creds = new CredentialManager({ service: pdsUrl }); 39 + const session = await creds.login({ 40 + identifier: config.repoDid, 41 + password: config.appPass, 42 + }); 43 + export const atpClient = new Client({ handler: creds }); 44 + 45 + // fetch host record for this host 46 + const maybeRecord = await getRecord( 47 + "systems.gaze.barometer.host", 48 + store.hostname, 49 + ); 50 + if (maybeRecord.ok) { 51 + store.host = maybeRecord.value; 52 + } 53 + 54 + // if it doesnt exist we make a new one 55 + if (store.host === null) { 56 + await putRecord( 57 + { 58 + $type: "systems.gaze.barometer.host", 59 + name: config.hostName ?? store.hostname, 60 + description: config.hostDescription, 61 + os: os.platform(), 62 + }, 63 + store.hostname, 64 + ); 65 + } 66 + 67 + const traceRequest: Middleware = async (req) => { 68 + const url = new URL(req.url); 69 + log.info(`${req.method} ${url.pathname}`); 70 + return req; 71 + }; 72 + const server = Bun.serve({ 73 + routes: applyMiddlewareAll([traceRequest], routes), 74 + }); 75 + 76 + console.log(`server running on http://localhost:${server.port}`); 77 + 78 + await handleEvents();
+126
proxy/src/jetstream.ts
··· 1 + import { JetstreamSubscription } from "@atcute/jetstream"; 2 + import { 3 + is, 4 + parse, 5 + parseCanonicalResourceUri, 6 + type RecordKey, 7 + } from "@atcute/lexicons"; 8 + import { 9 + SystemsGazeBarometerService, 10 + SystemsGazeBarometerCheck, 11 + } from "barometer-lexicon"; 12 + import { config } from "./config"; 13 + import store, { type Service } from "./store"; 14 + import { expect, getRecord, getUri, log, type ServiceUri } from "./utils"; 15 + 16 + const subscription = new JetstreamSubscription({ 17 + url: "wss://jetstream2.us-east.bsky.network", 18 + wantedCollections: [ 19 + "systems.gaze.barometer.service", 20 + "systems.gaze.barometer.check", 21 + "systems.gaze.barometer.state", 22 + ], 23 + wantedDids: [config.repoDid], 24 + }); 25 + 26 + const handleService = async ( 27 + record: Record<string, unknown>, 28 + rkey: RecordKey, 29 + ) => { 30 + const collection = "systems.gaze.barometer.service"; 31 + const serviceRecord = parse(SystemsGazeBarometerService.mainSchema, record); 32 + // we dont care if its a dangling service 33 + if (!serviceRecord.hostedBy) return true; 34 + const hostAtUri = expect(parseCanonicalResourceUri(serviceRecord.hostedBy)); 35 + // not our host 36 + if (hostAtUri.rkey !== store.hostname) return true; 37 + const serviceUri = getUri(collection, rkey); 38 + const service: Service = store.services.get(serviceUri) ?? { 39 + record: serviceRecord, 40 + checks: new Set(), 41 + rkey, 42 + }; 43 + store.services.set(serviceUri, { 44 + ...service, 45 + record: serviceRecord, 46 + }); 47 + return false; 48 + }; 49 + 50 + const handleCheck = async ( 51 + record: Record<string, unknown>, 52 + rkey: RecordKey, 53 + ) => { 54 + const collection = "systems.gaze.barometer.check"; 55 + const checkRecord = parse(SystemsGazeBarometerCheck.mainSchema, record); 56 + const checkUri = getUri(collection, rkey); 57 + const serviceUri = checkRecord.forService as ServiceUri; 58 + const maybeService = await store.getOrFetch(serviceUri); 59 + if (!maybeService.ok) { 60 + log.error( 61 + `can't fetch service record (${serviceUri}) for check record (${checkUri})`, 62 + ); 63 + return true; 64 + } 65 + const service = maybeService.value; 66 + service.checks.add(rkey); 67 + store.checks.set(checkUri, { 68 + record: checkRecord, 69 + rkey, 70 + }); 71 + store.services.set(serviceUri, service); 72 + return false; 73 + }; 74 + 75 + export const handleEvents = async () => { 76 + for await (const event of subscription) { 77 + if (event.kind !== "commit") continue; 78 + const { operation, collection, rkey } = event.commit; 79 + // log.info(`${operation} at://${event.did}/${collection}/${rkey}`); 80 + if (operation === "create" || operation === "update") { 81 + const record = event.commit.record; 82 + switch (collection) { 83 + case "systems.gaze.barometer.service": { 84 + if (await handleService(record, rkey)) continue; 85 + break; 86 + } 87 + case "systems.gaze.barometer.check": { 88 + if (await handleCheck(record, rkey)) continue; 89 + break; 90 + } 91 + } 92 + } else { 93 + switch (collection) { 94 + case "systems.gaze.barometer.service": { 95 + const serviceUri = getUri(collection, rkey); 96 + const service = store.services.get(serviceUri); 97 + if (!service) continue; 98 + for (const checkRkey of service.checks) { 99 + store.checks.delete( 100 + getUri("systems.gaze.barometer.check", checkRkey), 101 + ); 102 + } 103 + store.services.delete(serviceUri); 104 + break; 105 + } 106 + case "systems.gaze.barometer.check": { 107 + const checkUri = getUri(collection, rkey); 108 + const check = store.checks.get(checkUri); 109 + if (!check) continue; 110 + const serviceUri = check.record.forService as ServiceUri; 111 + const service = store.services.get(serviceUri); 112 + if (service) { 113 + service.checks.delete(rkey); 114 + store.services.set(serviceUri, service); 115 + } 116 + store.checks.delete(checkUri); 117 + break; 118 + } 119 + case "systems.gaze.barometer.state": { 120 + store.states.delete(getUri(collection, rkey)); 121 + break; 122 + } 123 + } 124 + } 125 + } 126 + };
+3
proxy/src/routes/_health.ts
··· 1 + export const GET = async (req: Bun.BunRequest) => { 2 + return new Response(JSON.stringify({ version: "0.1.0" })); 3 + };
+13
proxy/src/routes/index.ts
··· 1 + // Auto-generated route index 2 + import * as _healthRoute from "./_health"; 3 + import * as pushRoute from "./push"; 4 + 5 + export const routes: Record< 6 + string, 7 + Record<string, Bun.RouterTypes.RouteHandler<string>> 8 + > = { 9 + "/_health": _healthRoute, 10 + "/push": pushRoute 11 + }; 12 + 13 + export default routes;
+171
proxy/src/routes/push.ts
··· 1 + import { 2 + err, 3 + expect, 4 + getRecord, 5 + getUri, 6 + ok, 7 + putRecord, 8 + type CheckUri, 9 + type CollectionUri, 10 + type Result, 11 + type ServiceUri, 12 + type StateUri, 13 + } from "../utils"; 14 + import { 15 + parseCanonicalResourceUri, 16 + safeParse, 17 + type CanonicalResourceUri, 18 + type ParsedCanonicalResourceUri, 19 + type ResourceUri, 20 + } from "@atcute/lexicons"; 21 + import store, { type Check, type Service, type State } from "../store"; 22 + import { systemctlShow } from "../systemd"; 23 + import { config } from "../config"; 24 + import { now as generateTid } from "@atcute/tid"; 25 + import * as v from "@atcute/lexicons/validations"; 26 + import type { SystemsGazeBarometerService } from "barometer-lexicon"; 27 + import type { SystemsGazeBarometerState } from "barometer-lexicon"; 28 + 29 + // this is hacky but we want to make forService be optional so its okay 30 + const StateSchemaSubset = v.record( 31 + v.tidString(), 32 + v.object({ 33 + changedAt: v.optional(v.datetimeString()), 34 + forService: v.optional(v.resourceUriString()), 35 + generatedBy: v.optional(v.resourceUriString()), 36 + reason: v.optional(v.string()), 37 + state: v.literalEnum([ 38 + "systems.gaze.barometer.status.degraded", 39 + "systems.gaze.barometer.status.healthy", 40 + ]), 41 + }), 42 + ); 43 + 44 + interface PushRequest { 45 + serviceName?: string; // service manager service name 46 + state: v.InferOutput<typeof StateSchemaSubset>; 47 + } 48 + 49 + const parsePushRequest = (json: unknown): Result<PushRequest, string> => { 50 + if (typeof json !== "object" || json === null) { 51 + return err("invalid request"); 52 + } 53 + if ("serviceName" in json && typeof json.serviceName !== "string") { 54 + return err("serviceName is not a string"); 55 + } 56 + if ("state" in json) { 57 + const parsed = safeParse(StateSchemaSubset, json.state); 58 + if (!parsed.ok) { 59 + return err(`state is invalid: ${parsed.message}`); 60 + } 61 + } else { 62 + return err("state not found"); 63 + } 64 + return ok(json as PushRequest); 65 + }; 66 + 67 + const error = <Error extends { msg: string }>( 68 + error: Error, 69 + status: number = 400, 70 + ) => { 71 + return new Response(JSON.stringify(error), { status }); 72 + }; 73 + export const POST = async (req: Bun.BunRequest) => { 74 + const maybeData = parsePushRequest(await req.json()); 75 + if (!maybeData.ok) { 76 + return error({ 77 + msg: `invalid request: ${maybeData.error}`, 78 + }); 79 + } 80 + const data = maybeData.value; 81 + 82 + let service: Service | undefined = undefined; 83 + let serviceAtUri: ServiceUri | undefined; 84 + if (data.state.forService) { 85 + serviceAtUri = data.state.forService as ServiceUri; 86 + const maybeService = await store.getOrFetch(serviceAtUri); 87 + if (!maybeService.ok) 88 + return error({ 89 + msg: `could not fetch service: ${maybeService.error}`, 90 + }); 91 + service = maybeService.value; 92 + } else if (data.serviceName) { 93 + const maybeService = await store.getServiceFromSystemd(data.serviceName); 94 + if (!maybeService.ok) 95 + return error({ 96 + msg: `could not fetch service from systemd: ${maybeService.error}`, 97 + }); 98 + const [uri, srv] = maybeService.value; 99 + serviceAtUri = uri; 100 + service = srv; 101 + } else { 102 + return error({ 103 + msg: `either 'state.forService' or 'serviceName' must be provided`, 104 + }); 105 + } 106 + 107 + let check: Check | undefined = undefined; 108 + if (data.state.generatedBy) { 109 + const maybeCheck = await store.getOrFetch( 110 + data.state.generatedBy as CheckUri, 111 + ); 112 + if (!maybeCheck.ok) return error({ msg: maybeCheck.error }); 113 + check = maybeCheck.value; 114 + if (check.record.forService !== serviceAtUri) 115 + return error({ 116 + msg: `check record does not point to the same service as the state record service`, 117 + }); 118 + // update services with check 119 + service.checks.add(check.rkey); 120 + store.services.set(serviceAtUri, service); 121 + } 122 + 123 + // get current state uri 124 + const currentStateUri = 125 + check && check.record.currentState 126 + ? check.record.currentState 127 + : service.record.currentState; 128 + 129 + if (currentStateUri) { 130 + // fetch current state 131 + const record = await store.getOrFetch(currentStateUri as StateUri); 132 + if (!record.ok) return error({ msg: record.error }); 133 + const currentState = record.value; 134 + 135 + // check if the state has changed 136 + if (currentState.record.state === data.state.state) 137 + return error( 138 + { 139 + msg: `state can't be the same as the latest state`, 140 + }, 141 + 208, 142 + ); 143 + } 144 + 145 + const stateRecord: SystemsGazeBarometerState.Main = { 146 + $type: "systems.gaze.barometer.state", 147 + ...data.state, 148 + forService: serviceAtUri, 149 + changedAt: data.state.changedAt ?? new Date().toISOString(), 150 + previous: currentStateUri, 151 + }; 152 + const rkey = generateTid(); 153 + const result = await putRecord(stateRecord, rkey); 154 + 155 + // store committed state in "cache" 156 + store.states.set(result.uri as StateUri, { record: stateRecord, rkey }); 157 + 158 + // update check with new state url 159 + if (check) { 160 + check.record.currentState = result.uri; 161 + store.checks.set(getUri("systems.gaze.barometer.check", check.rkey), check); 162 + await putRecord(check.record, check.rkey); 163 + } else { 164 + // update service with new state url 165 + service.record.currentState = result.uri; 166 + store.services.set(serviceAtUri, service); 167 + await putRecord(service.record, service.rkey); 168 + } 169 + 170 + return new Response(JSON.stringify({ cid: result.cid, uri: result.uri })); 171 + };
+130
proxy/src/store.ts
··· 1 + import os from "os"; 2 + import { parseCanonicalResourceUri, type RecordKey } from "@atcute/lexicons"; 3 + import type { 4 + SystemsGazeBarometerCheck, 5 + SystemsGazeBarometerHost, 6 + SystemsGazeBarometerService, 7 + SystemsGazeBarometerState, 8 + } from "barometer-lexicon"; 9 + import { 10 + err, 11 + expect, 12 + getRecord, 13 + ok, 14 + putRecord, 15 + type CheckUri, 16 + type CollectionUri, 17 + type Result, 18 + type ServiceUri, 19 + type StateUri, 20 + } from "./utils"; 21 + import { systemctlShow } from "./systemd"; 22 + import { config } from "./config"; 23 + import { now as generateTid } from "@atcute/tid"; 24 + 25 + export interface State { 26 + rkey: RecordKey; 27 + record: SystemsGazeBarometerState.Main; 28 + } 29 + export interface Check { 30 + rkey: RecordKey; 31 + record: SystemsGazeBarometerCheck.Main; 32 + } 33 + export interface Service { 34 + checks: Set<RecordKey>; 35 + rkey: RecordKey; 36 + record: SystemsGazeBarometerService.Main; 37 + } 38 + 39 + class Store { 40 + services: Map<ServiceUri, Service>; 41 + checks: Map<CheckUri, Check>; 42 + states: Map<StateUri, State>; 43 + host: SystemsGazeBarometerHost.Main | null; 44 + hostname: string; 45 + 46 + constructor() { 47 + this.services = new Map(); 48 + this.checks = new Map(); 49 + this.states = new Map(); 50 + this.host = null; 51 + this.hostname = os.hostname(); 52 + } 53 + 54 + getOrFetch = async < 55 + Nsid extends 56 + | "systems.gaze.barometer.state" 57 + | "systems.gaze.barometer.check" 58 + | "systems.gaze.barometer.service", 59 + Uri extends CollectionUri<Nsid>, 60 + >( 61 + uri: Uri, 62 + ): Promise< 63 + Result< 64 + Uri extends StateUri 65 + ? State 66 + : Uri extends CheckUri 67 + ? Check 68 + : Uri extends ServiceUri 69 + ? Service 70 + : never, 71 + string 72 + > 73 + > => { 74 + const parsedUri = expect(parseCanonicalResourceUri(uri)); 75 + const nsid = parsedUri.collection; 76 + const record = await getRecord(nsid as Nsid, parsedUri.rkey); 77 + if (!record.ok) 78 + return err(`record not found or is invalid: ${record.error}`); 79 + const data = { 80 + record: record.value, 81 + rkey: parsedUri.rkey, 82 + }; 83 + 84 + switch (nsid) { 85 + case "systems.gaze.barometer.state": 86 + this.states.set(uri as StateUri, data as State); 87 + return ok(data as any); 88 + case "systems.gaze.barometer.check": 89 + this.checks.set(uri as CheckUri, data as Check); 90 + return ok(data as any); 91 + case "systems.gaze.barometer.service": 92 + this.services.set( 93 + uri as ServiceUri, 94 + { checks: new Set(), ...data } as Service, 95 + ); 96 + return ok(data as any); 97 + default: 98 + throw new Error(`unsupported namespace: ${nsid}`); 99 + } 100 + }; 101 + 102 + getServiceFromSystemd = async ( 103 + serviceName: string, 104 + ): Promise<Result<[ServiceUri, Service], string>> => { 105 + const serviceInfo = await systemctlShow(serviceName); 106 + if (serviceInfo.ok) { 107 + const record: SystemsGazeBarometerService.Main = { 108 + $type: "systems.gaze.barometer.service", 109 + name: serviceName, 110 + description: serviceInfo.value.description, 111 + hostedBy: `at://${config.repoDid}/systems.gaze.barometer.host/${store.hostname}`, 112 + }; 113 + const rkey = generateTid(); 114 + const putAt = await putRecord(record, rkey); 115 + const serviceUri = putAt.uri as ServiceUri; 116 + const service: Service = { 117 + record, 118 + checks: new Set(), 119 + rkey, 120 + }; 121 + store.services.set(serviceUri, service); 122 + return ok([serviceUri, service]); 123 + } else { 124 + return err(`could not fetch service from systemd: ${serviceInfo.error}`); 125 + } 126 + }; 127 + } 128 + 129 + const store = new Store(); 130 + export default store;
+92
proxy/src/systemd.ts
··· 1 + import { spawn } from "bun"; 2 + import P from "parsimmon"; 3 + import { err, ok, type Result } from "./utils"; 4 + 5 + interface SystemctlShowOutput { 6 + [key: string]: string; 7 + } 8 + 9 + // Parsimmon parsers for systemctl output 10 + const newline = P.string("\n"); 11 + const equals = P.string("="); 12 + 13 + // Key: anything except = and newline 14 + const key = P.regexp(/[^=\n]+/).map((s) => s.trim()); 15 + 16 + // Single line value: everything until newline (or end of input) 17 + const singleLineValue = P.regexp(/[^\n]*/); 18 + 19 + // Continuation line: newline followed by whitespace and content 20 + const continuationLine = P.seq( 21 + newline, 22 + P.regexp(/[ \t]*/), // optional whitespace 23 + P.regexp(/[^\n]*/), // content 24 + ).map(([, , content]) => "\n" + content); 25 + 26 + // Multi-line value: first line + any continuation lines 27 + const multiLineValue = P.seq(singleLineValue, continuationLine.many()).map( 28 + ([first, continuations]) => (first + continuations.join("")).trim(), 29 + ); 30 + 31 + // Key-value pair: key = value 32 + const keyValuePair = P.seq(key, equals, multiLineValue).map(([k, , v]) => ({ 33 + key: k, 34 + value: v, 35 + })); 36 + 37 + // Empty line (just whitespace) 38 + const emptyLine = P.regexp(/[ \t]*/).result(null); 39 + 40 + // A line is either a key-value pair or empty line 41 + const line = P.alt(keyValuePair, emptyLine); 42 + 43 + // Complete systemctl output: lines separated by newlines, ending with optional newline 44 + const systemctlOutput = P.seq(line.sepBy(newline), P.alt(newline, P.eof)).map( 45 + ([lines]) => 46 + lines.filter((l): l is { key: string; value: string } => l !== null), 47 + ); 48 + 49 + const parseSystemctlOutput = ( 50 + output: string, 51 + ): Result<SystemctlShowOutput, string> => { 52 + const result = systemctlOutput.parse(output); 53 + 54 + if (!result.status) { 55 + return err( 56 + `Parse error at position ${result.index.offset}: ${result.expected.join(", ")}`, 57 + ); 58 + } 59 + 60 + const kvMap: SystemctlShowOutput = {}; 61 + 62 + for (const { key, value } of result.value) { 63 + if (value.length > 0) { 64 + kvMap[key.toLowerCase()] = value; 65 + } 66 + } 67 + 68 + return ok(kvMap); 69 + }; 70 + 71 + export const systemctlShow = async ( 72 + serviceName: string, 73 + ): Promise<Result<SystemctlShowOutput, string>> => { 74 + try { 75 + const proc = spawn(["systemctl", "show", `${serviceName}.service`], { 76 + stdout: "pipe", 77 + stderr: "pipe", 78 + }); 79 + 80 + const output = await new Response(proc.stdout).text(); 81 + const exitCode = await proc.exited; 82 + 83 + if (exitCode !== 0) { 84 + const error = await new Response(proc.stderr).text(); 85 + return err(`systemctl show failed with exit code ${exitCode}: ${error}`); 86 + } 87 + 88 + return parseSystemctlOutput(output); 89 + } catch (error) { 90 + return err(`failed to execute systemctl show: ${error}`); 91 + } 92 + };
+141
proxy/src/utils.ts
··· 1 + import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons"; 2 + import { schemas as BarometerSchemas } from "barometer-lexicon"; 3 + import { config } from "./config"; 4 + import { ok as clientOk } from "@atcute/client"; 5 + import { atpClient } from "."; 6 + import { now as generateTid } from "@atcute/tid"; 7 + import type { AtprotoDid } from "@atcute/lexicons/syntax"; 8 + 9 + export type CollectionUri<Nsid extends string> = 10 + `at://${AtprotoDid}/${Nsid}/${RecordKey}`; 11 + export type StateUri = CollectionUri<"systems.gaze.barometer.state">; 12 + export type CheckUri = CollectionUri<"systems.gaze.barometer.check">; 13 + export type ServiceUri = CollectionUri<"systems.gaze.barometer.service">; 14 + 15 + export 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 + 27 + export type Result<T, E> = 28 + | { 29 + ok: true; 30 + value: T; 31 + } 32 + | { 33 + ok: false; 34 + error: E; 35 + }; 36 + 37 + export const ok = <T, E>(value: T): Result<T, E> => { 38 + return { ok: true, value }; 39 + }; 40 + export const err = <T, E>(error: E): Result<T, E> => { 41 + return { ok: false, error }; 42 + }; 43 + 44 + export 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 + 54 + export 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 + 79 + export 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 + 97 + export const log = { 98 + info: console.log, 99 + warn: console.warn, 100 + error: console.error, 101 + }; 102 + 103 + export type Middleware = ( 104 + req: Bun.BunRequest, 105 + ) => Promise<Bun.BunRequest | Response>; 106 + 107 + export 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 + 121 + type Routes = Record< 122 + string, 123 + Record<string, Bun.RouterTypes.RouteHandler<string>> 124 + >; 125 + export 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 + };
+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 - }