| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- /**
- * Opt-in SQLite persistence provider. Logical sessions remain unchanged;
- * the physical backend packs eligible chunk runs into schema-17 rows.
- * @module @deepseek-ai/dsh-session-persistence-sqlite
- */
- import { Context, Service } from '@deepseek-ai/cordis'
- import z from '@deepseek-ai/schemastery'
- import type {
- Session,
- SessionEvent,
- SessionHeader,
- SessionId,
- SessionPreparation,
- } from '@deepseek-ai/dsh-session'
- import {
- DEFAULT_PREPARED_SESSION_CACHE_SIZE,
- DEFAULT_WRITE_BATCH_MAX_DELAY_MS,
- MAX_WRITE_BATCH_DELAY_MS,
- type BorrowedSessionSource,
- PersistenceCoordinator,
- SessionPersistence,
- type SessionInspection,
- type SessionLocation,
- type SessionPersistenceSnapshot,
- } from '@deepseek-ai/dsh-session-persistence'
- import type { JournalMode } from './schema.ts'
- import { SqliteStore } from './store.ts'
- export { SCHEMA_VERSION } from './schema.ts'
- /** Default wait for another SQLite connection's write reservation. */
- export const DEFAULT_BUSY_TIMEOUT_MS = 5_000
- /** Largest busy timeout accepted by SQLite's signed millisecond interface. */
- export const MAX_BUSY_TIMEOUT_MS = 2_147_483_647
- /** Plugin configuration. */
- export interface Config {
- /** SQLite database path, or `:memory:` for an in-process database. */
- path: string
- /** Durable SQLite journal mode; defaults to `wal`. */
- journalMode?: JournalMode
- /** Maximum wait for another SQLite connection's lock; defaults to 5,000 ms. */
- busyTimeoutMs?: number
- /** Maximum cold Session preparations retained for history-to-resume reuse. */
- preparedSessionCacheSize?: number
- /** Fixed live-event coalescing window; not a backend completion deadline. */
- writeBatchMaxDelayMs?: number
- }
- /**
- * SQLite `SessionPersistence` provider with a schema-owned physical codec.
- */
- export class SqliteSessionPersistence extends SessionPersistence {
- override readonly supportsRawArtifacts = false
- override readonly name = 'session-persistence-sqlite'
- static inject = ['sessions']
- static Config: z<Config> = z.object({
- path: z.string().required(),
- journalMode: z.union(['wal', 'delete', 'truncate', 'persist'] as const).default('wal'),
- busyTimeoutMs: z.number().step(1).min(0).max(MAX_BUSY_TIMEOUT_MS).default(DEFAULT_BUSY_TIMEOUT_MS),
- preparedSessionCacheSize: z.number().step(1).min(1).default(DEFAULT_PREPARED_SESSION_CACHE_SIZE),
- writeBatchMaxDelayMs: z.number().step(1).min(1).max(MAX_WRITE_BATCH_DELAY_MS)
- .default(DEFAULT_WRITE_BATCH_MAX_DELAY_MS),
- })
- private readonly store: SqliteStore
- private readonly coordinator: PersistenceCoordinator<number>
- constructor(ctx: Context, public config: Config) {
- super(ctx)
- const preparedSessionCacheSize = config.preparedSessionCacheSize
- ?? DEFAULT_PREPARED_SESSION_CACHE_SIZE
- const writeBatchMaxDelayMs = config.writeBatchMaxDelayMs
- ?? DEFAULT_WRITE_BATCH_MAX_DELAY_MS
- this.store = new SqliteStore({
- path: config.path,
- journalMode: config.journalMode ?? 'wal',
- busyTimeoutMs: config.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS,
- })
- this.coordinator = new PersistenceCoordinator(this.ctx, this.store, {
- preparedSessionCacheSize,
- writeBatchMaxDelayMs,
- })
- }
- /** Reject self-contained path and ownership failures without loading Node SQLite. */
- protected async [Service.init](): Promise<void> {
- await this.store.validatePath()
- }
- /** SQLite has one database, not an independent per-session artifact. */
- locate(_meta: SessionHeader): SessionLocation | undefined {
- return undefined
- }
- create(meta: SessionHeader): Promise<void> {
- return this.coordinator.create(meta)
- }
- override ensureMaterialized(session: Session): Promise<void> {
- return this.coordinator.ensureMaterialized(session)
- }
- append(id: SessionId, events: readonly SessionEvent[]): Promise<void> {
- return this.coordinator.append(id, events)
- }
- override prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation> {
- return this.coordinator.prepare(id, signal)
- }
- load(id: SessionId): Promise<SessionInspection> {
- return this.coordinator.load(id)
- }
- inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection> {
- return this.coordinator.inspect(id, signal)
- }
- override borrowSession(id: SessionId, signal?: AbortSignal): Promise<BorrowedSessionSource> {
- return this.coordinator.borrowSession(id, signal)
- }
- readFrom(
- id: SessionId,
- fromSeq: number,
- signal?: AbortSignal,
- ): Promise<{ meta: SessionHeader; events: SessionEvent[] }> {
- return this.coordinator.readFrom(id, fromSeq, signal)
- }
- list(signal?: AbortSignal): Promise<SessionHeader[]> {
- return this.store.list(signal)
- }
- listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]> {
- return this.store.listSnapshots(signal)
- }
- }
- export default SqliteSessionPersistence
|