Demonstration bridge between ATproto and GraphQL. Generate schema types and interface with the ATmosphere via GraphQL queries. Includes a TypeScript server with IDE.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: switch to using lex

Tim Ryan d4984e01 82318312

+184 -189
+3 -3
.gitignore
··· 52 52 . history 53 53 54 54 # Python 55 - __pycache__ 56 - schema/schema-generated.graphql 57 - .pytest_cache 55 + schema-generated.graphql 58 56 59 57 # Node.js 60 58 node_modules 59 + lexicons 60 + lexicons.json
-3
.gitmodules
··· 1 - [submodule "deps/atproto"] 2 - path = deps/atproto 3 - url = https://github.com/bluesky-social/atproto.git
+107 -107
schema-generated.graphql
··· 1 1 # app.bsky.actor.defs#profileViewDetailed 2 2 type Lexicon_app_bsky_actor_defs_profileViewDetailed { 3 3 did: ID! 4 - handle: ID! 5 - displayName: String 6 - description: String 7 - pronouns: String 8 - website: String 9 4 avatar: String 10 5 banner: String 11 - followersCount: Int 12 - followsCount: Int 13 - postsCount: Int 6 + handle: ID! 7 + labels: [Lexicon_com_atproto_label_defs_label!] 8 + status: Lexicon_app_bsky_actor_defs_statusView 9 + viewer: Lexicon_app_bsky_actor_defs_viewerState 10 + website: String 11 + pronouns: String 12 + createdAt: String 13 + indexedAt: String 14 14 associated: Lexicon_app_bsky_actor_defs_profileAssociated 15 - joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic 16 - indexedAt: String 17 - createdAt: String 18 - viewer: Lexicon_app_bsky_actor_defs_viewerState 19 - labels: [Lexicon_com_atproto_label_defs_label!] 20 15 pinnedPost: Lexicon_com_atproto_repo_strongRef 16 + postsCount: Int 17 + description: String 18 + displayName: String 19 + followsCount: Int 21 20 verification: Lexicon_app_bsky_actor_defs_verificationState 22 - status: Lexicon_app_bsky_actor_defs_statusView 21 + followersCount: Int 22 + joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic 23 23 } 24 24 25 25 26 26 27 27 # com.atproto.server.getSessionOutput 28 28 type Lexicon_com_atproto_server_getSessionOutput { 29 - handle: ID! 30 29 did: ID! 31 30 email: String 31 + active: Boolean 32 + handle: ID! 33 + status: String 32 34 emailConfirmed: Boolean 33 35 emailAuthFactor: Boolean 34 - active: Boolean 35 - status: String 36 36 } 37 37 38 38 39 39 40 - # app.bsky.actor.defs#profileAssociated 41 - type Lexicon_app_bsky_actor_defs_profileAssociated { 42 - lists: Int 43 - feedgens: Int 44 - starterPacks: Int 45 - labeler: Boolean 46 - chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat 47 - activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription 48 - germ: Lexicon_app_bsky_actor_defs_profileAssociatedGerm 40 + # com.atproto.label.defs#label 41 + type Lexicon_com_atproto_label_defs_label { 42 + cid: String 43 + cts: String! 44 + exp: String 45 + neg: Boolean 46 + sig: String 47 + src: ID! 48 + uri: String! 49 + val: String! 50 + ver: Int 49 51 } 50 52 51 53 52 54 53 - # app.bsky.graph.defs#starterPackViewBasic 54 - type Lexicon_app_bsky_graph_defs_starterPackViewBasic { 55 - uri: String! 56 - cid: String! 57 - creator: Lexicon_app_bsky_actor_defs_profileViewBasic! 58 - listItemCount: Int 59 - joinedWeekCount: Int 60 - joinedAllTimeCount: Int 61 - labels: [Lexicon_com_atproto_label_defs_label!] 62 - indexedAt: String! 55 + # app.bsky.actor.defs#statusView 56 + type Lexicon_app_bsky_actor_defs_statusView { 57 + cid: String 58 + uri: String 59 + embed: Unknown 60 + status: String! 61 + isActive: Boolean 62 + expiresAt: String 63 + isDisabled: Boolean 63 64 } 64 65 65 66 ··· 67 68 # app.bsky.actor.defs#viewerState 68 69 type Lexicon_app_bsky_actor_defs_viewerState { 69 70 muted: Boolean 70 - mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic 71 + blocking: String 71 72 blockedBy: Boolean 72 - blocking: String 73 - blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic 74 73 following: String 75 74 followedBy: String 75 + mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic 76 + blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic 76 77 knownFollowers: Lexicon_app_bsky_actor_defs_knownFollowers 77 78 activitySubscription: Lexicon_app_bsky_notification_defs_activitySubscription 78 79 } 79 80 80 81 81 82 82 - # com.atproto.label.defs#label 83 - type Lexicon_com_atproto_label_defs_label { 84 - ver: Int 85 - src: ID! 86 - uri: String! 87 - cid: String 88 - val: String! 89 - neg: Boolean 90 - cts: String! 91 - exp: String 92 - sig: String 83 + # app.bsky.actor.defs#profileAssociated 84 + type Lexicon_app_bsky_actor_defs_profileAssociated { 85 + chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat 86 + germ: Lexicon_app_bsky_actor_defs_profileAssociatedGerm 87 + lists: Int 88 + labeler: Boolean 89 + feedgens: Int 90 + starterPacks: Int 91 + activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription 93 92 } 94 93 95 94 96 95 97 96 # com.atproto.repo.strongRef 98 97 type Lexicon_com_atproto_repo_strongRef { 99 - uri: String! 100 98 cid: String! 99 + uri: String! 101 100 } 102 101 103 102 ··· 111 110 112 111 113 112 114 - # app.bsky.actor.defs#statusView 115 - type Lexicon_app_bsky_actor_defs_statusView { 116 - uri: String 117 - cid: String 118 - status: String! 119 - embed: Unknown 120 - expiresAt: String 121 - isActive: Boolean 122 - isDisabled: Boolean 123 - } 124 - 125 - 126 - 127 - # app.bsky.actor.defs#profileAssociatedChat 128 - type Lexicon_app_bsky_actor_defs_profileAssociatedChat { 129 - allowIncoming: String! 130 - } 131 - 132 - 133 - 134 - # app.bsky.actor.defs#profileAssociatedActivitySubscription 135 - type Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription { 136 - allowSubscriptions: String! 137 - } 138 - 139 - 140 - 141 - # app.bsky.actor.defs#profileAssociatedGerm 142 - type Lexicon_app_bsky_actor_defs_profileAssociatedGerm { 143 - messageMeUrl: String! 144 - showButtonTo: String! 145 - } 146 - 147 - 148 - 149 - # app.bsky.actor.defs#profileViewBasic 150 - type Lexicon_app_bsky_actor_defs_profileViewBasic { 151 - did: ID! 152 - handle: ID! 153 - displayName: String 154 - pronouns: String 155 - avatar: String 156 - associated: Lexicon_app_bsky_actor_defs_profileAssociated 157 - viewer: Lexicon_app_bsky_actor_defs_viewerState 113 + # app.bsky.graph.defs#starterPackViewBasic 114 + type Lexicon_app_bsky_graph_defs_starterPackViewBasic { 115 + cid: String! 116 + uri: String! 158 117 labels: [Lexicon_com_atproto_label_defs_label!] 159 - createdAt: String 160 - verification: Lexicon_app_bsky_actor_defs_verificationState 161 - status: Lexicon_app_bsky_actor_defs_statusView 118 + creator: Lexicon_app_bsky_actor_defs_profileViewBasic! 119 + indexedAt: String! 120 + listItemCount: Int 121 + joinedWeekCount: Int 122 + joinedAllTimeCount: Int 162 123 } 163 124 164 125 165 126 166 127 # app.bsky.graph.defs#listViewBasic 167 128 type Lexicon_app_bsky_graph_defs_listViewBasic { 168 - uri: String! 169 129 cid: String! 130 + uri: String! 170 131 name: String! 171 - purpose: Lexicon_app_bsky_graph_defs_listPurpose! 172 132 avatar: String 173 - listItemCount: Int 174 133 labels: [Lexicon_com_atproto_label_defs_label!] 175 134 viewer: Lexicon_app_bsky_graph_defs_listViewerState 135 + purpose: Lexicon_app_bsky_graph_defs_listPurpose! 176 136 indexedAt: String 137 + listItemCount: Int 177 138 } 178 139 179 140 ··· 194 155 195 156 196 157 158 + # app.bsky.actor.defs#profileAssociatedChat 159 + type Lexicon_app_bsky_actor_defs_profileAssociatedChat { 160 + allowIncoming: String! 161 + } 162 + 163 + 164 + 165 + # app.bsky.actor.defs#profileAssociatedGerm 166 + type Lexicon_app_bsky_actor_defs_profileAssociatedGerm { 167 + messageMeUrl: String! 168 + showButtonTo: String! 169 + } 170 + 171 + 172 + 173 + # app.bsky.actor.defs#profileAssociatedActivitySubscription 174 + type Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription { 175 + allowSubscriptions: String! 176 + } 177 + 178 + 179 + 197 180 # app.bsky.actor.defs#verificationView 198 181 type Lexicon_app_bsky_actor_defs_verificationView { 199 - issuer: ID! 200 182 uri: String! 183 + issuer: ID! 201 184 isValid: Boolean! 202 185 createdAt: String! 203 186 } 204 187 205 188 206 189 207 - # app.bsky.graph.defs#listPurpose 208 - enum Lexicon_app_bsky_graph_defs_listPurpose { 209 - APP_BSKY_GRAPH_DEFS_MODLIST 210 - APP_BSKY_GRAPH_DEFS_CURATELIST 211 - APP_BSKY_GRAPH_DEFS_REFERENCELIST 190 + # app.bsky.actor.defs#profileViewBasic 191 + type Lexicon_app_bsky_actor_defs_profileViewBasic { 192 + did: ID! 193 + avatar: String 194 + handle: ID! 195 + labels: [Lexicon_com_atproto_label_defs_label!] 196 + status: Lexicon_app_bsky_actor_defs_statusView 197 + viewer: Lexicon_app_bsky_actor_defs_viewerState 198 + pronouns: String 199 + createdAt: String 200 + associated: Lexicon_app_bsky_actor_defs_profileAssociated 201 + displayName: String 202 + verification: Lexicon_app_bsky_actor_defs_verificationState 212 203 } 213 204 214 205 ··· 217 208 type Lexicon_app_bsky_graph_defs_listViewerState { 218 209 muted: Boolean 219 210 blocked: String 211 + } 212 + 213 + 214 + 215 + # app.bsky.graph.defs#listPurpose 216 + enum Lexicon_app_bsky_graph_defs_listPurpose { 217 + APP_BSKY_GRAPH_DEFS_MODLIST 218 + APP_BSKY_GRAPH_DEFS_CURATELIST 219 + APP_BSKY_GRAPH_DEFS_REFERENCELIST 220 220 } 221 221 222 222
+55
src/bin/main.ts
··· 1 + import { readSchemaFiles } from "../generateLexiconSchema"; 2 + import { generateDefinitions } from "../generateLexiconSchema"; 3 + 4 + const args = process.argv.slice(2); 5 + let lexiconIds: string[] = []; 6 + let output: string | null = null; 7 + let appendSchema: string[] | null = null; 8 + 9 + // Parse arguments 10 + let i = 0; 11 + while (i < args.length) { 12 + const arg = args[i]; 13 + 14 + if (arg === "--output" || arg === "-o") { 15 + i++; 16 + if (i < args.length) { 17 + output = args[i]; 18 + i++; 19 + } 20 + } else if (arg === "--append-schema" || arg === "-a") { 21 + i++; 22 + if (i < args.length) { 23 + appendSchema = appendSchema || []; 24 + appendSchema.push(args[i]); 25 + i++; 26 + } 27 + } else { 28 + // Positional arguments (lexicon IDs) 29 + lexiconIds.push(arg); 30 + i++; 31 + } 32 + } 33 + 34 + if (lexiconIds.length === 0) { 35 + console.error("Error: No lexicon identifiers provided"); 36 + process.exit(1); 37 + } 38 + 39 + let outputContent = ""; 40 + 41 + // Append additional schemas if provided via --append-schema 42 + if (appendSchema) { 43 + const additionalSchemas = await readSchemaFiles(appendSchema); 44 + outputContent += additionalSchemas; 45 + } 46 + 47 + // Append the new schema. 48 + outputContent += generateDefinitions(lexiconIds).join("\n\n"); 49 + 50 + // Write to output file 51 + if (output) { 52 + fs.writeFileSync(output, outputContent); 53 + } else { 54 + console.log(outputContent); 55 + }
+15 -72
src/generateLexiconSchema.ts
··· 28 28 } 29 29 30 30 // Constants 31 - const BASE_DIR = path.dirname(path.resolve(import.meta.dirname, ".")); 32 - const LEXICON_DIR = path.join(BASE_DIR, "../deps/atproto/lexicons"); 31 + const BASE_DIR = path.dirname(path.resolve(import.meta.dirname)); 32 + const LEXICON_DIR = path.join(BASE_DIR, "lexicons"); 33 33 34 34 const TYPE_MAPPING: Record<string, string> = { 35 35 string: "String", ··· 125 125 const typeName = `Lexicon_${ref.replace(/#/g, ".").replace(/\./g, "_")}`; 126 126 output = typeName; 127 127 } else if (lexType.type === "string") { 128 - output = lexType.format && FORMAT_MAPPING[lexType.format] 129 - ? FORMAT_MAPPING[lexType.format] 130 - : "String"; 128 + output = 129 + lexType.format && FORMAT_MAPPING[lexType.format] 130 + ? FORMAT_MAPPING[lexType.format] 131 + : "String"; 131 132 } else if (TYPE_MAPPING[lexType.type]) { 132 133 output = TYPE_MAPPING[lexType.type]; 133 134 } else { ··· 135 136 } 136 137 137 138 const nullable_indicator = isRequired ? "!" : ""; 138 - return `${output}${nullable_indicator}` 139 + return `${output}${nullable_indicator}`; 139 140 } 140 141 141 142 function toUpperSnakeCase(s: string): string { ··· 191 192 return graphqlLines.join("\n"); 192 193 } 193 194 194 - function outputLexiconNamespaces( 195 + export function outputLexiconNamespaces( 195 196 namespaces: Record<string, string>, 196 197 root: LexiconPath, 197 198 ): string[] { ··· 231 232 } 232 233 233 234 const content = lines.map((line) => ` ${line}`).join("\n"); 234 - const typeName = ['Lexicon'].concat(root.segments).join("_"); 235 + const typeName = ["Lexicon"].concat(root.segments).join("_"); 235 236 chunks.push(`type ${typeName} {\n${content}\n}\n`); 236 237 return chunks; 237 238 } 238 239 239 - function generateLexiconStructure(lexiconPath: LexiconPath): { 240 + export function generateLexiconStructure(lexiconPath: LexiconPath): { 240 241 methodDef: string; 241 242 objectDefinitions: Record<string, any>; 242 243 } { ··· 247 248 const lexiconData = JSON.parse(fs.readFileSync(filePath, "utf-8")); 248 249 const parsed = jsonToLex(lexiconData); 249 250 250 - if (!parsed || typeof parsed !== 'object' || !('defs' in parsed)) { 251 + if (!parsed || typeof parsed !== "object" || !("defs" in parsed)) { 251 252 return { methodDef: "", objectDefinitions: {} }; 252 253 } 253 254 ··· 329 330 } 330 331 } 331 332 332 - function generateDefinitions(lexiconIds: string[]): string[] { 333 + export function generateDefinitions(lexiconIds: string[]): string[] { 333 334 const chunks: string[] = []; 334 335 const lexiconFields: Record<string, string> = {}; 335 336 let objectDefinitions: Record<string, any> = {}; ··· 406 407 return chunks; 407 408 } 408 409 409 - async function readSchemaFiles(schemaFiles: string[] | null): Promise<string> { 410 + export async function readSchemaFiles( 411 + schemaFiles: string[] | null, 412 + ): Promise<string> { 410 413 if (!schemaFiles || schemaFiles.length === 0) { 411 414 return ""; 412 415 } ··· 433 436 434 437 return output; 435 438 } 436 - 437 - async function main() { 438 - const args = process.argv.slice(2); 439 - let lexiconIds: string[] = []; 440 - let output: string | null = null; 441 - let appendSchema: string[] | null = null; 442 - 443 - // Parse arguments 444 - let i = 0; 445 - while (i < args.length) { 446 - const arg = args[i]; 447 - 448 - if (arg === "--output" || arg === "-o") { 449 - i++; 450 - if (i < args.length) { 451 - output = args[i]; 452 - i++; 453 - } 454 - } else if (arg === "--append-schema" || arg === "-a") { 455 - i++; 456 - if (i < args.length) { 457 - appendSchema = appendSchema || []; 458 - appendSchema.push(args[i]); 459 - i++; 460 - } 461 - } else { 462 - // Positional arguments (lexicon IDs) 463 - lexiconIds.push(arg); 464 - i++; 465 - } 466 - } 467 - 468 - if (lexiconIds.length === 0) { 469 - console.error("Error: No lexicon identifiers provided"); 470 - process.exit(1); 471 - } 472 - 473 - let outputContent = ""; 474 - 475 - // Append additional schemas if provided via --append-schema 476 - if (appendSchema) { 477 - const additionalSchemas = await readSchemaFiles(appendSchema); 478 - outputContent += additionalSchemas; 479 - } 480 - 481 - // Append the new schema. 482 - outputContent += generateDefinitions(lexiconIds).join("\n\n"); 483 - 484 - // Write to output file 485 - if (output) { 486 - fs.writeFileSync(output, outputContent); 487 - } else { 488 - console.log(outputContent); 489 - } 490 - } 491 - 492 - main().catch((err) => { 493 - console.error("Error:", err); 494 - process.exit(1); 495 - });
+4 -4
tsconfig.json
··· 1 1 { 2 2 "compilerOptions": { 3 - "target": "ES2020", 4 - "module": "CommonJS", 3 + "target": "ES2022", 4 + "module": "es2022", 5 5 "outDir": "./dist", 6 6 "rootDir": "./src", 7 7 "strict": true, 8 8 "esModuleInterop": true, 9 9 "skipLibCheck": true, 10 - "forceConsistentCasingInFileNames": true 10 + "forceConsistentCasingInFileNames": true, 11 11 }, 12 12 "include": ["src/**/*.ts"], 13 - "exclude": ["node_modules"] 13 + "exclude": ["node_modules"], 14 14 }