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 schema
  • graphql_notification_reader – streams real-time updates
  • graphql_entity_mutation_service – handles entity writes

For each request, graphql_query_context_data and graphql_subscription_context_data inject:

  • macro_user_id and organization_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 cache
  • graphql_cache_read(key: String) – Retrieve cached entity
  • graphql_cache_write(key: String, data: String) – Store normalized result
  • graphql_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

  1. Schema extraction – The custom cargo command exports the Rust-defined schema to a static asset
  2. Type generation – GraphQL Code Generator produces TypeScript types from schema.graphql
  3. Request flow – Urql operations hit Axum endpoints with session-authenticated context
  4. Resolution – Resolvers use injected services to read/write entities and broadcast notifications
  5. 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →