Skip to content

impl(server,web,cli): extract @forager/server (tauri port phase 1)#74

Draft
andykais-claude wants to merge 1 commit into
mainfrom
cursor/tauri-server-phase1-1e82
Draft

impl(server,web,cli): extract @forager/server (tauri port phase 1)#74
andykais-claude wants to merge 1 commit into
mainfrom
cursor/tauri-server-phase1-1e82

Conversation

@andykais-claude

Copy link
Copy Markdown
Collaborator

Phase 1 of the Tauri port plan (docs/design/tauri-port.md). Carves the RPC endpoint, media-file streaming, thumbnail streaming, and the Forager config schema out of the SvelteKit app into a new framework-agnostic @forager/server package, so a future Tauri shell and a remote-mode browser can both talk to the same backend.

No frontend behaviour changes. Same URLs, same response shapes; only the call graph behind them moves.

Walkthrough

forager_gui_phase1_smoketest.mp4
End-to-end manual test of forager gui after the refactor: thumbnails render, the image preview loads, the video plays (HTTP Range request served by the new @forager/server path).

Browse view with two media tiles served via /files/thumbnail
Image preview served via /files/media_file
Video player streaming via /files/media_file with Range requests

What moves where

New: packages/server (@forager/server)

Module Source
src/api.ts moved from packages/web/src/lib/api.ts
src/config.ts moved from packages/web/src/lib/server/config.ts
src/handlers/rpc.ts extracted from packages/web/src/routes/rpc/[signature]/+server.ts
src/handlers/media_file.ts extracted from packages/web/src/routes/files/media_file/[id]/+server.ts
src/handlers/thumbnail.ts extracted from packages/web/src/routes/files/thumbnail/[id]/+server.ts
src/server.ts new — ForagerServer class

ForagerServer.try_handle_request(request) returns a Response if the request matches /rpc/* or /files/(media_file|thumbnail)/:id, otherwise null so embedders can route it elsewhere. ForagerServer.start({ port, hostname }) runs it standalone — that's what forager serve will use in a later phase.

Implementation note on the RPC handler: ts-rpc 0.2.4 only exports framework adapters (Oak, SvelteKit). The SvelteKit adapter only reads event.request, so for now create_rpc_handler wraps it with a stub { request } event. Once ts-rpc ships a Request → Response adapter, that wrapper goes away.

Changed: packages/web

  • src/lib/api.ts → 4-line re-export shim of @forager/server/api.
  • src/lib/server/config.ts → 8-line re-export shim of @forager/server/config. All $lib/server/config.ts imports across the frontend keep working unchanged.
  • src/routes/rpc/[signature]/+server.ts, src/routes/files/media_file/[id]/+server.ts, src/routes/files/thumbnail/[id]/+server.ts → thin delegates into @forager/server/handlers. This keeps vite dev working: SvelteKit still owns the routes, the logic just lives in the new package.
  • adapter/lib/mod.ts (the JSR-published web.Server):
    • New optional forager + config options.
    • When both are present, the request handler invokes ForagerServer.try_handle_request before falling through to SvelteKit — so in production, /rpc/* and /files/* skip the SvelteKit handler entirely.
    • When absent, behaviour is identical to today (everything goes through SvelteKit).

Changed: packages/cli

init and gui commands now pass forager and config directly into web.Server so the bypass kicks in.

Workspace

packages/server added to the root deno.json workspace list.

Why split this way

The design doc described @forager/web becoming "a thin wrapper that runs ForagerServer + serves SvelteKit assets exactly as today." This PR delivers exactly that: the runtime now has two entry points into the same handler functions — SvelteKit (for dev mode) and web.Server (production bypass) — both backed by the new package. The bypass is what the future Tauri shell will use to talk to a remote Forager.

Testing

  • deno check packages/server/src/mod.ts — clean
  • deno check packages/web/adapter/lib/mod.ts — clean
  • deno check packages/cli/src/cli.ts — clean
  • deno task compile:local — full production build of CLI binary with embedded @forager/web succeeds.
  • deno task --cwd packages/cli test — all 2 tests pass (1 step ignored, same as main).
  • ⚠️ deno task --cwd packages/core test — 26 pass, 4 fail with the same ffmpeg-precision failures that exist on main (verified by running on main with my changes stashed). Documented in AGENTS.md as known.
  • ✅ Manual end-to-end smoke test of the compiled forager gui binary against a fresh database:
    • curl http://127.0.0.1:8000/200 (SvelteKit HTML)
    • PUT /rpc/forager.media.search → JSON RPC response with the seeded media (handled by @forager/server)
    • GET /files/thumbnail/1200, image/jpeg, 36544 bytes
    • GET /files/media_file/1200, image/jpeg, 874874 bytes (byte-identical to source, EXIF preserved)
    • GET /files/media_file/2 with Range: bytes=0-1023206, content-range: bytes 0-1023/400240 (video seeking works)
    • GET /files/media_file/9999404 Media not found (NotFound branch)
    • GET /nonexistent-page404 from SvelteKit (fall-through path works)
  • ✅ Browser smoke test (recorded above): thumbnails, image preview, and video playback all working end-to-end.

The 3 HTML validation warnings the browser flagged on <label for=…> are pre-existing and unrelated to this PR.

Follow-ups (not in this PR)

  • Phase 2 — thread a Connection abstraction through BaseController and replace hardcoded /files/... URLs.
  • Phase 3 — switch to adapter-static; collapse web.Server into a thinner mount around ForagerServer.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

Carve the RPC endpoint, media-file streaming, thumbnail streaming, and the
Forager config schema out of the SvelteKit app into a new framework-agnostic
@forager/server package.

@forager/server
  - api.ts (moved from packages/web/src/lib/api.ts)
  - config.ts (moved from packages/web/src/lib/server/config.ts)
  - handlers/rpc.ts (wraps ts-rpc's SvelteKit adapter with a Request stub
    until ts-rpc publishes a Request adapter; module is internal)
  - handlers/media_file.ts, handlers/thumbnail.ts (pure Request -> Response)
  - ForagerServer class with try_handle_request(request) so embedders can
    mount it ahead of their own routes, plus a standalone start() for the
    upcoming 'forager serve' command.

@forager/web
  - SvelteKit +server.ts routes become thin delegates so dev mode (vite +
    sveltekit hooks) keeps working unchanged.
  - The published web.Server now accepts forager + config directly and
    dispatches /rpc/* and /files/* through ForagerServer, bypassing
    SvelteKit. Static assets and HTML routes still flow through SvelteKit.
  - $lib/api.ts and $lib/server/config.ts become shims re-exporting from
    @forager/server so frontend imports don't need to move yet.

@forager/cli
  - Pass forager + config into web.Server in both 'init' and 'gui' so the
    bypass kicks in.

No frontend behaviour changes. Same URLs, same response shapes.

Co-authored-by: andykais-claude <andykais-claude@users.noreply.github.com>
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.

2 participants