@postxl/generator 0.15.4 → 0.15.6

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,109 @@ 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
+ /**
40
+ * Set of repositories (one for each referenced model) to be injected into the constructor.
41
+ */
42
+ const dataRepositoryVariableName = Types.toVariableName('data');
43
+ dependencies.set(meta.data.repositoryClassName, {
44
+ repositoryClassName: meta.data.repositoryClassName,
45
+ localRepositoryVariableName: dataRepositoryVariableName,
40
46
  });
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();
47
+ const linkedForeignBacklinkFields = new Map();
48
+ // Converts each relationship to a variable that is pulled from the referenced model's repository - and validates
49
+ // that it exists in case the relationship is required.
50
+ // Variable definition and name of the variable is stored in a Map for each relationship - and is referenced in
51
+ // the `getLinkedItem` function.
48
52
  for (const relation of (0, fields_1.getRelationFields)(model)) {
49
- if (relation.relationToModel.typeName === model.typeName) {
50
- continue;
51
- }
52
53
  const refModel = relation.relationToModel;
53
54
  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,
55
+ imports.addImport({ from: refMeta.data.importPath, items: [refMeta.data.repositoryClassName] });
56
+ /**
57
+ * Internal variable pointing to an instance of a referenced repository.
58
+ */
59
+ const repoReferenceName = relation.relationToModel.typeName !== model.typeName ? refMeta.internalPluralName : dataRepositoryVariableName;
60
+ dependencies.set(refMeta.data.repositoryClassName, {
61
+ localRepositoryVariableName: repoReferenceName,
62
+ repositoryClassName: refMeta.data.repositoryClassName,
65
63
  });
66
- linkedItems.set(relation.name, {
64
+ const variableDefinition = `
65
+ const ${relation.relatedModelBacklinkFieldName} = this.${repoReferenceName}.get(itemRaw.${relation.name})
66
+ ${!relation.isRequired
67
+ ? ''
68
+ : `if (!${relation.relatedModelBacklinkFieldName}) {
69
+ throw new Error(\`Could not find ${refMeta.types.typeName} with id \${itemRaw.${relation.name}} for ${model.typeName}.${relation.name}!\`)
70
+ }`}
71
+ `;
72
+ linkedForeignBacklinkFields.set(relation.name, {
67
73
  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
- }`}`,
74
+ variableDefinition,
76
75
  });
77
76
  }
78
- const hasLinkedItems = linkedItems.size > 0;
77
+ const hasLinkedItems = linkedForeignBacklinkFields.size > 0;
79
78
  if (hasLinkedItems) {
80
- imports.addImport({
81
- items: [meta.types.linkedTypeName],
82
- from: meta.types.importPath,
83
- });
79
+ imports.addImport({ from: meta.types.importPath, items: [meta.types.linkedTypeName] });
84
80
  }
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
- });
81
+ const constructorParameters = [...dependencies.values()].map(({ localRepositoryVariableName, repositoryClassName }) => `private readonly ${localRepositoryVariableName}: ${repositoryClassName}`);
82
+ const linkedItemsGetterFn = `
83
+ /**
84
+ * Returns the linked ${meta.userFriendlyName} with the given id or null if it does not exist.
85
+ * Linked: The ${meta.userFriendlyName} contains the linked (raw) items themselves, not only the ids.
86
+ */
87
+ public getLinkedItem(id: ${model.brandedIdType}): ${meta.types.linkedTypeName} | null {
88
+ const itemRaw = this.${dataRepositoryVariableName}.get(id)
89
+ if (!itemRaw) return null
90
+ ${[...linkedForeignBacklinkFields.values()].map(({ variableDefinition }) => variableDefinition).join('\n')}
91
+ const item: ${meta.types.linkedTypeName} = {
92
+ ${model.fields
93
+ .map((f) => {
94
+ if (f.kind !== 'relation') {
95
+ return `${f.name}: itemRaw.${f.name}`;
96
+ }
97
+ const linked = linkedForeignBacklinkFields.get(f.name);
98
+ if (!linked) {
99
+ throw new Error(`Could not find linked item for ${model.typeName}.${f.name}`);
100
+ }
101
+ return `${linked.fieldName}`;
102
+ })
103
+ .join(',\n')}
104
+ }
105
+ return item
91
106
  }
107
+ `;
92
108
  return `
93
109
  import { Injectable } from '@nestjs/common'
94
- ${imports.generate()}
110
+
111
+ ${imports.generate()}
95
112
 
96
113
  @Injectable()
97
114
  export class ${meta.businessLogic.serviceClassName} {
98
- constructor(
99
- ${[...repos.values()].map(({ name, repoName }) => `private readonly ${name}: ${repoName}`).join(',\n')}
100
- ) {}
115
+ constructor(${constructorParameters.join(',\n')}) {}
101
116
 
102
117
  /**
103
118
  * Returns the raw ${meta.userFriendlyName} with the given id or null if it does not exist.
104
119
  * Raw: The ${meta.userFriendlyName} only contains linked Ids, not the linked items themselves.
105
120
  */
106
121
  public get(id: ${model.brandedIdType}): ${meta.types.typeName} | null {
107
- return this.${dataRepo}.get(id)
122
+ return this.${dataRepositoryVariableName}.get(id)
108
123
  }
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
- }`}
124
+
125
+ ${hasLinkedItems ? linkedItemsGetterFn : ''}
136
126
 
137
127
  /**
138
128
  * Returns a map of all ${meta.userFriendlyName}s.
139
129
  */
140
130
  public getAll(): Map<${meta.types.brandedIdType}, ${model.typeName}> {
141
- return this.${dataRepo}.getAll()
131
+ return this.${dataRepositoryVariableName}.getAll()
142
132
  }
143
133
 
144
134
  /**
145
135
  * Creates a new ${meta.userFriendlyName}.
146
136
  */
147
137
  public async create(item: Omit<${model.typeName}, 'id'>): Promise<${model.typeName}> {
148
- return this.${dataRepo}.create(item)
138
+ return this.${dataRepositoryVariableName}.create(item)
149
139
  }
150
140
 
151
141
  /**
@@ -154,7 +144,7 @@ export class ${meta.businessLogic.serviceClassName} {
154
144
  public async update(item: Partial<${model.typeName}> & {
155
145
  id: ${model.brandedIdType}
156
146
  }): Promise<${model.typeName}> {
157
- return this.${dataRepo}.update(item)
147
+ return this.${dataRepositoryVariableName}.update(item)
158
148
  }
159
149
 
160
150
  /**
@@ -164,7 +154,7 @@ export class ${meta.businessLogic.serviceClassName} {
164
154
  * If the items is a dependency of another item, the deletion will fail!
165
155
  */
166
156
  public async delete(id: ${model.brandedIdType}): Promise<void> {
167
- return this.${dataRepo}.delete(id)
157
+ return this.${dataRepositoryVariableName}.delete(id)
168
158
  }
169
159
  }
170
160
  `;
@@ -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.6",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {