@vertz/db 0.2.14 → 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>;
@@ -94,6 +113,7 @@ interface ColumnBuilder<
94
113
  readonly _meta: TMeta;
95
114
  primary(options?: {
96
115
  generate?: "cuid" | "uuid" | "nanoid";
116
+ generated?: boolean;
97
117
  }): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault" | "generate"> & {
98
118
  readonly primary: true;
99
119
  readonly hasDefault: true;
@@ -290,28 +310,241 @@ interface TableOptions {
290
310
  relations?: Record<string, RelationDef>;
291
311
  indexes?: IndexDef[];
292
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
+ }
293
491
  /**
294
- * Database Adapter Types for @vertz/db
492
+ * Database<TModels> type that carries the full model registry.
295
493
  *
296
- * Generic adapter interface that abstracts database operations.
297
- * 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.
298
496
  */
299
- 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>;
300
503
  where?: Record<string, unknown>;
301
504
  orderBy?: Record<string, "asc" | "desc">;
302
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;
303
529
  /** Cursor-based pagination: fetch records after this ID. */
304
530
  after?: string;
305
- }
306
- interface EntityDbAdapter {
307
- get(id: string): Promise<Record<string, unknown> | null>;
308
- list(options?: ListOptions): Promise<{
309
- 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"][];
310
543
  total: number;
311
544
  }>;
312
- create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
313
- update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
314
- 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>;
315
548
  }
316
549
  interface D1DatabaseBinding {
317
550
  prepare(sql: string): D1PreparedStatement;
@@ -641,201 +874,6 @@ declare function toReadError(error: unknown, query?: string): ReadError;
641
874
  */
642
875
  declare function toWriteError(error: unknown, query?: string): WriteError;
643
876
  /**
644
- * Query executor — wraps raw SQL execution with error mapping.
645
- *
646
- * Takes a query function (from the database driver) and wraps it to:
647
- * 1. Execute parameterized SQL
648
- * 2. Map PG errors to typed DbError subclasses
649
- * 3. Return typed QueryResult
650
- */
651
- interface ExecutorResult<T> {
652
- readonly rows: readonly T[];
653
- readonly rowCount: number;
654
- }
655
- type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
656
- /** Operators available for comparable types (number, string, Date, bigint). */
657
- interface ComparisonOperators<T> {
658
- readonly eq?: T;
659
- readonly ne?: T;
660
- readonly gt?: T;
661
- readonly gte?: T;
662
- readonly lt?: T;
663
- readonly lte?: T;
664
- readonly in?: readonly T[];
665
- readonly notIn?: readonly T[];
666
- }
667
- /** Additional operators for string columns. */
668
- interface StringOperators {
669
- readonly contains?: string;
670
- readonly startsWith?: string;
671
- readonly endsWith?: string;
672
- }
673
- /** The `isNull` operator — only available for nullable columns. */
674
- interface NullOperator {
675
- readonly isNull?: boolean;
676
- }
677
- /**
678
- * Resolves the filter operators for a single column based on its inferred type
679
- * and nullable metadata.
680
- *
681
- * - All types get comparison + in/notIn
682
- * - String types additionally get contains, startsWith, endsWith
683
- * - Nullable columns additionally get isNull
684
- *
685
- * Uses [T] extends [string] to prevent union distribution -- ensures that a
686
- * union like 'admin' | 'editor' keeps the full union in each operator slot.
687
- */
688
- type ColumnFilterOperators<
689
- TType,
690
- TNullable extends boolean
691
- > = ([TType] extends [string] ? ComparisonOperators<TType> & StringOperators : ComparisonOperators<TType>) & (TNullable extends true ? NullOperator : unknown);
692
- /** Determine whether a column is nullable from its metadata. */
693
- type IsNullable<C> = C extends ColumnBuilder<unknown, infer M> ? M extends {
694
- readonly nullable: true;
695
- } ? true : false : false;
696
- /**
697
- * FilterType<TColumns> — typed where clause.
698
- *
699
- * Each key maps to either:
700
- * - A direct value (shorthand for `{ eq: value }`)
701
- * - An object with typed filter operators
702
- */
703
- type FilterType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : InferColumnType<TColumns[K]> | ColumnFilterOperators<InferColumnType<TColumns[K]>, IsNullable<TColumns[K]>> };
704
- type OrderByType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : "asc" | "desc" };
705
- /**
706
- * SelectOption<TColumns> — the `select` field in query options.
707
- *
708
- * Either:
709
- * - `{ not: Annotation | Annotation[] }` — exclude columns by annotation(s)
710
- * - `{ [column]: true }` — explicitly pick columns
711
- *
712
- * The two forms are mutually exclusive, enforced via `never` mapped keys.
713
- */
714
- type SelectOption<TColumns extends ColumnRecord> = ({
715
- readonly not: AllAnnotations<TColumns> | readonly AllAnnotations<TColumns>[];
716
- } & { readonly [K in keyof TColumns]? : never }) | ({ readonly [K in keyof TColumns]? : true } & {
717
- readonly not?: never;
718
- });
719
- /** Extract selected keys from a select map (keys set to `true`). */
720
- type SelectedKeys<
721
- TColumns extends ColumnRecord,
722
- TSelect
723
- > = { [K in keyof TSelect] : K extends keyof TColumns ? (TSelect[K] extends true ? K : never) : never }[keyof TSelect];
724
- /**
725
- * Normalize `not` value to a union of annotation strings.
726
- * - `'annotation'` → `'annotation'`
727
- * - `readonly ['a', 'b']` → `'a' | 'b'`
728
- */
729
- type NormalizeAnnotations<T> = T extends readonly (infer F)[] ? F extends string ? F : never : T extends string ? T : never;
730
- /**
731
- * SelectNarrow<TColumns, TSelect> — applies a select clause to narrow the result type.
732
- *
733
- * - `{ not: 'sensitive' }` → excludes 'sensitive'-annotated AND 'hidden'-annotated columns
734
- * - `{ not: ['sensitive', 'patchable'] }` → excludes columns with ANY listed annotation + 'hidden'
735
- * - `{ id: true, name: true }` → picks only id and name
736
- * - `undefined` → default: excludes 'hidden'-annotated columns ($infer behavior)
737
- */
738
- type SelectNarrow<
739
- TColumns extends ColumnRecord,
740
- TSelect
741
- > = TSelect extends {
742
- not: infer TNot;
743
- } ? { [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]> };
744
- /** Relations record — maps relation names to RelationDef. */
745
- type RelationsRecord = Record<string, RelationDef>;
746
- /**
747
- * The shape of include options for a given relations record.
748
- * Each relation can be:
749
- * - `true` — include with default fields
750
- * - An object with optional `select` clause for narrowing
751
- */
752
- type IncludeOption<TRelations extends RelationsRecord> = { [K in keyof TRelations]? : true | {
753
- select?: Record<string, true>;
754
- } };
755
- /** Extract the target table from a RelationDef. */
756
- type RelationTarget<R> = R extends RelationDef<infer TTarget, "one" | "many"> ? TTarget : never;
757
- /** Extract the relation type ('one' | 'many') from a RelationDef. */
758
- type RelationType<R> = R extends RelationDef<TableDef<ColumnRecord>, infer TType> ? TType : never;
759
- /**
760
- * Resolve a single included relation.
761
- * - 'one' relations return a single object
762
- * - 'many' relations return an array
763
- * - When a `select` sub-clause is provided, the result is narrowed
764
- */
765
- type ResolveOneInclude<
766
- R extends RelationDef,
767
- TIncludeValue,
768
- _Depth extends readonly unknown[] = []
769
- > = TIncludeValue extends {
770
- select: infer TSubSelect;
771
- } ? RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, TSubSelect> : never : RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, undefined> : never;
772
- /**
773
- * IncludeResolve<TRelations, TInclude, Depth> — resolves all included relations.
774
- *
775
- * Depth is tracked using a tuple counter. Default cap = 2.
776
- */
777
- type IncludeResolve<
778
- TRelations extends RelationsRecord,
779
- TInclude,
780
- _Depth extends readonly unknown[] = []
781
- > = _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 };
782
- /** Query options shape used by FindResult. */
783
- interface FindOptions<
784
- TColumns extends ColumnRecord = ColumnRecord,
785
- TRelations extends RelationsRecord = RelationsRecord
786
- > {
787
- select?: SelectOption<TColumns>;
788
- include?: IncludeOption<TRelations>;
789
- where?: FilterType<TColumns>;
790
- orderBy?: OrderByType<TColumns>;
791
- }
792
- /**
793
- * FindResult<TTable, TOptions> — the return type of a typed query.
794
- *
795
- * Combines:
796
- * - SelectNarrow for column selection
797
- * - IncludeResolve for relation includes
798
- *
799
- * TOptions is structurally typed (not constrained to FindOptions) so that
800
- * literal option objects flow through without widening.
801
- */
802
- type FindResult<
803
- TTable extends TableDef<ColumnRecord>,
804
- TOptions = unknown,
805
- TRelations extends RelationsRecord = RelationsRecord
806
- > = TTable extends TableDef<infer TColumns> ? SelectNarrow<TColumns, TOptions extends {
807
- select: infer S;
808
- } ? S : undefined> & (TOptions extends {
809
- include: infer I;
810
- } ? IncludeResolve<TRelations, I> : unknown) : never;
811
- /**
812
- * InsertInput<TTable> — standalone insert type utility.
813
- * Makes columns with defaults optional, all others required.
814
- */
815
- type InsertInput<TTable extends TableDef<ColumnRecord>> = TTable["$insert"];
816
- /**
817
- * UpdateInput<TTable> — standalone update type utility.
818
- * All non-PK columns, all optional.
819
- */
820
- type UpdateInput<TTable extends TableDef<ColumnRecord>> = TTable["$update"];
821
- /** A model entry in the database registry, pairing a table with its relations. */
822
- interface ModelEntry<
823
- TTable extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
824
- TRelations extends RelationsRecord = RelationsRecord
825
- > {
826
- readonly table: TTable;
827
- readonly relations: TRelations;
828
- }
829
- /**
830
- * Database<TModels> — type that carries the full model registry.
831
- *
832
- * Used as the foundation for typed query methods (implemented in later tickets).
833
- * Provides type-safe access to table definitions and their relations.
834
- */
835
- interface Database<TModels extends Record<string, ModelEntry> = Record<string, ModelEntry>> {
836
- readonly _models: TModels;
837
- }
838
- /**
839
877
  * SQL tagged template literal and escape hatch.
840
878
  *
841
879
  * Provides a safe, composable way to write raw SQL with automatic parameterization.
@@ -1089,9 +1127,26 @@ interface DatabaseInternals<TModels extends Record<string, ModelEntry>> {
1089
1127
  /** The computed tenant scoping graph. */
