@postxl/generator 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/generator.js CHANGED
@@ -178,7 +178,7 @@ const generate = ({ models, enums, config, prismaClientPath, logger, }) => __awa
178
178
  generated.write(`/${meta.data.dataMockModuleFilePath}.ts`, (0, datamockmodule_generator_1.generateDataMockModule)({ models, meta }));
179
179
  generated.write(`/${meta.data.dataModuleFilePath}.ts`, (0, datamodule_generator_1.generateDataModule)({ models, meta }));
180
180
  generated.write(`/${meta.data.dataServiceFilePath}.ts`, (0, dataservice_generator_1.generateDataService)({ models, meta }));
181
- generated.write(`/${meta.data.testDataServiceFilePath}.ts`, (0, testdataservice_generator_1.generateTestDataService)({ meta }));
181
+ generated.write(`/${meta.data.testDataServiceFilePath}.ts`, (0, testdataservice_generator_1.generateTestDataService)({ models, meta }));
182
182
  generated.write(`/${meta.data.repositoriesConstFilePath}.ts`, (0, repositories_generator_1.generateRepositoriesArray)({ models, meta }));
183
183
  generated.write(`/${meta.data.repositoriesIndexFilePath}.ts`, (0, repositories_generator_1.generateRepositoriesIndex)({ models, meta }));
184
184
  generated.write(`/${meta.data.stubIndexFilePath}.ts`, (0, stubs_generator_1.generateStubsIndex)({ models, meta }));
@@ -9,7 +9,7 @@ function generateEmptyDatabaseStoredProcedure({ models, meta, }) {
9
9
  .map((model) => `\t${model.sourceSchemaName !== undefined ? `"${model.sourceSchemaName}".` : ''}"${model.sourceName}"`)
10
10
  .join(',\n');
11
11
  return `
12
- CREATE OR REPLACE PROCEDURE "emptyDatabase"() AS $BODY$ BEGIN IF EXISTS (
12
+ CREATE OR REPLACE PROCEDURE "emptyDatabase"() AS $BODY$ BEGIN IF EXISTS (
13
13
  SELECT
14
14
  FROM "Configuration"."Config"
15
15
  WHERE NOT "isTest"
@@ -1,7 +1,9 @@
1
1
  import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
2
3
  /**
3
4
  * Generates a generic data service object that may be used on the server to access database.
4
5
  */
5
- export declare function generateTestDataService({ meta }: {
6
+ export declare function generateTestDataService({ meta, models }: {
6
7
  meta: SchemaMetaData;
8
+ models: Model[];
7
9
  }): string;
@@ -24,16 +24,27 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.generateTestDataService = void 0;
27
+ const meta_1 = require("../../lib/meta");
27
28
  const Types = __importStar(require("../../lib/schema/types"));
28
29
  const imports_1 = require("../../lib/imports");
29
30
  /**
30
31
  * Generates a generic data service object that may be used on the server to access database.
31
32
  */
32
- function generateTestDataService({ meta }) {
33
+ function generateTestDataService({ meta, models }) {
33
34
  const imports = imports_1.ImportsGenerator.from(meta.data.testDataServiceFilePath).addImport({
34
35
  items: [Types.toVariableName('MockData')],
35
36
  from: meta.data.dataMockModuleFilePath,
36
37
  });
38
+ const mm = models.map((model) => ({ model, meta: (0, meta_1.getModelMetadata)({ model }) }));
39
+ for (const { meta } of mm) {
40
+ imports.addImport({
41
+ items: [meta.data.repositoryClassName],
42
+ from: meta.data.repoFilePath,
43
+ });
44
+ }
45
+ const constructor = mm
46
+ .map(({ meta }) => `private ${meta.data.dataServiceName} :${meta.data.repositoryClassName}`)
47
+ .join(',\n');
37
48
  return `
38
49
  import { Injectable, Logger } from '@nestjs/common'
39
50
 
@@ -47,14 +58,19 @@ function generateTestDataService({ meta }) {
47
58
  private logger = new Logger(TestDataService.name)
48
59
  constructor(
49
60
  private db: DbService,
50
-
51
61
  private resetService: ResetService,
62
+ ${constructor}
52
63
  ) {}
53
64
 
54
65
  public async resetTestData(data: MockData) {
55
66
  this.logger.log(\`✅ Reset test data\`)
56
67
  await this.db.emptyDatabase()
57
- return this.resetService.reset()
68
+
69
+ ${mm
70
+ .map(({ meta }) => `await this.${meta.data.dataServiceName}.reInit(data.${meta.data.mockDataPropertyName} ?? [])`)
71
+ .join('\n')}
72
+
73
+ return this.resetService.reset()
58
74
  }
59
75
  }
60
76
  `;
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateRepository = void 0;
4
4
  const fields_1 = require("../../lib/schema/fields");
5
5
  const string_1 = require("../../lib/utils/string");
6
+ const schema_1 = require("../../lib/schema/schema");
7
+ const meta_1 = require("../../lib/meta");
6
8
  const imports_1 = require("../../lib/imports");
7
9
  /**
8
10
  * Generates repository data structure for a given model.
@@ -13,6 +15,9 @@ function generateRepository({ model, meta }) {
13
15
  const decoder = meta.data.repository.decoderFnName;
14
16
  const uniqueStringFields = fields.filter(fields_1.isUniqueStringField);
15
17
  const maxLengthStringFields = fields.filter(fields_1.isMaxLengthStringField);
18
+ const relations = fields
19
+ .filter(schema_1.isFieldRelation)
20
+ .map((field) => (Object.assign(Object.assign({}, field), { meta: (0, meta_1.getModelMetadata)({ model: field.relationToModel }), fieldMeta: (0, meta_1.getFieldMetadata)({ field }) })));
16
21
  const defaultValueInitFn = `
17
22
  if (item.${(_a = model.defaultField) === null || _a === void 0 ? void 0 : _a.name}) {
18
23
  if (this.defaultValue) {
@@ -34,6 +39,12 @@ function generateRepository({ model, meta }) {
34
39
  from: meta.types.importPath,
35
40
  })
36
41
  .addImport({ items: [meta.types.zodDecoderFnName], from: meta.types.importPath });
42
+ relations.forEach((r) => {
43
+ imports.addImport({
44
+ items: [r.meta.types.brandedIdType],
45
+ from: r.meta.types.importPath,
46
+ });
47
+ });
37
48
  return `
38
49
  import { Injectable, Logger } from '@nestjs/common'
39
50
  import { DbService, ${model.sourceName} as DbType } from '@${model.schemaConfig.project}/db'
@@ -51,6 +62,12 @@ export class ${meta.data.repositoryClassName} implements Repository<
51
62
  protected data: Map<${model.brandedIdType}, ${model.typeName}> = new Map()
52
63
  protected logger = new Logger(${meta.data.repositoryClassName}.name)
53
64
 
65
+ ${relations
66
+ .map((r) => `
67
+ protected ${r.name}Map: Map<${r.meta.types.brandedIdType}, Set<${model.brandedIdType}>> = new Map()
68
+ `)
69
+ .join('\n')}
70
+
54
71
  ${model.defaultField ? `public defaultValue!: ${model.typeName}` : ''}
55
72
 
56
73
  ${isGenerated
@@ -169,9 +186,7 @@ export class ${meta.data.repositoryClassName} implements Repository<
169
186
  }),
170
187
  )`}
171
188
 
172
- ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)`).join('\n')}
173
-
174
- this.data.set(newItem.id, newItem)
189
+ this.set(newItem)
175
190
  return newItem
176
191
  }
177
192
 
@@ -205,9 +220,7 @@ export class ${meta.data.repositoryClassName} implements Repository<
205
220
  }),
206
221
  )`}
207
222
 
208
- ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)`).join('\n')}
209
-
210
- this.data.set(newItem.id, newItem)
223
+ this.set(newItem)
211
224
  return newItem`}
212
225
  }
213
226
 
@@ -228,9 +241,7 @@ export class ${meta.data.repositoryClassName} implements Repository<
228
241
  })`}
229
242
 
230
243
  for (const item of items) {
231
- this.data.set(item.id, item)
232
- ${isGenerated ? `if (item.id > this.currentMaxId) { this.currentMaxId = item.id }` : ''}
233
- ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(item.${f.name}, item)`).join('\n')}
244
+ this.set(item)
234
245
  }
235
246
 
236
247
  return items
@@ -286,20 +297,9 @@ export class ${meta.data.repositoryClassName} implements Repository<
286
297
  }),
287
298
  )`}
288
299
 
289
- ${uniqueStringFields
290
- .map((f) => {
291
- return `
292
- if (item.${f.name} !== undefined && existingItem.${f.name} !== item.${f.name}) {
293
- this.uniqueIds.${f.name}.delete(existingItem.${f.name})
294
- if (item.${f.name} !== null) {
295
- this.uniqueIds.${f.name}.set(newItem.${f.name}, newItem)
296
- }
297
- }
298
- `;
299
- })
300
- .join('\n')}
300
+ this.remove(existingItem)
301
+ this.set(newItem)
301
302
 
302
- this.data.set(newItem.id, newItem)
303
303
  return newItem
304
304
  }
305
305
 
@@ -317,11 +317,28 @@ export class ${meta.data.repositoryClassName} implements Repository<
317
317
  await this.db.${meta.data.repository.getMethodFnName}.delete({ where: { ${idField.sourceName}:id } })
318
318
  `}
319
319
 
320
- ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.delete(existingItem.${f.name})`).join('\n')}
321
-
322
- this.data.delete(${meta.types.toBrandedIdTypeFnName}(id))
320
+ this.remove(existingItem)
323
321
  }
324
322
 
323
+ ${relations
324
+ .map((r) => `
325
+ /**
326
+ * Function to retrieve all ${(0, string_1.pluralize)(model.name)} that are related to a ${r.name}
327
+ */
328
+ public ${r.fieldMeta.getByForeignKeyMethodFnName}(id: ${r.meta.types.brandedIdType}): ${model.typeName}[] {
329
+ return this.${r.fieldMeta.getByForeignKeyIdsMethodFnName}(id).map((id) => this.get(id) as ${model.typeName})
330
+ }
331
+
332
+ /**
333
+ * Function to retrieve all ${model.brandedIdType}s that are related to a ${r.name}
334
+ */
335
+ public ${r.fieldMeta.getByForeignKeyIdsMethodFnName}(id: ${r.meta.types.brandedIdType}): ${model.brandedIdType}[] {
336
+ const s = this.${r.name}Map.get(id)
337
+ if (!s) return []
338
+ return Array.from(s)
339
+ }
340
+ `)
341
+ .join('\n')}
325
342
  ${maxLengthStringFields.map((f) => {
326
343
  return `
327
344
  /**
@@ -359,6 +376,50 @@ export class ${meta.data.repositoryClassName} implements Repository<
359
376
  })
360
377
  .join('\n\n')}
361
378
 
379
+ /**
380
+ * Function that adds/updates a given item to the internal data store, indexes, etc.
381
+ */
382
+ private set(item: ${model.typeName}): void {
383
+ ${isGenerated ? `if (item.id > this.currentMaxId) { this.currentMaxId = item.id }` : ''}
384
+ this.data.set(item.id, item)
385
+ ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.set(item.${f.name}, item)`).join('\n')}
386
+
387
+ ${relations
388
+ .map((r) => `
389
+ ${!r.isRequired ? `if (item.${r.name}) {` : ''}
390
+ let ${r.name}Set = this.${r.name}Map.get(item.${r.name})
391
+ if (!${r.name}Set) {
392
+ ${r.name}Set = new Set()
393
+ this.${r.name}Map.set(item.${r.name}, ${r.name}Set)
394
+ }
395
+ ${r.name}Set.add(item.id)
396
+ ${!r.isRequired ? `}` : ''}
397
+ `)
398
+ .join('\n')}
399
+ }
400
+
401
+ /**
402
+ * Function that removes a given item from the internal data store, indexes, etc.
403
+ */
404
+ private remove(item: ${model.typeName}): void {
405
+ this.data.delete(item.id)
406
+ ${uniqueStringFields.map((f) => `this.uniqueIds.${f.name}.delete(item.${f.name})`).join('\n')}
407
+
408
+ ${relations
409
+ .map((r) => `
410
+ ${!r.isRequired ? `if (item.${r.name}) {` : ''}
411
+ const ${r.name}Set = this.${r.name}Map.get(item.${r.name})
412
+ if (${r.name}Set) {
413
+ ${r.name}Set.delete(item.id)
414
+ if (${r.name}Set.size === 0) {
415
+ this.${r.name}Map.delete(item.${r.name})
416
+ }
417
+ }
418
+ ${!r.isRequired ? '}' : ''}
419
+ `)
420
+ .join('\n')}
421
+ }
422
+
362
423
  /**
363
424
  * Utility function that converts a given Database object to a TypeScript model instance
364
425
  */
@@ -90,7 +90,7 @@ function generateFieldData({ field, model, index, exampleMode, }) {
90
90
  case 'relation':
91
91
  return generateFieldDataRelation({ field, model, index, itemCount: exampleMode.itemCount });
92
92
  case 'enum':
93
- return generateFieldDataEnum({ field });
93
+ return generateFieldDataEnum({ field, exampleMode, index });
94
94
  default:
95
95
  throw new types_1.ExhaustiveSwitchCheck(field);
96
96
  }
@@ -106,7 +106,12 @@ function generateFieldDataScalar({ field, model, index, exampleMode, }) {
106
106
  const { hasExample, example } = getFieldExample({ field, model, index, exampleMode });
107
107
  switch (field.typeName) {
108
108
  case 'string':
109
- return `'${quoteSingleQuote(hasExample ? example : generateFieldDataString({ field, model, index }))}'`;
109
+ const result = hasExample ? example : generateFieldDataString({ field, model, index });
110
+ if (result === null)
111
+ return 'null';
112
+ if (result === undefined)
113
+ return 'undefined';
114
+ return `'${quoteSingleQuote(result)}'`;
110
115
  case 'number':
111
116
  return hasExample ? example : generateFieldDataNumber({ field, model, index });
112
117
  case 'boolean':
@@ -162,7 +167,13 @@ function generateFieldDataRelation({ field, model, index, itemCount, }) {
162
167
  const brandingFn = refModelMeta.types.toBrandedIdTypeFnName;
163
168
  return `${brandingFn}(${field.unbrandedTypeName === 'string' ? `'${referenceId}'` : referenceId})`;
164
169
  }
165
- function generateFieldDataEnum({ field }) {
170
+ function generateFieldDataEnum({ field, exampleMode, index, }) {
171
+ if (exampleMode.mode === 'Tuples') {
172
+ if (field.attributes.examples) {
173
+ const example = field.attributes.examples[(index - 1) % field.attributes.examples.length];
174
+ return `'${example}'`;
175
+ }
176
+ }
166
177
  return `'${faker_1.faker.helpers.arrayElement(field.enumerator.values)}'`;
167
178
  }
168
179
  /**
@@ -312,11 +312,19 @@ export type ModelMetaData = {
312
312
  export declare function getModelMetadata({ model }: {
313
313
  model: Schema.ModelCore;
314
314
  }): ModelMetaData;
315
- type FieldMetaData = {
315
+ export type FieldMetaData = {
316
316
  /**
317
317
  * Name of the field as it should appear in the "exposed" properties of the generated type.
318
318
  */
319
319
  tsFieldName: Types.VariableName;
320
+ /**
321
+ * The name of the method that should be used to get all child objects for a given item, e.g. `getItemsForAggregation`.
322
+ */
323
+ getByForeignKeyMethodFnName: Types.Function;
324
+ /**
325
+ * The name of the method that should be used to get all child Ids for a given item, e.g. `getIdsForAggregation`.
326
+ */
327
+ getByForeignKeyIdsMethodFnName: Types.Function;
320
328
  };
321
329
  /**
322
330
  * A collection of hardcoded values shared across multiple generators related to the given field in the model.
@@ -372,4 +380,3 @@ export type EnumMetaData = {
372
380
  export declare function getEnumMetadata({ enumerator: { name, schemaConfig: config }, }: {
373
381
  enumerator: Schema.Enum;
374
382
  }): EnumMetaData;
375
- export {};
package/dist/lib/meta.js CHANGED
@@ -153,24 +153,30 @@ exports.getModelMetadata = getModelMetadata;
153
153
  * A collection of hardcoded values shared across multiple generators related to the given field in the model.
154
154
  */
155
155
  function getFieldMetadata({ field }) {
156
- switch (field.kind) {
157
- case 'id':
158
- return {
159
- tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
160
- };
161
- case 'enum':
162
- return {
163
- tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
164
- };
165
- case 'relation':
166
- return {
167
- tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
168
- };
169
- case 'scalar':
170
- return {
171
- tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
172
- };
173
- }
156
+ const PascalCase = (0, string_1.toPascalCase)(field.name);
157
+ return {
158
+ tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
159
+ getByForeignKeyMethodFnName: Types.toFunction(`get${PascalCase}Items`),
160
+ getByForeignKeyIdsMethodFnName: Types.toFunction(`get${PascalCase}Ids`),
161
+ };
162
+ // switch (field.kind) {
163
+ // case 'id':
164
+ // return {
165
+ // tsFieldName: Types.toVariableName(toCamelCase(field.name)),
166
+ // }
167
+ // case 'enum':
168
+ // return {
169
+ // tsFieldName: Types.toVariableName(toCamelCase(field.name)),
170
+ // }
171
+ // case 'relation':
172
+ // return {
173
+ // tsFieldName: Types.toVariableName(toCamelCase(field.name)),
174
+ // }
175
+ // case 'scalar':
176
+ // return {
177
+ // tsFieldName: Types.toVariableName(toCamelCase(field.name)),
178
+ // }
179
+ // }
174
180
  }
175
181
  exports.getFieldMetadata = getFieldMetadata;
176
182
  /**
@@ -246,6 +246,36 @@ export type FieldId = Prettify<Omit<FieldCore, 'name'> & {
246
246
  */
247
247
  unbrandedTypeName: Types.TypeName;
248
248
  }>;
249
+ /**
250
+ * Utility function to check if a field is an id field.
251
+ */
252
+ export declare const isFieldId: (field: Field) => field is Prettify<Omit<FieldCore, "name"> & {
253
+ kind: 'id';
254
+ /**
255
+ * Name of the field and variable (e.g. id)
256
+ */
257
+ name: string;
258
+ /**
259
+ * Model that this ID field identifies.
260
+ */
261
+ model: ModelCore;
262
+ /**
263
+ * If true, each element is unique.
264
+ * Note: This is ensured by the repository/database
265
+ */
266
+ isUnique: boolean;
267
+ /**
268
+ * True if the id field is an autoIncremented field.
269
+ */
270
+ isGenerated: boolean;
271
+ /**
272
+ * Name of the unbranded TypeScript type of the id field, e.g. string
273
+ *
274
+ * Note: This should only be used in the type generator - all other generators
275
+ * should use the branded type "typeName"!
276
+ */
277
+ unbrandedTypeName: Types.TypeName;
278
+ }>;
249
279
  /**
250
280
  * A relation field, representing a "to-relation" in the schema.
251
281
  */
@@ -269,6 +299,29 @@ export type FieldRelation = Prettify<Omit<FieldCore, 'name'> & {
269
299
  */
270
300
  relationToModel: ModelCore;
271
301
  }>;
302
+ /**
303
+ * Utility function to check if a field is an relation field.
304
+ */
305
+ export declare const isFieldRelation: (field: Field) => field is Prettify<Omit<FieldCore, "name"> & {
306
+ kind: 'relation';
307
+ /**
308
+ * Name of the field and variable (e.g. aggregationId)
309
+ */
310
+ name: string;
311
+ /**
312
+ * Name of the unbranded TypeScript type of the field, e.g. string
313
+ *
314
+ * Note: This should only be used in the type generator - all other generators
315
+ * should use the branded type "typeName"!
316
+ *
317
+ * @internal
318
+ */
319
+ unbrandedTypeName: Types.TypeName;
320
+ /**
321
+ * The referenced model
322
+ */
323
+ relationToModel: ModelCore;
324
+ }>;
272
325
  /**
273
326
  * An enum field, e.g. a field that can only have a value of a specific enum.
274
327
  */
@@ -287,6 +340,24 @@ export type FieldEnum = Prettify<Omit<FieldCore, 'name'> & {
287
340
  */
288
341
  enumerator: Enum;
289
342
  }>;
343
+ /**
344
+ * Utility function to check if a field is an enumid field.
345
+ */
346
+ export declare const isFieldEnum: (field: Field) => field is Prettify<Omit<FieldCore, "name"> & {
347
+ kind: 'enum';
348
+ /**
349
+ * Name of the field and variable (e.g. language)
350
+ */
351
+ name: string;
352
+ /**
353
+ * Name of the TypeScript type of the field, e.g. Language
354
+ */
355
+ typeName: Types.TypeName;
356
+ /**
357
+ * Enum definition of the field.
358
+ */
359
+ enumerator: Enum;
360
+ }>;
290
361
  /**
291
362
  * Definition of an enum, consisting of a name and a list of enum members.
292
363
  */
@@ -1,2 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFieldEnum = exports.isFieldRelation = exports.isFieldId = void 0;
4
+ /**
5
+ * Utility function to check if a field is an id field.
6
+ */
7
+ const isFieldId = (field) => field.kind === 'id';
8
+ exports.isFieldId = isFieldId;
9
+ /**
10
+ * Utility function to check if a field is an relation field.
11
+ */
12
+ const isFieldRelation = (field) => field.kind === 'relation';
13
+ exports.isFieldRelation = isFieldRelation;
14
+ /**
15
+ * Utility function to check if a field is an enumid field.
16
+ */
17
+ const isFieldEnum = (field) => field.kind === 'enum';
18
+ exports.isFieldEnum = isFieldEnum;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {