@cleverbrush/orm 0.0.0-beta-20260424142030

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 ADDED
@@ -0,0 +1,349 @@
1
+ # `@cleverbrush/orm`
2
+
3
+ EF-Core-like typed ORM layer on top of [`@cleverbrush/knex-schema`](../knex-schema).
4
+
5
+ - Define entities with `defineEntity(schema)` and declare relations via
6
+ `.hasOne()` / `.hasMany()` / `.belongsTo()` / `.belongsToMany()`.
7
+ - Group entities into a typed context with `createDb(knex, { todos, users })`.
8
+ - Access `db.todos`, `db.users` as fully-typed `DbSet<TEntity>` instances.
9
+ - Eager-load related entities with `.include(t => t.author)`.
10
+ - Use `{ tracking: true }` for an identity map + change-tracking context.
11
+ - Model inheritance with STI (single-table) and CTI (class-table) variants.
12
+ - Manage schema migrations with [`@cleverbrush/orm-cli`](../orm-cli).
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```sh
19
+ npm install @cleverbrush/orm
20
+ ```
21
+
22
+ `knex` is a peer dependency (install it alongside `@cleverbrush/orm`). `@cleverbrush/knex-schema` is a direct dependency and is installed automatically.
23
+
24
+ ---
25
+
26
+ ## Quick start
27
+
28
+ ### 1. Define a schema and entity
29
+
30
+ ```ts
31
+ import { number, object, string, defineEntity } from '@cleverbrush/orm';
32
+
33
+ const UserSchema = object({
34
+ id: number().primaryKey(),
35
+ email: string().hasColumnName('email_address'),
36
+ name: string(),
37
+ }).hasTableName('users');
38
+
39
+ export const UserEntity = defineEntity(UserSchema);
40
+ ```
41
+
42
+ ### 2. Create a `DbContext`
43
+
44
+ ```ts
45
+ import knex from 'knex';
46
+ import { createDb } from '@cleverbrush/orm';
47
+ import { UserEntity } from './schemas.js';
48
+
49
+ const knexClient = knex({ client: 'pg', connection: process.env.DATABASE_URL });
50
+
51
+ const db = createDb(knexClient, { users: UserEntity });
52
+ ```
53
+
54
+ ### 3. Query and mutate
55
+
56
+ ```ts
57
+ // Find by PK
58
+ const alice = await db.users.find(1);
59
+
60
+ // Find with WHERE clause
61
+ const user = await db.users.where(t => t.email, 'alice@example.com').first();
62
+
63
+ // Insert
64
+ const created = await db.users.save({ email: 'bob@example.com', name: 'Bob' });
65
+
66
+ // Update (PK present → UPDATE)
67
+ const updated = await db.users.save({ id: 1, email: 'alice@example.com', name: 'Alice' });
68
+ ```
69
+
70
+ ---
71
+
72
+ ## `DbSet<TEntity>` API
73
+
74
+ | Method | Description |
75
+ |--------|-------------|
76
+ | `.find(pk)` | Find one row by PK; returns `undefined` when not found |
77
+ | `.findOrFail(pk)` | Like `.find`, but throws `EntityNotFoundError` when not found |
78
+ | `.findMany([pk1, pk2, …])` | Fetch multiple rows by PK in one query |
79
+ | `.all()` | Alias for `.execute()` — returns all rows |
80
+ | `.first()` | Returns the first matching row or `undefined` |
81
+ | `.where(col, value)` | Adds a `WHERE` predicate (chainable) |
82
+ | `.include(t => t.rel)` | Eager-loads a relation (chainable) |
83
+ | `.save(graph)` | Insert or update a row graph (transactional) |
84
+ | `.ofVariant(key)` | Return a typed `VariantDbSet` scoped to a polymorphic variant |
85
+ | `.query()` | Returns the underlying `EntityQuery` for advanced querying |
86
+ | `.withTransaction(trx)` | Returns a new `DbSet` bound to an existing transaction |
87
+
88
+ Any method from the underlying `SchemaQueryBuilder` (e.g. `.execute()`,
89
+ `.where()`, `.orderBy()`, `.pluck()`, `.count()`) is also available and
90
+ fully typed.
91
+
92
+ ---
93
+
94
+ ## Relations
95
+
96
+ Declare relations on the entity using fluent builders. Relations are optional
97
+ by default (omit from the save-graph to skip them).
98
+
99
+ ```ts
100
+ const TodoSchema = object({
101
+ id: number().primaryKey(),
102
+ title: string(),
103
+ userId: number().hasColumnName('user_id'),
104
+ author: object({ id: number().primaryKey(), name: string() })
105
+ .hasTableName('users').optional(),
106
+ }).hasTableName('todos');
107
+
108
+ const TodoEntity = defineEntity(TodoSchema)
109
+ .belongsTo(t => t.author, 'userId'); // FK is on todos.user_id → users.id
110
+
111
+ const UserEntity = defineEntity(UserSchema)
112
+ .hasMany(t => t.todos, TodoEntity, 'userId'); // FK on todos.user_id
113
+ ```
114
+
115
+ ### Eager-loading
116
+
117
+ ```ts
118
+ const todo = await db.todos
119
+ .where(t => t.id, 42)
120
+ .include(t => t.author)
121
+ .first();
122
+
123
+ console.log(todo?.author?.name); // fully typed
124
+ ```
125
+
126
+ ### Saving with relations
127
+
128
+ `db.todos.save(graph)` traverses the whole object graph and persists every
129
+ level in the correct FK order inside a single transaction.
130
+
131
+ ```ts
132
+ // Create a new user and a new todo in one call.
133
+ const result = await db.users.save({
134
+ name: 'Alice',
135
+ todos: [
136
+ { title: 'Buy milk', completed: false, userId: 0 },
137
+ ],
138
+ });
139
+ ```
140
+
141
+ Topology rules:
142
+ - `belongsTo` parents are inserted first; their PK feeds the child FK.
143
+ - The root entity is then written.
144
+ - `hasOne` / `hasMany` children inherit the root PK into their FK.
145
+ - `belongsToMany` children may be new objects (inserted + pivot) or bare
146
+ `{ pk: value }` references (pivot only).
147
+
148
+ ---
149
+
150
+ ## Change-tracking context
151
+
152
+ Pass `{ tracking: true }` to `createDb` to get a `TrackedDbContext`. The
153
+ context maintains an **identity map** (same PK → same object reference) and
154
+ tracks every mutation automatically.
155
+
156
+ ```ts
157
+ const db = createDb(knex, { users: UserEntity }, { tracking: true });
158
+
159
+ // Load entity — it's now in the identity map.
160
+ const user = await db.users.find(1);
161
+
162
+ // Mutate normally.
163
+ user.name = 'Updated';
164
+
165
+ // Flush all dirty entries in a single transaction.
166
+ const { inserted, updated, deleted } = await db.saveChanges();
167
+ ```
168
+
169
+ ### `TrackedDbContext` API
170
+
171
+ | Method / property | Description |
172
+ |---|---|
173
+ | `.attach(key, entity)` | Register an existing entity as `Unchanged` |
174
+ | `.entry(entity)` | Return the `EntityEntry` for a tracked entity |
175
+ | `.entry(entity).state` | `'Added' \| 'Modified' \| 'Unchanged' \| 'Deleted'` |
176
+ | `.entry(entity).isModified(field?)` | Check if a specific (or any) field changed |
177
+ | `.entry(entity).reset()` | Revert the entity to its last snapshot |
178
+ | `.detach(entity)` | Remove an entity from the tracker |
179
+ | `.remove(entity)` | Mark a tracked entity as `Deleted` |
180
+ | `.saveChanges()` | Flush all pending changes to the DB |
181
+ | `.discardChanges()` | Roll back all in-memory changes |
182
+ | `.reload(entity)` | Re-fetch the entity from the DB and refresh its snapshot |
183
+ | `.onSavingChanges(hook)` | Register a pre-flush callback (e.g. for audit fields) |
184
+ | `[Symbol.asyncDispose]()` | Usable in `await using` blocks; throws on pending changes |
185
+
186
+ ### Row versioning
187
+
188
+ Mark any numeric or timestamp column as a row version:
189
+
190
+ ```ts
191
+ const OrderSchema = object({
192
+ id: number().primaryKey(),
193
+ status: string(),
194
+ version: number().rowVersion(), // auto-incremented by the ORM on every UPDATE
195
+ }).hasTableName('orders');
196
+ ```
197
+
198
+ `saveChanges()` appends `AND version = <snapshot>` to every `UPDATE` and throws
199
+ `ConcurrencyError` if `rowCount === 0`.
200
+
201
+ ### `await using` integration
202
+
203
+ ```ts
204
+ async function updateUser(userId: number) {
205
+ await using db = createDb(knex, { users: UserEntity }, { tracking: true });
206
+ const user = await db.users.find(userId);
207
+ if (!user) return;
208
+ user.name = 'Updated';
209
+ await db.saveChanges();
210
+ } // Symbol.asyncDispose fires here; throws if changes are still pending
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Polymorphic entities (STI / CTI)
216
+
217
+ ### Single-Table Inheritance (STI)
218
+
219
+ All variants share one table; a discriminator column identifies the type.
220
+
221
+ ```ts
222
+ const ActivityBase = object({
223
+ id: number().primaryKey(),
224
+ type: string(),
225
+ todoId: number().hasColumnName('todo_id'),
226
+ }).hasTableName('activities');
227
+
228
+ const ActivityEntity = defineEntity(ActivityBase)
229
+ .discriminator('type')
230
+ .stiVariant('assigned', object({
231
+ type: string('assigned'),
232
+ assigneeId: number().hasColumnName('assignee_id').optional(),
233
+ }))
234
+ .stiVariant('commented', object({
235
+ type: string('commented'),
236
+ body: string().optional(),
237
+ }));
238
+ ```
239
+
240
+ ### Class-Table Inheritance (CTI)
241
+
242
+ The base row is in one table; each variant has its own extension table.
243
+
244
+ ```ts
245
+ const AssignedExtras = defineEntity(
246
+ object({
247
+ activityId: number().hasColumnName('activity_id'),
248
+ assigneeId: number().hasColumnName('assignee_id'),
249
+ }).hasTableName('assigned_activities')
250
+ );
251
+
252
+ const ActivityEntity = defineEntity(ActivityBase)
253
+ .discriminator('type')
254
+ .ctiVariant('assigned', AssignedExtras, t => t.activityId);
255
+ ```
256
+
257
+ ### Querying variants
258
+
259
+ Call `db.set.ofVariant('key')` to obtain a **`VariantDbSet`** — a typed view
260
+ scoped to that variant, analogous to EF Core's `Set<DerivedType>()`. All
261
+ reads are pre-filtered by the discriminator; writes use the correct STI / CTI
262
+ logic automatically.
263
+
264
+ ```ts
265
+ // Insert a new variant row (discriminator is set automatically)
266
+ const activity = await db.activities.ofVariant('assigned').insert({
267
+ todoId: 42,
268
+ assigneeId: 9,
269
+ });
270
+ // activity.type === 'assigned'
271
+ // activity.assigneeId === 9
272
+
273
+ // Find a single variant by PK
274
+ const found = await db.activities.ofVariant('assigned').find(activityId);
275
+
276
+ // Update matching rows (chain .where() before .update())
277
+ await db.activities.ofVariant('assigned').where(t => t.id, 3).update({ assigneeId: 99 });
278
+
279
+ // Delete matching rows
280
+ await db.activities.ofVariant('assigned').where(t => t.id, 3).delete();
281
+ ```
282
+
283
+ ### `VariantDbSet<TEntity, K>` API
284
+
285
+ | Method | Description |
286
+ |--------|-------------|
287
+ | `.insert(payload)` | Insert a new variant row; discriminator is set automatically |
288
+ | `.update(patch)` | Update variant columns for rows matched by the current `WHERE` clause |
289
+ | `.delete()` | Delete rows matched by the current `WHERE` clause (CTI: atomic) |
290
+ | `.find(pk)` | Find a single variant row by PK; `undefined` if not found |
291
+ | `.findOrFail(pk)` | Like `.find`, but throws `EntityNotFoundError` |
292
+ | `.findMany([pk…])` | Fetch multiple variant rows by PK in one query |
293
+ | `.where(col, value)` | Adds a `WHERE` predicate (chainable; returns `VariantDbSet`) |
294
+ | `.include(t => t.rel)` | Eager-loads a relation (chainable; returns `VariantDbSet`) |
295
+ | `.withTransaction(trx)` | Returns a new `VariantDbSet` bound to an existing transaction |
296
+
297
+ Calling `.insert()` / `.update()` / `.delete()` directly on the polymorphic
298
+ base `DbSet` (without `ofVariant`) throws a runtime error — use `ofVariant`
299
+ for all writes on polymorphic entities.
300
+
301
+ ---
302
+
303
+ ## Transactions
304
+
305
+ ```ts
306
+ // One-off transaction (non-tracking context)
307
+ await db.transaction(async trx => {
308
+ const user = await trx.users.save({ name: 'Alice' });
309
+ await trx.todos.save({ title: 'Buy milk', userId: user.id });
310
+ });
311
+
312
+ // Wrap existing transaction
313
+ const user = await db.users.withTransaction(existingTrx).save({ name: 'Alice' });
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Error classes
319
+
320
+ | Class | Thrown when |
321
+ |-------|-------------|
322
+ | `EntityNotFoundError` | `findOrFail` can't locate the requested PK |
323
+ | `ConcurrencyError` | `saveChanges` UPDATE/DELETE hits a row-version mismatch |
324
+ | `InvariantViolationError` | PK or discriminator column mutated on a tracked entity |
325
+ | `PendingChangesError` | `[Symbol.asyncDispose]` fires with unsaved changes |
326
+
327
+ ---
328
+
329
+ ## Schema migrations
330
+
331
+ Use [`@cleverbrush/orm-cli`](../orm-cli) to generate and apply migration files
332
+ from your entity definitions:
333
+
334
+ ```sh
335
+ # Diff schema vs DB → emit a TypeScript migration file
336
+ npx cb-orm migrate generate add_users_table
337
+
338
+ # Apply pending migrations
339
+ npx cb-orm migrate run
340
+ ```
341
+
342
+ ---
343
+
344
+ ## See also
345
+
346
+ - [`@cleverbrush/knex-schema`](../knex-schema) — the underlying schema DSL and
347
+ query builder
348
+ - [`@cleverbrush/orm-cli`](../orm-cli) — migration CLI tool
349
+ - [API reference](https://cleverbrush.github.io/framework/api-docs/latest)
@@ -0,0 +1,118 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Entry state in the identity map. Mirrors EF Core's EntityState.
4
+ * @public
5
+ */
6
+ export type EntryState = 'Added' | 'Unchanged' | 'Modified' | 'Deleted';
7
+ /**
8
+ * Public view of a tracked entry, exposed via `db.entry(entity)`.
9
+ *
10
+ * @public
11
+ */
12
+ export interface EntityEntry<T extends object> {
13
+ /** Current state of the entry. */
14
+ readonly state: EntryState;
15
+ /** Snapshot of the values at the time the entity was last loaded/saved. */
16
+ readonly originalValues: Readonly<T>;
17
+ /** Live object (same reference as the tracked entity). */
18
+ readonly currentValues: T;
19
+ /**
20
+ * Returns `true` when a specific field has changed since the last
21
+ * snapshot, or `true` when any field has changed when called without
22
+ * arguments.
23
+ */
24
+ isModified(field?: keyof T): boolean;
25
+ /**
26
+ * Reset all changes to the snapshot values and transition state back
27
+ * to `'Unchanged'` (or `'Added'` if the entity was never persisted).
28
+ */
29
+ reset(): void;
30
+ }
31
+ /** @internal Per-entity-set configuration needed by the tracker. */
32
+ export interface EntitySetConfig {
33
+ /** The entity schema (used for PK resolution). */
34
+ schema: any;
35
+ /** Logical entity-set name (e.g. `'todos'`). */
36
+ entitySetKey: string;
37
+ }
38
+ /**
39
+ * Pre-save hook callback signature registered via
40
+ * `TrackedDbContext.onSavingChanges(hook)`.
41
+ * @public
42
+ */
43
+ export type SavingChangesHook = (entry: EntityEntry<object>) => void | Promise<void>;
44
+ /**
45
+ * The change-tracking core used by tracked `DbContext` instances.
46
+ *
47
+ * Consumers interact with this via the `DbContext` API methods; the
48
+ * `ChangeTracker` itself is not exported as part of the public API.
49
+ *
50
+ * @internal
51
+ */
52
+ export declare class ChangeTracker {
53
+ #private;
54
+ /** Register an entity set so the tracker knows its schema. */
55
+ registerEntitySet(config: EntitySetConfig): void;
56
+ /** Register a pre-save hook. */
57
+ onSavingChanges(hook: SavingChangesHook): void;
58
+ /**
59
+ * Attach an entity to the tracker under the given entity-set key.
60
+ *
61
+ * If an entry with the same PK already exists, returns the EXISTING
62
+ * tracked object (identity-map guarantee). If the entity is already the
63
+ * same object, just updates its snapshot.
64
+ *
65
+ * @returns The tracked entity (same ref when already in the map).
66
+ */
67
+ attach<T extends object>(entitySetKey: string, entity: T, state?: EntryState): T;
68
+ /** Detach an entity from tracking. */
69
+ detach(entity: object): void;
70
+ /**
71
+ * Mark an entity for deletion on the next `saveChanges()` call.
72
+ * The entity must already be tracked.
73
+ */
74
+ remove(entity: object): void;
75
+ /** Return the public `EntityEntry` view for a tracked entity. */
76
+ entry<T extends object>(entity: T): EntityEntry<T>;
77
+ /**
78
+ * Discard all pending changes: reset all Modified entries to their
79
+ * snapshots, remove Added entries from the tracker, restore Deleted
80
+ * entries to Unchanged.
81
+ */
82
+ discardChanges(): void;
83
+ /**
84
+ * Refresh a tracked entity from the DB, replacing its current values
85
+ * and snapshot with the freshly-loaded row.
86
+ */
87
+ reload(entity: object, knex: Knex): Promise<void>;
88
+ /**
89
+ * Returns `true` when there are any pending changes (Added / Modified /
90
+ * Deleted entries, or silently mutated Unchanged entries).
91
+ */
92
+ hasPendingChanges(): boolean;
93
+ /**
94
+ * Flush all pending changes to the database within a single transaction.
95
+ *
96
+ * Returns the number of rows inserted, updated, and deleted.
97
+ *
98
+ * Invariants checked:
99
+ * - PK columns must not have changed since the snapshot (throws
100
+ * `InvariantViolationError`).
101
+ * - Discriminator columns must not have changed (throws
102
+ * `InvariantViolationError`).
103
+ * - For `rowVersion` columns: WHERE clause enforces the snapshot value;
104
+ * zero affected rows throws `ConcurrencyError`.
105
+ */
106
+ saveChanges(knex: Knex): Promise<{
107
+ inserted: number;
108
+ updated: number;
109
+ deleted: number;
110
+ }>;
111
+ /**
112
+ * Generate a summary of pending changes for error messages.
113
+ * @internal
114
+ */
115
+ pendingSummary(): string;
116
+ /** Clear the entire identity map (used on dispose). */
117
+ clear(): void;
118
+ }
@@ -0,0 +1,130 @@
1
+ import type { Entity } from '@cleverbrush/knex-schema';
2
+ import type { Knex } from 'knex';
3
+ import { type EntityEntry, type SavingChangesHook } from './change-tracker.js';
4
+ import { type DbSet } from './dbset.js';
5
+ /**
6
+ * The map of entity name → entity definition passed to {@link createDb}.
7
+ *
8
+ * @public
9
+ */
10
+ export type EntityMap = Record<string, Entity<any, any, any>>;
11
+ /**
12
+ * The shape of the context object returned by {@link createDb}: every
13
+ * registered entity becomes a typed `DbSet` property.
14
+ *
15
+ * @public
16
+ */
17
+ export type DbContext<TMap extends EntityMap> = {
18
+ readonly [K in keyof TMap]: DbSet<TMap[K]>;
19
+ } & {
20
+ /**
21
+ * The underlying Knex instance the context was constructed with.
22
+ */
23
+ readonly knex: Knex;
24
+ /**
25
+ * Run `callback` inside a Knex transaction. The callback receives a new
26
+ * `DbContext` whose `DbSet`s are bound to the transaction.
27
+ */
28
+ transaction<T>(callback: (db: DbContext<TMap>) => Promise<T>): Promise<T>;
29
+ /**
30
+ * Return a new `DbContext` whose `DbSet`s are bound to `trx`. Useful
31
+ * when you already have a Knex transaction in scope.
32
+ */
33
+ withTransaction(trx: Knex.Transaction): DbContext<TMap>;
34
+ };
35
+ /**
36
+ * Extended context returned when `{ tracking: true }` is passed to
37
+ * {@link createDb}. Adds identity-map tracking, unit-of-work `saveChanges`,
38
+ * and `[Symbol.asyncDispose]` support for `await using` blocks.
39
+ *
40
+ * @public
41
+ */
42
+ export type TrackedDbContext<TMap extends EntityMap> = DbContext<TMap> & {
43
+ /**
44
+ * Attach an entity to the identity map so its changes will be tracked.
45
+ * If an entity with the same primary key is already tracked, the existing
46
+ * tracked object is returned (identity-map guarantee).
47
+ *
48
+ * @param entitySetKey — the property name used when registering the entity
49
+ * (e.g. `'todos'` for `createDb(knex, { todos: TodoEntity })`).
50
+ */
51
+ attach<T extends object>(entitySetKey: string, entity: T): T;
52
+ /**
53
+ * Remove an entity from the identity map. Any subsequent changes to the
54
+ * object will not be persisted by `saveChanges`.
55
+ */
56
+ detach(entity: object): void;
57
+ /**
58
+ * Mark a tracked entity for deletion on the next `saveChanges()` call.
59
+ */
60
+ remove(entity: object): void;
61
+ /**
62
+ * Get the public entry-state view for a tracked entity.
63
+ */
64
+ entry<T extends object>(entity: T): EntityEntry<T>;
65
+ /**
66
+ * Register a callback that is invoked for each changed entry just before
67
+ * the changes are flushed to the database. Use to apply audit fields etc.
68
+ */
69
+ onSavingChanges(hook: SavingChangesHook): void;
70
+ /**
71
+ * Reload a tracked entity from the database, overwriting its current
72
+ * values and refreshing the internal snapshot.
73
+ */
74
+ reload(entity: object): Promise<void>;
75
+ /**
76
+ * Discard all pending changes. Modified entries are reset to their
77
+ * snapshots; Added entries are detached; Deleted entries are restored
78
+ * to `Unchanged`.
79
+ */
80
+ discardChanges(): void;
81
+ /**
82
+ * Flush all pending changes to the database inside a single transaction.
83
+ *
84
+ * @returns Counts of inserted, updated, and deleted rows.
85
+ */
86
+ saveChanges(): Promise<{
87
+ inserted: number;
88
+ updated: number;
89
+ deleted: number;
90
+ }>;
91
+ /**
92
+ * Implements `Symbol.asyncDispose` for `await using` blocks.
93
+ *
94
+ * Throws {@link PendingChangesError} when there are unsaved changes at
95
+ * the time of disposal — callers must call `saveChanges()` or
96
+ * `discardChanges()` before the scope exits.
97
+ */
98
+ [Symbol.asyncDispose](): Promise<void>;
99
+ };
100
+ /**
101
+ * Create a typed database context.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const TodoEntity = defineEntity(TodoSchema)
106
+ * .belongsTo(t => t.author, l => l.userId, r => r.id);
107
+ *
108
+ * const UserEntity = defineEntity(UserSchema);
109
+ *
110
+ * const db = createDb(knex, { todos: TodoEntity, users: UserEntity });
111
+ *
112
+ * const todo = await db.todos
113
+ * .where(t => t.id, '=', 42)
114
+ * .include(t => t.author)
115
+ * .first();
116
+ *
117
+ * await db.transaction(async dbTrx => {
118
+ * const u = await dbTrx.users.insert({ email: 'a@b.c', role: 'user', authProvider: 'local', createdAt: new Date() });
119
+ * await dbTrx.todos.insert({ title: 'Hi', userId: u.id, completed: false, createdAt: new Date(), updatedAt: new Date() });
120
+ * });
121
+ * ```
122
+ *
123
+ * @public
124
+ */
125
+ export declare function createDb<TMap extends EntityMap>(knex: Knex, entities: TMap, opts: {
126
+ tracking: true;
127
+ }): TrackedDbContext<TMap>;
128
+ export declare function createDb<TMap extends EntityMap>(knex: Knex, entities: TMap, opts?: {
129
+ tracking?: false | undefined;
130
+ }): DbContext<TMap>;