@postxl/generator 0.55.0 → 0.56.1

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
@@ -221,7 +221,7 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
221
221
  generated.write(`/${meta.importExport.decoder.fullDecoderFilePath}.ts`, (0, importexport_decoder_generator_1.generateImportExportDecoder)({ models, meta }));
222
222
  generated.write(`/${meta.importExport.converterFunctions.filePath}.ts`, (0, importexport_convert_import_functions_generator_1.generateImportExportConvertImportFunctions)({ models, meta }));
223
223
  // Actions
224
- generated.write(`/${meta.actions.dispatcherService.filePath}.ts`, (0, dispatcher_service_generator_1.generateActionsDispatcherService)({ models, meta }));
224
+ generated.write(`/${meta.actions.dispatcher.filePath}.ts`, (0, dispatcher_service_generator_1.generateActionsDispatcherService)({ models, meta }));
225
225
  // Business Logic
226
226
  generated.write(`/${meta.businessLogic.view.indexFilePath}.ts`, (0, businesslogic_view_index_generator_1.generateBusinessLogicViewIndex)({ models, meta }));
227
227
  generated.write(`/${meta.businessLogic.view.moduleFilePath}.ts`, (0, businesslogic_view_module_generator_1.generateBusinessLogicViewModule)({ models, meta }));
@@ -3,27 +3,38 @@ 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
+ const file_1 = require("../../lib/utils/file");
6
8
  /**
7
9
  * Generates the action types for the BusinessLogicModule.
8
10
  */
9
11
  function generateBusinessLogicActionTypes({ models, meta }) {
10
- const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.actionTypesFilePath);
12
+ const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.actionTypesFilePath).addImport({
13
+ from: meta.actions.importPath,
14
+ items: [(0, types_1.toTypeName)('ActionDefPayload'), (0, types_1.toTypeName)('ActionDefResult')],
15
+ });
16
+ const updateServiceImports = [];
11
17
  const modelNameTypeElements = [];
12
18
  const actionsTypeElements = [];
13
19
  const actionResultTypeElements = [];
14
20
  for (const model of models) {
15
21
  const modelMeta = (0, meta_1.getModelMetadata)({ model });
16
- imports.addImport({
17
- items: [modelMeta.businessLogic.update.actionName, modelMeta.businessLogic.update.actionResultName],
18
- from: modelMeta.businessLogic.update.serviceFilePath,
22
+ const className = modelMeta.businessLogic.update.serviceClassName;
23
+ const scope = modelMeta.businessLogic.update.actionModelDiscriminantName;
24
+ modelNameTypeElements.push(`'${scope}'`);
25
+ const relativeImportPath = (0, file_1.getRelativePath)({
26
+ from: meta.businessLogic.update.actionTypesFilePath,
27
+ to: modelMeta.businessLogic.update.serviceFilePath,
19
28
  });
20
- modelNameTypeElements.push(`'${modelMeta.businessLogic.update.actionModelDiscriminantName}'`);
21
- actionsTypeElements.push(modelMeta.businessLogic.update.actionName);
22
- actionResultTypeElements.push(`${modelMeta.businessLogic.update.actionModelDiscriminantName}: ${modelMeta.businessLogic.update.actionResultName}`);
29
+ updateServiceImports.push(`import type * as ${className} from '${relativeImportPath}'`);
30
+ actionsTypeElements.push(`ActionDefPayload<${className}.Scope, ${className}.Actions>`);
31
+ actionResultTypeElements.push(`${scope}: ActionDefResult<${className}.Actions>`);
23
32
  }
24
33
  return /* ts */ `
25
34
  ${imports.generate()}
26
35
 
36
+ ${updateServiceImports.join('\n')}
37
+
27
38
  /**
28
39
  * Used by any of the default actions (create, update, delete) to identify the model.
29
40
  */
@@ -13,7 +13,9 @@ function generateBusinessLogicUpdateIndex({ models, meta }) {
13
13
  exports.exportEverythingFromPath(meta.businessLogic.update.actionTypesFilePath);
14
14
  for (const model of models) {
15
15
  const meta = (0, meta_1.getModelMetadata)({ model });
16
- exports.exportEverythingFromPath(meta.businessLogic.update.serviceFilePath);
16
+ exports.exportSelectionFromPath(meta.businessLogic.update.serviceFilePath, [
17
+ meta.businessLogic.update.serviceClassName,
18
+ ]);
17
19
  }
18
20
  return exports.generate();
19
21
  }
@@ -8,7 +8,7 @@ const types_1 = require("../../lib/schema/types");
8
8
  * Generates the action dispatcher service.
9
9
  */
10
10
  function generateActionsDispatcherService({ models, meta }) {
11
- const imports = imports_1.ImportsGenerator.from(meta.actions.dispatcherService.filePath).addImports({
11
+ const imports = imports_1.ImportsGenerator.from(meta.actions.dispatcher.filePath).addImports({
12
12
  [meta.seed.importPath]: [meta.seed.serviceClassName],
13
13
  [meta.importExport.importPath]: [meta.importExport.importService.name],
14
14
  [meta.businessLogic.update.importPath]: [meta.businessLogic.update.serviceClassName],
@@ -32,7 +32,7 @@ import { Action, ResultOfAction } from './actions.types'
32
32
 
33
33
 
34
34
  @Injectable()
35
- export class ${meta.actions.dispatcherService.class} {
35
+ export class ${meta.actions.dispatcher.class} {
36
36
 
37
37
  // As SeedModule and ImportExportModule get instantiated after the ActionsModule, we use this hack to avoid a circular dependency:
38
38
  // we set the seedService and the importService to value and then overwrite it in the services' constructors.
@@ -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.dispatcherService.class],
25
+ [meta.actions.importPath]: [meta.actions.actionExecution.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),
@@ -61,7 +61,7 @@ type Delta_Result<Model extends ${dto.genericModel}<ID>, ID extends ${dto.idType
61
61
  export class ${meta.importExport.importService.name} {
62
62
  constructor(
63
63
  private readonly data: ${meta.data.dataService.class},
64
- private readonly dispatcher: ${meta.actions.dispatcherService.class},
64
+ private readonly dispatcher: ${meta.actions.dispatcher.class},
65
65
  ) {
66
66
  // We have a circular dependency between DispatcherService and ${meta.importExport.importService.name}.
67
67
  // In order to avoid trouble, instead of using Nest's forwardRef(), we inject the dispatcher service here
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateSeedMigration = 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");
6
7
  /**
7
8
  * Generates the initial migration based on the generated seed data.
8
9
  */
@@ -14,21 +15,22 @@ function generateSeedMigration({ models, meta }) {
14
15
  imports.addImports({
15
16
  [modelMeta.seed.filePath]: [modelMeta.seed.constantName],
16
17
  [meta.importExport.importPath]: [meta.importExport.converterFunctions.importedDataToBulkMutations],
18
+ [meta.seed.importPath]: [(0, types_1.toTypeName)('Action_Seed_Data')],
17
19
  });
18
20
  modelTypes.push(`${modelMeta.seed.constantName}`);
19
21
  }
20
22
  return /* ts */ `
21
- import { createActionSeedData } from '${meta.seed.importPath}'
22
23
  ${imports.generate()}
23
24
 
24
- export const MIGRATION_001_BASEDATA = createActionSeedData({
25
+ export const MIGRATION_001_BASEDATA: Action_Seed_Data = {
26
+ scope: 'seed',
27
+ type: 'data',
25
28
  name: 'Base data',
26
29
  order: 1,
27
- data: ${meta.importExport.converterFunctions.importedDataToBulkMutations}({
28
- ${modelTypes.join(',\n')}
29
- },
30
- ),
31
- })
30
+ payload: ${meta.importExport.converterFunctions.importedDataToBulkMutations}({
31
+ ${modelTypes.join(',\n')}
32
+ }),
33
+ }
32
34
  `;
33
35
  }
34
36
  exports.generateSeedMigration = generateSeedMigration;
@@ -16,7 +16,6 @@ function generateTestDataService({ models, meta: schemaMeta, }) {
16
16
  (0, types_1.toAnnotatedTypeName)(schemaMeta.data.dataMockDataType),
17
17
  ],
18
18
  [schemaMeta.importExport.importPath]: [schemaMeta.importExport.converterFunctions.mockDataToBulkMutations],
19
- [schemaMeta.seed.importPath]: [(0, types_1.toClassName)('createActionSeedData')],
20
19
  });
21
20
  const reInitCalls = [];
22
21
  const modelMetas = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
@@ -57,12 +56,19 @@ export class TestDataService {
57
56
  this.logger.log('✍ Setting test data')
58
57
 
59
58
  const mockData = ${schemaMeta.importExport.converterFunctions.mockDataToBulkMutations}(data)
60
- const action = createActionSeedData({ name: 'E2E', order: 0, data: [] })
59
+
61
60
  const actionExecution = await this.actionExecutionFactory.create({
62
- action,
61
+ action: {
62
+ scope: 'seed',
63
+ type: 'data',
64
+ name: 'E2E',
65
+ order: 0,
66
+ payload: []
67
+ },
63
68
  dbService: this.db,
64
69
  user: this.dataService.users.rootUser,
65
70
  })
71
+
66
72
  try {
67
73
  ${reInitCalls.join('\n')}
68
74
 
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateModelBusinessLogicUpdate = void 0;
4
4
  const imports_1 = require("../../lib/imports");
5
5
  const meta_1 = require("../../lib/meta");
6
+ const fields_1 = require("../../lib/schema/fields");
6
7
  const types_1 = require("../../lib/schema/types");
8
+ const zod_1 = require("../../lib/schema/zod");
7
9
  const jsdoc_1 = require("../../lib/utils/jsdoc");
8
- const repository_generator_1 = require("./repository.generator");
9
10
  /**
10
11
  * Generates update business logic for a given model.
11
12
  * The update logic handles all Create/Update/Delete/Upsert operations. See template's readme for more info.
@@ -15,239 +16,182 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
15
16
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.serviceFilePath);
16
17
  imports.addImports({
17
18
  [meta.data.importPath]: meta.data.repository.className,
18
- [schemaMeta.actions.importPath]: schemaMeta.actions.actionExecution.interface,
19
- [meta.types.importPath]: [(0, types_1.toAnnotatedTypeName)(model.brandedIdType), (0, types_1.toAnnotatedTypeName)(meta.types.typeName)],
19
+ [meta.types.importPath]: [
20
+ (0, types_1.toAnnotatedTypeName)(model.brandedIdType),
21
+ (0, types_1.toAnnotatedTypeName)(meta.types.typeName),
22
+ meta.types.toBrandedIdTypeFnName,
23
+ ],
20
24
  [meta.businessLogic.view.serviceFilePath]: [meta.businessLogic.view.serviceClassName],
25
+ [schemaMeta.actions.importPath]: [
26
+ schemaMeta.actions.actionExecution.interface,
27
+ schemaMeta.actions.dispatcher.interface,
28
+ schemaMeta.actions.dispatcher.actionMethod,
29
+ ],
21
30
  [schemaMeta.businessLogic.update.serviceFilePath]: schemaMeta.businessLogic.update.serviceClassName,
22
31
  [schemaMeta.businessLogic.view.serviceFilePath]: schemaMeta.businessLogic.view.serviceClassName,
23
- [meta.data.importPath]: [meta.data.repository.className],
24
32
  });
33
+ const { dispatcher } = schemaMeta.actions;
34
+ for (const relation of (0, fields_1.getRelationFields)(model)) {
35
+ // NOTE: We add branded id type and type name imports only for foreign models.
36
+ if (relation.relationToModel.typeName === model.typeName) {
37
+ continue;
38
+ }
39
+ const refMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
40
+ imports.addImport({
41
+ items: [refMeta.types.toBrandedIdTypeFnName],
42
+ from: refMeta.types.filePath,
43
+ });
44
+ }
25
45
  /**
26
46
  * The name of the variable that holds the repository instance for the current model
27
47
  * (e.g. when we generate business logic service for Aggregation, the AggregationRepository
28
48
  * would be referenced using `this.data` variable).
29
49
  */
30
50
  const modelRepositoryVariableName = meta.businessLogic.dataRepositoryVariableName;
31
- /**
32
- * The name of the variable that holds the central business logic service instance.
33
- * Instead of injecting a repository instance for each model, we inject this single instance
34
- * which then provides access to all models' business logic.
35
- */
36
- const updateServiceClassName = 'updateService';
37
- const viewServiceClassName = 'viewService';
38
- const actionBlocks = generateActionsBuildingBlocks({ model, meta });
39
- imports.addImport({ from: meta.types.importPath, items: actionBlocks.importTypes });
40
51
  const constructorParameters = [
41
52
  `public readonly ${modelRepositoryVariableName}: ${meta.data.repository.className}`,
42
- `@Inject(forwardRef(() => ${schemaMeta.businessLogic.update.serviceClassName})) private readonly ${updateServiceClassName}: ${schemaMeta.businessLogic.update.serviceClassName}`,
43
- `@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly ${viewServiceClassName}: ${schemaMeta.businessLogic.view.serviceClassName}`,
53
+ `@Inject(forwardRef(() => ${schemaMeta.businessLogic.update.serviceClassName})) private readonly updateService: ${schemaMeta.businessLogic.update.serviceClassName}`,
54
+ `@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly viewService: ${schemaMeta.businessLogic.view.serviceClassName}`,
44
55
  ];
45
- const methodTypeSignatures = (0, repository_generator_1.getRepositoryMethodsTypeSignatures)({ model, meta });
56
+ const { zodCreateObject, zodUpdateObject, zodUpsertObject } = meta.businessLogic.update;
46
57
  return /* ts */ `
47
- import { Inject, Injectable, forwardRef } from '@nestjs/common'
48
- import { ExhaustiveSwitchCheck } from '@backend/common'
49
-
50
- ${imports.generate()}
51
-
52
- ${actionBlocks.actionTypeDefinition}
53
-
54
- ${actionBlocks.resultMap}
55
-
56
- ${actionBlocks.typeDefinitionWithCreateFunction}
57
-
58
- @Injectable()
59
- export class ${meta.businessLogic.update.serviceClassName} {
60
-
61
- /**
62
- * Instance of the ${meta.userFriendlyName} view service for convenience.
63
- */
64
- private view: ${meta.businessLogic.view.serviceClassName}
65
-
66
- constructor(${constructorParameters.join(',\n')}) {
67
- this.view = this.${viewServiceClassName}.${meta.businessLogic.view.serviceVariableName}
68
- }
69
-
70
- ${actionBlocks.dispatcher}
71
-
72
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.create.jsDoc)}
73
- public async create(
74
- { item, execution }: ${methodTypeSignatures.create.parameters[0]}
75
- ): ${methodTypeSignatures.create.returnType} {
76
- return this.${modelRepositoryVariableName}.create({ item, execution })
77
- }
78
-
79
-
80
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.createMany.jsDoc)}
81
- public async createMany(
82
- { items, execution }: ${methodTypeSignatures.createMany.parameters[0]}
83
- ): ${methodTypeSignatures.createMany.returnType} {
84
- return this.${modelRepositoryVariableName}.createMany({ items, execution })
85
- }
86
-
87
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.update.jsDoc)}
88
- public async update(
89
- { item, execution }: ${methodTypeSignatures.update.parameters[0]}
90
- ): ${methodTypeSignatures.update.returnType} {
91
- return this.${modelRepositoryVariableName}.update({ item, execution })
92
- }
93
-
94
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.updateMany.jsDoc)}
95
- public async updateMany(
96
- { items, execution }: ${methodTypeSignatures.updateMany.parameters[0]}
97
- ): ${methodTypeSignatures.updateMany.returnType} {
98
- return this.${modelRepositoryVariableName}.updateMany({ items, execution })
99
- }
100
-
101
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsert.jsDoc)}
102
- public async upsert(
103
- { item, execution }: ${methodTypeSignatures.upsert.parameters[0]}
104
- ): ${methodTypeSignatures.upsert.returnType} {
105
- return this.${modelRepositoryVariableName}.upsert({ item, execution })
106
- }
107
-
108
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsertMany.jsDoc)}
109
- public async upsertMany(
110
- { items, execution }: ${methodTypeSignatures.upsertMany.parameters[0]}
111
- ): ${methodTypeSignatures.upsertMany.returnType} {
112
- return this.${modelRepositoryVariableName}.upsertMany({ items, execution })
113
- }
114
-
115
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.delete.jsDoc)}
116
- public async delete(
117
- { id, execution }: ${methodTypeSignatures.delete.parameters[0]}
118
- ): ${methodTypeSignatures.delete.returnType} {
119
- return this.${modelRepositoryVariableName}.delete({ id, execution })
120
- }
121
-
122
- ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.deleteMany.jsDoc)}
123
- public async deleteMany(
124
- { ids, execution }: ${methodTypeSignatures.deleteMany.parameters[0]}
125
- ): ${methodTypeSignatures.deleteMany.returnType} {
126
- return this.${modelRepositoryVariableName}.deleteMany({ ids, execution })
127
- }
128
-
129
- }`;
130
- }
131
- exports.generateModelBusinessLogicUpdate = generateModelBusinessLogicUpdate;
132
- function generateActionsBuildingBlocks({ model, meta }) {
133
- const actionTypeDefinition = [];
134
- const resultMap = [];
135
- const typeDefinitionWithCreateFunction = [];
136
- const importTypes = [];
137
- const dispatcher = [];
138
- const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
139
- const actionDefinitions = prepareActionDefinitions({ model, meta });
140
- for (const [type, config] of Object.entries(actionDefinitions)) {
141
- const def = generateAction({ model, meta, type: type, config });
142
- actionTypeDefinition.push(def.actionName),
143
- resultMap.push(def.resultMap),
144
- typeDefinitionWithCreateFunction.push(def.typeDefinition, def.createFunction, ''),
145
- importTypes.push(...def.importTypes),
146
- dispatcher.push(def.dispatchCaseExpression);
58
+ import { Inject, Injectable, forwardRef } from '@nestjs/common'
59
+ import { ExhaustiveSwitchCheck, UnionOmit } from '@backend/common'
60
+ import { z } from 'zod'
61
+
62
+ ${imports.generate()}
63
+
64
+ export type Scope = "${meta.actions.actionScopeConstType}"
65
+
66
+
67
+
68
+ /**
69
+ * Zod decoder for validating the create input of a ${meta.userFriendlyName}.
70
+ */
71
+ export const ${zodCreateObject} = z.object({
72
+ ${model.fields
73
+ .filter((f) => !f.attributes.isReadonly)
74
+ .map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f })}`)
75
+ .join(',')}
76
+ })
77
+
78
+ /**
79
+ * Zod decoder for validating the update input of a ${meta.userFriendlyName} .
80
+ */
81
+ export const ${zodUpdateObject} = z.object({
82
+ ${model.fields
83
+ .filter((f) => !f.attributes.isReadonly || f.kind === 'id')
84
+ .map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f, allowAnyOptionalField: f.kind !== 'id' })}`)
85
+ .join(',')}
86
+ })
87
+
88
+ /**
89
+ * Zod decoder for validating the upsert input of a ${meta.userFriendlyName} .
90
+ */
91
+ export const ${zodUpsertObject} = z.union([${zodUpdateObject}, ${zodCreateObject}])
92
+
93
+ export type Actions = {
94
+ ${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} and returns it.`])}
95
+ create: {
96
+ payload: z.infer<typeof ${zodCreateObject}>
97
+ result: ${meta.types.typeName}
98
+ }
99
+
100
+ ${(0, jsdoc_1.toJsDocComment)([`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`])}
101
+ createMany: {
102
+ payload: z.infer<typeof ${zodCreateObject}>[]
103
+ result: ${meta.types.typeName}[]
104
+ }
105
+
106
+ ${(0, jsdoc_1.toJsDocComment)([`Updates a ${meta.userFriendlyName} and returns it.`])}
107
+ update: {
108
+ payload: z.infer<typeof ${zodUpdateObject}>
109
+ result: ${meta.types.typeName}
110
+ }
111
+
112
+ ${(0, jsdoc_1.toJsDocComment)([`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
113
+ updateMany: {
114
+ payload: z.infer<typeof ${zodUpdateObject}>[]
115
+ result: ${meta.types.typeName}[]
116
+ }
117
+
118
+ ${(0, jsdoc_1.toJsDocComment)([`Creates or updates a ${meta.userFriendlyName} and returns it.`])}
119
+ upsert: {
120
+ payload: z.infer<typeof ${zodUpsertObject}>
121
+ result: ${meta.types.typeName}
122
+ }
123
+
124
+ ${(0, jsdoc_1.toJsDocComment)([`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`])}
125
+ upsertMany: {
126
+ payload: z.infer<typeof ${zodUpsertObject}>[]
127
+ result: ${meta.types.typeName}[]
128
+ }
129
+
130
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes a ${meta.userFriendlyName} and returns its id.`])}
131
+ delete: {
132
+ payload: ${model.brandedIdType}
133
+ result: ${model.brandedIdType}
134
+ }
135
+
136
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`])}
137
+ deleteMany: {
138
+ payload: ${model.brandedIdType}[]
139
+ result: ${model.brandedIdType}[]
140
+ }
147
141
  }
148
- return {
149
- importTypes,
150
- actionTypeDefinition: `export type Action_${meta.businessLogic.update.actionNameModelPart} = ${actionTypeDefinition.join(' | ')}`,
151
- resultMap: `export type ActionResult_${meta.businessLogic.update.actionNameModelPart} = {
152
- ${resultMap.join('\n')}
153
- }`,
154
- typeDefinitionWithCreateFunction: `${typeDefinitionWithCreateFunction.join('\n')}`,
155
- dispatcher: `
156
- public async dispatch<A extends Action_${meta.businessLogic.update.actionNameModelPart}>({ action, execution }: {
157
- action: A;
158
- execution: ${schemaMeta.actions.actionExecution.interface}
159
- }) {
142
+
143
+ @Injectable()
144
+ export class ${meta.businessLogic.update.serviceClassName} implements ${dispatcher.interface}<Scope, Actions> {
145
+ /**
146
+ * Instance of the ${meta.userFriendlyName} view service for convenience.
147
+ */
148
+ private view: ${meta.businessLogic.view.serviceClassName}
149
+
150
+ constructor(${constructorParameters.join(',\n')}) {
151
+ this.view = this.viewService.${meta.businessLogic.view.serviceVariableName}
152
+ }
153
+
154
+ /**
155
+ * Dispatches an action to the appropriate method of the repository.
156
+ */
157
+ public async dispatch<A extends ${dispatcher.actionMethod}<Scope, Actions>>({
158
+ action,
159
+ execution,
160
+ }: {
161
+ action: UnionOmit<A, 'result'>
162
+ execution: ${schemaMeta.actions.actionExecution.interface}
163
+ }): Promise<A['result']> {
160
164
  switch (action.type) {
161
- ${dispatcher.join('\n')}
162
- default: {
165
+ case 'create':
166
+ return this.data.create({ item: action.payload, execution })
167
+
168
+ case 'createMany':
169
+ return this.data.createMany({ items: action.payload, execution })
170
+
171
+ case 'update':
172
+ return this.data.update({ item: action.payload, execution })
173
+
174
+ case 'updateMany':
175
+ return this.data.updateMany({ items: action.payload, execution })
176
+
177
+ case 'upsert':
178
+ return this.data.upsert({ item: action.payload, execution })
179
+
180
+ case 'upsertMany':
181
+ return this.data.upsertMany({ items: action.payload, execution })
182
+
183
+ case 'delete':
184
+ return this.data.delete({ id: action.payload, execution })
185
+
186
+ case 'deleteMany':
187
+ return this.data.deleteMany({ ids: action.payload, execution })
188
+
189
+ default:
163
190
  throw new ExhaustiveSwitchCheck(action)
164
- }
191
+
165
192
  }
166
- }`,
167
- };
168
- }
169
- function prepareActionDefinitions({ model, meta, }) {
170
- return {
171
- create: {
172
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_Create`,
173
- createFunctionName: meta.businessLogic.update.createActionFunctionNameCreate,
174
- payload: meta.types.dto.create,
175
- resultType: meta.types.typeName,
176
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.create)],
177
- dispatcherParameterName: 'item',
178
- },
179
- createMany: {
180
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_CreateMany`,
181
- createFunctionName: meta.businessLogic.update.createActionFunctionNameCreateMany,
182
- payload: `${meta.types.dto.create}[]`,
183
- resultType: `${meta.types.typeName}[]`,
184
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.create)],
185
- dispatcherParameterName: 'items',
186
- },
187
- update: {
188
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_Update`,
189
- createFunctionName: meta.businessLogic.update.createActionFunctionNameUpdate,
190
- payload: meta.types.dto.update,
191
- resultType: meta.types.typeName,
192
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.update)],
193
- dispatcherParameterName: 'item',
194
- },
195
- updateMany: {
196
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_UpdateMany`,
197
- createFunctionName: meta.businessLogic.update.createActionFunctionNameUpdateMany,
198
- payload: `${meta.types.dto.update}[]`,
199
- resultType: `${meta.types.typeName}[]`,
200
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.update)],
201
- dispatcherParameterName: 'items',
202
- },
203
- upsert: {
204
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_Upsert`,
205
- createFunctionName: meta.businessLogic.update.createActionFunctionNameUpsert,
206
- payload: meta.types.dto.upsert,
207
- resultType: meta.types.typeName,
208
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.upsert)],
209
- dispatcherParameterName: 'item',
210
- },
211
- upsertMany: {
212
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_UpsertMany`,
213
- createFunctionName: meta.businessLogic.update.createActionFunctionNameUpsertMany,
214
- payload: `${meta.types.dto.upsert}[]`,
215
- resultType: `${meta.types.typeName}[]`,
216
- imports: [(0, types_1.toAnnotatedTypeName)(meta.types.dto.upsert)],
217
- dispatcherParameterName: 'items',
218
- },
219
- delete: {
220
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_Delete`,
221
- createFunctionName: meta.businessLogic.update.createActionFunctionNameDelete,
222
- payload: model.brandedIdType,
223
- resultType: model.brandedIdType,
224
- imports: [(0, types_1.toAnnotatedTypeName)(model.brandedIdType)],
225
- dispatcherParameterName: 'id',
226
- },
227
- deleteMany: {
228
- actionName: `Action_${meta.businessLogic.update.actionNameModelPart}_DeleteMany`,
229
- createFunctionName: meta.businessLogic.update.createActionFunctionNameDeleteMany,
230
- payload: `${model.brandedIdType}[]`,
231
- resultType: `${model.brandedIdType}[]`,
232
- imports: [(0, types_1.toAnnotatedTypeName)(model.brandedIdType)],
233
- dispatcherParameterName: 'ids',
234
- },
235
- };
236
- }
237
- function generateAction({ meta, type, config: { imports, actionName, payload, createFunctionName, resultType, dispatcherParameterName }, }) {
238
- return {
239
- actionName,
240
- actionType: type,
241
- importTypes: imports,
242
- resultMap: `${type}: ${resultType}`,
243
- typeDefinition: `
244
- export type ${actionName} = { scope: '${meta.actions.actionScopeConstType}'; type: '${type}'; payload: ${payload} }`,
245
- createFunction: `
246
- export function ${createFunctionName}(payload: ${payload}): ${actionName} {
247
- return { scope: '${meta.actions.actionScopeConstType}', type: '${type}', payload }
248
- }`,
249
- dispatchCaseExpression: `
250
- case '${type}':
251
- return this.${type}({ ${dispatcherParameterName}: action.payload, execution })`,
252
- };
193
+ }
194
+ }
195
+ `;
253
196
  }
197
+ exports.generateModelBusinessLogicUpdate = generateModelBusinessLogicUpdate;
@@ -5,7 +5,9 @@ const imports_1 = require("../../lib/imports");
5
5
  const meta_1 = require("../../lib/meta");
6
6
  const fields_1 = require("../../lib/schema/fields");
7
7
  const types_1 = require("../../lib/schema/types");
8
+ const types_2 = require("../../lib/types");
8
9
  const ast_1 = require("../../lib/utils/ast");
10
+ const jsdoc_1 = require("../../lib/utils/jsdoc");
9
11
  /**
10
12
  * Generates view business logic for a given model.
11
13
  * The view logic exposes all information and links of a model. See template's readme for more info.
@@ -56,10 +58,18 @@ function generateModelBusinessLogicView({ model, meta }) {
56
58
  variableName: relationVariableName,
57
59
  variableDefinition: `const ${relationVariableName} = ${relationVariableDefinition}`,
58
60
  });
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] });
64
+ }
59
65
  }
60
66
  const hasLinkedItems = variables.size > 0;
61
67
  if (hasLinkedItems) {
62
- imports.addTypeImport({ from: meta.types.importPath, items: [meta.types.linkedTypeName] });
68
+ // NOTE: If we need to generate the linked item type, we need to import the enum types.
69
+ for (const enumField of (0, fields_1.getEnumFields)(model)) {
70
+ const enumMeta = (0, meta_1.getEnumMetadata)(enumField);
71
+ imports.addTypeImport({ from: enumMeta.types.filePath, items: [enumField.typeName] });
72
+ }
63
73
  }
64
74
  const linkedItemsGetterFn = `
