187 lines
7.4 KiB
Markdown
187 lines
7.4 KiB
Markdown
# 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:
|
|
|
|
```js
|
|
{
|
|
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:
|
|
|
|
```js
|
|
{
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
/__cad/server
|
|
/__cad/catalog
|
|
/__cad/download
|
|
/__cad/reveal
|
|
```
|
|
|
|
The local production server can mount the Blob catalog path by environment:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|