@vertz/db 0.2.15 → 0.2.16

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/dist/index.d.ts CHANGED
@@ -39,6 +39,19 @@ declare function formatDiagnostic(diag: DiagnosticResult): string;
39
39
  */
40
40
  declare function explainError(message: string): string;
41
41
  /**
42
+ * Query executor — wraps raw SQL execution with error mapping.
43
+ *
44
+ * Takes a query function (from the database driver) and wraps it to:
45
+ * 1. Execute parameterized SQL
46
+ * 2. Map PG errors to typed DbError subclasses
47
+ * 3. Return typed QueryResult
48
+ */
49
+ interface ExecutorResult<T> {
50
+ readonly rows: readonly T[];
51
+ readonly rowCount: number;
52
+ }
53
+ type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
54
+ /**
42
55
  * Database driver interface.
43
56
  *
44
57
  * Provides a unified interface for different database backends
@@ -56,6 +69,12 @@ interface DbDriver {
56
69
  rowsAffected: number;
57
70
  }>;
58
71
  /**
72
+ * Execute a callback within a database transaction.
73
+ * The callback receives a transaction-scoped QueryFn.
74
+ * Optional — not all drivers support transactions (e.g., D1).
75
+ */
76
+ beginTransaction?<T>(fn: (txQueryFn: QueryFn) => Promise<T>): Promise<T>;
77
+ /**
59
78
  * Close the database connection.
60
79
  */
61
80
  close(): Promise<void>;
@@ -291,28 +310,241 @@ interface TableOptions {
291
310
  relations?: Record<string, RelationDef>;
292
311
  indexes?: IndexDef[];
293
312
  }
