@postxl/generator 0.56.2 → 0.56.4

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
@@ -60,6 +60,7 @@ const importexport_exporter_class_generator_1 = require("./generators/indices/im
60
60
  const importexport_import_service_generator_1 = require("./generators/indices/importexport-import-service.generator");
61
61
  const importexport_types_generator_1 = require("./generators/indices/importexport-types.generator");
62
62
  const repositories_generator_1 = require("./generators/indices/repositories.generator");
63
+ const routes_index_generator_1 = require("./generators/indices/routes-index.generator");
63
64
  const seed_migration_generator_1 = require("./generators/indices/seed-migration.generator");
64
65
  const seed_template_generator_1 = require("./generators/indices/seed-template.generator");
65
66
  const selectors_generator_1 = require("./generators/indices/selectors.generator");
@@ -234,7 +235,7 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
234
235
  generated.write(`/${meta.seedData.initialMigrationFilePath}.ts`, (0, seed_migration_generator_1.generateSeedMigration)({ models, meta }));
235
236
  generated.write(`/${meta.seedData.templateExcelFilePath}`, yield (0, seed_template_generator_1.generateSeedExcelTemplate)({ models }));
236
237
  // Routes
237
- generated.write(`/${meta.trpc.routesFilePath}.ts`, (0, route_generator_1.generateRoutesIndex)({ models, meta }));
238
+ generated.write(`/${meta.trpc.routesFilePath}.ts`, (0, routes_index_generator_1.generateRoutesIndex)({ models, meta }));
238
239
  // Types
239
240
  generated.write(`/${meta.types.indexFilePath}.ts`, (0, types_generator_2.generateTypesIndex)({ models, enums, meta }));
240
241
  // -------------------------------------------------------------------------
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateBusinessLogicActionTypes = void 0;
4
4
  const imports_1 = require("../../lib/imports");
5
5
  const meta_1 = require("../../lib/meta");
6
- const types_1 = require("../../lib/schema/types");
7
6
  const file_1 = require("../../lib/utils/file");
8
7
  /**
9
8
  * Generates the action types for the BusinessLogicModule.
@@ -11,7 +10,7 @@ const file_1 = require("../../lib/utils/file");
11
10
  function generateBusinessLogicActionTypes({ models, meta }) {
12
11
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.actionTypesFilePath).addImport({
13
12
  from: meta.actions.importPath,
14
- items: [(0, types_1.toTypeName)('ActionDefPayload'), (0, types_1.toTypeName)('ActionDefResult')],
13
+ items: [meta.actions.definition.payload, meta.actions.definition.resultDict],
15
14
  });
16
15
  const updateServiceImports = [];
17
16
  const modelNameTypeElements = [];
@@ -27,8 +26,8 @@ function generateBusinessLogicActionTypes({ models, meta }) {
27
26
  to: modelMeta.businessLogic.update.serviceFilePath,
28
27
  });
29
28
  updateServiceImports.push(`import type * as ${className} from '${relativeImportPath}'`);
30
- actionsTypeElements.push(`ActionDefPayload<${className}.Scope, ${className}.Actions>`);
31
- actionResultTypeElements.push(`${scope}: ActionDefResult<${className}.Actions>`);
29
+ actionsTypeElements.push(`${meta.actions.definition.payload}<${className}.Scope, ${className}.Actions>`);
30
+ actionResultTypeElements.push(`${scope}: ${meta.actions.definition.resultDict}<${className}.Actions>`);
32
31
  }
33
32
  return /* ts */ `
34
33
  ${imports.generate()}
@@ -36,7 +36,7 @@ function generateDataMockModule({ models, meta }) {
36
36
  [meta.data.dataModuleFilePath]: [Types.toVariableName('DataModule')],
37
37
  [meta.data.dataService.filePath]: [meta.data.dataService.class],
38
38
  // we need to import the file directly instead via the normal index file as this would cause a circular dependency else
39
- [meta.actions.importPath]: [meta.actions.actionExecution.mock],
39
+ [meta.actions.importPath]: [meta.actions.execution.mock],
40
40
  });
41
41
  for (const { model, meta } of mm) {
42
42
  imports.addTypeImport({ items: [model.typeName], from: meta.types.importPath });
@@ -29,7 +29,7 @@ function generateDataService({ models, meta }) {
29
29
  [meta.types.importPath]: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.create), (0, types_1.toAnnotatedTypeName)(meta.types.dto.update)],
30
30
  [meta.data.repository.typeFilePath]: [(0, types_1.toAnnotatedTypeName)(meta.data.repository.typeName)],
31
31
  [meta.data.types.filePath]: [(0, types_1.toAnnotatedTypeName)(meta.data.types.bulkMutation)],
32
- [meta.actions.importPath]: [meta.actions.actionExecution.interface],
32
+ [meta.actions.importPath]: [meta.actions.execution.interface],
33
33
  });
34
34
  const creates = [];
35
35
  const updates = [];
@@ -77,7 +77,7 @@ export class ${meta.data.dataService.class} {
77
77
  }
78
78
  public async ${meta.data.dataService.executeBulkMutations}(
79
79
  { steps, execution }:
80
- { steps: ${meta.data.types.bulkMutation}[]; execution: ${meta.actions.actionExecution.interface} }
80
+ { steps: ${meta.data.types.bulkMutation}[]; execution: ${meta.actions.execution.interface} }
81
81
  ) {
82
82
  let index = 0
83
83
  for (const step of steps) {
@@ -88,7 +88,7 @@ export class ${meta.data.dataService.class} {
88
88
 
89
89
  public async ${meta.data.dataService.executeBulkMutation}(
90
90
  { data, execution }:
91
- { data: ${meta.data.types.bulkMutation}, execution: ${meta.actions.actionExecution.interface}}
91
+ { data: ${meta.data.types.bulkMutation}, execution: ${meta.actions.execution.interface}}
92
92
  ) {
93
93
  // NOTE: the order of these calls is important, because of foreign key constraints
94
94
  // The current order is based on the order of the models in the schema
@@ -114,7 +114,7 @@ export class ${meta.data.dataService.class} {
114
114
  name: string
115
115
  data: ${meta.types.dto.create}<T, ID>[] | undefined
116
116
  repo: ${meta.data.repository.typeName}<T, ID>
117
- execution: ${meta.actions.actionExecution.interface}
117
+ execution: ${meta.actions.execution.interface}
118
118
  }): Promise<void> {
119
119
  if (!data) {
120
120
  return
@@ -146,7 +146,7 @@ export class ${meta.data.dataService.class} {
146
146
  name: string
147
147
  data: ${meta.types.dto.update}<T, ID>[] | undefined
148
148
  repo: ${meta.data.repository.typeName}<T, ID>
149
- execution: ${meta.actions.actionExecution.interface}
149
+ execution: ${meta.actions.execution.interface}
150
150
  }): Promise<void> {
151
151
  if (!data) {
152
152
  return
@@ -178,7 +178,7 @@ export class ${meta.data.dataService.class} {
178
178
  name: string
179
179
  data: (${meta.types.dto.create}<T, ID> | ${meta.types.dto.update}<T, ID>)[] | undefined
180
180
  repo: ${meta.data.repository.typeName}<T, ID>
181
- execution: ${meta.actions.actionExecution.interface}
181
+ execution: ${meta.actions.execution.interface}
182
182
  }): Promise<void> {
183
183
  if (!data) {
184
184
  return
@@ -210,7 +210,7 @@ export class ${meta.data.dataService.class} {
210
210
  name: string
211
211
  data: ID[] | undefined
212
212
  repo: ${meta.data.repository.typeName}<T, ID>
213
- execution: ${meta.actions.actionExecution.interface}
213
+ execution: ${meta.actions.execution.interface}
214
214
  }): Promise<void> {
215
215
  if (!data) {
216
216
  return
@@ -54,7 +54,7 @@ export class ${meta.actions.dispatcher.class} {
54
54
  const execution = await this.actionExecutionFactory.create({ action, dbService: this.dbService, user })
55
55
 
56
56
  try {
57
- const result = await (this.execute({ action, execution }) as Promise<ResultOfAction<A>>)
57
+ const result = await this.execute({ action, execution })
58
58
 
59
59
  await execution.success(result)
60
60
 
@@ -65,22 +65,35 @@ export class ${meta.actions.dispatcher.class} {
65
65
  }
66
66
  }
67
67
 
68
- private async execute({ action, execution }: { action: Action; execution: IActionExecution }) {
68
+ private async execute<A extends Action>({
69
+ action,
70
+ execution,
71
+ }: {
72
+ action: A
73
+ execution: IActionExecution
74
+ }): Promise<ResultOfAction<A>> {
69
75
  switch (action.scope) {
70
76
  ${models
71
77
  .map((model) => {
72
78
  const modelMeta = (0, meta_1.getModelMetadata)({ model });
73
79
  return `
74
- case '${modelMeta.businessLogic.scopeName}':
75
- return this.updateService.${modelMeta.businessLogic.update.serviceVariableName}.dispatch({ action, execution })
80
+ case '${modelMeta.businessLogic.scopeName}': {
81
+ const method = this.updateService.${modelMeta.businessLogic.update.serviceVariableName}[action.type] as (params: {
82
+ data: A['payload']
83
+ execution: IActionExecution
84
+ }) => Promise<ResultOfAction<A>>
85
+
86
+ return method({ data: action.payload, execution })
87
+ }
76
88
  `;
77
89
  })
78
90
  .join('\n')}
91
+
79
92
  case 'seed':
80
- return this.seedService.dispatch({ action, execution })
93
+ return this.seedService.dispatch({ action, execution }) as Promise<ResultOfAction<A>>
81
94
 
82
95
  case 'import':
83
- return this.importService.dispatch({ action, execution })
96
+ return this.importService.dispatch({ action, execution }) as Promise<ResultOfAction<A>>
84
97
 
85
98
  default:
86
99
  throw new ExhaustiveSwitchCheck(action)
@@ -9,7 +9,6 @@ const types_1 = require("../../lib/schema/types");
9
9
  */
10
10
  function generateImportExportExporterClass({ models, meta }) {
11
11
  const imports = imports_1.ImportsGenerator.from(meta.importExport.exporterClass.filePath);
12
- const modelsMap = new Map(models.map((model) => [model.name, model]));
13
12
  imports.addImports({
14
13
  [meta.businessLogic.view.importPath]: [meta.businessLogic.view.serviceClassName],
15
14
  [meta.importExport.decoder.fullDecoderFilePath]: [
@@ -50,27 +49,15 @@ function generateImportExportExporterClass({ models, meta }) {
50
49
  }
51
50
  }
52
51
  const childItemCalls = [];
53
- for (const relatedModelCore of model.relatedModels) {
54
- const relatedModel = modelsMap.get(relatedModelCore.name);
55
- // This should not happen as the related models are always present
56
- if (!relatedModel) {
57
- continue;
58
- }
59
- for (const field of relatedModel.fields) {
60
- if (field.kind !== 'relation') {
61
- continue;
62
- }
63
- if (field.relationToModel.name !== model.name) {
64
- continue;
65
- }
66
- const linkedModelMeta = (0, meta_1.getModelMetadata)({ model: relatedModel });
67
- const linkedFieldMeta = (0, meta_1.getFieldMetadata)({ field: field });
68
- childItemCalls.push(`
69
- // Add all ${linkedModelMeta.userFriendlyNamePlural} that are related to the ${modelMeta.userFriendlyName} via ${field.name}
70
- for (const ${field.name} of await this.viewService.${linkedModelMeta.businessLogic.view.serviceVariableName}.data.${linkedFieldMeta.getByForeignKeyIdsMethodFnName}(id)) {
71
- await this.${linkedModelMeta.importExport.exportAddFunctionName}({id: ${field.name}, includeChildren})
72
- }`);
73
- }
52
+ for (const { referencingField, referencingModel } of model.references) {
53
+ const linkedModelMeta = (0, meta_1.getModelMetadata)({ model: referencingModel });
54
+ const linkedFieldMeta = (0, meta_1.getFieldMetadata)({ field: referencingField });
55
+ childItemCalls.push(`
56
+ // Add all ${linkedModelMeta.userFriendlyNamePlural} that are related to the ${modelMeta.userFriendlyName} via ${referencingField.name}
57
+ for (const ${referencingField.name} of await this.viewService.${linkedModelMeta.businessLogic.view.serviceVariableName}.data.${linkedFieldMeta.getByForeignKeyIdsMethodFnName}(id)) {
58
+ await this.${linkedModelMeta.importExport.exportAddFunctionName}({id: ${referencingField.name}, includeChildren})
59
+ }
60
+ `);
74
61
  }
75
62
  const childItems = childItemCalls.length > 0
76
63
  ? `
@@ -22,7 +22,7 @@ function generateImportExportImportService({ models, meta }) {
22
22
  (0, types_1.toAnnotatedTypeName)(dto.idType),
23
23
  (0, types_1.toAnnotatedTypeName)(dto.update),
24
24
  ],
25
- [meta.actions.importPath]: [meta.actions.actionExecution.interface, meta.actions.dispatcher.class],
25
+ [meta.actions.importPath]: [meta.actions.execution.interface, meta.actions.dispatcher.class],
26
26
  [types.filePath]: [
27
27
  (0, types_1.toAnnotatedTypeName)(delta_Fields),
28
28
  (0, types_1.toAnnotatedTypeName)(delta_Model.type),
@@ -78,7 +78,7 @@ export class ${meta.importExport.importService.name} {
78
78
  execution
79
79
  }: {
80
80
  action: Action_Import;
81
- execution: ${meta.actions.actionExecution.interface}
81
+ execution: ${meta.actions.execution.interface}
82
82
  }) {
83
83
  switch (action.type) {
84
84
  case 'detect-delta':
@@ -110,7 +110,7 @@ export class ${meta.importExport.importService.name} {
110
110
  execution
111
111
  }: {
112
112
  delta: Delta;
113
- execution: ${meta.actions.actionExecution.interface}
113
+ execution: ${meta.actions.execution.interface}
114
114
  }) {
115
115
  const bulkMutations = ${converterFunctions.deltaToBulkMutations}(delta)
116
116
  return this.data.${meta.data.dataService.executeBulkMutations}({ steps: bulkMutations, execution })
@@ -121,7 +121,7 @@ export class ${meta.importExport.importService.name} {
121
121
  execution
122
122
  }: {
123
123
  data: ${meta.importExport.decoder.decodedPXLModelDataTypeName};
124
- execution: ${meta.actions.actionExecution.interface}
124
+ execution: ${meta.actions.execution.interface}
125
125
  }) {
126
126
  const { bulkMutations } = await this.detectDelta(data)
127
127
  return this.data.${meta.data.dataService.executeBulkMutations}({ steps: bulkMutations, execution })
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates the index file for all the routes.
5
+ */
6
+ export declare function generateRoutesIndex({ models, meta }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateRoutesIndex = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ /**
7
+ * Generates the index file for all the routes.
8
+ */
9
+ function generateRoutesIndex({ models, meta }) {
10
+ const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
11
+ const imports = imports_1.ImportsGenerator.from(meta.trpc.routesFilePath);
12
+ for (const { meta } of mm) {
13
+ imports.addImport({ items: [meta.trpc.routerName], from: meta.trpc.routerFilePath });
14
+ }
15
+ return /* ts */ `
16
+ ${imports.generate()}
17
+
18
+ /**
19
+ * Object with all generated routes.
20
+ */
21
+ export const routes = {
22
+ ${mm.map(({ meta }) => `${meta.trpc.routerName}`).join(',\n')}
23
+ }
24
+ `;
25
+ }
26
+ exports.generateRoutesIndex = generateRoutesIndex;
@@ -13,25 +13,48 @@ const jsdoc_1 = require("../../lib/utils/jsdoc");
13
13
  */
14
14
  function generateModelBusinessLogicUpdate({ model, meta }) {
15
15
  const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
16
+ /**
17
+ * Shorthand variable for relevant metadata.
18
+ */
19
+ const m = {
20
+ /**
21
+ * The name of the interface that handles the action execution.
22
+ */
23
+ iExecution: schemaMeta.actions.execution.interface,
24
+ /**
25
+ * The name of the type that represents a fully typed, flat object, e.g. `Aggregation`
26
+ */
27
+ typeName: meta.types.typeName,
28
+ /**
29
+ * Type of the ID field that is specific to this model, e.g. `AggregationId`
30
+ */
31
+ brandedId: model.brandedIdType,
32
+ /**
33
+ * Internal type name for create payload, e.g. `CreateAggregation`
34
+ */
35
+ createType: `Create${meta.internalSingularNameCapitalized}`,
36
+ /**
37
+ * Internal type name for update payload, e.g. `UpdateAggregation`
38
+ */
39
+ updateType: `Update${meta.internalSingularNameCapitalized}`,
40
+ /**
41
+ * Internal type name for upsert payload, e.g. `UpsertAggregation`
42
+ */
43
+ upsertType: `Upsert${meta.internalSingularNameCapitalized}`,
44
+ };
16
45
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.serviceFilePath);
17
46
  imports.addImports({
18
47
  [meta.data.importPath]: meta.data.repository.className,
19
48
  [meta.types.importPath]: [
20
- (0, types_1.toAnnotatedTypeName)(model.brandedIdType),
21
- (0, types_1.toAnnotatedTypeName)(meta.types.typeName),
49
+ (0, types_1.toAnnotatedTypeName)(m.brandedId),
50
+ (0, types_1.toAnnotatedTypeName)(m.typeName),
22
51
  meta.types.toBrandedIdTypeFnName,
23
52
  ],
24
53
  [meta.businessLogic.view.serviceFilePath]: [meta.businessLogic.view.serviceClassName],
25
- [schemaMeta.actions.importPath]: [
26
- schemaMeta.actions.actionExecution.interface,
27
- schemaMeta.actions.dispatcher.result,
28
- schemaMeta.actions.dispatcher.interface,
29
- schemaMeta.actions.dispatcher.actionMethod,
30
- ],
54
+ [schemaMeta.actions.importPath]: [schemaMeta.actions.execution.interface, schemaMeta.actions.dispatcher.definition],
31
55
  [schemaMeta.businessLogic.update.serviceFilePath]: schemaMeta.businessLogic.update.serviceClassName,
32
56
  [schemaMeta.businessLogic.view.serviceFilePath]: schemaMeta.businessLogic.view.serviceClassName,
33
57
  });
34
- const { dispatcher } = schemaMeta.actions;
35
58
  for (const relation of (0, fields_1.getRelationFields)(model)) {
36
59
  // NOTE: We add branded id type and type name imports only for foreign models.
37
60
  if (relation.relationToModel.typeName === model.typeName) {
@@ -40,7 +63,7 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
40
63
  const refMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
41
64
  imports.addImport({
42
65
  items: [refMeta.types.toBrandedIdTypeFnName],
43
- from: refMeta.types.filePath,
66
+ from: refMeta.types.importPath,
44
67
  });
45
68
  }
46
69
  /**
@@ -50,27 +73,70 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
50
73
  */
51
74
  const modelRepositoryVariableName = meta.businessLogic.dataRepositoryVariableName;
52
75
  const constructorParameters = [
53
- `public readonly ${modelRepositoryVariableName}: ${meta.data.repository.className}`,
76
+ `private readonly ${modelRepositoryVariableName}: ${meta.data.repository.className}`,
54
77
  `@Inject(forwardRef(() => ${schemaMeta.businessLogic.update.serviceClassName})) private readonly updateService: ${schemaMeta.businessLogic.update.serviceClassName}`,
55
78
  `@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly viewService: ${schemaMeta.businessLogic.view.serviceClassName}`,
56
79
  ];
57
80
  const { zodCreateObject, zodUpdateObject, zodUpsertObject } = meta.businessLogic.update;
58
- /**
59
- * A return value of the dispatcher.
60
- *
61
- * NOTE: We need to cast to the return value every time so that the function correctly determines the return type.
62
- */
63
- const dispatcherReturnValue = `Promise<${schemaMeta.actions.dispatcher.result}<Actions, AM>>`;
81
+ const { view, update } = meta.businessLogic;
82
+ /* prettier-ignore */
64
83
  return /* ts */ `
65
84
  import { Inject, Injectable, forwardRef } from '@nestjs/common'
66
- import { ExhaustiveSwitchCheck, UnionOmit } from '@backend/common'
67
85
  import { z } from 'zod'
68
86
 
69
87
  ${imports.generate()}
70
88
 
71
89
  export type Scope = "${meta.actions.actionScopeConstType}"
72
90
 
73
-
91
+ export type Actions = {
92
+ ${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} and returns it.`])}
93
+ create: {
94
+ payload: ${m.createType}
95
+ result: ${m.typeName}
96
+ }
97
+
98
+ ${(0, jsdoc_1.toJsDocComment)([`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`])}
99
+ createMany: {
100
+ payload: ${m.createType}[]
101
+ result: ${m.typeName}[]
102
+ }
103
+
104
+ ${(0, jsdoc_1.toJsDocComment)([`Updates a ${meta.userFriendlyName} and returns it.`])}
105
+ update: {
106
+ payload: ${m.updateType}
107
+ result: ${m.typeName}
108
+ }
109
+
110
+ ${(0, jsdoc_1.toJsDocComment)([`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
111
+ updateMany: {
112
+ payload: ${m.updateType}[]
113
+ result: ${m.typeName}[]
114
+ }
115
+
116
+ ${(0, jsdoc_1.toJsDocComment)([`Creates or updates a ${meta.userFriendlyName} and returns it.`])}
117
+ upsert: {
118
+ payload: ${m.upsertType}
119
+ result: ${m.typeName}
120
+ }
121
+
122
+ ${(0, jsdoc_1.toJsDocComment)([`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
123
+ upsertMany: {
124
+ payload: ${m.upsertType}[]
125
+ result: ${m.typeName}[]
126
+ }
127
+
128
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes a ${meta.userFriendlyName} and returns its id.`])}
129
+ delete: {
130
+ payload: ${m.brandedId}
131
+ result: ${m.brandedId}
132
+ }
133
+
134
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`])}
135
+ deleteMany: {
136
+ payload: ${m.brandedId}[]
137
+ result: ${m.brandedId}[]
138
+ }
139
+ }
74
140
 
75
141
  /**
76
142
  * Zod decoder for validating the create input of a ${meta.userFriendlyName}.
@@ -82,6 +148,8 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
82
148
  .join(',')}
83
149
  })
84
150
 
151
+ type ${m.createType} = z.infer<typeof ${zodCreateObject}>
152
+
85
153
  /**
86
154
  * Zod decoder for validating the update input of a ${meta.userFriendlyName} .
87
155
  */
@@ -92,112 +160,68 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
92
160
  .join(',')}
93
161
  })
94
162
 
163
+ type ${m.updateType} = z.infer<typeof ${zodUpdateObject}>
164
+
95
165
  /**
96
166
  * Zod decoder for validating the upsert input of a ${meta.userFriendlyName} .
97
167
  */
98
168
  export const ${zodUpsertObject} = z.union([${zodUpdateObject}, ${zodCreateObject}])
169
+
170
+ type ${m.upsertType} = z.infer<typeof ${zodUpsertObject}>
171
+
172
+ export type ${update.serviceInterfaceName} = ${schemaMeta.actions.dispatcher.definition}<Actions>
173
+
174
+ @Injectable()
175
+ export class ${update.serviceClassName} implements ${update.serviceInterfaceName} {
176
+
177
+ /**
178
+ * Instance of the ${meta.userFriendlyName} view service for convenience.
179
+ */
180
+ private view: ${view.serviceClassName}
181
+
182
+ constructor(${constructorParameters.join(',\n')}) {
183
+ this.view = this.viewService.${view.serviceVariableName}
184
+ }
99
185
 
100
- export type Actions = {
101
186
  ${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} and returns it.`])}
102
- create: {
103
- payload: z.infer<typeof ${zodCreateObject}>
104
- result: ${meta.types.typeName}
187
+ public async create({ data, execution }: { data: ${m.createType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
188
+ return this.data.create({ item: data, execution })
105
189
  }
106
190
 
107
191
  ${(0, jsdoc_1.toJsDocComment)([`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`])}
108
- createMany: {
109
- payload: z.infer<typeof ${zodCreateObject}>[]
110
- result: ${meta.types.typeName}[]
192
+ public async createMany({ data, execution }: { data: ${m.createType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
193
+ return this.data.createMany({ items: data, execution })
111
194
  }
112
195
 
113
196
  ${(0, jsdoc_1.toJsDocComment)([`Updates a ${meta.userFriendlyName} and returns it.`])}
114
- update: {
115
- payload: z.infer<typeof ${zodUpdateObject}>
116
- result: ${meta.types.typeName}
197
+ public async update({ data, execution }: { data: ${m.updateType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
198
+ return this.data.update({ item: data, execution })
117
199
  }
118
200
 
119
201
  ${(0, jsdoc_1.toJsDocComment)([`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
120
- updateMany: {
121
- payload: z.infer<typeof ${zodUpdateObject}>[]
122
- result: ${meta.types.typeName}[]
202
+ public async updateMany({ data, execution }: { data: ${m.updateType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
203
+ return this.data.updateMany({ items: data, execution })
123
204
  }
124
205
 
125
206
  ${(0, jsdoc_1.toJsDocComment)([`Creates or updates a ${meta.userFriendlyName} and returns it.`])}
126
- upsert: {
127
- payload: z.infer<typeof ${zodUpsertObject}>
128
- result: ${meta.types.typeName}
207
+ public async upsert({ data, execution }: { data: ${m.upsertType}; execution: ${m.iExecution} }): Promise<${m.typeName}> {
208
+ return this.data.upsert({ item: data, execution })
129
209
  }
130
210
 
131
211
  ${(0, jsdoc_1.toJsDocComment)([`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
132
- upsertMany: {
133
- payload: z.infer<typeof ${zodUpsertObject}>[]
134
- result: ${meta.types.typeName}[]
212
+ public async upsertMany({ data, execution }: { data: ${m.upsertType}[]; execution: ${m.iExecution} }): Promise<${m.typeName}[]> {
213
+ return this.data.upsertMany({ items: data, execution })
135
214
  }
136
-
215
+
137
216
  ${(0, jsdoc_1.toJsDocComment)([`Deletes a ${meta.userFriendlyName} and returns its id.`])}
138
- delete: {
139
- payload: ${model.brandedIdType}
140
- result: ${model.brandedIdType}
217
+ public async delete({ data, execution }: { data: ${m.brandedId}; execution: ${m.iExecution} }): Promise<${m.brandedId}> {
218
+ return this.data.delete({ id: data, execution })
141
219
  }
142
220
 
143
221
  ${(0, jsdoc_1.toJsDocComment)([`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`])}
144
- deleteMany: {
145
- payload: ${model.brandedIdType}[]
146
- result: ${model.brandedIdType}[]
147
- }
148
- }
149
-
150
- @Injectable()
151
- export class ${meta.businessLogic.update.serviceClassName} implements ${dispatcher.interface}<Scope, Actions> {
152
- /**
153
- * Instance of the ${meta.userFriendlyName} view service for convenience.
154
- */
155
- private view: ${meta.businessLogic.view.serviceClassName}
156
-
157
- constructor(${constructorParameters.join(',\n')}) {
158
- this.view = this.viewService.${meta.businessLogic.view.serviceVariableName}
159
- }
160
-
161
- /**
162
- * Dispatches an action to the appropriate method of the repository.
163
- */
164
- public async dispatch<AM extends ${dispatcher.actionMethod}<Scope, Actions>>({
165
- action,
166
- execution,
167
- }: {
168
- action: UnionOmit<AM, 'result'>
169
- execution: ${schemaMeta.actions.actionExecution.interface}
170
- }): ${dispatcherReturnValue} {
171
- switch (action.type) {
172
- case 'create':
173
- return this.data.create({ item: action.payload, execution }) as ${dispatcherReturnValue}
174
-
175
- case 'createMany':
176
- return this.data.createMany({ items: action.payload, execution }) as ${dispatcherReturnValue}
177
-
178
- case 'update':
179
- return this.data.update({ item: action.payload, execution }) as ${dispatcherReturnValue}
180
-
181
- case 'updateMany':
182
- return this.data.updateMany({ items: action.payload, execution }) as ${dispatcherReturnValue}
183
-
184
- case 'upsert':
185
- return this.data.upsert({ item: action.payload, execution }) as ${dispatcherReturnValue}
186
-
187
- case 'upsertMany':
188
- return this.data.upsertMany({ items: action.payload, execution }) as ${dispatcherReturnValue}
189
-
190
- case 'delete':
191
- return this.data.delete({ id: action.payload, execution }) as ${dispatcherReturnValue}
192
-
193
- case 'deleteMany':
194
- return this.data.deleteMany({ ids: action.payload, execution }) as ${dispatcherReturnValue}
195
-
196
- default:
197
- throw new ExhaustiveSwitchCheck(action)
198
-
199
- }
200
- }
222
+ public async deleteMany({ data, execution }: { data: ${m.brandedId}[]; execution: ${m.iExecution} }): Promise<${m.brandedId}[]> {
223
+ return this.data.deleteMany({ ids: data, execution })
224
+ }
201
225
  }
202
226
  `;
203
227
  }
@@ -59,8 +59,8 @@ function generateModelBusinessLogicView({ model, meta }) {
59
59
  variableDefinition: `const ${relationVariableName} = ${relationVariableDefinition}`,
60
60
  });
61
61
  if (relation.relationToModel.typeName !== model.typeName) {
62
- imports.addImport({ from: refMeta.types.filePath, items: [refMeta.types.toBrandedIdTypeFnName] });
63
- imports.addTypeImport({ from: refMeta.types.filePath, items: [refModel.brandedIdType, refMeta.types.typeName] });
62
+ imports.addImport({ from: refMeta.types.importPath, items: [refMeta.types.toBrandedIdTypeFnName] });
63
+ imports.addTypeImport({ from: refMeta.types.importPath, items: [refModel.brandedIdType, refMeta.types.typeName] });
64
64
  }
65
65
  }
66
66
  const hasLinkedItems = variables.size > 0;
@@ -68,7 +68,7 @@ function generateModelBusinessLogicView({ model, meta }) {
68
68
  // NOTE: If we need to generate the linked item type, we need to import the enum types.
69
69
  for (const enumField of (0, fields_1.getEnumFields)(model)) {
70
70
  const enumMeta = (0, meta_1.getEnumMetadata)(enumField);
71
- imports.addTypeImport({ from: enumMeta.types.filePath, items: [enumField.typeName] });
71
+ imports.addTypeImport({ from: enumMeta.types.importPath, items: [enumField.typeName] });
72
72
  }
73
73
  }
74
74
  const linkedItemsGetterFn = `
@@ -25,7 +25,7 @@ function generateRepository({ model, meta }) {
25
25
  (0, types_1.toAnnotatedTypeName)(meta.types.dto.update),
26
26
  (0, types_1.toAnnotatedTypeName)(meta.types.dto.upsert),
27
27
  ],
28
- [schemaMeta.actions.importPath]: [schemaMeta.actions.actionExecution.interface],
28
+ [schemaMeta.actions.importPath]: [schemaMeta.actions.execution.interface],
29
29
  });
30
30
  // NOTE: We first generate different parts of the code responsible for a particular task
31
31
  // and then we combine them into the final code block.
@@ -990,42 +990,42 @@ function getRepositoryMethodsTypeSignatures({ model, meta }) {
990
990
  return {
991
991
  create: {
992
992
  jsDoc: [`Creates a new ${meta.userFriendlyName} and returns it.`],
993
- parameters: [`{item: ${meta.types.dto.create}, execution: ${schemaMeta.actions.actionExecution.interface}}`],
993
+ parameters: [`{item: ${meta.types.dto.create}, execution: ${schemaMeta.actions.execution.interface}}`],
994
994
  returnType: `Promise<${model.typeName}>`,
995
995
  },
996
996
  createMany: {
997
997
  jsDoc: [`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`],
998
- parameters: [`{items: ${meta.types.dto.create}[], execution: ${schemaMeta.actions.actionExecution.interface}}`],
998
+ parameters: [`{items: ${meta.types.dto.create}[], execution: ${schemaMeta.actions.execution.interface}}`],
999
999
  returnType: `Promise<${model.typeName}[]>`,
1000
1000
  },
1001
1001
  update: {
1002
1002
  jsDoc: [`Updates a ${meta.userFriendlyName} and returns it.`],
1003
- parameters: [`{item: ${meta.types.dto.update}, execution: ${schemaMeta.actions.actionExecution.interface}}`],
1003
+ parameters: [`{item: ${meta.types.dto.update}, execution: ${schemaMeta.actions.execution.interface}}`],
1004
1004
  returnType: `Promise<${model.typeName}>`,
1005
1005
  },
1006
1006
  updateMany: {
1007
1007
  jsDoc: [`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`],
1008
- parameters: [`{items: ${meta.types.dto.update}[], execution: ${schemaMeta.actions.actionExecution.interface}}`],
1008
+ parameters: [`{items: ${meta.types.dto.update}[], execution: ${schemaMeta.actions.execution.interface}}`],
1009
1009
  returnType: `Promise<${model.typeName}[]>`,
1010
1010
  },
1011
1011
  upsert: {
1012
1012
  jsDoc: [`Creates or updates a ${meta.userFriendlyName} and returns it.`],
1013
- parameters: [`{item: ${meta.types.dto.upsert}, execution: ${schemaMeta.actions.actionExecution.interface}}`],
1013
+ parameters: [`{item: ${meta.types.dto.upsert}, execution: ${schemaMeta.actions.execution.interface}}`],
1014
1014
  returnType: `Promise<${model.typeName}>`,
1015
1015
  },
1016
1016
  upsertMany: {
1017
1017
  jsDoc: [`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`],
1018
- parameters: [`{items: ${meta.types.dto.upsert}[], execution: ${schemaMeta.actions.actionExecution.interface}}`],
1018
+ parameters: [`{items: ${meta.types.dto.upsert}[], execution: ${schemaMeta.actions.execution.interface}}`],
1019
1019
  returnType: `Promise<${model.typeName}[]>`,
1020
1020
  },
1021
1021
  delete: {
1022
1022
  jsDoc: [`Deletes a ${meta.userFriendlyName} and returns its id.`],
1023
- parameters: [`{id: ${model.brandedIdType}, execution: ${schemaMeta.actions.actionExecution.interface}}`],
1023
+ parameters: [`{id: ${model.brandedIdType}, execution: ${schemaMeta.actions.execution.interface}}`],
1024
1024
  returnType: `Promise<${model.brandedIdType}>`,
1025
1025
  },
1026
1026
  deleteMany: {
1027
1027
  jsDoc: [`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`],
1028
- parameters: [`{ids: ${model.brandedIdType}[], execution: ${schemaMeta.actions.actionExecution.interface}}`],
1028
+ parameters: [`{ids: ${model.brandedIdType}[], execution: ${schemaMeta.actions.execution.interface}}`],
1029
1029
  returnType: `Promise<${model.brandedIdType}[]>`,
1030
1030
  },
1031
1031
  };
@@ -1,4 +1,4 @@
1
- import { ModelMetaData, SchemaMetaData } from '../../lib/meta';
1
+ import { ModelMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
4
  * Generates TRPC route for a given model.
@@ -7,10 +7,3 @@ export declare function generateRoute({ model, meta }: {
7
7
  model: Model;
8
8
  meta: ModelMetaData;
9
9
  }): string;
10
- /**
11
- * Generates the index file for all the routes.
12
- */
13
- export declare function generateRoutesIndex({ models, meta }: {
14
- models: Model[];
15
- meta: SchemaMetaData;
16
- }): string;
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateRoutesIndex = exports.generateRoute = void 0;
3
+ exports.generateRoute = void 0;
4
4
  const imports_1 = require("../../lib/imports");
5
- const meta_1 = require("../../lib/meta");
6
5
  const types_1 = require("../../lib/schema/types");
7
6
  /**
8
7
  * Generates TRPC route for a given model.
@@ -96,24 +95,3 @@ export const ${meta.trpc.routerName} = router({
96
95
  `;
97
96
  }
98
97
  exports.generateRoute = generateRoute;
99
- /**
100
- * Generates the index file for all the routes.
101
- */
102
- function generateRoutesIndex({ models, meta }) {
103
- const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
104
- const imports = imports_1.ImportsGenerator.from(meta.trpc.routesFilePath);
105
- for (const { meta } of mm) {
106
- imports.addImport({ items: [meta.trpc.routerName], from: meta.trpc.routerFilePath });
107
- }
108
- return /* ts */ `
109
- ${imports.generate()}
110
-
111
- /**
112
- * Object with all generated routes.
113
- */
114
- export const routes = {
115
- ${mm.map(({ meta }) => `${meta.trpc.routerName}`).join(',\n')}
116
- }
117
- `;
118
- }
119
- exports.generateRoutesIndex = generateRoutesIndex;
@@ -93,7 +93,7 @@ export type SchemaMetaData = {
93
93
  * Path that may be used to import from the action module.
94
94
  */
95
95
  importPath: Types.BackendModulePath;
96
- actionExecution: {
96
+ execution: {
97
97
  /**
98
98
  * The name of the interface that handles the action execution.
99
99
  */
@@ -117,17 +117,27 @@ export type SchemaMetaData = {
117
117
  */
118
118
  class: Types.ClassName;
119
119
  /**
120
- * The name of the interface that indicates a service can dispatch actions.
120
+ * The definition type that may be used to create a dispatcher conforming class.
121
121
  */
122
- interface: Types.ClassName;
122
+ definition: Types.TypeName;
123
+ };
124
+ definition: {
125
+ /**
126
+ * The schema type to define action definitions.
127
+ */
128
+ schema: Types.TypeName;
123
129
  /**
124
- * The name of a util type that is used to create a dictionary of action payloads and results from the action descriptions.
130
+ * The generated payload of the defined actions.
125
131
  */
126
- actionMethod: Types.TypeName;
132
+ payload: Types.TypeName;
127
133
  /**
128
- * The name of the type that represents the result of the action method.
134
+ * The generated result of the defined actions.
129
135
  */
130
136
  result: Types.TypeName;
137
+ /**
138
+ * The transformer type that converts action definitions into results dictionary.
139
+ */
140
+ resultDict: Types.TypeName;
131
141
  };
132
142
  };
133
143
  /**
@@ -909,9 +919,13 @@ export type ModelMetaData = {
909
919
  */
910
920
  update: {
911
921
  /**
912
- * The name by which the model's update class is exposed in the updateService/context. (e.g. aggregations)
922
+ * The name by which the model's update class is exposed in the updateService/context. (e.g. AggregationUpdateService)
913
923
  */
914
924
  serviceClassName: Types.ClassName;
925
+ /**
926
+ * The name of the interface that represents the update service for this model. (e.g. IAggregationUpdateService)
927
+ */
928
+ serviceInterfaceName: Types.TypeName;
915
929
  /**
916
930
  * The name by which the model's service is exposed in the updateService/context. (e.g. aggregations)
917
931
  */
package/dist/lib/meta.js CHANGED
@@ -81,7 +81,7 @@ function getSchemaMetadata({ config }) {
81
81
  actions: {
82
82
  moduleName: Types.toClassName(`ActionModule`),
83
83
  importPath: Types.toBackendModulePath(`@backend/actions`),
84
- actionExecution: {
84
+ execution: {
85
85
  interface: Types.toClassName(`IActionExecution`),
86
86
  class: Types.toClassName(`ActionExecution`),
87
87
  mock: Types.toClassName(`MockActionExecution`),
@@ -89,9 +89,13 @@ function getSchemaMetadata({ config }) {
89
89
  dispatcher: {
90
90
  filePath: Types.toPath(`${config.paths.actionsPath}dispatcher.service`),
91
91
  class: Types.toClassName(`DispatcherService`),
92
- interface: Types.toClassName(`IDispatcher`),
93
- actionMethod: Types.toTypeName(`ActionMethod`),
94
- result: Types.toTypeName(`ActionMethodResult`),
92
+ definition: Types.toTypeName('IDispatcherDefinition'),
93
+ },
94
+ definition: {
95
+ schema: Types.toTypeName('ActionDef'),
96
+ payload: Types.toTypeName('ActionDefPayload'),
97
+ result: Types.toTypeName('ActionDefResult'),
98
+ resultDict: Types.toTypeName('ActionDefResultDict'),
95
99
  },
96
100
  },
97
101
  businessLogic: {
@@ -334,6 +338,7 @@ function getModelMetadata({ model }) {
334
338
  },
335
339
  update: {
336
340
  serviceClassName: Types.toClassName(`${PascalCase}UpdateService`),
341
+ serviceInterfaceName: Types.toTypeName(`I${PascalCase}UpdateService`),
337
342
  serviceVariableName: Types.toVariableName(`${uncapitalizedPlural}`),
338
343
  serviceFileName: Types.toFileName(`${camelCase}.update.service`),
339
344
  serviceFilePath: Types.toPath(`${config.paths.businessLogicPath}update/${camelCase}.update.service`),
@@ -173,7 +173,16 @@ export type ModelFields = {
173
173
  /**
174
174
  * A list of models that reference this model in their relations.
175
175
  */
176
- relatedModels: ModelCore[];
176
+ references: {
177
+ /**
178
+ * The name of the field in the referencing model that references this model.
179
+ */
180
+ referencingField: FieldRelation;
181
+ /**
182
+ * The model that references this model.
183
+ */
184
+ referencingModel: ModelCore;
185
+ }[];
177
186
  };
178
187
  /**
179
188
  * A field of a model.
@@ -41,9 +41,27 @@ function parsePrismaSchema({ datamodel: { enums: enumsRaw, models: modelsRaw },
41
41
  // NOTE: We preprocess models and enums so that we can populate relationships.
42
42
  const models = modelsRaw.map((dmmfModel) => parseModelCore({ dmmfModel, config }));
43
43
  const enums = enumsRaw.map((dmmfEnum) => parseEnum({ dmmfEnum, config }));
44
+ // NOTE: Then we parse the fields of the models.
44
45
  const modelsWithFields = modelsRaw
45
46
  .map((dmmfModel) => parseModel({ dmmfModel, models, enums, config }))
46
47
  .filter(isModelNotIgnored);
48
+ // NOTE: Lastly we link back-relations to models.
49
+ for (const mwf of modelsWithFields) {
50
+ for (const field of mwf.fields) {
51
+ if (field.kind !== 'relation') {
52
+ continue;
53
+ }
54
+ const referencedModel = modelsWithFields.find((m) => m.sourceName === field.relationToModel.sourceName);
55
+ if (!referencedModel) {
56
+ // NOTE: This should never happen because Prisma should validate the validity of the relation.
57
+ (0, error_1.throwError)(`Model ${highlight(mwf.name)} not found!`);
58
+ }
59
+ referencedModel.references.push({
60
+ referencingModel: mwf,
61
+ referencingField: field,
62
+ });
63
+ }
64
+ }
47
65
  return { models: modelsWithFields, enums };
48
66
  }
49
67
  exports.parsePrismaSchema = parsePrismaSchema;
@@ -144,10 +162,6 @@ function parseModel({ dmmfModel, enums, models, config, }) {
144
162
  // we need to preprocess relations and then figure out which scalar
145
163
  // fields are actually foreign-keys.
146
164
  const referencedModels = {};
147
- /**
148
- * A map of models that are referenced in any way in the relations.
149
- */
150
- const relatedModels = {};
151
165
  for (const dmmfField of dmmfModel.fields) {
152
166
  if (dmmfField.kind !== 'object' || !dmmfField.relationName) {
153
167
  continue;
@@ -158,7 +172,6 @@ function parseModel({ dmmfModel, enums, models, config, }) {
158
172
  }
159
173
  if (!dmmfField.relationFromFields || dmmfField.relationFromFields.length === 0) {
160
174
  // NOTE: This field has no foreign-key values in this model so it must be a back-relation.
161
- relatedModels[dmmfField.type] = referencedModel;
162
175
  continue;
163
176
  }
164
177
  if (dmmfField.relationFromFields.length > 1) {
@@ -172,11 +185,10 @@ function parseModel({ dmmfModel, enums, models, config, }) {
172
185
  referencedModels[dmmfField.relationFromFields[0]] = referencedModel;
173
186
  }
174
187
  const relationFields = dmmfModel.fields
175
- .filter((f) => { var _a; return f.kind === 'object' && ((_a = f.relationToFields) === null || _a === void 0 ? void 0 : _a.length) === 1; })
188
+ .filter((f) => { var _a, _b; return f.kind === 'object' && ((_a = f.relationToFields) === null || _a === void 0 ? void 0 : _a.length) === 1 && ((_b = f.relationFromFields) === null || _b === void 0 ? void 0 : _b.length) === 1; })
176
189
  .reduce((acc, f) => {
177
- if (f.relationFromFields && f.relationFromFields[0]) {
178
- acc[f.relationFromFields[0]] = f;
179
- }
190
+ // NOTE: It's safe to assume that `relationFromFields` and `relationToFields` are defined because of the filter above.
191
+ acc[f.relationFromFields[0]] = f;
180
192
  return acc;
181
193
  }, {});
182
194
  const fields = dmmfModel.fields
@@ -246,7 +258,9 @@ function parseModel({ dmmfModel, enums, models, config, }) {
246
258
  nameField,
247
259
  fields,
248
260
  createdAtField,
249
- updatedAtField, relatedModels: Object.values(relatedModels) });
261
+ updatedAtField,
262
+ // NOTE: This is only a stub because we link references after all models were parsed above.
263
+ references: [] });
250
264
  }
251
265
  /**
252
266
  * Checks that there is exactly one id field and that there is at most one default field.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.56.2",
3
+ "version": "0.56.4",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {