@solidstarters/solid-core 1.2.148 → 1.2.150
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/ai-interaction.controller.d.ts +1 -1
- package/dist/controllers/ai-interaction.controller.js +1 -1
- package/dist/dtos/create-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/create-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/create-ai-interaction.dto.js +20 -1
- package/dist/dtos/create-ai-interaction.dto.js.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/update-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.js +19 -1
- package/dist/dtos/update-ai-interaction.dto.js.map +1 -1
- package/dist/entities/ai-interaction.entity.d.ts +3 -0
- package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
- package/dist/entities/ai-interaction.entity.js +17 -1
- package/dist/entities/ai-interaction.entity.js.map +1 -1
- package/dist/helpers/environment.helper.d.ts +2 -0
- package/dist/helpers/environment.helper.d.ts.map +1 -0
- package/dist/helpers/environment.helper.js +11 -0
- package/dist/helpers/environment.helper.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +2 -0
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +83 -3
- package/dist/services/ai-interaction.service.d.ts +1 -1
- package/dist/services/ai-interaction.service.d.ts.map +1 -1
- package/dist/services/ai-interaction.service.js +5 -1
- package/dist/services/ai-interaction.service.js.map +1 -1
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +2 -2
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +3 -2
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.js +6 -4
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/services/mq-message.service.d.ts +9 -0
- package/dist/services/mq-message.service.d.ts.map +1 -1
- package/dist/services/mq-message.service.js +61 -0
- package/dist/services/mq-message.service.js.map +1 -1
- package/dist/services/poller.service.js +1 -1
- package/dist/services/poller.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +1 -1
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +2 -1
- package/dist/solid-core.module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-ai-interaction.dto.ts +16 -5
- package/src/dtos/update-ai-interaction.dto.ts +16 -5
- package/src/entities/ai-interaction.entity.ts +11 -3
- package/src/helpers/environment.helper.ts +7 -0
- package/src/index.ts +3 -0
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +2 -0
- package/src/seeders/seed-data/solid-core-metadata.json +83 -3
- package/src/services/ai-interaction.service.ts +8 -3
- package/src/services/media.service.ts +2 -3
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
- package/src/services/mediaStorageProviders/file-storage-provider.ts +4 -4
- package/src/services/mq-message.service.ts +116 -0
- package/src/services/poller.service.ts +1 -1
- package/src/services/queues/database-subscriber.service.ts +1 -1
- package/src/solid-core.module.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.150",
|
|
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",
|
|
@@ -48,9 +48,20 @@ export class CreateAiInteractionDto {
|
|
|
48
48
|
@IsJSON()
|
|
49
49
|
@ApiProperty()
|
|
50
50
|
metadata: any;
|
|
51
|
-
|
|
52
|
-
@
|
|
53
|
-
@
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
@IsOptional()
|
|
52
|
+
@IsBoolean()
|
|
53
|
+
@ApiProperty()
|
|
54
|
+
isApplied: boolean = false;
|
|
55
|
+
@IsOptional()
|
|
56
|
+
@IsInt()
|
|
57
|
+
@ApiProperty()
|
|
58
|
+
parentInteractionId: number;
|
|
59
|
+
@IsString()
|
|
60
|
+
@IsOptional()
|
|
61
|
+
@ApiProperty()
|
|
62
|
+
parentInteractionUserKey: string;
|
|
63
|
+
@IsOptional()
|
|
64
|
+
@IsBoolean()
|
|
65
|
+
@ApiProperty()
|
|
66
|
+
isAutoApply: boolean = false;
|
|
56
67
|
}
|
|
@@ -52,9 +52,20 @@ export class UpdateAiInteractionDto {
|
|
|
52
52
|
@IsJSON()
|
|
53
53
|
@ApiProperty()
|
|
54
54
|
metadata: any;
|
|
55
|
-
|
|
56
|
-
@
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
@IsOptional()
|
|
56
|
+
@IsBoolean()
|
|
57
|
+
@ApiProperty()
|
|
58
|
+
isApplied: boolean;
|
|
59
|
+
@IsOptional()
|
|
60
|
+
@IsInt()
|
|
61
|
+
@ApiProperty()
|
|
62
|
+
parentInteractionId: number;
|
|
63
|
+
@IsString()
|
|
64
|
+
@IsOptional()
|
|
65
|
+
@ApiProperty()
|
|
66
|
+
parentInteractionUserKey: string;
|
|
67
|
+
@IsOptional()
|
|
68
|
+
@IsBoolean()
|
|
69
|
+
@ApiProperty()
|
|
70
|
+
isAutoApply: boolean;
|
|
60
71
|
}
|
|
@@ -28,7 +28,15 @@ export class AiInteraction extends CommonEntity {
|
|
|
28
28
|
responseTimeMs: number;
|
|
29
29
|
@Column({ type: "jsonb", nullable: true })
|
|
30
30
|
metadata: any;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
@Column({ type: "boolean", nullable: true, default: false })
|
|
32
|
+
isApplied: boolean = false;
|
|
33
|
+
@Index()
|
|
34
|
+
@ManyToOne(() => AiInteraction, { onDelete: "SET NULL", nullable: true })
|
|
35
|
+
@JoinColumn()
|
|
36
|
+
parentInteraction: AiInteraction;
|
|
37
|
+
@Index({ unique: true })
|
|
38
|
+
@Column({ type: "varchar" })
|
|
39
|
+
externalId: string;
|
|
40
|
+
@Column({ type: "boolean", nullable: true, default: false })
|
|
41
|
+
isAutoApply: boolean = false;
|
|
34
42
|
}
|
package/src/index.ts
CHANGED
|
@@ -166,6 +166,8 @@ export * from './helpers/field-crud-managers/SelectionDynamicFieldCrudManager' /
|
|
|
166
166
|
export * from './helpers/field-crud-managers/SelectionStaticFieldCrudManager' //rename
|
|
167
167
|
export * from './helpers/field-crud-managers/ShortTextFieldCrudManager' //rename
|
|
168
168
|
export * from './helpers/field-crud-managers/UUIDFieldCrudManager' //rename
|
|
169
|
+
export * from './helpers/environment.helper'
|
|
170
|
+
|
|
169
171
|
export * from './services/crud.service'
|
|
170
172
|
export * from './interceptors/logging.interceptor'
|
|
171
173
|
export * from './interceptors/wrap-response.interceptor'
|
|
@@ -264,6 +266,7 @@ export * from './services/csv.service'
|
|
|
264
266
|
export * from './services/queues/publisher-factory.service'
|
|
265
267
|
export * from './services/queues/database-publisher.service'
|
|
266
268
|
export * from './services/queues/database-subscriber.service'
|
|
269
|
+
export * from './services/ai-interaction.service'
|
|
267
270
|
|
|
268
271
|
// Factories
|
|
269
272
|
export * from './factories/mail.factory'
|
|
@@ -58,6 +58,7 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
58
58
|
await this.aiInteractionService.create({
|
|
59
59
|
userId: aiInteraction.user.id,
|
|
60
60
|
threadId: aiInteraction.threadId,
|
|
61
|
+
parentInteractionId: aiInteraction.id,
|
|
61
62
|
role: 'gen-ai',
|
|
62
63
|
message: '-',
|
|
63
64
|
contentType: aiResponse.content_type,
|
|
@@ -77,6 +78,7 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
77
78
|
await this.aiInteractionService.create({
|
|
78
79
|
userId: aiInteraction.user.id,
|
|
79
80
|
threadId: aiInteraction.threadId,
|
|
81
|
+
parentInteractionId: aiInteraction.id,
|
|
80
82
|
role: 'gen-ai',
|
|
81
83
|
message: nestedResponse,
|
|
82
84
|
contentType: aiResponse.content_type,
|
|
@@ -4919,7 +4919,7 @@
|
|
|
4919
4919
|
"tableName": "ss_ai_interactions",
|
|
4920
4920
|
"dataSource": "default",
|
|
4921
4921
|
"dataSourceType": "postgres",
|
|
4922
|
-
"userKeyFieldUserKey": "
|
|
4922
|
+
"userKeyFieldUserKey": "externalId",
|
|
4923
4923
|
"isSystem": false,
|
|
4924
4924
|
"fields": [
|
|
4925
4925
|
{
|
|
@@ -4938,6 +4938,35 @@
|
|
|
4938
4938
|
"relationModelModuleName": "solid-core",
|
|
4939
4939
|
"isSystem": true
|
|
4940
4940
|
},
|
|
4941
|
+
{
|
|
4942
|
+
"name": "externalId",
|
|
4943
|
+
"displayName": "External ID",
|
|
4944
|
+
"description": "Used to track using a reference number of each ai interaction.",
|
|
4945
|
+
"type": "computed",
|
|
4946
|
+
"ormType": "varchar",
|
|
4947
|
+
"isSystem": false,
|
|
4948
|
+
"computedFieldValueType": "string",
|
|
4949
|
+
"computedFieldTriggerConfig": [
|
|
4950
|
+
{
|
|
4951
|
+
"modelName": "aiInteraction",
|
|
4952
|
+
"moduleName": "solid-core",
|
|
4953
|
+
"operations": [
|
|
4954
|
+
"before-insert"
|
|
4955
|
+
]
|
|
4956
|
+
}
|
|
4957
|
+
],
|
|
4958
|
+
"computedFieldValueProvider": "AlphaNumExternalIdComputationProvider",
|
|
4959
|
+
"computedFieldValueProviderCtxt": "{\n \"prefix\": \"AI\",\n \"length\": \"10\"\n}",
|
|
4960
|
+
"required": true,
|
|
4961
|
+
"unique": true,
|
|
4962
|
+
"index": true,
|
|
4963
|
+
"private": false,
|
|
4964
|
+
"encrypt": false,
|
|
4965
|
+
"encryptionType": null,
|
|
4966
|
+
"decryptWhen": null,
|
|
4967
|
+
"columnName": null,
|
|
4968
|
+
"isUserKey": true
|
|
4969
|
+
},
|
|
4941
4970
|
{
|
|
4942
4971
|
"name": "threadId",
|
|
4943
4972
|
"displayName": "Thread ID",
|
|
@@ -4950,6 +4979,22 @@
|
|
|
4950
4979
|
"private": false,
|
|
4951
4980
|
"encrypt": false
|
|
4952
4981
|
},
|
|
4982
|
+
{
|
|
4983
|
+
"name": "parentInteraction",
|
|
4984
|
+
"displayName": "Parent Interaction",
|
|
4985
|
+
"type": "relation",
|
|
4986
|
+
"required": false,
|
|
4987
|
+
"unique": false,
|
|
4988
|
+
"index": true,
|
|
4989
|
+
"private": false,
|
|
4990
|
+
"encrypt": false,
|
|
4991
|
+
"relationType": "many-to-one",
|
|
4992
|
+
"relationCoModelSingularName": "aiInteraction",
|
|
4993
|
+
"relationCreateInverse": false,
|
|
4994
|
+
"relationCascade": "set null",
|
|
4995
|
+
"relationModelModuleName": "solid-core",
|
|
4996
|
+
"isSystem": true
|
|
4997
|
+
},
|
|
4953
4998
|
{
|
|
4954
4999
|
"name": "role",
|
|
4955
5000
|
"displayName": "Role",
|
|
@@ -5057,6 +5102,17 @@
|
|
|
5057
5102
|
"index": false,
|
|
5058
5103
|
"private": false,
|
|
5059
5104
|
"encrypt": false
|
|
5105
|
+
},
|
|
5106
|
+
{
|
|
5107
|
+
"name": "isAutoApply",
|
|
5108
|
+
"displayName": "Is Auto Apply",
|
|
5109
|
+
"type": "boolean",
|
|
5110
|
+
"ormType": "boolean",
|
|
5111
|
+
"required": false,
|
|
5112
|
+
"unique": false,
|
|
5113
|
+
"index": false,
|
|
5114
|
+
"private": false,
|
|
5115
|
+
"encrypt": false
|
|
5060
5116
|
}
|
|
5061
5117
|
]
|
|
5062
5118
|
}
|
|
@@ -11867,7 +11923,7 @@
|
|
|
11867
11923
|
}
|
|
11868
11924
|
},
|
|
11869
11925
|
{
|
|
11870
|
-
"name": "
|
|
11926
|
+
"name": "aiInteraction-list-view",
|
|
11871
11927
|
"displayName": "AI Interaction",
|
|
11872
11928
|
"type": "list",
|
|
11873
11929
|
"context": "{}",
|
|
@@ -11900,6 +11956,18 @@
|
|
|
11900
11956
|
"name": "threadId"
|
|
11901
11957
|
}
|
|
11902
11958
|
},
|
|
11959
|
+
{
|
|
11960
|
+
"type": "field",
|
|
11961
|
+
"attrs": {
|
|
11962
|
+
"name": "externalId"
|
|
11963
|
+
}
|
|
11964
|
+
},
|
|
11965
|
+
{
|
|
11966
|
+
"type": "field",
|
|
11967
|
+
"attrs": {
|
|
11968
|
+
"name": "parentInteraction"
|
|
11969
|
+
}
|
|
11970
|
+
},
|
|
11903
11971
|
{
|
|
11904
11972
|
"type": "field",
|
|
11905
11973
|
"attrs": {
|
|
@@ -11934,7 +12002,7 @@
|
|
|
11934
12002
|
}
|
|
11935
12003
|
},
|
|
11936
12004
|
{
|
|
11937
|
-
"name": "
|
|
12005
|
+
"name": "aiInteraction-form-view",
|
|
11938
12006
|
"displayName": "AI Interaction",
|
|
11939
12007
|
"type": "form",
|
|
11940
12008
|
"context": "{}",
|
|
@@ -11987,6 +12055,12 @@
|
|
|
11987
12055
|
"name": "user"
|
|
11988
12056
|
}
|
|
11989
12057
|
},
|
|
12058
|
+
{
|
|
12059
|
+
"type": "field",
|
|
12060
|
+
"attrs": {
|
|
12061
|
+
"name": "externalId"
|
|
12062
|
+
}
|
|
12063
|
+
},
|
|
11990
12064
|
{
|
|
11991
12065
|
"type": "field",
|
|
11992
12066
|
"attrs": {
|
|
@@ -12034,6 +12108,12 @@
|
|
|
12034
12108
|
"attrs": {
|
|
12035
12109
|
"name": "responseTimeMs"
|
|
12036
12110
|
}
|
|
12111
|
+
},
|
|
12112
|
+
{
|
|
12113
|
+
"type": "field",
|
|
12114
|
+
"attrs": {
|
|
12115
|
+
"name": "parentInteraction"
|
|
12116
|
+
}
|
|
12037
12117
|
}
|
|
12038
12118
|
]
|
|
12039
12119
|
}
|
|
@@ -43,7 +43,7 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
|
|
|
43
43
|
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'aiInteraction', 'solid-core', moduleRef);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
async triggerMcpClientJob(prompt: string): Promise<
|
|
46
|
+
async triggerMcpClientJob(prompt: string): Promise<any> {
|
|
47
47
|
const activeUser: ActiveUserData = this.requestContextService.getActiveUser();
|
|
48
48
|
|
|
49
49
|
const aiInteraction = await this.create({
|
|
@@ -65,7 +65,12 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
|
|
|
65
65
|
parentEntityId: aiInteraction.id,
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const queueMessageId = await this.publisherFactory.publish(m, 'TriggerMcpClientPublisher');
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
queueMessageId: queueMessageId,
|
|
72
|
+
aiInteractionId: aiInteraction.id
|
|
73
|
+
}
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
/**
|
|
@@ -206,7 +211,7 @@ export class AiInteractionService extends CRUDService<AiInteraction> {
|
|
|
206
211
|
|
|
207
212
|
// TODO: This provider to implement an interface - IMcpToolResponseHandler ... apply(aiInteraction: AiInteraction)
|
|
208
213
|
// throw new Error('Method not implemented.');
|
|
209
|
-
|
|
214
|
+
|
|
210
215
|
// Mark the interaction as applied
|
|
211
216
|
await this.update(aiInteraction.id, { isApplied: true }, [], true);
|
|
212
217
|
|
|
@@ -84,7 +84,6 @@ export class MediaService extends CRUDService<Media> {
|
|
|
84
84
|
const savedMedias = [];
|
|
85
85
|
for (let i = 0; i < files.length; i++) {
|
|
86
86
|
|
|
87
|
-
|
|
88
87
|
createDto['fieldMetadata'] = await this.fieldMetadataRepo.findOne({
|
|
89
88
|
where: {
|
|
90
89
|
id: createDto['fieldMetadataId']
|
|
@@ -113,9 +112,9 @@ export class MediaService extends CRUDService<Media> {
|
|
|
113
112
|
const fileName = this.getFileName(file);
|
|
114
113
|
let awsFileUrl;
|
|
115
114
|
if (createDto.mediaStorageProviderMetadata.isPublic === true) {
|
|
116
|
-
awsFileUrl = await this.fileService.copyToS3(file.path, file.mimetype, fileName, createDto.mediaStorageProviderMetadata.bucketName,);
|
|
117
|
-
} else {
|
|
118
115
|
awsFileUrl = await this.fileService.copyToS3WithPublic(file.path, file.mimetype, fileName, createDto.mediaStorageProviderMetadata.bucketName,);
|
|
116
|
+
} else {
|
|
117
|
+
awsFileUrl = await this.fileService.copyToS3(file.path, file.mimetype, fileName, createDto.mediaStorageProviderMetadata.bucketName,);
|
|
119
118
|
}
|
|
120
119
|
// createDto['relativeUri'] = this.getAwsS3FullFilePath(awsFileUrl, createDto.mediaStorageProviderMetadata.bucketName, createDto.mediaStorageProviderMetadata.region);
|
|
121
120
|
createDto['relativeUri'] = awsFileUrl
|
|
@@ -60,7 +60,7 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
60
60
|
throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
|
|
61
61
|
}
|
|
62
62
|
const result: Media[] = [];
|
|
63
|
-
|
|
63
|
+
for (const file of files) {
|
|
64
64
|
const fileName = this.getFileName(file);
|
|
65
65
|
// Store the file in the configured S3 Bucket
|
|
66
66
|
let awsFileUrl;
|
|
@@ -84,7 +84,7 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
84
84
|
}) as unknown as Media;
|
|
85
85
|
result.push(mediaEntity);
|
|
86
86
|
this.logger.debug(`Stored media with`, mediaEntity);
|
|
87
|
-
}
|
|
87
|
+
};
|
|
88
88
|
return result;
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -38,7 +38,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
38
38
|
throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
|
|
39
39
|
}
|
|
40
40
|
const result: Media[] = [];
|
|
41
|
-
|
|
41
|
+
for (const file of files) {
|
|
42
42
|
// Store the file in the configured file storage directory
|
|
43
43
|
const fileStoragePath = this.getFullFilePath(this.getFileName(file));
|
|
44
44
|
await this.fileService.copyFile(file.path, fileStoragePath);
|
|
@@ -57,7 +57,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
57
57
|
}) as unknown as Media;
|
|
58
58
|
result.push(mediaEntity);
|
|
59
59
|
this.logger.debug(`Stored media with`, mediaEntity);
|
|
60
|
-
}
|
|
60
|
+
};
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -66,7 +66,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
66
66
|
throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
|
|
67
67
|
}
|
|
68
68
|
const result: Media[] = [];
|
|
69
|
-
|
|
69
|
+
for (const pair of streamPairs) {
|
|
70
70
|
const stream = pair[0];
|
|
71
71
|
const fileName = pair[1];
|
|
72
72
|
this.fileService.writeStreamToFile(stream, this.getFullFilePath(fileName));
|
|
@@ -78,7 +78,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
78
78
|
fieldMetadataId: mediaFieldMetadata.id
|
|
79
79
|
}) as unknown as Media;
|
|
80
80
|
this.logger.debug(`Stored media with`, mediaEntity);
|
|
81
|
-
}
|
|
81
|
+
};
|
|
82
82
|
return result;
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -68,4 +68,120 @@ export class MqMessageService extends CRUDService<MqMessage> {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Wait until a queue message reaches a terminal status (succeeded/failed).
|
|
73
|
+
*
|
|
74
|
+
* @param messageId string – the external message id you store in `ss_mq_message.messageId`
|
|
75
|
+
* @param opts.timeoutMs total time to wait before giving up (default 60s)
|
|
76
|
+
* @param opts.intervalMs initial poll interval (default 500ms)
|
|
77
|
+
* @param opts.maxIntervalMs cap for exponential backoff (default 2000ms)
|
|
78
|
+
* @param opts.throwOnFailure if true, throws when stage === 'failed' (default false)
|
|
79
|
+
* @param opts.parseJson try JSON.parse on `output` and `error` (default true)
|
|
80
|
+
* @returns resolves with the final MqMessage row when terminal, rejects on timeout (or failure if throwOnFailure)
|
|
81
|
+
*/
|
|
82
|
+
async waitForTerminalStatus(
|
|
83
|
+
messageId: string,
|
|
84
|
+
opts?: {
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
intervalMs?: number;
|
|
87
|
+
maxIntervalMs?: number;
|
|
88
|
+
throwOnFailure?: boolean;
|
|
89
|
+
parseJson?: boolean;
|
|
90
|
+
logEveryN?: number;
|
|
91
|
+
},
|
|
92
|
+
): Promise<MqMessage> {
|
|
93
|
+
const {
|
|
94
|
+
timeoutMs = 60_000,
|
|
95
|
+
intervalMs = 500,
|
|
96
|
+
maxIntervalMs = 2_000,
|
|
97
|
+
throwOnFailure = false,
|
|
98
|
+
parseJson = true,
|
|
99
|
+
logEveryN = 10, // log every N polls to avoid noisy logs
|
|
100
|
+
} = opts || {};
|
|
101
|
+
|
|
102
|
+
const start = Date.now();
|
|
103
|
+
let attempt = 0;
|
|
104
|
+
let delay = intervalMs;
|
|
105
|
+
|
|
106
|
+
// Small helper
|
|
107
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
108
|
+
|
|
109
|
+
while (true) {
|
|
110
|
+
attempt++;
|
|
111
|
+
|
|
112
|
+
// Fetch minimal columns needed for quick polling
|
|
113
|
+
const rec = await this.repo.findOne({
|
|
114
|
+
where: { messageId },
|
|
115
|
+
select: {
|
|
116
|
+
id: true,
|
|
117
|
+
messageId: true,
|
|
118
|
+
stage: true,
|
|
119
|
+
finishedAt: true,
|
|
120
|
+
elapsedMillis: true,
|
|
121
|
+
output: true,
|
|
122
|
+
error: true,
|
|
123
|
+
input: true,
|
|
124
|
+
// add other fields if you need to return them
|
|
125
|
+
} as any,
|
|
126
|
+
loadEagerRelations: false,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (attempt % logEveryN === 0) {
|
|
130
|
+
this.logger.debug(
|
|
131
|
+
`waitForTerminalStatus(${messageId}) poll #${attempt} -> ${rec?.stage ?? 'not_found'}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!rec) {
|
|
136
|
+
// Not found yet – keep waiting until timeout
|
|
137
|
+
} else if (rec.stage === 'succeeded' || rec.stage === 'failed') {
|
|
138
|
+
// Optionally parse output/error if they contain JSON strings
|
|
139
|
+
if (parseJson) {
|
|
140
|
+
rec.output = this.safeJsonParse(rec.output);
|
|
141
|
+
rec.error = this.safeJsonParse(rec.error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (rec.stage === 'failed' && throwOnFailure) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Queue message ${messageId} failed` +
|
|
147
|
+
(rec.error ? `: ${JSON.stringify(rec.error).slice(0, 500)}` : '')
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return rec;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Timeout?
|
|
154
|
+
const elapsed = Date.now() - start;
|
|
155
|
+
if (elapsed >= timeoutMs) {
|
|
156
|
+
throw new Error(`Timed out after ${timeoutMs}ms waiting for message ${messageId} to reach terminal status`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Backoff with cap
|
|
160
|
+
await sleep(delay);
|
|
161
|
+
delay = Math.min(Math.floor(delay * 1.5), maxIntervalMs);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// /**
|
|
166
|
+
// * Optional wrapper: publish and then wait (if your publisher returns the messageId).
|
|
167
|
+
// */
|
|
168
|
+
// async publishAndWait<T>(
|
|
169
|
+
// publishFn: () => Promise<string>, // returns messageId
|
|
170
|
+
// waitOpts?: Parameters<MqMessageService['waitForTerminalStatus']>[1],
|
|
171
|
+
// ): Promise<MqMessage> {
|
|
172
|
+
// const messageId = await publishFn();
|
|
173
|
+
// return this.waitForTerminalStatus(messageId, waitOpts);
|
|
174
|
+
// }
|
|
175
|
+
|
|
176
|
+
private safeJsonParse(value: unknown): unknown {
|
|
177
|
+
if (value == null) return value;
|
|
178
|
+
if (typeof value !== 'string') return value;
|
|
179
|
+
const s = value.trim();
|
|
180
|
+
if (!s) return s;
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(s);
|
|
183
|
+
} catch {
|
|
184
|
+
return value; // leave as-is if not valid JSON
|
|
185
|
+
}
|
|
186
|
+
}
|
|
71
187
|
}
|
|
@@ -43,7 +43,7 @@ export class PollerService implements OnModuleDestroy, BeforeApplicationShutdown
|
|
|
43
43
|
const opts: Required<PollOptions> = {
|
|
44
44
|
baseDelayMs: options.baseDelayMs ?? 1000,
|
|
45
45
|
maxDelayMs: options.maxDelayMs ?? 30_000,
|
|
46
|
-
timeoutPerIterationMs: options.timeoutPerIterationMs ?? 60_000,
|
|
46
|
+
timeoutPerIterationMs: options.timeoutPerIterationMs ?? 5 * 60_000,
|
|
47
47
|
jitter: options.jitter ?? true,
|
|
48
48
|
};
|
|
49
49
|
|
|
@@ -108,7 +108,7 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
108
108
|
this.poller.start(queueName, (q) => this.processNext(q), {
|
|
109
109
|
baseDelayMs: 1000,
|
|
110
110
|
maxDelayMs: 30_000,
|
|
111
|
-
timeoutPerIterationMs: 60_000,
|
|
111
|
+
timeoutPerIterationMs: 5 * 60_000,
|
|
112
112
|
jitter: true,
|
|
113
113
|
});
|
|
114
114
|
|
package/src/solid-core.module.ts
CHANGED