diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 8660a673c9b..528cf96ac2d 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -405,24 +405,31 @@ const layer = Layer.effect( input.cursor === undefined ? input : yield* ListCursor.parse(input.cursor).pipe(Effect.mapError(() => new InvalidListCursorError())) - const data = yield* store.list({ ...filter, limit: input.limit ?? DefaultListLimit }) + const limit = input.limit ?? DefaultListLimit + const backward = "anchor" in filter && filter.anchor.direction === "previous" + // One extra row reveals whether another page exists; a backward page arrives reversed, so it sits first. + const rows = yield* store.list({ ...filter, limit: limit + 1 }) + const more = rows.length > limit + const data = more && backward ? rows.slice(1) : rows.slice(0, limit) const first = data[0] const last = data.at(-1) return { data, cursor: { - previous: first - ? ListCursor.make({ - ...filter, - anchor: { id: first.id, time: DateTime.toEpochMillis(first.time.updated), direction: "previous" }, - }) - : undefined, - next: last - ? ListCursor.make({ - ...filter, - anchor: { id: last.id, time: DateTime.toEpochMillis(last.time.updated), direction: "next" }, - }) - : undefined, + previous: + first && (more || !backward) + ? ListCursor.make({ + ...filter, + anchor: { id: first.id, time: DateTime.toEpochMillis(first.time.updated), direction: "previous" }, + }) + : undefined, + next: + last && (more || backward) + ? ListCursor.make({ + ...filter, + anchor: { id: last.id, time: DateTime.toEpochMillis(last.time.updated), direction: "next" }, + }) + : undefined, }, } }), diff --git a/packages/core/test/session-list.test.ts b/packages/core/test/session-list.test.ts index 1138130ab6f..62e2187452b 100644 --- a/packages/core/test/session-list.test.ts +++ b/packages/core/test/session-list.test.ts @@ -43,12 +43,44 @@ describe("Session.list", () => { const first = yield* session.list({ ...filter, limit: 2 }) const second = yield* session.list({ cursor: first.cursor.next, limit: 2 }) const back = yield* session.list({ cursor: second.cursor.previous, limit: 2 }) - const end = yield* session.list({ cursor: second.cursor.next, limit: 2 }) expect(expected).toHaveLength(3) expect([...first.data, ...second.data].map((item) => item.id)).toEqual(expected) + expect(second.cursor.next).toBeUndefined() expect(back.data.map((item) => item.id)).toEqual(expected.slice(0, 2)) - expect(end).toEqual({ data: [], cursor: { previous: undefined, next: undefined } }) + }), + ) + + it.effect("offers a cursor in the direction of travel only while more sessions exist", () => + Effect.gen(function* () { + const session = yield* Session.Service + yield* Effect.forEach(Array.from({ length: 6 }), () => session.create({ location: project }), { discard: true }) + const filter = { directory: project.directory, order: "asc" as const } + const store = yield* SessionStore.Service + const expected = (yield* store.list(filter)).map((item) => item.id) + const ids = (page: { data: ReadonlyArray }) => page.data.map((item) => item.id) + + const first = yield* session.list({ ...filter, limit: 2 }) + const second = yield* session.list({ cursor: first.cursor.next, limit: 2 }) + const last = yield* session.list({ cursor: second.cursor.next, limit: 2 }) + const back = yield* session.list({ cursor: last.cursor.previous, limit: 2 }) + const start = yield* session.list({ cursor: back.cursor.previous, limit: 2 }) + + expect(expected).toHaveLength(6) + expect([ids(first), ids(second), ids(last), ids(back), ids(start)]).toEqual([ + expected.slice(0, 2), + expected.slice(2, 4), + expected.slice(4, 6), + expected.slice(2, 4), + expected.slice(0, 2), + ]) + expect([first, second, last, back, start].map((page) => [!!page.cursor.previous, !!page.cursor.next])).toEqual([ + [true, true], + [true, true], + [true, false], + [true, true], + [false, true], + ]) }), )