@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/dist/filters/http-exception.filter.js +1 -1
- package/dist/filters/http-exception.filter.js.map +1 -1
- package/dist/helpers/error-mapper.service.d.ts.map +1 -1
- package/dist/helpers/error-mapper.service.js +1 -1
- package/dist/helpers/error-mapper.service.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/services/export-transaction.service.d.ts +3 -1
- package/dist/services/export-transaction.service.d.ts.map +1 -1
- package/dist/services/export-transaction.service.js +70 -16
- package/dist/services/export-transaction.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/filters/http-exception.filter.ts +1 -1
- package/src/helpers/error-mapper.service.ts +1 -35
- package/src/helpers/model-metadata-helper.service.ts +1 -1
- package/src/services/export-transaction.service.ts +118 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
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 {
|
|
@@ -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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
150
|
+
const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
|
|
148
151
|
// const templateName = exportTransaction.exportTemplate.templateName;
|
|
149
|
-
|
|
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
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
221
|
-
if (field.relationType && field.relationCoModelSingularName
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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);
|