History

Add Effect persistence runtime modules and wire service bootstrap

Cursor Agent committed 8 months ago

Commit 2cf729e1 parent 40d49f110 files · +80 −21Browse files

Unified · Split

10 files
  1. apps/server/src/persistence/config.ts +16 −0
  2. apps/server/src/persistence/errors.ts +6 −0
  3. apps/server/src/persistence/migrator.ts +6 −0
  4. apps/server/src/persistence/repos/documentsRepo.ts +1 −0
  5. apps/server/src/persistence/repos/metadataRepo.ts +1 −0
  6. apps/server/src/persistence/repos/providerEventsRepo.ts +1 −0
  7. apps/server/src/persistence/repos/stateEventsRepo.ts +1 −0
  8. apps/server/src/persistence/runtime.ts +19 −0
  9. apps/server/src/persistence/sqliteLayer.ts +10 −0
  10. apps/server/src/persistenceService.ts +19 −21
apps/server/src/persistence/config.ts+16−0
@@ -0,0 +1,16 @@
1+import path from "node:path";
2+
3+export interface PersistenceConfig {
4+ dbPath: string;
5+ legacyProjectsJsonPath?: string;
6+}
7+
8+export function resolvePersistenceConfig(input: PersistenceConfig): PersistenceConfig {
9+ const resolved: PersistenceConfig = {
10+ dbPath: path.resolve(input.dbPath),
11+ };
12+ if (input.legacyProjectsJsonPath) {
13+ resolved.legacyProjectsJsonPath = path.resolve(input.legacyProjectsJsonPath);
14+ }
15+ return resolved;
16+}
apps/server/src/persistence/errors.ts+6−0
@@ -0,0 +1,6 @@
1+export class PersistenceInitializationError extends Error {
2+ constructor(message: string, options?: { cause?: unknown }) {
3+ super(message, options);
4+ this.name = "PersistenceInitializationError";
5+ }
6+}
apps/server/src/persistence/migrator.ts+6−0
@@ -0,0 +1,6 @@
1+import type { SqliteDatabase } from "../sqliteAdapter";
2+import { runStateMigrations } from "../stateMigrations";
3+
4+export function runPersistenceMigrations(db: SqliteDatabase): void {
5+ runStateMigrations(db);
6+}
apps/server/src/persistence/repos/documentsRepo.ts+1−0
@@ -0,0 +1 @@
1+export * from "../documentsRepo";
apps/server/src/persistence/repos/metadataRepo.ts+1−0
@@ -0,0 +1 @@
1+export * from "../metadataRepo";
apps/server/src/persistence/repos/providerEventsRepo.ts+1−0
@@ -0,0 +1 @@
1+export * from "../providerEventsRepo";
apps/server/src/persistence/repos/stateEventsRepo.ts+1−0
@@ -0,0 +1 @@
1+export * from "../stateEventsRepo";
apps/server/src/persistence/runtime.ts+19−0
@@ -0,0 +1,19 @@
1+import type * as Effect from "effect/Effect";
2+import type * as SqlClient from "effect/unstable/sql/SqlClient";
3+
4+import type { EffectSqliteDatabaseAdapter, SqliteDatabase } from "../sqliteAdapter";
5+
6+function isEffectSqliteDatabase(db: SqliteDatabase): db is EffectSqliteDatabaseAdapter {
7+ return "runWithSqlClient" in db && typeof db.runWithSqlClient === "function";
8+}
9+
10+export function runWithSqlClient<A>(
11+ db: SqliteDatabase,
12+ effect: Effect.Effect<A, unknown, SqlClient.SqlClient>,
13+ fallback: () => A,
14+): A {
15+ if (isEffectSqliteDatabase(db)) {
16+ return db.runWithSqlClient(effect);
17+ }
18+ return fallback();
19+}
apps/server/src/persistence/sqliteLayer.ts+10−0
@@ -0,0 +1,10 @@
1+import fs from "node:fs";
2+import path from "node:path";
3+
4+import { openSqliteDatabase, type SqliteDatabase } from "../sqliteAdapter";
5+
6+export function openPersistenceSqliteDatabase(dbPath: string): SqliteDatabase {
7+ const resolvedPath = path.resolve(dbPath);
8+ fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
9+ return openSqliteDatabase(resolvedPath);
10+}
apps/server/src/persistenceService.ts+19−21
@@ -77,23 +77,23 @@ import { · 76 unmodified lines
7777 readNextSortKey as readNextSortKeyEffect,
7878 upsertDocument as upsertDocumentEffect,
7979 type DocumentRow,
80−} from "./persistence/documentsRepo";
80+} from "./persistence/repos/documentsRepo";
8181 import {
8282 insertProviderEvent as insertProviderEventEffect,
8383 listCompletedItemEventsBySessionTurn as listCompletedItemEventsBySessionTurnEffect,
8484 listCompletedItemEventsByThreadTurn as listCompletedItemEventsByThreadTurnEffect,
8585 listCompletedItemEventsByTurn as listCompletedItemEventsByTurnEffect,
8686 type CompletedProviderItemRow,
87−} from "./persistence/providerEventsRepo";
87+} from "./persistence/repos/providerEventsRepo";
8888 import {
8989 appendStateEvent as appendStateEventEffect,
9090 listStateEventsAfterSeq as listStateEventsAfterSeqEffect,
9191 readLastStateSeq as readLastStateSeqEffect,
92−} from "./persistence/stateEventsRepo";
92+} from "./persistence/repos/stateEventsRepo";
9393 import {
9494 readMetadataValue as readMetadataValueEffect,
9595 writeMetadataValue as writeMetadataValueEffect,
96−} from "./persistence/metadataRepo";
96+} from "./persistence/repos/metadataRepo";
9797 import {
9898 buildUpdatedAppSettings,
9999 resolveAppSettings,
@@ -119,8 +119,12 @@ import { · 19 unmodified lines
119119 } from "./persistence/domain/stateSync";
120120 import { fallbackGroupId, normalizeTerminalIds, normalizeThread } from "./persistence/domain/threads";
121121 import { mergeTurnSummaryFiles, summarizeUnifiedDiff } from "./persistence/domain/turnSummaries";
122−import { openSqliteDatabase, type EffectSqliteDatabaseAdapter, type SqliteDatabase } from "./sqliteAdapter";
123−import { runStateMigrations } from "./stateMigrations";
122+import { resolvePersistenceConfig } from "./persistence/config";
123+import { PersistenceInitializationError } from "./persistence/errors";
124+import { runPersistenceMigrations } from "./persistence/migrator";
125+import { runWithSqlClient } from "./persistence/runtime";
126+import { openPersistenceSqliteDatabase } from "./persistence/sqliteLayer";
127+import type { SqliteDatabase } from "./sqliteAdapter";
124128
125129 const METADATA_KEY_PROJECTS_JSON_IMPORTED = "migration.projects_json_imported";
126130 const METADATA_KEY_APP_SETTINGS = "app.settings.v1";
@@ -197,21 +201,22 @@ export class PersistenceService extends EventEmitter<PersistenceServiceEvents> { · 70 unmodified lines
197201
198202 constructor(options: PersistenceServiceOptions) {
199203 super();
200− const dbPath = path.resolve(options.dbPath);
201− fs.mkdirSync(path.dirname(dbPath), { recursive: true });
202− this.db = openSqliteDatabase(dbPath);
204+ const config = resolvePersistenceConfig(options);
205+ this.db = openPersistenceSqliteDatabase(config.dbPath);
203206 try {
204− runStateMigrations(this.db);
207+ runPersistenceMigrations(this.db);
205208 } catch (error) {
206209 try {
207210 this.db.close();
208211 } catch {
209212 // Best effort close on failed initialization.
210213 }
211− throw error;
214+ throw new PersistenceInitializationError("Failed to initialize persistence database", {
215+ cause: error,
216+ });
212217 }
213− if (options.legacyProjectsJsonPath) {
214− this.importProjectsJsonIfNeeded(options.legacyProjectsJsonPath);
218+ if (config.legacyProjectsJsonPath) {
219+ this.importProjectsJsonIfNeeded(config.legacyProjectsJsonPath);
215220 }
216221 }
217222
@@ -1716,13 +1721,6 @@ export class PersistenceService extends EventEmitter<PersistenceServiceEvents> { · 1498 unmodified lines
17161721 effect: Effect.Effect<A, unknown, SqlClient.SqlClient>,
17171722 fallback: () => A,
17181723 ): A {
1719− if (isEffectSqliteDatabase(this.db)) {
1720− return this.db.runWithSqlClient(effect);
1721− }
1722− return fallback();
1724+ return runWithSqlClient(this.db, effect, fallback);
17231725 }
17241726 }
1725−
1726−function isEffectSqliteDatabase(db: SqliteDatabase): db is EffectSqliteDatabaseAdapter {
1727− return "runWithSqlClient" in db && typeof db.runWithSqlClient === "function";
1728−}