relq 1.0.26 → 1.0.28

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.
@@ -1,4 +1,5 @@
1
1
  import { toCamelCase, escapeString, isBalanced, } from "./ast/codegen/utils.js";
2
+ import { format as formatSQL } from 'sql-formatter';
2
3
  import { astToBuilder } from "./ast/codegen/builder.js";
3
4
  import { getColumnBuilder, getColumnBuilderWithInfo } from "./ast/codegen/type-map.js";
4
5
  import { formatDefaultValue, resetDefaultImportFlags, getDefaultImportNeeded, getDefaultSqlImportNeeded, } from "./ast/codegen/defaults.js";
@@ -546,16 +547,17 @@ function generateSequenceCode(seq, useCamelCase) {
546
547
  const optsStr = opts.length > 0 ? `, { ${opts.join(', ')} }` : '';
547
548
  return `export const ${seqName} = pgSequence('${seq.name}'${optsStr})`;
548
549
  }
549
- function generateFunctionCode(func, useCamelCase) {
550
- const funcName = useCamelCase ? toCamelCase(func.name) : func.name;
550
+ function generateFunctionCode(func, useCamelCase, varNameOverride) {
551
+ const funcName = varNameOverride || (useCamelCase ? toCamelCase(func.name) : func.name);
551
552
  const parts = [];
552
553
  parts.push(`export const ${funcName} = pgFunction('${func.name}', {`);
553
554
  if (func.args.length > 0) {
554
- const argsStr = func.args.map(arg => {
555
- const argParts = [`type: '${arg.type}'`];
556
- if (arg.name)
557
- argParts.push(`name: '${arg.name}'`);
558
- if (arg.mode)
555
+ const argsStr = func.args.map((arg, index) => {
556
+ const argParts = [];
557
+ const argName = arg.name || `$${index + 1}`;
558
+ argParts.push(`name: '${argName}'`);
559
+ argParts.push(`type: '${arg.type}'`);
560
+ if (arg.mode && arg.mode !== 'IN')
559
561
  argParts.push(`mode: '${arg.mode}'`);
560
562
  if (arg.default)
561
563
  argParts.push(`default: '${escapeString(arg.default)}'`);
@@ -565,8 +567,26 @@ function generateFunctionCode(func, useCamelCase) {
565
567
  }
566
568
  parts.push(` returns: '${func.returnType}',`);
567
569
  parts.push(` language: '${func.language}',`);
568
- const escapedBody = func.body.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
569
- parts.push(` body: \`${escapedBody}\`,`);
570
+ let formattedBody = func.body;
571
+ const lang = func.language?.toLowerCase();
572
+ if (func.body && (lang === 'plpgsql' || lang === 'sql')) {
573
+ try {
574
+ formattedBody = formatSQL(func.body, {
575
+ language: 'postgresql',
576
+ tabWidth: 4,
577
+ keywordCase: 'upper',
578
+ });
579
+ }
580
+ catch {
581
+ formattedBody = func.body;
582
+ }
583
+ }
584
+ const escapedBody = formattedBody.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
585
+ const indentedBody = escapedBody
586
+ .split('\n')
587
+ .map(line => ' ' + line)
588
+ .join('\n');
589
+ parts.push(` body: \`\n${indentedBody}\n \`,`);
570
590
  if (func.volatility)
571
591
  parts.push(` volatility: '${func.volatility}',`);
572
592
  if (func.isStrict)
@@ -574,6 +594,9 @@ function generateFunctionCode(func, useCamelCase) {
574
594
  if (func.securityDefiner)
575
595
  parts.push(` securityDefiner: true,`);
576
596
  parts.push(`})`);
597
+ if (func.trackingId) {
598
+ parts[parts.length - 1] = parts[parts.length - 1].replace('})', `}).$id('${func.trackingId}')`);
599
+ }
577
600
  return parts.join('\n');
578
601
  }
579
602
  function generateViewCode(view, useCamelCase) {
@@ -1033,3 +1056,134 @@ function generateFKSQLComment(fk, camelCase) {
1033
1056
  }
1034
1057
  return parts.join(' | ');
1035
1058
  }
1059
+ function resolveFunctionVarName(func, usedNames, useCamelCase) {
1060
+ const baseName = useCamelCase ? toCamelCase(func.name) : func.name;
1061
+ const count = usedNames.get(baseName) || 0;
1062
+ usedNames.set(baseName, count + 1);
1063
+ if (count === 0) {
1064
+ return baseName;
1065
+ }
1066
+ return `${baseName}_${count + 1}`;
1067
+ }
1068
+ export function generateFunctionsFile(schema, options = {}) {
1069
+ const { camelCase = true, importPath = 'relq/schema-builder', schemaImportPath = './schema', } = options;
1070
+ if (schema.functions.length === 0) {
1071
+ return null;
1072
+ }
1073
+ const parts = [];
1074
+ parts.push('/**');
1075
+ parts.push(' * Auto-generated by Relq CLI');
1076
+ parts.push(` * Generated at: ${new Date().toISOString()}`);
1077
+ parts.push(' * DO NOT EDIT - changes will be overwritten');
1078
+ parts.push(' */');
1079
+ parts.push('');
1080
+ parts.push(`import { pgFunction } from '${importPath}';`);
1081
+ void schemaImportPath;
1082
+ parts.push('');
1083
+ const usedNames = new Map();
1084
+ const functionVarNames = [];
1085
+ parts.push('// =============================================================================');
1086
+ parts.push('// FUNCTIONS');
1087
+ parts.push('// =============================================================================');
1088
+ parts.push('');
1089
+ for (const func of schema.functions) {
1090
+ const varName = resolveFunctionVarName(func, usedNames, camelCase);
1091
+ functionVarNames.push(varName);
1092
+ parts.push(generateFunctionCode(func, camelCase, varName));
1093
+ parts.push('');
1094
+ }
1095
+ parts.push('// =============================================================================');
1096
+ parts.push('// EXPORTS');
1097
+ parts.push('// =============================================================================');
1098
+ parts.push('');
1099
+ parts.push('export const functions = {');
1100
+ for (const name of functionVarNames) {
1101
+ parts.push(` ${name},`);
1102
+ }
1103
+ parts.push('} as const;');
1104
+ parts.push('');
1105
+ return parts.join('\n');
1106
+ }
1107
+ export function generateTriggersFile(schema, options = {}) {
1108
+ const { camelCase = true, importPath = 'relq/schema-builder', schemaImportPath = './schema', functionsImportPath = './schema.functions', } = options;
1109
+ if (schema.triggers.length === 0) {
1110
+ return null;
1111
+ }
1112
+ const parts = [];
1113
+ parts.push('/**');
1114
+ parts.push(' * Auto-generated by Relq CLI');
1115
+ parts.push(` * Generated at: ${new Date().toISOString()}`);
1116
+ parts.push(' * DO NOT EDIT - changes will be overwritten');
1117
+ parts.push(' */');
1118
+ parts.push('');
1119
+ parts.push(`import { pgTrigger } from '${importPath}';`);
1120
+ parts.push(`import { schema } from '${schemaImportPath}';`);
1121
+ if (schema.functions.length > 0) {
1122
+ parts.push(`import { functions } from '${functionsImportPath}';`);
1123
+ }
1124
+ parts.push('');
1125
+ const functionNames = new Set();
1126
+ const usedFuncNames = new Map();
1127
+ for (const func of schema.functions) {
1128
+ const varName = resolveFunctionVarName(func, usedFuncNames, camelCase);
1129
+ functionNames.add(varName);
1130
+ }
1131
+ parts.push('// =============================================================================');
1132
+ parts.push('// TRIGGERS');
1133
+ parts.push('// =============================================================================');
1134
+ parts.push('');
1135
+ const triggerVarNames = [];
1136
+ for (const trigger of schema.triggers) {
1137
+ const triggerName = camelCase ? toCamelCase(trigger.name) : trigger.name;
1138
+ const tableName = camelCase ? toCamelCase(trigger.table) : trigger.table;
1139
+ triggerVarNames.push(triggerName);
1140
+ parts.push(`export const ${triggerName} = pgTrigger('${trigger.name}', {`);
1141
+ parts.push(` on: schema.${tableName},`);
1142
+ const events = trigger.events.length === 1
1143
+ ? `'${trigger.events[0]}'`
1144
+ : `[${trigger.events.map(e => `'${e}'`).join(', ')}]`;
1145
+ if (trigger.timing === 'BEFORE') {
1146
+ parts.push(` before: ${events},`);
1147
+ }
1148
+ else if (trigger.timing === 'AFTER') {
1149
+ parts.push(` after: ${events},`);
1150
+ }
1151
+ else if (trigger.timing === 'INSTEAD OF') {
1152
+ parts.push(` insteadOf: ${events},`);
1153
+ }
1154
+ parts.push(` forEach: '${trigger.forEach || 'ROW'}',`);
1155
+ const funcVarName = camelCase ? toCamelCase(trigger.functionName) : trigger.functionName;
1156
+ if (functionNames.has(funcVarName)) {
1157
+ parts.push(` execute: functions.${funcVarName},`);
1158
+ }
1159
+ else {
1160
+ parts.push(` execute: '${trigger.functionName}',`);
1161
+ }
1162
+ if (trigger.whenClause)
1163
+ parts.push(` when: '${escapeString(trigger.whenClause)}',`);
1164
+ if (trigger.isConstraint)
1165
+ parts.push(` constraint: true,`);
1166
+ if (trigger.deferrable)
1167
+ parts.push(` deferrable: true,`);
1168
+ if (trigger.initiallyDeferred)
1169
+ parts.push(` initially: 'DEFERRED',`);
1170
+ if (trigger.trackingId) {
1171
+ parts.push(`}).$id('${trigger.trackingId}');`);
1172
+ }
1173
+ else {
1174
+ parts.push(`});`);
1175
+ }
1176
+ parts.push('');
1177
+ }
1178
+ parts.push('// =============================================================================');
1179
+ parts.push('// EXPORTS');
1180
+ parts.push('// =============================================================================');
1181
+ parts.push('');
1182
+ parts.push('export const triggers = {');
1183
+ for (const name of triggerVarNames) {
1184
+ parts.push(` ${name},`);
1185
+ }
1186
+ parts.push('} as const;');
1187
+ parts.push('');
1188
+ return parts.join('\n');
1189
+ }
@@ -698,7 +698,8 @@ export async function introspectedToParsedSchema(schema) {
698
698
  args: parseArgTypes(f.argTypes),
699
699
  returnType: f.returnType,
700
700
  language: f.language,
701
- body: f.definition || '',
701
+ body: extractFunctionBody(f.definition || ''),
702
+ volatility: f.volatility,
702
703
  isStrict: false,
703
704
  securityDefiner: false,
704
705
  });
@@ -709,7 +710,7 @@ export async function introspectedToParsedSchema(schema) {
709
710
  table: t.tableName,
710
711
  timing: t.timing,
711
712
  events: [t.event],
712
- forEach: 'STATEMENT',
713
+ forEach: t.forEach || 'ROW',
713
714
  functionName: t.functionName || '',
714
715
  isConstraint: false,
715
716
  });
@@ -799,6 +800,19 @@ function parseArgTypes(argTypes) {
799
800
  type: typeof arg === 'string' ? arg.trim() : String(arg),
800
801
  }));
801
802
  }
803
+ function extractFunctionBody(definition) {
804
+ if (!definition)
805
+ return '';
806
+ const bodyMatch = definition.match(/AS\s+\$([a-zA-Z_]*)\$\s*([\s\S]*?)\s*\$\1\$/i);
807
+ if (bodyMatch) {
808
+ return bodyMatch[2].trim();
809
+ }
810
+ const singleQuoteMatch = definition.match(/AS\s+'([\s\S]*?)'\s*$/i);
811
+ if (singleQuoteMatch) {
812
+ return singleQuoteMatch[1].replace(/''/g, "'");
813
+ }
814
+ return '';
815
+ }
802
816
  export { parse, deparse };
803
817
  export function normalizedToParsedSchema(schema) {
804
818
  return {
@@ -417,6 +417,10 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
417
417
  JOIN pg_language l ON p.prolang = l.oid
418
418
  WHERE n.nspname = 'public'
419
419
  AND p.prokind IN ('f', 'a')
420
+ -- Exclude C language functions (typically extension functions like pgcrypto)
421
+ AND l.lanname != 'c'
422
+ -- Exclude internal language functions
423
+ AND l.lanname != 'internal'
420
424
  ORDER BY p.proname;
421
425
  `);
422
426
  functions = functionsResult.rows.map(f => ({
@@ -450,6 +454,10 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
450
454
  WHEN t.tgtype & 16 > 0 THEN 'UPDATE'
451
455
  ELSE 'UNKNOWN'
452
456
  END as event,
457
+ CASE
458
+ WHEN t.tgtype & 1 > 0 THEN 'ROW'
459
+ ELSE 'STATEMENT'
460
+ END as for_each,
453
461
  p.proname as function_name,
454
462
  pg_get_triggerdef(t.oid) as definition,
455
463
  t.tgenabled != 'D' as is_enabled
@@ -459,6 +467,11 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
459
467
  JOIN pg_proc p ON t.tgfoid = p.oid
460
468
  WHERE n.nspname = 'public'
461
469
  AND NOT t.tgisinternal
470
+ -- Exclude triggers on partition child tables
471
+ AND NOT EXISTS (
472
+ SELECT 1 FROM pg_inherits i
473
+ WHERE i.inhrelid = c.oid
474
+ )
462
475
  ORDER BY c.relname, t.tgname;
463
476
  `);
464
477
  triggers = triggersResult.rows.map(t => ({
@@ -466,6 +479,7 @@ export async function fastIntrospectDatabase(connection, onProgress, options) {
466
479
  tableName: t.table_name,
467
480
  timing: t.timing,
468
481
  event: t.event,
482
+ forEach: t.for_each,
469
483
  functionName: t.function_name,
470
484
  definition: t.definition || '',
471
485
  isEnabled: t.is_enabled,
@@ -84,6 +84,30 @@ function compareDomains(before, after) {
84
84
  changes.push(createChange('DROP', 'DOMAIN', name, domain, null));
85
85
  }
86
86
  }
87
+ for (const [name, afterDomain] of afterMap) {
88
+ const beforeDomain = beforeMap.get(name);
89
+ if (!beforeDomain)
90
+ continue;
91
+ const baseTypeChanged = beforeDomain.baseType !== afterDomain.baseType;
92
+ const notNullChanged = (beforeDomain.isNotNull || false) !== (afterDomain.isNotNull || false);
93
+ const defaultChanged = (beforeDomain.defaultValue || null) !== (afterDomain.defaultValue || null);
94
+ const checkChanged = (beforeDomain.checkExpression || null) !== (afterDomain.checkExpression || null);
95
+ if (baseTypeChanged || notNullChanged || defaultChanged || checkChanged) {
96
+ changes.push(createChange('ALTER', 'DOMAIN', name, {
97
+ name: beforeDomain.name,
98
+ baseType: beforeDomain.baseType,
99
+ notNull: beforeDomain.isNotNull,
100
+ default: beforeDomain.defaultValue,
101
+ check: beforeDomain.checkExpression,
102
+ }, {
103
+ name: afterDomain.name,
104
+ baseType: afterDomain.baseType,
105
+ notNull: afterDomain.isNotNull,
106
+ default: afterDomain.defaultValue,
107
+ check: afterDomain.checkExpression,
108
+ }));
109
+ }
110
+ }
87
111
  return changes;
88
112
  }
89
113
  function compareCompositeTypes(before, after) {
@@ -249,7 +249,7 @@ export async function introspectDatabase(connection, onProgress, options) {
249
249
  if (includeFunctions) {
250
250
  onProgress?.('fetching_functions');
251
251
  const functionsResult = await pool.query(`
252
- SELECT
252
+ SELECT
253
253
  p.proname as name,
254
254
  n.nspname as schema,
255
255
  pg_get_function_result(p.oid) as return_type,
@@ -263,6 +263,17 @@ export async function introspectDatabase(connection, onProgress, options) {
263
263
  JOIN pg_language l ON p.prolang = l.oid
264
264
  WHERE n.nspname = 'public'
265
265
  AND p.prokind IN ('f', 'a')
266
+ -- Exclude functions that belong to extensions (e.g., pgcrypto, uuid-ossp)
267
+ AND NOT EXISTS (
268
+ SELECT 1 FROM pg_depend d
269
+ JOIN pg_extension e ON d.refobjid = e.oid
270
+ WHERE d.objid = p.oid
271
+ AND d.deptype = 'e'
272
+ )
273
+ -- Exclude C language functions (typically extension functions)
274
+ AND l.lanname != 'c'
275
+ -- Exclude internal language functions
276
+ AND l.lanname != 'internal'
266
277
  ORDER BY p.proname;
267
278
  `);
268
279
  functions = functionsResult.rows.map(f => ({
@@ -279,20 +290,24 @@ export async function introspectDatabase(connection, onProgress, options) {
279
290
  let triggers = [];
280
291
  if (includeTriggers) {
281
292
  const triggersResult = await pool.query(`
282
- SELECT
293
+ SELECT
283
294
  t.tgname as name,
284
295
  c.relname as table_name,
285
- CASE
296
+ CASE
286
297
  WHEN t.tgtype & 2 > 0 THEN 'BEFORE'
287
298
  WHEN t.tgtype & 64 > 0 THEN 'INSTEAD OF'
288
299
  ELSE 'AFTER'
289
300
  END as timing,
290
- CASE
301
+ CASE
291
302
  WHEN t.tgtype & 4 > 0 THEN 'INSERT'
292
303
  WHEN t.tgtype & 8 > 0 THEN 'DELETE'
293
304
  WHEN t.tgtype & 16 > 0 THEN 'UPDATE'
294
305
  ELSE 'UNKNOWN'
295
306
  END as event,
307
+ CASE
308
+ WHEN t.tgtype & 1 > 0 THEN 'ROW'
309
+ ELSE 'STATEMENT'
310
+ END as for_each,
296
311
  p.proname as function_name,
297
312
  pg_get_triggerdef(t.oid) as definition,
298
313
  t.tgenabled != 'D' as is_enabled
@@ -302,6 +317,11 @@ export async function introspectDatabase(connection, onProgress, options) {
302
317
  JOIN pg_proc p ON t.tgfoid = p.oid
303
318
  WHERE n.nspname = 'public'
304
319
  AND NOT t.tgisinternal
320
+ -- Exclude triggers on partition child tables (only show on parent)
321
+ AND NOT EXISTS (
322
+ SELECT 1 FROM pg_inherits i
323
+ WHERE i.inhrelid = c.oid
324
+ )
305
325
  ORDER BY c.relname, t.tgname;
306
326
  `);
307
327
  triggers = triggersResult.rows.map(t => ({
@@ -309,6 +329,7 @@ export async function introspectDatabase(connection, onProgress, options) {
309
329
  tableName: t.table_name,
310
330
  timing: t.timing,
311
331
  event: t.event,
332
+ forEach: t.for_each,
312
333
  functionName: t.function_name,
313
334
  definition: t.definition || '',
314
335
  isEnabled: t.is_enabled,
@@ -861,7 +861,9 @@ export function parseTriggers(sql) {
861
861
  const tableMatch = body.match(/\bON\s+(\w+)/i);
862
862
  const tableName = tableMatch ? tableMatch[1] : '';
863
863
  const forEachMatch = body.match(/FOR\s+EACH\s+(ROW|STATEMENT)/i);
864
- const forEach = forEachMatch ? forEachMatch[1].toUpperCase() : 'STATEMENT';
864
+ const forEach = forEachMatch
865
+ ? forEachMatch[1].toUpperCase()
866
+ : 'ROW';
865
867
  const whenMatch = body.match(/WHEN\s*\((.+?)\)\s*(?:EXECUTE|$)/is);
866
868
  const condition = whenMatch ? whenMatch[1].trim() : undefined;
867
869
  const refOldMatch = body.match(/REFERENCING\s+.*?OLD\s+TABLE\s+AS\s+(\w+)/i);
@@ -877,6 +879,7 @@ export function parseTriggers(sql) {
877
879
  tableName,
878
880
  timing,
879
881
  event: events[0] || 'INSERT',
882
+ forEach,
880
883
  functionName,
881
884
  definition: match[0],
882
885
  isEnabled: true,
@@ -191,5 +191,8 @@ export function validateConfig(config) {
191
191
  errors.push('Pool max must be at least 1');
192
192
  }
193
193
  }
194
+ if (config.includeTriggers && !config.includeFunctions) {
195
+ errors.push('Triggers require functions to be enabled. Set includeFunctions: true in your config.');
196
+ }
194
197
  return { valid: errors.length === 0, errors };
195
198
  }
@@ -94,6 +94,10 @@ export function pgFunction(name, options) {
94
94
  trackingId: this.$trackingId,
95
95
  };
96
96
  },
97
+ $id(trackingId) {
98
+ this.$trackingId = trackingId;
99
+ return this;
100
+ },
97
101
  };
98
102
  }
99
103
  export function isFunctionConfig(value) {
@@ -20,7 +20,7 @@ function getFunctionName(func) {
20
20
  }
21
21
  export function generateTriggerSQL(config) {
22
22
  const { $triggerName, $options } = config;
23
- const { on, before, after, insteadOf, updateOf, forEachRow = true, forEachStatement, when, referencing, constraint, deferrable, initially, execute, executeArgs, } = $options;
23
+ const { on, before, after, insteadOf, updateOf, forEach = 'ROW', when, referencing, constraint, deferrable, initially, execute, executeArgs, } = $options;
24
24
  const parts = ['CREATE'];
25
25
  if (constraint) {
26
26
  parts.push('CONSTRAINT');
@@ -68,10 +68,10 @@ export function generateTriggerSQL(config) {
68
68
  }
69
69
  parts.push(refParts.join(' '));
70
70
  }
71
- if (forEachStatement) {
71
+ if (forEach === 'STATEMENT') {
72
72
  parts.push('\n FOR EACH STATEMENT');
73
73
  }
74
- else if (forEachRow !== false) {
74
+ else {
75
75
  parts.push('\n FOR EACH ROW');
76
76
  }
77
77
  if (when) {
@@ -113,7 +113,7 @@ export function pgTrigger(name, options) {
113
113
  timing = 'BEFORE';
114
114
  events = ['INSERT'];
115
115
  }
116
- const forEach = opts.forEachStatement ? 'STATEMENT' : 'ROW';
116
+ const forEachLevel = opts.forEach || 'ROW';
117
117
  const functionName = typeof opts.execute === 'string'
118
118
  ? opts.execute
119
119
  : opts.execute.$functionName;
@@ -122,7 +122,7 @@ export function pgTrigger(name, options) {
122
122
  table: getTableName(opts.on),
123
123
  timing,
124
124
  events,
125
- forEach,
125
+ forEach: forEachLevel,
126
126
  functionName,
127
127
  whenClause: opts.when,
128
128
  isConstraint: opts.constraint ?? false,
@@ -131,6 +131,10 @@ export function pgTrigger(name, options) {
131
131
  trackingId: this.$trackingId,
132
132
  };
133
133
  },
134
+ $id(trackingId) {
135
+ this.$trackingId = trackingId;
136
+ return this;
137
+ },
134
138
  };
135
139
  }
136
140
  export function isTriggerConfig(value) {
@@ -3258,6 +3258,10 @@ export declare function addEnumValueSQL(enumName: string, newValue: string, posi
3258
3258
  export type FunctionVolatility = "VOLATILE" | "STABLE" | "IMMUTABLE";
3259
3259
  export type FunctionParallel = "UNSAFE" | "RESTRICTED" | "SAFE";
3260
3260
  export type FunctionSecurity = "INVOKER" | "DEFINER";
3261
+ /** Common PostgreSQL function languages */
3262
+ export type FunctionLanguage = "plpgsql" | "sql" | "plpython3u" | "plperl" | "pltcl" | (string & {});
3263
+ /** Common PostgreSQL return types with autocomplete support */
3264
+ export type FunctionReturnType = "trigger" | "void" | "integer" | "bigint" | "smallint" | "numeric" | "real" | "double precision" | "text" | "varchar" | "char" | "name" | "boolean" | "timestamp" | "timestamptz" | "timestamp with time zone" | "timestamp without time zone" | "date" | "time" | "timetz" | "interval" | "bytea" | "uuid" | "json" | "jsonb" | "text[]" | "integer[]" | "uuid[]" | "jsonb[]" | "record" | "SETOF record" | (string & {});
3261
3265
  export interface FunctionArgument {
3262
3266
  /** Argument name */
3263
3267
  name: string;
@@ -3271,10 +3275,10 @@ export interface FunctionArgument {
3271
3275
  export interface FunctionOptions {
3272
3276
  /** Function arguments */
3273
3277
  args?: FunctionArgument[];
3274
- /** Return type (e.g., 'INTEGER', 'TRIGGER', 'SETOF UUID', 'TABLE(...)') */
3275
- returns: string;
3278
+ /** Return type (e.g., 'integer', 'trigger', 'SETOF uuid', 'TABLE(...)') */
3279
+ returns: FunctionReturnType;
3276
3280
  /** Language (default: 'plpgsql') */
3277
- language?: string;
3281
+ language?: FunctionLanguage;
3278
3282
  /** Volatility category */
3279
3283
  volatility?: FunctionVolatility;
3280
3284
  /** Parallel safety */
@@ -3313,6 +3317,8 @@ export interface FunctionConfig {
3313
3317
  $trackingId?: string;
3314
3318
  /** Returns AST for schema diffing and migration generation */
3315
3319
  toAST(): ParsedFunction;
3320
+ /** Set tracking ID for rename detection */
3321
+ $id(trackingId: string): FunctionConfig;
3316
3322
  }
3317
3323
  /**
3318
3324
  * Generate SQL for a function definition
@@ -3408,13 +3414,9 @@ export interface TriggerOptions<TTable = unknown> {
3408
3414
  */
3409
3415
  updateOf?: string[];
3410
3416
  /**
3411
- * FOR EACH ROW (default: true for row-level trigger)
3412
- */
3413
- forEachRow?: boolean;
3414
- /**
3415
- * FOR EACH STATEMENT
3417
+ * FOR EACH ROW or FOR EACH STATEMENT (default: 'ROW')
3416
3418
  */
3417
- forEachStatement?: boolean;
3419
+ forEach?: TriggerLevel;
3418
3420
  /**
3419
3421
  * WHEN condition (SQL expression)
3420
3422
  * Can reference OLD and NEW
@@ -3459,6 +3461,8 @@ export interface TriggerConfig<TTable = unknown> {
3459
3461
  $trackingId?: string;
3460
3462
  /** Returns AST for schema diffing and migration generation */
3461
3463
  toAST(): ParsedTrigger;
3464
+ /** Set tracking ID for rename detection */
3465
+ $id(trackingId: string): TriggerConfig<TTable>;
3462
3466
  }
3463
3467
  /**
3464
3468
  * Generate SQL for a trigger definition
@@ -3477,7 +3481,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3477
3481
  * export const trgUpdateTimestamp = pgTrigger('trg_update_timestamp', {
3478
3482
  * on: usersTable,
3479
3483
  * before: 'UPDATE',
3480
- * forEachRow: true,
3484
+ * forEach: 'ROW',
3481
3485
  * execute: updateTimestampFunc
3482
3486
  * });
3483
3487
  *
@@ -3485,7 +3489,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3485
3489
  * export const trgAudit = pgTrigger('trg_audit', {
3486
3490
  * on: ordersTable,
3487
3491
  * after: ['INSERT', 'UPDATE', 'DELETE'],
3488
- * forEachRow: true,
3492
+ * forEach: 'ROW',
3489
3493
  * execute: auditChangesFunc
3490
3494
  * });
3491
3495
  *
@@ -3494,7 +3498,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3494
3498
  * on: usersTable,
3495
3499
  * after: 'UPDATE',
3496
3500
  * updateOf: ['status'],
3497
- * forEachRow: true,
3501
+ * forEach: 'ROW',
3498
3502
  * when: 'OLD.status IS DISTINCT FROM NEW.status',
3499
3503
  * execute: notifyStatusChangeFunc
3500
3504
  * });
@@ -3503,7 +3507,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3503
3507
  * export const trgBulkUpdate = pgTrigger('trg_bulk_update', {
3504
3508
  * on: productsTable,
3505
3509
  * after: 'UPDATE',
3506
- * forEachStatement: true,
3510
+ * forEach: 'STATEMENT',
3507
3511
  * referencing: {
3508
3512
  * oldTable: 'old_products',
3509
3513
  * newTable: 'new_products'
@@ -3515,7 +3519,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3515
3519
  * export const trgViewInsert = pgTrigger('trg_view_insert', {
3516
3520
  * on: 'users_view',
3517
3521
  * insteadOf: 'INSERT',
3518
- * forEachRow: true,
3522
+ * forEach: 'ROW',
3519
3523
  * execute: handleViewInsertFunc
3520
3524
  * });
3521
3525
  *
@@ -3526,7 +3530,7 @@ export declare function dropTriggerSQL(config: TriggerConfig, ifExists?: boolean
3526
3530
  * constraint: true,
3527
3531
  * deferrable: true,
3528
3532
  * initially: 'DEFERRED',
3529
- * forEachRow: true,
3533
+ * forEach: 'ROW',
3530
3534
  * execute: validateOrderFunc
3531
3535
  * });
3532
3536
  * ```
@@ -3696,8 +3700,8 @@ export declare namespace sql {
3696
3700
  __raw: string;
3697
3701
  };
3698
3702
  }
3699
- export type FunctionReturnType = "trigger" | "void" | "integer" | "bigint" | "smallint" | "real" | "double precision" | "numeric" | "text" | "varchar" | "char" | "boolean" | "json" | "jsonb" | "uuid" | "timestamp" | "timestamptz" | "date" | "time" | "timetz" | "interval" | "bytea" | "record" | "setof record" | (string & {});
3700
- export type FunctionLanguage = "plpgsql" | "sql" | "plv8" | "plpython3u" | "plperl" | (string & {});
3703
+ type FunctionReturnType$1 = "trigger" | "void" | "integer" | "bigint" | "smallint" | "real" | "double precision" | "numeric" | "text" | "varchar" | "char" | "boolean" | "json" | "jsonb" | "uuid" | "timestamp" | "timestamptz" | "date" | "time" | "timetz" | "interval" | "bytea" | "record" | "setof record" | (string & {});
3704
+ type FunctionLanguage$1 = "plpgsql" | "sql" | "plv8" | "plpython3u" | "plperl" | (string & {});
3701
3705
  export type FunctionType = "function" | "procedure";
3702
3706
  type FunctionVolatility$1 = "volatile" | "stable" | "immutable";
3703
3707
  type FunctionSecurity$1 = "invoker" | "definer";
@@ -3711,9 +3715,9 @@ interface FunctionOptions$1 {
3711
3715
  /** Function name */
3712
3716
  name: string;
3713
3717
  /** Return type with autocomplete */
3714
- returns: FunctionReturnType;
3718
+ returns: FunctionReturnType$1;
3715
3719
  /** Language with autocomplete */
3716
- language: FunctionLanguage;
3720
+ language: FunctionLanguage$1;
3717
3721
  /** Function or procedure */
3718
3722
  as?: FunctionType;
3719
3723
  /** Function parameters */
@@ -3754,7 +3758,9 @@ export interface FunctionDefinition {
3754
3758
  export declare function createFunction(options: FunctionOptions$1): FunctionDefinition;
3755
3759
 
3756
3760
  export {
3761
+ FunctionLanguage$1 as FunctionLanguage,
3757
3762
  FunctionOptions$1 as FunctionOptions,
3763
+ FunctionReturnType$1 as FunctionReturnType,
3758
3764
  FunctionSecurity$1 as FunctionSecurity,
3759
3765
  FunctionVolatility$1 as FunctionVolatility,
3760
3766
  ParsedColumn$1 as ParsedColumn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",