Skip to content

Local dev: end-to-end Lambda + filesystem-backed S3 mirror - #82

Merged
wasimxyz merged 6 commits into
stagingfrom
cursor/cli-handler-local-mirror
May 22, 2026
Merged

Local dev: end-to-end Lambda + filesystem-backed S3 mirror#82
wasimxyz merged 6 commits into
stagingfrom
cursor/cli-handler-local-mirror

Conversation

@wasimxyz

@wasimxyz wasimxyz commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Local-dev workflow improvements that together let a developer iterate on process_file() modules and custom run/file viewer components against the local web app without S3, AWS credentials, or LocalStack.

1. data-hub-process handler CLI

New subcommand on the lambda CLI that runs lambda_handler end-to-end against a gitignored lambda/.local-s3/ directory mirroring the S3 layout:

  • Stages --source FILE into <mirror>/<raw-bucket>/<instrument-id>/<run-id>/<filename>.
  • Monkey-patches data_hub_shared.s3_utils.download_file / upload_file to copy via shutil.copy2 (no boto3, no credentials).
  • Builds a synthetic S3 event and invokes lambda_handler(event, ctx) so the same dispatch path production uses runs against the local Next.js dev API.

Reusable patch surface in lambda/src/data_hub_lambda/local_s3_mirror.py mirrors what tests/integration/conftest.py already does for CI.

2. Filesystem-backed S3 mirror in web/

When LOCAL_S3_MIRROR is set (and NODE_ENV != production), the four helpers in web/lib/s3.ts short-circuit the AWS SDK and resolve to <root>/<bucket>/<key> via a dev-only catch-all at /api/local-s3/{bucket}/{...key}:

  • GET streams the file with optional Content-Disposition (matches presigned-URL behavior).
  • PUT writes the request body to disk via pipeline() for clean error/abort handling.
  • Both 404 unconditionally when production or the env var is unset.

(Folder is local-s3 not _local-s3 — the App Router treats _-prefixed folders as private and excludes them from routing.)

Components don't change: every existing run viewer fetches /api/v1/files/<id>/download, which 302s to whatever getPresignedDownloadUrl returns, so swapping the backend is invisible.

3. Realistic seed + automated handler run

  • Instrument types backed by a real process_file (qPCR, gel doc, plate reader) seed under their canonical kebab ids (azure-cielo-qpcr, azure-600-gel-doc, spectramax-id3-plate-reader) with instrument-shaped run ids (Experiment_20260129, 26.02.02_10.45.05, 012926_AR_OD600, …). Other types keep seed-<type> / seed-run-N.
  • Fixture-bearing runs render exactly one file row — the real fixture — instead of mixing in synthetic CSV siblings.
  • After seeding, the seed probes the dev API with the just-minted PAT and (when reachable) spawns data-hub-process handler for each fixture-bearing run so processed artifacts (gel-doc PNGs, plate-reader CSVs, qPCR metadata) appear in the dashboard immediately. If the API is down, it prints a hint and skips. npm run db:process-fixtures re-runs the same step on demand once npm run dev is up.

Files

  • lambda/src/data_hub_lambda/cli.py — new handler subcommand
  • lambda/src/data_hub_lambda/local_s3_mirror.pypatched_s3 context manager
  • web/lib/s3-local-mirror.tsgetLocalMirrorRoot, resolveMirrorPath, URL builders, MIME map
  • web/app/api/local-s3/[bucket]/[...key]/route.ts — GET + PUT handlers
  • web/lib/s3.ts — local-mirror branch on each of the four exports
  • web/lib/db/seed.tsCANONICAL_INSTRUMENT_ID, INSTRUMENT_FIXTURES, fixture copying
  • web/scripts/process-fixtures.ts — shared probe + spawn helper
  • web/scripts/process-seeded-fixtures.tsnpm run db:process-fixtures entry point
  • web/scripts/seed-database.ts — calls processSeededFixtures after seeding
  • web/.env.example, docs/local-development.md, docs/guides/adding-an-instrument.md — documentation
  • .gitignorelambda/.local-s3/

