first commit
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user