@postxl/generator 0.15.7 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,8 +10,26 @@ function generateEnumType({ enumerator, meta, prismaClientPath, }) {
10
10
 
11
11
  export type ${enumerator.typeName} = Prisma.${enumerator.sourceName}
12
12
  export const ${enumerator.typeName} = Prisma.${enumerator.sourceName}
13
+ ${enumerator.description
14
+ ? `/**
15
+ * ${enumerator.description.split('\n').join('\n * ')}
16
+ */
17
+ `
18
+ : ''}
13
19
  export const ${meta.types.membersList}: ${enumerator.typeName}[] = [
14
- ${enumerator.values.map((v) => `'${v}'`).join(', ')}
20
+ ${enumerator.values
21
+ .map((v) => {
22
+ var _a;
23
+ const description = (_a = enumerator.attributes.valueDescription) === null || _a === void 0 ? void 0 : _a[v];
24
+ if (description) {
25
+ return `
26
+ /** ${description} */
27
+ '${v}'
28
+ `;
29
+ }
30
+ return `'${v}'`;
31
+ })
32
+ .join(', ')}
15
33
  ]
16
34
  `;
17
35
  }
@@ -7,7 +7,9 @@ const meta_1 = require("../../lib/meta");
7
7
  * Generates a business Logic module class.
8
8
  */
9
9
  function generateBusinessLogicModule({ models, meta }) {
10
- const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
10
+ const mm = models
11
+ .map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }))
12
+ .sort((a, b) => a.meta.businessLogic.serviceClassName.localeCompare(b.meta.businessLogic.serviceClassName));
11
13
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.moduleFilePath)
12
14
  .addImport({
13
15
  items: [meta.data.moduleName],
@@ -7,7 +7,9 @@ const meta_1 = require("../../lib/meta");
7
7
  * Generates the business logic service class.
8
8
  */
9
9
  function generateBusinessLogicService({ models, meta }) {
10
- const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
10
+ const mm = models
11
+ .map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }))
12
+ .sort((a, b) => a.meta.businessLogic.serviceClassName.localeCompare(b.meta.businessLogic.serviceClassName));
11
13
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.serviceFilePath);
12
14
  for (const { meta } of mm) {
13
15
  imports.addImport({
@@ -16,10 +18,10 @@ function generateBusinessLogicService({ models, meta }) {
16
18
  });
17
19
  }
18
20
  const constructor = mm
19
- .map(({ meta }) => `public ${meta.businessLogic.serviceVariableName} :${meta.businessLogic.serviceClassName}`)
21
+ .map(({ meta }) => `@Inject(forwardRef(() => ${meta.businessLogic.serviceClassName})) public ${meta.businessLogic.serviceVariableName} :${meta.businessLogic.serviceClassName}`)
20
22
  .join(',\n');
21
23
  return `
22
- import { Injectable } from '@nestjs/common'
24
+ import { Inject, Injectable, forwardRef } from '@nestjs/common'
23
25
 
24
26
  ${imports.generate()}
25
27
 
@@ -32,6 +32,7 @@ const Types = __importStar(require("../../lib/schema/types"));
32
32
  * Generates business logic for a given model.
33
33
  */
34
34
  function generateModelBusinessLogic({ model, meta }) {
35
+ const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
35
36
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.serviceFilePath);
36
37
  imports.addImport({ from: meta.data.importPath, items: [meta.data.repositoryClassName] });
37
38
  imports.addImport({ from: meta.types.importPath, items: [model.brandedIdType, meta.types.typeName] });
@@ -42,9 +43,20 @@ function generateModelBusinessLogic({ model, meta }) {
42
43
  */
43
44
  const modelRepositoryVariableName = Types.toVariableName('data');
44
45
  /**
45
- * A map of referenced models indexed by the name of model's repository class.
46
+ * The name of the variable that holds the central business logic service instance.
47
+ * Instead of injecting a repository instance for each model, we inject this single instance
48
+ * which then provides access to all models' business logic.
46
49
  */
47
- const dependencies = new Map();
50
+ const businessLogicServiceClassName = Types.toVariableName('businessLogic');
51
+ const constructorParameters = [
52
+ `public readonly ${modelRepositoryVariableName}: ${meta.data.repositoryClassName}`,
53
+ `@Inject(forwardRef(() => ${schemaMeta.businessLogic.serviceClassName})) private readonly ${businessLogicServiceClassName}: ${schemaMeta.businessLogic.serviceClassName}`,
54
+ ];
55
+ imports.addImport({
56
+ from: schemaMeta.businessLogic.serviceFilePath,
57
+ items: [schemaMeta.businessLogic.serviceClassName],
58
+ });
59
+ imports.addImport({ from: meta.data.importPath, items: [meta.data.repositoryClassName] });
48
60
  /**
49
61
  * Variable names and their definitions indexed by the name of the relation they represent.
50
62
  */
@@ -52,47 +64,26 @@ function generateModelBusinessLogic({ model, meta }) {
52
64
  for (const relation of (0, fields_1.getRelationFields)(model)) {
53
65
  const refModel = relation.relationToModel;
54
66
  const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
55
- imports.addImport({ from: refMeta.data.importPath, items: [refMeta.data.repositoryClassName] });
67
+ const variableGetter = `this.${businessLogicServiceClassName}.${refMeta.businessLogic.serviceVariableName}.get(itemRaw.${relation.name})`;
56
68
  const variablePresenceCheck = `
57
- if (!${relation.relatedModelBacklinkFieldName}) {
69
+ if (!${relation.relatedModelBacklinkFieldName}) {
58
70
  throw new Error(\`Could not find ${refMeta.types.typeName} with id \${itemRaw.${relation.name}} for ${model.typeName}.${relation.name}!\`)
59
71
  }
60
- `;
72
+ `;
61
73
  const relationVariableName = relation.relatedModelBacklinkFieldName;
62
- if (relation.relationToModel.typeName === model.typeName) {
63
- // NOTE: If the referenced model is a self-relation, we want to reuse the instance of the repository
64
- // that is already injected into the constructor meaning that we don't need to set up a dependency and a local variable.
65
- variables.set(relation.name, {
66
- variableName: relationVariableName,
67
- variableDefinition: `
68
- const ${relationVariableName} = this.data.get(itemRaw.${relation.name})
69
- ${relation.isRequired ? variablePresenceCheck : ``}
70
- `,
71
- });
72
- }
73
- else {
74
- const refRepoVariableName = refMeta.internalPluralName;
75
- dependencies.set(refMeta.data.repositoryClassName, {
76
- localRepositoryVariableName: refRepoVariableName,
77
- repositoryClassName: refMeta.data.repositoryClassName,
78
- });
79
- variables.set(relation.name, {
80
- variableName: relationVariableName,
81
- variableDefinition: `
82
- const ${relationVariableName} = this.${refRepoVariableName}.get(itemRaw.${relation.name})
83
- ${relation.isRequired ? variablePresenceCheck : ``}
84
- `,
85
- });
86
- }
74
+ variables.set(relation.name, {
75
+ variableName: relationVariableName,
76
+ variableDefinition: `
77
+ const ${relationVariableName} = ${relation.isRequired
78
+ ? `${variableGetter};${variablePresenceCheck}`
79
+ : `itemRaw.${relation.name} !== null ? ${variableGetter} : null`}
80
+ `,
81
+ });
87
82
  }
88
83
  const hasLinkedItems = variables.size > 0;
89
84
  if (hasLinkedItems) {
90
85
  imports.addImport({ from: meta.types.importPath, items: [meta.types.linkedTypeName] });
91
86
  }
92
- const constructorParameters = [`public readonly ${modelRepositoryVariableName}: ${meta.data.repositoryClassName}`];
93
- for (const [, { localRepositoryVariableName, repositoryClassName }] of dependencies) {
94
- constructorParameters.push(`private readonly ${localRepositoryVariableName}: ${repositoryClassName}`);
95
- }
96
87
  const linkedItemsGetterFn = `
97
88
  /**
98
89
  * Returns the linked ${meta.userFriendlyName} with the given id or null if it does not exist.
@@ -124,7 +115,7 @@ function generateModelBusinessLogic({ model, meta }) {
124
115
  }
125
116
  `;
126
117
  return `
127
- import { Injectable } from '@nestjs/common'
118
+ import { Inject, Injectable, forwardRef } from '@nestjs/common'
128
119
 
129
120
  ${imports.generate()}
130
121
 
@@ -96,7 +96,7 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
96
96
  }
97
97
  }}
98
98
  >
99
- {({ isSubmitting, submitForm, isValid }) => (
99
+ {({ isSubmitting, submitForm }) => (
100
100
  <ModalWithActions actions={
101
101
  <ConfirmButton
102
102
  color="primary"
@@ -79,7 +79,9 @@ function generateRepository({ model, meta }) {
79
79
  return `
80
80
  import { Injectable, Logger } from '@nestjs/common'
81
81
  ${model.attributes.inMemoryOnly
82
- ? ''
82
+ ? `
83
+ import { ${indexes.length === 0 ? '' : 'NestedMap'} } from '@pxl/common'
84
+ `
83
85
  : `
84
86
  import { DbService, ${model.sourceName} as DbType } from '@${model.schemaConfig.project}/db'
85
87
  import { format, pluralize ${indexes.length === 0 ? '' : ', NestedMap'} } from '@pxl/common'
@@ -93,6 +93,9 @@ function getFieldComment(f) {
93
93
  if (f.description) {
94
94
  comment = ` * ${f.description.split('\n').join('\n * ')}\n`;
95
95
  }
96
+ else if (f.kind === 'enum' && f.enumerator.description) {
97
+ comment = ` * ${f.enumerator.description.split('\n').join('\n * ')}\n`;
98
+ }
96
99
  if (examples) {
97
100
  comment += ` * ${examples}\n`;
98
101
  }
@@ -68,3 +68,18 @@ export type FieldAttributes = {
68
68
  */
69
69
  isLabel?: boolean;
70
70
  };
71
+ export type EnumAttributes = {
72
+ /**
73
+ * The description of the enum.
74
+ * Schema tag: ´@@Description("Description of the model")`
75
+ */
76
+ description?: string;
77
+ /**
78
+ * A dictionary of value descriptions.
79
+ *
80
+ * Note: Unfortunately, the DMMF does not support attributes for Enum values. So we use an attribute on the enum itself to define the descriptions.
81
+ *
82
+ * Schema tag: ´@@Schema("ValueDescription")`
83
+ */
84
+ valueDescription?: Record<string, string>;
85
+ };
@@ -143,7 +143,7 @@ export type ModelMetaData = {
143
143
  *
144
144
  * NOTE: This name is meant to be used in internal variables only.
145
145
  * For any variables, class names, etc. that are exposed, please use a more specific name.
146
- * (e.g. `reactCreateModalComponentName`)
146
+ * (e.g. `reactCreateModalComponentName`)
147
147
  */
148
148
  internalSingularName: Types.VariableName;
149
149
  /**
@@ -151,7 +151,7 @@ export type ModelMetaData = {
151
151
  *
152
152
  * NOTE: This name is meant to be used in internal variables only.
153
153
  * For any variables, class names, etc. that are exposed, please use a more specific name.
154
- * (e.g. `reactCreateModalComponentName`)
154
+ * (e.g. `reactCreateModalComponentName`)
155
155
  */
156
156
  internalPluralName: Types.VariableName;
157
157
  /**
@@ -1,4 +1,4 @@
1
- import { FieldAttributes, ModelAttributes } from '../attributes';
1
+ import { EnumAttributes, FieldAttributes, ModelAttributes } from '../attributes';
2
2
  import { Prettify } from '../utils/types';
3
3
  import * as Types from './types';
4
4
  /**
@@ -388,10 +388,18 @@ export declare const isFieldEnum: (field: Field) => field is Prettify<Omit<Field
388
388
  */
389
389
  export type Enum = {
390
390
  name: string;
391
+ /**
392
+ * Description of the enum, ideally providing business background and detailed information
393
+ */
394
+ description?: string;
391
395
  /**
392
396
  * TypeScript type that should be used to reference an enumerator.
393
397
  */
394
398
  typeName: Types.TypeName;
399
+ /**
400
+ * Object containing all optional attributes of the enum.
401
+ */
402
+ attributes: EnumAttributes;
395
403
  /**
396
404
  * Name of the enum as it appears in the source (e.g. in the Prisma file).
397
405
  */
@@ -1,5 +1,5 @@
1
1
  import { DMMF } from '@prisma/generator-helper';
2
- import type { Attributes, AttributeValue, FieldAttributes, ModelAttributes } from '../lib/attributes';
2
+ import type { Attributes, AttributeValue, EnumAttributes, FieldAttributes, ModelAttributes } from '../lib/attributes';
3
3
  /**
4
4
  * Parses attributes from a given string using provided prefix.
5
5
  */
@@ -11,6 +11,10 @@ export declare function parseArgumentToStringOrStringArray(str: string): Attribu
11
11
  * Returns attribute information for a given model.
12
12
  */
13
13
  export declare function getModelAttributes(model: DMMF.Model): ModelAttributes;
14
+ /**
15
+ * Returns attribute information for a given enum.
16
+ */
17
+ export declare function getEnumAttributes(model: DMMF.DatamodelEnum): EnumAttributes;
14
18
  /**
15
19
  * Returns all attributes assigned to a field
16
20
  */
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getFieldAttributes = exports.getModelAttributes = exports.parseArgumentToStringOrStringArray = exports.parseAttributesFromDocumentation = void 0;
6
+ exports.getFieldAttributes = exports.getEnumAttributes = exports.getModelAttributes = exports.parseArgumentToStringOrStringArray = exports.parseAttributesFromDocumentation = void 0;
7
7
  const remeda_1 = require("remeda");
8
8
  const string_1 = require("../lib/utils/string");
9
9
  const zod_1 = __importDefault(require("zod"));
@@ -93,12 +93,30 @@ function getModelAttributes(model) {
93
93
  if (!result.success) {
94
94
  throw new Error(`Model ${model.name} has invalid model attributes: ${result.error.toString()}`);
95
95
  }
96
- if (result.data.description === 'The calculated metric value for a dataset.') {
97
- // TODO: What is this supposed to do?
98
- }
99
96
  return result.data;
100
97
  }
101
98
  exports.getModelAttributes = getModelAttributes;
99
+ /**
100
+ * Returns attribute information for a given enum.
101
+ */
102
+ function getEnumAttributes(model) {
103
+ const attributes = parseAttributesFromDocumentation(model);
104
+ const decoder = zod_1.default
105
+ .object({
106
+ description: zod_1.default.string().optional(),
107
+ valueDescription: zod_1.default.record(zod_1.default.string()).optional(),
108
+ })
109
+ .transform((obj) => ({
110
+ description: obj.description,
111
+ valueDescription: obj.valueDescription,
112
+ }));
113
+ const result = decoder.safeParse(attributes);
114
+ if (!result.success) {
115
+ throw new Error(`Model ${model.name} has invalid model attributes: ${result.error.toString()}`);
116
+ }
117
+ return result.data;
118
+ }
119
+ exports.getEnumAttributes = getEnumAttributes;
102
120
  /**
103
121
  * Returns all attributes assigned to a field
104
122
  */
@@ -222,8 +222,11 @@ function isFieldIgnored({ field }) {
222
222
  * Parses a given enumerator type.
223
223
  */
224
224
  function parseEnum({ dmmfEnum, config }) {
225
+ const attributes = (0, attributes_1.getEnumAttributes)(dmmfEnum);
225
226
  return {
226
227
  name: dmmfEnum.name,
228
+ description: attributes.description,
229
+ attributes,
227
230
  typeName: Types.toTypeName((0, string_1.toPascalCase)(dmmfEnum.name)),
228
231
  sourceName: dmmfEnum.name,
229
232
  values: dmmfEnum.values.map((v) => v.name),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.15.7",
3
+ "version": "0.16.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "prettier": "^2.8.7",
23
23
  "remeda": "1.9.4",
24
24
  "zod": "3.21.4",
25
- "@postxl/lock": "0.4.1"
25
+ "@postxl/lock": "0.4.2"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@prisma/client": "4.12.0",