@smartive/graphql-magic 23.5.0 → 23.6.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.
@@ -8,12 +8,12 @@ jobs:
8
8
  docs:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
- - uses: actions/checkout@v6
11
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
12
12
  with:
13
13
  persist-credentials: false
14
14
 
15
15
  - name: Setup Node
16
- uses: actions/setup-node@v6
16
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
17
17
  with:
18
18
  node-version: '24'
19
19
  - name: Install and Build
@@ -22,7 +22,7 @@ jobs:
22
22
  npm install
23
23
  npm run build
24
24
  - name: Deploy
25
- uses: JamesIves/github-pages-deploy-action@v4
25
+ uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4
26
26
  with:
27
27
  branch: gh-pages
28
28
  folder: docs/build
@@ -19,10 +19,10 @@ jobs:
19
19
  name: build and release
20
20
  runs-on: ubuntu-latest
21
21
  steps:
22
- - uses: actions/checkout@v6
22
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
23
23
  with:
24
24
  submodules: true
25
- - uses: actions/setup-node@v6
25
+ - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
26
26
  with:
27
27
  node-version: 24
28
28
  registry-url: 'https://registry.npmjs.org'
@@ -44,7 +44,7 @@ jobs:
44
44
 
45
45
  steps:
46
46
  - name: Checkout code
47
- uses: actions/checkout@v6
47
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
48
48
  - name: Install dependencies
49
49
  run: npm ci
50
50
  - name: Lint
package/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
- ## [23.5.0](https://github.com/smartive/graphql-magic/compare/v23.4.1...v23.5.0) (2026-02-09)
1
+ ## [23.6.0-next.2](https://github.com/smartive/graphql-magic/compare/v23.6.0-next.1...v23.6.0-next.2) (2026-02-23)
2
2
 
3
- ### Features
3
+ ### Bug Fixes
4
4
 
5
- * Functions migration generator ([0525bb5](https://github.com/smartive/graphql-magic/commit/0525bb5d182bdd258fd6408be32bfb9ce5146faa))
5
+ * Simplify type checking in normalizeValueByTypeDefinition function ([0a06610](https://github.com/smartive/graphql-magic/commit/0a066109c83114b4b0c06afd8531addb4fbbac17))
package/dist/bin/gqm.cjs CHANGED
@@ -357,6 +357,7 @@ var EntityModel = class extends Model {
357
357
  displayField;
358
358
  defaultOrderBy;
359
359
  fields;
360
+ constraints;
360
361
  // temporary fields for the generation of migrations
361
362
  deleted;
362
363
  oldName;
@@ -384,6 +385,13 @@ var EntityModel = class extends Model {
384
385
  for (const field of definition.fields) {
385
386
  this.fieldsByName[field.name] = field;
386
387
  }
388
+ if (this.constraints?.length) {
389
+ for (const constraint of this.constraints) {
390
+ if (constraint.kind === "check") {
391
+ validateCheckConstraint(this, constraint);
392
+ }
393
+ }
394
+ }
387
395
  }
388
396
  getField(name2) {
389
397
  return get(this.fieldsByName, name2);
@@ -607,6 +615,31 @@ var isManyToManyRelationEntityModel = ({
607
615
  }
608
616
  return manyToManyRelation;
609
617
  };
618
+ var extractColumnReferencesFromCheckExpression = (expression) => {
619
+ const normalized = expression.replace(/\s+/g, " ").trim();
620
+ const quotedIdentifiers = [];
621
+ const re = /"([^"]+)"/g;
622
+ let match;
623
+ while ((match = re.exec(normalized)) !== null) {
624
+ quotedIdentifiers.push(match[1]);
625
+ }
626
+ return [...new Set(quotedIdentifiers)];
627
+ };
628
+ var validateCheckConstraint = (model, constraint) => {
629
+ const identifiers = extractColumnReferencesFromCheckExpression(constraint.expression);
630
+ if (identifiers.length === 0) {
631
+ return;
632
+ }
633
+ const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
634
+ for (const identifier of identifiers) {
635
+ if (!validColumnNames.has(identifier)) {
636
+ const validList = [...validColumnNames].sort().join(", ");
637
+ throw new Error(
638
+ `Constraint "${constraint.name}" references column "${identifier}" which does not exist on model ${model.name}. Valid columns: ${validList}`
639
+ );
640
+ }
641
+ }
642
+ };
610
643
 
611
644
  // src/db/generate.ts
612
645
  var import_code_block_writer = __toESM(require("code-block-writer"), 1);
@@ -976,6 +1009,8 @@ var MigrationGenerator = class {
976
1009
  });
977
1010
  schema;
978
1011
  columns = {};
1012
+ /** table name -> constraint name -> check clause expression */
1013
+ existingCheckConstraints = {};
979
1014
  uuidUsed;
980
1015
  nowUsed;
981
1016
  needsMigration = false;
@@ -987,6 +1022,22 @@ var MigrationGenerator = class {
987
1022
  for (const table of tables) {
988
1023
  this.columns[table] = await schema.columnInfo(table);
989
1024
  }
1025
+ const checkResult = await schema.knex.raw(
1026
+ `SELECT tc.table_name, tc.constraint_name, cc.check_clause
1027
+ FROM information_schema.table_constraints tc
1028
+ JOIN information_schema.check_constraints cc
1029
+ ON tc.constraint_schema = cc.constraint_schema AND tc.constraint_name = cc.constraint_name
1030
+ WHERE tc.table_schema = ? AND tc.constraint_type = ?`,
1031
+ ["public", "CHECK"]
1032
+ );
1033
+ const rows = "rows" in checkResult && Array.isArray(checkResult.rows) ? checkResult.rows : [];
1034
+ for (const row of rows) {
1035
+ const tableName = row.table_name;
1036
+ if (!this.existingCheckConstraints[tableName]) {
1037
+ this.existingCheckConstraints[tableName] = /* @__PURE__ */ new Map();
1038
+ }
1039
+ this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
1040
+ }
990
1041
  const up = [];
991
1042
  const down = [];
992
1043
  this.createEnums(
@@ -1061,6 +1112,20 @@ var MigrationGenerator = class {
1061
1112
  }
1062
1113
  });
1063
1114
  });
1115
+ if (model.constraints?.length) {
1116
+ for (let i = 0; i < model.constraints.length; i++) {
1117
+ const entry = model.constraints[i];
1118
+ if (entry.kind === "check") {
1119
+ validateCheckConstraint(model, entry);
1120
+ const table = model.name;
1121
+ const constraintName = this.getCheckConstraintName(model, entry, i);
1122
+ const expression = entry.expression;
1123
+ up.push(() => {
1124
+ this.addCheckConstraint(table, constraintName, expression);
1125
+ });
1126
+ }
1127
+ }
1128
+ }
1064
1129
  down.push(() => {
1065
1130
  this.dropTable(model.name);
1066
1131
  });
@@ -1095,6 +1160,37 @@ var MigrationGenerator = class {
1095
1160
  (field) => (!field.generateAs || field.generateAs.type === "expression") && this.hasChanged(model, field)
1096
1161
  );
1097
1162
  this.updateFields(model, existingFields, up, down);
1163
+ if (model.constraints?.length) {
1164
+ const existingMap = this.existingCheckConstraints[model.name];
1165
+ for (let i = 0; i < model.constraints.length; i++) {
1166
+ const entry = model.constraints[i];
1167
+ if (entry.kind !== "check") {
1168
+ continue;
1169
+ }
1170
+ validateCheckConstraint(model, entry);
1171
+ const table = model.name;
1172
+ const constraintName = this.getCheckConstraintName(model, entry, i);
1173
+ const newExpression = entry.expression;
1174
+ const existingExpression = existingMap?.get(constraintName);
1175
+ if (existingExpression === void 0) {
1176
+ up.push(() => {
1177
+ this.addCheckConstraint(table, constraintName, newExpression);
1178
+ });
1179
+ down.push(() => {
1180
+ this.dropCheckConstraint(table, constraintName);
1181
+ });
1182
+ } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
1183
+ up.push(() => {
1184
+ this.dropCheckConstraint(table, constraintName);
1185
+ this.addCheckConstraint(table, constraintName, newExpression);
1186
+ });
1187
+ down.push(() => {
1188
+ this.dropCheckConstraint(table, constraintName);
1189
+ this.addCheckConstraint(table, constraintName, existingExpression);
1190
+ });
1191
+ }
1192
+ }
1193
+ }
1098
1194
  }
1099
1195
  if (isUpdatableModel(model)) {
1100
1196
  if (!tables.includes(`${model.name}Revision`)) {
@@ -1466,6 +1562,27 @@ var MigrationGenerator = class {
1466
1562
  renameColumn(from, to) {
1467
1563
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
1468
1564
  }
1565
+ getCheckConstraintName(model, entry, index) {
1566
+ return `${model.name}_${entry.name}_${entry.kind}_${index}`;
1567
+ }
1568
+ normalizeCheckExpression(expr) {
1569
+ return expr.replace(/\s+/g, " ").trim();
1570
+ }
1571
+ /** Escape expression for embedding inside a template literal in generated code */
1572
+ escapeExpressionForRaw(expr) {
1573
+ return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
1574
+ }
1575
+ addCheckConstraint(table, constraintName, expression) {
1576
+ const escaped = this.escapeExpressionForRaw(expression);
1577
+ this.writer.writeLine(
1578
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
1579
+ );
1580
+ this.writer.blankLine();
1581
+ }
1582
+ dropCheckConstraint(table, constraintName) {
1583
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
1584
+ this.writer.blankLine();
1585
+ }
1469
1586
  value(value2) {
1470
1587
  if (typeof value2 === "string") {
1471
1588
  return `'${value2}'`;
@@ -79,6 +79,7 @@ __export(index_exports, {
79
79
  document: () => document,
80
80
  enm: () => enm,
81
81
  execute: () => execute,
82
+ extractColumnReferencesFromCheckExpression: () => extractColumnReferencesFromCheckExpression,
82
83
  extractFunctionBody: () => extractFunctionBody,
83
84
  fetchDisplay: () => fetchDisplay,
84
85
  fetchManyToManyDisplay: () => fetchManyToManyDisplay,
@@ -194,6 +195,7 @@ __export(index_exports, {
194
195
  updateEntities: () => updateEntities,
195
196
  updateEntity: () => updateEntity,
196
197
  updateFunctions: () => updateFunctions,
198
+ validateCheckConstraint: () => validateCheckConstraint,
197
199
  value: () => value
198
200
  });
199
201
  module.exports = __toCommonJS(index_exports);
@@ -618,6 +620,7 @@ var EntityModel = class extends Model {
618
620
  displayField;
619
621
  defaultOrderBy;
620
622
  fields;
623
+ constraints;
621
624
  // temporary fields for the generation of migrations
622
625
  deleted;
623
626
  oldName;
@@ -645,6 +648,13 @@ var EntityModel = class extends Model {
645
648
  for (const field of definition.fields) {
646
649
  this.fieldsByName[field.name] = field;
647
650
  }
651
+ if (this.constraints?.length) {
652
+ for (const constraint of this.constraints) {
653
+ if (constraint.kind === "check") {
654
+ validateCheckConstraint(this, constraint);
655
+ }
656
+ }
657
+ }
648
658
  }
649
659
  getField(name2) {
650
660
  return get(this.fieldsByName, name2);
@@ -913,6 +923,31 @@ var isManyToManyRelationEntityModel = ({
913
923
  }
914
924
  return manyToManyRelation;
915
925
  };
926
+ var extractColumnReferencesFromCheckExpression = (expression) => {
927
+ const normalized = expression.replace(/\s+/g, " ").trim();
928
+ const quotedIdentifiers = [];
929
+ const re = /"([^"]+)"/g;
930
+ let match;
931
+ while ((match = re.exec(normalized)) !== null) {
932
+ quotedIdentifiers.push(match[1]);
933
+ }
934
+ return [...new Set(quotedIdentifiers)];
935
+ };
936
+ var validateCheckConstraint = (model, constraint) => {
937
+ const identifiers = extractColumnReferencesFromCheckExpression(constraint.expression);
938
+ if (identifiers.length === 0) {
939
+ return;
940
+ }
941
+ const validColumnNames = new Set(model.fields.map((f) => getColumnName(f)));
942
+ for (const identifier of identifiers) {
943
+ if (!validColumnNames.has(identifier)) {
944
+ const validList = [...validColumnNames].sort().join(", ");
945
+ throw new Error(
946
+ `Constraint "${constraint.name}" references column "${identifier}" which does not exist on model ${model.name}. Valid columns: ${validList}`
947
+ );
948
+ }
949
+ }
950
+ };
916
951
 
917
952
  // src/client/queries.ts
918
953
  var fieldIsSearchable = (model, fieldName) => {
@@ -1201,7 +1236,7 @@ function normalizeValue(value2, type, schema) {
1201
1236
  }
1202
1237
  }
1203
1238
  var normalizeValueByTypeDefinition = (value2, type, schema) => {
1204
- if (!type || type.kind !== import_graphql3.Kind.INPUT_OBJECT_TYPE_DEFINITION) {
1239
+ if (type?.kind !== import_graphql3.Kind.INPUT_OBJECT_TYPE_DEFINITION) {
1205
1240
  return value2;
1206
1241
  }
1207
1242
  if (value2 === null || value2 === void 0) {
@@ -2980,6 +3015,8 @@ var MigrationGenerator = class {
2980
3015
  });
2981
3016
  schema;
2982
3017
  columns = {};
3018
+ /** table name -> constraint name -> check clause expression */
3019
+ existingCheckConstraints = {};
2983
3020
  uuidUsed;
2984
3021
  nowUsed;
2985
3022
  needsMigration = false;
@@ -2991,6 +3028,22 @@ var MigrationGenerator = class {
2991
3028
  for (const table of tables) {
2992
3029
  this.columns[table] = await schema.columnInfo(table);
2993
3030
  }
3031
+ const checkResult = await schema.knex.raw(
3032
+ `SELECT tc.table_name, tc.constraint_name, cc.check_clause
3033
+ FROM information_schema.table_constraints tc
3034
+ JOIN information_schema.check_constraints cc
3035
+ ON tc.constraint_schema = cc.constraint_schema AND tc.constraint_name = cc.constraint_name
3036
+ WHERE tc.table_schema = ? AND tc.constraint_type = ?`,
3037
+ ["public", "CHECK"]
3038
+ );
3039
+ const rows = "rows" in checkResult && Array.isArray(checkResult.rows) ? checkResult.rows : [];
3040
+ for (const row of rows) {
3041
+ const tableName = row.table_name;
3042
+ if (!this.existingCheckConstraints[tableName]) {
3043
+ this.existingCheckConstraints[tableName] = /* @__PURE__ */ new Map();
3044
+ }
3045
+ this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
3046
+ }
2994
3047
  const up = [];
2995
3048
  const down = [];
2996
3049
  this.createEnums(
@@ -3065,6 +3118,20 @@ var MigrationGenerator = class {
3065
3118
  }
3066
3119
  });
3067
3120
  });
3121
+ if (model.constraints?.length) {
3122
+ for (let i = 0; i < model.constraints.length; i++) {
3123
+ const entry = model.constraints[i];
3124
+ if (entry.kind === "check") {
3125
+ validateCheckConstraint(model, entry);
3126
+ const table = model.name;
3127
+ const constraintName = this.getCheckConstraintName(model, entry, i);
3128
+ const expression = entry.expression;
3129
+ up.push(() => {
3130
+ this.addCheckConstraint(table, constraintName, expression);
3131
+ });
3132
+ }
3133
+ }
3134
+ }
3068
3135
  down.push(() => {
3069
3136
  this.dropTable(model.name);
3070
3137
  });
@@ -3099,6 +3166,37 @@ var MigrationGenerator = class {
3099
3166
  (field) => (!field.generateAs || field.generateAs.type === "expression") && this.hasChanged(model, field)
3100
3167
  );
3101
3168
  this.updateFields(model, existingFields, up, down);
3169
+ if (model.constraints?.length) {
3170
+ const existingMap = this.existingCheckConstraints[model.name];
3171
+ for (let i = 0; i < model.constraints.length; i++) {
3172
+ const entry = model.constraints[i];
3173
+ if (entry.kind !== "check") {
3174
+ continue;
3175
+ }
3176
+ validateCheckConstraint(model, entry);
3177
+ const table = model.name;
3178
+ const constraintName = this.getCheckConstraintName(model, entry, i);
3179
+ const newExpression = entry.expression;
3180
+ const existingExpression = existingMap?.get(constraintName);
3181
+ if (existingExpression === void 0) {
3182
+ up.push(() => {
3183
+ this.addCheckConstraint(table, constraintName, newExpression);
3184
+ });
3185
+ down.push(() => {
3186
+ this.dropCheckConstraint(table, constraintName);
3187
+ });
3188
+ } else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
3189
+ up.push(() => {
3190
+ this.dropCheckConstraint(table, constraintName);
3191
+ this.addCheckConstraint(table, constraintName, newExpression);
3192
+ });
3193
+ down.push(() => {
3194
+ this.dropCheckConstraint(table, constraintName);
3195
+ this.addCheckConstraint(table, constraintName, existingExpression);
3196
+ });
3197
+ }
3198
+ }
3199
+ }
3102
3200
  }
3103
3201
  if (isUpdatableModel(model)) {
3104
3202
  if (!tables.includes(`${model.name}Revision`)) {
@@ -3470,6 +3568,27 @@ var MigrationGenerator = class {
3470
3568
  renameColumn(from, to) {
3471
3569
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
3472
3570
  }
3571
+ getCheckConstraintName(model, entry, index) {
3572
+ return `${model.name}_${entry.name}_${entry.kind}_${index}`;
3573
+ }
3574
+ normalizeCheckExpression(expr) {
3575
+ return expr.replace(/\s+/g, " ").trim();
3576
+ }
3577
+ /** Escape expression for embedding inside a template literal in generated code */
3578
+ escapeExpressionForRaw(expr) {
3579
+ return expr.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
3580
+ }
3581
+ addCheckConstraint(table, constraintName, expression) {
3582
+ const escaped = this.escapeExpressionForRaw(expression);
3583
+ this.writer.writeLine(
3584
+ `await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`
3585
+ );
3586
+ this.writer.blankLine();
3587
+ }
3588
+ dropCheckConstraint(table, constraintName) {
3589
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
3590
+ this.writer.blankLine();
3591
+ }
3473
3592
  value(value2) {
3474
3593
  if (typeof value2 === "string") {
3475
3594
  return `'${value2}'`;
@@ -4446,6 +4565,7 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
4446
4565
  document,
4447
4566
  enm,
4448
4567
  execute,
4568
+ extractColumnReferencesFromCheckExpression,
4449
4569
  extractFunctionBody,
4450
4570
  fetchDisplay,
4451
4571
  fetchManyToManyDisplay,
@@ -4561,5 +4681,6 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql6.buildAST
4561
4681
  updateEntities,
4562
4682
  updateEntity,
4563
4683
  updateFunctions,
4684
+ validateCheckConstraint,
4564
4685
  value
4565
4686
  });
@@ -7,6 +7,8 @@ export declare class MigrationGenerator {
7
7
  private writer;
8
8
  private schema;
9
9
  private columns;
10
+ /** table name -> constraint name -> check clause expression */
11
+ private existingCheckConstraints;
10
12
  private uuidUsed?;
11
13
  private nowUsed?;
12
14
  needsMigration: boolean;
@@ -28,6 +30,12 @@ export declare class MigrationGenerator {
28
30
  private dropTable;
29
31
  private renameTable;
30
32
  private renameColumn;
33
+ private getCheckConstraintName;
34
+ private normalizeCheckExpression;
35
+ /** Escape expression for embedding inside a template literal in generated code */
36
+ private escapeExpressionForRaw;
37
+ private addCheckConstraint;
38
+ private dropCheckConstraint;
31
39
  private value;
32
40
  private columnRaw;
33
41
  private column;
@@ -1,7 +1,7 @@
1
1
  import CodeBlockWriter from 'code-block-writer';
2
2
  import { SchemaInspector } from 'knex-schema-inspector';
3
3
  import lowerFirst from 'lodash/lowerFirst';
4
- import { and, get, isCreatableModel, isInherited, isUpdatableField, isUpdatableModel, modelNeedsTable, not, summonByName, typeToField, } from '../models/utils';
4
+ import { and, get, isCreatableModel, isInherited, isUpdatableField, isUpdatableModel, modelNeedsTable, not, summonByName, typeToField, validateCheckConstraint, } from '../models/utils';
5
5
  import { getColumnName } from '../resolvers';
6
6
  import { getDatabaseFunctions, normalizeAggregateDefinition, normalizeFunctionBody, } from './update-functions';
7
7
  export class MigrationGenerator {
@@ -14,6 +14,8 @@ export class MigrationGenerator {
14
14
  });
15
15
  schema;
16
16
  columns = {};
17
+ /** table name -> constraint name -> check clause expression */
18
+ existingCheckConstraints = {};
17
19
  uuidUsed;
18
20
  nowUsed;
19
21
  needsMigration = false;
@@ -31,6 +33,21 @@ export class MigrationGenerator {
31
33
  for (const table of tables) {
32
34
  this.columns[table] = await schema.columnInfo(table);
33
35
  }
36
+ const checkResult = await schema.knex.raw(`SELECT tc.table_name, tc.constraint_name, cc.check_clause
37
+ FROM information_schema.table_constraints tc
38
+ JOIN information_schema.check_constraints cc
39
+ ON tc.constraint_schema = cc.constraint_schema AND tc.constraint_name = cc.constraint_name
40
+ WHERE tc.table_schema = ? AND tc.constraint_type = ?`, ['public', 'CHECK']);
41
+ const rows = 'rows' in checkResult && Array.isArray(checkResult.rows)
42
+ ? checkResult.rows
43
+ : [];
44
+ for (const row of rows) {
45
+ const tableName = row.table_name;
46
+ if (!this.existingCheckConstraints[tableName]) {
47
+ this.existingCheckConstraints[tableName] = new Map();
48
+ }
49
+ this.existingCheckConstraints[tableName].set(row.constraint_name, row.check_clause);
50
+ }
34
51
  const up = [];
35
52
  const down = [];
36
53
  this.createEnums(this.models.enums.filter((enm) => !enums.includes(lowerFirst(enm.name))), up, down);
@@ -106,6 +123,20 @@ export class MigrationGenerator {
106
123
  }
107
124
  });
108
125
  });
126
+ if (model.constraints?.length) {
127
+ for (let i = 0; i < model.constraints.length; i++) {
128
+ const entry = model.constraints[i];
129
+ if (entry.kind === 'check') {
130
+ validateCheckConstraint(model, entry);
131
+ const table = model.name;
132
+ const constraintName = this.getCheckConstraintName(model, entry, i);
133
+ const expression = entry.expression;
134
+ up.push(() => {
135
+ this.addCheckConstraint(table, constraintName, expression);
136
+ });
137
+ }
138
+ }
139
+ }
109
140
  down.push(() => {
110
141
  this.dropTable(model.name);
111
142
  });
@@ -139,6 +170,38 @@ export class MigrationGenerator {
139
170
  }
140
171
  const existingFields = model.fields.filter((field) => (!field.generateAs || field.generateAs.type === 'expression') && this.hasChanged(model, field));
141
172
  this.updateFields(model, existingFields, up, down);
173
+ if (model.constraints?.length) {
174
+ const existingMap = this.existingCheckConstraints[model.name];
175
+ for (let i = 0; i < model.constraints.length; i++) {
176
+ const entry = model.constraints[i];
177
+ if (entry.kind !== 'check') {
178
+ continue;
179
+ }
180
+ validateCheckConstraint(model, entry);
181
+ const table = model.name;
182
+ const constraintName = this.getCheckConstraintName(model, entry, i);
183
+ const newExpression = entry.expression;
184
+ const existingExpression = existingMap?.get(constraintName);
185
+ if (existingExpression === undefined) {
186
+ up.push(() => {
187
+ this.addCheckConstraint(table, constraintName, newExpression);
188
+ });
189
+ down.push(() => {
190
+ this.dropCheckConstraint(table, constraintName);
191
+ });
192
+ }
193
+ else if (this.normalizeCheckExpression(existingExpression) !== this.normalizeCheckExpression(newExpression)) {
194
+ up.push(() => {
195
+ this.dropCheckConstraint(table, constraintName);
196
+ this.addCheckConstraint(table, constraintName, newExpression);
197
+ });
198
+ down.push(() => {
199
+ this.dropCheckConstraint(table, constraintName);
200
+ this.addCheckConstraint(table, constraintName, existingExpression);
201
+ });
202
+ }
203
+ }
204
+ }
142
205
  }
143
206
  if (isUpdatableModel(model)) {
144
207
  if (!tables.includes(`${model.name}Revision`)) {
@@ -524,6 +587,25 @@ export class MigrationGenerator {
524
587
  renameColumn(from, to) {
525
588
  this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
526
589
  }
590
+ getCheckConstraintName(model, entry, index) {
591
+ return `${model.name}_${entry.name}_${entry.kind}_${index}`;
592
+ }
593
+ normalizeCheckExpression(expr) {
594
+ return expr.replace(/\s+/g, ' ').trim();
595
+ }
596
+ /** Escape expression for embedding inside a template literal in generated code */
597
+ escapeExpressionForRaw(expr) {
598
+ return expr.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
599
+ }
600
+ addCheckConstraint(table, constraintName, expression) {
601
+ const escaped = this.escapeExpressionForRaw(expression);
602
+ this.writer.writeLine(`await knex.raw(\`ALTER TABLE "${table}" ADD CONSTRAINT "${constraintName}" CHECK (${escaped})\`);`);
603
+ this.writer.blankLine();
604
+ }
605
+ dropCheckConstraint(table, constraintName) {
606
+ this.writer.writeLine(`await knex.raw('ALTER TABLE "${table}" DROP CONSTRAINT "${constraintName}"');`);
607
+ this.writer.blankLine();
608
+ }
527
609
  value(value) {
528
610
  if (typeof value === 'string') {
529
611
  return `'${value}'`;