feat: implement GraphQL write interface with mutations (issue #10)
Some checks failed
Test Suite / performance-tests (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled

Added comprehensive GraphQL mutations for CRUD operations on markdown files and schemas.

Key features:
- Complete mutation schema with structured payload types
- Markdown file mutations: add, update with front matter support
- Schema mutations: add, update, delete with JSON validation
- CLI integration with `graphql-mutate` command
- Comprehensive error handling and validation
- Full test coverage with 24 test cases
- Updated documentation with mutation examples and API usage

Resolves issue #10: Expose a GraphQL Write Interface

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-03 16:48:03 +02:00
parent d4e5992213
commit 2a15dde228
6 changed files with 2084 additions and 8 deletions

View File

@@ -1,8 +1,8 @@
# GraphQL Read Interface for MarkiTect
# GraphQL Interface for MarkiTect
## Overview
The GraphQL read interface provides a powerful, type-safe way to query 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.
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
@@ -13,6 +13,8 @@ The GraphQL read interface provides a powerful, type-safe way to query MarkiTect
- **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
@@ -265,6 +267,350 @@ Query AST using JSONPath expressions:
}
```
## 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
@@ -305,6 +651,36 @@ markitect graphql-query "query($limit: Int) { markdownFiles(limit: $limit) { fil
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
@@ -363,6 +739,103 @@ result = client.execute_local("""
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
@@ -490,8 +963,14 @@ Use specific search types when possible:
## Security Considerations
### Read-Only Interface
The GraphQL interface is read-only and does not support mutations, providing safe access to MarkiTect data.
### 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: