@postxl/generator 0.14.0 → 0.15.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.
Files changed (31) hide show
  1. package/dist/generator.js +16 -0
  2. package/dist/generators/indices/businesslogicindex.generator.d.ts +9 -0
  3. package/dist/generators/indices/businesslogicindex.generator.js +19 -0
  4. package/dist/generators/indices/businesslogicmodule.generator.d.ts +9 -0
  5. package/dist/generators/indices/businesslogicmodule.generator.js +43 -0
  6. package/dist/generators/indices/businesslogicservice.generator.d.ts +9 -0
  7. package/dist/generators/indices/businesslogicservice.generator.js +32 -0
  8. package/dist/generators/indices/datamockmodule.generator.js +1 -1
  9. package/dist/generators/indices/datamodule.generator.js +22 -21
  10. package/dist/generators/indices/dataservice.generator.js +1 -1
  11. package/dist/generators/indices/seed-template-decoder.generator.js +32 -11
  12. package/dist/generators/models/businesslogic.generator.d.ts +9 -0
  13. package/dist/generators/models/businesslogic.generator.js +172 -0
  14. package/dist/generators/models/react.generator/context.generator.js +5 -3
  15. package/dist/generators/models/react.generator/library.generator.js +1 -1
  16. package/dist/generators/models/react.generator/lookup.generator.js +1 -1
  17. package/dist/generators/models/react.generator/modals.generator.d.ts +1 -1
  18. package/dist/generators/models/react.generator/modals.generator.js +118 -60
  19. package/dist/generators/models/repository.generator.js +18 -2
  20. package/dist/generators/models/route.generator.js +6 -6
  21. package/dist/generators/models/seed.generator.js +5 -2
  22. package/dist/generators/models/stub.generator.js +11 -9
  23. package/dist/generators/models/types.generator.js +32 -2
  24. package/dist/lib/imports.js +12 -3
  25. package/dist/lib/meta.d.ts +101 -1
  26. package/dist/lib/meta.js +21 -0
  27. package/dist/lib/schema/fields.d.ts +5 -1
  28. package/dist/lib/schema/schema.d.ts +26 -1
  29. package/dist/lib/schema/schema.js +1 -1
  30. package/dist/prisma/parse.js +15 -3
  31. package/package.json +2 -2
@@ -24,12 +24,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.generateDeleteModalModelComponent = exports.generateEditModalModelComponent = exports.generateModelCreateModalComponent = void 0;
27
- const serializer_1 = require("../../../lib/serializer");
28
- const StringUtils = __importStar(require("../../../lib/utils/string"));
27
+ const imports_1 = require("../../../lib/imports");
29
28
  const meta_1 = require("../../../lib/meta");
30
29
  const fields_1 = require("../../../lib/schema/fields");
31
- const imports_1 = require("../../../lib/imports");
30
+ const serializer_1 = require("../../../lib/serializer");
32
31
  const types_1 = require("../../../lib/types");
32
+ const StringUtils = __importStar(require("../../../lib/utils/string"));
33
33
  /**
34
34
  * Utility generator that creates a create modal component for a given model.
35
35
  */
@@ -37,10 +37,11 @@ function generateModelCreateModalComponent({ model, meta }) {
37
37
  const { fields } = model;
38
38
  const { react: { components: { modals }, }, trpc, } = meta;
39
39
  return `
40
+ /* eslint-disable @typescript-eslint/no-unused-vars */
40
41
  ${getFormImports({ model, meta })}
41
42
 
42
43
  type CreateInputData = {
43
- ${getFormInputFields({ model, nullable: true })}
44
+ ${getCreateFormInputFields({ model })}
44
45
  }
45
46
 
46
47
  /**
@@ -62,7 +63,7 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
62
63
  validate={(values) => {
63
64
  const errors: FormikErrors<CreateInputData> = {}
64
65
 
65
- ${getFormikValidationCases({ model, includeId: false })}
66
+ ${getFormikValidationCases({ model })}
66
67
 
67
68
  return errors
68
69
  }}
@@ -72,7 +73,7 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
72
73
  const res = await toast.promise(
73
74
  mutation.mutateAsync(
74
75
  {
75
- ${getFormikMutationData({ model, includeId: false })}
76
+ ${getFormikCreateMutationData({ model })}
76
77
  },
77
78
  {
78
79
  async onSuccess() {
@@ -95,15 +96,14 @@ export const ${modals.createComponentName} = ({ show, onHide }: { show: boolean;
95
96
  }
96
97
  }}
97
98
  >
98
- {({ isSubmitting, handleSubmit, isValid }) => (
99
+ {({ isSubmitting, submitForm, isValid }) => (
99
100
  <ModalWithActions actions={
100
101
  <ConfirmButton
101
102
  color="primary"
102
103
  label="Create"
103
104
  fill="fill"
104
- onClick={handleSubmit}
105
+ onClick={submitForm}
105
106
  loading={isSubmitting}
106
- disabled={!isValid}
107
107
  />
108
108
  }>
109
109
  ${getFormFieldComponents({ model })}
@@ -124,12 +124,13 @@ function generateEditModalModelComponent({ model, meta }) {
124
124
  const { fields } = model;
125
125
  const { react: { components }, trpc, } = meta;
126
126
  return `
127
+ /* eslint-disable @typescript-eslint/no-unused-vars */
127
128
  ${getFormImports({ model, meta })}
128
129
 
129
130
 
130
131
  type EditInputData = {
131
132
  ${getFormikFieldName(model.idField.name)}: ${model.brandedIdType}
132
- ${getFormInputFields({ model, nullable: false })}
133
+ ${getEditFormInputFields({ model })}
133
134
  }
134
135
 
135
136
  /**
@@ -168,7 +169,7 @@ export const ${components.modals.editComponentName} = ({
168
169
  validate={(values) => {
169
170
  const errors: { [K in keyof EditInputData]?: string } = {}
170
171
 
171
- ${getFormikValidationCases({ model, includeId: true })}
172
+ ${getFormikValidationCases({ model })}
172
173
 
173
174
  return errors
174
175
  }}
@@ -177,7 +178,7 @@ export const ${components.modals.editComponentName} = ({
177
178
  await toast.promise(
178
179
  mutation.mutateAsync(
179
180
  {
180
- ${getFormikMutationData({ model, includeId: true })}
181
+ ${getEditFormikMutationData({ model })}
181
182
  },
182
183
  {
183
184
  async onSuccess() {
@@ -198,15 +199,14 @@ export const ${components.modals.editComponentName} = ({
198
199
  }
199
200
  }}
200
201
  >
201
- {({ isSubmitting, isValid, handleSubmit }) => (
202
+ {({ isSubmitting, submitForm }) => (
202
203
  <ModalWithActions
203
204
  actions={
204
205
  <ConfirmButton
205
206
  label="Save"
206
207
  color="primary"
207
- onClick={handleSubmit}
208
+ onClick={submitForm}
208
209
  loading={isSubmitting}
209
- disabled={!isValid}
210
210
  />
211
211
  }
212
212
  >
@@ -230,6 +230,7 @@ function generateDeleteModalModelComponent({ model, meta }) {
230
230
  from: meta.types.importPath,
231
231
  });
232
232
  return `
233
+ /* eslint-disable @typescript-eslint/no-unused-vars */
233
234
  import { Formik, FormikErrors } from 'formik'
234
235
  import React, { useCallback } from 'react'
235
236
  import { toast } from 'react-hot-toast'
@@ -335,17 +336,86 @@ function getFormImports({ model, meta }) {
335
336
  ${imports.generate()}
336
337
  `;
337
338
  }
339
+ /**
340
+ * Returns a string containing TypeScript type definition for the input data of the create mutation.
341
+ *
342
+ * NOTE: This function assumes that fields might also be null because we have no data yet.
343
+ */
344
+ function getCreateFormInputFields({ model }) {
345
+ const form = new serializer_1.Serializer();
346
+ for (const field of model.fields.values()) {
347
+ switch (field.kind) {
348
+ case 'id':
349
+ continue;
350
+ case 'relation':
351
+ form.append(`${getFormikFieldName(field.name)}: { id: ${field.relationToModel.brandedIdType} } | null`);
352
+ break;
353
+ case 'scalar':
354
+ form.append(`${getFormikFieldName(field.name)}: ${field.typeName} | null`);
355
+ break;
356
+ case 'enum':
357
+ form.append(`${getFormikFieldName(field.name)}: { id: ${field.typeName} } | null`);
358
+ break;
359
+ default:
360
+ throw new types_1.ExhaustiveSwitchCheck(field);
361
+ }
362
+ }
363
+ return form.print();
364
+ }
365
+ function getFormikCreateMutationData({ model: { fields } }) {
366
+ return fields
367
+ .map((field) => {
368
+ const formikFieldName = getFormikFieldName(field.name);
369
+ switch (field.kind) {
370
+ case 'id':
371
+ case 'scalar':
372
+ // NOTE: Create methods generate the ID field upon submision.
373
+ if (field.kind === 'id') {
374
+ return '';
375
+ }
376
+ if (field.isRequired) {
377
+ return `
378
+ // NOTE: We force unwrap values, because we validate values using Formik.
379
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
380
+ ${field.name}: values.${formikFieldName}!,
381
+ `;
382
+ }
383
+ return `${field.name}: values.${formikFieldName},`;
384
+ case 'relation':
385
+ if (field.isRequired) {
386
+ return `
387
+ // NOTE: We force unwrap values, because we validate values using Formik.
388
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
389
+ ${field.name}: values.${formikFieldName}!.id,
390
+ `;
391
+ }
392
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
393
+ case 'enum':
394
+ if (field.isRequired) {
395
+ return `
396
+ // NOTE: We force unwrap values, because we validate values using Formik.
397
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
398
+ ${field.name}: values.${formikFieldName}!.id,
399
+ `;
400
+ }
401
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
402
+ default:
403
+ throw new types_1.ExhaustiveSwitchCheck(field);
404
+ }
405
+ })
406
+ .join('\n');
407
+ }
338
408
  /**
339
409
  * Returns a string containing the fields of the input data without the ID field.
340
410
  */
341
- function getFormInputFields({ model, nullable }) {
411
+ function getEditFormInputFields({ model }) {
342
412
  const form = new serializer_1.Serializer();
343
413
  for (const field of model.fields.values()) {
344
414
  switch (field.kind) {
345
415
  case 'id':
346
416
  continue;
347
417
  case 'relation':
348
- if (field.isRequired && nullable !== true) {
418
+ if (field.isRequired) {
349
419
  form.append(`${getFormikFieldName(field.name)}: { id: ${field.relationToModel.brandedIdType} }`);
350
420
  }
351
421
  else {
@@ -353,7 +423,7 @@ function getFormInputFields({ model, nullable }) {
353
423
  }
354
424
  break;
355
425
  case 'scalar':
356
- if (field.isRequired && nullable !== true) {
426
+ if (field.isRequired) {
357
427
  form.append(`${getFormikFieldName(field.name)}: ${field.typeName}`);
358
428
  }
359
429
  else {
@@ -361,7 +431,7 @@ function getFormInputFields({ model, nullable }) {
361
431
  }
362
432
  break;
363
433
  case 'enum':
364
- if (field.isRequired && nullable !== true) {
434
+ if (field.isRequired) {
365
435
  form.append(`${getFormikFieldName(field.name)}: { id: ${field.typeName} }`);
366
436
  }
367
437
  else {
@@ -374,6 +444,32 @@ function getFormInputFields({ model, nullable }) {
374
444
  }
375
445
  return form.print();
376
446
  }
447
+ function getEditFormikMutationData({ model: { fields } }) {
448
+ return fields
449
+ .map((field) => {
450
+ const formikFieldName = getFormikFieldName(field.name);
451
+ switch (field.kind) {
452
+ case 'id':
453
+ // NOTE: Edit mutation expects the ID field to identify the object.
454
+ // eslint-disable-next-line no-fallthrough
455
+ case 'scalar':
456
+ return `${field.name}: values.${formikFieldName},`;
457
+ case 'relation':
458
+ if (field.isRequired) {
459
+ return `${field.name}: values.${formikFieldName}.id,`;
460
+ }
461
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
462
+ case 'enum':
463
+ if (field.isRequired) {
464
+ return `${field.name}: values.${formikFieldName}.id,`;
465
+ }
466
+ return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
467
+ default:
468
+ throw new types_1.ExhaustiveSwitchCheck(field);
469
+ }
470
+ })
471
+ .join('\n');
472
+ }
377
473
  /**
378
474
  * Returns a string containing all the components that should appear in the Formik form for this model.
379
475
  */
@@ -462,11 +558,12 @@ function getFormFieldComponents({ model }) {
462
558
  /**
463
559
  * Returns a string containing a list of checks for Formik validation function.
464
560
  */
465
- function getFormikValidationCases({ model, includeId }) {
561
+ function getFormikValidationCases({ model }) {
466
562
  const form = new serializer_1.Serializer();
467
563
  for (const field of model.fields.values()) {
468
- if (!includeId && field.kind === 'id')
564
+ if (field.kind === 'id') {
469
565
  continue;
566
+ }
470
567
  const formikFieldName = getFormikFieldName(field.name);
471
568
  if (field.isRequired) {
472
569
  form.append(`
@@ -478,45 +575,6 @@ function getFormikValidationCases({ model, includeId }) {
478
575
  }
479
576
  return form.print();
480
577
  }
481
- function getFormikMutationData({ model: { fields }, includeId }) {
482
- return fields
483
- .map((field) => {
484
- const formikFieldName = getFormikFieldName(field.name);
485
- switch (field.kind) {
486
- case 'id':
487
- case 'scalar':
488
- if (field.kind === 'id' && !includeId) {
489
- return '';
490
- }
491
- if (field.isRequired) {
492
- return `
493
- // NOTE: We force unwrap values, because we validate values using Formik.
494
- ${field.name}: values.${formikFieldName}!,
495
- `;
496
- }
497
- return `${field.name}: values.${formikFieldName},`;
498
- case 'relation':
499
- if (field.isRequired) {
500
- return `
501
- // NOTE: We force unwrap values, because we validate values using Formik.
502
- ${field.name}: values.${formikFieldName}!.id,
503
- `;
504
- }
505
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
506
- case 'enum':
507
- if (field.isRequired) {
508
- return `
509
- // NOTE: We force unwrap values, because we validate values using Formik.
510
- ${field.name}: values.${formikFieldName}!.id,
511
- `;
512
- }
513
- return `${field.name}: values.${formikFieldName}?.id ? values.${formikFieldName}!.id : null,`;
514
- default:
515
- throw new types_1.ExhaustiveSwitchCheck(field);
516
- }
517
- })
518
- .join('\n');
519
- }
520
578
  // NOTE: This function is separated because many parts rely on the name of the field.
521
579
  function getFormikFieldName(name) {
522
580
  return `formik${StringUtils.toPascalCase(name)}`;
@@ -60,14 +60,16 @@ function generateRepository({ model, meta }) {
60
60
  const idIntInitFn = `this.currentMaxId = (await this.db.${meta.data.repository.getMethodFnName}.aggregate({ _max: { ${idField.sourceName}: true } }))._max.${idField.sourceName} ?? 0`;
61
61
  const imports = imports_1.ImportsGenerator.from(meta.data.repoFilePath)
62
62
  .addImport({
63
- items: [model.typeName, model.brandedIdType, meta.types.toBrandedIdTypeFnName],
63
+ items: [model.typeName, model.brandedIdType, ...(isGenerated ? [meta.types.toBrandedIdTypeFnName] : [])],
64
64
  from: meta.types.importPath,
65
65
  })
66
- .addImport({ items: [meta.types.zodDecoderFnName], from: meta.types.importPath })
67
66
  .addImport({
68
67
  items: [(0, types_1.toClassName)('Repository')],
69
68
  from: (0, types_1.toFileName)(model.schemaConfig.paths.dataLibPath + 'repository.type'),
70
69
  });
70
+ if (!model.attributes.inMemoryOnly) {
71
+ imports.addImport({ items: [meta.types.zodDecoderFnName], from: meta.types.importPath });
72
+ }
71
73
  relations.forEach((r) => {
72
74
  imports.addImport({
73
75
  items: [r.meta.types.brandedIdType],
@@ -76,8 +78,12 @@ function generateRepository({ model, meta }) {
76
78
  });
77
79
  return `
78
80
  import { Injectable, Logger } from '@nestjs/common'
81
+ ${model.attributes.inMemoryOnly
82
+ ? ''
83
+ : `
79
84
  import { DbService, ${model.sourceName} as DbType } from '@${model.schemaConfig.project}/db'
80
85
  import { format, pluralize ${indexes.length === 0 ? '' : ', NestedMap'} } from '@pxl/common'
86
+ `}
81
87
 
82
88
  ${imports.generate()}
83
89
 
@@ -237,6 +243,11 @@ export class ${meta.data.repositoryClassName} implements Repository<
237
243
  ${[...model.fields.values()].map((f) => `${f.sourceName}: item.${f.name}`).join(',\n')}
238
244
  }
239
245
  }
246
+ ${model.attributes.inMemoryOnly
247
+ ? `
248
+ // Non-mocked version is async - so we keep type-compatible signatures for create() and createWithId()
249
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars`
250
+ : ''}
240
251
  public async createWithId(item: Omit<${model.typeName}, '${idField.name}'> & {
241
252
  id: ${model.brandedIdType}
242
253
  }): Promise<${model.typeName}> {
@@ -256,6 +267,11 @@ export class ${meta.data.repositoryClassName} implements Repository<
256
267
  return newItem
257
268
  }
258
269
 
270
+ ${model.attributes.inMemoryOnly
271
+ ? `
272
+ // Non-mocked version is async - so we keep type-compatible signatures for create() and createWithId()
273
+ // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars`
274
+ : ''}
259
275
  public async create(
260
276
  item: Omit<${model.typeName}, 'id'>
261
277
  ): Promise<${model.typeName}> {
@@ -11,7 +11,7 @@ const imports_1 = require("../../lib/imports");
11
11
  function generateRoute({ model, meta }) {
12
12
  const { idField, defaultField } = model;
13
13
  const defaultValueMethod = `
14
- getDefault: procedure.query(({ ctx }) => ctx.dataService.${meta.data.dataServiceName}.defaultValue),
14
+ getDefault: procedure.query(({ ctx }) => ctx.data.${meta.data.dataServiceName}.defaultValue),
15
15
  `;
16
16
  const createMethod = getCreateMethod({ model, meta });
17
17
  const updateMethod = getUpdateMethod({ model, meta });
@@ -38,8 +38,8 @@ export const ${meta.trpc.routerName} = router({
38
38
 
39
39
  get: procedure
40
40
  .input(z.${idField.unbrandedTypeName}().transform(${meta.types.toBrandedIdTypeFnName}))
41
- .query(async ({ input, ctx }) => await ctx.dataService.${meta.data.dataServiceName}.get(input)),
42
- getMap: procedure.query(({ ctx }) => ctx.dataService.${meta.data.dataServiceName}.getAll()),
41
+ .query(({ input, ctx }) => ctx.data.${meta.data.dataServiceName}.get(input)),
42
+ getMap: procedure.query(({ ctx }) => ctx.data.${meta.data.dataServiceName}.getAll()),
43
43
 
44
44
  ${omit(createMethod, model.attributes.skipCreate)}
45
45
  ${omit(updateMethod, model.attributes.skipUpdate)}
@@ -66,7 +66,7 @@ function getCreateMethod({ model: { fields }, meta }) {
66
66
  create: procedure.input(z.object({
67
67
  ${parameters}
68
68
  }))
69
- .mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.create(input)),
69
+ .mutation(({ input, ctx }) => ctx.data.${meta.data.dataServiceName}.create(input)),
70
70
  `;
71
71
  }
72
72
  function getUpdateMethod({ model: { fields }, meta }) {
@@ -79,14 +79,14 @@ function getUpdateMethod({ model: { fields }, meta }) {
79
79
  ${parameters}
80
80
  })
81
81
  )
82
- .mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.update(input)),
82
+ .mutation(({ input, ctx }) => ctx.data.${meta.data.dataServiceName}.update(input)),
83
83
  `;
84
84
  }
85
85
  function getDeleteMethod({ idField, meta }) {
86
86
  return `
87
87
  delete: procedure
88
88
  .input(z.${idField.unbrandedTypeName}().transform(${meta.types.toBrandedIdTypeFnName}))
89
- .mutation(({ input, ctx }) => ctx.dataService.${meta.data.dataServiceName}.delete(input)),
89
+ .mutation(({ input, ctx }) => ctx.data.${meta.data.dataServiceName}.delete(input)),
90
90
  `;
91
91
  }
92
92
  /**
@@ -99,12 +99,15 @@ function generateFieldDataId({ field, index }) {
99
99
  const idModelMeta = (0, meta_1.getModelMetadata)({ model: field.model });
100
100
  return `${idModelMeta.types.toBrandedIdTypeFnName}(${field.unbrandedTypeName === 'string' ? `'${index}'` : index})`;
101
101
  }
102
+ function quoteSingleQuote(str) {
103
+ return str.replace(/'/g, "\\'");
104
+ }
102
105
  function generateFieldDataScalar({ field, model, index, exampleMode, }) {
103
106
  const { hasExample, example } = getFieldExample({ field, model, index, exampleMode });
104
107
  switch (field.typeName) {
105
108
  case 'string': {
106
- if (hasExample) {
107
- return `${example}`;
109
+ if (hasExample && typeof example === 'string') {
110
+ return `'${quoteSingleQuote(example)}'`;
108
111
  }
109
112
  const result = generateFieldDataString({ field, model, index });
110
113
  if (result === null) {
@@ -14,13 +14,7 @@ function generateStub({ model, meta }) {
14
14
  items: [model.typeName, meta.types.toBrandedIdTypeFnName],
15
15
  from: meta.types.importPath,
16
16
  });
17
- for (const relation of (0, fields_1.getRelationFields)(model)) {
18
- const depMeta = (0, meta_1.getModelMetadata)({ model: relation.relationToModel });
19
- imports.addImport({
20
- items: [relation.relationToModel.typeName, depMeta.types.toBrandedIdTypeFnName],
21
- from: meta.types.importPath,
22
- });
23
- }
17
+ const assignments = getAssignmentStatementModel({ fields, imports });
24
18
  return `
25
19
  ${imports.generate()}
26
20
 
@@ -28,7 +22,7 @@ ${imports.generate()}
28
22
  * Utility object containing default values for all fields in a model.
29
23
  */
30
24
  export const ${meta.data.defaultStubConstantName}: ${model.typeName} = {
31
- ${getAssigmentStatementModel({ fields })}
25
+ ${assignments}
32
26
  }
33
27
 
34
28
  /**
@@ -46,7 +40,7 @@ exports.generateStub = generateStub;
46
40
  /**
47
41
  * Return an assignment statement for a model where each field is assigned null or its type's default value.
48
42
  */
49
- function getAssigmentStatementModel({ fields }) {
43
+ function getAssignmentStatementModel({ fields, imports }) {
50
44
  return fields
51
45
  .map((f) => {
52
46
  if (!f.isRequired) {
@@ -58,10 +52,18 @@ function getAssigmentStatementModel({ fields }) {
58
52
  }
59
53
  case 'id': {
60
54
  const idRefMeta = (0, meta_1.getModelMetadata)({ model: f.model });
55
+ imports.addImport({
56
+ items: [idRefMeta.types.toBrandedIdTypeFnName],
57
+ from: idRefMeta.types.importPath,
58
+ });
61
59
  return `${f.name}: ${idRefMeta.types.toBrandedIdTypeFnName}(${(0, fields_1.getDefaultValueForType)(f.unbrandedTypeName)})`;
62
60
  }
63
61
  case 'relation': {
64
62
  const refModelMeta = (0, meta_1.getModelMetadata)({ model: f.relationToModel });
63
+ imports.addImport({
64
+ items: [refModelMeta.types.toBrandedIdTypeFnName],
65
+ from: refModelMeta.types.importPath,
66
+ });
65
67
  return `${f.name}: ${refModelMeta.types.toBrandedIdTypeFnName}(${(0, fields_1.getDefaultValueForType)(f.unbrandedTypeName)})`;
66
68
  }
67
69
  case 'enum': {
@@ -12,6 +12,7 @@ const types_1 = require("../../lib/types");
12
12
  function generateModelTypes({ model, meta }) {
13
13
  const idField = model.idField;
14
14
  const imports = imports_1.ImportsGenerator.from(meta.types.filePath);
15
+ let hasLinkedItems = false;
15
16
  for (const relation of (0, fields_1.getRelationFields)(model)) {
16
17
  if (relation.relationToModel.typeName === model.typeName) {
17
18
  continue;
@@ -19,9 +20,10 @@ function generateModelTypes({ model, meta }) {
19
20
  const refModel = relation.relationToModel;
20
21
  const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
21
22
  imports.addImport({
22
- items: [refMeta.types.toBrandedIdTypeFnName, refModel.brandedIdType],
23
+ items: [refMeta.types.toBrandedIdTypeFnName, refModel.brandedIdType, refMeta.types.typeName],
23
24
  from: refMeta.types.filePath,
24
25
  });
26
+ hasLinkedItems = true;
25
27
  }
26
28
  for (const f of (0, fields_1.getEnumFields)(model)) {
27
29
  const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: f.enumerator });
@@ -42,7 +44,7 @@ ${model.description
42
44
  * ${model.description.split('\n').join('\n * ')}
43
45
  */`
44
46
  : ''}
45
- export type ${model.typeName} = {
47
+ export type ${meta.types.typeName} = {
46
48
  ${model.fields
47
49
  .map((f) => `
48
50
  ${getFieldComment(f)}
@@ -50,6 +52,17 @@ ${f.name}: ${getFieldType(f)}${f.isRequired ? '' : ' | null'}`)
50
52
  .join('\n')}
51
53
  }
52
54
 
55
+ ${!hasLinkedItems
56
+ ? ``
57
+ : `
58
+ export type ${meta.types.linkedTypeName} = {
59
+ ${model.fields
60
+ .map((f) => `
61
+ ${getFieldComment(f)}
62
+ ${getLinkedFieldType(f)}${f.isRequired ? '' : ' | null'}`)
63
+ .join('\n')}
64
+ }`}
65
+
53
66
  /**
54
67
  * Branded Id type that should be used to identify an instance of this model.
55
68
  */
@@ -112,3 +125,20 @@ function getFieldType(f) {
112
125
  throw new types_1.ExhaustiveSwitchCheck(f);
113
126
  }
114
127
  }
128
+ /**
129
+ * Converts a field to a TypeScript type definition with linked fields.
130
+ */
131
+ function getLinkedFieldType(f) {
132
+ switch (f.kind) {
133
+ case 'enum':
134
+ return `${f.name}: ${f.typeName}`;
135
+ case 'relation':
136
+ return `${f.relatedModelBacklinkFieldName}: ${f.relationToModel.typeName}`;
137
+ case 'id':
138
+ return `${f.name}: ${f.model.brandedIdType}`;
139
+ case 'scalar':
140
+ return `${f.name}: ${f.typeName}`;
141
+ default:
142
+ throw new types_1.ExhaustiveSwitchCheck(f);
143
+ }
144
+ }
@@ -41,15 +41,24 @@ class ImportsGenerator {
41
41
  * Returns the TypeScript import statement.
42
42
  */
43
43
  generate() {
44
- const stetements = Object.entries(this._imports)
45
- .sort(([a], [b]) => a.localeCompare(b))
44
+ const statements = Object.entries(this._imports)
45
+ .sort(([a], [b]) => {
46
+ // //in case a or b start with "@", they will be sorted first
47
+ // if (a.startsWith('@') && !b.startsWith('@')) {
48
+ // return -1
49
+ // }
50
+ // if (!a.startsWith('@') && b.startsWith('@')) {
51
+ // return 1
52
+ // }
53
+ return a.localeCompare(b);
54
+ })
46
55
  .map(([path, items]) => {
47
56
  const alphabeticallySortedItems = Array.from(items).sort((a, b) => a.localeCompare(b));
48
57
  // NOTE: You cannot remove imports and we check that there's at least one imported item for every path.
49
58
  return `import { ${alphabeticallySortedItems.join(', ')} } from '${path}'`;
50
59
  })
51
60
  .join('\n');
52
- return stetements;
61
+ return statements;
53
62
  }
54
63
  }
55
64
  exports.ImportsGenerator = ImportsGenerator;