@smartive/graphql-magic 23.6.1 → 23.7.0-next.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
- ## [23.6.1](https://github.com/smartive/graphql-magic/compare/v23.6.0...v23.6.1) (2026-02-24)
1
+ ## [23.7.0-next.2](https://github.com/smartive/graphql-magic/compare/v23.7.0-next.1...v23.7.0-next.2) (2026-03-04)
2
2
 
3
3
  ### Bug Fixes
4
4
 
5
- * correctly generate types for fields that are dynamic e.g. generateAs expression ([8a63cf1](https://github.com/smartive/graphql-magic/commit/8a63cf18a742b5b6530f582c20e8f802ef2cb354))
5
+ * gen ([4cf0f15](https://github.com/smartive/graphql-magic/commit/4cf0f159cacc23134bd9f0760b581ac007dc24c8))
package/dist/bin/gqm.cjs CHANGED
@@ -389,6 +389,8 @@ var EntityModel = class extends Model {
389
389
  for (const constraint of this.constraints) {
390
390
  if (constraint.kind === "check") {
391
391
  validateCheckConstraint(this, constraint);
392
+ } else if (constraint.kind === "exclude") {
393
+ validateExcludeConstraint(this, constraint);
392
394
  }
393
395
  }
394
396
  }
@@ -644,6 +646,19 @@ var validateCheckConstraint = (model, constraint) => {
644
646
  }
645
647
  }
646
648
  };
649
+ var validateExcludeConstraint = (model, constraint) => {
650
+ const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
651
+ for (const el of constraint.elements) {
652
+ if ("column" in el) {
653
+ if (!validColumnNames.has(el.column)) {
654
+ const validList = [...validColumnNames].sort().join(", ");
655
+ throw new Error(
656
+ `Exclude constraint "${constraint.name}" references column "${el.column}" which does not exist on model ${model.name}. Valid columns: ${validList}`
657
+ );
658
+ }
659
+ }
660
+ }
661
+ };
647
662
 
648
663
  // src/db/generate.ts
649
664
  var import_code_block_writer = __toESM(require("code-block-writer"), 1);
@@ -1084,6 +1099,10 @@ var MigrationGenerator = class {
1084
1099
  columns = {};
1085
1100
  /** table name -> constraint name -> check clause expression */
1086
1101
  existingCheckConstraints = {};
1102
+ /** table name -> constraint name -> exclude definition (normalized) */
1103
+ existingExcludeConstraints = {};
1104
+ /** table name -> constraint name -> trigger definition (normalized) */
1105
+ existingConstraintTriggers = {};
1087
1106
  uuidUsed;
1088
1107
  nowUsed;
1089
1108
  needsMigration = false;
@@ -1111,8 +1130,46 @@ var MigrationGenerator = class {
1111
1130
  }
1112
1131
  this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
1113
1132
  }
1133
+ const excludeResult = await schema.knex.raw(
1134
+ `SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_constraintdef(c.oid) as constraint_def
1135
+ FROM pg_constraint c
1136
+ JOIN pg_namespace n ON c.connamespace = n.oid
1137
+ WHERE n.nspname = 'public' AND c.contype = 'x'`
1138
+ );
1139
+ const excludeRows = "rows" in excludeResult && Array.isArray(excludeResult.rows) ? excludeResult.rows : [];
1140
+ for (const row of excludeRows) {
1141
+ const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
1142
+ if (!this.existingExcludeConstraints[tableName]) {
1143
+ this.existingExcludeConstraints[tableName] = /* @__PURE__ */ new Map();
1144
+ }
1145
+ this.existingExcludeConstraints[tableName].set(row.constraint_name, this.normalizeExcludeDef(row.constraint_def));
1146
+ }
1147
+ const triggerResult = await schema.knex.raw(
1148
+ `SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
1149
+ FROM pg_constraint c
1150
+ JOIN pg_trigger t ON t.tgconstraint = c.oid
1151
+ JOIN pg_namespace n ON c.connamespace = n.oid
1152
+ WHERE n.nspname = 'public' AND c.contype = 't'`
1153
+ );
1154
+ const triggerRows = "rows" in triggerResult && Array.isArray(triggerResult.rows) ? triggerResult.rows : [];
1155
+ for (const row of triggerRows) {
1156
+ const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
1157
+ if (!this.existingConstraintTriggers[tableName]) {
1158
+ this.existingConstraintTriggers[tableName] = /* @__PURE__ */ new Map();
1159
+ }
1160
+ this.existingConstraintTriggers[tableName].set(row.constraint_name, this.normalizeTriggerDef(row.trigger_def));
1161
+ }
1114
1162
  const up = [];
1115
1163
  const down = [];
1164
+ const needsBtreeGist = models.entities.some(
1165
+ (model) => model.constraints?.some((c) => c.kind === "exclude" && c.elements.some((el) => "column" in el && el.operator === "="))
1166
+ );
1167
+ if (needsBtreeGist) {
1168
+ up.unshift(() => {
1169
+ this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
1170
+ this.writer.blankLine();
1171
+ });
1172
+ }
1116
1173
  this.createEnums(
1117
1174
  this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
1118
1175
  up,
@@ -1191,10 +1248,22 @@ var MigrationGenerator = class {
1191
1248
  if (entry.kind === "check") {
1192
1249
  validateCheckConstraint(model, entry);
1193
1250
  const table = model.name;
1194
- const constraintName = this.getCheckConstraintName(model, entry, i);
1195
- const expression = entry.expression;
1251
+ const constraintName = this.getConstraintName(model, entry, i);
1252
+ up.push(() => {
1253
+ this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
1254
+ });
1255
+ } else if (entry.kind === "exclude") {
1256
+ validateExcludeConstraint(model, entry);
1257
+ const table = model.name;
1258
+ const constraintName = this.getConstraintName(model, entry, i);
1259
+ up.push(() => {
1260
+ this.addExcludeConstraint(table, constraintName, entry);
1261
+ });
1262
+ } else if (entry.kind === "constraint_trigger") {
1263
+ const table = model.name;
1264
+ const constraintName = this.getConstraintName(model, entry, i);
1196
1265
  up.push(() => {
1197
- this.addCheckConstraint(table, constraintName, expression);
1266
+ this.addConstraintTrigger(table, constraintName, entry);
1198
1267
  });
1199
1268
  }
1200
1269
  }
@@ -1234,33 +1303,81 @@ var MigrationGenerator = class {
1234
1303
  );
1235
1304
  this.updateFields(model, existingFields, up, down);
1236
1305
  if (model.constraints?.length) {
1237
- const existingMap = this.existingCheckConstraints[model.name];
1306
+ const existingCheckMap = this.existingCheckConstraints[model.name];
1307
+ const existingExcludeMap = this.existingExcludeConstraints[model.name];
1308
+ const existingTriggerMap = this.existingConstraintTriggers[model.name];
1238
1309
  for (let i = 0; i < model.constraints.length; i++) {
1239
1310
  const entry = model.constraints[i];
1240
- if (entry.kind !== "check") {
1241
- continue;
1242
- }
1243
- validateCheckConstraint(model, entry);
1244
1311
  const table = model.name;
1245
- const constraintName = this.getCheckConstraintName(model, entry, i);
1246
- const newExpression = entry.expression;
1247
- const existingExpression = existingMap?.get(constraintName);
1248
- if (existingExpression === void 0) {
1249
- up.push(() => {
1250
- this.addCheckConstraint(table, constraintName, newExpression);
1251
- });
1252
- down.push(() => {
1253
- this.dropCheckConstraint(table, constraintName);
1254
- });
1255
- } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
1256
- up.push(() => {
1257
- this.dropCheckConstraint(table, constraintName);
1258
- this.addCheckConstraint(table, constraintName, newExpression);
1259
- });
1260
- down.push(() => {
1261
- this.dropCheckConstraint(table, constraintName);
1262
- this.addCheckConstraint(table, constraintName, existingExpression);
1263
- });
1312
+ const constraintName = this.getConstraintName(model, entry, i);
1313
+ if (entry.kind === "check") {
1314
+ validateCheckConstraint(model, entry);
1315
+ const newExpression = entry.expression;
1316
+ const existingExpression = existingCheckMap?.get(constraintName);
1317
+ if (existingExpression === void 0) {
1318
+ up.push(() => {
1319
+ this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
1320
+ });
1321
+ down.push(() => {
1322
+ this.dropCheckConstraint(table, constraintName);
1323
+ });
1324
+ } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
1325
+ up.push(() => {
1326
+ this.dropCheckConstraint(table, constraintName);
1327
+ this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
1328
+ });
1329
+ down.push(() => {
1330
+ this.dropCheckConstraint(table, constraintName);
1331
+ this.addCheckConstraint(table, constraintName, existingExpression);
1332
+ });
1333
+ }
1334
+ } else if (entry.kind === "exclude") {
1335
+ validateExcludeConstraint(model, entry);
1336
+ const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
1337
+ const existingDef = existingExcludeMap?.get(constraintName);
1338
+ if (existingDef === void 0) {
1339
+ up.push(() => {
1340
+ this.addExcludeConstraint(table, constraintName, entry);
1341
+ });
1342
+ down.push(() => {
1343
+ this.dropExcludeConstraint(table, constraintName);
1344
+ });
1345
+ } else if (existingDef !== newDef) {
1346
+ up.push(() => {
1347
+ this.dropExcludeConstraint(table, constraintName);
1348
+ this.addExcludeConstraint(table, constraintName, entry);
1349
+ });
1350
+ down.push(() => {
1351
+ this.dropExcludeConstraint(table, constraintName);
1352
+ const escaped = this.escapeExpressionForRaw(existingDef);
1353
+ this.writer.writeLine(
1354
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
1355
+ );
1356
+ this.writer.blankLine();
1357
+ });
1358
+ }
1359
+ } else if (entry.kind === "constraint_trigger") {
1360
+ const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
1361
+ const existingDef = existingTriggerMap?.get(constraintName);
1362
+ if (existingDef === void 0) {
1363
+ up.push(() => {
1364
+ this.addConstraintTrigger(table, constraintName, entry);
1365
+ });
1366
+ down.push(() => {
1367
+ this.dropConstraintTrigger(table, constraintName);
1368
+ });
1369
+ } else if (existingDef !== newDef) {
1370
+ up.push(() => {
1371
+ this.dropConstraintTrigger(table, constraintName);
1372
+ this.addConstraintTrigger(table, constraintName, entry);
1373
+ });
1374
+ down.push(() => {
1375
+ this.dropConstraintTrigger(table, constraintName);
1376
+ const escaped = existingDef.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1377
+ this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
1378
+ this.writer.blankLine();
1379
+ });
1380
+ }
1264
1381
  }
1265
1382
  }
1266
1383
  }
@@ -1635,20 +1752,27 @@ var MigrationGenerator = class {
1635
1752
  renameColumn(from, to) {
1636
1753
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
1637
1754
  }
1638
- getCheckConstraintName(model, entry, index) {
1755
+ getConstraintName(model, entry, index) {
1639
1756
  return `${model.name}_${entry.name}_${entry.kind}_${index}`;
1640
1757
  }
1641
1758
  normalizeCheckExpression(expr) {
1642
1759
  return expr.replace(/\s+/g, " ").trim();
1643
1760
  }
1761
+ normalizeExcludeDef(def) {
1762
+ return def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").trim();
1763
+ }
1764
+ normalizeTriggerDef(def) {
1765
+ return def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").trim();
1766
+ }
1644
1767
  /** Escape expression for embedding inside a template literal in generated code */
1645
1768
  escapeExpressionForRaw(expr) {
1646
1769
  return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1647
1770
  }
1648
- addCheckConstraint(table, constraintName, expression) {
1771
+ addCheckConstraint(table, constraintName, expression, deferrable) {
1649
1772
  const escaped = this.escapeExpressionForRaw(expression);
1773
+ const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
1650
1774
  this.writer.writeLine(
1651
- `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
1775
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
1652
1776
  );
1653
1777
  this.writer.blankLine();
1654
1778
  }
@@ -1656,6 +1780,43 @@ var MigrationGenerator = class {
1656
1780
  this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1657
1781
  this.writer.blankLine();
1658
1782
  }
1783
+ buildExcludeDef(entry) {
1784
+ const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
1785
+ const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
1786
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
1787
+ return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
1788
+ }
1789
+ addExcludeConstraint(table, constraintName, entry) {
1790
+ const def = this.buildExcludeDef(entry);
1791
+ const escaped = this.escapeExpressionForRaw(def);
1792
+ this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
1793
+ this.writer.blankLine();
1794
+ }
1795
+ dropExcludeConstraint(table, constraintName) {
1796
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1797
+ this.writer.blankLine();
1798
+ }
1799
+ buildConstraintTriggerDef(table, constraintName, entry) {
1800
+ const eventsStr = entry.events.join(" OR ");
1801
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
1802
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
1803
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
1804
+ return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
1805
+ }
1806
+ addConstraintTrigger(table, constraintName, entry) {
1807
+ const eventsStr = entry.events.join(" OR ");
1808
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
1809
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
1810
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
1811
+ this.writer.writeLine(
1812
+ `await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
1813
+ );
1814
+ this.writer.blankLine();
1815
+ }
1816
+ dropConstraintTrigger(table, constraintName) {
1817
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1818
+ this.writer.blankLine();
1819
+ }
1659
1820
  value(value2) {
1660
1821
  if (typeof value2 === "string") {
1661
1822
  return `'${value2}'`;
@@ -198,6 +198,7 @@ __export(index_exports, {
198
198
  updateEntity: () => updateEntity,
199
199
  updateFunctions: () => updateFunctions,
200
200
  validateCheckConstraint: () => validateCheckConstraint,
201
+ validateExcludeConstraint: () => validateExcludeConstraint,
201
202
  value: () => value
202
203
  });
203
204
  module.exports = __toCommonJS(index_exports);
@@ -654,6 +655,8 @@ var EntityModel = class extends Model {
654
655
  for (const constraint of this.constraints) {
655
656
  if (constraint.kind === "check") {
656
657
  validateCheckConstraint(this, constraint);
658
+ } else if (constraint.kind === "exclude") {
659
+ validateExcludeConstraint(this, constraint);
657
660
  }
658
661
  }
659
662
  }
@@ -952,6 +955,19 @@ var validateCheckConstraint = (model, constraint) => {
952
955
  }
953
956
  }
954
957
  };
958
+ var validateExcludeConstraint = (model, constraint) => {
959
+ const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
960
+ for (const el of constraint.elements) {
961
+ if ("column" in el) {
962
+ if (!validColumnNames.has(el.column)) {
963
+ const validList = [...validColumnNames].sort().join(", ");
964
+ throw new Error(
965
+ `Exclude constraint "${constraint.name}" references column "${el.column}" which does not exist on model ${model.name}. Valid columns: ${validList}`
966
+ );
967
+ }
968
+ }
969
+ }
970
+ };
955
971
 
956
972
  // src/client/queries.ts
957
973
  var fieldIsSearchable = (model, fieldName) => {
@@ -3098,6 +3114,10 @@ var MigrationGenerator = class {
3098
3114
  columns = {};
3099
3115
  /** table name -> constraint name -> check clause expression */
3100
3116
  existingCheckConstraints = {};
3117
+ /** table name -> constraint name -> exclude definition (normalized) */
3118
+ existingExcludeConstraints = {};
3119
+ /** table name -> constraint name -> trigger definition (normalized) */
3120
+ existingConstraintTriggers = {};
3101
3121
  uuidUsed;
3102
3122
  nowUsed;
3103
3123
  needsMigration = false;
@@ -3125,8 +3145,46 @@ var MigrationGenerator = class {
3125
3145
  }
3126
3146
  this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
3127
3147
  }
3148
+ const excludeResult = await schema.knex.raw(
3149
+ `SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_constraintdef(c.oid) as constraint_def
3150
+ FROM pg_constraint c
3151
+ JOIN pg_namespace n ON c.connamespace = n.oid
3152
+ WHERE n.nspname = 'public' AND c.contype = 'x'`
3153
+ );
3154
+ const excludeRows = "rows" in excludeResult && Array.isArray(excludeResult.rows) ? excludeResult.rows : [];
3155
+ for (const row of excludeRows) {
3156
+ const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
3157
+ if (!this.existingExcludeConstraints[tableName]) {
3158
+ this.existingExcludeConstraints[tableName] = /* @__PURE__ */ new Map();
3159
+ }
3160
+ this.existingExcludeConstraints[tableName].set(row.constraint_name, this.normalizeExcludeDef(row.constraint_def));
3161
+ }
3162
+ const triggerResult = await schema.knex.raw(
3163
+ `SELECT c.conrelid::regclass::text as table_name, c.conname as constraint_name, pg_get_triggerdef(t.oid) as trigger_def
3164
+ FROM pg_constraint c
3165
+ JOIN pg_trigger t ON t.tgconstraint = c.oid
3166
+ JOIN pg_namespace n ON c.connamespace = n.oid
3167
+ WHERE n.nspname = 'public' AND c.contype = 't'`
3168
+ );
3169
+ const triggerRows = "rows" in triggerResult && Array.isArray(triggerResult.rows) ? triggerResult.rows : [];
3170
+ for (const row of triggerRows) {
3171
+ const tableName = row.table_name.split(".").pop()?.replace(/^"|"$/g, "") ?? row.table_name;
3172
+ if (!this.existingConstraintTriggers[tableName]) {
3173
+ this.existingConstraintTriggers[tableName] = /* @__PURE__ */ new Map();
3174
+ }
3175
+ this.existingConstraintTriggers[tableName].set(row.constraint_name, this.normalizeTriggerDef(row.trigger_def));
3176
+ }
3128
3177
  const up = [];
3129
3178
  const down = [];
3179
+ const needsBtreeGist = models.entities.some(
3180
+ (model) => model.constraints?.some((c) => c.kind === "exclude" && c.elements.some((el) => "column" in el && el.operator === "="))
3181
+ );
3182
+ if (needsBtreeGist) {
3183
+ up.unshift(() => {
3184
+ this.writer.writeLine(`await knex.raw('CREATE EXTENSION IF NOT EXISTS btree_gist');`);
3185
+ this.writer.blankLine();
3186
+ });
3187
+ }
3130
3188
  this.createEnums(
3131
3189
  this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
3132
3190
  up,
@@ -3205,10 +3263,22 @@ var MigrationGenerator = class {
3205
3263
  if (entry.kind === "check") {
3206
3264
  validateCheckConstraint(model, entry);
3207
3265
  const table = model.name;
3208
- const constraintName = this.getCheckConstraintName(model, entry, i);
3209
- const expression = entry.expression;
3266
+ const constraintName = this.getConstraintName(model, entry, i);
3210
3267
  up.push(() => {
3211
- this.addCheckConstraint(table, constraintName, expression);
3268
+ this.addCheckConstraint(table, constraintName, entry.expression, entry.deferrable);
3269
+ });
3270
+ } else if (entry.kind === "exclude") {
3271
+ validateExcludeConstraint(model, entry);
3272
+ const table = model.name;
3273
+ const constraintName = this.getConstraintName(model, entry, i);
3274
+ up.push(() => {
3275
+ this.addExcludeConstraint(table, constraintName, entry);
3276
+ });
3277
+ } else if (entry.kind === "constraint_trigger") {
3278
+ const table = model.name;
3279
+ const constraintName = this.getConstraintName(model, entry, i);
3280
+ up.push(() => {
3281
+ this.addConstraintTrigger(table, constraintName, entry);
3212
3282
  });
3213
3283
  }
3214
3284
  }
@@ -3248,33 +3318,81 @@ var MigrationGenerator = class {
3248
3318
  );
3249
3319
  this.updateFields(model, existingFields, up, down);
3250
3320
  if (model.constraints?.length) {
3251
- const existingMap = this.existingCheckConstraints[model.name];
3321
+ const existingCheckMap = this.existingCheckConstraints[model.name];
3322
+ const existingExcludeMap = this.existingExcludeConstraints[model.name];
3323
+ const existingTriggerMap = this.existingConstraintTriggers[model.name];
3252
3324
  for (let i = 0; i < model.constraints.length; i++) {
3253
3325
  const entry = model.constraints[i];
3254
- if (entry.kind !== "check") {
3255
- continue;
3256
- }
3257
- validateCheckConstraint(model, entry);
3258
3326
  const table = model.name;
3259
- const constraintName = this.getCheckConstraintName(model, entry, i);
3260
- const newExpression = entry.expression;
3261
- const existingExpression = existingMap?.get(constraintName);
3262
- if (existingExpression === void 0) {
3263
- up.push(() => {
3264
- this.addCheckConstraint(table, constraintName, newExpression);
3265
- });
3266
- down.push(() => {
3267
- this.dropCheckConstraint(table, constraintName);
3268
- });
3269
- } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
3270
- up.push(() => {
3271
- this.dropCheckConstraint(table, constraintName);
3272
- this.addCheckConstraint(table, constraintName, newExpression);
3273
- });
3274
- down.push(() => {
3275
- this.dropCheckConstraint(table, constraintName);
3276
- this.addCheckConstraint(table, constraintName, existingExpression);
3277
- });
3327
+ const constraintName = this.getConstraintName(model, entry, i);
3328
+ if (entry.kind === "check") {
3329
+ validateCheckConstraint(model, entry);
3330
+ const newExpression = entry.expression;
3331
+ const existingExpression = existingCheckMap?.get(constraintName);
3332
+ if (existingExpression === void 0) {
3333
+ up.push(() => {
3334
+ this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
3335
+ });
3336
+ down.push(() => {
3337
+ this.dropCheckConstraint(table, constraintName);
3338
+ });
3339
+ } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
3340
+ up.push(() => {
3341
+ this.dropCheckConstraint(table, constraintName);
3342
+ this.addCheckConstraint(table, constraintName, newExpression, entry.deferrable);
3343
+ });
3344
+ down.push(() => {
3345
+ this.dropCheckConstraint(table, constraintName);
3346
+ this.addCheckConstraint(table, constraintName, existingExpression);
3347
+ });
3348
+ }
3349
+ } else if (entry.kind === "exclude") {
3350
+ validateExcludeConstraint(model, entry);
3351
+ const newDef = this.normalizeExcludeDef(this.buildExcludeDef(entry));
3352
+ const existingDef = existingExcludeMap?.get(constraintName);
3353
+ if (existingDef === void 0) {
3354
+ up.push(() => {
3355
+ this.addExcludeConstraint(table, constraintName, entry);
3356
+ });
3357
+ down.push(() => {
3358
+ this.dropExcludeConstraint(table, constraintName);
3359
+ });
3360
+ } else if (existingDef !== newDef) {
3361
+ up.push(() => {
3362
+ this.dropExcludeConstraint(table, constraintName);
3363
+ this.addExcludeConstraint(table, constraintName, entry);
3364
+ });
3365
+ down.push(() => {
3366
+ this.dropExcludeConstraint(table, constraintName);
3367
+ const escaped = this.escapeExpressionForRaw(existingDef);
3368
+ this.writer.writeLine(
3369
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`
3370
+ );
3371
+ this.writer.blankLine();
3372
+ });
3373
+ }
3374
+ } else if (entry.kind === "constraint_trigger") {
3375
+ const newDef = this.normalizeTriggerDef(this.buildConstraintTriggerDef(table, constraintName, entry));
3376
+ const existingDef = existingTriggerMap?.get(constraintName);
3377
+ if (existingDef === void 0) {
3378
+ up.push(() => {
3379
+ this.addConstraintTrigger(table, constraintName, entry);
3380
+ });
3381
+ down.push(() => {
3382
+ this.dropConstraintTrigger(table, constraintName);
3383
+ });
3384
+ } else if (existingDef !== newDef) {
3385
+ up.push(() => {
3386
+ this.dropConstraintTrigger(table, constraintName);
3387
+ this.addConstraintTrigger(table, constraintName, entry);
3388
+ });
3389
+ down.push(() => {
3390
+ this.dropConstraintTrigger(table, constraintName);
3391
+ const escaped = existingDef.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
3392
+ this.writer.writeLine(`await knex.raw(\`${escaped}\`);`);
3393
+ this.writer.blankLine();
3394
+ });
3395
+ }
3278
3396
  }
3279
3397
  }
3280
3398
  }
@@ -3649,20 +3767,27 @@ var MigrationGenerator = class {
3649
3767
  renameColumn(from, to) {
3650
3768
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
3651
3769
  }
3652
- getCheckConstraintName(model, entry, index) {
3770
+ getConstraintName(model, entry, index) {
3653
3771
  return `${model.name}_${entry.name}_${entry.kind}_${index}`;
3654
3772
  }
3655
3773
  normalizeCheckExpression(expr) {
3656
3774
  return expr.replace(/\s+/g, " ").trim();
3657
3775
  }
3776
+ normalizeExcludeDef(def) {
3777
+ return def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").trim();
3778
+ }
3779
+ normalizeTriggerDef(def) {
3780
+ return def.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").trim();
3781
+ }
3658
3782
  /** Escape expression for embedding inside a template literal in generated code */
3659
3783
  escapeExpressionForRaw(expr) {
3660
3784
  return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
3661
3785
  }
3662
- addCheckConstraint(table, constraintName, expression) {
3786
+ addCheckConstraint(table, constraintName, expression, deferrable) {
3663
3787
  const escaped = this.escapeExpressionForRaw(expression);
3788
+ const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
3664
3789
  this.writer.writeLine(
3665
- `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
3790
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
3666
3791
  );
3667
3792
  this.writer.blankLine();
3668
3793
  }
@@ -3670,6 +3795,43 @@ var MigrationGenerator = class {
3670
3795
  this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
3671
3796
  this.writer.blankLine();
3672
3797
  }
3798
+ buildExcludeDef(entry) {
3799
+ const elementsStr = entry.elements.map((el) => "column" in el ? `"${el.column}" WITH ${el.operator}` : `${el.expression} WITH ${el.operator}`).join(", ");
3800
+ const whereClause = entry.where ? ` WHERE (${entry.where})` : "";
3801
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
3802
+ return `EXCLUDE USING ${entry.using} (${elementsStr})${whereClause}${deferrableClause}`;
3803
+ }
3804
+ addExcludeConstraint(table, constraintName, entry) {
3805
+ const def = this.buildExcludeDef(entry);
3806
+ const escaped = this.escapeExpressionForRaw(def);
3807
+ this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" ${escaped}\`);`);
3808
+ this.writer.blankLine();
3809
+ }
3810
+ dropExcludeConstraint(table, constraintName) {
3811
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
3812
+ this.writer.blankLine();
3813
+ }
3814
+ buildConstraintTriggerDef(table, constraintName, entry) {
3815
+ const eventsStr = entry.events.join(" OR ");
3816
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
3817
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
3818
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
3819
+ return `CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}`;
3820
+ }
3821
+ addConstraintTrigger(table, constraintName, entry) {
3822
+ const eventsStr = entry.events.join(" OR ");
3823
+ const deferrableClause = entry.deferrable ? ` DEFERRABLE ${entry.deferrable}` : "";
3824
+ const argsStr = entry.function.args?.length ? entry.function.args.map((a) => `"${a}"`).join(", ") : "";
3825
+ const executeClause = argsStr ? `EXECUTE FUNCTION ${entry.function.name}(${argsStr})` : `EXECUTE FUNCTION ${entry.function.name}()`;
3826
+ this.writer.writeLine(
3827
+ `await knex.raw(\`CREATE CONSTRAINT TRIGGER "${constraintName}" ${entry.when} ${eventsStr} ON "${table}"${deferrableClause} FOR EACH ${entry.forEach} ${executeClause}\`);`
3828
+ );
3829
+ this.writer.blankLine();
3830
+ }
3831
+ dropConstraintTrigger(table, constraintName) {
3832
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
3833
+ this.writer.blankLine();
3834
+ }
3673
3835
  value(value2) {
3674
3836
  if (typeof value2 === "string") {
3675
3837
  return `'${value2}'`;
@@ -4696,5 +4858,6 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
4696
4858
  updateEntity,
4697
4859
  updateFunctions,
4698
4860
  validateCheckConstraint,
4861
+ validateExcludeConstraint,
4699
4862
  value
4700
4863
  });
@@ -9,6 +9,10 @@ export declare class MigrationGenerator {
9
9
  private columns;
10
10
  /** table name -> constraint name -> check clause expression */
11
11
  private existingCheckConstraints;
12
+ /** table name -> constraint name -> exclude definition (normalized) */
13
+ private existingExcludeConstraints;
14
+ /** table name -> constraint name -> trigger definition (normalized) */
15
+ private existingConstraintTriggers;
12
16
  private uuidUsed?;
13
17
  private nowUsed?;
14
18
  needsMigration: boolean;
@@ -30,12 +34,20 @@ export declare class MigrationGenerator {
30
34
  private dropTable;
31
35
  private renameTable;
32
36
  private renameColumn;
33
- private getCheckConstraintName;
37
+ private getConstraintName;
34
38
  private normalizeCheckExpression;
39
+ private normalizeExcludeDef;
40
+ private normalizeTriggerDef;
35
41
  /** Escape expression for embedding inside a template literal in generated code */
36
42
  private escapeExpressionForRaw;
37
43
  private addCheckConstraint;
38
44
  private dropCheckConstraint;
45
+ private buildExcludeDef;
46
+ private addExcludeConstraint;
47
+ private dropExcludeConstraint;
48
+ private buildConstraintTriggerDef;
49
+ private addConstraintTrigger;
50
+ private dropConstraintTrigger;
39
51
  private value;
40
52
  private columnRaw;
41
53
  private column;