@postxl/generator 0.16.7 → 0.17.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.
package/dist/generator.js CHANGED
@@ -67,6 +67,7 @@ const businesslogicmodule_generator_1 = require("./generators/indices/businesslo
67
67
  const businesslogic_generator_1 = require("./generators/models/businesslogic.generator");
68
68
  const seed_template_decoder_generator_1 = require("./generators/indices/seed-template-decoder.generator");
69
69
  const seed_template_generator_1 = require("./generators/indices/seed-template.generator");
70
+ const seed_service_generator_1 = require("./generators/indices/seed-service.generator");
70
71
  const CONFIG_SCHEMA = zod_1.z
71
72
  .object({
72
73
  project: zod_1.z.string(),
@@ -206,6 +207,7 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
206
207
  }
207
208
  if (!config.disableGenerators.seed) {
208
209
  generated.write(`/${meta.seed.indexFilePath}.ts`, (0, seed_generator_1.generateSeedIndex)({ models, meta }));
210
+ generated.write(`/${meta.seed.serviceFilePath}.ts`, (0, seed_service_generator_1.generateSeedService)({ models, meta }));
209
211
  generated.write(`/${meta.seed.templateExcelFilePath}`, yield (0, seed_template_generator_1.generateSeedExcelTemplate)({ models }));
210
212
  generated.write(`/${meta.seed.templateDecoderFilePath}.ts`, (0, seed_template_decoder_generator_1.generateSeedTemplateDecoder)({ models, meta }));
211
213
  }
@@ -27,19 +27,45 @@ function generateBusinessLogicModule({ models, meta }) {
27
27
  });
28
28
  providers.push(meta.businessLogic.serviceClassName);
29
29
  }
30
+ const moduleName = meta.businessLogic.moduleName;
30
31
  return `
31
- import { Module } from '@nestjs/common'
32
+ import { DynamicModule } from '@${meta.config.project}/common'
32
33
 
33
34
  ${imports.generate()}
34
35
 
35
36
  const providers = [${providers.join(', ')}]
36
- @Module({
37
- imports: [${meta.data.moduleName}],
38
- providers,
39
- exports: providers,
40
- })
41
- export class BusinessLogicModule {}
42
37
 
38
+ export class ${moduleName} {
39
+ /**
40
+ * Internal cache for the module instance.
41
+ */
42
+ private static cachedModule: DynamicModule | undefined = undefined
43
+
44
+ /**
45
+ * The getInstance method should be called by any module that needs the module.
46
+ */
47
+ static getInstance(): DynamicModule {
48
+ if (!${moduleName}.cachedModule) throw new Error('${moduleName} must be called via .provide first!')
49
+ return ${moduleName}.cachedModule
50
+ }
51
+
52
+ /**
53
+ * The forRoot method should only be called once by the root module.
54
+ */
55
+ static forRoot(): DynamicModule {
56
+ if (${moduleName}.cachedModule) {
57
+ throw new Error('${moduleName} is already instantiated, please call .forRoot only once from root...')
58
+ }
59
+
60
+ ${moduleName}.cachedModule = {
61
+ module: ${moduleName},
62
+ providers,
63
+ imports: [${meta.data.moduleName}.getInstance()],
64
+ exports: providers,
65
+ }
66
+ return ${moduleName}.cachedModule
67
+ }
68
+ }
43
69
  `;
44
70
  }
45
71
  exports.generateBusinessLogicModule = generateBusinessLogicModule;
@@ -69,16 +69,7 @@ import { DbModule } from '@${meta.config.project}/db'
69
69
  ${imports.generate()}
70
70
 
71
71
  export class DataMockModule {
72
- private static cachedModule: DynamicModule | undefined = undefined
73
-
74
- static isInstantiated(): boolean {
75
- return !!DataMockModule.cachedModule
76
- }
77
- static getInstance(): DataModule {
78
- if (!DataMockModule.cachedModule) throw new Error('DataMockModule must be called via .mock first!')
79
- return DataMockModule.cachedModule
80
- }
81
-
72
+
82
73
  // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
83
74
  static async mock(seed?: MockData): Promise<DynamicModule> {
84
75
  const providers = [
@@ -87,15 +78,17 @@ export class DataMockModule {
87
78
  ${providers}
88
79
  ]
89
80
 
90
- DataMockModule.cachedModule = {
81
+ const cachedModule = {
91
82
  module: DataModule,
92
83
  imports: [DbModule.provideMock()],
93
84
  providers: providers,
94
85
  exports: providers,
95
86
  global: true,
96
87
  }
97
-
98
- return DataMockModule.cachedModule
88
+
89
+ ${meta.data.moduleName}._storeMockModule(cachedModule)
90
+
91
+ return cachedModule
99
92
  }
100
93
  }
101
94
 
@@ -49,7 +49,8 @@ function generateDataModule({ models, meta }) {
49
49
  }
50
50
  const moduleName = meta.data.moduleName;
51
51
  return `
52
- import { DynamicModule, Provider, Type } from '@nestjs/common'
52
+ import { Provider, Type } from '@nestjs/common'
53
+ import { DynamicModule } from '@${meta.config.project}/common'
53
54
  import { DbModule, DbService } from '@${meta.config.project}/db'
54
55
 
55
56
  import { AsyncInit, Repository } from './repository.type'
@@ -70,21 +71,33 @@ const createRepositoryProvider = <T extends AsyncInit>(t: Type<T>, loadData: boo
70
71
  })
71
72
 
72
73
  export class ${moduleName} {
74
+ /**
75
+ * Internal cache for the module instance.
76
+ */
73
77
  private static cachedModule: DynamicModule | undefined = undefined
74
78
 
75
- static isInstantiated(): boolean {
76
- return !!${moduleName}.cachedModule
77
- }
78
-
79
- static getInstance(): ${moduleName} {
79
+ /**
80
+ * The getInstance method should be called by any module that needs the module.
81
+ */
82
+ static getInstance(): DynamicModule {
80
83
  if (!${moduleName}.cachedModule) throw new Error('${moduleName} must be called via .provide first!')
81
84
  return ${moduleName}.cachedModule
82
85
  }
86
+
87
+ /**
88
+ * Stores another module instance in the cache.
89
+ * Note: This should only be used for testing purposes.
90
+ */
91
+ static _storeMockModule(module: DynamicModule): void {
92
+ ${moduleName}.cachedModule = module
93
+ }
83
94
 
84
- static provide({ loadData }: { loadData: boolean }): DynamicModule {
95
+ /**
96
+ * The forRoot method should only be called once by the root module.
97
+ */
98
+ static forRoot({ loadData }: { loadData: boolean }): DynamicModule {
85
99
  if (${moduleName}.cachedModule) {
86
- console.warn('${moduleName} is already instantiated, skipping...')
87
- return ${moduleName}.cachedModule
100
+ throw new Error('${moduleName} is already instantiated, please call .forRoot only once from root...')
88
101
  }
89
102
 
90
103
  const repositoryProviders = [
@@ -94,7 +107,7 @@ export class ${moduleName} {
94
107
  ${moduleName}.cachedModule = {
95
108
  module: ${moduleName},
96
109
  global: true,
97
- imports: [DbModule.provide()],
110
+ imports: [DbModule.forRoot()],
98
111
  providers: [DataService, ...repositoryProviders],
99
112
  exports: [DataService, ...repositoryProviders],
100
113
  }
@@ -116,7 +129,7 @@ export class ${moduleName} {
116
129
  ${moduleName}.cachedModule = {
117
130
  module: ${moduleName},
118
131
  global: true,
119
- imports: [DbModule.provide()],
132
+ imports: [DbModule.forRoot()],
120
133
  providers: [...providers],
121
134
  exports: providers,
122
135
  }
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates index file for all seed files.
5
+ */
6
+ export declare function generateSeedService({ models, meta }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSeedService = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ /**
7
+ * Generates index file for all seed files.
8
+ */
9
+ function generateSeedService({ models, meta }) {
10
+ const imports = imports_1.ImportsGenerator.from(meta.seed.serviceFilePath).addImport({
11
+ from: meta.seed.templateDecoderFilePath,
12
+ items: [meta.seed.templateDecoderName],
13
+ });
14
+ const dataLoader = [];
15
+ for (const model of models) {
16
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
17
+ dataLoader.push(`await this.upload({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}, repo: this.dataService.${modelMeta.data.dataServiceName}, log })`);
18
+ }
19
+ return `
20
+ import { Injectable, Logger } from '@nestjs/common'
21
+ import { format, pluralize } from '@pxl/common'
22
+ import { DataService, Repository } from '@pxl/data'
23
+ import { DbService } from '@pxl/db'
24
+ import { XlPortService } from '@pxl/xlport'
25
+ ${imports.generate()}
26
+
27
+ import * as SEEDDATA from './data'
28
+
29
+ /**
30
+ * Configuration options for seeding
31
+ */
32
+ export type SeedServiceOptions = {
33
+ /**
34
+ * If true, the database will be emptied before seeding.
35
+ * This is only possible if the database is set to test database!
36
+ */
37
+ reset?: boolean
38
+ /**
39
+ * If true, the seed data will be logged to the console.
40
+ * Note: This is significantly slower - but can be useful for debugging.
41
+ */
42
+ log?: boolean
43
+ /**
44
+ * If true, data will be loaded into the repositories after seeding.
45
+ * This should be used on startup of the application, but typically
46
+ * not when running tests or from the CLI.
47
+ */
48
+ loadDataAfterSeeding?: boolean
49
+ }
50
+
51
+ const DEFAULTOPTIONS: SeedServiceOptions = { reset: false, log: false, loadDataAfterSeeding: false }
52
+ @Injectable()
53
+ export class SeedService {
54
+ private readonly logger = new Logger(SeedService.name)
55
+ constructor(
56
+ private readonly dataService: DataService,
57
+ private readonly xlPortService: XlPortService,
58
+ private dbService: DbService,
59
+ ) {}
60
+ /**
61
+ * Seeds the database with the given data. Any data not provided will be taken from the default seed data.
62
+ */
63
+ public async seed({ data, options = DEFAULTOPTIONS }: { data?: Partial<typeof SEEDDATA>; options?: SeedServiceOptions }) {
64
+ return this._seed({ data: { ...SEEDDATA, ...data }, options })
65
+ }
66
+
67
+ /**
68
+ * Seeds the database with the given data from an Excel file.
69
+ */
70
+ public async seedFromExcel({
71
+ templatePath = './template.xlsx',
72
+ options = DEFAULTOPTIONS,
73
+ }: {
74
+ templatePath?: string
75
+ options?: SeedServiceOptions
76
+ }) {
77
+ const xlData = this.xlPortService.importFromFile(templatePath)
78
+
79
+ const dataParsed = ${meta.seed.templateDecoderName}.safeParse(xlData)
80
+ if (!dataParsed.success) {
81
+ throw new Error(\`Error in parsing Excel seed data: \${JSON.stringify(dataParsed.error.message)}\`)
82
+ }
83
+ return this._seed({ data: dataParsed.data, options })
84
+ }
85
+
86
+ private async _seed({ data, options: { loadDataAfterSeeding, reset, log } }: { data: typeof SEEDDATA; options: SeedServiceOptions }) {
87
+ if (reset) {
88
+ this.logger.log('Resetting database')
89
+ await this.dbService.emptyDatabase()
90
+ } else {
91
+ const config = await this.dbService.config.findFirst({ where: { id: true } })
92
+ if (!config) {
93
+ throw new Error('Database is not initialized')
94
+ }
95
+ if (config.isSeeded) {
96
+ this.logger.log('Database is already seeded')
97
+ return
98
+ }
99
+ }
100
+
101
+ await this.uploadData({ data, log })
102
+
103
+ await this.dbService.config.update({
104
+ where: { id: true },
105
+ data: { isSeeded: true },
106
+ })
107
+
108
+ if (loadDataAfterSeeding) {
109
+ await this.dataService.init()
110
+ }
111
+
112
+ this.logger.log('✅ Done')
113
+ }
114
+
115
+ private async uploadData({ log = false, data }: { data: typeof SEEDDATA; log?: boolean }) {
116
+ // NOTE: the order of these calls is important, because of foreign key constraints
117
+ // The current order is based on the order of the models in the schema
118
+ // Change the order based on your needs.
119
+ // Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
120
+ // e.g. by removing any foreign key first - and then update these after all data is created.
121
+ ${dataLoader.join('\n')}
122
+ }
123
+
124
+ private async upload<T extends { id: ID }, ID extends number | string | boolean>({
125
+ name,
126
+ data,
127
+ repo,
128
+ log = false,
129
+ onlyOnReset = true,
130
+ }: {
131
+ name: string
132
+ data: T[]
133
+ repo: Repository<T, ID>
134
+ log?: boolean
135
+ onlyOnReset?: boolean
136
+ }): Promise<void> {
137
+ if (repo.count() > 0 && onlyOnReset) {
138
+ this.logger.log(\`\${pluralize(name)} already exist, skipping\`)
139
+ return
140
+ }
141
+ if (!log) {
142
+ await repo.createMany(data)
143
+ } else {
144
+ let i = 0
145
+ for (const item of data) {
146
+ i++
147
+ console.log(name, i, item.id)
148
+ try {
149
+ await repo.createWithId?.(item)
150
+ } catch (error) {
151
+ console.log(item)
152
+ throw error
153
+ }
154
+ }
155
+ }
156
+
157
+ this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
158
+ }
159
+ }
160
+ `;
161
+ }
162
+ exports.generateSeedService = generateSeedService;
@@ -28,7 +28,7 @@ function generateSeedTemplateDecoder({ models, meta }) {
28
28
 
29
29
  ${decoders.join('\n')}
30
30
 
31
- export const seedTemplateDecoder = z
31
+ export const ${meta.seed.templateDecoderName} = z
32
32
  .object({${tableDecoders.join(',\n')}})
33
33
  .transform((item) => ({${renameTransforms.join(',\n')}}))
34
34
  `;
@@ -73,8 +73,6 @@ function generateTableDecoder({ model, meta, imports, }) {
73
73
  break;
74
74
  }
75
75
  case 'enum': {
76
- const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: field.enumerator });
77
- imports.addImport({ items: [field.enumerator.tsTypeName], from: refEnumMeta.types.importPath });
78
76
  fieldDecoders.push(`${fieldMeta.excelColumnName}: z.enum([${field.enumerator.values.map((v) => `'${v}'`).join(', ')}])${field.isRequired ? '' : '.nullable()'}`);
79
77
  break;
80
78
  }
@@ -73,6 +73,10 @@ export type SchemaMetaData = {
73
73
  * Path to the index file for the seed package.
74
74
  */
75
75
  indexFilePath: Types.Path;
76
+ /**
77
+ * Path to the SeedService file.
78
+ */
79
+ serviceFilePath: Types.Path;
76
80
  /**
77
81
  * Path to seed Excel template file.
78
82
  */
@@ -81,6 +85,10 @@ export type SchemaMetaData = {
81
85
  * Path to template decoder file.
82
86
  */
83
87
  templateDecoderFilePath: Types.Path;
88
+ /**
89
+ * The name of the template decoder function.
90
+ */
91
+ templateDecoderName: Types.Fnction;
84
92
  /**
85
93
  * Path that may be used in the import statement.
86
94
  */
package/dist/lib/meta.js CHANGED
@@ -55,9 +55,11 @@ function getSchemaMetadata({ config }) {
55
55
  importPath: Types.toPath(`@${config.project}/trpc`),
56
56
  },
57
57
  seed: {
58
- indexFilePath: Types.toPath(`${config.paths.seedPath}index`),
58
+ indexFilePath: Types.toPath(`${config.paths.seedPath}data/index`),
59
+ serviceFilePath: Types.toPath(`${config.paths.seedPath}seed.service`),
59
60
  templateExcelFilePath: Types.toPath(`${config.paths.seedPath}template.xlsx`),
60
- templateDecoderFilePath: Types.toPath(`${config.paths.seedPath}seed-decoder`),
61
+ templateDecoderFilePath: Types.toPath(`${config.paths.seedPath}seed.decoder`),
62
+ templateDecoderName: Types.toFunction(`seedTemplateDecoder`),
61
63
  importPath: Types.toPath(`@${config.project}/seed`),
62
64
  randomSeed: config.randomSeed,
63
65
  },
@@ -108,7 +110,7 @@ function getModelMetadata({ model }) {
108
110
  dataRepositoryVariableName: Types.toVariableName(`data`),
109
111
  },
110
112
  seed: {
111
- filePath: Types.toPath(`${config.paths.seedPath}${uncapitalizedPlural}`),
113
+ filePath: Types.toPath(`${config.paths.seedPath}data/${uncapitalizedPlural}`),
112
114
  constantName: Types.toVariableName(`${uncapitalizedPlural}`),
113
115
  importPath: Types.toPath(`@${config.project}/seed`),
114
116
  excel: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.16.7",
3
+ "version": "0.17.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {
@@ -45,7 +45,7 @@
45
45
  "scripts": {
46
46
  "build": "tsc -p tsconfig.build.json",
47
47
  "prepublish": "tsc -p tsconfig.build.json",
48
- "dev": "tsc -b -w",
48
+ "dev": "tsc -p tsconfig.build.json -w",
49
49
  "test": "jest",
50
50
  "test:watch": "jest --watch",
51
51
  "test:types": "tsc --noEmit",