@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
@@ -133,12 +133,12 @@ __export(index_exports, {
133
133
  isCreatableField: () => isCreatableField,
134
134
  isCreatableModel: () => isCreatableModel,
135
135
  isCustomField: () => isCustomField,
136
+ isDynamicField: () => isDynamicField,
136
137
  isEntityModel: () => isEntityModel,
137
138
  isEnum: () => isEnum,
138
139
  isEnumModel: () => isEnumModel,
139
140
  isFieldNode: () => isFieldNode,
140
141
  isFragmentSpreadNode: () => isFragmentSpreadNode,
141
- isGenerateAsField: () => isGenerateAsField,
142
142
  isInTable: () => isInTable,
143
143
  isInherited: () => isInherited,
144
144
  isInlineFragmentNode: () => isInlineFragmentNode,
@@ -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
  }
@@ -833,8 +836,8 @@ var isScalarModel = (model) => model instanceof ScalarModel;
833
836
  var isObjectModel = (model) => model instanceof ObjectModel;
834
837
  var isInputModel = (model) => model instanceof InputModel;
835
838
  var isInterfaceModel = (model) => model instanceof InterfaceModel;
836
- var isCreatableModel = (model) => model.creatable && model.fields.some(isCreatableField);
837
- var isUpdatableModel = (model) => model.updatable && model.fields.some(isUpdatableField);
839
+ var isCreatableModel = (model) => !!model.creatable && model.fields.some(isCreatableField);
840
+ var isUpdatableModel = (model) => !!model.updatable && model.fields.some(isUpdatableField);
838
841
  var isCreatableField = (field) => !field.inherited && !!field.creatable;
839
842
  var isUpdatableField = (field) => !field.inherited && !!field.updatable;
840
843
  var modelNeedsTable = (model) => model.fields.some((field) => !field.inherited);
@@ -847,8 +850,8 @@ var isInTable = (field) => field.name === "id" || !field.inherited;
847
850
  var isToOneRelation = (field) => isRelation(field) && !!field.toOne;
848
851
  var isQueriableField = ({ queriable }) => queriable !== false;
849
852
  var isCustomField = (field) => field.kind === "custom";
850
- var isGenerateAsField = (field) => !!field.generateAs;
851
- var isStoredInDatabase = (field) => field.generateAs?.type !== "expression";
853
+ var isDynamicField = (field) => !!field.generateAs || isCustomField(field);
854
+ var isStoredInDatabase = (field) => !isCustomField(field) && field.generateAs?.type !== "expression";
852
855
  var isVisible = ({ hidden }) => hidden !== true;
853
856
  var isSimpleField = and(not(isRelation), not(isCustomField));
854
857
  var isUpdatable = ({ updatable }) => !!updatable;
@@ -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) => {
@@ -1072,12 +1088,12 @@ var generateDBModels = (models, dateLibrary) => {
1072
1088
  for (const model of models.entities) {
1073
1089
  const fields2 = model.relations.some((relation) => relation.field.foreignKey === "id") ? model.fields.filter((field) => field.name !== "id") : model.fields;
1074
1090
  writer.write(`export type ${model.name} = `).inlineBlock(() => {
1075
- for (const field of fields2.filter(not(isCustomField)).filter(isStoredInDatabase)) {
1091
+ for (const field of fields2.filter(isStoredInDatabase)) {
1076
1092
  writer.write(`'${getColumnName(field)}': ${getFieldType(field, dateLibrary)}${field.nonNull ? "" : " | null"};`).newLine();
1077
1093
  }
1078
1094
  }).blankLine();
1079
1095
  writer.write(`export type ${model.name}Initializer = `).inlineBlock(() => {
1080
- for (const field of fields2.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
1096
+ for (const field of fields2.filter(and(isInTable, not(isDynamicField)))) {
1081
1097
  writer.write(
1082
1098
  `'${getColumnName(field)}'${field.nonNull && field.defaultValue === void 0 ? "" : "?"}: ${getFieldType(
1083
1099
  field,
@@ -1088,7 +1104,7 @@ var generateDBModels = (models, dateLibrary) => {
1088
1104
  }
1089
1105
  }).blankLine();
1090
1106
  writer.write(`export type ${model.name}Mutator = `).inlineBlock(() => {
1091
- for (const field of fields2.filter(not(isCustomField)).filter(isInTable).filter(not(isGenerateAsField))) {
1107
+ for (const field of fields2.filter(and(isInTable, not(isDynamicField)))) {
1092
1108
  writer.write(
1093
1109
  `'${getColumnName(field)}'?: ${getFieldType(field, dateLibrary, true)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
1094
1110
  ).newLine();
@@ -1096,7 +1112,7 @@ var generateDBModels = (models, dateLibrary) => {
1096
1112
  }).blankLine();
1097
1113
  if (!isRootModel(model)) {
1098
1114
  writer.write(`export type ${model.name}Seed = `).inlineBlock(() => {
1099
- for (const field of fields2.filter(not(isCustomField)).filter(not(isGenerateAsField))) {
1115
+ for (const field of fields2.filter(not(isDynamicField))) {
1100
1116
  if (model.parent && field.name === "type") {
1101
1117
  continue;
1102
1118
  }
@@ -1157,6 +1173,75 @@ var generateKnexTables = (models) => {
1157
1173
  return writer.toString();
1158
1174
  };
1159
1175
 
1176
+ // src/migrations/generate-functions.ts
1177
+ var generateFunctionsFromDatabase = async (knex) => {
1178
+ const regularFunctions = await knex.raw(`
1179
+ SELECT
1180
+ pg_get_functiondef(p.oid) as definition
1181
+ FROM pg_proc p
1182
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1183
+ WHERE n.nspname = 'public'
1184
+ AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
1185
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1186
+ `);
1187
+ const aggregateFunctions = await knex.raw(`
1188
+ SELECT
1189
+ p.proname as name,
1190
+ pg_get_function_identity_arguments(p.oid) as arguments,
1191
+ a.aggtransfn::regproc::text as trans_func,
1192
+ a.aggfinalfn::regproc::text as final_func,
1193
+ a.agginitval as init_val,
1194
+ pg_catalog.format_type(a.aggtranstype, NULL) as state_type
1195
+ FROM pg_proc p
1196
+ JOIN pg_aggregate a ON p.oid = a.aggfnoid
1197
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1198
+ WHERE n.nspname = 'public'
1199
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1200
+ `);
1201
+ const functions = [];
1202
+ for (const row of regularFunctions.rows || []) {
1203
+ if (row.definition) {
1204
+ functions.push(row.definition.trim());
1205
+ }
1206
+ }
1207
+ for (const row of aggregateFunctions.rows || []) {
1208
+ const name2 = row.name || "";
1209
+ const argumentsStr = row.arguments || "";
1210
+ const transFunc = row.trans_func || "";
1211
+ const finalFunc = row.final_func || "";
1212
+ const initVal = row.init_val;
1213
+ const stateType = row.state_type || "";
1214
+ if (!name2 || !transFunc || !stateType) {
1215
+ continue;
1216
+ }
1217
+ let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
1218
+ `;
1219
+ aggregateDef += ` SFUNC = ${transFunc},
1220
+ `;
1221
+ aggregateDef += ` STYPE = ${stateType}`;
1222
+ if (finalFunc) {
1223
+ aggregateDef += `,
1224
+ FINALFUNC = ${finalFunc}`;
1225
+ }
1226
+ if (initVal !== null && initVal !== void 0) {
1227
+ const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
1228
+ aggregateDef += `,
1229
+ INITCOND = ${initValStr}`;
1230
+ }
1231
+ aggregateDef += "\n);";
1232
+ functions.push(aggregateDef);
1233
+ }
1234
+ if (functions.length === 0) {
1235
+ return `export const functions: string[] = [];
1236
+ `;
1237
+ }
1238
+ const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
1239
+ return `export const functions: string[] = [
1240
+ ${functionsArrayString},
1241
+ ];
1242
+ `;
1243
+ };
1244
+
1160
1245
  // src/migrations/generate.ts
1161
1246
  var import_code_block_writer2 = __toESM(require("code-block-writer"), 1);
1162
1247
  var import_knex_schema_inspector = require("knex-schema-inspector");
@@ -1790,7 +1875,7 @@ var checkCanWrite = async (ctx, model, data, action) => {
1790
1875
  }
1791
1876
  const query = ctx.knex.first();
1792
1877
  let linked = false;
1793
- for (const field of model.fields.filter(not(isGenerateAsField)).filter((field2) => field2.generated || (action === "CREATE" ? field2.creatable : field2.updatable))) {
1878
+ for (const field of model.fields.filter(isStoredInDatabase).filter((field2) => field2.generated || (action === "CREATE" ? field2.creatable : field2.updatable))) {
1794
1879
  const fieldPermissions = field[action === "CREATE" ? "creatable" : "updatable"];
1795
1880
  const role2 = getRole(ctx);
1796
1881
  if (getColumnName(field) in data && fieldPermissions && typeof fieldPermissions === "object" && !fieldPermissions.roles?.includes(role2)) {
@@ -2302,7 +2387,7 @@ var createEntity = async (modelName, input2, ctx, trigger = "direct-call") => wi
2302
2387
  if (model.parent) {
2303
2388
  const rootInput = {};
2304
2389
  const childInput = { id };
2305
- for (const field of model.fields.filter(not(isGenerateAsField))) {
2390
+ for (const field of model.fields.filter(not(isDynamicField))) {
2306
2391
  const columnName = field.kind === "relation" ? `${field.name}Id` : field.name;
2307
2392
  if (columnName in normalizedInput) {
2308
2393
  if (field.inherited) {
@@ -2316,7 +2401,7 @@ var createEntity = async (modelName, input2, ctx, trigger = "direct-call") => wi
2316
2401
  await ctx2.knex(model.name).insert(childInput);
2317
2402
  } else {
2318
2403
  const insertData = { ...normalizedInput };
2319
- for (const field of model.fields.filter(isGenerateAsField)) {
2404
+ for (const field of model.fields.filter(isDynamicField)) {
2320
2405
  const columnName = field.kind === "relation" ? `${field.name}Id` : field.name;
2321
2406
  delete insertData[columnName];
2322
2407
  }
@@ -2682,7 +2767,7 @@ var createRevision = async (model, data, ctx) => {
2682
2767
  rootRevisionData.deleted = data.deleted || false;
2683
2768
  }
2684
2769
  const childRevisionData = { id: revisionId };
2685
- for (const field of model.fields.filter(({ updatable }) => updatable).filter(not(isGenerateAsField))) {
2770
+ for (const field of model.fields.filter(and(isUpdatableField, not(isDynamicField)))) {
2686
2771
  const col = field.kind === "relation" ? `${field.name}Id` : field.name;
2687
2772
  let value2;
2688
2773
  if (field.nonNull && (!(col in data) || col === void 0 || col === null)) {
@@ -2746,7 +2831,7 @@ var doUpdate = async (model, currentEntity, update, ctx) => {
2746
2831
  if (model.parent) {
2747
2832
  const rootInput = {};
2748
2833
  const childInput = {};
2749
- for (const field of model.fields.filter(not(isGenerateAsField))) {
2834
+ for (const field of model.fields.filter(not(isDynamicField))) {
2750
2835
  const columnName = field.kind === "relation" ? `${field.name}Id` : field.name;
2751
2836
  if (columnName in update) {
2752
2837
  if (field.inherited) {
@@ -2764,7 +2849,7 @@ var doUpdate = async (model, currentEntity, update, ctx) => {
2764
2849
  }
2765
2850
  } else {
2766
2851
  const updateData = { ...update };
2767
- for (const field of model.fields.filter(isGenerateAsField)) {
2852
+ for (const field of model.fields.filter(isDynamicField)) {
2768
2853
  const columnName = field.kind === "relation" ? `${field.name}Id` : field.name;
2769
2854
  delete updateData[columnName];
2770
2855
  }
@@ -2792,10 +2877,10 @@ var getResolvers = (models) => {
2792
2877
  ])
2793
2878
  };
2794
2879
  const mutations = [
2795
- ...models.entities.filter(not(isRootModel)).filter(({ creatable }) => creatable).map((model) => ({
2880
+ ...models.entities.filter(and(not(isRootModel), isCreatable)).map((model) => ({
2796
2881
  [`create${model.name}`]: mutationResolver
2797
2882
  })),
2798
- ...models.entities.filter(not(isRootModel)).filter(({ updatable }) => updatable).map((model) => ({
2883
+ ...models.entities.filter(and(not(isRootModel), isUpdatable)).map((model) => ({
2799
2884
  [`update${model.name}`]: mutationResolver
2800
2885
  })),
2801
2886
  ...models.entities.filter(not(isRootModel)).filter(({ deletable }) => deletable).map((model) => ({
@@ -3029,6 +3114,10 @@ var MigrationGenerator = class {
3029
3114
  columns = {};
3030
3115
  /** table name -> constraint name -> check clause expression */
3031
3116
  existingCheckConstraints = {};
3117
+ /** table name -> constraint name -> exclude definition (normalized) */
3118
+ existingExcludeConstraints = {};
3119
+ /** table name -> constraint name -> trigger definition (normalized) */
3120
+ existingConstraintTriggers = {};
3032
3121
  uuidUsed;
3033
3122
  nowUsed;
3034
3123
  needsMigration = false;
@@ -3056,8 +3145,46 @@ var MigrationGenerator = class {
3056
3145
  }
3057
3146
  this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
3058
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
+ }
3059
3177
  const up = [];
3060
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
+ }
3061
3188
  this.createEnums(
3062
3189
  this.models.enums.filter((enm2) => !enums.includes((0, import_lowerFirst.default)(enm2.name))),
3063
3190
  up,
@@ -3136,10 +3263,22 @@ var MigrationGenerator = class {
3136
3263
  if (entry.kind === "check") {
3137
3264
  validateCheckConstraint(model, entry);
3138
3265
  const table = model.name;
3139
- const constraintName = this.getCheckConstraintName(model, entry, i);
3140
- const expression = entry.expression;
3266
+ const constraintName = this.getConstraintName(model, entry, i);
3267
+ up.push(() => {
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);
3141
3274
  up.push(() => {
3142
- this.addCheckConstraint(table, constraintName, expression);
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);
3143
3282
  });
3144
3283
  }
3145
3284
  }
@@ -3179,33 +3318,81 @@ var MigrationGenerator = class {
3179
3318
  );
3180
3319
  this.updateFields(model, existingFields, up, down);
3181
3320
  if (model.constraints?.length) {
3182
- 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];
3183
3324
  for (let i = 0; i < model.constraints.length; i++) {
3184
3325
  const entry = model.constraints[i];
3185
- if (entry.kind !== "check") {
3186
- continue;
3187
- }
3188
- validateCheckConstraint(model, entry);
3189
3326
  const table = model.name;
3190
- const constraintName = this.getCheckConstraintName(model, entry, i);
3191
- const newExpression = entry.expression;
3192
- const existingExpression = existingMap?.get(constraintName);
3193
- if (existingExpression === void 0) {
3194
- up.push(() => {
3195
- this.addCheckConstraint(table, constraintName, newExpression);
3196
- });
3197
- down.push(() => {
3198
- this.dropCheckConstraint(table, constraintName);
3199
- });
3200
- } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
3201
- up.push(() => {
3202
- this.dropCheckConstraint(table, constraintName);
3203
- this.addCheckConstraint(table, constraintName, newExpression);
3204
- });
3205
- down.push(() => {
3206
- this.dropCheckConstraint(table, constraintName);
3207
- this.addCheckConstraint(table, constraintName, existingExpression);
3208
- });
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
+ }
3209
3396
  }
3210
3397
  }
3211
3398
  }
@@ -3231,7 +3418,7 @@ var MigrationGenerator = class {
3231
3418
  writer.writeLine(`deleteRootType: row.deleteRootType,`);
3232
3419
  writer.writeLine(`deleteRootId: row.deleteRootId,`);
3233
3420
  }
3234
- for (const { name: name2, kind } of model.fields.filter(isUpdatableField).filter(not(isGenerateAsField))) {
3421
+ for (const { name: name2, kind } of model.fields.filter(and(isUpdatableField, isStoredInDatabase))) {
3235
3422
  const col = kind === "relation" ? `${name2}Id` : name2;
3236
3423
  writer.writeLine(`${col}: row.${col},`);
3237
3424
  }
@@ -3251,8 +3438,8 @@ var MigrationGenerator = class {
3251
3438
  up,
3252
3439
  down
3253
3440
  );
3254
- const missingRevisionFields = model.fields.filter(isUpdatableField).filter(not(isGenerateAsField)).filter(
3255
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
3441
+ const missingRevisionFields = model.fields.filter(and(isUpdatableField, isStoredInDatabase)).filter(
3442
+ ({ name: name2, ...field }) => !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
3256
3443
  );
3257
3444
  this.createRevisionFields(model, missingRevisionFields, up, down);
3258
3445
  const revisionFieldsToRemove = model.fields.filter(
@@ -3410,7 +3597,7 @@ var MigrationGenerator = class {
3410
3597
  });
3411
3598
  });
3412
3599
  if (isUpdatableModel(model)) {
3413
- const updatableFields = fields2.filter(isUpdatableField).filter(not(isGenerateAsField));
3600
+ const updatableFields = fields2.filter(and(isUpdatableField, isStoredInDatabase));
3414
3601
  if (!updatableFields.length) {
3415
3602
  return;
3416
3603
  }
@@ -3458,7 +3645,7 @@ var MigrationGenerator = class {
3458
3645
  });
3459
3646
  });
3460
3647
  if (isUpdatableModel(model)) {
3461
- const updatableFields = fields2.filter(isUpdatableField).filter(not(isGenerateAsField));
3648
+ const updatableFields = fields2.filter(and(isUpdatableField, isStoredInDatabase));
3462
3649
  if (!updatableFields.length) {
3463
3650
  return;
3464
3651
  }
@@ -3496,7 +3683,7 @@ var MigrationGenerator = class {
3496
3683
  writer.writeLine(`table.uuid('deleteRootId');`);
3497
3684
  }
3498
3685
  }
3499
- for (const field of model.fields.filter(and(isUpdatableField, not(isInherited))).filter(not(isGenerateAsField))) {
3686
+ for (const field of model.fields.filter(and(isUpdatableField, not(isInherited), isStoredInDatabase))) {
3500
3687
  this.column(field, { setUnique: false, setDefault: false });
3501
3688
  }
3502
3689
  });
@@ -3580,20 +3767,27 @@ var MigrationGenerator = class {
3580
3767
  renameColumn(from, to) {
3581
3768
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
3582
3769
  }
3583
- getCheckConstraintName(model, entry, index) {
3770
+ getConstraintName(model, entry, index) {
3584
3771
  return `${model.name}_${entry.name}_${entry.kind}_${index}`;
3585
3772
  }
3586
3773
  normalizeCheckExpression(expr) {
3587
3774
  return expr.replace(/\s+/g, " ").trim();
3588
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
+ }
3589
3782
  /** Escape expression for embedding inside a template literal in generated code */
3590
3783
  escapeExpressionForRaw(expr) {
3591
3784
  return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
3592
3785
  }
3593
- addCheckConstraint(table, constraintName, expression) {
3786
+ addCheckConstraint(table, constraintName, expression, deferrable) {
3594
3787
  const escaped = this.escapeExpressionForRaw(expression);
3788
+ const deferrableClause = deferrable ? ` DEFERRABLE ${deferrable}` : "";
3595
3789
  this.writer.writeLine(
3596
- `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
3790
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})${deferrableClause}\`);`
3597
3791
  );
3598
3792
  this.writer.blankLine();
3599
3793
  }
@@ -3601,6 +3795,43 @@ var MigrationGenerator = class {
3601
3795
  this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
3602
3796
  this.writer.blankLine();
3603
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}" FOR EACH ${entry.forEach}${deferrableClause} ${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}" FOR EACH ${entry.forEach}${deferrableClause} ${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
+ }
3604
3835
  value(value2) {
3605
3836
  if (typeof value2 === "string") {
3606
3837
  return `'${value2}'`;
@@ -3938,75 +4169,6 @@ var getMigrationDate = () => {
3938
4169
  return `${year}${month}${day}${hours}${minutes}${seconds}`;
3939
4170
  };
3940
4171
 
3941
- // src/migrations/generate-functions.ts
3942
- var generateFunctionsFromDatabase = async (knex) => {
3943
- const regularFunctions = await knex.raw(`
3944
- SELECT
3945
- pg_get_functiondef(p.oid) as definition
3946
- FROM pg_proc p
3947
- JOIN pg_namespace n ON p.pronamespace = n.oid
3948
- WHERE n.nspname = 'public'
3949
- AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
3950
- ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
3951
- `);
3952
- const aggregateFunctions = await knex.raw(`
3953
- SELECT
3954
- p.proname as name,
3955
- pg_get_function_identity_arguments(p.oid) as arguments,
3956
- a.aggtransfn::regproc::text as trans_func,
3957
- a.aggfinalfn::regproc::text as final_func,
3958
- a.agginitval as init_val,
3959
- pg_catalog.format_type(a.aggtranstype, NULL) as state_type
3960
- FROM pg_proc p
3961
- JOIN pg_aggregate a ON p.oid = a.aggfnoid
3962
- JOIN pg_namespace n ON p.pronamespace = n.oid
3963
- WHERE n.nspname = 'public'
3964
- ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
3965
- `);
3966
- const functions = [];
3967
- for (const row of regularFunctions.rows || []) {
3968
- if (row.definition) {
3969
- functions.push(row.definition.trim());
3970
- }
3971
- }
3972
- for (const row of aggregateFunctions.rows || []) {
3973
- const name2 = row.name || "";
3974
- const argumentsStr = row.arguments || "";
3975
- const transFunc = row.trans_func || "";
3976
- const finalFunc = row.final_func || "";
3977
- const initVal = row.init_val;
3978
- const stateType = row.state_type || "";
3979
- if (!name2 || !transFunc || !stateType) {
3980
- continue;
3981
- }
3982
- let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
3983
- `;
3984
- aggregateDef += ` SFUNC = ${transFunc},
3985
- `;
3986
- aggregateDef += ` STYPE = ${stateType}`;
3987
- if (finalFunc) {
3988
- aggregateDef += `,
3989
- FINALFUNC = ${finalFunc}`;
3990
- }
3991
- if (initVal !== null && initVal !== void 0) {
3992
- const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
3993
- aggregateDef += `,
3994
- INITCOND = ${initValStr}`;
3995
- }
3996
- aggregateDef += "\n);";
3997
- functions.push(aggregateDef);
3998
- }
3999
- if (functions.length === 0) {
4000
- return `export const functions: string[] = [];
4001
- `;
4002
- }
4003
- const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
4004
- return `export const functions: string[] = [
4005
- ${functionsArrayString},
4006
- ];
4007
- `;
4008
- };
4009
-
4010
4172
  // src/permissions/generate.ts
4011
4173
  var ACTIONS = ["READ", "CREATE", "UPDATE", "DELETE", "RESTORE", "LINK"];
4012
4174
  var generatePermissions = (models, config) => {
@@ -4364,7 +4526,7 @@ var generateDefinitions = ({
4364
4526
  types.push(
4365
4527
  input(
4366
4528
  `Create${model.name}`,
4367
- model.fields.filter(({ creatable }) => creatable).filter(not(isGenerateAsField)).map(
4529
+ model.fields.filter(and(isCreatable, isStoredInDatabase)).map(
4368
4530
  (field) => field.kind === "relation" ? { name: `${field.name}Id`, type: "ID", nonNull: field.nonNull } : {
4369
4531
  name: field.name,
4370
4532
  type: field.kind === "json" ? `Create${field.type}` : field.type,
@@ -4379,7 +4541,7 @@ var generateDefinitions = ({
4379
4541
  types.push(
4380
4542
  input(
4381
4543
  `Update${model.name}`,
4382
- model.fields.filter(({ updatable }) => updatable).filter(not(isGenerateAsField)).map(
4544
+ model.fields.filter(and(isUpdatable, isStoredInDatabase)).map(
4383
4545
  (field) => field.kind === "relation" ? { name: `${field.name}Id`, type: "ID" } : {
4384
4546
  name: field.name,
4385
4547
  type: field.kind === "json" ? `Update${field.type}` : field.type,
@@ -4631,12 +4793,12 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
4631
4793
  isCreatableField,
4632
4794
  isCreatableModel,
4633
4795
  isCustomField,
4796
+ isDynamicField,
4634
4797
  isEntityModel,
4635
4798
  isEnum,
4636
4799
  isEnumModel,
4637
4800
  isFieldNode,
4638
4801
  isFragmentSpreadNode,
4639
- isGenerateAsField,
4640
4802
  isInTable,
4641
4803
  isInherited,
4642
4804
  isInlineFragmentNode,
@@ -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
  });