@postxl/generator 0.38.2 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/generator.js +43 -21
  2. package/dist/generators/enums/types.generator.js +1 -1
  3. package/dist/generators/indices/businesslogic-actiontypes.generator.js +1 -1
  4. package/dist/generators/indices/businesslogic-update-module.generator.js +2 -2
  5. package/dist/generators/indices/businesslogic-update-service.generator.js +1 -1
  6. package/dist/generators/indices/businesslogic-view-module.generator.js +2 -2
  7. package/dist/generators/indices/businesslogic-view-service.generator.js +1 -1
  8. package/dist/generators/indices/{seed-service.generator.d.ts → data-types.generator.d.ts} +2 -2
  9. package/dist/generators/indices/data-types.generator.js +48 -0
  10. package/dist/generators/indices/datamock-module.generator.js +11 -11
  11. package/dist/generators/indices/datamocker.generator.js +1 -1
  12. package/dist/generators/indices/datamodule.generator.js +34 -52
  13. package/dist/generators/indices/dataservice.generator.js +202 -9
  14. package/dist/generators/indices/dispatcher-service.generator.js +20 -10
  15. package/dist/generators/indices/emptydatabasemigration.generator.d.ts +2 -0
  16. package/dist/generators/indices/emptydatabasemigration.generator.js +14 -7
  17. package/dist/generators/indices/importexport-convert-import-functions.generator.d.ts +9 -0
  18. package/dist/generators/indices/importexport-convert-import-functions.generator.js +528 -0
  19. package/dist/generators/indices/importexport-exporter-class.generator.d.ts +9 -0
  20. package/dist/generators/indices/importexport-exporter-class.generator.js +116 -0
  21. package/dist/generators/indices/importexport-import-service.generator.d.ts +9 -0
  22. package/dist/generators/indices/importexport-import-service.generator.js +563 -0
  23. package/dist/generators/indices/{seeddata-type.generator.d.ts → importexport-types.generator.d.ts} +2 -2
  24. package/dist/generators/indices/importexport-types.generator.js +234 -0
  25. package/dist/generators/indices/repositories.generator.js +8 -8
  26. package/dist/generators/indices/seed-migration.generator.js +1 -1
  27. package/dist/generators/indices/seed-template.generator.js +1 -1
  28. package/dist/generators/indices/selectors.generator.d.ts +7 -0
  29. package/dist/generators/indices/selectors.generator.js +82 -0
  30. package/dist/generators/indices/{seed-template-decoder.generator.d.ts → testdata-service.generator.d.ts} +2 -2
  31. package/dist/generators/indices/testdata-service.generator.js +71 -0
  32. package/dist/generators/models/businesslogic-update.generator.js +6 -6
  33. package/dist/generators/models/businesslogic-view.generator.js +4 -4
  34. package/dist/generators/models/importexport-decoder.generator.d.ts +23 -0
  35. package/dist/generators/models/importexport-decoder.generator.js +234 -0
  36. package/dist/generators/models/react.generator/library.generator.js +4 -0
  37. package/dist/generators/models/react.generator/modals.generator.js +35 -8
  38. package/dist/generators/models/repository.generator.js +156 -18
  39. package/dist/generators/models/route.generator.js +2 -2
  40. package/dist/generators/models/stub.generator.js +1 -1
  41. package/dist/generators/models/types.generator.js +1 -1
  42. package/dist/lib/id-collector.d.ts +43 -0
  43. package/dist/lib/id-collector.js +53 -0
  44. package/dist/lib/imports.d.ts +1 -1
  45. package/dist/lib/meta.d.ts +480 -122
  46. package/dist/lib/meta.js +187 -74
  47. package/dist/lib/schema/schema.d.ts +58 -43
  48. package/dist/lib/schema/types.d.ts +63 -12
  49. package/dist/lib/schema/types.js +27 -7
  50. package/dist/lib/utils/string.d.ts +1 -0
  51. package/dist/lib/utils/string.js +1 -0
  52. package/dist/prisma/parse.js +4 -4
  53. package/package.json +2 -2
  54. package/dist/generators/indices/seed-service.generator.js +0 -356
  55. package/dist/generators/indices/seed-template-decoder.generator.js +0 -151
  56. package/dist/generators/indices/seeddata-type.generator.js +0 -42
@@ -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
+ }
@@ -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 generateImportExportExporterClass({ models, meta }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
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;