Test plan

  • make check-all passes (ruff, pyright, prettier, eslint, tsc).
  • make db-reseed && make dev then visit http://localhost:3000/instruments/azure-cielo-qpcr/runs/Experiment_20260129 — fixture CSV downloads and processed metadata is set on the run.
  • Visit http://localhost:3000/instruments/azure-600-gel-doc/runs/26.02.02_10.45.05 — TIFF renders and processed PNG appears under the run.
  • cd lambda && uv run data-hub-process handler agilent-4150-tapestation run-1 sample.csv --source <path> — file row appears with viewable bytes.
  • Reseed with npm run dev not running → seed prints the skip hint; npm run db:process-fixtures after starting dev produces the same result as case 1.
  • Production-safety: setting LOCAL_S3_MIRROR with NODE_ENV=production does not expose /api/local-s3/... (returns 404).
  • Existing integration suite (web/tests/integration/, lambda/tests/integration/) continues to pass.

Made with Cursor

wasimxyz and others added 2 commits May 21, 2026 12:39
Adds a new `handler` subcommand to the lambda CLI that drives
`lambda_handler` against a gitignored local directory mirroring the S3
layout, so devs can iterate on `process_file` modules against the
zero-credential local web app without S3, AWS credentials, or
LocalStack.

The CLI stages a `--source` file into
`lambda/.local-s3/<raw-bucket>/<instrument-id>/<run-id>/<filename>`,
monkey-patches `s3_utils.download_file` / `upload_file` to copy
from/to the mirror, and invokes `lambda_handler` with a synthesized S3
event. The reusable patch surface lives in `local_s3_mirror.py` and
mirrors what `tests/integration/conftest.py` already does for CI.

Documents the workflow in `docs/local-development.md` and links from
the "Adding an instrument" guide.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wires the Next.js app into the same `lambda/.local-s3` mirror that the
lambda CLI's `data-hub-process handler` writes to, so file downloads,
server-side reads, archive HEAD checks, and watcher-style PUT uploads
work locally without AWS or LocalStack.

When `LOCAL_S3_MIRROR` is set (and `NODE_ENV != production`), the four
helpers in `web/lib/s3.ts` short-circuit the AWS SDK and resolve to
`<root>/<bucket>/<key>` via a dev-only catch-all route at
`/api/_local-s3/{bucket}/{...key}`. The route handles GET (streams the
file with optional `Content-Disposition`) and PUT (writes the request
body to disk via `pipeline()`).

The dev seed copies fixture bytes from `lambda/tests/fixtures/` into
the mirror for qPCR / gel doc / plate reader runs so seeded runs
render real bytes immediately after `make db-reseed`. Other instrument
types still 404 — devs stage real bytes via the lambda CLI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
data-hub Ready Ready Preview, Comment May 22, 2026 6:25pm

Request Review

@wasimxyz wasimxyz self-assigned this May 21, 2026
Next.js App Router treats `_`-prefixed folders as private and excludes
them from routing, so requests to /api/_local-s3/... 404'd before the
handler ran.

Co-authored-by: Cursor <cursoragent@cursor.com>
Mirrors the route folder rename in comments, env example, and the
local-development guide, and adds a note explaining why the leading
underscore breaks routing.

Co-authored-by: Cursor <cursoragent@cursor.com>
…instruments

qPCR / gel doc / plate reader rows now seed under the kebab-case ids
the lambda's `Instrument` enum and `process_file` modules expect, with
instrument-shaped run ids (e.g. Experiment_20260129, 26.02.02_10.45.05,
012926_AR_OD600). Fixture-bearing runs render exactly one file row —
the real fixture — instead of mixing in synthetic CSV siblings.

Co-authored-by: Cursor <cursoragent@cursor.com>
The seed now probes the dev API with the just-minted PAT and, when
reachable, spawns `data-hub-process handler` for each canonical
fixture-bearing run so processed artifacts (gel-doc PNGs, plate-
reader CSVs, qPCR metadata) appear in the dashboard immediately
after a reseed. When the API is down, prints a hint and skips —
`npm run db:process-fixtures` re-runs the same step on demand once
`npm run dev` is up.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wasimxyz
wasimxyz marked this pull request as ready for review May 22, 2026 18:25
@wasimxyz
wasimxyz merged commit 1c6e482 into staging May 22, 2026
6 checks passed
@wasimxyz
wasimxyz deleted the cursor/cli-handler-local-mirror branch May 22, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant