@postxl/generator 0.33.4 → 0.35.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.
Files changed (63) hide show
  1. package/dist/generator.js +40 -22
  2. package/dist/generators/indices/businesslogic-actiontypes.generator.d.ts +9 -0
  3. package/dist/generators/indices/businesslogic-actiontypes.generator.js +39 -0
  4. package/dist/generators/indices/businesslogic-update-index.generator.d.ts +9 -0
  5. package/dist/generators/indices/businesslogic-update-index.generator.js +20 -0
  6. package/dist/generators/indices/businesslogic-update-module.generator.d.ts +9 -0
  7. package/dist/generators/indices/businesslogic-update-module.generator.js +69 -0
  8. package/dist/generators/indices/businesslogic-update-service.generator.d.ts +9 -0
  9. package/dist/generators/indices/businesslogic-update-service.generator.js +34 -0
  10. package/dist/generators/indices/businesslogic-view-index.generator.d.ts +9 -0
  11. package/dist/generators/indices/businesslogic-view-index.generator.js +19 -0
  12. package/dist/generators/indices/{businesslogicindex.generator.d.ts → businesslogic-view-module.generator.d.ts} +2 -2
  13. package/dist/generators/indices/{businesslogicmodule.generator.js → businesslogic-view-module.generator.js} +22 -26
  14. package/dist/generators/indices/{businesslogicservice.generator.d.ts → businesslogic-view-service.generator.d.ts} +1 -1
  15. package/dist/generators/indices/{businesslogicservice.generator.js → businesslogic-view-service.generator.js} +9 -10
  16. package/dist/generators/indices/{datamockmodule.generator.js → datamock-module.generator.js} +8 -16
  17. package/dist/generators/indices/datamocker.generator.js +3 -7
  18. package/dist/generators/indices/datamodule.generator.js +7 -13
  19. package/dist/generators/indices/{businesslogicmodule.generator.d.ts → dispatcher-service.generator.d.ts} +2 -2
  20. package/dist/generators/indices/dispatcher-service.generator.js +81 -0
  21. package/dist/generators/indices/seed-migration.generator.d.ts +9 -0
  22. package/dist/generators/indices/seed-migration.generator.js +35 -0
  23. package/dist/generators/indices/seed-service.generator.d.ts +1 -1
  24. package/dist/generators/indices/seed-service.generator.js +327 -123
  25. package/dist/generators/indices/seed-template-decoder.generator.js +22 -6
  26. package/dist/generators/indices/{seed.generator.d.ts → seeddata-type.generator.d.ts} +2 -2
  27. package/dist/generators/indices/seeddata-type.generator.js +42 -0
  28. package/dist/generators/indices/types.generator.d.ts +1 -1
  29. package/dist/generators/indices/types.generator.js +8 -6
  30. package/dist/generators/models/businesslogic-update.generator.d.ts +10 -0
  31. package/dist/generators/models/businesslogic-update.generator.js +243 -0
  32. package/dist/generators/models/businesslogic-view.generator.d.ts +10 -0
  33. package/dist/generators/models/businesslogic-view.generator.js +253 -0
  34. package/dist/generators/models/react.generator/modals.generator.js +20 -4
  35. package/dist/generators/models/repository.generator.d.ts +9 -0
  36. package/dist/generators/models/repository.generator.js +496 -148
  37. package/dist/generators/models/route.generator.js +45 -54
  38. package/dist/generators/models/seed.generator.js +6 -2
  39. package/dist/generators/models/types.generator.js +60 -13
  40. package/dist/lib/attributes.d.ts +32 -2
  41. package/dist/lib/imports.d.ts +23 -2
  42. package/dist/lib/imports.js +19 -1
  43. package/dist/lib/meta.d.ts +287 -34
  44. package/dist/lib/meta.js +87 -16
  45. package/dist/lib/schema/fields.d.ts +7 -4
  46. package/dist/lib/schema/fields.js +11 -4
  47. package/dist/lib/schema/schema.d.ts +32 -6
  48. package/dist/lib/schema/types.d.ts +4 -0
  49. package/dist/lib/utils/ast.d.ts +29 -0
  50. package/dist/lib/utils/ast.js +23 -0
  51. package/dist/lib/utils/jsdoc.d.ts +1 -1
  52. package/dist/lib/utils/jsdoc.js +8 -5
  53. package/dist/lib/utils/string.js +2 -1
  54. package/dist/prisma/attributes.js +45 -26
  55. package/dist/prisma/parse.js +44 -11
  56. package/package.json +1 -1
  57. package/dist/generators/indices/businesslogicindex.generator.js +0 -19
  58. package/dist/generators/indices/seed.generator.js +0 -17
  59. package/dist/generators/indices/testdataservice.generator.d.ts +0 -9
  60. package/dist/generators/indices/testdataservice.generator.js +0 -78
  61. package/dist/generators/models/businesslogic.generator.d.ts +0 -9
  62. package/dist/generators/models/businesslogic.generator.js +0 -259
  63. /package/dist/generators/indices/{datamockmodule.generator.d.ts → datamock-module.generator.d.ts} +0 -0
@@ -13,21 +13,20 @@ const string_1 = require("../../lib/utils/string");
13
13
  */
