@postxl/generator 0.38.2 → 0.40.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 (56) hide show
  1. package/dist/generator.js +43 -21
  2. package/dist/generators/enums/types.generator.js +1 -1
  3. package/dist/generators/indices/businesslogic-actiontypes.generator.js +1 -1
  4. package/dist/generators/indices/businesslogic-update-module.generator.js +2 -2
  5. package/dist/generators/indices/businesslogic-update-service.generator.js +1 -1
  6. package/dist/generators/indices/businesslogic-view-module.generator.js +2 -2
  7. package/dist/generators/indices/businesslogic-view-service.generator.js +1 -1
  8. package/dist/generators/indices/{seed-service.generator.d.ts → data-types.generator.d.ts} +2 -2
  9. package/dist/generators/indices/data-types.generator.js +48 -0
  10. package/dist/generators/indices/datamock-module.generator.js +11 -11
  11. package/dist/generators/indices/datamocker.generator.js +1 -1
  12. package/dist/generators/indices/datamodule.generator.js +34 -52
  13. package/dist/generators/indices/dataservice.generator.js +202 -9
  14. package/dist/generators/indices/dispatcher-service.generator.js +20 -10
  15. package/dist/generators/indices/emptydatabasemigration.generator.d.ts +2 -0
  16. package/dist/generators/indices/emptydatabasemigration.generator.js +14 -7
  17. package/dist/generators/indices/importexport-convert-import-functions.generator.d.ts +9 -0
  18. package/dist/generators/indices/importexport-convert-import-functions.generator.js +528 -0
  19. package/dist/generators/indices/importexport-exporter-class.generator.d.ts +9 -0
  20. package/dist/generators/indices/importexport-exporter-class.generator.js +116 -0
  21. package/dist/generators/indices/importexport-import-service.generator.d.ts +9 -0
  22. package/dist/generators/indices/importexport-import-service.generator.js +563 -0
  23. package/dist/generators/indices/{seeddata-type.generator.d.ts → importexport-types.generator.d.ts} +2 -2
  24. package/dist/generators/indices/importexport-types.generator.js +234 -0
  25. package/dist/generators/indices/repositories.generator.js +8 -8
  26. package/dist/generators/indices/seed-migration.generator.js +1 -1
  27. package/dist/generators/indices/seed-template.generator.js +1 -1
  28. package/dist/generators/indices/selectors.generator.d.ts +7 -0
  29. package/dist/generators/indices/selectors.generator.js +82 -0
  30. package/dist/generators/indices/{seed-template-decoder.generator.d.ts → testdata-service.generator.d.ts} +2 -2
  31. package/dist/generators/indices/testdata-service.generator.js +71 -0
  32. package/dist/generators/models/businesslogic-update.generator.js +6 -6
  33. package/dist/generators/models/businesslogic-view.generator.js +4 -4
  34. package/dist/generators/models/importexport-decoder.generator.d.ts +23 -0
  35. package/dist/generators/models/importexport-decoder.generator.js +234 -0
  36. package/dist/generators/models/react.generator/library.generator.js +4 -0
  37. package/dist/generators/models/react.generator/modals.generator.js +35 -8
  38. package/dist/generators/models/repository.generator.js +156 -18
  39. package/dist/generators/models/route.generator.js +2 -2
  40. package/dist/generators/models/stub.generator.js +1 -1
  41. package/dist/generators/models/types.generator.js +1 -1
  42. package/dist/lib/id-collector.d.ts +43 -0
  43. package/dist/lib/id-collector.js +53 -0
  44. package/dist/lib/imports.d.ts +1 -1
  45. package/dist/lib/meta.d.ts +480 -122
  46. package/dist/lib/meta.js +187 -74
  47. package/dist/lib/schema/schema.d.ts +58 -43
  48. package/dist/lib/schema/types.d.ts +63 -12
  49. package/dist/lib/schema/types.js +27 -7
  50. package/dist/lib/utils/string.d.ts +1 -0
  51. package/dist/lib/utils/string.js +1 -0
  52. package/dist/prisma/parse.js +4 -4
  53. package/package.json +2 -2
  54. package/dist/generators/indices/seed-service.generator.js +0 -356
  55. package/dist/generators/indices/seed-template-decoder.generator.js +0 -151
  56. package/dist/generators/indices/seeddata-type.generator.js +0 -42
@@ -8,30 +8,55 @@ const meta_1 = require("../../lib/meta");
8
8
  */
9
9
  function generateDataService({ models, meta }) {
10
10
  const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
11
- const imports = imports_1.ImportsGenerator.from(meta.data.dataServiceFilePath);
11
+ const imports = imports_1.ImportsGenerator.from(meta.data.dataService.filePath);
12
12
  for (const { meta } of mm) {
13
13
  imports.addImport({
14
- items: [meta.data.repositoryClassName],
15
- from: meta.data.repoFilePath,
14
+ items: [meta.data.repository.className],
15
+ from: meta.data.repository.filePath,
16
16
  });
17
17
  }
18
18
  const constructor = mm
19
- .map(({ meta }) => `public ${meta.data.dataServiceName} :${meta.data.repositoryClassName}`)
19
+ .map(({ meta }) => `public ${meta.data.dataServiceName} :${meta.data.repository.className}`)
20
20
  .join(',\n');
21
21
  const initializer = mm.map(({ meta }) => `await this.${meta.data.dataServiceName}.init()`).join('\n');
22
22
  const excelExports = mm
23
- .map(({ meta }) => `${meta.data.excelExportTableName}: mapValues(await this.${meta.data.dataServiceName}.getAll()),`)
23
+ .map(({ meta }) => `${meta.importExport.tableName}: mapValues(await this.${meta.data.dataServiceName}.getAll()),`)
24
24
  .join('\n');
25
25
  const isEmptyChecks = mm.map(({ meta }) => `(await this.${meta.data.dataServiceName}.count()) === 0`).join(' &&');
26
- return `
27
- import { Injectable } from '@nestjs/common'
26
+ // Building blocks for bulk mutations
27
+ imports.addImports({
28
+ [meta.types.importPath]: [meta.types.dto.create, meta.types.dto.update],
29
+ [meta.data.repository.typeFilePath]: [meta.data.repository.typeName],
30
+ [meta.data.types.filePath]: [meta.data.types.bulkMutation],
31
+ [meta.actions.importPath]: [meta.actions.actionExecution.interface],
32
+ });
33
+ const creates = [];
34
+ const updates = [];
35
+ const upserts = [];
36
+ const deletes = [];
37
+ for (const model of models) {
38
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
39
+ creates.push(`await this.create({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.create, repo: this.${modelMeta.data.dataServiceName}, execution })`);
40
+ updates.push(`await this.update({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.update, repo: this.${modelMeta.data.dataServiceName}, execution })`);
41
+ upserts.push(`await this.upsert({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.upsert, repo: this.${modelMeta.data.dataServiceName}, execution })`);
42
+ deletes.push(`await this.delete({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.delete, repo: this.${modelMeta.data.dataServiceName}, execution })`);
43
+ }
44
+ return /* ts */ `
45
+ import { Injectable, Logger } from '@nestjs/common'
28
46
 
29
- import { mapValues } from '@pxl/common'
47
+ import { format, mapValues, pluralize } from '@pxl/common'
30
48
 
31
49
  ${imports.generate()}
32
50
 
51
+ // If true, the bulk mutation will be executed one by one, instead of all at once (which is the default).
52
+ // Also, each item will be logged to the console.
53
+ // This should help to quickly identify the item that causes an error in a bulk mutation.
54
+ const DEBUG = false
55
+
33
56
  @Injectable()
34
- export class DataService {
57
+ export class ${meta.data.dataService.class} {
58
+ private readonly logger = new Logger(${meta.data.dataService.class}.name)
59
+
35
60
  constructor(${constructor}) {}
36
61
 
37
62
  public async prepareExcelExport() {
@@ -49,6 +74,174 @@ export class DataService {
49
74
  ${isEmptyChecks}
50
75
  )
51
76
  }
77
+ public async ${meta.data.dataService.executeBulkMutations}(
78
+ { steps, execution }:
79
+ { steps: ${meta.data.types.bulkMutation}[]; execution: ${meta.actions.actionExecution.interface} }
80
+ ) {
81
+ let index = 0
82
+ for (const step of steps) {
83
+ this.logger.log(\`Uploading data step \${++index}/\${steps.length}\`)
84
+ await this.${meta.data.dataService.executeBulkMutation}({ data: step, execution })
85
+ }
86
+ }
87
+
88
+ public async ${meta.data.dataService.executeBulkMutation}(
89
+ { data, execution }:
90
+ { data: ${meta.data.types.bulkMutation}, execution: ${meta.actions.actionExecution.interface}}
91
+ ) {
92
+ // NOTE: the order of these calls is important, because of foreign key constraints
93
+ // The current order is based on the order of the models in the schema
94
+ // Change the order based on your needs.
95
+ // Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
96
+ // e.g. by removing any foreign key first - and then update these after all data is created.
97
+ // Alternatively, you can also do this in multiple steps
98
+ ${creates.join('\n')}
99
+
100
+ ${updates.join('\n')}
101
+
102
+ ${upserts.join('\n')}
103
+
104
+ ${deletes.join('\n')}
105
+ }
106
+
107
+ private async create<T extends { id: ID }, ID extends number | string | boolean>({
108
+ name,
109
+ data,
110
+ repo,
111
+ execution,
112
+ }: {
113
+ name: string
114
+ data: ${meta.types.dto.create}<T, ID>[] | undefined
115
+ repo: ${meta.data.repository.typeName}<T, ID>
116
+ execution: ${meta.actions.actionExecution.interface}
117
+ }): Promise<void> {
118
+ if (!data) {
119
+ return
120
+ }
121
+ if (!DEBUG) {
122
+ await repo.createMany?.({ items: data, execution })
123
+ } else {
124
+ let i = 0
125
+ for (const item of data) {
126
+ i++
127
+ this.logger.log(\`Creating \${name} \${i} - \${_getItemDescription(item)}\`)
128
+ try {
129
+ await repo.create?.({ item, execution })
130
+ } catch (error) {
131
+ this.logger.error(\`Error creating \${name} \${i} - \${_getItemDescription(item)}\`, error)
132
+ throw error
133
+ }
134
+ }
135
+ }
136
+ this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
137
+ }
138
+
139
+ private async update<T extends { id: ID }, ID extends number | string | boolean>({
140
+ name,
141
+ data,
142
+ repo,
143
+ execution,
144
+ }: {
145
+ name: string
146
+ data: ${meta.types.dto.update}<T, ID>[] | undefined
147
+ repo: ${meta.data.repository.typeName}<T, ID>
148
+ execution: ${meta.actions.actionExecution.interface}
149
+ }): Promise<void> {
150
+ if (!data) {
151
+ return
152
+ }
153
+ if (!DEBUG) {
154
+ await repo.updateMany?.({ items: data, execution })
155
+ } else {
156
+ let i = 0
157
+ for (const item of data) {
158
+ i++
159
+ this.logger.log(\`Updating \${name} \${i} - \${_getItemDescription(item)}\`)
160
+ try {
161
+ await repo.update?.({ item, execution })
162
+ } catch (error) {
163
+ this.logger.error(\`Error updating \${name} \${i} - \${_getItemDescription(item)}\`, error)
164
+ throw error
165
+ }
166
+ }
167
+ }
168
+ this.logger.log(\`✅ Updated \${format(data.length)} \${pluralize(name)}\`)
169
+ }
170
+
171
+ private async upsert<T extends { id: ID }, ID extends number | string | boolean>({
172
+ name,
173
+ data,
174
+ repo,
175
+ execution,
176
+ }: {
177
+ name: string
178
+ data: (${meta.types.dto.create}<T, ID> | ${meta.types.dto.update}<T, ID>)[] | undefined
179
+ repo: ${meta.data.repository.typeName}<T, ID>
180
+ execution: ${meta.actions.actionExecution.interface}
181
+ }): Promise<void> {
182
+ if (!data) {
183
+ return
184
+ }
185
+ if (!DEBUG) {
186
+ await repo.upsertMany?.({ items: data, execution })
187
+ } else {
188
+ let i = 0
189
+ for (const item of data) {
190
+ i++
191
+ this.logger.log(\`Upserting \${name} \${i} - \${_getItemDescription(item)}\`)
192
+ try {
193
+ await repo.upsert?.({ item, execution })
194
+ } catch (error) {
195
+ this.logger.error(\`Error upserting \${name} \${i} - \${_getItemDescription(item)}\`, error)
196
+ throw error
197
+ }
198
+ }
199
+ }
200
+ this.logger.log(\`✅ Upserted \${format(data.length)} \${pluralize(name)}\`)
201
+ }
202
+
203
+ private async delete<T extends { id: ID }, ID extends number | string | boolean>({
204
+ name,
205
+ data,
206
+ repo,
207
+ execution,
208
+ }: {
209
+ name: string
210
+ data: ID[] | undefined
211
+ repo: ${meta.data.repository.typeName}<T, ID>
212
+ execution: ${meta.actions.actionExecution.interface}
213
+ }): Promise<void> {
214
+ if (!data) {
215
+ return
216
+ }
217
+ if (!DEBUG) {
218
+ await repo.deleteMany?.({ ids: data, execution })
219
+ } else {
220
+ let i = 0
221
+ for (const id of data) {
222
+ i++
223
+ this.logger.log(\`Deleting \${name} \${i} - id: \${id.toString()}\`)
224
+ try {
225
+ await repo.delete?.({ id, execution })
226
+ } catch (error) {
227
+ this.logger.error(\`Error deleting \${name} \${i} - id: \${id.toString()}\`, error)
228
+ throw error
229
+ }
230
+ }
231
+ }
232
+ this.logger.log(\`✅ Deleted \${format(data.length)} \${pluralize(name)}\`)
233
+ }
234
+ }
235
+
236
+ function _getItemDescription<T extends Partial<{ id: ID }>, ID extends number | string | boolean>(item: T): string {
237
+ const id = \`id: \${item.id !== undefined ? item.id.toString() : 'not provided'}\`
238
+
239
+ // If the item has a name, we can assume it is a string and add it to the description
240
+ if ('name' in item) {
241
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
242
+ return \`\${id}, name: \${item.name}\`
243
+ }
244
+ return id
52
245
  }
53
246
  `;
54
247
  }
@@ -7,8 +7,9 @@ const meta_1 = require("../../lib/meta");
7
7
  * Generates the action dispatcher service.
8
8
  */
9
9
  function generateActionsDispatcherService({ models, meta }) {
10
- const imports = imports_1.ImportsGenerator.from(meta.seed.serviceFilePath).addImports({
10
+ const imports = imports_1.ImportsGenerator.from(meta.actions.dispatcherService.filePath).addImports({
11
11
  [meta.seed.importPath]: [meta.seed.serviceClassName],
12
+ [meta.importExport.importPath]: [meta.importExport.importService.name],
12
13
  [meta.businessLogic.update.importPath]: [meta.businessLogic.update.serviceClassName],
13
14
  [meta.types.importPath]: meta.config.userType,
14
15
  });
@@ -17,7 +18,7 @@ function generateActionsDispatcherService({ models, meta }) {
17
18
  const modelMeta = (0, meta_1.getModelMetadata)({ model });
18
19
  dataLoader.push(`await this.upload({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}, repo: this.dataService.${modelMeta.data.dataServiceName}, log })`);
19
20
  }
20
- return `
21
+ return /* ts */ `
21
22
  import { Injectable } from '@nestjs/common'
22
23
 
23
24
  import { ExhaustiveSwitchCheck } from '@pxl/common'
@@ -25,26 +26,31 @@ import { DbService } from '@pxl/db'
25
26
 
26
27
  ${imports.generate()}
27
28
 
28
- import { ActionExecution, createActionExecution } from './actionExecution.class'
29
+ import { ActionExecutionFactory, IActionExecution } from './actionExecution'
29
30
  import { Action, ResultOfAction } from './actions.types'
30
31
 
31
32
 
32
33
  @Injectable()
33
- export class DispatcherService {
34
- // as the SeedModule gets instantiated after the ActionsModule, we use this hack to avoid a circular dependency:
35
- // we set the seedService to a dummy value and then overwrite it in the ${meta.seed.serviceClassName} constructor.
34
+ export class ${meta.actions.dispatcherService.class} {
35
+
36
+ // As SeedModule and ImportExportModule get instantiated after the ActionsModule, we use this hack to avoid a circular dependency:
37
+ // we set the seedService and the importService to value and then overwrite it in the services' constructors.
36
38
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
37
- public seedService: ${meta.seed.serviceClassName} = {} as unknown as any
39
+ public seedService: SeedService = {} as unknown as any
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
41
+ public ${meta.importExport.importService.sharedName}: ImportService = {} as unknown as any
42
+
38
43
  constructor(
39
44
  private readonly updateService: ${meta.businessLogic.update.serviceClassName},
40
- private readonly dbService: DbService
45
+ private readonly dbService: DbService,
46
+ private readonly actionExecutionFactory: ActionExecutionFactory,
41
47
  ) {}
42
48
 
43
49
  public async dispatch<A extends Action>({ action, user }: {
44
50
  action: A;
45
51
  user: ${meta.config.userType}
46
52
  }): Promise<ResultOfAction<A>> {
47
- const execution = await createActionExecution({ action, dbService: this.dbService, user })
53
+ const execution = await this.actionExecutionFactory.create({ action, dbService: this.dbService, user })
48
54
 
49
55
  try {
50
56
  const result = await (this.execute({ action, execution }) as Promise<ResultOfAction<A>>)
@@ -58,7 +64,7 @@ export class DispatcherService {
58
64
  }
59
65
  }
60
66
 
61
- private async execute({ action, execution }: { action: Action; execution: ActionExecution }) {
67
+ private async execute({ action, execution }: { action: Action; execution: IActionExecution }) {
62
68
  switch (action.scope) {
63
69
  ${models
64
70
  .map((model) => {
@@ -71,6 +77,10 @@ export class DispatcherService {
71
77
  .join('\n')}
72
78
  case 'seed':
73
79
  return this.seedService.dispatch({ action, execution })
80
+
81
+ case 'import':
82
+ return this.importService.dispatch({ action, execution })
83
+
74
84
  default:
75
85
  throw new ExhaustiveSwitchCheck(action)
76
86
  }
@@ -2,6 +2,8 @@ import { SchemaMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
4
  * Generates a the Prisma migration to create the `emptyDatabase` stored procedure.
5
+ *
6
+ * The routine is used in e2e tests to empty the database before each test.
5
7
  */
6
8
  export declare function generateEmptyDatabaseStoredProcedure({ models }: {
7
9
  models: Model[];
@@ -7,13 +7,20 @@ exports.prismaMigrationExists = exports.deriveEmptyDatabaseMigrationFilePath = e
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  /**
9
9
  * Generates a the Prisma migration to create the `emptyDatabase` stored procedure.
10
+ *
11
+ * The routine is used in e2e tests to empty the database before each test.
10
12
  */
11
13
  function generateEmptyDatabaseStoredProcedure({ models }) {
12
- const tables = models
13
- .map((model) => `\t${model.sourceSchemaName !== undefined ? `"${model.sourceSchemaName}".` : ''}"${model.sourceName}"`)
14
- .join(',\n');
15
- const useSchemas = models.some((model) => model.sourceSchemaName !== undefined);
16
- const configTableName = useSchemas ? ' "Configuration"."Config"' : '"Config"';
14
+ const modelTables = models
15
+ .map((model) => `\t${model.sourceSchemaName !== undefined ? `"${model.sourceSchemaName}".` : ''}"${model.sourceName}"`);
16
+ // We determine the schema used for all system tables by looking at the User model's schema.
17
+ const userModel = models.find((model) => model.name === 'User');
18
+ if (!userModel) {
19
+ throw new Error('Model definition for "User" could not be found - hence schema for system table cannot be derived!');
20
+ }
21
+ const configSchema = userModel.sourceSchemaName !== undefined ? `"${userModel.sourceSchemaName}".` : '';
22
+ const systemTables = ["Action", "Mutation"].map((table) => `\t${configSchema}"${table}"`);
23
+ const configTableName = `${configSchema}"Config"`;
17
24
  return `
18
25
  CREATE OR REPLACE PROCEDURE "emptyDatabase"() AS $BODY$ BEGIN IF EXISTS (
19
26
  SELECT
@@ -22,8 +29,8 @@ CREATE OR REPLACE PROCEDURE "emptyDatabase"() AS $BODY$ BEGIN IF EXISTS (
22
29
  ) THEN RETURN;
23
30
  END IF;
24
31
  TRUNCATE TABLE
25
- ${tables};
26
- UPDATE ${configTableName} SET "isSeeded"=false;
32
+ ${[...systemTables, ...modelTables].join(',\n')};
33
+
27
34
  END;
28
35
  $BODY$ LANGUAGE plpgsql;
29
36
  `;
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates the Exporter class for the Import-Export module
5
+ */
6
+ export declare function generateImportExportConvertImportFunctions({ models, meta, }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;