@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.
- package/.gqmrc.json +2 -1
- package/CHANGELOG.md +3 -3
- package/dist/bin/gqm.cjs +278 -115
- package/dist/cjs/index.cjs +288 -125
- package/dist/esm/db/generate.js +5 -5
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.d.ts +13 -1
- package/dist/esm/migrations/generate.js +197 -41
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/migrations/index.d.ts +2 -1
- package/dist/esm/migrations/index.js +2 -1
- package/dist/esm/migrations/index.js.map +1 -1
- package/dist/esm/models/model-definitions.d.ts +27 -2
- package/dist/esm/models/models.d.ts +1 -5
- package/dist/esm/models/models.js +4 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +16 -7
- package/dist/esm/models/utils.js +16 -6
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +2 -2
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +6 -6
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/resolvers.js +3 -9
- package/dist/esm/resolvers/resolvers.js.map +1 -1
- package/dist/esm/schema/generate.js +3 -9
- package/dist/esm/schema/generate.js.map +1 -1
- package/docker-compose.yml +2 -3
- package/docs/docs/2-models.md +18 -4
- package/docs/docs/5-migrations.md +11 -5
- package/package.json +3 -3
- package/src/bin/gqm/parse-knexfile.ts +1 -0
- package/src/bin/gqm/settings.ts +4 -0
- package/src/db/generate.ts +5 -15
- package/src/migrations/generate.ts +257 -42
- package/src/migrations/index.ts +2 -1
- package/src/models/model-definitions.ts +20 -1
- package/src/models/models.ts +4 -1
- package/src/models/utils.ts +27 -8
- package/src/permissions/check.ts +2 -2
- package/src/resolvers/mutations.ts +6 -6
- package/src/resolvers/resolvers.ts +7 -13
- package/src/schema/generate.ts +28 -26
- package/tests/unit/constraints.spec.ts +98 -2
- package/tests/unit/generate-as.spec.ts +6 -6
- package/tests/utils/functions.ts +1 -0
package/.gqmrc.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
## [23.
|
|
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
|
-
###
|
|
3
|
+
### Features
|
|
4
4
|
|
|
5
|
-
*
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
1124
|
-
|
|
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.
|
|
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
|
|
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.
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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)
|
|
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)
|
|
1239
|
-
({ name: name2, ...field }) =>
|
|
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)
|
|
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)
|
|
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)))
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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,
|