@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.
- package/README.md +923 -0
- package/dist/diagnostic/index.d.ts +41 -0
- package/dist/diagnostic/index.js +10 -0
- package/dist/index.d.ts +1346 -0
- package/dist/index.js +2010 -0
- package/dist/internals.d.ts +223 -0
- package/dist/internals.js +25 -0
- package/dist/plugin/index.d.ts +66 -0
- package/dist/plugin/index.js +66 -0
- package/dist/shared/chunk-3f2grpak.js +428 -0
- package/dist/shared/chunk-hrfdj0rr.js +13 -0
- package/dist/shared/chunk-wj026daz.js +86 -0
- package/dist/shared/chunk-xp022dyp.js +296 -0
- package/dist/sql/index.d.ts +213 -0
- package/dist/sql/index.js +64 -0
- package/package.json +72 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|