@postxl/generator 0.27.1 → 0.27.3

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.
@@ -6,6 +6,7 @@ const meta_1 = require("../../lib/meta");
6
6
  /**
7
7
  * Generates a business Logic module class.
8
8
  */
9
+ // TODO: https://github.com/PostXL/PostXL/issues/347
9
10
  function generateBusinessLogicModule({ models, meta }) {
10
11
  const mm = models
11
12
  .map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }))
@@ -6,6 +6,7 @@ const meta_1 = require("../../lib/meta");
6
6
  /**
7
7
  * Generates the business logic service class.
8
8
  */
9
+ // TODO: https://github.com/PostXL/PostXL/issues/347
9
10
  function generateBusinessLogicService({ models, meta }) {
10
11
  const mm = models
11
12
  .map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }))
@@ -23,12 +24,12 @@ function generateBusinessLogicService({ models, meta }) {
23
24
  return `
24
25
  import { Inject, Injectable, forwardRef } from '@nestjs/common'
25
26
 
26
- ${imports.generate()}
27
+ ${imports.generate()}
27
28
 
28
- @Injectable()
29
- export class ${meta.businessLogic.serviceClassName} {
30
- constructor(${constructor}) {}
31
- }
32
- `;
29
+ @Injectable()
30
+ export class ${meta.businessLogic.serviceClassName} {
31
+ constructor(${constructor}) {}
32
+ }
33
+ `;
33
34
  }
34
35
  exports.generateBusinessLogicService = generateBusinessLogicService;
@@ -30,6 +30,7 @@ const Types = __importStar(require("../../lib/schema/types"));
30
30
  /**
31
31
  * Generates a mocking class
32
32
  */
33
+ // TODO: https://github.com/PostXL/PostXL/issues/347
33
34
  function generateDataMockModule({ models, meta }) {
34
35
  const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
35
36
  const imports = imports_1.ImportsGenerator.from(meta.data.dataMockModuleFilePath)
@@ -30,6 +30,7 @@ const Types = __importStar(require("../../lib/schema/types"));
30
30
  /**
31
31
  * Generates a data module class.
32
32
  */
33
+ // TODO: https://github.com/PostXL/PostXL/issues/347
33
34
  function generateDataModule({ models, meta }) {
34
35
  const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
35
36
  const imports = imports_1.ImportsGenerator.from(meta.data.dataModuleFilePath)
@@ -1,7 +1,7 @@
1
1
  import { SchemaMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
- * Generates an index file that exports all stubs.
4
+ * Generates an index file that exports all repository stubs.
5
5
  */
6
6
  export declare function generateRepositoriesIndex({ models, meta }: {
7
7
  models: Model[];
@@ -5,7 +5,7 @@ const exports_1 = require("../../lib/exports");
5
5
  const imports_1 = require("../../lib/imports");
6
6
  const meta_1 = require("../../lib/meta");
7
7
  /**
8
- * Generates an index file that exports all stubs.
8
+ * Generates an index file that exports all repository stubs.
9
9
  */
10
10
  function generateRepositoriesIndex({ models, meta }) {
11
11
  const exports = exports_1.ExportsGenerator.from(meta.data.repositoriesIndexFilePath);
@@ -1,7 +1,7 @@
1
1
  import { SchemaMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
- * Creates a decoder for the Seed Excel template
4
+ * Creates a decoder for the Seed Excel template.
5
5
  */
6
6
  export declare function generateSeedTemplateDecoder({ models, meta }: {
7
7
  models: Model[];
@@ -8,7 +8,7 @@ const types_2 = require("../../lib/types");
8
8
  // TODO: Remove hardcoded path that seems to be reused in multiple places!
9
9
  const PXL_COMMON = (0, types_1.toPath)('@pxl/common');
10
10
  /**
11
- * Creates a decoder for the Seed Excel template
11
+ * Creates a decoder for the Seed Excel template.
12
12
  */
13
13
  function generateSeedTemplateDecoder({ models, meta }) {
14
14
  const imports = imports_1.ImportsGenerator.from(meta.seed.templateDecoderFilePath);
@@ -28,9 +28,11 @@ const imports_1 = require("../../lib/imports");
28
28
  const meta_1 = require("../../lib/meta");
29
29
  const fields_1 = require("../../lib/schema/fields");
30
30
  const Types = __importStar(require("../../lib/schema/types"));
31
+ const repository_generator_1 = require("./repository.generator");
31
32
  /**
32
33
  * Generates business logic for a given model.
33
34
  */
35
+ // TODO: https://github.com/PostXL/PostXL/issues/347
34
36
  function generateModelBusinessLogic({ model, meta }) {
35
37
  const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
36
38
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.serviceFilePath);
@@ -114,6 +116,7 @@ function generateModelBusinessLogic({ model, meta }) {
114
116
  return item
115
117
  }
116
118
  `;
119
+ const methodTypeSignatures = (0, repository_generator_1.getRepositoryMethodsTypeSignatures)({ model, meta });
117
120
  // prettier-ignore
118
121
  return `
119
122
  import { Inject, Injectable, forwardRef } from '@nestjs/common'
@@ -170,14 +173,14 @@ export class ${meta.businessLogic.serviceClassName} {
170
173
  /**
171
174
  * Creates a new ${meta.userFriendlyName}.
172
175
  */
173
- public async create(item: Omit<${model.typeName}, '${model.idField.name}'> & Partial<{${model.idField.name}: ${model.idField.unbrandedTypeName}}>): Promise<${model.typeName}> {
176
+ public async create(item: ${methodTypeSignatures.create.parameters[0]}): ${methodTypeSignatures.create.returnType} {
174
177
  return this.${modelRepositoryVariableName}.create(item)
175
178
  }
176
179
 
177
180
  /**
178
181
  * Updates the given ${meta.userFriendlyName}.
179
182
  */
180
- public async update(item: Partial<${model.typeName}> & {id: ${model.brandedIdType}}): Promise<${model.typeName}> {
183
+ public async update(item: ${methodTypeSignatures.update.parameters[0]}): ${methodTypeSignatures.update.returnType} {
181
184
  return this.${modelRepositoryVariableName}.update(item)
182
185
  }
183
186
 
@@ -187,7 +190,7 @@ export class ${meta.businessLogic.serviceClassName} {
187
190
  * Note: This does NOT delete any linked items.
188
191
  * If the items is a dependency of another item, the deletion will fail!
189
192
  */
190
- public async delete(id: ${model.brandedIdType}): Promise<void> {
193
+ public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
191
194
  return this.${modelRepositoryVariableName}.delete(id)
192
195
  }
193
196
  }
@@ -2,25 +2,45 @@ import { ModelMetaData } from '../../lib/meta';
2
2
  import { Model } from '../../lib/schema/schema';
3
3
  /**
4
4
  * Generates repository data structure for a given model.
5
- * Based on the model, the repository is generated slightly differently, based on:
6
- * - if the model has a default field, the repository will have a public variable "defaultValue"
7
- * that is set and verified during init
8
- * - if the model has a generated id, the repository will have a function "generateNextId" that
9
- * is used to generate the next id. The `create` and `createMany` functions will use this function
10
- * and allow being called with an id.
11
- * - unique string fields are ensured to be unique
12
- * - max length string fields are ensured to not exceed their max length
13
- * - index for fields marked with index attribute
14
- * - relations are indexed by the foreign key
15
5
  */
16
6
  export declare function generateRepository({ model, meta }: {
17
7
  model: Model;
18
8
  meta: ModelMetaData;
19
9
  }): string;
20
10
  /**
21
- * Generates a mock repository data structure for a given model: same a repository, but in memory only
11
+ * Generates a mock repository data structure for a given model: same a repository, but in memory only.
22
12
  */
23
13
  export declare function generateMockRepository({ model: modelSource, meta: metaSource, }: {
24
14
  model: Model;
25
15
  meta: ModelMetaData;
26
16
  }): string;
17
+ type FnSignature = {
18
+ /**
19
+ * A list of ordered TypeScript types where the first type in the list corresponds to the type of the
20
+ * first parameter and so on. Note that the parameters don't contain names, only types.
21
+ *
22
+ * @example
23
+ *
24
+ * ```
25
+ * function foo(a: string, b: number, c: boolean) {}
26
+ * // ['string', 'number', 'boolean']
27
+ * ```
28
+ */
29
+ parameters: string[];
30
+ /**
31
+ * The return type of the function.
32
+ */
33
+ returnType: string;
34
+ };
35
+ /**
36
+ * Returns a collection of type signatures for the repository methods of a given model.
37
+ */
38
+ export declare function getRepositoryMethodsTypeSignatures({ model, meta }: {
39
+ model: Model;
40
+ meta: ModelMetaData;
41
+ }): {
42
+ create: FnSignature;
43
+ update: FnSignature;
44
+ delete: FnSignature;
45
+ };
46
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateMockRepository = exports.generateRepository = void 0;
3
+ exports.getRepositoryMethodsTypeSignatures = exports.generateMockRepository = exports.generateRepository = void 0;
4
4
  const imports_1 = require("../../lib/imports");
5
5
  const meta_1 = require("../../lib/meta");
6
6
  const fields_1 = require("../../lib/schema/fields");
@@ -10,16 +10,6 @@ const jsdoc_1 = require("../../lib/utils/jsdoc");
10
10
  const string_1 = require("../../lib/utils/string");
11
11
  /**
12
12
  * Generates repository data structure for a given model.
13
- * Based on the model, the repository is generated slightly differently, based on:
14
- * - if the model has a default field, the repository will have a public variable "defaultValue"
15
- * that is set and verified during init
16
- * - if the model has a generated id, the repository will have a function "generateNextId" that
17
- * is used to generate the next id. The `create` and `createMany` functions will use this function
18
- * and allow being called with an id.
19
- * - unique string fields are ensured to be unique
20
- * - max length string fields are ensured to not exceed their max length
21
- * - index for fields marked with index attribute
22
- * - relations are indexed by the foreign key
23
13
  */
24
14
  function generateRepository({ model, meta }) {
25
15
  const { idField } = model;
@@ -30,13 +20,63 @@ function generateRepository({ model, meta }) {
30
20
  })
31
21
  .addImport({
32
22
  items: [(0, types_1.toClassName)('Repository')],
23
+ // TODO: Unify DataLib Imports https://github.com/PostXL/PostXL/issues/344!
33
24
  from: (0, types_1.toFileName)(model.schemaConfig.paths.dataLibPath + 'repository.type'),
25
+ })
26
+ .addImport({
27
+ items: [meta.types.toBrandedIdTypeFnName],
28
+ from: meta.types.importPath,
34
29
  });
35
- const blocks = generateBlocks({ model, meta, imports });
36
- const mainBlocks = generateMainBuildingBlocks({ model, meta, imports, blocks });
30
+ // NOTE: We first generate different parts of the code responisble for a particular task
31
+ // and then we combine them into the final code block.
32
+ //
33
+ // Based on the model, the repository is generated slightly differently:
34
+ // 1.) if the model has a default field, the repository will have a public variable "defaultValue"
35
+ // that is set and verified during init,
36
+ // 2.) if the model has a generated id, the repository will have a function "generateNextId" that
37
+ // is used to generate the next id. The `create` and `createMany` functions will use this function
38
+ // and allow being called with an id,
39
+ // 3.) unique string fields are ensured to be unique,
40
+ // 4.) max length string fields are ensured to not exceed their max length,
41
+ // 5.) index for fields marked with index attribute,
42
+ // 6.) relations are indexed by the foreign key.
43
+ const idBlocks = generateIdBlocks({ model, meta });
44
+ const defaultValueBlocks = generateDefaultBlocks({ model, meta });
45
+ const uniqueStringFieldsBlocks = generateUniqueFieldsBlocks({ model, meta });
46
+ const maxLengthBlocks = generateMaxLengthBlocks({ model, meta });
47
+ const indexBlocks = generateIndexBlocks({ model, meta, imports });
48
+ const relationsBlocks = generateRelationsBlocks({ model, meta, imports });
49
+ let mainBlocks;
50
+ if (model.attributes.inMemoryOnly) {
51
+ mainBlocks = _generateMainBuildingBlocks_InMemoryOnly({
52
+ model,
53
+ meta,
54
+ blocks: {
55
+ uniqueStringFieldsBlocks,
56
+ defaultValueBlocks,
57
+ idBlocks,
58
+ indexBlocks,
59
+ maxLengthBlocks,
60
+ },
61
+ });
62
+ }
63
+ else {
64
+ mainBlocks = generateMainBuildingBlocks_InDatabase({
65
+ model,
66
+ meta,
67
+ imports,
68
+ blocks: {
69
+ uniqueStringFieldsBlocks,
70
+ defaultValueBlocks,
71
+ idBlocks,
72
+ indexBlocks,
73
+ maxLengthBlocks,
74
+ },
75
+ });
76
+ }
37
77
  return `
38
78
  import { Injectable, Logger } from '@nestjs/common'
39
- ${blocks.id.libraryImports}
79
+ ${idBlocks.libraryImports}
40
80
  ${imports.generate()}
41
81
 
42
82
  @Injectable()
@@ -44,17 +84,17 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
44
84
  protected data: Map<${model.brandedIdType}, ${model.typeName}> = new Map()
45
85
  protected logger = new Logger(${meta.data.repositoryClassName}.name)
46
86
 
47
- ${blocks.relations.mapDeclarations.join('\n')}
87
+ ${relationsBlocks.mapDeclarations.join('\n')}
48
88
 
49
- ${blocks.default.publicVariableDeclaration}
89
+ ${defaultValueBlocks.publicVariableDeclaration}
50
90
 
51
- ${blocks.id.generateNextIdFunctionName}
91
+ ${idBlocks.generateNextIdFunctionName}
52
92
 
53
93
  protected uniqueIds = {
54
- ${blocks.uniqueStringFields.mapDeclarations.join(',\n')}
94
+ ${uniqueStringFieldsBlocks.mapDeclarations.join(',\n')}
55
95
  }
56
96
 
57
- ${blocks.index.nestedMapDeclarations.join('\n')}
97
+ ${indexBlocks.nestedMapDeclarations.join('\n')}
58
98
 
59
99
  ${mainBlocks.constructorCode}
60
100
 
@@ -79,7 +119,7 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
79
119
  return Array.from(this.data.values())
80
120
  }
81
121
 
82
- ${blocks.index.getterFunctions.join('\n')}
122
+ ${indexBlocks.getterFunctions.join('\n')}
83
123
 
84
124
  public filter(predicate: (item: ${model.typeName}) => boolean): ${model.typeName}[] {
85
125
  return this.getAllAsArray().filter(predicate)
@@ -95,17 +135,18 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
95
135
 
96
136
  /**${(0, jsdoc_1.convertToJsDocComments)([
97
137
  `Checks that item has the ${idField.name} field.`,
98
- `In case none exists, ${blocks.id.verifyFunctionComment}`,
99
- blocks.uniqueStringFields.verifyFunctionComment,
100
- blocks.maxLength.verifyFunctionComment,
138
+ `In case none exists, ${idBlocks.verifyFunctionComment}`,
139
+ uniqueStringFieldsBlocks.verifyFunctionComment,
140
+ maxLengthBlocks.verifyFunctionComment,
101
141
  ])}
102
142
  */
103
- private verifyItem(item: ${blocks.id.verifyFunctionParameterType}): ${model.typeName} {
104
- ${blocks.id.verifyCode}
105
143
 
106
- ${blocks.maxLength.verifyCode.join('\n')}
144
+ private verifyItem(item: ${idBlocks.verifyFunctionParameterType}): ${model.typeName} {
145
+ ${idBlocks.verifyCode}
107
146
 
108
- ${blocks.uniqueStringFields.verifyCode.join('\n')}
147
+ ${maxLengthBlocks.verifyCode.join('\n')}
148
+
149
+ ${uniqueStringFieldsBlocks.verifyCode.join('\n')}
109
150
 
110
151
  return {
111
152
  ${idField.name},
@@ -130,25 +171,25 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
130
171
 
131
172
  ${mainBlocks.deleteCode}
132
173
 
133
- ${blocks.relations.getterFunctions.join('\n')}
174
+ ${relationsBlocks.getterFunctions.join('\n')}
134
175
 
135
- ${blocks.maxLength.ensureMaxLengthFunctions.join('\n')}
176
+ ${maxLengthBlocks.ensureMaxLengthFunctions.join('\n')}
136
177
 
137
- ${blocks.uniqueStringFields.ensureUniqueFunctions.join('\n\n')}
178
+ ${uniqueStringFieldsBlocks.ensureUniqueFunctions.join('\n\n')}
138
179
 
139
180
  /**
140
181
  * Function that adds/updates a given item to the internal data store, indexes, etc.
141
182
  */
142
183
  private set(item: ${model.typeName}): void {
143
- ${blocks.id.setCode}
184
+ ${idBlocks.setCode}
144
185
 
145
186
  this.data.set(item.id, item)
146
187
 
147
- ${blocks.uniqueStringFields.setCode.join('\n')}
188
+ ${uniqueStringFieldsBlocks.setCode.join('\n')}
148
189
 
149
- ${blocks.relations.setCode.join('\n')}
190
+ ${relationsBlocks.setCode.join('\n')}
150
191
 
151
- ${blocks.index.setCode.join('\n')}
192
+ ${indexBlocks.setCode.join('\n')}
152
193
  }
153
194
 
154
195
  /**
@@ -156,11 +197,11 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
156
197
  */
157
198
  private remove(item: ${model.typeName}): void {
158
199
  this.data.delete(item.id)
159
- ${blocks.uniqueStringFields.removeCode.join('\n')}
200
+ ${uniqueStringFieldsBlocks.removeCode.join('\n')}
160
201
 
161
- ${blocks.relations.removeCode.join('\n')}
202
+ ${relationsBlocks.removeCode.join('\n')}
162
203
 
163
- ${blocks.index.removeCode.join('\n')}
204
+ ${indexBlocks.removeCode.join('\n')}
164
205
  }
165
206
 
166
207
  ${mainBlocks.databaseDecoderCode}
@@ -169,7 +210,7 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
169
210
  }
170
211
  exports.generateRepository = generateRepository;
171
212
  /**
172
- * Generates a mock repository data structure for a given model: same a repository, but in memory only
213
+ * Generates a mock repository data structure for a given model: same a repository, but in memory only.
173
214
  */
174
215
  function generateMockRepository({ model: modelSource, meta: metaSource, }) {
175
216
  // We re-use the repository block, but we change the meta data to use the mock repository name and the model to be in memory only
@@ -178,26 +219,21 @@ function generateMockRepository({ model: modelSource, meta: metaSource, }) {
178
219
  return generateRepository({ model, meta });
179
220
  }
180
221
  exports.generateMockRepository = generateMockRepository;
181
- function generateMainBuildingBlocks({ model, meta, imports, blocks, }) {
182
- if (model.attributes.inMemoryOnly) {
183
- return generateMainBuildingBlocks_InMemoryOnly({ model, meta, imports, blocks });
184
- }
185
- else {
186
- return generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks });
187
- }
188
- }
189
- function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
190
- const { idField } = model;
222
+ /**
223
+ * Generates the main building blocks of the repository for in-memory model.
224
+ */
225
+ function _generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
226
+ const methodTypeSignatures = getRepositoryMethodsTypeSignatures({ model, meta });
191
227
  return {
192
228
  constructorCode: '',
193
229
  initCode: `
194
230
  public async init() {
195
231
  this.data.clear()
196
232
 
197
- ${blocks.uniqueStringFields.clearCode.join('\n')}
198
- ${blocks.default.init.resetCode}
233
+ ${blocks.uniqueStringFieldsBlocks.clearCode.join('\n')}
234
+ ${blocks.defaultValueBlocks.init.resetCode}
199
235
 
200
- ${blocks.index.initCode.join('\n')}
236
+ ${blocks.indexBlocks.initCode.join('\n')}
201
237
 
202
238
  return Promise.resolve()
203
239
  }`,
@@ -210,18 +246,17 @@ function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
210
246
  public async deleteAll(): Promise<void> {
211
247
  return this.init()
212
248
  }`,
213
- // prettier-ignore
214
249
  createCode: `
215
250
  // Non-mocked version is async - so we keep type-compatible signatures for create() and createWithId()
216
251
  // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
217
- public async create(item: Omit<${model.typeName}, '${idField.name}'> & Partial<{id: ${idField.unbrandedTypeName}}>): Promise<${model.typeName}> {
252
+ public async create(item: ${methodTypeSignatures.create.parameters[0]}): ${methodTypeSignatures.create.returnType} {
218
253
  const newItem = await Promise.resolve(this.verifyItem(item))
219
254
 
220
255
  this.set(newItem)
221
256
  return newItem
222
257
  }`,
223
258
  createManyCode: `
224
- public async createMany(items: ${blocks.id.verifyFunctionParameterType}[]) {
259
+ public async createMany(items: ${blocks.idBlocks.verifyFunctionParameterType}[]) {
225
260
  const newItems = items.map((item) => this.verifyItem(item))
226
261
 
227
262
  await Promise.resolve()
@@ -232,10 +267,9 @@ function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
232
267
 
233
268
  return newItems
234
269
  }`,
270
+ // prettier-ignore
235
271
  updateCode: `
236
- public async update(item: Partial<${model.typeName}> & {
237
- id: ${model.brandedIdType}
238
- }): Promise<${model.typeName}> {
272
+ public async update(item: ${methodTypeSignatures.update.parameters[0]}): ${methodTypeSignatures.update.returnType} {
239
273
  const existingItem = this.get(item.id)
240
274
 
241
275
  if (!existingItem) {
@@ -243,9 +277,9 @@ function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
243
277
  }
244
278
 
245
279
 
246
- ${blocks.maxLength.updateCode.join('\n')}
280
+ ${blocks.maxLengthBlocks.updateCode.join('\n')}
247
281
 
248
- ${blocks.uniqueStringFields.updateCode.join('\n')}
282
+ ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
249
283
 
250
284
  const newItem = await Promise.resolve({ ...existingItem, ...item })
251
285
 
@@ -255,7 +289,7 @@ function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
255
289
  return newItem
256
290
  }`,
257
291
  deleteCode: `
258
- public async delete(id: ${model.brandedIdType}): Promise<void> {
292
+ public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
259
293
  const existingItem = this.get(id)
260
294
  if (!existingItem) {
261
295
  throw new Error(\`Could not delete ${model.typeName} with id \${id}. Not found!\`)
@@ -268,6 +302,9 @@ function generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
268
302
  databaseDecoderCode: ``,
269
303
  };
270
304
  }
305
+ /**
306
+ * Generates the methods of the repository for a model that is stored in the database.
307
+ */
271
308
  function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }) {
272
309
  const decoderFunctionName = meta.data.repository.decoderFnName;
273
310
  const { idField } = model;
@@ -281,16 +318,22 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
281
318
  items: [`format`, `pluralize`].map(types_1.toTypeName),
282
319
  from: (0, types_1.toFileName)('@pxl/common'),
283
320
  });
321
+ const dbTableName = [model.sourceSchemaName, model.sourceName]
322
+ .filter((s) => s != null)
323
+ .map((part) => `"${part}"`)
324
+ .join('.');
325
+ const methodTypeSignatures = getRepositoryMethodsTypeSignatures({ model, meta });
284
326
  return {
285
327
  constructorCode: `constructor(protected db: DbService) {}`,
286
328
  initCode: `
287
329
  public async init() {
288
330
  this.data.clear()
289
331
 
290
- ${blocks.uniqueStringFields.clearCode.join('\n')}
291
- ${blocks.default.init.resetCode}
332
+ ${blocks.uniqueStringFieldsBlocks.clearCode.join('\n')}
333
+
334
+ ${blocks.defaultValueBlocks.init.resetCode}
292
335
 
293
- ${blocks.index.initCode.join('\n')}
336
+ ${blocks.indexBlocks.initCode.join('\n')}
294
337
 
295
338
  const data = await this.db.${meta.data.repository.getMethodFnName}.findMany({})
296
339
 
@@ -298,15 +341,15 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
298
341
  const item = this.${decoderFunctionName}(rawItem)
299
342
  this.set(item)
300
343
 
301
- ${blocks.default.init.setCode}
344
+ ${blocks.defaultValueBlocks.init.setCode}
302
345
  }
303
346
 
304
- ${blocks.id.initCode}
347
+ ${blocks.idBlocks.initCode}
305
348
 
306
- ${blocks.default.init.checkCode}
349
+ ${blocks.defaultValueBlocks.init.checkCode}
307
350
 
308
351
  this.logger.log(\`\${format(this.data.size)} \${pluralize('${model.typeName}', this.data.size)} loaded\`)
309
- ${blocks.index.initLogCode.join('\n')}
352
+ ${blocks.indexBlocks.initLogCode.join('\n')}
310
353
  }`,
311
354
  reInitCode: `
312
355
  public async reInit(data: ${model.typeName}[]): Promise<void> {
@@ -316,19 +359,20 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
316
359
  this.logger.error(errorMsg)
317
360
  throw new Error(errorMsg)
318
361
  }
362
+
319
363
  await this.db.runOnlyOnTestDb(() => this.db.${meta.data.repository.getMethodFnName}.createMany({ data: data.map((i) => this.toCreateItem(i)) }))
364
+
320
365
  return this.init()
321
366
  }`,
322
367
  deleteAllCode: `
323
368
  public async deleteAll(): Promise<void> {
324
- await this.db.runOnlyOnTestDb(() => this.db.$executeRaw\`DELETE FROM ${model.sourceSchemaName !== undefined ? `"${model.sourceSchemaName}".` : ''}"${model.sourceName}"\`)
325
-
369
+ await this.db.runOnlyOnTestDb(() => this.db.$executeRaw\`DELETE FROM ${dbTableName}\`)
370
+
326
371
  return this.init()
327
372
  }
328
373
  `,
329
- // prettier-ignore
330
374
  createCode: `
331
- public async create(item: Omit<${model.typeName}, '${idField.name}'> & Partial<{ id: ${idField.unbrandedTypeName} }>): Promise<${model.typeName}> {
375
+ public async create(item: ${methodTypeSignatures.create.parameters[0]}): ${methodTypeSignatures.create.returnType} {
332
376
  const newItem = this.${decoderFunctionName}(
333
377
  await this.db.${meta.data.repository.getMethodFnName}.create({
334
378
  data: this.toCreateItem(this.verifyItem(item)),
@@ -339,7 +383,7 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
339
383
  return newItem
340
384
  }`,
341
385
  createManyCode: `
342
- public async createMany(items: ${blocks.id.verifyFunctionParameterType}[]) {
386
+ public async createMany(items: ${blocks.idBlocks.verifyFunctionParameterType}[]) {
343
387
  const newItems = items.map((item) => this.verifyItem(item))
344
388
 
345
389
  await this.db.${meta.data.repository.getMethodFnName}.createMany({ data: newItems.map(i => this.toCreateItem(i)) })
@@ -352,16 +396,15 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
352
396
  }`,
353
397
  // prettier-ignore
354
398
  updateCode: `
355
- public async update(item: Partial<${model.typeName}> & { id: ${model.brandedIdType}}): Promise<${model.typeName}> {
356
- const existingItem = this.get(item.id)
357
-
399
+ public async update(item: ${methodTypeSignatures.update.parameters[0]}): ${methodTypeSignatures.update.returnType} {
400
+ const existingItem = this.get(item.${idField.name})
358
401
  if (!existingItem) {
359
402
  throw new Error(\`Could not update ${meta.userFriendlyName} with id \${item.id}. Not found!\`)
360
403
  }
361
404
 
362
- ${blocks.maxLength.updateCode.join('\n')}
405
+ ${blocks.maxLengthBlocks.updateCode.join('\n')}
363
406
 
364
- ${blocks.uniqueStringFields.updateCode.join('\n')}
407
+ ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
365
408
 
366
409
  const newItem = this.${decoderFunctionName}(
367
410
  await this.db.${meta.data.repository.getMethodFnName}.update({
@@ -385,7 +428,7 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
385
428
  return newItem
386
429
  }`,
387
430
  deleteCode: `
388
- public async delete(id: ${model.brandedIdType}): Promise<void> {
431
+ public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
389
432
  const existingItem = this.get(id)
390
433
  if (!existingItem) {
391
434
  throw new Error(\`Could not delete ${model.typeName} with id \${id}. Not found!\`)
@@ -395,48 +438,69 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
395
438
 
396
439
  this.remove(existingItem)
397
440
  }`,
441
+ // prettier-ignore
398
442
  databaseDecoderCode: `
399
443
  /**
400
444
  * Utility function that converts a given Database object to a TypeScript model instance
401
445
  */
402
- private ${decoderFunctionName}(item: Pick<DbType, ${[...model.fields.values()]
403
- .map((f) => `'${f.sourceName}'`)
404
- .join(' | ')}>): ${model.typeName} {
446
+ private ${decoderFunctionName}(item: Pick<DbType, ${Array.from(model.fields.values()).map((f) => `'${f.sourceName}'`).join(' | ')}>): ${model.typeName} {
405
447
  return ${meta.types.zodDecoderFnName}.parse({
406
- ${[...model.fields.values()].map((f) => `${f.name}: item.${f.sourceName}`).join(',\n')}
448
+ ${Array.from(model.fields.values()).map((f) => `${f.name}: item.${f.sourceName}`).join(',\n')}
407
449
  })
408
450
  }`,
409
451
  };
410
452
  }
411
- function generateBlocks({ model, meta, imports, }) {
412
- return {
413
- id: generateIdBlocks({ model, meta, imports }),
414
- default: generateDefaultBlocks({ model, meta }),
415
- uniqueStringFields: generateUniqueFieldsBlocks({ model, meta }),
416
- maxLength: generateMaxLengthBlocks({ model, meta }),
417
- index: generateIndexBlocks({ model, meta, imports }),
418
- relations: generateRelationsBlocks({ model, meta, imports }),
419
- };
420
- }
421
- function generateIdBlocks({ model, meta, imports, }) {
453
+ /**
454
+ * Generates code chunks responsible for verifying the ID validity of a model instance and generating the id
455
+ * value of a model with auto-generated id.
456
+ */
457
+ function generateIdBlocks({ model, meta }) {
422
458
  const idField = model.idField;
423
459
  if (!idField.isGenerated) {
424
- return generateIdBlocks_NoGeneration({ idField, model, meta });
460
+ return _generateIdBlocks_NoGeneration({ idField, model, meta });
425
461
  }
426
462
  if (idField.schemaType === 'Int') {
427
- return generateIdBlock_Int({ idField, model, meta, imports });
463
+ return _generateIdBlock_Int({ idField, model, meta });
428
464
  }
429
465
  if (idField.schemaType === 'String') {
430
- return generateIdBlock_UUID({ idField, model, meta, imports });
466
+ return _generateIdBlock_UUID({ idField, model, meta });
431
467
  }
432
468
  throw new Error(`Repository block only supports Id generation for number and strings! Found ${idField.schemaType} for model ${model.name} instead!`);
433
469
  }
434
- function generateIdBlocks_NoGeneration({ idField, model, meta, }) {
470
+ /**
471
+ * Returns a collection of type signatures for the repository methods of a given model.
472
+ */
473
+ // NOTE: We export this function as an interface simplification to the repository generator. Internally, we
474
+ // use the same functions as the ones used in this function which we don't want to expose.
475
+ function getRepositoryMethodsTypeSignatures({ model, meta }) {
476
+ const { verifyFunctionParameterType } = generateIdBlocks({ model, meta });
477
+ return {
478
+ create: {
479
+ parameters: [verifyFunctionParameterType],
480
+ returnType: `Promise<${model.typeName}>`,
481
+ },
482
+ update: {
483
+ parameters: [`Partial<${model.typeName}> & { ${model.idField.name}: ${model.brandedIdType} }`],
484
+ returnType: `Promise<${model.typeName}>`,
485
+ },
486
+ delete: {
487
+ parameters: [model.brandedIdType],
488
+ returnType: `Promise<void>`,
489
+ },
490
+ };
491
+ }
492
+ exports.getRepositoryMethodsTypeSignatures = getRepositoryMethodsTypeSignatures;
493
+ /**
494
+ * Generates the id block code chunks for a model that requires an ID field to be manually
495
+ * supplied to the create function.
496
+ */
497
+ function _generateIdBlocks_NoGeneration({ idField, model, meta, }) {
435
498
  return {
436
499
  libraryImports: '',
437
500
  generateNextIdFunctionName: '',
438
501
  initCode: '',
439
502
  verifyFunctionComment: `an error is thrown as field has no default setting in schema.`,
503
+ // TODO: This creates a bug!
440
504
  verifyFunctionParameterType: model.typeName,
441
505
  verifyCode: `
442
506
  if (item.${idField.name} === undefined) {
@@ -446,11 +510,11 @@ function generateIdBlocks_NoGeneration({ idField, model, meta, }) {
446
510
  setCode: '',
447
511
  };
448
512
  }
449
- function generateIdBlock_Int({ idField, model, meta, imports, }) {
450
- imports.addImport({
451
- items: [meta.types.toBrandedIdTypeFnName],
452
- from: meta.types.importPath,
453
- });
513
+ /**
514
+ * Generates the id block code chunks for a model that has an integer id field.
515
+ * Given chunks make sure that the id is unique if provided or generate a new one if not.
516
+ */
517
+ function _generateIdBlock_Int({ idField, model, meta, }) {
454
518
  return {
455
519
  libraryImports: '',
456
520
  generateNextIdFunctionName: `
@@ -465,11 +529,11 @@ function generateIdBlock_Int({ idField, model, meta, imports, }) {
465
529
  setCode: `if (item.id > this.currentMaxId) { this.currentMaxId = item.id }`,
466
530
  };
467
531
  }
468
- function generateIdBlock_UUID({ idField, model, meta, imports, }) {
469
- imports.addImport({
470
- items: [meta.types.toBrandedIdTypeFnName],
471
- from: meta.types.importPath,
472
- });
532
+ /**
533
+ * Generates the id block code chunks for a model that has a UUID id field.
534
+ * It allows you to provide a custom id or generates a new one if not.
535
+ */
536
+ function _generateIdBlock_UUID({ idField, model, meta, }) {
473
537
  return {
474
538
  libraryImports: `import { randomUUID } from 'crypto'`,
475
539
  generateNextIdFunctionName: `
@@ -483,6 +547,9 @@ function generateIdBlock_UUID({ idField, model, meta, imports, }) {
483
547
  setCode: '',
484
548
  };
485
549
  }
550
+ /**
551
+ * Returns the code chunks that define the default value property and its initialization.
552
+ */
486
553
  function generateDefaultBlocks({ model, meta }) {
487
554
  const defaultField = model.defaultField;
488
555
  if (!defaultField) {
@@ -520,6 +587,9 @@ function generateDefaultBlocks({ model, meta }) {
520
587
  `,
521
588
  };
522
589
  }
590
+ /**
591
+ * Generates code chunks that enforce the uniqueness of a given field.
592
+ */
523
593
  function generateUniqueFieldsBlocks({ model }) {
524
594
  const fields = model.fields.filter(fields_1.isUniqueStringField);
525
595
  const result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.27.1",
3
+ "version": "0.27.3",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@faker-js/faker": "7.6.0",
18
- "@prisma/generator-helper": "4.12.0",
19
- "@prisma/internals": "^4.12.0",
18
+ "@prisma/generator-helper": "5.1.0",
19
+ "@prisma/internals": "^5.1.0",
20
20
  "exceljs": "^4.3.0",
21
21
  "fast-glob": "^3.2.12",
22
22
  "prettier": "^2.8.7",
@@ -25,11 +25,11 @@
25
25
  "@postxl/lock": "0.4.7"
26
26
  },
27
27
  "devDependencies": {
28
- "@prisma/client": "4.12.0",
28
+ "@prisma/client": "5.1.0",
29
29
  "@types/jest": "^29.5.0",
30
30
  "@types/node": "18.15.10",
31
31
  "jest": "29.5.0",
32
- "prisma": "4.12.0",
32
+ "prisma": "5.1.0",
33
33
  "ts-jest": "29.0.5",
34
34
  "ts-node": "10.9.1",
35
35
  "ts-toolbelt": "^9.6.0",
@@ -46,7 +46,7 @@
46
46
  "build": "tsc -p tsconfig.build.json",
47
47
  "prepublish": "tsc -p tsconfig.build.json",
48
48
  "dev": "tsc -p tsconfig.build.json -w",
49
- "test": "./scripts/test.sh",
49
+ "test:generation": "./scripts/test.sh",
50
50
  "test:jest": "jest",
51
51
  "test:watch": "jest --watch",
52
52
  "test:types": "tsc --noEmit"