relq 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/commands/add.cjs +252 -12
- package/dist/cjs/cli/commands/commit.cjs +12 -1
- package/dist/cjs/cli/commands/export.cjs +25 -19
- package/dist/cjs/cli/commands/import.cjs +219 -100
- package/dist/cjs/cli/commands/init.cjs +86 -14
- package/dist/cjs/cli/commands/pull.cjs +104 -23
- package/dist/cjs/cli/commands/push.cjs +38 -3
- package/dist/cjs/cli/index.cjs +9 -1
- package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
- package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
- package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
- package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
- package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
- package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
- package/dist/cjs/cli/utils/ast/index.cjs +19 -0
- package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
- package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
- package/dist/cjs/cli/utils/ast/types.cjs +2 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
- package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
- package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
- package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
- package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
- package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
- package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
- package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
- package/dist/cjs/cli/utils/type-generator.cjs +114 -15
- package/dist/cjs/core/relq-client.cjs +22 -6
- package/dist/cjs/schema-definition/column-types.cjs +150 -13
- package/dist/cjs/schema-definition/defaults.cjs +72 -0
- package/dist/cjs/schema-definition/index.cjs +15 -1
- package/dist/cjs/schema-definition/introspection.cjs +7 -3
- package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
- package/dist/cjs/schema-definition/pg-view.cjs +30 -0
- package/dist/cjs/schema-definition/table-definition.cjs +110 -4
- package/dist/cjs/types/config-types.cjs +13 -4
- package/dist/cjs/utils/aws-dsql.cjs +177 -0
- package/dist/config.d.ts +146 -1
- package/dist/esm/cli/commands/add.js +250 -13
- package/dist/esm/cli/commands/commit.js +12 -1
- package/dist/esm/cli/commands/export.js +25 -19
- package/dist/esm/cli/commands/import.js +221 -102
- package/dist/esm/cli/commands/init.js +86 -14
- package/dist/esm/cli/commands/pull.js +106 -25
- package/dist/esm/cli/commands/push.js +39 -4
- package/dist/esm/cli/index.js +9 -1
- package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
- package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
- package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
- package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
- package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
- package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
- package/dist/esm/cli/utils/ast/index.js +3 -0
- package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
- package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
- package/dist/esm/cli/utils/ast/types.js +1 -0
- package/dist/esm/cli/utils/ast-codegen.js +945 -0
- package/dist/esm/cli/utils/ast-transformer.js +907 -0
- package/dist/esm/cli/utils/change-tracker.js +50 -1
- package/dist/esm/cli/utils/cli-utils.js +147 -0
- package/dist/esm/cli/utils/fast-introspect.js +149 -23
- package/dist/esm/cli/utils/pg-parser.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +114 -4
- package/dist/esm/cli/utils/schema-comparator.js +98 -14
- package/dist/esm/cli/utils/schema-introspect.js +56 -19
- package/dist/esm/cli/utils/snapshot-manager.js +0 -1
- package/dist/esm/cli/utils/sql-generator.js +353 -64
- package/dist/esm/cli/utils/type-generator.js +114 -15
- package/dist/esm/core/relq-client.js +23 -7
- package/dist/esm/schema-definition/column-types.js +147 -12
- package/dist/esm/schema-definition/defaults.js +69 -0
- package/dist/esm/schema-definition/index.js +3 -0
- package/dist/esm/schema-definition/introspection.js +7 -3
- package/dist/esm/schema-definition/pg-relations.js +161 -0
- package/dist/esm/schema-definition/pg-view.js +24 -0
- package/dist/esm/schema-definition/table-definition.js +110 -4
- package/dist/esm/types/config-types.js +12 -4
- package/dist/esm/utils/aws-dsql.js +139 -0
- package/dist/index.d.ts +159 -1
- package/dist/schema-builder.d.ts +1314 -32
- package/package.json +1 -1
package/dist/schema-builder.d.ts
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe PostgreSQL DEFAULT value helpers
|
|
3
|
+
* Covers all PostgreSQL default value types with 100% typed output
|
|
4
|
+
*/
|
|
5
|
+
export interface DefaultValue {
|
|
6
|
+
readonly $sql: string;
|
|
7
|
+
readonly $isDefault: true;
|
|
8
|
+
}
|
|
9
|
+
export declare const DEFAULT: {
|
|
10
|
+
readonly genRandomUuid: () => DefaultValue;
|
|
11
|
+
readonly uuidGenerateV4: () => DefaultValue;
|
|
12
|
+
readonly uuidGenerateV1: () => DefaultValue;
|
|
13
|
+
readonly uuidGenerateV1mc: () => DefaultValue;
|
|
14
|
+
readonly uuidNil: () => DefaultValue;
|
|
15
|
+
readonly now: () => DefaultValue;
|
|
16
|
+
readonly currentTimestamp: () => DefaultValue;
|
|
17
|
+
readonly currentDate: () => DefaultValue;
|
|
18
|
+
readonly currentTime: () => DefaultValue;
|
|
19
|
+
readonly localTimestamp: () => DefaultValue;
|
|
20
|
+
readonly localTime: () => DefaultValue;
|
|
21
|
+
readonly transactionTimestamp: () => DefaultValue;
|
|
22
|
+
readonly statementTimestamp: () => DefaultValue;
|
|
23
|
+
readonly clockTimestamp: () => DefaultValue;
|
|
24
|
+
readonly timeofday: () => DefaultValue;
|
|
25
|
+
readonly interval: (value: string) => DefaultValue;
|
|
26
|
+
readonly currentUser: () => DefaultValue;
|
|
27
|
+
readonly sessionUser: () => DefaultValue;
|
|
28
|
+
readonly user: () => DefaultValue;
|
|
29
|
+
readonly currentSchema: () => DefaultValue;
|
|
30
|
+
readonly currentDatabase: () => DefaultValue;
|
|
31
|
+
readonly currentCatalog: () => DefaultValue;
|
|
32
|
+
readonly inetClientAddr: () => DefaultValue;
|
|
33
|
+
readonly inetClientPort: () => DefaultValue;
|
|
34
|
+
readonly inetServerAddr: () => DefaultValue;
|
|
35
|
+
readonly inetServerPort: () => DefaultValue;
|
|
36
|
+
readonly pgBackendPid: () => DefaultValue;
|
|
37
|
+
readonly nextval: (sequenceName: string) => DefaultValue;
|
|
38
|
+
readonly currval: (sequenceName: string) => DefaultValue;
|
|
39
|
+
readonly lastval: () => DefaultValue;
|
|
40
|
+
readonly random: () => DefaultValue;
|
|
41
|
+
readonly pi: () => DefaultValue;
|
|
42
|
+
readonly emptyString: () => DefaultValue;
|
|
43
|
+
readonly emptyObject: () => DefaultValue;
|
|
44
|
+
readonly emptyJson: () => DefaultValue;
|
|
45
|
+
readonly emptyJsonb: () => DefaultValue;
|
|
46
|
+
readonly emptyArray: () => DefaultValue;
|
|
47
|
+
readonly emptyArrayOf: (type: string) => DefaultValue;
|
|
48
|
+
readonly true: () => DefaultValue;
|
|
49
|
+
readonly false: () => DefaultValue;
|
|
50
|
+
readonly null: () => DefaultValue;
|
|
51
|
+
readonly zero: () => DefaultValue;
|
|
52
|
+
readonly one: () => DefaultValue;
|
|
53
|
+
readonly negativeOne: () => DefaultValue;
|
|
54
|
+
readonly string: (value: string) => DefaultValue;
|
|
55
|
+
readonly number: (value: number) => DefaultValue;
|
|
56
|
+
readonly integer: (value: number) => DefaultValue;
|
|
57
|
+
readonly decimal: (value: number, precision?: number) => DefaultValue;
|
|
58
|
+
readonly cast: (value: string | number, type: string) => DefaultValue;
|
|
59
|
+
readonly emptyTsvector: () => DefaultValue;
|
|
60
|
+
readonly point: (x: number, y: number) => DefaultValue;
|
|
61
|
+
readonly inet: (address: string) => DefaultValue;
|
|
62
|
+
readonly cidr: (network: string) => DefaultValue;
|
|
63
|
+
readonly macaddr: (address: string) => DefaultValue;
|
|
64
|
+
readonly emptyInt4range: () => DefaultValue;
|
|
65
|
+
readonly emptyInt8range: () => DefaultValue;
|
|
66
|
+
readonly emptyNumrange: () => DefaultValue;
|
|
67
|
+
readonly emptyTsrange: () => DefaultValue;
|
|
68
|
+
readonly emptyTstzrange: () => DefaultValue;
|
|
69
|
+
readonly emptyDaterange: () => DefaultValue;
|
|
70
|
+
readonly emptyHstore: () => DefaultValue;
|
|
71
|
+
readonly emptyBytea: () => DefaultValue;
|
|
72
|
+
readonly money: (value: number | string) => DefaultValue;
|
|
73
|
+
readonly zeroMoney: () => DefaultValue;
|
|
74
|
+
};
|
|
1
75
|
export declare const EMPTY_OBJECT: unique symbol;
|
|
2
76
|
export declare const EMPTY_ARRAY: unique symbol;
|
|
3
77
|
export interface ColumnConfig<T = unknown> {
|
|
@@ -5,7 +79,7 @@ export interface ColumnConfig<T = unknown> {
|
|
|
5
79
|
$sqlType?: string;
|
|
6
80
|
$tsType?: T;
|
|
7
81
|
$nullable?: boolean;
|
|
8
|
-
$default?: T | (() => T) | string | object | typeof EMPTY_OBJECT | typeof EMPTY_ARRAY;
|
|
82
|
+
$default?: T | (() => T) | string | object | typeof EMPTY_OBJECT | typeof EMPTY_ARRAY | DefaultValue;
|
|
9
83
|
$primaryKey?: boolean;
|
|
10
84
|
$unique?: boolean;
|
|
11
85
|
$references?: {
|
|
@@ -27,6 +101,7 @@ export interface ColumnConfig<T = unknown> {
|
|
|
27
101
|
$scale?: number;
|
|
28
102
|
$withTimezone?: boolean;
|
|
29
103
|
$columnName?: string;
|
|
104
|
+
$trackingId?: string;
|
|
30
105
|
}
|
|
31
106
|
/** Table column references for generated columns - each column becomes a ChainableExpr */
|
|
32
107
|
export type GeneratedTableRefs<T extends Record<string, ColumnConfig>> = {
|
|
@@ -109,13 +184,60 @@ export interface GeneratedFn {
|
|
|
109
184
|
extract(field: "year" | "month" | "day" | "hour" | "minute" | "second" | "dow" | "doy" | "week" | "quarter" | "epoch", col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
110
185
|
datePart(field: string, col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
111
186
|
age(col1: ChainableExpr | GeneratedExpr, col2: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
112
|
-
|
|
187
|
+
/** Create tsvector from text using specified config (e.g., 'english', 'simple') */
|
|
188
|
+
toTsvector(config: string, col: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
189
|
+
/** Assign weight (A, B, C, or D) to tsvector for ranking */
|
|
190
|
+
setweight(tsvector: ChainableExpr | GeneratedExpr, weight: "A" | "B" | "C" | "D"): ChainableExpr;
|
|
191
|
+
/** Concatenate multiple tsvectors using || operator */
|
|
192
|
+
tsvectorConcat(...vectors: (ChainableExpr | GeneratedExpr)[]): ChainableExpr;
|
|
193
|
+
/** Generic concatenation using || operator (works for text, tsvector, arrays) */
|
|
194
|
+
concat(...args: (ChainableExpr | GeneratedExpr | string)[]): ChainableExpr;
|
|
195
|
+
/** Text-specific concatenation using || operator */
|
|
196
|
+
textConcat(...args: (ChainableExpr | GeneratedExpr | string)[]): ChainableExpr;
|
|
197
|
+
/** Create tsquery from text */
|
|
198
|
+
toTsquery(config: string, col: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
199
|
+
/** Create tsquery from plain text (handles spaces and punctuation) */
|
|
200
|
+
plaintoTsquery(config: string, col: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
201
|
+
/** Create tsquery from web-style search (handles OR, quotes, etc.) */
|
|
202
|
+
websearchToTsquery(config: string, col: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
113
203
|
similarity(col1: ChainableExpr | GeneratedExpr, col2: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
114
204
|
point(x: ChainableExpr | GeneratedExpr | number, y: ChainableExpr | GeneratedExpr | number): ChainableExpr;
|
|
115
205
|
arrayLength(col: ChainableExpr | GeneratedExpr, dim?: number): ChainableExpr;
|
|
116
206
|
arrayPosition(arr: ChainableExpr | GeneratedExpr, elem: ChainableExpr | GeneratedExpr | string | number): ChainableExpr;
|
|
117
207
|
md5(col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
118
208
|
sha256(col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
209
|
+
/** Reference a column by name (used when importing SQL schemas) */
|
|
210
|
+
col(name: string, alias?: string): ChainableExpr;
|
|
211
|
+
/** Pass raw SQL expression directly - use for complex expressions */
|
|
212
|
+
raw(sql: string): ChainableExpr;
|
|
213
|
+
/** Cast to any PostgreSQL type by name */
|
|
214
|
+
cast(value: ChainableExpr | GeneratedExpr | string | number, typeName: string): ChainableExpr;
|
|
215
|
+
/** Call any SQL function by name */
|
|
216
|
+
func(name: string, ...args: (ChainableExpr | GeneratedExpr | string | number)[]): ChainableExpr;
|
|
217
|
+
/** Embed raw SQL - use with caution */
|
|
218
|
+
sql(expression: string): ChainableExpr;
|
|
219
|
+
/** Apply an operator between two values */
|
|
220
|
+
op(left: ChainableExpr | GeneratedExpr | string | number, operator: string, right: ChainableExpr | GeneratedExpr | string | number): ChainableExpr;
|
|
221
|
+
setWeight(tsvector: ChainableExpr | GeneratedExpr, weight: string): ChainableExpr;
|
|
222
|
+
asVarchar(col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
223
|
+
/** Concatenate text or tsvector values using || operator */
|
|
224
|
+
textConcat(left: ChainableExpr | GeneratedExpr | string, right: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
225
|
+
/** Full-text search match: tsvector @@ tsquery */
|
|
226
|
+
tsMatch(left: ChainableExpr | GeneratedExpr, right: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
227
|
+
/** Compare two values with a comparison operator */
|
|
228
|
+
compare(left: ChainableExpr | GeneratedExpr | string | number, op: "=" | "<>" | "!=" | "<" | ">" | "<=" | ">=", right: ChainableExpr | GeneratedExpr | string | number): ChainableExpr;
|
|
229
|
+
/** Apply regex match operator */
|
|
230
|
+
regex(value: ChainableExpr | GeneratedExpr, op: "~" | "~*" | "!~" | "!~*", pattern: ChainableExpr | GeneratedExpr | string): ChainableExpr;
|
|
231
|
+
/** Logical AND */
|
|
232
|
+
and(...args: (ChainableExpr | GeneratedExpr)[]): ChainableExpr;
|
|
233
|
+
/** Logical OR */
|
|
234
|
+
or(...args: (ChainableExpr | GeneratedExpr)[]): ChainableExpr;
|
|
235
|
+
/** Logical NOT */
|
|
236
|
+
not(arg: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
237
|
+
/** IS NULL check */
|
|
238
|
+
isNull(col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
239
|
+
/** IS NOT NULL check */
|
|
240
|
+
isNotNull(col: ChainableExpr | GeneratedExpr): ChainableExpr;
|
|
119
241
|
}
|
|
120
242
|
/** CASE expression builder */
|
|
121
243
|
export interface CaseBuilder {
|
|
@@ -128,6 +250,133 @@ export interface GeneratedExpr {
|
|
|
128
250
|
readonly $sql: string;
|
|
129
251
|
readonly $expr: true;
|
|
130
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Fluent chainable expression for generated columns.
|
|
255
|
+
* Provides a readable, chainable API similar to index WHERE clauses.
|
|
256
|
+
*
|
|
257
|
+
* ## Basic Usage
|
|
258
|
+
*
|
|
259
|
+
* Access columns via the table parameter in generatedAlwaysAs:
|
|
260
|
+
* ```typescript
|
|
261
|
+
* generatedAlwaysAs(table =>
|
|
262
|
+
* table.firstName.concat(' ', table.lastName)
|
|
263
|
+
* )
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* ## Chainable Methods
|
|
267
|
+
*
|
|
268
|
+
* ### Null Handling
|
|
269
|
+
* - `.coalesce(fallback)` - Returns first non-null value
|
|
270
|
+
* - `.nullif(value)` - Returns NULL if equal to value
|
|
271
|
+
*
|
|
272
|
+
* ### Type Casting
|
|
273
|
+
* - `.asText()` - Cast to TEXT
|
|
274
|
+
* - `.asInteger()` - Cast to INTEGER
|
|
275
|
+
* - `.asVarchar()` - Cast to VARCHAR
|
|
276
|
+
*
|
|
277
|
+
* ### Text Functions
|
|
278
|
+
* - `.lower()` - Convert to lowercase
|
|
279
|
+
* - `.upper()` - Convert to uppercase
|
|
280
|
+
* - `.trim()` - Remove whitespace
|
|
281
|
+
* - `.concat(...parts)` - Concatenate with other values/columns
|
|
282
|
+
* - `.substring(start, length?)` - Extract substring
|
|
283
|
+
* - `.replace(from, to)` - Replace text
|
|
284
|
+
* - `.length()` - Get string length
|
|
285
|
+
*
|
|
286
|
+
* ### Full-Text Search
|
|
287
|
+
* - `.toTsvector(config)` - Create tsvector (e.g., 'english')
|
|
288
|
+
* - `.setWeight(weight)` - Set weight A/B/C/D for ranking
|
|
289
|
+
* - `.tsvConcat(other)` - Concatenate tsvectors (|| operator)
|
|
290
|
+
*
|
|
291
|
+
* ### JSON/JSONB
|
|
292
|
+
* - `.jsonExtract(key)` - Extract JSON value (->)
|
|
293
|
+
* - `.jsonExtractText(key)` - Extract as text (->>)
|
|
294
|
+
*
|
|
295
|
+
* ### Math
|
|
296
|
+
* - `.add(n)`, `.subtract(n)`, `.multiply(n)`, `.divide(n)`
|
|
297
|
+
* - `.abs()`, `.round(precision?)`, `.floor()`, `.ceil()`
|
|
298
|
+
*
|
|
299
|
+
* @example Full-text search vector
|
|
300
|
+
* ```typescript
|
|
301
|
+
* searchVector: tsvector().generatedAlwaysAs(table =>
|
|
302
|
+
* table.email.coalesce('').asText().toTsvector('english').setWeight('A')
|
|
303
|
+
* .tsvConcat(table.name.coalesce('').toTsvector('english').setWeight('B'))
|
|
304
|
+
* )
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* @example Computed full name
|
|
308
|
+
* ```typescript
|
|
309
|
+
* fullName: text().generatedAlwaysAs(table =>
|
|
310
|
+
* table.firstName.concat(' ', table.lastName)
|
|
311
|
+
* )
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
export interface FluentGenExpr extends GeneratedExpr {
|
|
315
|
+
/** COALESCE - returns first non-null value */
|
|
316
|
+
coalesce(fallback: FluentGenExpr | string | number): FluentGenExpr;
|
|
317
|
+
/** NULLIF - returns NULL if equal to value */
|
|
318
|
+
nullif(value: FluentGenExpr | string | number): FluentGenExpr;
|
|
319
|
+
/** Cast to TEXT */
|
|
320
|
+
asText(): FluentGenExpr;
|
|
321
|
+
/** Cast to INTEGER */
|
|
322
|
+
asInteger(): FluentGenExpr;
|
|
323
|
+
/** Cast to VARCHAR */
|
|
324
|
+
asVarchar(): FluentGenExpr;
|
|
325
|
+
/** Cast to any PostgreSQL type */
|
|
326
|
+
cast(typeName: string): FluentGenExpr;
|
|
327
|
+
/** Convert to lowercase */
|
|
328
|
+
lower(): FluentGenExpr;
|
|
329
|
+
/** Convert to uppercase */
|
|
330
|
+
upper(): FluentGenExpr;
|
|
331
|
+
/** Remove leading/trailing whitespace */
|
|
332
|
+
trim(): FluentGenExpr;
|
|
333
|
+
/** Concatenate with other values using || operator */
|
|
334
|
+
concat(...parts: (FluentGenExpr | string)[]): FluentGenExpr;
|
|
335
|
+
/** Extract substring */
|
|
336
|
+
substring(start: number, length?: number): FluentGenExpr;
|
|
337
|
+
/** Replace text */
|
|
338
|
+
replace(from: string, to: string): FluentGenExpr;
|
|
339
|
+
/** Get string length */
|
|
340
|
+
length(): FluentGenExpr;
|
|
341
|
+
/** Left pad to specified length */
|
|
342
|
+
lpad(length: number, fill?: string): FluentGenExpr;
|
|
343
|
+
/** Right pad to specified length */
|
|
344
|
+
rpad(length: number, fill?: string): FluentGenExpr;
|
|
345
|
+
/** Create tsvector with specified config (e.g., 'english') */
|
|
346
|
+
toTsvector(config: string): FluentGenExpr;
|
|
347
|
+
/** Set weight for tsvector ranking (A/B/C/D) */
|
|
348
|
+
setWeight(weight: "A" | "B" | "C" | "D"): FluentGenExpr;
|
|
349
|
+
/** Concatenate tsvectors using || operator */
|
|
350
|
+
tsvConcat(other: FluentGenExpr): FluentGenExpr;
|
|
351
|
+
/** Extract JSON value (->) */
|
|
352
|
+
jsonExtract(key: string): FluentGenExpr;
|
|
353
|
+
/** Extract JSON value as text (->>) */
|
|
354
|
+
jsonExtractText(key: string): FluentGenExpr;
|
|
355
|
+
/** Add value */
|
|
356
|
+
add(value: FluentGenExpr | number): FluentGenExpr;
|
|
357
|
+
/** Subtract value */
|
|
358
|
+
subtract(value: FluentGenExpr | number): FluentGenExpr;
|
|
359
|
+
/** Multiply by value */
|
|
360
|
+
multiply(value: FluentGenExpr | number): FluentGenExpr;
|
|
361
|
+
/** Divide by value */
|
|
362
|
+
divide(value: FluentGenExpr | number): FluentGenExpr;
|
|
363
|
+
/** Absolute value */
|
|
364
|
+
abs(): FluentGenExpr;
|
|
365
|
+
/** Round to precision */
|
|
366
|
+
round(precision?: number): FluentGenExpr;
|
|
367
|
+
/** Floor */
|
|
368
|
+
floor(): FluentGenExpr;
|
|
369
|
+
/** Ceiling */
|
|
370
|
+
ceil(): FluentGenExpr;
|
|
371
|
+
/** Extract date part */
|
|
372
|
+
extract(field: "year" | "month" | "day" | "hour" | "minute" | "second"): FluentGenExpr;
|
|
373
|
+
/** Get array length */
|
|
374
|
+
arrayLength(dimension?: number): FluentGenExpr;
|
|
375
|
+
}
|
|
376
|
+
/** Table refs for fluent generated column expressions */
|
|
377
|
+
export type FluentGenTableRefs<T extends Record<string, ColumnConfig>> = {
|
|
378
|
+
[K in keyof T]: FluentGenExpr;
|
|
379
|
+
};
|
|
131
380
|
export type ColumnBuilder<T, Config extends ColumnConfig<T> = ColumnConfig<T>> = Config & {
|
|
132
381
|
notNull(): ColumnBuilder<T, Config & {
|
|
133
382
|
$nullable: false;
|
|
@@ -135,7 +384,7 @@ export type ColumnBuilder<T, Config extends ColumnConfig<T> = ColumnConfig<T>> =
|
|
|
135
384
|
nullable(): ColumnBuilder<T, Config & {
|
|
136
385
|
$nullable: true;
|
|
137
386
|
}>;
|
|
138
|
-
default<V extends T | (() => T)>(value: V): ColumnBuilder<T, Config & {
|
|
387
|
+
default<V extends T | (() => T) | DefaultValue>(value: V): ColumnBuilder<T, Config & {
|
|
139
388
|
$default: V;
|
|
140
389
|
}>;
|
|
141
390
|
primaryKey(): ColumnBuilder<T, Config & {
|
|
@@ -149,28 +398,31 @@ export type ColumnBuilder<T, Config extends ColumnConfig<T> = ColumnConfig<T>> =
|
|
|
149
398
|
onUpdate?: string;
|
|
150
399
|
}): ColumnBuilder<T, Config>;
|
|
151
400
|
/**
|
|
152
|
-
* Add CHECK constraint with enum-like values.
|
|
153
|
-
* Narrows the column type to the union of provided values.
|
|
154
|
-
* @param
|
|
401
|
+
* Add CHECK constraint with enum-like values and explicit constraint name.
|
|
402
|
+
* Narrows the column type to the union of provided values for autocomplete.
|
|
403
|
+
* @param name - Constraint name (preserved for 1:1 SQL parity)
|
|
404
|
+
* @param values - Allowed string values array (literal types auto-inferred)
|
|
155
405
|
* @example
|
|
156
|
-
* .check('active', 'inactive', 'banned')
|
|
406
|
+
* .check('users_status_check', ['active', 'inactive', 'banned'])
|
|
157
407
|
* // Type becomes 'active' | 'inactive' | 'banned'
|
|
158
408
|
*/
|
|
159
|
-
check<V extends string>(
|
|
409
|
+
check<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<V[number], ColumnConfig<V[number]> & {
|
|
160
410
|
$check: string;
|
|
161
|
-
$
|
|
411
|
+
$checkName: string;
|
|
412
|
+
$checkValues: V;
|
|
162
413
|
}>;
|
|
163
414
|
/**
|
|
164
415
|
* Add CHECK constraint that excludes specific values.
|
|
165
|
-
*
|
|
166
|
-
* @param values - Disallowed string values
|
|
416
|
+
* @param name - Constraint name (preserved for 1:1 SQL parity)
|
|
417
|
+
* @param values - Disallowed string values array
|
|
167
418
|
* @example
|
|
168
|
-
* .checkNot('deleted', 'archived')
|
|
419
|
+
* .checkNot('users_status_check', ['deleted', 'archived'])
|
|
169
420
|
* // Column cannot have these values
|
|
170
421
|
*/
|
|
171
|
-
checkNot<V extends string>(
|
|
422
|
+
checkNot<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<T, Config & {
|
|
172
423
|
$checkNot: string;
|
|
173
|
-
$
|
|
424
|
+
$checkNotName: string;
|
|
425
|
+
$checkNotValues: V;
|
|
174
426
|
}>;
|
|
175
427
|
/** @deprecated Use generatedAlwaysAs with callback instead */
|
|
176
428
|
generatedAs(expression: string, stored?: boolean): ColumnBuilder<T, Config & {
|
|
@@ -199,6 +451,14 @@ export type ColumnBuilder<T, Config extends ColumnConfig<T> = ColumnConfig<T>> =
|
|
|
199
451
|
* (table, F) => F.concat(table.firstName, ' ', table.lastName)
|
|
200
452
|
* )
|
|
201
453
|
*/
|
|
454
|
+
generatedAlwaysAs(callback: (F: GeneratedFn) => GeneratedExpr, options?: {
|
|
455
|
+
stored?: boolean;
|
|
456
|
+
}): ColumnBuilder<T, Config & {
|
|
457
|
+
$generated: {
|
|
458
|
+
expression: string;
|
|
459
|
+
stored?: boolean;
|
|
460
|
+
};
|
|
461
|
+
}>;
|
|
202
462
|
generatedAlwaysAs<Cols extends Record<string, ColumnConfig>>(callback: (table: GeneratedTableRefs<Cols>, F: GeneratedFn) => GeneratedExpr, options?: {
|
|
203
463
|
stored?: boolean;
|
|
204
464
|
}): ColumnBuilder<T, Config & {
|
|
@@ -245,7 +505,30 @@ export type ColumnBuilder<T, Config extends ColumnConfig<T> = ColumnConfig<T>> =
|
|
|
245
505
|
dimensions(d: number): ColumnBuilder<T, Config & {
|
|
246
506
|
$dimensions: number;
|
|
247
507
|
}>;
|
|
508
|
+
/**
|
|
509
|
+
* Add a comment/description to the column.
|
|
510
|
+
* This will be stored in the database schema and used for documentation.
|
|
511
|
+
* @param text - The comment text
|
|
512
|
+
* @example
|
|
513
|
+
* email: varchar(255).notNull().comment('User primary email address')
|
|
514
|
+
*/
|
|
515
|
+
comment(text: string): ColumnBuilder<T, Config & {
|
|
516
|
+
$comment: string;
|
|
517
|
+
}>;
|
|
518
|
+
/**
|
|
519
|
+
* Set tracking ID for rename detection.
|
|
520
|
+
* This ID persists across column renames and allows detecting RENAME vs DROP+ADD.
|
|
521
|
+
* Auto-generated during pull/import if not present.
|
|
522
|
+
* @param trackingId - Unique tracking identifier (e.g., 'col_a1b2c3')
|
|
523
|
+
* @example
|
|
524
|
+
* userId: varchar('user_id').notNull().$id('col_abc123')
|
|
525
|
+
*/
|
|
526
|
+
$id(trackingId: string): ColumnBuilder<T, Config & {
|
|
527
|
+
$trackingId: string;
|
|
528
|
+
}>;
|
|
248
529
|
};
|
|
530
|
+
/** Create a fluent chainable expression for generated columns */
|
|
531
|
+
export declare function createFluentGenExpr(sql: string): FluentGenExpr;
|
|
249
532
|
export declare const integer: (columnName?: string) => ColumnBuilder<number, ColumnConfig<number>>;
|
|
250
533
|
export declare const int: (columnName?: string) => ColumnBuilder<number, ColumnConfig<number>>;
|
|
251
534
|
export declare const int4: (columnName?: string) => ColumnBuilder<number, ColumnConfig<number>>;
|
|
@@ -764,14 +1047,17 @@ export declare function generateCompositeTypeSQL<T extends Record<string, Column
|
|
|
764
1047
|
* data: customType('my_type').notNull().array()
|
|
765
1048
|
*/
|
|
766
1049
|
export declare const customType: <T = string>(typeName: string, columnName?: string) => ColumnBuilder<T, ColumnConfig<T>>;
|
|
767
|
-
export declare const enumType: <T extends string>(name: string
|
|
1050
|
+
export declare const enumType: <T extends string>(name: string | {
|
|
1051
|
+
name: string;
|
|
1052
|
+
values: readonly T[];
|
|
1053
|
+
}, values?: readonly T[], columnName?: string) => ColumnConfig<T> & {
|
|
768
1054
|
notNull(): ColumnBuilder<T, ColumnConfig<T> & {
|
|
769
1055
|
$nullable: false;
|
|
770
1056
|
}>;
|
|
771
1057
|
nullable(): ColumnBuilder<T, ColumnConfig<T> & {
|
|
772
1058
|
$nullable: true;
|
|
773
1059
|
}>;
|
|
774
|
-
default<V extends T | (() => T)>(value: V): ColumnBuilder<T, ColumnConfig<T> & {
|
|
1060
|
+
default<V extends DefaultValue | T | (() => T)>(value: V): ColumnBuilder<T, ColumnConfig<T> & {
|
|
775
1061
|
$default: V;
|
|
776
1062
|
}>;
|
|
777
1063
|
primaryKey(): ColumnBuilder<T, ColumnConfig<T> & {
|
|
@@ -785,28 +1071,31 @@ export declare const enumType: <T extends string>(name: string, values: readonly
|
|
|
785
1071
|
onUpdate?: string;
|
|
786
1072
|
}): ColumnBuilder<T, ColumnConfig<T>>;
|
|
787
1073
|
/**
|
|
788
|
-
* Add CHECK constraint with enum-like values.
|
|
789
|
-
* Narrows the column type to the union of provided values.
|
|
790
|
-
* @param
|
|
1074
|
+
* Add CHECK constraint with enum-like values and explicit constraint name.
|
|
1075
|
+
* Narrows the column type to the union of provided values for autocomplete.
|
|
1076
|
+
* @param name - Constraint name (preserved for 1:1 SQL parity)
|
|
1077
|
+
* @param values - Allowed string values array (literal types auto-inferred)
|
|
791
1078
|
* @example
|
|
792
|
-
* .check('active', 'inactive', 'banned')
|
|
1079
|
+
* .check('users_status_check', ['active', 'inactive', 'banned'])
|
|
793
1080
|
* // Type becomes 'active' | 'inactive' | 'banned'
|
|
794
1081
|
*/
|
|
795
|
-
check<V extends string>(
|
|
1082
|
+
check<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<V[number], ColumnConfig<V[number]> & {
|
|
796
1083
|
$check: string;
|
|
797
|
-
$
|
|
1084
|
+
$checkName: string;
|
|
1085
|
+
$checkValues: V;
|
|
798
1086
|
}>;
|
|
799
1087
|
/**
|
|
800
1088
|
* Add CHECK constraint that excludes specific values.
|
|
801
|
-
*
|
|
802
|
-
* @param values - Disallowed string values
|
|
1089
|
+
* @param name - Constraint name (preserved for 1:1 SQL parity)
|
|
1090
|
+
* @param values - Disallowed string values array
|
|
803
1091
|
* @example
|
|
804
|
-
* .checkNot('deleted', 'archived')
|
|
1092
|
+
* .checkNot('users_status_check', ['deleted', 'archived'])
|
|
805
1093
|
* // Column cannot have these values
|
|
806
1094
|
*/
|
|
807
|
-
checkNot<V extends string>(
|
|
1095
|
+
checkNot<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<T, ColumnConfig<T> & {
|
|
808
1096
|
$checkNot: string;
|
|
809
|
-
$
|
|
1097
|
+
$checkNotName: string;
|
|
1098
|
+
$checkNotValues: V;
|
|
810
1099
|
}>;
|
|
811
1100
|
/** @deprecated Use generatedAlwaysAs with callback instead */
|
|
812
1101
|
generatedAs(expression: string, stored?: boolean): ColumnBuilder<T, ColumnConfig<T> & {
|
|
@@ -835,6 +1124,14 @@ export declare const enumType: <T extends string>(name: string, values: readonly
|
|
|
835
1124
|
* (table, F) => F.concat(table.firstName, ' ', table.lastName)
|
|
836
1125
|
* )
|
|
837
1126
|
*/
|
|
1127
|
+
generatedAlwaysAs(callback: (F: GeneratedFn) => GeneratedExpr, options?: {
|
|
1128
|
+
stored?: boolean;
|
|
1129
|
+
}): ColumnBuilder<T, ColumnConfig<T> & {
|
|
1130
|
+
$generated: {
|
|
1131
|
+
expression: string;
|
|
1132
|
+
stored?: boolean;
|
|
1133
|
+
};
|
|
1134
|
+
}>;
|
|
838
1135
|
generatedAlwaysAs<Cols extends Record<string, ColumnConfig>>(callback: (table: GeneratedTableRefs<Cols>, F: GeneratedFn) => GeneratedExpr, options?: {
|
|
839
1136
|
stored?: boolean;
|
|
840
1137
|
}): ColumnBuilder<T, ColumnConfig<T> & {
|
|
@@ -881,9 +1178,43 @@ export declare const enumType: <T extends string>(name: string, values: readonly
|
|
|
881
1178
|
dimensions(d: number): ColumnBuilder<T, ColumnConfig<T> & {
|
|
882
1179
|
$dimensions: number;
|
|
883
1180
|
}>;
|
|
1181
|
+
/**
|
|
1182
|
+
* Add a comment/description to the column.
|
|
1183
|
+
* This will be stored in the database schema and used for documentation.
|
|
1184
|
+
* @param text - The comment text
|
|
1185
|
+
* @example
|
|
1186
|
+
* email: varchar(255).notNull().comment('User primary email address')
|
|
1187
|
+
*/
|
|
1188
|
+
comment(text: string): ColumnBuilder<T, ColumnConfig<T> & {
|
|
1189
|
+
$comment: string;
|
|
1190
|
+
}>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Set tracking ID for rename detection.
|
|
1193
|
+
* This ID persists across column renames and allows detecting RENAME vs DROP+ADD.
|
|
1194
|
+
* Auto-generated during pull/import if not present.
|
|
1195
|
+
* @param trackingId - Unique tracking identifier (e.g., 'col_a1b2c3')
|
|
1196
|
+
* @example
|
|
1197
|
+
* userId: varchar('user_id').notNull().$id('col_abc123')
|
|
1198
|
+
*/
|
|
1199
|
+
$id(trackingId: string): ColumnBuilder<T, ColumnConfig<T> & {
|
|
1200
|
+
$trackingId: string;
|
|
1201
|
+
}>;
|
|
884
1202
|
} & {
|
|
885
1203
|
$enumValues: readonly T[];
|
|
886
1204
|
};
|
|
1205
|
+
/**
|
|
1206
|
+
* Create a column that references a domain type
|
|
1207
|
+
* @param domainDef - The domain definition from pgDomain() or just the domain name
|
|
1208
|
+
* @param columnName - Optional explicit column name
|
|
1209
|
+
* @example
|
|
1210
|
+
* // With domain definition
|
|
1211
|
+
* email: domainType(emailDomain)
|
|
1212
|
+
* // With domain name
|
|
1213
|
+
* email: domainType('email_domain')
|
|
1214
|
+
*/
|
|
1215
|
+
export declare const domainType: <T = string>(domainDef: string | {
|
|
1216
|
+
$domainName: string;
|
|
1217
|
+
}, columnName?: string) => ColumnBuilder<T, ColumnConfig<T>>;
|
|
887
1218
|
/** @deprecated Use pgComposite() instead for better type inference */
|
|
888
1219
|
export declare const compositeType: <T>(typeName: string, columnName?: string) => ColumnBuilder<T, ColumnConfig<T>>;
|
|
889
1220
|
/** SQL expression brand - marks a value as SQL expression for runtime detection */
|
|
@@ -1257,6 +1588,49 @@ export interface IndexDefinition {
|
|
|
1257
1588
|
include?: string[];
|
|
1258
1589
|
/** Expression for expression-based indexes */
|
|
1259
1590
|
expression?: string;
|
|
1591
|
+
/**
|
|
1592
|
+
* Generate CREATE INDEX IF NOT EXISTS instead of CREATE INDEX.
|
|
1593
|
+
*
|
|
1594
|
+
* When true, the generated SQL will be:
|
|
1595
|
+
* `CREATE INDEX IF NOT EXISTS index_name ON table_name (...)`
|
|
1596
|
+
*
|
|
1597
|
+
* This makes index creation idempotent - if the index already exists,
|
|
1598
|
+
* PostgreSQL will skip creation instead of throwing an error.
|
|
1599
|
+
*
|
|
1600
|
+
* Use cases:
|
|
1601
|
+
* - Idempotent migrations that can be run multiple times safely
|
|
1602
|
+
* - Manual schema management where you want to avoid errors on re-runs
|
|
1603
|
+
* - Incremental schema updates in development environments
|
|
1604
|
+
* - CI/CD pipelines where schema might already exist
|
|
1605
|
+
*
|
|
1606
|
+
* @default false
|
|
1607
|
+
*/
|
|
1608
|
+
ifNotExists?: boolean;
|
|
1609
|
+
/**
|
|
1610
|
+
* Use ON ONLY clause for partitioned tables.
|
|
1611
|
+
*
|
|
1612
|
+
* When true, the generated SQL will be:
|
|
1613
|
+
* `CREATE INDEX index_name ON ONLY table_name (...)`
|
|
1614
|
+
*
|
|
1615
|
+
* This creates an index on the parent partitioned table only, without
|
|
1616
|
+
* automatically creating matching indexes on child partitions. Each
|
|
1617
|
+
* partition must have its own index created separately.
|
|
1618
|
+
*
|
|
1619
|
+
* Use cases:
|
|
1620
|
+
* - When you want different index configurations per partition
|
|
1621
|
+
* - When partitions have different access patterns
|
|
1622
|
+
* - When you want to control index creation timing per partition
|
|
1623
|
+
* - For declarative partitioning with custom index strategies
|
|
1624
|
+
* - When some partitions don't need certain indexes (e.g., archive partitions)
|
|
1625
|
+
*
|
|
1626
|
+
* @default false
|
|
1627
|
+
* @see https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-MAINTENANCE
|
|
1628
|
+
*/
|
|
1629
|
+
tableOnly?: boolean;
|
|
1630
|
+
/** Index comment/description */
|
|
1631
|
+
comment?: string;
|
|
1632
|
+
/** Tracking ID for rename detection */
|
|
1633
|
+
trackingId?: string;
|
|
1260
1634
|
}
|
|
1261
1635
|
export interface IndexBuilder {
|
|
1262
1636
|
name: string;
|
|
@@ -1278,6 +1652,8 @@ export interface IndexBuilder {
|
|
|
1278
1652
|
on<T>(callback: (F: SqlFunctions) => SqlExpr): IndexBuilder;
|
|
1279
1653
|
/** Expression-based index columns */
|
|
1280
1654
|
on(...columns: string[]): IndexBuilder;
|
|
1655
|
+
/** Add a comment/description to the index */
|
|
1656
|
+
comment(text: string): IndexBuilder;
|
|
1281
1657
|
}
|
|
1282
1658
|
/** Where builder for index conditions */
|
|
1283
1659
|
export interface IndexWhereBuilder<T extends Record<string, unknown>> {
|
|
@@ -1348,6 +1724,11 @@ export interface CheckExpr<T = unknown> extends SqlExpr {
|
|
|
1348
1724
|
* @example table.role.neq('banned') // role <> 'banned'
|
|
1349
1725
|
*/
|
|
1350
1726
|
neq(value: T | CheckExpr<T>): WhereCondition;
|
|
1727
|
+
/**
|
|
1728
|
+
* Not equals (alias for neq) - SQL: `column <> value`
|
|
1729
|
+
* @example table.role.ne('banned') // role <> 'banned'
|
|
1730
|
+
*/
|
|
1731
|
+
ne(value: T | CheckExpr<T>): WhereCondition;
|
|
1351
1732
|
/**
|
|
1352
1733
|
* Is null - SQL: `column IS NULL`
|
|
1353
1734
|
* @example table.deletedAt.isNull() // deleted_at IS NULL
|
|
@@ -1440,25 +1821,136 @@ export interface CheckConstraintBuilder<T extends Record<string, unknown>> {
|
|
|
1440
1821
|
col(name: keyof T): CheckExpr;
|
|
1441
1822
|
/** Create a named check constraint */
|
|
1442
1823
|
constraint(name: string, condition: WhereCondition): CheckConstraintDef;
|
|
1824
|
+
/** Create a raw SQL expression condition */
|
|
1825
|
+
raw(expression: string): WhereCondition;
|
|
1443
1826
|
}
|
|
1444
|
-
/**
|
|
1827
|
+
/**
|
|
1828
|
+
* Column expression for use in index WHERE clauses and check constraints.
|
|
1829
|
+
*
|
|
1830
|
+
* Provides a fluent, chainable API for building type-safe SQL conditions.
|
|
1831
|
+
* All comparison methods return a {@link WhereCondition} which can be further
|
|
1832
|
+
* chained with `.and()` and `.or()` for compound conditions.
|
|
1833
|
+
*
|
|
1834
|
+
* ## Basic Usage
|
|
1835
|
+
*
|
|
1836
|
+
* In index definitions, access columns via the table parameter:
|
|
1837
|
+
* ```typescript
|
|
1838
|
+
* defineTable('users', { ... }, {
|
|
1839
|
+
* indexes: [{
|
|
1840
|
+
* columns: ['email'],
|
|
1841
|
+
* where: table => table.isDeleted.eq(false)
|
|
1842
|
+
* }]
|
|
1843
|
+
* })
|
|
1844
|
+
* ```
|
|
1845
|
+
*
|
|
1846
|
+
* ## Comparison Operators
|
|
1847
|
+
*
|
|
1848
|
+
* | Method | SQL | Example |
|
|
1849
|
+
* |--------|-----|---------|
|
|
1850
|
+
* | `.eq(value)` | `=` | `table.status.eq('active')` → `"status" = 'active'` |
|
|
1851
|
+
* | `.neq(value)` | `!=` | `table.status.neq('deleted')` → `"status" != 'deleted'` |
|
|
1852
|
+
* | `.ne(value)` | `!=` | Alias for `.neq()` |
|
|
1853
|
+
* | `.gt(value)` | `>` | `table.age.gt(18)` → `"age" > 18` |
|
|
1854
|
+
* | `.gte(value)` | `>=` | `table.age.gte(21)` → `"age" >= 21` |
|
|
1855
|
+
* | `.lt(value)` | `<` | `table.price.lt(100)` → `"price" < 100` |
|
|
1856
|
+
* | `.lte(value)` | `<=` | `table.price.lte(50)` → `"price" <= 50` |
|
|
1857
|
+
*
|
|
1858
|
+
* ## Null Checks
|
|
1859
|
+
*
|
|
1860
|
+
* | Method | SQL | Example |
|
|
1861
|
+
* |--------|-----|---------|
|
|
1862
|
+
* | `.isNull()` | `IS NULL` | `table.deletedAt.isNull()` → `"deleted_at" IS NULL` |
|
|
1863
|
+
* | `.isNotNull()` | `IS NOT NULL` | `table.email.isNotNull()` → `"email" IS NOT NULL` |
|
|
1864
|
+
*
|
|
1865
|
+
* ## Collection Operators
|
|
1866
|
+
*
|
|
1867
|
+
* | Method | SQL | Example |
|
|
1868
|
+
* |--------|-----|---------|
|
|
1869
|
+
* | `.in([...])` | `IN (...)` | `table.status.in(['a', 'b'])` → `"status" IN ('a', 'b')` |
|
|
1870
|
+
* | `.notIn([...])` | `NOT IN (...)` | `table.type.notIn([1, 2])` → `"type" NOT IN (1, 2)` |
|
|
1871
|
+
* | `.between(min, max)` | `BETWEEN ... AND ...` | `table.age.between(18, 65)` → `"age" BETWEEN 18 AND 65` |
|
|
1872
|
+
*
|
|
1873
|
+
* ## Pattern Matching
|
|
1874
|
+
*
|
|
1875
|
+
* | Method | SQL | Example |
|
|
1876
|
+
* |--------|-----|---------|
|
|
1877
|
+
* | `.like(pattern)` | `LIKE` | `table.name.like('John%')` → `"name" LIKE 'John%'` |
|
|
1878
|
+
* | `.ilike(pattern)` | `ILIKE` | `table.email.ilike('%@gmail%')` → `"email" ILIKE '%@gmail%'` |
|
|
1879
|
+
*
|
|
1880
|
+
* ## Compound Conditions
|
|
1881
|
+
*
|
|
1882
|
+
* Chain conditions using `.and()` and `.or()`:
|
|
1883
|
+
*
|
|
1884
|
+
* ```typescript
|
|
1885
|
+
* // Simple AND: status = 'active' AND is_deleted = false
|
|
1886
|
+
* table.status.eq('active').and(table.isDeleted.eq(false))
|
|
1887
|
+
*
|
|
1888
|
+
* // Simple OR: role = 'admin' OR role = 'moderator'
|
|
1889
|
+
* table.role.eq('admin').or(table.role.eq('moderator'))
|
|
1890
|
+
*
|
|
1891
|
+
* // Complex: (a = 3 AND b = 7) OR (a = 9 AND b = 1)
|
|
1892
|
+
* table.a.eq(3).and(table.b.eq(7))
|
|
1893
|
+
* .or(table.a.eq(9).and(table.b.eq(1)))
|
|
1894
|
+
* ```
|
|
1895
|
+
*
|
|
1896
|
+
* Each `.and()` and `.or()` wraps operands in parentheses for correct precedence.
|
|
1897
|
+
*
|
|
1898
|
+
* ## Arithmetic (for computed expressions)
|
|
1899
|
+
*
|
|
1900
|
+
* | Method | SQL | Example |
|
|
1901
|
+
* |--------|-----|---------|
|
|
1902
|
+
* | `.plus(n)` | `+` | `table.price.plus(tax)` → `("price" + "tax")` |
|
|
1903
|
+
* | `.minus(n)` | `-` | `table.total.minus(5)` → `("total" - 5)` |
|
|
1904
|
+
*
|
|
1905
|
+
* @example Partial index on active, non-deleted users
|
|
1906
|
+
* ```typescript
|
|
1907
|
+
* indexes: [{
|
|
1908
|
+
* columns: ['email'],
|
|
1909
|
+
* unique: true,
|
|
1910
|
+
* where: table => table.status.eq('active').and(table.isDeleted.eq(false))
|
|
1911
|
+
* }]
|
|
1912
|
+
* ```
|
|
1913
|
+
*
|
|
1914
|
+
* @example Partial index with null check
|
|
1915
|
+
* ```typescript
|
|
1916
|
+
* indexes: [{
|
|
1917
|
+
* columns: ['verified_at'],
|
|
1918
|
+
* where: table => table.verifiedAt.isNotNull()
|
|
1919
|
+
* }]
|
|
1920
|
+
* ```
|
|
1921
|
+
*/
|
|
1445
1922
|
export interface ColumnExpr extends SqlExpr {
|
|
1923
|
+
/** Equals: `column = value` */
|
|
1446
1924
|
eq(value: unknown): WhereCondition;
|
|
1925
|
+
/** Not equals (alternative syntax): `column != value` */
|
|
1447
1926
|
neq(value: unknown): WhereCondition;
|
|
1927
|
+
/** Not equals (SQL standard): `column <> value` */
|
|
1928
|
+
ne(value: unknown): WhereCondition;
|
|
1929
|
+
/** Greater than: `column > value` */
|
|
1448
1930
|
gt(value: unknown): WhereCondition;
|
|
1931
|
+
/** Greater than or equal: `column >= value` */
|
|
1449
1932
|
gte(value: unknown): WhereCondition;
|
|
1933
|
+
/** Less than: `column < value` */
|
|
1450
1934
|
lt(value: unknown): WhereCondition;
|
|
1935
|
+
/** Less than or equal: `column <= value` */
|
|
1451
1936
|
lte(value: unknown): WhereCondition;
|
|
1937
|
+
/** Is null: `column IS NULL` */
|
|
1452
1938
|
isNull(): WhereCondition;
|
|
1939
|
+
/** Is not null: `column IS NOT NULL` */
|
|
1453
1940
|
isNotNull(): WhereCondition;
|
|
1941
|
+
/** In list: `column IN (value1, value2, ...)` */
|
|
1454
1942
|
in(values: unknown[]): WhereCondition;
|
|
1943
|
+
/** Not in list: `column NOT IN (value1, value2, ...)` */
|
|
1455
1944
|
notIn(values: unknown[]): WhereCondition;
|
|
1945
|
+
/** Between range: `column BETWEEN min AND max` */
|
|
1456
1946
|
between(min: unknown, max: unknown): WhereCondition;
|
|
1947
|
+
/** Like pattern (case-sensitive): `column LIKE pattern` */
|
|
1457
1948
|
like(pattern: string): WhereCondition;
|
|
1949
|
+
/** Like pattern (case-insensitive): `column ILIKE pattern` */
|
|
1458
1950
|
ilike(pattern: string): WhereCondition;
|
|
1459
|
-
/** Add
|
|
1951
|
+
/** Add value: `(column + value)` - returns ColumnExpr for chaining */
|
|
1460
1952
|
plus(value: SqlExpr | number): ColumnExpr;
|
|
1461
|
-
/** Subtract
|
|
1953
|
+
/** Subtract value: `(column - value)` - returns ColumnExpr for chaining */
|
|
1462
1954
|
minus(value: SqlExpr | number): ColumnExpr;
|
|
1463
1955
|
}
|
|
1464
1956
|
export type IndexInput = IndexDefinition | IndexBuilder | IndexBuilderChain | {
|
|
@@ -1503,6 +1995,140 @@ export interface IndexBuilderChain {
|
|
|
1503
1995
|
desc(): IndexBuilderChain;
|
|
1504
1996
|
include(...columns: string[]): IndexBuilderChain;
|
|
1505
1997
|
opclass(opclass: string): IndexBuilderChain;
|
|
1998
|
+
/**
|
|
1999
|
+
* Generate CREATE INDEX IF NOT EXISTS instead of CREATE INDEX.
|
|
2000
|
+
*
|
|
2001
|
+
* This makes the index creation idempotent - if the index already exists,
|
|
2002
|
+
* PostgreSQL will skip creation instead of throwing an error.
|
|
2003
|
+
*
|
|
2004
|
+
* @example
|
|
2005
|
+
* ```typescript
|
|
2006
|
+
* indexes: (table, index) => [
|
|
2007
|
+
* // Idempotent index - safe to run multiple times
|
|
2008
|
+
* index('idx_users_email')
|
|
2009
|
+
* .on(table.email)
|
|
2010
|
+
* .ifNotExists(),
|
|
2011
|
+
* ]
|
|
2012
|
+
* ```
|
|
2013
|
+
*
|
|
2014
|
+
* Generated SQL:
|
|
2015
|
+
* ```sql
|
|
2016
|
+
* CREATE INDEX IF NOT EXISTS idx_users_email ON users (email);
|
|
2017
|
+
* ```
|
|
2018
|
+
*
|
|
2019
|
+
* **Use cases:**
|
|
2020
|
+
* - **Idempotent migrations** - Migrations that can be run multiple times
|
|
2021
|
+
* without causing errors, useful for CI/CD pipelines
|
|
2022
|
+
* - **Manual schema management** - When you want to manually run schema
|
|
2023
|
+
* scripts without tracking which statements have already been executed
|
|
2024
|
+
* - **Development environments** - When databases may already have some
|
|
2025
|
+
* indexes and you want to apply incremental changes
|
|
2026
|
+
* - **Multi-tenant systems** - When creating tenant-specific indexes that
|
|
2027
|
+
* may or may not exist
|
|
2028
|
+
*
|
|
2029
|
+
* **Important Notes:**
|
|
2030
|
+
* - When the index already exists, PostgreSQL will NOT verify that the
|
|
2031
|
+
* existing index matches your definition (columns, type, options)
|
|
2032
|
+
* - If you need to modify an existing index, you must DROP and recreate it
|
|
2033
|
+
* - For schema validation, use `relq diff` to compare definitions
|
|
2034
|
+
*
|
|
2035
|
+
* @returns The index builder chain for further method chaining
|
|
2036
|
+
*/
|
|
2037
|
+
ifNotExists(): IndexBuilderChain;
|
|
2038
|
+
/**
|
|
2039
|
+
* Use ON ONLY clause to create index on parent partitioned table only.
|
|
2040
|
+
*
|
|
2041
|
+
* When called, generates: `CREATE INDEX name ON ONLY table_name (...)`
|
|
2042
|
+
*
|
|
2043
|
+
* By default, when you create an index on a partitioned table, PostgreSQL
|
|
2044
|
+
* automatically creates matching indexes on all child partitions. Using
|
|
2045
|
+
* `tableOnly()` prevents this automatic propagation.
|
|
2046
|
+
*
|
|
2047
|
+
* @example
|
|
2048
|
+
* ```typescript
|
|
2049
|
+
* indexes: (table, index) => [
|
|
2050
|
+
* // Index only on parent table, not on partitions
|
|
2051
|
+
* index('idx_orders_date')
|
|
2052
|
+
* .on(table.orderDate)
|
|
2053
|
+
* .tableOnly(),
|
|
2054
|
+
* ]
|
|
2055
|
+
* ```
|
|
2056
|
+
*
|
|
2057
|
+
* Generated SQL:
|
|
2058
|
+
* ```sql
|
|
2059
|
+
* CREATE INDEX idx_orders_date ON ONLY orders (order_date);
|
|
2060
|
+
* ```
|
|
2061
|
+
*
|
|
2062
|
+
* Use cases:
|
|
2063
|
+
* - When you want different index configurations per partition
|
|
2064
|
+
* - When partitions have vastly different access patterns
|
|
2065
|
+
* - When you want to control index creation timing per partition
|
|
2066
|
+
* - For declarative partitioning with custom index strategies
|
|
2067
|
+
* - When some partitions don't need certain indexes (e.g., archive partitions)
|
|
2068
|
+
*
|
|
2069
|
+
* Without `tableOnly()`, PostgreSQL would:
|
|
2070
|
+
* 1. Create the index on the parent table
|
|
2071
|
+
* 2. Automatically create matching indexes on ALL child partitions
|
|
2072
|
+
*
|
|
2073
|
+
* With `tableOnly()`, you must manually create indexes on each partition:
|
|
2074
|
+
* ```sql
|
|
2075
|
+
* CREATE INDEX idx_orders_2024_date ON orders_2024 (order_date);
|
|
2076
|
+
* CREATE INDEX idx_orders_2025_date ON orders_2025 (order_date);
|
|
2077
|
+
* ```
|
|
2078
|
+
*
|
|
2079
|
+
* @returns The index builder chain for further method chaining
|
|
2080
|
+
* @see https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-DECLARATIVE-MAINTENANCE
|
|
2081
|
+
*/
|
|
2082
|
+
tableOnly(): IndexBuilderChain;
|
|
2083
|
+
/** Add a comment/description to the index */
|
|
2084
|
+
comment(text: string): IndexBuilderChain;
|
|
2085
|
+
/**
|
|
2086
|
+
* Set tracking ID for rename detection.
|
|
2087
|
+
* This ID persists across index renames and allows detecting RENAME vs DROP+ADD.
|
|
2088
|
+
* @param trackingId - Unique tracking identifier (e.g., 'idx_a1b2c3')
|
|
2089
|
+
*/
|
|
2090
|
+
$id(trackingId: string): IndexBuilderChain;
|
|
2091
|
+
}
|
|
2092
|
+
/** Options for constraints (name is optional - auto-generated if not provided) */
|
|
2093
|
+
export interface ConstraintOptions {
|
|
2094
|
+
name?: string;
|
|
2095
|
+
columns: ColumnRef$1[];
|
|
2096
|
+
}
|
|
2097
|
+
/** Constraint builder for table-level constraints (composite PKs, etc.) */
|
|
2098
|
+
export interface ConstraintBuilder<T extends Record<string, ColumnConfig>> {
|
|
2099
|
+
/**
|
|
2100
|
+
* Define composite primary key constraint
|
|
2101
|
+
* @example
|
|
2102
|
+
* // Without name (auto-generates name):
|
|
2103
|
+
* constraint.primaryKey(table.id, table.userId)
|
|
2104
|
+
*
|
|
2105
|
+
* // With name:
|
|
2106
|
+
* constraint.primaryKey({ name: 'pk_results', columns: [table.id, table.userId] })
|
|
2107
|
+
*/
|
|
2108
|
+
primaryKey(...columns: ColumnRef$1[]): ConstraintDef;
|
|
2109
|
+
primaryKey(options: ConstraintOptions): ConstraintDef;
|
|
2110
|
+
/**
|
|
2111
|
+
* Define unique constraint across multiple columns
|
|
2112
|
+
* @example
|
|
2113
|
+
* // Without name (auto-generates name):
|
|
2114
|
+
* constraint.unique(table.email, table.tenantId)
|
|
2115
|
+
*
|
|
2116
|
+
* // With name:
|
|
2117
|
+
* constraint.unique({ name: 'uq_email_tenant', columns: [table.email, table.tenantId] })
|
|
2118
|
+
*/
|
|
2119
|
+
unique(...columns: ColumnRef$1[]): ConstraintDef;
|
|
2120
|
+
unique(options: ConstraintOptions): ConstraintDef;
|
|
2121
|
+
/** Define exclusion constraint with raw SQL expression */
|
|
2122
|
+
exclude(name: string, expression: string): ConstraintDef;
|
|
2123
|
+
}
|
|
2124
|
+
/** Constraint definition result */
|
|
2125
|
+
export interface ConstraintDef {
|
|
2126
|
+
readonly $type: "PRIMARY KEY" | "UNIQUE" | "EXCLUDE";
|
|
2127
|
+
readonly $name: string;
|
|
2128
|
+
readonly $columns: string[];
|
|
2129
|
+
readonly $expression?: string;
|
|
2130
|
+
/** Tracking ID for rename detection */
|
|
2131
|
+
readonly $trackingId?: string;
|
|
1506
2132
|
}
|
|
1507
2133
|
export interface TableConfig<T extends Record<string, ColumnConfig>> {
|
|
1508
2134
|
columns: T;
|
|
@@ -1512,6 +2138,15 @@ export interface TableConfig<T extends Record<string, ColumnConfig>> {
|
|
|
1512
2138
|
columns: string[];
|
|
1513
2139
|
name?: string;
|
|
1514
2140
|
}>;
|
|
2141
|
+
/**
|
|
2142
|
+
* Table-level constraints with typed callback API (composite primary keys, etc.)
|
|
2143
|
+
* @example
|
|
2144
|
+
* constraints: (table, constraint) => [
|
|
2145
|
+
* constraint.primaryKey(table.id, table.userId),
|
|
2146
|
+
* constraint.unique({ name: 'uq_email_date', columns: [table.email, table.date] }),
|
|
2147
|
+
* ]
|
|
2148
|
+
*/
|
|
2149
|
+
constraints?: ((table: ColumnRefs<T>, constraint: ConstraintBuilder<T>) => ConstraintDef[]);
|
|
1515
2150
|
/**
|
|
1516
2151
|
* CHECK constraints with typed callback API
|
|
1517
2152
|
* @example
|
|
@@ -1531,9 +2166,11 @@ export interface TableConfig<T extends Record<string, ColumnConfig>> {
|
|
|
1531
2166
|
onDelete?: "CASCADE" | "SET NULL" | "SET DEFAULT" | "RESTRICT" | "NO ACTION";
|
|
1532
2167
|
onUpdate?: "CASCADE" | "SET NULL" | "SET DEFAULT" | "RESTRICT" | "NO ACTION";
|
|
1533
2168
|
name?: string;
|
|
2169
|
+
/** Tracking ID for rename detection */
|
|
2170
|
+
trackingId?: string;
|
|
1534
2171
|
}>;
|
|
1535
|
-
/** Index definitions - use (table, index, F) => [index('name').on(
|
|
1536
|
-
indexes?: IndexInput[] | ((table:
|
|
2172
|
+
/** Index definitions - use (table, index, F) => [index('name').on(table.email).where(table.isActive.eq(true))] */
|
|
2173
|
+
indexes?: IndexInput[] | ((table: IndexTableRefs<T>, index: IndexFactory, F: SqlFunctions) => IndexInput[]);
|
|
1537
2174
|
inherits?: string[];
|
|
1538
2175
|
/** Partition strategy - use (table, p) => p.list(table.col) / p.range(table.col) / p.hash(table.col, modulus) */
|
|
1539
2176
|
partitionBy?: (table: ColumnRefs<T>, p: PartitionStrategyFactory) => PartitionStrategyDef;
|
|
@@ -1541,6 +2178,68 @@ export interface TableConfig<T extends Record<string, ColumnConfig>> {
|
|
|
1541
2178
|
partitions?: (partition: PartitionFactory) => ChildPartitionDef[];
|
|
1542
2179
|
tablespace?: string;
|
|
1543
2180
|
withOptions?: Record<string, unknown>;
|
|
2181
|
+
/**
|
|
2182
|
+
* Generate CREATE TABLE IF NOT EXISTS instead of CREATE TABLE.
|
|
2183
|
+
*
|
|
2184
|
+
* When `ifNotExists: true`, the generated SQL will be:
|
|
2185
|
+
* `CREATE TABLE IF NOT EXISTS table_name (...)`
|
|
2186
|
+
*
|
|
2187
|
+
* This makes table creation idempotent - if the table already exists,
|
|
2188
|
+
* PostgreSQL will skip creation instead of throwing an error.
|
|
2189
|
+
*
|
|
2190
|
+
* **Use Cases:**
|
|
2191
|
+
* - **Idempotent migrations** - Migrations that can be run multiple times
|
|
2192
|
+
* without causing errors, useful for CI/CD pipelines
|
|
2193
|
+
* - **Manual schema management** - When you want to manually run schema
|
|
2194
|
+
* scripts without tracking which statements have already been executed
|
|
2195
|
+
* - **Development environments** - When databases may already have some
|
|
2196
|
+
* tables and you want to apply incremental changes
|
|
2197
|
+
* - **Multi-tenant systems** - When creating tenant-specific tables that
|
|
2198
|
+
* may or may not exist
|
|
2199
|
+
* - **Bootstrap scripts** - Initial setup scripts that might run on
|
|
2200
|
+
* existing databases
|
|
2201
|
+
*
|
|
2202
|
+
* **Example:**
|
|
2203
|
+
* ```typescript
|
|
2204
|
+
* export const users = defineTable('users', {
|
|
2205
|
+
* id: uuid().primaryKey().default('gen_random_uuid()'),
|
|
2206
|
+
* email: varchar(255).notNull(),
|
|
2207
|
+
* }, {
|
|
2208
|
+
* ifNotExists: true, // Safe to run multiple times
|
|
2209
|
+
* });
|
|
2210
|
+
* ```
|
|
2211
|
+
*
|
|
2212
|
+
* **Generated SQL:**
|
|
2213
|
+
* ```sql
|
|
2214
|
+
* CREATE TABLE IF NOT EXISTS users (
|
|
2215
|
+
* id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
2216
|
+
* email VARCHAR(255) NOT NULL
|
|
2217
|
+
* );
|
|
2218
|
+
* ```
|
|
2219
|
+
*
|
|
2220
|
+
* **Important Notes:**
|
|
2221
|
+
* - When `ifNotExists: true` and the table already exists, PostgreSQL
|
|
2222
|
+
* will NOT verify that the existing table matches your schema
|
|
2223
|
+
* - Column differences, missing columns, or type mismatches are NOT detected
|
|
2224
|
+
* - For schema validation, use proper migration tools or `relq diff`
|
|
2225
|
+
* - The existing table's structure may differ from your definition
|
|
2226
|
+
*
|
|
2227
|
+
* @default false - Standard CREATE TABLE that throws if table exists
|
|
2228
|
+
*/
|
|
2229
|
+
ifNotExists?: boolean;
|
|
2230
|
+
/**
|
|
2231
|
+
* Table comment/description.
|
|
2232
|
+
* This will be stored in the database schema and used for documentation.
|
|
2233
|
+
* @example
|
|
2234
|
+
* defineTable('users', { ... }, { comment: 'Stores user account information' })
|
|
2235
|
+
*/
|
|
2236
|
+
comment?: string;
|
|
2237
|
+
/**
|
|
2238
|
+
* Tracking ID for rename detection.
|
|
2239
|
+
* Auto-generated during pull/import if not present.
|
|
2240
|
+
* @example 't1a2b3'
|
|
2241
|
+
*/
|
|
2242
|
+
$trackingId?: string;
|
|
1544
2243
|
}
|
|
1545
2244
|
type ColumnRef$1 = string & {
|
|
1546
2245
|
__columnRef: true;
|
|
@@ -1548,6 +2247,16 @@ type ColumnRef$1 = string & {
|
|
|
1548
2247
|
export type ColumnRefs<T extends Record<string, ColumnConfig>> = {
|
|
1549
2248
|
[K in keyof T]: ColumnRef$1;
|
|
1550
2249
|
};
|
|
2250
|
+
/**
|
|
2251
|
+
* Table refs for index WHERE clauses - each column is a ColumnExpr with comparison methods
|
|
2252
|
+
*
|
|
2253
|
+
* Used in: `indexes: (table, index) => [index('name').on(table.col).where(table.isActive.eq(true))]`
|
|
2254
|
+
*
|
|
2255
|
+
* @template T - Record of column names to ColumnConfig types
|
|
2256
|
+
*/
|
|
2257
|
+
export type IndexTableRefs<T extends Record<string, ColumnConfig>> = {
|
|
2258
|
+
[K in keyof T]: ColumnExpr;
|
|
2259
|
+
};
|
|
1551
2260
|
/**
|
|
1552
2261
|
* Table refs for check constraints - each column is a typed CheckExpr
|
|
1553
2262
|
* The type is inferred from the column's ColumnConfig type parameter
|
|
@@ -1649,6 +2358,8 @@ export interface TableDefinition<T extends Record<string, ColumnConfig>> {
|
|
|
1649
2358
|
expression: string;
|
|
1650
2359
|
name?: string;
|
|
1651
2360
|
}>;
|
|
2361
|
+
/** Table-level constraints (composite PKs, etc.) */
|
|
2362
|
+
$constraints?: ConstraintDef[];
|
|
1652
2363
|
$foreignKeys?: Array<{
|
|
1653
2364
|
columns: string[];
|
|
1654
2365
|
references: {
|
|
@@ -1658,12 +2369,18 @@ export interface TableDefinition<T extends Record<string, ColumnConfig>> {
|
|
|
1658
2369
|
onDelete?: string;
|
|
1659
2370
|
onUpdate?: string;
|
|
1660
2371
|
name?: string;
|
|
2372
|
+
/** Tracking ID for rename detection */
|
|
2373
|
+
trackingId?: string;
|
|
1661
2374
|
}>;
|
|
1662
2375
|
$indexes?: IndexDefinition[];
|
|
1663
2376
|
$inherits?: string[];
|
|
1664
2377
|
$partitionBy?: PartitionStrategyDef;
|
|
1665
2378
|
$tablespace?: string;
|
|
1666
2379
|
$withOptions?: Record<string, unknown>;
|
|
2380
|
+
/** Whether to use CREATE TABLE IF NOT EXISTS */
|
|
2381
|
+
$ifNotExists?: boolean;
|
|
2382
|
+
/** Tracking ID for rename detection */
|
|
2383
|
+
$trackingId?: string;
|
|
1667
2384
|
$inferSelect: BuildSelectType<T>;
|
|
1668
2385
|
$inferInsert: BuildInsertType<T>;
|
|
1669
2386
|
toSQL(): string;
|
|
@@ -1770,6 +2487,523 @@ export declare function manyToMany<TFrom extends TableDefinition<Record<string,
|
|
|
1770
2487
|
throughFromKey?: string;
|
|
1771
2488
|
throughToKey?: string;
|
|
1772
2489
|
}): ManyToManyRelation<TFrom, TTo, TThrough>;
|
|
2490
|
+
/**
|
|
2491
|
+
* PostgreSQL referential actions for ON DELETE and ON UPDATE clauses.
|
|
2492
|
+
*
|
|
2493
|
+
* | Action | Behavior |
|
|
2494
|
+
* |--------|----------|
|
|
2495
|
+
* | `'NO ACTION'` | Default. Raises error if referenced rows exist. Checked at statement end. |
|
|
2496
|
+
* | `'RESTRICT'` | Like NO ACTION but checked immediately (cannot be deferred). |
|
|
2497
|
+
* | `'CASCADE'` | Deletes/updates child rows when parent is deleted/updated. |
|
|
2498
|
+
* | `'SET NULL'` | Sets FK columns to NULL when parent is deleted/updated. |
|
|
2499
|
+
* | `'SET DEFAULT'` | Sets FK columns to their default values when parent changes. |
|
|
2500
|
+
*
|
|
2501
|
+
* @example
|
|
2502
|
+
* ```typescript
|
|
2503
|
+
* // When an order is deleted, delete all order items
|
|
2504
|
+
* orderId: r.referenceTo.orders(t => ({ onDelete: 'CASCADE' }))
|
|
2505
|
+
*
|
|
2506
|
+
* // When a user is deleted, orphan their comments (set user_id to NULL)
|
|
2507
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'SET NULL' }))
|
|
2508
|
+
*
|
|
2509
|
+
* // Prevent deleting a category that has products
|
|
2510
|
+
* categoryId: r.referenceTo.categories(t => ({ onDelete: 'RESTRICT' }))
|
|
2511
|
+
* ```
|
|
2512
|
+
*/
|
|
2513
|
+
export type ReferentialAction = "NO ACTION" | "RESTRICT" | "CASCADE" | "SET NULL" | "SET DEFAULT";
|
|
2514
|
+
/**
|
|
2515
|
+
* PostgreSQL MATCH types for foreign key constraints.
|
|
2516
|
+
*
|
|
2517
|
+
* | Match Type | Behavior |
|
|
2518
|
+
* |------------|----------|
|
|
2519
|
+
* | `'SIMPLE'` | Default. Any NULL in FK columns satisfies constraint. |
|
|
2520
|
+
* | `'FULL'` | All FK columns must be NULL together, or all must be non-NULL. |
|
|
2521
|
+
*
|
|
2522
|
+
* Note: MATCH PARTIAL is defined in SQL standard but not implemented in PostgreSQL.
|
|
2523
|
+
*
|
|
2524
|
+
* @example
|
|
2525
|
+
* ```typescript
|
|
2526
|
+
* // Composite FK where all columns must match or all be NULL
|
|
2527
|
+
* productWarehouse: r.referenceTo.inventory(t => ({
|
|
2528
|
+
* columns: [r.orderItems.productId, r.orderItems.warehouseId],
|
|
2529
|
+
* references: [t.productId, t.warehouseId],
|
|
2530
|
+
* match: 'FULL',
|
|
2531
|
+
* }))
|
|
2532
|
+
* ```
|
|
2533
|
+
*/
|
|
2534
|
+
export type MatchType = "SIMPLE" | "FULL";
|
|
2535
|
+
/**
|
|
2536
|
+
* Column reference with table and column metadata for relations.
|
|
2537
|
+
* Used internally by the builder to track column references.
|
|
2538
|
+
*/
|
|
2539
|
+
export interface RelationColumnRef<TTableName extends string = string, TColumnName extends string = string> {
|
|
2540
|
+
/** SQL table name */
|
|
2541
|
+
$table: TTableName;
|
|
2542
|
+
/** SQL column name */
|
|
2543
|
+
$column: TColumnName;
|
|
2544
|
+
/** Schema key (TypeScript property name) */
|
|
2545
|
+
$schemaKey?: string;
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Options for a single-column foreign key constraint.
|
|
2549
|
+
*
|
|
2550
|
+
* Maps to PostgreSQL syntax:
|
|
2551
|
+
* ```sql
|
|
2552
|
+
* column_name type REFERENCES target_table(target_column)
|
|
2553
|
+
* [ON DELETE action]
|
|
2554
|
+
* [ON UPDATE action]
|
|
2555
|
+
* [MATCH FULL | MATCH SIMPLE]
|
|
2556
|
+
* [DEFERRABLE | NOT DEFERRABLE]
|
|
2557
|
+
* [INITIALLY DEFERRED | INITIALLY IMMEDIATE]
|
|
2558
|
+
* ```
|
|
2559
|
+
*
|
|
2560
|
+
* @example
|
|
2561
|
+
* ```typescript
|
|
2562
|
+
* // Simple FK to primary key (references defaults to PK)
|
|
2563
|
+
* userId: r.referenceTo.users(t => ({
|
|
2564
|
+
* onDelete: 'CASCADE',
|
|
2565
|
+
* }))
|
|
2566
|
+
*
|
|
2567
|
+
* // FK to non-PK unique column
|
|
2568
|
+
* walletAddress: r.referenceTo.wallets(t => ({
|
|
2569
|
+
* references: t.address, // References wallets.address, not wallets.id
|
|
2570
|
+
* onDelete: 'SET NULL',
|
|
2571
|
+
* }))
|
|
2572
|
+
*
|
|
2573
|
+
* // Full options
|
|
2574
|
+
* orderId: r.referenceTo.orders(t => ({
|
|
2575
|
+
* references: t.id,
|
|
2576
|
+
* onDelete: 'CASCADE',
|
|
2577
|
+
* onUpdate: 'NO ACTION',
|
|
2578
|
+
* match: 'SIMPLE',
|
|
2579
|
+
* deferrable: true,
|
|
2580
|
+
* initiallyDeferred: true,
|
|
2581
|
+
* }))
|
|
2582
|
+
* ```
|
|
2583
|
+
*/
|
|
2584
|
+
export interface ForeignKeyOptions<TTargetColumns extends Record<string, RelationColumnRef>> {
|
|
2585
|
+
/**
|
|
2586
|
+
* Target column on the referenced table.
|
|
2587
|
+
*
|
|
2588
|
+
* - If omitted: References the PRIMARY KEY of the target table
|
|
2589
|
+
* - If specified: Must be a PRIMARY KEY or UNIQUE column
|
|
2590
|
+
*
|
|
2591
|
+
* @example
|
|
2592
|
+
* ```typescript
|
|
2593
|
+
* // References users.id (primary key, default)
|
|
2594
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'CASCADE' }))
|
|
2595
|
+
*
|
|
2596
|
+
* // References users.email (must be UNIQUE)
|
|
2597
|
+
* userEmail: r.referenceTo.users(t => ({
|
|
2598
|
+
* references: t.email,
|
|
2599
|
+
* onDelete: 'SET NULL',
|
|
2600
|
+
* }))
|
|
2601
|
+
* ```
|
|
2602
|
+
*/
|
|
2603
|
+
references?: RelationColumnRef;
|
|
2604
|
+
/**
|
|
2605
|
+
* Action when referenced row is deleted.
|
|
2606
|
+
*
|
|
2607
|
+
* | Action | Effect |
|
|
2608
|
+
* |--------|--------|
|
|
2609
|
+
* | `'NO ACTION'` | Raises error (default, checked at statement end) |
|
|
2610
|
+
* | `'RESTRICT'` | Raises error (checked immediately) |
|
|
2611
|
+
* | `'CASCADE'` | Deletes this row too |
|
|
2612
|
+
* | `'SET NULL'` | Sets this column to NULL |
|
|
2613
|
+
* | `'SET DEFAULT'` | Sets this column to its default value |
|
|
2614
|
+
*
|
|
2615
|
+
* @default 'NO ACTION'
|
|
2616
|
+
*
|
|
2617
|
+
* @example
|
|
2618
|
+
* ```typescript
|
|
2619
|
+
* // Delete order items when order is deleted
|
|
2620
|
+
* orderId: r.referenceTo.orders(t => ({ onDelete: 'CASCADE' }))
|
|
2621
|
+
*
|
|
2622
|
+
* // Keep comment but orphan it when user deleted
|
|
2623
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'SET NULL' }))
|
|
2624
|
+
* ```
|
|
2625
|
+
*/
|
|
2626
|
+
onDelete?: ReferentialAction;
|
|
2627
|
+
/**
|
|
2628
|
+
* Action when referenced column value is updated.
|
|
2629
|
+
*
|
|
2630
|
+
* Rarely used since primary keys typically don't change.
|
|
2631
|
+
* Same actions as onDelete.
|
|
2632
|
+
*
|
|
2633
|
+
* @default 'NO ACTION'
|
|
2634
|
+
*
|
|
2635
|
+
* @example
|
|
2636
|
+
* ```typescript
|
|
2637
|
+
* // If order.id changes, update order_items.order_id too
|
|
2638
|
+
* orderId: r.referenceTo.orders(t => ({
|
|
2639
|
+
* onDelete: 'CASCADE',
|
|
2640
|
+
* onUpdate: 'CASCADE',
|
|
2641
|
+
* }))
|
|
2642
|
+
* ```
|
|
2643
|
+
*/
|
|
2644
|
+
onUpdate?: ReferentialAction;
|
|
2645
|
+
/**
|
|
2646
|
+
* MATCH type for the foreign key constraint.
|
|
2647
|
+
*
|
|
2648
|
+
* - `'SIMPLE'` (default): Any NULL in FK columns satisfies constraint
|
|
2649
|
+
* - `'FULL'`: All FK columns must be NULL together, or all non-NULL
|
|
2650
|
+
*
|
|
2651
|
+
* Only relevant for composite (multi-column) foreign keys.
|
|
2652
|
+
*
|
|
2653
|
+
* @default 'SIMPLE'
|
|
2654
|
+
*/
|
|
2655
|
+
match?: MatchType;
|
|
2656
|
+
/**
|
|
2657
|
+
* Whether the constraint can be deferred to transaction end.
|
|
2658
|
+
*
|
|
2659
|
+
* When `true`, you can use `SET CONSTRAINTS ... DEFERRED` to defer
|
|
2660
|
+
* constraint checking until transaction commit.
|
|
2661
|
+
*
|
|
2662
|
+
* Useful for circular references or bulk operations.
|
|
2663
|
+
*
|
|
2664
|
+
* @default false
|
|
2665
|
+
*
|
|
2666
|
+
* @example
|
|
2667
|
+
* ```typescript
|
|
2668
|
+
* // Allow deferring this constraint
|
|
2669
|
+
* parentId: r.referenceTo.categories(t => ({
|
|
2670
|
+
* onDelete: 'CASCADE',
|
|
2671
|
+
* deferrable: true,
|
|
2672
|
+
* }))
|
|
2673
|
+
*
|
|
2674
|
+
* // Then in a transaction:
|
|
2675
|
+
* // SET CONSTRAINTS category_parent_fk DEFERRED;
|
|
2676
|
+
* // ... insert circular references ...
|
|
2677
|
+
* // COMMIT; -- constraint checked here
|
|
2678
|
+
* ```
|
|
2679
|
+
*/
|
|
2680
|
+
deferrable?: boolean;
|
|
2681
|
+
/**
|
|
2682
|
+
* Whether deferred constraints start in deferred mode.
|
|
2683
|
+
*
|
|
2684
|
+
* Only applies when `deferrable: true`.
|
|
2685
|
+
* - `false`: Constraint is checked after each statement (can be deferred manually)
|
|
2686
|
+
* - `true`: Constraint is checked at transaction commit by default
|
|
2687
|
+
*
|
|
2688
|
+
* @default false
|
|
2689
|
+
*/
|
|
2690
|
+
initiallyDeferred?: boolean;
|
|
2691
|
+
/**
|
|
2692
|
+
* Tracking ID for rename detection.
|
|
2693
|
+
* Auto-generated during pull/import if not present.
|
|
2694
|
+
* @example 'fk_a1b2c3'
|
|
2695
|
+
*/
|
|
2696
|
+
trackingId?: string;
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* Options for a composite (multi-column) foreign key constraint.
|
|
2700
|
+
*
|
|
2701
|
+
* Maps to PostgreSQL table-level constraint:
|
|
2702
|
+
* ```sql
|
|
2703
|
+
* FOREIGN KEY (col1, col2, ...)
|
|
2704
|
+
* REFERENCES target_table(target_col1, target_col2, ...)
|
|
2705
|
+
* [ON DELETE action]
|
|
2706
|
+
* [ON UPDATE action]
|
|
2707
|
+
* ...
|
|
2708
|
+
* ```
|
|
2709
|
+
*
|
|
2710
|
+
* @example
|
|
2711
|
+
* ```typescript
|
|
2712
|
+
* // Composite FK: (product_id, warehouse_id) → inventory(product_id, warehouse_id)
|
|
2713
|
+
* productWarehouse: r.referenceTo.inventory(t => ({
|
|
2714
|
+
* columns: [r.orderItems.productId, r.orderItems.warehouseId],
|
|
2715
|
+
* references: [t.productId, t.warehouseId],
|
|
2716
|
+
* onDelete: 'CASCADE',
|
|
2717
|
+
* match: 'FULL', // All columns must match or all be NULL
|
|
2718
|
+
* }))
|
|
2719
|
+
* ```
|
|
2720
|
+
*/
|
|
2721
|
+
export interface CompositeForeignKeyOptions<TTargetColumns extends Record<string, RelationColumnRef>> {
|
|
2722
|
+
/**
|
|
2723
|
+
* Source columns on THIS table (array for composite FK).
|
|
2724
|
+
* Column count must match `references` array.
|
|
2725
|
+
*/
|
|
2726
|
+
columns: RelationColumnRef[];
|
|
2727
|
+
/**
|
|
2728
|
+
* Target columns on the referenced table (array for composite FK).
|
|
2729
|
+
* Must be a PRIMARY KEY or UNIQUE constraint together.
|
|
2730
|
+
*/
|
|
2731
|
+
references: RelationColumnRef[];
|
|
2732
|
+
/** Action when referenced row is deleted */
|
|
2733
|
+
onDelete?: ReferentialAction;
|
|
2734
|
+
/** Action when referenced column values are updated */
|
|
2735
|
+
onUpdate?: ReferentialAction;
|
|
2736
|
+
/** MATCH type (FULL recommended for composite FKs) */
|
|
2737
|
+
match?: MatchType;
|
|
2738
|
+
/** Whether constraint can be deferred */
|
|
2739
|
+
deferrable?: boolean;
|
|
2740
|
+
/** Whether deferred constraints start deferred */
|
|
2741
|
+
initiallyDeferred?: boolean;
|
|
2742
|
+
/** Tracking ID for rename detection */
|
|
2743
|
+
trackingId?: string;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Foreign key relation definition (runtime representation).
|
|
2747
|
+
* Stores all metadata about a REFERENCES constraint.
|
|
2748
|
+
*/
|
|
2749
|
+
export interface ForeignKeyRelationDef {
|
|
2750
|
+
/** Relation type marker */
|
|
2751
|
+
$type: "foreignKey";
|
|
2752
|
+
/** Target table name (SQL name) */
|
|
2753
|
+
$targetTable: string;
|
|
2754
|
+
/** Source column(s) on this table */
|
|
2755
|
+
$columns: Array<{
|
|
2756
|
+
table: string;
|
|
2757
|
+
column: string;
|
|
2758
|
+
}>;
|
|
2759
|
+
/** Target column(s) on referenced table (undefined = PK) */
|
|
2760
|
+
$references?: Array<{
|
|
2761
|
+
table: string;
|
|
2762
|
+
column: string;
|
|
2763
|
+
}>;
|
|
2764
|
+
/** ON DELETE action */
|
|
2765
|
+
$onDelete?: ReferentialAction;
|
|
2766
|
+
/** ON UPDATE action */
|
|
2767
|
+
$onUpdate?: ReferentialAction;
|
|
2768
|
+
/** MATCH type */
|
|
2769
|
+
$match?: MatchType;
|
|
2770
|
+
/** DEFERRABLE flag */
|
|
2771
|
+
$deferrable?: boolean;
|
|
2772
|
+
/** INITIALLY DEFERRED flag */
|
|
2773
|
+
$initiallyDeferred?: boolean;
|
|
2774
|
+
/** Tracking ID for rename detection */
|
|
2775
|
+
$trackingId?: string;
|
|
2776
|
+
}
|
|
2777
|
+
/** Relation definition type */
|
|
2778
|
+
export type RelationDef = ForeignKeyRelationDef;
|
|
2779
|
+
/** Table's relations map */
|
|
2780
|
+
export type TableRelations = Record<string, RelationDef>;
|
|
2781
|
+
/** All relations for a schema */
|
|
2782
|
+
export type SchemaRelations = Record<string, TableRelations>;
|
|
2783
|
+
/**
|
|
2784
|
+
* Target table column accessor.
|
|
2785
|
+
* Provides autocomplete for target table's columns.
|
|
2786
|
+
*/
|
|
2787
|
+
export type TargetTableColumns<TTable extends TableDefinition<any>> = {
|
|
2788
|
+
[K in keyof TTable["$columns"]]: RelationColumnRef<TTable["$name"], K & string>;
|
|
2789
|
+
};
|
|
2790
|
+
/**
|
|
2791
|
+
* Callback function type for referenceTo builder.
|
|
2792
|
+
* Receives target table columns and returns FK options.
|
|
2793
|
+
*/
|
|
2794
|
+
export type ReferenceToCallback<TTargetTable extends TableDefinition<any>> = (t: TargetTableColumns<TTargetTable>) => ForeignKeyOptions<TargetTableColumns<TTargetTable>> | CompositeForeignKeyOptions<TargetTableColumns<TTargetTable>>;
|
|
2795
|
+
/**
|
|
2796
|
+
* The referenceTo builder - provides target table selection with autocomplete.
|
|
2797
|
+
*
|
|
2798
|
+
* @example
|
|
2799
|
+
* ```typescript
|
|
2800
|
+
* // r.referenceTo.users gives autocomplete for users table
|
|
2801
|
+
* userId: r.referenceTo.users(t => ({
|
|
2802
|
+
* references: t.id, // Autocomplete shows users columns
|
|
2803
|
+
* onDelete: 'CASCADE',
|
|
2804
|
+
* }))
|
|
2805
|
+
* ```
|
|
2806
|
+
*/
|
|
2807
|
+
export type ReferenceToBuilder<TSchema extends Record<string, TableDefinition<any>>> = {
|
|
2808
|
+
[K in keyof TSchema]: (callback: ReferenceToCallback<TSchema[K]>) => ForeignKeyRelationDef;
|
|
2809
|
+
};
|
|
2810
|
+
/**
|
|
2811
|
+
* Source table column accessor.
|
|
2812
|
+
* Used for composite FK columns on the current table.
|
|
2813
|
+
*/
|
|
2814
|
+
export type SourceTableColumns<TTable extends TableDefinition<any>> = {
|
|
2815
|
+
[K in keyof TTable["$columns"]]: RelationColumnRef<TTable["$name"], K & string>;
|
|
2816
|
+
};
|
|
2817
|
+
/**
|
|
2818
|
+
* All source table column refs for the schema.
|
|
2819
|
+
* Enables referencing columns on any table for composite FKs.
|
|
2820
|
+
*
|
|
2821
|
+
* @example
|
|
2822
|
+
* ```typescript
|
|
2823
|
+
* // Access columns from any table
|
|
2824
|
+
* columns: [r.orderItems.productId, r.orderItems.warehouseId]
|
|
2825
|
+
* ```
|
|
2826
|
+
*/
|
|
2827
|
+
export type SchemaColumnRefs<TSchema extends Record<string, TableDefinition<any>>> = {
|
|
2828
|
+
[K in keyof TSchema]: SourceTableColumns<TSchema[K]>;
|
|
2829
|
+
};
|
|
2830
|
+
/**
|
|
2831
|
+
* Full relation builder passed to pgRelations callback.
|
|
2832
|
+
*
|
|
2833
|
+
* @example
|
|
2834
|
+
* ```typescript
|
|
2835
|
+
* pgRelations(schema, (r) => ({
|
|
2836
|
+
* // r.referenceTo.tableName - define FK to that table
|
|
2837
|
+
* // r.tableName.columnName - reference columns (for composite FKs)
|
|
2838
|
+
* }))
|
|
2839
|
+
* ```
|
|
2840
|
+
*/
|
|
2841
|
+
export interface FullRelationBuilder<TSchema extends Record<string, TableDefinition<any>>> {
|
|
2842
|
+
/**
|
|
2843
|
+
* Define a foreign key reference to another table.
|
|
2844
|
+
*
|
|
2845
|
+
* @example
|
|
2846
|
+
* ```typescript
|
|
2847
|
+
* // Single-column FK
|
|
2848
|
+
* userId: r.referenceTo.users(t => ({
|
|
2849
|
+
* references: t.id,
|
|
2850
|
+
* onDelete: 'CASCADE',
|
|
2851
|
+
* }))
|
|
2852
|
+
*
|
|
2853
|
+
* // FK to non-PK column
|
|
2854
|
+
* email: r.referenceTo.users(t => ({
|
|
2855
|
+
* references: t.email, // Must be UNIQUE
|
|
2856
|
+
* onDelete: 'SET NULL',
|
|
2857
|
+
* }))
|
|
2858
|
+
* ```
|
|
2859
|
+
*/
|
|
2860
|
+
referenceTo: ReferenceToBuilder<TSchema>;
|
|
2861
|
+
}
|
|
2862
|
+
/** Full builder with table column refs for composite FKs */
|
|
2863
|
+
export type FullRelationBuilderWithRefs<TSchema extends Record<string, TableDefinition<any>>> = FullRelationBuilder<TSchema> & SchemaColumnRefs<TSchema>;
|
|
2864
|
+
/**
|
|
2865
|
+
* Define PostgreSQL foreign key relations between tables.
|
|
2866
|
+
*
|
|
2867
|
+
* Creates a type-safe relations object that mirrors PostgreSQL REFERENCES constraints
|
|
2868
|
+
* exactly, including ON DELETE, ON UPDATE, MATCH, and DEFERRABLE options.
|
|
2869
|
+
*
|
|
2870
|
+
* @param schema - The schema object containing all table definitions (use `as const`)
|
|
2871
|
+
* @param builder - Callback receiving the relation builder for each table
|
|
2872
|
+
* @returns The relations object with full FK metadata
|
|
2873
|
+
*
|
|
2874
|
+
* @example Basic foreign keys
|
|
2875
|
+
* ```typescript
|
|
2876
|
+
* export const schema = { users, posts, comments } as const;
|
|
2877
|
+
*
|
|
2878
|
+
* export const relations = pgRelations(schema, (r) => ({
|
|
2879
|
+
* posts: {
|
|
2880
|
+
* // posts.user_id REFERENCES users ON DELETE CASCADE
|
|
2881
|
+
* userId: r.referenceTo.users(t => ({
|
|
2882
|
+
* onDelete: 'CASCADE',
|
|
2883
|
+
* })),
|
|
2884
|
+
* },
|
|
2885
|
+
* comments: {
|
|
2886
|
+
* postId: r.referenceTo.posts(t => ({ onDelete: 'CASCADE' })),
|
|
2887
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'SET NULL' })),
|
|
2888
|
+
* },
|
|
2889
|
+
* }));
|
|
2890
|
+
* ```
|
|
2891
|
+
*
|
|
2892
|
+
* @example FK to non-primary key column
|
|
2893
|
+
* ```typescript
|
|
2894
|
+
* export const relations = pgRelations(schema, (r) => ({
|
|
2895
|
+
* orders: {
|
|
2896
|
+
* // orders.wallet_address REFERENCES wallets(address)
|
|
2897
|
+
* walletAddress: r.referenceTo.wallets(t => ({
|
|
2898
|
+
* references: t.address, // Not wallets.id, but wallets.address
|
|
2899
|
+
* onDelete: 'SET NULL',
|
|
2900
|
+
* })),
|
|
2901
|
+
* },
|
|
2902
|
+
* }));
|
|
2903
|
+
* ```
|
|
2904
|
+
*
|
|
2905
|
+
* @example Composite (multi-column) foreign key
|
|
2906
|
+
* ```typescript
|
|
2907
|
+
* export const relations = pgRelations(schema, (r) => ({
|
|
2908
|
+
* orderItems: {
|
|
2909
|
+
* // FOREIGN KEY (product_id, warehouse_id)
|
|
2910
|
+
* // REFERENCES inventory(product_id, warehouse_id)
|
|
2911
|
+
* productWarehouse: r.referenceTo.inventory(t => ({
|
|
2912
|
+
* columns: [r.orderItems.productId, r.orderItems.warehouseId],
|
|
2913
|
+
* references: [t.productId, t.warehouseId],
|
|
2914
|
+
* onDelete: 'CASCADE',
|
|
2915
|
+
* match: 'FULL',
|
|
2916
|
+
* })),
|
|
2917
|
+
* },
|
|
2918
|
+
* }));
|
|
2919
|
+
* ```
|
|
2920
|
+
*
|
|
2921
|
+
* @example Deferrable constraint (for circular references)
|
|
2922
|
+
* ```typescript
|
|
2923
|
+
* export const relations = pgRelations(schema, (r) => ({
|
|
2924
|
+
* employees: {
|
|
2925
|
+
* // Can defer constraint check to transaction commit
|
|
2926
|
+
* managerId: r.referenceTo.employees(t => ({
|
|
2927
|
+
* references: t.id,
|
|
2928
|
+
* onDelete: 'SET NULL',
|
|
2929
|
+
* deferrable: true,
|
|
2930
|
+
* initiallyDeferred: true,
|
|
2931
|
+
* })),
|
|
2932
|
+
* },
|
|
2933
|
+
* }));
|
|
2934
|
+
* ```
|
|
2935
|
+
*/
|
|
2936
|
+
export declare function pgRelations<TSchema extends Record<string, TableDefinition<any>>>(schema: TSchema, builder: (tables: {
|
|
2937
|
+
[K in keyof TSchema]: (defineRelations: (r: FullRelationBuilderWithRefs<TSchema>) => TableRelations) => TableRelations;
|
|
2938
|
+
}) => SchemaRelations): SchemaRelations;
|
|
2939
|
+
/**
|
|
2940
|
+
* Simplified pgRelations API - direct table → relations mapping.
|
|
2941
|
+
*
|
|
2942
|
+
* @example
|
|
2943
|
+
* ```typescript
|
|
2944
|
+
* export const relations = defineRelations(schema, {
|
|
2945
|
+
* posts: (r) => ({
|
|
2946
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'CASCADE' })),
|
|
2947
|
+
* }),
|
|
2948
|
+
* comments: (r) => ({
|
|
2949
|
+
* postId: r.referenceTo.posts(t => ({ onDelete: 'CASCADE' })),
|
|
2950
|
+
* userId: r.referenceTo.users(t => ({ onDelete: 'SET NULL' })),
|
|
2951
|
+
* }),
|
|
2952
|
+
* });
|
|
2953
|
+
* ```
|
|
2954
|
+
*/
|
|
2955
|
+
export declare function defineRelations<TSchema extends Record<string, TableDefinition<any>>, TRelations extends {
|
|
2956
|
+
[K in keyof TSchema]?: (r: FullRelationBuilderWithRefs<TSchema>) => TableRelations;
|
|
2957
|
+
}>(schema: TSchema, relationDefs: TRelations): SchemaRelations;
|
|
2958
|
+
/**
|
|
2959
|
+
* Helper type to derive DatabaseSchema type from a schema const object.
|
|
2960
|
+
*
|
|
2961
|
+
* Use this to create a type alias for your database schema, enabling
|
|
2962
|
+
* full type inference across your application.
|
|
2963
|
+
*
|
|
2964
|
+
* @example
|
|
2965
|
+
* ```typescript
|
|
2966
|
+
* // In db/schema.ts
|
|
2967
|
+
* export const schema = { users, posts, comments } as const;
|
|
2968
|
+
* export type DatabaseSchema = RelqDatabaseSchema<typeof schema>;
|
|
2969
|
+
*
|
|
2970
|
+
* // Now you can use DatabaseSchema for typing
|
|
2971
|
+
* function getUser(db: Relq<DatabaseSchema>, id: string) {
|
|
2972
|
+
* return db.table.users.select('*').where(q => q.equal('id', id)).one();
|
|
2973
|
+
* }
|
|
2974
|
+
* ```
|
|
2975
|
+
*/
|
|
2976
|
+
export type RelqDatabaseSchema<TSchema extends Record<string, TableDefinition<any>>> = {
|
|
2977
|
+
[K in keyof TSchema]: TSchema[K];
|
|
2978
|
+
};
|
|
2979
|
+
/**
|
|
2980
|
+
* Convert action code from pgsql-parser to ReferentialAction string.
|
|
2981
|
+
*
|
|
2982
|
+
* @internal Used by code generator
|
|
2983
|
+
*/
|
|
2984
|
+
export declare function actionCodeToString(code: string): ReferentialAction;
|
|
2985
|
+
/**
|
|
2986
|
+
* Convert ReferentialAction to action code for pgsql-parser.
|
|
2987
|
+
*
|
|
2988
|
+
* @internal Used for SQL generation
|
|
2989
|
+
*/
|
|
2990
|
+
export declare function stringToActionCode(action: ReferentialAction): string;
|
|
2991
|
+
/**
|
|
2992
|
+
* Convert match code from pgsql-parser to MatchType string.
|
|
2993
|
+
*
|
|
2994
|
+
* @internal Used by code generator
|
|
2995
|
+
*/
|
|
2996
|
+
export declare function matchCodeToString(code: string): MatchType;
|
|
2997
|
+
/**
|
|
2998
|
+
* Generate SQL REFERENCES clause from a ForeignKeyRelationDef.
|
|
2999
|
+
*
|
|
3000
|
+
* @example
|
|
3001
|
+
* ```typescript
|
|
3002
|
+
* const sql = generateReferencesSQL(relationDef, 'user_id');
|
|
3003
|
+
* // "user_id uuid REFERENCES users(id) ON DELETE CASCADE"
|
|
3004
|
+
* ```
|
|
3005
|
+
*/
|
|
3006
|
+
export declare function generateReferencesSQL(relation: ForeignKeyRelationDef, columnName: string, columnType?: string): string;
|
|
1773
3007
|
export interface PgEnumConfig<T extends readonly string[]> {
|
|
1774
3008
|
/** Enum type name in PostgreSQL */
|
|
1775
3009
|
$enumName: string;
|
|
@@ -2173,6 +3407,54 @@ export declare function dropSequenceSQL(seq: SequenceConfig): string;
|
|
|
2173
3407
|
* Type guard to check if a value is a SequenceConfig
|
|
2174
3408
|
*/
|
|
2175
3409
|
export declare function isSequenceConfig(value: unknown): value is SequenceConfig;
|
|
3410
|
+
/**
|
|
3411
|
+
* PostgreSQL View Definitions
|
|
3412
|
+
*
|
|
3413
|
+
* Provides typed helpers for defining views and materialized views.
|
|
3414
|
+
*/
|
|
3415
|
+
export interface ViewConfig {
|
|
3416
|
+
_type: "view";
|
|
3417
|
+
name: string;
|
|
3418
|
+
definition: string;
|
|
3419
|
+
isMaterialized: false;
|
|
3420
|
+
}
|
|
3421
|
+
export interface MaterializedViewConfig {
|
|
3422
|
+
_type: "materialized_view";
|
|
3423
|
+
name: string;
|
|
3424
|
+
definition: string;
|
|
3425
|
+
isMaterialized: true;
|
|
3426
|
+
withData?: boolean;
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Define a PostgreSQL view
|
|
3430
|
+
*
|
|
3431
|
+
* @example
|
|
3432
|
+
* export const activeUsers = pgView('active_users', `
|
|
3433
|
+
* SELECT id, name, email FROM users WHERE status = 'active'
|
|
3434
|
+
* `)
|
|
3435
|
+
*/
|
|
3436
|
+
export declare function pgView(name: string, definition: string): ViewConfig;
|
|
3437
|
+
/**
|
|
3438
|
+
* Define a PostgreSQL materialized view
|
|
3439
|
+
*
|
|
3440
|
+
* @example
|
|
3441
|
+
* export const userStats = pgMaterializedView('user_stats', `
|
|
3442
|
+
* SELECT user_id, COUNT(*) as order_count, SUM(total) as total_spent
|
|
3443
|
+
* FROM orders
|
|
3444
|
+
* GROUP BY user_id
|
|
3445
|
+
* `)
|
|
3446
|
+
*/
|
|
3447
|
+
export declare function pgMaterializedView(name: string, definition: string, options?: {
|
|
3448
|
+
withData?: boolean;
|
|
3449
|
+
}): MaterializedViewConfig;
|
|
3450
|
+
/**
|
|
3451
|
+
* Generate CREATE VIEW SQL
|
|
3452
|
+
*/
|
|
3453
|
+
export declare function viewToSQL(view: ViewConfig): string;
|
|
3454
|
+
/**
|
|
3455
|
+
* Generate CREATE MATERIALIZED VIEW SQL
|
|
3456
|
+
*/
|
|
3457
|
+
export declare function materializedViewToSQL(view: MaterializedViewConfig): string;
|
|
2176
3458
|
/**
|
|
2177
3459
|
* SQL Tagged Template Literal
|
|
2178
3460
|
*
|