@postxl/generator 0.34.0 → 0.35.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 (58) hide show
  1. package/dist/generator.js +40 -22
  2. package/dist/generators/indices/businesslogic-actiontypes.generator.d.ts +9 -0
  3. package/dist/generators/indices/businesslogic-actiontypes.generator.js +39 -0
  4. package/dist/generators/indices/businesslogic-update-index.generator.d.ts +9 -0
  5. package/dist/generators/indices/businesslogic-update-index.generator.js +20 -0
  6. package/dist/generators/indices/businesslogic-update-module.generator.d.ts +9 -0
  7. package/dist/generators/indices/businesslogic-update-module.generator.js +69 -0
  8. package/dist/generators/indices/businesslogic-update-service.generator.d.ts +9 -0
  9. package/dist/generators/indices/businesslogic-update-service.generator.js +34 -0
  10. package/dist/generators/indices/businesslogic-view-index.generator.d.ts +9 -0
  11. package/dist/generators/indices/businesslogic-view-index.generator.js +19 -0
  12. package/dist/generators/indices/{businesslogicindex.generator.d.ts → businesslogic-view-module.generator.d.ts} +2 -2
  13. package/dist/generators/indices/{businesslogicmodule.generator.js → businesslogic-view-module.generator.js} +22 -26
  14. package/dist/generators/indices/{businesslogicservice.generator.d.ts → businesslogic-view-service.generator.d.ts} +1 -1
  15. package/dist/generators/indices/{businesslogicservice.generator.js → businesslogic-view-service.generator.js} +9 -10
  16. package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
  17. package/dist/generators/indices/datamocker.generator.js +3 -7
  18. package/dist/generators/indices/datamodule.generator.js +7 -13
  19. package/dist/generators/indices/{businesslogicmodule.generator.d.ts → dispatcher-service.generator.d.ts} +2 -2
  20. package/dist/generators/indices/dispatcher-service.generator.js +81 -0
  21. package/dist/generators/indices/seed-migration.generator.d.ts +9 -0
  22. package/dist/generators/indices/seed-migration.generator.js +35 -0
  23. package/dist/generators/indices/seed-service.generator.d.ts +1 -1
  24. package/dist/generators/indices/seed-service.generator.js +327 -123
  25. package/dist/generators/indices/seed-template-decoder.generator.js +22 -6
  26. package/dist/generators/indices/{seed.generator.d.ts → seeddata-type.generator.d.ts} +2 -2
  27. package/dist/generators/indices/seeddata-type.generator.js +42 -0
  28. package/dist/generators/indices/types.generator.d.ts +1 -1
  29. package/dist/generators/indices/types.generator.js +8 -6
  30. package/dist/generators/models/businesslogic-update.generator.d.ts +10 -0
  31. package/dist/generators/models/businesslogic-update.generator.js +243 -0
  32. package/dist/generators/models/businesslogic-view.generator.d.ts +10 -0
  33. package/dist/generators/models/{businesslogic.generator.js → businesslogic-view.generator.js} +23 -72
  34. package/dist/generators/models/react.generator/modals.generator.js +2 -2
  35. package/dist/generators/models/repository.generator.d.ts +9 -0
  36. package/dist/generators/models/repository.generator.js +420 -131
  37. package/dist/generators/models/route.generator.js +45 -55
  38. package/dist/generators/models/seed.generator.js +6 -2
  39. package/dist/generators/models/types.generator.js +60 -13
  40. package/dist/lib/attributes.d.ts +5 -0
  41. package/dist/lib/imports.d.ts +23 -2
  42. package/dist/lib/imports.js +19 -1
  43. package/dist/lib/meta.d.ts +287 -34
  44. package/dist/lib/meta.js +87 -16
  45. package/dist/lib/schema/schema.d.ts +24 -6
  46. package/dist/lib/schema/types.d.ts +4 -0
  47. package/dist/lib/utils/jsdoc.d.ts +1 -1
  48. package/dist/lib/utils/jsdoc.js +8 -6
  49. package/dist/lib/utils/string.js +2 -1
  50. package/dist/prisma/attributes.js +7 -3
  51. package/dist/prisma/parse.js +25 -5
  52. package/package.json +1 -1
  53. package/dist/generators/indices/businesslogicindex.generator.js +0 -19
  54. package/dist/generators/indices/seed.generator.js +0 -17
  55. package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
  56. package/dist/generators/indices/testdataservice.generator.js +0 -78
  57. package/dist/generators/models/businesslogic.generator.d.ts +0 -9
  58. /package/dist/generators/indices/{datamockmodule.generator.d.ts → datamock-module.generator.d.ts} +0 -0
