refactor(database): split 5 god modules into domain-scoped files

Five files under `src/main/database/` exceeded 400 LOC by combining
multiple responsibilities. Split along natural fault lines. Zero
behavior change — each original `build*Methods` factory still returns
the same method surface, now via spread of smaller builders.

| Original                          | Split into                          | LOC each       |
|-----------------------------------|-------------------------------------|----------------|
| tasks-crud.ts (640)               | tasks-crud / task-sessions-crud /   | 250 / 252 /    |
|                                   | task-queries                        | 177            |
| agents-hierarchy.ts (487)         | agents-hierarchy / agent-permissions| 350 / 151      |
| company-settings.ts (476)         | company-settings / company-metrics  | 122 / 370      |
| documents.ts (426)                | documents / config-revisions        | 318 / 126      |
| runs.ts (413)                     | runs / run-logs / run-events /      | 245 / 96 /     |
|                                   | cost-entries                        | 82 / 52        |

AppDatabase call sites unchanged:
- db.tasks.saveTask, db.tasks.ensureAgentSession (task-sessions-crud),
  db.tasks.listAgentTasks (task-queries)
- db.agents.getChainOfCommand, db.agents.hasAgentPermission
- db.company.getCompanySettings, db.company.getStandupReport
  (company-metrics)
- db.documentsMod.saveDocument, db.documentsMod.recordConfigRevision
- db.runs.createRun, db.runs.appendRunLog (run-logs),
  db.runs.appendRunEvent (run-events), db.runs.recordCostEntry
  (cost-entries)

`database-base.ts` (449) kept as-is — thin facade where every
pass-through is one line; splitting adds mechanical files without
behavior benefit. Will revisit if it grows further.

Aggregator files (`tasks.ts`, `agents.ts`, `company.ts`) each import
and spread the new sub-builders. No renamed exports.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-17 16:35:19 -04:00
1 parent f012f8e880
commit dbdec86cdd
16 files changed
+1329 -1172

No files matched your search

+4 -112
View File
@@ -1,8 +1,8 @@
import { randomUUID } from "node:crypto";
import type { DatabaseSync, SQLInputValue } from "node:sqlite";
import type { AgentRecord } from "@shared/types";
import type { ReadAll, SqliteRow, Transaction } from "./helpers";
import { ensureId, nowIso, parseJson } from "./helpers";
import type { ReadAll, Transaction } from "./helpers";
import { ensureId, nowIso } from "./helpers";
import { buildConfigRevisionMethods } from "./config-revisions";
/**
* Document row shape returned by listDocuments — used by
@@ -53,6 +53,7 @@ export function buildDocumentMethods(
transaction: Transaction,
) {
return {
...buildConfigRevisionMethods(getDb, readAll, transaction),
saveDocument(input: {
id?: string | null; companyId: string; type: string; title: string; content?: string;
authorAgentId?: string | null; reviewerAgentId?: string | null; projectId?: string | null;
@@ -313,114 +314,5 @@ export function buildDocumentMethods(
}));
},
// ── Config Revisions ──
recordConfigRevision(input: {
companyId: string; agentId: string; changedKeys: string[];
configBefore: Record<string, unknown>; configAfter: Record<string, unknown>;
source?: string; rolledBackFromRevisionId?: string | null;
createdByAgentId?: string | null; createdByUserId?: string | null;
}): string {
const id = randomUUID();
const ts = nowIso();
const lastRev = getDb().prepare(
"SELECT MAX(revision_number) as maxRev FROM agent_config_revisions WHERE agent_id = ?"
).get(input.agentId) as { maxRev: number | null } | undefined;
const revisionNumber = (lastRev?.maxRev ?? 0) + 1;
getDb().prepare(
"INSERT INTO agent_config_revisions (id, company_id, agent_id, revision_number, changed_keys, config_before, config_after, source, rolled_back_from_revision_id, created_by_agent_id, created_by_user_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
).run(
id, input.companyId, input.agentId, revisionNumber,
JSON.stringify(input.changedKeys), JSON.stringify(input.configBefore),
JSON.stringify(input.configAfter), input.source ?? "patch",
input.rolledBackFromRevisionId ?? null, input.createdByAgentId ?? null,
input.createdByUserId ?? null, ts
);
return id;
},
listConfigRevisions(agentId: string, companyId: string): Array<{
id: string; agentId: string; revisionNumber: number; changedKeys: string[];
source: string; rolledBackFromRevisionId: string | null;
createdByAgentId: string | null; createdByUserId: string | null; createdAt: string;
}> {
return readAll<SqliteRow>(
"SELECT id, agent_id, revision_number, changed_keys, source, rolled_back_from_revision_id, created_by_agent_id, created_by_user_id, created_at FROM agent_config_revisions WHERE agent_id = ? AND company_id = ? ORDER BY revision_number DESC",
agentId, companyId
).map((row) => ({
id: row.id, agentId: row.agent_id, revisionNumber: row.revision_number,
changedKeys: parseJson<string[]>(row.changed_keys, []),
source: row.source, rolledBackFromRevisionId: row.rolled_back_from_revision_id ?? null,
createdByAgentId: row.created_by_agent_id ?? null,
createdByUserId: row.created_by_user_id ?? null, createdAt: row.created_at,
}));
},
getConfigRevision(revisionId: string, companyId: string): {
id: string; agentId: string; revisionNumber: number; changedKeys: string[];
configBefore: Record<string, unknown>; configAfter: Record<string, unknown>;
source: string; rolledBackFromRevisionId: string | null;
createdByAgentId: string | null; createdByUserId: string | null; createdAt: string;
} {
const row = getDb().prepare(
"SELECT * FROM agent_config_revisions WHERE id = ? AND company_id = ?"
).get(revisionId, companyId) as {
id: string; agent_id: string; revision_number: number; changed_keys: string;
config_before: string; config_after: string; source: string;
rolled_back_from_revision_id: string | null;
created_by_agent_id: string | null; created_by_user_id: string | null; created_at: string;
} | undefined;
if (!row) throw new Error(`Config revision ${revisionId} not found.`);
return {
id: row.id, agentId: row.agent_id, revisionNumber: row.revision_number,
changedKeys: parseJson<string[]>(row.changed_keys, []),
configBefore: parseJson<Record<string, unknown>>(row.config_before, {}),
configAfter: parseJson<Record<string, unknown>>(row.config_after, {}),
source: row.source, rolledBackFromRevisionId: row.rolled_back_from_revision_id ?? null,
createdByAgentId: row.created_by_agent_id ?? null,
createdByUserId: row.created_by_user_id ?? null, createdAt: row.created_at,
};
},
rollbackConfigRevision(agentId: string, revisionId: string, companyId: string, selfDeps: {
getConfigRevision: (revisionId: string, companyId: string) => {
id: string; agentId: string; revisionNumber: number; changedKeys: string[];
configBefore: Record<string, unknown>; configAfter: Record<string, unknown>;
source: string; rolledBackFromRevisionId: string | null;
createdByAgentId: string | null; createdByUserId: string | null; createdAt: string;
};
getAgent: (id: string) => AgentRecord;
recordConfigRevision: (input: {
companyId: string; agentId: string; changedKeys: string[];
configBefore: Record<string, unknown>; configAfter: Record<string, unknown>;
source?: string; rolledBackFromRevisionId?: string | null;
createdByAgentId?: string | null; createdByUserId?: string | null;
}) => string;
}): string {
return transaction(() => {
const revision = selfDeps.getConfigRevision(revisionId, companyId);
if (revision.agentId !== agentId) throw new Error("Revision does not belong to this agent.");
const before = revision.configBefore as Record<string, string | number | null>;
const ts = nowIso();
getDb().prepare(
"UPDATE agents SET name = ?, role = ?, title = ?, department = ?, capabilities = ?, connector_id = ?, model = ?, budget_monthly_usd = ?, metadata_json = ?, updated_at = ? WHERE id = ? AND company_id = ?"
).run(
before.name ?? "", before.role ?? "", before.title ?? "", before.department ?? null,
before.capabilities ?? "", before.connectorId ?? before.connector_id ?? "",
before.model ?? null, before.budgetMonthlyUsd ?? before.budget_monthly_usd ?? 0,
before.metadataJson ?? before.metadata_json ?? "{}", ts, agentId, companyId
);
const agent = selfDeps.getAgent(agentId);
const currentConfig = { name: agent.name, role: agent.role, title: agent.title, department: agent.department, capabilities: agent.capabilities, connectorId: agent.connectorId, model: agent.model, budgetMonthlyUsd: agent.budgetMonthlyUsd, metadataJson: agent.metadataJson };
return selfDeps.recordConfigRevision({
companyId, agentId,
changedKeys: revision.changedKeys,
configBefore: currentConfig,
configAfter: before,
source: "rollback",
rolledBackFromRevisionId: revisionId,
});
});
},
};
}