@solidstarters/solid-core 1.2.123 → 1.2.125

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 (94) hide show
  1. package/dist/decorators/disallow-in-production.decorator.d.ts +6 -0
  2. package/dist/decorators/disallow-in-production.decorator.d.ts.map +1 -0
  3. package/dist/decorators/disallow-in-production.decorator.js +25 -0
  4. package/dist/decorators/disallow-in-production.decorator.js.map +1 -0
  5. package/dist/dtos/create-field-metadata.dto.d.ts +14 -0
  6. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  7. package/dist/dtos/create-field-metadata.dto.js +15 -2
  8. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  9. package/dist/entities/field-metadata.entity.d.ts +2 -0
  10. package/dist/entities/field-metadata.entity.d.ts.map +1 -1
  11. package/dist/entities/field-metadata.entity.js +5 -1
  12. package/dist/entities/field-metadata.entity.js.map +1 -1
  13. package/dist/helpers/solid-registry.d.ts +15 -3
  14. package/dist/helpers/solid-registry.d.ts.map +1 -1
  15. package/dist/helpers/solid-registry.js +14 -5
  16. package/dist/helpers/solid-registry.js.map +1 -1
  17. package/dist/interfaces.d.ts +11 -0
  18. package/dist/interfaces.d.ts.map +1 -1
  19. package/dist/interfaces.js.map +1 -1
  20. package/dist/jobs/database/computed-field-evaluation-publisher.service.d.ts +12 -0
  21. package/dist/jobs/database/computed-field-evaluation-publisher.service.d.ts.map +1 -0
  22. package/dist/jobs/database/computed-field-evaluation-publisher.service.js +39 -0
  23. package/dist/jobs/database/computed-field-evaluation-publisher.service.js.map +1 -0
  24. package/dist/jobs/database/computed-field-evaluation-queue-options.d.ts +8 -0
  25. package/dist/jobs/database/computed-field-evaluation-queue-options.d.ts.map +1 -0
  26. package/dist/jobs/database/computed-field-evaluation-queue-options.js +10 -0
  27. package/dist/jobs/database/computed-field-evaluation-queue-options.js.map +1 -0
  28. package/dist/jobs/database/computed-field-evaluation-subscriber.service.d.ts +16 -0
  29. package/dist/jobs/database/computed-field-evaluation-subscriber.service.d.ts.map +1 -0
  30. package/dist/jobs/database/computed-field-evaluation-subscriber.service.js +48 -0
  31. package/dist/jobs/database/computed-field-evaluation-subscriber.service.js.map +1 -0
  32. package/dist/repository/field-metadata.repository.d.ts +8 -0
  33. package/dist/repository/field-metadata.repository.d.ts.map +1 -0
  34. package/dist/repository/field-metadata.repository.js +38 -0
  35. package/dist/repository/field-metadata.repository.js.map +1 -0
  36. package/dist/repository/field.repository.d.ts +1 -1
  37. package/dist/repository/field.repository.d.ts.map +1 -1
  38. package/dist/repository/field.repository.js +6 -6
  39. package/dist/repository/field.repository.js.map +1 -1
  40. package/dist/seeders/seed-data/solid-core-metadata.json +14 -1
  41. package/dist/seeders/system-fields-seeder.service.d.ts +3 -3
  42. package/dist/seeders/system-fields-seeder.service.d.ts.map +1 -1
  43. package/dist/seeders/system-fields-seeder.service.js +5 -6
  44. package/dist/seeders/system-fields-seeder.service.js.map +1 -1
  45. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts +14 -0
  46. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts.map +1 -0
  47. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js +45 -0
  48. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js.map +1 -0
  49. package/dist/services/crud.service.d.ts +1 -0
  50. package/dist/services/crud.service.d.ts.map +1 -1
  51. package/dist/services/crud.service.js +9 -1
  52. package/dist/services/crud.service.js.map +1 -1
  53. package/dist/services/field-metadata.service.d.ts +6 -2
  54. package/dist/services/field-metadata.service.d.ts.map +1 -1
  55. package/dist/services/field-metadata.service.js +21 -4
  56. package/dist/services/field-metadata.service.js.map +1 -1
  57. package/dist/services/model-metadata.service.d.ts.map +1 -1
  58. package/dist/services/model-metadata.service.js +7 -0
  59. package/dist/services/model-metadata.service.js.map +1 -1
  60. package/dist/services/module-metadata.service.d.ts.map +1 -1
  61. package/dist/services/module-metadata.service.js +7 -0
  62. package/dist/services/module-metadata.service.js.map +1 -1
  63. package/dist/services/view-metadata.service.js +1 -2
  64. package/dist/services/view-metadata.service.js.map +1 -1
  65. package/dist/solid-core.module.d.ts.map +1 -1
  66. package/dist/solid-core.module.js +64 -54
  67. package/dist/solid-core.module.js.map +1 -1
  68. package/dist/subscribers/computed-entity-field.subscriber.d.ts +23 -0
  69. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -0
  70. package/dist/subscribers/computed-entity-field.subscriber.js +95 -0
  71. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -0
  72. package/dist/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +1 -1
  74. package/src/# computed field pending issues.md +3 -0
  75. package/src/decorators/disallow-in-production.decorator.ts +28 -0
  76. package/src/dtos/create-field-metadata.dto.ts +18 -0
  77. package/src/entities/field-metadata.entity.ts +4 -0
  78. package/src/helpers/solid-registry.ts +46 -13
  79. package/src/interfaces.ts +18 -0
  80. package/src/jobs/database/computed-field-evaluation-publisher.service.ts +23 -0
  81. package/src/jobs/database/computed-field-evaluation-queue-options.ts +9 -0
  82. package/src/jobs/database/computed-field-evaluation-subscriber.service.ts +36 -0
  83. package/src/repository/field-metadata.repository.ts +26 -0
  84. package/src/repository/field.repository.ts +5 -2
  85. package/src/seeders/seed-data/solid-core-metadata.json +14 -1
  86. package/src/seeders/system-fields-seeder.service.ts +3 -4
  87. package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +52 -0
  88. package/src/services/crud.service.ts +9 -1
  89. package/src/services/field-metadata.service.ts +38 -8
  90. package/src/services/model-metadata.service.ts +2 -0
  91. package/src/services/module-metadata.service.ts +2 -0
  92. package/src/services/view-metadata.service.ts +2 -2
  93. package/src/solid-core.module.ts +64 -54
  94. package/src/subscribers/computed-entity-field.subscriber.ts +108 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.123",
