@solidstarters/solid-core 1.2.146 → 1.2.149
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/config/common.config.d.ts +10 -0
- package/dist/config/common.config.d.ts.map +1 -1
- package/dist/config/common.config.js +5 -0
- package/dist/config/common.config.js.map +1 -1
- 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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +5 -5
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/jobs/database/api-email-subscriber-database.service.d.ts +3 -1
- package/dist/jobs/database/api-email-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/api-email-subscriber-database.service.js +6 -3
- package/dist/jobs/database/api-email-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/computed-field-evaluation-subscriber.service.d.ts +3 -1
- package/dist/jobs/database/computed-field-evaluation-subscriber.service.d.ts.map +1 -1
- package/dist/jobs/database/computed-field-evaluation-subscriber.service.js +6 -3
- package/dist/jobs/database/computed-field-evaluation-subscriber.service.js.map +1 -1
- package/dist/jobs/database/generate-code-subscriber-database.service.d.ts +3 -1
- package/dist/jobs/database/generate-code-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/generate-code-subscriber-database.service.js +6 -3
- package/dist/jobs/database/generate-code-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/otp-subscriber-database.service.d.ts +3 -1
- package/dist/jobs/database/otp-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/otp-subscriber-database.service.js +5 -2
- package/dist/jobs/database/otp-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/sms-subscriber-database.service.d.ts +4 -2
- package/dist/jobs/database/sms-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/sms-subscriber-database.service.js +7 -4
- package/dist/jobs/database/sms-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/smtp-email-subscriber-database.service.d.ts +4 -2
- package/dist/jobs/database/smtp-email-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/smtp-email-subscriber-database.service.js +6 -3
- package/dist/jobs/database/smtp-email-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/test-queue-subscriber-database.service.d.ts +3 -1
- package/dist/jobs/database/test-queue-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/test-queue-subscriber-database.service.js +6 -3
- package/dist/jobs/database/test-queue-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.d.ts +3 -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 +7 -2
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/jobs/database/twilio-sms-publisher-database.service.d.ts +11 -0
- package/dist/jobs/database/twilio-sms-publisher-database.service.d.ts.map +1 -0
- package/dist/jobs/database/twilio-sms-publisher-database.service.js +39 -0
- package/dist/jobs/database/twilio-sms-publisher-database.service.js.map +1 -0
- package/dist/jobs/database/twilio-sms-queue-database-options.d.ts +8 -0
- package/dist/jobs/database/twilio-sms-queue-database-options.d.ts.map +1 -0
- package/dist/jobs/database/twilio-sms-queue-database-options.js +10 -0
- package/dist/jobs/database/twilio-sms-queue-database-options.js.map +1 -0
- package/dist/jobs/database/twilio-sms-subscriber-database.service.d.ts +17 -0
- package/dist/jobs/database/twilio-sms-subscriber-database.service.d.ts.map +1 -0
- package/dist/jobs/database/twilio-sms-subscriber-database.service.js +48 -0
- package/dist/jobs/database/twilio-sms-subscriber-database.service.js.map +1 -0
- package/dist/jobs/database/whatsapp-subscriber-database.service.d.ts +3 -1
- package/dist/jobs/database/whatsapp-subscriber-database.service.d.ts.map +1 -1
- package/dist/jobs/database/whatsapp-subscriber-database.service.js +6 -3
- package/dist/jobs/database/whatsapp-subscriber-database.service.js.map +1 -1
- package/dist/jobs/sms-subscriber.service.d.ts +1 -1
- package/dist/jobs/sms-subscriber.service.js +1 -1
- package/dist/jobs/sms-subscriber.service.js.map +1 -1
- package/dist/jobs/smtp-email-subscriber.service.d.ts +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +15 -4
- package/dist/seeders/module-metadata-seeder.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/mail/smtp-email.service.d.ts +4 -4
- package/dist/services/mail/smtp-email.service.d.ts.map +1 -1
- package/dist/services/mail/smtp-email.service.js +12 -8
- package/dist/services/mail/smtp-email.service.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.d.ts +24 -0
- package/dist/services/poller.service.d.ts.map +1 -0
- package/dist/services/poller.service.js +131 -0
- package/dist/services/poller.service.js.map +1 -0
- package/dist/services/queues/database-subscriber.service.d.ts +4 -1
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +13 -13
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/sms/Msg91BaseSMSService.d.ts +2 -2
- package/dist/services/sms/Msg91BaseSMSService.d.ts.map +1 -1
- package/dist/services/sms/Msg91BaseSMSService.js.map +1 -1
- package/dist/services/sms/Msg91OTPService.d.ts +1 -1
- package/dist/services/sms/Msg91OTPService.d.ts.map +1 -1
- package/dist/services/sms/Msg91OTPService.js.map +1 -1
- package/dist/services/sms/Msg91SMSService.d.ts +1 -1
- package/dist/services/sms/Msg91SMSService.d.ts.map +1 -1
- package/dist/services/sms/Msg91SMSService.js.map +1 -1
- package/dist/services/sms/TwilioSMSService.d.ts +18 -0
- package/dist/services/sms/TwilioSMSService.d.ts.map +1 -0
- package/dist/services/sms/TwilioSMSService.js +115 -0
- package/dist/services/sms/TwilioSMSService.js.map +1 -0
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +11 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -3
- package/src/config/common.config.ts +5 -0
- 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 +5 -0
- package/src/interfaces.ts +5 -5
- package/src/jobs/database/api-email-subscriber-database.service.ts +3 -1
- package/src/jobs/database/computed-field-evaluation-subscriber.service.ts +4 -2
- package/src/jobs/database/generate-code-subscriber-database.service.ts +3 -1
- package/src/jobs/database/otp-subscriber-database.service.ts +3 -1
- package/src/jobs/database/sms-subscriber-database.service.ts +4 -2
- package/src/jobs/database/smtp-email-subscriber-database.service.ts +3 -1
- package/src/jobs/database/test-queue-subscriber-database.service.ts +3 -1
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +5 -1
- package/src/jobs/database/twilio-sms-publisher-database.service.ts +23 -0
- package/src/jobs/database/twilio-sms-queue-database-options.ts +9 -0
- package/src/jobs/database/twilio-sms-subscriber-database.service.ts +32 -0
- package/src/jobs/database/whatsapp-subscriber-database.service.ts +3 -1
- package/src/jobs/sms-subscriber.service.ts +1 -1
- package/src/seeders/module-metadata-seeder.service.ts +18 -15
- package/src/seeders/seed-data/solid-core-metadata.json +83 -3
- package/src/services/ai-interaction.service.ts +8 -3
- package/src/services/mail/smtp-email.service.ts +18 -17
- package/src/services/mq-message.service.ts +116 -0
- package/src/services/poller.service.ts +163 -0
- package/src/services/queues/database-subscriber.service.ts +39 -12
- package/src/services/sms/Msg91BaseSMSService.ts +2 -2
- package/src/services/sms/Msg91OTPService.ts +1 -1
- package/src/services/sms/Msg91SMSService.ts +1 -1
- package/src/services/sms/TwilioSMSService.ts +118 -0
- package/src/solid-core.module.ts +14 -0
|
@@ -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
|
|
|
@@ -47,7 +47,7 @@ export class SMTPEMailService implements IMail {
|
|
|
47
47
|
cc?: string[],
|
|
48
48
|
bcc?: string[],
|
|
49
49
|
from?: string
|
|
50
|
-
)
|
|
50
|
+
) {
|
|
51
51
|
// Load template and evaluate it.
|
|
52
52
|
const emailTemplate = await this.emailTemplateService.findOneByName(templateName);
|
|
53
53
|
if (!emailTemplate) {
|
|
@@ -63,7 +63,7 @@ export class SMTPEMailService implements IMail {
|
|
|
63
63
|
const subject = subjectTemplate(templateParams);
|
|
64
64
|
|
|
65
65
|
// Finally send the email.
|
|
66
|
-
await this.sendEmail(to, subject, body, shouldQueueEmails, wrapperAttachments, attachments, parentEntity, parentEntityId, cc, bcc, from);
|
|
66
|
+
return await this.sendEmail(to, subject, body, shouldQueueEmails, wrapperAttachments, attachments, parentEntity, parentEntityId, cc, bcc, from);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async sendEmail(
|
|
@@ -78,7 +78,7 @@ export class SMTPEMailService implements IMail {
|
|
|
78
78
|
cc?: string[],
|
|
79
79
|
bcc?: string[],
|
|
80
80
|
from?: string
|
|
81
|
-
)
|
|
81
|
+
) {
|
|
82
82
|
const message = {
|
|
83
83
|
payload: {
|
|
84
84
|
from: from || this.commonConfiguration.smtpMail.from,
|
|
@@ -95,33 +95,34 @@ export class SMTPEMailService implements IMail {
|
|
|
95
95
|
|
|
96
96
|
// Send using queue if the developer has explicitly invoked with true.
|
|
97
97
|
if (shouldQueueEmails === true) {
|
|
98
|
-
this.sendEmailAsynchronously(message);
|
|
98
|
+
return this.sendEmailAsynchronously(message);
|
|
99
99
|
}
|
|
100
100
|
// If developer has not, however system config mandates that we send using queue, still we send.
|
|
101
101
|
else if (shouldQueueEmails == false && this.commonConfiguration.shouldQueueEmails === true) {
|
|
102
|
-
this.sendEmailAsynchronously(message);
|
|
102
|
+
return this.sendEmailAsynchronously(message);
|
|
103
103
|
}
|
|
104
|
-
// Else we send
|
|
104
|
+
// Else we send synchronously
|
|
105
105
|
else {
|
|
106
|
-
await this.sendEmailSynchronously(message);
|
|
106
|
+
return await this.sendEmailSynchronously(message);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
async sendEmailAsynchronously(message) {
|
|
111
111
|
const { to, subject, body } = message.payload;
|
|
112
|
-
// this.notificationPublisherService.publish(message);
|
|
113
|
-
// this.emailPublisher.publish(message);
|
|
114
|
-
// this.emailDbPublisher.publish(message);
|
|
115
|
-
|
|
116
|
-
this.publisherFactory.publish(message, 'SmtpEmailQueuePublisher');
|
|
117
|
-
|
|
118
112
|
this.logger.debug(`Queueing email to ${to} with subject ${subject} and body ${body}`);
|
|
113
|
+
return this.publisherFactory.publish(message, 'SmtpEmailQueuePublisher');
|
|
119
114
|
}
|
|
120
115
|
|
|
121
|
-
async sendEmailSynchronously(message: QueueMessage<any>)
|
|
122
|
-
const { from, to, subject, body, attachments, cc, bcc } = message.payload;
|
|
116
|
+
async sendEmailSynchronously(message: QueueMessage<any>) {
|
|
117
|
+
const { from, to, subject, body, attachments = [], cc, bcc } = message.payload;
|
|
123
118
|
|
|
124
|
-
|
|
119
|
+
// if any of the required fields are missing, throw an error.
|
|
120
|
+
if (!from || !to || !subject || !body) {
|
|
121
|
+
this.logger.error(`Required fields are missing in the email message: ${JSON.stringify(message.payload)}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const attachmentsList = attachments?.map((attachment: MailAttachment) => {
|
|
125
126
|
const attachmentEntry = {
|
|
126
127
|
filename: attachment.filename,
|
|
127
128
|
contentType: attachment.contentType,
|
|
@@ -133,7 +134,7 @@ export class SMTPEMailService implements IMail {
|
|
|
133
134
|
attachmentEntry['content'] = attachment.content;
|
|
134
135
|
}
|
|
135
136
|
return attachmentEntry;
|
|
136
|
-
});
|
|
137
|
+
}) || [];
|
|
137
138
|
|
|
138
139
|
// throw new Error('Random error....');
|
|
139
140
|
const r = await this.transporter.sendMail({
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/common/poller/poller.service.ts
|
|
2
|
+
import {
|
|
3
|
+
Injectable,
|
|
4
|
+
Logger,
|
|
5
|
+
OnModuleDestroy,
|
|
6
|
+
BeforeApplicationShutdown,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
|
|
9
|
+
export interface PollOptions {
|
|
10
|
+
/** Wait after a successful iteration */
|
|
11
|
+
baseDelayMs?: number; // default 1000
|
|
12
|
+
/** Maximum delay after repeated failures */
|
|
13
|
+
maxDelayMs?: number; // default 30000
|
|
14
|
+
/** Per-iteration timeout guard */
|
|
15
|
+
timeoutPerIterationMs?: number; // default 60000
|
|
16
|
+
/** Add jitter to spread load */
|
|
17
|
+
jitter?: boolean; // default true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type ProcessNextFn = (queueName: string) => Promise<unknown>;
|
|
21
|
+
|
|
22
|
+
interface PollerState {
|
|
23
|
+
queueName: string;
|
|
24
|
+
processNext: ProcessNextFn;
|
|
25
|
+
opts: Required<PollOptions>;
|
|
26
|
+
inFlight: boolean;
|
|
27
|
+
stopped: boolean;
|
|
28
|
+
backoff: number;
|
|
29
|
+
nextTimer?: NodeJS.Timeout;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Injectable()
|
|
33
|
+
export class PollerService implements OnModuleDestroy, BeforeApplicationShutdown {
|
|
34
|
+
private readonly logger = new Logger(PollerService.name);
|
|
35
|
+
private readonly pollers = new Map<string, PollerState>();
|
|
36
|
+
|
|
37
|
+
start(queueName: string, processNext: ProcessNextFn, options: PollOptions = {}): void {
|
|
38
|
+
if (this.pollers.has(queueName)) {
|
|
39
|
+
this.logger.warn(`Poller "${queueName}" already started; ignoring.`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const opts: Required<PollOptions> = {
|
|
44
|
+
baseDelayMs: options.baseDelayMs ?? 1000,
|
|
45
|
+
maxDelayMs: options.maxDelayMs ?? 30_000,
|
|
46
|
+
timeoutPerIterationMs: options.timeoutPerIterationMs ?? 5 * 60_000,
|
|
47
|
+
jitter: options.jitter ?? true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const state: PollerState = {
|
|
51
|
+
queueName,
|
|
52
|
+
processNext,
|
|
53
|
+
opts,
|
|
54
|
+
inFlight: false,
|
|
55
|
+
stopped: false,
|
|
56
|
+
backoff: opts.baseDelayMs,
|
|
57
|
+
nextTimer: undefined,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.pollers.set(queueName, state);
|
|
61
|
+
// kick off on next tick
|
|
62
|
+
setImmediate(() => this.poll(state).catch(() => { }));
|
|
63
|
+
this.logger.log(`Started poller "${queueName}"`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
stop(queueName: string): void {
|
|
67
|
+
const state = this.pollers.get(queueName);
|
|
68
|
+
if (!state) return;
|
|
69
|
+
|
|
70
|
+
state.stopped = true;
|
|
71
|
+
if (state.nextTimer) {
|
|
72
|
+
clearTimeout(state.nextTimer);
|
|
73
|
+
state.nextTimer = undefined;
|
|
74
|
+
}
|
|
75
|
+
this.pollers.delete(queueName);
|
|
76
|
+
this.logger.log(`Stopped poller "${queueName}"`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stopAll(): void {
|
|
80
|
+
for (const name of Array.from(this.pollers.keys())) {
|
|
81
|
+
this.stop(name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async onModuleDestroy(): Promise<void> {
|
|
86
|
+
this.stopAll();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async beforeApplicationShutdown(): Promise<void> {
|
|
90
|
+
this.stopAll();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- internals ----
|
|
94
|
+
|
|
95
|
+
private async poll(state: PollerState): Promise<void> {
|
|
96
|
+
if (state.stopped || state.inFlight) return;
|
|
97
|
+
state.inFlight = true;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await this.withTimeout(
|
|
101
|
+
state.processNext(state.queueName),
|
|
102
|
+
state.opts.timeoutPerIterationMs,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// success: reset backoff and schedule next run after base delay
|
|
106
|
+
state.backoff = state.opts.baseDelayMs;
|
|
107
|
+
// this.logger.debug(`[${state.queueName}] iteration completed`);
|
|
108
|
+
this.schedule(state, state.opts.baseDelayMs);
|
|
109
|
+
} catch (err: unknown) {
|
|
110
|
+
const msg = this.errorToString(err);
|
|
111
|
+
this.logger.error(`[${state.queueName}] iteration failed: ${msg}`);
|
|
112
|
+
|
|
113
|
+
// failure: schedule with backoff + optional jitter, then increase backoff
|
|
114
|
+
const wait = this.computeWait(state.backoff, state.opts);
|
|
115
|
+
state.backoff = Math.min(state.backoff * 2, state.opts.maxDelayMs);
|
|
116
|
+
this.schedule(state, wait);
|
|
117
|
+
} finally {
|
|
118
|
+
state.inFlight = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private schedule(state: PollerState, delayMs: number) {
|
|
123
|
+
if (state.stopped) return;
|
|
124
|
+
if (state.nextTimer) clearTimeout(state.nextTimer);
|
|
125
|
+
|
|
126
|
+
state.nextTimer = setTimeout(() => {
|
|
127
|
+
// clear reference before calling poll to avoid re-entrancy confusion
|
|
128
|
+
state.nextTimer = undefined;
|
|
129
|
+
this.poll(state).catch(() => { });
|
|
130
|
+
}, delayMs);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private computeWait(currentBackoff: number, opts: Required<PollOptions>): number {
|
|
134
|
+
if (!opts.jitter) return currentBackoff;
|
|
135
|
+
// Full jitter: random in [250ms, currentBackoff * 2], clamped to maxDelayMs
|
|
136
|
+
const doubled = Math.min(currentBackoff * 2, opts.maxDelayMs);
|
|
137
|
+
const jittered = Math.floor(Math.random() * doubled);
|
|
138
|
+
return Math.max(250, jittered);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
|
|
142
|
+
let timer: NodeJS.Timeout | undefined;
|
|
143
|
+
try {
|
|
144
|
+
return await Promise.race<T>([
|
|
145
|
+
p,
|
|
146
|
+
new Promise<never>((_, rej) => {
|
|
147
|
+
timer = setTimeout(() => rej(new Error(`Iteration timed out after ${ms} ms`)), ms);
|
|
148
|
+
}),
|
|
149
|
+
]);
|
|
150
|
+
} finally {
|
|
151
|
+
if (timer) clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private errorToString(err: unknown): string {
|
|
156
|
+
if (err instanceof Error) return err.stack ?? err.message;
|
|
157
|
+
try {
|
|
158
|
+
return JSON.stringify(err);
|
|
159
|
+
} catch {
|
|
160
|
+
return String(err);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -3,6 +3,7 @@ import { QueuesModuleOptions } from "../../interfaces";
|
|
|
3
3
|
import { QueueMessage, QueueSubscriber } from '../../interfaces/mq';
|
|
4
4
|
import { MqMessageQueueService } from '../mq-message-queue.service';
|
|
5
5
|
import { MqMessageService } from '../mq-message.service';
|
|
6
|
+
import { PollerService } from '../poller.service';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscriber<T> {
|
|
@@ -13,6 +14,7 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
13
14
|
constructor(
|
|
14
15
|
protected readonly mqMessageService: MqMessageService,
|
|
15
16
|
protected readonly mqMessageQueueService: MqMessageQueueService,
|
|
17
|
+
protected readonly poller: PollerService,
|
|
16
18
|
) {
|
|
17
19
|
this.serviceRole = process.env.QUEUES_SERVICE_ROLE;
|
|
18
20
|
if (!this.serviceRole) {
|
|
@@ -70,6 +72,31 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
70
72
|
// this.logger.debug(`#### DatabaseSubscriber finished processing message from queue: ${queueName}`);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
// async onModuleInit(): Promise<void> {
|
|
76
|
+
// // we will start subscriber only if the current service role is subscriber.
|
|
77
|
+
// if (['both', 'subscriber'].includes(this.serviceRole)) {
|
|
78
|
+
|
|
79
|
+
// const options = this.options();
|
|
80
|
+
|
|
81
|
+
// const queueName = options.queueName;
|
|
82
|
+
// // setInterval(() => this.processNext(queueName), 1000);
|
|
83
|
+
// const poll = async () => {
|
|
84
|
+
// try {
|
|
85
|
+
// await this.processNext(queueName);
|
|
86
|
+
// } catch (err) {
|
|
87
|
+
// this.logger.error(`Polling error: ${err.message}`);
|
|
88
|
+
// } finally {
|
|
89
|
+
// setTimeout(poll, 1000); // Wait 1s *after* processing finishes
|
|
90
|
+
// }
|
|
91
|
+
// };
|
|
92
|
+
|
|
93
|
+
// // start the loop
|
|
94
|
+
// poll();
|
|
95
|
+
|
|
96
|
+
// this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);
|
|
97
|
+
// }
|
|
98
|
+
// }
|
|
99
|
+
|
|
73
100
|
async onModuleInit(): Promise<void> {
|
|
74
101
|
// we will start subscriber only if the current service role is subscriber.
|
|
75
102
|
if (['both', 'subscriber'].includes(this.serviceRole)) {
|
|
@@ -77,24 +104,24 @@ export abstract class DatabaseSubscriber<T> implements OnModuleInit, QueueSubscr
|
|
|
77
104
|
const options = this.options();
|
|
78
105
|
|
|
79
106
|
const queueName = options.queueName;
|
|
80
|
-
// setInterval(() => this.processNext(queueName), 1000);
|
|
81
|
-
const poll = async () => {
|
|
82
|
-
try {
|
|
83
|
-
await this.processNext(queueName);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
this.logger.error(`Polling error: ${err.message}`);
|
|
86
|
-
} finally {
|
|
87
|
-
setTimeout(poll, 1000); // Wait 1s *after* processing finishes
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
this.poller.start(queueName, (q) => this.processNext(q), {
|
|
109
|
+
baseDelayMs: 1000,
|
|
110
|
+
maxDelayMs: 30_000,
|
|
111
|
+
timeoutPerIterationMs: 5 * 60_000,
|
|
112
|
+
jitter: true,
|
|
113
|
+
});
|
|
93
114
|
|
|
94
115
|
this.logger.log(`DatabaseSubscriber ready to consume messages: ${JSON.stringify(this.options())}`);
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
|
|
119
|
+
onModuleDestroy() {
|
|
120
|
+
const options = this.options();
|
|
121
|
+
const queueName = options.queueName;
|
|
122
|
+
this.poller.stop(queueName);
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
/**
|
|
99
126
|
* Abstract method for message processing logic.
|
|
100
127
|
*/
|
|
@@ -20,7 +20,7 @@ export abstract class Msg91BaseSMSService implements ISMS {
|
|
|
20
20
|
throw new Error(`Msg91 does not support sending plain text messages, you need to register a template and use the templateId to send the SMS.`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async sendSMSUsingTemplate(to: string, templateName: string, templateParams: any, shouldQueueSms = false): Promise<
|
|
23
|
+
async sendSMSUsingTemplate(to: string, templateName: string, templateParams: any, shouldQueueSms = false): Promise<any> {
|
|
24
24
|
// Load template and evaluate it.
|
|
25
25
|
const emailTemplate = await this.smsTemplateService.findOneByName(templateName);
|
|
26
26
|
if (!emailTemplate) {
|
|
@@ -76,5 +76,5 @@ export abstract class Msg91BaseSMSService implements ISMS {
|
|
|
76
76
|
this.logger.debug(`Queueing SMS to ${to} with message ${JSON.stringify(message)}`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
abstract sendSMSSynchronously(message: QueueMessage<any>): Promise<
|
|
79
|
+
abstract sendSMSSynchronously(message: QueueMessage<any>): Promise<any>
|
|
80
80
|
}
|
|
@@ -28,7 +28,7 @@ export class Msg91OTPService extends Msg91BaseSMSService implements ISMS {
|
|
|
28
28
|
super(commonConfiguration, 'OTPQueuePublisher', publisherFactory, smsTemplateService);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async sendSMSSynchronously(message: QueueMessage<any>): Promise<
|
|
31
|
+
async sendSMSSynchronously(message: QueueMessage<any>): Promise<any> {
|
|
32
32
|
const { to, templateId, otp } = message.payload;
|
|
33
33
|
const params = { otp, template_id: templateId, mobile: to, authkey: this.commonConfiguration.msg91Sms.apiKey }
|
|
34
34
|
const otpUrl = `${this.commonConfiguration.msg91Sms.url}/otp?${this.paramsToQueryString(params)}`;
|
|
@@ -21,7 +21,7 @@ export class Msg91SMSService extends Msg91BaseSMSService implements ISMS {
|
|
|
21
21
|
super(commonConfiguration, 'SmsQueuePublisher', publisherFactory, smsTemplateService)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
async sendSMSSynchronously(message: QueueMessage<any>): Promise<
|
|
24
|
+
async sendSMSSynchronously(message: QueueMessage<any>): Promise<any> {
|
|
25
25
|
const { to, templateId, ...templateParams } = message.payload;
|
|
26
26
|
const body = { template_id: templateId, short_url: "0", recipients: [{ mobiles: to, ...templateParams }] };
|
|
27
27
|
const headers = { "authkey": this.commonConfiguration.msg91Sms.apiKey };
|