Skip to main content

Storage Backends

Every service in the platform defines a store interface that describes its data operations. The actual storage mechanism is selected at runtime via the SHIFT_STORAGE environment variable. This separation allows services to work identically across different backends without changing application code.

Backend Selection

SHIFT_STORAGE=convex    # Default -- uses Convex HTTP API
SHIFT_STORAGE=file # JSON files in local dotfile directories
SHIFT_STORAGE=gateway # Routes through the gateway API (used by CLI)

The createStore() factory function in each service reads SHIFT_STORAGE and returns the appropriate implementation.

Store Interface Pattern

Each service defines a ServiceStore interface in its packages/shared/src/store/ directory. This interface declares all data operations the service needs (CRUD, queries, etc.) without specifying how they are implemented.

Service (CLI / API / Web)
--> ServiceStore interface (packages/shared/src/store/index.ts)
--> FileStore (store/file.ts)
--> ConvexStore (store/convex.ts)
--> GatewayStore (store/gateway.ts)

Per-Service Store Files

Each service follows a consistent file layout:

FilePurpose
store/index.tsStore interface (the contract)
store/file.tsFileStore -- reads/writes JSON files on disk
store/convex.tsConvexStore -- calls Convex HTTP API
store/gateway.tsGatewayStore -- REST calls through the gateway
store/factory.tscreateStore() -- selects backend based on SHIFT_STORAGE

Convex Backend (Default)

When SHIFT_STORAGE=convex (or unset in API/gateway context), services use the Convex HTTP API for persistence. All services share a single Convex deployment with table prefixes to prevent collisions:

PrefixService
yp_Yellow Pages
passport_Passport
pulse_Pulse
ledger_Ledger
palette_Palette
stage_Stage
billing_Billing
inference_Inference
git_Git
auth_Gateway auth state
compute_Compute

See Convex Backend for a deep dive on schema, functions, and deployment.

SID Bridging

The application layer uses nanoid(8) identifiers in the id field. Convex uses its own internal _id system. Every Convex table includes a sid (shift ID) field with an index, bridging the two ID systems transparently.

When a ConvexStore receives an app-level id, it queries the Convex table by the sid index to find the corresponding Convex document. When returning data, it maps the sid field back to id. This bridging is invisible to the service code above the store layer.

App layer:  { id: "a1b2c3d4", name: "my-service", ... }
|
v (ConvexStore translates)
Convex: { _id: "k17abc...", sid: "a1b2c3d4", name: "my-service", ... }

File Backend

When SHIFT_STORAGE=file, services store data as JSON files in dotfile directories within the project root. Each service has its own directory:

.yellowpages/
services/
<id>.json
systems/
<id>.json

.ledger/
events/
<id>.json
policies/
<id>.json

.passport/
providers/
<id>.json

This backend is designed to be git-native: files can be committed, diffed, and reviewed in pull requests. Cache files are gitignored.

The FileStore wraps synchronous read/write operations on these JSON files, implementing the same store interface as the ConvexStore.

Gateway Backend

When SHIFT_STORAGE=gateway (the default for the CLI), store operations are routed through the gateway's REST API. The GatewayStore translates store method calls into HTTP requests to the appropriate /api/v1/<service>/ endpoints.

This is the standard mode for the shift-cli binary, which communicates with the deployed platform at app.the-shift.dev.

Environment Variables

VariablePurpose
SHIFT_STORAGEBackend selection: convex, file, or gateway
CONVEX_URLConvex Cloud deployment URL
CONVEX_SELF_HOSTED_URLSelf-hosted Convex URL (e.g., http://127.0.0.1:3210)
CONVEX_ADMIN_KEYAdmin key for self-hosted Convex
SHIFT_GATEWAY_URLGateway URL for SHIFT_STORAGE=gateway mode