> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Normalized Caching

> Efficient package caching with content-addressed IDs

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 100KB WebSocket limit

## Two Resolution Approaches

### Full Resolution (resolvedPackage)

Use `resolvedPackage` when you need all package data in a single request:

```graphql theme={null}
query GetContentResolved($id: ID!) {
  content(id: $id) {
    id
    name
    compiled
    resolvedPackage {
      package {
        id
        version
        components
        assets
      }
      dependencies {
        id
        version
        components
        assets
      }
    }
  }
}
```

### Reference Resolution (resolvedRef)

Use `resolvedRef` for optimal caching and smaller payloads:

```graphql theme={null}
query GetContentWithCaching($id: ID!) {
  content(id: $id) {
    id
    name
    compiled
    resolvedRef {
      package
      dependencies
    }
  }
}
```

Response:

```json theme={null}
{
  "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):

```graphql theme={null}
query GetPackageData($packageId: ID!) {
  resolvedPackageData(packageId: $packageId) {
    id
    version
    components
    assets
    cdnUrl
  }
}
```

Response:

```json theme={null}
{
  "data": {
    "resolvedPackageData": {
      "id": "abc123def456...",
      "version": "1.0.0",
      "components": "{\"ArticleLayout\":\"const body = ...\"}",
      "assets": "{\"ArticleLayout\":[{\"name\":\"hero\",\"url\":\"...\"}]}",
      "cdnUrl": "https://cdn.metabind.ai/packages/abc123def456..."
    }
  }
}
```

## Client Implementation

### Caching Strategy

```javascript theme={null}
// 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: { packageId: 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

```javascript theme={null}
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

```javascript theme={null}
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}`

```javascript theme={null}
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 100KB limit
5. **Reduced Bandwidth**: Only fetch packages once, regardless of how many content items use them
