Files
2026-05-27 23:29:53 +01:00

103 lines
8.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks — Pagination streaming
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened.
## 0. Pre-flight (non-negotiable)
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## Audit Note (2026-05-02)
**Minimal progress** — DEFERRED, do not archive.
- No `packages/data/pagination/src/streaming/` directory exists. Existing dirs: `cursor`, `keyset`, `offset`, `helpers`, `adapters`, `tokens`, `errors`, `otel`, `schemas`, `types`.
- `grep -rln "streamPaginated\|stream-pagination\|collectStream\|countStream\|takeFromStream\|filterStream\|mapStream" packages/data/pagination/src/` returns zero hits.
- `bunx turbo lint typecheck test --filter=@suaveplan/pagination` is GREEN — but the streaming module is not implemented.
- All 57 tasks remain: `streamPaginated<T>` AsyncIterable, five operator helpers (`collectStream`, `countStream`, `takeFromStream`, `filterStream`, `mapStream`), `DEFAULT_STREAM_PAGE_SIZE`, AbortSignal contract, subpath export, sibling `.md`, full test matrix.
## 1. Spec authoring
- [ ] 1.1 Read `../suaveplan/enterprise/pagination.md` end-to-end
- [ ] 1.2 Read `openspec/specs/pagination/spec.md` (already exists — confirm PG-12 AbortSignal contract still aligned)
- [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
- [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md`
- [ ] 1.5 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification
- [ ] 1.6 Draft `specs/pagination/spec.md` from `openspec/templates/spec-delta.md` — note: this is a delta against the existing canonical spec, so add as ADDED Requirements (PG-14, PG-15) numbered after the current PG-13
- [ ] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
## 2. Implementation — `streaming/stream-pagination`
- [ ] 2.1 Create `packages/data/pagination/src/streaming/stream-pagination.ts` exporting `streamPaginated<T>(adapter, options?): AsyncIterable<T>`
- [ ] 2.2 Default `pageSize` to 50 (constant `DEFAULT_STREAM_PAGE_SIZE`); export the constant from `constants/index.ts`
- [ ] 2.3 Honour the one-page-in-memory invariant — lazy-init on first `next()`; fetch next page only when current page exhausted; mark exhausted when `currentPage.length < pageSize`
- [ ] 2.4 Forward `options.filter` and `options.sort` to `adapter.fetchPage(offset, pageSize, sort, filter)` byte-for-byte with upstream
- [ ] 2.5 Honour `options.signal` — check `signal.aborted` before each `fetchPage` AND before yielding each item; on abort throw `PaginationError(PAGINATION.ABORTED)`
- [ ] 2.6 Add `StreamPaginationOptions` type (`pageSize`, `filter`, `sort`, `signal`) to `types/pagination.types.ts`
- [ ] 2.7 Add `streaming/stream-pagination.md` sibling doc (≥200 words, basic + advanced examples)
## 3. Implementation — operator helpers
- [ ] 3.1 Add `collectStream<T>(iterable): Promise<T[]>` — drains; verify it does NOT short-circuit
- [ ] 3.2 Add `countStream<T>(iterable): Promise<number>` — counts without retaining
- [ ] 3.3 Add `takeFromStream<T>(iterable, count): Promise<T[]>` — short-circuits once `items.length >= count`
- [ ] 3.4 Add `filterStream<T>(iterable, predicate): AsyncIterable<T>` — lazy filter passthrough
- [ ] 3.5 Add `mapStream<T, U>(iterable, mapper): AsyncIterable<U>` — lazy map passthrough
- [ ] 3.6 All five helpers honour `AbortSignal` indirectly via the upstream iterable (no new signal parameter; cancellation propagates through the `for await` chain)
## 4. Subpath exports + barrel
- [ ] 4.1 Add `"./streaming/stream-pagination"` to `packages/data/pagination/package.json` exports map (matching existing per-feature subpath pattern)
- [ ] 4.2 Re-export `streamPaginated`, `collectStream`, `countStream`, `takeFromStream`, `filterStream`, `mapStream`, `StreamPaginationOptions`, `DEFAULT_STREAM_PAGE_SIZE` from `src/index.ts`
## 5. Tests
- [ ] 5.1 `streaming/stream-pagination.test.ts::"streams items from MemoryPaginationAdapter"` — basic round-trip over 100 items with `pageSize=10`
- [ ] 5.2 `streaming/stream-pagination.test.ts::"defaults pageSize to 50 when omitted"` — instrument `fetchPage` and assert called with `pageSize=50`
- [ ] 5.3 `streaming/stream-pagination.test.ts::"holds at most one page in memory"` — instrument iterator state; verify `currentPage` size ≤ `pageSize` at every step
- [ ] 5.4 `streaming/stream-pagination.test.ts::"forwards filter and sort to adapter.fetchPage"` — assert call args
- [ ] 5.5 `streaming/stream-pagination.test.ts::"is lazy — no fetch before first next()"` — instrument; verify zero calls before first `next()`
- [ ] 5.6 `streaming/stream-pagination.test.ts::"stops when partial page returned"` — verify exhausted-on-partial behavior
- [ ] 5.7 `streaming/stream-pagination.spec-conformance.test.ts::"throws PAGINATION.ABORTED before first fetchPage when signal already aborted"` — verify `fetchPage` is NOT called
- [ ] 5.8 `streaming/stream-pagination.spec-conformance.test.ts::"throws PAGINATION.ABORTED on next() after signal abort between yields"` — abort mid-iteration
- [ ] 5.9 `streaming/stream-pagination.spec-conformance.test.ts::"propagates rejection when signal aborts in-flight fetchPage"` — adapter aware of signal rejects naturally
- [ ] 5.10 `streaming/stream-pagination.spec-conformance.test.ts::"pins StreamPaginationOptions shape via expectTypeOf"` — type-level assertion
- [ ] 5.11 `streaming/stream-pagination.test.ts::"collectStream drains all items"` — happy path
- [ ] 5.12 `streaming/stream-pagination.test.ts::"countStream returns total without retention"` — verify result is correct count
- [ ] 5.13 `streaming/stream-pagination.test.ts::"takeFromStream short-circuits at count"` — instrument fetches; verify only enough pages fetched to satisfy take
- [ ] 5.14 `streaming/stream-pagination.test.ts::"filterStream is lazy and passthrough"` — verify items skipped don't pass through
- [ ] 5.15 `streaming/stream-pagination.test.ts::"mapStream is lazy and passthrough"` — verify mapper called once per yielded item
- [ ] 5.16 Every `it(...)` in 5.1–5.15 carries `{ timeout: 5000 }`
## 6. Documentation
- [ ] 6.1 Sibling `.md` for `stream-pagination.ts` (≥200 words, basic + advanced examples)
- [ ] 6.2 Update `packages/data/pagination/README.md` Modules section — add link to streaming module; add Quick-start snippet showing `streamPaginated` + `for await`
- [ ] 6.3 Update `CLAUDE.md` package listing line for `@suaveplan/pagination` to mention streaming
## 7. Pre-archive verification (the 5-gate triple)
- [ ] 7.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo
- [ ] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/pagination` — green
- [ ] 7.3 `bunx turbo test:coverage --filter=@suaveplan/pagination` — 100/100/100/100
- [ ] 7.4 `bun scripts/openspec-conformance.ts @suaveplan/pagination` — exit 0, no `error` findings
- [ ] 7.5 `bunx turbo build --filter=@suaveplan/pagination` — green
- [ ] 7.6 Dependent sweep: `bunx turbo test --filter=...@suaveplan/pagination` (covers `@suaveplan/search`)
## 8. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [ ] 8.1 Generics threaded through `streamPaginated<T>`, `mapStream<T, U>`, `filterStream<T>`, etc.
- [ ] 8.2 Zod schemas wired at the `StreamPaginationOptions` boundary if exposed across a network edge (likely no — purely in-memory contract)
- [ ] 8.3 `PAGINATION.ABORTED` thrown by at least one production path (the streaming helper)
- [ ] 8.4 Strict-mode behaviors tested both on and off (signal supplied vs absent)
- [ ] 8.5 OTel instruments emitted (page-fetch counter, page-fetch duration histogram) and asserted with a real in-memory `MeterProvider`
- [ ] 8.6 Lifecycle dedup not applicable here — streaming is single-iterator instances, no shared registry
- [ ] 8.7 No retroactive tick — every box above reflects work done in this change
## 9. Archive
- [ ] 9.1 Fold spec delta into `openspec/specs/pagination/spec.md` — add as PG-14 (streaming pagination contract) and PG-15 (streaming operator helpers)
- [ ] 9.2 Move `openspec/changes/pagination-streaming/` → `openspec/archive/<yyyy-mm-dd>-pagination-streaming/`
- [ ] 9.3 Refresh `MANIFEST.md` if package set or deps changed (no expected change here)