How the GraphQL API Layer Is Structured for the Web Client in Macro
The Macro repository implements a three-layer GraphQL architecture: a server-side async-GraphQL engine in the Document Storage Service, a shared schema/resolver crate named "Soup," and a Tauri-based web client with a normalized SQLite cache and WebSocket subscriptions.
The Macro platform's web client consumes its GraphQL API through the Document Storage Service (document_storage_service). This service exposes the "Soup" GraphQL layer—a unified interface for documents, projects, teams, and other entities. The web client, built with Tauri, leverages a normalized cache plugin for performance and real-time synchronization via WebSocket subscriptions.
Server-Side GraphQL Engine
The server layer routes HTTP requests and WebSocket connections into the async-GraphQL runtime. It also constructs request-scoped context containing user identity and shared mutation services.
Axum Route Registration
In services/document_storage_service/src/api/graphql_soup.rs, the service registers three endpoints on Axum:
GET /soup/graphql→ GraphiQL IDE (development only)POST /soup/graphql→ Query/mutation handler (graphql_handler)GET /soup/graphql/ws→ Subscription handler (graphql_ws_handler)
These routes merge into the main API router through services/document_storage_service/src/api/mod.rs.
Request Context Injection
The AppState struct in services/document_storage_service/src/api/context.rs holds three critical components:
graphql_soup_schema– the compiled async-GraphQL schemagraphql_notification_reader– streams real-time updatesgraphql_entity_mutation_service– handles entity writes
For each request, graphql_query_context_data and graphql_subscription_context_data inject:
macro_user_idandorganization_id(from session)- Shared service handles (mutation service, notification reader)
Resolvers access these via ctx.data::<T>() without explicit parameters.
GraphQL Schema and Resolvers
The Soup schema lives in the graphql_soup crate, separating type definitions from resolver implementations.
Object Types
crates/graphql_soup/src/objects.rs defines GraphQL object types including GraphqlSoupEntity—a polymorphic wrapper returned by the entity(id: ID!) query. Other types cover documents, projects, teams, permissions, and organizational structures.
Resolver Implementation
crates/graphql_soup/src/resolvers.rs implements three operation categories:
| Operation Type | Example | Purpose |
|---|---|---|
| Queries | entity(id: ID!) |
Fetch single polymorphic entity by global ID |
| Mutations | entityMutation |
Bundle all entity modifications (rename, move, trash, restore) |
| Subscriptions | soupUpdates, notificationUpdates |
Stream real-time changes to connected clients |
All resolvers are async and access injected services through the request context.
Client-Side Consumption
The web client in apps/web combines Urql, a Tauri-hosted cache plugin, and generated TypeScript types.
Urql Client Configuration
apps/web/src/lib/service-clients/service-storage/graphql-soup.ts creates the client:
import { createClient, cacheExchange, fetchExchange } from '@urql/core';
import { normalizedCacheExchange } from '@urql/exchange-graphcache';
export const soupClient = createClient({
url: '/soup/graphql',
exchanges: [
normalizedCacheExchange({
// Type policies for polymorphic Soup entities
updates: {
Mutation: {
entityMutation: (result, args, cache, info) => {
// Optimistic update handling
}
}
}
}),
fetchExchange
],
fetchOptions: () => ({
credentials: 'include' // Session cookie propagation
})
});
The WebSocket subscription endpoint connects separately:
// Subscription client for real-time updates
const wsClient = createWSClient({
url: `wss://${host}/soup/graphql/ws`
});
Tauri Cache Plugin
apps/web/tauri/graphql_cache_plugin/src/lib.rs exposes host commands to the JavaScript runtime:
graphql_cache_init()– Initialize SQLite-backed normalized cachegraphql_cache_read(key: String)– Retrieve cached entitygraphql_cache_write(key: String, data: String)– Store normalized resultgraphql_cache_enqueue_optimistic_mutation(op: String)– Queue optimistic update
These commands enable the normalizedCacheExchange to persist across sessions and survive process restarts.
Type Generation Pipeline
The build process maintains type safety through schema extraction:
# Run from repository root
cargo x graphql-soup-schema
This writes to apps/web/static_assets/schema.graphql, which feeds the TypeScript generator. Output lands in apps/web/src/lib/service-clients/service-storage/graphql/generated/graphql.ts, providing fully typed operations.
How the Layers Connect
- Schema extraction – The custom cargo command exports the Rust-defined schema to a static asset
- Type generation – GraphQL Code Generator produces TypeScript types from
schema.graphql - Request flow – Urql operations hit Axum endpoints with session-authenticated context
- Resolution – Resolvers use injected services to read/write entities and broadcast notifications
- Response handling – Results normalize into the SQLite cache, trigger optimistic updates, and stream via WebSocket subscriptions
Key Files Reference
| Component | File Path |
|---|---|
| Router integration | services/document_storage_service/src/api/mod.rs |
| GraphQL endpoints | services/document_storage_service/src/api/graphql_soup.rs |
| Request context | services/document_storage_service/src/api/context.rs |
| Schema objects | crates/graphql_soup/src/objects.rs |
| Resolvers | crates/graphql_soup/src/resolvers.rs |
| Static schema | apps/web/static_assets/schema.graphql |
| Urql client | apps/web/src/lib/service-clients/service-storage/graphql-soup.ts |
| Tauri cache plugin | apps/web/tauri/graphql_cache_plugin/src/lib.rs |
| Generated types | apps/web/src/lib/service-clients/service-storage/graphql/generated/graphql.ts |
Summary
- The GraphQL API layer spans a Rust async-GraphQL server and a Tauri-hosted web client with normalized caching
- Soup provides a unified polymorphic API for all entity types through queries, mutations, and subscriptions
- Request context injection eliminates explicit service parameters while maintaining type safety
- WebSocket subscriptions enable real-time collaboration with server-sent notifications
- SQLite-backed normalization persists cache state across client restarts and supports optimistic UI patterns
Frequently Asked Questions
What is "Soup" in Macro's GraphQL API?
Soup is the internal name for Macro's unified GraphQL schema layer that exposes all entity types—documents, projects, teams, and more—through a polymorphic GraphqlSoupEntity type. Located in the crates/graphql_soup crate, it provides a single entry point for queries, mutations, and subscriptions rather than separate domain-specific APIs.
How does the web client maintain cache consistency?
The web client uses a normalized cache implemented as a Tauri plugin with SQLite storage. The normalizedCacheExchange from Urql ensures that updates to shared entities propagate across all active queries. Optimistic mutations enqueue immediately while awaiting server confirmation, and subscription events (soupUpdates, notificationUpdates) invalidate or refresh cached data in real time.
Why does Macro use WebSocket subscriptions over HTTP polling?
WebSocket subscriptions at /soup/graphql/ws provide push-based real-time updates essential for collaborative document editing. Compared to polling, this reduces latency, server load, and notification delay. The subscription endpoint shares the same context injection and resolver infrastructure as standard queries, ensuring consistent authorization and data access patterns.
How are TypeScript types kept synchronized with the Rust schema?
A custom build tool (cargo x graphql-soup-schema) extracts the complete GraphQL schema from the running Rust code and writes it to apps/web/static_assets/schema.graphql. The web client's build pipeline runs GraphQL Code Generator against this file to produce generated/graphql.ts. This guarantees that TypeScript operations remain type-safe against the actual server implementation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →