Skip to content

Commit 72c3725

Browse files
srousseyclaude
andcommitted
feat(cli-v2): implement db status and db stats commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f7b030f commit 72c3725

3 files changed

Lines changed: 207 additions & 4 deletions

File tree

src/cli/groups/db.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Command } from "commander";
22
import { setupAllDatabases } from "../../config/setupAllDatabases";
33
import { runCommand } from "../runCommand";
4+
import { getDbStatus, getDbStats } from "../queries/DbStatus";
5+
import { renderTable } from "../output/TableRenderer";
46

57
export function addDbCommands(program: Command): void {
68
const db = program.command("db").description("Database management commands");
@@ -15,14 +17,45 @@ export function addDbCommands(program: Command): void {
1517

1618
db.command("status")
1719
.description("Show database connection status")
18-
.action(async () => {
19-
console.log("not yet implemented");
20+
.option("--format <format>", "Output format (table, json)", "table")
21+
.action(async (options: Record<string, string>) => {
22+
await runCommand(async () => {
23+
const status = await getDbStatus();
24+
25+
if (options.format === "json") {
26+
console.log(JSON.stringify(status, null, 2));
27+
return;
28+
}
29+
30+
const fmt = (n: number): string => n.toLocaleString();
31+
console.log("Database Status\n");
32+
console.log(` Entities: ${fmt(status.entityCount)}`);
33+
console.log(` Filings: ${fmt(status.filingCount)}`);
34+
console.log(` Company Facts: ${fmt(status.factsCount)}`);
35+
console.log(` Processed Submissions: ${fmt(status.processedSubmissions)}`);
36+
console.log(` Processed Facts: ${fmt(status.processedFacts)}`);
37+
console.log(` Processed Filings: ${fmt(status.processedFilings)}`);
38+
});
2039
});
2140

2241
db.command("stats")
2342
.description("Show row counts and database size")
24-
.action(async () => {
25-
console.log("not yet implemented");
43+
.option("--format <format>", "Output format (table, json)", "table")
44+
.action(async (options: Record<string, string>) => {
45+
await runCommand(async () => {
46+
const stats = await getDbStats();
47+
48+
const columns = [
49+
{ key: "table", header: "Table", width: 25 },
50+
{ key: "rows", header: "Rows", width: 12 },
51+
];
52+
53+
console.log(
54+
renderTable(stats as unknown as Record<string, unknown>[], columns, {
55+
format: (options.format as "table" | "json") ?? "table",
56+
})
57+
);
58+
});
2659
});
2760

2861
db.command("reset")

src/cli/queries/DbStatus.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { beforeEach, describe, expect, it } from "bun:test";
2+
import { resetDependencyInjectionsForTesting } from "../../config/TestingDI";
3+
import { globalServiceRegistry } from "@workglow/util";
4+
import { ENTITY_REPOSITORY_TOKEN } from "../../storage/entity/EntitySchema";
5+
import { getDbStatus, getDbStats } from "./DbStatus";
6+
7+
describe("getDbStatus", () => {
8+
beforeEach(() => {
9+
resetDependencyInjectionsForTesting();
10+
});
11+
12+
it("returns zero counts for empty db", async () => {
13+
const result = await getDbStatus();
14+
expect(result.entityCount).toBe(0);
15+
expect(result.filingCount).toBe(0);
16+
expect(result.factsCount).toBe(0);
17+
expect(result.processedSubmissions).toBe(0);
18+
expect(result.processedFacts).toBe(0);
19+
expect(result.processedFilings).toBe(0);
20+
});
21+
22+
it("counts entities after insertion", async () => {
23+
const repo = globalServiceRegistry.get(ENTITY_REPOSITORY_TOKEN);
24+
await repo.put({
25+
cik: 1318605,
26+
name: "Tesla, Inc.",
27+
type: null,
28+
sic: 3711,
29+
ein: null,
30+
description: null,
31+
website: null,
32+
investor_website: null,
33+
category: null,
34+
fiscal_year: null,
35+
state_incorporation: "TX",
36+
state_incorporation_desc: null,
37+
});
38+
39+
const result = await getDbStatus();
40+
expect(result.entityCount).toBe(1);
41+
});
42+
});
43+
44+
describe("getDbStats", () => {
45+
beforeEach(() => {
46+
resetDependencyInjectionsForTesting();
47+
});
48+
49+
it("returns array of table stats", async () => {
50+
const result = await getDbStats();
51+
expect(Array.isArray(result)).toBe(true);
52+
expect(result.length).toBeGreaterThanOrEqual(10);
53+
});
54+
55+
it("each stat has table and rows properties", async () => {
56+
const result = await getDbStats();
57+
for (const stat of result) {
58+
expect(typeof stat.table).toBe("string");
59+
expect(typeof stat.rows).toBe("number");
60+
}
61+
});
62+
});

src/cli/queries/DbStatus.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { globalServiceRegistry } from "@workglow/util";
2+
import {
3+
ENTITY_REPOSITORY_TOKEN,
4+
} from "../../storage/entity/EntitySchema";
5+
import {
6+
FILING_REPOSITORY_TOKEN,
7+
} from "../../storage/filing/FilingSchema";
8+
import {
9+
COMPANY_FACTS_REPOSITORY_TOKEN,
10+
} from "../../storage/facts/CompanyFactsSchema";
11+
import {
12+
PROCESSED_SUBMISSIONS_REPOSITORY_TOKEN,
13+
} from "../../storage/processing/ProcessedSubmissionsSchema";
14+
import {
15+
PROCESSED_FACTS_REPOSITORY_TOKEN,
16+
} from "../../storage/processing/ProcessedFactsSchema";
17+
import {
18+
PROCESSED_FILINGS_REPOSITORY_TOKEN,
19+
} from "../../storage/processing/ProcessedFilingsSchema";
20+
import {
21+
INVESTMENT_OFFERING_REPOSITORY_TOKEN,
22+
} from "../../storage/investment-offering/InvestmentOfferingSchema";
23+
import {
24+
CROWDFUNDING_REPOSITORY_TOKEN,
25+
} from "../../storage/portal/CrowdfundingSchema";
26+
import {
27+
PERSON_REPOSITORY_TOKEN,
28+
} from "../../storage/person/PersonSchema";
29+
import {
30+
ADDRESS_REPOSITORY_TOKEN,
31+
} from "../../storage/address/AddressSchema";
32+
import {
33+
PHONE_REPOSITORY_TOKEN,
34+
} from "../../storage/phone/PhoneSchema";
35+
import {
36+
COMPANY_REPOSITORY_TOKEN,
37+
} from "../../storage/company/CompanySchema";
38+
import {
39+
PORTAL_REPOSITORY_TOKEN,
40+
} from "../../storage/portal/PortalSchema";
41+
import type { ServiceToken } from "@workglow/util";
42+
43+
export interface DbStatusResult {
44+
readonly entityCount: number;
45+
readonly filingCount: number;
46+
readonly factsCount: number;
47+
readonly processedSubmissions: number;
48+
readonly processedFacts: number;
49+
readonly processedFilings: number;
50+
}
51+
52+
export interface TableStat {
53+
readonly table: string;
54+
readonly rows: number;
55+
}
56+
57+
async function countRows(token: ServiceToken<{ getAll(): Promise<unknown[]> }>): Promise<number> {
58+
const repo = globalServiceRegistry.get(token);
59+
const all = (await repo.getAll()) ?? [];
60+
return all.length;
61+
}
62+
63+
export async function getDbStatus(): Promise<DbStatusResult> {
64+
const [entityCount, filingCount, factsCount, processedSubmissions, processedFacts, processedFilings] =
65+
await Promise.all([
66+
countRows(ENTITY_REPOSITORY_TOKEN as any),
67+
countRows(FILING_REPOSITORY_TOKEN as any),
68+
countRows(COMPANY_FACTS_REPOSITORY_TOKEN as any),
69+
countRows(PROCESSED_SUBMISSIONS_REPOSITORY_TOKEN as any),
70+
countRows(PROCESSED_FACTS_REPOSITORY_TOKEN as any),
71+
countRows(PROCESSED_FILINGS_REPOSITORY_TOKEN as any),
72+
]);
73+
74+
return {
75+
entityCount,
76+
filingCount,
77+
factsCount,
78+
processedSubmissions,
79+
processedFacts,
80+
processedFilings,
81+
};
82+
}
83+
84+
const TABLE_TOKENS: ReadonlyArray<{
85+
readonly table: string;
86+
readonly token: ServiceToken<{ getAll(): Promise<unknown[]> }>;
87+
}> = [
88+
{ table: "entity", token: ENTITY_REPOSITORY_TOKEN as any },
89+
{ table: "filing", token: FILING_REPOSITORY_TOKEN as any },
90+
{ table: "company_facts", token: COMPANY_FACTS_REPOSITORY_TOKEN as any },
91+
{ table: "investment_offering", token: INVESTMENT_OFFERING_REPOSITORY_TOKEN as any },
92+
{ table: "crowdfunding", token: CROWDFUNDING_REPOSITORY_TOKEN as any },
93+
{ table: "person", token: PERSON_REPOSITORY_TOKEN as any },
94+
{ table: "address", token: ADDRESS_REPOSITORY_TOKEN as any },
95+
{ table: "phone", token: PHONE_REPOSITORY_TOKEN as any },
96+
{ table: "company", token: COMPANY_REPOSITORY_TOKEN as any },
97+
{ table: "portal", token: PORTAL_REPOSITORY_TOKEN as any },
98+
];
99+
100+
export async function getDbStats(): Promise<TableStat[]> {
101+
const results = await Promise.all(
102+
TABLE_TOKENS.map(async ({ table, token }) => {
103+
const rows = await countRows(token);
104+
return { table, rows };
105+
})
106+
);
107+
return results;
108+
}

0 commit comments

Comments
 (0)