14
14
  function generateRepository({ model, meta }) {
15
15
  const { idField } = model;
16
- const imports = imports_1.ImportsGenerator.from(meta.data.repoFilePath)
17
- .addImport({
18
- items: [model.typeName, model.brandedIdType],
19
- from: meta.types.importPath,
20
- })
21
- .addImport({
22
- items: [(0, types_1.toClassName)('Repository')],
23
- // TODO: Unify DataLib Imports https://github.com/PostXL/PostXL/issues/344!
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,
16
+ const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
17
+ const imports = imports_1.ImportsGenerator.from(meta.data.repoFilePath).addImports({
18
+ [schemaMeta.data.repositoryTypeFilePath]: schemaMeta.data.repositoryTypeName,
19
+ [meta.types.importPath]: [
20
+ model.typeName,
21
+ model.brandedIdType,
22
+ meta.types.toBrandedIdTypeFnName,
23
+ meta.types.dto.create,
24
+ meta.types.dto.update,
25
+ meta.types.dto.upsert,
26
+ ],
27
+ [schemaMeta.actions.importPath]: [schemaMeta.actions.actionExecutionInterface],
29
28
  });
30
- // NOTE: We first generate different parts of the code responisble for a particular task
29
+ // NOTE: We first generate different parts of the code responsible for a particular task
31
30
  // and then we combine them into the final code block.
32
31
  //
33
32
  // Based on the model, the repository is generated slightly differently:
@@ -40,6 +39,10 @@ function generateRepository({ model, meta }) {
40
39
  // 4.) max length string fields are ensured to not exceed their max length,
41
40
  // 5.) index for fields marked with index attribute,
42
41
  // 6.) relations are indexed by the foreign key.
42
+ //
43
+ // The repository is generated differently based on whether the model is stored in the database or in memory.
44
+ // If the model is stored in the database, all CUD operations will result in a database call. If the model is
45
+ // stored in memory, all CUD operations will be performed in memory without any database calls.
43
46
  const idBlocks = generateIdBlocks({ model, meta });
44
47
  const defaultValueBlocks = generateDefaultBlocks({ model, meta });
45
48
  const uniqueStringFieldsBlocks = generateUniqueFieldsBlocks({ model, meta });
@@ -51,6 +54,7 @@ function generateRepository({ model, meta }) {
51
54
  mainBlocks = _generateMainBuildingBlocks_InMemoryOnly({
52
55
  model,
53
56
  meta,
57
+ schemaMeta,
54
58
  blocks: {
55
59
  uniqueStringFieldsBlocks,
56
60
  defaultValueBlocks,
@@ -64,6 +68,7 @@ function generateRepository({ model, meta }) {
64
68
  mainBlocks = generateMainBuildingBlocks_InDatabase({
65
69
  model,
66
70
  meta,
71
+ schemaMeta,
67
72
  imports,
68
73
  blocks: {
69
74
  uniqueStringFieldsBlocks,
@@ -132,36 +137,6 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
132
137
  public count(): number {
133
138
  return this.data.size
134
139
  }
135
-
136
- /**${(0, jsdoc_1.convertToJsDocComments)([
137
- `Checks that item has the ${idField.name} field.`,
138
- `In case none exists, ${idBlocks.verifyFunctionComment}`,
139
- uniqueStringFieldsBlocks.verifyFunctionComment,
140
- maxLengthBlocks.verifyFunctionComment,
141
- ])}
142
- */
143
-
144
- private verifyItem(item: ${idBlocks.verifyFunctionParameterType}): ${model.typeName} {
145
- ${idBlocks.verifyCode}
146
-
147
- ${maxLengthBlocks.verifyCode.join('\n')}
148
-
149
- ${uniqueStringFieldsBlocks.verifyCode.join('\n')}
150
-
151
- return {
152
- ${idField.name},
153
- ${[...model.fields.values()]
154
- .filter((f) => f.kind !== 'id')
155
- .map((f) => `${f.name}: item.${f.name}`)
156
- .join(',\n')}
157
- }
158
- }
159
-
160
- private toCreateItem(item: ${model.typeName}) {
161
- return {
162
- ${[...model.fields.values()].map((f) => `${f.sourceName}: item.${f.name}`).join(',\n')}
163
- }
164
- }
165
140
 
166
141
  ${mainBlocks.createCode}
167
142
 
@@ -169,8 +144,16 @@ export class ${meta.data.repositoryClassName} implements Repository<${model.type
169
144
 
170
145
  ${mainBlocks.updateCode}
171
146
 
147
+ ${mainBlocks.updateManyCode}
148
+
149
+ ${mainBlocks.upsertCode}
150
+
151
+ ${mainBlocks.upsertManyCode}
152
+
172
153
  ${mainBlocks.deleteCode}
173
154
 
155
+ ${mainBlocks.deleteManyCode}
156
+
174
157
  ${relationsBlocks.getterFunctions.join('\n')}
175
158
 
176
159
  ${maxLengthBlocks.ensureMaxLengthFunctions.join('\n')}
@@ -238,65 +221,211 @@ function _generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
238
221
  return Promise.resolve()
239
222
  }`,
240
223
  reInitCode: `
241
- public async reInit(data: ${model.typeName}[]): Promise<void> {
224
+ public async reInit({items, execution}: ${methodTypeSignatures.createMany.parameters[0]}): Promise<void> {
242
225
  await this.init()
243
- await this.createMany(data)
226
+ await this.createMany({items, execution})
244
227
  }`,
245
228
  deleteAllCode: `
246
229
  public async deleteAll(): Promise<void> {
247
230
  return this.init()
248
231
  }`,
249
232
  createCode: `
233
+ ${(0, jsdoc_1.toJsDocComment)([
234
+ `Checks that item has the ${model.idField.name} field.`,
235
+ `In case none exists, ${blocks.idBlocks.verifyFunctionComment}`,
236
+ blocks.uniqueStringFieldsBlocks.verifyFunctionComment,
237
+ blocks.maxLengthBlocks.verifyFunctionComment,
238
+ ])}
239
+ private verifyItem(
240
+ item: ${blocks.idBlocks.verifyFunctionParameterType}
241
+ ): ${model.name} {
242
+ ${blocks.idBlocks.verifyCode}
243
+
244
+ ${blocks.maxLengthBlocks.verifyCode.join('\n')}
245
+
246
+ ${blocks.uniqueStringFieldsBlocks.verifyCode.join('\n')}
247
+
248
+ return {
249
+ ${model.idField.name},
250
+ ${model.fields
251
+ .filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
252
+ .map((f) => `${f.name}: item.${f.name}`)
253
+ .join(',\n')},
254
+ ${model.createdAtField ? `${model.createdAtField.sourceName}: new Date(),` : ''}
255
+ ${model.updatedAtField ? `${model.updatedAtField.sourceName}: new Date(),` : ''}
256
+ }
257
+ }
258
+
259
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.create.jsDoc)}
250
260
  // Non-mocked version is async - so we keep type-compatible signatures for create() and createWithId()
251
261
  // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
252
- public async create(item: ${methodTypeSignatures.create.parameters[0]}): ${methodTypeSignatures.create.returnType} {
253
- const newItem = await Promise.resolve(this.verifyItem(item))
254
-
255
- this.set(newItem)
256
- return newItem
262
+ public async create(
263
+ { item, execution }: ${methodTypeSignatures.create.parameters[0]}
264
+ ): ${methodTypeSignatures.create.returnType} {
265
+ const mutationId = await execution.startCreateMutation({
266
+ model: '${meta.actions.actionScopeConstType}',
267
+ createObject: item
268
+ })
269
+
270
+ try {
271
+ const newItem = await Promise.resolve(this.verifyItem(item))
272
+
273
+ this.set(newItem)
274
+ await execution.finishCreateMutation({ mutationId, createdObject: newItem, entityId: newItem.id })
275
+ return newItem
276
+ } catch (error) {
277
+ await execution.errorMutation({ mutationId, error })
278
+ throw error
279
+ }
257
280
  }`,
258
281
  createManyCode: `
259
- public async createMany(items: ${blocks.idBlocks.verifyFunctionParameterType}[]) {
260
- const newItems = items.map((item) => this.verifyItem(item))
261
-
262
- await Promise.resolve()
282
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.createMany.jsDoc)}
283
+ public async createMany(
284
+ { items, execution }: ${methodTypeSignatures.createMany.parameters[0]}
285
+ ): ${methodTypeSignatures.createMany.returnType} {
286
+ const mutationId = await execution.startCreateManyMutation({
287
+ model: '${meta.actions.actionScopeConstType}',
288
+ createObjects: items
289
+ })
263
290
 
264
- for (const item of newItems) {
265
- this.set(item)
291
+ try {
292
+ const newItems = items.map((item) => this.verifyItem(item))
293
+
294
+ await Promise.resolve()
295
+
296
+ for (const item of newItems) {
297
+ this.set(item)
298
+ }
299
+
300
+ await execution.finishCreateManyMutation({
301
+ mutationId,
302
+ createdObjects: newItems,
303
+ entityIds: newItems.map((i) => i.id),
304
+ })
305
+ return newItems
306
+ } catch (error) {
307
+ await execution.errorMutation({ mutationId, error })
308
+ throw error
266
309
  }
267
-
268
- return newItems
269
310
  }`,
270
- // prettier-ignore
271
311
  updateCode: `
272
- public async update(item: ${methodTypeSignatures.update.parameters[0]}): ${methodTypeSignatures.update.returnType} {
312
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.update.jsDoc)}
313
+ public async update(
314
+ { item, execution }: ${methodTypeSignatures.update.parameters[0]}
315
+ ): ${methodTypeSignatures.update.returnType} {
273
316
  const existingItem = this.get(item.id)
274
317
 
275
318
  if (!existingItem) {
276
319
  throw new Error(\`Could not update ${meta.userFriendlyName} with id \${item.id}. Not found!\`)
277
320
  }
321
+ const mutationId = await execution.startUpdateMutation({
322
+ model: 'post',
323
+ entityId: item.id,
324
+ sourceObject: existingItem,
325
+ updateObject: item,
326
+ })
327
+ try {
328
+ ${blocks.maxLengthBlocks.updateCode.join('\n')}
278
329
 
279
- ${blocks.maxLengthBlocks.updateCode.join('\n')}
280
-
281
- ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
282
-
283
- const newItem = await Promise.resolve({ ...existingItem, ...item })
284
-
285
- this.remove(existingItem)
286
- this.set(newItem)
287
-
288
- return newItem
330
+ ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
331
+
332
+ const newItem = await Promise.resolve({ ...existingItem, ...item })
333
+ ${model.updatedAtField ? `newItem.${model.updatedAtField.name} = new Date()` : ''}
334
+
335
+ this.remove(existingItem)
336
+ this.set(newItem)
337
+
338
+ await execution.finishUpdateMutation({ mutationId, updatedObject: newItem })
339
+ return newItem
340
+ } catch (error) {
341
+ await execution.errorMutation({ mutationId, error })
342
+ throw error
343
+ }
344
+ }`,
345
+ updateManyCode: `
346
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.updateMany.jsDoc)}
347
+ public async updateMany(
348
+ { items, execution }: ${methodTypeSignatures.updateMany.parameters[0]}
349
+ ): ${methodTypeSignatures.updateMany.returnType} {
350
+ const result: ${model.typeName}[] = []
351
+ for (const item of items) {
352
+ try {
353
+ const updated = await this.update({ item, execution })
354
+ result.push(updated)
355
+ } catch {
356
+ /* empty */
357
+ }
358
+ }
359
+ return result
360
+ }`,
361
+ upsertCode: `
362
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsert.jsDoc)}
363
+ public async upsert(
364
+ { item, execution }: ${methodTypeSignatures.upsert.parameters[0]}
365
+ ): ${methodTypeSignatures.upsert.returnType} {
366
+ const existingItem = item.${model.idField.name} ? this.get(item.${model.idField.name}) : null
367
+ if (existingItem) {
368
+ return this.update({ item: item as ${meta.types.dto.update}, execution })
369
+ } else {
370
+ return this.create({ item: item as ${meta.types.dto.create}, execution })
371
+ }
372
+ }`,
373
+ upsertManyCode: `
374
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsertMany.jsDoc)}
375
+ public async upsertMany(
376
+ { items, execution }: ${methodTypeSignatures.upsertMany.parameters[0]}
377
+ ): ${methodTypeSignatures.upsertMany.returnType} {
378
+ const result: ${model.typeName}[] = []
379
+ for (const item of items) {
380
+ try {
381
+ const updated = await this.upsert({ item, execution })
382
+ result.push(updated)
383
+ } catch {
384
+ /* empty */
385
+ }
386
+ }
387
+ return result
289
388
  }`,
290
389
  deleteCode: `
291
- public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
390
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.delete.jsDoc)}
391
+ public async delete(
392
+ { id, execution }: ${methodTypeSignatures.delete.parameters[0]}
393
+ ): ${methodTypeSignatures.delete.returnType} {
292
394
  const existingItem = this.get(id)
293
395
  if (!existingItem) {
294
396
  throw new Error(\`Could not delete ${model.typeName} with id \${id}. Not found!\`)
295
397
  }
296
-
297
- await Promise.resolve()
398
+ const mutationId = await execution.startDeleteMutation({
399
+ model: '${meta.actions.actionScopeConstType}',
400
+ entityId: id,
401
+ sourceObject: existingItem,
402
+ })
403
+ try {
404
+ await Promise.resolve()
298
405
 
299
- this.remove(existingItem)
406
+ this.remove(existingItem)
407
+ await execution.finishDeleteMutation({ mutationId })
408
+ return id
409
+ } catch (error) {
410
+ await execution.errorMutation({ mutationId, error })
411
+ throw error
412
+ }
413
+ }`,
414
+ deleteManyCode: `
415
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.deleteMany.jsDoc)}
416
+ public async deleteMany(
417
+ { ids, execution }: ${methodTypeSignatures.deleteMany.parameters[0]}
418
+ ): ${methodTypeSignatures.deleteMany.returnType} {
419
+ const deletedIds: ${model.brandedIdType}[] = []
420
+ for (const id of ids) {
421
+ try {
422
+ await this.delete({ id, execution })
423
+ deletedIds.push(id)
424
+ } catch {
425
+ /* empty */
426
+ }
427
+ }
428
+ return deletedIds
300
429
  }`,
301
430
  databaseDecoderCode: ``,
302
431
  };
@@ -307,15 +436,10 @@ function _generateMainBuildingBlocks_InMemoryOnly({ model, meta, blocks, }) {
307
436
  function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }) {
308
437
  const decoderFunctionName = meta.data.repository.decoderFnName;
309
438
  const { idField } = model;
310
- imports
311
- .addImport({ items: [meta.types.zodDecoderFnName], from: meta.types.importPath })
312
- .addImport({
313
- items: [`DbService`, `${model.sourceName} as DbType`].map(types_1.toTypeName),
314
- from: (0, types_1.toFileName)('@pxl/db'),
315
- })
316
- .addImport({
317
- items: [`format`, `pluralize`].map(types_1.toTypeName),
318
- from: (0, types_1.toFileName)('@pxl/common'),
439
+ imports.addImports({
440
+ [meta.types.importPath]: [meta.types.zodDecoderFnNames.fromDatabase],
441
+ [(0, types_1.toFileName)('@pxl/db')]: [`DbService`, `${model.sourceName} as DbType`].map(types_1.toTypeName),
442
+ [(0, types_1.toFileName)('@pxl/common')]: [`format`, `pluralize`].map(types_1.toTypeName),
319
443
  });
320
444
  const dbTableName = [model.sourceSchemaName, model.sourceName]
321
445
  .filter((s) => s != null)
@@ -351,7 +475,9 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
351
475
  ${blocks.indexBlocks.initLogCode.join('\n')}
352
476
  }`,
353
477
  reInitCode: `
354
- public async reInit(data: ${model.typeName}[]): Promise<void> {
478
+ public async reInit(
479
+ { items, execution }: ${methodTypeSignatures.createMany.parameters[0]}
480
+ ): Promise<void> {
355
481
  if (!this.db.useE2ETestDB) {
356
482
  const errorMsg =
357
483
  'ReInit() shall only be called in tests using MockRepositories or in DB configured for E2E tests!'
@@ -359,7 +485,7 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
359
485
  throw new Error(errorMsg)
360
486
  }
361
487
 
362
- await this.db.runOnlyOnTestDb(() => this.db.${meta.data.repository.getMethodFnName}.createMany({ data: data.map((i) => this.toCreateItem(i)) }))
488
+ await this.db.runOnlyOnTestDb(() => this.createMany({ items, execution }))
363
489
 
364
490
  return this.init()
365
491
  }`,
@@ -370,81 +496,247 @@ function generateMainBuildingBlocks_InDatabase({ model, meta, imports, blocks, }
370
496
  return this.init()
371
497
  }
372
498
  `,
499
+ // prettier-ignore
373
500
  createCode: `
374
- public async create(item: ${methodTypeSignatures.create.parameters[0]}): ${methodTypeSignatures.create.returnType} {
375
- const newItem = this.${decoderFunctionName}(
376
- await this.db.${meta.data.repository.getMethodFnName}.create({
377
- data: this.toCreateItem(this.verifyItem(item)),
378
- }),
379
- )
501
+ ${(0, jsdoc_1.toJsDocComment)([
502
+ `Checks that item has the ${idField.name} field.`,
503
+ `In case none exists, ${blocks.idBlocks.verifyFunctionComment}`,
504
+ blocks.uniqueStringFieldsBlocks.verifyFunctionComment,
505
+ blocks.maxLengthBlocks.verifyFunctionComment,
506
+ ])}
507
+ private verifyItem(
508
+ item: ${blocks.idBlocks.verifyFunctionParameterType}
509
+ ): ${blocks.idBlocks.createFunctionParameterType} {
510
+ ${blocks.idBlocks.verifyCode}
511
+
512
+ ${blocks.maxLengthBlocks.verifyCode.join('\n')}
513
+
514
+ ${blocks.uniqueStringFieldsBlocks.verifyCode.join('\n')}
515
+
516
+ return {
517
+ ${idField.name},
518
+ ${model.fields
519
+ .filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
520
+ .map((f) => `${f.name}: item.${f.name}`)
521
+ .join(',\n')}
522
+ }
523
+ }
380
524
 
381
- this.set(newItem)
382
- return newItem
525
+ private toCreateItem(item: ${blocks.idBlocks.createFunctionParameterType}) {
526
+ return {
527
+ ${model.fields
528
+ .filter((f) => !f.attributes.isReadonly || f.kind === 'id')
529
+ .map((f) => `${f.sourceName}: item.${f.name}`)
530
+ .join(',\n')},
531
+ }
532
+ }
533
+
534
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.create.jsDoc)}
535
+ public async create(
536
+ { item, execution }: ${methodTypeSignatures.create.parameters[0]}
537
+ ): ${methodTypeSignatures.create.returnType} {
538
+ const mutationId = await execution.startCreateMutation({
539
+ model: '${meta.actions.actionScopeConstType}',
540
+ createObject: item
541
+ })
542
+
543
+ try {
544
+ const newItem = this.${decoderFunctionName}(
545
+ await this.db.${meta.data.repository.getMethodFnName}.create({
546
+ data: this.toCreateItem(this.verifyItem(item)),
547
+ }),
548
+ )
549
+
550
+ this.set(newItem)
551
+ await execution.finishCreateMutation({ mutationId, createdObject: newItem, entityId: newItem.id })
552
+ return newItem
553
+ } catch (error) {
554
+ await execution.errorMutation({ mutationId, error })
555
+ throw error
556
+ }
383
557
  }`,
558
+ // prettier-ignore
384
559
  createManyCode: `
385
- public async createMany(items: ${blocks.idBlocks.verifyFunctionParameterType}[]) {
386
- const newItems = items.map((item) => this.verifyItem(item))
387
-
388
- await this.db.${meta.data.repository.getMethodFnName}.createMany({ data: newItems.map(i => this.toCreateItem(i)) })
560
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.createMany.jsDoc)}
561
+ public async createMany(
562
+ {items, execution}: ${methodTypeSignatures.createMany.parameters[0]}
563
+ ): ${methodTypeSignatures.createMany.returnType} {
564
+ const mutationId = await execution.startCreateManyMutation({
565
+ model: '${meta.actions.actionScopeConstType}',
566
+ createObjects: items
567
+ })
389
568
 
390
- for (const item of newItems) {
391
- this.set(item)
569
+ try {
570
+ const newItems = items.map((item) => this.verifyItem(item))
571
+
572
+ await this.db.${meta.data.repository.getMethodFnName}.createMany({ data: newItems.map(i => this.toCreateItem(i)) })
573
+
574
+ const dbItems = await this.db.${meta.data.repository.getMethodFnName}.findMany({
575
+ where: {
576
+ ${model.idField.sourceName}: { in: newItems.map(i => i.${model.idField.name}) }
577
+ }
578
+ })
579
+
580
+ const result = dbItems.map((item) => this.${decoderFunctionName}(item))
581
+
582
+ for (const item of result) {
583
+ this.set(item)
584
+ }
585
+
586
+ await execution.finishCreateManyMutation({
587
+ mutationId,
588
+ createdObjects: result,
589
+ entityIds: newItems.map((i) => i.id),
590
+ })
591
+ return result
592
+ } catch (error) {
593
+ await execution.errorMutation({ mutationId, error })
594
+ throw error
392
595
  }
393
-
394
- return newItems
395
596
  }`,
396
- // prettier-ignore
397
597
  updateCode: `
398
- public async update(item: ${methodTypeSignatures.update.parameters[0]}): ${methodTypeSignatures.update.returnType} {
598
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.update.jsDoc)}
599
+ public async update(
600
+ { item, execution }: ${methodTypeSignatures.update.parameters[0]}
601
+ ): ${methodTypeSignatures.update.returnType} {
399
602
  const existingItem = this.get(item.${idField.name})
400
603
  if (!existingItem) {
401
604
  throw new Error(\`Could not update ${meta.userFriendlyName} with id \${item.id}. Not found!\`)
402
605
  }
403
606
 
404
- ${blocks.maxLengthBlocks.updateCode.join('\n')}
405
-
406
- ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
407
-
408
- const newItem = this.${decoderFunctionName}(
409
- await this.db.${meta.data.repository.getMethodFnName}.update({
410
- where: {
411
- ${idField.sourceName}: item.${idField.name},
412
- },
413
- data: {
414
- ${[...model.fields.values()]
415
- .filter((f) => f.kind !== 'id')
607
+ const mutationId = await execution.startUpdateMutation({
608
+ model: 'post',
609
+ entityId: item.id,
610
+ sourceObject: existingItem,
611
+ updateObject: item,
612
+ })
613
+ try {
614
+ ${blocks.maxLengthBlocks.updateCode.join('\n')}
615
+
616
+ ${blocks.uniqueStringFieldsBlocks.updateCode.join('\n')}
617
+
618
+ const newItem = this.${decoderFunctionName}(
619
+ await this.db.${meta.data.repository.getMethodFnName}.update({
620
+ where: {
621
+ ${idField.sourceName}: item.${idField.name},
622
+ },
623
+ data: {
624
+ ${[...model.fields.values()]
625
+ .filter((f) => f.kind !== 'id' && !f.attributes.isReadonly)
416
626
  .map((f) => f.isRequired
417
627
  ? `${f.sourceName}: item.${f.name} ?? existingItem.${f.name}`
418
628
  : `${f.sourceName}: item.${f.name}`)
419
629
  .join(',\n')}
420
- },
421
- }),
422
- )
423
-
424
- this.remove(existingItem)
425
- this.set(newItem)
426
-
427
- return newItem
630
+ },
631
+ }),
632
+ )
633
+
634
+ this.remove(existingItem)
635
+ this.set(newItem)
636
+
637
+ await execution.finishUpdateMutation({ mutationId, updatedObject: newItem })
638
+ return newItem
639
+ } catch (error) {
640
+ await execution.errorMutation({ mutationId, error })
641
+ throw error
642
+ }
643
+ }`,
644
+ updateManyCode: `
645
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.updateMany.jsDoc)}
646
+ public async updateMany(
647
+ { items, execution }: ${methodTypeSignatures.updateMany.parameters[0]}
648
+ ): ${methodTypeSignatures.updateMany.returnType} {
649
+ const result: ${model.typeName}[] = []
650
+ for (const item of items) {
651
+ try {
652
+ const updated = await this.update({ item, execution })
653
+ result.push(updated)
654
+ } catch {
655
+ /* empty */
656
+ }
657
+ }
658
+ return result
659
+ }`,
660
+ upsertCode: `
661
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsert.jsDoc)}
662
+ public async upsert(
663
+ { item, execution }: ${methodTypeSignatures.upsert.parameters[0]}
664
+ ): ${methodTypeSignatures.upsert.returnType} {
665
+ const existingItem = item.${model.idField.name} ? this.get(item.${model.idField.name}) : null
666
+ if (existingItem) {
667
+ return this.update({ item: item as ${meta.types.dto.update}, execution })
668
+ } else {
669
+ return this.create({ item: item as ${meta.types.dto.create}, execution })
670
+ }
671
+ }`,
672
+ upsertManyCode: `
673
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.upsertMany.jsDoc)}
674
+ public async upsertMany(
675
+ { items, execution }: ${methodTypeSignatures.upsertMany.parameters[0]}
676
+ ): ${methodTypeSignatures.upsertMany.returnType} {
677
+ const result: ${model.typeName}[] = []
678
+ for (const item of items) {
679
+ try {
680
+ const updated = await this.upsert({ item, execution })
681
+ result.push(updated)
682
+ } catch {
683
+ /* empty */
684
+ }
685
+ }
686
+ return result
428
687
  }`,
429
688
  deleteCode: `
430
- public async delete(id: ${methodTypeSignatures.delete.parameters[0]}): ${methodTypeSignatures.delete.returnType} {
689
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.delete.jsDoc)}
690
+ public async delete(
691
+ { id, execution }: ${methodTypeSignatures.delete.parameters[0]}
692
+ ): ${methodTypeSignatures.delete.returnType} {
431
693
  const existingItem = this.get(id)
432
694
  if (!existingItem) {
433
695
  throw new Error(\`Could not delete ${model.typeName} with id \${id}. Not found!\`)
434
696
  }
435
697
 
436
- await this.db.${meta.data.repository.getMethodFnName}.delete({ where: { ${idField.sourceName}:id } })
437
-
438
- this.remove(existingItem)
698
+ const mutationId = await execution.startDeleteMutation({
699
+ model: '${meta.actions.actionScopeConstType}',
700
+ entityId: id,
701
+ sourceObject: existingItem,
702
+ })
703
+ try {
704
+
705
+ await this.db.${meta.data.repository.getMethodFnName}.delete({ where: { ${idField.sourceName}:id } })
706
+
707
+ this.remove(existingItem)
708
+ await execution.finishDeleteMutation({ mutationId })
709
+ return id
710
+ } catch (error) {
711
+ await execution.errorMutation({ mutationId, error })
712
+ throw error
713
+ }
714
+ }`,
715
+ deleteManyCode: `
716
+ ${(0, jsdoc_1.toJsDocComment)(methodTypeSignatures.deleteMany.jsDoc)}
717
+ public async deleteMany(
718
+ { ids, execution }: ${methodTypeSignatures.deleteMany.parameters[0]}
719
+ ): ${methodTypeSignatures.deleteMany.returnType} {
720
+ const deletedIds: ${model.brandedIdType}[] = []
721
+ for (const id of ids) {
722
+ try {
723
+ await this.delete({ id, execution })
724
+ deletedIds.push(id)
725
+ } catch {
726
+ /* empty */
727
+ }
728
+ }
729
+ return deletedIds
439
730
  }`,
440
- // prettier-ignore
441
731
  databaseDecoderCode: `
442
732
  /**
443
733
  * Utility function that converts a given Database object to a TypeScript model instance
444
734
  */
445
- private ${decoderFunctionName}(item: Pick<DbType, ${Array.from(model.fields.values()).map((f) => `'${f.sourceName}'`).join(' | ')}>): ${model.typeName} {
446
- return ${meta.types.zodDecoderFnName}.parse({
447
- ${Array.from(model.fields.values()).map((f) => `${f.name}: item.${f.sourceName}`).join(',\n')}
735
+ private ${decoderFunctionName}(
736
+ item: Pick<DbType, ${model.fields.map((f) => `'${f.sourceName}'`).join(' | ')}>
737
+ ): ${model.typeName} {
738
+ return ${meta.types.zodDecoderFnNames.fromDatabase}.parse({
739
+ ${model.fields.map((f) => `${f.name}: item.${f.sourceName}`).join(',\n')}
448
740
  })
449
741
  }`,
450
742
  };
@@ -472,19 +764,47 @@ function generateIdBlocks({ model, meta }) {
472
764
  // NOTE: We export this function as an interface simplification to the repository generator. Internally, we
473
765
  // use the same functions as the ones used in this function which we don't want to expose.
474
766
  function getRepositoryMethodsTypeSignatures({ model, meta }) {
475
- const { verifyFunctionParameterType } = generateIdBlocks({ model, meta });
767
+ const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
476
768
  return {
477
769
  create: {
478
- parameters: [verifyFunctionParameterType],
770
+ jsDoc: [`Creates a new ${meta.userFriendlyName} and returns it.`],
771
+ parameters: [`{item: ${meta.types.dto.create}, execution: ${schemaMeta.actions.actionExecutionInterface}}`],
479
772
  returnType: `Promise<${model.typeName}>`,
480
773
  },
774
+ createMany: {
775
+ jsDoc: [`Creates multiple new ${meta.userFriendlyNamePlural} and returns them.`],
776
+ parameters: [`{items: ${meta.types.dto.create}[], execution: ${schemaMeta.actions.actionExecutionInterface}}`],
777
+ returnType: `Promise<${model.typeName}[]>`,
778
+ },
481
779
  update: {
482
- parameters: [`Partial<${model.typeName}> & { ${model.idField.name}: ${model.brandedIdType} }`],
780
+ jsDoc: [`Updates a ${meta.userFriendlyName} and returns it.`],
781
+ parameters: [`{item: ${meta.types.dto.update}, execution: ${schemaMeta.actions.actionExecutionInterface}}`],
782
+ returnType: `Promise<${model.typeName}>`,
783
+ },
784
+ updateMany: {
785
+ jsDoc: [`Updates multiple ${meta.userFriendlyNamePlural} and returns them.`],
786
+ parameters: [`{items: ${meta.types.dto.update}[], execution: ${schemaMeta.actions.actionExecutionInterface}}`],
787
+ returnType: `Promise<${model.typeName}[]>`,
788
+ },
789
+ upsert: {
790
+ jsDoc: [`Creates or updates a ${meta.userFriendlyName} and returns it.`],
791
+ parameters: [`{item: ${meta.types.dto.upsert}, execution: ${schemaMeta.actions.actionExecutionInterface}}`],
483
792
  returnType: `Promise<${model.typeName}>`,
484
793
  },
794
+ upsertMany: {
795
+ jsDoc: [`Creates or updates multiple ${meta.userFriendlyNamePlural} and returns them.`],
796
+ parameters: [`{items: ${meta.types.dto.upsert}[], execution: ${schemaMeta.actions.actionExecutionInterface}}`],
797
+ returnType: `Promise<${model.typeName}[]>`,
798
+ },
485
799
  delete: {
486
- parameters: [model.brandedIdType],
487
- returnType: `Promise<void>`,
800
+ jsDoc: [`Deletes a ${meta.userFriendlyName} and returns its id.`],
801
+ parameters: [`{id: ${model.brandedIdType}, execution: ${schemaMeta.actions.actionExecutionInterface}}`],
802
+ returnType: `Promise<${model.brandedIdType}>`,
803
+ },
804
+ deleteMany: {
805
+ jsDoc: [`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`],
806
+ parameters: [`{ids: ${model.brandedIdType}[], execution: ${schemaMeta.actions.actionExecutionInterface}}`],
807
+ returnType: `Promise<${model.brandedIdType}[]>`,
488
808
  },
489
809
  };
490
810
  }
@@ -499,14 +819,14 @@ function _generateIdBlocks_NoGeneration({ idField, model, meta, }) {
499
819
  generateNextIdFunctionName: '',
500
820
  initCode: '',
501
821
  verifyFunctionComment: `an error is thrown as field has no default setting in schema.`,
502
- // TODO: This creates a bug!
503
- verifyFunctionParameterType: model.typeName,
822
+ verifyFunctionParameterType: meta.types.dto.create,
504
823
  verifyCode: `
505
824
  if (item.${idField.name} === undefined) {
506
825
  throw new Error('Id field ${idField.name} is required!')
507
826
  }
508
827
  const ${idField.name} = ${meta.types.toBrandedIdTypeFnName}(item.${idField.name})`,
509
828
  setCode: '',
829
+ createFunctionParameterType: model.typeName,
510
830
  };
511
831
  }
512
832
  /**
@@ -514,6 +834,8 @@ function _generateIdBlocks_NoGeneration({ idField, model, meta, }) {
514
834
  * Given chunks make sure that the id is unique if provided or generate a new one if not.
515
835
  */
516
836
  function _generateIdBlock_Int({ idField, model, meta, }) {
837
+ const generatedFields = model.fields.filter((f) => f.kind === 'id' || f.attributes.isReadonly);
838
+ const readonlyFields = model.fields.filter((f) => f.attributes.isReadonly && f.kind !== 'id');
517
839
  return {
518
840
  libraryImports: '',
519
841
  generateNextIdFunctionName: `
@@ -523,8 +845,14 @@ function _generateIdBlock_Int({ idField, model, meta, }) {
523
845
  }`,
524
846
  initCode: `this.currentMaxId = (await this.db.${meta.data.repository.getMethodFnName}.aggregate({ _max: { ${idField.sourceName}: true } }))._max.${idField.sourceName} ?? 0`,
525
847
  verifyFunctionComment: 'the id is generated by increasing the highest former id and assigned to the item.',
526
- verifyFunctionParameterType: `(Omit<${model.typeName}, '${idField.name}'> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
848
+ // prettier-ignore
849
+ verifyFunctionParameterType: `(Omit<${model.typeName}, ${generatedFields.map((f) => `'${f.name}'`).join(' | ')}> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
527
850
  verifyCode: `const ${idField.name} = (item.${idField.name} !== undefined) ? ${meta.types.toBrandedIdTypeFnName}(item.${idField.name}) : this.generateNextId()`,
851
+ createFunctionParameterType:
852
+ // NOTE: In case we have readonly fields, we need to omit them from the create function.
853
+ readonlyFields.length === 0
854
+ ? model.typeName
855
+ : `Omit<${model.typeName}, ${readonlyFields.map((f) => `'${f.name}'`).join(' |')}>`,
528
856
  setCode: `if (item.id > this.currentMaxId) { this.currentMaxId = item.id }`,
529
857
  };
530
858
  }
@@ -533,6 +861,8 @@ function _generateIdBlock_Int({ idField, model, meta, }) {
533
861
  * It allows you to provide a custom id or generates a new one if not.
534
862
  */
535
863
  function _generateIdBlock_UUID({ idField, model, meta, }) {
864
+ const dbGeneratedFields = model.fields.filter((f) => f.kind === 'id' || f.attributes.isReadonly);
865
+ const readonlyFields = model.fields.filter((f) => f.attributes.isReadonly && f.kind !== 'id');
536
866
  return {
537
867
  libraryImports: `import { randomUUID } from 'crypto'`,
538
868
  generateNextIdFunctionName: `
@@ -541,8 +871,14 @@ function _generateIdBlock_UUID({ idField, model, meta, }) {
541
871
  }`,
542
872
  initCode: '',
543
873
  verifyFunctionComment: 'a new UUID is generated and assigned to the item.',
544
- verifyFunctionParameterType: `(Omit<${model.typeName}, '${idField.name}'> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
874
+ // prettier-ignore
875
+ verifyFunctionParameterType: `(Omit<${model.typeName}, ${dbGeneratedFields.map((f) => `'${f.name}'`).join(' | ')}> & Partial<{${idField.name}: ${idField.unbrandedTypeName}}>)`,
545
876
  verifyCode: `const ${idField.name} = (item.${idField.name} !== undefined) ? ${meta.types.toBrandedIdTypeFnName}(item.${idField.name}) : this.generateNextId()`,
877
+ createFunctionParameterType:
878
+ // NOTE: In case we have readonly fields, we need to omit them from the create function.
879
+ readonlyFields.length === 0
880
+ ? model.typeName
881
+ : `Omit<${model.typeName}, ${readonlyFields.map((f) => `'${f.name}'`).join(' |')}>`,
546
882
  setCode: '',
547
883
  };
548
884
  }
@@ -614,8 +950,12 @@ function generateUniqueFieldsBlocks({ model }) {
614
950
  * Utility function that ensures that the ${f.name} field is unique
615
951
  */
616
952
  private ${getEnsureUniqueFnName(f)}(item: { ${f.name}?: string }) {
617
- if (!item.${f.name}) return
618
- if (!this.uniqueIds.${f.name}.has(item.${f.name})) return
953
+ if (!item.${f.name}) {
954
+ return
955
+ }
956
+ if (!this.uniqueIds.${f.name}.has(item.${f.name})) {
957
+ return
958
+ }
619
959
  let counter = 1
620
960
 
621
961
  let ${f.name}: string
@@ -754,23 +1094,31 @@ function generateRelationsBlocks({ model, imports, }) {
754
1094
  from: relationModelMeta.types.importPath,
755
1095
  });
756
1096
  result.mapDeclarations.push(`protected ${r.name}Map: Map<${relationModelMeta.types.brandedIdType}, Map<${model.brandedIdType}, ${model.typeName}>> = new Map()`);
757
- // prettier-ignore
758
1097
  result.getterFunctions.push(`
759
1098
  /**
760
1099
  * Function to retrieve all ${(0, string_1.pluralize)(model.name)} that are related to a ${r.name}
761
1100
  */
762
- public ${fieldMeta.getByForeignKeyMethodFnName}(id: ${relationModelMeta.types.brandedIdType}): Map<${model.brandedIdType}, ${model.typeName}> {
1101
+ public ${fieldMeta.getByForeignKeyMethodFnName}(
1102
+ id: ${relationModelMeta.types.brandedIdType}
1103
+ ): Map<${model.brandedIdType}, ${model.typeName}> {
763
1104
  const result = this.${r.name}Map.get(id)
764
- if (!result) return new Map()
1105
+ if (!result) {
1106
+ return new Map()
1107
+ }
765
1108
  return new Map(result)
766
1109
  }
767
1110
 
768
1111
  /**
769
1112
  * Function to retrieve all ${model.brandedIdType}s that are related to a ${r.name}
770
1113
  */
771
- public ${fieldMeta.getByForeignKeyIdsMethodFnName}(id: ${relationModelMeta.types.brandedIdType}): ${model.brandedIdType}[] {
1114
+ public ${fieldMeta.getByForeignKeyIdsMethodFnName}(
1115
+ id: ${relationModelMeta.types.brandedIdType}
1116
+ ): ${model.brandedIdType}[] {
772
1117
  const s = this.${r.name}Map.get(id)
773
- if (!s) return []
1118
+ if (!s) {
1119
+ return []
1120
+ }
1121
+
774
1122
  return Array.from(s.keys())
775
1123
  }`);
776
1124
  result.setCode.push(`