Skip to content

Add cursor pagination to /flowsheet/search (step 3)#473

Merged
jakebromberg merged 1 commit into
mainfrom
worktree-feat+cursor-pagination
Apr 25, 2026
Merged

Add cursor pagination to /flowsheet/search (step 3)#473
jakebromberg merged 1 commit into
mainfrom
worktree-feat+cursor-pagination

Conversation

@jakebromberg

Copy link
Copy Markdown
Member

Summary

  • Adds an optional cursor query parameter to GET /flowsheet/search. When provided with sort=date, the WHERE switches from OFFSET to a row-value comparison (add_time, id) < (cursor_time, cursor_id) (or > for asc) so each page costs O(limit) regardless of depth.
  • Cursor is compound (add_time, id) rather than add_time alone — the legacy ETL backfilled many rows with batch-import timestamps that share the same add_time to the microsecond, and a single-column cursor would silently drop those rows on page boundaries.
  • Other sorts (artist, song, dj) ignore cursor and continue using offset because their sort columns are not unique and no compound index supports a (sort_col, id) cursor.
  • nextCursor returned only when cursor mode is active and the page came back full. totalPages stays in the response always — both shapes coexist for the dj-site backward-compat window.
  • Encoding format is ${ISO_timestamp}_${id}. Malformed cursor → 400.

Implements step 3 from docs/playlist-search/README.md.

Test plan

  • parseCursor / encodeCursor codec tests including round-trip and a comprehensive set of malformed-cursor cases
  • Service tests cover: nextCursor on full page, omission on short page, omission in offset mode, omission when sort is not date, total still returned in cursor mode
  • Controller tests cover: forwarding a valid cursor, undefined-when-missing, 400 on malformed cursor, response shape with and without nextCursor
  • Existing offset-based tests still pass — backward compat verified by the unchanged tests
  • npm run typecheck clean
  • npm run lint clean (warnings unchanged from main)
  • npx prettier --check clean on touched files
  • CI integration tests verify nothing regresses against a real DB

Closes #472

Cursor mode keeps every page O(limit) regardless of depth — OFFSET grows linearly because Postgres still scans and discards skipped rows. Pairs cleanly with the flowsheet_track_add_time_idx partial index from migration 0050.

Cursor only applies to sort=date. Other sorts (artist, song, dj) keep using offset because their sort columns are not unique and there is no compound index supporting a (sort_col, id) cursor — and date is the default and the only sort the recent-tracks landing page uses.

Cursor is compound (add_time, id) rather than add_time alone because the legacy ETL backfilled many rows with batch-import timestamps that share the same add_time value to the microsecond — a single-column cursor would silently drop those rows on page boundaries. Encoding format is ISO_timestamp_id; malformed cursors return 400.

Both response shapes coexist for backward compat: totalPages is always returned, nextCursor is added only when cursor mode produced a full page. The dj-site useLazySearchPlaylistsQuery currently reads totalPages and can migrate to nextCursor when convenient — that is a separate follow-up PR.

Adds parseCursor / encodeCursor unit tests including round-trip and a comprehensive set of malformed-cursor cases. Adds searchFlowsheet tests covering: nextCursor presence on full page, omission on short page, omission in offset mode, omission when sort is not date, total still returned in cursor mode. Adds controller tests for valid forwarding, undefined-when-missing, and 400 on malformed cursor. Updates the design doc step 3 section to reflect the compound-cursor decision and the date-only sort coverage.

Implements step 3 from docs/playlist-search/README.md.
@jakebromberg jakebromberg merged commit 1d62ac0 into main Apr 25, 2026
4 checks passed
@jakebromberg jakebromberg deleted the worktree-feat+cursor-pagination branch April 25, 2026 20:48
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.

Add cursor pagination to /flowsheet/search (step 3)

1 participant