@postxl/generator 0.15.3 → 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
|
|
37
|
-
imports.addImport({
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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.${
|
|
120
|
+
return this.${dataRepositoryVariableName}.get(id)
|
|
108
121
|
}
|
|
109
|
-
|
|
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.${
|
|
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.${
|
|
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.${
|
|
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.${
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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} }>(
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
components:
|
|
86
|
+
<${components.modals.editComponentName}
|
|
87
|
+
data={item}
|
|
88
|
+
show={isEditModalOpen}
|
|
89
|
+
onHide={() => setIsEditModalOpen(false)}
|
|
90
|
+
/>
|
|
91
|
+
`,
|
|
92
|
+
components: reactContextProvidersOfDependencies,
|
|
91
93
|
})}
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
${
|
|
129
|
+
${_nestInComponents({ child, components: rest })}
|
|
117
130
|
</${parent}>
|
|
118
131
|
`;
|
|
119
132
|
}
|
|
@@ -182,7 +182,7 @@ export const ${components.modals.editComponentName} = ({
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
async onSuccess() {
|
|
185
|
-
await cache.${trpc.
|
|
185
|
+
await cache.${trpc.routerName}.invalidate()
|
|
186
186
|
},
|
|
187
187
|
},
|
|
188
188
|
),
|
|
@@ -260,7 +260,7 @@ export const ${components.modals.deleteComponentName} = ({
|
|
|
260
260
|
await toast.promise(
|
|
261
261
|
mutation.mutateAsync(id, {
|
|
262
262
|
async onSuccess() {
|
|
263
|
-
await cache.${trpc.
|
|
263
|
+
await cache.${trpc.routerName}.invalidate()
|
|
264
264
|
},
|
|
265
265
|
}),
|
|
266
266
|
{
|
|
@@ -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}
|
|
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}
|
|
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}
|
|
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}
|
|
466
|
+
return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}.id : null,`;
|
|
467
467
|
default:
|
|
468
468
|
throw new types_1.ExhaustiveSwitchCheck(field);
|
|
469
469
|
}
|