@smartive/graphql-magic 15.4.1 → 16.0.0

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 (62) hide show
  1. package/.gqmrc.json +4 -2
  2. package/CHANGELOG.md +1 -6
  3. package/dist/bin/gqm.cjs +115 -42
  4. package/dist/cjs/index.cjs +111 -30
  5. package/dist/esm/context.d.ts +5 -4
  6. package/dist/esm/db/generate.d.ts +2 -1
  7. package/dist/esm/db/generate.js +13 -8
  8. package/dist/esm/db/generate.js.map +1 -1
  9. package/dist/esm/index.d.ts +1 -0
  10. package/dist/esm/index.js +1 -0
  11. package/dist/esm/index.js.map +1 -1
  12. package/dist/esm/migrations/generate.d.ts +1 -0
  13. package/dist/esm/migrations/generate.js +28 -6
  14. package/dist/esm/migrations/generate.js.map +1 -1
  15. package/dist/esm/models/mutation-hook.d.ts +5 -12
  16. package/dist/esm/models/utils.d.ts +18 -5
  17. package/dist/esm/models/utils.js +8 -0
  18. package/dist/esm/models/utils.js.map +1 -1
  19. package/dist/esm/permissions/check.d.ts +2 -3
  20. package/dist/esm/permissions/check.js.map +1 -1
  21. package/dist/esm/resolvers/arguments.d.ts +1 -1
  22. package/dist/esm/resolvers/mutations.js +5 -2
  23. package/dist/esm/resolvers/mutations.js.map +1 -1
  24. package/dist/esm/schema/utils.js +19 -8
  25. package/dist/esm/schema/utils.js.map +1 -1
  26. package/dist/esm/utils/dates.d.ts +12 -0
  27. package/dist/esm/utils/dates.js +37 -0
  28. package/dist/esm/utils/dates.js.map +1 -0
  29. package/dist/esm/utils/index.d.ts +1 -0
  30. package/dist/esm/utils/index.js +3 -0
  31. package/dist/esm/utils/index.js.map +1 -0
  32. package/dist/esm/values.d.ts +1 -3
  33. package/docker-compose.yml +0 -1
  34. package/docs/docs/1-tutorial.md +6 -6
  35. package/docs/docs/6-graphql-server.md +1 -3
  36. package/docs/docs/7-graphql-client.md +1 -1
  37. package/docs/docs/8-permissions.md +145 -0
  38. package/docs/package-lock.json +4 -4
  39. package/docs/package.json +1 -1
  40. package/knexfile.ts +2 -2
  41. package/migrations/20230912185644_setup.ts +37 -8
  42. package/package.json +5 -4
  43. package/src/bin/gqm/codegen.ts +4 -3
  44. package/src/bin/gqm/gqm.ts +4 -2
  45. package/src/bin/gqm/settings.ts +37 -2
  46. package/src/bin/gqm/templates.ts +19 -8
  47. package/src/context.ts +9 -5
  48. package/src/db/generate.ts +15 -8
  49. package/src/index.ts +1 -0
  50. package/src/migrations/generate.ts +34 -16
  51. package/src/models/mutation-hook.ts +5 -8
  52. package/src/models/utils.ts +24 -0
  53. package/src/permissions/check.ts +2 -3
  54. package/src/resolvers/mutations.ts +10 -6
  55. package/src/schema/utils.ts +14 -2
  56. package/src/utils/dates.ts +48 -0
  57. package/src/utils/index.ts +3 -0
  58. package/src/values.ts +1 -5
  59. package/tests/generated/client/index.ts +3 -1
  60. package/tests/generated/db/index.ts +43 -43
  61. package/tests/utils/database/seed.ts +9 -5
  62. package/tests/utils/server.ts +3 -3
package/.gqmrc.json CHANGED
@@ -2,5 +2,7 @@
2
2
  "modelsPath": "tests/utils/models.ts",
3
3
  "generatedFolderPath": "tests/generated",
4
4
  "graphqlQueriesPath": "tests",
5
- "gqlModule": "../../../src"
6
- }
5
+ "gqlModule": "../../../src",
6
+ "knexfilePath": "knexfile.ts",
7
+ "dateLibrary": "luxon"
8
+ }
package/CHANGELOG.md CHANGED
@@ -1,6 +1 @@
1
- ## [15.4.1](https://github.com/smartive/graphql-magic/compare/v15.4.0...v15.4.1) (2024-04-05)
2
-
3
-
4
- ### Bug Fixes
5
-
6
- * **deps:** update docusaurus monorepo to v3.2.1 ([359211a](https://github.com/smartive/graphql-magic/commit/359211a0cd45951ea7a744066d7be5b3f9a990df))
1
+ # [16.0.0](https://github.com/smartive/graphql-magic/compare/v15.4.1...v16.0.0) (2024-04-30)
package/dist/bin/gqm.cjs CHANGED
@@ -504,7 +504,9 @@ var getLabel = (s) => (0, import_lodash3.startCase)((0, import_lodash3.camelCase
504
504
  var and = (...predicates) => (field) => predicates.every((predicate) => predicate(field));
505
505
  var not = (predicate) => (field) => !predicate(field);
506
506
  var isRootModel = (model) => model.root;
507
+ var isCreatableModel = (model) => model.creatable && model.fields.some(isCreatableField);
507
508
  var isUpdatableModel = (model) => model.updatable && model.fields.some(isUpdatableField);
509
+ var isCreatableField = (field) => !field.inherited && !!field.creatable;
508
510
  var isUpdatableField = (field) => !field.inherited && !!field.updatable;
509
511
  var modelNeedsTable = (model) => model.fields.some((field) => !field.inherited);
510
512
  var isRelation = (field) => field.kind === "relation";
@@ -546,22 +548,35 @@ var get = (object2, key) => {
546
548
 
547
549
  // src/db/generate.ts
548
550
  var import_code_block_writer = __toESM(require("code-block-writer"), 1);
551
+
552
+ // src/utils/dates.ts
553
+ var import_dayjs = require("dayjs");
554
+ var import_luxon = require("luxon");
555
+ var DATE_CLASS = {
556
+ luxon: "DateTime",
557
+ dayjs: "Dayjs"
558
+ };
559
+ var DATE_CLASS_IMPORT = {
560
+ luxon: `import { DateTime } from 'luxon';`,
561
+ dayjs: `import { Dayjs } from 'dayjs';`
562
+ };
563
+
564
+ // src/db/generate.ts
549
565
  var PRIMITIVE_TYPES = {
550
566
  ID: "string",
551
567
  Boolean: "boolean",
552
568
  Upload: "string",
553
569
  Int: "number",
554
570
  Float: "number",
555
- String: "string",
556
- DateTime: "DateTime | string"
571
+ String: "string"
557
572
  };
558
573
  var OPTIONAL_SEED_FIELDS = ["createdAt", "createdById", "updatedAt", "updatedById", "deletedAt", "deletedById"];
559
- var generateDBModels = (models) => {
574
+ var generateDBModels = (models, dateLibrary) => {
560
575
  const writer = new import_code_block_writer.default["default"]({
561
576
  useSingleQuote: true,
562
577
  indentNumberOfSpaces: 2
563
578
  });
564
- writer.write(`import { DateTime } from 'luxon';`).blankLine();
579
+ writer.write(DATE_CLASS_IMPORT[dateLibrary]).blankLine();
565
580
  for (const enm2 of models.enums) {
566
581
  writer.write(`export type ${enm2.name} = ${enm2.values.map((v) => `'${v}'`).join(" | ")};`).blankLine();
567
582
  }
@@ -569,14 +584,16 @@ var generateDBModels = (models) => {
569
584
  const fields2 = model.relations.some((relation) => relation.field.foreignKey === "id") ? model.fields.filter((field) => field.name !== "id") : model.fields;
570
585
  writer.write(`export type ${model.name} = `).inlineBlock(() => {
571
586
  for (const field of fields2.filter(not(isCustomField))) {
572
- writer.write(`'${getColumnName(field)}': ${getFieldType(field)}${field.nonNull ? "" : " | null"};`).newLine();
587
+ writer.write(`'${getColumnName(field)}': ${getFieldType(field, dateLibrary)}${field.nonNull ? "" : " | null"};`).newLine();
573
588
  }
574
589
  }).blankLine();
575
590
  writer.write(`export type ${model.name}Initializer = `).inlineBlock(() => {
576
591
  for (const field of fields2.filter(not(isCustomField)).filter(isInTable)) {
577
592
  writer.write(
578
593
  `'${getColumnName(field)}'${field.nonNull && field.defaultValue === void 0 ? "" : "?"}: ${getFieldType(
579
- field
594
+ field,
595
+ dateLibrary,
596
+ true
580
597
  )}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
581
598
  ).newLine();
582
599
  }
@@ -584,7 +601,7 @@ var generateDBModels = (models) => {
584
601
  writer.write(`export type ${model.name}Mutator = `).inlineBlock(() => {
585
602
  for (const field of fields2.filter(not(isCustomField)).filter(isInTable)) {
586
603
  writer.write(
587
- `'${getColumnName(field)}'?: ${getFieldType(field)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
604
+ `'${getColumnName(field)}'?: ${getFieldType(field, dateLibrary, true)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
588
605
  ).newLine();
589
606
  }
590
607
  }).blankLine();
@@ -596,7 +613,7 @@ var generateDBModels = (models) => {
596
613
  }
597
614
  const fieldName = getColumnName(field);
598
615
  writer.write(
599
- `'${getColumnName(field)}'${field.nonNull && field.defaultValue === void 0 && !OPTIONAL_SEED_FIELDS.includes(fieldName) ? "" : "?"}: ${field.kind === "enum" ? field.list ? "string[]" : "string" : getFieldType(field)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
616
+ `'${getColumnName(field)}'${field.nonNull && field.defaultValue === void 0 && !OPTIONAL_SEED_FIELDS.includes(fieldName) ? "" : "?"}: ${field.kind === "enum" ? field.list ? "string[]" : "string" : getFieldType(field, dateLibrary, true)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"};`
600
617
  ).newLine();
601
618
  }
602
619
  }).blankLine();
@@ -609,7 +626,7 @@ var generateDBModels = (models) => {
609
626
  });
610
627
  return writer.toString();
611
628
  };
612
- var getFieldType = (field) => {
629
+ var getFieldType = (field, dateLibrary, input2) => {
613
630
  const kind = field.kind;
614
631
  switch (kind) {
615
632
  case "json":
@@ -622,6 +639,9 @@ var getFieldType = (field) => {
622
639
  throw new Error(`Custom fields are not in the db.`);
623
640
  case "primitive":
624
641
  case void 0:
642
+ if (field.type === "DateTime") {
643
+ return (input2 ? `(${DATE_CLASS[dateLibrary]} | string)` : DATE_CLASS[dateLibrary]) + (field.list ? "[]" : "");
644
+ }
625
645
  return get(PRIMITIVE_TYPES, field.type) + (field.list ? "[]" : "");
626
646
  default: {
627
647
  const exhaustiveCheck = kind;
@@ -758,15 +778,13 @@ var MigrationGenerator = class {
758
778
  this.createFields(
759
779
  model,
760
780
  model.fields.filter(not(isInherited)).filter(
761
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.columns[model.name].some(
762
- (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
763
- )
781
+ ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(model.name, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
764
782
  ),
765
783
  up,
766
784
  down
767
785
  );
768
786
  const existingFields = model.fields.filter(({ name: name2, kind, nonNull: nonNull2 }) => {
769
- const col = this.columns[model.name].find((col2) => col2.name === (kind === "relation" ? `${name2}Id` : name2));
787
+ const col = this.getColumn(model.name, kind === "relation" ? `${name2}Id` : name2);
770
788
  if (!col) {
771
789
  return false;
772
790
  }
@@ -808,15 +826,11 @@ var MigrationGenerator = class {
808
826
  } else {
809
827
  const revisionTable = `${model.name}Revision`;
810
828
  const missingRevisionFields = model.fields.filter(isUpdatableField).filter(
811
- ({ name: name2, ...field }) => field.kind !== "custom" && !this.columns[revisionTable].some(
812
- (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
813
- )
829
+ ({ name: name2, ...field }) => field.kind !== "custom" && !this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
814
830
  );
815
831
  this.createRevisionFields(model, missingRevisionFields, up, down);
816
832
  const revisionFieldsToRemove = model.fields.filter(
817
- ({ name: name2, updatable, generated, ...field }) => !generated && field.kind !== "custom" && !updatable && !(field.kind === "relation" && field.foreignKey === "id") && this.columns[revisionTable].some(
818
- (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
819
- )
833
+ ({ name: name2, updatable, generated, ...field }) => !generated && field.kind !== "custom" && !updatable && !(field.kind === "relation" && field.foreignKey === "id") && this.getColumn(revisionTable, field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
820
834
  );
821
835
  this.createRevisionFields(model, revisionFieldsToRemove, down, up);
822
836
  }
@@ -825,12 +839,26 @@ var MigrationGenerator = class {
825
839
  }
826
840
  for (const model of models.entities) {
827
841
  if (tables.includes(model.name)) {
828
- this.createFields(
829
- model,
830
- model.fields.filter(({ name: name2, deleted }) => deleted && this.columns[model.name].some((col) => col.name === name2)),
831
- down,
832
- up
833
- );
842
+ const fieldsToDelete = model.fields.filter(({ name: name2, deleted }) => deleted && this.getColumn(model.name, name2));
843
+ if (!isCreatableModel(model)) {
844
+ if (this.getColumn(model.name, "createdAt")) {
845
+ fieldsToDelete.push({ name: "createdAt", type: "DateTime", nonNull: true });
846
+ }
847
+ if (this.getColumn(model.name, "createdBy")) {
848
+ fieldsToDelete.push({ name: "createdBy", kind: "relation", type: "User", nonNull: true });
849
+ }
850
+ }
851
+ if (!isUpdatableModel(model)) {
852
+ if (this.getColumn(model.name, "updatedAt")) {
853
+ fieldsToDelete.push({ name: "updatedAt", type: "DateTime", nonNull: true });
854
+ }
855
+ if (this.getColumn(model.name, "updatedBy")) {
856
+ fieldsToDelete.push({ name: "updatedBy", kind: "relation", type: "User", nonNull: true });
857
+ }
858
+ }
859
+ if (fieldsToDelete.length) {
860
+ this.createFields(model, fieldsToDelete, down, up);
861
+ }
834
862
  if (isUpdatableModel(model)) {
835
863
  this.createRevisionFields(
836
864
  model,
@@ -1166,6 +1194,9 @@ var MigrationGenerator = class {
1166
1194
  }
1167
1195
  }
1168
1196
  }
1197
+ getColumn(tableName, columnName) {
1198
+ return this.columns[tableName].find((col) => col.name === columnName);
1199
+ }
1169
1200
  };
1170
1201
  var getMigrationDate = () => {
1171
1202
  const date = /* @__PURE__ */ new Date();
@@ -1200,7 +1231,8 @@ var import_flatMap = __toESM(require("lodash/flatMap"), 1);
1200
1231
  var import_graphql5 = require("graphql");
1201
1232
 
1202
1233
  // src/schema/utils.ts
1203
- var import_luxon = require("luxon");
1234
+ var import_dayjs2 = require("dayjs");
1235
+ var import_luxon2 = require("luxon");
1204
1236
  var document = (definitions) => ({
1205
1237
  kind: "Document",
1206
1238
  definitions
@@ -1319,10 +1351,13 @@ var value = (val = null) => val === null ? {
1319
1351
  } : Array.isArray(val) ? {
1320
1352
  kind: "ListValue",
1321
1353
  values: val.map(value)
1322
- } : val instanceof import_luxon.DateTime ? {
1354
+ } : val instanceof import_luxon2.DateTime ? {
1323
1355
  kind: "StringValue",
1324
1356
  value: val.toString()
1325
- } : {
1357
+ } : val instanceof import_dayjs2.Dayjs ? {
1358
+ kind: "StringValue",
1359
+ value: val.toISOString()
1360
+ } : typeof val === "object" ? {
1326
1361
  kind: "ObjectValue",
1327
1362
  fields: Object.keys(val).map(
1328
1363
  (nme) => ({
@@ -1331,6 +1366,9 @@ var value = (val = null) => val === null ? {
1331
1366
  value: value(val[nme])
1332
1367
  })
1333
1368
  )
1369
+ } : doThrow(`Unsupported value ${val}`);
1370
+ var doThrow = (message) => {
1371
+ throw new Error(message);
1334
1372
  };
1335
1373
 
1336
1374
  // src/schema/generate.ts
@@ -1614,14 +1652,9 @@ const modelDefinitions: ModelDefinitions = [
1614
1652
 
1615
1653
  export const models = new Models(modelDefinitions);
1616
1654
  `;
1617
- var KNEXFILE = `import { DateTime } from 'luxon';
1655
+ var KNEXFILE = `
1618
1656
  import { types } from 'pg';
1619
1657
 
1620
- const dateOids = { date: 1082, timestamptz: 1184, timestamp: 1114 };
1621
- for (const oid of Object.values(dateOids)) {
1622
- types.setTypeParser(oid, (val) => DateTime.fromSQL(val));
1623
- }
1624
-
1625
1658
  const numberOids = { int8: 20, float8: 701, numeric: 1700 };
1626
1659
  for (const oid of Object.values(numberOids)) {
1627
1660
  types.setTypeParser(oid, Number);
@@ -1646,6 +1679,22 @@ const knexConfig = {
1646
1679
 
1647
1680
  export default knexConfig;
1648
1681
  `;
1682
+ var KNEXFILE_LUXON_TYPE_PARSERS = (timeZone) => `
1683
+ import { DateTime } from 'luxon';
1684
+
1685
+ const dateOids = { date: 1082, timestamptz: 1184, timestamp: 1114 };
1686
+ for (const oid of Object.values(dateOids)) {
1687
+ types.setTypeParser(oid, (val) => DateTime.fromSQL(val, { zone: "${timeZone}" }));
1688
+ }
1689
+ `;
1690
+ var KNEXFILE_DAYJS_TYPE_PARSERS = `
1691
+ import { dayjs } from 'dayjs';
1692
+
1693
+ const dateOids = { date: 1082, timestamptz: 1184, timestamp: 1114 };
1694
+ for (const oid of Object.values(dateOids)) {
1695
+ types.setTypeParser(oid, (val) => dayjs(val));
1696
+ }
1697
+ `;
1649
1698
  var GET_ME = `import { gql } from '@smartive/graphql-magic';
1650
1699
 
1651
1700
  export const GET_ME = gql\`
@@ -1661,7 +1710,6 @@ import knexConfig from "@/knexfile";
1661
1710
  import { Context, User, execute } from "@smartive/graphql-magic";
1662
1711
  import { randomUUID } from "crypto";
1663
1712
  import { knex } from 'knex';
1664
- import { DateTime } from "luxon";
1665
1713
  import { models } from "../config/models";
1666
1714
 
1667
1715
  export const executeGraphql = async <T, V = undefined>(
@@ -1684,7 +1732,6 @@ export const executeGraphql = async <T, V = undefined>(
1684
1732
  user,
1685
1733
  models: models,
1686
1734
  permissions: { ADMIN: true, UNAUTHENTICATED: true },
1687
- now: DateTime.local(),
1688
1735
  });
1689
1736
  await db.destroy();
1690
1737
 
@@ -1741,12 +1788,37 @@ var DEFAULTS = {
1741
1788
  },
1742
1789
  gqlModule: {
1743
1790
  defaultValue: "@smartive/graphql-magic"
1791
+ },
1792
+ dateLibrary: {
1793
+ question: "Which date library to use (dayjs|luxon)?",
1794
+ defaultValue: "dayjs",
1795
+ init: async (dateLibrary) => {
1796
+ const knexfilePath = await getSetting("knexfilePath");
1797
+ switch (dateLibrary) {
1798
+ case "luxon": {
1799
+ const timeZone = await getSetting("timeZone");
1800
+ ensureFileContains(knexfilePath, "luxon", KNEXFILE_LUXON_TYPE_PARSERS(timeZone));
1801
+ break;
1802
+ }
1803
+ case "dayjs":
1804
+ ensureFileContains(knexfilePath, "dayjs", KNEXFILE_DAYJS_TYPE_PARSERS);
1805
+ break;
1806
+ default:
1807
+ throw new Error("Invalid or unsupported date library.");
1808
+ }
1809
+ }
1810
+ },
1811
+ timeZone: {
1812
+ question: "Which time zone to use?",
1813
+ defaultValue: "Europe/Zurich",
1814
+ init: () => {
1815
+ }
1744
1816
  }
1745
1817
  };
1746
1818
  var initSetting = async (name2) => {
1747
1819
  const { question, defaultValue, init } = DEFAULTS[name2];
1748
1820
  const value2 = await readLine(`${question} (${defaultValue})`) || defaultValue;
1749
- init(value2);
1821
+ await init(value2);
1750
1822
  return value2;
1751
1823
  };
1752
1824
  var initSettings = async () => {
@@ -1825,7 +1897,7 @@ var writeToFile = (filePath, content) => {
1825
1897
  };
1826
1898
 
1827
1899
  // src/bin/gqm/codegen.ts
1828
- var generateGraphqlApiTypes = async () => {
1900
+ var generateGraphqlApiTypes = async (dateLibrary) => {
1829
1901
  const generatedFolderPath = await getSetting("generatedFolderPath");
1830
1902
  await (0, import_cli.generate)({
1831
1903
  overwrite: true,
@@ -1833,12 +1905,12 @@ var generateGraphqlApiTypes = async () => {
1833
1905
  documents: void 0,
1834
1906
  generates: {
1835
1907
  [`${generatedFolderPath}/api/index.ts`]: {
1836
- plugins: ["typescript", "typescript-resolvers", { add: { content: `import { DateTime } from 'luxon';` } }]
1908
+ plugins: ["typescript", "typescript-resolvers", { add: { content: DATE_CLASS_IMPORT[dateLibrary] } }]
1837
1909
  }
1838
1910
  },
1839
1911
  config: {
1840
1912
  scalars: {
1841
- DateTime: "DateTime"
1913
+ DateTime: DATE_CLASS[dateLibrary]
1842
1914
  }
1843
1915
  }
1844
1916
  });
@@ -2165,9 +2237,10 @@ import_commander.program.command("generate").description("Generate all the thing
2165
2237
  const gqlModule = await getSetting("gqlModule");
2166
2238
  writeToFile(`${generatedFolderPath}/schema.graphql`, printSchemaFromModels(models));
2167
2239
  writeToFile(`${generatedFolderPath}/client/mutations.ts`, generateMutations(models, gqlModule));
2168
- writeToFile(`${generatedFolderPath}/db/index.ts`, generateDBModels(models));
2240
+ const dateLibrary = await getSetting("dateLibrary");
2241
+ writeToFile(`${generatedFolderPath}/db/index.ts`, generateDBModels(models, dateLibrary));
2169
2242
  writeToFile(`${generatedFolderPath}/db/knex.ts`, generateKnexTables(models));
2170
- await generateGraphqlApiTypes();
2243
+ await generateGraphqlApiTypes(dateLibrary);
2171
2244
  await generateGraphqlClientTypes();
2172
2245
  });
2173
2246
  import_commander.program.command("generate-migration [<name>] [<date>]").description("Generate Migration").action(async (name2, date) => {