@postxl/generator 0.34.0 → 0.36.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 (60) hide show
  1. package/dist/generator.js +91 -32
  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/{businesslogicservice.generator.js → businesslogic-update-service.generator.js} +9 -10
  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/businesslogic-view-service.generator.js +34 -0
  16. package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
  17. package/dist/generators/indices/datamocker.generator.js +46 -40
  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} +24 -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/lib/vfs.d.ts +33 -0
  51. package/dist/lib/vfs.js +157 -0
  52. package/dist/prisma/attributes.js +7 -3
  53. package/dist/prisma/parse.js +25 -5
  54. package/package.json +8 -3
  55. package/dist/generators/indices/businesslogicindex.generator.js +0 -19
  56. package/dist/generators/indices/seed.generator.js +0 -17
  57. package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
  58. package/dist/generators/indices/testdataservice.generator.js +0 -78
  59. package/dist/generators/models/businesslogic.generator.d.ts +0 -9
  60. /package/dist/generators/indices/{datamockmodule.generator.d.ts → datamock-module.generator.d.ts} +0 -0
@@ -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;
@@ -5,20 +5,21 @@ const imports_1 = require("../../lib/imports");
5
5
  const meta_1 = require("../../lib/meta");
6
6
  const types_1 = require("../../lib/schema/types");
7
7
  const types_2 = require("../../lib/types");
8
+ const string_1 = require("../../lib/utils/string");
8
9
  // TODO: Remove hardcoded path that seems to be reused in multiple places!
9
10
  const PXL_COMMON = (0, types_1.toPath)('@pxl/common');
10
11
  /**
11
12
  * Creates a decoder for the Seed Excel template.
12
13
  */
13
14
  function generateSeedTemplateDecoder({ models, meta }) {
14
- const imports = imports_1.ImportsGenerator.from(meta.seed.templateDecoderFilePath);
15
+ const imports = imports_1.ImportsGenerator.from(meta.seedData.templateDecoderFilePath);
15
16
  const decoders = [];
16
17
  const tableDecoders = [];
17
18
  const renameTransforms = [];
18
19
  for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
19
20
  const modelMeta = (0, meta_1.getModelMetadata)({ model });
20
21
  decoders.push(generateTableDecoder({ model, meta: modelMeta, imports }));
21
- tableDecoders.push(`${modelMeta.seed.excel.tableName}: z.array(${modelMeta.seed.decoder.schemaName})`);
22
+ tableDecoders.push(`${modelMeta.seed.excel.tableName}: ${modelMeta.seed.decoder.decoderName}`);
22
23
  renameTransforms.push(`${modelMeta.seed.decoder.dataName}: item['${modelMeta.seed.excel.tableName}']`);
23
24
  }
24
25
  return `
@@ -26,9 +27,14 @@ function generateSeedTemplateDecoder({ models, meta }) {
26
27
 
27
28
  ${imports.generate()}
28
29
 
30
+ /**
31
+ * Helper schema to identify blank Excel cells
32
+ */
33
+ const nullOrBlankSchema = z.union([z.null(), z.literal('')])
34
+
29
35
  ${decoders.join('\n')}
30
36
 
31
- export const ${meta.seed.templateDecoderName} = z
37
+ export const ${meta.seedData.templateDecoderName} = z
32
38
  .object({${tableDecoders.join(',\n')}})
33
39
  .transform((item) => ({${renameTransforms.join(',\n')}}))
34
40
  `;
