@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/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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((
|
|
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(
|
|
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(
|
|
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)
|
|
2880
|
+
...models.entities.filter(and(not(isRootModel), isCreatable)).map((model) => ({
|
|
2796
2881
|
[`create${model.name}`]: mutationResolver
|
|
2797
2882
|
})),
|
|
2798
|
-
...models.entities.filter(not(isRootModel)
|
|
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.
|
|
3140
|
-
|
|
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.
|
|
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
|
|
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.
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
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)
|
|
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)
|
|
3255
|
-
({ name: name2, ...field }) =>
|
|
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)
|
|
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)
|
|
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)))
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
});
|