mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-03 17:44:38 +00:00
Replace exposed layer graph assembly with opaque declarations, checked substitutions, and lifetime-aware compilation. Preserve deep replacement, ordered startup, and Effect-owned resource lifetimes; migrate callers and verify source and published package contracts.
190 lines
6.7 KiB
TypeScript
190 lines
6.7 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { Effect } from "effect"
|
|
import { Bus } from "@opencode-ai/core/bus"
|
|
import { Database } from "@opencode-ai/core/database/database"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
|
import { SessionStore } from "@opencode-ai/core/session/store"
|
|
import { Event } from "@opencode-ai/schema/event"
|
|
import { Project } from "@opencode-ai/schema/project"
|
|
import { AbsolutePath } from "@opencode-ai/schema/schema"
|
|
import { Session } from "@opencode-ai/schema/session"
|
|
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
|
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const it = testEffect(
|
|
AppNodeBuilder.build(LayerNode.group([Database.node, Bus.node, SessionProjector.node, SessionStore.node]), [
|
|
Bus.node.replace(Bus.configured({ persist: true })),
|
|
]),
|
|
)
|
|
|
|
const seedSessions = (rows: { id: string; updated: number }[]) =>
|
|
Effect.gen(function* () {
|
|
const database = yield* Database.Service
|
|
const bus = yield* Bus.Service
|
|
const directory = AbsolutePath.make("/project")
|
|
yield* database.db.insert(ProjectTable).values({ id: Project.ID.global, worktree: directory, sandboxes: [] }).run()
|
|
yield* Effect.forEach(rows, (row) =>
|
|
Effect.gen(function* () {
|
|
const sessionID = Session.ID.make(row.id)
|
|
yield* bus.publish(SessionEvent.Created, {
|
|
sessionID,
|
|
projectID: Project.ID.global,
|
|
location: { directory },
|
|
slug: "store-test",
|
|
version: "test",
|
|
})
|
|
yield* bus.replay({
|
|
id: Event.ID.create(),
|
|
created: row.updated,
|
|
aggregateID: sessionID,
|
|
seq: 1,
|
|
type: Bus.versionedType(SessionEvent.Renamed.type, 1),
|
|
data: { sessionID, title: row.id },
|
|
})
|
|
}),
|
|
)
|
|
return bus
|
|
})
|
|
|
|
describe("SessionStore", () => {
|
|
it.effect("lists by updated time and ID with exclusive two-item pages in either direction", () =>
|
|
Effect.gen(function* () {
|
|
yield* seedSessions([
|
|
{ id: "ses_d", updated: 20 },
|
|
{ id: "ses_z", updated: 10 },
|
|
{ id: "ses_a", updated: 30 },
|
|
{ id: "ses_c", updated: 20 },
|
|
{ id: "ses_y", updated: 10 },
|
|
{ id: "ses_e", updated: 30 },
|
|
{ id: "ses_b", updated: 20 },
|
|
])
|
|
const store = yield* SessionStore.Service
|
|
expect((yield* store.list()).map((session) => String(session.id))).toEqual([
|
|
"ses_e",
|
|
"ses_a",
|
|
"ses_d",
|
|
"ses_c",
|
|
"ses_b",
|
|
"ses_z",
|
|
"ses_y",
|
|
])
|
|
expect((yield* store.list({ order: "asc" })).map((session) => String(session.id))).toEqual([
|
|
"ses_y",
|
|
"ses_z",
|
|
"ses_b",
|
|
"ses_c",
|
|
"ses_d",
|
|
"ses_a",
|
|
"ses_e",
|
|
])
|
|
const pages: { order: "asc" | "desc"; direction: "next" | "previous"; ids: string[] }[] = [
|
|
{ order: "asc", direction: "next", ids: ["ses_d", "ses_a"] },
|
|
{ order: "asc", direction: "previous", ids: ["ses_z", "ses_b"] },
|
|
{ order: "desc", direction: "next", ids: ["ses_b", "ses_z"] },
|
|
{ order: "desc", direction: "previous", ids: ["ses_a", "ses_d"] },
|
|
]
|
|
yield* Effect.forEach(pages, (page) =>
|
|
Effect.gen(function* () {
|
|
const sessions = yield* store.list({
|
|
order: page.order,
|
|
limit: 2,
|
|
anchor: { id: Session.ID.make("ses_c"), time: 20, direction: page.direction },
|
|
})
|
|
expect(sessions.map((session) => String(session.id))).toEqual(page.ids)
|
|
}),
|
|
)
|
|
}),
|
|
)
|
|
|
|
it.effect("pages messages by durable sequence, not timestamp or ID, and scopes cursor lookup", () =>
|
|
Effect.gen(function* () {
|
|
const sessionID = Session.ID.make("ses_messages")
|
|
const foreignID = Session.ID.make("ses_foreign")
|
|
const bus = yield* seedSessions([
|
|
{ id: sessionID, updated: 0 },
|
|
{ id: foreignID, updated: 0 },
|
|
])
|
|
const store = yield* SessionStore.Service
|
|
yield* Effect.forEach(
|
|
[
|
|
{ id: "evt_z", created: 300 },
|
|
{ id: "evt_b", created: 700 },
|
|
{ id: "evt_x", created: 100 },
|
|
{ id: "evt_c", created: 400 },
|
|
{ id: "evt_w", created: 200 },
|
|
{ id: "evt_a", created: 600 },
|
|
{ id: "evt_y", created: 500 },
|
|
],
|
|
(event, index) =>
|
|
bus.replay({
|
|
id: Event.ID.make(event.id),
|
|
created: event.created,
|
|
aggregateID: sessionID,
|
|
seq: index + 2,
|
|
type: Bus.versionedType(SessionEvent.Synthetic.type, 1),
|
|
data: { sessionID, text: event.id },
|
|
}),
|
|
)
|
|
yield* bus.publish(
|
|
SessionEvent.Synthetic,
|
|
{ sessionID: foreignID, text: "foreign" },
|
|
{
|
|
id: Event.ID.make("evt_foreign"),
|
|
},
|
|
)
|
|
expect((yield* store.messages({ sessionID })).map((message) => String(message.id))).toEqual([
|
|
"msg_y",
|
|
"msg_a",
|
|
"msg_w",
|
|
"msg_c",
|
|
"msg_x",
|
|
"msg_b",
|
|
"msg_z",
|
|
])
|
|
expect((yield* store.messages({ sessionID, order: "asc" })).map((message) => String(message.id))).toEqual([
|
|
"msg_z",
|
|
"msg_b",
|
|
"msg_x",
|
|
"msg_c",
|
|
"msg_w",
|
|
"msg_a",
|
|
"msg_y",
|
|
])
|
|
const pages: { order: "asc" | "desc"; direction: "next" | "previous"; ids: string[] }[] = [
|
|
{ order: "asc", direction: "next", ids: ["msg_w", "msg_a"] },
|
|
{ order: "asc", direction: "previous", ids: ["msg_b", "msg_x"] },
|
|
{ order: "desc", direction: "next", ids: ["msg_x", "msg_b"] },
|
|
{ order: "desc", direction: "previous", ids: ["msg_a", "msg_w"] },
|
|
]
|
|
yield* Effect.forEach(pages, (page) =>
|
|
Effect.gen(function* () {
|
|
const messages = yield* store.messages({
|
|
sessionID,
|
|
order: page.order,
|
|
limit: 2,
|
|
cursor: { id: SessionMessage.ID.make("msg_c"), direction: page.direction },
|
|
})
|
|
expect(messages.map((message) => String(message.id))).toEqual(page.ids)
|
|
}),
|
|
)
|
|
expect(yield* store.messages({ sessionID: Session.ID.make("ses_missing") })).toEqual([])
|
|
expect(
|
|
yield* store.messages({
|
|
sessionID,
|
|
cursor: { id: SessionMessage.ID.make("msg_missing"), direction: "next" },
|
|
}),
|
|
).toEqual([])
|
|
expect(
|
|
yield* store.messages({
|
|
sessionID,
|
|
order: "asc",
|
|
cursor: { id: SessionMessage.ID.make("msg_foreign"), direction: "next" },
|
|
}),
|
|
).toEqual([])
|
|
}),
|
|
)
|
|
})
|