Files
cadSet/text-to-cad/viewer/docs/backend.md
2026-07-22 19:38:36 +08:00

7.4 KiB

Backend Storage

CAD Viewer uses a small backend interface so the same React app can run against local files during development and hosted object storage in deployment. Client code talks to HTTP routes and catalog URLs; it does not read filesystem paths directly.

Interface

Backend implementations expose this core shape:

{
  kind,
  readCatalog({ rootDir, fileRef }),
  refreshCatalog({ rootDir, fileRef }),
  resolveFileAssetAccess({ fileRef, asset, catalog }),
}

Trusted upload tooling for hosted storage serializes catalog JSON and uploads it as catalog.json. Writable backends may expose writeAsset({ fileRef, body, contentType }) and generateStepArtifact({ fileRef, force, catalog }).

Local filesystem backends also expose helpers used by Vite and the local production server:

{
  resolveRoot(rootDir),
  openFileAsset({ fileRef, asset, catalog }),
  assetPathForFileRef(fileRef, { resolvedRoot }),
  entryForSourcePath(catalog, resolvedRoot, sourcePath),
  contentTypeForPath(filePath),
}

readCatalog() returns catalog JSON from the backend's source of truth. refreshCatalog() lets an adapter update or regenerate that in-memory view. Writable helpers may write servable CAD assets such as hidden STEP GLBs or run local CAD generation.

Local Filesystem

src/server/localAssetBackend.mjs is the development and local deployment implementation. readCatalog() and refreshCatalog() scan the absolute ?dir= root for the current request, keep the catalog as an in-memory object, and return schema v4 entries whose file values are absolute paths plus rootRelativeFile values for URL navigation. The local backend does not write catalog.json or any hidden catalog cache file.

Local filesystem deployments are intentionally URL-driven. ?dir= may be absolute or relative to the directory where the Viewer was started; when omitted it defaults to the startup --dir, or to the startup directory if --dir was not passed. That default directory is also the first active directory. ?file= values are always relative to the active ?dir= directory. VIEWER_LOCAL_ROOT_DIR, VIEWER_LOCAL_WORKSPACE_ROOT, and the old fixed-root startup flag have been removed and now fail at startup.

The local backend serves asset bytes from the active root and writes regenerated artifacts back into it. It rejects path traversal and only serves or writes supported CAD Viewer asset types.

Local STEP GLB/topology regeneration calls the Python cadpy package. The root viewer carries a generated, installable copy under viewer/packages/cadpy; run scripts/bundle/bundle-skill.sh cad-viewer after changing packages/cadpy, then install viewer/requirements.txt into the Python runtime used by the viewer. The generated cad-viewer skill runtime bundles that same installable package under scripts/viewer/packages/cadpy and does not need the repository root.

Vite dev mounts this backend for:

  • GET /__cad/server
  • GET /__cad/catalog
  • GET /__cad/asset?file=...
  • GET /__cad/download?file=...&asset=output|source
  • POST /__cad/reveal?file=...&asset=output|source
  • POST /__cad/step-artifact

download streams the requested asset bytes and works for both local and hosted deployments. reveal opens the asset in Finder or the platform file manager and is only available for the local filesystem backend. asset=output resolves the catalog entry file itself; asset=source resolves optional source code, such as a same-stem Python generator for Python-backed STEP files.

The local production server uses the same backend:

npm run build
npm run serve

Then open the printed server URL with ?dir=/absolute/root&file=model.step. Pass --port <number> to npm run serve -- only when the default production port is already in use.

Vercel Blob

src/server/vercelBlobAssetBackend.mjs is the hosted storage adapter. Vercel deployments construct it in read-only mode: the hosted API reads the catalog and serves public Blob assets, but it does not write Blob objects or regenerate STEP artifacts.

Hosted catalog reads are deliberately conservative about Blob traffic. Sustained per-request fetches of the public catalog URL from shared serverless egress IPs trip Vercel's abuse mitigation with intermittent 403 Forbidden responses, so the hosted backend caches the parsed catalog in-function for 60 seconds (serving the last good catalog if a refresh fails), hosted /__cad/catalog responses carry s-maxage/stale-while-revalidate cache-control so the Vercel CDN absorbs client polling, and hosted viewer builds poll the catalog every 60 seconds instead of the local 2-second development cadence.

Expected deployment shape:

  • Build the frontend normally.
  • Run a trusted local upload/maintenance script to publish supported assets and a schema v4 catalog.json containing public Blob URLs.
  • Deploy the viewer with VIEWER_ASSET_BACKEND=vercel-blob and a read-only catalog configuration. Catalog warnings such as stale STEP artifacts are shown in the UI instead of being fixed by the hosted app.
  • Hosted Vercel routes ignore local filesystem ?dir= query values and read the configured hosted catalog.json instead.

The Vercel deployment entrypoints are intentionally thin:

api/cad/server.js
api/cad/catalog.js
api/cad/download.js
api/cad/reveal.js
vercel.json

vercel.json rewrites the client-facing routes below to those API functions:

/__cad/server
/__cad/catalog
/__cad/download
/__cad/reveal

The local production server can mount the Blob catalog path by environment:

VIEWER_ASSET_BACKEND=vercel-blob \
VIEWER_VERCEL_BLOB_PREFIX=https://<store-id>.public.blob.vercel-storage.com/models2 \
npm run serve

Upload a local directory catalog and supported viewer assets with:

VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN=... \
VIEWER_VERCEL_BLOB_PREFIX=models2 \
npm --prefix viewer run upload:blob -- models

BLOB_READ_WRITE_TOKEN may be used instead of VIEWER_VERCEL_BLOB_READ_WRITE_TOKEN, matching the environment variable Vercel Blob integrations create.

The uploader also reads .vieweruploadignore from the uploaded directory and accepts repeated --ignore-file options for gitignore-style exclude patterns. It excludes mechbench/, mechbench2/, 7dof_arm/, and Python source files by default, and public Blob catalogs omit Python source paths and URLs. Add --skip-existing --fetch-missing-lfs for publish-style runs that compare against the remote catalog, skip matching assets, and fetch only the Git LFS objects needed for new or changed uploads. The repository publish wrapper, scripts/viewer/upload-viewer-models-catalog.sh, owns the branch-defined Blob path prefix for model catalog uploads. Catalog uploads use Vercel Blob's minimum supported cache TTL so in-place catalog.json overwrites are visible quickly, while large immutable model assets keep the default Blob cache policy. Hosted catalog reads also append a catalog cache-busting query parameter so the viewer does not inherit stale edge entries from earlier catalog overwrites.

For token-free read-only deployments, VIEWER_VERCEL_BLOB_PREFIX should be the public Blob URL for the prefix directory. The hosted backend always reads catalog.json at the root of that prefix. Trusted local upload or maintenance scripts may use the path prefix, such as models2, with a read/write token.

The writable Blob helper methods remain available to local scripts that import the backend directly and pass a Blob read/write token. They are not exposed by the hosted Vercel API.