feat(spaces): implement Phase 6 API Layer
Implements API layer for Information Spaces: - GraphQL schema types for spaces, documents, variables - GraphQL queries and mutations for space operations - CLI command group with all space management commands - Resolver functions connecting GraphQL to SpaceService - 38 unit tests for API components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
397
markitect/graphql/space_schema.py
Normal file
397
markitect/graphql/space_schema.py
Normal file
@@ -0,0 +1,397 @@
|
||||
"""
|
||||
GraphQL schema extension for Information Spaces.
|
||||
|
||||
Defines GraphQL types, queries, and mutations for the space system.
|
||||
"""
|
||||
|
||||
import graphene
|
||||
from graphene import ObjectType, String, Int, DateTime, List, Field, JSONString, Enum
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# Enums
|
||||
class SpaceStatusEnum(Enum):
|
||||
"""GraphQL enum for space status."""
|
||||
DRAFT = "draft"
|
||||
ACTIVE = "active"
|
||||
ARCHIVED = "archived"
|
||||
DELETED = "deleted"
|
||||
|
||||
|
||||
class SyncDirectionEnum(Enum):
|
||||
"""GraphQL enum for sync direction."""
|
||||
SPACE_TO_DIRECTORY = "space_to_directory"
|
||||
DIRECTORY_TO_SPACE = "directory_to_space"
|
||||
BIDIRECTIONAL = "bidirectional"
|
||||
|
||||
|
||||
class ConflictResolutionEnum(Enum):
|
||||
"""GraphQL enum for conflict resolution strategy."""
|
||||
SPACE_WINS = "space_wins"
|
||||
DIRECTORY_WINS = "directory_wins"
|
||||
NEWER_WINS = "newer_wins"
|
||||
SKIP = "skip"
|
||||
|
||||
|
||||
# Types
|
||||
class SpaceConfigType(ObjectType):
|
||||
"""GraphQL type for space configuration."""
|
||||
default_variant = String(description="Default export variant")
|
||||
enable_caching = graphene.Boolean(description="Whether caching is enabled")
|
||||
theme = String(description="Theme for rendering")
|
||||
history_enabled = graphene.Boolean(description="Whether history tracking is enabled")
|
||||
variable_scope = String(description="Default variable scope")
|
||||
|
||||
|
||||
class SpaceMetadataType(ObjectType):
|
||||
"""GraphQL type for space metadata."""
|
||||
tags = List(String, description="Tags for categorization")
|
||||
author = String(description="Author identifier")
|
||||
custom = JSONString(description="Custom metadata fields")
|
||||
|
||||
|
||||
class InformationSpaceType(ObjectType):
|
||||
"""GraphQL type for Information Spaces."""
|
||||
id = String(required=True, description="Unique space identifier")
|
||||
name = String(required=True, description="Space name")
|
||||
description = String(description="Space description")
|
||||
status = String(description="Space lifecycle status")
|
||||
config = Field(SpaceConfigType, description="Space configuration")
|
||||
metadata = Field(SpaceMetadataType, description="Space metadata")
|
||||
parent_space_id = String(description="Parent space ID for inheritance")
|
||||
created_at = DateTime(description="Creation timestamp")
|
||||
updated_at = DateTime(description="Last update timestamp")
|
||||
|
||||
# Computed fields
|
||||
document_count = Int(description="Number of documents in space")
|
||||
variable_count = Int(description="Number of variables defined")
|
||||
|
||||
|
||||
class SpaceDocumentType(ObjectType):
|
||||
"""GraphQL type for documents in a space."""
|
||||
id = String(required=True, description="Document association ID")
|
||||
space_id = String(required=True, description="Parent space ID")
|
||||
document_id = String(required=True, description="Document identifier")
|
||||
space_path = String(required=True, description="Path within space")
|
||||
order_index = Int(description="Order index for sorting")
|
||||
metadata = JSONString(description="Document-specific metadata")
|
||||
added_at = DateTime(description="When document was added to space")
|
||||
|
||||
|
||||
class SpaceVariableType(ObjectType):
|
||||
"""GraphQL type for space variables."""
|
||||
space_id = String(required=True, description="Parent space ID")
|
||||
name = String(required=True, description="Variable name")
|
||||
value = JSONString(description="Variable value")
|
||||
scope = String(description="Variable scope (space, document, request)")
|
||||
|
||||
|
||||
class RenderResultType(ObjectType):
|
||||
"""GraphQL type for render results."""
|
||||
content = String(required=True, description="Rendered content")
|
||||
format = String(required=True, description="Output format")
|
||||
content_hash = String(description="Hash of rendered content")
|
||||
source_hash = String(description="Hash of source content")
|
||||
document_id = String(description="Rendered document ID")
|
||||
cached = graphene.Boolean(description="Whether result was from cache")
|
||||
|
||||
|
||||
class SyncResultType(ObjectType):
|
||||
"""GraphQL type for sync results."""
|
||||
success = graphene.Boolean(required=True, description="Whether sync succeeded")
|
||||
created_count = Int(description="Number of items created")
|
||||
updated_count = Int(description="Number of items updated")
|
||||
deleted_count = Int(description="Number of items deleted")
|
||||
conflict_count = Int(description="Number of conflicts encountered")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class SyncConflictType(ObjectType):
|
||||
"""GraphQL type for sync conflicts."""
|
||||
path = String(required=True, description="Conflicting path")
|
||||
space_hash = String(description="Content hash in space")
|
||||
directory_hash = String(description="Content hash in directory")
|
||||
resolution = String(description="How conflict was resolved")
|
||||
winner = String(description="Which side won")
|
||||
|
||||
|
||||
# Input Types
|
||||
class SpaceConfigInput(graphene.InputObjectType):
|
||||
"""Input type for space configuration."""
|
||||
default_variant = String()
|
||||
enable_caching = graphene.Boolean()
|
||||
theme = String()
|
||||
history_enabled = graphene.Boolean()
|
||||
variable_scope = String()
|
||||
|
||||
|
||||
class SpaceMetadataInput(graphene.InputObjectType):
|
||||
"""Input type for space metadata."""
|
||||
tags = List(String)
|
||||
author = String()
|
||||
custom = JSONString()
|
||||
|
||||
|
||||
class CreateSpaceInput(graphene.InputObjectType):
|
||||
"""Input for creating a space."""
|
||||
name = String(required=True)
|
||||
description = String()
|
||||
config = graphene.Argument(SpaceConfigInput)
|
||||
metadata = graphene.Argument(SpaceMetadataInput)
|
||||
parent_space_id = String()
|
||||
|
||||
|
||||
class UpdateSpaceInput(graphene.InputObjectType):
|
||||
"""Input for updating a space."""
|
||||
name = String()
|
||||
description = String()
|
||||
config = graphene.Argument(SpaceConfigInput)
|
||||
metadata = graphene.Argument(SpaceMetadataInput)
|
||||
|
||||
|
||||
class AddDocumentInput(graphene.InputObjectType):
|
||||
"""Input for adding a document to a space."""
|
||||
document_id = String(required=True)
|
||||
space_path = String(required=True)
|
||||
order_index = Int()
|
||||
metadata = JSONString()
|
||||
|
||||
|
||||
class RenderOptionsInput(graphene.InputObjectType):
|
||||
"""Input for render options."""
|
||||
theme = String()
|
||||
include_toc = graphene.Boolean()
|
||||
force_refresh = graphene.Boolean()
|
||||
|
||||
|
||||
class SyncOptionsInput(graphene.InputObjectType):
|
||||
"""Input for sync options."""
|
||||
direction = String()
|
||||
conflict_resolution = String()
|
||||
dry_run = graphene.Boolean()
|
||||
|
||||
|
||||
# Payload Types
|
||||
class CreateSpacePayload(ObjectType):
|
||||
"""Payload for createSpace mutation."""
|
||||
space = Field(InformationSpaceType, description="The created space")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class UpdateSpacePayload(ObjectType):
|
||||
"""Payload for updateSpace mutation."""
|
||||
space = Field(InformationSpaceType, description="The updated space")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class DeleteSpacePayload(ObjectType):
|
||||
"""Payload for deleteSpace mutation."""
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
deleted_id = String(description="ID of deleted space")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class AddDocumentPayload(ObjectType):
|
||||
"""Payload for addDocument mutation."""
|
||||
document = Field(SpaceDocumentType, description="The added document")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class RemoveDocumentPayload(ObjectType):
|
||||
"""Payload for removeDocument mutation."""
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
removed_path = String(description="Path of removed document")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class SetVariablePayload(ObjectType):
|
||||
"""Payload for setVariable mutation."""
|
||||
variable = Field(SpaceVariableType, description="The set variable")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class RenderDocumentPayload(ObjectType):
|
||||
"""Payload for renderDocument mutation."""
|
||||
result = Field(RenderResultType, description="Render result")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
class SyncSpacePayload(ObjectType):
|
||||
"""Payload for syncSpace mutation."""
|
||||
result = Field(SyncResultType, description="Sync result")
|
||||
conflicts = List(SyncConflictType, description="Conflicts encountered")
|
||||
success = graphene.Boolean(description="Whether operation succeeded")
|
||||
errors = List(String, description="Error messages")
|
||||
|
||||
|
||||
# Query Extension
|
||||
class SpaceQuery(ObjectType):
|
||||
"""Query extension for spaces."""
|
||||
|
||||
# Single space queries
|
||||
space = Field(
|
||||
InformationSpaceType,
|
||||
id=String(description="Space ID"),
|
||||
name=String(description="Space name"),
|
||||
description="Get a specific space"
|
||||
)
|
||||
|
||||
# List queries
|
||||
spaces = List(
|
||||
InformationSpaceType,
|
||||
status=String(description="Filter by status"),
|
||||
limit=Int(default_value=50, description="Maximum results"),
|
||||
offset=Int(default_value=0, description="Pagination offset"),
|
||||
description="List all spaces"
|
||||
)
|
||||
|
||||
# Document queries
|
||||
space_documents = List(
|
||||
SpaceDocumentType,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
description="List documents in a space"
|
||||
)
|
||||
|
||||
space_document = Field(
|
||||
SpaceDocumentType,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
space_path=String(required=True, description="Document path"),
|
||||
description="Get a specific document in a space"
|
||||
)
|
||||
|
||||
# Variable queries
|
||||
space_variables = List(
|
||||
SpaceVariableType,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
scope=String(description="Filter by scope"),
|
||||
description="List variables in a space"
|
||||
)
|
||||
|
||||
space_variable = Field(
|
||||
SpaceVariableType,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
name=String(required=True, description="Variable name"),
|
||||
description="Get a specific variable"
|
||||
)
|
||||
|
||||
|
||||
# Mutation Extension
|
||||
class SpaceMutation(ObjectType):
|
||||
"""Mutation extension for spaces."""
|
||||
|
||||
# Space lifecycle
|
||||
create_space = Field(
|
||||
CreateSpacePayload,
|
||||
input=graphene.Argument(CreateSpaceInput, required=True),
|
||||
description="Create a new space"
|
||||
)
|
||||
|
||||
update_space = Field(
|
||||
UpdateSpacePayload,
|
||||
id=String(required=True, description="Space ID"),
|
||||
input=graphene.Argument(UpdateSpaceInput, required=True),
|
||||
description="Update a space"
|
||||
)
|
||||
|
||||
delete_space = Field(
|
||||
DeleteSpacePayload,
|
||||
id=String(required=True, description="Space ID"),
|
||||
description="Delete a space"
|
||||
)
|
||||
|
||||
activate_space = Field(
|
||||
UpdateSpacePayload,
|
||||
id=String(required=True, description="Space ID"),
|
||||
description="Activate a draft space"
|
||||
)
|
||||
|
||||
archive_space = Field(
|
||||
UpdateSpacePayload,
|
||||
id=String(required=True, description="Space ID"),
|
||||
description="Archive a space"
|
||||
)
|
||||
|
||||
# Document operations
|
||||
add_document = Field(
|
||||
AddDocumentPayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
input=graphene.Argument(AddDocumentInput, required=True),
|
||||
description="Add a document to a space"
|
||||
)
|
||||
|
||||
remove_document = Field(
|
||||
RemoveDocumentPayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
space_path=String(required=True, description="Document path"),
|
||||
description="Remove a document from a space"
|
||||
)
|
||||
|
||||
# Variable operations
|
||||
set_variable = Field(
|
||||
SetVariablePayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
name=String(required=True, description="Variable name"),
|
||||
value=JSONString(required=True, description="Variable value"),
|
||||
scope=String(description="Variable scope"),
|
||||
description="Set a variable in a space"
|
||||
)
|
||||
|
||||
delete_variable = Field(
|
||||
SetVariablePayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
name=String(required=True, description="Variable name"),
|
||||
description="Delete a variable from a space"
|
||||
)
|
||||
|
||||
# Rendering
|
||||
render_document = Field(
|
||||
RenderDocumentPayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
document_path=String(required=True, description="Document path"),
|
||||
options=graphene.Argument(RenderOptionsInput),
|
||||
description="Render a document to HTML"
|
||||
)
|
||||
|
||||
render_space = Field(
|
||||
RenderDocumentPayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
options=graphene.Argument(RenderOptionsInput),
|
||||
description="Render all documents in a space"
|
||||
)
|
||||
|
||||
# Sync operations
|
||||
sync_space = Field(
|
||||
SyncSpacePayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
directory=String(required=True, description="Directory path"),
|
||||
options=graphene.Argument(SyncOptionsInput),
|
||||
description="Sync space with directory"
|
||||
)
|
||||
|
||||
export_space = Field(
|
||||
SyncSpacePayload,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
directory=String(required=True, description="Export directory"),
|
||||
description="Export space to directory"
|
||||
)
|
||||
|
||||
import_directory = Field(
|
||||
SyncSpacePayload,
|
||||
space_id=String(required=True, description="Target space ID"),
|
||||
directory=String(required=True, description="Directory to import"),
|
||||
description="Import directory into space"
|
||||
)
|
||||
|
||||
# Cache operations
|
||||
invalidate_cache = Field(
|
||||
graphene.Boolean,
|
||||
space_id=String(required=True, description="Space ID"),
|
||||
document_path=String(description="Optional specific document"),
|
||||
description="Invalidate render cache"
|
||||
)
|
||||
Reference in New Issue
Block a user