refactor(session-query): unify query service

This commit is contained in:
Hypatia May
2026-07-23 20:16:14 +08:00
parent a2a89bf300
commit 1e457b22e0
48 changed files with 482 additions and 365 deletions

View File

@@ -3,7 +3,7 @@ import { Context } from 'cordis'
import { CallId } from '@deepseek-ai/dsh-llm'
import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
import SessionQueryService, {
import {
buildSessionEventRecords,
buildSessionEventSearchDocuments,
compileSessionTextFilter,
@@ -12,15 +12,9 @@ import SessionQueryService, {
filterSessionResults,
materializeSessionEventResultFilters,
materializeSessionResultFilters,
SessionSearchService,
type SessionEventSearchHit,
type SessionEventSearchRequest,
type SessionQueryErrorCode,
type SessionSearchExecContext,
type SessionSearchHit,
type SessionSearchPage,
type SessionSearchRequest,
} from '@deepseek-ai/dsh-session-query'
import { TestSessionQueryService } from './test-service.ts'
const id = SessionId('session')
@@ -203,10 +197,10 @@ describe('session-query document and filter helpers', () => {
.toThrow(expectCode('SESSION_QUERY_INVALID_FILTER'))
})
it('exposes the scan path on the concrete exact-read service', async () => {
it('exposes the scan path on the combined query service', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQueryService)
await ctx.plugin(TestSessionQueryService)
const session = ctx.sessions.create(id)
session.append('user/message', { content: [{ type: 'text', text: 'Alpha\n beta' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
session.append('user/message', { content: [{ type: 'text', text: 'other' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
@@ -215,21 +209,12 @@ describe('session-query document and filter helpers', () => {
})
})
class TestSearchService extends SessionSearchService {
searchSessions(_request: SessionSearchRequest, _exec?: SessionSearchExecContext): Promise<SessionSearchPage<SessionSearchHit>> {
return Promise.resolve({ items: [] })
}
searchEvents(_request: SessionEventSearchRequest, _exec?: SessionSearchExecContext): Promise<SessionSearchPage<SessionEventSearchHit>> {
return Promise.resolve({ items: [] })
}
}
it('registers the abstract search seam under its independent ctx key', async () => {
it('registers exact and abstract search behavior under one ctx key', async () => {
const ctx = new Context()
const fiber = await ctx.plugin(TestSearchService)
await expect(ctx.sessionSearch.searchSessions({ query: 'AI' })).resolves.toEqual({ items: [] })
await expect(ctx.sessionSearch.searchEvents({ sessionId: id, query: 'AI' })).resolves.toEqual({ items: [] })
await ctx.plugin(SessionStore)
const fiber = await ctx.plugin(TestSessionQueryService)
await expect(ctx.sessionQuery.searchSessions({ query: 'AI' })).resolves.toEqual({ items: [] })
await expect(ctx.sessionQuery.searchEvents({ sessionId: id, query: 'AI' })).resolves.toEqual({ items: [] })
await fiber.dispose()
expect(ctx.sessionSearch).toBeUndefined()
expect(ctx.sessionQuery).toBeUndefined()
})

View File

@@ -8,6 +8,7 @@ import SessionQueryService, {
type SessionQueryErrorCode,
} from '@deepseek-ai/dsh-session-query'
import { SessionTitleProviderId } from '@deepseek-ai/dsh-session-title'
import { TestSessionQueryService } from './test-service.ts'
function header(id: string, createdAt = 1, extra: Partial<SessionHeader> = {}): SessionHeader {
return { version: SESSION_FORMAT_VERSION, id: SessionId(id), createdAt, ...extra }
@@ -75,10 +76,10 @@ class TestPersistence extends SessionPersistence {
}
}
async function liveContext(config: ConstructorParameters<typeof SessionQueryService>[1] = {}): Promise<Context> {
async function liveContext(config: ConstructorParameters<typeof TestSessionQueryService>[1] = {}): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQueryService, config)
await ctx.plugin(TestSessionQueryService, config)
return ctx
}
@@ -413,18 +414,18 @@ describe('session-query exact reads', () => {
const direct = new Context()
await direct.plugin(SessionStore)
expect(new SessionQueryService(direct)).toBeInstanceOf(SessionQueryService)
expect(new TestSessionQueryService(direct)).toBeInstanceOf(SessionQueryService)
const invalid = new Context()
await invalid.plugin(SessionStore)
expect(() => new SessionQueryService(invalid, { readWindowMax: -1 }))
expect(() => new TestSessionQueryService(invalid, { readWindowMax: -1 }))
.toThrow(expectCode('SESSION_QUERY_INVALID_CONFIG'))
})
it('leaves the optional persistence dependency optional', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const fiber = await ctx.plugin(SessionQueryService)
expect(ctx.sessionQuery).toBeInstanceOf(SessionQueryService)
const fiber = await ctx.plugin(TestSessionQueryService)
expect(ctx.sessionQuery).toBeInstanceOf(TestSessionQueryService)
await fiber.dispose()
expect(ctx.sessionQuery).toBeUndefined()
})
@@ -433,7 +434,7 @@ describe('session-query exact reads', () => {
TestPersistence.reset()
const ctx = new Context()
await ctx.plugin(SessionStore)
const query = await ctx.plugin(SessionQueryService)
const query = await ctx.plugin(TestSessionQueryService)
const persistence = await ctx.plugin(TestPersistence)
const optional = (ctx.sessionQuery as unknown as {
_corpus: { _optionalPersistenceFiber: Fiber }

View File

@@ -0,0 +1,26 @@
import SessionQueryService from '@deepseek-ai/dsh-session-query'
import type {
SessionEventSearchHit,
SessionEventSearchRequest,
SessionSearchExecContext,
SessionSearchHit,
SessionSearchPage,
SessionSearchRequest,
} from '@deepseek-ai/dsh-session-query'
/** Test-only concrete query service for backend-independent behavior. */
export class TestSessionQueryService extends SessionQueryService {
override searchSessions(
_request: SessionSearchRequest,
_exec?: SessionSearchExecContext,
): Promise<SessionSearchPage<SessionSearchHit>> {
return Promise.resolve({ items: [] })
}
override searchEvents(
_request: SessionEventSearchRequest,
_exec?: SessionSearchExecContext,
): Promise<SessionSearchPage<SessionEventSearchHit>> {
return Promise.resolve({ items: [] })
}
}

View File

@@ -3,7 +3,8 @@ import { Context } from 'cordis'
import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
import type { Session, SessionEvent, SessionHeader, SessionId as SessionIdType } from '@deepseek-ai/dsh-session'
import SessionPersistence from '@deepseek-ai/dsh-session-persistence'
import SessionQueryService, { type SessionQueryErrorCode } from '@deepseek-ai/dsh-session-query'
import { type SessionQueryErrorCode } from '@deepseek-ai/dsh-session-query'
import { TestSessionQueryService } from './test-service.ts'
type MutableSessionHeader = { -readonly [K in keyof SessionHeader]: SessionHeader[K] }
@@ -84,7 +85,7 @@ class TracePersistence extends SessionPersistence {
async function queryContext(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(SessionQueryService)
await ctx.plugin(TestSessionQueryService)
return ctx
}