@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.
- package/dist/generator.js +91 -32
- package/dist/generators/indices/businesslogic-actiontypes.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-actiontypes.generator.js +39 -0
- package/dist/generators/indices/businesslogic-update-index.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-index.generator.js +20 -0
- package/dist/generators/indices/businesslogic-update-module.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-module.generator.js +69 -0
- package/dist/generators/indices/businesslogic-update-service.generator.d.ts +9 -0
- package/dist/generators/indices/{businesslogicservice.generator.js → businesslogic-update-service.generator.js} +9 -10
- package/dist/generators/indices/businesslogic-view-index.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-view-index.generator.js +19 -0
- package/dist/generators/indices/{businesslogicindex.generator.d.ts → businesslogic-view-module.generator.d.ts} +2 -2
- package/dist/generators/indices/{businesslogicmodule.generator.js → businesslogic-view-module.generator.js} +22 -26
- package/dist/generators/indices/{businesslogicservice.generator.d.ts → businesslogic-view-service.generator.d.ts} +1 -1
- package/dist/generators/indices/businesslogic-view-service.generator.js +34 -0
- package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
- package/dist/generators/indices/datamocker.generator.js +46 -40
- package/dist/generators/indices/datamodule.generator.js +7 -13
- package/dist/generators/indices/{businesslogicmodule.generator.d.ts → dispatcher-service.generator.d.ts} +2 -2
- package/dist/generators/indices/dispatcher-service.generator.js +81 -0
- package/dist/generators/indices/seed-migration.generator.d.ts +9 -0
- package/dist/generators/indices/seed-migration.generator.js +35 -0
- package/dist/generators/indices/seed-service.generator.d.ts +1 -1
- package/dist/generators/indices/seed-service.generator.js +327 -123
- package/dist/generators/indices/seed-template-decoder.generator.js +22 -6
- package/dist/generators/indices/{seed.generator.d.ts → seeddata-type.generator.d.ts} +2 -2
- package/dist/generators/indices/seeddata-type.generator.js +42 -0
- package/dist/generators/indices/types.generator.d.ts +1 -1
- package/dist/generators/indices/types.generator.js +8 -6
- package/dist/generators/models/businesslogic-update.generator.d.ts +10 -0
- package/dist/generators/models/businesslogic-update.generator.js +243 -0
- package/dist/generators/models/businesslogic-view.generator.d.ts +10 -0
- package/dist/generators/models/{businesslogic.generator.js → businesslogic-view.generator.js} +24 -72
- package/dist/generators/models/react.generator/modals.generator.js +2 -2
- package/dist/generators/models/repository.generator.d.ts +9 -0
- package/dist/generators/models/repository.generator.js +420 -131
- package/dist/generators/models/route.generator.js +45 -55
- package/dist/generators/models/seed.generator.js +6 -2
- package/dist/generators/models/types.generator.js +60 -13
- package/dist/lib/attributes.d.ts +5 -0
- package/dist/lib/imports.d.ts +23 -2
- package/dist/lib/imports.js +19 -1
- package/dist/lib/meta.d.ts +287 -34
- package/dist/lib/meta.js +87 -16
- package/dist/lib/schema/schema.d.ts +24 -6
- package/dist/lib/schema/types.d.ts +4 -0
- package/dist/lib/utils/jsdoc.d.ts +1 -1
- package/dist/lib/utils/jsdoc.js +8 -6
- package/dist/lib/utils/string.js +2 -1
- package/dist/lib/vfs.d.ts +33 -0
- package/dist/lib/vfs.js +157 -0
- package/dist/prisma/attributes.js +7 -3
- package/dist/prisma/parse.js +25 -5
- package/package.json +8 -3
- package/dist/generators/indices/businesslogicindex.generator.js +0 -19
- package/dist/generators/indices/seed.generator.js +0 -17
- package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
- package/dist/generators/indices/testdataservice.generator.js +0 -78
- package/dist/generators/models/businesslogic.generator.d.ts +0 -9
- /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
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
throw new
|
|
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
|
-
|
|
145
|
+
startOrder = order
|
|
78
146
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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.
|
|
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}:
|
|
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.
|
|
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
|
|
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
|
|
4
|
+
* Generates a type for all SeedData.
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
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(
|
|
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
|
|
13
|
-
exports.exportEverythingFromPath(
|
|
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
|
|
17
|
-
exports.exportEverythingFromPath(
|
|
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;
|