@solidstarters/solid-core 1.2.169 → 1.2.170

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.169",
3
+ "version": "1.2.170",
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",
@@ -33,7 +33,7 @@ export class HttpExceptionFilter implements ExceptionFilter {
33
33
  // Canonical code + static message
34
34
  const code: ErrorCode = this.errorMapper.mapException(exception);
35
35
  const defaultStatus = this.errorMapper.getHttpStatus(code);
36
- const message = code === 'unknown-error' ? `${exception?.message}` : this.errorMapper.getMessage(code);
36
+ const message = code === 'solidx-unknown-error' ? `${exception?.message}` : this.errorMapper.getMessage(code);
37
37
 
38
38
  const status = explicitStatus ?? defaultStatus ?? 500;
39
39
 
@@ -1,40 +1,6 @@
1
1
  import { Injectable, Logger } from '@nestjs/common';
2
2
  import { SolidRegistry } from 'src/helpers/solid-registry';
3
3
  import { ErrorCode, ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
4
-
5
- // export const ERROR_CODES = [
6
- // 'db-duplicate-key',
7
- // 'db-foreign-key-error',
8
- // 'solidx-mcp-server-unavailable',
9
- // 'unknown-error',
10
- // ] as const;
11
-
12
- // export type ErrorCode = typeof ERROR_CODES[number];
13
-
14
- // type ErrorMeta = {
15
- // message: string;
16
- // httpStatus?: number;
17
- // };
18
-
19
- // const ERROR_MESSAGES: Record<ErrorCode, ErrorMeta> = {
20
- // 'db-duplicate-key': {
21
- // message: 'Duplicate key violation. A record with these values already exists.',
22
- // httpStatus: 409,
23
- // },
24
- // 'db-foreign-key-error': {
25
- // message: 'Foreign key constraint prevents this operation due to related records.',
26
- // httpStatus: 409,
27
- // },
28
- // 'solidx-mcp-server-unavailable': {
29
- // message: 'SolidX MCP server is unreachable. Please verify the MCP endpoint.',
30
- // httpStatus: 503,
31
- // },
32
- // 'unknown-error': {
33
- // message: 'An unexpected error occurred.',
34
- // httpStatus: 500,
35
- // },
36
- // };
37
-
38
4
  @Injectable()
39
5
  export class ErrorMapperService {
40
6
  private readonly logger = new Logger(ErrorMapperService.name);
@@ -76,7 +42,7 @@ export class ErrorMapperService {
76
42
  this.logger.warn(`Error rule threw in match(): code=${rule.code} provider? — ${e}`);
77
43
  }
78
44
  }
79
- return 'unknown-error';
45
+ return 'solidx-unknown-error';
80
46
  }
81
47
 
82
48
  private lookupMeta(code: ErrorCode): ErrorMeta | undefined {
@@ -122,7 +122,7 @@ export class ModelMetadataHelperService {
122
122
  }
123
123
  }
124
124
  });
125
- const fields = [];
125
+ const fields: any[] = [];
126
126
  if (model) {
127
127
  // Add the fields of the current model
128
128
  fields.push(...model.fields);
@@ -29,6 +29,7 @@ import { SolidIntrospectService } from './solid-introspect.service';
29
29
  import { ModelMetadata } from 'src/entities/model-metadata.entity';
30
30
  import { UpdateExportTemplateDto } from 'src/dtos/update-export-template.dto';
31
31
  import { ERROR_MESSAGES } from 'src/constants/error-messages';
32
+ import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
32
33
 
33
34
  const EXPORT_CHUNK_SIZE = 100;
34
35
  enum ExportStatus {
@@ -71,20 +72,22 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
71
72
  @InjectRepository(FieldMetadata, 'default')
72
73
  readonly fieldRepo: Repository<FieldMetadata>,
73
74
  @InjectRepository(ModelMetadata, 'default')
74
- readonly ModelMetadataRepo : Repository<ModelMetadata>,
75
- readonly moduleRef: ModuleRef
75
+ readonly ModelMetadataRepo: Repository<ModelMetadata>,
76
+ readonly moduleRef: ModuleRef,
77
+ private readonly modelMetadataHelperService: ModelMetadataHelperService,
78
+
76
79
  ) {
77
- super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'exportTransaction', 'solid-core',moduleRef);
80
+ super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'exportTransaction', 'solid-core', moduleRef);
78
81
  }
79
82
 
80
83
  // Return the export stream
81
- async triggerExportSync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto , filters: any): Promise<ExportTransactionFileInfo> {
84
+ async triggerExportSync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto, filters: any): Promise<ExportTransactionFileInfo> {
82
85
  try {
83
86
  // const loadedExportTransaction = await this.loadExportTransaction(id);
84
87
  // from updateDto, get modelId and get modelMetadata
85
88
  const modeldata = await this.ModelMetadataRepo.findOne({
86
- where: { id: updateDto?.modelMetadataId},
87
- relations: { fields: true},
89
+ where: { id: updateDto?.modelMetadataId },
90
+ relations: { fields: true },
88
91
  })
89
92
  const modelName = modeldata?.singularName;
90
93
  const modelTemplateName = modelName;
@@ -102,13 +105,13 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
102
105
  }
103
106
 
104
107
  // Store the export stream using the appropriate storage provider
105
- async triggerExportAsync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto, filters:any): Promise<void> {
108
+ async triggerExportAsync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto, filters: any): Promise<void> {
106
109
  try {
107
110
  // const loadedExportTransaction = await this.loadExportTransaction(id)
108
111
  // from updateDto, get modelId and get modelMetadata
109
112
  const modeldata = await this.ModelMetadataRepo.findOne({
110
- where: { id: updateDto?.modelMetadataId},
111
- relations: { fields: true},
113
+ where: { id: updateDto?.modelMetadataId },
114
+ relations: { fields: true },
112
115
  })
113
116
  const modelName = modeldata?.singularName;
114
117
  const modelTemplateName = modelName;
@@ -129,7 +132,7 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
129
132
  private async loadExportTransaction(id: number) {
130
133
  return await this.repo.findOne({
131
134
  where: { id: id },
132
- relations: { exportTemplate: { modelMetadata: {fields: true} }},
135
+ relations: { exportTemplate: { modelMetadata: { fields: true } } },
133
136
  }
134
137
  );
135
138
  }
@@ -138,15 +141,15 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
138
141
  await this.repo.update(id, { status, error });
139
142
  }
140
143
 
141
- private async getExportStreamDetails(modelName: string, templateName: string, fields:any, modelData:any, templateFormat:string, id:number, exportTransaction: any, filters: any) {
144
+ private async getExportStreamDetails(modelName: string, templateName: string, fields: any, modelData: any, templateFormat: string, id: number, exportTransaction: any, filters: any) {
142
145
  // Get the columns which need to be exported & the model id
143
146
  // const fields = JSON.parse(exportTransaction.exportTemplate.fields);
144
147
 
145
148
  // // Get the appropriate service for the model by trying to fetch a model service matching a particular name
146
149
  // const modelName = exportTransaction.exportTemplate.modelMetadata.singularName;
147
- const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
150
+ const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
148
151
  // const templateName = exportTransaction.exportTemplate.templateName;
149
- const uuid = String(id); //TODO can be renamed to exportTransactionUUID
152
+ const uuid = String(id); //TODO can be renamed to exportTransactionUUID
150
153
  // const modelData = exportTransaction.exportTemplate.modelMetadata;
151
154
 
152
155
  // Get the data records function
@@ -208,17 +211,21 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
208
211
  return (fileFormat === ExportFormat.EXCEL) ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv';
209
212
  }
210
213
 
211
- private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any>, modelMetadata: any, filters:any): Promise<(chunkIndex: number, chunkSize: number) => Promise<any[]>> {
212
- // Return a function which will take the chunkIndex & chunkSize and return the data
213
- // Get the relation fields to populate
214
- const relatedFieldNames = modelMetadata?.fields
215
- .filter((field: { relationType: any; }) => field.relationType !== null)
216
- .map((field: { name: any; }) => field.name);
214
+ private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any>, modelMetadata: any, filters: any): Promise<(chunkIndex: number, chunkSize: number) => Promise<any[]>> {
215
+ //Load all possible fields for the model
216
+ const allModelFields = await this.modelMetadataHelperService.loadFieldHierarchy(
217
+ modelMetadata.singularName,
218
+ );
219
+
220
+ // Filter only the fields requested in the export payload
221
+ const modelFields = allModelFields.filter((f: any) =>
222
+ fields.includes(f.name),
223
+ );
217
224
 
218
225
  //Get the model metadata of relation field with userKey details
219
226
  const relatedModelsUserKeyMap = new Map<string, string>();
220
- for (const field of modelMetadata?.fields || []) {
221
- if (field.relationType && field.relationCoModelSingularName && fields.includes(field.name)) {
227
+ for (const field of modelFields) {
228
+ if (field.relationType && field.relationCoModelSingularName) {
222
229
  const relatedModelMetadata = await this.ModelMetadataRepo.findOne({
223
230
  where: { singularName: field.relationCoModelSingularName },
224
231
  relations: ['userKeyField'],
@@ -230,12 +237,24 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
230
237
  }
231
238
  }
232
239
 
240
+ // Build fieldName -> displayName map
241
+ const fieldNameToDisplayName = new Map<string, string>();
242
+ for (const field of modelFields || []) {
243
+ if (field.name) {
244
+ fieldNameToDisplayName.set(field.name, field.displayName ?? field.name);
245
+ }
246
+ }
247
+
248
+
233
249
  return async (chunkIndex: number, chunkSize: number) => {
234
250
  const offset = chunkIndex * chunkSize;
235
251
  const recordFilterDto: BasicFilterDto = {
236
252
  limit: chunkSize,
237
253
  offset,
238
- populate: relatedFieldNames
254
+ //only contains relational fields (so TypeORM includes relations in the result).
255
+ populate: modelFields
256
+ .filter((f: any) => f.relationType !== null)
257
+ .map((f: any) => f.name),
239
258
  };
240
259
  const cleanedFilters = cleanNullsFromObject(filters);
241
260
 
@@ -243,43 +262,87 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
243
262
  recordFilterDto.filters = cleanedFilters;
244
263
  }
245
264
 
246
- //Get the non relation fields which are in fields array passed to this function
247
- const nonRelationalFieldSet = new Set(
248
- modelMetadata?.fields
249
- .filter((field: { name: any; relationType: any; }) => fields.includes(field.name) && field.relationType === null)
250
- .map((field: { name: any; }) => field.name)
251
- );
252
265
  const data = await modelService.instance.find(recordFilterDto);
253
266
  const records = data.records ?? [];
254
- const cleanedRecords = records.map((record: Record<string, any>) => {
255
- const newRecord: Record<string, any> = {};
256
-
257
- // Include non-relational fields
258
- for (const key of nonRelationalFieldSet as Set<string>) {
259
- newRecord[key] = record[key];
260
- }
261
-
262
- // Include userKey from each related field
263
- for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
264
- const relatedData = record[relatedFieldName];
265
-
266
- if (Array.isArray(relatedData)) {
267
- // For many-to-many or one-to-many
268
- const values = relatedData.map(item => item?.[userKeyFieldName]).filter(Boolean);
269
- newRecord[relatedFieldName] = values.join(', ');
270
- } else if (relatedData && typeof relatedData === 'object') {
271
- // For many-to-one or one-to-one
272
- newRecord[relatedFieldName] = relatedData?.[userKeyFieldName] ?? null;
273
- } else {
274
- newRecord[relatedFieldName] = null;
267
+ const cleanedRecords = records.map((record: Record<string, any>) => {
268
+ const newRecord: Record<string, any> = {};
269
+
270
+ // Include non-relational fields
271
+ for (const field of modelFields) {
272
+ if (!field.relationType) {
273
+ // newRecord[field.name] = record[field.name];
274
+ const displayKey = fieldNameToDisplayName.get(field.name) ?? field.name;
275
+ const fieldMeta = modelFields.find(f => f.name === field.name);
276
+
277
+ if ((fieldMeta?.type === 'datetime' || fieldMeta?.type === 'date') && record[field.name]) {
278
+ newRecord[displayKey] = new Date(record[field.name]).toISOString();
279
+ } else {
280
+ newRecord[displayKey] = record[field.name];
281
+ }
282
+ }
275
283
  }
276
- }
277
-
278
- return newRecord;
279
- });
280
- return cleanedRecords
284
+
285
+ // Include userKey from each related field
286
+ for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
287
+ const relatedData = record[relatedFieldName];
288
+ const displayKey = fieldNameToDisplayName.get(relatedFieldName) ?? relatedFieldName;
289
+
290
+ if (Array.isArray(relatedData)) {
291
+ // For many-to-many or one-to-many
292
+ const values = relatedData
293
+ .map(item => {
294
+ let val = item?.[userKeyFieldName];
295
+ const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
296
+ if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
297
+ val = new Date(val).toISOString();
298
+ }
299
+ return val;
300
+ })
301
+ .filter(Boolean);
302
+ newRecord[displayKey] = values.join(', ');
303
+ } else if (relatedData && typeof relatedData === 'object') {
304
+ // For many-to-one or one-to-one
305
+ newRecord[relatedFieldName] = relatedData?.[userKeyFieldName] ?? null;
306
+ } else {
307
+ newRecord[displayKey] = null;
308
+ }
309
+ }
310
+
311
+ // Include userKey from each related field (with displayName)
312
+ for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
313
+ const relatedData = record[relatedFieldName];
314
+ const displayKey = fieldNameToDisplayName.get(relatedFieldName) ?? relatedFieldName;
315
+
316
+ if (Array.isArray(relatedData)) {
317
+ // For many-to-many or one-to-many
318
+ const values = relatedData
319
+ .map(item => {
320
+ const val = item?.[userKeyFieldName];
321
+ // Convert datetime to ISO if needed
322
+ const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
323
+ if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
324
+ return new Date(val).toISOString();
325
+ }
326
+ return val;
327
+ })
328
+ .filter(Boolean);
329
+ newRecord[displayKey] = values.join(', ');
330
+ } else if (relatedData && typeof relatedData === 'object') {
331
+ let val = relatedData?.[userKeyFieldName] ?? null;
332
+ const relatedFieldMeta = modelFields.find(f => f.name === relatedFieldName);
333
+ if ((relatedFieldMeta?.type === 'datetime' || relatedFieldMeta?.type === 'date') && val) {
334
+ val = new Date(val).toISOString();
335
+ }
336
+ newRecord[displayKey] = val;
337
+ } else {
338
+ newRecord[displayKey] = null;
339
+ }
340
+ }
341
+ return newRecord;
342
+ });
343
+ return cleanedRecords
344
+ }
281
345
  }
282
- }
283
346
 
284
347
  async toDto(data: Partial<CreateExportTransactionDto>): Promise<CreateExportTransactionDto> {
285
348
  const dto = new CreateExportTransactionDto(data);