65
75
  /**
@@ -91,6 +101,16 @@ function generateModelBusinessLogicView({ model, meta }) {
91
101
 
92
102
  return item
93
103
  }
104
+ `;
105
+ const linkedTypeDefinition = `
106
+ export type ${meta.types.linkedTypeName} = {
107
+ ${model.fields
108
+ .map((f) => `
109
+ ${(0, jsdoc_1.getFieldComment)(f)}
110
+ ${getLinkedFieldType(f)}${f.isRequired ? '' : ' | null'}
111
+ `)
112
+ .join('\n')}
113
+ }
94
114
  `;
95
115
  return /* ts */ `
96
116
  /* eslint-disable @typescript-eslint/no-unused-vars */
@@ -99,6 +119,8 @@ import { FilterOperator } from '@backend/common'
99
119
 
100
120
  ${imports.generate()}
101
121
 
122
+ ${hasLinkedItems ? linkedTypeDefinition : ''}
123
+
102
124
  @Injectable()
103
125
  export class ${meta.businessLogic.view.serviceClassName} {
104
126
  constructor(${constructorParameters.join(',\n')}) {}
@@ -252,3 +274,20 @@ function compare(a: ${model.typeName}, b: ${model.typeName}, field: keyof ${mode
252
274
 
253
275
  `;
