Stable orientation for FoxSchema. For where a change belongs see CODE_MAP.md. Workflow engine runbook: WORKFLOW.md.
Database schema diff & migration tool. Compare a source schema against a target,
generate dialect-native migration SQL, and deploy it. Fourteen SQL dialects:
Postgres (and CockroachDB / YugabyteDB / Redshift), MySQL (and MariaDB / TiDB),
SQL Server / Azure SQL, Oracle, Db2, SQLite, DuckDB, ClickHouse. MongoDB and Redis
carry connection settings only. Distributions: CLI (foxschema via npm/Homebrew)
and Docker (single amd64 image with Db2).
# 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 the Fastify API + Vite frontend)
npm run dev # sign-in required; first open runs setup
npm run dev:auth # multi-user auth mode
npm run dev:with-workflow # plus workflow-server on :8081
# 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/sql # dialect-layer tests only
npx vitest run packages/db # driver/runtime tests only
npx vitest run --reporter=verbose # with test names
npx vitest <pattern> # e.g. npx vitest sql-generator
# CLI (development)
cd apps/cli && npm run foxschema -- doctor
cd apps/cli && npm run foxschema -- compare --source ... --target ...Backend changes (packages/server, packages/sql, packages/db) hot-reload via tsx watch.
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/sql/ @foxschema/sql — dialect knowledge (pure, browser-safe)
src/interfaces/ TableSchema, TableDiff, ColumnDiff, MigrationStep, etc.
src/modules/ One folder per domain: dialect, sql-text, schema-diff,
migrations, lokee-weave, sql-editor, access, utilities
src/providers/ 14 SQL dialects, each with settings + sql-dialect
(+ optional *.user-sql.ts for account DDL)
(MongoDB and Redis carry settings only)
src/cores/ Connection strings, catalog rows → TableSchema,
connection auth (password / Windows NTLM / Db2 LDAP)
packages/db/ @foxschema/db — Node runtime: drivers, pooling, execution
src/providers/ One adapter and provider per dialect
src/cores/ ConnectionFactory, pooling, circuit breaker
packages/shared/ Contracts the frontend, server and CLI agree on:
permission names, error codes, wire message shapes
packages/server/ The backend: Fastify HTTP layer, feature modules,
metadata store
packages/workflow-contract Browser-safe engine types and token/route names
packages/workflow-engine Runtime, SQLite stores, built-in pipes (Node)
apps/web/src/frontend/
app/ Application shell, settings screens, global stores
features/ One folder per business domain
shared/ API client, UI components, lib, utils
apps/cli/ `foxschema` CLI — browser launcher (:3210), line commands, Ink TUI
apps/workflow-server/ Workflow engine HTTP process (:8081); optional scheduler/worker
apps/e2e/ Playwright tests that drive the running application
packaging/homebrew/ Scripts to refresh Formula/foxschema.rb (Homebrew, same repo)
docs/CODE_MAP.md says what each of those folders covers, and where a
change belongs.
Formula/ Homebrew formula (tap this GitHub repo directly)
Saved credentials keep ConnectionOptions (including optional authMethod
and domain) inside encrypted_config. Methods:
password(default) — SQL / native username + password.windows— SQL Server / Azure NTLM. The adapter builds tediousauthentication.type = 'ntlm'from domain + user + Windows password. Do not emitAuthentication=Active Directory Integrated(that is AAD).ldap— Db2 directory user. Still UID/PWD; LDAP is server-side (Authentication=SERVER_ENCRYPTwith the existing SERVER retry).
SavedConnectionSummary exposes authMethod / domain / hasPassword but
never the secret. Windows integrated SSO (no password) is not implemented.
The frontend imports browser-safe workspace packages through Vite aliases
(@foxschema/sql, @foxschema/shared, @foxschema/workflow-contract,
@foxschema/workflow-engine/definitions). Facades live in
apps/web/src/frontend/shared/lib/. @foxschema/db and @foxschema/server
are not aliased — a UI import of either fails the build.
shared/lib/provider-settings.ts used to be a real copy of the dialect registry,
kept on the theory that the browser should not pull the driver runtime in. It does
not: @foxschema/sql is dependency-free and Node-free by design, enforced by
purity.test.ts. The copy drifted on postgres.schemaRequired and broke schema
browsing for schema-less PostgreSQL connections, so it is a facade now, and
provider-settings-facade.test.ts asserts its exports are the same objects as
core's.
- 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 SQL dialect has three layers, split across packages/sql/src/providers/
(dialect + settings) and packages/db/src/providers/ (adapter + provider).
Wire-compatible relatives share a family (dialectFamily() in
packages/sql/src/providers/provider-settings.ts): MariaDB / TiDB → mysql;
CockroachDB / YugabyteDB / Redshift → postgres; Azure SQL → sqlserver. Use
that helper instead of listing dialects by hand (Format / Monaco language, access
SQL, file-import batch size). packages/sql lists 14 SQL dialects (MongoDB and
Redis carry settings only):
| 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 modules/dialect/registry.ts.
Account DDL (CREATE/ALTER/DROP USER|ROLE) is a sibling strategy, not on SqlDialect:
<d>.user-sql.ts implementing UserSqlDialect, registered in
modules/access/user-sql.registry.ts. Aliases re-export (e.g. Azure→SQL Server,
TiDB→MySQL); Redshift has its own module (GROUP, not ROLE). Db2 has no CREATE USER
(canCreateUser: false); buildDb2OsUserInstructions emits copy-paste docker +
GRANT CONNECT steps for the foxschema-db2 container instead. GRANT/REVOKE is the
same pattern: <d>.access-sql.ts / modules/access/access-sql.registry.ts
(Redshift reuses Postgres GRANT; account DDL stays separate). Both stay in
@foxschema/sql so the browser Access Assistant can generate SQL — do not put
these emitters in @foxschema/db (Node drivers only).
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/sql/src/providers/DIALECTS.md (local, gitignored).
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 dialect files in
packages/sql/src/providers/<name>/and the driver files inpackages/db/src/providers/<name>/ - Register in
provider-settings.ts,adapter-registry.ts,provider-registry.ts,modules/dialect/registry.ts(andmodules/access/user-sql.registry.ts/modules/access/access-sql.registry.tswhen the engine has account or GRANT SQL) - Add the dialect name to the
Dialectunion and theDIALECTSarray inpackages/sql/src/providers/provider-settings.ts(dialect-registry.test.tsfails until they matchPROVIDER_SETTINGS). Nothing inapps/webneeds editing — the frontend re-exports this registry. - 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