# GraphQL Interface for MarkiTect ## Overview The GraphQL interface provides a powerful, type-safe way to query and modify MarkiTect's database content including Markdown files, ASTs, schemas, and metadata. This interface enables developers to build rich applications on top of MarkiTect's data with flexible querying capabilities and complete CRUD operations. ## Features - **Complete Type Safety**: Strongly-typed GraphQL schema with comprehensive field definitions - **Flexible Querying**: Query exactly the data you need with GraphQL's selective field syntax - **Rich Data Access**: Access markdown files, schemas, ASTs, and computed fields - **Search Capabilities**: Full-text search across files and schemas with relevance scoring - **Pagination Support**: Efficient pagination for large datasets - **Real-time Introspection**: Schema introspection and GraphQL Playground for development - **Multiple Access Methods**: HTTP server, local execution, and CLI integration - **Full CRUD Operations**: Create, read, update, and delete markdown files and schemas via mutations - **Structured Error Handling**: Comprehensive error responses with validation and success indicators ## Getting Started ### Prerequisites Install the required dependencies: ```bash pip install graphene flask flask-cors jsonpath-ng requests ``` ### Starting the GraphQL Server Start the GraphQL server using the CLI: ```bash markitect graphql-serve ``` By default, this starts the server on `http://localhost:5000`. You can customize the host and port: ```bash markitect graphql-serve --host 0.0.0.0 --port 8080 ``` ### Accessing the GraphQL Playground Once the server is running, visit `http://localhost:5000/graphql` in your browser to access the GraphQL Playground, an interactive development environment for testing queries. ## GraphQL Schema ### Core Types #### MarkdownFile Represents a Markdown file in the MarkiTect database: ```graphql type MarkdownFile { id: Int! filename: String! content: String frontMatter: [FrontMatter] frontMatterRaw: JSONString createdAt: DateTime wordCount: Int lineCount: Int hasFrontMatter: Boolean } ``` #### Schema Represents a JSON schema: ```graphql type Schema { id: Int! filename: String! title: String description: String schemaContent: JSONString! createdAt: DateTime updatedAt: DateTime schemaVersion: String propertyCount: Int } ``` #### AST Represents an Abstract Syntax Tree for a Markdown file: ```graphql type AST { fileId: Int filename: String! tree: [ASTNode] metadata: JSONString headingCount: Int linkCount: Int imageCount: Int codeBlockCount: Int } ``` #### DatabaseStats Provides statistics about the MarkiTect database: ```graphql type DatabaseStats { totalFiles: Int totalSchemas: Int totalSizeBytes: Int lastUpdated: DateTime } ``` #### SearchResult Represents search results across files and schemas: ```graphql type SearchResult { type: String! score: Float file: MarkdownFile schema: Schema highlight: String } ``` ### Query Operations #### Single Item Queries Get a specific markdown file: ```graphql { markdownFile(id: 1) { id filename content wordCount hasFrontMatter } } ``` Get a specific schema: ```graphql { schema(filename: "user-schema.json") { title description schemaVersion propertyCount } } ``` Get AST for a file: ```graphql { ast(filename: "document.md") { headingCount linkCount tree { type value level } } } ``` #### List Queries Get all markdown files with pagination: ```graphql { markdownFiles(limit: 10, offset: 0) { id filename createdAt hasFrontMatter } } ``` Filter files by front matter presence: ```graphql { markdownFiles(hasFrontMatter: true, limit: 5) { filename frontMatter { key value } } } ``` Get all schemas: ```graphql { schemas(limit: 20) { filename title description propertyCount } } ``` #### Search Queries Search across all content: ```graphql { search(query: "documentation", type: "all", limit: 10) { type score file { filename content } schema { title description } highlight } } ``` Search only in files: ```graphql { search(query: "API", type: "file", limit: 5) { score file { filename wordCount } highlight } } ``` #### Database Statistics Get database overview: ```graphql { databaseStats { totalFiles totalSchemas totalSizeBytes lastUpdated } } ``` #### Advanced AST Queries Query AST using JSONPath expressions: ```graphql { astQuery( filename: "document.md", jsonpath: "$.children[?(@.type=='heading')]" ) } ``` ## Mutation Operations The GraphQL interface supports full CRUD operations for markdown files and schemas through mutations. All mutations return structured payloads with success indicators and error handling. ### Mutation Types #### Markdown File Mutations ##### Add Markdown File Create a new markdown file in the database: ```graphql mutation { addMarkdownFile( filename: "new-document.md" content: "# New Document\n\nThis is a new markdown file." ) { success markdownFile { id filename content wordCount hasFrontMatter createdAt } errors } } ``` ##### Add Markdown File with Front Matter Create a file with YAML front matter: ```graphql mutation { addMarkdownFile( filename: "blog-post.md" content: """--- title: "My Blog Post" date: "2024-01-15" tags: ["tech", "api"] --- # My Blog Post Content goes here. """ ) { success markdownFile { id filename frontMatter { key value } hasFrontMatter } errors } } ``` ##### Update Markdown File Update an existing markdown file: ```graphql mutation { updateMarkdownFile( id: 1 content: "# Updated Document\n\nThis content has been updated." ) { success markdownFile { id filename content wordCount lineCount } errors } } ``` #### Schema Mutations ##### Add JSON Schema Create a new JSON schema: ```graphql mutation { addSchema( filename: "user-schema.json" schemaContent: { "type": "object", "title": "User Schema", "description": "Schema for user objects", "properties": { "name": {"type": "string"}, "email": {"type": "string", "format": "email"}, "age": {"type": "integer", "minimum": 0} }, "required": ["name", "email"] } ) { success schema { id filename title description propertyCount schemaVersion } errors } } ``` ##### Update JSON Schema Update an existing schema: ```graphql mutation { updateSchema( id: 1 schemaContent: { "type": "object", "title": "Updated User Schema", "description": "Updated schema for user objects", "properties": { "name": {"type": "string"}, "email": {"type": "string", "format": "email"}, "age": {"type": "integer", "minimum": 0}, "country": {"type": "string"} }, "required": ["name", "email"] } ) { success schema { id filename title propertyCount updatedAt } errors } } ``` ##### Delete Schema Remove a schema from the database: ```graphql mutation { deleteSchema(filename: "old-schema.json") { success deletedFilename errors } } ``` ### Mutation Payload Types All mutations return structured payload types with the following pattern: #### AddMarkdownFilePayload ```graphql type AddMarkdownFilePayload { success: Boolean! markdownFile: MarkdownFile errors: [String!]! } ``` #### UpdateMarkdownFilePayload ```graphql type UpdateMarkdownFilePayload { success: Boolean! markdownFile: MarkdownFile errors: [String!]! } ``` #### AddSchemaPayload ```graphql type AddSchemaPayload { success: Boolean! schema: Schema errors: [String!]! } ``` #### UpdateSchemaPayload ```graphql type UpdateSchemaPayload { success: Boolean! schema: Schema errors: [String!]! } ``` #### DeleteSchemaPayload ```graphql type DeleteSchemaPayload { success: Boolean! deletedFilename: String errors: [String!]! } ``` ### Error Handling in Mutations Mutations provide comprehensive error handling: #### Validation Errors Invalid input data returns structured errors: ```graphql mutation { addMarkdownFile(filename: "", content: "test") { success # false markdownFile # null errors # ["Filename cannot be empty"] } } ``` #### Duplicate Filename Handling Attempting to create files with existing names: ```graphql mutation { addMarkdownFile(filename: "existing.md", content: "test") { success # false errors # ["File with filename 'existing.md' already exists"] } } ``` #### Not Found Errors Updating non-existent resources: ```graphql mutation { updateMarkdownFile(id: 999, content: "test") { success # false errors # ["Markdown file with ID 999 not found"] } } ``` ### Complete CRUD Workflow Example Here's a complete example showing the full lifecycle of a markdown file: ```graphql # 1. Create a new file mutation CreateFile { addMarkdownFile( filename: "api-guide.md" content: """--- title: "API Usage Guide" version: "1.0" --- # API Usage Guide Basic API documentation. """ ) { success markdownFile { id filename hasFrontMatter } errors } } # 2. Query the created file { markdownFile(filename: "api-guide.md") { id content wordCount frontMatter { key value } } } # 3. Update the file content mutation UpdateFile { updateMarkdownFile( id: 1 content: """--- title: "API Usage Guide" version: "2.0" updated: "2024-01-15" --- # API Usage Guide Comprehensive API documentation with examples. ## Authentication ... """ ) { success markdownFile { id wordCount lineCount frontMatter { key value } } errors } } # 4. Verify the update { markdownFile(id: 1) { filename wordCount frontMatter { key value } } } ``` ## CLI Integration ### Available Commands #### graphql-serve Start the GraphQL server: ```bash markitect graphql-serve [OPTIONS] Options: --host TEXT Host to bind to (default: 127.0.0.1) --port INTEGER Port to bind to (default: 5000) --debug Enable debug mode --no-cors Disable CORS ``` #### graphql-query Execute GraphQL queries from the command line: ```bash markitect graphql-query QUERY [OPTIONS] Options: --variables TEXT JSON string of query variables --endpoint TEXT GraphQL endpoint URL --local Execute query locally without HTTP --format [json|yaml|table] Output format (default: json) ``` Examples: ```bash # Execute a simple query locally markitect graphql-query "{ databaseStats { totalFiles } }" --local # Execute query with variables markitect graphql-query "query($limit: Int) { markdownFiles(limit: $limit) { filename } }" --variables '{"limit": 5}' --local # Execute query against remote server markitect graphql-query "{ schemas { title } }" --endpoint http://localhost:5000/graphql ``` #### graphql-mutate Execute GraphQL mutations from the command line: ```bash markitect graphql-mutate MUTATION [OPTIONS] Options: --variables TEXT JSON string of mutation variables --endpoint TEXT GraphQL endpoint URL --local Execute mutation locally without HTTP --format [json|yaml|table] Output format (default: json) ``` Examples: ```bash # Add a new markdown file locally markitect graphql-mutate 'mutation { addMarkdownFile(filename: "test.md", content: "# Test") { success markdownFile { id filename } errors } }' --local # Add markdown file with variables markitect graphql-mutate 'mutation($filename: String!, $content: String!) { addMarkdownFile(filename: $filename, content: $content) { success errors } }' --variables '{"filename": "doc.md", "content": "# Documentation"}' --local # Update a file remotely markitect graphql-mutate 'mutation { updateMarkdownFile(id: 1, content: "# Updated") { success errors } }' --endpoint http://localhost:5000/graphql # Add a JSON schema markitect graphql-mutate 'mutation { addSchema(filename: "user.json", schemaContent: {"type": "object", "properties": {"name": {"type": "string"}}}) { success schema { id title } errors } }' --local # Delete a schema markitect graphql-mutate 'mutation { deleteSchema(filename: "old-schema.json") { success deletedFilename errors } }' --local ``` #### graphql-schema Get the GraphQL schema definition: ```bash markitect graphql-schema [OPTIONS] Options: --format [sdl|json] Schema format (default: sdl) --endpoint TEXT GraphQL endpoint URL ``` #### graphql-examples Show example queries and usage: ```bash markitect graphql-examples ``` ## Programming API ### Using the GraphQL Client ```python from markitect.graphql import GraphQLClient # Create a client client = GraphQLClient("http://localhost:5000/graphql") # Execute a query result = client.execute(""" { markdownFiles(limit: 5) { filename wordCount } } """) print(result['data']) ``` ### Local Execution ```python from markitect.graphql import GraphQLClient # Execute queries locally without HTTP client = GraphQLClient() result = client.execute_local(""" { databaseStats { totalFiles totalSchemas } } """) print(result['data']) ``` ### Executing Mutations ```python from markitect.graphql import GraphQLClient # Create a client client = GraphQLClient("http://localhost:5000/graphql") # Add a new markdown file create_mutation = """ mutation { addMarkdownFile( filename: "api-docs.md" content: "# API Documentation\\n\\nComprehensive API guide." ) { success markdownFile { id filename wordCount } errors } } """ result = client.execute(create_mutation) if result['data']['addMarkdownFile']['success']: file_id = result['data']['addMarkdownFile']['markdownFile']['id'] print(f"Created file with ID: {file_id}") else: print("Errors:", result['data']['addMarkdownFile']['errors']) # Update the file update_mutation = """ mutation UpdateFile($id: Int!, $content: String!) { updateMarkdownFile(id: $id, content: $content) { success markdownFile { wordCount lineCount } errors } } """ variables = { "id": file_id, "content": "# API Documentation\\n\\nUpdated comprehensive API guide with examples." } result = client.execute(update_mutation, variables=variables) print("Update result:", result['data']['updateMarkdownFile']['success']) ``` ### Local Mutation Execution ```python from markitect.graphql import GraphQLClient # Execute mutations locally without HTTP client = GraphQLClient() # Add a JSON schema locally schema_mutation = """ mutation { addSchema( filename: "product-schema.json" schemaContent: { "type": "object", "title": "Product Schema", "properties": { "name": {"type": "string"}, "price": {"type": "number", "minimum": 0}, "category": {"type": "string"} }, "required": ["name", "price"] } ) { success schema { id title propertyCount } errors } } """ result = client.execute_local(schema_mutation) if result['data']['addSchema']['success']: schema_info = result['data']['addSchema']['schema'] print(f"Created schema: {schema_info['title']} with {schema_info['propertyCount']} properties") ``` ### Using the Server Directly ```python from markitect.graphql import GraphQLServer # Create and start server server = GraphQLServer(db_path="/path/to/markitect.db") app = server.create_app() # Start server server.run(host="0.0.0.0", port=8080, debug=True) ``` ## API Endpoints When running the GraphQL server, the following endpoints are available: - `POST /graphql` - Main GraphQL endpoint for queries - `GET /graphql` - GraphQL Playground interface - `GET /schema` - Schema introspection - `GET /health` - Health check endpoint ### Health Check Response ```json { "status": "healthy", "database": "connected", "database_path": "/path/to/markitect.db" } ``` ## Error Handling The GraphQL interface provides comprehensive error handling: ### Query Errors Invalid GraphQL syntax or non-existent fields return structured errors: ```json { "data": null, "errors": [ { "message": "Cannot query field 'nonexistentField' on type 'Query'" } ] } ``` ### Database Errors Database connectivity issues are handled gracefully: ```json { "data": { "databaseStats": null }, "errors": [ { "message": "Database connection error" } ] } ``` ## Performance Considerations ### Pagination Always use pagination for large datasets: ```graphql { markdownFiles(limit: 50, offset: 0) { id filename } } ``` ### Field Selection Only query the fields you need: ```graphql # Good - selective fields { markdownFiles { id filename } } # Avoid - requesting large content fields unnecessarily { markdownFiles { id filename content # Only include if needed } } ``` ### Search Optimization Use specific search types when possible: ```graphql # Better - search only files { search(query: "API", type: "file", limit: 10) { file { filename } } } # Less efficient - search all types { search(query: "API", type: "all", limit: 10) { type file { filename } schema { title } } } ``` ## Security Considerations ### Write Operations Security The GraphQL interface supports both read and write operations through mutations. Write operations require careful consideration: - **Data Validation**: All mutations include comprehensive input validation and structured error responses - **Transaction Safety**: Database operations are wrapped in transactions to ensure data consistency - **File Overwrites**: Duplicate filenames in the same mutation will overwrite existing files - **Schema Validation**: JSON schemas are validated before storage to ensure they are well-formed - **Error Handling**: Failed operations return detailed error messages without exposing internal system details ### CORS Configuration CORS is enabled by default for browser access. Disable with `--no-cors` if needed: ```bash markitect graphql-serve --no-cors ``` ### Database Access The interface uses the same database path configuration as the main MarkiTect CLI, respecting the `MARKITECT_DB` environment variable. ## Troubleshooting ### Common Issues #### "Flask is required" Error Install Flask dependencies: ```bash pip install flask flask-cors ``` #### "requests is required" Error Install requests for HTTP client functionality: ```bash pip install requests ``` #### Database Connection Errors Check that your MarkiTect database exists and is accessible: ```bash # Check database location echo $MARKITECT_DB # Verify database exists ls -la ~/.markitect/markitect.db ``` #### GraphQL Syntax Errors Use the GraphQL Playground at `http://localhost:5000/graphql` to validate your queries with real-time syntax checking and auto-completion. ## Examples ### Complete Data Export Export all markdown files with metadata: ```graphql { markdownFiles { id filename content createdAt wordCount lineCount hasFrontMatter frontMatter { key value } } } ``` ### Schema Analysis Analyze all schemas in the database: ```graphql { schemas { filename title description schemaVersion propertyCount schemaContent } } ``` ### Content Discovery Find all documents containing specific terms: ```graphql { search(query: "authentication security", type: "all", limit: 20) { type score file { filename wordCount hasFrontMatter } schema { title description } highlight } } ``` ### Database Overview Get a complete overview of your MarkiTect database: ```graphql { databaseStats { totalFiles totalSchemas totalSizeBytes lastUpdated } recentFiles: markdownFiles(limit: 5) { filename createdAt } recentSchemas: schemas(limit: 5) { filename title createdAt } } ``` ## Integration Examples ### With JavaScript/TypeScript ```typescript interface DatabaseStats { totalFiles: number; totalSchemas: number; totalSizeBytes: number; lastUpdated: string; } async function getStats(): Promise { const response = await fetch('http://localhost:5000/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: ` { databaseStats { totalFiles totalSchemas totalSizeBytes lastUpdated } } ` }) }); const { data } = await response.json(); return data.databaseStats; } ``` ### With Python ```python import requests from typing import List, Dict, Any def search_content(query: str, limit: int = 10) -> List[Dict[str, Any]]: """Search for content in MarkiTect database.""" response = requests.post('http://localhost:5000/graphql', json={ 'query': ''' query SearchContent($query: String!, $limit: Int!) { search(query: $query, type: "all", limit: $limit) { type score file { filename wordCount } schema { title description } highlight } } ''', 'variables': { 'query': query, 'limit': limit } }) return response.json()['data']['search'] # Usage results = search_content("API documentation", limit=5) for result in results: print(f"Found {result['type']}: {result['highlight']}") ``` This GraphQL interface provides a powerful, flexible way to access and query MarkiTect data, enabling rich integrations and applications built on top of your Markdown content management system.