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.

Add schema generation script

Tim Ryan 1f082628 260b8a37

+1041
+3
.gitignore
··· 50 50 51 51 # Other editors 52 52 . history 53 + 54 + # Python 55 + __pycache__
schema/__init__.py

This is a binary file and will not be displayed.

+16
schema/copy_to_json.py
··· 1 + import json 2 + import os 3 + 4 + # Get the absolute path of the script's directory 5 + script_dir = os.path.dirname(os.path.abspath(__file__)) 6 + 7 + # Load the GraphQL schema from file 8 + with open(os.path.join(script_dir, "schema-generated.graphql"), "r") as graphql_file: 9 + graphql_schema = graphql_file.read() 10 + 11 + # Encode the GraphQL schema as JSON 12 + graphql_json = json.dumps(graphql_schema) 13 + 14 + # Save the JSON-encoded schema to a new file 15 + with open(os.path.join(script_dir, "schema-generated.graphql.json"), "w") as json_file: 16 + _ = json_file.write(graphql_json)
+466
schema/generate_lexicon_schema.py
··· 1 + """Convert Lexicon definitions to GraphQL""" 2 + 3 + import os 4 + import re 5 + import sys 6 + import traceback 7 + from pathlib import Path 8 + from typing import Annotated, TypeAlias 9 + 10 + from atproto_lexicon.models import ( 11 + LexArray, 12 + LexBlob, 13 + LexDefinition, 14 + LexObject, 15 + LexPrimitive, 16 + LexRef, 17 + LexRefUnion, 18 + LexRefVariant, 19 + LexString, 20 + LexXrpcQuery, 21 + ) 22 + from atproto_lexicon.parser import lexicon_parse_file 23 + from typer import Argument, Option, run 24 + 25 + 26 + def normalize_type_name(s: str) -> str: 27 + """ 28 + Normalize type name to PascalCase 29 + """ 30 + # Convert to PascalCase 31 + return s[0].upper() + s[1:] if s else s 32 + 33 + 34 + class LexiconPath: 35 + _segments: list[str] 36 + 37 + def __init__(self, path: str | None = None) -> None: 38 + if path is None: 39 + self._segments = [] 40 + else: 41 + self._segments = path.split(".") 42 + 43 + @property 44 + def segments(self): 45 + return self._segments[:] 46 + 47 + def dot_path(self): 48 + return ".".join(self._segments) 49 + 50 + def file_path(self): 51 + return "/".join(self._segments) 52 + 53 + 54 + # Constants 55 + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 56 + LEXICON_DIR = os.path.join(BASE_DIR, "../deps/atproto/lexicons") 57 + 58 + TYPE_MAPPING = { 59 + "string": "String", 60 + "integer": "Int", 61 + "number": "Float", 62 + "boolean": "Boolean", 63 + "bytes": "String", 64 + } 65 + 66 + FORMAT_MAPPING = { 67 + "did": "ID", 68 + "handle": "ID", 69 + "uri": "String", 70 + "cid": "String", 71 + "datetime": "String", 72 + "date": "String", 73 + "time": "String", 74 + "at-identifier": "ID", 75 + } 76 + 77 + SKIP_FIELDS = {"debug"} # Fields to skip in GraphQL output 78 + 79 + 80 + def resolve_lexicon_path(lexicon_path: LexiconPath) -> str: 81 + """ 82 + Resolve a lexicon ID to its file path 83 + """ 84 + dir_parts = lexicon_path.segments 85 + 86 + # Construct path: app.bsky.actor.getProfile -> app/bsky/actor/getProfile.json 87 + file_path = os.path.join(LEXICON_DIR, *dir_parts[:-1], f"{dir_parts[-1]}.json") 88 + 89 + if not os.path.exists(file_path): 90 + raise FileNotFoundError(f"Lexicon file not found: {file_path}") 91 + 92 + return file_path 93 + 94 + 95 + def normalize_reference(base: str, ref: str) -> str: 96 + """ 97 + Normalize a reference like '#profileViewDetailed' 98 + """ 99 + if "#" in base and ref[0] == "#": 100 + return base.split("#")[0] + ref 101 + elif "#" not in base and ref[0] == "#": 102 + return base + ref 103 + else: 104 + return ref 105 + 106 + 107 + def resolve_reference(ref: str) -> LexDefinition | None: 108 + if "#" in ref: 109 + lexicon_id, type_name = ref.split("#", 1) 110 + else: 111 + lexicon_id = ref 112 + type_name = "main" 113 + try: 114 + lexicon_path = resolve_lexicon_path(LexiconPath(lexicon_id)) 115 + 116 + # Use structured parsing instead of JSON 117 + lexicon_structured_data = lexicon_parse_file(lexicon_path) 118 + 119 + # Find the type definition in the structured data 120 + if not lexicon_structured_data: 121 + return None 122 + 123 + defs = lexicon_structured_data.defs 124 + 125 + # Check if the type_name is in defs directly (as dict key) 126 + if type_name in defs: 127 + return defs[type_name] 128 + 129 + return None 130 + except Exception as e: 131 + print(f"Warning: Could not resolve reference {ref}: {e}", file=sys.stderr) 132 + return None 133 + 134 + 135 + def lex_type_to_graphql_type( 136 + base: str, 137 + lex_type: LexRefVariant | LexPrimitive | LexBlob | LexObject | LexArray, 138 + is_required: bool = False, 139 + ) -> str: 140 + """ 141 + Convert Lexicon type to GraphQL type 142 + """ 143 + if isinstance(lex_type, LexArray): 144 + items_type = lex_type.items 145 + item_type = lex_type_to_graphql_type(base, items_type, True) 146 + output = f"[{item_type}]" 147 + elif isinstance(lex_type, LexRef): 148 + # For reference types, we use the type name directly 149 + ref = normalize_reference(base, lex_type.ref) or "" 150 + # Convert to GraphQL naming convention - camelCase to PascalCase 151 + type_name = f"Lexicon_{ref.replace('#', '.').replace('.', '_')}" 152 + output = type_name 153 + elif isinstance(lex_type, LexString): 154 + if lex_type.format in FORMAT_MAPPING: 155 + output = FORMAT_MAPPING[lex_type.format] 156 + else: 157 + output = "String" 158 + elif lex_type.type in TYPE_MAPPING: 159 + output = TYPE_MAPPING[lex_type.type] 160 + else: 161 + output = "Unknown" 162 + 163 + nullable_indicator = "!" if is_required else "" 164 + return f"{output}{nullable_indicator}" 165 + 166 + 167 + TypeDef: TypeAlias = LexObject | LexString 168 + TypeDefs: TypeAlias = dict[str, TypeDef] 169 + 170 + 171 + def to_upper_snake_case(s: str) -> str: 172 + return re.sub(r"[^a-zA-Z0-9]+", "_", s).upper() 173 + 174 + 175 + def lex_object_definition_to_graphql(type_name: str, type_def: TypeDef) -> str: 176 + """ 177 + Convert a LexObject definition from lexicon to GraphQL 178 + """ 179 + 180 + if isinstance(type_def, LexString): 181 + # Enums 182 + graphql_lines = [ 183 + f"# {type_name}\nenum Lexicon_{type_name.replace('#', '_').replace('.', '_')} {{" 184 + ] 185 + 186 + for field_name in type_def.known_values or []: 187 + graphql_lines.append(f" {to_upper_snake_case(field_name)}") 188 + else: 189 + # Struct/object definitions 190 + graphql_lines = [ 191 + f"# {type_name}\ntype Lexicon_{type_name.replace('#', '_').replace('.', '_')} {{" 192 + ] 193 + 194 + properties = type_def.properties 195 + required_fields = set(type_def.required or []) 196 + 197 + for field_name, field_def in properties.items(): 198 + # Skip debug field as per requirements 199 + if field_name in SKIP_FIELDS: 200 + continue 201 + 202 + # Skip ref unions(?) 203 + if isinstance(field_def, LexRefUnion): 204 + continue 205 + 206 + # Skip "unknown" fields 207 + if field_def.type == "unknown": 208 + continue 209 + 210 + is_required = field_name in required_fields 211 + graphql_type = lex_type_to_graphql_type(type_name, field_def, is_required) 212 + graphql_lines.append(f" {field_name}: {graphql_type}") 213 + 214 + graphql_lines.append("}") 215 + return "\n".join(graphql_lines) 216 + 217 + 218 + # Build the nested types from the outside in 219 + # For app.bsky.actor.getProfile, we need: 220 + # 1. type LexiconApp { bsky: LexiconAppBsky! } 221 + # 2. type LexiconAppBsky { actor: LexiconAppBskyActor! } 222 + # 3. type LexiconAppBskyActor { getProfile(...): ... } 223 + def output_lexicon_namespaces( 224 + namespaces: dict[str, str], root: LexiconPath 225 + ) -> list[str]: 226 + lines: list[str] = [] 227 + chunks: list[str] = [] 228 + root_len = len(root.segments) 229 + while len(namespaces): 230 + lexicon_path = LexiconPath(next(iter(namespaces))) 231 + if root_len == len(lexicon_path.segments) - 1: 232 + # Done, output the type 233 + lines.append(namespaces.pop(lexicon_path.dot_path())) 234 + else: 235 + next_segment = lexicon_path.segments[root_len] 236 + next_path = ( 237 + f"{root.dot_path()}.{next_segment}" if root_len > 0 else next_segment 238 + ) 239 + 240 + group: dict[str, str] = dict() 241 + for path in list(namespaces.keys()): 242 + if path.startswith(f"{next_path}."): 243 + group[path] = namespaces.pop(path) 244 + chunks += output_lexicon_namespaces(group, LexiconPath(next_path)) 245 + 246 + lines.append( 247 + f"{next_segment}: {'_'.join(['Lexicon'] + LexiconPath(next_path).segments)}!" 248 + ) 249 + 250 + content = " " + "\n ".join(lines) 251 + chunks.append(f"type {'_'.join(['Lexicon'] + root.segments)} {{\n{content}\n}}\n") 252 + return chunks 253 + 254 + 255 + def generate_lexicon_structure( 256 + lexicon_path: LexiconPath, 257 + ) -> tuple[str, TypeDefs]: 258 + """ 259 + Generate the nested Lexicon structure for a lexicon endpoint 260 + Returns (graphql_type, set(refs)) 261 + """ 262 + 263 + method_name = lexicon_path.segments[-1] 264 + 265 + try: 266 + # Structured parsing of the Lexicon .json file 267 + lexicon_structured_data = lexicon_parse_file(resolve_lexicon_path(lexicon_path)) 268 + 269 + if not lexicon_structured_data: 270 + return ("", dict()) 271 + 272 + defs = lexicon_structured_data.defs 273 + main_def = defs["main"] 274 + assert isinstance(main_def, LexXrpcQuery) 275 + 276 + # Build the method definition 277 + referenced_types: set[str] = set() 278 + object_definitions: TypeDefs = dict() 279 + 280 + # Add parameters as fields 281 + method_fields: list[str] = [] 282 + if main_def.parameters: 283 + params = main_def.parameters 284 + required_fields = params.required or [] 285 + for field_name, field_def in params.properties.items(): 286 + # Convert the structured field definition to dict for get_graphql_type 287 + is_required = field_name in required_fields 288 + graphql_type = lex_type_to_graphql_type( 289 + lexicon_path.dot_path(), field_def, is_required 290 + ) 291 + method_fields.append(f"{field_name}: {graphql_type}") 292 + 293 + # Sweep up refs 294 + if isinstance(field_def, LexRef): 295 + referenced_types.add(field_def.ref) 296 + 297 + method_params = f"({', '.join(method_fields)})" if len(method_fields) else "" 298 + 299 + return_type = None 300 + 301 + if main_def.output: 302 + output_schema = main_def.output.schema_ 303 + if isinstance(output_schema, LexRef): 304 + # Use the output type as return type 305 + return_type = lex_type_to_graphql_type( 306 + lexicon_path.dot_path(), output_schema 307 + ) 308 + 309 + # Sweep up refs 310 + referenced_types.add(output_schema.ref) 311 + elif isinstance(output_schema, LexObject): 312 + # Handle inline object definitions for return type 313 + output_type = f"Lexicon_{'_'.join(lexicon_path.segments)}Output" 314 + return_type = output_type 315 + object_definitions[lexicon_path.dot_path() + "Output"] = output_schema 316 + 317 + # Resolve references 318 + for ref in referenced_types: 319 + ref = normalize_reference(lexicon_path.dot_path(), ref) 320 + ref_def = resolve_reference(ref) 321 + if isinstance(ref_def, TypeDef): 322 + object_definitions[ref] = ref_def 323 + 324 + if return_type: 325 + method_def = f"{method_name}{method_params}: {return_type}" 326 + else: 327 + method_def = f"{method_name}{method_params}" 328 + 329 + return (method_def, object_definitions) 330 + except Exception as e: 331 + print( 332 + f"Error processing lexicon {'.'.join(lexicon_path.segments)}: {e}", 333 + file=sys.stderr, 334 + ) 335 + traceback.print_exc() 336 + return ("", dict()) 337 + 338 + 339 + def generate_definitions(lexicon_ids: list[str]) -> list[str]: 340 + chunks: list[str] = [] 341 + lexicon_fields: dict[str, str] = dict() 342 + object_definitions: TypeDefs = dict() 343 + for lexicon_id in lexicon_ids: 344 + lexicon_path = LexiconPath(lexicon_id) 345 + 346 + # Generate main lexicon structure 347 + method_type, collected_object_definitions = generate_lexicon_structure( 348 + lexicon_path 349 + ) 350 + lexicon_fields[lexicon_path.dot_path()] = method_type 351 + object_definitions.update(collected_object_definitions) 352 + 353 + # Walk output types for more refs 354 + final_definitions: TypeDefs = dict() 355 + new_definitions: TypeDefs = dict() 356 + while len(object_definitions): 357 + for obj_name, obj_def in object_definitions.items(): 358 + if isinstance(obj_def, LexObject): 359 + for field_def in obj_def.properties.values(): 360 + # Export refs 361 + if isinstance(field_def, LexRef): 362 + ref = normalize_reference(obj_name, field_def.ref) 363 + ref_def = resolve_reference(ref) 364 + if isinstance(ref_def, TypeDef): 365 + new_definitions[ref] = ref_def 366 + continue 367 + elif isinstance(field_def, LexArray) and isinstance( 368 + field_def.items, LexRef 369 + ): 370 + ref = normalize_reference(obj_name, field_def.items.ref) 371 + ref_def = resolve_reference(ref) 372 + if isinstance(ref_def, LexObject) or isinstance( 373 + ref_def, LexString 374 + ): 375 + new_definitions[ref] = ref_def 376 + continue 377 + 378 + final_definitions[obj_name] = obj_def 379 + 380 + # Reset object_definitions to only be ones we haven't seen yet. 381 + object_definitions = dict() 382 + for obj_key, obj_def in new_definitions.items(): 383 + if obj_key not in final_definitions: 384 + object_definitions[obj_key] = obj_def 385 + object_definitions = final_definitions 386 + 387 + # Generate types for all output types 388 + for name, type in object_definitions.items(): 389 + chunks.append(lex_object_definition_to_graphql(name, type) + "\n\n") 390 + 391 + # Generate Lexicon structs iteratively. 392 + chunks += output_lexicon_namespaces(lexicon_fields, LexiconPath()) 393 + 394 + return chunks 395 + 396 + 397 + def read_schema_files(schema_files: list[str] | None) -> str: 398 + """Read content from schema files or folders recursively.""" 399 + if not schema_files: 400 + return "" 401 + 402 + output = "" 403 + for schema_file in schema_files: 404 + path = Path(schema_file) 405 + if path.is_file(): 406 + # If it's a single file, read it 407 + with open(str(path), "r") as f: 408 + content = f.read() 409 + output += "\n" + content + "\n" 410 + elif path.is_dir(): 411 + # If it's a directory, recursively read all .graphql files 412 + for graphql_file in path.rglob("*.graphql"): 413 + with open(str(graphql_file), "r") as f: 414 + content = f.read() 415 + output += "\n" + content + "\n" 416 + return output 417 + 418 + 419 + def main( 420 + lexicon_ids: Annotated[ 421 + list[str], 422 + Argument( 423 + ..., 424 + help="List of lexicon identifiers to generate definitions for", 425 + ), 426 + ], 427 + append_schema: Annotated[ 428 + list[str], 429 + Option( 430 + "--append-schema", 431 + "-a", 432 + help="Additional GraphQL schema files or folders to append. Can be used multiple times.", 433 + ), 434 + ], 435 + output: Annotated[ 436 + str, 437 + Option( 438 + "--output", 439 + "-o", 440 + help="Output file path (default: stdout)", 441 + ), 442 + ], 443 + ) -> None: 444 + """Generate GraphQL schema from lexicon_ids and source files.""" 445 + 446 + output_content: str = "" 447 + 448 + # Append additional schemas if provided via --append-schema 449 + if append_schema: 450 + additional_schemas = read_schema_files(append_schema) 451 + output_content += additional_schemas 452 + 453 + # Append the new schema. 454 + output_content += "\n\n".join(generate_definitions(lexicon_ids)) 455 + 456 + # Write to output file 457 + if output: 458 + output_path: str = output 459 + with open(output_path, "w") as f: 460 + _ = f.write(output_content) 461 + else: 462 + print(output) 463 + 464 + 465 + if __name__ == "__main__": 466 + run(main)
+402
schema/schema-generated.graphql
··· 1 + 2 + type AuthSession { 3 + did: String! 4 + } 5 + 6 + type AuthStatus { 7 + success: Boolean! 8 + session: AuthSession 9 + } 10 + 11 + type InventoryItem { 12 + id: ID! 13 + title: String! 14 + cost: String! 15 + images: [String!]! 16 + description: String! 17 + location: String 18 + condition: String 19 + category: String 20 + postedAt: String 21 + owner: ID! 22 + owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed 23 + } 24 + 25 + type PageInfo { 26 + hasNextPage: Boolean! 27 + hasPreviousPage: Boolean! 28 + startCursor: String 29 + endCursor: String 30 + } 31 + 32 + type InventoryEdge { 33 + cursor: String! 34 + node: InventoryItem! 35 + } 36 + 37 + type InventoryConnection { 38 + edges: [InventoryEdge!]! 39 + pageInfo: PageInfo! 40 + } 41 + 42 + enum Sender { 43 + them 44 + me 45 + } 46 + 47 + type Message { 48 + id: ID! 49 + sender: Sender! 50 + text: String! 51 + time: String! 52 + } 53 + 54 + type Chat { 55 + id: ID! 56 + unread: Boolean! 57 + sender: String! 58 + avatar: String 59 + messages: [Message!]! 60 + } 61 + 62 + ############# 63 + # Notifications 64 + ############# 65 + 66 + type Notification { 67 + id: ID! 68 + title: String! 69 + message: String! 70 + time: String! 71 + read: Boolean! 72 + } 73 + 74 + ############# 75 + # Root object 76 + ############# 77 + 78 + type Query { 79 + inventory( 80 + first: Int 81 + after: String 82 + last: Int 83 + before: String 84 + category: String 85 + location: String 86 + condition: String 87 + search: String 88 + minPrice: Float 89 + maxPrice: Float 90 + ): InventoryConnection! 91 + notifications: [Notification!]! 92 + chats: [Chat!]! 93 + user_notification_settings: UserNotificationSettings! 94 + lexicon: Lexicon! 95 + } 96 + 97 + type SessionDestroyResult { 98 + success: Boolean! 99 + } 100 + 101 + type Mutation { 102 + session_retrieve: AuthSession 103 + session_destroy: SessionDestroyResult 104 + update_user_notification_settings( 105 + enableAll: Boolean 106 + listingUpdates: Boolean 107 + userRequests: Boolean 108 + messages: Boolean 109 + promotions: Boolean 110 + news: Boolean 111 + ): UserNotificationSettings! 112 + create_inventory( 113 + title: String! 114 + cost: String! 115 + description: String! 116 + location: String 117 + condition: String 118 + category: String 119 + images: [String!]! 120 + ): CreateInventoryResult! 121 + authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus 122 + trigger_session_deleted(did: String!): SessionDeletedTriggerResult! 123 + } 124 + 125 + type SessionDeletedTriggerResult { 126 + success: Boolean! 127 + message: String! 128 + } 129 + 130 + type CreateInventoryResult { 131 + edge: InventoryEdge 132 + } 133 + 134 + type UserNotificationSettings { 135 + id: ID! 136 + enableAll: Boolean! 137 + listingUpdates: Boolean! 138 + userRequests: Boolean! 139 + messages: Boolean! 140 + promotions: Boolean! 141 + news: Boolean! 142 + } 143 + 144 + type SessionDeletedPayload { 145 + did: String! 146 + } 147 + 148 + type Subscription { 149 + session_deleted: SessionDeletedPayload! 150 + } 151 + 152 + ############# 153 + # ATProto Lexicon 154 + ############# 155 + 156 + # app.bsky.actor.defs#profileViewDetailed 157 + type Lexicon_app_bsky_actor_defs_profileViewDetailed { 158 + did: ID! 159 + handle: ID! 160 + displayName: String 161 + description: String 162 + pronouns: String 163 + website: String 164 + avatar: String 165 + banner: String 166 + followersCount: Int 167 + followsCount: Int 168 + postsCount: Int 169 + associated: Lexicon_app_bsky_actor_defs_profileAssociated 170 + joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic 171 + indexedAt: String 172 + createdAt: String 173 + viewer: Lexicon_app_bsky_actor_defs_viewerState 174 + labels: [Lexicon_com_atproto_label_defs_label!] 175 + pinnedPost: Lexicon_com_atproto_repo_strongRef 176 + verification: Lexicon_app_bsky_actor_defs_verificationState 177 + status: Lexicon_app_bsky_actor_defs_statusView 178 + } 179 + 180 + 181 + 182 + # com.atproto.server.getSessionOutput 183 + type Lexicon_com_atproto_server_getSessionOutput { 184 + handle: ID! 185 + did: ID! 186 + email: String 187 + emailConfirmed: Boolean 188 + emailAuthFactor: Boolean 189 + active: Boolean 190 + status: String 191 + } 192 + 193 + 194 + 195 + # app.bsky.actor.defs#profileAssociated 196 + type Lexicon_app_bsky_actor_defs_profileAssociated { 197 + lists: Int 198 + feedgens: Int 199 + starterPacks: Int 200 + labeler: Boolean 201 + chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat 202 + activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription 203 + } 204 + 205 + 206 + 207 + # app.bsky.graph.defs#starterPackViewBasic 208 + type Lexicon_app_bsky_graph_defs_starterPackViewBasic { 209 + uri: String! 210 + cid: String! 211 + creator: Lexicon_app_bsky_actor_defs_profileViewBasic! 212 + listItemCount: Int 213 + joinedWeekCount: Int 214 + joinedAllTimeCount: Int 215 + labels: [Lexicon_com_atproto_label_defs_label!] 216 + indexedAt: String! 217 + } 218 + 219 + 220 + 221 + # app.bsky.actor.defs#viewerState 222 + type Lexicon_app_bsky_actor_defs_viewerState { 223 + muted: Boolean 224 + mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic 225 + blockedBy: Boolean 226 + blocking: String 227 + blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic 228 + following: String 229 + followedBy: String 230 + knownFollowers: Lexicon_app_bsky_actor_defs_knownFollowers 231 + activitySubscription: Lexicon_app_bsky_notification_defs_activitySubscription 232 + } 233 + 234 + 235 + 236 + # com.atproto.label.defs#label 237 + type Lexicon_com_atproto_label_defs_label { 238 + ver: Int 239 + src: ID! 240 + uri: String! 241 + cid: String 242 + val: String! 243 + neg: Boolean 244 + cts: String! 245 + exp: String 246 + sig: String 247 + } 248 + 249 + 250 + 251 + # com.atproto.repo.strongRef 252 + type Lexicon_com_atproto_repo_strongRef { 253 + uri: String! 254 + cid: String! 255 + } 256 + 257 + 258 + 259 + # app.bsky.actor.defs#verificationState 260 + type Lexicon_app_bsky_actor_defs_verificationState { 261 + verifications: [Lexicon_app_bsky_actor_defs_verificationView!]! 262 + verifiedStatus: String! 263 + trustedVerifierStatus: String! 264 + } 265 + 266 + 267 + 268 + # app.bsky.actor.defs#statusView 269 + type Lexicon_app_bsky_actor_defs_statusView { 270 + uri: String 271 + cid: String 272 + status: String! 273 + expiresAt: String 274 + isActive: Boolean 275 + isDisabled: Boolean 276 + } 277 + 278 + 279 + 280 + # app.bsky.actor.defs#profileAssociatedChat 281 + type Lexicon_app_bsky_actor_defs_profileAssociatedChat { 282 + allowIncoming: String! 283 + } 284 + 285 + 286 + 287 + # app.bsky.actor.defs#profileAssociatedActivitySubscription 288 + type Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription { 289 + allowSubscriptions: String! 290 + } 291 + 292 + 293 + 294 + # app.bsky.actor.defs#profileViewBasic 295 + type Lexicon_app_bsky_actor_defs_profileViewBasic { 296 + did: ID! 297 + handle: ID! 298 + displayName: String 299 + pronouns: String 300 + avatar: String 301 + associated: Lexicon_app_bsky_actor_defs_profileAssociated 302 + viewer: Lexicon_app_bsky_actor_defs_viewerState 303 + labels: [Lexicon_com_atproto_label_defs_label!] 304 + createdAt: String 305 + verification: Lexicon_app_bsky_actor_defs_verificationState 306 + status: Lexicon_app_bsky_actor_defs_statusView 307 + } 308 + 309 + 310 + 311 + # app.bsky.graph.defs#listViewBasic 312 + type Lexicon_app_bsky_graph_defs_listViewBasic { 313 + uri: String! 314 + cid: String! 315 + name: String! 316 + purpose: Lexicon_app_bsky_graph_defs_listPurpose! 317 + avatar: String 318 + listItemCount: Int 319 + labels: [Lexicon_com_atproto_label_defs_label!] 320 + viewer: Lexicon_app_bsky_graph_defs_listViewerState 321 + indexedAt: String 322 + } 323 + 324 + 325 + 326 + # app.bsky.actor.defs#knownFollowers 327 + type Lexicon_app_bsky_actor_defs_knownFollowers { 328 + count: Int! 329 + followers: [Lexicon_app_bsky_actor_defs_profileViewBasic!]! 330 + } 331 + 332 + 333 + 334 + # app.bsky.notification.defs#activitySubscription 335 + type Lexicon_app_bsky_notification_defs_activitySubscription { 336 + post: Boolean! 337 + reply: Boolean! 338 + } 339 + 340 + 341 + 342 + # app.bsky.actor.defs#verificationView 343 + type Lexicon_app_bsky_actor_defs_verificationView { 344 + issuer: ID! 345 + uri: String! 346 + isValid: Boolean! 347 + createdAt: String! 348 + } 349 + 350 + 351 + 352 + # app.bsky.graph.defs#listPurpose 353 + enum Lexicon_app_bsky_graph_defs_listPurpose { 354 + APP_BSKY_GRAPH_DEFS_MODLIST 355 + APP_BSKY_GRAPH_DEFS_CURATELIST 356 + APP_BSKY_GRAPH_DEFS_REFERENCELIST 357 + } 358 + 359 + 360 + 361 + # app.bsky.graph.defs#listViewerState 362 + type Lexicon_app_bsky_graph_defs_listViewerState { 363 + muted: Boolean 364 + blocked: String 365 + } 366 + 367 + 368 + 369 + type Lexicon_app_bsky_actor { 370 + getProfile(actor: ID!): Lexicon_app_bsky_actor_defs_profileViewDetailed 371 + } 372 + 373 + 374 + type Lexicon_app_bsky { 375 + actor: Lexicon_app_bsky_actor! 376 + } 377 + 378 + 379 + type Lexicon_app { 380 + bsky: Lexicon_app_bsky! 381 + } 382 + 383 + 384 + type Lexicon_com_atproto_server { 385 + getSession: Lexicon_com_atproto_server_getSessionOutput 386 + } 387 + 388 + 389 + type Lexicon_com_atproto { 390 + server: Lexicon_com_atproto_server! 391 + } 392 + 393 + 394 + type Lexicon_com { 395 + atproto: Lexicon_com_atproto! 396 + } 397 + 398 + 399 + type Lexicon { 400 + app: Lexicon_app! 401 + com: Lexicon_com! 402 + }
+1
schema/schema-generated.graphql.json
··· 1 + "\ntype AuthSession {\n did: String!\n}\n\ntype AuthStatus {\n success: Boolean!\n session: AuthSession\n}\n\ntype InventoryItem {\n id: ID!\n title: String!\n cost: String!\n images: [String!]!\n description: String!\n location: String\n condition: String\n category: String\n postedAt: String\n owner: ID!\n owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed\n}\n\ntype PageInfo {\n hasNextPage: Boolean!\n hasPreviousPage: Boolean!\n startCursor: String\n endCursor: String\n}\n\ntype InventoryEdge {\n cursor: String!\n node: InventoryItem!\n}\n\ntype InventoryConnection {\n edges: [InventoryEdge!]!\n pageInfo: PageInfo!\n}\n\nenum Sender {\n them\n me\n}\n\ntype Message {\n id: ID!\n sender: Sender!\n text: String!\n time: String!\n}\n\ntype Chat {\n id: ID!\n unread: Boolean!\n sender: String!\n avatar: String\n messages: [Message!]!\n}\n\n#############\n# Notifications\n#############\n\ntype Notification {\n id: ID!\n title: String!\n message: String!\n time: String!\n read: Boolean!\n}\n\n#############\n# Root object\n#############\n\ntype Query {\n inventory(\n first: Int\n after: String\n last: Int\n before: String\n category: String\n location: String\n condition: String\n search: String\n minPrice: Float\n maxPrice: Float\n ): InventoryConnection!\n notifications: [Notification!]!\n chats: [Chat!]!\n user_notification_settings: UserNotificationSettings!\n lexicon: Lexicon!\n}\n\ntype SessionDestroyResult {\n success: Boolean!\n}\n\ntype Mutation {\n session_retrieve: AuthSession\n session_destroy: SessionDestroyResult\n update_user_notification_settings(\n enableAll: Boolean\n listingUpdates: Boolean\n userRequests: Boolean\n messages: Boolean\n promotions: Boolean\n news: Boolean\n ): UserNotificationSettings!\n create_inventory(\n title: String!\n cost: String!\n description: String!\n location: String\n condition: String\n category: String\n images: [String!]!\n ): CreateInventoryResult!\n authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus\n trigger_session_deleted(did: String!): SessionDeletedTriggerResult!\n}\n\ntype SessionDeletedTriggerResult {\n success: Boolean!\n message: String!\n}\n\ntype CreateInventoryResult {\n edge: InventoryEdge\n}\n\ntype UserNotificationSettings {\n id: ID!\n enableAll: Boolean!\n listingUpdates: Boolean!\n userRequests: Boolean!\n messages: Boolean!\n promotions: Boolean!\n news: Boolean!\n}\n\ntype SessionDeletedPayload {\n did: String!\n}\n\ntype Subscription {\n session_deleted: SessionDeletedPayload!\n}\n\n#############\n# ATProto Lexicon\n#############\n\n# app.bsky.actor.defs#profileViewDetailed\ntype Lexicon_app_bsky_actor_defs_profileViewDetailed {\n did: ID!\n handle: ID!\n displayName: String\n description: String\n pronouns: String\n website: String\n avatar: String\n banner: String\n followersCount: Int\n followsCount: Int\n postsCount: Int\n associated: Lexicon_app_bsky_actor_defs_profileAssociated\n joinedViaStarterPack: Lexicon_app_bsky_graph_defs_starterPackViewBasic\n indexedAt: String\n createdAt: String\n viewer: Lexicon_app_bsky_actor_defs_viewerState\n labels: [Lexicon_com_atproto_label_defs_label!]\n pinnedPost: Lexicon_com_atproto_repo_strongRef\n verification: Lexicon_app_bsky_actor_defs_verificationState\n status: Lexicon_app_bsky_actor_defs_statusView\n}\n\n\n\n# com.atproto.server.getSessionOutput\ntype Lexicon_com_atproto_server_getSessionOutput {\n handle: ID!\n did: ID!\n email: String\n emailConfirmed: Boolean\n emailAuthFactor: Boolean\n active: Boolean\n status: String\n}\n\n\n\n# app.bsky.actor.defs#profileAssociated\ntype Lexicon_app_bsky_actor_defs_profileAssociated {\n lists: Int\n feedgens: Int\n starterPacks: Int\n labeler: Boolean\n chat: Lexicon_app_bsky_actor_defs_profileAssociatedChat\n activitySubscription: Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription\n}\n\n\n\n# app.bsky.graph.defs#starterPackViewBasic\ntype Lexicon_app_bsky_graph_defs_starterPackViewBasic {\n uri: String!\n cid: String!\n creator: Lexicon_app_bsky_actor_defs_profileViewBasic!\n listItemCount: Int\n joinedWeekCount: Int\n joinedAllTimeCount: Int\n labels: [Lexicon_com_atproto_label_defs_label!]\n indexedAt: String!\n}\n\n\n\n# app.bsky.actor.defs#viewerState\ntype Lexicon_app_bsky_actor_defs_viewerState {\n muted: Boolean\n mutedByList: Lexicon_app_bsky_graph_defs_listViewBasic\n blockedBy: Boolean\n blocking: String\n blockingByList: Lexicon_app_bsky_graph_defs_listViewBasic\n following: String\n followedBy: String\n knownFollowers: Lexicon_app_bsky_actor_defs_knownFollowers\n activitySubscription: Lexicon_app_bsky_notification_defs_activitySubscription\n}\n\n\n\n# com.atproto.label.defs#label\ntype Lexicon_com_atproto_label_defs_label {\n ver: Int\n src: ID!\n uri: String!\n cid: String\n val: String!\n neg: Boolean\n cts: String!\n exp: String\n sig: String\n}\n\n\n\n# com.atproto.repo.strongRef\ntype Lexicon_com_atproto_repo_strongRef {\n uri: String!\n cid: String!\n}\n\n\n\n# app.bsky.actor.defs#verificationState\ntype Lexicon_app_bsky_actor_defs_verificationState {\n verifications: [Lexicon_app_bsky_actor_defs_verificationView!]!\n verifiedStatus: String!\n trustedVerifierStatus: String!\n}\n\n\n\n# app.bsky.actor.defs#statusView\ntype Lexicon_app_bsky_actor_defs_statusView {\n uri: String\n cid: String\n status: String!\n expiresAt: String\n isActive: Boolean\n isDisabled: Boolean\n}\n\n\n\n# app.bsky.actor.defs#profileAssociatedChat\ntype Lexicon_app_bsky_actor_defs_profileAssociatedChat {\n allowIncoming: String!\n}\n\n\n\n# app.bsky.actor.defs#profileAssociatedActivitySubscription\ntype Lexicon_app_bsky_actor_defs_profileAssociatedActivitySubscription {\n allowSubscriptions: String!\n}\n\n\n\n# app.bsky.actor.defs#profileViewBasic\ntype Lexicon_app_bsky_actor_defs_profileViewBasic {\n did: ID!\n handle: ID!\n displayName: String\n pronouns: String\n avatar: String\n associated: Lexicon_app_bsky_actor_defs_profileAssociated\n viewer: Lexicon_app_bsky_actor_defs_viewerState\n labels: [Lexicon_com_atproto_label_defs_label!]\n createdAt: String\n verification: Lexicon_app_bsky_actor_defs_verificationState\n status: Lexicon_app_bsky_actor_defs_statusView\n}\n\n\n\n# app.bsky.graph.defs#listViewBasic\ntype Lexicon_app_bsky_graph_defs_listViewBasic {\n uri: String!\n cid: String!\n name: String!\n purpose: Lexicon_app_bsky_graph_defs_listPurpose!\n avatar: String\n listItemCount: Int\n labels: [Lexicon_com_atproto_label_defs_label!]\n viewer: Lexicon_app_bsky_graph_defs_listViewerState\n indexedAt: String\n}\n\n\n\n# app.bsky.actor.defs#knownFollowers\ntype Lexicon_app_bsky_actor_defs_knownFollowers {\n count: Int!\n followers: [Lexicon_app_bsky_actor_defs_profileViewBasic!]!\n}\n\n\n\n# app.bsky.notification.defs#activitySubscription\ntype Lexicon_app_bsky_notification_defs_activitySubscription {\n post: Boolean!\n reply: Boolean!\n}\n\n\n\n# app.bsky.actor.defs#verificationView\ntype Lexicon_app_bsky_actor_defs_verificationView {\n issuer: ID!\n uri: String!\n isValid: Boolean!\n createdAt: String!\n}\n\n\n\n# app.bsky.graph.defs#listPurpose\nenum Lexicon_app_bsky_graph_defs_listPurpose {\n APP_BSKY_GRAPH_DEFS_MODLIST\n APP_BSKY_GRAPH_DEFS_CURATELIST\n APP_BSKY_GRAPH_DEFS_REFERENCELIST\n}\n\n\n\n# app.bsky.graph.defs#listViewerState\ntype Lexicon_app_bsky_graph_defs_listViewerState {\n muted: Boolean\n blocked: String\n}\n\n\n\ntype Lexicon_app_bsky_actor {\n getProfile(actor: ID!): Lexicon_app_bsky_actor_defs_profileViewDetailed\n}\n\n\ntype Lexicon_app_bsky {\n actor: Lexicon_app_bsky_actor!\n}\n\n\ntype Lexicon_app {\n bsky: Lexicon_app_bsky!\n}\n\n\ntype Lexicon_com_atproto_server {\n getSession: Lexicon_com_atproto_server_getSessionOutput\n}\n\n\ntype Lexicon_com_atproto {\n server: Lexicon_com_atproto_server!\n}\n\n\ntype Lexicon_com {\n atproto: Lexicon_com_atproto!\n}\n\n\ntype Lexicon {\n app: Lexicon_app!\n com: Lexicon_com!\n}\n"
+153
schema/sources/types.graphql
··· 1 + type AuthSession { 2 + did: String! 3 + } 4 + 5 + type AuthStatus { 6 + success: Boolean! 7 + session: AuthSession 8 + } 9 + 10 + type InventoryItem { 11 + id: ID! 12 + title: String! 13 + cost: String! 14 + images: [String!]! 15 + description: String! 16 + location: String 17 + condition: String 18 + category: String 19 + postedAt: String 20 + owner: ID! 21 + owner_profile: Lexicon_app_bsky_actor_defs_profileViewDetailed 22 + } 23 + 24 + type PageInfo { 25 + hasNextPage: Boolean! 26 + hasPreviousPage: Boolean! 27 + startCursor: String 28 + endCursor: String 29 + } 30 + 31 + type InventoryEdge { 32 + cursor: String! 33 + node: InventoryItem! 34 + } 35 + 36 + type InventoryConnection { 37 + edges: [InventoryEdge!]! 38 + pageInfo: PageInfo! 39 + } 40 + 41 + enum Sender { 42 + them 43 + me 44 + } 45 + 46 + type Message { 47 + id: ID! 48 + sender: Sender! 49 + text: String! 50 + time: String! 51 + } 52 + 53 + type Chat { 54 + id: ID! 55 + unread: Boolean! 56 + sender: String! 57 + avatar: String 58 + messages: [Message!]! 59 + } 60 + 61 + ############# 62 + # Notifications 63 + ############# 64 + 65 + type Notification { 66 + id: ID! 67 + title: String! 68 + message: String! 69 + time: String! 70 + read: Boolean! 71 + } 72 + 73 + ############# 74 + # Root object 75 + ############# 76 + 77 + type Query { 78 + inventory( 79 + first: Int 80 + after: String 81 + last: Int 82 + before: String 83 + category: String 84 + location: String 85 + condition: String 86 + search: String 87 + minPrice: Float 88 + maxPrice: Float 89 + ): InventoryConnection! 90 + notifications: [Notification!]! 91 + chats: [Chat!]! 92 + user_notification_settings: UserNotificationSettings! 93 + lexicon: Lexicon! 94 + } 95 + 96 + type SessionDestroyResult { 97 + success: Boolean! 98 + } 99 + 100 + type Mutation { 101 + session_retrieve: AuthSession 102 + session_destroy: SessionDestroyResult 103 + update_user_notification_settings( 104 + enableAll: Boolean 105 + listingUpdates: Boolean 106 + userRequests: Boolean 107 + messages: Boolean 108 + promotions: Boolean 109 + news: Boolean 110 + ): UserNotificationSettings! 111 + create_inventory( 112 + title: String! 113 + cost: String! 114 + description: String! 115 + location: String 116 + condition: String 117 + category: String 118 + images: [String!]! 119 + ): CreateInventoryResult! 120 + authenticate(code: String!, state: String!, iss: String!, redirect_uri: String!): AuthStatus 121 + trigger_session_deleted(did: String!): SessionDeletedTriggerResult! 122 + } 123 + 124 + type SessionDeletedTriggerResult { 125 + success: Boolean! 126 + message: String! 127 + } 128 + 129 + type CreateInventoryResult { 130 + edge: InventoryEdge 131 + } 132 + 133 + type UserNotificationSettings { 134 + id: ID! 135 + enableAll: Boolean! 136 + listingUpdates: Boolean! 137 + userRequests: Boolean! 138 + messages: Boolean! 139 + promotions: Boolean! 140 + news: Boolean! 141 + } 142 + 143 + type SessionDeletedPayload { 144 + did: String! 145 + } 146 + 147 + type Subscription { 148 + session_deleted: SessionDeletedPayload! 149 + } 150 + 151 + ############# 152 + # ATProto Lexicon 153 + #############