@solidstarters/solid-core 1.2.88 → 1.2.89
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/controllers/export-template.controller.d.ts +3 -2
- package/dist/controllers/export-template.controller.d.ts.map +1 -1
- package/dist/controllers/export-template.controller.js +23 -10
- package/dist/controllers/export-template.controller.js.map +1 -1
- package/dist/dtos/export.dto.d.ts +5 -0
- package/dist/dtos/export.dto.d.ts.map +1 -0
- package/dist/dtos/export.dto.js +12 -0
- package/dist/dtos/export.dto.js.map +1 -0
- package/dist/entities/export-transaction.entity.js +1 -1
- package/dist/entities/export-transaction.entity.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +1 -1
- package/dist/services/export-template.service.d.ts +3 -2
- package/dist/services/export-template.service.d.ts.map +1 -1
- package/dist/services/export-template.service.js +6 -6
- package/dist/services/export-template.service.js.map +1 -1
- package/dist/services/export-transaction.service.d.ts +6 -3
- package/dist/services/export-transaction.service.d.ts.map +1 -1
- package/dist/services/export-transaction.service.js +101 -22
- package/dist/services/export-transaction.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/controllers/export-template.controller.ts +20 -7
- package/src/dtos/export.dto.ts +5 -0
- package/src/entities/export-transaction.entity.ts +1 -1
- package/src/seeders/seed-data/solid-core-metadata.json +1 -1
- package/src/services/export-template.service.ts +7 -6
- package/src/services/export-transaction.service.ts +136 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.89",
|
|
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",
|
|
@@ -5,6 +5,7 @@ import { ExportTemplateService } from '../services/export-template.service';
|
|
|
5
5
|
import { CreateExportTemplateDto } from '../dtos/create-export-template.dto';
|
|
6
6
|
import { UpdateExportTemplateDto } from '../dtos/update-export-template.dto';
|
|
7
7
|
import { Response } from 'express';
|
|
8
|
+
import { StartExportSyncDto } from 'src/dtos/export.dto';
|
|
8
9
|
|
|
9
10
|
@ApiTags('Solid')
|
|
10
11
|
@Controller('export-template') //FIXME: Change this to the model plural name
|
|
@@ -74,13 +75,19 @@ export class ExportTemplateController {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
@ApiBearerAuth("jwt")
|
|
77
|
-
@Post('
|
|
78
|
-
async startExportSync(@
|
|
79
|
-
const
|
|
78
|
+
@Post('/startExport/sync')
|
|
79
|
+
async startExportSync(@Body() dto: StartExportSyncDto, @Res() res: Response) {
|
|
80
|
+
const { filters, ...rest } = dto;
|
|
81
|
+
let updateDto = { ...rest };
|
|
82
|
+
// Check if templateName is present → create template
|
|
83
|
+
if (updateDto?.templateName) {
|
|
84
|
+
const newTemplate = await this.service.create(updateDto, []);
|
|
85
|
+
updateDto = { ...updateDto, id: newTemplate.id };
|
|
86
|
+
}
|
|
87
|
+
const exportFileInfo = await this.service.startExportSync(updateDto, filters);
|
|
80
88
|
if (exportFileInfo.exportStream === null) {
|
|
81
89
|
throw new InternalServerErrorException("Export stream is null");
|
|
82
90
|
}
|
|
83
|
-
|
|
84
91
|
// ✅ Set response headers for streaming
|
|
85
92
|
res.setHeader('Content-Disposition', `attachment; filename="${exportFileInfo.fileName}"`);
|
|
86
93
|
res.setHeader('Content-Type', exportFileInfo.mimeType);
|
|
@@ -90,9 +97,15 @@ export class ExportTemplateController {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
@ApiBearerAuth("jwt")
|
|
93
|
-
@Post('
|
|
94
|
-
async startExportAsync(@
|
|
95
|
-
|
|
100
|
+
@Post('/startExport/async')
|
|
101
|
+
async startExportAsync(@Body() dto: StartExportSyncDto) {
|
|
102
|
+
const { filters, ...rest } = dto;
|
|
103
|
+
let updateDto = { ...rest };
|
|
104
|
+
if (updateDto.templateName) {
|
|
105
|
+
const newTemplate = await this.service.create(updateDto, []);
|
|
106
|
+
updateDto = { ...updateDto, id: newTemplate.id };
|
|
107
|
+
}
|
|
108
|
+
return this.service.startExportAsync(updateDto, filters);
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
}
|
|
@@ -17,6 +17,6 @@ export class ExportTransaction extends CommonEntity {
|
|
|
17
17
|
@Column({ type: "text", nullable: true })
|
|
18
18
|
error: string;
|
|
19
19
|
@Index()
|
|
20
|
-
@ManyToOne(() => ExportTemplate, { onDelete: "CASCADE", nullable:
|
|
20
|
+
@ManyToOne(() => ExportTemplate, { onDelete: "CASCADE", nullable: true })
|
|
21
21
|
exportTemplate: ExportTemplate;
|
|
22
22
|
}
|
|
@@ -18,35 +18,36 @@ import { ExportTransaction } from 'src/entities/export-transaction.entity';
|
|
|
18
18
|
import { Readable } from 'stream';
|
|
19
19
|
import { ExportTemplate } from '../entities/export-template.entity';
|
|
20
20
|
import { ExportTransactionFileInfo, ExportTransactionService } from './export-transaction.service';
|
|
21
|
+
import { UpdateExportTemplateDto } from 'src/dtos/update-export-template.dto';
|
|
21
22
|
|
|
22
23
|
@Injectable()
|
|
23
24
|
export class ExportTemplateService extends CRUDService<ExportTemplate>{
|
|
24
|
-
async startExportSync(
|
|
25
|
+
async startExportSync(updateDto: UpdateExportTemplateDto, filters:any): Promise<ExportTransactionFileInfo> {
|
|
25
26
|
// Create the export transaction entry, with status 'started'
|
|
26
27
|
const exportTransaction: CreateExportTransactionDto = await this.exportTransactionService.toDto({
|
|
27
28
|
datetime: new Date(),
|
|
28
29
|
status: 'started',
|
|
29
|
-
exportTemplateId: id,
|
|
30
|
+
exportTemplateId: updateDto?.id ? updateDto?.id : null,
|
|
30
31
|
});
|
|
31
32
|
const exportTransactionEntity = await this.exportTransactionService.create(exportTransaction);
|
|
32
33
|
|
|
33
34
|
// Trigger the export process
|
|
34
|
-
const exportFileInfo = await this.exportTransactionService.triggerExportSync(exportTransactionEntity.id);
|
|
35
|
+
const exportFileInfo = await this.exportTransactionService.triggerExportSync(exportTransactionEntity.id, exportTransactionEntity, updateDto, filters);
|
|
35
36
|
// It should return the export transaction id
|
|
36
37
|
return exportFileInfo;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
async startExportAsync(
|
|
40
|
+
async startExportAsync(updateDto: UpdateExportTemplateDto, filters:any): Promise<ExportTransaction>{
|
|
40
41
|
// Create the export transaction entry, with status 'started'
|
|
41
42
|
const exportTransaction: CreateExportTransactionDto = await this.exportTransactionService.toDto({
|
|
42
43
|
datetime: new Date(),
|
|
43
44
|
status: 'started',
|
|
44
|
-
exportTemplateId: id,
|
|
45
|
+
exportTemplateId: updateDto?.id ? updateDto?.id : null,
|
|
45
46
|
});
|
|
46
47
|
const exportTransactionEntity = await this.exportTransactionService.create(exportTransaction);
|
|
47
48
|
|
|
48
49
|
// Trigger the export process
|
|
49
|
-
this.exportTransactionService.triggerExportAsync(exportTransactionEntity.id);
|
|
50
|
+
this.exportTransactionService.triggerExportAsync(exportTransactionEntity.id, exportTransactionEntity, updateDto, filters);
|
|
50
51
|
|
|
51
52
|
// It should return the export transaction id, so client can use this to check the status
|
|
52
53
|
return exportTransactionEntity;
|
|
@@ -26,6 +26,8 @@ import { CsvService } from './csv.service';
|
|
|
26
26
|
import { ExcelService } from './excel.service';
|
|
27
27
|
import { getMediaStorageProvider } from './mediaStorageProviders';
|
|
28
28
|
import { SolidIntrospectService } from './solid-introspect.service';
|
|
29
|
+
import { ModelMetadata } from 'src/entities/model-metadata.entity';
|
|
30
|
+
import { UpdateExportTemplateDto } from 'src/dtos/update-export-template.dto';
|
|
29
31
|
|
|
30
32
|
const EXPORT_CHUNK_SIZE = 100;
|
|
31
33
|
enum ExportStatus {
|
|
@@ -67,19 +69,30 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
67
69
|
// readonly fieldMetadataService: FieldMetadataService,
|
|
68
70
|
@InjectRepository(FieldMetadata, 'default')
|
|
69
71
|
readonly fieldRepo: Repository<FieldMetadata>,
|
|
72
|
+
@InjectRepository(ModelMetadata, 'default')
|
|
73
|
+
readonly ModelMetadataRepo : Repository<ModelMetadata>,
|
|
70
74
|
readonly moduleRef: ModuleRef
|
|
71
75
|
) {
|
|
72
76
|
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'exportTransaction', 'solid-core',moduleRef);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
// Return the export stream
|
|
76
|
-
async triggerExportSync(id: number): Promise<ExportTransactionFileInfo> {
|
|
80
|
+
async triggerExportSync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto , filters: any): Promise<ExportTransactionFileInfo> {
|
|
77
81
|
try {
|
|
78
|
-
const loadedExportTransaction = await this.loadExportTransaction(id);
|
|
79
|
-
|
|
82
|
+
// const loadedExportTransaction = await this.loadExportTransaction(id);
|
|
83
|
+
// from updateDto, get modelId and get modelMetadata
|
|
84
|
+
const modeldata = await this.ModelMetadataRepo.findOne({
|
|
85
|
+
where: { id: updateDto?.modelMetadataId},
|
|
86
|
+
relations: { fields: true},
|
|
87
|
+
})
|
|
88
|
+
const modelName = modeldata?.singularName;
|
|
89
|
+
const modelTemplateName = modelName;
|
|
90
|
+
const fields = JSON.parse(updateDto?.fields);
|
|
91
|
+
const templateFormat = updateDto?.templateFormat;
|
|
92
|
+
const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
|
|
80
93
|
this.updateExportTransaction(id, ExportStatus.COMPLETED);
|
|
81
|
-
const fileName = this.getFileName(templateName, uuid,
|
|
82
|
-
const mimeType = this.getMimeType(
|
|
94
|
+
const fileName = this.getFileName(templateName, uuid, templateFormat);
|
|
95
|
+
const mimeType = this.getMimeType(templateFormat);
|
|
83
96
|
return { exportStream, fileName, mimeType, exportTransaction };
|
|
84
97
|
} catch (error) {
|
|
85
98
|
this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
|
|
@@ -88,13 +101,22 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
// Store the export stream using the appropriate storage provider
|
|
91
|
-
async triggerExportAsync(id: number): Promise<void> {
|
|
104
|
+
async triggerExportAsync(id: number, exportTransactionEntity: any, updateDto: UpdateExportTemplateDto, filters:any): Promise<void> {
|
|
92
105
|
try {
|
|
93
|
-
const loadedExportTransaction = await this.loadExportTransaction(id)
|
|
94
|
-
|
|
95
|
-
const
|
|
106
|
+
// const loadedExportTransaction = await this.loadExportTransaction(id)
|
|
107
|
+
// from updateDto, get modelId and get modelMetadata
|
|
108
|
+
const modeldata = await this.ModelMetadataRepo.findOne({
|
|
109
|
+
where: { id: updateDto?.modelMetadataId},
|
|
110
|
+
relations: { fields: true},
|
|
111
|
+
})
|
|
112
|
+
const modelName = modeldata?.singularName;
|
|
113
|
+
const modelTemplateName = modelName;
|
|
114
|
+
const fields = JSON.parse(updateDto?.fields);
|
|
115
|
+
const templateFormat = updateDto?.templateFormat;
|
|
116
|
+
const { exportStream, templateName, uuid, exportTransaction } = await this.getExportStreamDetails(modelName, modelTemplateName, fields, modeldata, templateFormat, id, exportTransactionEntity, filters);
|
|
117
|
+
// const fileFormat = loadedExportTransaction.exportTemplate.templateFormat;
|
|
96
118
|
// Store the file using the appropriate storage provider
|
|
97
|
-
await this.storeExportStream(exportStream, exportTransaction, this.getFileName(templateName, uuid,
|
|
119
|
+
await this.storeExportStream(exportStream, exportTransaction, this.getFileName(templateName, uuid, templateFormat));
|
|
98
120
|
this.updateExportTransaction(id, ExportStatus.COMPLETED);
|
|
99
121
|
} catch (error) {
|
|
100
122
|
this.updateExportTransaction(id, ExportStatus.FAILED, error.message);
|
|
@@ -106,7 +128,7 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
106
128
|
private async loadExportTransaction(id: number) {
|
|
107
129
|
return await this.repo.findOne({
|
|
108
130
|
where: { id: id },
|
|
109
|
-
relations: { exportTemplate: { modelMetadata: true
|
|
131
|
+
relations: { exportTemplate: { modelMetadata: {fields: true} }},
|
|
110
132
|
}
|
|
111
133
|
);
|
|
112
134
|
}
|
|
@@ -115,22 +137,26 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
115
137
|
await this.repo.update(id, { status, error });
|
|
116
138
|
}
|
|
117
139
|
|
|
118
|
-
private async getExportStreamDetails(exportTransaction:
|
|
140
|
+
private async getExportStreamDetails(modelName: string, templateName: string, fields:any, modelData:any, templateFormat:string, id:number, exportTransaction: any, filters: any) {
|
|
119
141
|
// Get the columns which need to be exported & the model id
|
|
120
|
-
const fields = JSON.parse(exportTransaction.exportTemplate.fields);
|
|
121
|
-
|
|
122
|
-
// Get the appropriate service for the model by trying to fetch a model service matching a particular name
|
|
123
|
-
const modelName = exportTransaction.exportTemplate.modelMetadata.singularName;
|
|
124
|
-
const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
|
|
125
|
-
const templateName = exportTransaction.exportTemplate.templateName;
|
|
126
|
-
const uuid = exportTransaction.exportTransactionId; //TODO can be renamed to exportTransactionUUID
|
|
142
|
+
// const fields = JSON.parse(exportTransaction.exportTemplate.fields);
|
|
127
143
|
|
|
144
|
+
// // Get the appropriate service for the model by trying to fetch a model service matching a particular name
|
|
145
|
+
// const modelName = exportTransaction.exportTemplate.modelMetadata.singularName;
|
|
146
|
+
const modelService = this.introspectService.getProvider(`${classify(modelName)}Service`);
|
|
147
|
+
// const templateName = exportTransaction.exportTemplate.templateName;
|
|
148
|
+
const uuid = String(id); //TODO can be renamed to exportTransactionUUID
|
|
149
|
+
// const modelData = exportTransaction.exportTemplate.modelMetadata;
|
|
128
150
|
|
|
129
151
|
// Get the data records function
|
|
130
|
-
const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService);
|
|
152
|
+
//const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService,modelData, filters);
|
|
153
|
+
const dataRecordsFunc = await this.getDataRecordsFunc(fields, modelService, modelData, filters);
|
|
131
154
|
|
|
132
155
|
// Get the export passthru stream (since it is a passthru stream, nothing is stored in memory & it is streamed directly when the stream is read)
|
|
133
|
-
let exportStream = await this.getExportStream(exportTransaction.exportTemplate.templateFormat, dataRecordsFunc);
|
|
156
|
+
// let exportStream = await this.getExportStream(exportTransaction.exportTemplate.templateFormat, dataRecordsFunc);
|
|
157
|
+
// return { exportStream, templateName, uuid, exportTransaction };
|
|
158
|
+
|
|
159
|
+
let exportStream = await this.getExportStream(templateFormat, dataRecordsFunc);
|
|
134
160
|
return { exportStream, templateName, uuid, exportTransaction };
|
|
135
161
|
}
|
|
136
162
|
|
|
@@ -181,20 +207,78 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
181
207
|
return (fileFormat === ExportFormat.EXCEL) ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'text/csv';
|
|
182
208
|
}
|
|
183
209
|
|
|
184
|
-
private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any
|
|
210
|
+
private async getDataRecordsFunc(fields: any, modelService: InstanceWrapper<any>, modelMetadata: any, filters:any): Promise<(chunkIndex: number, chunkSize: number) => Promise<any[]>> {
|
|
185
211
|
// Return a function which will take the chunkIndex & chunkSize and return the data
|
|
212
|
+
// Get the relation fields to populate
|
|
213
|
+
const relatedFieldNames = modelMetadata?.fields
|
|
214
|
+
.filter((field: { relationType: any; }) => field.relationType !== null)
|
|
215
|
+
.map((field: { name: any; }) => field.name);
|
|
216
|
+
|
|
217
|
+
//Get the model metadata of relation field with userKey details
|
|
218
|
+
const relatedModelsUserKeyMap = new Map<string, string>();
|
|
219
|
+
for (const field of modelMetadata?.fields || []) {
|
|
220
|
+
if (field.relationType && field.relationCoModelSingularName && fields.includes(field.name)) {
|
|
221
|
+
const relatedModelMetadata = await this.ModelMetadataRepo.findOne({
|
|
222
|
+
where: { singularName: field.relationCoModelSingularName },
|
|
223
|
+
relations: ['userKeyField'],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (relatedModelMetadata?.userKeyField?.name) {
|
|
227
|
+
relatedModelsUserKeyMap.set(field.name, relatedModelMetadata.userKeyField.name);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
186
232
|
return async (chunkIndex: number, chunkSize: number) => {
|
|
187
233
|
const offset = chunkIndex * chunkSize;
|
|
188
234
|
const recordFilterDto: BasicFilterDto = {
|
|
189
|
-
fields,
|
|
190
235
|
limit: chunkSize,
|
|
191
236
|
offset,
|
|
237
|
+
populate: relatedFieldNames
|
|
192
238
|
};
|
|
239
|
+
const cleanedFilters = cleanNullsFromObject(filters);
|
|
240
|
+
|
|
241
|
+
if (cleanedFilters && Object.keys(cleanedFilters).length > 0) {
|
|
242
|
+
recordFilterDto.filters = cleanedFilters;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//Get the non relation fields which are in fields array passed to this function
|
|
246
|
+
const nonRelationalFieldSet = new Set(
|
|
247
|
+
modelMetadata?.fields
|
|
248
|
+
.filter((field: { name: any; relationType: any; }) => fields.includes(field.name) && field.relationType === null)
|
|
249
|
+
.map((field: { name: any; }) => field.name)
|
|
250
|
+
);
|
|
193
251
|
const data = await modelService.instance.find(recordFilterDto);
|
|
194
252
|
const records = data.records ?? [];
|
|
195
|
-
|
|
196
|
-
|
|
253
|
+
const cleanedRecords = records.map((record: Record<string, any>) => {
|
|
254
|
+
const newRecord: Record<string, any> = {};
|
|
255
|
+
|
|
256
|
+
// Include non-relational fields
|
|
257
|
+
for (const key of nonRelationalFieldSet as Set<string>) {
|
|
258
|
+
newRecord[key] = record[key];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Include userKey from each related field
|
|
262
|
+
for (const [relatedFieldName, userKeyFieldName] of relatedModelsUserKeyMap.entries()) {
|
|
263
|
+
const relatedData = record[relatedFieldName];
|
|
264
|
+
|
|
265
|
+
if (Array.isArray(relatedData)) {
|
|
266
|
+
// For many-to-many or one-to-many
|
|
267
|
+
const values = relatedData.map(item => item?.[userKeyFieldName]).filter(Boolean);
|
|
268
|
+
newRecord[relatedFieldName] = values.join(', ');
|
|
269
|
+
} else if (relatedData && typeof relatedData === 'object') {
|
|
270
|
+
// For many-to-one or one-to-one
|
|
271
|
+
newRecord[relatedFieldName] = relatedData?.[userKeyFieldName] ?? null;
|
|
272
|
+
} else {
|
|
273
|
+
newRecord[relatedFieldName] = null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return newRecord;
|
|
278
|
+
});
|
|
279
|
+
return cleanedRecords
|
|
197
280
|
}
|
|
281
|
+
}
|
|
198
282
|
|
|
199
283
|
async toDto(data: Partial<CreateExportTransactionDto>): Promise<CreateExportTransactionDto> {
|
|
200
284
|
const dto = new CreateExportTransactionDto(data);
|
|
@@ -206,3 +290,30 @@ export class ExportTransactionService extends CRUDService<ExportTransaction> {
|
|
|
206
290
|
return dto;
|
|
207
291
|
}
|
|
208
292
|
}
|
|
293
|
+
|
|
294
|
+
function cleanNullsFromObject(obj: any): any {
|
|
295
|
+
if (Array.isArray(obj)) {
|
|
296
|
+
return obj
|
|
297
|
+
.filter(item => item !== null && item !== undefined)
|
|
298
|
+
.map(cleanNullsFromObject);
|
|
299
|
+
} else if (typeof obj === 'object' && obj !== null) {
|
|
300
|
+
const newObj: any = {};
|
|
301
|
+
for (const key in obj) {
|
|
302
|
+
const value = obj[key];
|
|
303
|
+
if (value !== null && value !== undefined) {
|
|
304
|
+
const cleanedValue = cleanNullsFromObject(value);
|
|
305
|
+
// Only assign non-empty objects/arrays or non-null primitives
|
|
306
|
+
if (
|
|
307
|
+
(typeof cleanedValue === 'object' && Object.keys(cleanedValue).length > 0) ||
|
|
308
|
+
(Array.isArray(cleanedValue) && cleanedValue.length > 0) ||
|
|
309
|
+
typeof cleanedValue !== 'object'
|
|
310
|
+
) {
|
|
311
|
+
newObj[key] = cleanedValue;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return newObj;
|
|
316
|
+
}
|
|
317
|
+
return obj;
|
|
318
|
+
}
|
|
319
|
+
|