relq 1.0.5 → 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.
Files changed (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +149 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +146 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. package/package.json +1 -1
@@ -5,6 +5,8 @@ const EXCLUDED_FUNCTIONS = [
5
5
  'encrypt_iv'
6
6
  ];
7
7
  function toCamelCase(str) {
8
+ if (!str)
9
+ return 'unknown';
8
10
  const cleaned = str.replace(/^[_0-9]+/, '');
9
11
  return cleaned
10
12
  .split('_')
@@ -12,6 +14,8 @@ function toCamelCase(str) {
12
14
  .join('');
13
15
  }
14
16
  function toPascalCase(str) {
17
+ if (!str)
18
+ return 'Unknown';
15
19
  const cleaned = str.replace(/^[_0-9]+/, '');
16
20
  return cleaned
17
21
  .split('_')
@@ -19,6 +23,8 @@ function toPascalCase(str) {
19
23
  .join('');
20
24
  }
21
25
  function inferTsTypeFromSqlType(sqlType) {
26
+ if (!sqlType)
27
+ return 'string';
22
28
  const normalized = sqlType.toLowerCase().replace(/\([^)]*\)/g, '').trim();
23
29
  if (['integer', 'int', 'int4', 'smallint', 'int2', 'bigint', 'int8',
24
30
  'numeric', 'decimal', 'real', 'float4', 'float8', 'double precision',
@@ -40,6 +46,8 @@ function inferTsTypeFromSqlType(sqlType) {
40
46
  return 'string';
41
47
  }
42
48
  function generateColumnBuilderFromType(sqlType) {
49
+ if (!sqlType)
50
+ return 'text()';
43
51
  const normalized = sqlType.toLowerCase().trim();
44
52
  const paramMatch = normalized.match(/^([a-z_][a-z0-9_\s]*?)(?:\s*\(([^)]+)\))?$/i);
45
53
  if (!paramMatch)
@@ -257,6 +265,10 @@ function splitCheckByAnd(expr) {
257
265
  return parts;
258
266
  }
259
267
  function getColumnType(col, enumNames, domainNames, compositeNames) {
268
+ if (!col.dataType) {
269
+ console.warn(`Warning: Column ${col.name} has no dataType, defaulting to text`);
270
+ return 'text()';
271
+ }
260
272
  let pgType = col.dataType.toLowerCase();
261
273
  const isArray = pgType.startsWith('_') || pgType.endsWith('[]');
262
274
  if (pgType.startsWith('_')) {
@@ -414,12 +426,11 @@ function parseGeneratedExpression(expr, camelCase) {
414
426
  const colName = camelCase ? toCamelCase(expr) : expr;
415
427
  return `table.${colName}`;
416
428
  }
417
- const colMatch = expr.match(/[a-z_][a-z0-9_]*/i);
418
- if (colMatch) {
419
- const colName = camelCase ? toCamelCase(colMatch[0]) : colMatch[0];
420
- return `table.${colName}`;
421
- }
422
- return `table.id /* TODO: Fix expression: ${expr.replace(/\*\//g, '* /')} */`;
429
+ const escapedExpr = expr
430
+ .replace(/\\/g, '\\\\')
431
+ .replace(/`/g, '\\`')
432
+ .replace(/\$\{/g, '\\${');
433
+ return `sql\`${escapedExpr}\``;
423
434
  }
424
435
  function parseArithmeticExpression(expr, camelCase) {
425
436
  const tokens = [];
@@ -664,6 +675,13 @@ function formatDefaultValue(val, col, domainMap) {
664
675
  return 'true';
665
676
  if (cleaned.toLowerCase() === 'false')
666
677
  return 'false';
678
+ if (!col.dataType) {
679
+ if (cleaned.startsWith("'") && cleaned.endsWith("'")) {
680
+ const str = cleaned.slice(1, -1).replace(/'/g, "\\'");
681
+ return `'${str}'`;
682
+ }
683
+ return null;
684
+ }
667
685
  let baseDataType = col.dataType.toLowerCase().replace(/\([^)]*\)$/, '').trim();
668
686
  if (domainMap && domainMap.has(baseDataType)) {
669
687
  baseDataType = domainMap.get(baseDataType).toLowerCase().replace(/\([^)]*\)$/, '').trim();
@@ -675,7 +693,7 @@ function formatDefaultValue(val, col, domainMap) {
675
693
  return `'${cleaned}'`;
676
694
  }
677
695
  else if (isBigInt) {
678
- return `BigInt(${cleaned})`;
696
+ return `${cleaned}n`;
679
697
  }
680
698
  return cleaned;
681
699
  }
@@ -752,7 +770,7 @@ function formatDefaultValue(val, col, domainMap) {
752
770
  }
753
771
  return null;
754
772
  }
755
- function generateColumnDef(col, seenColumns, camelCase = true, checkValues, enumNames, domainNames, compositeNames, domainMap) {
773
+ function generateColumnDef(col, seenColumns, camelCase = true, checkInfo, enumNames, domainNames, compositeNames, domainMap) {
756
774
  if (seenColumns.has(col.name)) {
757
775
  return null;
758
776
  }
@@ -774,9 +792,9 @@ function generateColumnDef(col, seenColumns, camelCase = true, checkValues, enum
774
792
  typeCall = colType;
775
793
  }
776
794
  const parts = [typeCall];
777
- if (checkValues && checkValues.length > 0) {
778
- const quotedValues = checkValues.map(v => `'${v}'`).join(', ');
779
- parts.push(`.check(${quotedValues})`);
795
+ if (checkInfo && checkInfo.values.length > 0) {
796
+ const quotedValues = checkInfo.values.map(v => `'${v}'`).join(', ');
797
+ parts.push(`.check('${checkInfo.name}', [${quotedValues}])`);
780
798
  }
781
799
  if (col.isPrimaryKey) {
782
800
  parts.push('.primaryKey()');
@@ -1554,6 +1572,36 @@ function generateCheckConstraints(constraints, camelCase) {
1554
1572
  function generateDefineTable(table, camelCase = true, childPartitions, enumNames, domainNames, compositeNames, domainMap) {
1555
1573
  const tableName = toCamelCase(table.name);
1556
1574
  const seenColumns = new Set();
1575
+ const pkColumns = new Set();
1576
+ const compositePKs = [];
1577
+ const pkConstraints = (table.constraints || []).filter(c => c.type === 'PRIMARY KEY');
1578
+ for (const pk of pkConstraints) {
1579
+ const match = pk.definition.match(/PRIMARY\s+KEY\s*\(([^)]+)\)/i);
1580
+ if (match) {
1581
+ const cols = match[1].split(',').map(c => c.trim().replace(/"/g, ''));
1582
+ if (cols.length === 1) {
1583
+ pkColumns.add(cols[0]);
1584
+ }
1585
+ else if (cols.length > 1) {
1586
+ compositePKs.push({ name: pk.name, columns: cols });
1587
+ }
1588
+ }
1589
+ }
1590
+ const uniqueColumns = new Set();
1591
+ const compositeUniques = [];
1592
+ const uniqueConstraints = (table.constraints || []).filter(c => c.type === 'UNIQUE');
1593
+ for (const uq of uniqueConstraints) {
1594
+ const match = uq.definition.match(/UNIQUE\s*\(([^)]+)\)/i);
1595
+ if (match) {
1596
+ const cols = match[1].split(',').map(c => c.trim().replace(/"/g, ''));
1597
+ if (cols.length === 1) {
1598
+ uniqueColumns.add(cols[0]);
1599
+ }
1600
+ else if (cols.length > 1) {
1601
+ compositeUniques.push({ name: uq.name, columns: cols });
1602
+ }
1603
+ }
1604
+ }
1557
1605
  const checkConstraints = (table.constraints || []).filter(c => c.type === 'CHECK');
1558
1606
  const columnChecks = new Map();
1559
1607
  for (const check of checkConstraints) {
@@ -1563,14 +1611,19 @@ function generateDefineTable(table, camelCase = true, childPartitions, enumNames
1563
1611
  const valuesStr = enumMatch[2];
1564
1612
  const values = valuesStr.match(/'([^']+)'/g)?.map(v => v.replace(/'/g, '')) || [];
1565
1613
  if (values.length > 0) {
1566
- columnChecks.set(colName, values);
1614
+ columnChecks.set(colName, { name: check.name, values });
1567
1615
  }
1568
1616
  }
1569
1617
  }
1570
1618
  const columns = table.columns
1571
1619
  .map(col => {
1572
- const checkValues = columnChecks.get(col.name);
1573
- return generateColumnDef(col, seenColumns, camelCase, checkValues, enumNames, domainNames, compositeNames, domainMap);
1620
+ const colWithConstraints = {
1621
+ ...col,
1622
+ isPrimaryKey: pkColumns.has(col.name) ? true : col.isPrimaryKey,
1623
+ isUnique: uniqueColumns.has(col.name) ? true : col.isUnique,
1624
+ };
1625
+ const checkInfo = columnChecks.get(col.name);
1626
+ return generateColumnDef(colWithConstraints, seenColumns, camelCase, checkInfo, enumNames, domainNames, compositeNames, domainMap);
1574
1627
  })
1575
1628
  .filter(Boolean);
1576
1629
  const columnNames = Array.from(seenColumns);
@@ -1578,7 +1631,10 @@ function generateDefineTable(table, camelCase = true, childPartitions, enumNames
1578
1631
  const checkConstraintsFn = table.constraints ? generateCheckConstraints(table.constraints, camelCase) : null;
1579
1632
  const hasPartition = table.isPartitioned && table.partitionType && table.partitionKey?.length;
1580
1633
  const hasChildPartitions = childPartitions && childPartitions.length > 0;
1581
- if (hasPartition || indexesFn || hasChildPartitions || checkConstraintsFn) {
1634
+ const hasCompositePK = compositePKs.length > 0;
1635
+ const hasCompositeUnique = compositeUniques.length > 0;
1636
+ const hasTableConstraints = hasCompositePK || hasCompositeUnique;
1637
+ if (hasPartition || indexesFn || hasChildPartitions || checkConstraintsFn || hasTableConstraints) {
1582
1638
  const parts = [];
1583
1639
  if (hasPartition) {
1584
1640
  let rawPartitionKey = table.partitionKey || [];
@@ -1631,6 +1687,18 @@ function generateDefineTable(table, camelCase = true, childPartitions, enumNames
1631
1687
  });
1632
1688
  parts.push(` partitions: (partition) => [\n${partitionLines.join(',\n')},\n ]`);
1633
1689
  }
1690
+ if (hasTableConstraints) {
1691
+ const constraintLines = [];
1692
+ for (const pk of compositePKs) {
1693
+ const colRefs = pk.columns.map(c => `table.${camelCase ? toCamelCase(c) : c}`).join(', ');
1694
+ constraintLines.push(` constraint.primaryKey('${pk.name}', ${colRefs})`);
1695
+ }
1696
+ for (const uq of compositeUniques) {
1697
+ const colRefs = uq.columns.map(c => `table.${camelCase ? toCamelCase(c) : c}`).join(', ');
1698
+ constraintLines.push(` constraint.unique('${uq.name}', ${colRefs})`);
1699
+ }
1700
+ parts.push(` constraints: (table, constraint) => [\n${constraintLines.join(',\n')},\n ]`);
1701
+ }
1634
1702
  if (checkConstraintsFn) {
1635
1703
  parts.push(` checkConstraints: ${checkConstraintsFn.trim()}`);
1636
1704
  }
@@ -1665,6 +1733,10 @@ export function generateTypeScript(schema, options = {}) {
1665
1733
  const imports = new Set(['defineTable']);
1666
1734
  for (const table of tables) {
1667
1735
  for (const col of table.columns) {
1736
+ if (!col.dataType) {
1737
+ console.warn(`Warning: Column ${table.name}.${col.name} has no dataType, skipping`);
1738
+ continue;
1739
+ }
1668
1740
  const rawType = col.dataType.toLowerCase().replace(/^_/, '');
1669
1741
  const pgType = rawType.replace(/\([^)]*\)$/, '').trim();
1670
1742
  if (['int4', 'integer'].includes(pgType))
@@ -1750,6 +1822,15 @@ export function generateTypeScript(schema, options = {}) {
1750
1822
  if (d.includes("'[]'") || d.includes('array[]'))
1751
1823
  imports.add('emptyArray');
1752
1824
  }
1825
+ if (col.isGenerated && col.generationExpression) {
1826
+ const expr = col.generationExpression.toLowerCase();
1827
+ const complexFunctions = ['setweight', 'to_tsvector', 'plainto_tsquery', 'ts_rank',
1828
+ 'regexp_replace', 'array_agg', 'string_agg', 'jsonb_build_object',
1829
+ 'jsonb_agg', 'row_to_json', 'json_build_object'];
1830
+ if (complexFunctions.some(f => expr.includes(f))) {
1831
+ imports.add('sql');
1832
+ }
1833
+ }
1753
1834
  }
1754
1835
  }
1755
1836
  const userFunctions = schema.functions?.filter((f) => !f.name.startsWith('pg_') &&
@@ -1759,6 +1840,9 @@ export function generateTypeScript(schema, options = {}) {
1759
1840
  }
1760
1841
  if (includeTriggers && schema.triggers && schema.triggers.length > 0) {
1761
1842
  imports.add('pgTrigger');
1843
+ if (!includeFunctions) {
1844
+ imports.add('pgFunction');
1845
+ }
1762
1846
  }
1763
1847
  if (schema.sequences && schema.sequences.length > 0) {
1764
1848
  imports.add('pgSequence');
@@ -2014,6 +2098,21 @@ export function generateTypeScript(schema, options = {}) {
2014
2098
  const validTriggers = schema.triggers.filter((t) => tableNames.has(t.tableName.toLowerCase()) &&
2015
2099
  !partitionTableNames.has(t.tableName.toLowerCase()));
2016
2100
  if (validTriggers.length > 0) {
2101
+ const triggerFuncNames = new Set();
2102
+ for (const trigger of validTriggers) {
2103
+ triggerFuncNames.add(trigger.functionName);
2104
+ }
2105
+ if (!includeFunctions && triggerFuncNames.size > 0) {
2106
+ parts.push('// ============================================');
2107
+ parts.push('// Trigger Function References');
2108
+ parts.push('// ============================================');
2109
+ parts.push('');
2110
+ for (const funcName of triggerFuncNames) {
2111
+ const funcCamel = toCamelCase(funcName);
2112
+ parts.push(`export const ${funcCamel} = pgFunction('${funcName}', { returns: 'trigger', language: 'plpgsql' });`);
2113
+ }
2114
+ parts.push('');
2115
+ }
2017
2116
  parts.push('// ============================================');
2018
2117
  parts.push('// Database Triggers');
2019
2118
  parts.push('// ============================================');
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { toPoolConfig } from "../types/config-types.js";
2
+ import { toPoolConfig, isAwsDsqlConfig } from "../types/config-types.js";
3
+ import { getAwsDsqlToken } from "../utils/aws-dsql.js";
3
4
  import { SelectBuilder } from "../select/select-builder.js";
4
5
  import { AggregateQueryBuilder } from "../select/aggregate-builder.js";
5
6
  import { InsertBuilder } from "../insert/insert-builder.js";
@@ -411,8 +412,18 @@ export class Relq {
411
412
  }
412
413
  this.initPromise = (async () => {
413
414
  const { Pool: PgPoolClass, Client: PgClientClass } = await loadPg();
415
+ let resolvedPassword = this.config.password;
416
+ if (isAwsDsqlConfig(this.config)) {
417
+ debugLog(this.config, 'Resolving AWS DSQL token...');
418
+ resolvedPassword = await getAwsDsqlToken(this.config.aws);
419
+ debugLog(this.config, 'AWS DSQL token resolved successfully');
420
+ }
414
421
  if (this.usePooling) {
415
- this.pool = new PgPoolClass(toPoolConfig(this.config));
422
+ const poolConfig = toPoolConfig(this.config);
423
+ if (resolvedPassword) {
424
+ poolConfig.password = resolvedPassword;
425
+ }
426
+ this.pool = new PgPoolClass(poolConfig);
416
427
  this.setupPoolErrorHandling();
417
428
  this.pool.on('connect', (client) => {
418
429
  this.emitter.emit('connect', client);
@@ -422,14 +433,19 @@ export class Relq {
422
433
  this.pool.on('remove', (client) => this.emitter.emit('remove', client));
423
434
  }
424
435
  else {
436
+ const isAws = isAwsDsqlConfig(this.config);
437
+ const host = isAws ? this.config.aws.hostname : (this.config.host || 'localhost');
438
+ const port = this.config.aws?.port ?? this.config.port ?? 5432;
439
+ const user = this.config.aws?.user ?? this.config.user ?? (isAws ? 'admin' : undefined);
440
+ const ssl = isAws ? (this.config.aws.ssl ?? true) : this.config.ssl;
425
441
  this.client = new PgClientClass({
426
- host: this.config.host || 'localhost',
427
- port: this.config.port || 5432,
442
+ host,
443
+ port,
428
444
  database: this.config.database,
429
- user: this.config.user,
430
- password: this.config.password,
445
+ user,
446
+ password: resolvedPassword,
431
447
  connectionString: this.config.connectionString,
432
- ssl: this.config.ssl
448
+ ssl
433
449
  });
434
450
  this.client.on('end', () => this.emitter.emit('end'));
435
451
  this.client.on('error', (err) => this.emitter.emit('error', err));
@@ -29,6 +29,111 @@ function chainableExpr(sql) {
29
29
  };
30
30
  return expr;
31
31
  }
32
+ export function createFluentGenExpr(sql) {
33
+ const self = {
34
+ $sql: sql,
35
+ $expr: true,
36
+ coalesce(fallback) {
37
+ const fallbackSql = typeof fallback === 'object' && '$sql' in fallback
38
+ ? fallback.$sql
39
+ : formatGenValue(fallback);
40
+ return createFluentGenExpr(`COALESCE(${this.$sql}, ${fallbackSql})`);
41
+ },
42
+ nullif(value) {
43
+ return createFluentGenExpr(`NULLIF(${this.$sql}, ${formatGenValue(value)})`);
44
+ },
45
+ asText() {
46
+ return createFluentGenExpr(`(${this.$sql})::TEXT`);
47
+ },
48
+ asInteger() {
49
+ return createFluentGenExpr(`(${this.$sql})::INTEGER`);
50
+ },
51
+ asVarchar() {
52
+ return createFluentGenExpr(`(${this.$sql})::VARCHAR`);
53
+ },
54
+ cast(typeName) {
55
+ return createFluentGenExpr(`(${this.$sql})::${typeName}`);
56
+ },
57
+ lower() {
58
+ return createFluentGenExpr(`LOWER(${this.$sql})`);
59
+ },
60
+ upper() {
61
+ return createFluentGenExpr(`UPPER(${this.$sql})`);
62
+ },
63
+ trim() {
64
+ return createFluentGenExpr(`TRIM(${this.$sql})`);
65
+ },
66
+ concat(...parts) {
67
+ const partsSql = parts.map(p => typeof p === 'object' && '$sql' in p ? p.$sql : formatGenValue(p));
68
+ return createFluentGenExpr(`(${[this.$sql, ...partsSql].join(' || ')})`);
69
+ },
70
+ substring(start, length) {
71
+ return length !== undefined
72
+ ? createFluentGenExpr(`SUBSTRING(${this.$sql} FROM ${start} FOR ${length})`)
73
+ : createFluentGenExpr(`SUBSTRING(${this.$sql} FROM ${start})`);
74
+ },
75
+ replace(from, to) {
76
+ return createFluentGenExpr(`REPLACE(${this.$sql}, '${from.replace(/'/g, "''")}', '${to.replace(/'/g, "''")}')`);
77
+ },
78
+ length() {
79
+ return createFluentGenExpr(`LENGTH(${this.$sql})`);
80
+ },
81
+ lpad(len, fill = ' ') {
82
+ return createFluentGenExpr(`LPAD(${this.$sql}, ${len}, '${fill}')`);
83
+ },
84
+ rpad(len, fill = ' ') {
85
+ return createFluentGenExpr(`RPAD(${this.$sql}, ${len}, '${fill}')`);
86
+ },
87
+ toTsvector(config) {
88
+ return createFluentGenExpr(`TO_TSVECTOR('${config}', ${this.$sql})`);
89
+ },
90
+ setWeight(weight) {
91
+ return createFluentGenExpr(`SETWEIGHT(${this.$sql}, '${weight}')`);
92
+ },
93
+ tsvConcat(other) {
94
+ return createFluentGenExpr(`(${this.$sql} || ${other.$sql})`);
95
+ },
96
+ jsonExtract(key) {
97
+ return createFluentGenExpr(`${this.$sql}->'${key}'`);
98
+ },
99
+ jsonExtractText(key) {
100
+ return createFluentGenExpr(`${this.$sql}->>'${key}'`);
101
+ },
102
+ add(value) {
103
+ return createFluentGenExpr(`(${this.$sql} + ${formatGenValue(value)})`);
104
+ },
105
+ subtract(value) {
106
+ return createFluentGenExpr(`(${this.$sql} - ${formatGenValue(value)})`);
107
+ },
108
+ multiply(value) {
109
+ return createFluentGenExpr(`(${this.$sql} * ${formatGenValue(value)})`);
110
+ },
111
+ divide(value) {
112
+ return createFluentGenExpr(`(${this.$sql} / ${formatGenValue(value)})`);
113
+ },
114
+ abs() {
115
+ return createFluentGenExpr(`ABS(${this.$sql})`);
116
+ },
117
+ round(precision) {
118
+ return precision !== undefined
119
+ ? createFluentGenExpr(`ROUND(${this.$sql}, ${precision})`)
120
+ : createFluentGenExpr(`ROUND(${this.$sql})`);
121
+ },
122
+ floor() {
123
+ return createFluentGenExpr(`FLOOR(${this.$sql})`);
124
+ },
125
+ ceil() {
126
+ return createFluentGenExpr(`CEIL(${this.$sql})`);
127
+ },
128
+ extract(field) {
129
+ return createFluentGenExpr(`EXTRACT(${field.toUpperCase()} FROM ${this.$sql})`);
130
+ },
131
+ arrayLength(dimension = 1) {
132
+ return createFluentGenExpr(`ARRAY_LENGTH(${this.$sql}, ${dimension})`);
133
+ },
134
+ };
135
+ return self;
136
+ }
32
137
  function createCaseBuilder() {
33
138
  const whens = [];
34
139
  let elseResult = null;
@@ -119,6 +224,23 @@ const generatedFnMethods = {
119
224
  arrayPosition: (arr, elem) => chainableExpr(`ARRAY_POSITION(${arr.$sql}, ${formatGenValue(elem)})`),
120
225
  md5: (col) => chainableExpr(`MD5(${col.$sql})`),
121
226
  sha256: (col) => chainableExpr(`ENCODE(SHA256(${col.$sql}::BYTEA), 'hex')`),
227
+ col: (name, alias) => chainableExpr(alias ? `"${alias}"."${name}"` : `"${name}"`),
228
+ cast: (value, typeName) => chainableExpr(`(${formatGenValue(value)})::${typeName}`),
229
+ func: (name, ...args) => chainableExpr(`${name.toUpperCase()}(${args.map(formatGenValue).join(', ')})`),
230
+ sql: (expression) => chainableExpr(expression),
231
+ op: (left, operator, right) => chainableExpr(`(${formatGenValue(left)} ${operator} ${formatGenValue(right)})`),
232
+ setweight: (tsvector, weight) => chainableExpr(`SETWEIGHT(${tsvector.$sql}, '${weight}')`),
233
+ setWeight: (tsvector, weight) => chainableExpr(`SETWEIGHT(${tsvector.$sql}, '${weight}')`),
234
+ asVarchar: (col) => chainableExpr(`(${col.$sql})::VARCHAR`),
235
+ textConcat: (left, right) => chainableExpr(`(${formatGenValue(left)} || ${formatGenValue(right)})`),
236
+ tsMatch: (left, right) => chainableExpr(`(${left.$sql} @@ ${right.$sql})`),
237
+ compare: (left, op, right) => chainableExpr(`(${formatGenValue(left)} ${op} ${formatGenValue(right)})`),
238
+ regex: (value, op, pattern) => chainableExpr(`(${value.$sql} ${op} ${formatGenValue(pattern)})`),
239
+ and: (...args) => chainableExpr(`(${args.map(a => a.$sql).join(' AND ')})`),
240
+ or: (...args) => chainableExpr(`(${args.map(a => a.$sql).join(' OR ')})`),
241
+ not: (arg) => chainableExpr(`(NOT ${arg.$sql})`),
242
+ isNull: (col) => chainableExpr(`(${col.$sql} IS NULL)`),
243
+ isNotNull: (col) => chainableExpr(`(${col.$sql} IS NOT NULL)`),
122
244
  };
123
245
  const generatedFn = new Proxy(function (col) {
124
246
  return chainableExpr(col.$sql);
@@ -159,21 +281,19 @@ function createColumn(type) {
159
281
  config.$references = { table, column, ...options };
160
282
  return Object.assign(this, { $references: config.$references });
161
283
  },
162
- check(...values) {
163
- if (values.length === 1 && /[<>=!()&|+\-*/ ]/.test(values[0])) {
164
- config.$check = values[0];
165
- return Object.assign(this, { $check: values[0] });
166
- }
167
- const expression = `IN ('${values.join("', '")}')`;
284
+ check(name, values) {
285
+ const expression = `${name} IN ('${values.join("', '")}')`;
286
+ config.$checkName = name;
168
287
  config.$checkValues = values;
169
288
  config.$check = expression;
170
- return Object.assign(this, { $check: expression, $checkValues: values });
289
+ return Object.assign(this, { $check: expression, $checkName: name, $checkValues: values });
171
290
  },
172
- checkNot(...values) {
173
- const expression = `NOT IN ('${values.join("', '")}')`;
291
+ checkNot(name, values) {
292
+ const expression = `${name} NOT IN ('${values.join("', '")}')`;
293
+ config.$checkNotName = name;
174
294
  config.$checkNotValues = values;
175
295
  config.$checkNot = expression;
176
- return Object.assign(this, { $checkNot: expression, $checkNotValues: values });
296
+ return Object.assign(this, { $checkNot: expression, $checkNotName: name, $checkNotValues: values });
177
297
  },
178
298
  generatedAs(expression, stored = true) {
179
299
  config.$generated = { expression, stored };
@@ -234,6 +354,14 @@ function createColumn(type) {
234
354
  config.$dimensions = d;
235
355
  config.$type = `VECTOR(${d})`;
236
356
  return Object.assign(this, { $dimensions: d, $type: config.$type });
357
+ },
358
+ comment(text) {
359
+ config.$comment = text;
360
+ return Object.assign(this, { $comment: text });
361
+ },
362
+ $id(trackingId) {
363
+ config.$trackingId = trackingId;
364
+ return Object.assign(this, { $trackingId: trackingId });
237
365
  }
238
366
  };
239
367
  return builder;
@@ -700,10 +828,16 @@ export function generateCompositeTypeSQL(composite) {
700
828
  }
701
829
  export const customType = (typeName, columnName) => createColumnWithName(typeName, columnName);
702
830
  export const enumType = (name, values, columnName) => {
703
- const col = createColumnWithName(name, columnName);
704
- col.$enumValues = values;
831
+ const enumName = typeof name === 'string' ? name : name.name;
832
+ const enumValues = typeof name === 'string' ? (values || []) : name.values;
833
+ const col = createColumnWithName(enumName, columnName);
834
+ col.$enumValues = enumValues;
705
835
  return col;
706
836
  };
837
+ export const domainType = (domainDef, columnName) => {
838
+ const domainName = typeof domainDef === 'string' ? domainDef : domainDef.$domainName;
839
+ return createColumnWithName(domainName, columnName);
840
+ };
707
841
  export const compositeType = (typeName, columnName) => createColumnWithName(typeName, columnName);
708
842
  export const SQL_BRAND = Symbol.for('relq.sql.brand');
709
843
  function sqlExpr(sql) {
@@ -0,0 +1,69 @@
1
+ function createDefault(sql) {
2
+ return { $sql: sql, $isDefault: true };
3
+ }
4
+ export const DEFAULT = {
5
+ genRandomUuid: () => createDefault('gen_random_uuid()'),
6
+ uuidGenerateV4: () => createDefault('uuid_generate_v4()'),
7
+ uuidGenerateV1: () => createDefault('uuid_generate_v1()'),
8
+ uuidGenerateV1mc: () => createDefault('uuid_generate_v1mc()'),
9
+ uuidNil: () => createDefault('uuid_nil()'),
10
+ now: () => createDefault('NOW()'),
11
+ currentTimestamp: () => createDefault('CURRENT_TIMESTAMP'),
12
+ currentDate: () => createDefault('CURRENT_DATE'),
13
+ currentTime: () => createDefault('CURRENT_TIME'),
14
+ localTimestamp: () => createDefault('LOCALTIMESTAMP'),
15
+ localTime: () => createDefault('LOCALTIME'),
16
+ transactionTimestamp: () => createDefault('transaction_timestamp()'),
17
+ statementTimestamp: () => createDefault('statement_timestamp()'),
18
+ clockTimestamp: () => createDefault('clock_timestamp()'),
19
+ timeofday: () => createDefault('timeofday()'),
20
+ interval: (value) => createDefault(`INTERVAL '${value}'`),
21
+ currentUser: () => createDefault('CURRENT_USER'),
22
+ sessionUser: () => createDefault('SESSION_USER'),
23
+ user: () => createDefault('USER'),
24
+ currentSchema: () => createDefault('current_schema()'),
25
+ currentDatabase: () => createDefault('current_database()'),
26
+ currentCatalog: () => createDefault('current_catalog'),
27
+ inetClientAddr: () => createDefault('inet_client_addr()'),
28
+ inetClientPort: () => createDefault('inet_client_port()'),
29
+ inetServerAddr: () => createDefault('inet_server_addr()'),
30
+ inetServerPort: () => createDefault('inet_server_port()'),
31
+ pgBackendPid: () => createDefault('pg_backend_pid()'),
32
+ nextval: (sequenceName) => createDefault(`nextval('${sequenceName}'::regclass)`),
33
+ currval: (sequenceName) => createDefault(`currval('${sequenceName}'::regclass)`),
34
+ lastval: () => createDefault('lastval()'),
35
+ random: () => createDefault('random()'),
36
+ pi: () => createDefault('pi()'),
37
+ emptyString: () => createDefault("''"),
38
+ emptyObject: () => createDefault("'{}'::jsonb"),
39
+ emptyJson: () => createDefault("'{}'::json"),
40
+ emptyJsonb: () => createDefault("'{}'::jsonb"),
41
+ emptyArray: () => createDefault("'{}'"),
42
+ emptyArrayOf: (type) => createDefault(`ARRAY[]::${type}[]`),
43
+ true: () => createDefault('TRUE'),
44
+ false: () => createDefault('FALSE'),
45
+ null: () => createDefault('NULL'),
46
+ zero: () => createDefault('0'),
47
+ one: () => createDefault('1'),
48
+ negativeOne: () => createDefault('-1'),
49
+ string: (value) => createDefault(`'${value.replace(/'/g, "''")}'`),
50
+ number: (value) => createDefault(String(value)),
51
+ integer: (value) => createDefault(String(Math.floor(value))),
52
+ decimal: (value, precision) => createDefault(precision !== undefined ? value.toFixed(precision) : String(value)),
53
+ cast: (value, type) => createDefault(`${typeof value === 'string' ? `'${value}'` : value}::${type}`),
54
+ emptyTsvector: () => createDefault("''::tsvector"),
55
+ point: (x, y) => createDefault(`point(${x}, ${y})`),
56
+ inet: (address) => createDefault(`'${address}'::inet`),
57
+ cidr: (network) => createDefault(`'${network}'::cidr`),
58
+ macaddr: (address) => createDefault(`'${address}'::macaddr`),
59
+ emptyInt4range: () => createDefault("'empty'::int4range"),
60
+ emptyInt8range: () => createDefault("'empty'::int8range"),
61
+ emptyNumrange: () => createDefault("'empty'::numrange"),
62
+ emptyTsrange: () => createDefault("'empty'::tsrange"),
63
+ emptyTstzrange: () => createDefault("'empty'::tstzrange"),
64
+ emptyDaterange: () => createDefault("'empty'::daterange"),
65
+ emptyHstore: () => createDefault("''::hstore"),
66
+ emptyBytea: () => createDefault("'\\x'::bytea"),
67
+ money: (value) => createDefault(`'${value}'::money`),
68
+ zeroMoney: () => createDefault("'0'::money"),
69
+ };
@@ -3,8 +3,11 @@ export { defineTable } from "./table-definition.js";
3
3
  export { sqlFunctions, pgExtensions, getSql, createWhereBuilder, createGeneratedExprBuilder, createTableColumnRefs, expressionBuilder, createExpressionBuilder, } from "./sql-expressions.js";
4
4
  export { parseCreateTable, generateSchemaCode, introspectSQL, introspectMultiple } from "./introspection.js";
5
5
  export { one, many, manyToMany } from "./relations.js";
6
+ export { pgRelations, defineRelations, generateReferencesSQL, actionCodeToString, stringToActionCode, matchCodeToString, } from "./pg-relations.js";
6
7
  export { pgEnum, generateEnumSQL, dropEnumSQL, addEnumValueSQL } from "./pg-enum.js";
7
8
  export { pgFunction, generateFunctionSQL, dropFunctionSQL, isFunctionConfig, } from "./pg-function.js";
8
9
  export { pgTrigger, generateTriggerSQL, dropTriggerSQL, isTriggerConfig, } from "./pg-trigger.js";
9
10
  export { pgSequence, generateSequenceSQL, dropSequenceSQL, isSequenceConfig, } from "./pg-sequence.js";
11
+ export { pgView, pgMaterializedView, viewToSQL, materializedViewToSQL, } from "./pg-view.js";
10
12
  export { partitionStrategyFactory, generatePartitionBySQL, generateChildPartitionSQL, } from "./partitions.js";
13
+ export { DEFAULT } from "./defaults.js";
@@ -472,7 +472,7 @@ export function generateSchemaCode(table, options) {
472
472
  modifiers.push('.unique()');
473
473
  }
474
474
  if (col.default !== undefined) {
475
- const defaultVal = formatDefaultValue(col.default);
475
+ const defaultVal = formatDefaultValue(col.default, col.type);
476
476
  modifiers.push(`.default(${defaultVal})`);
477
477
  }
478
478
  if (col.references) {
@@ -577,7 +577,7 @@ function toPascalCase(str) {
577
577
  function escapeString(str) {
578
578
  return str.replace(/'/g, "\\'").replace(/\\/g, '\\\\');
579
579
  }
580
- function formatDefaultValue(value) {
580
+ function formatDefaultValue(value, colType) {
581
581
  const upper = value.toUpperCase().trim();
582
582
  if (upper === 'NOW()' || upper === 'CURRENT_TIMESTAMP' ||
583
583
  upper === 'CURRENT_DATE' || upper === 'CURRENT_TIME') {
@@ -592,7 +592,11 @@ function formatDefaultValue(value) {
592
592
  if (upper === 'NULL') {
593
593
  return 'null';
594
594
  }
595
- if (/^-?\d+(\.\d+)?$/.test(value)) {
595
+ const isBigint = colType?.toLowerCase().startsWith('bigint');
596
+ if (/^-?\d+$/.test(value)) {
597
+ return isBigint ? `${value}n` : value;
598
+ }
599
+ if (/^-?\d+\.\d+$/.test(value)) {
596
600
  return value;
597
601
  }
598
602
  if (value.includes('(')) {