@comet/api-generator 8.0.0-beta.3 → 8.0.0-beta.5

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.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  require("ts-node").register({
3
3
  require: ["tsconfig-paths/register"],
4
+ transpileOnly: true,
4
5
  });
5
6
  require("../lib/apiGenerator");
@@ -10,44 +10,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.generateCommand = void 0;
13
- const cli_1 = require("@mikro-orm/cli");
14
- const lazy_metadata_storage_1 = require("@nestjs/graphql/dist/schema-builder/storages/lazy-metadata.storage");
15
13
  const commander_1 = require("commander");
16
- const generate_crud_1 = require("./generateCrud/generate-crud");
17
- const generate_crud_single_1 = require("./generateCrudSingle/generate-crud-single");
18
- const write_generated_files_1 = require("./utils/write-generated-files");
19
- exports.generateCommand = new commander_1.Command("generate").action((options) => __awaiter(void 0, void 0, void 0, function* () {
20
- let orm = null;
21
- try {
22
- orm = yield cli_1.CLIHelper.getORM(undefined, undefined, { dbName: "generator" });
14
+ const generateFiles_1 = require("./generateFiles");
15
+ const watchMode_1 = require("./watchMode/watchMode");
16
+ exports.generateCommand = new commander_1.Command("generate")
17
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
18
+ if (options.watch) {
19
+ yield (0, generateFiles_1.generateFiles)();
20
+ console.log("Watching for modified entities...");
21
+ yield (0, watchMode_1.watchMode)();
23
22
  }
24
- catch (e) {
25
- console.warn(e);
23
+ else {
24
+ (0, generateFiles_1.generateFiles)(options.file);
26
25
  }
27
- if (orm != null) {
28
- const entities = orm.em.getMetadata().getAll();
29
- lazy_metadata_storage_1.LazyMetadataStorage.load();
30
- for (const name in entities) {
31
- const entity = entities[name];
32
- if (!entity.class) {
33
- // Ignore e.g. relation entities that don't have a class
34
- continue;
35
- }
36
- {
37
- const generatorOptions = Reflect.getMetadata(`data:crudGeneratorOptions`, entity.class);
38
- if (generatorOptions) {
39
- const files = yield (0, generate_crud_1.generateCrud)(generatorOptions, entity);
40
- yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
41
- }
42
- }
43
- {
44
- const generatorOptions = Reflect.getMetadata(`data:crudSingleGeneratorOptions`, entity.class);
45
- if (generatorOptions) {
46
- const files = yield (0, generate_crud_single_1.generateCrudSingle)(generatorOptions, entity);
47
- yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
48
- }
49
- }
50
- }
51
- yield orm.close(true);
52
- }
53
- }));
26
+ }))
27
+ .option("-f, --file <file>", "path to entity file")
28
+ .option("-w, --watch", "Watch for changes");
@@ -11,9 +11,6 @@ export declare function buildOptions(metadata: EntityMetadata<any>, generatorOpt
11
11
  hasSlugProp: boolean;
12
12
  hasPositionProp: boolean;
13
13
  positionGroupProps: import("@mikro-orm/postgresql").EntityProperty<any, any>[];
14
- statusProp: import("@mikro-orm/postgresql").EntityProperty<any, any> | undefined;
15
- statusActiveItems: (string | number)[] | undefined;
16
- hasStatusFilter: boolean | undefined;
17
14
  scopeProp: import("@mikro-orm/postgresql").EntityProperty<any, any> | undefined;
18
15
  skipScopeCheck: boolean;
19
16
  argsClassName: string;
@@ -56,7 +56,7 @@ const generate_imports_code_1 = require("../utils/generate-imports-code");
56
56
  const ts_morph_helper_1 = require("../utils/ts-morph-helper");
57
57
  // TODO move into own file
58
58
  function buildOptions(metadata, generatorOptions) {
59
- var _a, _b, _c, _d;
59
+ var _a, _b;
60
60
  const { classNameSingular, classNamePlural, fileNameSingular, fileNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
61
61
  const dedicatedResolverArgProps = metadata.props.filter((prop) => {
62
62
  if ((0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "dedicatedResolverArg")) {
@@ -72,34 +72,6 @@ function buildOptions(metadata, generatorOptions) {
72
72
  });
73
73
  const crudSearchPropNames = (0, cms_api_1.getCrudSearchFieldsFromMetadata)(metadata);
74
74
  const hasSearchArg = crudSearchPropNames.length > 0;
75
- let statusProp = metadata.props.find((prop) => prop.name == "status");
76
- if (statusProp) {
77
- if (!statusProp.enum) {
78
- console.warn(`${metadata.className} status prop must be an enum to be supported by crud generator`);
79
- statusProp = undefined;
80
- }
81
- else if (statusProp.nullable) {
82
- console.warn(`${metadata.className} status prop must not be nullable to be supported by crud generator`);
83
- statusProp = undefined;
84
- }
85
- else if (((_a = (0, ts_morph_helper_1.morphTsProperty)(statusProp.name, metadata).getInitializer()) === null || _a === void 0 ? void 0 : _a.getText()) == "") {
86
- console.warn(`${metadata.className} status prop must have a default value to be supported by crud generator`);
87
- statusProp = undefined;
88
- }
89
- }
90
- let statusActiveItems = undefined;
91
- if (statusProp) {
92
- if (!statusProp.items)
93
- throw new Error("Status enum prop has not items");
94
- statusActiveItems = statusProp.items.filter((item) => {
95
- if (typeof item == "number") {
96
- console.warn(`${metadata.className} status prop must not have numeric items to be supported by crud generator`);
97
- return false;
98
- }
99
- return ["Active", "Visible", "Invisible", "Published", "Unpublished"].includes(item);
100
- });
101
- }
102
- const hasStatusFilter = statusProp && statusActiveItems && statusActiveItems.length != ((_b = statusProp.items) === null || _b === void 0 ? void 0 : _b.length); //if all items are active ones, no need for status filter
103
75
  const crudFilterProps = metadata.props.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "filter") &&
104
76
  !prop.name.startsWith("scope_") &&
105
77
  prop.name != "position" &&
@@ -117,7 +89,8 @@ function buildOptions(metadata, generatorOptions) {
117
89
  prop.kind === "m:1" ||
118
90
  prop.kind === "1:m" ||
119
91
  prop.kind === "m:n" ||
120
- prop.type === "EnumArrayType") &&
92
+ prop.type === "EnumArrayType" ||
93
+ prop.type === "uuid") &&
121
94
  !dedicatedResolverArgProps.some((dedicatedResolverArgProp) => dedicatedResolverArgProp.name == prop.name));
122
95
  const hasFilterArg = crudFilterProps.length > 0;
123
96
  const crudSortProps = metadata.props.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "sort") &&
@@ -133,7 +106,8 @@ function buildOptions(metadata, generatorOptions) {
133
106
  prop.type === "DateType" ||
134
107
  prop.type === "Date" ||
135
108
  prop.kind === "m:1" ||
136
- prop.type === "EnumArrayType"));
109
+ prop.type === "EnumArrayType" ||
110
+ prop.enum));
137
111
  const hasSortArg = crudSortProps.length > 0;
138
112
  const hasSlugProp = metadata.props.some((prop) => prop.name == "slug");
139
113
  const scopeProp = metadata.props.find((prop) => prop.name == "scope");
@@ -141,7 +115,7 @@ function buildOptions(metadata, generatorOptions) {
141
115
  throw new Error("Scope prop has no targetMeta");
142
116
  const hasPositionProp = metadata.props.some((prop) => prop.name == "position");
143
117
  const positionGroupPropNames = hasPositionProp
144
- ? ((_d = (_c = generatorOptions.position) === null || _c === void 0 ? void 0 : _c.groupByFields) !== null && _d !== void 0 ? _d : [
118
+ ? ((_b = (_a = generatorOptions.position) === null || _a === void 0 ? void 0 : _a.groupByFields) !== null && _b !== void 0 ? _b : [
145
119
  ...(scopeProp ? [scopeProp.name] : []), // if there is a scope prop it's effecting position-group, if not groupByFields should be used
146
120
  ])
147
121
  : [];
@@ -163,9 +137,6 @@ function buildOptions(metadata, generatorOptions) {
163
137
  hasSlugProp,
164
138
  hasPositionProp,
165
139
  positionGroupProps,
166
- statusProp,
167
- statusActiveItems,
168
- hasStatusFilter,
169
140
  scopeProp,
170
141
  skipScopeCheck,
171
142
  argsClassName,
@@ -177,7 +148,7 @@ function buildOptions(metadata, generatorOptions) {
177
148
  function generateFilterDto({ generatorOptions, metadata }) {
178
149
  const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
179
150
  const { crudFilterProps } = buildOptions(metadata, generatorOptions);
180
- let importsOut = "";
151
+ const imports = [];
181
152
  let enumFiltersOut = "";
182
153
  const generatedEnumNames = new Set();
183
154
  const generatedEnumsNames = new Set();
@@ -190,7 +161,7 @@ function generateFilterDto({ generatorOptions, metadata }) {
190
161
  enumFiltersOut += `@InputType()
191
162
  class ${enumName}EnumsFilter extends createEnumsFilter(${enumName}) {}
192
163
  `;
193
- importsOut += `import { ${enumName} } from "${importPath}";`;
164
+ imports.push({ name: enumName, importPath });
194
165
  }
195
166
  }
196
167
  else if (prop.enum) {
@@ -201,15 +172,15 @@ function generateFilterDto({ generatorOptions, metadata }) {
201
172
  enumFiltersOut += `@InputType()
202
173
  class ${enumName}EnumFilter extends createEnumFilter(${enumName}) {}
203
174
  `;
204
- importsOut += `import { ${enumName} } from "${importPath}";`;
175
+ imports.push({ name: enumName, importPath });
205
176
  }
206
177
  }
207
178
  });
208
- const filterOut = `import { StringFilter, NumberFilter, BooleanFilter, DateFilter, DateTimeFilter, ManyToOneFilter, OneToManyFilter, ManyToManyFilter, createEnumFilter, createEnumsFilter } from "@comet/cms-api";
179
+ const filterOut = `import { StringFilter, NumberFilter, BooleanFilter, DateFilter, DateTimeFilter, ManyToOneFilter, OneToManyFilter, ManyToManyFilter, IdFilter, createEnumFilter, createEnumsFilter } from "@comet/cms-api";
209
180
  import { Field, InputType } from "@nestjs/graphql";
210
181
  import { Type } from "class-transformer";
211
182
  import { IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
212
- ${importsOut}
183
+ ${(0, generate_imports_code_1.generateImportsCode)(imports)}
213
184
 
214
185
  ${enumFiltersOut}
215
186
 
@@ -301,6 +272,14 @@ function generateFilterDto({ generatorOptions, metadata }) {
301
272
  ${prop.name}?: ManyToManyFilter;
302
273
  `;
303
274
  }
275
+ else if (prop.type == "uuid") {
276
+ return `@Field(() => IdFilter, { nullable: true })
277
+ @ValidateNested()
278
+ @IsOptional()
279
+ @Type(() => IdFilter)
280
+ ${prop.name}?: IdFilter;
281
+ `;
282
+ }
304
283
  else {
305
284
  //unsupported type TODO support more
306
285
  }
@@ -368,29 +347,12 @@ function generatePaginatedDto({ generatorOptions, metadata }) {
368
347
  return paginatedOut;
369
348
  }
370
349
  function generateArgsDto({ generatorOptions, metadata }) {
371
- var _a;
372
350
  const { classNameSingular, fileNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
373
- const { scopeProp, argsClassName, hasSearchArg, hasSortArg, hasFilterArg, statusProp, statusActiveItems, hasStatusFilter, dedicatedResolverArgProps, } = buildOptions(metadata, generatorOptions);
351
+ const { scopeProp, argsClassName, hasSearchArg, hasSortArg, hasFilterArg, dedicatedResolverArgProps } = buildOptions(metadata, generatorOptions);
374
352
  const imports = [];
375
353
  if (scopeProp && scopeProp.targetMeta) {
376
354
  imports.push(generateEntityImport(scopeProp.targetMeta, `${generatorOptions.targetDirectory}/dto`));
377
355
  }
378
- let statusFilterClassName = undefined;
379
- let statusFilterDefaultValue;
380
- if (hasStatusFilter && statusProp) {
381
- statusFilterClassName = (0, ts_morph_helper_1.findEnumName)(statusProp.name, metadata);
382
- const importPath = (0, ts_morph_helper_1.findEnumImportPath)(statusFilterClassName, `${generatorOptions.targetDirectory}/dto`, metadata);
383
- imports.push({
384
- name: statusFilterClassName,
385
- importPath,
386
- });
387
- if (statusActiveItems && statusActiveItems.length > 1) {
388
- statusFilterDefaultValue = `[${statusActiveItems.map((i) => `${statusFilterClassName}.${i}`).join(", ")}]`;
389
- }
390
- else {
391
- statusFilterDefaultValue = `[${(_a = (0, ts_morph_helper_1.morphTsProperty)(statusProp.name, metadata).getInitializer()) === null || _a === void 0 ? void 0 : _a.getText()}]`;
392
- }
393
- }
394
356
  const argsOut = `import { ArgsType, Field, IntersectionType, registerEnumType, ID } from "@nestjs/graphql";
395
357
  import { Type } from "class-transformer";
396
358
  import { IsOptional, IsString, ValidateNested, IsEnum, IsUUID } from "class-validator";
@@ -427,14 +389,6 @@ function generateArgsDto({ generatorOptions, metadata }) {
427
389
  })
428
390
  .join("")}
429
391
 
430
- ${hasStatusFilter
431
- ? `
432
- @Field(() => [${statusFilterClassName}], { defaultValue: ${statusFilterDefaultValue} })
433
- @IsEnum(${statusFilterClassName}, { each: true })
434
- status: ${statusFilterClassName}[];
435
- `
436
- : ""}
437
-
438
392
  ${hasSearchArg
439
393
  ? `
440
394
  @Field({ nullable: true })
@@ -481,9 +435,7 @@ function generateService({ generatorOptions, metadata }) {
481
435
  })
482
436
  .join(",")} }`
483
437
  : false;
484
- const serviceOut = `import { FilterQuery } from "@mikro-orm/postgresql";
485
- import { InjectRepository } from "@mikro-orm/nestjs";
486
- import { EntityRepository, EntityManager, raw } from "@mikro-orm/postgresql";
438
+ const serviceOut = `import { EntityManager, FilterQuery, raw } from "@mikro-orm/postgresql";
487
439
  import { Injectable } from "@nestjs/common";
488
440
 
489
441
  ${(0, generate_imports_code_1.generateImportsCode)([generateEntityImport(metadata, generatorOptions.targetDirectory)])}
@@ -500,7 +452,6 @@ function generateService({ generatorOptions, metadata }) {
500
452
  ${hasPositionProp
501
453
  ? `constructor(
502
454
  private readonly entityManager: EntityManager,
503
- @InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>,
504
455
  ) {}`
505
456
  : ""}
506
457
 
@@ -508,7 +459,7 @@ function generateService({ generatorOptions, metadata }) {
508
459
  ? `
509
460
  async incrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
510
461
  // Increment positions between newPosition (inclusive) and oldPosition (exclusive)
511
- await this.repository.nativeUpdate(
462
+ await this.entityManager.nativeUpdate(${metadata.className},
512
463
  ${positionGroupProps.length
513
464
  ? `{
514
465
  $and: [
@@ -523,7 +474,7 @@ function generateService({ generatorOptions, metadata }) {
523
474
 
524
475
  async decrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
525
476
  // Decrement positions between oldPosition (exclusive) and newPosition (inclusive)
526
- await this.repository.nativeUpdate(
477
+ await this.entityManager.nativeUpdate(${metadata.className},
527
478
  ${positionGroupProps.length
528
479
  ? `{
529
480
  $and: [
@@ -537,7 +488,7 @@ function generateService({ generatorOptions, metadata }) {
537
488
  }
538
489
 
539
490
  async getLastPosition(${positionGroupProps.length ? `group: ${positionGroupType}` : ``}) {
540
- return this.repository.count(${positionGroupProps.length ? `this.getPositionGroupCondition(group)` : `{}`});
491
+ return this.entityManager.count(${metadata.className}, ${positionGroupProps.length ? `this.getPositionGroupCondition(group)` : `{}`});
541
492
  }
542
493
 
543
494
  ${positionGroupProps.length
@@ -562,7 +513,6 @@ function generateEntityImport(targetMetadata, relativeTo) {
562
513
  function generateInputHandling(options, metadata, generatorOptions) {
563
514
  const { instanceNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
564
515
  const { blockProps, scopeProp, hasPositionProp, dedicatedResolverArgProps } = buildOptions(metadata, generatorOptions);
565
- const injectRepositories = [];
566
516
  const props = metadata.props.filter((prop) => !options.excludeFields || !options.excludeFields.includes(prop.name));
567
517
  const relationManyToOneProps = props.filter((prop) => prop.kind === "m:1");
568
518
  const relationOneToManyProps = props.filter((prop) => prop.kind === "1:m");
@@ -578,13 +528,11 @@ function generateInputHandling(options, metadata, generatorOptions) {
578
528
  const targetMeta = prop.targetMeta;
579
529
  if (!targetMeta)
580
530
  throw new Error("targetMeta is not set for relation");
581
- injectRepositories.push(targetMeta);
582
531
  return {
583
532
  name: prop.name,
584
533
  singularName: (0, pluralize_1.singular)(prop.name),
585
534
  nullable: prop.nullable,
586
535
  type: prop.type,
587
- repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
588
536
  };
589
537
  });
590
538
  const inputRelationOneToOneProps = relationOneToOneProps
@@ -593,13 +541,11 @@ function generateInputHandling(options, metadata, generatorOptions) {
593
541
  const targetMeta = prop.targetMeta;
594
542
  if (!targetMeta)
595
543
  throw new Error("targetMeta is not set for relation");
596
- injectRepositories.push(targetMeta);
597
544
  return {
598
545
  name: prop.name,
599
546
  singularName: (0, pluralize_1.singular)(prop.name),
600
547
  nullable: prop.nullable,
601
548
  type: prop.type,
602
- repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
603
549
  targetMeta,
604
550
  };
605
551
  });
@@ -609,20 +555,17 @@ function generateInputHandling(options, metadata, generatorOptions) {
609
555
  const targetMeta = prop.targetMeta;
610
556
  if (!targetMeta)
611
557
  throw new Error("targetMeta is not set for relation");
612
- injectRepositories.push(targetMeta);
613
558
  return {
614
559
  name: prop.name,
615
560
  singularName: (0, pluralize_1.singular)(prop.name),
616
561
  nullable: prop.nullable,
617
562
  type: prop.type,
618
- repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
619
563
  orphanRemoval: prop.orphanRemoval,
620
564
  targetMeta,
621
565
  };
622
566
  });
623
567
  function innerGenerateInputHandling(...args) {
624
568
  const ret = generateInputHandling(...args);
625
- injectRepositories.push(...ret.injectRepositories);
626
569
  return ret.code;
627
570
  }
628
571
  const noAssignProps = [...inputRelationToManyProps, ...inputRelationManyToOneProps, ...inputRelationOneToOneProps, ...blockProps];
@@ -636,13 +579,13 @@ function generateInputHandling(options, metadata, generatorOptions) {
636
579
  ${options.mode == "create"
637
580
  ? dedicatedResolverArgProps
638
581
  .map((dedicatedResolverArgProp) => {
639
- return `${dedicatedResolverArgProp.name}: Reference.create(await this.${(0, build_name_variants_1.classNameToInstanceName)(dedicatedResolverArgProp.type)}Repository.findOneOrFail(${dedicatedResolverArgProp.name})), `;
582
+ return `${dedicatedResolverArgProp.name}: Reference.create(await this.entityManager.findOneOrFail(${dedicatedResolverArgProp.type}, ${dedicatedResolverArgProp.name})), `;
640
583
  })
641
584
  .join("")
642
585
  : ""}
643
586
  ${options.mode == "create" || options.mode == "updateNested"
644
587
  ? inputRelationManyToOneProps
645
- .map((prop) => `${prop.name}: ${prop.nullable ? `${prop.name}Input ? ` : ""}Reference.create(await this.${prop.repositoryName}.findOneOrFail(${prop.name}Input))${prop.nullable ? ` : undefined` : ""}, `)
588
+ .map((prop) => `${prop.name}: ${prop.nullable ? `${prop.name}Input ? ` : ""}Reference.create(await this.entityManager.findOneOrFail(${prop.type}, ${prop.name}Input))${prop.nullable ? ` : undefined` : ""}, `)
646
589
  .join("")
647
590
  : ""}
648
591
  ${options.mode == "create" || options.mode == "updateNested"
@@ -655,8 +598,8 @@ ${inputRelationToManyProps
655
598
  const code = innerGenerateInputHandling({
656
599
  mode: "updateNested",
657
600
  inputName: `${prop.singularName}Input`,
658
- // alternative `return this.${prop.repositoryName}.create({` requires back relation to be set
659
- assignEntityCode: `return this.${prop.repositoryName}.assign(new ${prop.type}(), {`,
601
+ // alternative `return this.entityManager.create(${prop.type}, {` requires back relation to be set
602
+ assignEntityCode: `return this.entityManager.assign(new ${prop.type}(), {`,
660
603
  excludeFields: prop.targetMeta.props
661
604
  .filter((prop) => prop.kind == "m:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
662
605
  .map((prop) => prop.name),
@@ -676,7 +619,7 @@ ${inputRelationToManyProps
676
619
  else {
677
620
  return `
678
621
  if (${prop.name}Input) {
679
- const ${prop.name} = await this.${prop.repositoryName}.find({ id: ${prop.name}Input });
622
+ const ${prop.name} = await this.entityManager.find(${prop.type}, { id: ${prop.name}Input });
680
623
  if (${prop.name}.length != ${prop.name}Input.length) throw new Error("Couldn't find all ${prop.name} that were passed as input");
681
624
  await ${instanceNameSingular}.${prop.name}.loadItems();
682
625
  ${instanceNameSingular}.${prop.name}.set(${prop.name}.map((${prop.singularName}) => Reference.create(${prop.singularName})));
@@ -694,7 +637,7 @@ ${inputRelationOneToOneProps
694
637
  ${innerGenerateInputHandling({
695
638
  mode: "updateNested",
696
639
  inputName: `${prop.name}Input`,
697
- assignEntityCode: `this.${prop.repositoryName}.assign(${prop.singularName}, {`,
640
+ assignEntityCode: `this.entityManager.assign(${prop.singularName}, {`,
698
641
  excludeFields: prop.targetMeta.props
699
642
  .filter((prop) => prop.kind == "1:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
700
643
  .map((prop) => prop.name),
@@ -706,7 +649,7 @@ ${options.mode == "update"
706
649
  .map((prop) => `if (${prop.name}Input !== undefined) {
707
650
  ${instanceNameSingular}.${prop.name} =
708
651
  ${prop.nullable ? `${prop.name}Input ? ` : ""}
709
- Reference.create(await this.${prop.repositoryName}.findOneOrFail(${prop.name}Input))
652
+ Reference.create(await this.entityManager.findOneOrFail(${prop.type}, ${prop.name}Input))
710
653
  ${prop.nullable ? ` : undefined` : ""};
711
654
  }`)
712
655
  .join("")
@@ -720,7 +663,7 @@ ${options.mode == "update"
720
663
  .join("")
721
664
  : ""}
722
665
  `;
723
- return { code, injectRepositories };
666
+ return { code };
724
667
  }
725
668
  function generateNestedEntityResolver({ generatorOptions, metadata }) {
726
669
  const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
@@ -837,7 +780,7 @@ function generateRelationsFieldResolver({ generatorOptions, metadata }) {
837
780
  }
838
781
  function generateResolver({ generatorOptions, metadata }) {
839
782
  const { classNameSingular, fileNameSingular, instanceNameSingular, classNamePlural, fileNamePlural, instanceNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
840
- const { scopeProp, skipScopeCheck, argsClassName, argsFileName, hasSlugProp, hasSearchArg, hasSortArg, hasFilterArg, hasPositionProp, positionGroupProps, statusProp, hasStatusFilter, dedicatedResolverArgProps, } = buildOptions(metadata, generatorOptions);
783
+ const { scopeProp, skipScopeCheck, argsClassName, argsFileName, hasSlugProp, hasSearchArg, hasSortArg, hasFilterArg, hasPositionProp, positionGroupProps, dedicatedResolverArgProps, } = buildOptions(metadata, generatorOptions);
841
784
  const relationManyToOneProps = metadata.props.filter((prop) => prop.kind === "m:1");
842
785
  const relationOneToManyProps = metadata.props.filter((prop) => prop.kind === "1:m");
843
786
  const relationManyToManyProps = metadata.props.filter((prop) => prop.kind === "m:n");
@@ -847,16 +790,12 @@ function generateResolver({ generatorOptions, metadata }) {
847
790
  const outputRelationManyToManyProps = relationManyToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
848
791
  const outputRelationOneToOneProps = relationOneToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
849
792
  const imports = [];
850
- const injectRepositories = new Array();
851
- const { code: createInputHandlingCode, injectRepositories: createInputHandlingInjectRepositories } = generateInputHandling({ mode: "create", inputName: "input", assignEntityCode: `const ${instanceNameSingular} = this.repository.create({` }, metadata, generatorOptions);
852
- injectRepositories.push(...createInputHandlingInjectRepositories);
853
- const { code: updateInputHandlingCode, injectRepositories: updateInputHandlingInjectRepositories } = generateInputHandling({ mode: "update", inputName: "input", assignEntityCode: `${instanceNameSingular}.assign({` }, metadata, generatorOptions);
854
- injectRepositories.push(...updateInputHandlingInjectRepositories);
855
- injectRepositories.push(...dedicatedResolverArgProps.map((prop) => {
856
- if (!prop.targetMeta)
857
- throw new Error("targetMeta is not set for relation");
858
- return prop.targetMeta;
859
- }));
793
+ const { code: createInputHandlingCode } = generateInputHandling({
794
+ mode: "create",
795
+ inputName: "input",
796
+ assignEntityCode: `const ${instanceNameSingular} = this.entityManager.create(${metadata.className}, {`,
797
+ }, metadata, generatorOptions);
798
+ const { code: updateInputHandlingCode } = generateInputHandling({ mode: "update", inputName: "input", assignEntityCode: `${instanceNameSingular}.assign({` }, metadata, generatorOptions);
860
799
  const { imports: relationsFieldResolverImports, code: relationsFieldResolverCode, hasOutputRelations, needsBlocksTransformer, } = generateRelationsFieldResolver({
861
800
  generatorOptions,
862
801
  metadata,
@@ -866,15 +805,6 @@ function generateResolver({ generatorOptions, metadata }) {
866
805
  if (scopeProp && scopeProp.targetMeta) {
867
806
  imports.push(generateEntityImport(scopeProp.targetMeta, generatorOptions.targetDirectory));
868
807
  }
869
- imports.push(...injectRepositories.map((meta) => generateEntityImport(meta, generatorOptions.targetDirectory)));
870
- if (statusProp) {
871
- const enumName = (0, ts_morph_helper_1.findEnumName)(statusProp.name, metadata);
872
- const importPath = (0, ts_morph_helper_1.findEnumImportPath)(enumName, generatorOptions.targetDirectory, metadata);
873
- imports.push({
874
- name: enumName,
875
- importPath,
876
- });
877
- }
878
808
  function generateIdArg(name, metadata) {
879
809
  if (constants_1.integerTypes.includes(metadata.properties[name].type)) {
880
810
  return `@Args("${name}", { type: () => ID }, { transform: (value) => parseInt(value) }) ${name}: number`;
@@ -891,9 +821,7 @@ function generateResolver({ generatorOptions, metadata }) {
891
821
  imports.push({ name: "RootBlockDataScalar", importPath: "@comet/cms-api" });
892
822
  imports.push({ name: "BlocksTransformerService", importPath: "@comet/cms-api" });
893
823
  imports.push({ name: "gqlArgsToMikroOrmQuery", importPath: "@comet/cms-api" });
894
- const resolverOut = `import { InjectRepository } from "@mikro-orm/nestjs";
895
- import { EntityRepository, EntityManager } from "@mikro-orm/postgresql";
896
- import { FindOptions, ObjectQuery, Reference } from "@mikro-orm/postgresql";
824
+ const resolverOut = `import { EntityManager, FindOptions, ObjectQuery, Reference } from "@mikro-orm/postgresql";
897
825
  import { Args, ID, Info, Mutation, Query, Resolver, ResolveField, Parent } from "@nestjs/graphql";
898
826
  import { GraphQLResolveInfo } from "graphql";
899
827
 
@@ -908,18 +836,19 @@ function generateResolver({ generatorOptions, metadata }) {
908
836
  export class ${classNameSingular}Resolver {
909
837
  constructor(
910
838
  private readonly entityManager: EntityManager,${hasPositionProp ? `private readonly ${instanceNamePlural}Service: ${classNamePlural}Service,` : ``}
911
- @InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>,
912
- ${[...new Set(injectRepositories.map((meta) => meta.className))]
913
- .map((type) => `@InjectRepository(${type}) private readonly ${(0, build_name_variants_1.classNameToInstanceName)(type)}Repository: EntityRepository<${type}>,`)
914
- .join("")}${needsBlocksTransformer ? `private readonly blocksTransformer: BlocksTransformerService,` : ""}
839
+ ${needsBlocksTransformer ? `private readonly blocksTransformer: BlocksTransformerService,` : ""}
915
840
  ) {}
916
841
 
842
+ ${generatorOptions.single
843
+ ? `
917
844
  @Query(() => ${metadata.className})
918
845
  @AffectedEntity(${metadata.className})
919
846
  async ${instanceNameSingular}(${generateIdArg("id", metadata)}): Promise<${metadata.className}> {
920
- const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
847
+ const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
921
848
  return ${instanceNameSingular};
922
849
  }
850
+ `
851
+ : ""}
923
852
 
924
853
  ${hasSlugProp
925
854
  ? `
@@ -928,7 +857,7 @@ function generateResolver({ generatorOptions, metadata }) {
928
857
  @Args("slug") slug: string
929
858
  ${scopeProp ? `, @Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type}` : ""}
930
859
  ): Promise<${metadata.className} | null> {
931
- const ${instanceNameSingular} = await this.repository.findOne({ slug${scopeProp ? `, scope` : ""}});
860
+ const ${instanceNameSingular} = await this.entityManager.findOne(${metadata.className}, { slug${scopeProp ? `, scope` : ""}});
932
861
 
933
862
  return ${instanceNameSingular} ?? null;
934
863
  }
@@ -948,16 +877,15 @@ function generateResolver({ generatorOptions, metadata }) {
948
877
  @Args() {${Object.entries(Object.assign(Object.assign({ scope: !!scopeProp }, dedicatedResolverArgProps.reduce((acc, dedicatedResolverArgProp) => {
949
878
  acc[dedicatedResolverArgProp.name] = true;
950
879
  return acc;
951
- }, {})), { status: !!hasStatusFilter, search: !!hasSearchArg, filter: !!hasFilterArg, sort: !!hasSortArg, offset: true, limit: true }))
880
+ }, {})), { search: !!hasSearchArg, filter: !!hasFilterArg, sort: !!hasSortArg, offset: true, limit: true }))
952
881
  .filter(([key, use]) => use)
953
882
  .map(([key]) => key)
954
883
  .join(", ")}}: ${argsClassName}
955
884
  ${hasOutputRelations ? `, @Info() info: GraphQLResolveInfo` : ""}
956
885
  ): Promise<Paginated${classNamePlural}> {
957
886
  const where${hasSearchArg || hasFilterArg
958
- ? ` = gqlArgsToMikroOrmQuery({ ${hasSearchArg ? `search, ` : ""}${hasFilterArg ? `filter, ` : ""} }, this.repository);`
887
+ ? ` = gqlArgsToMikroOrmQuery({ ${hasSearchArg ? `search, ` : ""}${hasFilterArg ? `filter, ` : ""} }, this.entityManager.getMetadata(${metadata.className}));`
959
888
  : `: ObjectQuery<${metadata.className}> = {}`}
960
- ${hasStatusFilter ? `where.status = { $in: status };` : ""}
961
889
  ${scopeProp ? `where.scope = scope;` : ""}
962
890
  ${dedicatedResolverArgProps
963
891
  .map((dedicatedResolverArgProp) => {
@@ -988,7 +916,7 @@ function generateResolver({ generatorOptions, metadata }) {
988
916
  }`
989
917
  : ""}
990
918
 
991
- const [entities, totalCount] = await this.repository.findAndCount(where, options);
919
+ const [entities, totalCount] = await this.entityManager.findAndCount(${metadata.className}, where, options);
992
920
  return new Paginated${classNamePlural}(entities, totalCount);
993
921
  }
994
922
  `
@@ -1059,7 +987,7 @@ function generateResolver({ generatorOptions, metadata }) {
1059
987
  ${generateIdArg("id", metadata)},
1060
988
  @Args("input", { type: () => ${classNameSingular}UpdateInput }) input: ${classNameSingular}UpdateInput
1061
989
  ): Promise<${metadata.className}> {
1062
- const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
990
+ const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
1063
991
 
1064
992
  ${hasPositionProp
1065
993
  ? `
@@ -1108,7 +1036,7 @@ function generateResolver({ generatorOptions, metadata }) {
1108
1036
  @Mutation(() => Boolean)
1109
1037
  @AffectedEntity(${metadata.className})
1110
1038
  async delete${metadata.className}(${generateIdArg("id", metadata)}): Promise<boolean> {
1111
- const ${instanceNameSingular} = await this.repository.findOneOrFail(id);
1039
+ const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
1112
1040
  this.entityManager.remove(${instanceNameSingular});${hasPositionProp
1113
1041
  ? `await this.${instanceNamePlural}Service.decrementPositions(${positionGroupProps.length
1114
1042
  ? `{ ${positionGroupProps
@@ -1131,8 +1059,11 @@ function generateResolver({ generatorOptions, metadata }) {
1131
1059
  }
1132
1060
  function generateCrud(generatorOptionsParam, metadata) {
1133
1061
  return __awaiter(this, void 0, void 0, function* () {
1134
- var _a, _b, _c, _d;
1135
- const generatorOptions = Object.assign(Object.assign({}, generatorOptionsParam), { create: (_a = generatorOptionsParam.create) !== null && _a !== void 0 ? _a : true, update: (_b = generatorOptionsParam.update) !== null && _b !== void 0 ? _b : true, delete: (_c = generatorOptionsParam.delete) !== null && _c !== void 0 ? _c : true, list: (_d = generatorOptionsParam.list) !== null && _d !== void 0 ? _d : true });
1062
+ var _a, _b, _c, _d, _e;
1063
+ const generatorOptions = Object.assign(Object.assign({}, generatorOptionsParam), { create: (_a = generatorOptionsParam.create) !== null && _a !== void 0 ? _a : true, update: (_b = generatorOptionsParam.update) !== null && _b !== void 0 ? _b : true, delete: (_c = generatorOptionsParam.delete) !== null && _c !== void 0 ? _c : true, list: (_d = generatorOptionsParam.list) !== null && _d !== void 0 ? _d : true, single: (_e = generatorOptionsParam.single) !== null && _e !== void 0 ? _e : true });
1064
+ if (!generatorOptions.create && !generatorOptions.update && !generatorOptions.delete && !generatorOptions.list && !generatorOptions.single) {
1065
+ throw new Error("At least one of create, update, delete, list or single must be true");
1066
+ }
1136
1067
  const generatedFiles = [];
1137
1068
  const { fileNameSingular, fileNamePlural, instanceNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
1138
1069
  const { hasFilterArg, hasSortArg, argsFileName, hasPositionProp } = buildOptions(metadata, generatorOptions);
@@ -96,6 +96,7 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
96
96
  const fieldOptions = tsCodeRecordToString({ nullable: "true", defaultValue });
97
97
  isOptional = true;
98
98
  decorators.push(`@IsOptional()`);
99
+ imports.push({ name: "Min", importPath: "class-validator" });
99
100
  decorators.push(`@Min(1)`);
100
101
  decorators.push("@IsInt()");
101
102
  decorators.push(`@Field(() => Int, ${fieldOptions})`);
@@ -395,6 +396,16 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
395
396
  decorators.push("@IsUUID()");
396
397
  type = "string";
397
398
  }
399
+ else if ((0, ts_morph_helper_1.getFieldDecoratorClassName)(prop.name, metadata)) {
400
+ //for custom mikro-orm type
401
+ const className = (0, ts_morph_helper_1.getFieldDecoratorClassName)(prop.name, metadata);
402
+ const importPath = (0, ts_morph_helper_1.findInputClassImportPath)(className, `${generatorOptions.targetDirectory}/dto`, metadata);
403
+ imports.push({ name: className, importPath });
404
+ decorators.push(`@ValidateNested()`);
405
+ decorators.push(`@Type(() => ${className})`);
406
+ decorators.push(`@Field(() => ${className}${prop.nullable ? ", { nullable: true }" : ""})`);
407
+ type = className;
408
+ }
398
409
  else {
399
410
  console.warn(`${prop.name}: unsupported type ${type}`);
400
411
  continue;
@@ -434,7 +445,7 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
434
445
  const className = (_m = options.className) !== null && _m !== void 0 ? _m : `${metadata.className}Input`;
435
446
  const inputOut = `import { Field, InputType, ID, Int } from "@nestjs/graphql";
436
447
  import { Transform, Type } from "class-transformer";
437
- import { IsString, IsNotEmpty, ValidateNested, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsUUID, IsArray, IsInt, Min } from "class-validator";
448
+ import { IsString, IsNotEmpty, ValidateNested, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsUUID, IsArray, IsInt } from "class-validator";
438
449
  import { GraphQLJSONObject } from "graphql-scalars";
439
450
  import { GraphQLDate } from "graphql-scalars";
440
451
  ${(0, generate_imports_code_1.generateImportsCode)(imports)}
@@ -67,8 +67,6 @@ function generateCrudSingle(generatorOptions, metadata) {
67
67
  return (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input") && prop.type === "RootBlockType";
68
68
  });
69
69
  const serviceOut = `import { ObjectQuery } from "@mikro-orm/postgresql";
70
- import { InjectRepository } from "@mikro-orm/nestjs";
71
- import { EntityRepository } from "@mikro-orm/postgresql";
72
70
  import { Injectable } from "@nestjs/common";
73
71
  import { ${metadata.className} } from "${path.relative(generatorOptions.targetDirectory, metadata.path).replace(/\.ts$/, "")}";
74
72
 
@@ -78,9 +76,7 @@ function generateCrudSingle(generatorOptions, metadata) {
78
76
  }
79
77
  `;
80
78
  generatedFiles.push({ name: `${fileNamePlural}.service.ts`, content: serviceOut, type: "service" });
81
- const resolverOut = `import { InjectRepository } from "@mikro-orm/nestjs";
82
- import { EntityRepository, EntityManager } from "@mikro-orm/postgresql";
83
- import { FindOptions } from "@mikro-orm/postgresql";
79
+ const resolverOut = `import { FindOptions, EntityManager } from "@mikro-orm/postgresql";
84
80
  import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql";
85
81
  import { RequiredPermission, SortDirection, validateNotModified } from "@comet/cms-api";
86
82
 
@@ -92,22 +88,20 @@ function generateCrudSingle(generatorOptions, metadata) {
92
88
  : ""}
93
89
  import { ${classNamePlural}Service } from "./${fileNamePlural}.service";
94
90
  import { ${classNameSingular}Input } from "./dto/${fileNameSingular}.input";
95
- import { Paginated${classNamePlural} } from "./dto/paginated-${fileNamePlural}";
96
91
 
97
92
  @Resolver(() => ${metadata.className})
98
93
  @RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${!scopeProp ? `, { skipScopeCheck: true }` : ""})
99
94
  export class ${classNameSingular}Resolver {
100
95
  constructor(
101
96
  private readonly entityManager: EntityManager,
102
- private readonly ${instanceNamePlural}Service: ${classNamePlural}Service,
103
- @InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>
97
+ private readonly ${instanceNamePlural}Service: ${classNamePlural}Service
104
98
  ) {}
105
99
 
106
100
  @Query(() => ${metadata.className}, { nullable: true })
107
101
  async ${instanceNameSingular}(
108
102
  ${scopeProp ? `@Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type},` : ""}
109
103
  ): Promise<${metadata.className} | null> {
110
- const ${instanceNamePlural} = await this.repository.find({${scopeProp ? `scope` : ""}});
104
+ const ${instanceNamePlural} = await this.entityManager.find(${metadata.className}, {${scopeProp ? `scope` : ""}});
111
105
  if (${instanceNamePlural}.length > 1) {
112
106
  throw new Error("There must be only one ${instanceNameSingular}");
113
107
  }
@@ -120,10 +114,10 @@ function generateCrudSingle(generatorOptions, metadata) {
120
114
  ${scopeProp ? `@Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type},` : ""}
121
115
  @Args("input", { type: () => ${classNameSingular}Input }) input: ${classNameSingular}Input
122
116
  ): Promise<${metadata.className}> {
123
- let ${instanceNameSingular} = await this.repository.findOne({${scopeProp ? `scope` : ""}});
117
+ let ${instanceNameSingular} = await this.entityManager.findOne(${metadata.className}, {${scopeProp ? `scope` : ""}});
124
118
 
125
119
  if (!${instanceNameSingular}) {
126
- ${instanceNameSingular} = this.repository.create({
120
+ ${instanceNameSingular} = this.entityManager.create(${metadata.className}, {
127
121
  ...input,
128
122
  ${blockProps.length ? `${blockProps.map((prop) => `${prop.name}: input.${prop.name}.transformToBlockData()`).join(", ")}, ` : ""}
129
123
  ${scopeProp ? `scope,` : ""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generate mode for the generator.
3
+ *
4
+ * Generates CRUD files for all entities or a specific entity available at file path.
5
+ * @param file
6
+ */
7
+ export declare const generateFiles: (file?: string) => Promise<void>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.generateFiles = void 0;
16
+ const node_console_1 = __importDefault(require("node:console"));
17
+ const cli_1 = require("@mikro-orm/cli");
18
+ const lazy_metadata_storage_1 = require("@nestjs/graphql/dist/schema-builder/storages/lazy-metadata.storage");
19
+ const generate_crud_1 = require("./generateCrud/generate-crud");
20
+ const generate_crud_single_1 = require("./generateCrudSingle/generate-crud-single");
21
+ const write_generated_files_1 = require("./utils/write-generated-files");
22
+ /**
23
+ * Generate mode for the generator.
24
+ *
25
+ * Generates CRUD files for all entities or a specific entity available at file path.
26
+ * @param file
27
+ */
28
+ const generateFiles = (
29
+ /**
30
+ * File path to the entity for which to generate CRUD files.
31
+ *
32
+ * @default undefined -> generate all entities
33
+ */
34
+ file) => __awaiter(void 0, void 0, void 0, function* () {
35
+ let orm = null;
36
+ try {
37
+ orm = yield cli_1.CLIHelper.getORM(undefined, undefined, { dbName: "generator" });
38
+ }
39
+ catch (e) {
40
+ node_console_1.default.warn(e);
41
+ }
42
+ if (orm != null) {
43
+ const entities = orm.em.getMetadata().getAll();
44
+ lazy_metadata_storage_1.LazyMetadataStorage.load();
45
+ for (const name in entities) {
46
+ const entity = entities[name];
47
+ if (!entity.class) {
48
+ // Ignore e.g. relation entities that don't have a class
49
+ continue;
50
+ }
51
+ if (file == null || entity.path === `./${file}`) {
52
+ {
53
+ const generatorOptions = Reflect.getMetadata(`data:crudGeneratorOptions`, entity.class);
54
+ if (generatorOptions) {
55
+ node_console_1.default.log(`🚀 start generateCrud for Entity ${entity.path}`);
56
+ const files = yield (0, generate_crud_1.generateCrud)(generatorOptions, entity);
57
+ yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
58
+ }
59
+ }
60
+ {
61
+ const generatorOptions = Reflect.getMetadata(`data:crudSingleGeneratorOptions`, entity.class);
62
+ if (generatorOptions) {
63
+ node_console_1.default.log(`🚀 start generateCrudSingle for Entity ${entity.path}`);
64
+ const files = yield (0, generate_crud_single_1.generateCrudSingle)(generatorOptions, entity);
65
+ yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ });
72
+ exports.generateFiles = generateFiles;
@@ -1,5 +1,4 @@
1
1
  import { type EntityMetadata } from "@mikro-orm/postgresql";
2
- export declare function classNameToInstanceName(className: string): string;
3
2
  export declare function buildNameVariants(metadata: EntityMetadata<any>): {
4
3
  classNameSingular: string;
5
4
  classNamePlural: string;
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.classNameToInstanceName = classNameToInstanceName;
4
3
  exports.buildNameVariants = buildNameVariants;
5
4
  const pluralize_1 = require("pluralize");
6
5
  function classNameToInstanceName(className) {
@@ -8,3 +8,4 @@ export declare function findValidatorImportPath(validatorName: string, generator
8
8
  export declare function findBlockName(propertyName: string, metadata: EntityMetadata<any>): string;
9
9
  export declare function findBlockImportPath(blockName: string, targetDirectory: string, metadata: EntityMetadata<any>): string;
10
10
  export declare function findInputClassImportPath(className: string, targetDirectory: string, metadata: EntityMetadata<any>): string;
11
+ export declare function getFieldDecoratorClassName(propertyName: string, metadata: EntityMetadata<any>): string | false;
@@ -40,6 +40,7 @@ exports.findValidatorImportPath = findValidatorImportPath;
40
40
  exports.findBlockName = findBlockName;
41
41
  exports.findBlockImportPath = findBlockImportPath;
42
42
  exports.findInputClassImportPath = findInputClassImportPath;
43
+ exports.getFieldDecoratorClassName = getFieldDecoratorClassName;
43
44
  const path = __importStar(require("path"));
44
45
  const ts_morph_1 = require("ts-morph");
45
46
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -61,8 +62,14 @@ function morphTsClass(metadata) {
61
62
  return tsClass;
62
63
  }
63
64
  function morphTsProperty(name, metadata) {
64
- const tsClass = morphTsClass(metadata);
65
- return tsClass.getPropertyOrThrow(name);
65
+ let currentClass = morphTsClass(metadata);
66
+ while (currentClass) {
67
+ const prop = currentClass.getProperty(name);
68
+ if (prop)
69
+ return prop;
70
+ currentClass = currentClass.getBaseClass();
71
+ }
72
+ throw new Error(`Property ${name} not found in ${metadata.className}`);
66
73
  }
67
74
  function findImportPath(importName, targetDirectory, metadata) {
68
75
  var _a;
@@ -189,3 +196,22 @@ function findInputClassImportPath(className, targetDirectory, metadata) {
189
196
  }
190
197
  return returnImportPath;
191
198
  }
199
+ function getFieldDecoratorClassName(propertyName, metadata) {
200
+ const definedDecorators = morphTsProperty(propertyName, metadata).getDecorators();
201
+ const fieldDecorator = definedDecorators.find((decorator) => decorator.getName() == "Field");
202
+ if (!fieldDecorator) {
203
+ return false;
204
+ }
205
+ const typeFnArg = fieldDecorator.getArguments()[0];
206
+ if (!typeFnArg)
207
+ throw new Error(`${propertyName}: @Field is missing argument`);
208
+ if (typeFnArg.getKind() != ts_morph_1.SyntaxKind.ArrowFunction)
209
+ throw new Error(`${propertyName}: @Field first argument must be an ArrowFunction`);
210
+ const typeReturningArrowFunction = typeFnArg;
211
+ const body = typeReturningArrowFunction.getBody();
212
+ if (body.getKind() != ts_morph_1.SyntaxKind.Identifier)
213
+ throw new Error(`${propertyName}: @Field first argument must be an ArrowFunction returning an Identifier`);
214
+ const identifier = body;
215
+ const className = identifier.getText();
216
+ return className;
217
+ }
@@ -1 +1,3 @@
1
+ import ts from "typescript";
1
2
  export declare function writeGeneratedFile(filePath: string, contents: string): Promise<void>;
3
+ export declare function removeUnusedImports(): ts.TransformerFactory<ts.SourceFile>;
@@ -41,29 +41,84 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
44
47
  Object.defineProperty(exports, "__esModule", { value: true });
45
48
  exports.writeGeneratedFile = writeGeneratedFile;
46
- const eslint_1 = require("eslint");
49
+ exports.removeUnusedImports = removeUnusedImports;
47
50
  const fs_1 = require("fs");
48
51
  const path = __importStar(require("path"));
52
+ const typescript_1 = __importDefault(require("typescript"));
49
53
  function writeGeneratedFile(filePath, contents) {
50
54
  return __awaiter(this, void 0, void 0, function* () {
51
55
  const header = `// This file has been generated by comet api-generator.
52
56
  // You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
53
57
  `;
54
58
  yield fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
55
- yield fs_1.promises.writeFile(filePath, contents);
56
- const eslint = new eslint_1.ESLint({
57
- cwd: process.cwd(),
58
- fix: true,
59
- });
60
- const lintResult = yield eslint.lintText(header + contents, {
61
- filePath,
59
+ const sourceFile = typescript_1.default.createSourceFile(filePath, header + contents, typescript_1.default.ScriptTarget.ES2024, true, typescript_1.default.ScriptKind.TS);
60
+ const result = typescript_1.default.transform(sourceFile, [removeUnusedImports()]);
61
+ const printer = typescript_1.default.createPrinter({
62
+ newLine: typescript_1.default.NewLineKind.LineFeed,
63
+ removeComments: false,
62
64
  });
63
- const output = lintResult[0] && lintResult[0].output ? lintResult[0].output : lintResult[0].source;
64
- if (output) {
65
- yield fs_1.promises.writeFile(filePath, output);
66
- }
65
+ const prettyCode = printer.printFile(result.transformed[0]);
66
+ result.dispose();
67
+ yield fs_1.promises.writeFile(filePath, prettyCode);
67
68
  console.log(`generated ${filePath}`);
68
69
  });
69
70
  }
71
+ function removeUnusedImports() {
72
+ return (context) => {
73
+ function visit(sourceFile) {
74
+ const usedIdentifiers = new Set();
75
+ // Step 1: Collect all used identifiers
76
+ function collectUsage(node) {
77
+ if (typescript_1.default.isIdentifier(node)) {
78
+ usedIdentifiers.add(node.text);
79
+ }
80
+ else if (typescript_1.default.isImportDeclaration(node)) {
81
+ // Skip import declarations (those identifiers are not usages)
82
+ return;
83
+ }
84
+ typescript_1.default.forEachChild(node, collectUsage);
85
+ }
86
+ typescript_1.default.forEachChild(sourceFile, collectUsage);
87
+ // Step 2: Visit and update the import declarations
88
+ function visitor(node) {
89
+ if (typescript_1.default.isImportDeclaration(node) && node.importClause) {
90
+ const { name, namedBindings } = node.importClause;
91
+ const updatedBindings = [];
92
+ if (name && usedIdentifiers.has(name.text)) {
93
+ // default import is used
94
+ }
95
+ else if (name) {
96
+ // default import is unused
97
+ return undefined;
98
+ }
99
+ if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
100
+ for (const specifier of namedBindings.elements) {
101
+ const importName = specifier.name.text;
102
+ if (usedIdentifiers.has(importName)) {
103
+ updatedBindings.push(specifier);
104
+ }
105
+ }
106
+ if (updatedBindings.length === 0 && !name) {
107
+ // nothing left in this import
108
+ return undefined;
109
+ }
110
+ return typescript_1.default.factory.updateImportDeclaration(node, node.modifiers, typescript_1.default.factory.updateImportClause(node.importClause, node.importClause.isTypeOnly, name, updatedBindings.length > 0 ? typescript_1.default.factory.updateNamedImports(namedBindings, updatedBindings) : undefined), node.moduleSpecifier, node.assertClause);
111
+ }
112
+ if (!namedBindings && !name) {
113
+ // empty import, probably a side-effect import
114
+ return node;
115
+ }
116
+ return node;
117
+ }
118
+ return typescript_1.default.visitEachChild(node, visitor, context);
119
+ }
120
+ return typescript_1.default.visitNode(sourceFile, visitor);
121
+ }
122
+ return (sourceFile) => visit(sourceFile);
123
+ };
124
+ }
@@ -0,0 +1,2 @@
1
+ import { type ChildProcessWithoutNullStreams } from "child_process";
2
+ export declare function handleChildProcess(child: ChildProcessWithoutNullStreams): Promise<string>;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleChildProcess = handleChildProcess;
4
+ function handleChildProcess(child) {
5
+ return new Promise((resolve, reject) => {
6
+ let scriptOutput = "";
7
+ let errorOutput = "";
8
+ child.stdout.setEncoding("utf8");
9
+ child.stdout.on("data", (data) => {
10
+ scriptOutput += data;
11
+ console.log(data);
12
+ });
13
+ child.stderr.on("data", (data) => {
14
+ errorOutput += data;
15
+ console.error(data);
16
+ });
17
+ child.on("close", (code) => {
18
+ if (code !== 0) {
19
+ reject(errorOutput);
20
+ }
21
+ else {
22
+ resolve(scriptOutput);
23
+ }
24
+ });
25
+ });
26
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Watch mode for the generator.
3
+ *
4
+ * Watches the `src` directory for changes and triggers child processes of generator for the changed file, to process it.
5
+ */
6
+ export declare const watchMode: () => Promise<void>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.watchMode = void 0;
16
+ const node_console_1 = __importDefault(require("node:console"));
17
+ const child_process_1 = require("child_process");
18
+ const chokidar_1 = require("chokidar");
19
+ const handleChildProcess_1 = require("./handleChildProcess");
20
+ const waitForExit = (proc) => {
21
+ return new Promise((resolve) => {
22
+ proc.on("exit", (code, signal) => {
23
+ resolve({ code, signal });
24
+ });
25
+ });
26
+ };
27
+ /**
28
+ * Watch mode for the generator.
29
+ *
30
+ * Watches the `src` directory for changes and triggers child processes of generator for the changed file, to process it.
31
+ */
32
+ const watchMode = () => __awaiter(void 0, void 0, void 0, function* () {
33
+ /**
34
+ * Collection of last ChildProcesses for each file
35
+ */
36
+ const childProcesses = {};
37
+ (0, chokidar_1.watch)("./src", {
38
+ awaitWriteFinish: {
39
+ stabilityThreshold: 300,
40
+ pollInterval: 200,
41
+ },
42
+ ignored: (path, stats) => {
43
+ if (stats === null || stats === void 0 ? void 0 : stats.isFile()) {
44
+ return !path.endsWith(".entity.ts");
45
+ }
46
+ return false;
47
+ },
48
+ })
49
+ .on("change", (path) => __awaiter(void 0, void 0, void 0, function* () {
50
+ node_console_1.default.log(`🚀 File changed: ${path}`);
51
+ // Kill running processes for the changed file
52
+ if (childProcesses[path]) {
53
+ if (childProcesses[path].exitCode == null) {
54
+ childProcesses[path].kill();
55
+ yield waitForExit(childProcesses[path]);
56
+ node_console_1.default.log("💀 Killed running process for file: ", path);
57
+ }
58
+ delete childProcesses[path];
59
+ }
60
+ // Generate changed file with child process.
61
+ //
62
+ // Triggering the generator with changed files in the same process has problems with
63
+ // reflection and new Classes / Decorator are not recognised correctly.
64
+ const childProcess = (0, child_process_1.spawn)(`node ${__dirname}/../../../../bin/api-generator.js generate -f ${path}`, {
65
+ shell: true,
66
+ });
67
+ childProcesses[path] = childProcess;
68
+ try {
69
+ (0, handleChildProcess_1.handleChildProcess)(childProcess);
70
+ }
71
+ catch (e) {
72
+ node_console_1.default.error(`❌ Error processing ${path} with error: ${e}`);
73
+ }
74
+ }))
75
+ .on("error", (error) => {
76
+ node_console_1.default.error(`Watcher error: ${error}`);
77
+ });
78
+ });
79
+ exports.watchMode = watchMode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comet/api-generator",
3
- "version": "8.0.0-beta.3",
3
+ "version": "8.0.0-beta.5",
4
4
  "repository": {
5
5
  "directory": "packages/api/api-generator",
6
6
  "type": "git",
@@ -17,19 +17,20 @@
17
17
  "lib/*"
18
18
  ],
19
19
  "dependencies": {
20
+ "chokidar": "^4.0.3",
20
21
  "commander": "^9.5.0",
21
22
  "pluralize": "^8.0.0",
22
23
  "ts-morph": "^25.0.1",
23
24
  "ts-node": "^10.9.2",
24
- "@comet/cms-api": "8.0.0-beta.3"
25
+ "@comet/cms-api": "8.0.0-beta.5"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@mikro-orm/cli": "^6.4.10",
28
29
  "@mikro-orm/core": "^6.4.10",
29
30
  "@mikro-orm/postgresql": "^6.4.10",
30
- "@nestjs/graphql": "^13.0.3",
31
+ "@nestjs/graphql": "^13.1.0",
31
32
  "@types/jest": "^29.5.14",
32
- "@types/node": "^22.13.10",
33
+ "@types/node": "^22.15.21",
33
34
  "@types/pluralize": "^0.0.33",
34
35
  "class-validator": "^0.14.1",
35
36
  "eslint": "^9.22.0",
@@ -41,7 +42,7 @@
41
42
  "ts-jest": "^29.2.6",
42
43
  "typescript": "^5.7.3",
43
44
  "uuid": "^11.1.0",
44
- "@comet/eslint-config": "8.0.0-beta.3"
45
+ "@comet/eslint-config": "8.0.0-beta.5"
45
46
  },
46
47
  "peerDependencies": {
47
48
  "@mikro-orm/cli": "^6.0.0",
@@ -64,6 +65,6 @@
64
65
  "lint:prettier": "npx prettier --check './**/*.{js,json,md,yml,yaml}'",
65
66
  "lint:tsc": "tsc",
66
67
  "test": "NODE_OPTIONS=--experimental-vm-modules npx jest",
67
- "test:watch": "jest --watch"
68
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
68
69
  }
69
70
  }