@vertz/db 0.2.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.
@@ -0,0 +1,1346 @@
1
+ /**
2
+ * @vertz/db diagnostic utilities.
3
+ *
4
+ * Maps common error patterns to human-readable explanations.
5
+ * Useful for developers and LLMs understanding type errors and runtime exceptions.
6
+ *
7
+ * @module
8
+ */
9
+ interface DiagnosticResult {
10
+ /** Short identifier for the error pattern. */
11
+ readonly code: string;
12
+ /** Human-readable explanation of the error. */
13
+ readonly explanation: string;
14
+ /** Suggested fix or next step. */
15
+ readonly suggestion: string;
16
+ }
17
+ /**
18
+ * Analyzes an error message and returns a human-readable diagnostic.
19
+ *
20
+ * Works with both TypeScript type error messages (from the branded types)
21
+ * and runtime DbError messages.
22
+ *
23
+ * @param message - The error message string to analyze
24
+ * @returns A DiagnosticResult if the pattern is recognized, or null
25
+ */
26
+ declare function diagnoseError(message: string): DiagnosticResult | null;
27
+ /**
28
+ * Formats a diagnostic result as a multi-line string for display.
29
+ *
30
+ * @param diag - The diagnostic result to format
31
+ * @returns A formatted string with the error code, explanation, and suggestion
32
+ */
33
+ declare function formatDiagnostic(diag: DiagnosticResult): string;
34
+ /**
35
+ * Convenience: diagnose and format in one step.
36
+ *
37
+ * @param message - The error message to analyze
38
+ * @returns Formatted diagnostic string, or a fallback message
39
+ */
40
+ declare function explainError(message: string): string;
41
+ interface JsonbValidator<T> {
42
+ parse(value: unknown): T;
43
+ }
44
+ interface ColumnMetadata {
45
+ readonly sqlType: string;
46
+ readonly primary: boolean;
47
+ readonly unique: boolean;
48
+ readonly nullable: boolean;
49
+ readonly hasDefault: boolean;
50
+ readonly sensitive: boolean;
51
+ readonly hidden: boolean;
52
+ readonly isTenant: boolean;
53
+ readonly references: {
54
+ readonly table: string;
55
+ readonly column: string;
56
+ } | null;
57
+ readonly check: string | null;
58
+ readonly defaultValue?: unknown;
59
+ readonly format?: string;
60
+ readonly length?: number;
61
+ readonly precision?: number;
62
+ readonly scale?: number;
63
+ readonly enumName?: string;
64
+ readonly enumValues?: readonly string[];
65
+ readonly validator?: JsonbValidator<unknown>;
66
+ }
67
+ /** Phantom symbol to carry the TypeScript type without a runtime value. */
68
+ declare const PhantomType: unique symbol;
69
+ interface ColumnBuilder<
70
+ TType,
71
+ TMeta extends ColumnMetadata = ColumnMetadata
72
+ > {
73
+ /** Phantom field -- only exists at the type level for inference. Do not access at runtime. */
74
+ readonly [PhantomType]: TType;
75
+ readonly _meta: TMeta;
76
+ primary(): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault"> & {
77
+ readonly primary: true;
78
+ readonly hasDefault: true;
79
+ }>;
80
+ unique(): ColumnBuilder<TType, Omit<TMeta, "unique"> & {
81
+ readonly unique: true;
82
+ }>;
83
+ nullable(): ColumnBuilder<TType | null, Omit<TMeta, "nullable"> & {
84
+ readonly nullable: true;
85
+ }>;
86
+ default(value: TType | "now"): ColumnBuilder<TType, Omit<TMeta, "hasDefault"> & {
87
+ readonly hasDefault: true;
88
+ readonly defaultValue: TType | "now";
89
+ }>;
90
+ sensitive(): ColumnBuilder<TType, Omit<TMeta, "sensitive"> & {
91
+ readonly sensitive: true;
92
+ }>;
93
+ hidden(): ColumnBuilder<TType, Omit<TMeta, "hidden"> & {
94
+ readonly hidden: true;
95
+ }>;
96
+ check(sql: string): ColumnBuilder<TType, Omit<TMeta, "check"> & {
97
+ readonly check: string;
98
+ }>;
99
+ references(table: string, column?: string): ColumnBuilder<TType, Omit<TMeta, "references"> & {
100
+ readonly references: {
101
+ readonly table: string;
102
+ readonly column: string;
103
+ };
104
+ }>;
105
+ }
106
+ type InferColumnType<C> = C extends ColumnBuilder<infer T, ColumnMetadata> ? T : never;
107
+ type DefaultMeta<TSqlType extends string> = {
108
+ readonly sqlType: TSqlType;
109
+ readonly primary: false;
110
+ readonly unique: false;
111
+ readonly nullable: false;
112
+ readonly hasDefault: false;
113
+ readonly sensitive: false;
114
+ readonly hidden: false;
115
+ readonly isTenant: false;
116
+ readonly references: null;
117
+ readonly check: null;
118
+ };
119
+ /** Metadata for varchar columns — carries the length constraint. */
120
+ type VarcharMeta<TLength extends number> = DefaultMeta<"varchar"> & {
121
+ readonly length: TLength;
122
+ };
123
+ /** Metadata for decimal/numeric columns — carries precision and scale. */
124
+ type DecimalMeta<
125
+ TPrecision extends number,
126
+ TScale extends number
127
+ > = DefaultMeta<"decimal"> & {
128
+ readonly precision: TPrecision;
129
+ readonly scale: TScale;
130
+ };
131
+ /** Metadata for enum columns — carries the enum name and its values. */
132
+ type EnumMeta<
133
+ TName extends string,
134
+ TValues extends readonly string[]
135
+ > = DefaultMeta<"enum"> & {
136
+ readonly enumName: TName;
137
+ readonly enumValues: TValues;
138
+ };
139
+ /** Metadata for columns with a format constraint (e.g., email). */
140
+ type FormatMeta<
141
+ TSqlType extends string,
142
+ TFormat extends string
143
+ > = DefaultMeta<TSqlType> & {
144
+ readonly format: TFormat;
145
+ };
146
+ type SerialMeta = {
147
+ readonly sqlType: "serial";
148
+ readonly primary: false;
149
+ readonly unique: false;
150
+ readonly nullable: false;
151
+ readonly hasDefault: true;
152
+ readonly sensitive: false;
153
+ readonly hidden: false;
154
+ readonly isTenant: false;
155
+ readonly references: null;
156
+ readonly check: null;
157
+ };
158
+ type TenantMeta = {
159
+ readonly sqlType: "uuid";
160
+ readonly primary: false;
161
+ readonly unique: false;
162
+ readonly nullable: false;
163
+ readonly hasDefault: false;
164
+ readonly sensitive: false;
165
+ readonly hidden: false;
166
+ readonly isTenant: true;
167
+ readonly references: {
168
+ readonly table: string;
169
+ readonly column: string;
170
+ };
171
+ readonly check: null;
172
+ };
173
+ interface ThroughDef<TJoin extends TableDef<ColumnRecord> = TableDef<ColumnRecord>> {
174
+ readonly table: () => TJoin;
175
+ readonly thisKey: string;
176
+ readonly thatKey: string;
177
+ }
178
+ interface RelationDef<
179
+ TTarget extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
180
+ TType extends "one" | "many" = "one" | "many"
181
+ > {
182
+ readonly _type: TType;
183
+ readonly _target: () => TTarget;
184
+ readonly _foreignKey: string | null;
185
+ readonly _through: ThroughDef | null;
186
+ }
187
+ interface ManyRelationDef<TTarget extends TableDef<ColumnRecord> = TableDef<ColumnRecord>> extends RelationDef<TTarget, "many"> {
188
+ through<TJoin extends TableDef<ColumnRecord>>(joinTable: () => TJoin, thisKey: string, thatKey: string): RelationDef<TTarget, "many">;
189
+ }
190
+ interface IndexDef {
191
+ readonly columns: readonly string[];
192
+ }
193
+ /** A record of column builders -- the shape passed to d.table(). */
194
+ type ColumnRecord = Record<string, ColumnBuilder<unknown, ColumnMetadata>>;
195
+ /** Extract the TypeScript type from every column in a record. */
196
+ type InferColumns<T extends ColumnRecord> = { [K in keyof T] : InferColumnType<T[K]> };
197
+ /** Keys of columns where a given metadata flag is `true`. */
198
+ type ColumnKeysWhere<
199
+ T extends ColumnRecord,
200
+ Flag extends keyof ColumnMetadata
201
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? K : never : never }[keyof T];
202
+ /** Keys of columns where a given metadata flag is NOT `true` (i.e., false). */
203
+ type ColumnKeysWhereNot<
204
+ T extends ColumnRecord,
205
+ Flag extends keyof ColumnMetadata
206
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? never : K : never }[keyof T];
207
+ /**
208
+ * $infer -- default SELECT type.
209
+ * Excludes hidden columns. Includes everything else (including sensitive).
210
+ */
211
+ type Infer<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
212
+ /**
213
+ * $infer_all -- all columns including hidden.
214
+ */
215
+ type InferAll<T extends ColumnRecord> = InferColumns<T>;
216
+ /**
217
+ * $insert -- write type. ALL columns included (visibility is read-side only).
218
+ * Columns with hasDefault: true become optional.
219
+ */
220
+ type Insert<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hasDefault">] : InferColumnType<T[K]> } & { [K in ColumnKeysWhere<T, "hasDefault">]? : InferColumnType<T[K]> };
221
+ /**
222
+ * $update -- write type. ALL non-primary-key columns, all optional.
223
+ * Primary key excluded (you don't update a PK).
224
+ */
225
+ type Update<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "primary">]? : InferColumnType<T[K]> };
226
+ /**
227
+ * $not_sensitive -- excludes columns marked .sensitive() OR .hidden().
228
+ * (hidden implies sensitive for read purposes)
229
+ */
230
+ type NotSensitive<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "sensitive"> & ColumnKeysWhereNot<T, "hidden"> & keyof T] : InferColumnType<T[K]> };
231
+ /**
232
+ * $not_hidden -- excludes columns marked .hidden().
233
+ * Same as $infer (excludes hidden, keeps sensitive).
234
+ */
235
+ type NotHidden<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
236
+ interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
237
+ readonly _name: string;
238
+ readonly _columns: TColumns;
239
+ readonly _indexes: readonly IndexDef[];
240
+ readonly _shared: boolean;
241
+ /** Default SELECT type -- excludes hidden columns. */
242
+ readonly $infer: Infer<TColumns>;
243
+ /** All columns including hidden. */
244
+ readonly $infer_all: InferAll<TColumns>;
245
+ /** Insert type -- defaulted columns optional. ALL columns included. */
246
+ readonly $insert: Insert<TColumns>;
247
+ /** Update type -- all non-PK columns optional. ALL columns included. */
248
+ readonly $update: Update<TColumns>;
249
+ /** Excludes sensitive and hidden columns. */
250
+ readonly $not_sensitive: NotSensitive<TColumns>;
251
+ /** Excludes hidden columns. */
252
+ readonly $not_hidden: NotHidden<TColumns>;
253
+ /** Mark this table as shared / cross-tenant. */
254
+ shared(): TableDef<TColumns>;
255
+ }
256
+ interface TableOptions {
257
+ relations?: Record<string, RelationDef>;
258
+ indexes?: IndexDef[];
259
+ }
260
+ interface ColumnSnapshot {
261
+ type: string;
262
+ nullable: boolean;
263
+ primary: boolean;
264
+ unique: boolean;
265
+ default?: string;
266
+ sensitive?: boolean;
267
+ hidden?: boolean;
268
+ }
269
+ interface IndexSnapshot {
270
+ columns: string[];
271
+ }
272
+ interface ForeignKeySnapshot {
273
+ column: string;
274
+ targetTable: string;
275
+ targetColumn: string;
276
+ }
277
+ interface TableSnapshot {
278
+ columns: Record<string, ColumnSnapshot>;
279
+ indexes: IndexSnapshot[];
280
+ foreignKeys: ForeignKeySnapshot[];
281
+ _metadata: Record<string, unknown>;
282
+ }
283
+ interface SchemaSnapshot {
284
+ version: 1;
285
+ tables: Record<string, TableSnapshot>;
286
+ enums: Record<string, string[]>;
287
+ }
288
+ /**
289
+ * The query function type expected by the migration runner.
290
+ */
291
+ type MigrationQueryFn = (sql: string, params: readonly unknown[]) => Promise<{
292
+ rows: readonly Record<string, unknown>[];
293
+ rowCount: number;
294
+ }>;
295
+ /**
296
+ * Represents a migration file on disk.
297
+ */
298
+ interface MigrationFile {
299
+ name: string;
300
+ sql: string;
301
+ timestamp: number;
302
+ }
303
+ /**
304
+ * Result of applying (or dry-running) a migration.
305
+ */
306
+ interface ApplyResult {
307
+ /** The migration name. */
308
+ name: string;
309
+ /** The SQL that was (or would be) executed. */
310
+ sql: string;
311
+ /** The computed checksum of the migration SQL. */
312
+ checksum: string;
313
+ /** Whether this was a dry run. */
314
+ dryRun: boolean;
315
+ /** The statements that were (or would be) executed, in order. */
316
+ statements: string[];
317
+ }
318
+ interface MigrateDeployOptions {
319
+ queryFn: MigrationQueryFn;
320
+ migrationFiles: MigrationFile[];
321
+ /** When true, return the SQL that would be executed without applying. */
322
+ dryRun?: boolean;
323
+ }
324
+ interface MigrateDeployResult {
325
+ applied: string[];
326
+ alreadyApplied: string[];
327
+ /** When dry-run is enabled, contains the details of each migration that would be applied. */
328
+ dryRun: boolean;
329
+ /** Detailed results for each migration that was (or would be) applied. */
330
+ migrations?: ApplyResult[];
331
+ }
332
+ /**
333
+ * Apply all pending migrations in order.
334
+ *
335
+ * In dry-run mode, returns the SQL that would be executed without modifying the database.
336
+ */
337
+ declare function migrateDeploy(options: MigrateDeployOptions): Promise<MigrateDeployResult>;
338
+ interface RenameSuggestion {
339
+ table: string;
340
+ oldColumn: string;
341
+ newColumn: string;
342
+ confidence: number;
343
+ }
344
+ interface MigrateDevOptions {
345
+ queryFn: MigrationQueryFn;
346
+ currentSnapshot: SchemaSnapshot;
347
+ previousSnapshot: SchemaSnapshot;
348
+ migrationName: string;
349
+ existingFiles: string[];
350
+ migrationsDir: string;
351
+ writeFile: (path: string, content: string) => Promise<void>;
352
+ dryRun: boolean;
353
+ }
354
+ interface MigrateDevResult {
355
+ migrationFile: string;
356
+ sql: string;
357
+ appliedAt?: Date;
358
+ dryRun: boolean;
359
+ renames?: RenameSuggestion[];
360
+ snapshot: SchemaSnapshot;
361
+ }
362
+ /**
363
+ * Generate a migration from schema diff, optionally apply it.
364
+ *
365
+ * In dry-run mode, generates SQL and returns it WITHOUT applying or writing files.
366
+ */
367
+ declare function migrateDev(options: MigrateDevOptions): Promise<MigrateDevResult>;
368
+ interface PushOptions {
369
+ queryFn: MigrationQueryFn;
370
+ currentSnapshot: SchemaSnapshot;
371
+ previousSnapshot: SchemaSnapshot;
372
+ }
373
+ interface PushResult {
374
+ sql: string;
375
+ tablesAffected: string[];
376
+ }
377
+ /**
378
+ * Push schema changes directly to the database without creating a migration file.
379
+ */
380
+ declare function push(options: PushOptions): Promise<PushResult>;
381
+ interface MigrateStatusOptions {
382
+ queryFn: MigrationQueryFn;
383
+ migrationFiles: MigrationFile[];
384
+ }
385
+ interface MigrationInfo {
386
+ name: string;
387
+ checksum: string;
388
+ appliedAt: Date;
389
+ }
390
+ interface MigrateStatusResult {
391
+ applied: MigrationInfo[];
392
+ pending: string[];
393
+ }
394
+ /**
395
+ * Report the status of migrations: which are applied and which are pending.
396
+ */
397
+ declare function migrateStatus(options: MigrateStatusOptions): Promise<MigrateStatusResult>;
398
+ /**
399
+ * Query executor — wraps raw SQL execution with error mapping.
400
+ *
401
+ * Takes a query function (from the database driver) and wraps it to:
402
+ * 1. Execute parameterized SQL
403
+ * 2. Map PG errors to typed DbError subclasses
404
+ * 3. Return typed QueryResult
405
+ */
406
+ interface ExecutorResult<T> {
407
+ readonly rows: readonly T[];
408
+ readonly rowCount: number;
409
+ }
410
+ type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
411
+ /** Operators available for comparable types (number, string, Date, bigint). */
412
+ interface ComparisonOperators<T> {
413
+ readonly eq?: T;
414
+ readonly ne?: T;
415
+ readonly gt?: T;
416
+ readonly gte?: T;
417
+ readonly lt?: T;
418
+ readonly lte?: T;
419
+ readonly in?: readonly T[];
420
+ readonly notIn?: readonly T[];
421
+ }
422
+ /** Additional operators for string columns. */
423
+ interface StringOperators {
424
+ readonly contains?: string;
425
+ readonly startsWith?: string;
426
+ readonly endsWith?: string;
427
+ }
428
+ /** The `isNull` operator — only available for nullable columns. */
429
+ interface NullOperator {
430
+ readonly isNull?: boolean;
431
+ }
432
+ /**
433
+ * Resolves the filter operators for a single column based on its inferred type
434
+ * and nullable metadata.
435
+ *
436
+ * - All types get comparison + in/notIn
437
+ * - String types additionally get contains, startsWith, endsWith
438
+ * - Nullable columns additionally get isNull
439
+ *
440
+ * Uses [T] extends [string] to prevent union distribution -- ensures that a
441
+ * union like 'admin' | 'editor' keeps the full union in each operator slot.
442
+ */
443
+ type ColumnFilterOperators<
444
+ TType,
445
+ TNullable extends boolean
446
+ > = ([TType] extends [string] ? ComparisonOperators<TType> & StringOperators : ComparisonOperators<TType>) & (TNullable extends true ? NullOperator : unknown);
447
+ /** Determine whether a column is nullable from its metadata. */
448
+ type IsNullable<C> = C extends ColumnBuilder<unknown, infer M> ? M extends {
449
+ readonly nullable: true;
450
+ } ? true : false : false;
451
+ /**
452
+ * FilterType<TColumns> — typed where clause.
453
+ *
454
+ * Each key maps to either:
455
+ * - A direct value (shorthand for `{ eq: value }`)
456
+ * - An object with typed filter operators
457
+ */
458
+ type FilterType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : InferColumnType<TColumns[K]> | ColumnFilterOperators<InferColumnType<TColumns[K]>, IsNullable<TColumns[K]>> };
459
+ type OrderByType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : "asc" | "desc" };
460
+ /**
461
+ * SelectOption<TColumns> — the `select` field in query options.
462
+ *
463
+ * Either:
464
+ * - `{ not: 'sensitive' | 'hidden' }` — exclude columns by visibility category
465
+ * - `{ [column]: true }` — explicitly pick columns
466
+ *
467
+ * The two forms are mutually exclusive, enforced via `never` mapped keys.
468
+ */
469
+ type SelectOption<TColumns extends ColumnRecord> = ({
470
+ readonly not: "sensitive" | "hidden";
471
+ } & { readonly [K in keyof TColumns]? : never }) | ({ readonly [K in keyof TColumns]? : true } & {
472
+ readonly not?: never;
473
+ });
474
+ /** Keys of columns where a given metadata flag is NOT `true`. */
475
+ type ColumnKeysWhereNot2<
476
+ T extends ColumnRecord,
477
+ Flag extends keyof ColumnMetadata
478
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? never : K : never }[keyof T];
479
+ /** Extract selected keys from a select map (keys set to `true`). */
480
+ type SelectedKeys<
481
+ TColumns extends ColumnRecord,
482
+ TSelect
483
+ > = { [K in keyof TSelect] : K extends keyof TColumns ? (TSelect[K] extends true ? K : never) : never }[keyof TSelect];
484
+ /**
485
+ * SelectNarrow<TColumns, TSelect> — applies a select clause to narrow the result type.
486
+ *
487
+ * - `{ not: 'sensitive' }` → excludes sensitive AND hidden columns
488
+ * - `{ not: 'hidden' }` → excludes hidden columns
489
+ * - `{ id: true, name: true }` → picks only id and name
490
+ * - `undefined` → default: excludes hidden columns ($infer behavior)
491
+ */
492
+ type SelectNarrow<
493
+ TColumns extends ColumnRecord,
494
+ TSelect
495
+ > = TSelect extends {
496
+ not: "sensitive";
497
+ } ? { [K in ColumnKeysWhereNot2<TColumns, "sensitive"> & ColumnKeysWhereNot2<TColumns, "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> } : TSelect extends {
498
+ not: "hidden";
499
+ } ? { [K in ColumnKeysWhereNot2<TColumns, "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> } : TSelect extends Record<string, true | undefined> ? { [K in SelectedKeys<TColumns, TSelect> & keyof TColumns] : InferColumnType<TColumns[K]> } : { [K in ColumnKeysWhereNot2<TColumns, "hidden"> & keyof TColumns] : InferColumnType<TColumns[K]> };
500
+ /** Relations record — maps relation names to RelationDef. */
501
+ type RelationsRecord = Record<string, RelationDef>;
502
+ /**
503
+ * The shape of include options for a given relations record.
504
+ * Each relation can be:
505
+ * - `true` — include with default fields
506
+ * - An object with optional `select` clause for narrowing
507
+ */
508
+ type IncludeOption<TRelations extends RelationsRecord> = { [K in keyof TRelations]? : true | {
509
+ select?: Record<string, true>;
510
+ } };
511
+ /** Extract the target table from a RelationDef. */
512
+ type RelationTarget<R> = R extends RelationDef<infer TTarget, "one" | "many"> ? TTarget : never;
513
+ /** Extract the relation type ('one' | 'many') from a RelationDef. */
514
+ type RelationType<R> = R extends RelationDef<TableDef<ColumnRecord>, infer TType> ? TType : never;
515
+ /**
516
+ * Resolve a single included relation.
517
+ * - 'one' relations return a single object
518
+ * - 'many' relations return an array
519
+ * - When a `select` sub-clause is provided, the result is narrowed
520
+ */
521
+ type ResolveOneInclude<
522
+ R extends RelationDef,
523
+ TIncludeValue,
524
+ _Depth extends readonly unknown[] = []
525
+ > = TIncludeValue extends {
526
+ select: infer TSubSelect;
527
+ } ? RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, TSubSelect> : never : RelationTarget<R> extends TableDef<infer TCols> ? SelectNarrow<TCols, undefined> : never;
528
+ /**
529
+ * IncludeResolve<TRelations, TInclude, Depth> — resolves all included relations.
530
+ *
531
+ * Depth is tracked using a tuple counter. Default cap = 2.
532
+ */
533
+ type IncludeResolve<
534
+ TRelations extends RelationsRecord,
535
+ TInclude,
536
+ _Depth extends readonly unknown[] = []
537
+ > = _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 };
538
+ /** Query options shape used by FindResult. */
539
+ interface FindOptions<
540
+ TColumns extends ColumnRecord = ColumnRecord,
541
+ TRelations extends RelationsRecord = RelationsRecord
542
+ > {
543
+ select?: SelectOption<TColumns>;
544
+ include?: IncludeOption<TRelations>;
545
+ where?: FilterType<TColumns>;
546
+ orderBy?: OrderByType<TColumns>;
547
+ }
548
+ /**
549
+ * FindResult<TTable, TOptions> — the return type of a typed query.
550
+ *
551
+ * Combines:
552
+ * - SelectNarrow for column selection
553
+ * - IncludeResolve for relation includes
554
+ *
555
+ * TOptions is structurally typed (not constrained to FindOptions) so that
556
+ * literal option objects flow through without widening.
557
+ */
558
+ type FindResult<
559
+ TTable extends TableDef<ColumnRecord>,
560
+ TOptions = unknown,
561
+ TRelations extends RelationsRecord = RelationsRecord
562
+ > = TTable extends TableDef<infer TColumns> ? SelectNarrow<TColumns, TOptions extends {
563
+ select: infer S;
564
+ } ? S : undefined> & (TOptions extends {
565
+ include: infer I;
566
+ } ? IncludeResolve<TRelations, I> : unknown) : never;
567
+ /**
568
+ * InsertInput<TTable> — standalone insert type utility.
569
+ * Makes columns with defaults optional, all others required.
570
+ */
571
+ type InsertInput<TTable extends TableDef<ColumnRecord>> = TTable["$insert"];
572
+ /**
573
+ * UpdateInput<TTable> — standalone update type utility.
574
+ * All non-PK columns, all optional.
575
+ */
576
+ type UpdateInput<TTable extends TableDef<ColumnRecord>> = TTable["$update"];
577
+ /** A table entry in the database registry, pairing a table with its relations. */
578
+ interface TableEntry<
579
+ TTable extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
580
+ TRelations extends RelationsRecord = RelationsRecord
581
+ > {
582
+ readonly table: TTable;
583
+ readonly relations: TRelations;
584
+ }
585
+ /**
586
+ * Database<TTables> — type that carries the full table registry.
587
+ *
588
+ * Used as the foundation for typed query methods (implemented in later tickets).
589
+ * Provides type-safe access to table definitions and their relations.
590
+ */
591
+ interface Database<TTables extends Record<string, TableEntry> = Record<string, TableEntry>> {
592
+ readonly _tables: TTables;
593
+ }
594
+ /**
595
+ * SQL tagged template literal and escape hatch.
596
+ *
597
+ * Provides a safe, composable way to write raw SQL with automatic parameterization.
598
+ *
599
+ * Usage:
600
+ * const result = sql`SELECT * FROM users WHERE id = ${userId}`;
601
+ * // { sql: 'SELECT * FROM users WHERE id = $1', params: [userId] }
602
+ *
603
+ * const col = sql.raw('created_at');
604
+ * const result = sql`SELECT ${col} FROM users`;
605
+ * // { sql: 'SELECT created_at FROM users', params: [] }
606
+ *
607
+ * const where = sql`WHERE active = ${true}`;
608
+ * const query = sql`SELECT * FROM users ${where}`;
609
+ * // { sql: 'SELECT * FROM users WHERE active = $1', params: [true] }
610
+ */
611
+ /**
612
+ * A fragment of SQL with parameterized values.
613
+ *
614
+ * The `_tag` property is used to identify SqlFragment instances during
615
+ * template composition, distinguishing them from regular interpolated values.
616
+ */
617
+ interface SqlFragment {
618
+ readonly _tag: "SqlFragment";
619
+ readonly sql: string;
620
+ readonly params: readonly unknown[];
621
+ }
622
+ interface TenantGraph {
623
+ /** The tenant root table name (e.g., "organizations"). Null if no tenant columns exist. */
624
+ readonly root: string | null;
625
+ /** Tables with a direct d.tenant() column pointing to the root. */
626
+ readonly directlyScoped: readonly string[];
627
+ /** Tables reachable from directly scoped tables via .references() chains. */
628
+ readonly indirectlyScoped: readonly string[];
629
+ /** Tables marked with .shared(). */
630
+ readonly shared: readonly string[];
631
+ }
632
+ interface TableRegistryEntry {
633
+ readonly table: TableDef<ColumnRecord>;
634
+ readonly relations: Record<string, unknown>;
635
+ }
636
+ type TableRegistry = Record<string, TableRegistryEntry>;
637
+ /**
638
+ * Analyzes a table registry to compute the tenant scoping graph.
639
+ *
640
+ * 1. Finds the tenant root — the table that tenant columns point to.
641
+ * 2. Classifies tables as directly scoped (has d.tenant()), indirectly scoped
642
+ * (references a scoped table via .references()), or shared (.shared()).
643
+ * 3. Tables that are none of the above are unscoped — the caller should
644
+ * log a notice for those.
645
+ */
646
+ declare function computeTenantGraph(registry: TableRegistry): TenantGraph;
647
+ interface PoolConfig {
648
+ /** Maximum number of connections in the pool. */
649
+ readonly max?: number;
650
+ /**
651
+ * Idle timeout in milliseconds before a connection is closed.
652
+ * Defaults to 30000 (30 seconds) if not specified, preventing
653
+ * idle connections from staying open indefinitely.
654
+ */
655
+ readonly idleTimeout?: number;
656
+ /** Connection timeout in milliseconds. */
657
+ readonly connectionTimeout?: number;
658
+ /**
659
+ * Health check timeout in milliseconds.
660
+ * Used by isHealthy() to prevent hanging on degraded databases.
661
+ * Defaults to 5000 (5 seconds) if not specified.
662
+ */
663
+ readonly healthCheckTimeout?: number;
664
+ /**
665
+ * Read replica URLs for query routing.
666
+ * Read-only queries (SELECT) can be routed to replicas for load balancing.
667
+ * The primary URL is always used for writes (INSERT, UPDATE, DELETE).
668
+ */
669
+ readonly replicas?: readonly string[];
670
+ }
671
+ interface CreateDbOptions<TTables extends Record<string, TableEntry>> {
672
+ /** PostgreSQL connection URL. */
673
+ readonly url: string;
674
+ /** Table registry mapping logical names to table definitions + relations. */
675
+ readonly tables: TTables;
676
+ /** Connection pool configuration. */
677
+ readonly pool?: PoolConfig;
678
+ /** Column name casing strategy. */
679
+ readonly casing?: "snake_case" | "camelCase";
680
+ /** Log function for notices (e.g., unscoped table warnings). */
681
+ readonly log?: (message: string) => void;
682
+ /**
683
+ * Raw query function injected by the driver layer.
684
+ * If not provided, query methods will throw.
685
+ * @internal — primarily for testing with PGlite.
686
+ */
687
+ readonly _queryFn?: QueryFn;
688
+ }
689
+ interface QueryResult<T> {
690
+ readonly rows: readonly T[];
691
+ readonly rowCount: number;
692
+ }
693
+ /** Extract the TableDef from a TableEntry. */
694
+ type EntryTable<TEntry extends TableEntry> = TEntry["table"];
695
+ /** Extract the relations record from a TableEntry. */
696
+ type EntryRelations<TEntry extends TableEntry> = TEntry["relations"];
697
+ /** Extract columns from a TableEntry's table. */
698
+ type EntryColumns<TEntry extends TableEntry> = EntryTable<TEntry>["_columns"];
699
+ /** Options for get / getOrThrow — typed per-table. */
700
+ type TypedGetOptions<TEntry extends TableEntry> = {
701
+ readonly where?: FilterType<EntryColumns<TEntry>>;
702
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
703
+ readonly orderBy?: OrderByType<EntryColumns<TEntry>>;
704
+ readonly include?: IncludeOption<EntryRelations<TEntry>>;
705
+ };
706
+ /** Options for list / listAndCount — typed per-table. */
707
+ type TypedListOptions<TEntry extends TableEntry> = {
708
+ readonly where?: FilterType<EntryColumns<TEntry>>;
709
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
710
+ readonly orderBy?: OrderByType<EntryColumns<TEntry>>;
711
+ readonly limit?: number;
712
+ readonly offset?: number;
713
+ /** Cursor object: column-value pairs marking the position to paginate from. */
714
+ readonly cursor?: Record<string, unknown>;
715
+ /** Number of rows to take (used with cursor). Aliases `limit` when cursor is present. */
716
+ readonly take?: number;
717
+ readonly include?: IncludeOption<EntryRelations<TEntry>>;
718
+ };
719
+ /** Options for create — typed per-table. */
720
+ type TypedCreateOptions<TEntry extends TableEntry> = {
721
+ readonly data: InsertInput<EntryTable<TEntry>>;
722
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
723
+ };
724
+ /** Options for createManyAndReturn — typed per-table. */
725
+ type TypedCreateManyAndReturnOptions<TEntry extends TableEntry> = {
726
+ readonly data: readonly InsertInput<EntryTable<TEntry>>[];
727
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
728
+ };
729
+ /** Options for createMany — typed per-table. */
730
+ type TypedCreateManyOptions<TEntry extends TableEntry> = {
731
+ readonly data: readonly InsertInput<EntryTable<TEntry>>[];
732
+ };
733
+ /** Options for update — typed per-table. */
734
+ type TypedUpdateOptions<TEntry extends TableEntry> = {
735
+ readonly where: FilterType<EntryColumns<TEntry>>;
736
+ readonly data: UpdateInput<EntryTable<TEntry>>;
737
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
738
+ };
739
+ /** Options for updateMany — typed per-table. */
740
+ type TypedUpdateManyOptions<TEntry extends TableEntry> = {
741
+ readonly where: FilterType<EntryColumns<TEntry>>;
742
+ readonly data: UpdateInput<EntryTable<TEntry>>;
743
+ };
744
+ /** Options for upsert — typed per-table. */
745
+ type TypedUpsertOptions<TEntry extends TableEntry> = {
746
+ readonly where: FilterType<EntryColumns<TEntry>>;
747
+ readonly create: InsertInput<EntryTable<TEntry>>;
748
+ readonly update: UpdateInput<EntryTable<TEntry>>;
749
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
750
+ };
751
+ /** Options for delete — typed per-table. */
752
+ type TypedDeleteOptions<TEntry extends TableEntry> = {
753
+ readonly where: FilterType<EntryColumns<TEntry>>;
754
+ readonly select?: SelectOption<EntryColumns<TEntry>>;
755
+ };
756
+ /** Options for deleteMany — typed per-table. */
757
+ type TypedDeleteManyOptions<TEntry extends TableEntry> = {
758
+ readonly where: FilterType<EntryColumns<TEntry>>;
759
+ };
760
+ /** Options for count — typed per-table. */
761
+ type TypedCountOptions<TEntry extends TableEntry> = {
762
+ readonly where?: FilterType<EntryColumns<TEntry>>;
763
+ };
764
+ interface DatabaseInstance<TTables extends Record<string, TableEntry>> {
765
+ /** The table registry for type-safe access. */
766
+ readonly _tables: TTables;
767
+ /** The computed tenant scoping graph. */
768
+ readonly $tenantGraph: TenantGraph;
769
+ /**
770
+ * Execute a raw SQL query via the sql tagged template.
771
+ */
772
+ query<T = Record<string, unknown>>(fragment: SqlFragment): Promise<QueryResult<T>>;
773
+ /**
774
+ * Close all pool connections.
775
+ */
776
+ close(): Promise<void>;
777
+ /**
778
+ * Check if the database connection is healthy.
779
+ */
780
+ isHealthy(): Promise<boolean>;
781
+ /**
782
+ * Get a single row or null.
783
+ */
784
+ get<
785
+ TName extends keyof TTables & string,
786
+ TOptions extends TypedGetOptions<TTables[TName]>
787
+ >(table: TName, options?: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>> | null>;
788
+ /**
789
+ * Get a single row or throw NotFoundError.
790
+ */
791
+ getOrThrow<
792
+ TName extends keyof TTables & string,
793
+ TOptions extends TypedGetOptions<TTables[TName]>
794
+ >(table: TName, options?: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>>;
795
+ /**
796
+ * List multiple rows.
797
+ */
798
+ list<
799
+ TName extends keyof TTables & string,
800
+ TOptions extends TypedListOptions<TTables[TName]>
801
+ >(table: TName, options?: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>[]>;
802
+ /**
803
+ * List multiple rows with total count.
804
+ */
805
+ listAndCount<
806
+ TName extends keyof TTables & string,
807
+ TOptions extends TypedListOptions<TTables[TName]>
808
+ >(table: TName, options?: TOptions): Promise<{
809
+ data: FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>[];
810
+ total: number;
811
+ }>;
812
+ /** @deprecated Use `get` instead */
813
+ findOne: DatabaseInstance<TTables>["get"];
814
+ /** @deprecated Use `getOrThrow` instead */
815
+ findOneOrThrow: DatabaseInstance<TTables>["getOrThrow"];
816
+ /** @deprecated Use `list` instead */
817
+ findMany: DatabaseInstance<TTables>["list"];
818
+ /** @deprecated Use `listAndCount` instead */
819
+ findManyAndCount: DatabaseInstance<TTables>["listAndCount"];
820
+ /**
821
+ * Insert a single row and return it.
822
+ */
823
+ create<
824
+ TName extends keyof TTables & string,
825
+ TOptions extends TypedCreateOptions<TTables[TName]>
826
+ >(table: TName, options: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>>;
827
+ /**
828
+ * Insert multiple rows and return the count.
829
+ */
830
+ createMany<TName extends keyof TTables & string>(table: TName, options: TypedCreateManyOptions<TTables[TName]>): Promise<{
831
+ count: number;
832
+ }>;
833
+ /**
834
+ * Insert multiple rows and return them.
835
+ */
836
+ createManyAndReturn<
837
+ TName extends keyof TTables & string,
838
+ TOptions extends TypedCreateManyAndReturnOptions<TTables[TName]>
839
+ >(table: TName, options: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>[]>;
840
+ /**
841
+ * Update matching rows and return the first. Throws NotFoundError if none match.
842
+ */
843
+ update<
844
+ TName extends keyof TTables & string,
845
+ TOptions extends TypedUpdateOptions<TTables[TName]>
846
+ >(table: TName, options: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>>;
847
+ /**
848
+ * Update matching rows and return the count.
849
+ */
850
+ updateMany<TName extends keyof TTables & string>(table: TName, options: TypedUpdateManyOptions<TTables[TName]>): Promise<{
851
+ count: number;
852
+ }>;
853
+ /**
854
+ * Insert or update a row.
855
+ */
856
+ upsert<
857
+ TName extends keyof TTables & string,
858
+ TOptions extends TypedUpsertOptions<TTables[TName]>
859
+ >(table: TName, options: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>>;
860
+ /**
861
+ * Delete a matching row and return it. Throws NotFoundError if none match.
862
+ */
863
+ delete<
864
+ TName extends keyof TTables & string,
865
+ TOptions extends TypedDeleteOptions<TTables[TName]>
866
+ >(table: TName, options: TOptions): Promise<FindResult<EntryTable<TTables[TName]>, TOptions, EntryRelations<TTables[TName]>>>;
867
+ /**
868
+ * Delete matching rows and return the count.
869
+ */
870
+ deleteMany<TName extends keyof TTables & string>(table: TName, options: TypedDeleteManyOptions<TTables[TName]>): Promise<{
871
+ count: number;
872
+ }>;
873
+ /**
874
+ * Count rows matching an optional filter.
875
+ */
876
+ count<TName extends keyof TTables & string>(table: TName, options?: TypedCountOptions<TTables[TName]>): Promise<number>;
877
+ /**
878
+ * Run aggregation functions on a table.
879
+ */
880
+ aggregate<TName extends keyof TTables & string>(table: TName, options: exports_aggregate.AggregateArgs): Promise<Record<string, unknown>>;
881
+ /**
882
+ * Group rows by columns and apply aggregation functions.
883
+ */
884
+ groupBy<TName extends keyof TTables & string>(table: TName, options: exports_aggregate.GroupByArgs): Promise<Record<string, unknown>[]>;
885
+ }
886
+ /**
887
+ * Creates a typed Database instance.
888
+ *
889
+ * Computes the tenant graph at creation time from d.tenant() metadata,
890
+ * traversing references to find indirect tenant paths.
891
+ * Logs notices for tables without tenant paths and not .shared().
892
+ *
893
+ * When `url` is provided and `_queryFn` is NOT provided, creates a real
894
+ * postgres connection using the `postgres` package (porsager/postgres).
895
+ * The `_queryFn` escape hatch still works for testing with PGlite.
896
+ *
897
+ * **Timestamp coercion:** The postgres driver automatically converts string
898
+ * values matching ISO 8601 timestamp patterns to `Date` objects. This applies
899
+ * to all columns, not just declared timestamp columns. If you store
900
+ * timestamp-formatted strings in plain text columns, they will be coerced
901
+ * to `Date` objects. See the postgres-driver source for details.
902
+ *
903
+ * **Connection pool defaults:** When no `pool.idleTimeout` is specified,
904
+ * idle connections are closed after 30 seconds. Set `idleTimeout` explicitly
905
+ * to override (value in milliseconds, e.g., `60000` for 60s).
906
+ */
907
+ declare function createDb<TTables extends Record<string, TableEntry>>(options: CreateDbOptions<TTables>): DatabaseInstance<TTables>;
908
+ interface EnumSchemaLike<T extends readonly string[]> {
909
+ readonly values: T;
910
+ }
911
+ declare const d: {
912
+ uuid(): ColumnBuilder<string, DefaultMeta<"uuid">>;
913
+ text(): ColumnBuilder<string, DefaultMeta<"text">>;
914
+ varchar<TLength extends number>(length: TLength): ColumnBuilder<string, VarcharMeta<TLength>>;
915
+ email(): ColumnBuilder<string, FormatMeta<"text", "email">>;
916
+ boolean(): ColumnBuilder<boolean, DefaultMeta<"boolean">>;
917
+ integer(): ColumnBuilder<number, DefaultMeta<"integer">>;
918
+ bigint(): ColumnBuilder<bigint, DefaultMeta<"bigint">>;
919
+ decimal<
920
+ TPrecision extends number,
921
+ TScale extends number
922
+ >(precision: TPrecision, scale: TScale): ColumnBuilder<string, DecimalMeta<TPrecision, TScale>>;
923
+ real(): ColumnBuilder<number, DefaultMeta<"real">>;
924
+ doublePrecision(): ColumnBuilder<number, DefaultMeta<"double precision">>;
925
+ serial(): ColumnBuilder<number, SerialMeta>;
926
+ timestamp(): ColumnBuilder<Date, DefaultMeta<"timestamp with time zone">>;
927
+ date(): ColumnBuilder<string, DefaultMeta<"date">>;
928
+ time(): ColumnBuilder<string, DefaultMeta<"time">>;
929
+ jsonb<T = unknown>(opts?: {
930
+ validator: JsonbValidator<T>;
931
+ }): ColumnBuilder<T, DefaultMeta<"jsonb">>;
932
+ textArray(): ColumnBuilder<string[], DefaultMeta<"text[]">>;
933
+ integerArray(): ColumnBuilder<number[], DefaultMeta<"integer[]">>;
934
+ enum<
935
+ TName extends string,
936
+ const TValues extends readonly string[]
937
+ >(name: TName, values: TValues): ColumnBuilder<TValues[number], EnumMeta<TName, TValues>>;
938
+ enum<
939
+ TName extends string,
940
+ const TValues extends readonly [string, ...string[]]
941
+ >(name: TName, schema: EnumSchemaLike<TValues>): ColumnBuilder<TValues[number], EnumMeta<TName, TValues>>;
942
+ tenant(targetTable: TableDef<ColumnRecord>): ColumnBuilder<string, TenantMeta>;
943
+ table<TColumns extends ColumnRecord>(name: string, columns: TColumns, options?: TableOptions): TableDef<TColumns>;
944
+ index(columns: string | string[]): IndexDef;
945
+ ref: {
946
+ one<TTarget extends TableDef<ColumnRecord>>(target: () => TTarget, foreignKey: string): RelationDef<TTarget, "one">;
947
+ many<TTarget extends TableDef<ColumnRecord>>(target: () => TTarget, foreignKey: string): RelationDef<TTarget, "many">;
948
+ many<TTarget extends TableDef<ColumnRecord>>(target: () => TTarget): ManyRelationDef<TTarget>;
949
+ };
950
+ entry<TTable extends TableDef<ColumnRecord>>(table: TTable): TableEntry<TTable, {}>;
951
+ entry<
952
+ TTable extends TableDef<ColumnRecord>,
953
+ TRelations extends Record<string, RelationDef>
954
+ >(table: TTable, relations: TRelations): TableEntry<TTable, TRelations>;
955
+ };
956
+ interface DbErrorJson {
957
+ readonly error: string;
958
+ readonly code: string;
959
+ readonly message: string;
960
+ readonly table?: string;
961
+ readonly column?: string;
962
+ }
963
+ declare abstract class DbError extends Error {
964
+ abstract readonly code: string;
965
+ /** Raw PostgreSQL SQLSTATE code, if applicable. */
966
+ readonly pgCode?: string | undefined;
967
+ readonly table?: string | undefined;
968
+ readonly query?: string | undefined;
969
+ constructor(message: string);
970
+ toJSON(): DbErrorJson;
971
+ }
972
+ interface UniqueConstraintErrorOptions {
973
+ readonly table: string;
974
+ readonly column: string;
975
+ readonly value?: string;
976
+ readonly query?: string;
977
+ }
978
+ declare class UniqueConstraintError extends DbError {
979
+ readonly code: "UNIQUE_VIOLATION";
980
+ readonly pgCode: "23505";
981
+ readonly table: string;
982
+ readonly query: string | undefined;
983
+ readonly column: string;
984
+ readonly value: string | undefined;
985
+ constructor(options: UniqueConstraintErrorOptions);
986
+ toJSON(): DbErrorJson;
987
+ }
988
+ interface ForeignKeyErrorOptions {
989
+ readonly table: string;
990
+ readonly constraint: string;
991
+ readonly detail?: string;
992
+ readonly query?: string;
993
+ }
994
+ declare class ForeignKeyError extends DbError {
995
+ readonly code: "FOREIGN_KEY_VIOLATION";
996
+ readonly pgCode: "23503";
997
+ readonly table: string;
998
+ readonly query: string | undefined;
999
+ readonly constraint: string;
1000
+ readonly detail: string | undefined;
1001
+ constructor(options: ForeignKeyErrorOptions);
1002
+ toJSON(): DbErrorJson;
1003
+ }
1004
+ interface NotNullErrorOptions {
1005
+ readonly table: string;
1006
+ readonly column: string;
1007
+ readonly query?: string;
1008
+ }
1009
+ declare class NotNullError extends DbError {
1010
+ readonly code: "NOT_NULL_VIOLATION";
1011
+ readonly pgCode: "23502";
1012
+ readonly table: string;
1013
+ readonly query: string | undefined;
1014
+ readonly column: string;
1015
+ constructor(options: NotNullErrorOptions);
1016
+ toJSON(): DbErrorJson;
1017
+ }
1018
+ interface CheckConstraintErrorOptions {
1019
+ readonly table: string;
1020
+ readonly constraint: string;
1021
+ readonly query?: string;
1022
+ }
1023
+ declare class CheckConstraintError extends DbError {
1024
+ readonly code: "CHECK_VIOLATION";
1025
+ readonly pgCode: "23514";
1026
+ readonly table: string;
1027
+ readonly query: string | undefined;
1028
+ readonly constraint: string;
1029
+ constructor(options: CheckConstraintErrorOptions);
1030
+ toJSON(): DbErrorJson;
1031
+ }
1032
+ declare class NotFoundError extends DbError {
1033
+ readonly code: "NOT_FOUND";
1034
+ readonly table: string;
1035
+ readonly query: string | undefined;
1036
+ constructor(table: string, query?: string);
1037
+ toJSON(): DbErrorJson;
1038
+ }
1039
+ declare class ConnectionError extends DbError {
1040
+ readonly code: string;
1041
+ constructor(message: string);
1042
+ }
1043
+ declare class ConnectionPoolExhaustedError extends ConnectionError {
1044
+ readonly code: "POOL_EXHAUSTED";
1045
+ constructor(poolSize: number);
1046
+ }
1047
+ /**
1048
+ * Maps semantic error names to their corresponding PostgreSQL SQLSTATE codes.
1049
+ *
1050
+ * Usage in switch statements:
1051
+ * ```ts
1052
+ * switch (error.code) {
1053
+ * case 'UNIQUE_VIOLATION': // ...
1054
+ * case 'FOREIGN_KEY_VIOLATION': // ...
1055
+ * }
1056
+ * ```
1057
+ *
1058
+ * Reverse lookup (semantic name -> PG code):
1059
+ * ```ts
1060
+ * DbErrorCode.UNIQUE_VIOLATION // '23505'
1061
+ * ```
1062
+ */
1063
+ declare const DbErrorCode: {
1064
+ readonly UNIQUE_VIOLATION: "23505";
1065
+ readonly FOREIGN_KEY_VIOLATION: "23503";
1066
+ readonly NOT_NULL_VIOLATION: "23502";
1067
+ readonly CHECK_VIOLATION: "23514";
1068
+ readonly EXCLUSION_VIOLATION: "23P01";
1069
+ readonly SERIALIZATION_FAILURE: "40001";
1070
+ readonly DEADLOCK_DETECTED: "40P01";
1071
+ readonly CONNECTION_EXCEPTION: "08000";
1072
+ readonly CONNECTION_DOES_NOT_EXIST: "08003";
1073
+ readonly CONNECTION_FAILURE: "08006";
1074
+ readonly NOT_FOUND: "NOT_FOUND";
1075
+ readonly CONNECTION_ERROR: "CONNECTION_ERROR";
1076
+ readonly POOL_EXHAUSTED: "POOL_EXHAUSTED";
1077
+ };
1078
+ /** Union of all semantic error code keys (e.g., `'UNIQUE_VIOLATION' | 'FOREIGN_KEY_VIOLATION' | ...`). */
1079
+ type DbErrorCodeName = keyof typeof DbErrorCode;
1080
+ /** Union of all raw PG error code values (e.g., `'23505' | '23503' | ...`). */
1081
+ type DbErrorCodeValue = (typeof DbErrorCode)[keyof typeof DbErrorCode];
1082
+ /**
1083
+ * Reverse map: raw PG code -> semantic name.
1084
+ * Built at module load time from DbErrorCode.
1085
+ */
1086
+ declare const PgCodeToName: Readonly<Record<string, DbErrorCodeName | undefined>>;
1087
+ /**
1088
+ * Look up the semantic name for a raw PG error code.
1089
+ * Returns the key (e.g., `'UNIQUE_VIOLATION'`) or `undefined` if unmapped.
1090
+ */
1091
+ declare function resolveErrorCode(pgCode: string): DbErrorCodeName | undefined;
1092
+ interface HttpErrorResponse {
1093
+ readonly status: number;
1094
+ readonly body: DbErrorJson;
1095
+ }
1096
+ /**
1097
+ * Maps a DbError to an HTTP error response with the appropriate status code.
1098
+ *
1099
+ * - UniqueConstraintError -> 409 Conflict
1100
+ * - NotFoundError -> 404 Not Found
1101
+ * - ForeignKeyError -> 422 Unprocessable Entity
1102
+ * - NotNullError -> 422 Unprocessable Entity
1103
+ * - CheckConstraintError -> 422 Unprocessable Entity
1104
+ * - ConnectionError -> 503 Service Unavailable
1105
+ * - Unknown DbError -> 500 Internal Server Error
1106
+ */
1107
+ declare function dbErrorToHttpError(error: DbError): HttpErrorResponse;
1108
+ interface PgErrorInput {
1109
+ readonly code: string;
1110
+ readonly message: string;
1111
+ readonly table?: string;
1112
+ readonly column?: string;
1113
+ readonly constraint?: string;
1114
+ readonly detail?: string;
1115
+ }
1116
+ /**
1117
+ * Maps a raw PostgreSQL error object to a typed DbError subclass.
1118
+ *
1119
+ * Extracts structured metadata (column, constraint, value) from the
1120
+ * PG error's `detail` and `message` fields.
1121
+ */
1122
+ declare function parsePgError(pgError: PgErrorInput, query?: string): DbError;
1123
+ /**
1124
+ * Extracts the string column keys from a TableDef's _columns record.
1125
+ * Used to constrain FK arguments at the type level.
1126
+ */
1127
+ type ColumnKeys<T extends TableDef<ColumnRecord>> = T extends TableDef<infer C> ? Extract<keyof C, string> : never;
1128
+ /**
1129
+ * A ref builder scoped to a specific source table within a registry.
1130
+ *
1131
+ * - `one()` validates: target name is a registry key, FK is a column of the SOURCE table
1132
+ * - `many()` validates: target name is a registry key, FK is a column of the TARGET table
1133
+ */
1134
+ interface TypedRef<
1135
+ TTables extends Record<string, TableDef<ColumnRecord>>,
1136
+ TSourceTable extends TableDef<ColumnRecord>
1137
+ > {
1138
+ /** belongsTo — FK lives on the source table */
1139
+ one<
1140
+ TTargetName extends Extract<keyof TTables, string>,
1141
+ TFK extends ColumnKeys<TSourceTable>
1142
+ >(target: TTargetName, foreignKey: TFK): RelationDef<TTables[TTargetName], "one">;
1143
+ /** hasMany — FK lives on the target table */
1144
+ many<
1145
+ TTargetName extends Extract<keyof TTables, string>,
1146
+ TFK extends ColumnKeys<TTables[TTargetName]>
1147
+ >(target: TTargetName, foreignKey: TFK): RelationDef<TTables[TTargetName], "many">;
1148
+ }
1149
+ /**
1150
+ * An object keyed by table name where each value is a TypedRef scoped
1151
+ * to that table as the source. This enables `ref.posts.one('users', 'authorId')`
1152
+ * where TypeScript knows 'authorId' must be a column of `posts`.
1153
+ */
1154
+ type PerTableRefFactory<TTables extends Record<string, TableDef<ColumnRecord>>> = { [K in Extract<keyof TTables, string>] : TypedRef<TTables, TTables[K]> };
1155
+ /**
1156
+ * Maps each table key to a TableEntry, merging the table with its relations
1157
+ * from the callback (or empty relations if the table was omitted).
1158
+ */
1159
+ type RegistryOutput<
1160
+ TTables extends Record<string, TableDef<ColumnRecord>>,
1161
+ TRelMap extends { [K in keyof TTables]? : Record<string, RelationDef> }
1162
+ > = { [K in keyof TTables] : TableEntry<TTables[K], K extends keyof TRelMap ? TRelMap[K] extends Record<string, RelationDef> ? TRelMap[K] : {} : {}> };
1163
+ /**
1164
+ * Creates a typed table registry with compile-time validated relations.
1165
+ *
1166
+ * Tables without relations in the callback are auto-wrapped with `{ table, relations: {} }`.
1167
+ * The `ref` parameter is keyed by table name — use `ref.posts.one('users', 'authorId')`
1168
+ * to get compile-time validation that 'users' is a registry key and 'authorId' is a column
1169
+ * of the `posts` table.
1170
+ *
1171
+ * @example
1172
+ * ```typescript
1173
+ * const tables = createRegistry(
1174
+ * { users, posts, comments },
1175
+ * (ref) => ({
1176
+ * posts: {
1177
+ * author: ref.posts.one('users', 'authorId'),
1178
+ * comments: ref.posts.many('comments', 'postId'),
1179
+ * },
1180
+ * comments: {
1181
+ * post: ref.comments.one('posts', 'postId'),
1182
+ * author: ref.comments.one('users', 'authorId'),
1183
+ * },
1184
+ * }),
1185
+ * );
1186
+ * ```
1187
+ */
1188
+ declare function createRegistry<
1189
+ TTables extends Record<string, TableDef<ColumnRecord>>,
1190
+ TRelMap extends { [K in keyof TTables]? : Record<string, RelationDef> }
1191
+ >(tables: TTables, relationsCallback: (ref: PerTableRefFactory<TTables>) => TRelMap): RegistryOutput<TTables, TRelMap>;
1192
+ /**
1193
+ * Shared enum registry — define enums once, reuse across tables.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * const enums = createEnumRegistry({
1198
+ * status: ['active', 'inactive', 'pending'],
1199
+ * role: ['admin', 'editor', 'viewer'],
1200
+ * } as const);
1201
+ *
1202
+ * const orders = d.table('orders', {
1203
+ * status: d.enum('status', enums.status),
1204
+ * });
1205
+ * const users = d.table('users', {
1206
+ * role: d.enum('role', enums.role),
1207
+ * });
1208
+ * ```
1209
+ */
1210
+ /** A registered enum entry with name and values accessible for d.enum(). */
1211
+ interface RegisteredEnum<TValues extends readonly string[]> {
1212
+ /** The enum name (same as the registry key). */
1213
+ readonly name: string;
1214
+ /** The enum values — compatible with d.enum()'s EnumSchemaLike interface. */
1215
+ readonly values: TValues;
1216
+ }
1217
+ type EnumRegistry<T extends Record<string, readonly string[]>> = { readonly [K in keyof T] : RegisteredEnum<T[K]> };
1218
+ /**
1219
+ * Creates a shared enum registry from a map of enum names to their values.
1220
+ * The returned object can be passed directly to `d.enum(name, enums.myEnum)`.
1221
+ */
1222
+ declare function createEnumRegistry<T extends Record<string, readonly string[]>>(definitions: T): EnumRegistry<T>;
1223
+ /**
1224
+ * Branded error types for readable compiler messages.
1225
+ *
1226
+ * When developers pass invalid column names, relation names, or filter types,
1227
+ * TypeScript produces raw generic expansion errors that are hard to read.
1228
+ * These branded types produce clear, actionable error messages instead.
1229
+ *
1230
+ * @module
1231
+ */
1232
+ /**
1233
+ * Produced when a column name in select/where/orderBy does not exist on the table.
1234
+ *
1235
+ * Example:
1236
+ * `ERROR: Column 'nonExistent' does not exist on table 'users'.`
1237
+ */
1238
+ type InvalidColumn<
1239
+ K extends string,
1240
+ Table extends string
1241
+ > = `ERROR: Column '${K}' does not exist on table '${Table}'.`;
1242
+ /**
1243
+ * Produced when a filter value has the wrong type.
1244
+ *
1245
+ * Example:
1246
+ * `ERROR: Filter on 'age' expects type 'number', got 'string'.`
1247
+ */
1248
+ type InvalidFilterType<
1249
+ Col extends string,
1250
+ Expected extends string,
1251
+ Got extends string
1252
+ > = `ERROR: Filter on '${Col}' expects type '${Expected}', got '${Got}'.`;
1253
+ /**
1254
+ * Produced when an include name does not match any declared relation.
1255
+ *
1256
+ * Example:
1257
+ * `ERROR: Relation 'bogus' does not exist. Available relations: author, comments.`
1258
+ */
1259
+ type InvalidRelation<
1260
+ K extends string,
1261
+ Available extends string
1262
+ > = `ERROR: Relation '${K}' does not exist. Available relations: ${Available}.`;
1263
+ /**
1264
+ * Produced when a select option mixes `not` with explicit field selection.
1265
+ *
1266
+ * Example:
1267
+ * `ERROR: Cannot combine 'not' with explicit field selection in select.`
1268
+ */
1269
+ type MixedSelectError = "ERROR: Cannot combine 'not' with explicit field selection in select.";
1270
+ /**
1271
+ * ValidateKeys<TKeys, TAllowed, TTable> — maps invalid keys to branded error messages.
1272
+ *
1273
+ * For each key K in TKeys:
1274
+ * - If K extends TAllowed, keep the original type
1275
+ * - Otherwise, produce an InvalidColumn error message
1276
+ */
1277
+ type ValidateKeys<
1278
+ TKeys extends Record<string, unknown>,
1279
+ TAllowed extends string,
1280
+ TTable extends string
1281
+ > = { [K in keyof TKeys] : K extends TAllowed ? TKeys[K] : InvalidColumn<K & string, TTable> };
1282
+ /**
1283
+ * StrictKeys<TRecord, TAllowed, TTable> — validates that all keys in TRecord
1284
+ * are in the TAllowed union, producing branded errors for invalid keys.
1285
+ */
1286
+ type StrictKeys<
1287
+ TRecord,
1288
+ TAllowed extends string,
1289
+ TTable extends string
1290
+ > = TRecord extends Record<string, unknown> ? { [K in keyof TRecord] : K extends TAllowed ? TRecord[K] : InvalidColumn<K & string, TTable> } : TRecord;
1291
+ /**
1292
+ * Type generation for DB client codegen.
1293
+ *
1294
+ * This module generates TypeScript types from domain definitions.
1295
+ */
1296
+ interface DomainField {
1297
+ type: "string" | "number" | "boolean" | "date" | "json" | "uuid" | "enum";
1298
+ primary?: boolean;
1299
+ required?: boolean;
1300
+ references?: string;
1301
+ enumName?: string;
1302
+ enumValues?: string[];
1303
+ }
1304
+ interface DomainRelation {
1305
+ type: "belongsTo" | "hasMany";
1306
+ target: string;
1307
+ foreignKey: string;
1308
+ }
1309
+ interface DomainDefinition {
1310
+ name: string;
1311
+ fields: Record<string, DomainField>;
1312
+ relations?: Record<string, DomainRelation>;
1313
+ }
1314
+ /**
1315
+ * Generate TypeScript types from a domain definition.
1316
+ * This function should generate:
1317
+ * - Entity interface (e.g., interface User { ... })
1318
+ * - Create input type (required fields only)
1319
+ * - Update input type (all fields optional)
1320
+ * - Where/filter type
1321
+ * - OrderBy type
1322
+ * - CRUD method signatures
1323
+ */
1324
+ declare function generateTypes(domain: DomainDefinition): string;
1325
+ /**
1326
+ * Generate the typed database client from domain definitions.
1327
+ * This function should generate:
1328
+ * - A db object with entity accessors
1329
+ * - Each entity has: list, get, create, update, delete methods
1330
+ * - Typed filter/where parameters
1331
+ * - Relation accessors
1332
+ */
1333
+ declare function generateClient(domains: DomainDefinition[]): string;
1334
+ /**
1335
+ * Define a domain for DB client codegen.
1336
+ * This creates a domain definition that can be used to generate types and client.
1337
+ */
1338
+ declare function defineDomain(name: string, config: {
1339
+ fields: Record<string, Omit<DomainField, "type"> & {
1340
+ type: DomainField["type"];
1341
+ }>;
1342
+ relations?: Record<string, Omit<DomainRelation, "type"> & {
1343
+ type: DomainRelation["type"];
1344
+ }>;
1345
+ }): DomainDefinition;
1346
+ export { resolveErrorCode, push, parsePgError, migrateStatus, migrateDev, migrateDeploy, generateTypes, generateClient, formatDiagnostic, explainError, diagnoseError, defineDomain, dbErrorToHttpError, d, createRegistry, createEnumRegistry, createDb, computeTenantGraph, VarcharMeta, ValidateKeys, UpdateInput, UniqueConstraintErrorOptions, UniqueConstraintError, TenantMeta, TenantGraph, TableEntry, TableDef, StrictKeys, SelectOption, SelectNarrow, RenameSuggestion, RelationDef, RegisteredEnum, QueryResult, PushResult, PushOptions, PoolConfig, PgErrorInput, PgCodeToName, OrderByType, NotNullErrorOptions, NotNullError, NotFoundError, MixedSelectError, MigrationInfo, MigrateStatusResult, MigrateStatusOptions, MigrateDevResult, MigrateDevOptions, MigrateDeployResult, MigrateDeployOptions, JsonbValidator, InvalidRelation, InvalidFilterType, InvalidColumn, InsertInput, InferColumnType, IndexDef, IncludeResolve, IncludeOption, HttpErrorResponse, FormatMeta, ForeignKeyErrorOptions, ForeignKeyError, FindResult, FindOptions, FilterType, EnumMeta, DomainRelation, DomainField, DomainDefinition, DiagnosticResult, DecimalMeta, DbErrorJson, DbErrorCodeValue, DbErrorCodeName, DbErrorCode, DbError, DatabaseInstance, Database, CreateDbOptions, ConnectionPoolExhaustedError, ConnectionError, ColumnMetadata, ColumnBuilder, CheckConstraintErrorOptions, CheckConstraintError };