@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 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
- pathToCypress: zod_1.z.string().optional(),
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 Cypress tests.
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 Cypress tests.
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
- ids.map((id) => {
26
- addCypressSelectorToNestedObject({ object, id });
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" __cypress_selector__="indexPage-buttons-create" onClick={() => setIsCreateModalOpen(true)}/>
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
- createType: `Create${meta.internalSingularNameCapitalized}`,
45
- updateType: `Update${meta.internalSingularNameCapitalized}`,
46
- upsertType: `Upsert${meta.internalSingularNameCapitalized}`,
47
- cloneType: `Clone${meta.internalSingularNameCapitalized}`,
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 { zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject } = meta.businessLogic.update;
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 ${zodCreateObject} = z.object({
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 ${zodCreateObject}>
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 ${zodUpdateObject} = z.object({
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 ${zodUpdateObject}>
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 ${zodUpsertObject} = z.union([${zodUpdateObject}, ${zodCreateObject}])
194
+ export const ${decoders.upsert} = z.union([${decoders.update}, ${decoders.create}])
183
195
 
184
- type ${m.upsertType} = z.infer<typeof ${zodUpsertObject}>
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 ${zodCloneObject} = z.object({
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 ${zodCloneObject}>
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 }: { data: ${m.cloneType}; execution: ${m.iExecution} }
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
- __cypress_action_selector__: "${selectorCollector.idFor('edit', { typePrefix: 'actions' })}",
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
- __cypress_action_selector__: "${selectorCollector.idFor('delete', { typePrefix: 'actions' })}",
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
- __cypress_field_selector__={
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
- __cypress_field_selector__={
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
- __cypress_options_selector__={
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
- __cypress_options_selector__={
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
- __cypress_combobox_selector__={
130
- delegated.__cypress_combobox_selector__
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
- __cypress_combobox_selector__={
151
- delegated.__cypress_combobox_selector__
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
- __cypress_input_field_selector__={
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
- __cypress_input_field_selector__={
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
- __cypress_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
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
- __cypress_selector__="${selectorCollector.idFor('submit', { typePrefix: 'buttons' })}"
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
- __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
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
- __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
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
- __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
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
- __cypress_options_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'options' })}"
644
- __cypress_combobox_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
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
- __cypress_field_selector__="${selectorCollector.idFor(field.name, { typePrefix: 'fields' })}"
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 { zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject } = meta.businessLogic.update;
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.serviceFilePath]: [zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject],
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(${zodCreateObject})
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(${zodCreateObject}))
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(${zodUpdateObject})
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(${zodUpdateObject}))
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(${zodUpsertObject})
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(${zodUpsertObject}))
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(${zodCloneObject})
96
+ .input(${decoders.name}.clone)
97
97
  .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "clone", payload: input})),
98
98
  })
99
99
  `;
@@ -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
  /**
@@ -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, e.g. `toAggregation`.
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, e.g. `aggregations`.
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, e.g. `addAggregation`.
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, e.g. `Aggregation_EncodedExcelData`.
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, e.g. `aggregationExcelTableDecoder`.
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, e.g. `encodeAggregation`.
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, e.g. `encodeAggregations`.
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, e.g. `aggregations`.
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 function that decodes a Create object to a fully typed object, e.g. `aggregationCreateDecoder`.
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
- zodCloneObject: Types.FunctionName;
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, e.g. `AggregationId`.
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, e.g. `aggregationIdDecoder`.
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, e.g. `aggregationDatabaseDecoder`.
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, e.g. `AggregationCreateDTO`.
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, e.g. `AggregationUpdateDTO`.
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, e.g. `AggregationUpsertDTO`.
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, e.g. `Aggregation`.
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, e.g. `AggregationFull`.
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, e.g. `Aggregation`.
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, e.g. `getItemsForAggregation`.
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, e.g. `getIdsForAggregation`.
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, e.g. `Aggregation`.
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, e.g. `./aggregation.type`.
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.cypressPath}support/data-mocker.class`),
155
- stubImportPath: Types.toPath(`${config.paths.cypressPath}support/stubs`),
156
- stubIndexFilePath: Types.toPath(`${config.paths.cypressPath}support/stubs/index`),
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.cypressPath}support/selectors`),
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.cypressPath}support/stubs/${camelCase}.stub`),
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
- zodCreateObject: Types.toFunctionName(`${camelCase}CreateDecoder`),
347
- zodUpdateObject: Types.toFunctionName(`${camelCase}UpdateDecoder`),
348
- zodUpsertObject: Types.toFunctionName(`${camelCase}UpsertDecoder`),
349
- zodCloneObject: Types.toFunctionName(`${camelCase}CloneDecoder`),
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 Cypress project.
26
+ * Path to the directory containing Playwright project.
27
27
  */
28
- cypressPath: Types.FilePath;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {