feat(files): page, search, look up and roll up workspace files over the session API

Merged
WA
1d ago
+32998-142

Summary

Server side of workspace-file pagination: bounded pages, search, batched lookup and folder rollups over the session API, so clients no longer need the whole workspace list.

  • Paged list: GET /api/workspaces/[id]/files takes folderId (root or an id; direct children), sortBy (name | size | uploadedAt | updatedAt), sortOrder, limit (≤500) and cursor, and returns { files, nextCursor }; with no paging params it returns the unchanged full response, so existing clients keep working during rollout. Cursors are bound to the route, workspace, scope and folder
  • Search: GET .../files/search?q&folderId&recursive&limit — case-insensitive name search (LIKE-escaped), optionally over a folder's subtree, with folderPath
  • Lookup: POST .../files/lookup { ids, keys, references } — one query for up to 100 of each, deduplicated
  • Folder stats: GET .../files/folder-stats?parentId — file count and bytes per direct child folder, over its subtree, from one grouped aggregate
  • One shared page implementation (readWorkspaceFilePage) behind the session API, v2 and the mothership VFS
  • The folder filter is written as coalesce(folder_id, '') = $f, so folder pages seek the existing folder-name index instead of scanning every live file; name pages seek on the cursor
  • uploadedAt/updatedAt keysets order on the plain column with microsecond cursors (matching the indexes); a cursor minted before this resumes on the old millisecond ordering, via a resumableTimestampKey helper shared with the logs API
  • Migration 0443: (workspace_id, coalesce(folder_id, ''), uploaded_at, id) for folder pages in upload order and a trigram GIN on original_name for search, both partial on live workspace files and built concurrently; content edits stay HOT

Type of Change

  • New feature
  • Other: performance

Testing

  • Integration (real Postgres, migrate- and push-provisioned): every sort in both directions across ties (including timestamps that differ only below the millisecond), legacy millisecond cursor resume, root/folder/recursive scopes, foreign or unknown folders, deleted/archived/chat-upload exclusion, empty folder id refused, search (case, subtree, limit, literal wildcards), lookup (ids/keys/references, foreign and deleted ignored), nested folder rollups, authorization (read member served, non-member forbidden)
  • Plan assertions on the SQL the managers send: workspace-wide upload-order pages use the upload-order index with no sort, folder pages the folder index, name pages the name index, search the trigram index
  • Route tests: the unpaged response is unchanged; a cursor reused under another folder is refused
  • bun run check:migrations origin/staging, drizzle-kit generate (no changes), bun run lint, bun run check:audits, bun run type-check

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)