feat(files): page, search, look up and roll up workspace files over the session API
Merged
WA
1d ago+32998+32998+01234567890123456789012345678901234567890123456789-142-142-012345678901234567890123456789
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]/filestakesfolderId(rootor an id; direct children),sortBy(name | size | uploadedAt | updatedAt),sortOrder,limit(≤500) andcursor, 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, withfolderPath - 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/updatedAtkeysets order on the plain column with microsecond cursors (matching the indexes); a cursor minted before this resumes on the old millisecond ordering, via aresumableTimestampKeyhelper 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 onoriginal_namefor 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-auditauthoring gate) - No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)