@postxl/generator 0.57.0 → 0.58.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.
- package/dist/generator.js +4 -2
- package/dist/generators/indices/businesslogic-update-clonecontext.generator.d.ts +9 -0
- package/dist/generators/indices/businesslogic-update-clonecontext.generator.js +39 -0
- package/dist/generators/indices/businesslogic-update-index.generator.js +1 -0
- package/dist/generators/indices/selectors.generator.d.ts +1 -1
- package/dist/generators/indices/selectors.generator.js +5 -11
- package/dist/generators/models/admin.page.generator.js +1 -1
- package/dist/generators/models/businesslogic-update.generator.js +60 -28
- package/dist/generators/models/react.generator/library.generator.js +2 -2
- package/dist/generators/models/react.generator/lookup.generator.js +18 -41
- package/dist/generators/models/react.generator/modals.generator.js +8 -8
- package/dist/generators/models/route.generator.js +9 -9
- package/dist/lib/attributes.d.ts +5 -0
- package/dist/lib/meta.d.ts +65 -35
- package/dist/lib/meta.js +18 -9
- package/dist/lib/schema/schema.d.ts +2 -2
- package/dist/prisma/attributes.js +3 -1
- package/package.json +1 -1
package/dist/generator.js
CHANGED
|
@@ -42,6 +42,7 @@ const lock_1 = require("@postxl/lock");
|
|
|
42
42
|
const react_generator_1 = require("./generators/enums/react.generator");
|
|
43
43
|
const types_generator_1 = require("./generators/enums/types.generator");
|
|
44
44
|
const businesslogic_actiontypes_generator_1 = require("./generators/indices/businesslogic-actiontypes.generator");
|
|
45
|
+
const businesslogic_update_clonecontext_generator_1 = require("./generators/indices/businesslogic-update-clonecontext.generator");
|
|
45
46
|
const businesslogic_update_index_generator_1 = require("./generators/indices/businesslogic-update-index.generator");
|
|
46
47
|
const businesslogic_update_module_generator_1 = require("./generators/indices/businesslogic-update-module.generator");
|
|
47
48
|
const businesslogic_update_service_generator_1 = require("./generators/indices/businesslogic-update-service.generator");
|
|
@@ -88,7 +89,7 @@ const CONFIG_SCHEMA = zod_1.z
|
|
|
88
89
|
.object({
|
|
89
90
|
pathToDataLib: zod_1.z.string().optional(),
|
|
90
91
|
pathToDbLib: zod_1.z.string().optional(),
|
|
91
|
-
|
|
92
|
+
pathToPlaywright: zod_1.z.string().optional(),
|
|
92
93
|
pathToE2ELib: zod_1.z.string().optional(),
|
|
93
94
|
pathToImportExport: zod_1.z.string().optional(),
|
|
94
95
|
pathToActions: zod_1.z.string().optional(),
|
|
@@ -113,8 +114,8 @@ const CONFIG_SCHEMA = zod_1.z
|
|
|
113
114
|
return {
|
|
114
115
|
paths: {
|
|
115
116
|
dataLibPath: (0, types_1.toPath)(s.pathToDataLib || './backend/libs/data/src/'),
|
|
117
|
+
playwrightPath: (0, types_1.toPath)(s.pathToPlaywright || './e2e/'),
|
|
116
118
|
dbLibPath: (0, types_1.toPath)(s.pathToDbLib || './backend/libs/db/src/'),
|
|
117
|
-
cypressPath: (0, types_1.toPath)(s.pathToCypress || './e2e/cypress/'),
|
|
118
119
|
e2eLibPath: (0, types_1.toPath)(s.pathToE2ELib || './backend/libs/e2e/src/'),
|
|
119
120
|
importExportPath: (0, types_1.toPath)(s.pathToImportExport || './backend/libs/import-export/src/'),
|
|
120
121
|
actionsPath: (0, types_1.toPath)(s.pathToActions || './backend/libs/actions/src/'),
|
|
@@ -233,6 +234,7 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
|
|
|
233
234
|
generated.write(`/${meta.businessLogic.view.moduleFilePath}.ts`, (0, businesslogic_view_module_generator_1.generateBusinessLogicViewModule)({ models, meta }));
|
|
234
235
|
generated.write(`/${meta.businessLogic.view.serviceFilePath}.ts`, (0, businesslogic_view_service_generator_1.generateBusinessLogicViewService)({ models, meta }));
|
|
235
236
|
generated.write(`/${meta.businessLogic.update.indexFilePath}.ts`, (0, businesslogic_update_index_generator_1.generateBusinessLogicUpdateIndex)({ models, meta }));
|
|
237
|
+
generated.write(`/${meta.businessLogic.update.cloneContextFilePath}.ts`, (0, businesslogic_update_clonecontext_generator_1.generateBusinessLogicCloneContext)({ models, meta }));
|
|
236
238
|
generated.write(`/${meta.businessLogic.update.moduleFilePath}.ts`, (0, businesslogic_update_module_generator_1.generateBusinessLogicUpdateModule)({ models, meta }));
|
|
237
239
|
generated.write(`/${meta.businessLogic.update.serviceFilePath}.ts`, (0, businesslogic_update_service_generator_1.generateBusinessLogicUpdateService)({ models, meta }));
|
|
238
240
|
generated.write(`/${meta.businessLogic.update.actionTypesFilePath}.ts`, (0, businesslogic_actiontypes_generator_1.generateBusinessLogicActionTypes)({ models, meta }));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SchemaMetaData } from '../../lib/meta';
|
|
2
|
+
import { Model } from '../../lib/schema/schema';
|
|
3
|
+
/**
|
|
4
|
+
* Generates the clone context for the business logic update service.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateBusinessLogicCloneContext({ models, meta: schemaMeta, }: {
|
|
7
|
+
models: Model[];
|
|
8
|
+
meta: SchemaMetaData;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateBusinessLogicCloneContext = void 0;
|
|
4
|
+
const imports_1 = require("../../lib/imports");
|
|
5
|
+
const meta_1 = require("../../lib/meta");
|
|
6
|
+
/**
|
|
7
|
+
* Generates the clone context for the business logic update service.
|
|
8
|
+
*/
|
|
9
|
+
function generateBusinessLogicCloneContext({ models, meta: schemaMeta, }) {
|
|
10
|
+
const imports = imports_1.ImportsGenerator.from(schemaMeta.businessLogic.update.cloneContextFilePath);
|
|
11
|
+
const typeDefinitions = [];
|
|
12
|
+
const mapAssignments = [];
|
|
13
|
+
for (const model of models) {
|
|
14
|
+
const modelMeta = (0, meta_1.getModelMetadata)({ model });
|
|
15
|
+
imports.addImports({
|
|
16
|
+
[schemaMeta.types.importPath]: [modelMeta.types.brandedIdType, modelMeta.types.typeName],
|
|
17
|
+
});
|
|
18
|
+
typeDefinitions.push(`${modelMeta.businessLogic.update.cloneContextMap}: Map<${modelMeta.types.brandedIdType}, ${modelMeta.types.typeName}>`);
|
|
19
|
+
mapAssignments.push(`${modelMeta.businessLogic.update.cloneContextMap}: new Map()`);
|
|
20
|
+
}
|
|
21
|
+
return /* ts */ `
|
|
22
|
+
${imports.generate()}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The context that is passed in a clone action.
|
|
26
|
+
*
|
|
27
|
+
* This context is used to track which items have already been cloned and link them to the cloned item.
|
|
28
|
+
*/
|
|
29
|
+
export type CloneContext = {
|
|
30
|
+
${typeDefinitions.join('\n')}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createCloneContext(): CloneContext {
|
|
34
|
+
return {
|
|
35
|
+
${mapAssignments.join(',\n')}
|
|
36
|
+
}
|
|
37
|
+
}`;
|
|
38
|
+
}
|
|
39
|
+
exports.generateBusinessLogicCloneContext = generateBusinessLogicCloneContext;
|
|
@@ -15,6 +15,7 @@ function generateBusinessLogicUpdateIndex({ models, meta }) {
|
|
|
15
15
|
const meta = (0, meta_1.getModelMetadata)({ model });
|
|
16
16
|
exports.exportSelectionFromPath(meta.businessLogic.update.serviceFilePath, [
|
|
17
17
|
meta.businessLogic.update.serviceClassName,
|
|
18
|
+
meta.businessLogic.update.decoders.name,
|
|
18
19
|
]);
|
|
19
20
|
}
|
|
20
21
|
return exports.generate();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates list of component selectors for
|
|
2
|
+
* Generates list of component selectors for E2E tests.
|
|
3
3
|
*
|
|
4
4
|
* Note: This generator does not need the models or meta data passed in.
|
|
5
5
|
* Instead it uses the SelectorCollector singleton that already collected all ids during the generation process of the individual models.
|
|
@@ -11,7 +11,7 @@ const HARDCODED_IDS = [
|
|
|
11
11
|
'confirmationModal-buttons-cancel',
|
|
12
12
|
];
|
|
13
13
|
/**
|
|
14
|
-
* Generates list of component selectors for
|
|
14
|
+
* Generates list of component selectors for E2E tests.
|
|
15
15
|
*
|
|
16
16
|
* Note: This generator does not need the models or meta data passed in.
|
|
17
17
|
* Instead it uses the SelectorCollector singleton that already collected all ids during the generation process of the individual models.
|
|
@@ -22,9 +22,10 @@ function generateSelectors() {
|
|
|
22
22
|
const object = {};
|
|
23
23
|
const ids = [...HARDCODED_IDS, ...collectedIds];
|
|
24
24
|
ids.sort();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
for (const id of ids) {
|
|
26
|
+
const keys = id.split('-');
|
|
27
|
+
extendObject(object, keys, `[data-test-id=${id}]`);
|
|
28
|
+
}
|
|
28
29
|
const selectors = JSON.stringify(object, null, 2);
|
|
29
30
|
return /* ts */ `
|
|
30
31
|
export const SELECTORS =
|
|
@@ -32,13 +33,6 @@ function generateSelectors() {
|
|
|
32
33
|
`;
|
|
33
34
|
}
|
|
34
35
|
exports.generateSelectors = generateSelectors;
|
|
35
|
-
/**
|
|
36
|
-
* Converts an id like `post-create-name` to a nested object like `{post: {create: {name: '[data-cy=post-create-name]'}}}`.
|
|
37
|
-
*/
|
|
38
|
-
function addCypressSelectorToNestedObject({ object, id, }) {
|
|
39
|
-
const keys = id.split('-');
|
|
40
|
-
extendObject(object, keys, `[data-cy=${id}]`);
|
|
41
|
-
}
|
|
42
36
|
/**
|
|
43
37
|
* Recursively traverses the keys and adds the value to the object in a nested way.
|
|
44
38
|
* E.g. `{object, keys: ['post', 'create', 'name'], value}` will extend `object` with `{post: {create: {name: value}}}`.
|
|
@@ -44,7 +44,7 @@ export default function Admin${meta.internalSingularNameCapitalized}Page() {
|
|
|
44
44
|
<Spacer key="Spacer" />
|
|
45
45
|
|
|
46
46
|
<ActionWrapper key="GlobalFilter">
|
|
47
|
-
<Button label={t['Create']} icon="plus" fill="fill"
|
|
47
|
+
<Button label={t['Create']} icon="plus" fill="fill" __e2e_selector__="indexPage-buttons-create" onClick={() => setIsCreateModalOpen(true)}/>
|
|
48
48
|
</ActionWrapper>
|
|
49
49
|
</ActionsBarWrapper>
|
|
50
50
|
</Header>
|
|
@@ -41,10 +41,18 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
41
41
|
iExecution: schemaMeta.actions.execution.interface,
|
|
42
42
|
typeName: meta.types.typeName,
|
|
43
43
|
brandedId: model.brandedIdType,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
decoders: {
|
|
45
|
+
name: meta.businessLogic.update.decoders.name,
|
|
46
|
+
createType: `Create${meta.internalSingularNameCapitalized}`,
|
|
47
|
+
updateType: `Update${meta.internalSingularNameCapitalized}`,
|
|
48
|
+
upsertType: `Upsert${meta.internalSingularNameCapitalized}`,
|
|
49
|
+
cloneType: `Clone${meta.internalSingularNameCapitalized}`,
|
|
50
|
+
},
|
|
51
|
+
cloneContext: {
|
|
52
|
+
type: schemaMeta.businessLogic.update.cloneContextType,
|
|
53
|
+
createMethod: schemaMeta.businessLogic.update.cloneContextCreateMethod,
|
|
54
|
+
map: meta.businessLogic.update.cloneContextMap,
|
|
55
|
+
},
|
|
48
56
|
};
|
|
49
57
|
const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.serviceFilePath);
|
|
50
58
|
imports.addImports({
|
|
@@ -58,6 +66,10 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
58
66
|
[meta.businessLogic.view.serviceFilePath]: [meta.businessLogic.view.serviceClassName],
|
|
59
67
|
[schemaMeta.actions.importPath]: [schemaMeta.actions.execution.interface, schemaMeta.actions.dispatcher.definition],
|
|
60
68
|
[schemaMeta.businessLogic.update.serviceFilePath]: schemaMeta.businessLogic.update.serviceClassName,
|
|
69
|
+
[schemaMeta.businessLogic.update.cloneContextFilePath]: [
|
|
70
|
+
schemaMeta.businessLogic.update.cloneContextType,
|
|
71
|
+
schemaMeta.businessLogic.update.cloneContextCreateMethod,
|
|
72
|
+
],
|
|
61
73
|
[schemaMeta.businessLogic.view.serviceFilePath]: schemaMeta.businessLogic.view.serviceClassName,
|
|
62
74
|
});
|
|
63
75
|
for (const relation of (0, fields_1.getRelationFields)(model)) {
|
|
@@ -85,7 +97,7 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
85
97
|
`@Inject(forwardRef(() => ${schemaMeta.businessLogic.update.serviceClassName})) private readonly updateService: ${schemaMeta.businessLogic.update.serviceClassName}`,
|
|
86
98
|
`@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly viewService: ${schemaMeta.businessLogic.view.serviceClassName}`,
|
|
87
99
|
];
|
|
88
|
-
const
|
|
100
|
+
const decoders = meta.businessLogic.update.decoders;
|
|
89
101
|
const { view, update } = meta.businessLogic;
|
|
90
102
|
/* prettier-ignore */
|
|
91
103
|
return /* ts */ `
|
|
@@ -99,37 +111,37 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
99
111
|
export type Actions = {
|
|
100
112
|
${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} and returns it.`])}
|
|
101
113
|
create: {
|
|
102
|
-
payload: ${m.createType}
|
|
114
|
+
payload: ${m.decoders.createType}
|
|
103
115
|
result: ${m.typeName}
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
${(0, jsdoc_1.toJsDocComment)([`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
107
119
|
createMany: {
|
|
108
|
-
payload: ${m.createType}[]
|
|
120
|
+
payload: ${m.decoders.createType}[]
|
|
109
121
|
result: ${m.typeName}[]
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
${(0, jsdoc_1.toJsDocComment)([`Updates a ${meta.userFriendlyName} and returns it.`])}
|
|
113
125
|
update: {
|
|
114
|
-
payload: ${m.updateType}
|
|
126
|
+
payload: ${m.decoders.updateType}
|
|
115
127
|
result: ${m.typeName}
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
${(0, jsdoc_1.toJsDocComment)([`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
119
131
|
updateMany: {
|
|
120
|
-
payload: ${m.updateType}[]
|
|
132
|
+
payload: ${m.decoders.updateType}[]
|
|
121
133
|
result: ${m.typeName}[]
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
${(0, jsdoc_1.toJsDocComment)([`Creates or updates a ${meta.userFriendlyName} and returns it.`])}
|
|
125
137
|
upsert: {
|
|
126
|
-
payload: ${m.upsertType}
|
|
138
|
+
payload: ${m.decoders.upsertType}
|
|
127
139
|
result: ${m.typeName}
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
${(0, jsdoc_1.toJsDocComment)([`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
131
143
|
upsertMany: {
|
|
132
|
-
payload: ${m.upsertType}[]
|
|
144
|
+
payload: ${m.decoders.upsertType}[]
|
|
133
145
|
result: ${m.typeName}[]
|
|
134
146
|
}
|
|
135
147
|
|
|
@@ -147,7 +159,7 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
147
159
|
|
|
148
160
|
${(0, jsdoc_1.toJsDocComment)([`Clones a ${meta.userFriendlyName} and returns the clone.`])}
|
|
149
161
|
clone: {
|
|
150
|
-
payload: ${m.cloneType}
|
|
162
|
+
payload: ${m.decoders.cloneType}
|
|
151
163
|
result: ${m.typeName}
|
|
152
164
|
}
|
|
153
165
|
}
|
|
@@ -155,46 +167,53 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
155
167
|
/**
|
|
156
168
|
* Zod decoder for validating the create input of a ${meta.userFriendlyName}.
|
|
157
169
|
*/
|
|
158
|
-
export const ${
|
|
170
|
+
export const ${decoders.create} = z.object({
|
|
159
171
|
${model.fields
|
|
160
172
|
.filter((f) => !f.attributes.isReadonly)
|
|
161
173
|
.map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f })}`)
|
|
162
174
|
.join(',')}
|
|
163
175
|
})
|
|
164
176
|
|
|
165
|
-
type ${m.createType} = z.infer<typeof ${
|
|
177
|
+
type ${m.decoders.createType} = z.infer<typeof ${decoders.create}>
|
|
166
178
|
|
|
167
179
|
/**
|
|
168
180
|
* Zod decoder for validating the update input of a ${meta.userFriendlyName} .
|
|
169
181
|
*/
|
|
170
|
-
export const ${
|
|
182
|
+
export const ${decoders.update} = z.object({
|
|
171
183
|
${model.fields
|
|
172
184
|
.filter((f) => !f.attributes.isReadonly || f.kind === 'id')
|
|
173
185
|
.map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f, allowAnyOptionalField: f.kind !== 'id' })}`)
|
|
174
186
|
.join(',')}
|
|
175
187
|
})
|
|
176
188
|
|
|
177
|
-
type ${m.updateType} = z.infer<typeof ${
|
|
189
|
+
type ${m.decoders.updateType} = z.infer<typeof ${decoders.update}>
|
|
178
190
|
|
|
179
191
|
/**
|
|
180
192
|
* Zod decoder for validating the upsert input of a ${meta.userFriendlyName} .
|
|
181
193
|
*/
|
|
182
|
-
export const ${
|
|
194
|
+
export const ${decoders.upsert} = z.union([${decoders.update}, ${decoders.create}])
|
|
183
195
|
|
|
184
|
-
type ${m.upsertType} = z.infer<typeof ${
|
|
196
|
+
type ${m.decoders.upsertType} = z.infer<typeof ${decoders.upsert}>
|
|
185
197
|
|
|
186
198
|
/**
|
|
187
199
|
* Zod decoder for validating the clone input of a ${meta.userFriendlyName} .
|
|
188
200
|
*/
|
|
189
|
-
export const ${
|
|
201
|
+
export const ${decoders.clone} = z.object({
|
|
190
202
|
${model.fields
|
|
191
203
|
.filter((f) => !f.attributes.isReadonly || f.kind === "id")
|
|
192
204
|
.map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f, allowAnyOptionalField: f.kind !== 'id' })}`)
|
|
193
205
|
.join(',')}
|
|
194
206
|
})
|
|
195
207
|
|
|
196
|
-
type ${m.cloneType} = z.infer<typeof ${
|
|
208
|
+
type ${m.decoders.cloneType} = z.infer<typeof ${decoders.clone}>
|
|
197
209
|
|
|
210
|
+
export const ${decoders.name} = {
|
|
211
|
+
create: ${decoders.create},
|
|
212
|
+
update: ${decoders.update},
|
|
213
|
+
upsert: ${decoders.upsert},
|
|
214
|
+
clone: ${decoders.clone},
|
|
215
|
+
}
|
|
216
|
+
|
|
198
217
|
export type ${update.serviceInterfaceName} = ${schemaMeta.actions.dispatcher.definition}<Actions>
|
|
199
218
|
|
|
200
219
|
@Injectable()
|
|
@@ -210,32 +229,32 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
|
|
|
210
229
|
}
|
|
211
230
|
|
|
212
231
|
${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} and returns it.`])}
|
|
213
|
-
public async create({ data, execution }: { data: ${m.createType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
232
|
+
public async create({ data, execution }: { data: ${m.decoders.createType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
214
233
|
return this.data.create({ item: data, execution })
|
|
215
234
|
}
|
|
216
235
|
|
|
217
236
|
${(0, jsdoc_1.toJsDocComment)([`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
218
|
-
public async createMany({ data, execution }: { data: ${m.createType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
237
|
+
public async createMany({ data, execution }: { data: ${m.decoders.createType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
219
238
|
return this.data.createMany({ items: data, execution })
|
|
220
239
|
}
|
|
221
240
|
|
|
222
241
|
${(0, jsdoc_1.toJsDocComment)([`Updates a ${meta.userFriendlyName} and returns it.`])}
|
|
223
|
-
public async update({ data, execution }: { data: ${m.updateType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
242
|
+
public async update({ data, execution }: { data: ${m.decoders.updateType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
224
243
|
return this.data.update({ item: data, execution })
|
|
225
244
|
}
|
|
226
245
|
|
|
227
246
|
${(0, jsdoc_1.toJsDocComment)([`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
228
|
-
public async updateMany({ data, execution }: { data: ${m.updateType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
247
|
+
public async updateMany({ data, execution }: { data: ${m.decoders.updateType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
229
248
|
return this.data.updateMany({ items: data, execution })
|
|
230
249
|
}
|
|
231
250
|
|
|
232
251
|
${(0, jsdoc_1.toJsDocComment)([`Creates or updates a ${meta.userFriendlyName} and returns it.`])}
|
|
233
|
-
public async upsert({ data, execution }: { data: ${m.upsertType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
252
|
+
public async upsert({ data, execution }: { data: ${m.decoders.upsertType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
|
|
234
253
|
return this.data.upsert({ item: data, execution })
|
|
235
254
|
}
|
|
236
255
|
|
|
237
256
|
${(0, jsdoc_1.toJsDocComment)([`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
|
|
238
|
-
public async upsertMany({ data, execution }: { data: ${m.upsertType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
257
|
+
public async upsertMany({ data, execution }: { data: ${m.decoders.upsertType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
|
|
239
258
|
return this.data.upsertMany({ items: data, execution })
|
|
240
259
|
}
|
|
241
260
|
|
|
@@ -330,6 +349,10 @@ function generateCloneMethod({ model, meta, m }) {
|
|
|
330
349
|
const backReferenceCloning = [];
|
|
331
350
|
const backReferenceNames = [];
|
|
332
351
|
for (const { referencingField, referencingModel } of model.references) {
|
|
352
|
+
// We only clone back references that are marked as cloneWithParent.
|
|
353
|
+
if (!referencingField.attributes.cloneWithParent) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
333
356
|
const refModelMeta = (0, meta_1.getModelMetadata)({ model: referencingModel });
|
|
334
357
|
const refFieldMeta = (0, meta_1.getFieldMetadata)({ field: referencingField });
|
|
335
358
|
backReferenceNames.push(`${refModelMeta.userFriendlyNamePlural}.${referencingField.name}`);
|
|
@@ -337,15 +360,22 @@ function generateCloneMethod({ model, meta, m }) {
|
|
|
337
360
|
backReferenceCloning.push(`
|
|
338
361
|
// ${referencingModel.name}.${referencingField.name}
|
|
339
362
|
for (const childId of await this.viewService.${view.serviceVariableName}.data.${refFieldMeta.getByForeignKeyIdsMethodFnName}(id)) {
|
|
340
|
-
await this.updateService.${update.serviceVariableName}.clone({ data: { id: childId, ${referencingField.name}: clone.id }, execution })
|
|
363
|
+
await this.updateService.${update.serviceVariableName}.clone({ data: { id: childId, ${referencingField.name}: clone.id }, execution, context })
|
|
341
364
|
}
|
|
342
365
|
`);
|
|
343
366
|
}
|
|
344
367
|
return `
|
|
345
368
|
${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} deep clone and returns it.`])}
|
|
346
369
|
public async clone(
|
|
347
|
-
{ data: { id, ...data }, execution
|
|
370
|
+
{ data: { id, ...data }, execution, context = ${m.cloneContext.createMethod}() }:
|
|
371
|
+
{ data: ${m.decoders.cloneType}; execution: ${m.iExecution}; context?: ${m.cloneContext.type} }
|
|
348
372
|
): Promise<${m.typeName}> {
|
|
373
|
+
const alreadyCloned = context.${m.cloneContext.map}.get(id)
|
|
374
|
+
if (alreadyCloned) {
|
|
375
|
+
// We already cloned this item before, so we return the cloned item.
|
|
376
|
+
return alreadyCloned
|
|
377
|
+
}
|
|
378
|
+
|
|
349
379
|
const source = await this.view.get(id)
|
|
350
380
|
if (!source) {
|
|
351
381
|
throw new Error(\`${meta.userFriendlyName} with id \${id} not found\`)
|
|
@@ -353,6 +383,8 @@ function generateCloneMethod({ model, meta, m }) {
|
|
|
353
383
|
|
|
354
384
|
const clone = await this.data.create({ item: { ...omitId(source), ...data }, execution })
|
|
355
385
|
|
|
386
|
+
context.${m.cloneContext.map}.set(id, clone)
|
|
387
|
+
|
|
356
388
|
${backReferenceCloning.join('\n')}
|
|
357
389
|
|
|
358
390
|
return clone
|
|
@@ -61,13 +61,13 @@ function generateModelLibraryComponents({ model, meta }) {
|
|
|
61
61
|
label: 'Edit',
|
|
62
62
|
icon: 'pencil-on-paper',
|
|
63
63
|
onClick: () => setIsEditModalOpen(true),
|
|
64
|
-
|
|
64
|
+
__e2e_action_selector__: "${selectorCollector.idFor('edit', { typePrefix: 'actions' })}",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
label: 'Delete',
|
|
68
68
|
icon: 'trash',
|
|
69
69
|
onClick: () => setIsDeleteModalOpen(true),
|
|
70
|
-
|
|
70
|
+
__e2e_action_selector__: "${selectorCollector.idFor('delete', { typePrefix: 'actions' })}",
|
|
71
71
|
},
|
|
72
72
|
]}
|
|
73
73
|
/>
|
|
@@ -3,12 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.generateModelLookupComponents = void 0;
|
|
4
4
|
const id_collector_1 = require("../../../lib/id-collector");
|
|
5
5
|
const imports_1 = require("../../../lib/imports");
|
|
6
|
+
/**
|
|
7
|
+
* Returns a selector string that links the component to the global selectors index.
|
|
8
|
+
*/
|
|
9
|
+
function selector(meta, typePrefix, options) {
|
|
10
|
+
var _a;
|
|
11
|
+
const selectorCollector = id_collector_1.SelectorCollector.from(meta.seed.constantName + '-formComponents');
|
|
12
|
+
return selectorCollector.idFor((_a = options === null || options === void 0 ? void 0 : options.element) !== null && _a !== void 0 ? _a : '', { typePrefix });
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* Utility generator that generates lookup components for a given model.
|
|
8
16
|
*/
|
|
9
17
|
function generateModelLookupComponents({ model, meta }) {
|
|
10
18
|
const { react: { context, components }, } = meta;
|
|
11
|
-
const selectorCollector = id_collector_1.SelectorCollector.from(meta.seed.constantName + '-formComponents');
|
|
12
19
|
const imports = imports_1.ImportsGenerator.from(meta.react.folderPath)
|
|
13
20
|
.addImport({
|
|
14
21
|
items: [context.hookFnName],
|
|
@@ -54,10 +61,7 @@ export const ${components.forms.selectInputName} = ({
|
|
|
54
61
|
options={list}
|
|
55
62
|
${labelProp}
|
|
56
63
|
loading={!ready}
|
|
57
|
-
|
|
58
|
-
delegated.__cypress_field_selector__
|
|
59
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'selectInput' })}"
|
|
60
|
-
}
|
|
64
|
+
__e2e_field_selector__="${selector(meta, 'selectInput')}"
|
|
61
65
|
{...delegated}
|
|
62
66
|
/>
|
|
63
67
|
}
|
|
@@ -71,10 +75,7 @@ export const ${components.forms.selectFieldName} = ({
|
|
|
71
75
|
options={list}
|
|
72
76
|
${labelProp}
|
|
73
77
|
loading={!ready}
|
|
74
|
-
|
|
75
|
-
delegated.__cypress_field_selector__
|
|
76
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'selectField' })}"
|
|
77
|
-
}
|
|
78
|
+
__e2e_field_selector__="${selector(meta, 'selectField')}"
|
|
78
79
|
{...delegated}
|
|
79
80
|
/>
|
|
80
81
|
}
|
|
@@ -90,10 +91,7 @@ export const ${components.forms.menuSelectInputName} = ({
|
|
|
90
91
|
options={list}
|
|
91
92
|
${labelProp}
|
|
92
93
|
loading={!ready}
|
|
93
|
-
|
|
94
|
-
delegated.__cypress_options_selector__
|
|
95
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'menuSelectInput' })}"
|
|
96
|
-
}
|
|
94
|
+
__e2e_options_selector__="${selector(meta, 'menuSelectInput')}"
|
|
97
95
|
{...delegated}
|
|
98
96
|
/>
|
|
99
97
|
}
|
|
@@ -107,10 +105,7 @@ export const ${components.forms.menuSelectFieldName} = ({
|
|
|
107
105
|
options={list}
|
|
108
106
|
${labelProp}
|
|
109
107
|
loading={!ready}
|
|
110
|
-
|
|
111
|
-
delegated.__cypress_options_selector__
|
|
112
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'menuSelectField' })}"
|
|
113
|
-
}
|
|
108
|
+
__e2e_options_selector__="${selector(meta, 'menuSelectField')}"
|
|
114
109
|
{...delegated}
|
|
115
110
|
/>
|
|
116
111
|
}
|
|
@@ -126,14 +121,8 @@ export const ${components.forms.searchInputName} = ({
|
|
|
126
121
|
options={list}
|
|
127
122
|
${labelProp}
|
|
128
123
|
loading={!ready}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
?? "${selectorCollector.idFor('field', { typePrefix: 'searchInput' })}"
|
|
132
|
-
}
|
|
133
|
-
__cypress_options_selector__={
|
|
134
|
-
delegated.__cypress_options_selector__
|
|
135
|
-
?? "${selectorCollector.idFor('options', { typePrefix: 'searchInput' })}"
|
|
136
|
-
}
|
|
124
|
+
__e2e_combobox_selector__="${selector(meta, 'searchInput', { element: 'field' })}"
|
|
125
|
+
__e2e_options_selector__="${selector(meta, 'searchInput', { element: 'options' })}"
|
|
137
126
|
{...delegated}
|
|
138
127
|
/>
|
|
139
128
|
}
|
|
@@ -147,14 +136,8 @@ export const ${components.forms.searchFieldName} = ({
|
|
|
147
136
|
options={list}
|
|
148
137
|
${labelProp}
|
|
149
138
|
loading={!ready}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
?? "${selectorCollector.idFor('field', { typePrefix: 'searchField' })}"
|
|
153
|
-
}
|
|
154
|
-
__cypress_options_selector__={
|
|
155
|
-
delegated.__cypress_options_selector__
|
|
156
|
-
?? "${selectorCollector.idFor('options', { typePrefix: 'searchField' })}"
|
|
157
|
-
}
|
|
139
|
+
__e2e_combobox_selector__="${selector(meta, 'searchField', { element: 'field' })}"
|
|
140
|
+
__e2e_options_selector__="${selector(meta, 'searchField', { element: 'options' })}"
|
|
158
141
|
{...delegated}
|
|
159
142
|
/>
|
|
160
143
|
}
|
|
@@ -169,10 +152,7 @@ export const ${components.forms.tableSelectInputName} = ({
|
|
|
169
152
|
options={list}
|
|
170
153
|
${labelProp}
|
|
171
154
|
loading={!ready}
|
|
172
|
-
|
|
173
|
-
delegated.__cypress_input_field_selector__
|
|
174
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'tableSelectInput' })}"
|
|
175
|
-
}
|
|
155
|
+
__e2e_input_field_selector__="${selector(meta, 'tableSelectInput')}"
|
|
176
156
|
{...delegated}
|
|
177
157
|
/>
|
|
178
158
|
}
|
|
@@ -186,10 +166,7 @@ export const ${components.forms.tableSelectFieldName} = ({
|
|
|
186
166
|
options={list}
|
|
187
167
|
${labelProp}
|
|
188
168
|
loading={!ready}
|
|
189
|
-
|
|
190
|
-
delegated.__cypress_input_field_selector__
|
|
191
|
-
?? "${selectorCollector.idFor('', { typePrefix: 'tableSelectField' })}"
|
|
192
|
-
}
|
|
169
|
+
__e2e_input_field_selector__="${selector(meta, 'tableSelectField')}"
|
|
193
170
|
{...delegated}
|
|
194
171
|
/>
|
|
195
172
|
}
|
|
@@ -150,7 +150,7 @@ export const ${modals.createComponentName} = ({
|
|
|
150
150
|
fill="fill"
|
|
151
151
|
onClick={submitForm}
|
|
152
152
|
loading={isSubmitting}
|
|
153
|
-
|
|
153
|
+
__e2e_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
|
|
154
154
|
/>
|
|
155
155
|
}>
|
|
156
156
|
${getFormFieldComponents({ model, selectorCollector })}
|
|
@@ -286,7 +286,7 @@ export const ${components.modals.editComponentName} = ({
|
|
|
286
286
|
color="primary"
|
|
287
287
|
onClick={submitForm}
|
|
288
288
|
loading={isSubmitting}
|
|
289
|
-
|
|
289
|
+
__e2e_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
|
|
290
290
|
/>
|
|
291
291
|
}
|
|
292
292
|
>
|
|
@@ -584,7 +584,7 @@ function getFormFieldComponents({ model, selectorCollector, }) {
|
|
|
584
584
|
<Typed.TextField
|
|
585
585
|
name="${formikFieldName}"
|
|
586
586
|
placeholder="Type..."
|
|
587
|
-
|
|
587
|
+
__e2e_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
|
|
588
588
|
/>
|
|
589
589
|
</div>
|
|
590
590
|
`);
|
|
@@ -602,7 +602,7 @@ function getFormFieldComponents({ model, selectorCollector, }) {
|
|
|
602
602
|
name="${formikFieldName}"
|
|
603
603
|
placeholder="2511"
|
|
604
604
|
decimals={${decimals}}
|
|
605
|
-
|
|
605
|
+
__e2e_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
|
|
606
606
|
/>
|
|
607
607
|
</div>
|
|
608
608
|
`);
|
|
@@ -615,7 +615,7 @@ function getFormFieldComponents({ model, selectorCollector, }) {
|
|
|
615
615
|
<Typed.CheckBoxField
|
|
616
616
|
name="${formikFieldName}"
|
|
617
617
|
label="${label}"
|
|
618
|
-
|
|
618
|
+
__e2e_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
|
|
619
619
|
/>
|
|
620
620
|
</div>
|
|
621
621
|
`);
|
|
@@ -640,8 +640,8 @@ function getFormFieldComponents({ model, selectorCollector, }) {
|
|
|
640
640
|
<Typed.${refMeta.react.components.forms.searchFieldName}
|
|
641
641
|
name="${formikFieldName}"
|
|
642
642
|
placeholder="Search..."
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
__e2e_options_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'options' })}"
|
|
644
|
+
__e2e_combobox_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
|
|
645
645
|
/>
|
|
646
646
|
</div>
|
|
647
647
|
`);
|
|
@@ -655,7 +655,7 @@ function getFormFieldComponents({ model, selectorCollector, }) {
|
|
|
655
655
|
<Typed.${enumMeta.react.selectFieldName}
|
|
656
656
|
name="${formikFieldName}"
|
|
657
657
|
placeholder="Search..."
|
|
658
|
-
|
|
658
|
+
__e2e_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
|
|
659
659
|
/>
|
|
660
660
|
</div>
|
|
661
661
|
`);
|
|
@@ -12,14 +12,14 @@ function generateRoute({ model, meta }) {
|
|
|
12
12
|
const defaultValueMethod = `
|
|
13
13
|
getDefault: procedure.query(({ ctx }) => ctx.view.${meta.data.dataServiceName}.${dataRepositoryVariableName}.defaultValue),
|
|
14
14
|
`;
|
|
15
|
-
const
|
|
15
|
+
const decoders = meta.businessLogic.update.decoders;
|
|
16
16
|
const imports = imports_1.ImportsGenerator.from(meta.trpc.routerFilePath).addImports({
|
|
17
17
|
[meta.types.importPath]: [
|
|
18
18
|
(0, types_1.toAnnotatedTypeName)(model.typeName),
|
|
19
19
|
meta.types.toBrandedIdTypeFnName,
|
|
20
20
|
meta.types.zodDecoderFnNames.id,
|
|
21
21
|
],
|
|
22
|
-
[meta.businessLogic.update.
|
|
22
|
+
[meta.businessLogic.update.importPath]: [decoders.name],
|
|
23
23
|
});
|
|
24
24
|
return /* ts */ `
|
|
25
25
|
import { z } from 'zod'
|
|
@@ -61,27 +61,27 @@ export const ${meta.trpc.routerName} = router({
|
|
|
61
61
|
}),
|
|
62
62
|
|
|
63
63
|
create: procedure
|
|
64
|
-
.input(${
|
|
64
|
+
.input(${decoders.name}.create)
|
|
65
65
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "create", payload: input})),
|
|
66
66
|
|
|
67
67
|
createMany: procedure
|
|
68
|
-
.input(z.array(${
|
|
68
|
+
.input(z.array(${decoders.name}.create))
|
|
69
69
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "createMany", payload: input})),
|
|
70
70
|
|
|
71
71
|
update: procedure
|
|
72
|
-
.input(${
|
|
72
|
+
.input(${decoders.name}.update)
|
|
73
73
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "update", payload: input})),
|
|
74
74
|
|
|
75
75
|
updateMany: procedure
|
|
76
|
-
.input(z.array(${
|
|
76
|
+
.input(z.array(${decoders.name}.update))
|
|
77
77
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "updateMany", payload: input})),
|
|
78
78
|
|
|
79
79
|
upsert: procedure
|
|
80
|
-
.input(${
|
|
80
|
+
.input(${decoders.name}.upsert)
|
|
81
81
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "upsert", payload: input})),
|
|
82
82
|
|
|
83
83
|
upsertMany: procedure
|
|
84
|
-
.input(z.array(${
|
|
84
|
+
.input(z.array(${decoders.name}.upsert))
|
|
85
85
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "upsertMany", payload: input})),
|
|
86
86
|
|
|
87
87
|
delete: procedure
|
|
@@ -93,7 +93,7 @@ export const ${meta.trpc.routerName} = router({
|
|
|
93
93
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "deleteMany", payload: input})),
|
|
94
94
|
|
|
95
95
|
clone: procedure
|
|
96
|
-
.input(${
|
|
96
|
+
.input(${decoders.name}.clone)
|
|
97
97
|
.mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "clone", payload: input})),
|
|
98
98
|
})
|
|
99
99
|
`;
|
package/dist/lib/attributes.d.ts
CHANGED
|
@@ -90,6 +90,11 @@ export type FieldAttributes = {
|
|
|
90
90
|
* The field that should be used as the default label for the model.
|
|
91
91
|
*/
|
|
92
92
|
isLabel?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Schema tag: `@@CloneWithParent()`
|
|
95
|
+
* If set, the all models that reference the parent that was cloned should also be cloned.
|
|
96
|
+
*/
|
|
97
|
+
cloneWithParent?: boolean;
|
|
93
98
|
};
|
|
94
99
|
export type EnumAttributes = {
|
|
95
100
|
/**
|
package/dist/lib/meta.d.ts
CHANGED
|
@@ -568,6 +568,18 @@ export type SchemaMetaData = {
|
|
|
568
568
|
* Path to the file containing the overall action types
|
|
569
569
|
*/
|
|
570
570
|
actionTypesFilePath: Types.FilePath;
|
|
571
|
+
/**
|
|
572
|
+
* Path to the file containing the clone context
|
|
573
|
+
*/
|
|
574
|
+
cloneContextFilePath: Types.FilePath;
|
|
575
|
+
/**
|
|
576
|
+
* The name of the clone context type
|
|
577
|
+
*/
|
|
578
|
+
cloneContextType: Types.TypeName;
|
|
579
|
+
/**
|
|
580
|
+
* The name of the function that creates a clone context
|
|
581
|
+
*/
|
|
582
|
+
cloneContextCreateMethod: Types.FunctionName;
|
|
571
583
|
};
|
|
572
584
|
};
|
|
573
585
|
seedData: {
|
|
@@ -788,11 +800,11 @@ export type ModelMetaData = {
|
|
|
788
800
|
*/
|
|
789
801
|
className: Types.ClassName;
|
|
790
802
|
/**
|
|
791
|
-
* The name of the function that decodes a source (database) object to a fully typed object
|
|
803
|
+
* The name of the function that decodes a source (database) object to a fully typed object (e.g. `toAggregation`.)
|
|
792
804
|
*/
|
|
793
805
|
decoderFnName: Types.FunctionName;
|
|
794
806
|
/**
|
|
795
|
-
* The name of the method that should be used to get objects from the database
|
|
807
|
+
* The name of the method that should be used to get objects from the database (e.g. `aggregations`.)
|
|
796
808
|
*/
|
|
797
809
|
getMethodFnName: Types.FunctionName;
|
|
798
810
|
};
|
|
@@ -834,7 +846,7 @@ export type ModelMetaData = {
|
|
|
834
846
|
*/
|
|
835
847
|
exportDataTypeName: Types.TypeName;
|
|
836
848
|
/**
|
|
837
|
-
* Name of the function that adds an instance of this model to the exporter
|
|
849
|
+
* Name of the function that adds an instance of this model to the exporter (e.g. `addAggregation`.)
|
|
838
850
|
*/
|
|
839
851
|
exportAddFunctionName: Types.FunctionName;
|
|
840
852
|
/**
|
|
@@ -858,23 +870,23 @@ export type ModelMetaData = {
|
|
|
858
870
|
*/
|
|
859
871
|
filePath: Types.FilePath;
|
|
860
872
|
/**
|
|
861
|
-
* Name of the type that represents the model in Excel import/export
|
|
873
|
+
* Name of the type that represents the model in Excel import/export (e.g. `Aggregation_EncodedExcelData`.)
|
|
862
874
|
*/
|
|
863
875
|
encodedExcelType: Types.TypeName;
|
|
864
876
|
/**
|
|
865
|
-
* Name of the decoder function that represents the Excel table import type
|
|
877
|
+
* Name of the decoder function that represents the Excel table import type (e.g. `aggregationExcelTableDecoder`.)
|
|
866
878
|
*/
|
|
867
879
|
tableDecoder: Types.FunctionName;
|
|
868
880
|
/**
|
|
869
|
-
* Name of the function that converts a model item to the Excel format
|
|
881
|
+
* Name of the function that converts a model item to the Excel format (e.g. `encodeAggregation`.)
|
|
870
882
|
*/
|
|
871
883
|
itemEncoderFunctionName: Types.FunctionName;
|
|
872
884
|
/**
|
|
873
|
-
* Name of the function that converts an array of model items to the Excel format
|
|
885
|
+
* Name of the function that converts an array of model items to the Excel format (e.g. `encodeAggregations`.)
|
|
874
886
|
*/
|
|
875
887
|
arrayEncoderFunctionName: Types.FunctionName;
|
|
876
888
|
/**
|
|
877
|
-
* Name of the array in the decoded data containing all entities of this model
|
|
889
|
+
* Name of the array in the decoded data containing all entities of this model (e.g. `aggregations`.)
|
|
878
890
|
*/
|
|
879
891
|
decodedModelArrayName: Types.VariableName;
|
|
880
892
|
};
|
|
@@ -897,6 +909,10 @@ export type ModelMetaData = {
|
|
|
897
909
|
* The definitions for the view service of a model
|
|
898
910
|
*/
|
|
899
911
|
view: {
|
|
912
|
+
/**
|
|
913
|
+
* Path that may be used to import the types of this model.
|
|
914
|
+
*/
|
|
915
|
+
importPath: Types.BackendModulePath;
|
|
900
916
|
/**
|
|
901
917
|
* The name by which the model's view class is exposed in the viewService/context. (e.g. aggregations)
|
|
902
918
|
*/
|
|
@@ -918,6 +934,10 @@ export type ModelMetaData = {
|
|
|
918
934
|
* The definitions for the update service of a model
|
|
919
935
|
*/
|
|
920
936
|
update: {
|
|
937
|
+
/**
|
|
938
|
+
* Path that may be used to import the types of this model.
|
|
939
|
+
*/
|
|
940
|
+
importPath: Types.BackendModulePath;
|
|
921
941
|
/**
|
|
922
942
|
* The name by which the model's update class is exposed in the updateService/context. (e.g. AggregationUpdateService)
|
|
923
943
|
*/
|
|
@@ -942,22 +962,32 @@ export type ModelMetaData = {
|
|
|
942
962
|
* The name of the model used as a discriminant for the action execution. (e.g. `aggregation`)
|
|
943
963
|
*/
|
|
944
964
|
actionModelDiscriminantName: Types.VariableName;
|
|
965
|
+
decoders: {
|
|
966
|
+
/**
|
|
967
|
+
* The name of the variable that holds the decoders for the model. (e.g. `aggregationDecoders`)
|
|
968
|
+
*/
|
|
969
|
+
name: Types.VariableName;
|
|
970
|
+
/**
|
|
971
|
+
* The name of the function that decodes a Create object to a fully typed object (e.g. `aggregationCreateDecoder`.)
|
|
972
|
+
*/
|
|
973
|
+
create: Types.FunctionName;
|
|
974
|
+
/**
|
|
975
|
+
* The name of the function that decodes an Update object to a fully typed object (e.g. `aggregationUpdateDecoder`.)
|
|
976
|
+
*/
|
|
977
|
+
update: Types.FunctionName;
|
|
978
|
+
/**
|
|
979
|
+
* The name of the function that decodes an Upsert object to a fully typed object (e.g. `aggregationUpsertDecoder`.)
|
|
980
|
+
*/
|
|
981
|
+
upsert: Types.FunctionName;
|
|
982
|
+
/**
|
|
983
|
+
* The name of the function that clones an object (e.g. `aggregationCloneDecoder`.)
|
|
984
|
+
*/
|
|
985
|
+
clone: Types.FunctionName;
|
|
986
|
+
};
|
|
945
987
|
/**
|
|
946
|
-
* The name of the
|
|
947
|
-
*/
|
|
948
|
-
zodCreateObject: Types.FunctionName;
|
|
949
|
-
/**
|
|
950
|
-
* The name of the function that decodes an Update object to a fully typed object, e.g. `aggregationUpdateDecoder`.
|
|
951
|
-
*/
|
|
952
|
-
zodUpdateObject: Types.FunctionName;
|
|
953
|
-
/**
|
|
954
|
-
* The name of the function that decodes an Upsert object to a fully typed object, e.g. `aggregationUpsertDecoder`.
|
|
955
|
-
*/
|
|
956
|
-
zodUpsertObject: Types.FunctionName;
|
|
957
|
-
/**
|
|
958
|
-
* The name of the function that clones an object, e.g. `aggregationCloneDecoder`.
|
|
988
|
+
* The name of the map variable in the clone context that holds the cloned items of this model (e.g. `aggregation`)
|
|
959
989
|
*/
|
|
960
|
-
|
|
990
|
+
cloneContextMap: Types.VariableName;
|
|
961
991
|
};
|
|
962
992
|
/**
|
|
963
993
|
* Name by which the business logic service exposes the data service.
|
|
@@ -1113,7 +1143,7 @@ export type ModelMetaData = {
|
|
|
1113
1143
|
*/
|
|
1114
1144
|
importPath: Types.BackendModulePath;
|
|
1115
1145
|
/**
|
|
1116
|
-
* The name of the type that represents a branded ID
|
|
1146
|
+
* The name of the type that represents a branded ID (e.g. `AggregationId`.)
|
|
1117
1147
|
*/
|
|
1118
1148
|
brandedIdType: Types.TypeName;
|
|
1119
1149
|
/**
|
|
@@ -1123,11 +1153,11 @@ export type ModelMetaData = {
|
|
|
1123
1153
|
toBrandedIdTypeFnName: Types.FunctionName;
|
|
1124
1154
|
zodDecoderFnNames: {
|
|
1125
1155
|
/**
|
|
1126
|
-
* The name of the function that decodes a scalar value to a branded ID type
|
|
1156
|
+
* The name of the function that decodes a scalar value to a branded ID type (e.g. `aggregationIdDecoder`.)
|
|
1127
1157
|
*/
|
|
1128
1158
|
id: Types.FunctionName;
|
|
1129
1159
|
/**
|
|
1130
|
-
* The name of the function that decodes a source (database) object to a fully typed object
|
|
1160
|
+
* The name of the function that decodes a source (database) object to a fully typed object (e.g. `aggregationDatabaseDecoder`.)
|
|
1131
1161
|
*/
|
|
1132
1162
|
fromDatabase: Types.FunctionName;
|
|
1133
1163
|
};
|
|
@@ -1136,15 +1166,15 @@ export type ModelMetaData = {
|
|
|
1136
1166
|
*/
|
|
1137
1167
|
dto: {
|
|
1138
1168
|
/**
|
|
1139
|
-
* The name of the type that represents a DTO for creating a new object
|
|
1169
|
+
* The name of the type that represents a DTO for creating a new object (e.g. `AggregationCreateDTO`.)
|
|
1140
1170
|
*/
|
|
1141
1171
|
create: Types.TypeName;
|
|
1142
1172
|
/**
|
|
1143
|
-
* The name of the type that represents a DTO for updating an existing object
|
|
1173
|
+
* The name of the type that represents a DTO for updating an existing object (e.g. `AggregationUpdateDTO`.)
|
|
1144
1174
|
*/
|
|
1145
1175
|
update: Types.TypeName;
|
|
1146
1176
|
/**
|
|
1147
|
-
* The name of the type that represents a DTO for upserting an existing object
|
|
1177
|
+
* The name of the type that represents a DTO for upserting an existing object (e.g. `AggregationUpsertDTO`.)
|
|
1148
1178
|
*/
|
|
1149
1179
|
upsert: Types.TypeName;
|
|
1150
1180
|
};
|
|
@@ -1153,17 +1183,17 @@ export type ModelMetaData = {
|
|
|
1153
1183
|
*/
|
|
1154
1184
|
typeDefFileName: Types.FileName;
|
|
1155
1185
|
/**
|
|
1156
|
-
* The name of the type that represents a fully typed, flat object
|
|
1186
|
+
* The name of the type that represents a fully typed, flat object (e.g. `Aggregation`.)
|
|
1157
1187
|
* This type only refers to related types by their branded ID.
|
|
1158
1188
|
*/
|
|
1159
1189
|
typeName: Types.TypeName;
|
|
1160
1190
|
/**
|
|
1161
|
-
* The name of the type that represents a fully typed object
|
|
1191
|
+
* The name of the type that represents a fully typed object (e.g. `AggregationFull`.)
|
|
1162
1192
|
* This type refers to relations by linking to the (flat) types.
|
|
1163
1193
|
*/
|
|
1164
1194
|
linkedTypeName: Types.TypeName;
|
|
1165
1195
|
/**
|
|
1166
|
-
* The name of the type that represents a source (i.e. database) object
|
|
1196
|
+
* The name of the type that represents a source (i.e. database) object (e.g. `Aggregation`.)
|
|
1167
1197
|
*/
|
|
1168
1198
|
sourceType: Types.TypeName;
|
|
1169
1199
|
};
|
|
@@ -1180,15 +1210,15 @@ export type FieldMetaData = {
|
|
|
1180
1210
|
*/
|
|
1181
1211
|
tsFieldName: Types.VariableName;
|
|
1182
1212
|
/**
|
|
1183
|
-
* The name of the method that should be used to get all child objects for a given item
|
|
1213
|
+
* The name of the method that should be used to get all child objects for a given item (e.g. `getItemsForAggregation`.)
|
|
1184
1214
|
*/
|
|
1185
1215
|
getByForeignKeyMethodFnName: Types.FunctionName;
|
|
1186
1216
|
/**
|
|
1187
|
-
* The name of the method that should be used to get all child Ids for a given item
|
|
1217
|
+
* The name of the method that should be used to get all child Ids for a given item (e.g. `getIdsForAggregation`.)
|
|
1188
1218
|
*/
|
|
1189
1219
|
getByForeignKeyIdsMethodFnName: Types.FunctionName;
|
|
1190
1220
|
/**
|
|
1191
|
-
* The name of the column in the seed Excel table
|
|
1221
|
+
* The name of the column in the seed Excel table (e.g. `Aggregation`.)
|
|
1192
1222
|
*/
|
|
1193
1223
|
excelColumnName: string;
|
|
1194
1224
|
};
|
|
@@ -1229,7 +1259,7 @@ export type EnumMetaData = {
|
|
|
1229
1259
|
*/
|
|
1230
1260
|
membersMap: Types.VariableName;
|
|
1231
1261
|
/**
|
|
1232
|
-
* Relative path to the file that contains the enum's types
|
|
1262
|
+
* Relative path to the file that contains the enum's types (e.g. `./aggregation.type`.)
|
|
1233
1263
|
*/
|
|
1234
1264
|
filePath: Types.FilePath;
|
|
1235
1265
|
/**
|
package/dist/lib/meta.js
CHANGED
|
@@ -119,6 +119,9 @@ function getSchemaMetadata({ config }) {
|
|
|
119
119
|
serviceClassName: Types.toClassName(`UpdateService`),
|
|
120
120
|
serviceFilePath: Types.toPath(`${config.paths.businessLogicPath}update/update.service`),
|
|
121
121
|
actionTypesFilePath: Types.toPath(`${config.paths.businessLogicPath}update/actions`),
|
|
122
|
+
cloneContextFilePath: Types.toPath(`${config.paths.businessLogicPath}update/clone.context`),
|
|
123
|
+
cloneContextType: Types.toTypeName(`CloneContext`),
|
|
124
|
+
cloneContextCreateMethod: Types.toFunctionName(`createCloneContext`),
|
|
122
125
|
},
|
|
123
126
|
},
|
|
124
127
|
data: {
|
|
@@ -151,11 +154,11 @@ function getSchemaMetadata({ config }) {
|
|
|
151
154
|
},
|
|
152
155
|
e2e: {
|
|
153
156
|
dataMocker: {
|
|
154
|
-
filePath: Types.toPath(`${config.paths.
|
|
155
|
-
stubImportPath: Types.toPath(`${config.paths.
|
|
156
|
-
stubIndexFilePath: Types.toPath(`${config.paths.
|
|
157
|
+
filePath: Types.toPath(`${config.paths.playwrightPath}support/data-mocker.class`),
|
|
158
|
+
stubImportPath: Types.toPath(`${config.paths.playwrightPath}support/stubs`),
|
|
159
|
+
stubIndexFilePath: Types.toPath(`${config.paths.playwrightPath}support/stubs/index`),
|
|
157
160
|
},
|
|
158
|
-
selectorsFilePath: Types.toPath(`${config.paths.
|
|
161
|
+
selectorsFilePath: Types.toPath(`${config.paths.playwrightPath}support/selectors`),
|
|
159
162
|
},
|
|
160
163
|
importExport: {
|
|
161
164
|
importPath: Types.toBackendModulePath(`@backend/import-export`),
|
|
@@ -308,7 +311,7 @@ function getModelMetadata({ model }) {
|
|
|
308
311
|
dataServiceIdName: Types.toVariableName(`${uncapitalized}`),
|
|
309
312
|
},
|
|
310
313
|
e2e: {
|
|
311
|
-
dataMockerStubFilePath: Types.toPath(`${config.paths.
|
|
314
|
+
dataMockerStubFilePath: Types.toPath(`${config.paths.playwrightPath}support/stubs/${camelCase}.stub`),
|
|
312
315
|
},
|
|
313
316
|
importExport: {
|
|
314
317
|
exportDataPropertyName: Types.toVariableName(`${capitalizedPlural}`),
|
|
@@ -331,22 +334,28 @@ function getModelMetadata({ model }) {
|
|
|
331
334
|
scopeName: Types.toVariableName(`${camelCase}`),
|
|
332
335
|
importPath: Types.toBackendModulePath(`@backend/business-logic`),
|
|
333
336
|
view: {
|
|
337
|
+
importPath: Types.toBackendModulePath(`@backend/business-logic/view`),
|
|
334
338
|
serviceClassName: Types.toClassName(`${PascalCase}ViewService`),
|
|
335
339
|
serviceVariableName: Types.toVariableName(`${uncapitalizedPlural}`),
|
|
336
340
|
serviceFileName: Types.toFileName(`${camelCase}.view.service`),
|
|
337
341
|
serviceFilePath: Types.toPath(`${config.paths.businessLogicPath}view/${camelCase}.view.service`),
|
|
338
342
|
},
|
|
339
343
|
update: {
|
|
344
|
+
importPath: Types.toBackendModulePath(`@backend/business-logic/update`),
|
|
340
345
|
serviceClassName: Types.toClassName(`${PascalCase}UpdateService`),
|
|
341
346
|
serviceInterfaceName: Types.toTypeName(`I${PascalCase}UpdateService`),
|
|
342
347
|
serviceVariableName: Types.toVariableName(`${uncapitalizedPlural}`),
|
|
343
348
|
serviceFileName: Types.toFileName(`${camelCase}.update.service`),
|
|
344
349
|
serviceFilePath: Types.toPath(`${config.paths.businessLogicPath}update/${camelCase}.update.service`),
|
|
345
350
|
actionModelDiscriminantName: Types.toVariableName(`${camelCase}`),
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
351
|
+
decoders: {
|
|
352
|
+
name: Types.toVariableName(`${camelCase}Decoders`),
|
|
353
|
+
create: Types.toFunctionName(`${camelCase}CreateDecoder`),
|
|
354
|
+
update: Types.toFunctionName(`${camelCase}UpdateDecoder`),
|
|
355
|
+
upsert: Types.toFunctionName(`${camelCase}UpsertDecoder`),
|
|
356
|
+
clone: Types.toFunctionName(`${camelCase}CloneDecoder`),
|
|
357
|
+
},
|
|
358
|
+
cloneContextMap: Types.toVariableName(`${camelCase}`),
|
|
350
359
|
},
|
|
351
360
|
dataRepositoryVariableName: Types.toVariableName(`data`),
|
|
352
361
|
},
|
|
@@ -23,9 +23,9 @@ export type SchemaConfig = {
|
|
|
23
23
|
*/
|
|
24
24
|
businessLogicPath: Types.FilePath;
|
|
25
25
|
/**
|
|
26
|
-
* Path to the directory containing
|
|
26
|
+
* Path to the directory containing Playwright project.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
playwrightPath: Types.FilePath;
|
|
29
29
|
/**
|
|
30
30
|
* Path to the directory containing e2e tests.
|
|
31
31
|
*/
|
|
@@ -144,9 +144,10 @@ function getFieldAttributes(field) {
|
|
|
144
144
|
.or(zod_1.default.string().transform((s) => parseInt(s, 10)))
|
|
145
145
|
.optional(),
|
|
146
146
|
readonly: blankStringBooleanDecoder,
|
|
147
|
+
cloneWithParent: blankStringBooleanDecoder,
|
|
147
148
|
})
|
|
148
149
|
.transform((obj) => {
|
|
149
|
-
var _a;
|
|
150
|
+
var _a, _b;
|
|
150
151
|
if (isPrismaIgnored && !obj.ignore) {
|
|
151
152
|
throw new Error(`Field ${field.name} is ignored by Prisma, but is missing the "ignore" PostXL attribute!`);
|
|
152
153
|
}
|
|
@@ -160,6 +161,7 @@ function getFieldAttributes(field) {
|
|
|
160
161
|
isReadonly: obj.readonly || field.isGenerated || field.isUpdatedAt || field.name === 'createdAt' || field.isId,
|
|
161
162
|
isUpdatedAt: (_a = field.isUpdatedAt) !== null && _a !== void 0 ? _a : false,
|
|
162
163
|
isCreatedAt: field.name === 'createdAt',
|
|
164
|
+
cloneWithParent: (_b = obj.cloneWithParent) !== null && _b !== void 0 ? _b : false,
|
|
163
165
|
};
|
|
164
166
|
});
|
|
165
167
|
const result = decoder.safeParse(attributes);
|