@@ -36,10 +42,12 @@ function generateSeedTemplateDecoder({ models, meta }) {
36
42
  exports.generateSeedTemplateDecoder = generateSeedTemplateDecoder;
37
43
  function generateTableDecoder({ model, meta, imports, }) {
38
44
  const fieldDecoders = [];
45
+ const blankFieldDecoders = [];
39
46
  const renameTransforms = [];
40
47
  for (const field of model.fields) {
41
48
  const fieldMeta = (0, meta_1.getFieldMetadata)({ field });
42
49
  renameTransforms.push(`${field.name}: item['${fieldMeta.excelColumnName}']`);
50
+ blankFieldDecoders.push(`${fieldMeta.excelColumnName}: nullOrBlankSchema`);
43
51
  switch (field.kind) {
44
52
  case 'id': {
45
53
  imports.addImport({ items: [meta.types.toBrandedIdTypeFnName], from: meta.types.importPath });
@@ -81,6 +89,7 @@ function generateTableDecoder({ model, meta, imports, }) {
81
89
  }
82
90
  }
83
91
  }
92
+ const blankSchemaName = `blank${(0, string_1.capitalize)(meta.seed.decoder.schemaName)}`;
84
93
  return `
85
94
  /**
86
95
  * The schema for the ${model.name} table
@@ -88,13 +97,20 @@ function generateTableDecoder({ model, meta, imports, }) {
88
97
  const ${meta.seed.decoder.schemaName} = z
89
98
  .object({${fieldDecoders.join(',\n')}})
90
99
  .transform((item) => ({${renameTransforms.join(',\n')}}))
91
-
92
- /**
100
+
101
+ /** The schema to identify blank rows in the ${model.name} table */
102
+ const ${blankSchemaName} = z
103
+ .object({${blankFieldDecoders.join(',\n')}})
104
+ .transform(() => undefined)
105
+
106
+ /**
93
107
  * The type of the ${model.name} table
94
108
  */
95
109
  export type ${meta.seed.decoder.decoderTypeName} = z.infer<typeof ${meta.seed.decoder.schemaName}>
96
110
 
97
- export const ${meta.seed.decoder.decoderName} = z.array(${meta.seed.decoder.schemaName})
111
+ export const ${meta.seed.decoder.decoderName} = z
112
+ .array(${meta.seed.decoder.schemaName}.or(${blankSchemaName}))
113
+ .transform((items) => items.filter(Boolean) as ${meta.seed.decoder.decoderTypeName}[])
98
114
  `;
99
115
  }
100
116
  function toExcelDecoder({ tsTypeName, dbTypeName: typeName, nullable, imports, }) {
@@ -1,9 +1,9 @@
1
1
  import { SchemaMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
- * Generates index file for all seed files.
4
+ * Generates a type for all SeedData.
5
5
  */
6
- export declare function generateSeedIndex({ models, meta }: {
6
+ export declare function generateSeedDataType({ models, meta }: {
7
7
  models: Model[];
8
8
  meta: SchemaMetaData;
9
9
  }): string;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSeedDataType = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ const types_1 = require("../../lib/schema/types");
7
+ /**
8
+ * Generates a type for all SeedData.
9
+ */
10
+ function generateSeedDataType({ models, meta }) {
11
+ const imports = imports_1.ImportsGenerator.from(meta.types.importPath);
12
+ imports.addImport({ from: meta.data.importPath, items: [(0, types_1.toClassName)('MockData')] });
13
+ const modelTypes = [];
14
+ const mockConverters = [];
15
+ for (const model of models) {
16
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
17
+ imports.addImport({
18
+ items: [modelMeta.types.dto.create, modelMeta.types.dto.update, modelMeta.types.dto.upsert],
19
+ from: modelMeta.types.importPath,
20
+ });
21
+ modelTypes.push(`${modelMeta.seed.constantName}?: {
22
+ create?: ${modelMeta.types.dto.create}[]
23
+ update?: ${modelMeta.types.dto.update}[]
24
+ upsert?: ${modelMeta.types.dto.upsert}[]
25
+ delete?: string[]
26
+ }`);
27
+ mockConverters.push(`${modelMeta.seed.constantName}: { create: data.${modelMeta.seed.constantName} }`);
28
+ }
29
+ return `
30
+ ${imports.generate()}
31
+
32
+ export type SeedData = {
33
+ ${modelTypes.join('\n')}
34
+ }
35
+
36
+ export function mockDataToCreate(data: MockData): SeedData {
37
+ return {
38
+ ${mockConverters.join(',\n')}
39
+ }
40
+ }`;
41
+ }
42
+ exports.generateSeedDataType = generateSeedDataType;
@@ -3,7 +3,7 @@ import { Enum, Model } from '../../lib/schema/schema';
3
3
  /**
4
4
  * Generates an index file that exports all types.
5
5
  */
6
- export declare function generateTypesIndex({ models, enums, meta, }: {
6
+ export declare function generateTypesIndex({ models, enums, meta: schemaMeta, }: {
7
7
  models: Model[];
8
8
  enums: Enum[];
9
9
  meta: SchemaMetaData;
@@ -6,16 +6,18 @@ const meta_1 = require("../../lib/meta");
6
6
  /**
7
7
  * Generates an index file that exports all types.
8
8
  */
9
- function generateTypesIndex({ models, enums, meta, }) {
10
- const exports = exports_1.ExportsGenerator.from(meta.types.indexFilePath);
9
+ function generateTypesIndex({ models, enums, meta: schemaMeta, }) {
10
+ const exports = exports_1.ExportsGenerator.from(schemaMeta.types.indexFilePath);
11
11
  for (const model of models) {
12
- const meta = (0, meta_1.getModelMetadata)({ model });
13
- exports.exportEverythingFromPath(meta.types.filePath);
12
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
13
+ exports.exportEverythingFromPath(modelMeta.types.filePath);
14
14
  }
15
15
  for (const enumerator of enums) {
16
- const meta = (0, meta_1.getEnumMetadata)({ enumerator });
17
- exports.exportEverythingFromPath(meta.types.filePath);
16
+ const enumMeta = (0, meta_1.getEnumMetadata)({ enumerator });
17
+ exports.exportEverythingFromPath(enumMeta.types.filePath);
18
18
  }
19
+ // dto.types contains generic types that are used by all models
20
+ exports.exportEverythingFromPath(schemaMeta.types.dto.path);
19
21
  return exports.generate();
20
22
  }
21
23
  exports.generateTypesIndex = generateTypesIndex;
@@ -0,0 +1,10 @@
1
+ import { ModelMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates update business logic for a given model.
5
+ * The update logic handles all Create/Update/Delete/Upsert operations. See template's readme for more info.
6
+ */
7
+ export declare function generateModelBusinessLogicUpdate({ model, meta }: {
8
+ model: Model;
9
+ meta: ModelMetaData;
10
+ }): string;