@smartive/graphql-magic 7.0.1 → 8.1.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 (81) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/README.md +8 -15
  3. package/dist/bin/gqm.cjs +1812 -0
  4. package/dist/cjs/index.cjs +568 -30
  5. package/dist/esm/bin/gqm.d.ts +2 -0
  6. package/dist/esm/bin/gqm.js +121 -0
  7. package/dist/esm/bin/gqm.js.map +1 -0
  8. package/dist/esm/client/mutations.d.ts +2 -2
  9. package/dist/esm/client/mutations.js +2 -1
  10. package/dist/esm/client/mutations.js.map +1 -1
  11. package/dist/esm/db/generate.js +7 -7
  12. package/dist/esm/db/generate.js.map +1 -1
  13. package/dist/esm/gqm/codegen.d.ts +2 -0
  14. package/dist/esm/gqm/codegen.js +46 -0
  15. package/dist/esm/gqm/codegen.js.map +1 -0
  16. package/dist/esm/gqm/index.d.ts +9 -0
  17. package/dist/esm/gqm/index.js +11 -0
  18. package/dist/esm/gqm/index.js.map +1 -0
  19. package/dist/esm/gqm/parse-knexfile.d.ts +2 -0
  20. package/dist/esm/gqm/parse-knexfile.js +19 -0
  21. package/dist/esm/gqm/parse-knexfile.js.map +1 -0
  22. package/dist/esm/gqm/parse-models.d.ts +2 -0
  23. package/dist/esm/gqm/parse-models.js +19 -0
  24. package/dist/esm/gqm/parse-models.js.map +1 -0
  25. package/dist/esm/gqm/readline.d.ts +1 -0
  26. package/dist/esm/gqm/readline.js +14 -0
  27. package/dist/esm/gqm/readline.js.map +1 -0
  28. package/dist/esm/gqm/settings.d.ts +9 -0
  29. package/dist/esm/gqm/settings.js +98 -0
  30. package/dist/esm/gqm/settings.js.map +1 -0
  31. package/dist/esm/gqm/static-eval.d.ts +3 -0
  32. package/dist/esm/gqm/static-eval.js +188 -0
  33. package/dist/esm/gqm/static-eval.js.map +1 -0
  34. package/dist/esm/gqm/templates.d.ts +4 -0
  35. package/dist/esm/gqm/templates.js +62 -0
  36. package/dist/esm/gqm/templates.js.map +1 -0
  37. package/dist/esm/gqm/utils.d.ts +2 -0
  38. package/dist/esm/gqm/utils.js +22 -0
  39. package/dist/esm/gqm/utils.js.map +1 -0
  40. package/dist/esm/gqm/visitor.d.ts +8 -0
  41. package/dist/esm/gqm/visitor.js +15 -0
  42. package/dist/esm/gqm/visitor.js.map +1 -0
  43. package/dist/esm/index.d.ts +1 -0
  44. package/dist/esm/index.js +1 -0
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/migrations/generate.d.ts +1 -0
  47. package/dist/esm/migrations/generate.js +16 -5
  48. package/dist/esm/migrations/generate.js.map +1 -1
  49. package/dist/esm/models/models.d.ts +16 -10
  50. package/dist/esm/models/utils.d.ts +17 -9
  51. package/dist/esm/models/utils.js +6 -5
  52. package/dist/esm/models/utils.js.map +1 -1
  53. package/dist/esm/resolvers/node.js +2 -2
  54. package/dist/esm/resolvers/node.js.map +1 -1
  55. package/dist/esm/resolvers/resolver.js +1 -1
  56. package/dist/esm/resolvers/resolver.js.map +1 -1
  57. package/dist/esm/schema/generate.js +16 -4
  58. package/dist/esm/schema/generate.js.map +1 -1
  59. package/package.json +18 -5
  60. package/src/bin/gqm.ts +146 -0
  61. package/src/client/mutations.ts +4 -3
  62. package/src/db/generate.ts +7 -7
  63. package/src/gqm/codegen.ts +47 -0
  64. package/src/gqm/index.ts +11 -0
  65. package/src/gqm/parse-knexfile.ts +21 -0
  66. package/src/gqm/parse-models.ts +24 -0
  67. package/src/gqm/readline.ts +15 -0
  68. package/src/gqm/settings.ts +112 -0
  69. package/src/gqm/static-eval.ts +203 -0
  70. package/src/gqm/templates.ts +64 -0
  71. package/src/gqm/utils.ts +23 -0
  72. package/src/gqm/visitor.ts +29 -0
  73. package/src/index.ts +1 -0
  74. package/src/migrations/generate.ts +18 -5
  75. package/src/models/models.ts +12 -7
  76. package/src/models/utils.ts +11 -8
  77. package/src/resolvers/node.ts +2 -2
  78. package/src/resolvers/resolver.ts +1 -1
  79. package/src/schema/generate.ts +17 -4
  80. package/tests/utils/generate-migration.ts +2 -13
  81. package/tests/utils/models.ts +4 -4
@@ -30,10 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var src_exports = {};
31
31
  __export(src_exports, {
32
32
  AliasGenerator: () => AliasGenerator,
33
+ CLIENT_CODEGEN: () => CLIENT_CODEGEN,
34
+ EMPTY_MODELS: () => EMPTY_MODELS,
33
35
  Enum: () => Enum,
34
36
  ForbiddenError: () => ForbiddenError,
37
+ GRAPHQL_CODEGEN: () => GRAPHQL_CODEGEN,
35
38
  GraphQLError: () => GraphQLError,
36
39
  ID_ALIAS: () => ID_ALIAS,
40
+ KNEXFILE: () => KNEXFILE,
41
+ KNEXFILE_PATH: () => KNEXFILE_PATH,
37
42
  MigrationGenerator: () => MigrationGenerator,
38
43
  NotFoundError: () => NotFoundError,
39
44
  PermissionError: () => PermissionError,
@@ -53,12 +58,16 @@ __export(src_exports, {
53
58
  displayField: () => displayField,
54
59
  document: () => document,
55
60
  enm: () => enm,
61
+ ensureFileExists: () => ensureFileExists,
56
62
  execute: () => execute,
57
63
  fieldType: () => fieldType,
58
64
  fields: () => fields,
65
+ findDeclarationInFile: () => findDeclarationInFile,
59
66
  generate: () => generate,
60
67
  generateDBModels: () => generateDBModels,
61
68
  generateDefinitions: () => generateDefinitions,
69
+ generateGraphqlApiTypes: () => generateGraphqlApiTypes,
70
+ generateGraphqlClientTypes: () => generateGraphqlClientTypes,
62
71
  generateKnexTables: () => generateKnexTables,
63
72
  generateMutations: () => generateMutations,
64
73
  generatePermissions: () => generatePermissions,
@@ -76,6 +85,7 @@ __export(src_exports, {
76
85
  getManyToManyRelation: () => getManyToManyRelation,
77
86
  getManyToManyRelations: () => getManyToManyRelations,
78
87
  getManyToManyRelationsQuery: () => getManyToManyRelationsQuery,
88
+ getMigrationDate: () => getMigrationDate,
79
89
  getModelLabel: () => getModelLabel,
80
90
  getModelLabelPlural: () => getModelLabelPlural,
81
91
  getModelPlural: () => getModelPlural,
@@ -88,6 +98,8 @@ __export(src_exports, {
88
98
  getResolverNode: () => getResolverNode,
89
99
  getResolvers: () => getResolvers,
90
100
  getRootFieldNode: () => getRootFieldNode,
101
+ getSetting: () => getSetting,
102
+ getSettings: () => getSettings,
91
103
  getSimpleFields: () => getSimpleFields,
92
104
  getString: () => getString,
93
105
  getType: () => getType,
@@ -102,20 +114,21 @@ __export(src_exports, {
102
114
  inputValues: () => inputValues,
103
115
  isCreatable: () => isCreatable,
104
116
  isCreatableBy: () => isCreatableBy,
117
+ isCustomField: () => isCustomField,
118
+ isEntityModel: () => isEntityModel,
105
119
  isEnum: () => isEnum,
106
120
  isEnumList: () => isEnumList,
107
121
  isEnumModel: () => isEnumModel,
108
122
  isFieldNode: () => isFieldNode,
109
123
  isFragmentSpreadNode: () => isFragmentSpreadNode,
110
124
  isInlineFragmentNode: () => isInlineFragmentNode,
125
+ isInputModel: () => isInputModel,
111
126
  isListType: () => isListType,
112
127
  isObjectModel: () => isObjectModel,
113
128
  isPrimitive: () => isPrimitive,
114
129
  isQueriableBy: () => isQueriableBy,
115
130
  isQueriableField: () => isQueriableField,
116
- isRaw: () => isRaw,
117
131
  isRawEnumModel: () => isRawEnumModel,
118
- isRawObjectModel: () => isRawObjectModel,
119
132
  isRelation: () => isRelation,
120
133
  isScalarModel: () => isScalarModel,
121
134
  isSimpleField: () => isSimpleField,
@@ -137,19 +150,25 @@ __export(src_exports, {
137
150
  not: () => not,
138
151
  object: () => object,
139
152
  ors: () => ors,
153
+ parseKnexfile: () => parseKnexfile,
154
+ parseModels: () => parseModels,
140
155
  printSchema: () => printSchema,
141
156
  printSchemaFromDocument: () => printSchemaFromDocument,
142
157
  printSchemaFromModels: () => printSchemaFromModels,
143
158
  queryRelations: () => queryRelations,
144
159
  queryResolver: () => queryResolver,
160
+ readLine: () => readLine,
145
161
  resolve: () => resolve,
146
162
  retry: () => retry,
147
163
  scalar: () => scalar,
164
+ staticEval: () => staticEval,
148
165
  summon: () => summon,
149
166
  summonByKey: () => summonByKey,
150
167
  summonByName: () => summonByName,
151
168
  typeToField: () => typeToField,
152
- value: () => value
169
+ value: () => value,
170
+ visit: () => visit,
171
+ writeToFile: () => writeToFile
153
172
  });
154
173
  module.exports = __toCommonJS(src_exports);
155
174
 
@@ -202,7 +221,7 @@ var import_upperCase = __toESM(require("lodash/upperCase"), 1);
202
221
  var constantCase = (str) => (0, import_upperCase.default)(str).replace(/ /g, "_");
203
222
  var generateMutations = (models) => {
204
223
  const parts = [];
205
- for (const { name: name2, creatable, updatable, deletable } of models) {
224
+ for (const { name: name2, creatable, updatable, deletable } of models.filter(isEntityModel)) {
206
225
  if (creatable) {
207
226
  parts.push(
208
227
  `export const CREATE_${constantCase(
@@ -261,11 +280,12 @@ var getModelSlug = (model) => (0, import_kebabCase.default)(getModelPlural(model
261
280
  var getModelLabelPlural = (model) => getLabel(getModelPlural(model));
262
281
  var getModelLabel = (model) => getLabel(model.name);
263
282
  var getLabel = (s) => (0, import_startCase.default)((0, import_camelCase.default)(s));
264
- var isObjectModel = (model) => model.kind === "object";
283
+ var isEntityModel = (model) => model.kind === "entity";
265
284
  var isEnumModel = (model) => model.kind === "enum";
266
285
  var isRawEnumModel = (model) => model.kind === "raw-enum";
267
286
  var isScalarModel = (model) => model.kind === "scalar";
268
- var isRawObjectModel = (model) => model.kind === "raw";
287
+ var isObjectModel = (model) => model.kind === "object";
288
+ var isInputModel = (model) => model.kind === "input";
269
289
  var isEnumList = (models, field) => field?.list === true && models.find(({ name: name2 }) => name2 === field.kind)?.kind === "enum";
270
290
  var and = (...predicates) => (field) => predicates.every((predicate) => predicate(field));
271
291
  var not = (predicate) => (field) => !predicate(field);
@@ -274,9 +294,9 @@ var isEnum = (field) => field.kind === "enum";
274
294
  var isRelation = (field) => field.kind === "relation";
275
295
  var isToOneRelation = (field) => isRelation(field) && !!field.toOne;
276
296
  var isQueriableField = ({ queriable }) => queriable !== false;
277
- var isRaw = (field) => field.kind === "raw";
297
+ var isCustomField = (field) => field.kind === "custom";
278
298
  var isVisible = ({ hidden }) => hidden !== true;
279
- var isSimpleField = and(not(isRelation), not(isRaw));
299
+ var isSimpleField = and(not(isRelation), not(isCustomField));
280
300
  var isUpdatable = ({ updatable }) => !!updatable;
281
301
  var isCreatable = ({ creatable }) => !!creatable;
282
302
  var isQueriableBy = (role) => (field) => field.queriable !== false && (field.queriable === void 0 || field.queriable === true || !field.queriable.roles || field.queriable.roles.includes(role));
@@ -286,7 +306,7 @@ var actionableRelations = (model, action) => model.fields.filter(isRelation).fil
286
306
  (field) => field[`${action === "filter" ? action : action.slice(0, -1)}able`]
287
307
  );
288
308
  var getModels = (rawModels) => {
289
- const models = rawModels.filter(isObjectModel).map((model) => {
309
+ const models = rawModels.filter(isEntityModel).map((model) => {
290
310
  const objectModel = {
291
311
  ...model,
292
312
  fieldsByName: {},
@@ -624,12 +644,12 @@ var generateDBModels = (rawModels) => {
624
644
  for (const model of models) {
625
645
  const fields2 = model.fields.some((field) => field.kind === "relation" && field.foreignKey === "id") ? model.fields.filter((field) => field.name !== "id") : model.fields;
626
646
  writer.write(`export type ${model.name} = `).inlineBlock(() => {
627
- for (const field of fields2.filter(not(isRaw))) {
647
+ for (const field of fields2.filter(not(isCustomField))) {
628
648
  writer.write(`'${getFieldName(field)}': ${getFieldType(field)}${field.nonNull ? "" : " | null"},`).newLine();
629
649
  }
630
650
  }).blankLine();
631
651
  writer.write(`export type ${model.name}Initializer = `).inlineBlock(() => {
632
- for (const field of fields2.filter(not(isRaw))) {
652
+ for (const field of fields2.filter(not(isCustomField))) {
633
653
  writer.write(
634
654
  `'${getFieldName(field)}'${field.nonNull && field.defaultValue === void 0 ? "" : "?"}: ${getFieldType(
635
655
  field
@@ -638,14 +658,14 @@ var generateDBModels = (rawModels) => {
638
658
  }
639
659
  }).blankLine();
640
660
  writer.write(`export type ${model.name}Mutator = `).inlineBlock(() => {
641
- for (const field of fields2.filter(not(isRaw))) {
661
+ for (const field of fields2.filter(not(isCustomField))) {
642
662
  writer.write(
643
663
  `'${getFieldName(field)}'?: ${getFieldType(field)}${field.list ? " | string" : ""}${field.nonNull ? "" : " | null"},`
644
664
  ).newLine();
645
665
  }
646
666
  }).blankLine();
647
667
  writer.write(`export type ${model.name}Seed = `).inlineBlock(() => {
648
- for (const field of fields2.filter(not(isRaw))) {
668
+ for (const field of fields2.filter(not(isCustomField))) {
649
669
  const fieldName = getFieldName(field);
650
670
  writer.write(
651
671
  `'${getFieldName(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"},`
@@ -670,8 +690,8 @@ var getFieldType = (field) => {
670
690
  return "string";
671
691
  case "enum":
672
692
  return field.type + (field.list ? "[]" : "");
673
- case "raw":
674
- throw new Error(`Raw fields are not in the db.`);
693
+ case "custom":
694
+ throw new Error(`Custom fields are not in the db.`);
675
695
  case "primitive":
676
696
  case void 0:
677
697
  return get(PRIMITIVE_TYPES, field.type) + (field.list ? "[]" : "");
@@ -701,6 +721,491 @@ var generateKnexTables = (rawModels) => {
701
721
  return writer.toString();
702
722
  };
703
723
 
724
+ // src/gqm/codegen.ts
725
+ var import_cli = require("@graphql-codegen/cli");
726
+
727
+ // src/gqm/settings.ts
728
+ var import_fs = require("fs");
729
+ var import_path = require("path");
730
+
731
+ // src/gqm/readline.ts
732
+ var import_readline = __toESM(require("readline"), 1);
733
+ var readLine = (prompt) => {
734
+ const rl = import_readline.default.createInterface({
735
+ input: process.stdin,
736
+ output: process.stdout
737
+ });
738
+ return new Promise((resolve2) => {
739
+ rl.question(prompt, (answer) => {
740
+ rl.close();
741
+ resolve2(answer);
742
+ });
743
+ });
744
+ };
745
+
746
+ // src/gqm/templates.ts
747
+ var EMPTY_MODELS = `
748
+ import { RawModels, getModels } from '@smartive/graphql-magic';
749
+
750
+ export const rawModels: RawModels = [
751
+ {
752
+ kind: 'entity',
753
+ name: 'User',
754
+ fields: []
755
+ },
756
+ ]
757
+
758
+ export const models = getModels(rawModels);
759
+ `;
760
+ var GRAPHQL_CODEGEN = (path) => `
761
+ overwrite: true
762
+ schema: '${path}/schema.graphql'
763
+ documents: null
764
+ generates:
765
+ ${path}/api/index.ts:
766
+ plugins:
767
+ - 'typescript'
768
+ - 'typescript-resolvers'
769
+ - add:
770
+ content: "import { DateTime } from 'luxon'"
771
+ config:
772
+ scalars:
773
+ DateTime: DateTime
774
+ `;
775
+ var CLIENT_CODEGEN = (path) => `
776
+ schema: ${path}/schema.graphql
777
+ documents: [ './src/**/*.ts', './src/**/*.tsx' ]
778
+ generates:
779
+ ${path}/client/index.ts:
780
+ plugins:
781
+ - typescript
782
+ - typescript-operations
783
+ - typescript-compatibility
784
+
785
+ config:
786
+ preResolveTypes: true # Simplifies the generated types
787
+ namingConvention: keep # Keeps naming as-is
788
+ nonOptionalTypename: true # Forces \`__typename\` on all selection sets
789
+ skipTypeNameForRoot: true # Don't generate __typename for root types
790
+ avoidOptionals: # Avoids optionals on the level of the field
791
+ field: true
792
+ scalars:
793
+ DateTime: string
794
+ `;
795
+ var KNEXFILE = `
796
+ const config = {
797
+ client: 'postgresql',
798
+ connection: {
799
+ host: process.env.DATABASE_HOST,
800
+ database: process.env.DATABASE_NAME,
801
+ user: process.env.DATABASE_USER,
802
+ password: process.env.DATABASE_PASSWORD,
803
+ },
804
+ } as const;
805
+
806
+ export default config;
807
+ `;
808
+
809
+ // src/gqm/settings.ts
810
+ var SETTINGS_PATH = ".gqmrc.json";
811
+ var DEFAULTS = {
812
+ modelsPath: {
813
+ question: "What is the models path?",
814
+ defaultValue: "src/config/models.ts",
815
+ init: (path) => {
816
+ ensureFileExists(path, EMPTY_MODELS);
817
+ }
818
+ },
819
+ generatedFolderPath: {
820
+ question: "What is the path for generated stuff?",
821
+ defaultValue: "src/generated",
822
+ init: (path) => {
823
+ ensureFileExists(`${path}/.gitkeep`, "");
824
+ ensureFileExists(`${path}/db/.gitkeep`, "");
825
+ ensureFileExists(`${path}/api/.gitkeep`, "");
826
+ ensureFileExists(`${path}/client/.gitkeep`, "");
827
+ ensureFileExists(`graphql-codegen.yml`, GRAPHQL_CODEGEN(path));
828
+ ensureFileExists(`client-codegen.yml`, CLIENT_CODEGEN(path));
829
+ }
830
+ }
831
+ };
832
+ var initSetting = async (name2) => {
833
+ const { question, defaultValue, init } = DEFAULTS[name2];
834
+ const value2 = await readLine(`${question} (${defaultValue})`) || defaultValue;
835
+ init(value2);
836
+ return value2;
837
+ };
838
+ var initSettings = async () => {
839
+ const settings = {};
840
+ for (const name2 of Object.keys(DEFAULTS)) {
841
+ settings[name2] = await initSetting(name2);
842
+ }
843
+ saveSettings(settings);
844
+ };
845
+ var saveSettings = (settings) => {
846
+ writeToFile(SETTINGS_PATH, JSON.stringify(settings, null, 2));
847
+ };
848
+ var getSettings = async () => {
849
+ if (!(0, import_fs.existsSync)(SETTINGS_PATH)) {
850
+ await initSettings();
851
+ }
852
+ return JSON.parse((0, import_fs.readFileSync)(SETTINGS_PATH, "utf8"));
853
+ };
854
+ var getSetting = async (name2) => {
855
+ const settings = await getSettings();
856
+ if (!(name2 in settings)) {
857
+ settings[name2] = await initSetting(name2);
858
+ saveSettings(settings);
859
+ }
860
+ return settings[name2];
861
+ };
862
+ var ensureDirectoryExists = (filePath) => {
863
+ const dir = (0, import_path.dirname)(filePath);
864
+ if ((0, import_fs.existsSync)(dir)) {
865
+ return true;
866
+ }
867
+ ensureDirectoryExists(dir);
868
+ try {
869
+ (0, import_fs.mkdirSync)(dir);
870
+ return true;
871
+ } catch (err) {
872
+ if (err.code === "EEXIST") {
873
+ return true;
874
+ }
875
+ throw err;
876
+ }
877
+ };
878
+ var ensureFileExists = (filePath, content) => {
879
+ if (!(0, import_fs.existsSync)(filePath)) {
880
+ console.info(`Creating ${filePath}`);
881
+ ensureDirectoryExists(filePath);
882
+ (0, import_fs.writeFileSync)(filePath, content);
883
+ }
884
+ };
885
+ var writeToFile = (filePath, content) => {
886
+ ensureDirectoryExists(filePath);
887
+ if ((0, import_fs.existsSync)(filePath)) {
888
+ const currentContent = (0, import_fs.readFileSync)(filePath, "utf-8");
889
+ if (content === currentContent) {
890
+ } else {
891
+ (0, import_fs.writeFileSync)(filePath, content);
892
+ console.info(`${filePath} updated`);
893
+ }
894
+ } else {
895
+ (0, import_fs.writeFileSync)(filePath, content);
896
+ console.info(`Created ${filePath}`);
897
+ }
898
+ };
899
+
900
+ // src/gqm/codegen.ts
901
+ var generateGraphqlApiTypes = async () => {
902
+ const generatedFolderPath = await getSetting("generatedFolderPath");
903
+ await (0, import_cli.generate)({
904
+ overwrite: true,
905
+ schema: `${generatedFolderPath}/schema.graphql`,
906
+ documents: null,
907
+ generates: {
908
+ [`${generatedFolderPath}/api/index.ts`]: {
909
+ plugins: ["typescript", "typescript-resolvers", { add: { content: `import { DateTime } from 'luxon';` } }]
910
+ }
911
+ },
912
+ config: {
913
+ scalars: {
914
+ DateTime: "DateTime"
915
+ }
916
+ }
917
+ });
918
+ };
919
+ var generateGraphqlClientTypes = async () => {
920
+ const generatedFolderPath = await getSetting("generatedFolderPath");
921
+ await (0, import_cli.generate)({
922
+ schema: `${generatedFolderPath}/schema.graphql`,
923
+ documents: ["./src/**/*.ts", "./src/**/*.tsx"],
924
+ generates: {
925
+ [`${generatedFolderPath}/client/index.ts`]: {
926
+ plugins: ["typescript", "typescript-operations", "typescript-compatibility"]
927
+ }
928
+ },
929
+ config: {
930
+ preResolveTypes: true,
931
+ // Simplifies the generated types
932
+ namingConvention: "keep",
933
+ // Keeps naming as-is
934
+ nonOptionalTypename: true,
935
+ // Forces `__typename` on all selection sets
936
+ skipTypeNameForRoot: true,
937
+ // Don't generate __typename for root types
938
+ avoidOptionals: {
939
+ // Avoids optionals on the level of the field
940
+ field: true
941
+ },
942
+ scalars: {
943
+ DateTime: "string"
944
+ }
945
+ }
946
+ });
947
+ };
948
+
949
+ // src/gqm/parse-knexfile.ts
950
+ var import_ts_morph4 = require("ts-morph");
951
+
952
+ // src/gqm/static-eval.ts
953
+ var import_ts_morph2 = require("ts-morph");
954
+
955
+ // src/gqm/visitor.ts
956
+ var import_ts_morph = require("ts-morph");
957
+ var visit = (node, context, visitor2) => {
958
+ const kind = node?.getKind();
959
+ if (kind in visitor2) {
960
+ return visitor2[kind](node.asKindOrThrow(kind), context);
961
+ }
962
+ if ("unknown" in visitor2) {
963
+ return visitor2.unknown(node);
964
+ }
965
+ console.error(node.getText());
966
+ console.error(node.getParent().getText());
967
+ throw new Error(
968
+ `Cannot handle kind ${get(
969
+ Object.entries(import_ts_morph.SyntaxKind).find(([, val]) => val === kind),
970
+ 0
971
+ )}`
972
+ );
973
+ };
974
+
975
+ // src/gqm/static-eval.ts
976
+ var staticEval = (node, context) => visit(node, context, visitor);
977
+ var visitor = {
978
+ undefined: () => void 0,
979
+ [import_ts_morph2.SyntaxKind.VariableDeclaration]: (node, context) => staticEval(node.getInitializer(), context),
980
+ [import_ts_morph2.SyntaxKind.ArrayLiteralExpression]: (node, context) => {
981
+ const values = [];
982
+ for (const value2 of node.getElements()) {
983
+ if (value2.isKind(import_ts_morph2.SyntaxKind.SpreadElement)) {
984
+ values.push(...staticEval(value2, context));
985
+ } else {
986
+ values.push(staticEval(value2, context));
987
+ }
988
+ }
989
+ return values;
990
+ },
991
+ [import_ts_morph2.SyntaxKind.ObjectLiteralExpression]: (node, context) => {
992
+ const result = {};
993
+ for (const property of node.getProperties()) {
994
+ Object.assign(result, staticEval(property, context));
995
+ }
996
+ return result;
997
+ },
998
+ [import_ts_morph2.SyntaxKind.StringLiteral]: (node) => node.getLiteralValue(),
999
+ [import_ts_morph2.SyntaxKind.PropertyAssignment]: (node, context) => ({
1000
+ [node.getName()]: staticEval(node.getInitializer(), context)
1001
+ }),
1002
+ [import_ts_morph2.SyntaxKind.ShorthandPropertyAssignment]: (node, context) => ({
1003
+ [node.getName()]: staticEval(node.getNameNode(), context)
1004
+ }),
1005
+ [import_ts_morph2.SyntaxKind.SpreadElement]: (node, context) => staticEval(node.getExpression(), context),
1006
+ [import_ts_morph2.SyntaxKind.SpreadAssignment]: (node, context) => staticEval(node.getExpression(), context),
1007
+ [import_ts_morph2.SyntaxKind.Identifier]: (node, context) => {
1008
+ switch (node.getText()) {
1009
+ case "undefined":
1010
+ return void 0;
1011
+ case "process":
1012
+ return process;
1013
+ }
1014
+ const definitionNodes = node.getDefinitionNodes();
1015
+ if (!definitionNodes.length) {
1016
+ throw new Error(`No definition node found for identifier ${node.getText()}.`);
1017
+ }
1018
+ return staticEval(definitionNodes[0], context);
1019
+ },
1020
+ [import_ts_morph2.SyntaxKind.ParenthesizedExpression]: (node, context) => staticEval(node.getExpression(), context),
1021
+ [import_ts_morph2.SyntaxKind.AsExpression]: (node, context) => staticEval(node.getExpression(), context),
1022
+ [import_ts_morph2.SyntaxKind.ConditionalExpression]: (node, context) => staticEval(node.getCondition(), context) ? staticEval(node.getWhenTrue(), context) : staticEval(node.getWhenFalse(), context),
1023
+ [import_ts_morph2.SyntaxKind.TrueKeyword]: () => true,
1024
+ [import_ts_morph2.SyntaxKind.FalseKeyword]: () => false,
1025
+ [import_ts_morph2.SyntaxKind.NumericLiteral]: (node) => node.getLiteralValue(),
1026
+ [import_ts_morph2.SyntaxKind.CallExpression]: (node, context) => {
1027
+ const method = staticEval(node.getExpression(), context);
1028
+ const args2 = node.getArguments().map((arg) => staticEval(arg, context));
1029
+ return method(...args2);
1030
+ },
1031
+ [import_ts_morph2.SyntaxKind.PropertyAccessExpression]: (node, context) => {
1032
+ const target = staticEval(node.getExpression(), context);
1033
+ const property = target[node.getName()];
1034
+ if (typeof property === "function") {
1035
+ if (Array.isArray(target)) {
1036
+ switch (node.getName()) {
1037
+ case "map":
1038
+ case "flatMap":
1039
+ case "includes":
1040
+ case "some":
1041
+ case "find":
1042
+ case "filter":
1043
+ return target[node.getName()].bind(target);
1044
+ }
1045
+ } else if (typeof target === "string") {
1046
+ const name2 = node.getName();
1047
+ switch (name2) {
1048
+ case "slice":
1049
+ case "toUpperCase":
1050
+ case "toLowerCase":
1051
+ return target[name2].bind(target);
1052
+ }
1053
+ }
1054
+ throw new Error(`Cannot handle method ${node.getName()} on type ${typeof target}`);
1055
+ }
1056
+ return property;
1057
+ },
1058
+ [import_ts_morph2.SyntaxKind.ArrowFunction]: (node, context) => {
1059
+ return (...args2) => {
1060
+ const parameters = {};
1061
+ let i = 0;
1062
+ for (const parameter of node.getParameters()) {
1063
+ parameters[parameter.getName()] = args2[i];
1064
+ i++;
1065
+ }
1066
+ return staticEval(node.getBody(), { ...context, ...parameters });
1067
+ };
1068
+ },
1069
+ [import_ts_morph2.SyntaxKind.Block]: (node, context) => {
1070
+ for (const statement of node.getStatements()) {
1071
+ return staticEval(statement, context);
1072
+ }
1073
+ },
1074
+ [import_ts_morph2.SyntaxKind.CaseClause]: (node, context) => {
1075
+ const statements = node.getStatements();
1076
+ if (statements.length !== 1) {
1077
+ console.error(node.getText());
1078
+ throw new Error(`Can only handle code blocks with 1 statement.`);
1079
+ }
1080
+ return staticEval(statements[0], context);
1081
+ },
1082
+ [import_ts_morph2.SyntaxKind.DefaultClause]: (node, context) => {
1083
+ const statements = node.getStatements();
1084
+ if (statements.length !== 1) {
1085
+ console.error(node.getText());
1086
+ throw new Error(`Can only handle code blocks with exactly 1 statement.`);
1087
+ }
1088
+ return staticEval(statements[0], context);
1089
+ },
1090
+ [import_ts_morph2.SyntaxKind.ReturnStatement]: (node, context) => {
1091
+ return staticEval(node.getExpression(), context);
1092
+ },
1093
+ [import_ts_morph2.SyntaxKind.SwitchStatement]: (node, context) => {
1094
+ const value2 = staticEval(node.getExpression(), context);
1095
+ let active = false;
1096
+ for (const clause of node.getCaseBlock().getClauses()) {
1097
+ switch (clause.getKind()) {
1098
+ case import_ts_morph2.SyntaxKind.DefaultClause:
1099
+ return staticEval(clause, context);
1100
+ case import_ts_morph2.SyntaxKind.CaseClause: {
1101
+ const caseClause = clause.asKindOrThrow(import_ts_morph2.SyntaxKind.CaseClause);
1102
+ if (caseClause.getStatements().length && active) {
1103
+ return staticEval(clause, context);
1104
+ }
1105
+ const caseValue = staticEval(caseClause.getExpression(), context);
1106
+ if (value2 === caseValue) {
1107
+ active = true;
1108
+ if (caseClause.getStatements().length) {
1109
+ return staticEval(clause, context);
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ },
1116
+ [import_ts_morph2.SyntaxKind.Parameter]: (node, context) => context[node.getName()],
1117
+ [import_ts_morph2.SyntaxKind.BinaryExpression]: (node, context) => {
1118
+ switch (node.getOperatorToken().getKind()) {
1119
+ case import_ts_morph2.SyntaxKind.EqualsEqualsEqualsToken:
1120
+ return staticEval(node.getLeft(), context) === staticEval(node.getRight(), context);
1121
+ case import_ts_morph2.SyntaxKind.BarBarToken:
1122
+ return staticEval(node.getLeft(), context) || staticEval(node.getRight(), context);
1123
+ default:
1124
+ throw new Error(`Cannot handle operator of kind ${node.getOperatorToken().getKindName()}`);
1125
+ }
1126
+ },
1127
+ [import_ts_morph2.SyntaxKind.SatisfiesExpression]: (node, context) => staticEval(node.getExpression(), context),
1128
+ [import_ts_morph2.SyntaxKind.TemplateExpression]: (node, context) => node.getHead().getLiteralText() + node.getTemplateSpans().map((span) => staticEval(span.getExpression(), context) + staticEval(span.getLiteral(), context)).join(""),
1129
+ [import_ts_morph2.SyntaxKind.TemplateTail]: (node) => node.getLiteralText(),
1130
+ [import_ts_morph2.SyntaxKind.TemplateMiddle]: (node) => node.getLiteralText(),
1131
+ [import_ts_morph2.SyntaxKind.PrefixUnaryExpression]: (node, context) => {
1132
+ switch (node.getOperatorToken()) {
1133
+ case import_ts_morph2.SyntaxKind.PlusToken:
1134
+ return +staticEval(node.getOperand(), context);
1135
+ case import_ts_morph2.SyntaxKind.MinusToken:
1136
+ return -staticEval(node.getOperand(), context);
1137
+ case import_ts_morph2.SyntaxKind.TildeToken:
1138
+ return ~staticEval(node.getOperand(), context);
1139
+ case import_ts_morph2.SyntaxKind.ExclamationToken:
1140
+ return !staticEval(node.getOperand(), context);
1141
+ case import_ts_morph2.SyntaxKind.PlusPlusToken:
1142
+ case import_ts_morph2.SyntaxKind.MinusMinusToken:
1143
+ throw new Error(`Cannot handle assignments.`);
1144
+ }
1145
+ },
1146
+ [import_ts_morph2.SyntaxKind.ElementAccessExpression]: (node, context) => {
1147
+ const target = staticEval(node.getExpression(), context);
1148
+ const argument = staticEval(node.getArgumentExpression(), context);
1149
+ return target[argument];
1150
+ },
1151
+ [import_ts_morph2.SyntaxKind.NoSubstitutionTemplateLiteral]: (node) => node.getLiteralValue()
1152
+ };
1153
+
1154
+ // src/gqm/utils.ts
1155
+ var import_ts_morph3 = require("ts-morph");
1156
+ var findDeclarationInFile = (sourceFile, name2) => {
1157
+ const syntaxList = sourceFile.getChildrenOfKind(import_ts_morph3.SyntaxKind.SyntaxList)[0];
1158
+ if (!syntaxList) {
1159
+ throw new Error("No SyntaxList");
1160
+ }
1161
+ const declaration = findDeclaration(syntaxList, name2);
1162
+ if (!declaration) {
1163
+ throw new Error(`No ${name2} declaration`);
1164
+ }
1165
+ return declaration;
1166
+ };
1167
+ var findDeclaration = (syntaxList, name2) => {
1168
+ for (const variableStatement of syntaxList.getChildrenOfKind(import_ts_morph3.SyntaxKind.VariableStatement)) {
1169
+ for (const declaration of variableStatement.getDeclarationList().getDeclarations()) {
1170
+ if (declaration.getName() === name2) {
1171
+ return declaration;
1172
+ }
1173
+ }
1174
+ }
1175
+ };
1176
+
1177
+ // src/gqm/parse-knexfile.ts
1178
+ var KNEXFILE_PATH = `knexfile.ts`;
1179
+ var parseKnexfile = async () => {
1180
+ const project = new import_ts_morph4.Project({
1181
+ manipulationSettings: {
1182
+ indentationText: import_ts_morph4.IndentationText.TwoSpaces
1183
+ }
1184
+ });
1185
+ ensureFileExists(KNEXFILE_PATH, KNEXFILE);
1186
+ const sourceFile = project.addSourceFileAtPath(KNEXFILE_PATH);
1187
+ const configDeclaration = findDeclarationInFile(sourceFile, "config");
1188
+ const config = staticEval(configDeclaration, {});
1189
+ return config;
1190
+ };
1191
+
1192
+ // src/gqm/parse-models.ts
1193
+ var import_ts_morph5 = require("ts-morph");
1194
+ var parseModels = async () => {
1195
+ const project = new import_ts_morph5.Project({
1196
+ manipulationSettings: {
1197
+ indentationText: import_ts_morph5.IndentationText.TwoSpaces
1198
+ }
1199
+ });
1200
+ const modelsPath = await getSetting("modelsPath");
1201
+ const sourceFile = project.addSourceFileAtPath(modelsPath);
1202
+ const modelsDeclaration = findDeclarationInFile(sourceFile, "rawModels");
1203
+ const rawModels = staticEval(modelsDeclaration, {});
1204
+ const generatedFolderPath = await getSetting("generatedFolderPath");
1205
+ writeToFile(`${generatedFolderPath}/models.json`, JSON.stringify(rawModels, null, 2));
1206
+ return rawModels;
1207
+ };
1208
+
704
1209
  // src/migrations/generate.ts
705
1210
  var import_code_block_writer2 = __toESM(require("code-block-writer"), 1);
706
1211
  var import_knex_schema_inspector = require("knex-schema-inspector");
@@ -804,7 +1309,7 @@ var MigrationGenerator = class {
804
1309
  this.createFields(
805
1310
  model,
806
1311
  model.fields.filter(
807
- ({ name: name2, ...field }) => field.kind !== "raw" && !this.columns[model.name].some(
1312
+ ({ name: name2, ...field }) => field.kind !== "custom" && !this.columns[model.name].some(
808
1313
  (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
809
1314
  )
810
1315
  ),
@@ -854,13 +1359,13 @@ var MigrationGenerator = class {
854
1359
  } else {
855
1360
  const revisionTable = `${model.name}Revision`;
856
1361
  const missingRevisionFields = model.fields.filter(
857
- ({ name: name2, updatable, ...field }) => field.kind !== "raw" && updatable && !this.columns[revisionTable].some(
1362
+ ({ name: name2, updatable, ...field }) => field.kind !== "custom" && updatable && !this.columns[revisionTable].some(
858
1363
  (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
859
1364
  )
860
1365
  );
861
1366
  this.createRevisionFields(model, missingRevisionFields, up, down);
862
1367
  const revisionFieldsToRemove = model.fields.filter(
863
- ({ name: name2, updatable, generated, ...field }) => !generated && field.kind !== "raw" && !updatable && !(field.kind === "relation" && field.foreignKey === "id") && this.columns[revisionTable].some(
1368
+ ({ name: name2, updatable, generated, ...field }) => !generated && field.kind !== "custom" && !updatable && !(field.kind === "relation" && field.foreignKey === "id") && this.columns[revisionTable].some(
864
1369
  (col) => col.name === (field.kind === "relation" ? field.foreignKey || `${name2}Id` : name2)
865
1370
  )
866
1371
  );
@@ -1147,6 +1652,7 @@ var MigrationGenerator = class {
1147
1652
  };
1148
1653
  const kind = field.kind;
1149
1654
  switch (kind) {
1655
+ case void 0:
1150
1656
  case "primitive":
1151
1657
  switch (field.type) {
1152
1658
  case "Boolean":
@@ -1200,8 +1706,8 @@ var MigrationGenerator = class {
1200
1706
  case "json":
1201
1707
  this.writer.write(`table.json('${typeToField(field.type)}')`);
1202
1708
  break;
1203
- case "raw":
1204
- throw new Error("Raw fields aren't stored in the database");
1709
+ case "custom":
1710
+ throw new Error("Custom fields aren't stored in the database");
1205
1711
  default: {
1206
1712
  const exhaustiveCheck = kind;
1207
1713
  throw new Error(exhaustiveCheck);
@@ -1209,6 +1715,16 @@ var MigrationGenerator = class {
1209
1715
  }
1210
1716
  }
1211
1717
  };
1718
+ var getMigrationDate = () => {
1719
+ const date = /* @__PURE__ */ new Date();
1720
+ const year = date.getFullYear();
1721
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1722
+ const day = String(date.getDate()).padStart(2, "0");
1723
+ const hours = String(date.getHours()).padStart(2, "0");
1724
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1725
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1726
+ return `${year}${month}${day}${hours}${minutes}${seconds}`;
1727
+ };
1212
1728
 
1213
1729
  // src/errors.ts
1214
1730
  var import_graphql2 = require("graphql");
@@ -1888,7 +2404,7 @@ var getJoins = (node, toMany) => {
1888
2404
  const fieldNameOrAlias = getNameOrAlias(subNode);
1889
2405
  const fieldDefinition = summonByKey(baseTypeDefinition.fields || [], "name.value", fieldName);
1890
2406
  const typeName = getTypeName(fieldDefinition.type);
1891
- if (isRawObjectModel(summonByName(ctx.rawModels, typeName))) {
2407
+ if (isObjectModel(summonByName(ctx.rawModels, typeName))) {
1892
2408
  continue;
1893
2409
  }
1894
2410
  const baseModel = summonByName(ctx.models, baseTypeDefinition.name.value);
@@ -1994,7 +2510,7 @@ var applySelects = (node, query, joins) => {
1994
2510
  { field: "id", alias: ID_ALIAS },
1995
2511
  ...getSimpleFields(node).filter((n) => {
1996
2512
  const field = node.model.fields.find(({ name: name2 }) => name2 === n.name.value);
1997
- if (!field || field.kind === "relation" || field.kind === "raw") {
2513
+ if (!field || field.kind === "relation" || field.kind === "custom") {
1998
2514
  return false;
1999
2515
  }
2000
2516
  if (typeof field.queriable === "object" && !field.queriable.roles?.includes(node.ctx.user.role)) {
@@ -2528,11 +3044,12 @@ var generateDefinitions = (rawModels) => {
2528
3044
  ...rawModels.filter(isEnumModel).map((model) => enm(model.name, model.values)),
2529
3045
  ...rawModels.filter(isRawEnumModel).map((model) => enm(model.name, model.values)),
2530
3046
  ...rawModels.filter(isScalarModel).map((model) => scalar(model.name)),
2531
- ...rawModels.filter(isRawObjectModel).map((model) => object(model.name, model.fields)),
2532
- ...rawModels.filter(isRawObjectModel).filter(
3047
+ ...rawModels.filter(isObjectModel).filter(({ name: name2 }) => !["Query", "Mutation"].includes(name2)).map((model) => object(model.name, model.fields)),
3048
+ ...rawModels.filter(isInputModel).map((model) => input(model.name, model.fields)),
3049
+ ...rawModels.filter(isObjectModel).filter(
2533
3050
  (model) => models.some((m) => m.creatable && m.fields.some((f) => f.creatable && f.kind === "json" && f.type === model.name))
2534
3051
  ).map((model) => input(`Create${model.name}`, model.fields)),
2535
- ...rawModels.filter(isRawObjectModel).filter(
3052
+ ...rawModels.filter(isObjectModel).filter(
2536
3053
  (model) => models.some((m) => m.creatable && m.fields.some((f) => f.creatable && f.kind === "json" && f.type === model.name))
2537
3054
  ).map((model) => input(`Update${model.name}`, model.fields)),
2538
3055
  ...(0, import_flatMap2.default)(
@@ -2656,7 +3173,8 @@ var generateDefinitions = (rawModels) => {
2656
3173
  { name: "limit", type: "Int" },
2657
3174
  { name: "offset", type: "Int" }
2658
3175
  ]
2659
- }))
3176
+ })),
3177
+ ...rawModels.filter(isObjectModel).filter((model) => model.name === "Query").flatMap((model) => model.fields)
2660
3178
  ]),
2661
3179
  object("Mutation", [
2662
3180
  ...(0, import_flatMap2.default)(
@@ -2727,7 +3245,8 @@ var generateDefinitions = (rawModels) => {
2727
3245
  }
2728
3246
  return mutations;
2729
3247
  })
2730
- )
3248
+ ),
3249
+ ...rawModels.filter(isObjectModel).filter((model) => model.name === "Mutation").flatMap((model) => model.fields)
2731
3250
  ])
2732
3251
  ];
2733
3252
  };
@@ -2742,10 +3261,15 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2742
3261
  // Annotate the CommonJS export names for ESM import in node:
2743
3262
  0 && (module.exports = {
2744
3263
  AliasGenerator,
3264
+ CLIENT_CODEGEN,
3265
+ EMPTY_MODELS,
2745
3266
  Enum,
2746
3267
  ForbiddenError,
3268
+ GRAPHQL_CODEGEN,
2747
3269
  GraphQLError,
2748
3270
  ID_ALIAS,
3271
+ KNEXFILE,
3272
+ KNEXFILE_PATH,
2749
3273
  MigrationGenerator,
2750
3274
  NotFoundError,
2751
3275
  PermissionError,
@@ -2765,12 +3289,16 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2765
3289
  displayField,
2766
3290
  document,
2767
3291
  enm,
3292
+ ensureFileExists,
2768
3293
  execute,
2769
3294
  fieldType,
2770
3295
  fields,
3296
+ findDeclarationInFile,
2771
3297
  generate,
2772
3298
  generateDBModels,
2773
3299
  generateDefinitions,
3300
+ generateGraphqlApiTypes,
3301
+ generateGraphqlClientTypes,
2774
3302
  generateKnexTables,
2775
3303
  generateMutations,
2776
3304
  generatePermissions,
@@ -2788,6 +3316,7 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2788
3316
  getManyToManyRelation,
2789
3317
  getManyToManyRelations,
2790
3318
  getManyToManyRelationsQuery,
3319
+ getMigrationDate,
2791
3320
  getModelLabel,
2792
3321
  getModelLabelPlural,
2793
3322
  getModelPlural,
@@ -2800,6 +3329,8 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2800
3329
  getResolverNode,
2801
3330
  getResolvers,
2802
3331
  getRootFieldNode,
3332
+ getSetting,
3333
+ getSettings,
2803
3334
  getSimpleFields,
2804
3335
  getString,
2805
3336
  getType,
@@ -2814,20 +3345,21 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2814
3345
  inputValues,
2815
3346
  isCreatable,
2816
3347
  isCreatableBy,
3348
+ isCustomField,
3349
+ isEntityModel,
2817
3350
  isEnum,
2818
3351
  isEnumList,
2819
3352
  isEnumModel,
2820
3353
  isFieldNode,
2821
3354
  isFragmentSpreadNode,
2822
3355
  isInlineFragmentNode,
3356
+ isInputModel,
2823
3357
  isListType,
2824
3358
  isObjectModel,
2825
3359
  isPrimitive,
2826
3360
  isQueriableBy,
2827
3361
  isQueriableField,
2828
- isRaw,
2829
3362
  isRawEnumModel,
2830
- isRawObjectModel,
2831
3363
  isRelation,
2832
3364
  isScalarModel,
2833
3365
  isSimpleField,
@@ -2849,17 +3381,23 @@ var printSchemaFromModels = (models) => printSchema((0, import_graphql5.buildAST
2849
3381
  not,
2850
3382
  object,
2851
3383
  ors,
3384
+ parseKnexfile,
3385
+ parseModels,
2852
3386
  printSchema,
2853
3387
  printSchemaFromDocument,
2854
3388
  printSchemaFromModels,
2855
3389
  queryRelations,
2856
3390
  queryResolver,
3391
+ readLine,
2857
3392
  resolve,
2858
3393
  retry,
2859
3394
  scalar,
3395
+ staticEval,
2860
3396
  summon,
2861
3397
  summonByKey,
2862
3398
  summonByName,
2863
3399
  typeToField,
2864
- value
3400
+ value,
3401
+ visit,
3402
+ writeToFile
2865
3403
  });