@flusys/nestjs-entity-builder 9.1.0-rc.1 → 9.1.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/fesm/719.js CHANGED
@@ -153,7 +153,7 @@ __webpack_require__.d(__webpack_exports__, {
153
153
 
154
154
 
155
155
 
156
- const SLUG_SUFFIX = {
156
+ /** The path of each endpoint's Request step: `api-flows/<entity>/<path>`. */ const ENTITY_FLOW_PATHS = {
157
157
  insert: 'insert',
158
158
  insertMany: 'insert-many',
159
159
  getById: 'get-by-id',
@@ -165,7 +165,7 @@ const SLUG_SUFFIX = {
165
165
  bulkUpsert: 'bulk-upsert',
166
166
  delete: 'delete'
167
167
  };
168
- const DEFAULT_FLOW_NAME = {
168
+ const DEFAULT_ENDPOINT_NAME = {
169
169
  insert: 'Insert',
170
170
  insertMany: 'Insert many',
171
171
  getById: 'Get by id',
@@ -183,7 +183,7 @@ const DEFAULT_INPUT_LABEL = {
183
183
  search: 'Search'
184
184
  };
185
185
  const DEFAULT_NO_MATCH_MESSAGE = 'No record matches the filter.';
186
- const MAX_FLOW_NAME_LENGTH = 255;
186
+ const MAX_NAME_LENGTH = 255;
187
187
  /** A supplied text when it has content, the English default otherwise. */ const textOr = (value, fallback)=>value?.trim() || fallback;
188
188
  const INPUT_TYPE = {
189
189
  [_enums_field_type_enum_js__rspack_import_1/* .FieldType.TEXT */.P.TEXT]: 'string',
@@ -200,8 +200,13 @@ const INPUT_TYPE = {
200
200
  [_enums_field_type_enum_js__rspack_import_1/* .FieldType.JSON */.P.JSON]: 'object',
201
201
  [_enums_field_type_enum_js__rspack_import_1/* .FieldType.FILE */.P.FILE]: 'string'
202
202
  };
203
- const MAX_SLUG_LENGTH = 63;
204
203
  const LIST_LIMIT = 50;
204
+ const READ_ENDPOINTS = new Set([
205
+ 'getById',
206
+ 'getByIds',
207
+ 'getAll',
208
+ 'getByFilter'
209
+ ]);
205
210
  /** A bulk flow runs up to three steps per item (bulkUpsert: check, write, keep), well inside FLOW_LIMITS.MAX_STEPS_PER_RUN. */ const BULK_MAX_ITEMS = 200;
206
211
  const COLUMN = {
207
212
  x: 40,
@@ -219,11 +224,7 @@ const lit = (value)=>({
219
224
  type: 'literal',
220
225
  value
221
226
  });
222
- /** `customer_ticket` + getAll -> `customer-ticket-get-all`, shortened so it always fits the slug length. */ function entityFlowSlug(entityCode, endpoint) {
223
- const suffix = SLUG_SUFFIX[endpoint];
224
- const base = entityCode.replace(/_/g, '-').slice(0, MAX_SLUG_LENGTH - suffix.length - 1).replace(/-+$/, '');
225
- return `${base}-${suffix}`;
226
- }
227
+ /** `customer_ticket` -> `customer-ticket`: an entity code always fits a flow URL name. */ const entityFlowSlug = (entityCode)=>entityCode.replace(/_/g, '-');
227
228
  function optionValues(options) {
228
229
  return (options ?? []).map((o)=>o && typeof o === 'object' && 'value' in o ? String(o.value) : String(o));
229
230
  }
@@ -275,56 +276,59 @@ const respond = (id, column, statusCode, body, row)=>({
275
276
  body
276
277
  }
277
278
  });
278
- /** The body is the list itself (`itemSchema` checks each item): foreach `input` -> `body` (per item) ... done -> respond with the collected results. */ function bulkGraph(itemSchema, body, bodyEdges, collect, statusCode) {
279
- return {
280
- bodyType: 'list',
281
- inputSchema: itemSchema,
282
- steps: [
283
- {
284
- id: 'each',
285
- type: 'foreach',
286
- column: 1,
287
- config: {
288
- items: ref('input'),
289
- maxItems: BULK_MAX_ITEMS,
290
- collect
291
- }
292
- },
293
- ...body,
294
- respond('respond', 2, statusCode, ref('context.each.results'))
295
- ],
296
- edges: [
297
- [
298
- 'each',
299
- 'each',
300
- body[0].id
301
- ],
302
- ...bodyEdges,
303
- [
304
- 'each',
305
- 'done',
306
- 'respond'
307
- ]
308
- ],
309
- isTransactional: true
310
- };
311
- }
312
- function endpointGraph(entity, endpoint, texts) {
279
+ /**
280
+ * The steps of one endpoint. Its step ids are `<endpoint>_<step>` (`id()`), so the endpoints of one
281
+ * flow never share a step. A bulk body is the list itself (`itemSchema` checks each item):
282
+ * foreach `input` -> `body` (per item) ... done -> respond with the collected results.
283
+ */ function endpointGraph(entity, endpoint, texts) {
284
+ const id = (step)=>`${endpoint}_${step}`;
313
285
  const entityCode = entity.code;
314
286
  const single = (inputSchema, step, statusCode = 200)=>({
315
287
  inputSchema,
316
288
  steps: [
317
289
  {
318
290
  ...step,
291
+ id: id('record'),
319
292
  column: 1
320
293
  },
321
- respond('respond', 2, statusCode, ref(`context.${step.id}`))
294
+ respond(id('respond'), 2, statusCode, ref(`context.${id('record')}`))
322
295
  ],
323
296
  edges: [
324
297
  [
325
- step.id,
298
+ id('record'),
326
299
  'out',
327
- 'respond'
300
+ id('respond')
301
+ ]
302
+ ]
303
+ });
304
+ const bulk = (itemSchema, body, bodyEdges, collect, statusCode)=>({
305
+ bodyType: 'list',
306
+ inputSchema: itemSchema,
307
+ steps: [
308
+ {
309
+ id: id('each'),
310
+ type: 'foreach',
311
+ column: 1,
312
+ config: {
313
+ items: ref('input'),
314
+ maxItems: BULK_MAX_ITEMS,
315
+ collect
316
+ }
317
+ },
318
+ ...body,
319
+ respond(id('respond'), 2, statusCode, ref(`context.${id('each')}.results`))
320
+ ],
321
+ edges: [
322
+ [
323
+ id('each'),
324
+ 'each',
325
+ body[0].id
326
+ ],
327
+ ...bodyEdges,
328
+ [
329
+ id('each'),
330
+ 'done',
331
+ id('respond')
328
332
  ]
329
333
  ]
330
334
  });
@@ -333,7 +337,6 @@ function endpointGraph(entity, endpoint, texts) {
333
337
  switch(endpoint){
334
338
  case 'insert':
335
339
  return single(insertInputs, {
336
- id: 'record',
337
340
  type: 'entity_insert',
338
341
  config: {
339
342
  entityCode,
@@ -341,9 +344,9 @@ function endpointGraph(entity, endpoint, texts) {
341
344
  }
342
345
  }, 201);
343
346
  case 'insertMany':
344
- return bulkGraph(insertInputs, [
347
+ return bulk(insertInputs, [
345
348
  {
346
- id: 'record',
349
+ id: id('record'),
347
350
  type: 'entity_insert',
348
351
  column: 1,
349
352
  row: 1,
@@ -352,12 +355,11 @@ function endpointGraph(entity, endpoint, texts) {
352
355
  fields: fieldMap(entity.fields, 'loop.item')
353
356
  }
354
357
  }
355
- ], [], ref('context.record'), 201);
358
+ ], [], ref(`context.${id('record')}`), 201);
356
359
  case 'getById':
357
360
  return single([
358
361
  idInput(texts)
359
362
  ], {
360
- id: 'record',
361
363
  type: 'entity_get',
362
364
  config: {
363
365
  entityCode,
@@ -368,7 +370,6 @@ function endpointGraph(entity, endpoint, texts) {
368
370
  return single([
369
371
  idsInput(texts)
370
372
  ], {
371
- id: 'records',
372
373
  type: 'entity_query',
373
374
  config: {
374
375
  entityCode,
@@ -390,7 +391,6 @@ function endpointGraph(entity, endpoint, texts) {
390
391
  },
391
392
  ...optionalFieldInputs(filterFields)
392
393
  ], {
393
- id: 'records',
394
394
  type: 'entity_query',
395
395
  config: {
396
396
  entityCode,
@@ -410,7 +410,7 @@ function endpointGraph(entity, endpoint, texts) {
410
410
  ],
411
411
  steps: [
412
412
  {
413
- id: 'records',
413
+ id: id('records'),
414
414
  type: 'entity_query',
415
415
  column: 1,
416
416
  config: {
@@ -423,7 +423,7 @@ function endpointGraph(entity, endpoint, texts) {
423
423
  }
424
424
  },
425
425
  {
426
- id: 'found',
426
+ id: id('found'),
427
427
  type: 'condition',
428
428
  column: 2,
429
429
  config: {
@@ -431,15 +431,15 @@ function endpointGraph(entity, endpoint, texts) {
431
431
  operator: 'AND',
432
432
  conditions: [
433
433
  {
434
- field: 'context.records.items',
434
+ field: `context.${id('records')}.items`,
435
435
  comparison: 'is_not_empty'
436
436
  }
437
437
  ]
438
438
  }
439
439
  }
440
440
  },
441
- respond('respond', 3, 200, ref('context.records.items.0')),
442
- respond('not_found', 3, 404, {
441
+ respond(id('respond'), 3, 200, ref(`context.${id('records')}.items.0`)),
442
+ respond(id('not_found'), 3, 404, {
443
443
  success: lit(false),
444
444
  message: lit(texts.noMatchMessage),
445
445
  messageKey: lit(_config_message_keys_js__rspack_import_2/* .GENERIC_RECORD_MESSAGES.NO_MATCH */.j4.NO_MATCH)
@@ -447,19 +447,19 @@ function endpointGraph(entity, endpoint, texts) {
447
447
  ],
448
448
  edges: [
449
449
  [
450
- 'records',
450
+ id('records'),
451
451
  'out',
452
- 'found'
452
+ id('found')
453
453
  ],
454
454
  [
455
- 'found',
455
+ id('found'),
456
456
  'true',
457
- 'respond'
457
+ id('respond')
458
458
  ],
459
459
  [
460
- 'found',
460
+ id('found'),
461
461
  'false',
462
- 'not_found'
462
+ id('not_found')
463
463
  ]
464
464
  ]
465
465
  };
@@ -468,7 +468,6 @@ function endpointGraph(entity, endpoint, texts) {
468
468
  idInput(texts),
469
469
  ...optionalFieldInputs(entity.fields)
470
470
  ], {
471
- id: 'record',
472
471
  type: 'entity_update',
473
472
  config: {
474
473
  entityCode,
@@ -477,12 +476,12 @@ function endpointGraph(entity, endpoint, texts) {
477
476
  }
478
477
  });
479
478
  case 'updateMany':
480
- return bulkGraph([
479
+ return bulk([
481
480
  idInput(texts),
482
481
  ...optionalFieldInputs(entity.fields)
483
482
  ], [
484
483
  {
485
- id: 'record',
484
+ id: id('record'),
486
485
  type: 'entity_update',
487
486
  column: 1,
488
487
  row: 1,
@@ -492,9 +491,9 @@ function endpointGraph(entity, endpoint, texts) {
492
491
  fields: fieldMap(entity.fields, 'loop.item')
493
492
  }
494
493
  }
495
- ], [], ref('context.record'), 200);
494
+ ], [], ref(`context.${id('record')}`), 200);
496
495
  case 'bulkUpsert':
497
- return bulkGraph([
496
+ return bulk([
498
497
  {
499
498
  ...idInput(texts),
500
499
  required: false
@@ -502,7 +501,7 @@ function endpointGraph(entity, endpoint, texts) {
502
501
  ...optionalFieldInputs(entity.fields)
503
502
  ], [
504
503
  {
505
- id: 'has_id',
504
+ id: id('has_id'),
506
505
  type: 'condition',
507
506
  column: 1,
508
507
  row: 1,
@@ -519,7 +518,7 @@ function endpointGraph(entity, endpoint, texts) {
519
518
  }
520
519
  },
521
520
  {
522
- id: 'updated',
521
+ id: id('updated'),
523
522
  type: 'entity_update',
524
523
  column: 2,
525
524
  row: 1,
@@ -530,7 +529,7 @@ function endpointGraph(entity, endpoint, texts) {
530
529
  }
531
530
  },
532
531
  {
533
- id: 'inserted',
532
+ id: id('inserted'),
534
533
  type: 'entity_insert',
535
534
  column: 2,
536
535
  row: 2,
@@ -540,54 +539,53 @@ function endpointGraph(entity, endpoint, texts) {
540
539
  }
541
540
  },
542
541
  {
543
- id: 'keep_updated',
542
+ id: id('keep_updated'),
544
543
  type: 'set',
545
544
  column: 3,
546
545
  row: 1,
547
546
  config: {
548
547
  values: {
549
- saved: ref('context.updated')
548
+ saved: ref(`context.${id('updated')}`)
550
549
  }
551
550
  }
552
551
  },
553
552
  {
554
- id: 'keep_inserted',
553
+ id: id('keep_inserted'),
555
554
  type: 'set',
556
555
  column: 3,
557
556
  row: 2,
558
557
  config: {
559
558
  values: {
560
- saved: ref('context.inserted')
559
+ saved: ref(`context.${id('inserted')}`)
561
560
  }
562
561
  }
563
562
  }
564
563
  ], [
565
564
  [
566
- 'has_id',
565
+ id('has_id'),
567
566
  'true',
568
- 'updated'
567
+ id('updated')
569
568
  ],
570
569
  [
571
- 'has_id',
570
+ id('has_id'),
572
571
  'false',
573
- 'inserted'
572
+ id('inserted')
574
573
  ],
575
574
  [
576
- 'updated',
575
+ id('updated'),
577
576
  'out',
578
- 'keep_updated'
577
+ id('keep_updated')
579
578
  ],
580
579
  [
581
- 'inserted',
580
+ id('inserted'),
582
581
  'out',
583
- 'keep_inserted'
582
+ id('keep_inserted')
584
583
  ]
585
584
  ], ref('vars.saved'), 200);
586
585
  case 'delete':
587
586
  return single([
588
587
  idInput(texts)
589
588
  ], {
590
- id: 'record',
591
589
  type: 'entity_delete',
592
590
  config: {
593
591
  entityCode,
@@ -597,11 +595,13 @@ function endpointGraph(entity, endpoint, texts) {
597
595
  }
598
596
  }
599
597
  /**
600
- * A ready-made flow for one record endpoint of the generic API controller. It runs as the
601
- * caller, so the entity permissions still decide who may use it, and it is an ordinary flow
602
- * afterwards - the user can change or delete it like any other. The bulk endpoints take the list
603
- * of records as the body and loop over it in one transaction, so a failing item rolls back the whole request.
604
- */ function buildEntityFlow(entity, endpoint, texts = {}) {
598
+ * The ready-made flow of an entity: `api-flows/<entity>` with one Request step per endpoint of the
599
+ * generic API controller, each on its own path (`ENTITY_FLOW_PATHS`) with its own body, laid out
600
+ * one below the other. It runs as the caller, so the entity permissions still decide who may use
601
+ * it, and it is an ordinary flow afterwards - the user can change or delete it like any other. A
602
+ * flow with a write endpoint is transactional: a bulk endpoint loops over its list in one
603
+ * transaction, so a failing item rolls back the whole request.
604
+ */ function buildEntityFlow(entity, endpoints, texts = {}) {
605
605
  const resolved = {
606
606
  inputLabels: {
607
607
  id: textOr(texts.inputLabels?.id, DEFAULT_INPUT_LABEL.id),
@@ -610,57 +610,65 @@ function endpointGraph(entity, endpoint, texts) {
610
610
  },
611
611
  noMatchMessage: textOr(texts.noMatchMessage, DEFAULT_NO_MATCH_MESSAGE)
612
612
  };
613
- const graph = endpointGraph(entity, endpoint, resolved);
614
- const nodes = [
615
- {
616
- id: 'trigger',
613
+ const nodes = [];
614
+ const edges = [];
615
+ let band = 0;
616
+ for (const endpoint of endpoints){
617
+ const graph = endpointGraph(entity, endpoint, resolved);
618
+ const at = (column, row = 0)=>({
619
+ x: COLUMN.x + column * COLUMN.gap,
620
+ y: ROW.y + (band + row) * ROW.gap
621
+ });
622
+ const config = {
623
+ path: ENTITY_FLOW_PATHS[endpoint],
624
+ bodyType: graph.bodyType ?? 'object',
625
+ inputSchema: graph.inputSchema
626
+ };
627
+ nodes.push({
628
+ id: endpoint,
617
629
  type: 'trigger',
618
- name: '',
619
- position: {
620
- x: COLUMN.x,
621
- y: ROW.y
622
- },
623
- config: {}
624
- },
625
- ...graph.steps.map(({ column, row, ...step })=>({
630
+ name: textOr(texts.names?.[endpoint], DEFAULT_ENDPOINT_NAME[endpoint]).slice(0, MAX_NAME_LENGTH),
631
+ position: at(0),
632
+ config
633
+ }, ...graph.steps.map(({ column, row, ...step })=>({
626
634
  ...step,
627
635
  name: '',
628
- position: {
629
- x: COLUMN.x + column * COLUMN.gap,
630
- y: ROW.y + (row ?? 0) * ROW.gap
631
- }
632
- }))
633
- ];
634
- const edges = [
635
- [
636
- 'trigger',
637
- 'out',
638
- graph.steps[0].id
639
- ],
640
- ...graph.edges
641
- ].map(([from, port, to])=>({
642
- id: `e_${from}_${port}_${to}`,
643
- from,
644
- port,
645
- to
646
- }));
636
+ position: at(column, row)
637
+ })));
638
+ edges.push(...[
639
+ [
640
+ endpoint,
641
+ 'out',
642
+ graph.steps[0].id
643
+ ],
644
+ ...graph.edges
645
+ ].map(([from, port, to])=>({
646
+ id: `e_${from}_${port}_${to}`,
647
+ from,
648
+ port,
649
+ to
650
+ })));
651
+ band += 1 + Math.max(0, ...graph.steps.map((step)=>step.row ?? 0));
652
+ }
647
653
  return {
648
- name: `${entity.label} - ${textOr(texts.names?.[endpoint], DEFAULT_FLOW_NAME[endpoint])}`.slice(0, MAX_FLOW_NAME_LENGTH),
649
- slug: entityFlowSlug(entity.code, endpoint),
654
+ name: entity.label.slice(0, MAX_NAME_LENGTH),
655
+ slug: entityFlowSlug(entity.code),
650
656
  isActive: true,
651
657
  authMode: 'jwt',
652
658
  runAs: 'caller',
653
- isTransactional: graph.isTransactional ?? false,
654
- bodyType: graph.bodyType ?? 'object',
655
- inputSchema: graph.inputSchema,
659
+ isTransactional: endpoints.some((endpoint)=>!READ_ENDPOINTS.has(endpoint)),
660
+ bodyType: 'object',
661
+ inputSchema: [],
656
662
  nodes,
657
663
  edges
658
664
  };
659
665
  }
660
666
 
661
667
  __webpack_require__.d(__webpack_exports__, {
662
- C: () => (buildEntityFlow),
663
- x: () => (entityFlowSlug)
668
+ CO: () => (buildEntityFlow)
669
+ }, {
670
+ IP: ENTITY_FLOW_PATHS,
671
+ x2: entityFlowSlug
664
672
  });
665
673
 
666
674
 
@@ -3417,18 +3425,18 @@ class SchemaSyncService {
3417
3425
  }
3418
3426
  }
3419
3427
  if (flowEndpoints.length) {
3420
- const slugs = flowEndpoints.map((endpoint)=>(0,_entity_flow_scaffold_js__rspack_import_9/* .entityFlowSlug */.x)(dto.code, endpoint));
3421
- const clashes = await ctx.qr.manager.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G).find({
3428
+ const slug = (0,_entity_flow_scaffold_js__rspack_import_9/* .entityFlowSlug */.x2)(dto.code);
3429
+ const clash = await ctx.qr.manager.getRepository(_entities_flow_definition_entity_js__rspack_import_6/* .FlowDefinition */.G).findOne({
3422
3430
  select: {
3423
3431
  slug: true
3424
3432
  },
3425
3433
  where: {
3426
- slug: (0,typeorm__rspack_import_3.In)(slugs)
3434
+ slug
3427
3435
  },
3428
3436
  withDeleted: true
3429
3437
  });
3430
- for (const clash of clashes)ctx.block((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_16/* .SCHEMA_BLOCKER_MESSAGES.FLOW_SLUG_TAKEN */.M3.FLOW_SLUG_TAKEN, {
3431
- slug: clash.slug
3438
+ if (clash) ctx.block((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_16/* .SCHEMA_BLOCKER_MESSAGES.FLOW_SLUG_TAKEN */.M3.FLOW_SLUG_TAKEN, {
3439
+ slug
3432
3440
  }));
3433
3441
  }
3434
3442
  if (ctx.blocked) return {
@@ -3519,36 +3527,34 @@ class SchemaSyncService {
3519
3527
  try {
3520
3528
  await this.entityPermissionService.provisionForEntity(created.definition);
3521
3529
  } finally{
3522
- created.warnings.push(...await this.createEndpointFlows(dto, flowEndpoints, user));
3530
+ if (flowEndpoints.length) created.warnings.push(...await this.createEndpointFlow(dto, flowEndpoints, user));
3523
3531
  }
3524
3532
  }
3525
3533
  };
3526
3534
  };
3527
3535
  }
3528
3536
  /**
3529
- * One ready-made flow per requested endpoint, saved through FlowDefinitionService so it is
3530
- * validated like any other flow. Each is saved on its own: one that fails does not stop the
3531
- * rest, and is reported as a warning since the entity is already committed.
3532
- */ async createEndpointFlows(dto, endpoints, user) {
3533
- const entity = {
3537
+ * The entity's ready-made flow, one Request step per requested endpoint, saved through
3538
+ * FlowDefinitionService so it is validated like any other flow. The entity is already committed,
3539
+ * so a flow that cannot be saved is reported as a warning.
3540
+ */ async createEndpointFlow(dto, endpoints, user) {
3541
+ const flow = (0,_entity_flow_scaffold_js__rspack_import_9/* .buildEntityFlow */.CO)({
3534
3542
  code: dto.code,
3535
3543
  label: dto.label,
3536
3544
  fields: dto.fields ?? []
3537
- };
3538
- const warnings = [];
3539
- for (const endpoint of endpoints){
3540
- const flow = (0,_entity_flow_scaffold_js__rspack_import_9/* .buildEntityFlow */.C)(entity, endpoint, dto.flowTexts);
3541
- try {
3542
- await this.flowDefinitionService.insert(flow, user);
3543
- } catch (error) {
3544
- this.logger.error(`Endpoint flow '${flow.slug}' of entity '${dto.code}' could not be created: ${error.message}`);
3545
- warnings.push((0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_16/* .SCHEMA_WARNING_MESSAGES.ENDPOINT_FLOW_FAILED */.Uu.ENDPOINT_FLOW_FAILED, {
3545
+ }, endpoints, dto.flowTexts);
3546
+ try {
3547
+ await this.flowDefinitionService.insert(flow, user);
3548
+ return [];
3549
+ } catch (error) {
3550
+ this.logger.error(`The flow '${flow.slug}' of entity '${dto.code}' could not be created: ${error.message}`);
3551
+ return [
3552
+ (0,_flusys_nestjs_shared_utils__rspack_import_1.messageRef)(_config_message_keys_js__rspack_import_16/* .SCHEMA_WARNING_MESSAGES.ENDPOINT_FLOW_FAILED */.Uu.ENDPOINT_FLOW_FAILED, {
3546
3553
  slug: flow.slug,
3547
3554
  detail: (0,_flow_engine_flow_errors_js__rspack_import_7/* .errorRef */.O$)(error)
3548
- }));
3549
- }
3555
+ })
3556
+ ];
3550
3557
  }
3551
- return warnings;
3552
3558
  }
3553
3559
  /** Preview of `createEntity`: the CREATE TABLE / CREATE INDEX statements, nothing is created. */ async planCreateEntity(dto, user) {
3554
3560
  this.validateCreateRequest(dto);