@postxl/generator 0.33.4 → 0.35.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 (63) hide show
  1. package/dist/generator.js +40 -22
  2. package/dist/generators/indices/businesslogic-actiontypes.generator.d.ts +9 -0
  3. package/dist/generators/indices/businesslogic-actiontypes.generator.js +39 -0
  4. package/dist/generators/indices/businesslogic-update-index.generator.d.ts +9 -0
  5. package/dist/generators/indices/businesslogic-update-index.generator.js +20 -0
  6. package/dist/generators/indices/businesslogic-update-module.generator.d.ts +9 -0
  7. package/dist/generators/indices/businesslogic-update-module.generator.js +69 -0
  8. package/dist/generators/indices/businesslogic-update-service.generator.d.ts +9 -0
  9. package/dist/generators/indices/businesslogic-update-service.generator.js +34 -0
  10. package/dist/generators/indices/businesslogic-view-index.generator.d.ts +9 -0
  11. package/dist/generators/indices/businesslogic-view-index.generator.js +19 -0
  12. package/dist/generators/indices/{businesslogicindex.generator.d.ts → businesslogic-view-module.generator.d.ts} +2 -2
  13. package/dist/generators/indices/{businesslogicmodule.generator.js → businesslogic-view-module.generator.js} +22 -26
  14. package/dist/generators/indices/{businesslogicservice.generator.d.ts → businesslogic-view-service.generator.d.ts} +1 -1
  15. package/dist/generators/indices/{businesslogicservice.generator.js → businesslogic-view-service.generator.js} +9 -10
  16. package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
  17. package/dist/generators/indices/datamocker.generator.js +3 -7
  18. package/dist/generators/indices/datamodule.generator.js +7 -13
  19. package/dist/generators/indices/{businesslogicmodule.generator.d.ts → dispatcher-service.generator.d.ts} +2 -2
  20. package/dist/generators/indices/dispatcher-service.generator.js +81 -0
  21. package/dist/generators/indices/seed-migration.generator.d.ts +9 -0
  22. package/dist/generators/indices/seed-migration.generator.js +35 -0
  23. package/dist/generators/indices/seed-service.generator.d.ts +1 -1
  24. package/dist/generators/indices/seed-service.generator.js +327 -123
  25. package/dist/generators/indices/seed-template-decoder.generator.js +22 -6
  26. package/dist/generators/indices/{seed.generator.d.ts → seeddata-type.generator.d.ts} +2 -2
  27. package/dist/generators/indices/seeddata-type.generator.js +42 -0
  28. package/dist/generators/indices/types.generator.d.ts +1 -1
  29. package/dist/generators/indices/types.generator.js +8 -6
  30. package/dist/generators/models/businesslogic-update.generator.d.ts +10 -0
  31. package/dist/generators/models/businesslogic-update.generator.js +243 -0
  32. package/dist/generators/models/businesslogic-view.generator.d.ts +10 -0
  33. package/dist/generators/models/businesslogic-view.generator.js +253 -0
  34. package/dist/generators/models/react.generator/modals.generator.js +20 -4
  35. package/dist/generators/models/repository.generator.d.ts +9 -0
  36. package/dist/generators/models/repository.generator.js +496 -148
  37. package/dist/generators/models/route.generator.js +45 -54
  38. package/dist/generators/models/seed.generator.js +6 -2
  39. package/dist/generators/models/types.generator.js +60 -13
  40. package/dist/lib/attributes.d.ts +32 -2
  41. package/dist/lib/imports.d.ts +23 -2
  42. package/dist/lib/imports.js +19 -1
  43. package/dist/lib/meta.d.ts +287 -34
  44. package/dist/lib/meta.js +87 -16
  45. package/dist/lib/schema/fields.d.ts +7 -4
  46. package/dist/lib/schema/fields.js +11 -4
  47. package/dist/lib/schema/schema.d.ts +32 -6
  48. package/dist/lib/schema/types.d.ts +4 -0
  49. package/dist/lib/utils/ast.d.ts +29 -0
  50. package/dist/lib/utils/ast.js +23 -0
  51. package/dist/lib/utils/jsdoc.d.ts +1 -1
  52. package/dist/lib/utils/jsdoc.js +8 -5
  53. package/dist/lib/utils/string.js +2 -1
  54. package/dist/prisma/attributes.js +45 -26
  55. package/dist/prisma/parse.js +44 -11
  56. package/package.json +1 -1
  57. package/dist/generators/indices/businesslogicindex.generator.js +0 -19
  58. package/dist/generators/indices/seed.generator.js +0 -17
  59. package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
  60. package/dist/generators/indices/testdataservice.generator.js +0 -78
  61. package/dist/generators/models/businesslogic.generator.d.ts +0 -9
  62. package/dist/generators/models/businesslogic.generator.js +0 -259
  63. /package/dist/generators/indices/{datamockmodule.generator.d.ts → datamock-module.generator.d.ts} +0 -0
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateModelBusinessLogicView = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ const fields_1 = require("../../lib/schema/fields");
7
+ const ast_1 = require("../../lib/utils/ast");
8
+ /**
9
+ * Generates view business logic for a given model.
10
+ * The view logic exposes all information and links of a model. See template's readme for more info.
11
+ */
12
+ function generateModelBusinessLogicView({ model, meta }) {
13
+ const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
14
+ const imports = imports_1.ImportsGenerator.from(meta.businessLogic.view.serviceFilePath);
15
+ imports.addImports({
16
+ [meta.data.importPath]: meta.data.repositoryClassName,
17
+ [meta.types.importPath]: [model.brandedIdType, meta.types.typeName],
18
+ [schemaMeta.businessLogic.view.serviceFilePath]: schemaMeta.businessLogic.view.serviceClassName,
19
+ [meta.data.importPath]: [meta.data.repositoryClassName],
20
+ });
21
+ /**
22
+ * The name of the variable that holds the repository instance for the current model
23
+ * (e.g. when we generate business logic service for Aggregation, the AggregationRepository
24
+ * would be referenced using `this.data` variable).
25
+ */
26
+ const modelRepositoryVariableName = meta.businessLogic.dataRepositoryVariableName;
27
+ /**
28
+ * The name of the variable that holds the central business logic service instance.
29
+ * Instead of injecting a repository instance for each model, we inject this single instance
30
+ * which then provides access to all models' business logic.
31
+ */
32
+ const viewServiceClassName = 'viewService';
33
+ const constructorParameters = [
34
+ `public readonly ${modelRepositoryVariableName}: ${meta.data.repositoryClassName}`,
35
+ `@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly ${viewServiceClassName}: ${schemaMeta.businessLogic.view.serviceClassName}`,
36
+ ];
37
+ /**
38
+ * Variable names and their definitions indexed by the name of the relation they represent.
39
+ */
40
+ const variables = new Map();
41
+ for (const relation of (0, fields_1.getRelationFields)(model)) {
42
+ const refModel = relation.relationToModel;
43
+ const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
44
+ const variableGetter = `this.${viewServiceClassName}.${refMeta.businessLogic.view.serviceVariableName}.get(itemRaw.${relation.name})`;
45
+ const variablePresenceCheck = `
46
+ if (!${relation.relatedModelBacklinkFieldName}) {
47
+ throw new Error(\`Could not find ${refMeta.types.typeName} with id \${itemRaw.${relation.name}} for ${model.typeName}.${relation.name}!\`)
48
+ }
49
+ `;
50
+ const relationVariableName = relation.relatedModelBacklinkFieldName;
51
+ variables.set(relation.name, {
52
+ variableName: relationVariableName,
53
+ variableDefinition: `
54
+ const ${relationVariableName} = ${relation.isRequired
55
+ ? `${variableGetter};${variablePresenceCheck}`
56
+ : `itemRaw.${relation.name} !== null ? ${variableGetter} : null`}
57
+ `,
58
+ });
59
+ }
60
+ const hasLinkedItems = variables.size > 0;
61
+ if (hasLinkedItems) {
62
+ imports.addImport({ from: meta.types.importPath, items: [meta.types.linkedTypeName] });
63
+ }
64
+ const linkedItemsGetterFn = `
65
+ /**
66
+ * Returns the linked ${meta.userFriendlyName} with the given id or null if it does not exist.
67
+ * Linked: The ${meta.userFriendlyName} contains the linked (raw) items themselves, not only the ids.
68
+ */
69
+ public getLinkedItem(id: ${model.brandedIdType}): ${meta.types.linkedTypeName} | null {
70
+ const itemRaw = this.${modelRepositoryVariableName}.get(id)
71
+ if (!itemRaw) {
72
+ return null
73
+ }
74
+
75
+ ${[...variables.values()].map((r) => r.variableDefinition).join('\n')}
76
+
77
+ const item: ${meta.types.linkedTypeName} = {
78
+ ${model.fields
79
+ .map((f) => {
80
+ if (f.kind !== 'relation') {
81
+ return `${f.name}: itemRaw.${f.name}`;
82
+ }
83
+ const linkedRel = variables.get(f.name);
84
+ if (!linkedRel) {
85
+ throw new Error(`Could not find linked item for ${model.typeName}.${f.name}`);
86
+ }
87
+ return `${linkedRel.variableName}`;
88
+ })
89
+ .join(',\n')}
90
+ }
91
+
92
+ return item
93
+ }
94
+ `;
95
+ return `
96
+ import { Inject, Injectable, forwardRef } from '@nestjs/common'
97
+ import { FilterOperator } from '@pxl/common'
98
+
99
+ ${imports.generate()}
100
+
101
+ @Injectable()
102
+ export class ${meta.businessLogic.view.serviceClassName} {
103
+ constructor(${constructorParameters.join(',\n')}) {}
104
+
105
+ /**
106
+ * Returns the raw ${meta.userFriendlyName} with the given id or null if it does not exist.
107
+ * Raw: The ${meta.userFriendlyName} only contains linked Ids, not the linked items themselves.
108
+ */
109
+ public get(id: ${model.brandedIdType}): ${meta.types.typeName} | null {
110
+ return this.${modelRepositoryVariableName}.get(id)
111
+ }
112
+
113
+ ${hasLinkedItems ? linkedItemsGetterFn : ''}
114
+
115
+ /**
116
+ * Returns a map of all ${meta.userFriendlyNamePlural}.
117
+ */
118
+ public getAll(): Map<${meta.types.brandedIdType}, ${model.typeName}> {
119
+ return this.${modelRepositoryVariableName}.getAll()
120
+ }
121
+
122
+ /**
123
+ * Returns a list of filtered, sorted and paginated ${meta.userFriendlyNamePlural}.
124
+ */
125
+ public getList({
126
+ filter,
127
+ sort,
128
+ cursor,
129
+ }: {
130
+ filter?: { field: keyof ${model.typeName}; operator: FilterOperator; value: string | number }
131
+ sort?: { field: keyof ${model.typeName}; ascending: boolean }
132
+ cursor?: { startRow: number; endRow: number }
133
+ }) {
134
+ const items = this.data.getAllAsArray()
135
+ const filtered = !filter
136
+ ? items
137
+ : items.filter((item) => filterFn(item, filter.field, filter.operator, filter.value))
138
+
139
+ const filteredAndSorted = !sort
140
+ ? filtered
141
+ : filtered.sort((a, b) => (sort.ascending ? compare(a, b, sort.field) : -compare(a, b, sort.field)))
142
+
143
+ const paginated = !cursor ? filteredAndSorted : filteredAndSorted.slice(cursor.startRow, cursor.endRow)
144
+ return { rows: paginated, count: items.length }
145
+ }
146
+
147
+ }
148
+
149
+ // Utility Functions
150
+
151
+ ${_createModelCompareFn({ model })}
152
+
153
+ ${_createModelFilterFn({ model })}
154
+ `;
155
+ }
156
+ exports.generateModelBusinessLogicView = generateModelBusinessLogicView;
157
+ /**
158
+ * Generates a utility filter function for the given model that can be used to filter out instances
159
+ * of a model using a given operator on a given field.
160
+ */
161
+ function _createModelFilterFn({ model }) {
162
+ const stringFieldFilters = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'string' }).map((f) => {
163
+ return {
164
+ match: `"${f.name}"`,
165
+ block: `
166
+ if (typeof value !== 'string') {
167
+ return false
168
+ }
169
+
170
+ switch (operator) {
171
+ case 'contains': {
172
+ return (item[field] || '').toLowerCase().includes(value.toLowerCase())
173
+ }
174
+ default: {
175
+ return false
176
+ }
177
+ }
178
+ `,
179
+ };
180
+ });
181
+ const numberFieldsFilters = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'number' }).map((f) => {
182
+ return {
183
+ match: `"${f.name}"`,
184
+ block: `
185
+ const toCompare = item[field]
186
+ if (typeof value !== 'number' || toCompare === null) {
187
+ return false
188
+ }
189
+
190
+ switch (operator) {
191
+ case 'eq': {
192
+ return item[field] === value
193
+ }
194
+ case 'gt': {
195
+ return toCompare > value
196
+ }
197
+ case 'lt': {
198
+ return toCompare < value
199
+ }
200
+ default: {
201
+ return false
202
+ }
203
+ }
204
+ `,
205
+ };
206
+ });
207
+ const fnBlock = (0, ast_1.createSwitchStatement)({
208
+ field: 'field',
209
+ cases: [...stringFieldFilters, ...numberFieldsFilters],
210
+ defaultBlock: 'return false',
211
+ });
212
+ return `
213
+ /**
214
+ * Filters the given ${model.typeName} by the given field, using provided operator and value.
215
+ */
216
+ function filterFn(
217
+ item: ${model.typeName},
218
+ field: keyof ${model.typeName},
219
+ operator: FilterOperator,
220
+ value: string | number
221
+ ): boolean {
222
+ ${fnBlock}
223
+ }
224
+ `;
225
+ }
226
+ /**
227
+ * Returns a utility compare function that lets you compare two instances of a given model
228
+ * by a given field.
229
+ */
230
+ function _createModelCompareFn({ model }) {
231
+ const stringFieldComparators = (0, fields_1.getScalarFields)({ fields: model.fields, tsTypeName: 'string' }).map((f) => {
232
+ return {
233
+ match: `"${f.name}"`,
234
+ block: `
235
+ return (a[field] || '').localeCompare(b[field] || '')
236
+ `,
237
+ };
238
+ });
239
+ const fnBlock = (0, ast_1.createSwitchStatement)({
240
+ field: 'field',
241
+ cases: [...stringFieldComparators],
242
+ defaultBlock: 'return 0',
243
+ });
244
+ return `
245
+ /**
246
+ * Compares two ${model.typeName} instances by the given field.
247
+ */
248
+ function compare(a: ${model.typeName}, b: ${model.typeName}, field: keyof ${model.typeName}) {
249
+ ${fnBlock}
250
+ }
251
+
252
+ `;
253
+ }
@@ -52,6 +52,9 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
52
52
  () => createTypedForm<CreateInputData>().with({ ${(() => {
53
53
  const components = new Set();
54
54
  for (const field of fields.values()) {
55
+ if (field.attributes.isReadonly) {
56
+ continue;
57
+ }
55
58
  switch (field.kind) {
56
59
  case 'enum': {
57
60
  const enumMeta = (0, meta_1.getEnumMetadata)({ enumerator: field.enumerator });
@@ -79,7 +82,7 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
79
82
  <Typed.Formik
80
83
  initialValues={{
81
84
  ${fields
82
- .filter((f) => f.kind !== 'id')
85
+ .filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
83
86
  .map((field) => `${getFormikFieldName(field.name)}: null,`)
84
87
  .join('\n')}
85
88
  }}
@@ -195,6 +198,7 @@ export const ${components.modals.editComponentName} = ({
195
198
  <Typed.Formik
196
199
  initialValues={{
197
200
  ${fields
201
+ .filter((f) => !f.attributes.isReadonly || f.kind === 'id')
198
202
  .map((field) => {
199
203
  switch (field.kind) {
200
204
  case 'enum':
@@ -387,6 +391,9 @@ function getFormImports({ model, meta }) {
387
391
  function getCreateFormInputFields({ model }) {
388
392
  const form = new serializer_1.Serializer();
389
393
  for (const field of model.fields.values()) {
394
+ if (field.attributes.isReadonly) {
395
+ continue;
396
+ }
390
397
  switch (field.kind) {
391
398
  case 'id':
392
399
  continue;
@@ -407,12 +414,13 @@ function getCreateFormInputFields({ model }) {
407
414
  }
408
415
  function getFormikCreateMutationData({ model: { fields } }) {
409
416
  return fields
417
+ .filter((f) => !f.attributes.isReadonly)
410
418
  .map((field) => {
411
419
  const formikFieldName = getFormikFieldName(field.name);
412
420
  switch (field.kind) {
413
421
  case 'id':
414
422
  case 'scalar':
415
- // NOTE: Create methods generate the ID field upon submision.
423
+ // NOTE: Create methods generate the ID field upon submission.
416
424
  if (field.kind === 'id') {
417
425
  return '';
418
426
  }
@@ -454,6 +462,9 @@ function getFormikCreateMutationData({ model: { fields } }) {
454
462
  function getEditFormInputFields({ model }) {
455
463
  const form = new serializer_1.Serializer();
456
464
  for (const field of model.fields.values()) {
465
+ if (field.attributes.isReadonly) {
466
+ continue;
467
+ }
457
468
  switch (field.kind) {
458
469
  case 'id':
459
470
  continue;
@@ -489,6 +500,7 @@ function getEditFormInputFields({ model }) {
489
500
  }
490
501
  function getEditFormikMutationData({ model: { fields } }) {
491
502
  return fields
503
+ .filter((f) => !f.attributes.isReadonly || f.kind === 'id')
492
504
  .map((field) => {
493
505
  const formikFieldName = getFormikFieldName(field.name);
494
506
  switch (field.kind) {
@@ -520,11 +532,15 @@ function getFormFieldComponents({ model }) {
520
532
  var _a;
521
533
  const form = new serializer_1.Serializer();
522
534
  for (const field of model.fields.values()) {
535
+ // By default, we hide generated fields like createdAt, updatedAt, deletedAt.
536
+ if (field.attributes.isReadonly) {
537
+ continue;
538
+ }
523
539
  const formikFieldName = getFormikFieldName(field.name);
524
540
  const label = StringUtils.toPascalCase(field.name);
525
541
  switch (field.kind) {
526
542
  case 'id': {
527
- // NOTE: We never show the ID field in the form even if it's in the type signiture of the form input.
543
+ // NOTE: We never show the ID field in the form even if it's in the type signature of the form input.
528
544
  break;
529
545
  }
530
546
  case 'scalar': {
@@ -604,7 +620,7 @@ function getFormFieldComponents({ model }) {
604
620
  function getFormikValidationCases({ model }) {
605
621
  const form = new serializer_1.Serializer();
606
622
  for (const field of model.fields.values()) {
607
- if (field.kind === 'id') {
623
+ if (field.kind === 'id' || field.attributes.isReadonly) {
608
624
  continue;
609
625
  }
610
626
  const formikFieldName = getFormikFieldName(field.name);
@@ -31,6 +31,10 @@ type FnSignature = {
31
31
  * The return type of the function.
32
32
  */
33
33
  returnType: string;
34
+ /**
35
+ * The JSDoc comment of the function.
36
+ */
37
+ jsDoc?: string[];
34
38
  };
35
39
  /**
36
40
  * Returns a collection of type signatures for the repository methods of a given model.
@@ -40,7 +44,12 @@ export declare function getRepositoryMethodsTypeSignatures({ model, meta }: {
40
44
  meta: ModelMetaData;
41
45
  }): {
42
46
  create: FnSignature;
47
+ createMany: FnSignature;
43
48
  update: FnSignature;
49
+ updateMany: FnSignature;
50
+ upsert: FnSignature;
51
+ upsertMany: FnSignature;
44
52
  delete: FnSignature;
53
+ deleteMany: FnSignature;
45
54
  };
46
55
  export {};