@smartive/graphql-magic 23.4.1 → 23.5.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/dist/bin/gqm.cjs +656 -59
  3. package/dist/cjs/index.cjs +2700 -2133
  4. package/dist/esm/migrations/generate-functions.d.ts +2 -0
  5. package/dist/esm/migrations/generate-functions.js +60 -0
  6. package/dist/esm/migrations/generate-functions.js.map +1 -0
  7. package/dist/esm/migrations/generate.d.ts +9 -1
  8. package/dist/esm/migrations/generate.js +269 -33
  9. package/dist/esm/migrations/generate.js.map +1 -1
  10. package/dist/esm/migrations/index.d.ts +2 -0
  11. package/dist/esm/migrations/index.js +2 -0
  12. package/dist/esm/migrations/index.js.map +1 -1
  13. package/dist/esm/migrations/types.d.ts +7 -0
  14. package/dist/esm/migrations/types.js +2 -0
  15. package/dist/esm/migrations/types.js.map +1 -0
  16. package/dist/esm/migrations/update-functions.d.ts +3 -0
  17. package/dist/esm/migrations/update-functions.js +177 -0
  18. package/dist/esm/migrations/update-functions.js.map +1 -0
  19. package/dist/esm/models/model-definitions.d.ts +4 -1
  20. package/dist/esm/resolvers/filters.js +76 -14
  21. package/dist/esm/resolvers/filters.js.map +1 -1
  22. package/dist/esm/resolvers/selects.js +20 -2
  23. package/dist/esm/resolvers/selects.js.map +1 -1
  24. package/dist/esm/resolvers/utils.d.ts +1 -0
  25. package/dist/esm/resolvers/utils.js +29 -0
  26. package/dist/esm/resolvers/utils.js.map +1 -1
  27. package/docs/docs/3-fields.md +149 -0
  28. package/docs/docs/5-migrations.md +9 -1
  29. package/package.json +2 -2
  30. package/src/bin/gqm/gqm.ts +44 -5
  31. package/src/bin/gqm/parse-functions.ts +141 -0
  32. package/src/bin/gqm/settings.ts +7 -0
  33. package/src/bin/gqm/utils.ts +1 -0
  34. package/src/migrations/generate-functions.ts +74 -0
  35. package/src/migrations/generate.ts +334 -41
  36. package/src/migrations/index.ts +2 -0
  37. package/src/migrations/types.ts +7 -0
  38. package/src/migrations/update-functions.ts +221 -0
  39. package/src/models/model-definitions.ts +4 -1
  40. package/src/resolvers/filters.ts +88 -25
  41. package/src/resolvers/selects.ts +22 -5
  42. package/src/resolvers/utils.ts +44 -0
