release: WRNexusJS 0.8.0
This commit is contained in:
+75
-4
@@ -1,5 +1,14 @@
|
||||
# @wrnexus/db
|
||||
|
||||
## Rollout-safe migrations
|
||||
|
||||
Run `wrnexus db check` in CI before deployment. The analyzer reports stable
|
||||
diagnostics for drops, renames, type changes, new/enforced required columns,
|
||||
and potentially blocking index creation, with an expand/backfill/switch/contract
|
||||
recommendation. `wrnexus db migrate` blocks critical issues in pending
|
||||
migrations. `--allow-breaking` is an explicit operator override; already-applied
|
||||
migrations do not block later releases.
|
||||
|
||||
> The database layer for WrNexus: TS models as the single source of truth for DDL, validation, and result typing, plus a driver-based `Db` client, migrations, and a sqlc-style query generator.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
@@ -85,7 +94,8 @@ A `Driver` (implemented by adapters) exposes `dialect`, `query(sql, params?)`,
|
||||
- `exec(sql, params?)` — `Promise<ExecResult>` (`{ changes, lastInsertId? }`).
|
||||
- `tx(fn)` — run `fn(db)` in a transaction; rolls back on throw. Nested `tx` reuses the current transaction.
|
||||
- `createTable(model)` — runs the model's `CREATE TABLE IF NOT EXISTS` DDL.
|
||||
- `close()`.
|
||||
- `close()` — idempotently rejects new top-level work, drains active queries and
|
||||
transactions, then closes the underlying pool.
|
||||
|
||||
Every query is parameterized (positional params). `createTableSql(model, dialect, ifNotExists?)`
|
||||
renders `CREATE TABLE` directly; `Dialect` is `"sqlite" | "postgres" | "mysql"`.
|
||||
@@ -98,7 +108,9 @@ A process-wide registry the runtime configures at startup from `wrnexus.config.t
|
||||
- `setDb(db)` / `setDb(name, db)` — set the default or a named connection.
|
||||
- `registerDb(name, db)` — alias of `setDb(name, db)`.
|
||||
- `getDb(name = "default")` — the default or a named `Db` (throws if unconfigured).
|
||||
- `hasDb(name?)`, `databaseNames()`, `closeDatabases()`.
|
||||
- `hasDb(name?)`, `databaseNames()`, `closeDatabases()`. Registry shutdown clears
|
||||
registrations first, attempts every open database, and reports close failures
|
||||
together with `AggregateError` instead of leaking later pools.
|
||||
|
||||
```ts
|
||||
const users = await getDb().all("SELECT * FROM users");
|
||||
@@ -121,8 +133,8 @@ Migrations are `.sql` files (in e.g. `app/db/migrations`), each split into
|
||||
- `parseMigration(name, content)` → `Migration` (`{ name, up, down }`).
|
||||
- `loadMigrations(dir)` — parse all `.sql` files, sorted by filename.
|
||||
- `appliedMigrations(db)` — applied names, oldest first.
|
||||
- `migrate(db, dir)` — apply all pending (each in a transaction); returns applied names.
|
||||
- `rollback(db, dir)` — roll back the most recent; returns its name or `null`.
|
||||
- `migrate(db, dir, options?)` — apply all pending (each in a transaction); returns applied names.
|
||||
- `rollback(db, dir, options?)` — roll back the most recent; returns its name or `null`.
|
||||
- `status(db, dir)` — `{ name, applied }[]` for every migration file.
|
||||
- `scaffoldMigration(dir, name, dialect, models?)` — write a new numbered migration; with `models` it generates `CREATE`/`DROP` for every table (referenced tables first via topological sort). Returns the file path.
|
||||
|
||||
@@ -199,6 +211,21 @@ const pageTwo = await paginate(
|
||||
);
|
||||
```
|
||||
|
||||
For deployments, `{ dryRun: true }` reports pending names without applying
|
||||
their SQL, `signal` cancels safely between migrations, and the default
|
||||
database-backed lock prevents concurrent deploy runners. A live lock produces
|
||||
`WRN-DB-MIGRATION-LOCKED`; crash-stale locks expire after `lockTimeoutMs` (five
|
||||
minutes by default). Disable it with `lock: false` only when an external deploy
|
||||
coordinator already guarantees exclusivity.
|
||||
|
||||
```ts
|
||||
const pending = await migrate(db, "app/db/migrations", { dryRun: true });
|
||||
await migrate(db, "app/db/migrations", {
|
||||
signal: shutdownController.signal,
|
||||
lockTimeoutMs: 10 * 60_000,
|
||||
});
|
||||
```
|
||||
|
||||
MongoDB (document API):
|
||||
|
||||
```ts
|
||||
@@ -226,3 +253,47 @@ SQL driver — use `@wrnexus/db/mongo` directly.
|
||||
`wrnexus.config.ts`.
|
||||
- The `mongodb` npm package is an optional, lazily-imported peer — install it
|
||||
only if you use `@wrnexus/db/mongo`. The core package stays dependency-free.
|
||||
|
||||
## Repository and transaction helpers
|
||||
|
||||
Repositories accept an immutable equality `scope`, normally `{ column: "tenant_id", value:
|
||||
ctx.tenant.id }`. The scope is injected into every read, count, update and delete, while create
|
||||
overwrites any caller-supplied tenant value. This makes accidental cross-tenant CRUD through the
|
||||
repository API fail closed.
|
||||
|
||||
```ts
|
||||
import { createRepository, retryTransaction, databaseHealth, batch } from "@wrnexus/db";
|
||||
|
||||
const users = createRepository<User>(db, {
|
||||
table: "users",
|
||||
allowedColumns: ["email", "name", "active"],
|
||||
});
|
||||
|
||||
const user = await users.require(42);
|
||||
await users.update(42, { active: true });
|
||||
```
|
||||
|
||||
Repository identifiers are validated, writes may be restricted to an allowlist, and values always use query parameters. Infrastructure packages remain helper-only and do not add UI dependencies to server code.
|
||||
|
||||
## 0.8 repository and transaction helpers
|
||||
|
||||
```ts
|
||||
import { createRepository, databaseHealth, firstOrThrow, retryTransaction } from "@wrnexus/db";
|
||||
|
||||
const usersRepo = createRepository<User>(db, {
|
||||
table: "users",
|
||||
allowedColumns: ["email", "name", "active"],
|
||||
maxListLimit: 250,
|
||||
});
|
||||
|
||||
const users = await usersRepo.all({
|
||||
orderBy: "name",
|
||||
direction: "asc",
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
});
|
||||
```
|
||||
|
||||
Repository SQL identifiers are validated and values remain parameterized. Placeholder generation is dialect-aware: PostgreSQL uses `$1`, `$2`, and SQLite/MySQL use `?`. List limits are bounded.
|
||||
|
||||
`retryTransaction()` retries recognized serialization, deadlock, and database-lock errors by default. Supply `shouldRetry` for application-specific retryable errors; ordinary validation or business errors are not retried automatically.
|
||||
|
||||
Reference in New Issue
Block a user