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

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 (46) hide show
  1. package/.gqmrc.json +2 -1
  2. package/CHANGELOG.md +3 -3
  3. package/dist/bin/gqm.cjs +278 -115
  4. package/dist/cjs/index.cjs +288 -125
  5. package/dist/esm/db/generate.js +5 -5
  6. package/dist/esm/db/generate.js.map +1 -1
  7. package/dist/esm/migrations/generate.d.ts +13 -1
  8. package/dist/esm/migrations/generate.js +197 -41
  9. package/dist/esm/migrations/generate.js.map +1 -1
  10. package/dist/esm/migrations/index.d.ts +2 -1
  11. package/dist/esm/migrations/index.js +2 -1
  12. package/dist/esm/migrations/index.js.map +1 -1
  13. package/dist/esm/models/model-definitions.d.ts +27 -2
  14. package/dist/esm/models/models.d.ts +1 -5
  15. package/dist/esm/models/models.js +4 -1
  16. package/dist/esm/models/models.js.map +1 -1
  17. package/dist/esm/models/utils.d.ts +16 -7
  18. package/dist/esm/models/utils.js +16 -6
  19. package/dist/esm/models/utils.js.map +1 -1
  20. package/dist/esm/permissions/check.js +2 -2
  21. package/dist/esm/permissions/check.js.map +1 -1
  22. package/dist/esm/resolvers/mutations.js +6 -6
  23. package/dist/esm/resolvers/mutations.js.map +1 -1
  24. package/dist/esm/resolvers/resolvers.js +3 -9
  25. package/dist/esm/resolvers/resolvers.js.map +1 -1
  26. package/dist/esm/schema/generate.js +3 -9
  27. package/dist/esm/schema/generate.js.map +1 -1
  28. package/docker-compose.yml +2 -3
  29. package/docs/docs/2-models.md +18 -4
  30. package/docs/docs/5-migrations.md +11 -5
  31. package/package.json +3 -3
  32. package/src/bin/gqm/parse-knexfile.ts +1 -0
  33. package/src/bin/gqm/settings.ts +4 -0
  34. package/src/db/generate.ts +5 -15
  35. package/src/migrations/generate.ts +257 -42
  36. package/src/migrations/index.ts +2 -1
  37. package/src/models/model-definitions.ts +20 -1
  38. package/src/models/models.ts +4 -1
  39. package/src/models/utils.ts +27 -8
  40. package/src/permissions/check.ts +2 -2
  41. package/src/resolvers/mutations.ts +6 -6
  42. package/src/resolvers/resolvers.ts +7 -13
  43. package/src/schema/generate.ts +28 -26
  44. package/tests/unit/constraints.spec.ts +98 -2
  45. package/tests/unit/generate-as.spec.ts +6 -6
  46. package/tests/utils/functions.ts +1 -0
package/.gqmrc.json CHANGED
@@ -4,5 +4,6 @@
4
4
  "graphqlQueriesPath": "tests",
5
5
  "gqmModule": "../../../src",
6
6
  "knexfilePath": "knexfile.ts",
7
- "dateLibrary": "luxon"
7
+ "dateLibrary": "luxon",
8
+ "functionsPath": "tests/functions.ts"
8
9
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
- ## [23.6.1-next.2](https://github.com/smartive/graphql-magic/compare/v23.6.1-next.1...v23.6.1-next.2) (2026-02-24)
1
+ ## [23.7.0-next.1](https://github.com/smartive/graphql-magic/compare/v23.6.1...v23.7.0-next.1) (2026-03-04)
2
2
 
3
- ### Bug Fixes
3
+ ### Features
4
4
 
5
- * Correct mock module name in unit tests for generateAs functionality ([bcf21db](https://github.com/smartive/graphql-magic/commit/bcf21dbcd083340cb07bf10080f6390f4b370bc2))
5
+ * Exclude constraints and triggers ([f399d34](https://github.com/smartive/graphql-magic/commit/f399d3489c82cba7c043d03139db2da965c5a74a))
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
  }
@@ -558,8 +560,8 @@ var getLabel = (s) => (0, import_startCase.default)((0, import_camelCase.default
558
560
  var and = (...predicates) => (field) => predicates.every((predicate) => predicate(field));
559
561
  var not = (predicate) => (field) => !predicate(field);
560
562
  var isRootModel = (model) => !!model.root;
561
- var isCreatableModel = (model) => model.creatable && model.fields.some(isCreatableField);
562
- var isUpdatableModel = (model) => model.updatable && model.fields.some(isUpdatableField);
563
+ var isCreatableModel = (model) => !!model.creatable && model.fields.some(isCreatableField);
564
+ var isUpdatableModel = (model) => !!model.updatable && model.fields.some(isUpdatableField);
563
565
  var isCreatableField = (field) => !field.inherited && !!field.creatable;
564
566
  var isUpdatableField = (field) => !field.inherited && !!field.updatable;
565
567
  var modelNeedsTable = (model) => model.fields.some((field) => !field.inherited);
@@ -568,9 +570,11 @@ var isInherited = (field) => !!field.inherited;
568
570
  var isInTable = (field) => field.name === "id" || !field.inherited;
569
571
  var isQueriableField = ({ queriable }) => queriable !== false;
570
572
  var isCustomField = (field) => field.kind === "custom";
571
- var isGenerateAsField = (field) => !!field.generateAs;
572
- var isStoredInDatabase = (field) => field.generateAs?.type !== "expression";
573
+ var isDynamicField = (field) => !!field.generateAs || isCustomField(field);
574
+ var isStoredInDatabase = (field) => !isCustomField(field) && field.generateAs?.type !== "expression";
573
575
  var isSimpleField = and(not(isRelation), not(isCustomField));
576
+ var isUpdatable = ({ updatable }) => !!updatable;
577
+ var isCreatable = ({ creatable }) => !!creatable;
574
578
  var summonByName = (array, value2) => summonByKey(array, "name", value2);
575
579
  var summonByKey = (array, key, value2) => summon(array, (element) => (0, import_get.default)(element, key) === value2, `No element found with ${key} ${value2}`);
576
580
  var summon = (array, cb, errorMessage) => {
@@ -642,6 +646,19 @@ var validateCheckConstraint = (model, constraint) => {
642
646
  }
643
647
  }
644
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
+ };
645
662
 
646
663
  // src/db/generate.ts
647
664
  var import_code_block_writer = __toESM(require("code-block-writer"), 1);
@@ -696,12 +713,12 @@ var generateDBModels = (models, dateLibrary) => {
696
713
  for (const model of models.entities) {
697
714
  const fields2 = model.relations.some((relation) => relation.field.foreignKey === "id") ? model.fields.filter((field) => field.name !== "id") : model.fields;
698
715
  writer.write(`export type ${model.name} = `).inlineBlock(() => {
699
- for (const field of fields2.filter(not(isCustomField)).filter(isStoredInDatabase)) {
716
+ for (const field of fields2.filter(isStoredInDatabase)) {
700
717
  writer.write(`'${getColumnName(field)}': ${getFieldType(field, dateLibrary)}${field.nonNull ? "" : " | null"};`).newLine();
701
718
  }
702
719
  }).blankLine();
703
720
  writer.write(`export type ${model.name}Initializer = `).inlineBlock(() => {
704
- for (const field of fields2.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
721
+ for (const field of fields2.filter(and(isInTable, not(isDynamicField)))) {
705
722
  writer.write(
706
723
  `'${getColumnName(field)}'${field.nonNull && field.defaultValue === void 0 ? "" : "?"}: ${getFieldType(
707
724
  field,
@@ -712,7 +729,7 @@ var generateDBModels = (models, dateLibrary) => {
712
729
  }
713
730
  }).blankLine();
714
731
  writer.write(`export type ${model.name}Mutator = `).inlineBlock(() => {
715
- for (const field of fields2.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
732
+ for (const field of fields2.filter(and(isInTable, not(isDynamicField)))) {
716
733
  writer.write(
717
734
  `'${getColumnName(field)}'?: ${getFieldType(field, dateLibrary, true)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
718
735
  ).newLine();
@@ -720,7 +737,7 @@ var generateDBModels = (models, dateLibrary) => {
720
737
  }).blankLine();
721
738
  if (!isRootModel(model)) {
722
739
  writer.write(`export type ${model.name}Seed = `).inlineBlock(() => {
723
- for (const field of fields2.filter(not(isCustomField)).filter(not(isGenerateAsField))) {
740
+ for (const field of fields2.filter(not(isDynamicField))) {
724
741
  if (model.parent && field.name === "type") {
725
742
  continue;
726
743
  }
@@ -781,6 +798,75 @@ var generateKnexTables = (models) => {
781
798
  return writer.toString();
782
799
  };
783
800
 
801
+ // src/migrations/generate-functions.ts
802
+ var generateFunctionsFromDatabase = async (knex2) => {
803
+ const regularFunctions = await knex2.raw(`
804
+ SELECT
805
+ pg_get_functiondef(p.oid) as definition
806
+ FROM pg_proc p
807
+ JOIN pg_namespace n ON p.pronamespace = n.oid
808
+ WHERE n.nspname = 'public'
809
+ AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
810
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
811
+ `);
812
+ const aggregateFunctions = await knex2.raw(`
813
+ SELECT
814
+ p.proname as name,
815
+ pg_get_function_identity_arguments(p.oid) as arguments,
816
+ a.aggtransfn::regproc::text as trans_func,
817
+ a.aggfinalfn::regproc::text as final_func,
818
+ a.agginitval as init_val,
819
+ pg_catalog.format_type(a.aggtranstype, NULL) as state_type
820
+ FROM pg_proc p
821
+ JOIN pg_aggregate a ON p.oid = a.aggfnoid
822
+ JOIN pg_namespace n ON p.pronamespace = n.oid
823
+ WHERE n.nspname = 'public'
824
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
825
+ `);
826
+ const functions = [];
827
+ for (const row of regularFunctions.rows || []) {
828
+ if (row.definition) {
829
+ functions.push(row.definition.trim());
830
+ }
831
+ }
832
+ for (const row of aggregateFunctions.rows || []) {
833
+ const name2 = row.name || "";
834
+ const argumentsStr = row.arguments || "";
835
+ const transFunc = row.trans_func || "";
836
+ const finalFunc = row.final_func || "";
837
+ const initVal = row.init_val;
838
+ const stateType = row.state_type || "";
839
+ if (!name2 || !transFunc || !stateType) {
840
+ continue;
841
+ }
842
+ let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
843
+ `;
844
+ aggregateDef += ` SFUNC = ${transFunc},
845
+ `;
846
+ aggregateDef += ` STYPE = ${stateType}`;
847
+ if (finalFunc) {
848
+ aggregateDef += `,
849
+ FINALFUNC = ${finalFunc}`;
850
+ }
851
+ if (initVal !== null && initVal !== void 0) {
852
+ const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
853
+ aggregateDef += `,
854
+ INITCOND = ${initValStr}`;
855
+ }
856
+ aggregateDef += "\n);";
857
+ functions.push(aggregateDef);
858
+ }
859
+ if (functions.length === 0) {
860
+ return `export const functions: string[] = [];
861
+ `;
862
+ }
863
+ const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
864
+ return `export const functions: string[] = [
865
+ ${functionsArrayString},
866
+ ];
867
+ `;
868
+ };
869
+
784
870
  // src/migrations/generate.ts
785
871
  var import_code_block_writer2 = __toESM(require("code-block-writer"), 1);
786
872
  var import_knex_schema_inspector = require("knex-schema-inspector");
@@ -1013,6 +1099,10 @@ var MigrationGenerator = class {
1013
1099
  columns = {};
1014
1100
  /** table name -> constraint name -> check clause expression */
1015
1101
  existingCheckConstraints = {};
1102
+ /** table name -> constraint name -> exclude definition (normalized) */
1103
+ existingExcludeConstraints = {};
1104
+ /** table name -> constraint name -> trigger definition (normalized) */
1105
+ existingConstraintTriggers = {};
1016
1106
  uuidUsed;
1017
1107
  nowUsed;
1018
1108
  needsMigration = false;
@@ -1040,8 +1130,46 @@ var MigrationGenerator = class {
1040
1130
  }
1041
1131
  this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
1042
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
+ }
1043
1162
  const up = [];
1044
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
+ }
1045
1173
  this.createEnums(
1046
1174
  this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
1047
1175
  up,
@@ -1120,10 +1248,22 @@ var MigrationGenerator = class {
1120
1248
  if (entry.kind === "check") {
1121
1249
  validateCheckConstraint(model, entry);
1122
1250
  const table = model.name;
1123
- const constraintName = this.getCheckConstraintName(model, entry, i);
1124
- 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);
1125
1265
  up.push(() => {
1126
- this.addCheckConstraint(table, constraintName, expression);
1266
+ this.addConstraintTrigger(table, constraintName, entry);
1127
1267
  });
1128
1268
  }
1129
1269
  }
@@ -1163,33 +1303,81 @@ var MigrationGenerator = class {
1163
1303
  );
1164
1304
  this.updateFields(model, existingFields, up, down);
1165
1305
  if (model.constraints?.length) {
1166
- 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];
1167
1309
  for (let i = 0; i < model.constraints.length; i++) {
1168
1310
  const entry = model.constraints[i];
1169
- if (entry.kind !== "check") {
1170
- continue;
1171
- }
1172
- validateCheckConstraint(model, entry);
1173
1311
  const table = model.name;
1174
- const constraintName = this.getCheckConstraintName(model, entry, i);
1175
- const newExpression = entry.expression;
1176
- const existingExpression = existingMap?.get(constraintName);
1177
- if (existingExpression === void 0) {
1178
- up.push(() => {
1179
- this.addCheckConstraint(table, constraintName, newExpression);
1180
- });
1181
- down.push(() => {
1182
- this.dropCheckConstraint(table, constraintName);
1183
- });
1184
- } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
1185
- up.push(() => {
1186
- this.dropCheckConstraint(table, constraintName);
1187
- this.addCheckConstraint(table, constraintName, newExpression);
1188
- });
1189
- down.push(() => {
1190
- this.dropCheckConstraint(table, constraintName);
1191
- this.addCheckConstraint(table, constraintName, existingExpression);
1192
- });
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
+ }
1193
1381
  }
1194
1382
  }
1195
1383
  }
@@ -1215,7 +1403,7 @@ var MigrationGenerator = class {
1215
1403
  writer.writeLine(`deleteRootType: row.deleteRootType,`);
1216
1404
  writer.writeLine(`deleteRootId: row.deleteRootId,`);
1217
1405
  }
1218
- for (const { name: name2, kind } of model.fields.filter(isUpdatableField).filter(not(isGenerateAsField))) {
1406
+ for (const { name: name2, kind } of model.fields.filter(and(isUpdatableField, isStoredInDatabase))) {
1219
1407
  const col = kind === "relation" ? `${name2}Id` : name2;
1220
1408
  writer.writeLine(`${col}: row.${col},`);
1221
1409
  }
@@ -1235,8 +1423,8 @@ var MigrationGenerator = class {
1235
1423
  up,
1236
1424
  down
1237
1425
  );
1238
- const missingRevisionFields = model.fields.filter(isUpdatableField).filter(not(isGenerateAsField)).filter(
1239
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
1426
+ const missingRevisionFields = model.fields.filter(and(isUpdatableField, isStoredInDatabase)).filter(
1427
+ ({ name: name2, ...field }) => !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
1240
1428
  );
1241
1429
  this.createRevisionFields(model, missingRevisionFields, up, down);
1242
1430
  const revisionFieldsToRemove = model.fields.filter(
@@ -1394,7 +1582,7 @@ var MigrationGenerator = class {
1394
1582
  });
1395
1583
  });
1396
1584
  if (isUpdatableModel(model)) {
1397
- const updatableFields = fields2.filter(isUpdatableField).filter(not(isGenerateAsField));
1585
+ const updatableFields = fields2.filter(and(isUpdatableField, isStoredInDatabase));
1398
1586
  if (!updatableFields.length) {
1399
1587
  return;
1400
1588
  }
@@ -1442,7 +1630,7 @@ var MigrationGenerator = class {
1442
1630
  });
1443
1631
  });
1444
1632
  if (isUpdatableModel(model)) {
1445
- const updatableFields = fields2.filter(isUpdatableField).filter(not(isGenerateAsField));
1633
+ const updatableFields = fields2.filter(and(isUpdatableField, isStoredInDatabase));
1446
1634
  if (!updatableFields.length) {
1447
1635
  return;
1448
1636
  }
@@ -1480,7 +1668,7 @@ var MigrationGenerator = class {
1480
1668
  writer.writeLine(`table.uuid('deleteRootId');`);
1481
1669
  }
1482
1670
  }
1483
- for (const field of model.fields.filter(and(isUpdatableField, not(isInherited))).filter(not(isGenerateAsField))) {
1671
+ for (const field of model.fields.filter(and(isUpdatableField, not(isInherited), isStoredInDatabase))) {
1484
1672
  this.column(field, { setUnique: false, setDefault: false });
1485
1673
  }
1486
1674
  });
@@ -1564,20 +1752,27 @@ var MigrationGenerator = class {
1564
1752
  renameColumn(from, to) {
1565
1753
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
1566
1754
  }
1567
- getCheckConstraintName(model, entry, index) {
1755
+ getConstraintName(model, entry, index) {
1568
1756
  return `${model.name}_${entry.name}_${entry.kind}_${index}`;
1569
1757
  }
1570
1758
  normalizeCheckExpression(expr) {
1571
1759
  return expr.replace(/\s+/g, " ").trim();
1572
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
+ }
1573
1767
  /** Escape expression for embedding inside a template literal in generated code */
1574
1768
  escapeExpressionForRaw(expr) {
1575
1769
  return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1576
1770
  }
1577
- addCheckConstraint(table, constraintName, expression) {
1771
+ addCheckConstraint(table, constraintName, expression, deferrable) {
1578
1772
  const escaped = this.escapeExpressionForRaw(expression);
1773
+ const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
1579
1774
  this.writer.writeLine(
1580
- `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
1775
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
1581
1776
  );
1582
1777
  this.writer.blankLine();
1583
1778
  }
@@ -1585,6 +1780,43 @@ var MigrationGenerator = class {
1585
1780
  this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1586
1781
  this.writer.blankLine();
1587
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}" FOR EACH ${entry.forEach}${deferrableClause} ${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}" FOR EACH ${entry.forEach}${deferrableClause} ${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
+ }
1588
1820
  value(value2) {
1589
1821
  if (typeof value2 === "string") {
1590
1822
  return `'${value2}'`;
@@ -1922,75 +2154,6 @@ var getMigrationDate = () => {
1922
2154
  return `${year}${month}${day}${hours}${minutes}${seconds}`;
1923
2155
  };
1924
2156
 
1925
- // src/migrations/generate-functions.ts
1926
- var generateFunctionsFromDatabase = async (knex2) => {
1927
- const regularFunctions = await knex2.raw(`
1928
- SELECT
1929
- pg_get_functiondef(p.oid) as definition
1930
- FROM pg_proc p
1931
- JOIN pg_namespace n ON p.pronamespace = n.oid
1932
- WHERE n.nspname = 'public'
1933
- AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
1934
- ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1935
- `);
1936
- const aggregateFunctions = await knex2.raw(`
1937
- SELECT
1938
- p.proname as name,
1939
- pg_get_function_identity_arguments(p.oid) as arguments,
1940
- a.aggtransfn::regproc::text as trans_func,
1941
- a.aggfinalfn::regproc::text as final_func,
1942
- a.agginitval as init_val,
1943
- pg_catalog.format_type(a.aggtranstype, NULL) as state_type
1944
- FROM pg_proc p
1945
- JOIN pg_aggregate a ON p.oid = a.aggfnoid
1946
- JOIN pg_namespace n ON p.pronamespace = n.oid
1947
- WHERE n.nspname = 'public'
1948
- ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1949
- `);
1950
- const functions = [];
1951
- for (const row of regularFunctions.rows || []) {
1952
- if (row.definition) {
1953
- functions.push(row.definition.trim());
1954
- }
1955
- }
1956
- for (const row of aggregateFunctions.rows || []) {
1957
- const name2 = row.name || "";
1958
- const argumentsStr = row.arguments || "";
1959
- const transFunc = row.trans_func || "";
1960
- const finalFunc = row.final_func || "";
1961
- const initVal = row.init_val;
1962
- const stateType = row.state_type || "";
1963
- if (!name2 || !transFunc || !stateType) {
1964
- continue;
1965
- }
1966
- let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
1967
- `;
1968
- aggregateDef += ` SFUNC = ${transFunc},
1969
- `;
1970
- aggregateDef += ` STYPE = ${stateType}`;
1971
- if (finalFunc) {
1972
- aggregateDef += `,
1973
- FINALFUNC = ${finalFunc}`;
1974
- }
1975
- if (initVal !== null && initVal !== void 0) {
1976
- const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
1977
- aggregateDef += `,
1978
- INITCOND = ${initValStr}`;
1979
- }
1980
- aggregateDef += "\n);";
1981
- functions.push(aggregateDef);
1982
- }
1983
- if (functions.length === 0) {
1984
- return `export const functions: string[] = [];
1985
- `;
1986
- }
1987
- const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
1988
- return `export const functions: string[] = [
1989
- ${functionsArrayString},
1990
- ];
1991
- `;
1992
- };
1993
-
1994
2157
  // src/permissions/generate.ts
1995
2158
  var ACTIONS = ["READ", "CREATE", "UPDATE", "DELETE", "RESTORE", "LINK"];
1996
2159
 
@@ -2255,7 +2418,7 @@ var generateDefinitions = ({
2255
2418
  types.push(
2256
2419
  input(
2257
2420
  `Create${model.name}`,
2258
- model.fields.filter(({ creatable }) => creatable).filter(not(isGenerateAsField)).map(
2421
+ model.fields.filter(and(isCreatable, isStoredInDatabase)).map(
2259
2422
  (field) => field.kind === "relation" ? { name: `${field.name}Id`, type: "ID", nonNull: field.nonNull } : {
2260
2423
  name: field.name,
2261
2424
  type: field.kind === "json" ? `Create${field.type}` : field.type,
@@ -2270,7 +2433,7 @@ var generateDefinitions = ({
2270
2433
  types.push(
2271
2434
  input(
2272
2435
  `Update${model.name}`,
2273
- model.fields.filter(({ updatable }) => updatable).filter(not(isGenerateAsField)).map(
2436
+ model.fields.filter(and(isUpdatable, isStoredInDatabase)).map(
2274
2437
  (field) => field.kind === "relation" ? { name: `${field.name}Id`, type: "ID" } : {
2275
2438
  name: field.name,
2276
2439
  type: field.kind === "json" ? `Update${field.type}` : field.type,