package/dist/bin/gqm.cjs CHANGED
@@ -750,9 +750,25 @@ var generateKnexTables = (models) => {
750
750
  var import_code_block_writer2 = __toESM(require("code-block-writer"), 1);
751
751
  var import_knex_schema_inspector = require("knex-schema-inspector");
752
752
  var import_lowerFirst = __toESM(require("lodash/lowerFirst"), 1);
753
+
754
+ // src/resolvers/arguments.ts
755
+ var import_graphql3 = require("graphql");
756
+
757
+ // src/resolvers/utils.ts
758
+ var import_graphql4 = require("graphql");
759
+ var import_isEqual = __toESM(require("lodash/isEqual"), 1);
760
+ var getColumnName = (field) => field.kind === "relation" ? field.foreignKey || `${field.name}Id` : field.name;
761
+
762
+ // src/resolvers/resolver.ts
763
+ var import_cloneDeep2 = __toESM(require("lodash/cloneDeep"), 1);
764
+ var import_flatMap = __toESM(require("lodash/flatMap"), 1);
765
+
766
+ // src/migrations/generate.ts
753
767
  var MigrationGenerator = class {
754
- constructor(knex2, models) {
768
+ constructor(knex2, models, parsedFunctions) {
755
769
  this.models = models;
770
+ this.parsedFunctions = parsedFunctions;
771
+ this.knex = knex2;
756
772
  this.schema = (0, import_knex_schema_inspector.SchemaInspector)(knex2);
757
773
  }
758
774
  // eslint-disable-next-line @typescript-eslint/dot-notation
@@ -765,6 +781,7 @@ var MigrationGenerator = class {
765
781
  uuidUsed;
766
782
  nowUsed;
767
783
  needsMigration = false;
784
+ knex;
768
785
  async generate() {
769
786
  const { writer, schema, models } = this;
770
787
  const enums = (await schema.knex("pg_type").where({ typtype: "e" }).select("typname")).map(({ typname }) => typname);
@@ -779,6 +796,7 @@ var MigrationGenerator = class {
779
796
  up,
780
797
  down
781
798
  );
799
+ await this.handleFunctions(up, down);
782
800
  for (const model of models.entities) {
783
801
  if (model.deleted) {
784
802
  up.push(() => {
@@ -840,7 +858,7 @@ var MigrationGenerator = class {
840
858
  foreignKey: "id"
841
859
  });
842
860
  }
843
- for (const field of model.fields.filter(not(isInherited))) {
861
+ for (const field of model.fields.filter(not(isInherited)).filter((f) => !(f.generateAs?.type === "expression"))) {
844
862
  this.column(field);
845
863
  }
846
864
  });
@@ -849,29 +867,25 @@ var MigrationGenerator = class {
849
867
  this.dropTable(model.name);
850
868
  });
851
869
  } else {
852
- this.renameFields(
853
- model,
854
- model.fields.filter(not(isInherited)).filter(({ oldName }) => oldName),
855
- up,
856
- down
857
- );
870
+ const fieldsToRename = model.fields.filter(not(isInherited)).filter(({ oldName }) => oldName);
871
+ this.renameFields(model.name, fieldsToRename, up, down);
858
872
  this.createFields(
859
873
  model,
860
874
  model.fields.filter(not(isInherited)).filter(
861
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(model.name, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
875
+ ({ name: name2, ...field }) => field.kind !== "custom" && !(field.generateAs?.type === "expression") && !this.getColumn(model.name, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
862
876
  ),
863
877
  up,
864
878
  down
865
879
  );
866
880
  const rawExistingFields = model.fields.filter((field) => {
867
- if (!field.generateAs) {
881
+ if (!field.generateAs || field.generateAs.type === "expression") {
868
882
  return false;
869
883
  }
870
884
  const col = this.getColumn(model.name, field.kind === "relation" ? `${field.name}Id` : field.name);
871
885
  if (!col) {
872
886
  return false;
873
887
  }
874
- if (col.generation_expression !== field.generateAs) {
888
+ if (col.generation_expression !== field.generateAs.expression) {
875
889
  return true;
876
890
  }
877
891
  return this.hasChanged(model, field);
@@ -879,7 +893,9 @@ var MigrationGenerator = class {
879
893
  if (rawExistingFields.length) {
880
894
  this.updateFieldsRaw(model, rawExistingFields, up, down);
881
895
  }
882
- const existingFields = model.fields.filter((field) => !field.generateAs && this.hasChanged(model, field));
896
+ const existingFields = model.fields.filter(
897
+ (field) => (!field.generateAs || field.generateAs.type === "expression") && this.hasChanged(model, field)
898
+ );
883
899
  this.updateFields(model, existingFields, up, down);
884
900
  }
885
901
  if (isUpdatableModel(model)) {
@@ -903,7 +919,7 @@ var MigrationGenerator = class {
903
919
  writer.writeLine(`deleteRootType: row.deleteRootType,`);
904
920
  writer.writeLine(`deleteRootId: row.deleteRootId,`);
905
921
  }
906
- for (const { name: name2, kind } of model.fields.filter(isUpdatableField)) {
922
+ for (const { name: name2, kind } of model.fields.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"))) {
907
923
  const col = kind === "relation" ? `${name2}Id` : name2;
908
924
  writer.writeLine(`${col}: row.${col},`);
909
925
  }
@@ -917,8 +933,14 @@ var MigrationGenerator = class {
917
933
  });
918
934
  } else {
919
935
  const revisionTable = `${model.name}Revision`;
936
+ this.renameFields(
937
+ revisionTable,
938
+ model.fields.filter(isUpdatableField).filter(not(isInherited)).filter(({ oldName }) => oldName),
939
+ up,
940
+ down
941
+ );
920
942
  const missingRevisionFields = model.fields.filter(isUpdatableField).filter(
921
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
943
+ ({ name: name2, ...field }) => field.kind !== "custom" && !(field.generateAs?.type === "expression") && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
922
944
  );
923
945
  this.createRevisionFields(model, missingRevisionFields, up, down);
924
946
  const revisionFieldsToRemove = model.fields.filter(
@@ -984,13 +1006,13 @@ var MigrationGenerator = class {
984
1006
  this.migration("down", down.reverse());
985
1007
  return writer.toString();
986
1008
  }
987
- renameFields(model, fields2, up, down) {
1009
+ renameFields(tableName, fields2, up, down) {
988
1010
  if (!fields2.length) {
989
1011
  return;
990
1012
  }
991
1013
  up.push(() => {
992
1014
  for (const field of fields2) {
993
- this.alterTable(model.name, () => {
1015
+ this.alterTable(tableName, () => {
994
1016
  this.renameColumn(
995
1017
  field.kind === "relation" ? `${field.oldName}Id` : get(field, "oldName"),
996
1018
  field.kind === "relation" ? `${field.name}Id` : field.name
@@ -1000,7 +1022,7 @@ var MigrationGenerator = class {
1000
1022
  });
1001
1023
  down.push(() => {
1002
1024
  for (const field of fields2) {
1003
- this.alterTable(model.name, () => {
1025
+ this.alterTable(tableName, () => {
1004
1026
  this.renameColumn(
1005
1027
  field.kind === "relation" ? `${field.name}Id` : field.name,
1006
1028
  field.kind === "relation" ? `${field.oldName}Id` : get(field, "oldName")
@@ -1009,7 +1031,7 @@ var MigrationGenerator = class {
1009
1031
  }
1010
1032
  });
1011
1033
  for (const field of fields2) {
1012
- summonByName(this.columns[model.name], field.kind === "relation" ? `${field.oldName}Id` : field.oldName).name = field.kind === "relation" ? `${field.name}Id` : field.name;
1034
+ summonByName(this.columns[tableName], field.kind === "relation" ? `${field.oldName}Id` : field.oldName).name = field.kind === "relation" ? `${field.name}Id` : field.name;
1013
1035
  }
1014
1036
  }
1015
1037
  createFields(model, fields2, up, down) {
@@ -1021,6 +1043,9 @@ var MigrationGenerator = class {
1021
1043
  const updates = [];
1022
1044
  const postAlter = [];
1023
1045
  for (const field of fields2) {
1046
+ if (field.generateAs?.type === "expression") {
1047
+ continue;
1048
+ }
1024
1049
  alter.push(() => this.column(field, { setNonNull: field.defaultValue !== void 0 }));
1025
1050
  if (field.generateAs) {
1026
1051
  continue;
@@ -1073,7 +1098,7 @@ var MigrationGenerator = class {
1073
1098
  });
1074
1099
  });
1075
1100
  if (isUpdatableModel(model)) {
1076
- const updatableFields = fields2.filter(isUpdatableField);
1101
+ const updatableFields = fields2.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"));
1077
1102
  if (!updatableFields.length) {
1078
1103
  return;
1079
1104
  }
@@ -1121,7 +1146,7 @@ var MigrationGenerator = class {
1121
1146
  });
1122
1147
  });
1123
1148
  if (isUpdatableModel(model)) {
1124
- const updatableFields = fields2.filter(isUpdatableField);
1149
+ const updatableFields = fields2.filter(isUpdatableField).filter((f) => !(f.generateAs?.type === "expression"));
1125
1150
  if (!updatableFields.length) {
1126
1151
  return;
1127
1152
  }
@@ -1159,7 +1184,7 @@ var MigrationGenerator = class {
1159
1184
  writer.writeLine(`table.uuid('deleteRootId');`);
1160
1185
  }
1161
1186
  }
1162
- for (const field of model.fields.filter(and(isUpdatableField, not(isInherited)))) {
1187
+ for (const field of model.fields.filter(and(isUpdatableField, not(isInherited))).filter((f) => !(f.generateAs?.type === "expression"))) {
1163
1188
  this.column(field, { setUnique: false, setDefault: false });
1164
1189
  }
1165
1190
  });
@@ -1173,14 +1198,19 @@ var MigrationGenerator = class {
1173
1198
  this.column(field, { setUnique: false, setNonNull: false, setDefault: false });
1174
1199
  }
1175
1200
  });
1176
- this.writer.write(`await knex('${model.name}Revision').update(`).inlineBlock(() => {
1177
- for (const { name: name2, kind: type } of missingRevisionFields) {
1178
- const col = type === "relation" ? `${name2}Id` : name2;
1179
- this.writer.write(
1180
- `${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${model.name}Revision"."${typeToField(model.name)}Id")'),`
1181
- ).newLine();
1182
- }
1183
- }).write(");").newLine().blankLine();
1201
+ const revisionFieldsWithDataToCopy = missingRevisionFields.filter(
1202
+ (field) => this.columns[model.name].find((col) => col.name === getColumnName(field)) || field.defaultValue !== void 0 || field.nonNull
1203
+ );
1204
+ if (revisionFieldsWithDataToCopy.length) {
1205
+ this.writer.write(`await knex('${model.name}Revision').update(`).inlineBlock(() => {
1206
+ for (const { name: name2, kind: type } of revisionFieldsWithDataToCopy) {
1207
+ const col = type === "relation" ? `${name2}Id` : name2;
1208
+ this.writer.write(
1209
+ `${col}: knex.raw('(select "${col}" from "${model.name}" where "${model.name}".id = "${model.name}Revision"."${typeToField(model.name)}Id")'),`
1210
+ ).newLine();
1211
+ }
1212
+ }).write(");").newLine().blankLine();
1213
+ }
1184
1214
  const nonNullableMissingRevisionFields = missingRevisionFields.filter(({ nonNull: nonNull2 }) => nonNull2);
1185
1215
  if (nonNullableMissingRevisionFields.length) {
1186
1216
  this.alterTable(revisionTable, () => {
@@ -1236,7 +1266,7 @@ var MigrationGenerator = class {
1236
1266
  return this.writer.writeLine(`await knex.schema.renameTable('${from}', '${to}');`).blankLine();
1237
1267
  }
1238
1268
  renameColumn(from, to) {
1239
- this.writer.writeLine(`table.renameColumn('${from}', '${to}')`);
1269
+ this.writer.writeLine(`table.renameColumn('${from}', '${to}');`);
1240
1270
  }
1241
1271
  value(value2) {
1242
1272
  if (typeof value2 === "string") {
@@ -1261,6 +1291,9 @@ var MigrationGenerator = class {
1261
1291
  };
1262
1292
  const kind = field.kind;
1263
1293
  if (field.generateAs) {
1294
+ if (field.generateAs.type === "expression") {
1295
+ throw new Error(`Expression fields cannot be created in SQL schema.`);
1296
+ }
1264
1297
  let type = "";
1265
1298
  switch (kind) {
1266
1299
  case void 0:
@@ -1269,6 +1302,9 @@ var MigrationGenerator = class {
1269
1302
  case "Float":
1270
1303
  type = `decimal(${field.precision ?? "undefined"}, ${field.scale ?? "undefined"})`;
1271
1304
  break;
1305
+ case "Boolean":
1306
+ type = "boolean";
1307
+ break;
1272
1308
  default:
1273
1309
  throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
1274
1310
  }
@@ -1288,10 +1324,10 @@ var MigrationGenerator = class {
1288
1324
  this.writer.write(`, ALTER COLUMN "${name2}" DROP NOT NULL`);
1289
1325
  }
1290
1326
  }
1291
- this.writer.write(`, ALTER COLUMN "${name2}" SET EXPRESSION AS (${field.generateAs})`);
1327
+ this.writer.write(`, ALTER COLUMN "${name2}" SET EXPRESSION AS (${field.generateAs.expression})`);
1292
1328
  } else {
1293
1329
  this.writer.write(
1294
- `${alter ? "ALTER" : "ADD"} COLUMN "${name2}" ${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs}) STORED`
1330
+ `${alter ? "ALTER" : "ADD"} COLUMN "${name2}" ${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs.expression}) STORED`
1295
1331
  );
1296
1332
  }
1297
1333
  return;
@@ -1315,6 +1351,9 @@ var MigrationGenerator = class {
1315
1351
  };
1316
1352
  const kind = field.kind;
1317
1353
  if (field.generateAs) {
1354
+ if (field.generateAs.type === "expression") {
1355
+ throw new Error(`Expression fields cannot be created in SQL schema.`);
1356
+ }
1318
1357
  let type = "";
1319
1358
  switch (kind) {
1320
1359
  case void 0:
@@ -1323,6 +1362,9 @@ var MigrationGenerator = class {
1323
1362
  case "Float":
1324
1363
  type = `decimal(${field.precision ?? "undefined"}, ${field.scale ?? "undefined"})`;
1325
1364
  break;
1365
+ case "Boolean":
1366
+ type = "boolean";
1367
+ break;
1326
1368
  default:
1327
1369
  throw new Error(`Generated columns of kind ${kind} and type ${field.type} are not supported yet.`);
1328
1370
  }
@@ -1331,7 +1373,7 @@ var MigrationGenerator = class {
1331
1373
  throw new Error(`Generated columns of kind ${kind} are not supported yet.`);
1332
1374
  }
1333
1375
  this.writer.write(
1334
- `table.specificType('${name2}', '${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs}) STORED')`
1376
+ `table.specificType('${name2}', '${type}${nonNull2() ? " not null" : ""} GENERATED ALWAYS AS (${field.generateAs.expression}) ${field.generateAs.type === "virtual" ? "VIRTUAL" : "STORED"}')`
1335
1377
  );
1336
1378
  if (alter) {
1337
1379
  this.writer.write(".alter()");
@@ -1429,14 +1471,17 @@ var MigrationGenerator = class {
1429
1471
  return this.columns[tableName].find((col) => col.name === columnName);
1430
1472
  }
1431
1473
  hasChanged(model, field) {
1474
+ if (field.generateAs?.type === "expression") {
1475
+ return false;
1476
+ }
1432
1477
  const col = this.getColumn(model.name, field.kind === "relation" ? `${field.name}Id` : field.name);
1433
1478
  if (!col) {
1434
1479
  return false;
1435
1480
  }
1436
1481
  if (field.generateAs) {
1437
- if (col.generation_expression !== field.generateAs) {
1482
+ if (col.generation_expression !== field.generateAs.expression) {
1438
1483
  throw new Error(
1439
- `Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs}`
1484
+ `Column ${col.name} has specific type ${col.generation_expression} but expected ${field.generateAs.expression}`
1440
1485
  );
1441
1486
  }
1442
1487
  }
@@ -1478,6 +1523,182 @@ var MigrationGenerator = class {
1478
1523
  }
1479
1524
  return false;
1480
1525
  }
1526
+ normalizeFunctionBody(body) {
1527
+ return body.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").trim();
1528
+ }
1529
+ normalizeAggregateDefinition(definition) {
1530
+ let normalized = definition.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").trim();
1531
+ const initCondMatch = normalized.match(/INITCOND\s*=\s*([^,)]+)/i);
1532
+ if (initCondMatch) {
1533
+ const initCondValue = initCondMatch[1].trim();
1534
+ const unquoted = initCondValue.replace(/^['"]|['"]$/g, "");
1535
+ if (/^\d+$/.test(unquoted)) {
1536
+ normalized = normalized.replace(/INITCOND\s*=\s*[^,)]+/i, `INITCOND = '${unquoted}'`);
1537
+ }
1538
+ }
1539
+ return normalized;
1540
+ }
1541
+ extractFunctionBody(definition) {
1542
+ const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
1543
+ if (dollarQuoteMatch) {
1544
+ return dollarQuoteMatch[2].trim();
1545
+ }
1546
+ const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
1547
+ if (bodyMatch) {
1548
+ return bodyMatch[1].trim();
1549
+ }
1550
+ return definition;
1551
+ }
1552
+ async getDatabaseFunctions() {
1553
+ const regularFunctions = await this.knex.raw(`
1554
+ SELECT
1555
+ p.proname as name,
1556
+ pg_get_function_identity_arguments(p.oid) as arguments,
1557
+ pg_get_functiondef(p.oid) as definition,
1558
+ false as is_aggregate
1559
+ FROM pg_proc p
1560
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1561
+ WHERE n.nspname = 'public'
1562
+ AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
1563
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1564
+ `);
1565
+ const aggregateFunctions = await this.knex.raw(`
1566
+ SELECT
1567
+ p.proname as name,
1568
+ pg_get_function_identity_arguments(p.oid) as arguments,
1569
+ a.aggtransfn::regproc::text as trans_func,
1570
+ a.aggfinalfn::regproc::text as final_func,
1571
+ a.agginitval as init_val,
1572
+ pg_catalog.format_type(a.aggtranstype, NULL) as state_type,
1573
+ true as is_aggregate
1574
+ FROM pg_proc p
1575
+ JOIN pg_aggregate a ON p.oid = a.aggfnoid
1576
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1577
+ WHERE n.nspname = 'public'
1578
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1579
+ `);
1580
+ const result = [];
1581
+ for (const row of regularFunctions.rows || []) {
1582
+ const definition = row.definition || "";
1583
+ const name2 = row.name || "";
1584
+ const argumentsStr = row.arguments || "";
1585
+ if (!definition) {
1586
+ continue;
1587
+ }
1588
+ const signature = `${name2}(${argumentsStr})`;
1589
+ const body = this.normalizeFunctionBody(this.extractFunctionBody(definition));
1590
+ result.push({
1591
+ name: name2,
1592
+ signature,
1593
+ body,
1594
+ isAggregate: false,
1595
+ definition
1596
+ });
1597
+ }
1598
+ for (const row of aggregateFunctions.rows || []) {
1599
+ const name2 = row.name || "";
1600
+ const argumentsStr = row.arguments || "";
1601
+ const transFunc = row.trans_func || "";
1602
+ const finalFunc = row.final_func || "";
1603
+ const initVal = row.init_val;
1604
+ const stateType = row.state_type || "";
1605
+ const signature = `${name2}(${argumentsStr})`;
1606
+ let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (`;
1607
+ aggregateDef += `SFUNC = ${transFunc}, STYPE = ${stateType}`;
1608
+ if (finalFunc) {
1609
+ aggregateDef += `, FINALFUNC = ${finalFunc}`;
1610
+ }
1611
+ if (initVal !== null && initVal !== void 0) {
1612
+ let initValStr;
1613
+ if (typeof initVal === "string") {
1614
+ initValStr = `'${initVal}'`;
1615
+ } else {
1616
+ const numStr = String(initVal);
1617
+ initValStr = /^\d+$/.test(numStr) ? `'${numStr}'` : numStr;
1618
+ }
1619
+ aggregateDef += `, INITCOND = ${initValStr}`;
1620
+ }
1621
+ aggregateDef += ");";
1622
+ result.push({
1623
+ name: name2,
1624
+ signature,
1625
+ body: this.normalizeAggregateDefinition(aggregateDef),
1626
+ isAggregate: true,
1627
+ definition: aggregateDef
1628
+ });
1629
+ }
1630
+ return result;
1631
+ }
1632
+ async handleFunctions(up, down) {
1633
+ if (!this.parsedFunctions || this.parsedFunctions.length === 0) {
1634
+ return;
1635
+ }
1636
+ const definedFunctions = this.parsedFunctions;
1637
+ const dbFunctions = await this.getDatabaseFunctions();
1638
+ const dbFunctionsBySignature = /* @__PURE__ */ new Map();
1639
+ for (const func of dbFunctions) {
1640
+ dbFunctionsBySignature.set(func.signature, func);
1641
+ }
1642
+ const definedFunctionsBySignature = /* @__PURE__ */ new Map();
1643
+ for (const func of definedFunctions) {
1644
+ definedFunctionsBySignature.set(func.signature, func);
1645
+ }
1646
+ const functionsToRestore = [];
1647
+ for (const definedFunc of definedFunctions) {
1648
+ const dbFunc = dbFunctionsBySignature.get(definedFunc.signature);
1649
+ if (!dbFunc) {
1650
+ up.push(() => {
1651
+ this.writer.writeLine(`await knex.raw(\`${definedFunc.fullDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
1652
+ });
1653
+ down.push(() => {
1654
+ const isAggregate = definedFunc.isAggregate;
1655
+ const dropMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?(FUNCTION|AGGREGATE)\s+([^(]+)\(/i);
1656
+ if (dropMatch) {
1657
+ const functionName = dropMatch[3].trim();
1658
+ const argsMatch = definedFunc.fullDefinition.match(
1659
+ /CREATE\s+(OR\s+REPLACE\s+)?(FUNCTION|AGGREGATE)\s+[^(]+\(([^)]*)\)/i
1660
+ );
1661
+ const args2 = argsMatch ? argsMatch[3].trim() : "";
1662
+ const dropType = isAggregate ? "AGGREGATE" : "FUNCTION";
1663
+ this.writer.writeLine(`await knex.raw(\`DROP ${dropType} IF EXISTS ${functionName}${args2 ? `(${args2})` : ""}\`);`).blankLine();
1664
+ }
1665
+ });
1666
+ } else {
1667
+ const dbBody = dbFunc.isAggregate ? this.normalizeAggregateDefinition(dbFunc.body) : this.normalizeFunctionBody(dbFunc.body);
1668
+ const definedBody = definedFunc.isAggregate ? this.normalizeAggregateDefinition(definedFunc.body) : this.normalizeFunctionBody(definedFunc.body);
1669
+ if (dbBody !== definedBody) {
1670
+ const oldDefinition = dbFunc.definition || dbFunc.body;
1671
+ up.push(() => {
1672
+ this.writer.writeLine(`await knex.raw(\`${definedFunc.fullDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
1673
+ });
1674
+ down.push(() => {
1675
+ if (oldDefinition) {
1676
+ this.writer.writeLine(`await knex.raw(\`${oldDefinition.replace(/`/g, "\\`")}\`);`).blankLine();
1677
+ }
1678
+ });
1679
+ }
1680
+ }
1681
+ }
1682
+ for (const dbFunc of dbFunctions) {
1683
+ if (!definedFunctionsBySignature.has(dbFunc.signature)) {
1684
+ const definition = dbFunc.definition || dbFunc.body;
1685
+ if (definition) {
1686
+ functionsToRestore.push({ func: dbFunc, definition });
1687
+ down.push(() => {
1688
+ const argsMatch = dbFunc.signature.match(/\(([^)]*)\)/);
1689
+ const args2 = argsMatch ? argsMatch[1] : "";
1690
+ const dropType = dbFunc.isAggregate ? "AGGREGATE" : "FUNCTION";
1691
+ this.writer.writeLine(`await knex.raw(\`DROP ${dropType} IF EXISTS ${dbFunc.name}${args2 ? `(${args2})` : ""}\`);`).blankLine();
1692
+ });
1693
+ }
1694
+ }
1695
+ }
1696
+ for (const { definition } of functionsToRestore) {
1697
+ up.push(() => {
1698
+ this.writer.writeLine(`await knex.raw(\`${definition.replace(/`/g, "\\`")}\`);`).blankLine();
1699
+ });
1700
+ }
1701
+ }
1481
1702
  };
1482
1703
  var getMigrationDate = () => {
1483
1704
  const date = /* @__PURE__ */ new Date();
@@ -1490,21 +1711,258 @@ var getMigrationDate = () => {
1490
1711
  return `${year}${month}${day}${hours}${minutes}${seconds}`;
1491
1712
  };
1492
1713
 
1493
- // src/resolvers/utils.ts
1494
- var import_graphql3 = require("graphql");
1495
- var import_isEqual = __toESM(require("lodash/isEqual"), 1);
1496
- var getColumnName = (field) => field.kind === "relation" ? field.foreignKey || `${field.name}Id` : field.name;
1714
+ // src/migrations/generate-functions.ts
1715
+ var generateFunctionsFromDatabase = async (knex2) => {
1716
+ const regularFunctions = await knex2.raw(`
1717
+ SELECT
1718
+ pg_get_functiondef(p.oid) as definition
1719
+ FROM pg_proc p
1720
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1721
+ WHERE n.nspname = 'public'
1722
+ AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
1723
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1724
+ `);
1725
+ const aggregateFunctions = await knex2.raw(`
1726
+ SELECT
1727
+ p.proname as name,
1728
+ pg_get_function_identity_arguments(p.oid) as arguments,
1729
+ a.aggtransfn::regproc::text as trans_func,
1730
+ a.aggfinalfn::regproc::text as final_func,
1731
+ a.agginitval as init_val,
1732
+ pg_catalog.format_type(a.aggtranstype, NULL) as state_type
1733
+ FROM pg_proc p
1734
+ JOIN pg_aggregate a ON p.oid = a.aggfnoid
1735
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1736
+ WHERE n.nspname = 'public'
1737
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1738
+ `);
1739
+ const functions = [];
1740
+ for (const row of regularFunctions.rows || []) {
1741
+ if (row.definition) {
1742
+ functions.push(row.definition.trim());
1743
+ }
1744
+ }
1745
+ for (const row of aggregateFunctions.rows || []) {
1746
+ const name2 = row.name || "";
1747
+ const argumentsStr = row.arguments || "";
1748
+ const transFunc = row.trans_func || "";
1749
+ const finalFunc = row.final_func || "";
1750
+ const initVal = row.init_val;
1751
+ const stateType = row.state_type || "";
1752
+ if (!name2 || !transFunc || !stateType) {
1753
+ continue;
1754
+ }
1755
+ let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (
1756
+ `;
1757
+ aggregateDef += ` SFUNC = ${transFunc},
1758
+ `;
1759
+ aggregateDef += ` STYPE = ${stateType}`;
1760
+ if (finalFunc) {
1761
+ aggregateDef += `,
1762
+ FINALFUNC = ${finalFunc}`;
1763
+ }
1764
+ if (initVal !== null && initVal !== void 0) {
1765
+ const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
1766
+ aggregateDef += `,
1767
+ INITCOND = ${initValStr}`;
1768
+ }
1769
+ aggregateDef += "\n);";
1770
+ functions.push(aggregateDef);
1771
+ }
1772
+ if (functions.length === 0) {
1773
+ return `export const functions: string[] = [];
1774
+ `;
1775
+ }
1776
+ const functionsArrayString = functions.map((func) => ` ${JSON.stringify(func)}`).join(",\n");
1777
+ return `export const functions: string[] = [
1778
+ ${functionsArrayString},
1779
+ ];
1780
+ `;
1781
+ };
1782
+
1783
+ // src/migrations/update-functions.ts
1784
+ var normalizeWhitespace = (str) => {
1785
+ return str.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").replace(/\s*;\s*/g, ";").trim();
1786
+ };
1787
+ var normalizeFunctionBody = (body) => {
1788
+ return normalizeWhitespace(body);
1789
+ };
1790
+ var extractFunctionBody = (definition) => {
1791
+ const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
1792
+ if (dollarQuoteMatch) {
1793
+ return dollarQuoteMatch[2].trim();
1794
+ }
1795
+ const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
1796
+ if (bodyMatch) {
1797
+ return bodyMatch[1].trim();
1798
+ }
1799
+ return definition;
1800
+ };
1801
+ var getDatabaseFunctions = async (knex2) => {
1802
+ const regularFunctions = await knex2.raw(`
1803
+ SELECT
1804
+ p.proname as name,
1805
+ pg_get_function_identity_arguments(p.oid) as arguments,
1806
+ pg_get_functiondef(p.oid) as definition,
1807
+ false as is_aggregate
1808
+ FROM pg_proc p
1809
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1810
+ WHERE n.nspname = 'public'
1811
+ AND NOT EXISTS (SELECT 1 FROM pg_aggregate a WHERE a.aggfnoid = p.oid)
1812
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1813
+ `);
1814
+ const aggregateFunctions = await knex2.raw(`
1815
+ SELECT
1816
+ p.proname as name,
1817
+ pg_get_function_identity_arguments(p.oid) as arguments,
1818
+ a.aggtransfn::regproc::text as trans_func,
1819
+ a.aggfinalfn::regproc::text as final_func,
1820
+ a.agginitval as init_val,
1821
+ pg_catalog.format_type(a.aggtranstype, NULL) as state_type,
1822
+ true as is_aggregate
1823
+ FROM pg_proc p
1824
+ JOIN pg_aggregate a ON p.oid = a.aggfnoid
1825
+ JOIN pg_namespace n ON p.pronamespace = n.oid
1826
+ WHERE n.nspname = 'public'
1827
+ ORDER BY p.proname, pg_get_function_identity_arguments(p.oid)
1828
+ `);
1829
+ const result = [];
1830
+ for (const row of regularFunctions.rows || []) {
1831
+ const definition = row.definition || "";
1832
+ const name2 = row.name || "";
1833
+ const argumentsStr = row.arguments || "";
1834
+ if (!definition) {
1835
+ continue;
1836
+ }
1837
+ const signature = `${name2}(${argumentsStr})`;
1838
+ const body = normalizeFunctionBody(extractFunctionBody(definition));
1839
+ result.push({
1840
+ name: name2,
1841
+ signature,
1842
+ body,
1843
+ isAggregate: false,
1844
+ definition
1845
+ });
1846
+ }
1847
+ for (const row of aggregateFunctions.rows || []) {
1848
+ const name2 = row.name || "";
1849
+ const argumentsStr = row.arguments || "";
1850
+ const transFunc = row.trans_func || "";
1851
+ const finalFunc = row.final_func || "";
1852
+ const initVal = row.init_val;
1853
+ const stateType = row.state_type || "";
1854
+ const signature = `${name2}(${argumentsStr})`;
1855
+ let aggregateDef = `CREATE AGGREGATE ${name2}(${argumentsStr}) (`;
1856
+ aggregateDef += `SFUNC = ${transFunc}, STYPE = ${stateType}`;
1857
+ if (finalFunc) {
1858
+ aggregateDef += `, FINALFUNC = ${finalFunc}`;
1859
+ }
1860
+ if (initVal !== null && initVal !== void 0) {
1861
+ const initValStr = typeof initVal === "string" ? `'${initVal}'` : String(initVal);
1862
+ aggregateDef += `, INITCOND = ${initValStr}`;
1863
+ }
1864
+ aggregateDef += ");";
1865
+ result.push({
1866
+ name: name2,
1867
+ signature,
1868
+ body: normalizeFunctionBody(aggregateDef),
1869
+ isAggregate: true,
1870
+ definition: aggregateDef
1871
+ });
1872
+ }
1873
+ return result;
1874
+ };
1875
+ var compareFunctions = (defined, db) => {
1876
+ const definedBody = normalizeFunctionBody(defined.body);
1877
+ const dbBody = normalizeFunctionBody(db.body);
1878
+ if (definedBody !== dbBody) {
1879
+ const definedPreview = definedBody.length > 200 ? `${definedBody.substring(0, 200)}...` : definedBody;
1880
+ const dbPreview = dbBody.length > 200 ? `${dbBody.substring(0, 200)}...` : dbBody;
1881
+ return {
1882
+ changed: true,
1883
+ diff: `Definition changed:
1884
+ File: ${definedPreview}
1885
+ DB: ${dbPreview}`
1886
+ };
1887
+ }
1888
+ return { changed: false };
1889
+ };
1890
+ var updateFunctions = async (knex2, parsedFunctions) => {
1891
+ if (parsedFunctions.length === 0) {
1892
+ return;
1893
+ }
1894
+ const definedFunctions = parsedFunctions;
1895
+ const dbFunctions = await getDatabaseFunctions(knex2);
1896
+ const dbFunctionsBySignature = /* @__PURE__ */ new Map();
1897
+ for (const func of dbFunctions) {
1898
+ dbFunctionsBySignature.set(func.signature, func);
1899
+ }
1900
+ console.info(`Found ${definedFunctions.length} function(s) in file, ${dbFunctions.length} function(s) in database.`);
1901
+ let updatedCount = 0;
1902
+ let skippedCount = 0;
1903
+ for (const definedFunc of definedFunctions) {
1904
+ const dbFunc = dbFunctionsBySignature.get(definedFunc.signature);
1905
+ if (!dbFunc) {
1906
+ try {
1907
+ await knex2.raw(definedFunc.fullDefinition);
1908
+ console.info(`\u2713 Created ${definedFunc.isAggregate ? "aggregate" : "function"}: ${definedFunc.signature}`);
1909
+ updatedCount++;
1910
+ } catch (error) {
1911
+ console.error(
1912
+ `\u2717 Failed to create ${definedFunc.isAggregate ? "aggregate" : "function"} ${definedFunc.signature}:`,
1913
+ error.message
1914
+ );
1915
+ throw error;
1916
+ }
1917
+ } else {
1918
+ const comparison = compareFunctions(definedFunc, dbFunc);
1919
+ if (comparison.changed) {
1920
+ console.info(`
1921
+ \u26A0 ${definedFunc.isAggregate ? "Aggregate" : "Function"} ${definedFunc.signature} has changes:`);
1922
+ if (comparison.diff) {
1923
+ console.info(comparison.diff);
1924
+ }
1925
+ try {
1926
+ if (definedFunc.isAggregate) {
1927
+ const dropMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+([^(]+)\(/i);
1928
+ if (dropMatch) {
1929
+ const functionName = dropMatch[2].trim();
1930
+ const argsMatch = definedFunc.fullDefinition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+[^(]+\(([^)]*)\)/i);
1931
+ const args2 = argsMatch ? argsMatch[2].trim() : "";
1932
+ await knex2.raw(`DROP AGGREGATE IF EXISTS ${functionName}${args2 ? `(${args2})` : ""}`);
1933
+ }
1934
+ }
1935
+ await knex2.raw(definedFunc.fullDefinition);
1936
+ console.info(`\u2713 Updated ${definedFunc.isAggregate ? "aggregate" : "function"}: ${definedFunc.signature}
1937
+ `);
1938
+ updatedCount++;
1939
+ } catch (error) {
1940
+ console.error(
1941
+ `\u2717 Failed to update ${definedFunc.isAggregate ? "aggregate" : "function"} ${definedFunc.signature}:`,
1942
+ error.message
1943
+ );
1944
+ throw error;
1945
+ }
1946
+ } else {
1947
+ console.info(
1948
+ `\u25CB Skipped ${definedFunc.isAggregate ? "aggregate" : "function"} (unchanged): ${definedFunc.signature}`
1949
+ );
1950
+ skippedCount++;
1951
+ }
1952
+ }
1953
+ }
1954
+ console.info(`
1955
+ Summary: ${updatedCount} updated, ${skippedCount} skipped`);
1956
+ if (updatedCount > 0) {
1957
+ console.info("Functions updated successfully.");
1958
+ } else {
1959
+ console.info("All functions are up to date.");
1960
+ }
1961
+ };
1497
1962
 
1498
1963
  // src/permissions/generate.ts
1499
1964
  var ACTIONS = ["READ", "CREATE", "UPDATE", "DELETE", "RESTORE", "LINK"];
1500
1965
 
1501
- // src/resolvers/arguments.ts
1502
- var import_graphql4 = require("graphql");
1503
-
1504
- // src/resolvers/resolver.ts
1505
- var import_cloneDeep2 = __toESM(require("lodash/cloneDeep"), 1);
1506
- var import_flatMap = __toESM(require("lodash/flatMap"), 1);
1507
-
1508
1966
  // src/schema/generate.ts
1509
1967
  var import_graphql6 = require("graphql");
1510
1968
 
@@ -2082,6 +2540,14 @@ var DEFAULTS = {
2082
2540
  ensureFileExists(path, EMPTY_MODELS);
2083
2541
  }
2084
2542
  },
2543
+ functionsPath: {
2544
+ question: "What is the PostgreSQL functions file path?",
2545
+ defaultValue: "src/config/functions.ts",
2546
+ init: (path) => {
2547
+ ensureFileExists(path, `export const functions: string[] = [];
2548
+ `);
2549
+ }
2550
+ },
2085
2551
  generatedFolderPath: {
2086
2552
  question: "What is the path for generated stuff?",
2087
2553
  defaultValue: "src/generated",
@@ -2261,7 +2727,8 @@ var generateGraphqlClientTypes = async () => {
2261
2727
  });
2262
2728
  };
2263
2729
 
2264
- // src/bin/gqm/parse-knexfile.ts
2730
+ // src/bin/gqm/parse-functions.ts
2731
+ var import_fs2 = require("fs");
2265
2732
  var import_ts_morph4 = require("ts-morph");
2266
2733
 
2267
2734
  // src/bin/gqm/static-eval.ts
@@ -2646,13 +3113,116 @@ var findDeclaration = (syntaxList, name2) => {
2646
3113
  }
2647
3114
  };
2648
3115
 
2649
- // src/bin/gqm/parse-knexfile.ts
2650
- var parseKnexfile = async () => {
3116
+ // src/bin/gqm/parse-functions.ts
3117
+ var normalizeWhitespace2 = (str) => {
3118
+ return str.replace(/\s+/g, " ").replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\s*,\s*/g, ",").replace(/\s*;\s*/g, ";").trim();
3119
+ };
3120
+ var normalizeFunctionBody2 = (body) => {
3121
+ return normalizeWhitespace2(body);
3122
+ };
3123
+ var normalizeAggregateDefinition = (definition) => {
3124
+ let normalized = normalizeWhitespace2(definition);
3125
+ const initCondMatch = normalized.match(/INITCOND\s*=\s*([^,)]+)/i);
3126
+ if (initCondMatch) {
3127
+ const initCondValue = initCondMatch[1].trim();
3128
+ const unquoted = initCondValue.replace(/^['"]|['"]$/g, "");
3129
+ if (/^\d+$/.test(unquoted)) {
3130
+ normalized = normalized.replace(/INITCOND\s*=\s*[^,)]+/i, `INITCOND = '${unquoted}'`);
3131
+ }
3132
+ }
3133
+ return normalized;
3134
+ };
3135
+ var extractFunctionSignature = (definition, isAggregate) => {
3136
+ if (isAggregate) {
3137
+ const createMatch2 = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+([^(]+)\(/i);
3138
+ if (!createMatch2) {
3139
+ return null;
3140
+ }
3141
+ const functionNamePart2 = createMatch2[2].trim().replace(/^[^.]+\./, "");
3142
+ const argsMatch = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE\s+[^(]+\(([^)]*)\)/i);
3143
+ const args3 = argsMatch ? argsMatch[2].trim() : "";
3144
+ return `${functionNamePart2}(${args3})`;
3145
+ }
3146
+ const createMatch = definition.match(/CREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+([^(]+)\(/i);
3147
+ if (!createMatch) {
3148
+ return null;
3149
+ }
3150
+ const functionNamePart = createMatch[2].trim().replace(/^[^.]+\./, "");
3151
+ const fullArgsMatch = definition.match(
3152
+ /CREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+[^(]+\(([\s\S]*?)\)\s*(RETURNS|LANGUAGE|AS|STRICT|IMMUTABLE|STABLE|VOLATILE|SECURITY)/i
3153
+ );
3154
+ if (!fullArgsMatch) {
3155
+ return null;
3156
+ }
3157
+ const argsSection = fullArgsMatch[2].trim();
3158
+ const args2 = argsSection.split(/\s*,\s*/).map((arg) => {
3159
+ return arg.trim().replace(/\s+/g, " ");
3160
+ }).join(", ");
3161
+ return `${functionNamePart}(${args2})`;
3162
+ };
3163
+ var extractFunctionBody2 = (definition) => {
3164
+ const dollarQuoteMatch = definition.match(/AS\s+\$([^$]*)\$([\s\S]*?)\$\1\$/i);
3165
+ if (dollarQuoteMatch) {
3166
+ return dollarQuoteMatch[2].trim();
3167
+ }
3168
+ const bodyMatch = definition.match(/AS\s+\$\$([\s\S]*?)\$\$/i) || definition.match(/AS\s+['"]([\s\S]*?)['"]/i);
3169
+ if (bodyMatch) {
3170
+ return bodyMatch[1].trim();
3171
+ }
3172
+ return definition;
3173
+ };
3174
+ var parseFunctionsFile = (filePath) => {
3175
+ if (!(0, import_fs2.existsSync)(filePath)) {
3176
+ return [];
3177
+ }
2651
3178
  const project = new import_ts_morph4.Project({
2652
3179
  manipulationSettings: {
2653
3180
  indentationText: import_ts_morph4.IndentationText.TwoSpaces
2654
3181
  }
2655
3182
  });
3183
+ const sourceFile = project.addSourceFileAtPath(filePath);
3184
+ try {
3185
+ const functionsDeclaration = findDeclarationInFile(sourceFile, "functions");
3186
+ const functionsArray = staticEval(functionsDeclaration, {});
3187
+ if (!Array.isArray(functionsArray)) {
3188
+ return [];
3189
+ }
3190
+ const parsedFunctions = [];
3191
+ for (const definition of functionsArray) {
3192
+ if (!definition || typeof definition !== "string") {
3193
+ continue;
3194
+ }
3195
+ const trimmedDefinition = definition.trim();
3196
+ const isAggregate = /CREATE\s+(OR\s+REPLACE\s+)?AGGREGATE/i.test(trimmedDefinition);
3197
+ const signature = extractFunctionSignature(trimmedDefinition, isAggregate);
3198
+ if (!signature) {
3199
+ continue;
3200
+ }
3201
+ const nameMatch = signature.match(/^([^(]+)\(/);
3202
+ const name2 = nameMatch ? nameMatch[1].trim().split(".").pop() || "" : "";
3203
+ const body = isAggregate ? trimmedDefinition : extractFunctionBody2(trimmedDefinition);
3204
+ parsedFunctions.push({
3205
+ name: name2,
3206
+ signature,
3207
+ body: isAggregate ? normalizeAggregateDefinition(body) : normalizeFunctionBody2(body),
3208
+ fullDefinition: trimmedDefinition,
3209
+ isAggregate
3210
+ });
3211
+ }
3212
+ return parsedFunctions;
3213
+ } catch (error) {
3214
+ return [];
3215
+ }
3216
+ };
3217
+
3218
+ // src/bin/gqm/parse-knexfile.ts
3219
+ var import_ts_morph5 = require("ts-morph");
3220
+ var parseKnexfile = async () => {
3221
+ const project = new import_ts_morph5.Project({
3222
+ manipulationSettings: {
3223
+ indentationText: import_ts_morph5.IndentationText.TwoSpaces
3224
+ }
3225
+ });
2656
3226
  const knexfilePath = await getSetting("knexfilePath");
2657
3227
  ensureFileExists(knexfilePath, KNEXFILE);
2658
3228
  const sourceFile = project.addSourceFileAtPath(knexfilePath);
@@ -2662,11 +3232,11 @@ var parseKnexfile = async () => {
2662
3232
  };
2663
3233
 
2664
3234
  // src/bin/gqm/parse-models.ts
2665
- var import_ts_morph5 = require("ts-morph");
3235
+ var import_ts_morph6 = require("ts-morph");
2666
3236
  var parseModels = async () => {
2667
- const project = new import_ts_morph5.Project({
3237
+ const project = new import_ts_morph6.Project({
2668
3238
  manipulationSettings: {
2669
- indentationText: import_ts_morph5.IndentationText.TwoSpaces
3239
+ indentationText: import_ts_morph6.IndentationText.TwoSpaces
2670
3240
  }
2671
3241
  });
2672
3242
  const modelsPath = await getSetting("modelsPath");
@@ -2679,11 +3249,11 @@ var parseModels = async () => {
2679
3249
  };
2680
3250
 
2681
3251
  // src/bin/gqm/permissions.ts
2682
- var import_ts_morph6 = require("ts-morph");
3252
+ var import_ts_morph7 = require("ts-morph");
2683
3253
  var generatePermissionTypes = (models) => {
2684
- const project = new import_ts_morph6.Project({
3254
+ const project = new import_ts_morph7.Project({
2685
3255
  manipulationSettings: {
2686
- indentationText: import_ts_morph6.IndentationText.TwoSpaces
3256
+ indentationText: import_ts_morph7.IndentationText.TwoSpaces
2687
3257
  }
2688
3258
  });
2689
3259
  const sourceFile = project.createSourceFile("permissions.ts", "", {
@@ -2786,7 +3356,9 @@ import_commander.program.command("generate-migration [<name>] [<date>]").descrip
2786
3356
  const db = (0, import_knex.default)(knexfile);
2787
3357
  try {
2788
3358
  const models = await parseModels();
2789
- const migrations = await new MigrationGenerator(db, models).generate();
3359
+ const functionsPath = await getSetting("functionsPath");
3360
+ const parsedFunctions = parseFunctionsFile(functionsPath);
3361
+ const migrations = await new MigrationGenerator(db, models, parsedFunctions).generate();
2790
3362
  writeToFile(`migrations/${date || getMigrationDate()}_${name2}.ts`, migrations);
2791
3363
  } finally {
2792
3364
  await db.destroy();
@@ -2797,7 +3369,9 @@ import_commander.program.command("check-needs-migration").description("Check if
2797
3369
  const db = (0, import_knex.default)(knexfile);
2798
3370
  try {
2799
3371
  const models = await parseModels();
2800
- const mg = new MigrationGenerator(db, models);
3372
+ const functionsPath = await getSetting("functionsPath");
3373
+ const parsedFunctions = parseFunctionsFile(functionsPath);
3374
+ const mg = new MigrationGenerator(db, models, parsedFunctions);
2801
3375
  await mg.generate();
2802
3376
  if (mg.needsMigration) {
2803
3377
  console.error("Migration is needed.");
@@ -2807,8 +3381,31 @@ import_commander.program.command("check-needs-migration").description("Check if
2807
3381
  await db.destroy();
2808
3382
  }
2809
3383
  });
2810
- import_commander.program.command("*", { noHelp: true }).description("Invalid command").action(() => {
2811
- console.error("Invalid command: %s\nSee --help for a list of available commands.", import_commander.program.args.join(" "));
3384
+ import_commander.program.command("generate-functions").description("Generate functions.ts file from database").action(async () => {
3385
+ const knexfile = await parseKnexfile();
3386
+ const db = (0, import_knex.default)(knexfile);
3387
+ try {
3388
+ const functionsPath = await getSetting("functionsPath");
3389
+ const functions = await generateFunctionsFromDatabase(db);
3390
+ writeToFile(functionsPath, functions);
3391
+ } finally {
3392
+ await db.destroy();
3393
+ }
3394
+ });
3395
+ import_commander.program.command("update-functions").description("Update database functions from functions.ts file").action(async () => {
3396
+ const knexfile = await parseKnexfile();
3397
+ const db = (0, import_knex.default)(knexfile);
3398
+ try {
3399
+ const functionsPath = await getSetting("functionsPath");
3400
+ const parsedFunctions = parseFunctionsFile(functionsPath);
3401
+ await updateFunctions(db, parsedFunctions);
3402
+ } finally {
3403
+ await db.destroy();
3404
+ }
3405
+ });
3406
+ import_commander.program.command("*").description("Invalid command").action((command) => {
3407
+ console.error(`Invalid command: ${command}
3408
+ See --help for a list of available commands.`);
2812
3409
  process.exit(1);
2813
3410
  });
2814
3411
  import_commander.program.parse(process.argv);