Skip to content

Shopify Integration

Per ADR-002, the rewrite adopts a GraphQL-first stance. Shopify’s REST Admin API is designated as “legacy” since October 2024.

Shopify Admin API is an external EngineeredSystem (IOF) — the primary source of commercial transaction Process Records and MaterialArtifact data.

Two token strategies (same as current system, but consolidated into one client):

MethodLifetimeUse case
Permanent offline token (preferred)No expirationAll production usage
Client Credentials Grant24 hours, cachedFallback if permanent token unavailable

The token is stored as a Wrangler secret (SHOPIFY_ACCESS_TOKEN), injected via Worker bindings.

The primary data ingestion primitive for the daily sync ProcedureExecution:

mutation {
bulkOperationRunQuery(
query: """
{
orders(query: "updated_at:>'2026-02-01'") {
edges {
node {
id
name
createdAt
updatedAt
totalPriceSet { shopMoney { amount currencyCode } }
tags
lineItems {
edges {
node {
id
product { id }
title
quantity
}
}
}
}
}
}
}
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}
ConstraintDetail
Concurrency1 bulk query per shop (API version < 2026-01); 5 per shop (>= 2026-01)
NestingMax 2 levels of connections
Result formatJSONL file, download via URL — stored as IBE in R2
Result retention7 days after completion
PollingRequired — no webhook notification for completion

Shopify uses a cost-based leaky bucket for GraphQL:

  • 50 points restored per second
  • Bucket capacity: ~1,000 points
  • Bulk operations have their own concurrency limits separate from point-based limits

Design implication: Interactive dashboard routes never call Shopify directly. All data comes from PlanetScale Measurement Data, refreshed by background ProcedureExecutions.

Replaces the 6 duplicated implementations. The client acts as a boundary between the external EngineeredSystem and BluntDashboard’s internal ontological domain:

interface ShopifyClient {
// Bulk operations (ProcedureExecution Steps)
bulkQuery(query: string): Promise<{ operationId: string }>
pollBulkOperation(id: string): Promise<BulkOpStatus>
downloadBulkResults(url: string): AsyncIterable<Record<string, unknown>>
// Single-entity operations (MaterialArtifact queries)
getProducts(params?: { sinceId?: string }): AsyncIterable<ShopifyProduct>
updateMetafield(ownerId: string, namespace: string, key: string, value: string): Promise<void>
// Utilities
withRateLimit<T>(fn: () => Promise<T>): Promise<T>
}

This client handles:

  • Token management (permanent preferred, CCG fallback with caching)
  • Rate limit tracking and backoff
  • Retry with exponential delay (using queue delay semantics for background ProcedureExecutions)
  • Structured logging for all API calls

REST is retained only behind the shared client for endpoints without GraphQL equivalents. Each REST call is tracked with metrics and flagged for eventual migration.