@postxl/generator 0.15.4 → 0.15.5

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.
@@ -33,119 +33,107 @@ const Types = __importStar(require("../../lib/schema/types"));
33
33
  */
34
34
  function generateModelBusinessLogic({ model, meta }) {
35
35
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.serviceFilePath);
36
- const repos = new Map();
37
- imports.addImport({
38
- items: [meta.data.repositoryClassName],
39
- from: meta.data.importPath,
36
+ const dependencies = new Map();
37
+ imports.addImport({ from: meta.data.importPath, items: [meta.data.repositoryClassName] });
38
+ imports.addImport({ from: meta.types.importPath, items: [model.brandedIdType, meta.types.typeName] });
39
+ const dataRepositoryVariableName = Types.toVariableName('data');
40
+ // Set of repositories (one for each referenced model) to be injected into the constructor.
41
+ dependencies.set(meta.data.repositoryClassName, {
42
+ repositoryClassName: meta.data.repositoryClassName,
43
+ localRepositoryVariableName: dataRepositoryVariableName,
40
44
  });
41
- imports.addImport({
42
- items: [model.brandedIdType, meta.types.typeName],
43
- from: meta.types.importPath,
44
- });
45
- const dataRepo = Types.toVariableName('data');
46
- repos.set(meta.data.repositoryClassName, { name: dataRepo, repoName: meta.data.repositoryClassName });
47
- const linkedItems = new Map();
45
+ const linkedForeignBacklinkFields = new Map();
46
+ // Converts each relationship to a variable that is pulled from the referenced model's repository - and validates
47
+ // that it exists in case the relationship is required.
48
+ // Variable definition and name of the variable is stored in a Map for each relationship - and is referenced in
49
+ // the `getLinkedItem` function.
48
50
  for (const relation of (0, fields_1.getRelationFields)(model)) {
49
- if (relation.relationToModel.typeName === model.typeName) {
50
- continue;
51
- }
52
51
  const refModel = relation.relationToModel;
53
52
  const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
54
- imports.addImport({
55
- items: [refModel.brandedIdType, refMeta.types.typeName],
56
- from: refMeta.types.importPath,
57
- });
58
- imports.addImport({
59
- items: [refMeta.data.repositoryClassName],
60
- from: refMeta.data.importPath,
61
- });
62
- repos.set(refMeta.data.repositoryClassName, {
63
- name: refMeta.internalPluralName,
64
- repoName: refMeta.data.repositoryClassName,
53
+ imports.addImport({ from: refMeta.data.importPath, items: [refMeta.data.repositoryClassName] });
54
+ /**
55
+ * Internal variable pointing to an instance of a referenced repository.
56
+ */
57
+ const repoReferenceName = relation.relationToModel.typeName !== model.typeName ? refMeta.internalPluralName : dataRepositoryVariableName;
58
+ dependencies.set(refMeta.data.repositoryClassName, {
59
+ localRepositoryVariableName: repoReferenceName,
60
+ repositoryClassName: refMeta.data.repositoryClassName,
65
61
  });
66
- linkedItems.set(relation.name, {
62
+ const variableDefinition = `
63
+ const ${relation.relatedModelBacklinkFieldName} = this.${repoReferenceName}.get(itemRaw.${relation.name})
64
+ ${!relation.isRequired
65
+ ? ''
66
+ : `if (!${relation.relatedModelBacklinkFieldName}) {
67
+ throw new Error(\`Could not find ${refMeta.types.typeName} with id \${itemRaw.${relation.name}} for ${model.typeName}.${relation.name}!\`)
68
+ }`}
69
+ `;
70
+ linkedForeignBacklinkFields.set(relation.name, {
67
71
  fieldName: relation.relatedModelBacklinkFieldName,
68
- variableDefinition: `
69
- const ${relation.relatedModelBacklinkFieldName} = this.${refMeta.internalPluralName}.get(itemRaw.${relation.name})
70
- ${!relation.isRequired
71
- ? ''
72
- : `
73
- if (!${relation.relatedModelBacklinkFieldName}) {
74
- throw new Error(\`Could not find ${refMeta.types.typeName} with id \${itemRaw.${relation.name}} for ${model.typeName}.${relation.name}!\`)
75
- }`}`,
72
+ variableDefinition,
76
73
  });
77
74
  }
78
- const hasLinkedItems = linkedItems.size > 0;
75
+ const hasLinkedItems = linkedForeignBacklinkFields.size > 0;
79
76
  if (hasLinkedItems) {
80
- imports.addImport({
81
- items: [meta.types.linkedTypeName],
82
- from: meta.types.importPath,
83
- });
77
+ imports.addImport({ from: meta.types.importPath, items: [meta.types.linkedTypeName] });
84
78
  }
85
- for (const f of (0, fields_1.getEnumFields)(model)) {
86
- const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: f.enumerator });
87
- imports.addImport({
88
- items: [f.enumerator.typeName],
89
- from: refEnumMeta.types.importPath,
90
- });
79
+ const constructorParameters = [...dependencies.values()].map(({ localRepositoryVariableName, repositoryClassName }) => `private readonly ${localRepositoryVariableName}: ${repositoryClassName}`);
80
+ const linkedItemsGetterFn = `
81
+ /**
82
+ * Returns the linked ${meta.userFriendlyName} with the given id or null if it does not exist.
83
+ * Linked: The ${meta.userFriendlyName} contains the linked (raw) items themselves, not only the ids.
84
+ */
85
+ public getLinkedItem(id: ${model.brandedIdType}): ${meta.types.linkedTypeName} | null {
86
+ const itemRaw = this.${dataRepositoryVariableName}.get(id)
87
+ if (!itemRaw) return null
88
+ ${[...linkedForeignBacklinkFields.values()].map(({ variableDefinition }) => variableDefinition).join('\n')}
89
+ const item: ${meta.types.linkedTypeName} = {
90
+ ${model.fields
91
+ .map((f) => {
92
+ if (f.kind !== 'relation') {
93
+ return `${f.name}: itemRaw.${f.name}`;
94
+ }
95
+ const linked = linkedForeignBacklinkFields.get(f.name);
96
+ if (!linked) {
97
+ throw new Error(`Could not find linked item for ${model.typeName}.${f.name}`);
98
+ }
99
+ return `${linked.fieldName}`;
100
+ })
101
+ .join(',\n')}
102
+ }
103
+ return item
91
104
  }
105
+ `;
92
106
  return `
93
107
  import { Injectable } from '@nestjs/common'
94
- ${imports.generate()}
108
+
109
+ ${imports.generate()}
95
110
 
96
111
  @Injectable()
97
112
  export class ${meta.businessLogic.serviceClassName} {
98
- constructor(
99
- ${[...repos.values()].map(({ name, repoName }) => `private readonly ${name}: ${repoName}`).join(',\n')}
100
- ) {}
113
+ constructor(${constructorParameters.join(',\n')}) {}
101
114
 
102
115
  /**
103
116
  * Returns the raw ${meta.userFriendlyName} with the given id or null if it does not exist.
104
117
  * Raw: The ${meta.userFriendlyName} only contains linked Ids, not the linked items themselves.
105
118
  */
106
119
  public get(id: ${model.brandedIdType}): ${meta.types.typeName} | null {
107
- return this.${dataRepo}.get(id)
120
+ return this.${dataRepositoryVariableName}.get(id)
108
121
  }
109
- ${!hasLinkedItems
110
- ? ''
111
- : `
112
- /**
113
- * Returns the linked ${meta.userFriendlyName} with the given id or null if it does not exist.
114
- * Linked: The ${meta.userFriendlyName} contains the linked (raw) items themselves, not only the ids.
115
- */
116
- public getLinkedItem(id: ${model.brandedIdType}): ${meta.types.linkedTypeName} | null {
117
- const itemRaw = this.${dataRepo}.get(id)
118
- if (!itemRaw) return null
119
- ${[...linkedItems.values()].map(({ variableDefinition }) => variableDefinition).join('\n')}
120
- const item: ${meta.types.linkedTypeName} = {
121
- ${model.fields
122
- .map((f) => {
123
- if (f.kind !== 'relation') {
124
- return `${f.name}: itemRaw.${f.name}`;
125
- }
126
- const linked = linkedItems.get(f.name);
127
- if (!linked) {
128
- throw new Error(`Could not find linked item for ${model.typeName}.${f.name}`);
129
- }
130
- return `${linked.fieldName}`;
131
- })
132
- .join(',\n')}
133
- }
134
- return item
135
- }`}
122
+
123
+ ${hasLinkedItems ? linkedItemsGetterFn : ''}
136
124
 
137
125
  /**
138
126
  * Returns a map of all ${meta.userFriendlyName}s.
139
127
  */
140
128
  public getAll(): Map<${meta.types.brandedIdType}, ${model.typeName}> {
141
- return this.${dataRepo}.getAll()
129
+ return this.${dataRepositoryVariableName}.getAll()
142
130
  }
143
131
 
144
132
  /**
145
133
  * Creates a new ${meta.userFriendlyName}.
146
134
  */
147
135
  public async create(item: Omit<${model.typeName}, 'id'>): Promise<${model.typeName}> {
148
- return this.${dataRepo}.create(item)
136
+ return this.${dataRepositoryVariableName}.create(item)
149
137
  }
150
138
 
151
139
  /**
@@ -154,7 +142,7 @@ export class ${meta.businessLogic.serviceClassName} {
154
142
  public async update(item: Partial<${model.typeName}> & {
155
143
  id: ${model.brandedIdType}
156
144
  }): Promise<${model.typeName}> {
157
- return this.${dataRepo}.update(item)
145
+ return this.${dataRepositoryVariableName}.update(item)
158
146
  }
159
147
 
160
148
  /**
@@ -164,7 +152,7 @@ export class ${meta.businessLogic.serviceClassName} {
164
152
  * If the items is a dependency of another item, the deletion will fail!
165
153
  */
166
154
  public async delete(id: ${model.brandedIdType}): Promise<void> {
167
- return this.${dataRepo}.delete(id)
155
+ return this.${dataRepositoryVariableName}.delete(id)
168
156
  }
169
157
  }
170
158
  `;
@@ -28,10 +28,11 @@ function generateModelLibraryComponents({ model, meta }) {
28
28
  from: refMeta.react.folderPath,
29
29
  });
30
30
  }
31
- const depContexts = (0, fields_1.getRelationFields)(model).map((field) => {
32
- const depMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
33
- return depMeta.react.context.contextProviderName;
34
- });
31
+ const reactContextProvidersOfDependencies = new Set();
32
+ for (const relation of (0, fields_1.getRelationFields)(model)) {
33
+ const refMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
34
+ reactContextProvidersOfDependencies.add(refMeta.react.context.contextProviderName);
35
+ }
35
36
  return `
36
37
  import React, { useState } from 'react'
37
38
 
@@ -56,64 +57,76 @@ function generateModelLibraryComponents({ model, meta }) {
56
57
  /**
57
58
  * Show a single ${model.name} entry.
58
59
  */
59
- export const ${components.cardComponentName} = React.forwardRef<HTMLLIElement, { item: ${model.typeName} }>(({ item }, forwardedRef) => {
60
- const [isEditModalOpen, setIsEditModalOpen] = useState(false)
61
- const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
60
+ export const ${components.cardComponentName} = React.forwardRef<HTMLLIElement, { item: ${model.typeName} }>(
61
+ ({ item }, forwardedRef) => {
62
+ const [isEditModalOpen, setIsEditModalOpen] = useState(false)
63
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
62
64
 
63
- return (
64
- <>
65
- <Card
66
- ref={forwardedRef}
67
- title={item.name}
68
- actions={[
69
- {
70
- label: 'Edit',
71
- icon: 'pencil-on-paper',
72
- onClick: () => setIsEditModalOpen(true),
73
- },
74
- {
75
- label: 'Delete',
76
- icon: 'trash',
77
- onClick: () => setIsDeleteModalOpen(true),
78
- },
79
- ]}
80
- />
81
-
82
- ${nestInParentComponents({
65
+ return (
66
+ <>
67
+ <Card
68
+ ref={forwardedRef}
69
+ title={item.name}
70
+ actions={[
71
+ {
72
+ label: 'Edit',
73
+ icon: 'pencil-on-paper',
74
+ onClick: () => setIsEditModalOpen(true),
75
+ },
76
+ {
77
+ label: 'Delete',
78
+ icon: 'trash',
79
+ onClick: () => setIsDeleteModalOpen(true),
80
+ },
81
+ ]}
82
+ />
83
+
84
+ ${nestChildrenInComponents({
83
85
  child: `
84
- <${components.modals.editComponentName}
85
- data={item}
86
- show={isEditModalOpen}
87
- onHide={() => setIsEditModalOpen(false)}
88
- />
89
- `,
90
- components: depContexts,
86
+ <${components.modals.editComponentName}
87
+ data={item}
88
+ show={isEditModalOpen}
89
+ onHide={() => setIsEditModalOpen(false)}
90
+ />
91
+ `,
92
+ components: reactContextProvidersOfDependencies,
91
93
  })}
92
94
 
93
- <${components.modals.deleteComponentName}
94
- id={item.id}
95
- show={isDeleteModalOpen}
96
- onHide={() => setIsDeleteModalOpen(false)}
97
- />
98
- </>
99
- )
100
- })
95
+ <${components.modals.deleteComponentName}
96
+ id={item.id}
97
+ show={isDeleteModalOpen}
98
+ onHide={() => setIsDeleteModalOpen(false)}
99
+ />
100
+ </>
101
+ )
102
+ }
103
+ )
101
104
 
102
105
  ${components.cardComponentName}.displayName = '${components.cardComponentName}'
103
106
  `;
104
107
  }
105
108
  exports.generateModelLibraryComponents = generateModelLibraryComponents;
106
109
  /**
107
- * Nests a given React component in an array of parent wrappers.
110
+ * Deterministifcally nests a given React component in an array of parent wrappers.
111
+ */
112
+ function nestChildrenInComponents({ child, components }) {
113
+ if (components.size === 0) {
114
+ return child;
115
+ }
116
+ const sortedComponents = [...components.values()].sort((a, b) => a.localeCompare(b));
117
+ return _nestInComponents({ child, components: sortedComponents });
118
+ }
119
+ /**
120
+ * DO NOT USE THIS FUNCTION DIRECTLY. Use `nestChildrenInComponents` instead.
108
121
  */
109
- function nestInParentComponents({ child, components }) {
122
+ function _nestInComponents({ child, components }) {
110
123
  if (components.length === 0) {
111
124
  return child;
112
125
  }
113
126
  const [parent, ...rest] = components;
114
127
  return `
115
128
  <${parent}>
116
- ${nestInParentComponents({ child, components: rest })}
129
+ ${_nestInComponents({ child, components: rest })}
117
130
  </${parent}>
118
131
  `;
119
132
  }
@@ -389,7 +389,7 @@ function getFormikCreateMutationData({ model: { fields } }) {
389
389
  ${field.name}: values.${formikFieldName}!.id,
390
390
  `;
391
391
  }
392
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
392
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}.id : null,`;
393
393
  case 'enum':
394
394
  if (field.isRequired) {
395
395
  return `
@@ -398,7 +398,7 @@ function getFormikCreateMutationData({ model: { fields } }) {
398
398
  ${field.name}: values.${formikFieldName}!.id,
399
399
  `;
400
400
  }
401
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
401
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}.id : null,`;
402
402
  default:
403
403
  throw new types_1.ExhaustiveSwitchCheck(field);
404
404
  }
@@ -458,12 +458,12 @@ function getEditFormikMutationData({ model: { fields } }) {
458
458
  if (field.isRequired) {
459
459
  return `${field.name}: values.${formikFieldName}.id,`;
460
460
  }
461
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
461
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}.id : null,`;
462
462
  case 'enum':
463
463
  if (field.isRequired) {
464
464
  return `${field.name}: values.${formikFieldName}.id,`;
465
465
  }
466
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
466
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}.id : null,`;
467
467
  default:
468
468
  throw new types_1.ExhaustiveSwitchCheck(field);
469
469
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.15.4",
3
+ "version": "0.15.5",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {