# mtbDB

[![npm](https://img.shields.io/npm/v/@mathislair/mtbdb.svg)](https://www.npmjs.com/package/@mathislair/mtbdb)

A schema-driven ORM for TypeScript: introspect your database, generate typed
beans and DAOs, and write code against them.

Inspired by [TheDataBaseMachine (TDBM)](https://thecodingmachine.github.io/tdbm/)
from the PHP world.

## Status

PostgreSQL is supported today. The driver layer is dialect-agnostic; MySQL and
SQLite drivers can be added by implementing the `Driver` interface in
`src/drivers/Driver.ts`.

## Install

```bash
npm install @mathislair/mtbdb
```

Or install from source:

```bash
git clone https://github.com/mathislair/orm.git
cd orm
npm install
npm run build
```

## Workflow

1. **Describe your DB** in `mtbDB.config.json`:

   ```json
   {
     "driver":   "postgres",
     "host":     "localhost",
     "port":     5432,
     "user":     "postgres",
     "password": "postgres",
     "database": "myapp",
     "schema":   "public",
     "outDir":   "./src/generated"
   }
   ```

2. **Generate** beans + DAOs:

   ```bash
   npx mtbdb generate
   ```

   This creates two files per table:
   - `_base/<Table>Bean.base.ts` (regenerated each run, do **not** edit)
   - `<Table>Bean.ts`            (user-editable, regenerated only if missing)

   Same pattern for DAOs. This is the same two-tier layout TDBM uses in PHP —
   you can re-run generation safely without losing your custom code.

3. **Use** them:

   ```ts
   import { Connection } from '@mathislair/mtbdb';
   import { UserDao, CountryDao } from './generated';

   const conn = await Connection.open({
     driver: 'postgres',
     host: 'localhost',
     database: 'myapp',
     user: 'postgres',
     password: 'postgres',
   });

   const userDao = conn.dao(UserDao);
   const countryDao = conn.dao(CountryDao);

   // Create
   const fr = countryDao.create();
   fr.code = 'FR';
   fr.name = 'France';
   await countryDao.save(fr);

   const u = userDao.create();
   u.email = 'alice@example.com';
   u.fullName = 'Alice';
   u.setCountry(fr);                   // FK setter keeps country_id in sync
   await userDao.save(u);

   // Read with FK navigation
   const found = await userDao.findOne({ email: 'alice@example.com' });
   const country = await found?.getCountry();   // typed: CountryBean | null

   // Update — only dirty columns hit the DB
   if (found) {
     found.fullName = 'Alice Smith';
     await userDao.save(found);
   }

   // Delete
   if (found) await userDao.delete(found);

   await conn.close();
   ```

## Architecture

```
src/
  drivers/
    Driver.ts             # Driver + Dialect interfaces (the seam for new DBs)
    PostgresDriver.ts     # pg-backed implementation
  schema/
    types.ts              # DatabaseSchema, TableInfo, ColumnInfo, ForeignKey
    PostgresIntrospector.ts
  runtime/
    Connection.ts         # entry point + DAO registry
    AbstractBean.ts       # dirty tracking, state machine
    AbstractDao.ts        # CRUD: find, findById, findOne, save, delete, count
    QueryBuilder.ts       # parameterized SQL via the Dialect
    IdentityMap.ts        # row-identity cache per Connection
  codegen/
    Generator.ts          # emits two-tier .ts files from the introspected schema
    naming.ts             # table -> ClassName conventions (singular)
  cli/
    index.ts              # `mtbdb generate`
```

## Adding a new database driver

1. Implement `Driver` and `Dialect` in `src/drivers/<Name>Driver.ts`.
2. Write an introspector that returns the same `DatabaseSchema` shape.
3. Register the driver name in `Connection.createDriver`.

The runtime, code generator, and CLI work unchanged — they only depend on the
driver interface and `DatabaseSchema`.

## Tests

```bash
npm test
```

The default test suite runs **without** a database (it tests the query builder,
bean state machine, and code generator end-to-end against a synthetic schema).

## Publishing to npm

Maintainers only. The `prepublishOnly` script builds and runs tests; only
`dist/`, `README.md`, and `LICENSE` are shipped (see the `files` whitelist in
`package.json`).

```bash
npm login
npm publish --access public
```

## See also

[**dbform**](https://github.com/mathislair/mtbform) — a companion package that
turns a database schema registry into auto-generated forms (React, Vue, Svelte).
The two packages are intentionally decoupled: dbform consumes a plain
`SchemaRegistry` value and takes no runtime dependency on mtbDB. The
introspected `DatabaseSchema` produced by mtbDB can be transformed into that
shape in a thin user-space adapter.
