@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,234 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateImportExportDecoderIndex = exports.generateImportExportDecoder = exports.generateModelImportExportDecoder = void 0;
4
+ const exports_1 = require("../../lib/exports");
5
+ const imports_1 = require("../../lib/imports");
6
+ const meta_1 = require("../../lib/meta");
7
+ const types_1 = require("../../lib/schema/types");
8
+ const types_2 = require("../../lib/types");
9
+ // TODO: Remove hardcoded path that seems to be reused in multiple places!
10
+ const PXL_COMMON = (0, types_1.toPath)('@pxl/common');
11
+ /**
12
+ * Creates a decoder for the Seed Excel template.
13
+ */
14
+ function generateModelImportExportDecoder({ model, meta }) {
15
+ const { filePath: decoderFilePath, itemEncoderFunctionName: rowExportFunctionName, encodedExcelType: excelType, arrayEncoderFunctionName: tableExportFunctionName, tableDecoder: tableImportDecoder, } = meta.importExport.decoder;
16
+ const imports = imports_1.ImportsGenerator.from(decoderFilePath);
17
+ imports.addImports({
18
+ [PXL_COMMON]: [
19
+ (0, types_1.toFunctionName)('uncapitalizeKeys'),
20
+ (0, types_1.toFunctionName)('capitalizeKeys'),
21
+ (0, types_1.toFunctionName)('nullOrBlankDecoder'),
22
+ ],
23
+ [meta.types.importPath]: [meta.types.typeName],
24
+ });
25
+ const { userFriendlyName: userFriendly, userFriendlyNamePlural: userFriendlyPlural, internalSingularName: singular, internalSingularNameCapitalized: singularCapitalized, } = meta;
26
+ const { fieldDecoders, blankFieldDecoders } = generateFieldDecoders({ model, meta, imports });
27
+ return `
28
+ import * as z from 'zod'
29
+ ${imports.generate()}
30
+
31
+ /**
32
+ * The decoder for an Excel row containing a ${userFriendly} entry
33
+ */
34
+ const ${singular}ExcelRowImportDecoderInput = z.object({
35
+ ${fieldDecoders.join(',\n')}
36
+ })
37
+
38
+
39
+ /**
40
+ * The type for rows in the ${userFriendly} table
41
+ */
42
+ export type ${excelType} = z.infer<typeof ${singular}ExcelRowImportDecoderInput>
43
+
44
+ const ${singular}ExcelRowImportDecoder = ${singular}ExcelRowImportDecoderInput.transform(uncapitalizeKeys)
45
+
46
+ /**
47
+ * The decoder to identify blank rows in the ${userFriendly} table
48
+ */
49
+ const blank${singularCapitalized}ExcelRowImportDecoderInput = z
50
+ .object({
51
+ ${blankFieldDecoders.join(',\n')}
52
+ })
53
+ .transform(() => undefined)
54
+
55
+ /**
56
+ * The decoder for an Excel table that contains ${userFriendly} entries
57
+ */
58
+ export const ${tableImportDecoder} = z
59
+ .array(${singular}ExcelRowImportDecoder.or(blank${singularCapitalized}ExcelRowImportDecoderInput))
60
+ .transform((items) => items.filter(Boolean) as ${model.typeName}[])
61
+
62
+ /**
63
+ * Converts a ${userFriendly} to an Excel row
64
+ */
65
+ export const ${rowExportFunctionName} = (item: ${model.typeName}): ${excelType} => capitalizeKeys(item)
66
+
67
+ /**
68
+ * Converts a list of ${userFriendlyPlural} to an Excel table
69
+ */
70
+ export const ${tableExportFunctionName} = (items: ${model.typeName}[]): ${excelType}[] => items.map(${rowExportFunctionName})
71
+ `;
72
+ }
73
+ exports.generateModelImportExportDecoder = generateModelImportExportDecoder;
74
+ function generateFieldDecoders({ model, meta, imports, }) {
75
+ const fieldDecoders = [];
76
+ const blankFieldDecoders = [];
77
+ for (const field of model.fields) {
78
+ const fieldMeta = (0, meta_1.getFieldMetadata)({ field });
79
+ blankFieldDecoders.push(`${fieldMeta.excelColumnName}: nullOrBlankDecoder`);
80
+ switch (field.kind) {
81
+ case 'id': {
82
+ imports.addImport({ items: [meta.types.toBrandedIdTypeFnName], from: meta.types.importPath });
83
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
84
+ tsTypeName: field.unbrandedTypeName,
85
+ dbTypeName: field.schemaType,
86
+ nullable: false,
87
+ imports,
88
+ })}.transform((id: ${field.unbrandedTypeName}) => ${meta.types.toBrandedIdTypeFnName}(id))`);
89
+ break;
90
+ }
91
+ case 'scalar': {
92
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
93
+ tsTypeName: field.tsTypeName,
94
+ dbTypeName: field.schemaType,
95
+ nullable: !field.isRequired,
96
+ imports,
97
+ })}`);
98
+ break;
99
+ }
100
+ case 'relation': {
101
+ const refModel = field.relationToModel;
102
+ const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
103
+ imports.addImport({ items: [refMeta.types.toBrandedIdTypeFnName], from: refMeta.types.importPath });
104
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
105
+ tsTypeName: field.unbrandedTypeName,
106
+ dbTypeName: field.schemaType,
107
+ nullable: !field.isRequired,
108
+ imports,
109
+ })}
110
+ .transform(
111
+ (id: ${field.unbrandedTypeName}${field.isRequired ? '' : '| null'}) =>
112
+ ${field.isRequired ? '' : ' id === null ? null : '}${refMeta.types.toBrandedIdTypeFnName}(id)
113
+ )`);
114
+ break;
115
+ }
116
+ case 'enum': {
117
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: z.enum([
118
+ ${field.enumerator.values.map((v) => `'${v}'`).join(', ')}
119
+ ])${field.isRequired ? '' : '.nullable()'}`);
120
+ break;
121
+ }
122
+ default: {
123
+ throw new types_2.ExhaustiveSwitchCheck(field);
124
+ }
125
+ }
126
+ }
127
+ return { fieldDecoders, blankFieldDecoders };
128
+ }
129
+ function toExcelDecoder({ tsTypeName, dbTypeName: typeName, nullable, imports, }) {
130
+ switch (tsTypeName) {
131
+ case 'string': {
132
+ const decoder = (0, types_1.toFunctionName)(nullable ? 'excelStringNullableDecoder' : 'excelStringDecoder');
133
+ imports.addImport({
134
+ items: [decoder],
135
+ from: PXL_COMMON,
136
+ });
137
+ return decoder;
138
+ }
139
+ case 'boolean':
140
+ imports.addImport({
141
+ items: [(0, types_1.toFunctionName)('excelBooleanDecoder')],
142
+ from: PXL_COMMON,
143
+ });
144
+ return 'excelBooleanDecoder';
145
+ case 'number':
146
+ switch (typeName) {
147
+ case 'Int':
148
+ case 'BigInt':
149
+ return `z.number().int()${nullable ? '.nullable()' : ''}`;
150
+ default:
151
+ return `z.number()${nullable ? '.nullable()' : ''}`;
152
+ }
153
+ case 'Date': {
154
+ const decoder = (0, types_1.toFunctionName)(nullable ? 'excelDateNullableDecoder' : 'excelDateDecoder');
155
+ imports.addImport({
156
+ items: [decoder],
157
+ from: PXL_COMMON,
158
+ });
159
+ return decoder;
160
+ }
161
+ default:
162
+ throw new Error('Unknown type');
163
+ }
164
+ }
165
+ /**
166
+ * Generates the data decoder, aggregating all the models.
167
+ */
168
+ function generateImportExportDecoder({ models, meta }) {
169
+ const { decodedPXLModelDataTypeName, encodedExcelDataTypeName, fullDecoderName, fullEncoderFunctionName } = meta.importExport.decoder;
170
+ const imports = imports_1.ImportsGenerator.from(meta.importExport.decoder.fullDecoderFilePath);
171
+ imports.addImports({
172
+ [PXL_COMMON]: (0, types_1.toFunctionName)('uncapitalizeKeys'),
173
+ });
174
+ const importTypes = [];
175
+ const decoderEntries = [];
176
+ const exportFields = [];
177
+ for (const model of models) {
178
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
179
+ imports.addImport({
180
+ from: modelMeta.importExport.decoder.filePath,
181
+ items: [
182
+ modelMeta.importExport.decoder.tableDecoder,
183
+ modelMeta.importExport.decoder.arrayEncoderFunctionName,
184
+ modelMeta.importExport.decoder.encodedExcelType,
185
+ ],
186
+ });
187
+ importTypes.push(`${modelMeta.importExport.tableName}?: ${modelMeta.importExport.decoder.encodedExcelType}[]`);
188
+ decoderEntries.push(`${modelMeta.importExport.tableName}: ${modelMeta.importExport.decoder.tableDecoder}.optional()`);
189
+ exportFields.push(`${modelMeta.importExport.tableName}:
190
+ data.${modelMeta.importExport.exportDataFullPropertyName} !== undefined ?
191
+ ${modelMeta.importExport.decoder.arrayEncoderFunctionName}(data.${modelMeta.importExport.exportDataFullPropertyName})
192
+ : undefined`);
193
+ }
194
+ return `
195
+ import * as z from 'zod'
196
+ ${imports.generate()}
197
+
198
+ export type ${encodedExcelDataTypeName} = {
199
+ ${importTypes.join(',\n')}
200
+ }
201
+
202
+ /**
203
+ * Decoder that converts Excel data to model data
204
+ */
205
+ export const ${fullDecoderName} = z
206
+ .object({
207
+ ${decoderEntries.join(',\n')}
208
+ })
209
+ .transform(uncapitalizeKeys)
210
+
211
+ export type ${decodedPXLModelDataTypeName} = z.infer<typeof excelDataDecoder>
212
+
213
+ /**
214
+ * Converts the model data to Excel format
215
+ */
216
+ export const ${fullEncoderFunctionName} = (data: ${decodedPXLModelDataTypeName}): ${encodedExcelDataTypeName} => ({
217
+ ${exportFields.join(',\n')}
218
+ })
219
+ `;
220
+ }
221
+ exports.generateImportExportDecoder = generateImportExportDecoder;
222
+ /**
223
+ * Generates the index file for all the routes.
224
+ */
225
+ function generateImportExportDecoderIndex({ models, meta }) {
226
+ const exports = exports_1.ExportsGenerator.from(meta.importExport.decoder.indexFilePath);
227
+ exports.exportEverythingFromPath(meta.importExport.decoder.fullDecoderFilePath);
228
+ for (const model of models) {
229
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
230
+ exports.exportEverythingFromPath(modelMeta.importExport.decoder.filePath);
231
+ }
232
+ return exports.generate();
233
+ }
234
+ exports.generateImportExportDecoderIndex = generateImportExportDecoderIndex;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateModelLibraryComponents = void 0;
4
+ const id_collector_1 = require("../../../lib/id-collector");
4
5
  const imports_1 = require("../../../lib/imports");
5
6
  /**
6
7
  * Generates components that may be used to list all entries of a given data type.
@@ -10,6 +11,7 @@ const imports_1 = require("../../../lib/imports");
10
11
  */
11
12
  function generateModelLibraryComponents({ model, meta }) {
12
13
  const { react: { context, components }, } = meta;
14
+ const selectorCollector = id_collector_1.SelectorCollector.from(meta.seed.constantName + '-card');
13
15
  const imports = imports_1.ImportsGenerator.from(meta.react.folderPath)
14
16
  .addImport({
15
17
  items: [model.typeName],
@@ -58,11 +60,13 @@ function generateModelLibraryComponents({ model, meta }) {
58
60
  label: 'Edit',
59
61
  icon: 'pencil-on-paper',
60
62
  onClick: () => setIsEditModalOpen(true),
63
+ __cypress_action_selector__: "${selectorCollector.idFor('edit', { typePrefix: 'actions' })}",
61
64
  },
62
65
  {
63
66
  label: 'Delete',
64
67
  icon: 'trash',
65
68
  onClick: () => setIsDeleteModalOpen(true),
69
+ __cypress_action_selector__: "${selectorCollector.idFor('delete', { typePrefix: 'actions' })}",
66
70
  },
67
71
  ]}
68
72
  />
@@ -24,6 +24,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.generateDeleteModalModelComponent = exports.generateEditModalModelComponent = exports.generateModelCreateModalComponent = void 0;
27
+ const id_collector_1 = require("../../../lib/id-collector");
27
28
  const imports_1 = require("../../../lib/imports");
28
29
  const meta_1 = require("../../../lib/meta");
29
30
  const fields_1 = require("../../../lib/schema/fields");
@@ -36,6 +37,7 @@ const StringUtils = __importStar(require("../../../lib/utils/string"));
36
37
  function generateModelCreateModalComponent({ model, meta }) {
37
38
  const { fields } = model;
38
39
  const { react: { components: { modals }, }, trpc, } = meta;
40
+ const selectorCollector = id_collector_1.SelectorCollector.from(meta.seed.constantName + '-createModal');
39
41
  return `
40
42
  /* eslint-disable @typescript-eslint/no-unused-vars */
41
43
  ${getFormImports({ model, meta })}
@@ -130,9 +132,10 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
130
132
  fill="fill"
131
133
  onClick={submitForm}
132
134
  loading={isSubmitting}
135
+ __cypress_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
133
136
  />
134
137
  }>
135
- ${getFormFieldComponents({ model })}
138
+ ${getFormFieldComponents({ model, selectorCollector })}
136
139
  </ModalWithActions>
137
140
  )}
138
141
  </Typed.Formik>
@@ -149,6 +152,7 @@ exports.generateModelCreateModalComponent = generateModelCreateModalComponent;
149
152
  function generateEditModalModelComponent({ model, meta }) {
150
153
  const { fields } = model;
151
154
  const { react: { components }, trpc, } = meta;
155
+ const selectorCollector = id_collector_1.SelectorCollector.from(meta.seed.constantName + '-editModal');
152
156
  return `
153
157
  /* eslint-disable @typescript-eslint/no-unused-vars */
154
158
  ${getFormImports({ model, meta })}
@@ -257,10 +261,11 @@ export const ${components.modals.editComponentName} = ({
257
261
  color="primary"
258
262
  onClick={submitForm}
259
263
  loading={isSubmitting}
264
+ __cypress_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
260
265
  />
261
266
  }
262
267
  >
263
- ${getFormFieldComponents({ model })}
268
+ ${getFormFieldComponents({ model, selectorCollector })}
264
269
  </ModalWithActions>
265
270
  )}
266
271
  </Typed.Formik>
@@ -528,7 +533,7 @@ function getEditFormikMutationData({ model: { fields } }) {
528
533
  /**
529
534
  * Returns a string containing all the components that should appear in the Formik form for this model.
530
535
  */
531
- function getFormFieldComponents({ model }) {
536
+ function getFormFieldComponents({ model, selectorCollector, }) {
532
537
  var _a;
533
538
  const form = new serializer_1.Serializer();
534
539
  for (const field of model.fields.values()) {
@@ -550,7 +555,11 @@ function getFormFieldComponents({ model }) {
550
555
  form.append(`
551
556
  <div>
552
557
  <Label>${label}</Label>
553
- <Typed.TextField placeholder="Type..." name="${formikFieldName}" />
558
+ <Typed.TextField
559
+ name="${formikFieldName}"
560
+ placeholder="Type..."
561
+ __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
562
+ />
554
563
  </div>
555
564
  `);
556
565
  break scalar;
@@ -563,7 +572,12 @@ function getFormFieldComponents({ model }) {
563
572
  form.append(`
564
573
  <div>
565
574
  <Label>${label}</Label>
566
- <Typed.NumberField placeholder="2511" name="${formikFieldName}" decimals={${decimals}}/>
575
+ <Typed.NumberField
576
+ name="${formikFieldName}"
577
+ placeholder="2511"
578
+ decimals={${decimals}}
579
+ __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
580
+ />
567
581
  </div>
568
582
  `);
569
583
  break scalar;
@@ -572,7 +586,11 @@ function getFormFieldComponents({ model }) {
572
586
  form.append(`
573
587
  <div>
574
588
  <Label>Is ${label}</Label>
575
- <Typed.CheckBoxField label="${label}" name="${formikFieldName}" />
589
+ <Typed.CheckBoxField
590
+ name="${formikFieldName}"
591
+ label="${label}"
592
+ __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
593
+ />
576
594
  </div>
577
595
  `);
578
596
  break scalar;
@@ -593,7 +611,12 @@ function getFormFieldComponents({ model }) {
593
611
  form.append(`
594
612
  <div>
595
613
  <Label>${refMeta.userFriendlyName}</Label>
596
- <Typed.${refMeta.react.components.forms.searchFieldName} name="${formikFieldName}" placeholder="Search..." />
614
+ <Typed.${refMeta.react.components.forms.searchFieldName}
615
+ name="${formikFieldName}"
616
+ placeholder="Search..."
617
+ __cypress_options_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'options' })}"
618
+ __cypress_combobox_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
619
+ />
597
620
  </div>
598
621
  `);
599
622
  break;
@@ -603,7 +626,11 @@ function getFormFieldComponents({ model }) {
603
626
  form.append(`
604
627
  <div>
605
628
  <Label>${label}</Label>
606
- <Typed.${enumMeta.react.selectFieldName} name="${formikFieldName}" placeholder="Search..." />
629
+ <Typed.${enumMeta.react.selectFieldName}
630
+ name="${formikFieldName}"
631
+ placeholder="Search..."
632
+ __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
633
+ />
607
634
  </div>
608
635
  `);
609
636
  break;