Launch OpenGPEX

Cloud Storage

How GPEX-Cloud persists, syncs, and serves OpenGPEX v2 projects — from the editor's serialization pipeline to the server-side storage model.


Architecture at a Glance

Editor (opengpex_v2)                     GPEX-Cloud
┌─────────────────────────┐              ┌──────────────────────────┐
│ core/cloud (client SDK)  │  ──HTTPS──▶  │ API Routes (/api/user/*) │
│  auth/ · protocol.ts     │              │  guard.ts (requireAuth)  │
│  storage.ts · types.ts   │              │ FileService (upsert)     │
└──────────┬──────────────┘              │ Postgres + R2 objects    │
           │                             └──────────────────────────┘
   .gpex container (state + assets)

The cloud client lives entirely in core/cloud — the editor core has no knowledge of backend internals. All requests carry the authenticated session as an Authorization: Bearer header (the access token lives only in memory; the refresh token is persisted in localStorage with rotation detection). Server-side guard.ts (requireAuth) resolves the caller before any file operation.


Data Model

Cloud files live in tb_user_files, owned by the authenticated business user (sys_user_id):

Field Purpose
file_local_id Client-generated upsert key; unique per user — re-saving the same project overwrites in place
manifest (JSONB) Lightweight document metadata for listings (name, dimensions, layer count, timestamps)
preview_b64 Base64 thumbnail for the Cloud Gallery, extracted server-side from the container
r2_path Absolute address of the binary blob in R2 object storage
file_size, version Quota accounting and per-save version counter
fingerprint SHA-256 of the uploaded bytes, returned to the client after each save

Binary payloads (the ZIP member of the .gpex container) go to R2; only the manifest, preview, and counters live in Postgres, keeping listing queries cheap.


Saving & Syncing

Sync to Cloud serializes the active document into a .gpex binary container (thumbnail + JSON manifest + ZIP of state and content-addressed assets) and uploads it to POST /api/user/files/save with the project's file_local_id. The server upserts on that key: a matching row keeps its file_id, bumps version, and overwrites the same R2 object (the path is derived from file_id), so there are no addressable historical snapshots — only the latest bytes plus a rising version counter. Limits enforced server-side: 20 MB per file, 200 MB storage quota per user.

Status Indicators

The UI derives a SyncStatus per frame (SYNCED / LOCAL_AHEAD / NEVER_SAVED / OFFLINE):

Status Meaning
NEVER_SAVED (Local only) Never saved to cloud
LOCAL_AHEAD (Unsaved changes) Local modifications since last sync
SYNCED Up-to-date with cloud (shows version)
OFFLINE Cloud service unavailable

Cloud Gallery

The Cloud Browser (XTEND_SLOT → CloudMenu) lists the authenticated user's files from the manifest table:

  • Open — download and hydrate into the editor
  • Share — create (or idempotently re-fetch) the file's public share link and copy it to the clipboard
  • Download — save locally as .gpex
  • Delete — remove manifest row and R2 object after an explicit confirmation (associated share links are deactivated)

Public Share Links

Files can be published via a share_token (POST /api/user/files/[id]/share; creation is idempotent — re-sharing returns the existing token). External visitors download through GET /api/share/[token]/download — token alone, no account and no file id. Because the token is bound to the file's file_id, which survives re-saves, links keep working when a project is updated. Server-side limits: up to 20 active share links per user (HTTP 429 beyond that); links can be revoked via DELETE /api/user/files/[id]/share, and deleting the file deactivates its shares.


Conflict Handling

Loading a cloud project that collides with the active local document (same frame id) raises a confirmation dialog — Overwrite or Cancel — preventing accidental loss of local work.


Roadmap: Teams and Workspaces

A workspace-and-team-collaboration architecture (personal workspace per user, team workspaces with owner/admin/editor/viewer roles, optimistic locking against concurrent overwrites) is designed but not yet shipped; today's model remains single-owner (sys_user_id) with per-user file isolation.


Privacy

  • Files are stored securely at rest; access is restricted to the authenticated owner's session
  • File contents are never analyzed or processed server-side beyond storage, manifest/preview extraction, and SHA-256 fingerprinting

Last updated: 2026-10-12