feat(session): filter v2 session list by parent (#35037)

Co-authored-by: Dax <mail@thdxr.com>
This commit is contained in:
opencode-agent[bot]
2026-07-02 19:48:49 -04:00
committed by GitHub
co-authored by Dax
parent 7ebd344fa2
commit 6555df912a
17 changed files with 180 additions and 21 deletions
+7 -2
View File
@@ -3,7 +3,7 @@ export * from "./session/schema"
import { DateTime, Effect, Layer, Schema, Context, Stream, Scope } from "effect"
import { ListAnchor } from "@opencode-ai/schema/session"
import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
import { and, asc, desc, eq, gt, isNull, like, lt, or, type SQL } from "drizzle-orm"
import { ProjectV2 } from "./project"
import { WorkspaceV2 } from "./workspace"
import { ModelV2 } from "./model"
@@ -61,6 +61,7 @@ const ListInputBase = {
search: Schema.String.pipe(Schema.optional),
limit: PositiveInt.pipe(Schema.optional),
order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional),
parentID: Schema.NullOr(SessionSchema.ID).pipe(Schema.optional),
anchor: ListAnchor.pipe(Schema.optional),
}
@@ -358,12 +359,16 @@ const layer = Layer.effect(
const direction = input.anchor?.direction ?? "next"
const requestedOrder = input.order ?? "desc"
const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
const sortColumn = SessionTable.time_created
const sortColumn = SessionTable.time_updated
const conditions: SQL[] = []
if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
if (input.parentID !== undefined)
conditions.push(
input.parentID === null ? isNull(SessionTable.parent_id) : eq(SessionTable.parent_id, input.parentID),
)
if (input.anchor) {
conditions.push(
order === "asc"
+47
View File
@@ -130,6 +130,53 @@ describe("SessionV2.create", () => {
}),
)
it.effect("filters root sessions before applying the page limit", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const { db } = yield* Database.Service
const staleRoot = yield* session.create({ location, title: "stale root" })
const root = yield* session.create({ location, title: "root" })
const children = yield* Effect.forEach(Array.from({ length: 60 }), (_, index) =>
session.create({ parentID: root.id, title: `child ${index}` }),
)
yield* Effect.forEach(children, (item, index) =>
db
.update(SessionTable)
.set({ time_created: index + 100, time_updated: index + 20_000 })
.where(eq(SessionTable.id, item.id))
.run(),
)
yield* db
.update(SessionTable)
.set({ time_created: 2, time_updated: 5_000 })
.where(eq(SessionTable.id, staleRoot.id))
.run()
yield* db
.update(SessionTable)
.set({ time_created: 1, time_updated: 10_000 })
.where(eq(SessionTable.id, root.id))
.run()
const page = yield* session.list({ directory: location.directory, parentID: null, limit: 1, order: "desc" })
expect(page.data.map((item) => item.id)).toEqual([root.id])
}),
)
it.effect("filters direct child sessions by parent ID", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const parent = yield* session.create({ location, title: "parent" })
const child = yield* session.create({ parentID: parent.id, title: "child" })
yield* session.create({ location, title: "other root" })
const page = yield* session.list({ parentID: parent.id })
expect(page.data.map((item) => item.id)).toEqual([child.id])
}),
)
it.effect("forks a session by replaying a durable fork event into copied projected rows", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service