1090
1128
  readonly tenantGraph: TenantGraph;
1091
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
+ };
1092
1141
  type DatabaseClient<TModels extends Record<string, ModelEntry>> = { readonly [K in keyof TModels] : ModelDelegate<TModels[K]> } & {
1093
1142
  /** Execute a raw SQL query via the sql tagged template. */
1094
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>;
1095
1150
  /** Close all pool connections. */
1096
1151
  close(): Promise<void>;
1097
1152
  /** Check if the database connection is healthy. */
@@ -1135,7 +1190,7 @@ declare function createDb<TModels extends Record<string, ModelEntry>>(options: C
1135
1190
  declare function createDatabaseBridgeAdapter<
1136
1191
  TModels extends Record<string, ModelEntry>,
1137
1192
  TName extends keyof TModels & string
1138
- >(db: DatabaseClient<TModels>, tableName: TName): EntityDbAdapter;
1193
+ >(db: DatabaseClient<TModels>, tableName: TName): EntityDbAdapter<TModels[TName]>;
1139
1194
  interface SqliteAdapterOptions<T extends ColumnRecord> {
1140
1195
  /** The table schema definition */
1141
1196
  schema: TableDef<T>;
@@ -1655,4 +1710,4 @@ type StrictKeys<
1655
1710
  TAllowed extends string,
1656
1711
  TTable extends string
1657
1712
  > = TRecord extends Record<string, unknown> ? { [K in keyof TRecord] : K extends TAllowed ? TRecord[K] : InvalidColumn<K & string, TTable> } : TRecord;
1658
- 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 };