Foundation unit for the cache/API design (#36).
Depends on the decrypt-fields unit (needs the isDeleted field on EnteFile).
Goal
Give Client enumeration variants that the cache refresh needs: they take a
starting cursor, return the final cursor, and surface deletion tombstones. The
existing whole-account methods stay for callers that still want them.
Current state (grounded, src/client.ts)
listCollections() always calls /collections/v2 with sinceTime: 0 and
drops every isDeleted collection. It cannot report a cursor or deletions.
listFiles(collectionID, collectionKey) paginates /collections/v2/diff but
always restarts at sinceTime: 0, drops isDeleted rows, and returns no
cursor. Its loop advances sinceTime only from rows it sees; if the server
returns hasMore: true with a page whose max updationTime does not advance,
it loops forever (this is #7).
Scope (extend existing)
Add cursor-taking, tombstone-surfacing variants (names are the implementer's to
choose, plain and descriptive). Suggested shape:
collectionsSince(args: { sinceTime: number }) → { collections: Collection[]; deleted: number[]; cursor: number }, where collections are live decrypted records, deleted are the ids of tombstoned collections, and cursor is the max updationTime seen (fall back to the input when the response is empty).
filesSince(args: { collectionID; collectionKey; sinceTime }) → { files: EnteFile[]; deleted: number[]; cursor: number }, paginating from the given cursor, decrypting live rows, collecting isDeleted file ids, returning the final cursor.
Guard against a non-advancing server (#7):
if hasMore is true but the page's max updationTime is <= the cursor we
entered the page with, stop and throw a clear error rather than loop. Cover
this with a test.
Keep listCollections()/listFiles() as thin wrappers (call the new variant
with sinceTime: 0, drop deletions) so existing callers and tests are
unaffected — or leave them exactly as-is and add the new methods alongside.
Do not change the low-level ApiClient; it already accepts an arbitrary sinceTime.
Definition of done
New enumeration methods return live records, deleted ids, and a usable cursor.
Passing the returned cursor into a second call fetches only newer rows (tested
with a fixture/mock that pages then returns an empty diff).
The non-advancing-server case is bounded and tested (closes #7).
Existing listCollections/listFiles behaviour and tests unchanged.
make check green.
Grounding
Files: src/client.ts, test/api/client.test.ts (and/or a new test file).
Subsumes #7 — reference it in the landing
commit and close it there too.
Dispatch notes: TDD; no scripted edits; no interactive questions; decide from
this brief. Squash subject ends (closes #<this issue>). End every message
with Model: opus-4-8.
Model: opus-4-8
Foundation unit for the cache/API design (https://git.eeqj.de/sneak/quak/issues/36).
Depends on the decrypt-fields unit (needs the `isDeleted` field on `EnteFile`).
## Goal
Give `Client` enumeration variants that the cache refresh needs: they take a
starting cursor, return the final cursor, and surface deletion tombstones. The
existing whole-account methods stay for callers that still want them.
## Current state (grounded, `src/client.ts`)
- `listCollections()` always calls `/collections/v2` with `sinceTime: 0` and
drops every `isDeleted` collection. It cannot report a cursor or deletions.
- `listFiles(collectionID, collectionKey)` paginates `/collections/v2/diff` but
always restarts at `sinceTime: 0`, drops `isDeleted` rows, and returns no
cursor. Its loop advances `sinceTime` only from rows it sees; if the server
returns `hasMore: true` with a page whose max `updationTime` does not advance,
it loops forever (this is https://git.eeqj.de/sneak/quak/issues/7).
## Scope (extend existing)
Add cursor-taking, tombstone-surfacing variants (names are the implementer's to
choose, plain and descriptive). Suggested shape:
- `collectionsSince(args: { sinceTime: number })` → `{ collections: Collection[]; deleted: number[]; cursor: number }`, where `collections` are live decrypted records, `deleted` are the ids of tombstoned collections, and `cursor` is the max `updationTime` seen (fall back to the input when the response is empty).
- `filesSince(args: { collectionID; collectionKey; sinceTime })` → `{ files: EnteFile[]; deleted: number[]; cursor: number }`, paginating from the given cursor, decrypting live rows, collecting `isDeleted` file ids, returning the final cursor.
- Guard against a non-advancing server (https://git.eeqj.de/sneak/quak/issues/7):
if `hasMore` is true but the page's max `updationTime` is `<=` the cursor we
entered the page with, stop and throw a clear error rather than loop. Cover
this with a test.
- Keep `listCollections()`/`listFiles()` as thin wrappers (call the new variant
with `sinceTime: 0`, drop deletions) so existing callers and tests are
unaffected — or leave them exactly as-is and add the new methods alongside.
Do not change the low-level `ApiClient`; it already accepts an arbitrary
`sinceTime`.
## Definition of done
- New enumeration methods return live records, deleted ids, and a usable cursor.
- Passing the returned cursor into a second call fetches only newer rows (tested
with a fixture/mock that pages then returns an empty diff).
- The non-advancing-server case is bounded and tested (closes
https://git.eeqj.de/sneak/quak/issues/7).
- Existing `listCollections`/`listFiles` behaviour and tests unchanged.
- `make check` green.
## Grounding
Files: `src/client.ts`, `test/api/client.test.ts` (and/or a new test file).
Subsumes https://git.eeqj.de/sneak/quak/issues/7 — reference it in the landing
commit and close it there too.
Dispatch notes: TDD; no scripted edits; no interactive questions; decide from
this brief. Squash subject ends ` (closes #<this issue>)`. End every message
with `Model: opus-4-8`.
Model: opus-4-8
Scope note: surfacing deletion tombstones through decryption now belongs here. #37 was narrowed to only carry fileSize/thumbSize/isDeleted onto live EnteFile records (it leaves listFiles filtering tombstones before decrypt). This unit owns the tombstone path: the resumable enumerators pass isDeleted rows through and decide the return shape for a tombstone (a genuinely minimal EnteFile is not type-clean under strict tsc, so design it here — e.g. a separate tombstone type or deleted ids alongside live records, as this issue already sketches).
Model: opus-4-8
Scope note: surfacing deletion tombstones through decryption now belongs here. https://git.eeqj.de/sneak/quak/issues/37 was narrowed to only carry `fileSize`/`thumbSize`/`isDeleted` onto live `EnteFile` records (it leaves `listFiles` filtering tombstones before decrypt). This unit owns the tombstone path: the resumable enumerators pass `isDeleted` rows through and decide the return shape for a tombstone (a genuinely minimal `EnteFile` is not type-clean under strict tsc, so design it here — e.g. a separate tombstone type or `deleted` ids alongside live records, as this issue already sketches).
Model: opus-4-8
Implemented in #57 (branch issue-38-resumable-enumeration, base next).
Added collectionsSince/filesSince on Client: they take a starting cursor, decrypt live records, surface tombstoned ids in a separate deleted list, and return the max updationTime seen as the cursor to resume from. Per the scope note, tombstones are surfaced as bare ids rather than hollow records, which keeps the return type clean under strict tsc.
filesSince throws instead of looping when the diff reports more rows without advancing the cursor, which also resolves #7 (the PR closes it). listCollections/listFiles are now thin wrappers from sinceTime 0 that drop deletions, so existing callers are unaffected.
Model: opus-4-8
Implemented in https://git.eeqj.de/sneak/quak/pulls/57 (branch `issue-38-resumable-enumeration`, base `next`).
Added `collectionsSince`/`filesSince` on `Client`: they take a starting cursor, decrypt live records, surface tombstoned ids in a separate `deleted` list, and return the max `updationTime` seen as the cursor to resume from. Per the scope note, tombstones are surfaced as bare ids rather than hollow records, which keeps the return type clean under strict tsc.
`filesSince` throws instead of looping when the diff reports more rows without advancing the cursor, which also resolves https://git.eeqj.de/sneak/quak/issues/7 (the PR closes it). `listCollections`/`listFiles` are now thin wrappers from `sinceTime` 0 that drop deletions, so existing callers are unaffected.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Foundation unit for the cache/API design (#36).
Depends on the decrypt-fields unit (needs the
isDeletedfield onEnteFile).Goal
Give
Clientenumeration variants that the cache refresh needs: they take astarting cursor, return the final cursor, and surface deletion tombstones. The
existing whole-account methods stay for callers that still want them.
Current state (grounded,
src/client.ts)listCollections()always calls/collections/v2withsinceTime: 0anddrops every
isDeletedcollection. It cannot report a cursor or deletions.listFiles(collectionID, collectionKey)paginates/collections/v2/diffbutalways restarts at
sinceTime: 0, dropsisDeletedrows, and returns nocursor. Its loop advances
sinceTimeonly from rows it sees; if the serverreturns
hasMore: truewith a page whose maxupdationTimedoes not advance,it loops forever (this is #7).
Scope (extend existing)
Add cursor-taking, tombstone-surfacing variants (names are the implementer's to
choose, plain and descriptive). Suggested shape:
collectionsSince(args: { sinceTime: number })→{ collections: Collection[]; deleted: number[]; cursor: number }, wherecollectionsare live decrypted records,deletedare the ids of tombstoned collections, andcursoris the maxupdationTimeseen (fall back to the input when the response is empty).filesSince(args: { collectionID; collectionKey; sinceTime })→{ files: EnteFile[]; deleted: number[]; cursor: number }, paginating from the given cursor, decrypting live rows, collectingisDeletedfile ids, returning the final cursor.if
hasMoreis true but the page's maxupdationTimeis<=the cursor weentered the page with, stop and throw a clear error rather than loop. Cover
this with a test.
listCollections()/listFiles()as thin wrappers (call the new variantwith
sinceTime: 0, drop deletions) so existing callers and tests areunaffected — or leave them exactly as-is and add the new methods alongside.
Do not change the low-level
ApiClient; it already accepts an arbitrarysinceTime.Definition of done
with a fixture/mock that pages then returns an empty diff).
#7).
listCollections/listFilesbehaviour and tests unchanged.make checkgreen.Grounding
Files:
src/client.ts,test/api/client.test.ts(and/or a new test file).Subsumes #7 — reference it in the landing
commit and close it there too.
Dispatch notes: TDD; no scripted edits; no interactive questions; decide from
this brief. Squash subject ends
(closes #<this issue>). End every messagewith
Model: opus-4-8.Model: opus-4-8
Scope note: surfacing deletion tombstones through decryption now belongs here. #37 was narrowed to only carry
fileSize/thumbSize/isDeletedonto liveEnteFilerecords (it leaveslistFilesfiltering tombstones before decrypt). This unit owns the tombstone path: the resumable enumerators passisDeletedrows through and decide the return shape for a tombstone (a genuinely minimalEnteFileis not type-clean under strict tsc, so design it here — e.g. a separate tombstone type ordeletedids alongside live records, as this issue already sketches).Model: opus-4-8
Implemented in #57 (branch
issue-38-resumable-enumeration, basenext).Added
collectionsSince/filesSinceonClient: they take a starting cursor, decrypt live records, surface tombstoned ids in a separatedeletedlist, and return the maxupdationTimeseen as the cursor to resume from. Per the scope note, tombstones are surfaced as bare ids rather than hollow records, which keeps the return type clean under strict tsc.filesSincethrows instead of looping when the diff reports more rows without advancing the cursor, which also resolves #7 (the PR closes it).listCollections/listFilesare now thin wrappers fromsinceTime0 that drop deletions, so existing callers are unaffected.Model: opus-4-8