254
276
  }
277
+ /**
278
+ * Converts a field to a TypeScript type definition with linked fields.
279
+ */
280
+ function getLinkedFieldType(f) {
281
+ switch (f.kind) {
282
+ case 'enum':
283
+ return `${f.name}: ${f.typeName}`;
284
+ case 'relation':
285
+ return `${f.relationFieldName}: ${f.relationToModel.typeName}`;
286
+ case 'id':
287
+ return `${f.name}: ${f.model.brandedIdType}`;
288
+ case 'scalar':
289
+ return `${f.name}: ${f.tsTypeName}`;
290
+ default:
291
+ throw new types_2.ExhaustiveSwitchCheck(f);
292
+ }
293
+ }
@@ -9,28 +9,18 @@ const types_1 = require("../../lib/schema/types");
9
9
  */
10
10
  function generateRoute({ model, meta }) {
11
11
  const { idField, defaultField } = model;
12
+ const { scopeName, dataRepositoryVariableName } = meta.businessLogic;
12
13
  const defaultValueMethod = `
13
- getDefault: procedure.query(({ ctx }) => ctx.view.${meta.data.dataServiceName}.${meta.businessLogic.dataRepositoryVariableName}.defaultValue),
14
+ getDefault: procedure.query(({ ctx }) => ctx.view.${meta.data.dataServiceName}.${dataRepositoryVariableName}.defaultValue),
14
15
  `;
16
+ const { zodCreateObject, zodUpdateObject, zodUpsertObject } = meta.businessLogic.update;
15
17
  const imports = imports_1.ImportsGenerator.from(meta.trpc.routerFilePath).addImports({
16
18
  [meta.types.importPath]: [
17
19
  (0, types_1.toAnnotatedTypeName)(model.typeName),
18
20
  meta.types.toBrandedIdTypeFnName,
19
- meta.types.zodDecoderFnNames.createObject,
20
- meta.types.zodDecoderFnNames.updateObject,
21
- meta.types.zodDecoderFnNames.upsertObject,
22
21
  meta.types.zodDecoderFnNames.id,
23
22
  ],
24
- [meta.businessLogic.importPath]: [
25
- meta.businessLogic.update.createActionFunctionNameCreate,
26
- meta.businessLogic.update.createActionFunctionNameCreateMany,
27
- meta.businessLogic.update.createActionFunctionNameUpdate,
28
- meta.businessLogic.update.createActionFunctionNameUpdateMany,
29
- meta.businessLogic.update.createActionFunctionNameUpsert,
30
- meta.businessLogic.update.createActionFunctionNameUpsertMany,
31
- meta.businessLogic.update.createActionFunctionNameDelete,
32
- meta.businessLogic.update.createActionFunctionNameDeleteMany,
33
- ],
23
+ [meta.businessLogic.update.serviceFilePath]: [zodCreateObject, zodUpdateObject, zodUpsertObject],
34
24
  });
35
25
  return /* ts */ `
36
26
  import { z } from 'zod'
@@ -71,30 +61,37 @@ export const ${meta.trpc.routerName} = router({
71
61
  return ctx.view.${meta.data.dataServiceName}.getList(input)
72
62
  }),
73
63
 
74
- create: procedure.input(${meta.types.zodDecoderFnNames.createObject})
75
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameCreate}(input))),
64
+ create: procedure
65
+ .input(${zodCreateObject})
66
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "create", payload: input})),
76
67
 
77
- createMany: procedure.input(z.array(${meta.types.zodDecoderFnNames.createObject}))
78
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameCreateMany}(input))),
68
+ createMany: procedure
69
+ .input(z.array(${zodCreateObject}))
70
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "createMany", payload: input})),
79
71
 
80
- update: procedure.input(${meta.types.zodDecoderFnNames.updateObject})
81
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameUpdate}(input))),
72
+ update: procedure
73
+ .input(${zodUpdateObject})
74
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "update", payload: input})),
82
75
 
83
- updateMany: procedure.input(z.array(${meta.types.zodDecoderFnNames.updateObject}))
84
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameUpdateMany}(input))),
76
+ updateMany: procedure
77
+ .input(z.array(${zodUpdateObject}))
78
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "updateMany", payload: input})),
85
79
 
86
- upsert: procedure.input(${meta.types.zodDecoderFnNames.upsertObject})
87
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameUpsert}(input))),
80
+ upsert: procedure
81
+ .input(${zodUpsertObject})
82
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "upsert", payload: input})),
88
83
 
89
- upsertMany: procedure.input(z.array(${meta.types.zodDecoderFnNames.upsertObject}))
90
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameUpsertMany}(input))),
84
+ upsertMany: procedure
85
+ .input(z.array(${zodUpsertObject}))
86
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "upsertMany", payload: input})),
91
87
 
92
- delete: procedure.input(${meta.types.zodDecoderFnNames.id})
93
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameDelete}(input))),
88
+ delete: procedure
89
+ .input(${meta.types.zodDecoderFnNames.id})
90
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "delete", payload: input})),
94
91
 
95
- deleteMany: procedure.input(z.array(${meta.types.zodDecoderFnNames.id}))
96
- .mutation(({ input, ctx }) => ctx.dispatch(${meta.businessLogic.update.createActionFunctionNameDeleteMany}(input))),
97
-
92
+ deleteMany: procedure
93
+ .input(z.array(${meta.types.zodDecoderFnNames.id}))
94
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "deleteMany", payload: input})),
98
95
  })
99
96
  `;
100
97
  }
@@ -14,27 +14,6 @@ function generateModelTypes({ model, meta }) {
14
14
  var _a;
15
15
  const idField = model.idField;
16
16
  const imports = imports_1.ImportsGenerator.from(meta.types.filePath);
17
- /**
18
- * Tells whether model references other models.
19
- */
20
- let hasLinkedItems = false;
21
- for (const relation of (0, fields_1.getRelationFields)(model)) {
22
- hasLinkedItems = true;
23
- if (relation.relationToModel.typeName === model.typeName) {
24
- // NOTE: All type definitions are already present in this file for this model.
25
- continue;
26
- }
27
- const refModel = relation.relationToModel;
28
- const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
29
- imports.addImport({
30
- items: [refMeta.types.toBrandedIdTypeFnName],
31
- from: refMeta.types.filePath,
32
- });
33
- imports.addTypeImport({
34
- items: [refModel.brandedIdType, refMeta.types.typeName],
35
- from: refMeta.types.filePath,
36
- });
37
- }
38
17
  for (const f of (0, fields_1.getEnumFields)(model)) {
39
18
  const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: f.enumerator });
40
19
  imports.addTypeImport({
@@ -48,16 +27,15 @@ function generateModelTypes({ model, meta }) {
48
27
  from: schemaMeta.types.dto.path,
49
28
  });
50
29
  const decoderNames = meta.types.zodDecoderFnNames;
51
- const linkedTypeDefinition = `
52
- export type ${meta.types.linkedTypeName} = {
53
- ${model.fields
54
- .map((f) => `
55
- ${getFieldComment(f)}
56
- ${getLinkedFieldType(f)}${f.isRequired ? '' : ' | null'}
57
- `)
58
- .join('\n')}
30
+ for (const relation of (0, fields_1.getRelationFields)(model)) {
31
+ if (relation.relationToModel.typeName === model.typeName) {
32
+ // NOTE: All type definitions are already present in this file for this model.
33
+ continue;
34
+ }
35
+ const refMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
36
+ imports.addImport({ from: refMeta.types.filePath, items: [refMeta.types.toBrandedIdTypeFnName] });
37
+ imports.addTypeImport({ from: refMeta.types.filePath, items: [relation.relationToModel.brandedIdType] });
59
38
  }
60
- `;
61
39
  return /* ts */ `
62
40
  import { z } from 'zod'
63
41
 
@@ -68,14 +46,14 @@ export type ${meta.types.typeName} = {
68
46
  ${model.fields
69
47
  .map((f) => {
70
48
  return `
71
- ${getFieldComment(f)}
49
+ ${(0, jsdoc_1.getFieldComment)(f)}
72
50
  ${f.name}: ${getFieldType(f)}${f.isRequired ? '' : ' | null'}
73
51
  `;
74
52
  })
75
53
  .join('\n')}
76
54
  }
77
55
 
78
- ${hasLinkedItems ? linkedTypeDefinition : ``}
56
+
79
57
 
80
58
  /**
81
59
  * Branded Id type that should be used to identify an instance of a ${meta.userFriendlyName}.
@@ -103,31 +81,6 @@ export const ${decoderNames.fromDatabase} = z.object({
103
81
  ${model.fields.map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field })}`).join(',')}
104
82
  })
105
83
 
106
- /**
107
- * Zod decoder for validating the create input of a ${meta.userFriendlyName}.
108
- */
109
- export const ${decoderNames.createObject} = z.object({
110
- ${model.fields
111
- .filter((f) => !f.attributes.isReadonly)
112
- .map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field })}`)
113
- .join(',')}
114
- })
115
-
116
- /**
117
- * Zod decoder for validating the update input of a ${meta.userFriendlyName} .
118
- */
119
- export const ${decoderNames.updateObject} = z.object({
120
- ${model.fields
121
- .filter((f) => !f.attributes.isReadonly || f.kind === 'id')
122
- .map((field) => `${field.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field, allowAnyOptionalField: field.kind !== 'id' })}`)
123
- .join(',')}
124
- })
125
-
126
- /**
127
- * Zod decoder for validating the upsert input of a ${meta.userFriendlyName} .
128
- */
129
- export const ${decoderNames.upsertObject} = z.union([${decoderNames.updateObject}, ${decoderNames.createObject}])
130
-
131
84
  /**
132
85
  * Data transfer object for creating a new ${meta.userFriendlyName} instance.
133
86
  */
@@ -145,31 +98,6 @@ export type ${meta.types.dto.upsert} = ${schemaMeta.types.dto.upsert}<${meta.typ
145
98
  `;
146
99
  }
147
100
  exports.generateModelTypes = generateModelTypes;
148
- function getFieldComment(f) {
149
- const examples = getFieldExamples(f);
150
- let comment = '';
151
- if (f.description) {
152
- comment = ` * ${f.description.split('\n').join('\n * ')}\n`;
153
- }
154
- else if (f.kind === 'enum' && f.enumerator.description) {
155
- comment = ` * ${f.enumerator.description.split('\n').join('\n * ')}\n`;
156
- }
157
- if (examples) {
158
- comment += ` * ${examples}\n`;
159
- }
160
- if (comment === '') {
161
- return '';
162
- }
163
- return `
164
- /**
165
- ${comment}*/`;
166
- }
167
- function getFieldExamples(f) {
168
- if (!f.attributes.examples) {
169
- return undefined;
170
- }
171
- return `Examples: ${f.attributes.examples.map((e) => `"${e}"`).join(', ')}`;
172
- }
173
101
  /**
174
102
  * Converts a field to a TypeScript type definition.
175
103
  */
@@ -187,20 +115,3 @@ function getFieldType(f) {
187
115
  throw new types_1.ExhaustiveSwitchCheck(f);
188
116
  }
189
117
  }