3
+ "version": "1.2.125",
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",
@@ -0,0 +1,3 @@
1
+ # computed fields
2
+ 1. we want the computation to happen before insert / update.
3
+ 2. we want the the provider to do both computation and saving
@@ -0,0 +1,28 @@
1
+ export enum Environment {
2
+ Production = 'prod',
3
+ Development = 'dev',
4
+ }
5
+
6
+ export function DisallowInProduction(action?: string) {
7
+ return function (
8
+ target: any,
9
+ propertyKey: string,
10
+ descriptor: PropertyDescriptor,
11
+ ) {
12
+ const originalMethod = descriptor.value;
13
+
14
+ descriptor.value = function (...args: any[]) {
15
+ const currentEnv = process.env.ENV as Environment;
16
+
17
+ if (currentEnv === Environment.Production) {
18
+ const className = target.constructor.name;
19
+ const methodName = action || propertyKey;
20
+ throw new Error(`❌ Cannot execute "${className}.${methodName}" in production`);
21
+ }
22
+
23
+ return originalMethod.apply(this, args);
24
+ };
25
+
26
+ return descriptor;
27
+ };
28
+ }
@@ -171,6 +171,21 @@ export enum ComputedFieldValueType {
171
171
  datetime = 'datetime',
172
172
  }
173
173
 
174
+ export enum ComputedFieldTriggerOperation {
175
+ beforeInsert = "before-insert",
176
+ afterInsert = "after-insert",
177
+ beforeUpdate = "before-update",
178
+ afterUpdate = "after-update",
179
+ beforeRemove = "before-delete",
180
+ afterRemove = "after-delete",
181
+ }
182
+
183
+ export interface ComputedFieldTriggerConfig {
184
+ moduleName: string; // Name of the module where the computed field is defined
185
+ modelName: string;
186
+ operations: ComputedFieldTriggerOperation[]; // List of operations where this computed field should be triggered
187
+ }
188
+
174
189
  export class CreateFieldMetadataDto {
175
190
  @ApiProperty({ description: 'Name of your field, this is also the name of the column or attribute', })
176
191
  // @Matches(/[a-z]+(_[a-z]+)*/, { message: 'Field name should follow snake case conventions only with all lower case.', })
@@ -370,6 +385,9 @@ export class CreateFieldMetadataDto {
370
385
  @IsOptional()
371
386
  computedFieldValueType: ComputedFieldValueType;
372
387
 
388
+ @IsOptional()
389
+ computedFieldTriggerConfig: ComputedFieldTriggerConfig[];
390
+
373
391
  @IsOptional()
374
392
  @ApiProperty({ description: 'Uuid.', })
375
393
  @IsString()
@@ -2,6 +2,7 @@ import { CommonEntity } from "src/entities/common.entity";
2
2
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
3
3
  import { MediaStorageProviderMetadata } from "./media-storage-provider-metadata.entity";
4
4
  import { ModelMetadata } from "./model-metadata.entity";
5
+ import { ComputedFieldTriggerConfig } from "src/dtos/create-field-metadata.dto";
5
6
 
6
7
  @Entity("ss_field_metadata")
7
8
  export class FieldMetadata extends CommonEntity {
@@ -132,6 +133,9 @@ export class FieldMetadata extends CommonEntity {
132
133
  @Column({ name: 'computed_field_value_type', nullable: true })
133
134
  computedFieldValueType: string;
134
135
 
136
+ @Column({ name: 'computed_field_trigger_config', nullable: true, type: 'jsonb' })
137
+ computedFieldTriggerConfig: ComputedFieldTriggerConfig[];
138
+
135
139
  @Column({ name: 'uuid', nullable: true })
136
140
  uuid: string;
137
141
 
@@ -1,10 +1,11 @@
1
1
  import { Injectable } from '@nestjs/common';
2
2
  import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
3
+ import { ComputedFieldTriggerConfig, ComputedFieldValueType } from 'src/dtos/create-field-metadata.dto';
3
4
  import { CommonEntity } from 'src/entities/common.entity';
5
+ import { Locale } from 'src/entities/locale.entity';
4
6
  import { SecurityRule } from 'src/entities/security-rule.entity';
5
7
  import { EntityManager } from 'typeorm';
6
8
  import { ISelectionProvider, ISelectionProviderContext } from "../interfaces";
7
- import { Locale } from 'src/entities/locale.entity';
8
9
 
9
10
  type ControllerMetadata = {
10
11
  name: string;
@@ -38,6 +39,18 @@ export enum RESERVED_SOLID_KEYWORDS {
38
39
  locale = "locale"
39
40
  }
40
41
 
42
+ export interface ComputedFieldMetadata<TContext = any> {
43
+ moduleName: string; // Name of the module where the computed field is defined
44
+ modelName: string; // Name of the model where the computed field is defined
45
+ fieldName: string; // Name of the field that is computed
46
+ computedFieldValueType: ComputedFieldValueType; // Type of the computed field value (e.g., string, number, etc.)
47
+ computedFieldTriggerConfig: ComputedFieldTriggerConfig[]; // JSON stringified object containing the trigger configuration
48
+ // Example: '{"models": ["User", "Product"], "operations": ["create", "update"]}'
49
+ computedFieldValueProviderName: string; // Name of the provider that computes the field value
50
+ // Example: '{"contextKey": "contextValue"}'
51
+ computedFieldValueProviderCtxt: TContext; // Context for the computed field value
52
+ }
53
+
41
54
  @Injectable()
42
55
  export class SolidRegistry {
43
56
  private seeders: Set<InstanceWrapper> = new Set();
@@ -47,7 +60,8 @@ export class SolidRegistry {
47
60
  private controllers: Set<ControllerMetadata> = new Set();
48
61
  private modules: Set<InstanceWrapper> = new Set();
49
62
  private securityRules: SecurityRule[] = [];
50
- private locales : Locale[] = [];
63
+ private locales: Locale[] = [];
64
+ private computedFieldMetadata: ComputedFieldMetadata[] = [];
51
65
 
52
66
  registerController(name: string, methodNames: string[]): void {
53
67
  this.controllers.add({ name: name, methods: methodNames });
@@ -104,6 +118,14 @@ export class SolidRegistry {
104
118
  return Array.from(this.computedFieldProviders);
105
119
  }
106
120
 
121
+ getComputedFieldProvider(name: string): InstanceWrapper {
122
+ const provider = this.getComputedFieldProviders().filter((provider) => provider.name === name).pop();
123
+ if (!provider) {
124
+ throw new Error(`Computed Field Provider with name ${name} not found`);
125
+ }
126
+ return provider
127
+ }
128
+
107
129
  getSolidDatabaseModules(): Array<InstanceWrapper> {
108
130
  return Array.from(this.solidDatabaseModules);
109
131
  }
@@ -121,10 +143,18 @@ export class SolidRegistry {
121
143
  this.securityRules = securityRules;
122
144
  }
123
145
 
124
- registerlocales(locales : Locale[]){
146
+ registerlocales(locales: Locale[]) {
125
147
  this.locales = locales;
126
148
  }
127
-
149
+
150
+ registerComputedFieldMetadata(computedFieldMetadata: ComputedFieldMetadata[]) {
151
+ this.computedFieldMetadata = computedFieldMetadata;
152
+ }
153
+
154
+ getComputedFieldMetadata(): ComputedFieldMetadata[] {
155
+ return this.computedFieldMetadata;
156
+ }
157
+
128
158
  //TODO:getlocales from locale model and return default locale where isDefault:true
129
159
  getDefaultLocale(): Locale | null {
130
160
  return this.locales.find(locale => locale.isDefault === true) || null;
@@ -141,16 +171,19 @@ export class SolidRegistry {
141
171
  });
142
172
  }
143
173
 
174
+ /**
175
+ * @deprecated You can directly pass the entityName to typeORM, if you are using this just for doing operations against a particular entity.
176
+ */
144
177
  // Returns the entity target class from the entity name
145
- getEntityTarget(entityManager: EntityManager, entityName: string): any { //TODO Can be refactored to use this function from crud helper service
146
- const entityMetadatas = entityManager.connection.entityMetadatas;
147
- const entityMetadata = entityMetadatas.find(em => em.name === entityName);
148
- return entityMetadata.target;
149
- }
150
-
151
- getCommonEntityKeys(): (keyof CommonEntity) [] {
152
- return [ 'id', 'createdAt', 'updatedAt', 'deletedAt', 'createdBy', 'updatedBy', 'deletedTracker', 'localeName', 'defaultEntityLocaleId', 'publishedAt'];
153
- // return Reflect.getMetadataKeys(CommonEntity.prototype) as (keyof CommonEntity)[];
178
+ // getEntityTarget(entityManager: EntityManager, entityName: string): any { //TODO Can be refactored to use this function from crud helper service
179
+ // const entityMetadatas = entityManager.connection.entityMetadatas;
180
+ // const entityMetadata = entityMetadatas.find(em => em.name === entityName);
181
+ // return entityMetadata.target;
182
+ // }
183
+
184
+ getCommonEntityKeys(): (keyof CommonEntity)[] {
185
+ return ['id', 'createdAt', 'updatedAt', 'deletedAt', 'createdBy', 'updatedBy', 'deletedTracker', 'localeName', 'defaultEntityLocaleId', 'publishedAt'];
186
+ // return Reflect.getMetadataKeys(CommonEntity.prototype) as (keyof CommonEntity)[];
154
187
  }
155
188
 
156
189
  }
package/src/interfaces.ts CHANGED
@@ -12,6 +12,7 @@ import { CreateViewMetadataDto } from './dtos/create-view-metadata.dto';
12
12
  import { FieldMetadata } from './entities/field-metadata.entity';
13
13
  import { Media } from './entities/media.entity';
14
14
  import { Readable } from 'stream';
15
+ import { ComputedFieldMetadata } from './helpers/solid-registry';
15
16
 
16
17
  export interface FieldCrudManager {
17
18
  // fieldMetadata: FieldMetadata;
@@ -80,6 +81,9 @@ export interface ISelectionProvider<T extends ISelectionProviderContext> {
80
81
  values(query: any, ctxt: T): Promise<readonly ISelectionProviderValues[]>;
81
82
  }
82
83
 
84
+ /**
85
+ * @deprecated Use `IEntityComputedFieldProvider` instead.
86
+ */
83
87
  export interface IComputedFieldProvider<T> {
84
88
  help(): string;
85
89
 
@@ -90,6 +94,20 @@ export interface IComputedFieldProvider<T> {
90
94
  computeValue(dto: any, ctxt: T): Promise<string | number>; // FIXME : Improve the types to make it more specific using generics
91
95
  }
92
96
 
97
+ export interface IEntityComputedFieldProvider {
98
+ help(): string;
99
+
100
+ name(): string;
101
+ }
102
+
103
+ export interface IEntityPreComputeFieldProvider<TTriggerEntity, TContext, TValue> extends IEntityComputedFieldProvider {
104
+ preComputeValue(entity: TTriggerEntity, computedFieldMetadata: ComputedFieldMetadata<TContext>): Promise<TValue>;
105
+ }
106
+
107
+ export interface IEntityPostComputeFieldProvider<TTriggerEntity, TContext> extends IEntityComputedFieldProvider {
108
+ postComputeAndSaveValue(triggerEntity: TTriggerEntity, computedFieldMetadata: ComputedFieldMetadata<TContext>): Promise<void>;
109
+ }
110
+
93
111
  export interface ISolidDatabaseModule {
94
112
  name(): string;
95
113
 
@@ -0,0 +1,23 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { QueuesModuleOptions } from "src/interfaces";
3
+ import { MqMessageQueueService } from "src/services/mq-message-queue.service";
4
+ import { MqMessageService } from "src/services/mq-message.service";
5
+ import { DatabasePublisher } from "src/services/queues/database-publisher.service";
6
+ import { ComputedFieldEvaluationPayload } from "src/subscribers/computed-entity-field.subscriber";
7
+ import computedFieldEvaluationQueueOptions from "./computed-field-evaluation-queue-options";
8
+
9
+ @Injectable()
10
+ export class ComputedFieldEvaluationPublisher extends DatabasePublisher<ComputedFieldEvaluationPayload> {
11
+ constructor(
12
+ protected readonly mqMessageService: MqMessageService,
13
+ protected readonly mqMessageQueueService: MqMessageQueueService,
14
+ ) {
15
+ super(mqMessageService, mqMessageQueueService);
16
+ }
17
+
18
+ options(): QueuesModuleOptions {
19
+ return {
20
+ ...computedFieldEvaluationQueueOptions
21
+ };
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import { BrokerType } from "../../interfaces";
2
+
3
+ const QUEUE_NAME = 'computed_field_evaluation_queue';
4
+
5
+ export default {
6
+ name: 'computedFieldEvaluationQueue',
7
+ type: BrokerType.Database,
8
+ queueName: QUEUE_NAME,
9
+ };
@@ -0,0 +1,36 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { SolidRegistry } from "src/helpers/solid-registry";
3
+ import { IEntityPostComputeFieldProvider, IEntityComputedFieldProvider, QueuesModuleOptions } from "src/interfaces";
4
+ import { QueueMessage } from "src/interfaces/mq";
5
+ import { MqMessageQueueService } from "src/services/mq-message-queue.service";
6
+ import { MqMessageService } from "src/services/mq-message.service";
7
+ import { DatabaseSubscriber } from "src/services/queues/database-subscriber.service";
8
+ import { ComputedFieldEvaluationPayload } from "src/subscribers/computed-entity-field.subscriber";
9
+ import computedFieldEvaluationQueueOptions from "./computed-field-evaluation-queue-options";
10
+
11
+ @Injectable()
12
+ export class ComputedFieldEvaluationSubscriber extends DatabaseSubscriber<ComputedFieldEvaluationPayload> {
13
+ constructor(
14
+ readonly mqMessageService: MqMessageService,
15
+ readonly mqMessageQueueService: MqMessageQueueService,
16
+ readonly solidRegistry: SolidRegistry
17
+ ) {
18
+ super(mqMessageService, mqMessageQueueService);
19
+ }
20
+
21
+ options(): QueuesModuleOptions {
22
+ return {
23
+ ...computedFieldEvaluationQueueOptions
24
+ }
25
+ }
26
+
27
+ // This method will use the ComputedFieldEvaluationPayload to evaluate the computed fields
28
+ // It will then call the corresponding provider computeAndSave method to perform the evaluation
29
+ async subscribe(message: QueueMessage<ComputedFieldEvaluationPayload>) {
30
+ const { databaseEntity, ...computedFieldMetadata } = message.payload;
31
+ const provider = this.solidRegistry.getComputedFieldProvider(computedFieldMetadata.computedFieldValueProviderName);
32
+ // Get the instance of the provider and assert it is of type IEntityComputedFieldProvider
33
+ const providerInstance = provider.instance as IEntityPostComputeFieldProvider<any, any>; // IEntityComputedFieldProvider
34
+ await providerInstance.postComputeAndSaveValue(databaseEntity, computedFieldMetadata); //FIXME There should some way to check/assert if the provider actually has a postComputeAndSaveValue
35
+ }
36
+ }
@@ -0,0 +1,26 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { SolidFieldType } from 'src/dtos/create-field-metadata.dto';
3
+ import { FieldMetadata } from 'src/entities/field-metadata.entity';
4
+ import { DataSource, Repository } from 'typeorm';
5
+
6
+ @Injectable()
7
+ export class FieldMetadataRepository extends Repository<FieldMetadata> {
8
+ private readonly logger = new Logger(FieldMetadataRepository.name);
9
+ constructor(
10
+ dataSource: DataSource,
11
+ ) {
12
+ super(FieldMetadata, dataSource.createEntityManager());
13
+ }
14
+
15
+ async findComputedFieldsPopulatedWithModelAndModule() {
16
+ const computedFields = await this.find({
17
+ where: {
18
+ type: SolidFieldType.computed,
19
+ },
20
+ relations: ['model', 'model.module'],
21
+ });
22
+
23
+ return computedFields;
24
+ }
25
+
26
+ }
@@ -1,8 +1,11 @@
1
- import { Repository, DataSource } from 'typeorm';
2
1
  import { Injectable } from '@nestjs/common';
3
- import { FieldMetadata } from 'src/entities/field-metadata.entity';
4
2
  import { InjectRepository } from '@nestjs/typeorm';
3
+ import { FieldMetadata } from 'src/entities/field-metadata.entity';
4
+ import { DataSource, Repository } from 'typeorm';
5
5
 
6
+ /**
7
+ * @deprecated Use `FieldMetadataRepository` instead.
8
+ */
6
9
  @Injectable()
7
10
  export class FieldRepository extends Repository<FieldMetadata> {
8
11
  constructor(
@@ -844,6 +844,19 @@
844
844
  "columnName": "computed_field_value_type",
845
845
  "isSystem": true
846
846
  },
847
+ {
848
+ "name": "computedFieldTriggerConfig",
849
+ "displayName": "Computed Field Value Trigger Configuration",
850
+ "type": "json",
851
+ "ormType": "text",
852
+ "required": false,
853
+ "unique": false,
854
+ "index": false,
855
+ "private": false,
856
+ "encrypt": false,
857
+ "columnName": "computed_field_trigger_config",
858
+ "isSystem": true
859
+ },
847
860
  {
848
861
  "name": "uuid",
849
862
  "displayName": "UUID",
@@ -2609,7 +2622,7 @@
2609
2622
  "ormType": "integer",
2610
2623
  "isSystem": false,
2611
2624
  "relationType": "many-to-one",
2612
- "relationCoModelFieldName": "bondTransactionDetailLogs",
2625
+ "relationCoModelFieldName": "savedFilters",
2613
2626
  "relationCreateInverse": false,
2614
2627
  "relationCoModelSingularName": "user",
2615
2628
  "relationCoModelColumnName": null,
@@ -4,7 +4,7 @@
4
4
  import { InjectRepository } from "@nestjs/typeorm";
5
5
  import { ModelMetadata } from "src/entities/model-metadata.entity";
6
6
  import { ModelMetadataHelperService } from "src/helpers/model-metadata-helper.service";
7
- import { FieldRepository } from "src/repository/field.repository";
7
+ import { FieldMetadataRepository } from "src/repository/field-metadata.repository";
8
8
  import { Repository } from "typeorm";
9
9
 
10
10
  export class SystemFieldsSeederService {
@@ -15,8 +15,7 @@ export class SystemFieldsSeederService {
15
15
  private readonly modelHelperService: ModelMetadataHelperService,
16
16
  @InjectRepository(ModelMetadata)
17
17
  private readonly modelRepository: Repository<ModelMetadata>, // Replace with actual model repository type
18
- @InjectRepository(FieldRepository)
19
- private readonly fieldRepository: Repository<FieldRepository>, // Replace with actual field repository type
18
+ private readonly fieldMetadataRepository: FieldMetadataRepository, // Replace with actual field repository type
20
19
  ) {}
21
20
 
22
21
  async seed() {
@@ -46,7 +45,7 @@ export class SystemFieldsSeederService {
46
45
  ...field,
47
46
  model: model, // Associate the field with the current model
48
47
  }));
49
- await this.fieldRepository.save(newFields);
48
+ await this.fieldMetadataRepository.save(newFields);
50
49
  }
51
50
  }
52
51
  }
@@ -0,0 +1,52 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { kebabCase } from "lodash";
3
+ import { ComputedFieldProvider } from "src/decorators/computed-field-provider.decorator";
4
+ import { CommonEntity } from "src/entities/common.entity";
5
+ import { ComputedFieldMetadata } from "src/helpers/solid-registry";
6
+ import { IEntityPreComputeFieldProvider } from "src/interfaces";
7
+
8
+
9
+ export interface ConcatComputedFieldContext {
10
+ separator: string; // The separator to use between concatenated values
11
+ fields: string[]; // The fields to concatenate
12
+ slugify?: boolean; // Optional: if true, slugify each field value before concatenation
13
+ }
14
+
15
+ @ComputedFieldProvider()
16
+ @Injectable()
17
+ export class ConcatEntityComputedFieldProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, ConcatComputedFieldContext, string> {
18
+
19
+ name(): string {
20
+ return "ConcatEntityComputedFieldProvider";
21
+ }
22
+
23
+ help(): string {
24
+ return "Computed field provider used to create fields whose value is a concatenation of other fields in the same model.";
25
+ }
26
+
27
+ async preComputeValue(entity: T, computedFieldMetadata: ComputedFieldMetadata<ConcatComputedFieldContext>): Promise<string> {
28
+ const { computedFieldValueProviderCtxt } = computedFieldMetadata;
29
+ const separator = computedFieldValueProviderCtxt.separator || ' '; // Default to space if no separator is provided
30
+ const fields = computedFieldValueProviderCtxt.fields || [];
31
+ const slugify = computedFieldValueProviderCtxt.slugify || false;
32
+
33
+ let concatenatedString = '';
34
+ for (let i = 0; i < fields.length; i++) {
35
+ const field = fields[i];
36
+
37
+ // if slugify then each field val to be converted to a slug before concatenation
38
+ let fieldVal = entity[field];
39
+ if (slugify && typeof fieldVal === 'string') {
40
+ fieldVal = kebabCase(fieldVal);
41
+ }
42
+
43
+ if (concatenatedString) {
44
+ concatenatedString += separator;
45
+ }
46
+ concatenatedString += fieldVal;
47
+ }
48
+
49
+ return concatenatedString;
50
+ }
51
+
52
+ }
@@ -403,7 +403,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
403
403
 
404
404
  // The value will be computed by the computed provider
405
405
  // Invoke the appropriate computed provider, get the value and add to the dto
406
- const options = { ...commonOptions, computedFieldProvider: fieldMetadata.computedFieldValueProvider, computedFieldValueProviderCtxt: fieldMetadata.computedFieldValueProviderCtxt, computedFieldValueType: fieldMetadata.computedFieldValueType as ComputedFieldValueType, discoveryService: this.discoveryService, skipComputation: isPartialUpdate };
406
+ const options = { ...commonOptions, computedFieldProvider: fieldMetadata.computedFieldValueProvider, computedFieldValueProviderCtxt: fieldMetadata.computedFieldValueProviderCtxt, computedFieldValueType: fieldMetadata.computedFieldValueType as ComputedFieldValueType, discoveryService: this.discoveryService, skipComputation: this.isSkipComputation(isPartialUpdate, fieldMetadata) };
407
407
  return new ComputedFieldCrudManager(options);
408
408
  }
409
409
  default:
@@ -411,6 +411,14 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
411
411
  }
412
412
  }
413
413
 
414
+ private isSkipComputation(isPartialUpdate: boolean, computedFieldMetadata: FieldMetadata) {
415
+ if (isPartialUpdate) return true; // If it is a partial update, then skip computation
416
+ if (computedFieldMetadata.computedFieldTriggerConfig && computedFieldMetadata.computedFieldTriggerConfig.length > 0) {
417
+ return true; // computedFieldTriggerConfig is a new field introduced as part of the IEntityComputedFieldProvider new interface, so this computation will be skiipped in crud service & will be called in the subscriber instead
418
+ }
419
+ return false; // If it is not a partial update, then do not skip computation
420
+ }
421
+
414
422
  async find(basicFilterDto: BasicFilterDto, solidRequestContext: any = {}) {
415
423
  const alias = 'entity';
416
424
  // Extract the required keys from the input query
@@ -1,25 +1,25 @@
1
- import { Injectable, Logger, NotFoundException } from '@nestjs/common';
2
- import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
1
+ import { classify } from '@angular-devkit/core/src/utils/strings';
2
+ import { Injectable, Logger, NotFoundException, OnApplicationBootstrap } from '@nestjs/common';
3
+ import { InjectDataSource } from '@nestjs/typeorm';
3
4
  import * as fs from 'fs/promises'; // Use the Promise-based version of fs for async/await
4
5
  import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
5
- import { SolidRegistry } from 'src/helpers/solid-registry';
6
+ import { ComputedFieldMetadata, SolidRegistry } from 'src/helpers/solid-registry';
7
+ import { FieldMetadataRepository } from 'src/repository/field-metadata.repository';
6
8
  import { DataSource, Repository, SelectQueryBuilder } from 'typeorm';
7
9
  import { BasicFilterDto } from '../dtos/basic-filters.dto';
8
- import { CascadeType, ComputedFieldValueType, CreateFieldMetadataDto, DecryptWhenType, EncryptionType, MediaType, PSQLType, RelationType, SelectionValueType, SolidFieldType } from '../dtos/create-field-metadata.dto';
10
+ import { CascadeType, ComputedFieldTriggerConfig, ComputedFieldTriggerOperation, ComputedFieldValueType, CreateFieldMetadataDto, DecryptWhenType, EncryptionType, MediaType, PSQLType, RelationType, SelectionValueType, SolidFieldType } from '../dtos/create-field-metadata.dto';
9
11
  import { SelectionDynamicQueryDto } from '../dtos/selection-dynamic-query.dto';
10
12
  import { UpdateFieldMetaDataDto } from '../dtos/update-field-metadata.dto';
11
13
  import { FieldMetadata } from '../entities/field-metadata.entity';
12
14
  import { ModelMetadata } from '../entities/model-metadata.entity';
13
15
  import { ISelectionProviderValues } from '../interfaces';
14
16
  import { CrudHelperService } from './crud-helper.service';
15
- import { classify } from '@angular-devkit/core/src/utils/strings';
16
17
 
17
18
 
18
19
  @Injectable()
19
- export class FieldMetadataService {
20
+ export class FieldMetadataService implements OnApplicationBootstrap {
20
21
  constructor(
21
- @InjectRepository(FieldMetadata)
22
- private readonly fieldMetadataRepo: Repository<FieldMetadata>,
22
+ private readonly fieldMetadataRepo: FieldMetadataRepository,
23
23
  @InjectDataSource()
24
24
  private readonly dataSource: DataSource,
25
25
  private readonly solidRegistry: SolidRegistry,
@@ -29,6 +29,36 @@ export class FieldMetadataService {
29
29
 
30
30
  private logger = new Logger(FieldMetadataService.name);
31
31
 
32
+ onApplicationBootstrap() {
33
+ this.loadAndRegisterComputedFieldsDetails();
34
+ }
35
+
36
+ async loadAndRegisterComputedFieldsDetails() {
37
+ // Load all the modules and models and within that load all the computed fields
38
+ const computedFieldsWithModelAndModule = await this.fieldMetadataRepo.findComputedFieldsPopulatedWithModelAndModule();
39
+
40
+ // Convert the computed fields object above to the ComputedFieldMetadata type
41
+ const computedFieldMetadata: ComputedFieldMetadata[] = computedFieldsWithModelAndModule.map((field) => {
42
+ // const defaultComputedFieldTriggerConfig: ComputedFieldTriggerConfig = {
43
+ // moduleName: field.model.module.name,
44
+ // modelName: field.model.singularName,
45
+ // operations: [ComputedFieldTriggerOperation.create, ComputedFieldTriggerOperation.update, ComputedFieldTriggerOperation.delete], // Default operations, can be overridden
46
+ // }
47
+ return {
48
+ moduleName: field.model.module.name,
49
+ modelName: field.model.singularName,
50
+ fieldName: field.name,
51
+ computedFieldValueType: field.computedFieldValueType as ComputedFieldValueType,
52
+ computedFieldTriggerConfig: field.computedFieldTriggerConfig ?? [], // Ensure it's an array, default to empty if not provided
53
+ computedFieldValueProviderName: field.computedFieldValueProvider,
54
+ computedFieldValueProviderCtxt: field.computedFieldValueProviderCtxt ? JSON.parse(field.computedFieldValueProviderCtxt) : {}, // Parse the context if it exists, default to empty object
55
+ };
56
+ });
57
+
58
+ // Register the computed fields in the SolidRegistry. Capture only computed field related info
59
+ this.solidRegistry.registerComputedFieldMetadata(computedFieldMetadata);
60
+ }
61
+
32
62
  async updateInverseField(field: FieldMetadata, fieldRepository: Repository<FieldMetadata>, modelRepository: Repository<ModelMetadata>) {
33
63
  if (!field.model || !field.model.module) {
34
64
  throw new Error('Model and module are required to update inverse field');
@@ -27,6 +27,7 @@ import { RoleMetadataService } from './role-metadata.service';
27
27
  import { GenerateCodePublisher } from 'src/jobs/database/generate-code-publisher.service';
28
28
  import { PermissionMetadata } from 'src/entities/permission-metadata.entity';
29
29
  import { classify, dasherize } from '@angular-devkit/core/src/utils/strings';
30
+ import { DisallowInProduction } from 'src/decorators/disallow-in-production.decorator';
30
31
 
31
32
  @Injectable()
32
33
  export class ModelMetadataService {
@@ -711,6 +712,7 @@ export class ModelMetadataService {
711
712
 
712
713
  }
713
714
 
715
+ @DisallowInProduction()
714
716
  async handleGenerateCode(options: CodeGenerationOptions): Promise<any> {
715
717
  // // see if the model record exists.
716
718
  // const model = await this.modelMetadataRepo.findOne({
@@ -21,6 +21,7 @@ import { CodeGenerationOptions, ModuleMetadataConfiguration } from '../interface
21
21
  import { CrudHelperService } from './crud-helper.service';
22
22
  import { ModelMetadataService } from './model-metadata.service';
23
23
  import { ModuleMetadataHelperService } from 'src/helpers/module-metadata-helper.service';
24
+ import { DisallowInProduction } from 'src/decorators/disallow-in-production.decorator';
24
25
 
25
26
  @Injectable()
26
27
  export class ModuleMetadataService {
@@ -349,6 +350,7 @@ export class ModuleMetadataService {
349
350
  return true
350
351
  }
351
352
 
353
+ @DisallowInProduction()
352
354
  async generateCode(options: CodeGenerationOptions): Promise<string> {
353
355
  if (!options.moduleId && !options.moduleUserKey) {
354
356
  throw new BadRequestException('Module ID or Module Name is required for generating code');
@@ -57,8 +57,8 @@ export class ViewMetadataService extends CRUDService<ViewMetadata> {
57
57
  defaultEntityLocaleIdFromQuery?: string
58
58
  ): Promise<{ records: any[], defaultEntityLocaleId: string | null }> {
59
59
  const solidRegistry = await this.moduleRef.get(SolidRegistry, { strict: false });
60
- const currentEntityTarget = solidRegistry.getEntityTarget(this.entityManager, classify(modelName));
61
- const currentEntityRepository = this.entityManager.getRepository(currentEntityTarget);
60
+ // const currentEntityTarget = solidRegistry.getEntityTarget(this.entityManager, classify(modelName));
61
+ const currentEntityRepository = this.entityManager.getRepository(classify(modelName));
62
62
 
63
63
  // Case 1: Creating a new record with no defaultEntityLocaleId to clone
64
64
  if (id === 'new' && !defaultEntityLocaleIdFromQuery) {