refactor: add type annotations and clean up code - #102
Conversation
- Add type hints to Python functions and TypeScript modules - Fix variable reference bug in egraph.ts lhs/rhs extraction - Simplify input validation logic in input/load modules - Remove unused imports across modules - Improve database initialization flow in main.ts
There was a problem hiding this comment.
Pull request overview
Refactors the DDSS CLI/components to add type annotations (TypeScript + Python) and simplify component wiring while cleaning up some rule parsing/extraction logic.
Changes:
- Added/updated type annotations across TS and Python entrypoints/modules.
- Centralized DB initialization in
ddss/main.tsand standardized componentmain()signatures to accept aSequelize/sessionmaker. - Fixed
egraph.tsLHS/RHS extraction logic and simplified input validation in Python loaders/interactive input.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ddss/utility.ts | Adds return type to patchStdout (unpatch function). |
| ddss/search.ts | Removes unused DB init import; adds return type; minor variable cleanup. |
| ddss/search.py | Adds async_sessionmaker[AsyncSession] annotation to main. |
| ddss/output.ts | Removes unused DB init import; adds return type. |
| ddss/output.py | Adds async_sessionmaker[AsyncSession] annotation to main. |
| ddss/orm.ts | Removes unused type import. |
| ddss/main.ts | Standardizes component signatures and moves DB init into run; makes cli async. |
| ddss/main.py | Adds typing for component map; changes unsupported-component handling to return. |
| ddss/load.ts | Refactors parsing/insertion path (currently introduces a type mismatch bug). |
| ddss/load.py | Simplifies empty/comment line filtering; adds sessionmaker type annotation. |
| ddss/input.ts | Refactors parsing/insertion path (currently introduces a type mismatch bug). |
| ddss/input.py | Simplifies empty/comment line filtering; adds sessionmaker type annotation. |
| ddss/egraph.ts | Fixes LHS/RHS extraction to use the correct list term. |
| ddss/egg.ts | Removes unused DB init import; adds return type. |
| ddss/egg.py | Adds async_sessionmaker[AsyncSession] annotation to main. |
| ddss/dump.ts | Changes main to require Sequelize (aligned with centralized DB init). |
| ddss/dump.py | Adds async_sessionmaker[AsyncSession] annotation to main. |
Comments suppressed due to low confidence (1)
ddss/main.ts:88
cli()is nowasync, but theimport.meta.mainblock calls it withoutawait. In Deno/ESM this can cause the program to exit before the async action finishes, and any rejections become unhandled. Consider usingawait cli()(top-level await is supported) and switching toprogram.parseAsync(...)so Commander waits for the async.actionhandler.
export async function cli(): Promise<void> {
const program = new Command();
program
.name("ddss")
.description("DDSS - Distributed Deductive System Sorts: Run DDSS with an interactive deductive environment.")
.option("-a, --addr <url>", "Database address URL. If not provided, uses a temporary SQLite database.")
.option("-c, --component <names...>", "Components to run.", ["input", "output", "search", "egg"])
.action(async (options) => {
let addr = options.addr;
let tmpDir: string | undefined;
if (!addr) {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "ddss-"));
const dbPath = path.join(tmpDir, "ddss.db");
addr = `sqlite:///${dbPath}`;
}
console.log(`addr: ${addr}`);
if (addr.startsWith("sqlite://")) {
addr = addr.replace("sqlite:///", "sqlite:");
} else if (addr.startsWith("mysql://")) {
// Nothing to change
} else if (addr.startsWith("mariadb://")) {
// Nothing to change
} else if (addr.startsWith("postgresql://")) {
addr = addr.replace("postgresql://", "postgres://");
} else {
console.error(`error: unsupported database: ${addr}`);
return;
}
await run(addr, options.component);
if (tmpDir) {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
program.parse();
}
if (import.meta.main) {
cli();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| const ds = parse(data); | ||
| const dsStr = ds.toString(); | ||
|
|
||
| await insertOrIgnore(Fact, dsStr); | ||
| const idea = strRuleGetStrIdea(dsStr); | ||
| await insertOrIgnore(Fact, ds); | ||
| const idea = strRuleGetStrIdea(ds); | ||
| if (idea) { | ||
| await insertOrIgnore(Idea, idea); |
There was a problem hiding this comment.
parse() returns a non-string value here, but insertOrIgnore expects data: string and strRuleGetStrIdea expects data: string. Passing ds directly will fail type-checking (and likely store [object Object] at runtime). Convert ds to a string before inserting/deriving the idea (e.g., use ds.toString() consistently as in other modules).
| const ds = parse(data); | ||
| const dsStr = ds.toString(); | ||
|
|
||
| await insertOrIgnore(Fact, dsStr); | ||
| const idea = strRuleGetStrIdea(dsStr); | ||
| await insertOrIgnore(Fact, ds); | ||
| const idea = strRuleGetStrIdea(ds); | ||
| if (idea) { | ||
| await insertOrIgnore(Idea, idea); |
There was a problem hiding this comment.
Same issue as in load.ts: insertOrIgnore and strRuleGetStrIdea both take string, but this code passes the parse() result directly. Convert the parsed value to a string (e.g., ds.toString()) before inserting/extracting the idea.
| async function run(addr: string, components: string[]): Promise<void> { | ||
| const sequelize = await initializeDatabase(addr); | ||
|
|
||
| for (const name of components) { | ||
| if (!(name in componentMap)) { | ||
| console.error(`error: unsupported component: ${name}`); | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
initializeDatabase(addr) is executed before validating components. If an unsupported component is provided, run() returns early without closing the Sequelize connection, leaking resources and possibly leaving a temp DB file locked. Validate components before initializing the database, or ensure sequelize.close() runs on all early-return paths (e.g., via try/finally).
| component_map: dict[str, callable[[async_sessionmaker[AsyncSession]], Awaitable[None]]] = { | ||
| "search": search, | ||
| "egg": egg, | ||
| "input": input, |
There was a problem hiding this comment.
The type annotation uses callable[...], but callable is a built-in function, not a typing construct. This will fail type checking. Use typing.Callable[[async_sessionmaker[AsyncSession]], Awaitable[None]] (and import Callable) for component_map’s value type.
close #100