Skip to main content
The GraphQL API supports normalized package caching to reduce payload sizes and enable efficient client-side caching.

How It Works

Package data is identified by content-addressed SHA-256 hashes. This means:
  • The same package content always has the same ID
  • Multiple content items can share the same package data
  • Packages can be cached indefinitely (they’re immutable)
  • Subscription payloads stay under AWS 128KB WebSocket limit

Two Resolution Approaches

Full Resolution (resolved)

Use resolved when you need all package data in a single request:
query GetContentResolved($id: ID!) {
  content(id: $id) {
    id
    name
    compiled
    resolved {
      package {
        id
        version
        components
        assets
      }
      dependencies {
        id
        version
        components
        assets
      }
    }
  }
}

Reference Resolution (resolvedRef)

Use resolvedRef for optimal caching and smaller payloads:
query GetContentWithCaching($id: ID!) {
  content(id: $id) {
    id
    name
    compiled
    resolvedRef {
      package
      dependencies
    }
  }
}
Response:
{
  "data": {
    "content": {
      "id": "cont123",
      "name": "Getting Started",
      "compiled": "const body = () => { ... }",
      "resolvedRef": {
        "package": "abc123def456...",
        "dependencies": ["xyz789ghi012..."]
      }
    }
  }
}

Fetching Package Data

Query package data by ID (only if not cached):
query GetPackageData($id: ID!) {
  resolvedPackageData(id: $id) {
    id
    version
    components
    assets
  }
}
Response:
{
  "data": {
    "resolvedPackageData": {
      "id": "abc123def456...",
      "version": "1.0.0",
      "components": "{\"ArticleLayout\":\"const body = ...\"}",
      "assets": "{\"ArticleLayout\":[{\"name\":\"hero\",\"url\":\"...\"}]}"
    }
  }
}

Client Implementation

Caching Strategy

// Package cache (content-addressed, so safe to cache forever)
const packageCache = new Map();

async function getPackageData(id) {
  // Check cache first
  if (packageCache.has(id)) {
    return packageCache.get(id);
  }

  // Fetch from API
  const { data } = await client.query({
    query: GET_PACKAGE_DATA,
    variables: { id }
  });

  // Cache the result
  packageCache.set(id, data.resolvedPackageData);
  return data.resolvedPackageData;
}

async function fetchMissingPackages(ids) {
  const missing = ids.filter(id => !packageCache.has(id));

  // Batch fetch missing packages
  const results = await Promise.all(
    missing.map(id => getPackageData(id))
  );

  return results;
}

Handling Content with Caching

async function loadContent(id) {
  // Fetch content with references only
  const { data } = await client.query({
    query: GET_CONTENT_WITH_CACHING,
    variables: { id }
  });

  const content = data.content;

  // Fetch any missing package data
  const packageIds = [
    content.resolvedRef.package,
    ...content.resolvedRef.dependencies
  ];

  await fetchMissingPackages(packageIds);

  // Now render with cached packages
  renderContent(content, packageCache);
}

Subscription Handler with Caching

client.subscribe({
  query: CONTENT_UPDATED_SUBSCRIPTION,
  variables: { id: 'cont123' }
}, {
  next: async (data) => {
    const update = data.data.contentUpdated;

    // Fetch any missing packages
    await fetchMissingPackages([
      update.resolvedRef.package,
      ...update.resolvedRef.dependencies
    ]);

    // Re-render with updated content and cached packages
    if (update.content) {
      renderContent(update.content, packageCache);
    } else {
      // Content was too large - fetch separately
      const content = await fetchContent(update.contentId);
      renderContent(content, packageCache);
    }
  }
});

Draft Package Handling

Draft packages use a special ID format: draft:{projectId}:{organizationId}
function isDraftPackage(id) {
  return id.startsWith('draft:');
}

async function getPackageData(id) {
  // Draft packages should not be cached long-term
  if (isDraftPackage(id)) {
    // Always fetch fresh or use short TTL
    return await fetchPackageData(id);
  }

  // Published packages can be cached forever
  if (packageCache.has(id)) {
    return packageCache.get(id);
  }

  const data = await fetchPackageData(id);
  packageCache.set(id, data);
  return data;
}

Benefits

  1. Smaller Payloads: Subscription updates only include IDs, not full package data
  2. Efficient Caching: Content-addressed IDs mean packages never change
  3. Shared Data: Multiple content items referencing the same package share cached data
  4. WebSocket Compliance: Payloads stay under AWS 128KB limit
  5. Reduced Bandwidth: Only fetch packages once, regardless of how many content items use them