TypeScript patterns and conventions for IntellyWeave CLI.
- Target: ES2022
- Module: ESNext (bundler resolution)
- Strict mode: Enabled (all strict flags on)
Use ESM with file extensions:
// CORRECT: Include .js extension
import { runMutation } from "./lib/execution.js";
import { getCliOptions } from "@/lib/options.js";
// INCORRECT: Missing extension
import { runMutation } from "./lib/execution";Prefer explicit types over any:
// GOOD
function processData(items: CollectionItem[]): ProcessResult {
// ...
}
// ACCEPTABLE in error handling
catch (error: any) {
console.error(error.message || String(error));
}| Type | Convention | Example |
|---|---|---|
| Files | kebab-case | weaviate-client.ts |
| Classes | PascalCase | WeaviateManager |
| Functions | camelCase | runMutation() |
| Constants | UPPER_SNAKE_CASE | DEFAULT_MODEL |
| Types/Interfaces | PascalCase | CliOptions |
| Private fields | underscore prefix | _client |
CRITICAL: All data-modifying operations MUST use runMutation():
import { runMutation } from "../lib/execution.js";
// CORRECT: Wrapped in runMutation
const result = await runMutation(
"import collection data", // Human-readable description
async () => await manager.import(), // The mutation function
{ collection: name } // Optional context
);
if (result !== undefined) {
// Mutation was executed (--live flag present)
console.log("Success!");
}
// INCORRECT: Direct mutation call
await manager.import(); // Bypasses safety checks!- Never call mutations directly
- Use
runMutation()for create/update/delete/import - Read-only operations don't need wrapping
- Do NOT add
--dry-runflags (default IS dry-run)
All commands follow this pattern:
import { Command } from "commander";
import { runMutation } from "../lib/execution.js";
import { getCliOptions } from "../lib/options.js";
export const myCommand = new Command("mycommand")
.description("Description of what this command does")
.option("-f, --flag <value>", "Description of flag")
.action(async (options) => {
const opts = getCliOptions(); // Get global flags
// Your command logic here
// For mutations, use runMutation()
const result = await runMutation(
"perform action",
async () => await doSomething(),
{ context: "data" }
);
});Order imports as:
- External packages
- Internal modules (using @/ alias)
- Type imports (if separate)
// External packages
import { Command } from "commander";
import chalk from "chalk";
// Internal modules
import { runMutation } from "@/lib/execution.js";
import { getCliOptions } from "@/lib/options.js";
// Types (if separate)
import type { CliOptions } from "@/types/index.js";try {
const result = await operation();
return result;
} catch (error) {
if (error instanceof SpecificError) {
throw new Error(`Failed to do X: ${error.message}`);
}
throw error; // Re-throw unknown errors
}The project uses Biome (v2.2.6):
# Check for issues
npm run lint
# Auto-fix issues
npm run lint:fix
# Format code
npm run format
# Run all checks
npm run check- Line width: 180 characters
- Indent: 2 spaces
- Quotes: Double quotes
- Semicolons: Always
- Trailing commas: ES5 style
noExplicitAny: OFF for error handling and global types- Prefer template literals (warning, not error)
src/
├── cli.ts # Main entry point
├── commands/ # Command implementations
├── lib/ # Shared utilities
├── shell/ # Shell-specific features
└── types/ # Type definitions
Commands should be thin wrappers that delegate to services:
// GOOD: Command delegates to service
.action(async () => {
const manager = new WeaviateManager();
const collections = await manager.listCollections();
displayCollections(collections);
});
// AVOID: Command does everything
.action(async () => {
const client = await weaviate.client();
const schema = await client.schema.getter().do();
// ... lots of logic
});// GOOD: Use @/ alias for src/
import { config } from "@/lib/config.js";
// AVOID: Deep relative paths
import { config } from "../../../lib/config.js";- Don't add features not requested
- Keep solutions simple
- No premature abstractions
- Three similar lines > abstraction