Stable orientation for FoxSchema. CLAUDE.md holds the must-follow rules and points
here for detail. For current state, gotchas, and pending work see IMPLEMENTATION_STATE.md.
Database schema diff & migration tool. Compare a source schema against a target,
generate dialect-native migration SQL, and deploy it. Primary target is DB2; Postgres,
MySQL, SQL Server, Oracle, SQLite, MariaDB, Azure SQL, ClickHouse, and Redshift are also
implemented. Distributions: CLI (foxschema via npm/Homebrew), Docker
(single amd64 image with Db2), and retired desktop (Tauri — not released).
# CLI-first (after npm i -g foxschema, or from the monorepo build)
foxschema # start UI on :3210 + open browser
foxschema stop
foxschema doctor
# Development (starts both Express API + Vite frontend)
npm run dev # single-user mode (no login)
npm run dev:auth # multi-user auth mode
# Typecheck — primary correctness gate
cd apps/web && npx tsc --noEmit
# Tests — run from repo root (covers packages/* and apps/web/**/*.test.ts)
npx vitest run # all tests
npx vitest run packages/core # core engine tests only
npx vitest run --reporter=verbose # with test names
npx vitest <pattern> # e.g. npx vitest sql-generator
# Desktop (legacy)
cd apps/desktop && npm run dev # builds sidecar then launches Tauri
# CLI (development)
cd apps/cli && npm run foxschema -- doctor
cd apps/cli && npm run foxschema -- compare --source ... --target ...Backend changes (apps/web/src/backend, packages/core) hot-reload via tsx watch.
Desktop sidecar requires cd apps/desktop && npm run dev after any backend change.
Playwright + Vitest against 6 Docker dialect containers. See the E2E workflow memory and
docs/plans/2026-07-01-seed-test-matrix.md for the operational details (reseed before
every run, restart backend before reseeding Oracle/DB2, DB2 /var/custom init).
# One-command reset: compose up + wait healthy + restart dev + reseed
bash scripts/seed/reset-all.sh
bash scripts/seed/seed-all.sh all # reseed only
# Run the suite (dev server must be up)
cd apps/e2e && node scripts/run-all.mjsnpm workspaces — not pnpm or Turborepo.
packages/core/ @foxschema/core — engine (no browser deps)
src/interfaces/ TableSchema, TableDiff, ColumnDiff, MigrationStep, etc.
src/modules/ CompareModule, SqlGeneratorModule, ConnectionModule, MigrationModule
src/providers/ 10 dialects — each has settings + adapter + provider + sql-dialect
src/cores/ ConnectionFactory, crypto, connection-string helpers
apps/web/
src/backend/ Express API (routes, auth, migration history, metadata DB)
database/stores/ Metadata DB providers: sqlite (default), postgres, mysql
src/frontend/
lib/ Browser-safe copies: types.ts, sql-generator.ts, provider-settings.ts
store/ Zustand: useSyncStore.ts + sync-types.ts + sync-helpers.ts
components/ UI components (SchemaTreePanel, ObjectDetailPanel, TopToolbar, …)
components/object-detail/ MigrationProgressPanel, DeployConfirmDialog, DependencyWarningDialog
apps/desktop/ Legacy Tauri v2 shell + Node sidecar packaging
apps/cli/ `foxschema` CLI — browser launcher (:3210), line commands, Ink TUI
packaging/homebrew/ Scripts to refresh Formula/foxschema.rb (Homebrew, same repo)
Formula/ Homebrew formula (tap this GitHub repo directly)
Frontend imports nothing from workspace packages — it uses standalone copies in
apps/web/src/frontend/lib/. Accepted duplication to avoid bundler complications.
- Compare (server-side):
POST /api/compare→CompareModule.compare()→TableDiff[] - Generate (client-side):
SqlGeneratorModule.generateMigrationPlan(diffs, targetDialect, mapping)→MigrationStep[]. Runs in the browser on every selection toggle — no round-trip. - Execute (server-side):
POST /api/migration/execute→MigrationModulestreamsMigrationEventobjects via SSE back toMigrationProgressPanel.
SchemaMapping threads through generation: sourceSchema, targetSchema, sourceDialect,
targetDialect, nonDestructive, targetServerVersion.
Each of the 10 dialects has three layers registered in packages/core/src/providers/:
| File | Interface | Registry |
|---|---|---|
<d>.settings.ts |
ProviderConnectionSettings |
provider-settings.ts |
<d>.adapter.ts |
DriverAdapter |
adapter-registry.ts |
<d>.provider.ts |
SchemaProvider |
provider-registry.ts |
Plus <d>.sql-dialect.ts implementing SqlDialect — registered in dialect-registry.ts.
The SqlDialect interface has optional hooks; the generator uses a generic fallback when a
hook is absent. Adding dialect-specific behavior = implement the hook in that dialect's file
only. Key hooks: dropForeignKeyStatement, dropIndexStatement, dropTriggerStatement,
createTriggerStatement, preDropTableStatements, createViewStatement, alterViewStatement,
wrapCreateSequence, dropTableStatement, dropViewStatement, dropSequenceStatement,
dropFunctionStatement, dropProcedureStatement. Full hook map + fallback behavior +
per-dialect gotchas live in packages/core/src/providers/DIALECTS.md.
Version-aware DDL: SchemaProvider.detectVersion?() → stored in Zustand as
targetServerVersion → flows into SchemaMapping → dialect drop hooks use it. Oracle pre-23c
uses PL/SQL exception blocks; DB2 (all versions) uses SQL PL CONTINUE HANDLER FOR SQLSTATE '42704'.
useSyncStore.ts (Zustand) is split across three files:
sync-types.ts—SyncStateinterface,MigrationProgressItem,ConnectionConfigsync-helpers.ts—buildRef,buildMapping,buildIncludedDiffs,regenerateSql, sharedsqlGeneratorModuleinstanceuseSyncStore.ts— the store implementation
regenerateSql is called on every selection toggle; it runs SqlGeneratorModule
synchronously in the browser. applyMigration sends the full MigrationStep[] plan to the
backend and streams results back via SSE.
- Create the four files in
packages/core/src/providers/<name>/ - Register in
provider-settings.ts,adapter-registry.ts,provider-registry.ts,dialect-registry.ts - Also update
apps/web/src/frontend/lib/provider-settings.ts(frontend copy) - Add
parseType/renderTyperound-trip tests intype-mapping.test.ts - Verify each optional hook against real DDL — the generic fallbacks are often wrong for DROP INDEX/TRIGGER/FK
npx vitest run+cd apps/web && npx tsc --noEmit