190
- /**
191
- * Converts a field to a TypeScript type definition with linked fields.
192
- */
193
- function getLinkedFieldType(f) {
194
- switch (f.kind) {
195
- case 'enum':
196
- return `${f.name}: ${f.typeName}`;
197
- case 'relation':
198
- return `${f.relationFieldName}: ${f.relationToModel.typeName}`;
199
- case 'id':
200
- return `${f.name}: ${f.model.brandedIdType}`;
201
- case 'scalar':
202
- return `${f.name}: ${f.tsTypeName}`;
203
- default:
204
- throw new types_1.ExhaustiveSwitchCheck(f);
205
- }
206
- }
@@ -34,6 +34,10 @@ export declare class ExportsGenerator {
34
34
  * NOTE: This should only be used when the generator is a root generator.
35
35
  */
36
36
  exportEverythingFromFile(file: string): ExportsGenerator;
37
+ /**
38
+ * Adds a given file to the collection of files we're exporting a selection from.
39
+ */
40
+ exportSelectionFromPath(from: Types.FilePath, elements: Types.ImportableTypes[]): ExportsGenerator;
37
41
  /**
38
42
  * Returns the TypeScript export statements.
39
43
  */
@@ -8,7 +8,7 @@ const file_1 = require("./utils/file");
8
8
  class ExportsGenerator {
9
9
  constructor({ path, isRoot }) {
10
10
  this._path = path;
11
- this._exports = new Set();
11
+ this._exports = new Map();
12
12
  this.isRoot = isRoot !== null && isRoot !== void 0 ? isRoot : false;
13
13
  }
14
14
  /**
@@ -31,7 +31,7 @@ class ExportsGenerator {
31
31
  throw new Error(`Cannot use "exportEverythingFromPath" on a root generator.`);
32
32
  }
33
33
  const resolvedPath = (0, file_1.getRelativePath)({ from: this._path, to: from });
34
- this._exports.add(resolvedPath);
34
+ this._exports.set(resolvedPath, { kind: 'wildcard' });
35
35
  return this;
36
36
  }
37
37
  /**
@@ -43,18 +43,48 @@ class ExportsGenerator {
43
43
  if (!this.isRoot) {
44
44
  throw new Error(`Cannot use "exportEverythingFromFile" on a non-root generator.`);
45
45
  }
46
- this._exports.add(file);
46
+ this._exports.set(file, { kind: 'wildcard' });
47
+ return this;
48
+ }
49
+ /**
50
+ * Adds a given file to the collection of files we're exporting a selection from.
51
+ */
52
+ exportSelectionFromPath(from, elements) {
53
+ if (this.isRoot) {
54
+ throw new Error(`Cannot use "exportSelectionFromPath" on a root generator.`);
55
+ }
56
+ const resolvedPath = (0, file_1.getRelativePath)({ from: this._path, to: from });
57
+ const _existing = this._exports.get(resolvedPath);
58
+ // NOTE: If we already export everything, we don't need to do anything.
59
+ if ((_existing === null || _existing === void 0 ? void 0 : _existing.kind) === 'wildcard') {
60
+ return this;
61
+ }
62
+ if ((_existing === null || _existing === void 0 ? void 0 : _existing.kind) === 'selection') {
63
+ for (const element of elements) {
64
+ _existing.elements.add(element);
65
+ }
66
+ return this;
67
+ }
68
+ this._exports.set(resolvedPath, { kind: 'selection', elements: new Set(elements) });
47
69
  return this;
48
70
  }
49
71
  /**
50
72
  * Returns the TypeScript export statements.
51
73
  */
52
74
  generate() {
53
- const stetements = [...this._exports.values()]
54
- .sort((a, b) => a.localeCompare(b))
55
- .map((path) => `export * from '${path}'`)
56
- .join('\n');
57
- return stetements;
75
+ const statements = [];
76
+ const exports = Array.from(this._exports.entries()).sort((a, b) => a[0].localeCompare(b[0]));
77
+ for (const [_path, _export] of exports) {
78
+ switch (_export.kind) {
79
+ case 'wildcard':
80
+ statements.push(`export * from '${_path}'`);
81
+ break;
82
+ case 'selection':
83
+ statements.push(`export { ${Array.from(_export.elements).join(', ')} } from '${_path}'`);
84
+ break;
85
+ }
86
+ }
87
+ return statements.join('\n');
58
88
  }
59
89
  }
60
90
  exports.ExportsGenerator = ExportsGenerator;
@@ -107,7 +107,7 @@ export type SchemaMetaData = {
107
107
  */
108
108
  mock: Types.ClassName;
109
109
  };
110
- dispatcherService: {
110
+ dispatcher: {
111
111
  /**
112
112
  * Path to the file containing the dispatcher service class definition.
113
113
  */
@@ -116,6 +116,14 @@ export type SchemaMetaData = {
116
116
  * The name of the dispatcher service class.
117
117
  */
118
118
  class: Types.ClassName;
119
+ /**
120
+ * The name of the interface that indicates a service can dispatch actions.
121
+ */
122
+ interface: Types.ClassName;
123
+ /**
124
+ * The name of a util type that is used to create a dictionary of action payloads and results from the action descriptions.
125
+ */
126
+ actionMethod: Types.TypeName;
119
127
  };
120
128
  };
121
129
  /**
@@ -917,50 +925,17 @@ export type ModelMetaData = {
917
925
  */
918
926
  actionModelDiscriminantName: Types.VariableName;
919
927
  /**
920
- * Name of the model's aggregated action type. (e.g. `Action_Aggregation`)
921
- */
922
- actionName: Types.VariableName;
923
- /**
924
- * Name of the model's aggregated action result type. (e.g. `ActionResult_Aggregation`)
925
- */
926
- actionResultName: Types.VariableName;
927
- /**
928
- * The model's name that is used to build the different action types.
929
- * (e.g. `Aggregation` which will be used to build `Action_Aggregation_Create`)
930
- */
931
- actionNameModelPart: Types.VariableName;
932
- /**
933
- * Name of the function that creates a `create` action for the model (e.g. `createActionAggregationCreate`)
934
- */
935
- createActionFunctionNameCreate: Types.FunctionName;
936
- /**
937
- * Name of the function that creates a `createMany` action for the model (e.g. `createActionAggregationCreateMany`)
938
- */
939
- createActionFunctionNameCreateMany: Types.FunctionName;
940
- /**
941
- * Name of the function that creates a `update` action for the model (e.g. `createActionAggregationUpdate`)
942
- */
943
- createActionFunctionNameUpdate: Types.FunctionName;
944
- /**
945
- * Name of the function that creates a `updateMany` action for the model (e.g. `createActionAggregationUpdateMany`)
946
- */
947
- createActionFunctionNameUpdateMany: Types.FunctionName;
948
- /**
949
- * Name of the function that creates a `upsert` action for the model (e.g. `createActionAggregationUpsert`)
950
- */
951
- createActionFunctionNameUpsert: Types.FunctionName;
952
- /**
953
- * Name of the function that creates a `upsertMany` action for the model (e.g. `createActionAggregationUpsertMany`)
928
+ * The name of the function that decodes a Create object to a fully typed object, e.g. `aggregationCreateDecoder`.
954
929
  */
955
- createActionFunctionNameUpsertMany: Types.FunctionName;
930
+ zodCreateObject: Types.FunctionName;
956
931
  /**
957
- * Name of the function that creates a `delete` action for the model (e.g. `createActionAggregationDelete`)
932
+ * The name of the function that decodes an Update object to a fully typed object, e.g. `aggregationUpdateDecoder`.
958
933
  */
959
- createActionFunctionNameDelete: Types.FunctionName;
934
+ zodUpdateObject: Types.FunctionName;
960
935
  /**
961
- * Name of the function that creates a `deleteMany` action for the model (e.g. `createActionAggregationDeleteMany`)
936
+ * The name of the function that decodes an Upsert object to a fully typed object, e.g. `aggregationUpsertDecoder`.
962
937
  */
963
- createActionFunctionNameDeleteMany: Types.FunctionName;
938
+ zodUpsertObject: Types.FunctionName;
964
939
  };
965
940
  /**
966
941
  * Name by which the business logic service exposes the data service.
@@ -1120,18 +1095,6 @@ export type ModelMetaData = {
1120
1095
  * The name of the function that decodes a source (database) object to a fully typed object, e.g. `aggregationDatabaseDecoder`.
1121
1096
  */
1122
1097
  fromDatabase: Types.FunctionName;
1123
- /**
1124
- * The name of the function that decodes a Create object to a fully typed object, e.g. `aggregationCreateDecoder`.
1125
- */
1126
- createObject: Types.FunctionName;
1127
- /**
1128
- * The name of the function that decodes an Update object to a fully typed object, e.g. `aggregationUpdateDecoder`.
1129
- */
1130
- updateObject: Types.FunctionName;
1131
- /**
1132
- * The name of the function that decodes an Upsert object to a fully typed object, e.g. `aggregationUpsertDecoder`.
1133
- */
1134
- upsertObject: Types.FunctionName;
1135
1098
  };
1136
1099
  /**
1137
1100
  * Type definitions for the different Data Transfer Objects (DTOs).
package/dist/lib/meta.js CHANGED
@@ -86,9 +86,11 @@ function getSchemaMetadata({ config }) {
86
86
  class: Types.toClassName(`ActionExecution`),
87
87
  mock: Types.toClassName(`MockActionExecution`),
88
88
  },
89
- dispatcherService: {
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`),
92
94
  },
93
95
  },
94
96
  businessLogic: {
@@ -335,17 +337,9 @@ function getModelMetadata({ model }) {
335
337
  serviceFileName: Types.toFileName(`${camelCase}.update.service`),
336
338
  serviceFilePath: Types.toPath(`${config.paths.businessLogicPath}update/${camelCase}.update.service`),
337
339
  actionModelDiscriminantName: Types.toVariableName(`${camelCase}`),
338
- actionName: Types.toVariableName(`Action_${PascalCase}`),
339
- actionNameModelPart: Types.toVariableName(`${PascalCase}`),
340
- actionResultName: Types.toVariableName(`ActionResult_${PascalCase}`),
341
- createActionFunctionNameCreate: Types.toFunctionName(`createAction${PascalCase}Create`),
342
- createActionFunctionNameCreateMany: Types.toFunctionName(`createAction${PascalCase}CreateMany`),
343
- createActionFunctionNameUpdate: Types.toFunctionName(`createAction${PascalCase}Update`),
344
- createActionFunctionNameUpdateMany: Types.toFunctionName(`createAction${PascalCase}UpdateMany`),
345
- createActionFunctionNameUpsert: Types.toFunctionName(`createAction${PascalCase}Upsert`),
346
- createActionFunctionNameUpsertMany: Types.toFunctionName(`createAction${PascalCase}UpsertMany`),
347
- createActionFunctionNameDelete: Types.toFunctionName(`createAction${PascalCase}Delete`),
348
- createActionFunctionNameDeleteMany: Types.toFunctionName(`createAction${PascalCase}DeleteMany`),
340
+ zodCreateObject: Types.toFunctionName(`${camelCase}CreateDecoder`),
341
+ zodUpdateObject: Types.toFunctionName(`${camelCase}UpdateDecoder`),
342
+ zodUpsertObject: Types.toFunctionName(`${camelCase}UpsertDecoder`),
349
343
  },
350
344
  dataRepositoryVariableName: Types.toVariableName(`data`),
351
345
  },
@@ -409,9 +403,6 @@ function getModelMetadata({ model }) {
409
403
  zodDecoderFnNames: {
410
404
  id: Types.toFunctionName(`${camelCase}IdDecoder`),
411
405
  fromDatabase: Types.toFunctionName(`${camelCase}DatabaseDecoder`),
412
- createObject: Types.toFunctionName(`${camelCase}CreateDecoder`),
413
- updateObject: Types.toFunctionName(`${camelCase}UpdateDecoder`),
414
- upsertObject: Types.toFunctionName(`${camelCase}UpsertDecoder`),
415
406
  },
416
407
  dto: {
417
408
  create: Types.toTypeName(`${PascalCase}CreateDTO`),
@@ -31,7 +31,7 @@ export type DiscriminantName = string & {
31
31
  export declare const toDiscriminantName: (t: string) => DiscriminantName;
32
32
  /**
33
33
  * The name of a model, e.g. "Aggregation".
34
- */
34
+ */
35
35
  export type ModelName = string & {
36
36
  readonly ___type: 'ModelName';
37
37
  };
@@ -67,7 +67,7 @@ export declare const toAnnotatedTypeName: (name: TypeName) => AnnotatedTypeName;
67
67
  export declare const isAnnotatedTypeName: (t: string | AnnotatedTypeName) => t is AnnotatedTypeName;
68
68
  /**
69
69
  * The name of a model field, e.g. "name".
70
- */
70
+ */
71
71
  export type FieldName = string & {
72
72
  readonly ___type: 'FieldName';
73
73
  };
@@ -77,7 +77,7 @@ export type FieldName = string & {
77
77
  export declare const toFieldName: (t: string) => FieldName;
78
78
  /**
79
79
  * The name of an Enum, e.g. "Language".
80
- */
80
+ */
81
81
  export type EnumName = string & {
82
82
  readonly ___type: 'EnumName';
83
83
  };
@@ -87,7 +87,7 @@ export type EnumName = string & {
87
87
  export declare const toEnumName: (t: string) => EnumName;
88
88
  /**
89
89
  * The name of a function (e.g. "toAggregation").
90
- */
90
+ */
91
91
  export type FunctionName = string & {
92
92
  readonly ___type: 'FunctionName';
93
93
  };
@@ -150,7 +150,7 @@ export declare const toPath: (t: string) => FilePath;
150
150
  /**
151
151
  * Branded string values that can be used as import statement values in the generators
152
152
  */
153
- export type ImportableTypes = FunctionName | ClassName | AnnotatedTypeName | VariableName | EnumName;
153
+ export type ImportableTypes = FunctionName | ClassName | TypeName | AnnotatedTypeName | VariableName | EnumName;
154
154
  /**
155
155
  * Branded string values that can be used as paths in import statements
156
156
  */
@@ -1,4 +1,9 @@
1
+ import type { Field } from '../schema/schema';
1
2
  /**
2
3
  * Returns a string of JSDoc comments from an array of lines.
3
4
  */
4
5
  export declare function toJsDocComment(comments: string[] | undefined): string;
6
+ /**
7
+ * Returns a JSDoc comment for a field.
8
+ */
9
+ export declare function getFieldComment(f: Field): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toJsDocComment = void 0;
3
+ exports.getFieldComment = exports.toJsDocComment = void 0;
4
4
  /**
5
5
  * Returns a string of JSDoc comments from an array of lines.
6
6
  */
@@ -14,3 +14,24 @@ function toJsDocComment(comments) {
14
14
  .join('')} \n */`;
15
15
  }
16
16
  exports.toJsDocComment = toJsDocComment;
17
+ /**
18
+ * Returns a JSDoc comment for a field.
19
+ */
20
+ function getFieldComment(f) {
21
+ const lines = [];
22
+ if (f.description) {
23
+ lines.push(...f.description.split('\n'));
24
+ }
25
+ else if (f.kind === 'enum' && f.enumerator.description) {
26
+ lines.push(...f.enumerator.description.split('\n'));
27
+ }
28
+ const _examples = f.attributes.examples;
29
+ if (_examples) {
30
+ lines.push(`Examples: ${_examples.map((e) => `"${e}"`).join(', ')}`);
31
+ }
32
+ if (lines.length === 0) {
33
+ return '';
34
+ }
35
+ return toJsDocComment(lines);
36
+ }
37
+ exports.getFieldComment = getFieldComment;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.55.0",
3
+ "version": "0.56.1",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {