@solidxai/core 0.1.17 → 0.1.18-beta.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.
@@ -26,7 +26,7 @@ export class SelectionStaticFieldCrudManager implements FieldCrudManager {
26
26
  return [
27
27
  {
28
28
  field: this.options.fieldName,
29
- error: `Field: ${this.options.fieldName} must be a valid array`,
29
+ error: `Field: ${this.options.fieldName} must be a valid string representation of a JSON array of selection values. Allowed values: '${this.formatAllowedValues()}'. Received: ${this.formatValue(fieldValue)}`,
30
30
  },
31
31
  ];
32
32
  }
@@ -43,7 +43,7 @@ export class SelectionStaticFieldCrudManager implements FieldCrudManager {
43
43
  }
44
44
 
45
45
  // Apply validations to each value
46
- const allErrors = await Promise.all(values.map((val) => this.applyValidations(val)));
46
+ const allErrors = await Promise.all(values.map((val) => this.applyValidations(val, values)));
47
47
  return allErrors.flat();
48
48
  } else {
49
49
  // For non-multi-select, apply validations to the single field value
@@ -69,22 +69,47 @@ export class SelectionStaticFieldCrudManager implements FieldCrudManager {
69
69
  }
70
70
 
71
71
 
72
- private applyValidations(fieldValue: any): ValidationError[] {
72
+ private applyValidations(fieldValue: any, receivedValue: any = fieldValue): ValidationError[] {
73
73
  const errors: ValidationError[] = [];
74
74
  this.isApplyRequiredValidation() && isEmpty(fieldValue) ? errors.push({ field: this.options.fieldName, error: `Field: ${this.options.fieldName} is required` }) : "no errors";
75
75
  if (isNotEmpty(fieldValue)) {
76
- errors.push(...this.applyFormatValidations(fieldValue));
76
+ errors.push(...this.applyFormatValidations(fieldValue, receivedValue));
77
77
  }
78
78
  return errors;
79
79
  }
80
80
 
81
- private applyFormatValidations(fieldValue: any): ValidationError[] {
81
+ private applyFormatValidations(fieldValue: any, receivedValue: any = fieldValue): ValidationError[] {
82
82
  const errors: ValidationError[] = [];
83
- !this.isValidSelectionValueType(fieldValue, this.options.selectionValueType) ? errors.push({ field: this.options.fieldName, error: 'Field value is invalid' }) : "no errors";
84
- !this.isValidSelectionStaticValue(fieldValue, this.options.selectionValueType, this.options.selectionStaticValues) ? errors.push({ field: this.options.fieldName, error: 'Field value is invalid' }) : "no errors";
83
+ const allowedValues = this.formatAllowedValues();
84
+ const received = this.formatValue(receivedValue);
85
+ !this.isValidSelectionValueType(fieldValue, this.options.selectionValueType) ? errors.push({
86
+ field: this.options.fieldName,
87
+ error: `Field: ${this.options.fieldName} has an invalid selection value type. Expected ${this.options.selectionValueType}. Allowed values: ${allowedValues}. Received: ${received}`,
88
+ }) : "no errors";
89
+ !this.isValidSelectionStaticValue(fieldValue, this.options.selectionValueType, this.options.selectionStaticValues) ? errors.push({
90
+ field: this.options.fieldName,
91
+ error: `Field: ${this.options.fieldName} contains an invalid selection value. Allowed values: ${allowedValues}. Received: ${received}`,
92
+ }) : "no errors";
85
93
  return errors;
86
94
  }
87
95
 
96
+ private formatAllowedValues(): string {
97
+ const values = this.options.selectionStaticValues.map((value) => {
98
+ const rawValue = value.split(":")[0];
99
+ return this.options.selectionValueType === SelectionValueType.int ? Number(rawValue) : rawValue;
100
+ });
101
+ return this.formatValue(values);
102
+ }
103
+
104
+ private formatValue(value: any): string {
105
+ try {
106
+ const formatted = JSON.stringify(value);
107
+ return formatted === undefined ? String(value) : formatted;
108
+ } catch {
109
+ return String(value);
110
+ }
111
+ }
112
+
88
113
  transformForCreate(dto: any): any {
89
114
  return dto;
90
115
  }
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export * from './decorators/security-rule-config-provider.decorator'
29
29
  export * from './decorators/sms-provider.decorator'
30
30
  export * from './decorators/settings-provider.decorator'
31
31
  export * from './decorators/extension-user-creation-provider.decorator'
32
+ export * from './decorators/whatsapp-provider.decorator'
32
33
  export * from './decorators/workflow-node-provider.decorator'
33
34
  export * from './decorators/workflow-field-data-provider.decorator'
34
35
 
@@ -300,15 +300,23 @@ private async prepareManyToManyAuditSnapshot(entity: T,id: number,modelSingularN
300
300
  field.relationType !== 'one-to-many'
301
301
  );
302
302
  if (auditRelationFields.length > 0) {
303
- const relations: any = {};
304
- auditRelationFields.forEach(field => relations[field.name] = true);
303
+ // Fetch each audit-tracked relation independently rather than joining all of
304
+ // them into a single query: for an entity with several many-to-many
305
+ // relations, one combined query multiplies row counts across every joined
306
+ // relation at once and can blow past the statement timeout as data grows.
305
307
  const auditBeforeEntity = await this.repo.findOne({
306
308
  where: {
307
309
  id: id,
308
310
  } as unknown as FindOptionsWhere<T>,
309
- relations: relations as any,
310
311
  });
311
312
  if (auditBeforeEntity) {
313
+ for (const field of auditRelationFields) {
314
+ (auditBeforeEntity as any)[field.name] = await this.repo.manager
315
+ .createQueryBuilder()
316
+ .relation(this.repo.target, field.name)
317
+ .of(id)
318
+ .loadMany();
319
+ }
312
320
  Object.defineProperty(entity, AUDIT_BEFORE_SNAPSHOT, {
313
321
  configurable: true,
314
322
  enumerable: false,
@@ -98,19 +98,26 @@ export class AuditSubscriber implements EntitySubscriberInterface {
98
98
  return null;
99
99
  }
100
100
 
101
- const relations: Record<string, boolean> = {};
102
-
103
- auditRelationFields.forEach(field => {
104
- relations[field.name] = true;
105
- });
106
-
107
101
  const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;
108
102
 
109
- const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({
103
+ // Same fix as CRUDService.prepareManyToManyAuditSnapshot: load each audit-tracked
104
+ // relation independently instead of joining them all into one query, so the row
105
+ // count of one relation doesn't multiply against every other joined relation.
106
+ const targetRepo = event.queryRunner.manager.getRepository(event.metadata.target as any);
107
+ const relationAfter = await targetRepo.findOne({
110
108
  where: { id: entityId } as any,
111
- relations: relations as any,
112
109
  });
113
110
 
111
+ if (relationAfter) {
112
+ for (const field of auditRelationFields) {
113
+ (relationAfter as any)[field.name] = await event.queryRunner.manager
114
+ .createQueryBuilder()
115
+ .relation(event.metadata.target as any, field.name)
116
+ .of(entityId)
117
+ .loadMany();
118
+ }
119
+ }
120
+
114
121
  if (relationBefore && relationAfter) {
115
122
  auditRelationFields.forEach(field => {
116
123
  const oldIds = Array.isArray(relationBefore[field.name])