mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 03:56:55 +03:00
fix(core): return session list cursors only when another page exists
Session.list returned cursor.next for every non-empty page, including the last one, so a list whose size is a multiple of the limit, or a short final page, made clients request one more empty page. Read one extra row to learn whether another page exists, and return the cursor in the direction of travel only then. A backward page arrives reversed, so its extra row is dropped from the front.
This commit is contained in:
@@ -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,
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -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<Session.Info> }) => 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],
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user