@solidstarters/solid-core 1.2.128 → 1.2.129

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 (29) hide show
  1. package/dist/filters/http-exception.filter.d.ts +2 -0
  2. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  3. package/dist/filters/http-exception.filter.js +17 -16
  4. package/dist/filters/http-exception.filter.js.map +1 -1
  5. package/dist/helpers/schematic.service.js +1 -1
  6. package/dist/helpers/schematic.service.js.map +1 -1
  7. package/dist/helpers/solid-registry.d.ts.map +1 -1
  8. package/dist/helpers/solid-registry.js.map +1 -1
  9. package/dist/seeders/seed-data/solid-core-metadata.json +16 -3
  10. package/dist/services/model-metadata.service.d.ts +1 -0
  11. package/dist/services/model-metadata.service.d.ts.map +1 -1
  12. package/dist/services/model-metadata.service.js +15 -3
  13. package/dist/services/model-metadata.service.js.map +1 -1
  14. package/dist/solid-core.module.d.ts.map +1 -1
  15. package/dist/solid-core.module.js +5 -0
  16. package/dist/solid-core.module.js.map +1 -1
  17. package/dist/subscribers/computed-entity-field.subscriber.d.ts +6 -4
  18. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  19. package/dist/subscribers/computed-entity-field.subscriber.js +28 -23
  20. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +1 -1
  23. package/src/filters/http-exception.filter.ts +16 -15
  24. package/src/helpers/schematic.service.ts +1 -1
  25. package/src/helpers/solid-registry.ts +0 -10
  26. package/src/seeders/seed-data/solid-core-metadata.json +16 -3
  27. package/src/services/model-metadata.service.ts +21 -23
  28. package/src/solid-core.module.ts +6 -0
  29. package/src/subscribers/computed-entity-field.subscriber.ts +45 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.128",
3
+ "version": "1.2.129",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,11 +1,16 @@
1
- import { ExceptionFilter, Catch, ArgumentsHost, Logger } from '@nestjs/common';
1
+ import { ExceptionFilter, Catch, ArgumentsHost, Logger, Injectable } from '@nestjs/common';
2
2
  import { Response } from 'express';
3
3
  import { HttpStatusCodeMessages } from '../interceptors/logging.interceptor';
4
4
 
5
5
  @Catch()
6
+ @Injectable()
6
7
  export class HttpExceptionFilter implements ExceptionFilter {
7
8
  private readonly logger = new Logger(HttpExceptionFilter.name);
8
9
 
10
+ constructor() {
11
+ this.logger.debug('HttpExceptionFilter initialized');
12
+ }
13
+
9
14
  catch(exception: any, host: ArgumentsHost) {
10
15
  const ctx = host.switchToHttp();
11
16
  const response = ctx.getResponse<Response>();
@@ -19,20 +24,16 @@ export class HttpExceptionFilter implements ExceptionFilter {
19
24
  this.logger.error(exception.stack || 'No stack trace available');
20
25
 
21
26
  // Send the response to the client
22
- if (exception.response) {
23
- response.status(status).json({
24
- ...exception.response,
25
- error: message,
26
- data: {}
27
- });
28
- }
29
- else {
30
- response.status(status).json({
31
- statusCode: status,
32
- message: [message],
33
- error: message,
34
- data: {}
35
- });
27
+ const errorJson = this.getErrorJson(status, message, exception.response);
28
+ response.status(status).json(errorJson);
29
+ }
30
+
31
+ private getErrorJson(status: number|string, message: string, additionalErrorData: unknown = {}): any {
32
+ return {
33
+ statusCode: status,
34
+ message: [message],
35
+ error: HttpStatusCodeMessages[status] || 'Internal Server Error',
36
+ data: additionalErrorData
36
37
  }
37
38
  }
38
39
  }
@@ -84,7 +84,7 @@ export class SchematicService {
84
84
  return !this.solidRegistry.getCommonEntityKeys().includes(field.name);
85
85
  })
86
86
  .map((field) => {
87
- return `--fields='${JSON.stringify(field)}'`;
87
+ return `--fields='${JSON.stringify(field).replace(/'/g, "\\'")}'`;
88
88
  })
89
89
  .join(' ');
90
90
  const schematicCommand = modelCommand + ' ' + fieldCommand;
@@ -191,16 +191,6 @@ export class SolidRegistry {
191
191
  });
192
192
  }
193
193
 
194
- /**
195
- * @deprecated You can directly pass the entityName to typeORM, if you are using this just for doing operations against a particular entity.
196
- */
197
- // Returns the entity target class from the entity name
198
- // getEntityTarget(entityManager: EntityManager, entityName: string): any { //TODO Can be refactored to use this function from crud helper service
199
- // const entityMetadatas = entityManager.connection.entityMetadatas;
200
- // const entityMetadata = entityMetadatas.find(em => em.name === entityName);
201
- // return entityMetadata.target;
202
- // }
203
-
204
194
  getCommonEntityKeys(): (keyof CommonEntity)[] {
205
195
  return ['id', 'createdAt', 'updatedAt', 'deletedAt', 'createdBy', 'updatedBy', 'deletedTracker', 'localeName', 'defaultEntityLocaleId', 'publishedAt'];
206
196
  // return Reflect.getMetadataKeys(CommonEntity.prototype) as (keyof CommonEntity)[];
@@ -5050,7 +5050,8 @@
5050
5050
  "rowButtons": [
5051
5051
  {
5052
5052
  "attrs": {
5053
- "className": "pi pi-cog",
5053
+ "className":"p-button-text",
5054
+ "icon": "pi pi-cog",
5054
5055
  "label": "Generate Code",
5055
5056
  "action": "GenerateModuleCodeRowAction",
5056
5057
  "openInPopup": true,
@@ -5217,11 +5218,23 @@
5217
5218
  "truncateAfter": 50,
5218
5219
  "create": true,
5219
5220
  "edit": true,
5220
- "delete": true,
5221
+ "delete": false,
5221
5222
  "rowButtons": [
5222
5223
  {
5223
5224
  "attrs": {
5224
- "className": "pi pi-cog",
5225
+ "icon":"pi pi-trash",
5226
+ "className": "p-button-danger p-button-text",
5227
+ "label": "Delete",
5228
+ "action": "DeleteModelRowAction",
5229
+ "customComponentIsSystem": true,
5230
+ "actionInContextMenu": true,
5231
+ "openInPopup": true
5232
+ }
5233
+ },
5234
+ {
5235
+ "attrs": {
5236
+ "icon": "pi pi-cog",
5237
+ "className":"p-button-text",
5225
5238
  "label": "Generate Code",
5226
5239
  "action": "GenerateModelCodeRowAction",
5227
5240
  "customComponentIsSystem": true,
@@ -714,29 +714,27 @@ export class ModelMetadataService {
714
714
 
715
715
  @DisallowInProduction()
716
716
  async handleGenerateCode(options: CodeGenerationOptions): Promise<any> {
717
- // // see if the model record exists.
718
- // const model = await this.modelMetadataRepo.findOne({
719
- // where: {
720
- // id: options.modelId,
721
- // },
722
- // });
723
- // if (!model) {
724
- // throw new NotFoundException(`Model record with #${options.modelId} not found`);
725
- // }
717
+ const affectedModelIds = [], refreshModelCodeOutputLines = [], removeFieldCodeOutputLines = [];
726
718
 
727
- // const m = {
728
- // payload: options,
729
- // parentEntity: model.singularName,
730
- // parentEntityId: options.modelId,
731
- // };
719
+ // Generate the code for the passed model
720
+ const { model, removeFieldCodeOuput, refreshModelCodeOutput } = await this.generateCode(options);
721
+ affectedModelIds.push(model.id);
722
+ refreshModelCodeOutputLines.push(refreshModelCodeOutput);
723
+ removeFieldCodeOutputLines.push(removeFieldCodeOuput);
732
724
 
733
- // const messageId = await this.generateCodePublihser.publish(m);
725
+ // Generate the code for models which are linked to fields having an inverse relation
726
+ await this.generateCodeForInverseModels(model, options, affectedModelIds, refreshModelCodeOutputLines, removeFieldCodeOutputLines);
734
727
 
735
- // return { messageId: messageId };
728
+ // Generate the VAM config for all the affected models
729
+ for (const modelId of affectedModelIds) {
730
+ await this.generateVAMConfig(modelId);
731
+ }
736
732
 
737
- const { model, removeFieldCodeOuput, refreshModelCodeOutput } = await this.generateCode(options);
733
+ // Return the aggregated code output
734
+ return `${removeFieldCodeOutputLines.join('\n')} \n ${refreshModelCodeOutputLines.join('\n')}`;
735
+ }
738
736
 
739
- // Generate the code for models which are linked to fields having an inverse relation
737
+ private async generateCodeForInverseModels(model: ModelMetadata, options: CodeGenerationOptions, affectedModelIds: any[], refreshModelCodeOutputLines: any[], removeFieldCodeOutputLines: any[]) {
740
738
  const coModelSingularNames = model.fields.
741
739
  filter(field => field.type === SolidFieldType.relation && field.relationCreateInverse === true)
742
740
  .map(field => field.relationCoModelSingularName);
@@ -747,11 +745,11 @@ export class ModelMetadataService {
747
745
  modelId: coModel.id,
748
746
  dryRun: options.dryRun
749
747
  };
750
- await this.generateCode(inverseOptions);
748
+ const { removeFieldCodeOuput, refreshModelCodeOutput } = await this.generateCode(inverseOptions);
749
+ affectedModelIds.push(coModel.id);
750
+ refreshModelCodeOutputLines.push(refreshModelCodeOutput);
751
+ removeFieldCodeOutputLines.push(removeFieldCodeOuput);
751
752
  }
752
- await this.generateVAMConfig(model.id);
753
-
754
- return `${removeFieldCodeOuput} \n ${refreshModelCodeOutput}`;
755
753
  }
756
754
 
757
755
  // Generate the View, Action and Menu configuration for the model
@@ -801,7 +799,7 @@ export class ModelMetadataService {
801
799
  }
802
800
 
803
801
  // Populate the View, Actions and Menus in the config file
804
- private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
802
+ private populateVAMConfigInFileInternal(formViewLayoutFields: any[], model: ModelMetadata, listViewLayoutFields: { type: string; attrs: { name: string; }; }[], metaData: any) {
805
803
  const column1Fields = [];
806
804
  const column2Fields = [];
807
805
 
@@ -1,6 +1,7 @@
1
1
  import { Global, Module } from '@nestjs/common';
2
2
  import { ConfigModule, ConfigService } from '@nestjs/config';
3
3
  import {
4
+ APP_FILTER,
4
5
  APP_GUARD,
5
6
  APP_INTERCEPTOR,
6
7
  DiscoveryService,
@@ -212,6 +213,7 @@ import { ComputedEntityFieldSubscriber } from './subscribers/computed-entity-fie
212
213
  import { CreatedByUpdatedBySubscriber } from './subscribers/created-by-updated-by.subscriber';
213
214
  import { SecurityRuleSubscriber } from './subscribers/security-rule.subscriber';
214
215
  import { ViewMetadataSubsciber } from './subscribers/view-metadata.subscriber';
216
+ import { HttpExceptionFilter } from './filters/http-exception.filter';
215
217
 
216
218
 
217
219
  @Global()
@@ -326,6 +328,10 @@ import { ViewMetadataSubsciber } from './subscribers/view-metadata.subscriber';
326
328
  provide: APP_GUARD,
327
329
  useClass: PermissionsGuard,
328
330
  },
331
+ {
332
+ provide: APP_FILTER,
333
+ useClass: HttpExceptionFilter
334
+ },
329
335
  ModuleMetadataService,
330
336
  ModuleMetadataHelperService,
331
337
  ModelMetadataService,
@@ -1,5 +1,5 @@
1
1
  import { camelize } from "@angular-devkit/core/src/utils/strings";
2
- import { Injectable } from "@nestjs/common";
2
+ import { Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
3
3
  import { InjectDataSource } from "@nestjs/typeorm";
4
4
  import { ComputedFieldTriggerOperation } from "src/dtos/create-field-metadata.dto";
5
5
  import { ComputedFieldMetadata, SolidRegistry } from "src/helpers/solid-registry";
@@ -15,6 +15,7 @@ export interface ComputedFieldEvaluationPayload extends ComputedFieldMetadata {
15
15
  @Injectable()
16
16
  @EventSubscriber()
17
17
  export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface {
18
+ private readonly logger = new Logger(this.constructor.name);
18
19
  constructor(
19
20
  @InjectDataSource()
20
21
  private readonly dataSource: DataSource,
@@ -28,61 +29,80 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
28
29
  await this.handleComputedFieldEvaluation(event.entity, ComputedFieldTriggerOperation.beforeInsert);
29
30
  }
30
31
 
31
- async beforeUpdate(event: UpdateEvent<any>): Promise<any>{
32
+ async beforeUpdate(event: UpdateEvent<any>): Promise<any> {
32
33
  await this.handleComputedFieldEvaluation(event.databaseEntity, ComputedFieldTriggerOperation.beforeUpdate);
33
34
  }
34
35
 
35
- async afterInsert(event: InsertEvent<any>) {
36
- await this.handleComputedFieldEvaluationJob(event.entity, ComputedFieldTriggerOperation.afterInsert);
36
+ afterInsert(event: InsertEvent<any>) {
37
+ this.handleComputedFieldEvaluationJob(event.entity, ComputedFieldTriggerOperation.afterInsert);
37
38
  }
38
39
 
39
- async afterUpdate(event: UpdateEvent<any>) {
40
- await this.handleComputedFieldEvaluationJob(event.databaseEntity, ComputedFieldTriggerOperation.afterUpdate);
40
+ afterUpdate(event: UpdateEvent<any>) {
41
+ this.handleComputedFieldEvaluationJob(event.databaseEntity, ComputedFieldTriggerOperation.afterUpdate);
41
42
  }
42
43
 
43
- async afterRemove(event: any) {
44
- await this.handleComputedFieldEvaluationJob(event.databaseEntity, ComputedFieldTriggerOperation.afterRemove);
44
+ afterRemove(event: any) {
45
+ this.handleComputedFieldEvaluationJob(event.databaseEntity, ComputedFieldTriggerOperation.afterRemove);
45
46
  }
46
47
 
47
- //FIXME: Need to add support for beforeRemmove, beforeSoftRemove, afterSoftRemove, beforeRecover, afterRecover
48
+ //FIXME: Need to add support for beforeRemove, beforeSoftRemove, afterSoftRemove, beforeRecover, afterRecover
48
49
 
49
50
  private async handleComputedFieldEvaluation(entity: any, currentOperation: ComputedFieldTriggerOperation): Promise<void> {
50
51
  if (!entity) {
51
52
  return;
52
53
  }
53
- const currentModelName = camelize(entity.constructor.name); // Resolve the model name from the entity class name
54
- const computedFieldsTobeEvaluated = this.getComputedFieldsToBeEvaluated(
54
+ const computedFieldsTobeEvaluated = this.getComputedFieldsForEvaluation(
55
55
  this.solidRegistry.getComputedFieldMetadata(),
56
56
  currentOperation,
57
- currentModelName
57
+ camelize(entity.constructor.name)
58
58
  );
59
- for (const computedFieldMetadata of computedFieldsTobeEvaluated) {
60
- const computedValue = await this.getComputedValue(computedFieldMetadata, entity); //FIXME: There should some way to check/assert if the provider actually has a postComputeAndSaveValue
61
- entity[computedFieldMetadata.fieldName] = computedValue; // Set the computed value on the entity
62
- }
59
+ //TODO: We can add a feature i.e dependsOn, where we can check if the computed field depends on other computed fields and evaluate them first
60
+ await Promise.all(
61
+ computedFieldsTobeEvaluated.map(c => this.evaluateComputedField(c, entity))
62
+ )
63
63
  }
64
64
 
65
- private async handleComputedFieldEvaluationJob(entity: any, currentOperation: ComputedFieldTriggerOperation): Promise<void> {
65
+ private handleComputedFieldEvaluationJob(entity: any, currentOperation: ComputedFieldTriggerOperation) {
66
66
  if (!entity) {
67
67
  return;
68
68
  }
69
- const currentModelName = camelize(entity.constructor.name); //Resolve the model name from the entity class name
70
- const computedFieldsTobeEvaluated = this.getComputedFieldsToBeEvaluated(
69
+ const computedFieldsTobeEvaluated = this.getComputedFieldsForEvaluation(
71
70
  this.solidRegistry.getComputedFieldMetadata(),
72
71
  currentOperation,
73
- currentModelName
72
+ camelize(entity.constructor.name)
74
73
  );
74
+ //TODO: We can add a feature i.e dependsOn, where we can check if the computed field depends on other computed fields and evaluate them first
75
75
  for (const computedField of computedFieldsTobeEvaluated) {
76
76
  this.enqueueComputedFieldEvaluationJob(computedField, entity);
77
77
  }
78
78
  }
79
79
 
80
+ // Based on the current model name and current operation, identify all the computed providers that need to be evaluated
81
+ // Pass the database entity and the context to the provider of type IEntityComputedFieldProvider
82
+ private getComputedFieldsForEvaluation(computedFieldMetadata: ComputedFieldMetadata[] = [], currentOperation: ComputedFieldTriggerOperation, currentModelName: string) {
83
+ return computedFieldMetadata.filter(
84
+ (computedField) => computedField.computedFieldTriggerConfig.some(
85
+ (trigger) => trigger.operations.includes(currentOperation) &&
86
+ trigger.modelName === currentModelName
87
+ )
88
+ );
89
+ }
90
+
91
+ private async evaluateComputedField(computedFieldMetadata: ComputedFieldMetadata<any>, entity: any) {
92
+ const computedValue = await this.getComputedValue(computedFieldMetadata, entity);
93
+ entity[computedFieldMetadata.fieldName] = computedValue; // Set the computed value on the entity
94
+ }
95
+
80
96
  private async getComputedValue(computedFieldMetadata: ComputedFieldMetadata<any>, entity: any) {
81
- const provider = this.solidRegistry.getComputedFieldProvider(computedFieldMetadata.computedFieldValueProviderName);
82
- // Get the instance of the provider and assert it is of type IEntityComputedFieldProvider
83
- const providerInstance = provider.instance as IEntityPreComputeFieldProvider<any, any, any>; // IEntityComputedFieldProvider
84
- const computedValue = await providerInstance.preComputeValue(entity, computedFieldMetadata); //FIXME There should some way to check/assert if the provider actually has a postComputeAndSaveValue
85
- return computedValue;
97
+ try {
98
+ const provider = this.solidRegistry.getComputedFieldProvider(computedFieldMetadata.computedFieldValueProviderName);
99
+ // Get the instance of the provider and assert it is of type IEntityComputedFieldProvider
100
+ const providerInstance = provider.instance as IEntityPreComputeFieldProvider<any, any, any>; // IEntityComputedFieldProvider
101
+ const computedValue = await providerInstance.preComputeValue(entity, computedFieldMetadata); //FIXME There should some way to check/assert if the provider actually has a postComputeAndSaveValue
102
+ return computedValue;
103
+ } catch (error) {
104
+ throw new InternalServerErrorException(`Error evaluating computed field ${computedFieldMetadata.fieldName} for model ${computedFieldMetadata.modelName} for triggered entity ${entity.constructor.name}: ${error.message}`);
105
+ }
86
106
  }
87
107
 
88
108
  private enqueueComputedFieldEvaluationJob(computedField: ComputedFieldMetadata<any>, databaseEntity: any) {
@@ -95,14 +115,4 @@ export class ComputedEntityFieldSubscriber implements EntitySubscriberInterface
95
115
  });
96
116
  }
97
117
 
98
- // Based on the current model name and current operation, identify all the computed providers that need to be evaluated
99
- // Pass the database entity and the context to the provider of type IEntityComputedFieldProvider
100
- private getComputedFieldsToBeEvaluated(computedFieldMetadata: ComputedFieldMetadata[], currentOperation: ComputedFieldTriggerOperation, currentModelName: string) {
101
- return computedFieldMetadata.filter(
102
- (computedField) => computedField.computedFieldTriggerConfig.some(
103
- (trigger) => trigger.operations.includes(currentOperation) &&
104
- trigger.modelName === currentModelName
105
- )
106
- );
107
- }
108
118
  }