tempest-db-js 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,14 @@ Typed extras, each with a [docs recipe](https://mauriciobenjamin700.github.io/te
77
77
  - **Active-record (opt-in)** — `activeRecord(User, session)` → `save`/`update`/`delete`/`reload` over `.data`; the plain-object default is unchanged.
78
78
  - **Query logging & errors** — `createEngine(url, { onQuery })` traces every statement; a failed statement throws `QueryExecutionError` carrying the SQL + params. `{ onNotice }` routes PostgreSQL notices to your logger instead of the driver printing them into your stdout, and `{ driverOptions }` passes through whatever the typed surface does not model.
79
79
  - **SQLite driver choice** — `node:sqlite` by default (nothing to install); `{ driver: "better-sqlite3" }` or `sqlite+better-sqlite3:///app.db` switches to the optional peer dependency, same rows and same coercion. An unknown driver name throws instead of being ignored.
80
+ - **Model mixins** — `withTimestamps` / `withSoftDelete` / `withAudit` declare the columns every table repeats once, as real columns (they reach `InferModel`, the migration IR and the DDL).
81
+ - **Cursor pagination** — `repo.cursorPaginate({ cursor, limit, orderBy })`: no `COUNT(*)`, a boundary that survives concurrent inserts, and the primary key appended as a tie-break so no row falls between pages.
82
+ - **Query surface** — CTEs (`cte` / `cteRecursive`, for walking a tree in one statement), window functions through `select().compute({ rank: over(rowNumber(), …) })`, set operations (`union`/`intersect`/`except`) with branch shapes checked at compile time, `exists`/`scalar` subqueries, `caseWhen`/`cast`, and writes that read another table (`insert().fromSelect`, `update().from`, `del().using`).
83
+ - **Text search** — `contains(["name", "email"], term)` tokenizes and **escapes** what the user typed (a raw `ilike` turns `100%` into "everything"), and `fullText`/`fullTextRank` use PostgreSQL's `to_tsvector`/`ts_rank`, falling back to substring matching elsewhere.
84
+ - **`parseIntegrityError`** — reads the driver's error back into `{ violation, constraint, columns }`, so `409 {"code":"EMAIL_TAKEN","field":"email"}` does not need a regex per service.
85
+ - **Repository signals, audit and multi-tenancy** — `onSignal(Model, "postSave", …)` (a throwing `preSave` vetoes the write), an append-only audit trail with a before/after diff written in the change's own transaction, and `TenantScopedRepository`, which injects the tenant into every read and stamps it on every write.
86
+ - **Transactional outbox** — `outboxModel(...)` plus a relay (`claim`/`markSent`/`markFailed`) whose concurrent workers take disjoint batches.
87
+ - **Unit of work (opt-in)** — `session.unitOfWork()` gives an identity map and a batched `flush()`; the plain-object default is unchanged.
80
88
  - **Durable queues** — `select(Job).where(...).limit(10).forUpdate({ skipLocked: true })` inside a transaction hands each worker a disjoint batch, and `set({ attempts: sql.raw("attempts + 1") })` increments in the database instead of read-modify-write. SQLite throws rather than emitting an unlocked `SELECT`.
81
89
  - **Partial-index upsert** — `onConflictDoNothing(["consumer", "idempotencyKey"], { where: { idempotencyKey: { isNull: false } } })` repeats the index predicate PostgreSQL requires to match a **partial** unique index as a conflict target.
82
90
  - **Column names** — `.name("consumer_name")` per column, or `static naming = "snake_case"` per table: a `snake_case` schema behind a `camelCase` model, mapped everywhere including the migration IR (so no false drift).