@postxl/generator 0.39.0 → 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.
- package/dist/generator.js +37 -21
- package/dist/generators/indices/businesslogic-update-module.generator.js +1 -1
- package/dist/generators/indices/businesslogic-view-module.generator.js +1 -1
- package/dist/generators/indices/{seed-service.generator.d.ts → data-types.generator.d.ts} +2 -2
- package/dist/generators/indices/data-types.generator.js +48 -0
- package/dist/generators/indices/datamock-module.generator.js +7 -7
- package/dist/generators/indices/datamodule.generator.js +13 -34
- package/dist/generators/indices/dataservice.generator.js +201 -8
- package/dist/generators/indices/dispatcher-service.generator.js +14 -5
- package/dist/generators/indices/importexport-convert-import-functions.generator.d.ts +9 -0
- package/dist/generators/indices/importexport-convert-import-functions.generator.js +528 -0
- package/dist/generators/indices/{seed-template-decoder.generator.d.ts → importexport-exporter-class.generator.d.ts} +2 -2
- package/dist/generators/indices/importexport-exporter-class.generator.js +116 -0
- package/dist/generators/indices/importexport-import-service.generator.d.ts +9 -0
- package/dist/generators/indices/importexport-import-service.generator.js +563 -0
- package/dist/generators/indices/{seeddata-type.generator.d.ts → importexport-types.generator.d.ts} +2 -2
- package/dist/generators/indices/importexport-types.generator.js +234 -0
- package/dist/generators/indices/repositories.generator.js +7 -7
- package/dist/generators/indices/seed-template.generator.js +1 -1
- package/dist/generators/indices/testdata-service.generator.js +5 -4
- package/dist/generators/models/businesslogic-update.generator.js +5 -5
- package/dist/generators/models/businesslogic-view.generator.js +3 -3
- package/dist/generators/models/importexport-decoder.generator.d.ts +23 -0
- package/dist/generators/models/importexport-decoder.generator.js +234 -0
- package/dist/generators/models/repository.generator.js +50 -21
- package/dist/lib/imports.d.ts +1 -1
- package/dist/lib/meta.d.ts +468 -134
- package/dist/lib/meta.js +187 -80
- package/dist/lib/schema/schema.d.ts +54 -43
- package/dist/lib/schema/types.d.ts +63 -12
- package/dist/lib/schema/types.js +27 -7
- package/dist/lib/utils/string.d.ts +1 -0
- package/dist/lib/utils/string.js +1 -0
- package/dist/prisma/parse.js +4 -4
- package/package.json +1 -1
- package/dist/generators/indices/seed-service.generator.js +0 -354
- package/dist/generators/indices/seed-template-decoder.generator.js +0 -151
- package/dist/generators/indices/seeddata-type.generator.js +0 -42
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateImportExportConvertImportFunctions = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
const types_1 = require("../../lib/types");
|
|
7
|
+
/**
|
|
8
|
+
* Generates the Exporter class for the Import-Export module
|
|
9
|
+
*/
|
|
10
|
+
function generateImportExportConvertImportFunctions({ models, meta, }) {
|
|
11
|
+
const { dto } = meta.types;
|
|
12
|
+
const { importedDataToBulkMutations, deltaToBulkMutations } = meta.importExport.converterFunctions;
|
|
13
|
+
const { types } = meta.importExport;
|
|
14
|
+
const { delta } = types;
|
|
15
|
+
const imports = imports_1.ImportsGenerator.from(meta.importExport.converterFunctions.filePath);
|
|
16
|
+
imports.addImports({
|
|
17
|
+
[meta.importExport.decoder.fullDecoderFilePath]: [meta.importExport.decoder.decodedPXLModelDataTypeName],
|
|
18
|
+
[meta.data.importPath]: [
|
|
19
|
+
meta.data.dataMockDataType,
|
|
20
|
+
meta.data.types.bulkMutation,
|
|
21
|
+
meta.data.types.bulkMutationForModel,
|
|
22
|
+
],
|
|
23
|
+
[meta.types.importPath]: [dto.genericModel, dto.idType],
|
|
24
|
+
[types.filePath]: [types.delta, types.delta_Model.type],
|
|
25
|
+
});
|
|
26
|
+
const importConverterBlocks = [];
|
|
27
|
+
const deltaConverterBlocks = [];
|
|
28
|
+
let state = initializeModelsState(models);
|
|
29
|
+
let nextStep = determineNextStep(state);
|
|
30
|
+
// logState({ state, nextStep, lastStep: 'init' })
|
|
31
|
+
let iteration = 0;
|
|
32
|
+
while (nextStep !== 'completed') {
|
|
33
|
+
iteration++;
|
|
34
|
+
const { importConvertBlock, deltaConvertBlock, state: stateUpdated, } = generateBlocks({ state, nextStep, imports, iteration, schemaMeta: meta });
|
|
35
|
+
importConverterBlocks.push(importConvertBlock);
|
|
36
|
+
deltaConverterBlocks.push(deltaConvertBlock);
|
|
37
|
+
state = updateModelState(stateUpdated);
|
|
38
|
+
nextStep = determineNextStep(state);
|
|
39
|
+
// logState({ state, nextStep, lastStep: 'updateModelState' })
|
|
40
|
+
}
|
|
41
|
+
return /* ts */ `
|
|
42
|
+
import { ExhaustiveSwitchCheck } from '@pxl/common'
|
|
43
|
+
${imports.generate()}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Converts ${types.delta} to ${meta.data.types.bulkMutation} steps, taking into account the order of dependencies.
|
|
47
|
+
* **Important**: It assumes that data has been pre-checked and converted to \`${delta}\`.
|
|
48
|
+
*/
|
|
49
|
+
export function ${deltaToBulkMutations}(
|
|
50
|
+
input: ${types.delta}
|
|
51
|
+
): ${meta.data.types.bulkMutation}[] {
|
|
52
|
+
return [${deltaConverterBlocks.join(',\n')}]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Converts any models to ${meta.data.types.bulkMutation} steps, taking into account the order of dependencies.
|
|
57
|
+
* **Important**: It does not check if the items exist, ie. it is meant mainly for seeding.
|
|
58
|
+
* For sophisticated imports that handle deltas, use \`${deltaToBulkMutations}\` instead!
|
|
59
|
+
*/
|
|
60
|
+
export function ${importedDataToBulkMutations}(
|
|
61
|
+
input: ${meta.importExport.decoder.decodedPXLModelDataTypeName}
|
|
62
|
+
): ${meta.data.types.bulkMutation}[] {
|
|
63
|
+
return [${importConverterBlocks.join(',\n')}]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Removes empty arrays from a ${meta.data.types.bulkMutation} object.
|
|
68
|
+
*/
|
|
69
|
+
function _removeBlank${meta.data.types.bulkMutation}<Model extends ${dto.genericModel}<ID>, ID extends ${dto.idType}>(
|
|
70
|
+
bulk: ${meta.data.types.bulkMutationForModel}<Model, ID>,
|
|
71
|
+
): ${meta.data.types.bulkMutationForModel}<Model, ID> {
|
|
72
|
+
const result: ${meta.data.types.bulkMutationForModel}<Model, ID> = {}
|
|
73
|
+
if (bulk.create && bulk.create.length > 0) { result.create = bulk.create }
|
|
74
|
+
if (bulk.update && bulk.update.length > 0) { result.update = bulk.update }
|
|
75
|
+
if (bulk.upsert && bulk.upsert.length > 0) { result.upsert = bulk.upsert }
|
|
76
|
+
if (bulk.delete && bulk.delete.length > 0) { result.delete = bulk.delete }
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Converts a list of Deltas for a model to a ${meta.data.types.bulkMutation} object.
|
|
83
|
+
*/
|
|
84
|
+
function deltaModelTo${meta.data.types.bulkMutation}<
|
|
85
|
+
Model extends ${dto.genericModel}<ID>,
|
|
86
|
+
ID extends ${dto.idType},
|
|
87
|
+
ModelErrors
|
|
88
|
+
>(
|
|
89
|
+
items?: ${types.delta_Model.type}<Model, ID, ModelErrors>[],
|
|
90
|
+
): ${meta.data.types.bulkMutationForModel}<Model, ID> {
|
|
91
|
+
const result: Required<${meta.data.types.bulkMutationForModel}<Model, ID>> = {
|
|
92
|
+
create: [],
|
|
93
|
+
update: [],
|
|
94
|
+
upsert: [],
|
|
95
|
+
delete: [],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const item of items ?? []) {
|
|
99
|
+
switch (item.type) {
|
|
100
|
+
case 'create':
|
|
101
|
+
result.create.push(item.createDTO)
|
|
102
|
+
break
|
|
103
|
+
case 'update':
|
|
104
|
+
result.update.push(item.updateDTO)
|
|
105
|
+
break
|
|
106
|
+
case 'delete':
|
|
107
|
+
result.delete.push(item.input.id)
|
|
108
|
+
break
|
|
109
|
+
case 'unchanged':
|
|
110
|
+
break
|
|
111
|
+
case 'errors':
|
|
112
|
+
throw new Error('Cannot convert delta to bulk mutation as it contains errors')
|
|
113
|
+
default:
|
|
114
|
+
throw new ExhaustiveSwitchCheck(item)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return _removeBlank${meta.data.types.bulkMutation}(result)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
${generateMockDataToBulkFunction({ models, meta })}
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
exports.generateImportExportConvertImportFunctions = generateImportExportConvertImportFunctions;
|
|
125
|
+
// Helpful for debugging the complex logic, so should not be removed
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
127
|
+
function logState({ iteration, state, nextStep, lastStep, }) {
|
|
128
|
+
console.log(`
|
|
129
|
+
Iteration: ${iteration++}
|
|
130
|
+
LastStep: ${lastStep}
|
|
131
|
+
Completed: ${[...state.completed.values()].map((model) => model.name).join(', ')}
|
|
132
|
+
CreatedToBeLinked: ${[...state.createdToBeLinked.values()].map((x) => x.model.name).join(', ')}
|
|
133
|
+
CanBeCreatedFullyLinked: ${[...state.canBeCreatedFullyLinked.values()].map((x) => x.model.name).join(', ')}
|
|
134
|
+
CanBeCreatedPartiallyLinked: ${[...state.canBeCreatedPartiallyLinked.values()].map((x) => x.model.name).join(', ')}
|
|
135
|
+
Blocked: ${[...state.blocked.values()].map((x) => x.model.name).join(', ')}
|
|
136
|
+
NextStep: ${nextStep}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Groups the fields of a model into fields that can be created, fields that can be created as null, and fields that block the creation of the model.
|
|
140
|
+
*/
|
|
141
|
+
function groupModelFields({ state, model }) {
|
|
142
|
+
const result = {
|
|
143
|
+
model,
|
|
144
|
+
create: new Set(),
|
|
145
|
+
nullableFieldsForLaterLinking: new Set(),
|
|
146
|
+
requiredFieldsBlockingLinking: new Set(),
|
|
147
|
+
ignoreFields: new Set(),
|
|
148
|
+
};
|
|
149
|
+
for (const field of model.fields) {
|
|
150
|
+
// We ignore createdAt and updatedAt fields as they are set automatically.
|
|
151
|
+
if (field.attributes.ignore || field.attributes.isCreatedAt || field.attributes.isUpdatedAt) {
|
|
152
|
+
result.ignoreFields.add(field);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Non-relations can always be created without extra checks.
|
|
156
|
+
if (field.kind !== 'relation') {
|
|
157
|
+
result.create.add(field);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// Related model is already created so we can create the field.
|
|
161
|
+
if (state.completed.has(field.relationToModel.name) || state.createdToBeLinked.has(field.relationToModel.name)) {
|
|
162
|
+
result.create.add(field);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// In nullable relation we can set the field to null and link it later.
|
|
166
|
+
if (!field.isRequired) {
|
|
167
|
+
result.nullableFieldsForLaterLinking.add(field);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// All other options are exhausted meaning this is a required relation and the related model hasn't been created yet.
|
|
171
|
+
// We cannot create the field yet so we add it to backlog.
|
|
172
|
+
result.requiredFieldsBlockingLinking.add(field);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Determines the state (created partially linked, or can be created fully linked) of a model based on its fields
|
|
178
|
+
*/
|
|
179
|
+
function determineModelNextStep(fieldsGrouped) {
|
|
180
|
+
// If we have blocking fields, we cannot create the model yet
|
|
181
|
+
if (fieldsGrouped.requiredFieldsBlockingLinking.size > 0) {
|
|
182
|
+
return 'blocked';
|
|
183
|
+
}
|
|
184
|
+
// If we have no blocking fields, we can create the model but need to link some fields later
|
|
185
|
+
if (fieldsGrouped.nullableFieldsForLaterLinking.size > 0) {
|
|
186
|
+
return 'canBeCreatedPartiallyLinked';
|
|
187
|
+
}
|
|
188
|
+
// If no field is blocking and all nullable fields can be linked, we can create the model and link all fields
|
|
189
|
+
return 'canBeCreatedFullyLinked';
|
|
190
|
+
}
|
|
191
|
+
function assignModelToState({ state, modelNextStep, fieldsGrouped, }) {
|
|
192
|
+
switch (modelNextStep) {
|
|
193
|
+
case 'blocked':
|
|
194
|
+
state.blocked.set(fieldsGrouped.model.name, fieldsGrouped);
|
|
195
|
+
break;
|
|
196
|
+
case 'canBeCreatedPartiallyLinked':
|
|
197
|
+
state.canBeCreatedPartiallyLinked.set(fieldsGrouped.model.name, fieldsGrouped);
|
|
198
|
+
break;
|
|
199
|
+
case 'canBeCreatedFullyLinked':
|
|
200
|
+
state.canBeCreatedFullyLinked.set(fieldsGrouped.model.name, fieldsGrouped);
|
|
201
|
+
break;
|
|
202
|
+
default:
|
|
203
|
+
throw new types_1.ExhaustiveSwitchCheck(modelNextStep);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function initializeModelsState(models) {
|
|
207
|
+
const state = {
|
|
208
|
+
completed: new Map(),
|
|
209
|
+
createdToBeLinked: new Map(),
|
|
210
|
+
canBeCreatedFullyLinked: new Map(),
|
|
211
|
+
canBeCreatedPartiallyLinked: new Map(),
|
|
212
|
+
blocked: new Map(),
|
|
213
|
+
};
|
|
214
|
+
for (const model of models) {
|
|
215
|
+
const fieldsGrouped = groupModelFields({ state, model });
|
|
216
|
+
const modelNextStep = determineModelNextStep(fieldsGrouped);
|
|
217
|
+
assignModelToState({ state, modelNextStep, fieldsGrouped });
|
|
218
|
+
}
|
|
219
|
+
return state;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Checks if any of the blocked or partially linked models can be created now.
|
|
223
|
+
*/
|
|
224
|
+
function updateModelState(oldState) {
|
|
225
|
+
const newState = copyModelState(oldState);
|
|
226
|
+
newState.blocked = new Map();
|
|
227
|
+
newState.canBeCreatedPartiallyLinked = new Map();
|
|
228
|
+
// We iterate over all blocked models and check if they can be created now
|
|
229
|
+
for (const [, fieldsGrouped] of oldState.blocked) {
|
|
230
|
+
const fieldsGroupedUpdated = groupModelFields({ state: oldState, model: fieldsGrouped.model });
|
|
231
|
+
const modelNextStep = determineModelNextStep(fieldsGroupedUpdated);
|
|
232
|
+
assignModelToState({ state: newState, modelNextStep, fieldsGrouped: fieldsGroupedUpdated });
|
|
233
|
+
}
|
|
234
|
+
// We iterate over all partially linked models and check if they can be created fully linked now
|
|
235
|
+
for (const [, fieldsGrouped] of oldState.canBeCreatedPartiallyLinked) {
|
|
236
|
+
const fieldsGroupedUpdated = groupModelFields({ state: oldState, model: fieldsGrouped.model });
|
|
237
|
+
const modelNextStep = determineModelNextStep(fieldsGroupedUpdated);
|
|
238
|
+
assignModelToState({ state: newState, modelNextStep, fieldsGrouped: fieldsGroupedUpdated });
|
|
239
|
+
}
|
|
240
|
+
return newState;
|
|
241
|
+
}
|
|
242
|
+
function determineNextStep(modelsState) {
|
|
243
|
+
// We prioritize models that can be created fully linked, so we do not need unnecessary linking steps later
|
|
244
|
+
if (modelsState.canBeCreatedFullyLinked.size > 0) {
|
|
245
|
+
return 'createFullyLinked';
|
|
246
|
+
}
|
|
247
|
+
// If no model can be created fully linked, we check if we can create models partially linked
|
|
248
|
+
if (modelsState.canBeCreatedPartiallyLinked.size > 0) {
|
|
249
|
+
return 'createPartiallyLinked';
|
|
250
|
+
}
|
|
251
|
+
// Lastly, we link all models that require linking
|
|
252
|
+
if (modelsState.createdToBeLinked.size > 0) {
|
|
253
|
+
return 'link';
|
|
254
|
+
}
|
|
255
|
+
// If we now still have blocked models, we have a circular dependency
|
|
256
|
+
if (modelsState.blocked.size > 0) {
|
|
257
|
+
throw new Error(`Cannot create the following models because they have a circular dependency: ${Array.from(modelsState.blocked.values())
|
|
258
|
+
.map((model) => model.model.name)
|
|
259
|
+
.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
// If we have no blocked models, we are done
|
|
262
|
+
return 'completed';
|
|
263
|
+
}
|
|
264
|
+
function generateBlocks({ state, nextStep, imports, iteration, schemaMeta, }) {
|
|
265
|
+
switch (nextStep) {
|
|
266
|
+
case 'createFullyLinked':
|
|
267
|
+
return generateBulkMutationBlockCreateFullyLinked({ state, imports, iteration, schemaMeta });
|
|
268
|
+
case 'createPartiallyLinked':
|
|
269
|
+
return generateBulkMutationBlockCreatePartiallyLinked({ state, imports, iteration, schemaMeta });
|
|
270
|
+
case 'link':
|
|
271
|
+
return generateBulkMutationBlockLink({ state, imports, iteration, schemaMeta });
|
|
272
|
+
default:
|
|
273
|
+
throw new types_1.ExhaustiveSwitchCheck(nextStep);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function copyModelState(state) {
|
|
277
|
+
return {
|
|
278
|
+
completed: new Map(state.completed),
|
|
279
|
+
createdToBeLinked: new Map(state.createdToBeLinked),
|
|
280
|
+
canBeCreatedFullyLinked: new Map(state.canBeCreatedFullyLinked),
|
|
281
|
+
canBeCreatedPartiallyLinked: new Map(state.canBeCreatedPartiallyLinked),
|
|
282
|
+
blocked: new Map(state.blocked),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function generateBulkMutationBlockCreateFullyLinked({ state, iteration, imports, schemaMeta, }) {
|
|
286
|
+
const newState = copyModelState(state);
|
|
287
|
+
newState.canBeCreatedFullyLinked = new Map();
|
|
288
|
+
const models = state.canBeCreatedFullyLinked;
|
|
289
|
+
const importConvertBlocks = [];
|
|
290
|
+
const deltaConvertBlocks = [];
|
|
291
|
+
const modelNames = [];
|
|
292
|
+
for (const [, fieldsGrouped] of models) {
|
|
293
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model: fieldsGrouped.model });
|
|
294
|
+
imports.addImports({
|
|
295
|
+
[schemaMeta.types.importPath]: [modelMeta.types.dto.create],
|
|
296
|
+
});
|
|
297
|
+
const { block: importConvertBlock } = generateModelCreateBlock({ fieldsGrouped, modelMeta });
|
|
298
|
+
importConvertBlocks.push(importConvertBlock);
|
|
299
|
+
deltaConvertBlocks.push(`${modelMeta.seed.constantName}: deltaModelTo${schemaMeta.data.types.bulkMutation}(input.${modelMeta.seed.constantName})`);
|
|
300
|
+
newState.completed.set(fieldsGrouped.model.name, fieldsGrouped.model);
|
|
301
|
+
modelNames.push(modelMeta.userFriendlyNamePlural);
|
|
302
|
+
}
|
|
303
|
+
const importConvertBlock = `
|
|
304
|
+
// Step ${iteration}: Create fully linked models: ${modelNames.join(', ')}
|
|
305
|
+
{
|
|
306
|
+
${importConvertBlocks.join(',\n')}
|
|
307
|
+
}`;
|
|
308
|
+
const deltaConvertBlock = `
|
|
309
|
+
// Step ${iteration}: Create/update entire entries for: ${modelNames.join(', ')}
|
|
310
|
+
{
|
|
311
|
+
${deltaConvertBlocks.join(',\n')}
|
|
312
|
+
}`;
|
|
313
|
+
return { importConvertBlock, deltaConvertBlock, state: newState };
|
|
314
|
+
}
|
|
315
|
+
function generateBulkMutationBlockCreatePartiallyLinked({ state, imports, iteration, schemaMeta, }) {
|
|
316
|
+
const newState = copyModelState(state);
|
|
317
|
+
newState.canBeCreatedPartiallyLinked = new Map();
|
|
318
|
+
const models = state.canBeCreatedPartiallyLinked;
|
|
319
|
+
const importConvertBlocks = [];
|
|
320
|
+
const deltaConvertBlocks = [];
|
|
321
|
+
const modelNames = [];
|
|
322
|
+
for (const [, fieldsGrouped] of models) {
|
|
323
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model: fieldsGrouped.model });
|
|
324
|
+
imports.addImport({
|
|
325
|
+
from: modelMeta.types.importPath,
|
|
326
|
+
items: [modelMeta.types.dto.create],
|
|
327
|
+
});
|
|
328
|
+
const { block: importConvertBlock, fieldsToBeLinkedLater } = generateModelCreateBlock({ fieldsGrouped, modelMeta });
|
|
329
|
+
importConvertBlocks.push(importConvertBlock);
|
|
330
|
+
const deltaConvertBlock = generateDeltaBlock({ fieldsGrouped, modelMeta, schemaMeta, imports });
|
|
331
|
+
deltaConvertBlocks.push(deltaConvertBlock);
|
|
332
|
+
newState.createdToBeLinked.set(fieldsGrouped.model.name, fieldsGrouped);
|
|
333
|
+
modelNames.push(`${modelMeta.userFriendlyNamePlural} (without ${fieldsToBeLinkedLater.join(', ')})`);
|
|
334
|
+
}
|
|
335
|
+
const importConvertBlock = `
|
|
336
|
+
// Step ${iteration}: Create partially linked models:
|
|
337
|
+
// * ${modelNames.join('\n // * ')}
|
|
338
|
+
{
|
|
339
|
+
${importConvertBlocks.join(',\n')}
|
|
340
|
+
}`;
|
|
341
|
+
const deltaConvertBlock = `
|
|
342
|
+
// Step ${iteration}: Create partially linked models:
|
|
343
|
+
// * ${modelNames.join('\n // * ')}
|
|
344
|
+
{
|
|
345
|
+
${deltaConvertBlocks.join(',\n')}
|
|
346
|
+
}`;
|
|
347
|
+
return { importConvertBlock, deltaConvertBlock, state: newState };
|
|
348
|
+
}
|
|
349
|
+
function generateModelCreateBlock({ fieldsGrouped, modelMeta, }) {
|
|
350
|
+
const { createFromObject: createAssignments, fieldsToBeLinkedLater } = generateFieldAssignmentBlocks({
|
|
351
|
+
fieldsGrouped,
|
|
352
|
+
modelMeta,
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
block: /* ts */ `
|
|
356
|
+
${modelMeta.seed.constantName}: {
|
|
357
|
+
create: input.${modelMeta.importExport.decoder.decodedModelArrayName}?.map(
|
|
358
|
+
(item: ${modelMeta.types.dto.create}) => ({
|
|
359
|
+
${createAssignments.join(',\n')}
|
|
360
|
+
}))
|
|
361
|
+
?? []
|
|
362
|
+
}`,
|
|
363
|
+
fieldsToBeLinkedLater,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function generateDeltaBlock({ fieldsGrouped, modelMeta, schemaMeta, imports, }) {
|
|
367
|
+
const { createFromDelta: createAssignments, updateFromDelta: updateAssignments } = generateFieldAssignmentBlocks({
|
|
368
|
+
fieldsGrouped,
|
|
369
|
+
modelMeta,
|
|
370
|
+
});
|
|
371
|
+
const genericsDefinition = `${modelMeta.types.typeName}, ${modelMeta.types.brandedIdType}`;
|
|
372
|
+
const { type, create, update } = schemaMeta.importExport.types.delta_Model;
|
|
373
|
+
imports.addImports({
|
|
374
|
+
[modelMeta.types.importPath]: [modelMeta.types.typeName, modelMeta.types.brandedIdType],
|
|
375
|
+
[schemaMeta.importExport.types.filePath]: [
|
|
376
|
+
type,
|
|
377
|
+
create.type,
|
|
378
|
+
create.typeGuard,
|
|
379
|
+
update.type,
|
|
380
|
+
update.typeGuard,
|
|
381
|
+
// modelMeta.importExport.delta_Model_Errors,
|
|
382
|
+
],
|
|
383
|
+
});
|
|
384
|
+
return /* ts */ `
|
|
385
|
+
${modelMeta.seed.constantName}: {
|
|
386
|
+
create:
|
|
387
|
+
input.${modelMeta.seed.constantName}
|
|
388
|
+
?.filter(${create.typeGuard})
|
|
389
|
+
.map((item: ${create.type}<${genericsDefinition}>) => ({
|
|
390
|
+
${createAssignments.join(',\n')}
|
|
391
|
+
})) ?? [],
|
|
392
|
+
update:
|
|
393
|
+
input.${modelMeta.seed.constantName}
|
|
394
|
+
?.filter(${update.typeGuard})
|
|
395
|
+
.map((item: ${update.type}<${genericsDefinition}>) => ({
|
|
396
|
+
id: item.input.id,
|
|
397
|
+
${updateAssignments.join(',\n')},
|
|
398
|
+
})) ?? [],
|
|
399
|
+
}`;
|
|
400
|
+
}
|
|
401
|
+
function generateBulkMutationBlockLink({ state, imports, iteration, schemaMeta, }) {
|
|
402
|
+
const newState = copyModelState(state);
|
|
403
|
+
newState.createdToBeLinked = new Map();
|
|
404
|
+
const models = state.createdToBeLinked;
|
|
405
|
+
const importConvertBlocks = [];
|
|
406
|
+
const deltaConvertBlocks = [];
|
|
407
|
+
const modelNames = [];
|
|
408
|
+
for (const [, fieldsGrouped] of models) {
|
|
409
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model: fieldsGrouped.model });
|
|
410
|
+
imports.addImport({
|
|
411
|
+
from: modelMeta.types.importPath,
|
|
412
|
+
items: [modelMeta.types.dto.update],
|
|
413
|
+
});
|
|
414
|
+
const { importConvertBlock, deltaConvertBlock } = generateModelLinkBlocks({ fieldsGrouped, modelMeta, schemaMeta });
|
|
415
|
+
importConvertBlocks.push(importConvertBlock);
|
|
416
|
+
deltaConvertBlocks.push(deltaConvertBlock);
|
|
417
|
+
newState.completed.set(fieldsGrouped.model.name, fieldsGrouped.model);
|
|
418
|
+
modelNames.push(modelMeta.userFriendlyNamePlural);
|
|
419
|
+
}
|
|
420
|
+
const importConvertBlock = `
|
|
421
|
+
// Step ${iteration}: Link models: ${modelNames.join(', ')}
|
|
422
|
+
{
|
|
423
|
+
${importConvertBlocks.join(',\n')}
|
|
424
|
+
}`;
|
|
425
|
+
const deltaConvertBlock = `
|
|
426
|
+
// Step ${iteration}: Link models: ${modelNames.join(', ')}
|
|
427
|
+
{
|
|
428
|
+
${deltaConvertBlocks.join(',\n')}
|
|
429
|
+
}`;
|
|
430
|
+
return { importConvertBlock, deltaConvertBlock, state: newState };
|
|
431
|
+
}
|
|
432
|
+
function generateModelLinkBlocks({ fieldsGrouped, modelMeta, schemaMeta, }) {
|
|
433
|
+
const { model, nullableFieldsForLaterLinking } = fieldsGrouped;
|
|
434
|
+
const genericsDefinition = `${modelMeta.types.typeName}, ${modelMeta.types.brandedIdType}`;
|
|
435
|
+
const { create, update } = schemaMeta.importExport.types.delta_Model;
|
|
436
|
+
if (nullableFieldsForLaterLinking.size === 0) {
|
|
437
|
+
throw new Error(`Cannot link fields for ${model.name} as it does not have any nullable fields`);
|
|
438
|
+
}
|
|
439
|
+
const { linkFromObject, linkFromCreateDTO, linkFromDelta } = generateFieldAssignmentBlocks({
|
|
440
|
+
fieldsGrouped,
|
|
441
|
+
modelMeta,
|
|
442
|
+
});
|
|
443
|
+
return {
|
|
444
|
+
importConvertBlock: /* ts */ `
|
|
445
|
+
${modelMeta.seed.constantName}: {
|
|
446
|
+
update: input.${modelMeta.importExport.decoder.decodedModelArrayName}?.map(
|
|
447
|
+
(item: ${modelMeta.types.dto.update}) => ({
|
|
448
|
+
id: item.id,
|
|
449
|
+
${linkFromObject.join(',\n')}
|
|
450
|
+
}))
|
|
451
|
+
?? []
|
|
452
|
+
}`,
|
|
453
|
+
deltaConvertBlock: /* ts */ `
|
|
454
|
+
${modelMeta.seed.constantName}: {
|
|
455
|
+
update: [
|
|
456
|
+
...input.${modelMeta.seed.constantName}
|
|
457
|
+
?.filter(${create.typeGuard})
|
|
458
|
+
.map((item: ${create.type}<${genericsDefinition}>) => ({
|
|
459
|
+
id: item.input.id,
|
|
460
|
+
${linkFromCreateDTO.join(',\n')}
|
|
461
|
+
})) ?? [],
|
|
462
|
+
|
|
463
|
+
...input.${modelMeta.seed.constantName}
|
|
464
|
+
?.filter(${update.typeGuard})
|
|
465
|
+
.map((item: ${update.type}<${genericsDefinition}>) => ({
|
|
466
|
+
id: item.input.id,
|
|
467
|
+
${linkFromDelta.join(',\n')},
|
|
468
|
+
})) ?? [],
|
|
469
|
+
]
|
|
470
|
+
}`,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function generateFieldAssignmentBlocks({ fieldsGrouped, }) {
|
|
474
|
+
const { model, create, nullableFieldsForLaterLinking, requiredFieldsBlockingLinking } = fieldsGrouped;
|
|
475
|
+
if (requiredFieldsBlockingLinking.size > 0) {
|
|
476
|
+
const _fields = Array.from(requiredFieldsBlockingLinking.values())
|
|
477
|
+
.map((field) => field.name)
|
|
478
|
+
.join(', ');
|
|
479
|
+
throw new Error(`Cannot create model ${model.name} because some required fields don't exist yet: ${_fields}`);
|
|
480
|
+
}
|
|
481
|
+
const result = {
|
|
482
|
+
fieldsToBeLinkedLater: [],
|
|
483
|
+
createFromObject: [],
|
|
484
|
+
createFromDelta: [],
|
|
485
|
+
updateFromDelta: [],
|
|
486
|
+
linkFromObject: [],
|
|
487
|
+
linkFromCreateDTO: [],
|
|
488
|
+
linkFromDelta: [],
|
|
489
|
+
};
|
|
490
|
+
for (const field of create) {
|
|
491
|
+
result.createFromObject.push(`${field.name}: item.${field.name}`);
|
|
492
|
+
result.createFromDelta.push(`${field.name}: item.input.${field.name}`);
|
|
493
|
+
if (field.kind !== 'id') {
|
|
494
|
+
result.updateFromDelta.push(`${field.name}: '${field.name}' in item.delta && item.delta['${field.name}'] ? item.delta['${field.name}'].new : undefined`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
for (const field of nullableFieldsForLaterLinking) {
|
|
498
|
+
result.createFromObject.push(`${field.name}: null`);
|
|
499
|
+
result.createFromDelta.push(`${field.name}: null`);
|
|
500
|
+
if (field.kind !== 'id') {
|
|
501
|
+
result.updateFromDelta.push(`${field.name}: '${field.name}' in item.delta ? null : undefined`);
|
|
502
|
+
}
|
|
503
|
+
result.fieldsToBeLinkedLater.push(field.name);
|
|
504
|
+
result.linkFromObject.push(`${field.name}: item.${field.name}`);
|
|
505
|
+
result.linkFromCreateDTO.push(`${field.name}: item.input.${field.name}`);
|
|
506
|
+
result.linkFromDelta.push(`${field.name}: '${field.name}' in item.delta && item.delta['${field.name}'] ? item.delta['${field.name}'].new : undefined`);
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
function generateMockDataToBulkFunction({ models, meta }) {
|
|
511
|
+
const mockConverters = [];
|
|
512
|
+
for (const model of models) {
|
|
513
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
514
|
+
mockConverters.push(`${modelMeta.seed.constantName}: { create: data.${modelMeta.seed.constantName} }`);
|
|
515
|
+
}
|
|
516
|
+
return /* ts */ `
|
|
517
|
+
/**
|
|
518
|
+
* Converts MockData from the DataMocker to ${meta.data.types.bulkMutation}, so it can be
|
|
519
|
+
* imported during the E2E seed process.
|
|
520
|
+
*/
|
|
521
|
+
export function ${meta.importExport.converterFunctions.mockDataToBulkMutations}(
|
|
522
|
+
data: ${meta.data.dataMockDataType}
|
|
523
|
+
): ${meta.data.types.bulkMutation} {
|
|
524
|
+
return {
|
|
525
|
+
${mockConverters.join(',\n')}
|
|
526
|
+
}
|
|
527
|
+
}`;
|
|
528
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SchemaMetaData } from '../../lib/meta';
|
|
2
2
|
import { Model } from '../../lib/schema/schema';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Generates the Exporter class for the Import-Export module
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function generateImportExportExporterClass({ models, meta }: {
|
|
7
7
|
models: Model[];
|
|
8
8
|
meta: SchemaMetaData;
|
|
9
9
|
}): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateImportExportExporterClass = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
/**
|
|
7
|
+
* Generates the Exporter class for the Import-Export module
|
|
8
|
+
*/
|
|
9
|
+
function generateImportExportExporterClass({ models, meta }) {
|
|
10
|
+
const imports = imports_1.ImportsGenerator.from(meta.importExport.exporterClass.filePath);
|
|
11
|
+
imports.addImports({
|
|
12
|
+
[meta.businessLogic.view.importPath]: [meta.businessLogic.view.serviceClassName],
|
|
13
|
+
[meta.importExport.decoder.fullDecoderFilePath]: [
|
|
14
|
+
meta.importExport.decoder.fullEncoderFunctionName,
|
|
15
|
+
meta.importExport.decoder.encodedExcelDataTypeName,
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
const typeExports = [];
|
|
19
|
+
const typeExportsDataEntries = [];
|
|
20
|
+
const privateMaps = [];
|
|
21
|
+
const exportDataProperties = [];
|
|
22
|
+
const addFunctions = [];
|
|
23
|
+
const addAllCalls = [];
|
|
24
|
+
for (const model of models) {
|
|
25
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
26
|
+
imports.addImport({
|
|
27
|
+
from: modelMeta.types.importPath,
|
|
28
|
+
items: [modelMeta.types.brandedIdType, modelMeta.types.typeName],
|
|
29
|
+
});
|
|
30
|
+
typeExports.push(`export type ${modelMeta.importExport.exportDataTypeName} = CapitalizedKeys<${model.name}>`);
|
|
31
|
+
typeExportsDataEntries.push(`${modelMeta.importExport.exportDataPropertyName}?: ${modelMeta.importExport.exportDataTypeName}[]`);
|
|
32
|
+
privateMaps.push(`private ${modelMeta.internalPluralName} = new Map<${modelMeta.types.brandedIdType}, ${modelMeta.types.typeName}>()`);
|
|
33
|
+
exportDataProperties.push(`${modelMeta.importExport.exportDataFullPropertyName}: mapValues(this.${modelMeta.internalPluralName})`);
|
|
34
|
+
const linkedItems = [];
|
|
35
|
+
for (const field of model.fields) {
|
|
36
|
+
if (field.kind !== 'relation') {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const linkedModelMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
|
|
40
|
+
if (field.isRequired) {
|
|
41
|
+
linkedItems.push(`await this.${linkedModelMeta.importExport.exportAddFunctionName}(item.${field.name})`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
linkedItems.push(`
|
|
45
|
+
if (item.${field.name}) {
|
|
46
|
+
await this.${linkedModelMeta.importExport.exportAddFunctionName}(item.${field.name})
|
|
47
|
+
}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
addFunctions.push(`
|
|
51
|
+
/**
|
|
52
|
+
* Adds a ${modelMeta.userFriendlyName} and all related (and nested) dependencies to the export.
|
|
53
|
+
*/
|
|
54
|
+
public async ${modelMeta.importExport.exportAddFunctionName}(id: ${model.brandedIdType}) {
|
|
55
|
+
if (this.${modelMeta.internalPluralName}.has(id)) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const item = await this.viewService.${modelMeta.businessLogic.view.serviceVariableName}.get(id)
|
|
59
|
+
if (!item) {
|
|
60
|
+
this.logger.error(\`Cannot find ${modelMeta.userFriendlyName} \${id}\`)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
this.${modelMeta.internalPluralName}.set(id, item)
|
|
64
|
+
|
|
65
|
+
${linkedItems.join('\n')}
|
|
66
|
+
}
|
|
67
|
+
`);
|
|
68
|
+
addAllCalls.push(`this.${modelMeta.internalPluralName} = await this.viewService.${modelMeta.businessLogic.view.serviceVariableName}.getAll()`);
|
|
69
|
+
}
|
|
70
|
+
return /* ts */ `
|
|
71
|
+
import { Logger } from '@nestjs/common'
|
|
72
|
+
|
|
73
|
+
import { mapValues } from '@pxl/common'
|
|
74
|
+
${imports.generate()}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Exporter helps to export data items and all their (nested) dependencies to Excel format.
|
|
78
|
+
*
|
|
79
|
+
* Usage:
|
|
80
|
+
* - Create an instance of the Exporter
|
|
81
|
+
* - Call the \`add{ModelName}(itemId)\` methods to add the items you want to export.
|
|
82
|
+
* This will automatically add all dependencies of the item.
|
|
83
|
+
* - Call the \`exportData()\` method to get the encoded Excel data.
|
|
84
|
+
* - Alternatively, you can call 'exportAll()' to dump all data.
|
|
85
|
+
*/
|
|
86
|
+
export class ${meta.importExport.exporterClass.name} {
|
|
87
|
+
private logger = new Logger(Exporter.name)
|
|
88
|
+
|
|
89
|
+
${privateMaps.join('\n ')}
|
|
90
|
+
|
|
91
|
+
constructor(private readonly viewService: ViewService) {}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Exports all data that was added by prior calls to \`.add{Model}\` functions.
|
|
95
|
+
*/
|
|
96
|
+
public exportData(): ${meta.importExport.decoder.encodedExcelDataTypeName} {
|
|
97
|
+
return ${meta.importExport.decoder.fullEncoderFunctionName}({
|
|
98
|
+
${exportDataProperties.join(',\n ')}
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Exports all data.
|
|
104
|
+
*/
|
|
105
|
+
public async exportAll(): Promise<${meta.importExport.decoder.encodedExcelDataTypeName}> {
|
|
106
|
+
${addAllCalls.join('\n')}
|
|
107
|
+
|
|
108
|
+
return this.exportData()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
${addFunctions.join('\n\n')}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
exports.generateImportExportExporterClass = generateImportExportExporterClass;
|
|
@@ -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 generateImportExportImportService({ models, meta }: {
|
|
7
|
+
models: Model[];
|
|
8
|
+
meta: SchemaMetaData;
|
|
9
|
+
}): string;
|