313
+ /** Operators available for comparable types (number, string, Date, bigint). */
314
+ interface ComparisonOperators<T> {
315
+ readonly eq?: T;
316
+ readonly ne?: T;
317
+ readonly gt?: T;
318
+ readonly gte?: T;
319
+ readonly lt?: T;
320
+ readonly lte?: T;
321
+ readonly in?: readonly T[];
322
+ readonly notIn?: readonly T[];
323
+ }
324
+ /** Additional operators for string columns. */
325
+ interface StringOperators {
326
+ readonly contains?: string;
327
+ readonly startsWith?: string;
328
+ readonly endsWith?: string;
329
+ }
330
+ /** The `isNull` operator — only available for nullable columns. */
331
+ interface NullOperator {
332
+ readonly isNull?: boolean;
333
+ }
334
+ /**
335
+ * Resolves the filter operators for a single column based on its inferred type
336
+ * and nullable metadata.
337
+ *
338
+ * - All types get comparison + in/notIn
339
+ * - String types additionally get contains, startsWith, endsWith
340
+ * - Nullable columns additionally get isNull
341
+ *
342
+ * Uses [T] extends [string] to prevent union distribution -- ensures that a
343
+ * union like 'admin' | 'editor' keeps the full union in each operator slot.
344
+ */
345
+ type ColumnFilterOperators<
346
+ TType,
347
+ TNullable extends boolean
348
+ > = ([TType] extends [string] ? ComparisonOperators<TType> & StringOperators : ComparisonOperators<TType>) & (TNullable extends true ? NullOperator : unknown);
349
+ /** Determine whether a column is nullable from its metadata. */
350
+ type IsNullable<C> = C extends ColumnBuilder<unknown, infer M> ? M extends {
351
+ readonly nullable: true;
352
+ } ? true : false : false;
353
+ /**
354
+ * FilterType<TColumns> — typed where clause.
355
+ *
356
+ * Each key maps to either:
357
+ * - A direct value (shorthand for `{ eq: value }`)
358
+ * - An object with typed filter operators
359
+ */
360
+ type FilterType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : InferColumnType<TColumns[K]> | ColumnFilterOperators<InferColumnType<TColumns[K]>, IsNullable<TColumns[K]>> };
361
+ type OrderByType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : "asc" | "desc" };
362
+ /**
363
+ * SelectOption<TColumns> — the `select` field in query options.
364
+ *
365
+ * Either:
366
+ * - `{ not: Annotation | Annotation[] }` — exclude columns by annotation(s)
367
+ * - `{ [column]: true }` — explicitly pick columns
368
+ *
369
+ * The two forms are mutually exclusive, enforced via `never` mapped keys.
370
+ */
371
+ type SelectOption<TColumns extends ColumnRecord> = ({
372
+ readonly not: AllAnnotations<TColumns> | readonly AllAnnotations<TColumns>[];
373
+ } & { readonly [K in keyof TColumns]? : never }) | ({ readonly [K in keyof TColumns]? : true } & {
374
+ readonly not?: never;
375
+ });
376
+ /** Extract selected keys from a select map (keys set to `true`). */
377
+ type SelectedKeys<
378
+ TColumns extends ColumnRecord,
379
+ TSelect
380
+ > = { [K in keyof TSelect] : K extends keyof TColumns ? (TSelect[K] extends true ? K : never) : never }[keyof TSelect];
381
+ /**
382
+ * Normalize `not` value to a union of annotation strings.
383
+ * - `'annotation'` → `'annotation'`
384
+ * - `readonly ['a', 'b']` → `'a' | 'b'`
385
+ */
386
+ type NormalizeAnnotations<T> = T extends readonly (infer F)[] ? F extends string ? F : never : T extends string ? T : never;
387
+ /**
388
+ * SelectNarrow<TColumns, TSelect> — applies a select clause to narrow the result type.
389
+ *
390
+ * - `{ not: 'sensitive' }` → excludes 'sensitive'-annotated AND 'hidden'-annotated columns
391
+ * - `{ not: ['sensitive', 'patchable'] }` → excludes columns with ANY listed annotation + 'hidden'
392
+ * - `{ id: true, name: true }` → picks only id and name
393
+ * - `undefined` → default: excludes 'hidden'-annotated columns ($infer behavior)
394
+ */
395
+ type SelectNarrow<
396
+ TColumns extends ColumnRecord,
397
+ TSelect
398
+ > = TSelect extends {
399
+ not: infer TNot;
400
+ } ? { [K in ColumnKeysWithoutAnyAnnotation<TColumns, NormalizeAnnotations<TNot> | "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> } : TSelect extends Record<string, true | undefined> ? { [K in SelectedKeys<TColumns, TSelect> & keyof TColumns] : InferColumnType<TColumns[K]> } : { [K in ColumnKeysWithoutAnyAnnotation<TColumns, "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> };
401
+ /** Relations record — maps relation names to RelationDef. */
402
+ type RelationsRecord = Record<string, RelationDef>;
403
+ /**
404
+ * The shape of include options for a given relations record.
405
+ * Each relation can be:
406
+ * - `true` — include with default fields
407
+ * - An object with `select`, `where`, `orderBy`, `limit` constrained to target table columns
408
+ */
409
+ type IncludeOption<TRelations extends RelationsRecord> = { [K in keyof TRelations]? : true | (RelationTarget<TRelations[K]> extends TableDef<infer TCols> ? {
410
+ select?: { [C in keyof TCols]? : true };
411
+ where?: FilterType<TCols>;
412
+ orderBy?: OrderByType<TCols>;
413
+ limit?: number;
414
+ /** Nested includes — untyped until full model registry is threaded through. */
415
+ include?: Record<string, unknown>;
416
+ } : never) };
417
+ /** Extract the target table from a RelationDef. */
418
+ type RelationTarget<R> = R extends RelationDef<infer TTarget, "one" | "many"> ? TTarget : never;
419
+ /** Extract the relation type ('one' | 'many') from a RelationDef. */
420
+ type RelationType<R> = R extends RelationDef<TableDef<ColumnRecord>, infer TType> ? TType : never;
421
+ /**
422
+ * Resolve a single included relation.
423
+ * - 'one' relations return a single object
424
+ * - 'many' relations return an array
425
+ * - When a `select` sub-clause is provided, the result is narrowed
426
+ */
427
+ type ResolveOneInclude<
428
+ R extends RelationDef,
429
+ TIncludeValue,
430
+ _Depth extends readonly unknown[] = []
431
+ > = TIncludeValue extends {
432
+ select: infer TSubSelect;
433
+ } ? RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, TSubSelect> : never : RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, undefined> : never;
434
+ /**
435
+ * IncludeResolve<TRelations, TInclude, Depth> — resolves all included relations.
436
+ *
437
+ * Depth is tracked using a tuple counter. Default cap = 2.
438
+ */
439
+ type IncludeResolve<
440
+ TRelations extends RelationsRecord,
441
+ TInclude,
442
+ _Depth extends readonly unknown[] = []
443
+ > = _Depth["length"] extends 3 ? unknown : { [K in keyof TInclude as K extends keyof TRelations ? TInclude[K] extends false | undefined ? never : K : never] : K extends keyof TRelations ? RelationType<TRelations[K]> extends "many" ? ResolveOneInclude<TRelations[K], TInclude[K], _Depth>[] : ResolveOneInclude<TRelations[K], TInclude[K], _Depth> : never };
444
+ /** Query options shape used by FindResult. */
445
+ interface FindOptions<
446
+ TColumns extends ColumnRecord = ColumnRecord,
447
+ TRelations extends RelationsRecord = RelationsRecord
448
+ > {
449
+ select?: SelectOption<TColumns>;
450
+ include?: IncludeOption<TRelations>;
451
+ where?: FilterType<TColumns>;
452
+ orderBy?: OrderByType<TColumns>;
453
+ }
454
+ /**
455
+ * FindResult<TTable, TOptions> — the return type of a typed query.
456
+ *
457
+ * Combines:
458
+ * - SelectNarrow for column selection
459
+ * - IncludeResolve for relation includes
460
+ *
461
+ * TOptions is structurally typed (not constrained to FindOptions) so that
462
+ * literal option objects flow through without widening.
463
+ */
464
+ type FindResult<
465
+ TTable extends TableDef<ColumnRecord>,
466
+ TOptions = unknown,
467
+ TRelations extends RelationsRecord = RelationsRecord
468
+ > = TTable extends TableDef<infer TColumns> ? SelectNarrow<TColumns, TOptions extends {
469
+ select: infer S;
470
+ } ? S : undefined> & (TOptions extends {
471
+ include: infer I;
472
+ } ? IncludeResolve<TRelations, I> : unknown) : never;
473
+ /**
474
+ * InsertInput<TTable> — standalone insert type utility.
475
+ * Makes columns with defaults optional, all others required.
476
+ */
477
+ type InsertInput<TTable extends TableDef<ColumnRecord>> = TTable["$insert"];
478
+ /**
479
+ * UpdateInput<TTable> — standalone update type utility.
480
+ * All non-PK columns, all optional.
481
+ */
482
+ type UpdateInput<TTable extends TableDef<ColumnRecord>> = TTable["$update"];
483
+ /** A model entry in the database registry, pairing a table with its relations. */
484
+ interface ModelEntry<
485
+ TTable extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
486
+ TRelations extends RelationsRecord = RelationsRecord
487
+ > {
488
+ readonly table: TTable;
489
+ readonly relations: TRelations;
490
+ }
294
491
  /**
295
- * Database Adapter Types for @vertz/db
492
+ * Database<TModels> type that carries the full model registry.
296
493
  *
297
- * Generic adapter interface that abstracts database operations.
298
- * Implemented by SQLite, D1, and other database adapters.
494
+ * Used as the foundation for typed query methods (implemented in later tickets).
495
+ * Provides type-safe access to table definitions and their relations.
299
496
  */
300
- interface ListOptions {
497
+ interface Database<TModels extends Record<string, ModelEntry> = Record<string, ModelEntry>> {
498
+ readonly _models: TModels;
499
+ }
500
+ /** A single include entry with optional query constraints. */
501
+ interface AdapterIncludeEntry {
502
+ select?: Record<string, true>;
301
503
  where?: Record<string, unknown>;
302
504
  orderBy?: Record<string, "asc" | "desc">;
303
505
  limit?: number;
506
+ include?: Record<string, unknown>;
507
+ }
508
+ /** Include specification: maps relation names to `true` or structured entries. */
509
+ type AdapterIncludeSpec = Record<string, true | AdapterIncludeEntry>;
510
+ /**
511
+ * Resolves the where clause type for a given entry.
512
+ * When TEntry is the default (unparameterized), falls back to Record<string, unknown>.
513
+ */
514
+ type ResolveWhere<TEntry extends ModelEntry> = TEntry extends ModelEntry<infer TTable> ? TTable extends TableDef<infer TCols> ? [ColumnRecord] extends [TCols] ? Record<string, unknown> : FilterType<TCols> : Record<string, unknown> : Record<string, unknown>;
515
+ /**
516
+ * Resolves the orderBy type for a given entry.
517
+ * When TEntry is the default (unparameterized), falls back to Record<string, 'asc' | 'desc'>.
518
+ */
519
+ type ResolveOrderBy<TEntry extends ModelEntry> = TEntry extends ModelEntry<infer TTable> ? TTable extends TableDef<infer TCols> ? [ColumnRecord] extends [TCols] ? Record<string, "asc" | "desc"> : OrderByType<TCols> : Record<string, "asc" | "desc"> : Record<string, "asc" | "desc">;
520
+ /**
521
+ * Resolves the include type for a given entry.
522
+ * When TEntry is the default (unparameterized), falls back to AdapterIncludeSpec.
523
+ */
524
+ type ResolveInclude<TEntry extends ModelEntry> = TEntry extends ModelEntry<TableDef<ColumnRecord>, infer TRels> ? [Record<string, never>] extends [TRels] ? AdapterIncludeSpec : IncludeOption<TRels> : AdapterIncludeSpec;
525
+ interface ListOptions<TEntry extends ModelEntry = ModelEntry> {
526
+ where?: ResolveWhere<TEntry>;
527
+ orderBy?: ResolveOrderBy<TEntry>;
528
+ limit?: number;
304
529
  /** Cursor-based pagination: fetch records after this ID. */
305
530
  after?: string;
306
- }
307
- interface EntityDbAdapter {
308
- get(id: string): Promise<Record<string, unknown> | null>;
309
- list(options?: ListOptions): Promise<{
310
- data: Record<string, unknown>[];
531
+ /** Relation include specification for relation loading. */
532
+ include?: ResolveInclude<TEntry>;
533
+ }
534
+ /** Options for get-by-id operations. */
535
+ interface GetOptions<TEntry extends ModelEntry = ModelEntry> {
536
+ /** Relation include specification for relation loading. */
537
+ include?: ResolveInclude<TEntry>;
538
+ }
539
+ interface EntityDbAdapter<TEntry extends ModelEntry = ModelEntry> {
540
+ get(id: string, options?: GetOptions<TEntry>): Promise<TEntry["table"]["$response"] | null>;
541
+ list(options?: ListOptions<TEntry>): Promise<{
542
+ data: TEntry["table"]["$response"][];
311
543
  total: number;
312
544
  }>;
313
- create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
314
- update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
315
- delete(id: string): Promise<Record<string, unknown> | null>;
545
+ create(data: TEntry["table"]["$create_input"]): Promise<TEntry["table"]["$response"]>;
546
+ update(id: string, data: TEntry["table"]["$update_input"]): Promise<TEntry["table"]["$response"]>;
547
+ delete(id: string): Promise<TEntry["table"]["$response"] | null>;
316
548
  }
317
549
  interface D1DatabaseBinding {
318
550
  prepare(sql: string): D1PreparedStatement;
@@ -642,201 +874,6 @@ declare function toReadError(error: unknown, query?: string): ReadError;
642
874
  */
643
875
  declare function toWriteError(error: unknown, query?: string): WriteError;
644
876
  /**
645
- * Query executor — wraps raw SQL execution with error mapping.
646
- *
647
- * Takes a query function (from the database driver) and wraps it to:
648
- * 1. Execute parameterized SQL
649
- * 2. Map PG errors to typed DbError subclasses
650
- * 3. Return typed QueryResult
651
- */
652
- interface ExecutorResult<T> {
653
- readonly rows: readonly T[];
654
- readonly rowCount: number;
655
- }
656
- type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
657
- /** Operators available for comparable types (number, string, Date, bigint). */
658
- interface ComparisonOperators<T> {
659
- readonly eq?: T;
660
- readonly ne?: T;
661
- readonly gt?: T;
662
- readonly gte?: T;
663
- readonly lt?: T;
664
- readonly lte?: T;
665
- readonly in?: readonly T[];
666
- readonly notIn?: readonly T[];
667
- }
668
- /** Additional operators for string columns. */
669
- interface StringOperators {
670
- readonly contains?: string;
671
- readonly startsWith?: string;
672
- readonly endsWith?: string;
673
- }
674
- /** The `isNull` operator — only available for nullable columns. */
675
- interface NullOperator {
676
- readonly isNull?: boolean;
677
- }
678
- /**
679
- * Resolves the filter operators for a single column based on its inferred type
680
- * and nullable metadata.
681
- *
682
- * - All types get comparison + in/notIn
683
- * - String types additionally get contains, startsWith, endsWith
684
- * - Nullable columns additionally get isNull
685
- *
686
- * Uses [T] extends [string] to prevent union distribution -- ensures that a
687
- * union like 'admin' | 'editor' keeps the full union in each operator slot.
688
- */
689
- type ColumnFilterOperators<
690
- TType,
691
- TNullable extends boolean
692
- > = ([TType] extends [string] ? ComparisonOperators<TType> & StringOperators : ComparisonOperators<TType>) & (TNullable extends true ? NullOperator : unknown);
693
- /** Determine whether a column is nullable from its metadata. */
694
- type IsNullable<C> = C extends ColumnBuilder<unknown, infer M> ? M extends {
695
- readonly nullable: true;
696
- } ? true : false : false;
697
- /**
698
- * FilterType<TColumns> — typed where clause.
699
- *
700
- * Each key maps to either:
701
- * - A direct value (shorthand for `{ eq: value }`)
702
- * - An object with typed filter operators
703
- */
704
- type FilterType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : InferColumnType<TColumns[K]> | ColumnFilterOperators<InferColumnType<TColumns[K]>, IsNullable<TColumns[K]>> };
705
- type OrderByType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : "asc" | "desc" };
706
- /**
707
- * SelectOption<TColumns> — the `select` field in query options.
708
- *
709
- * Either:
710
- * - `{ not: Annotation | Annotation[] }` — exclude columns by annotation(s)
711
- * - `{ [column]: true }` — explicitly pick columns
712
- *
713
- * The two forms are mutually exclusive, enforced via `never` mapped keys.
714
- */
715
- type SelectOption<TColumns extends ColumnRecord> = ({
716
- readonly not: AllAnnotations<TColumns> | readonly AllAnnotations<TColumns>[];
717
- } & { readonly [K in keyof TColumns]? : never }) | ({ readonly [K in keyof TColumns]? : true } & {
718
- readonly not?: never;
719
- });
720
- /** Extract selected keys from a select map (keys set to `true`). */
721
- type SelectedKeys<
722
- TColumns extends ColumnRecord,
723
- TSelect
724
- > = { [K in keyof TSelect] : K extends keyof TColumns ? (TSelect[K] extends true ? K : never) : never }[keyof TSelect];
725
- /**
726
- * Normalize `not` value to a union of annotation strings.
727
- * - `'annotation'` → `'annotation'`
728
- * - `readonly ['a', 'b']` → `'a' | 'b'`
729
- */
730
- type NormalizeAnnotations<T> = T extends readonly (infer F)[] ? F extends string ? F : never : T extends string ? T : never;
731
- /**
732
- * SelectNarrow<TColumns, TSelect> — applies a select clause to narrow the result type.
733
- *
734
- * - `{ not: 'sensitive' }` → excludes 'sensitive'-annotated AND 'hidden'-annotated columns
735
- * - `{ not: ['sensitive', 'patchable'] }` → excludes columns with ANY listed annotation + 'hidden'
736
- * - `{ id: true, name: true }` → picks only id and name
737
- * - `undefined` → default: excludes 'hidden'-annotated columns ($infer behavior)
738
- */
739
- type SelectNarrow<
740
- TColumns extends ColumnRecord,
741
- TSelect
742
- > = TSelect extends {
743
- not: infer TNot;
744
- } ? { [K in ColumnKeysWithoutAnyAnnotation<TColumns, NormalizeAnnotations<TNot> | "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> } : TSelect extends Record<string, true | undefined> ? { [K in SelectedKeys<TColumns, TSelect> & keyof TColumns] : InferColumnType<TColumns[K]> } : { [K in ColumnKeysWithoutAnyAnnotation<TColumns, "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> };
745
- /** Relations record — maps relation names to RelationDef. */
746
- type RelationsRecord = Record<string, RelationDef>;
747
- /**
748
- * The shape of include options for a given relations record.
749
- * Each relation can be:
750
- * - `true` — include with default fields
751
- * - An object with optional `select` clause for narrowing
752
- */
753
- type IncludeOption<TRelations extends RelationsRecord> = { [K in keyof TRelations]? : true | {
754
- select?: Record<string, true>;
755
- } };
756
- /** Extract the target table from a RelationDef. */
757
- type RelationTarget<R> = R extends RelationDef<infer TTarget, "one" | "many"> ? TTarget : never;
758
- /** Extract the relation type ('one' | 'many') from a RelationDef. */
759
- type RelationType<R> = R extends RelationDef<TableDef<ColumnRecord>, infer TType> ? TType : never;
760
- /**
761
- * Resolve a single included relation.
762
- * - 'one' relations return a single object
763
- * - 'many' relations return an array
764
- * - When a `select` sub-clause is provided, the result is narrowed
765
- */
766
- type ResolveOneInclude<
767
- R extends RelationDef,
768
- TIncludeValue,
769
- _Depth extends readonly unknown[] = []
770
- > = TIncludeValue extends {
771
- select: infer TSubSelect;
772
- } ? RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, TSubSelect> : never : RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, undefined> : never;
773
- /**
774
- * IncludeResolve<TRelations, TInclude, Depth> — resolves all included relations.
775
- *
776
- * Depth is tracked using a tuple counter. Default cap = 2.
777
- */
778
- type IncludeResolve<
779
- TRelations extends RelationsRecord,
780
- TInclude,
781
- _Depth extends readonly unknown[] = []
782
- > = _Depth["length"] extends 3 ? unknown : { [K in keyof TInclude as K extends keyof TRelations ? TInclude[K] extends false | undefined ? never : K : never] : K extends keyof TRelations ? RelationType<TRelations[K]> extends "many" ? ResolveOneInclude<TRelations[K], TInclude[K], _Depth>[] : ResolveOneInclude<TRelations[K], TInclude[K], _Depth> : never };
783
- /** Query options shape used by FindResult. */
784
- interface FindOptions<
785
- TColumns extends ColumnRecord = ColumnRecord,
786
- TRelations extends RelationsRecord = RelationsRecord
787
- > {
788
- select?: SelectOption<TColumns>;
789
- include?: IncludeOption<TRelations>;
790
- where?: FilterType<TColumns>;
791
- orderBy?: OrderByType<TColumns>;
792
- }
793
- /**
794
- * FindResult<TTable, TOptions> — the return type of a typed query.
795
- *
796
- * Combines:
797
- * - SelectNarrow for column selection
798
- * - IncludeResolve for relation includes
799
- *
800
- * TOptions is structurally typed (not constrained to FindOptions) so that
801
- * literal option objects flow through without widening.
802
- */
803
- type FindResult<
804
- TTable extends TableDef<ColumnRecord>,
805
- TOptions = unknown,
806
- TRelations extends RelationsRecord = RelationsRecord
807
- > = TTable extends TableDef<infer TColumns> ? SelectNarrow<TColumns, TOptions extends {
808
- select: infer S;
809
- } ? S : undefined> & (TOptions extends {
810
- include: infer I;
811
- } ? IncludeResolve<TRelations, I> : unknown) : never;
812
- /**
813
- * InsertInput<TTable> — standalone insert type utility.
814
- * Makes columns with defaults optional, all others required.
815
- */
816
- type InsertInput<TTable extends TableDef<ColumnRecord>> = TTable["$insert"];
817
- /**
818
- * UpdateInput<TTable> — standalone update type utility.
819
- * All non-PK columns, all optional.
820
- */
821
- type UpdateInput<TTable extends TableDef<ColumnRecord>> = TTable["$update"];
822
- /** A model entry in the database registry, pairing a table with its relations. */
823
- interface ModelEntry<
824
- TTable extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
825
- TRelations extends RelationsRecord = RelationsRecord
826
- > {
827
- readonly table: TTable;
828
- readonly relations: TRelations;
829
- }
830
- /**
831
- * Database<TModels> — type that carries the full model registry.
832
- *
833
- * Used as the foundation for typed query methods (implemented in later tickets).
834
- * Provides type-safe access to table definitions and their relations.
835
- */
836
- interface Database<TModels extends Record<string, ModelEntry> = Record<string, ModelEntry>> {
837
- readonly _models: TModels;
838
- }
839
- /**
840
877
  * SQL tagged template literal and escape hatch.
841
878
  *
842
879
  * Provides a safe, composable way to write raw SQL with automatic parameterization.
@@ -1090,9 +1127,26 @@ interface DatabaseInternals<TModels extends Record<string, ModelEntry>> {
1090
1127
  /** The computed tenant scoping graph. */
1091
1128
  readonly tenantGraph: TenantGraph;
1092
1129
  }
1130
+ /**
1131
+ * Scoped client for use within a transaction callback.
1132
+ * Provides the same model delegates and raw query as DatabaseClient —
1133
+ * all operations execute within a single atomic transaction.
1134
+ *
1135
+ * Auto-commits on success, auto-rolls-back on error.
1136
+ */
1137
+ type TransactionClient<TModels extends Record<string, ModelEntry>> = { readonly [K in keyof TModels] : ModelDelegate<TModels[K]> } & {
1138
+ /** Execute a raw SQL query within the transaction. */
1139
+ query<T = Record<string, unknown>>(fragment: SqlFragment): Promise<Result<QueryResult<T>, ReadError>>;
1140
+ };
1093
1141
  type DatabaseClient<TModels extends Record<string, ModelEntry>> = { readonly [K in keyof TModels] : ModelDelegate<TModels[K]> } & {
1094
1142
  /** Execute a raw SQL query via the sql tagged template. */
1095
1143
  query<T = Record<string, unknown>>(fragment: SqlFragment): Promise<Result<QueryResult<T>, ReadError>>;
1144
+ /**
1145
+ * Execute a callback within a database transaction.
1146
+ * All operations on the `tx` client are atomic — auto-commits on success,
1147
+ * auto-rolls-back if the callback throws.
1148
+ */
1149
+ transaction<T>(fn: (tx: TransactionClient<TModels>) => Promise<T>): Promise<T>;
1096
1150
  /** Close all pool connections. */
1097
1151
  close(): Promise<void>;
1098
1152
  /** Check if the database connection is healthy. */
@@ -1136,7 +1190,7 @@ declare function createDb<TModels extends Record<string, ModelEntry>>(options: C
1136
1190
  declare function createDatabaseBridgeAdapter<
1137
1191
  TModels extends Record<string, ModelEntry>,
1138
1192
  TName extends keyof TModels & string
1139
- >(db: DatabaseClient<TModels>, tableName: TName): EntityDbAdapter;
1193
+ >(db: DatabaseClient<TModels>, tableName: TName): EntityDbAdapter<TModels[TName]>;
1140
1194
  interface SqliteAdapterOptions<T extends ColumnRecord> {
1141
1195
  /** The table schema definition */
1142
1196
  schema: TableDef<T>;
@@ -1656,4 +1710,4 @@ type StrictKeys<
1656
1710
  TAllowed extends string,
1657
1711
  TTable extends string
1658
1712
  > = TRecord extends Record<string, unknown> ? { [K in keyof TRecord] : K extends TAllowed ? TRecord[K] : InvalidColumn<K & string, TTable> } : TRecord;
1659
- export { validateIndexes, toWriteError, toReadError, resolveErrorCode, reset, push, parsePgError, parseMigrationName, migrateStatus, migrateDev, migrateDeploy, generateId, formatDiagnostic, explainError, diagnoseError, detectSchemaDrift, defineAnnotations, defaultSqliteDialect, defaultPostgresDialect, dbErrorToHttpError, d, createSqliteDriver2 as createSqliteDriver, createSqliteAdapter, createSnapshot, createRegistry, createPostgresDriver, createEnumRegistry, createDb, createDatabaseBridgeAdapter, createD1Driver, createD1Adapter, computeTenantGraph, baseline, WriteError, VarcharMeta, ValidateKeys, UpdateInput, UniqueConstraintErrorOptions, UniqueConstraintError, TenantGraph, TableDef, StrictKeys, SqliteDialect, SqliteAdapterOptions, SelectOption, SelectNarrow, SchemaSnapshot, SchemaLike, ResetResult, ResetOptions, RenameSuggestion, RelationDef, RegisteredEnum, ReadError, QueryResult, PushResult, PushOptions, PostgresDriver, PostgresDialect, PoolConfig, PgErrorInput, PgCodeToName, OrderByType, NotNullErrorOptions, NotNullError, NotFoundError, ModelSchemas, ModelOptions, ModelEntry, ModelDelegate, ModelDef, MixedSelectError, MigrationQueryFn, MigrationInfo, MigrationFile, MigrationError2 as MigrationError, MigrateStatusResult, MigrateStatusOptions, MigrateDevResult, MigrateDevOptions, MigrateDeployResult, MigrateDeployOptions, ListOptions, JsonbValidator, InvalidRelation, InvalidFilterType, InvalidColumn, InsertInput, InferColumnType, IndexType, IndexOptions, IndexDef, IncludeResolve, IncludeOption, HttpErrorResponse, FormatMeta, ForeignKeyErrorOptions, ForeignKeyError, FindResult, FindOptions, FilterType, EnumMeta, EntityDbAdapter, DriftEntry, Dialect, DiagnosticResult, DecimalMeta, DbQueryError, DbNotFoundError, DbErrorJson, DbErrorCodeValue, DbErrorCodeName, DbErrorCode, DbErrorBase, DbError, DbDriver, DbConstraintError, DbConnectionError, DatabaseInternals, DatabaseClient, Database, D1PreparedStatement, D1DatabaseBinding, D1AdapterOptions, CreateDbOptions, ConnectionPoolExhaustedError, ConnectionError, ColumnTypeMeta, ColumnMetadata, ColumnBuilder, CodeChange, CheckConstraintErrorOptions, CheckConstraintError, BaselineResult, BaselineOptions };
1713
+ export { validateIndexes, toWriteError, toReadError, resolveErrorCode, reset, push, parsePgError, parseMigrationName, migrateStatus, migrateDev, migrateDeploy, generateId, formatDiagnostic, explainError, diagnoseError, detectSchemaDrift, defineAnnotations, defaultSqliteDialect, defaultPostgresDialect, dbErrorToHttpError, d, createSqliteDriver2 as createSqliteDriver, createSqliteAdapter, createSnapshot, createRegistry, createPostgresDriver, createEnumRegistry, createDb, createDatabaseBridgeAdapter, createD1Driver, createD1Adapter, computeTenantGraph, baseline, WriteError, VarcharMeta, ValidateKeys, UpdateInput, UniqueConstraintErrorOptions, UniqueConstraintError, TransactionClient, TenantGraph, TableDef, StrictKeys, SqliteDialect, SqliteAdapterOptions, SelectOption, SelectNarrow, SchemaSnapshot, SchemaLike, ResetResult, ResetOptions, RenameSuggestion, RelationDef, RegisteredEnum, ReadError, QueryResult, PushResult, PushOptions, PostgresDriver, PostgresDialect, PoolConfig, PgErrorInput, PgCodeToName, OrderByType, NotNullErrorOptions, NotNullError, NotFoundError, ModelSchemas, ModelOptions, ModelEntry, ModelDelegate, ModelDef, MixedSelectError, MigrationQueryFn, MigrationInfo, MigrationFile, MigrationError2 as MigrationError, MigrateStatusResult, MigrateStatusOptions, MigrateDevResult, MigrateDevOptions, MigrateDeployResult, MigrateDeployOptions, ListOptions, JsonbValidator, InvalidRelation, InvalidFilterType, InvalidColumn, InsertInput, InferColumnType, IndexType, IndexOptions, IndexDef, IncludeResolve, IncludeOption, HttpErrorResponse, GetOptions, FormatMeta, ForeignKeyErrorOptions, ForeignKeyError, FindResult, FindOptions, FilterType, EnumMeta, EntityDbAdapter, DriftEntry, Dialect, DiagnosticResult, DecimalMeta, DbQueryError, DbNotFoundError, DbErrorJson, DbErrorCodeValue, DbErrorCodeName, DbErrorCode, DbErrorBase, DbError, DbDriver, DbConstraintError, DbConnectionError, DatabaseInternals, DatabaseClient, Database, D1PreparedStatement, D1DatabaseBinding, D1AdapterOptions, CreateDbOptions, ConnectionPoolExhaustedError, ConnectionError, ColumnTypeMeta, ColumnMetadata, ColumnBuilder, CodeChange, CheckConstraintErrorOptions, CheckConstraintError, BaselineResult, BaselineOptions, AdapterIncludeSpec, AdapterIncludeEntry };