@@ -30,17 +30,10 @@ const Types = __importStar(require("../../lib/schema/types"));
30
30
  /**
31
31
  * Generates a data module class.
32
32
  */
33
- // TODO: https://github.com/PostXL/PostXL/issues/347
34
33
  function generateDataModule({ models, meta }) {
35
34
  const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
36
- const imports = imports_1.ImportsGenerator.from(meta.data.dataModuleFilePath)
37
- .addImport({
38
- items: [Types.toVariableName('TestDataService')],
39
- from: meta.data.testDataServiceFilePath,
40
- })
41
- .addImport({
42
- items: [Types.toVariableName('DataService')],
43
- from: meta.data.dataServiceFilePath,
35
+ const imports = imports_1.ImportsGenerator.from(meta.data.dataModuleFilePath).addImports({
36
+ [meta.data.dataServiceFilePath]: [Types.toVariableName('DataService')],
44
37
  });
45
38
  for (const { meta } of mm) {
46
39
  imports.addImport({
@@ -65,7 +58,9 @@ export class ${moduleName} {
65
58
  * The getInstance method should be called by any module that needs the module.
66
59
  */
67
60
  static getInstance(): DynamicModule {
68
- if (!${moduleName}.cachedModule) throw new Error('${moduleName} must be called via .provide first!')
61
+ if (!${moduleName}.cachedModule) {
62
+ throw new Error('${moduleName} must be called via .provide first!')
63
+ }
69
64
  return ${moduleName}.cachedModule
70
65
  }
71
66
 
@@ -106,11 +101,10 @@ export class ${moduleName} {
106
101
  return ${moduleName}.cachedModule
107
102
  }
108
103
 
109
- const repositoryProviders = [
104
+ const providers = [
110
105
  ${mm.map(({ meta }) => meta.data.repositoryClassName).join(',')}
111
106
  ]
112
- const providers = [TestDataService, ...repositoryProviders]
113
-
107
+
114
108
  ${moduleName}.cachedModule = {
115
109
  module: ${moduleName},
116
110
  global: true,
@@ -1,9 +1,9 @@
1
1
  import { SchemaMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
- * Generates a business Logic module class.
4
+ * Generates the action dispatcher service.
5
5
  */
6
- export declare function generateBusinessLogicModule({ models, meta }: {
6
+ export declare function generateActionsDispatcherService({ models, meta }: {
7
7
  models: Model[];
8
8
  meta: SchemaMetaData;
9
9
  }): string;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateActionsDispatcherService = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ /**
7
+ * Generates the action dispatcher service.
8
+ */
9
+ function generateActionsDispatcherService({ models, meta }) {
10
+ const imports = imports_1.ImportsGenerator.from(meta.seed.serviceFilePath).addImports({
11
+ [meta.seed.importPath]: [meta.seed.serviceClassName],
12
+ [meta.businessLogic.update.importPath]: [meta.businessLogic.update.serviceClassName],
13
+ [meta.types.importPath]: meta.config.userType,
14
+ });
15
+ const dataLoader = [];
16
+ for (const model of models) {
17
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
18
+ dataLoader.push(`await this.upload({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}, repo: this.dataService.${modelMeta.data.dataServiceName}, log })`);
19
+ }
20
+ return `
21
+ import { Injectable } from '@nestjs/common'
22
+
23
+ import { ExhaustiveSwitchCheck } from '@pxl/common'
24
+ import { DbService } from '@pxl/db'
25
+
26
+ ${imports.generate()}
27
+
28
+ import { ActionExecution, createActionExecution } from './actionExecution.class'
29
+ import { Action, ResultOfAction } from './actions.types'
30
+
31
+
32
+ @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.
36
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
37
+ public seedService: ${meta.seed.serviceClassName} = {} as unknown as any
38
+ constructor(
39
+ private readonly updateService: ${meta.businessLogic.update.serviceClassName},
40
+ private readonly dbService: DbService
41
+ ) {}
42
+
43
+ public async dispatch<A extends Action>({ action, user }: {
44
+ action: A;
45
+ user: ${meta.config.userType}
46
+ }): Promise<ResultOfAction<A>> {
47
+ const execution = await createActionExecution({ action, dbService: this.dbService, user })
48
+
49
+ try {
50
+ const result = await (this.execute({ action, execution }) as Promise<ResultOfAction<A>>)
51
+
52
+ await execution.success()
53
+
54
+ return result
55
+ } catch (e) {
56
+ await execution.error(e)
57
+ throw e
58
+ }
59
+ }
60
+
61
+ private async execute({ action, execution }: { action: Action; execution: ActionExecution }) {
62
+ switch (action.scope) {
63
+ ${models
64
+ .map((model) => {
65
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
66
+ return `
67
+ case '${modelMeta.businessLogic.scopeName}':
68
+ return this.updateService.${modelMeta.businessLogic.update.serviceVariableName}.dispatch({ action, execution })
69
+ `;
70
+ })
71
+ .join('\n')}
72
+ case 'seed':
73
+ return this.seedService.dispatch({ action, execution })
74
+ default:
75
+ throw new ExhaustiveSwitchCheck(action)
76
+ }
77
+ }
78
+ }
79
+ `;
80
+ }
81
+ exports.generateActionsDispatcherService = generateActionsDispatcherService;
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates the initial migration based on the generated seed data.
5
+ */
6
+ export declare function generateSeedMigration({ models, meta }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSeedMigration = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ /**
7
+ * Generates the initial migration based on the generated seed data.
8
+ */
9
+ function generateSeedMigration({ models, meta }) {
10
+ const imports = imports_1.ImportsGenerator.from(meta.seedData.initialMigrationFilePath);
11
+ const modelTypes = [];
12
+ for (const model of models) {
13
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
14
+ imports.addImport({
15
+ items: [modelMeta.seed.constantName],
16
+ from: modelMeta.seed.filePath,
17
+ });
18
+ modelTypes.push(`${modelMeta.seed.constantName}: { create: ${modelMeta.seed.constantName} }`);
19
+ }
20
+ return `
21
+ import { createActionSeedData } from '${meta.seed.importPath}'
22
+ ${imports.generate()}
23
+
24
+ export const MIGRATION_001_BASEDATA = createActionSeedData({
25
+ name: 'Base data',
26
+ order: 1,
27
+ data: [
28
+ {
29
+ ${modelTypes.join(',\n')}
30
+ },
31
+ ],
32
+ })
33
+ `;
34
+ }
35
+ exports.generateSeedMigration = generateSeedMigration;
@@ -3,7 +3,7 @@ import { Model } from '../../lib/schema/schema';
3
3
  /**
4
4
  * Generates index file for all seed files.
5
5
  */
6
- export declare function generateSeedService({ models, meta }: {
6
+ export declare function generateSeedService({ models }: {
7
7
  models: Model[];
8
8
  meta: SchemaMetaData;
9
9
  }): string;
@@ -1,152 +1,356 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateSeedService = void 0;
4
- const imports_1 = require("../../lib/imports");
5
4
  const meta_1 = require("../../lib/meta");
6
5
  /**
7
6
  * Generates index file for all seed files.
8
7
  */
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 = [];
8
+ function generateSeedService({ models }) {
9
+ const creates = [];
10
+ const updates = [];
11
+ const upserts = [];
12
+ const deletes = [];
15
13
  for (const model of models) {
16
14
  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 })`);
15
+ creates.push(`await this.create({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.create, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
16
+ updates.push(`await this.update({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.update, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
17
+ upserts.push(`await this.upsert({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.upsert, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
18
+ deletes.push(`await this.delete({ name: '${modelMeta.userFriendlyName}', data: data.${modelMeta.seed.constantName}?.delete, repo: this.dataService.${modelMeta.data.dataServiceName}, execution })`);
18
19
  }
19
20
  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
-
21
+ import { Injectable, Logger } from '@nestjs/common'
22
+ import { ExhaustiveSwitchCheck, format, pluralize } from '@pxl/common'
23
+ import { DataService, Repository } from '@pxl/data'
24
+ import { DbService } from '@pxl/db'
25
+ import { XlPortService } from '@pxl/xlport'
26
+
27
+ import { ActionExecution, DispatcherService } from '@pxl/actions'
28
+ import { RootUserService } from '@pxl/root-user'
29
+
30
+ import {
31
+ ActionPreparation_Seed,
32
+ ActionPreparation_Seed_Excel,
33
+ Action_Seed,
34
+ Action_Seed_CustomLogic,
35
+ Action_Seed_Excel,
36
+ createActionSeedExcel,
37
+ } from './action.types'
38
+ import { SeedData } from './seed-data.type'
39
+ import { CreateDTO, UpdateDTO } from '@pxl/types'
40
+ import { SEED_MIGRATIONS } from '@pxl/seed-data'
41
+
42
+ // If true, the seed data will be created/updated one by one, instead of all at once (which is the default).
43
+ // Also, each item will be logged to the console.
44
+ // This should help to quickly identify the item that causes an error in a bulk operation.
45
+ const DEBUG = false
46
+
47
+ @Injectable()
48
+ export class SeedService {
49
+ private readonly logger = new Logger(SeedService.name)
50
+
51
+ constructor(
52
+ private readonly dataService: DataService,
53
+ private readonly xlPortService: XlPortService,
54
+ private readonly dbService: DbService,
55
+ private readonly dispatcherService: DispatcherService,
56
+ private readonly rootUserService: RootUserService,
57
+ ) {
58
+ dispatcherService.seedService = this
59
+ }
60
+
29
61
  /**
30
- * Configuration options for seeding
62
+ * To be called on application startup. Will validate the seed data migrations and execute any pending migrations.
63
+ *
64
+ * Each migration is an action, i.e. will be dispatched via the default dispatcher service and logged like any other action.
31
65
  */
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
66
+ public async seed() {
67
+ this.verifyIntegrity()
68
+
69
+ let executed = 0
70
+ for (const migration of SEED_MIGRATIONS) {
71
+ // Skip any migrations that have already been executed
72
+ const migrationExists = await this.dbService.action.findFirst({
73
+ where: { migrationOrder: migration.order, status: 'Success' },
74
+ })
75
+ if (migrationExists) {
76
+ continue
77
+ }
78
+
79
+ const action = await this.convertToAction(migration)
80
+
81
+ this.logger.log(\`Executing seed migration \${migration.order} - \${migration.name}\`)
82
+ await this.dispatcherService.dispatch({ action, user: this.rootUserService.rootUser })
83
+
84
+ executed++
85
+ }
86
+
87
+ if (executed === 0) {
88
+ if (SEED_MIGRATIONS.length > 0) {
89
+ this.logger.log(\`✅ All \${format(SEED_MIGRATIONS.length)} seed migrations have already been applied executed\`)
90
+ } else {
91
+ this.logger.log(\`No seed migrations found.\`)
92
+ }
93
+ } else {
94
+ if (executed === SEED_MIGRATIONS.length) {
95
+ this.logger.log(\`✅ Executed all \${format(executed)} seed migrations\`)
96
+ } else {
97
+ this.logger.log(
98
+ \`✅ Executed \${format(executed)} seed migrations, skipped \${format(SEED_MIGRATIONS.length - executed)} previous migrations\`,
99
+ )
100
+ }
101
+ }
43
102
  }
44
103
 
45
- const DEFAULTOPTIONS: SeedServiceOptions = { reset: false, log: false }
46
- @Injectable()
47
- export class SeedService {
48
- private readonly logger = new Logger(SeedService.name)
49
- constructor(
50
- private readonly dataService: DataService,
51
- private readonly xlPortService: XlPortService,
52
- private dbService: DbService,
53
- ) {}
54
- /**
55
- * Seeds the database with the given data. Any data not provided will be taken from the default seed data.
56
- */
57
- public async seed({ data, options = DEFAULTOPTIONS }: { data?: Partial<typeof SEEDDATA>; options?: SeedServiceOptions }) {
58
- return this._seed({ data: { ...SEEDDATA, ...data }, options })
104
+ private async convertToAction(migration: ActionPreparation_Seed): Promise<Action_Seed> {
105
+ switch (migration.type) {
106
+ case 'data':
107
+ return migration
108
+ case 'excel':
109
+ return await this.convertExcelToAction(migration)
110
+ case 'custom':
111
+ return migration
112
+ default:
113
+ throw new ExhaustiveSwitchCheck(migration)
59
114
  }
60
-
61
- /**
62
- * Seeds the database with the given data from an Excel file.
63
- */
64
- public async seedFromExcel({
65
- templatePath = './template.xlsx',
66
- options = DEFAULTOPTIONS,
67
- }: {
68
- templatePath?: string
69
- options?: SeedServiceOptions
70
- }) {
71
- const xlData = this.xlPortService.importFromFile(templatePath)
72
-
73
- const dataParsed = ${meta.seed.templateDecoderName}.safeParse(xlData)
74
- if (!dataParsed.success) {
75
- throw new Error(\`Error in parsing Excel seed data: \${JSON.stringify(dataParsed.error.message)}\`)
115
+ }
116
+
117
+ /**
118
+ * Will be call by the dispatcher service to dispatch the given action.
119
+ */
120
+ public async dispatch({ action, execution }: { action: Action_Seed; execution: ActionExecution }) {
121
+ switch (action.type) {
122
+ case 'data':
123
+ return this.uploadDataSteps({ steps: action.payload, execution })
124
+ case 'excel':
125
+ return this.uploadData({ data: action.payload.data, execution })
126
+ case 'custom':
127
+ return this.handleCustomLogic({ action, execution })
128
+
129
+ default:
130
+ throw new ExhaustiveSwitchCheck(action)
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Checks if the order of the migrations is correct and if there are any missing migrations.
136
+ */
137
+ private verifyIntegrity() {
138
+ let startOrder = 0
139
+ for (const { name, order } of SEED_MIGRATIONS) {
140
+ if (order <= startOrder) {
141
+ throw new Error(
142
+ \`Invalid migration order. The order of migration "\${name}" must be after \${startOrder} but is \${order}. Is the order field correct and the order in the SEED_MIGRATIONS array correct?\`,
143
+ )
76
144
  }
77
- return this._seed({ data: dataParsed.data, options })
145
+ startOrder = order
78
146
  }
79
-
80
- private async _seed({ data, options: { reset, log } }: { data: typeof SEEDDATA; options: SeedServiceOptions }) {
81
- if (reset) {
82
- this.logger.log('Resetting database')
83
- await this.dbService.emptyDatabase()
84
- } else {
85
- const config = await this.dbService.config.findFirst({ where: { id: true } })
86
- if (!config) {
87
- throw new Error('Database is not initialized')
88
- }
89
- if (config.isSeeded) {
90
- this.logger.log('Database is already seeded')
91
- return
147
+ }
148
+
149
+ /**
150
+ * Reads the Excel file and parses it using the provided decoder.
151
+ * If the parsing is successful, the data is packaged into an Action_Seed_Excel.
152
+ */
153
+ private async convertExcelToAction({
154
+ name,
155
+ order,
156
+ filename,
157
+ decoder,
158
+ }: ActionPreparation_Seed_Excel): Promise<Action_Seed_Excel> {
159
+ const xlData = await this.xlPortService.importFromFile(filename)
160
+ if (xlData.status === 'error') {
161
+ this.logger.error(xlData.message)
162
+ throw new Error(\`Error importing Excel data\`)
163
+ }
164
+
165
+ const dataParsed = decoder.safeParse(xlData.data.tables)
166
+ if (!dataParsed.success) {
167
+ this.logger.error(dataParsed.error.message)
168
+ throw new Error(\`Error parsing Excel seed data\`)
169
+ }
170
+
171
+ return createActionSeedExcel({ name, order, filename, data: dataParsed.data })
172
+ }
173
+
174
+ /**
175
+ * Executes the custom logic of the given migration.
176
+ * To do this, we retrieve the handler from the migration and call it, injecting the data service into the handler.
177
+ */
178
+ private handleCustomLogic({ action, execution }: { action: Action_Seed_CustomLogic; execution: ActionExecution }) {
179
+ const actionPreparation = SEED_MIGRATIONS.find((m) => m.order === action.order)
180
+ if (!actionPreparation) {
181
+ throw new Error(\`Cannot find custom handler for migration with order \${action.order}\`)
182
+ }
183
+ if (actionPreparation.type !== 'custom') {
184
+ throw new Error(\`Cannot find custom handler for migration with order \${action.order}\`)
185
+ }
186
+ return actionPreparation.handler({ action, data: this.dataService, execution })
187
+ }
188
+ /**
189
+ * Executes the data loading step by step
190
+ */
191
+ private async uploadDataSteps({ steps, execution }: { steps: SeedData[]; execution: ActionExecution }) {
192
+ let index = 0
193
+ for (const step of steps) {
194
+ this.logger.log(\`Uploading data step \${++index}/\${steps.length}\`)
195
+ await this.uploadData({ data: step, execution })
196
+ }
197
+ }
198
+
199
+ private async uploadData({ data, execution }: { data: SeedData; execution: ActionExecution }) {
200
+ // NOTE: the order of these calls is important, because of foreign key constraints
201
+ // The current order is based on the order of the models in the schema
202
+ // Change the order based on your needs.
203
+ // Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
204
+ // e.g. by removing any foreign key first - and then update these after all data is created.
205
+ // Alternatively, you can also do this in multiple steps
206
+ ${creates.join('\n')}
207
+
208
+ ${updates.join('\n')}
209
+
210
+ ${upserts.join('\n')}
211
+
212
+ ${deletes.join('\n')}
213
+ }
214
+
215
+ private async create<T extends { id: ID }, ID extends number | string | boolean>({
216
+ name,
217
+ data,
218
+ repo,
219
+ execution,
220
+ }: {
221
+ name: string
222
+ data: CreateDTO<T, ID>[] | undefined
223
+ repo: Repository<T, ID>
224
+ execution: ActionExecution
225
+ }): Promise<void> {
226
+ if (!data) {
227
+ return
228
+ }
229
+ if (!DEBUG) {
230
+ await repo.createMany?.({ items: data, execution })
231
+ } else {
232
+ let i = 0
233
+ for (const item of data) {
234
+ i++
235
+ this.logger.log(\`Creating \${name} \${i} - \${getItemDescription(item)}\`)
236
+ try {
237
+ await repo.create?.({ item, execution })
238
+ } catch (error) {
239
+ this.logger.error(\`Error creating \${name} \${i} - \${getItemDescription(item)}\`, error)
240
+ throw error
92
241
  }
93
242
  }
94
-
95
- await this.uploadData({ data, log })
96
-
97
- await this.dbService.config.update({
98
- where: { id: true },
99
- data: { isSeeded: true },
100
- })
101
-
102
- this.logger.log('✅ Done')
103
243
  }
244
+ this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
245
+ }
104
246
 
105
- private async uploadData({ log = false, data }: { data: typeof SEEDDATA; log?: boolean }) {
106
- // NOTE: the order of these calls is important, because of foreign key constraints
107
- // The current order is based on the order of the models in the schema
108
- // Change the order based on your needs.
109
- // Attention: Depending on the dependencies in seed data, you may also have to take care of relations,
110
- // e.g. by removing any foreign key first - and then update these after all data is created.
111
- ${dataLoader.join('\n')}
247
+ private async update<T extends { id: ID }, ID extends number | string | boolean>({
248
+ name,
249
+ data,
250
+ repo,
251
+ execution,
252
+ }: {
253
+ name: string
254
+ data: UpdateDTO<T, ID>[] | undefined
255
+ repo: Repository<T, ID>
256
+ execution: ActionExecution
257
+ }): Promise<void> {
258
+ if (!data) {
259
+ return
112
260
  }
113
-
114
- private async upload<T extends { id: ID }, ID extends number | string | boolean>({
115
- name,
116
- data,
117
- repo,
118
- log = false,
119
- onlyOnReset = true,
120
- }: {
121
- name: string
122
- data: T[]
123
- repo: Repository<T, ID>
124
- log?: boolean
125
- onlyOnReset?: boolean
126
- }): Promise<void> {
127
- if (repo.count() > 0 && onlyOnReset) {
128
- this.logger.log(\`\${pluralize(name)} already exist, skipping\`)
129
- return
261
+ if (!DEBUG) {
262
+ await repo.updateMany?.({ items: data, execution })
263
+ } else {
264
+ let i = 0
265
+ for (const item of data) {
266
+ i++
267
+ this.logger.log(\`Updating \${name} \${i} - \${getItemDescription(item)}\`)
268
+ try {
269
+ await repo.update?.({ item, execution })
270
+ } catch (error) {
271
+ this.logger.error(\`Error updating \${name} \${i} - \${getItemDescription(item)}\`, error)
272
+ throw error
273
+ }
130
274
  }
131
- if (!log) {
132
- await repo.createMany(data)
133
- } else {
134
- let i = 0
135
- for (const item of data) {
136
- i++
137
- console.log(name, i, item.id)
138
- try {
139
- await repo.create?.(item)
140
- } catch (error) {
141
- console.log(item)
142
- throw error
143
- }
275
+ }
276
+ this.logger.log(\`✅ Updated \${format(data.length)} \${pluralize(name)}\`)
277
+ }
278
+
279
+ private async upsert<T extends { id: ID }, ID extends number | string | boolean>({
280
+ name,
281
+ data,
282
+ repo,
283
+ execution,
284
+ }: {
285
+ name: string
286
+ data: (CreateDTO<T, ID> | UpdateDTO<T, ID>)[] | undefined
287
+ repo: Repository<T, ID>
288
+ execution: ActionExecution
289
+ }): Promise<void> {
290
+ if (!data) {
291
+ return
292
+ }
293
+ if (!DEBUG) {
294
+ await repo.upsertMany?.({ items: data, execution })
295
+ } else {
296
+ let i = 0
297
+ for (const item of data) {
298
+ i++
299
+ this.logger.log(\`Upserting \${name} \${i} - \${getItemDescription(item)}\`)
300
+ try {
301
+ await repo.upsert?.({ item, execution })
302
+ } catch (error) {
303
+ this.logger.error(\`Error upserting \${name} \${i} - \${getItemDescription(item)}\`, error)
304
+ throw error
144
305
  }
145
306
  }
146
-
147
- this.logger.log(\`✅ Created \${format(data.length)} \${pluralize(name)}\`)
148
307
  }
149
- }
150
- `;
308
+ this.logger.log(\`✅ Upserted \${format(data.length)} \${pluralize(name)}\`)
309
+ }
310
+
311
+ private async delete<T extends { id: ID }, ID extends number | string | boolean>({
312
+ name,
313
+ data,
314
+ repo,
315
+ execution,
316
+ }: {
317
+ name: string
318
+ data: ID[] | undefined
319
+ repo: Repository<T, ID>
320
+ execution: ActionExecution
321
+ }): Promise<void> {
322
+ if (!data) {
323
+ return
324
+ }
325
+ if (!DEBUG) {
326
+ await repo.deleteMany?.({ ids: data, execution })
327
+ } else {
328
+ let i = 0
329
+ for (const id of data) {
330
+ i++
331
+ this.logger.log(\`Deleting \${name} \${i} - id: \${id.toString()}\`)
332
+ try {
333
+ await repo.delete?.({ id, execution })
334
+ } catch (error) {
335
+ this.logger.error(\`Error deleting \${name} \${i} - id: \${id.toString()}\`, error)
336
+ throw error
337
+ }
338
+ }
339
+ }
340
+ this.logger.log(\`✅ Deleted \${format(data.length)} \${pluralize(name)}\`)
341
+ }
342
+ }
343
+
344
+ function getItemDescription<T extends Partial<{ id: ID }>, ID extends number | string | boolean>(item: T): string {
345
+ const id = \`id: \${item.id !== undefined ? item.id.toString() : 'not provided'}\`
346
+
347
+ // If the item has a name, we can assume it is a string and add it to the description
348
+ if ('name' in item) {
349
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
350
+ return \`\${id}, name: \${item.name}\`
351
+ }
352
+ return id
353
+ }
354
+ `;
151
355
  }
152
356
  exports.generateSeedService = generateSeedService;