fix: serialize paginated session searches
This commit is contained in:
@@ -28,6 +28,7 @@ The database is disposable but reset is guarded: every recognized schema version
|
||||
| `maxLimit` | `100` | Largest accepted request page size; at most `Number.MAX_SAFE_INTEGER - 1`. |
|
||||
| `snippetChars` | `240` | Maximum snippet length in Unicode code points. |
|
||||
| `readWindowMax` | `50` | Maximum `before` or `after` raw-event count for inherited `readEvent()`. |
|
||||
| `persistedInspectConcurrency` | `4` | Maximum concurrent persisted-log inspections for inherited batch reads; must be a positive safe integer. |
|
||||
|
||||
## Tokenizer and limits
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
SessionPersistenceSnapshot,
|
||||
} from '@deepseek-ai/dsh-session-persistence'
|
||||
import SessionQueryService, {
|
||||
SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
|
||||
SESSION_QUERY_READ_WINDOW_MAX,
|
||||
SessionQueryError,
|
||||
SessionSearchCursor,
|
||||
@@ -87,6 +88,8 @@ export interface Config extends SessionQueryConfig {
|
||||
maxLimit?: number
|
||||
/** Maximum snippet length in Unicode code points. Defaults to 240. */
|
||||
snippetChars?: number
|
||||
/** Maximum concurrent persisted-log inspections in one inherited batch read. Defaults to 4. */
|
||||
persistedInspectConcurrency?: number
|
||||
}
|
||||
|
||||
interface ResolvedConfig {
|
||||
@@ -96,6 +99,7 @@ interface ResolvedConfig {
|
||||
maxLimit: number
|
||||
snippetChars: number
|
||||
readWindowMax: number
|
||||
persistedInspectConcurrency: number
|
||||
}
|
||||
|
||||
interface ObservedSession {
|
||||
@@ -176,6 +180,11 @@ export class SessionQuerySqlite extends SessionQueryService {
|
||||
maxLimit: z.number().step(1).min(1).max(SQLITE_MAX_PAGE_LIMIT).default(SESSION_QUERY_SQLITE_MAX_LIMIT),
|
||||
snippetChars: z.number().step(1).min(1).default(SESSION_QUERY_SQLITE_SNIPPET_CHARS),
|
||||
readWindowMax: z.number().step(1).min(0).default(SESSION_QUERY_READ_WINDOW_MAX),
|
||||
persistedInspectConcurrency: z.number()
|
||||
.step(1)
|
||||
.min(1)
|
||||
.max(Number.MAX_SAFE_INTEGER)
|
||||
.default(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY),
|
||||
})
|
||||
|
||||
/** Validated and defaulted backend configuration. */
|
||||
@@ -937,6 +946,8 @@ function resolveConfig(config: Config): ResolvedConfig {
|
||||
maxLimit: config.maxLimit ?? SESSION_QUERY_SQLITE_MAX_LIMIT,
|
||||
snippetChars: config.snippetChars ?? SESSION_QUERY_SQLITE_SNIPPET_CHARS,
|
||||
readWindowMax: config.readWindowMax ?? SESSION_QUERY_READ_WINDOW_MAX,
|
||||
persistedInspectConcurrency: config.persistedInspectConcurrency
|
||||
?? SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
|
||||
}
|
||||
if (typeof resolved.path !== 'string' || resolved.path.trim().length === 0) {
|
||||
throw invalidConfig('path must not be blank')
|
||||
@@ -947,6 +958,12 @@ function resolveConfig(config: Config): ResolvedConfig {
|
||||
if (!Number.isInteger(resolved.readWindowMax) || resolved.readWindowMax < 0) {
|
||||
throw invalidConfig('readWindowMax must be a non-negative integer')
|
||||
}
|
||||
if (
|
||||
!Number.isSafeInteger(resolved.persistedInspectConcurrency)
|
||||
|| resolved.persistedInspectConcurrency < 1
|
||||
) {
|
||||
throw invalidConfig('persistedInspectConcurrency must be a positive safe integer')
|
||||
}
|
||||
if (resolved.defaultLimit > resolved.maxLimit) {
|
||||
throw invalidConfig('defaultLimit must be less than or equal to maxLimit')
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import SessionQuerySqlite, {
|
||||
SESSION_QUERY_SQLITE_SCHEMA_VERSION,
|
||||
} from '@deepseek-ai/dsh-session-query-sqlite'
|
||||
import {
|
||||
SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
|
||||
SessionQueryError,
|
||||
SessionSearchCursor,
|
||||
type SessionAvailability,
|
||||
@@ -167,6 +168,29 @@ async function liveContext(config: ConstructorParameters<typeof SessionQuerySqli
|
||||
}
|
||||
|
||||
describe('SQLite session search', () => {
|
||||
it('defaults and validates persisted inspection concurrency through its Cordis config', async () => {
|
||||
const defaultCtx = await liveContext()
|
||||
expect((defaultCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
|
||||
.toBe(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY)
|
||||
|
||||
const configuredValue = 2
|
||||
const configured = new SessionQuerySqlite.Config({
|
||||
path: ':memory:',
|
||||
persistedInspectConcurrency: configuredValue,
|
||||
})
|
||||
expect(configured.persistedInspectConcurrency).toBe(configuredValue)
|
||||
const configuredCtx = await liveContext(configured)
|
||||
expect((configuredCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
|
||||
.toBe(configuredValue)
|
||||
|
||||
for (const persistedInspectConcurrency of [0, Number.MAX_SAFE_INTEGER + 1]) {
|
||||
expect(() => new SessionQuerySqlite.Config({
|
||||
path: ':memory:',
|
||||
persistedInspectConcurrency,
|
||||
})).toThrow()
|
||||
}
|
||||
})
|
||||
|
||||
it('searches two-character Unicode61 tokens in live-only sessions', async () => {
|
||||
const ctx = await liveContext({ path: ':memory:', snippetChars: 20 })
|
||||
const session = ctx.sessions.create(SessionId('live'), {
|
||||
@@ -486,6 +510,8 @@ describe('SQLite session search', () => {
|
||||
{ path: ':memory:', maxLimit: 1e100 },
|
||||
{ path: ':memory:', snippetChars: 0 },
|
||||
{ path: ':memory:', readWindowMax: -1 },
|
||||
{ path: ':memory:', persistedInspectConcurrency: 0 },
|
||||
{ path: ':memory:', persistedInspectConcurrency: Number.MAX_SAFE_INTEGER + 1 },
|
||||
{ path: ':memory:', defaultLimit: 3, maxLimit: 2 },
|
||||
{ path: ':memory:', journalMode: 'memory' },
|
||||
]) {
|
||||
|
||||
Reference in New Issue
Block a user