@spfn/notification 0.1.0-beta.17 → 0.1.0-beta.18
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/README.md +59 -4
- package/dist/server.d.ts +141 -24
- package/dist/server.js +1313 -833
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
package/dist/server.js
CHANGED
|
@@ -138,19 +138,19 @@ async function getSESClient() {
|
|
|
138
138
|
return sesClient;
|
|
139
139
|
}
|
|
140
140
|
try {
|
|
141
|
-
const {
|
|
142
|
-
sesClient = new
|
|
141
|
+
const { SESv2Client } = await import("@aws-sdk/client-sesv2");
|
|
142
|
+
sesClient = new SESv2Client({
|
|
143
143
|
region: env.AWS_REGION,
|
|
144
144
|
credentials: env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY ? {
|
|
145
145
|
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
146
146
|
secretAccessKey: env.AWS_SECRET_ACCESS_KEY
|
|
147
147
|
} : void 0
|
|
148
148
|
});
|
|
149
|
-
log.debug("SES client created", { region: env.AWS_REGION });
|
|
149
|
+
log.debug("SES v2 client created", { region: env.AWS_REGION });
|
|
150
150
|
return sesClient;
|
|
151
151
|
} catch {
|
|
152
152
|
throw new Error(
|
|
153
|
-
"@aws-sdk/client-
|
|
153
|
+
"@aws-sdk/client-sesv2 is not installed. Please install it: pnpm add @aws-sdk/client-sesv2"
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -159,29 +159,31 @@ var awsSesProvider = {
|
|
|
159
159
|
async send(params) {
|
|
160
160
|
try {
|
|
161
161
|
const client = await getSESClient();
|
|
162
|
-
const { SendEmailCommand } = await import("@aws-sdk/client-
|
|
162
|
+
const { SendEmailCommand } = await import("@aws-sdk/client-sesv2");
|
|
163
163
|
const command = new SendEmailCommand({
|
|
164
|
-
|
|
164
|
+
FromEmailAddress: params.from,
|
|
165
165
|
Destination: {
|
|
166
166
|
ToAddresses: params.to
|
|
167
167
|
},
|
|
168
168
|
ReplyToAddresses: params.replyTo ? [params.replyTo] : void 0,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
Body: {
|
|
175
|
-
...params.html && {
|
|
176
|
-
Html: {
|
|
177
|
-
Charset: "UTF-8",
|
|
178
|
-
Data: params.html
|
|
179
|
-
}
|
|
169
|
+
Content: {
|
|
170
|
+
Simple: {
|
|
171
|
+
Subject: {
|
|
172
|
+
Charset: "UTF-8",
|
|
173
|
+
Data: params.subject
|
|
180
174
|
},
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
Body: {
|
|
176
|
+
...params.html && {
|
|
177
|
+
Html: {
|
|
178
|
+
Charset: "UTF-8",
|
|
179
|
+
Data: params.html
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
...params.text && {
|
|
183
|
+
Text: {
|
|
184
|
+
Charset: "UTF-8",
|
|
185
|
+
Data: params.text
|
|
186
|
+
}
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
}
|
|
@@ -551,7 +553,7 @@ function registerBuiltinTemplates() {
|
|
|
551
553
|
}
|
|
552
554
|
|
|
553
555
|
// src/services/notification.service.ts
|
|
554
|
-
import { create, findOne, findMany, updateOne, count } from "@spfn/core/db";
|
|
556
|
+
import { create, createMany, findOne, findMany, updateOne, count } from "@spfn/core/db";
|
|
555
557
|
import { desc, eq, and, gte, lte } from "drizzle-orm";
|
|
556
558
|
|
|
557
559
|
// src/entities/schema.ts
|
|
@@ -694,6 +696,12 @@ async function createNotificationRecord(data) {
|
|
|
694
696
|
status: "pending"
|
|
695
697
|
});
|
|
696
698
|
}
|
|
699
|
+
async function createNotificationRecords(data) {
|
|
700
|
+
return await createMany(notifications, data.map((d) => ({
|
|
701
|
+
...d,
|
|
702
|
+
status: "pending"
|
|
703
|
+
})));
|
|
704
|
+
}
|
|
697
705
|
async function createScheduledNotification(data) {
|
|
698
706
|
return await create(notifications, {
|
|
699
707
|
...data,
|
|
@@ -817,749 +825,330 @@ async function findScheduledNotifications(options = {}) {
|
|
|
817
825
|
});
|
|
818
826
|
}
|
|
819
827
|
|
|
820
|
-
// src/channels/
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
function getProvider() {
|
|
830
|
-
const providerName = env.SPFN_NOTIFICATION_EMAIL_PROVIDER || "aws-ses";
|
|
831
|
-
const provider = providers[providerName];
|
|
832
|
-
if (!provider) {
|
|
833
|
-
throw new Error(`Email provider not found: ${providerName}`);
|
|
828
|
+
// src/channels/concurrency.ts
|
|
829
|
+
async function runWithConcurrency(items, fn, concurrency = 10) {
|
|
830
|
+
const results = new Array(items.length);
|
|
831
|
+
let cursor = 0;
|
|
832
|
+
async function worker() {
|
|
833
|
+
while (cursor < items.length) {
|
|
834
|
+
const i = cursor++;
|
|
835
|
+
results[i] = await fn(items[i]);
|
|
836
|
+
}
|
|
834
837
|
}
|
|
835
|
-
|
|
838
|
+
const workers = Array.from(
|
|
839
|
+
{ length: Math.min(concurrency, items.length) },
|
|
840
|
+
() => worker()
|
|
841
|
+
);
|
|
842
|
+
await Promise.all(workers);
|
|
843
|
+
return results;
|
|
836
844
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
845
|
+
|
|
846
|
+
// src/jobs/send-bulk-email-item.ts
|
|
847
|
+
import { job } from "@spfn/core/job";
|
|
848
|
+
|
|
849
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
850
|
+
var value_exports = {};
|
|
851
|
+
__export(value_exports, {
|
|
852
|
+
HasPropertyKey: () => HasPropertyKey,
|
|
853
|
+
IsArray: () => IsArray,
|
|
854
|
+
IsAsyncIterator: () => IsAsyncIterator,
|
|
855
|
+
IsBigInt: () => IsBigInt,
|
|
856
|
+
IsBoolean: () => IsBoolean,
|
|
857
|
+
IsDate: () => IsDate,
|
|
858
|
+
IsFunction: () => IsFunction,
|
|
859
|
+
IsIterator: () => IsIterator,
|
|
860
|
+
IsNull: () => IsNull,
|
|
861
|
+
IsNumber: () => IsNumber,
|
|
862
|
+
IsObject: () => IsObject,
|
|
863
|
+
IsRegExp: () => IsRegExp,
|
|
864
|
+
IsString: () => IsString,
|
|
865
|
+
IsSymbol: () => IsSymbol,
|
|
866
|
+
IsUint8Array: () => IsUint8Array,
|
|
867
|
+
IsUndefined: () => IsUndefined
|
|
868
|
+
});
|
|
869
|
+
function HasPropertyKey(value, key) {
|
|
870
|
+
return key in value;
|
|
871
|
+
}
|
|
872
|
+
function IsAsyncIterator(value) {
|
|
873
|
+
return IsObject(value) && !IsArray(value) && !IsUint8Array(value) && Symbol.asyncIterator in value;
|
|
874
|
+
}
|
|
875
|
+
function IsArray(value) {
|
|
876
|
+
return Array.isArray(value);
|
|
877
|
+
}
|
|
878
|
+
function IsBigInt(value) {
|
|
879
|
+
return typeof value === "bigint";
|
|
880
|
+
}
|
|
881
|
+
function IsBoolean(value) {
|
|
882
|
+
return typeof value === "boolean";
|
|
883
|
+
}
|
|
884
|
+
function IsDate(value) {
|
|
885
|
+
return value instanceof globalThis.Date;
|
|
886
|
+
}
|
|
887
|
+
function IsFunction(value) {
|
|
888
|
+
return typeof value === "function";
|
|
889
|
+
}
|
|
890
|
+
function IsIterator(value) {
|
|
891
|
+
return IsObject(value) && !IsArray(value) && !IsUint8Array(value) && Symbol.iterator in value;
|
|
892
|
+
}
|
|
893
|
+
function IsNull(value) {
|
|
894
|
+
return value === null;
|
|
895
|
+
}
|
|
896
|
+
function IsNumber(value) {
|
|
897
|
+
return typeof value === "number";
|
|
898
|
+
}
|
|
899
|
+
function IsObject(value) {
|
|
900
|
+
return typeof value === "object" && value !== null;
|
|
901
|
+
}
|
|
902
|
+
function IsRegExp(value) {
|
|
903
|
+
return value instanceof globalThis.RegExp;
|
|
904
|
+
}
|
|
905
|
+
function IsString(value) {
|
|
906
|
+
return typeof value === "string";
|
|
907
|
+
}
|
|
908
|
+
function IsSymbol(value) {
|
|
909
|
+
return typeof value === "symbol";
|
|
910
|
+
}
|
|
911
|
+
function IsUint8Array(value) {
|
|
912
|
+
return value instanceof globalThis.Uint8Array;
|
|
913
|
+
}
|
|
914
|
+
function IsUndefined(value) {
|
|
915
|
+
return value === void 0;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/clone/value.mjs
|
|
919
|
+
function ArrayType(value) {
|
|
920
|
+
return value.map((value2) => Visit(value2));
|
|
921
|
+
}
|
|
922
|
+
function DateType(value) {
|
|
923
|
+
return new Date(value.getTime());
|
|
924
|
+
}
|
|
925
|
+
function Uint8ArrayType(value) {
|
|
926
|
+
return new Uint8Array(value);
|
|
927
|
+
}
|
|
928
|
+
function RegExpType(value) {
|
|
929
|
+
return new RegExp(value.source, value.flags);
|
|
930
|
+
}
|
|
931
|
+
function ObjectType(value) {
|
|
932
|
+
const result = {};
|
|
933
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
934
|
+
result[key] = Visit(value[key]);
|
|
856
935
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
return {
|
|
860
|
-
success: false,
|
|
861
|
-
error: "Email subject is required"
|
|
862
|
-
};
|
|
936
|
+
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
937
|
+
result[key] = Visit(value[key]);
|
|
863
938
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
function Visit(value) {
|
|
942
|
+
return IsArray(value) ? ArrayType(value) : IsDate(value) ? DateType(value) : IsUint8Array(value) ? Uint8ArrayType(value) : IsRegExp(value) ? RegExpType(value) : IsObject(value) ? ObjectType(value) : value;
|
|
943
|
+
}
|
|
944
|
+
function Clone(value) {
|
|
945
|
+
return Visit(value);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/clone/type.mjs
|
|
949
|
+
function CloneType(schema, options) {
|
|
950
|
+
return options === void 0 ? Clone(schema) : Clone({ ...options, ...schema });
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/value/guard/guard.mjs
|
|
954
|
+
function IsObject2(value) {
|
|
955
|
+
return value !== null && typeof value === "object";
|
|
956
|
+
}
|
|
957
|
+
function IsArray2(value) {
|
|
958
|
+
return globalThis.Array.isArray(value) && !globalThis.ArrayBuffer.isView(value);
|
|
959
|
+
}
|
|
960
|
+
function IsUndefined2(value) {
|
|
961
|
+
return value === void 0;
|
|
962
|
+
}
|
|
963
|
+
function IsNumber2(value) {
|
|
964
|
+
return typeof value === "number";
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/system/policy.mjs
|
|
968
|
+
var TypeSystemPolicy;
|
|
969
|
+
(function(TypeSystemPolicy2) {
|
|
970
|
+
TypeSystemPolicy2.InstanceMode = "default";
|
|
971
|
+
TypeSystemPolicy2.ExactOptionalPropertyTypes = false;
|
|
972
|
+
TypeSystemPolicy2.AllowArrayObject = false;
|
|
973
|
+
TypeSystemPolicy2.AllowNaN = false;
|
|
974
|
+
TypeSystemPolicy2.AllowNullVoid = false;
|
|
975
|
+
function IsExactOptionalProperty(value, key) {
|
|
976
|
+
return TypeSystemPolicy2.ExactOptionalPropertyTypes ? key in value : value[key] !== void 0;
|
|
870
977
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
subject,
|
|
876
|
-
text: text3,
|
|
877
|
-
html
|
|
878
|
-
};
|
|
879
|
-
const provider = getProvider();
|
|
880
|
-
let historyId;
|
|
881
|
-
if (isHistoryEnabled()) {
|
|
882
|
-
try {
|
|
883
|
-
const record = await createNotificationRecord({
|
|
884
|
-
channel: "email",
|
|
885
|
-
recipient: recipients.join(","),
|
|
886
|
-
templateName: params.template,
|
|
887
|
-
templateData: params.data,
|
|
888
|
-
subject,
|
|
889
|
-
content: text3,
|
|
890
|
-
providerName: provider.name
|
|
891
|
-
});
|
|
892
|
-
historyId = record.id;
|
|
893
|
-
} catch (error) {
|
|
894
|
-
log2.warn("Failed to create notification history record", error);
|
|
895
|
-
}
|
|
978
|
+
TypeSystemPolicy2.IsExactOptionalProperty = IsExactOptionalProperty;
|
|
979
|
+
function IsObjectLike(value) {
|
|
980
|
+
const isObject = IsObject2(value);
|
|
981
|
+
return TypeSystemPolicy2.AllowArrayObject ? isObject : isObject && !IsArray2(value);
|
|
896
982
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
try {
|
|
901
|
-
const { html: trackedHtml } = processTrackingHtml(internalParams.html, {
|
|
902
|
-
notificationId: historyId,
|
|
903
|
-
baseUrl: trackingBaseUrl
|
|
904
|
-
});
|
|
905
|
-
internalParams.html = trackedHtml;
|
|
906
|
-
} catch (error) {
|
|
907
|
-
log2.warn("Failed to apply tracking to email HTML", error);
|
|
908
|
-
}
|
|
983
|
+
TypeSystemPolicy2.IsObjectLike = IsObjectLike;
|
|
984
|
+
function IsRecordLike(value) {
|
|
985
|
+
return IsObjectLike(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
909
986
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
} else {
|
|
914
|
-
log2.error("Email send failed", { to: recipients, subject, error: result.error });
|
|
987
|
+
TypeSystemPolicy2.IsRecordLike = IsRecordLike;
|
|
988
|
+
function IsNumberLike(value) {
|
|
989
|
+
return TypeSystemPolicy2.AllowNaN ? IsNumber2(value) : Number.isFinite(value);
|
|
915
990
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
} else {
|
|
921
|
-
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
922
|
-
}
|
|
923
|
-
} catch (error) {
|
|
924
|
-
log2.warn("Failed to update notification history record", error);
|
|
925
|
-
}
|
|
991
|
+
TypeSystemPolicy2.IsNumberLike = IsNumberLike;
|
|
992
|
+
function IsVoidLike(value) {
|
|
993
|
+
const isUndefined = IsUndefined2(value);
|
|
994
|
+
return TypeSystemPolicy2.AllowNullVoid ? isUndefined || value === null : isUndefined;
|
|
926
995
|
}
|
|
927
|
-
|
|
996
|
+
TypeSystemPolicy2.IsVoidLike = IsVoidLike;
|
|
997
|
+
})(TypeSystemPolicy || (TypeSystemPolicy = {}));
|
|
998
|
+
|
|
999
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/create/immutable.mjs
|
|
1000
|
+
function ImmutableArray(value) {
|
|
1001
|
+
return globalThis.Object.freeze(value).map((value2) => Immutable(value2));
|
|
928
1002
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
let successCount = 0;
|
|
932
|
-
let failureCount = 0;
|
|
933
|
-
for (const item of items) {
|
|
934
|
-
const result = await sendEmail(item);
|
|
935
|
-
results.push(result);
|
|
936
|
-
if (result.success) {
|
|
937
|
-
successCount++;
|
|
938
|
-
} else {
|
|
939
|
-
failureCount++;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
return { results, successCount, failureCount };
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// src/channels/sms/providers/aws-sns.ts
|
|
946
|
-
import { logger as logger3 } from "@spfn/core/logger";
|
|
947
|
-
var log3 = logger3.child("@spfn/notification:sns");
|
|
948
|
-
var snsClient = null;
|
|
949
|
-
async function getSNSClient() {
|
|
950
|
-
if (snsClient) {
|
|
951
|
-
return snsClient;
|
|
952
|
-
}
|
|
953
|
-
try {
|
|
954
|
-
const { SNSClient } = await import("@aws-sdk/client-sns");
|
|
955
|
-
snsClient = new SNSClient({
|
|
956
|
-
region: env.AWS_REGION,
|
|
957
|
-
credentials: env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY ? {
|
|
958
|
-
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
959
|
-
secretAccessKey: env.AWS_SECRET_ACCESS_KEY
|
|
960
|
-
} : void 0
|
|
961
|
-
});
|
|
962
|
-
log3.debug("SNS client created", { region: env.AWS_REGION });
|
|
963
|
-
return snsClient;
|
|
964
|
-
} catch {
|
|
965
|
-
throw new Error(
|
|
966
|
-
"@aws-sdk/client-sns is not installed. Please install it: pnpm add @aws-sdk/client-sns"
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
var awsSnsProvider = {
|
|
971
|
-
name: "aws-sns",
|
|
972
|
-
async send(params) {
|
|
973
|
-
try {
|
|
974
|
-
const client = await getSNSClient();
|
|
975
|
-
const { PublishCommand } = await import("@aws-sdk/client-sns");
|
|
976
|
-
const command = new PublishCommand({
|
|
977
|
-
PhoneNumber: params.to,
|
|
978
|
-
Message: params.message,
|
|
979
|
-
MessageAttributes: {
|
|
980
|
-
"AWS.SNS.SMS.SMSType": {
|
|
981
|
-
DataType: "String",
|
|
982
|
-
StringValue: "Transactional"
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
});
|
|
986
|
-
const response = await client.send(command);
|
|
987
|
-
return {
|
|
988
|
-
success: true,
|
|
989
|
-
messageId: response.MessageId
|
|
990
|
-
};
|
|
991
|
-
} catch (error) {
|
|
992
|
-
const err = error;
|
|
993
|
-
log3.error("SNS send failed", err, { to: params.to });
|
|
994
|
-
return {
|
|
995
|
-
success: false,
|
|
996
|
-
error: err.message
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
// src/channels/sms/utils.ts
|
|
1003
|
-
function normalizePhoneNumber(phone, defaultCountryCode) {
|
|
1004
|
-
let normalized = phone.replace(/[^\d+]/g, "");
|
|
1005
|
-
if (!normalized.startsWith("+")) {
|
|
1006
|
-
const countryCode = defaultCountryCode ?? getSmsDefaultCountryCode();
|
|
1007
|
-
if (normalized.startsWith("0")) {
|
|
1008
|
-
normalized = normalized.slice(1);
|
|
1009
|
-
}
|
|
1010
|
-
normalized = countryCode + normalized;
|
|
1011
|
-
}
|
|
1012
|
-
return normalized;
|
|
1003
|
+
function ImmutableDate(value) {
|
|
1004
|
+
return value;
|
|
1013
1005
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
import { logger as logger4 } from "@spfn/core/logger";
|
|
1017
|
-
var log4 = logger4.child("@spfn/notification:sms");
|
|
1018
|
-
var providers2 = {
|
|
1019
|
-
"aws-sns": awsSnsProvider
|
|
1020
|
-
};
|
|
1021
|
-
function registerSMSProvider(provider) {
|
|
1022
|
-
providers2[provider.name] = provider;
|
|
1006
|
+
function ImmutableUint8Array(value) {
|
|
1007
|
+
return value;
|
|
1023
1008
|
}
|
|
1024
|
-
function
|
|
1025
|
-
|
|
1026
|
-
const provider = providers2[providerName];
|
|
1027
|
-
if (!provider) {
|
|
1028
|
-
throw new Error(`SMS provider not found: ${providerName}`);
|
|
1029
|
-
}
|
|
1030
|
-
return provider;
|
|
1009
|
+
function ImmutableRegExp(value) {
|
|
1010
|
+
return value;
|
|
1031
1011
|
}
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (!hasTemplate(params.template)) {
|
|
1037
|
-
log4.warn(`Template not found: ${params.template}`);
|
|
1038
|
-
return {
|
|
1039
|
-
success: false,
|
|
1040
|
-
error: `Template not found: ${params.template}`
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
const rendered = renderTemplate(params.template, params.data || {}, "sms");
|
|
1044
|
-
if (rendered.sms) {
|
|
1045
|
-
message = rendered.sms.message;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
if (!message) {
|
|
1049
|
-
log4.warn("SMS message is required", { to: recipients });
|
|
1050
|
-
return {
|
|
1051
|
-
success: false,
|
|
1052
|
-
error: "SMS message is required"
|
|
1053
|
-
};
|
|
1012
|
+
function ImmutableObject(value) {
|
|
1013
|
+
const result = {};
|
|
1014
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
1015
|
+
result[key] = Immutable(value[key]);
|
|
1054
1016
|
}
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
for (const recipient of recipients) {
|
|
1058
|
-
const normalizedPhone = normalizePhoneNumber(recipient);
|
|
1059
|
-
const internalParams = {
|
|
1060
|
-
to: normalizedPhone,
|
|
1061
|
-
message
|
|
1062
|
-
};
|
|
1063
|
-
let historyId;
|
|
1064
|
-
if (isHistoryEnabled()) {
|
|
1065
|
-
try {
|
|
1066
|
-
const record = await createNotificationRecord({
|
|
1067
|
-
channel: "sms",
|
|
1068
|
-
recipient: normalizedPhone,
|
|
1069
|
-
templateName: params.template,
|
|
1070
|
-
templateData: params.data,
|
|
1071
|
-
content: message,
|
|
1072
|
-
providerName: provider.name
|
|
1073
|
-
});
|
|
1074
|
-
historyId = record.id;
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
log4.warn("Failed to create notification history record", error);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
const result = await provider.send(internalParams);
|
|
1080
|
-
if (result.success) {
|
|
1081
|
-
log4.info("SMS sent", { to: normalizedPhone, messageId: result.messageId });
|
|
1082
|
-
} else {
|
|
1083
|
-
log4.error("SMS send failed", { to: normalizedPhone, error: result.error });
|
|
1084
|
-
}
|
|
1085
|
-
if (historyId && isHistoryEnabled()) {
|
|
1086
|
-
try {
|
|
1087
|
-
if (result.success) {
|
|
1088
|
-
await markNotificationSent(historyId, result.messageId);
|
|
1089
|
-
} else {
|
|
1090
|
-
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
1091
|
-
}
|
|
1092
|
-
} catch (error) {
|
|
1093
|
-
log4.warn("Failed to update notification history record", error);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
results.push(result);
|
|
1017
|
+
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
1018
|
+
result[key] = Immutable(value[key]);
|
|
1097
1019
|
}
|
|
1098
|
-
|
|
1099
|
-
const messageIds = results.filter((r) => r.messageId).map((r) => r.messageId).join(",");
|
|
1100
|
-
const errors = results.filter((r) => r.error).map((r) => r.error).join("; ");
|
|
1101
|
-
return {
|
|
1102
|
-
success: allSuccess,
|
|
1103
|
-
messageId: messageIds || void 0,
|
|
1104
|
-
error: errors || void 0
|
|
1105
|
-
};
|
|
1020
|
+
return globalThis.Object.freeze(result);
|
|
1106
1021
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1022
|
+
function Immutable(value) {
|
|
1023
|
+
return IsArray(value) ? ImmutableArray(value) : IsDate(value) ? ImmutableDate(value) : IsUint8Array(value) ? ImmutableUint8Array(value) : IsRegExp(value) ? ImmutableRegExp(value) : IsObject(value) ? ImmutableObject(value) : value;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/create/type.mjs
|
|
1027
|
+
function CreateType(schema, options) {
|
|
1028
|
+
const result = options !== void 0 ? { ...options, ...schema } : schema;
|
|
1029
|
+
switch (TypeSystemPolicy.InstanceMode) {
|
|
1030
|
+
case "freeze":
|
|
1031
|
+
return Immutable(result);
|
|
1032
|
+
case "clone":
|
|
1033
|
+
return Clone(result);
|
|
1034
|
+
default:
|
|
1035
|
+
return result;
|
|
1119
1036
|
}
|
|
1120
|
-
return { results, successCount, failureCount };
|
|
1121
1037
|
}
|
|
1122
1038
|
|
|
1123
|
-
//
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
name: "webhook",
|
|
1128
|
-
async send(params) {
|
|
1129
|
-
try {
|
|
1130
|
-
const res = await fetch(params.webhookUrl, {
|
|
1131
|
-
method: "POST",
|
|
1132
|
-
headers: { "Content-Type": "application/json" },
|
|
1133
|
-
body: JSON.stringify({
|
|
1134
|
-
text: params.text,
|
|
1135
|
-
blocks: params.blocks
|
|
1136
|
-
})
|
|
1137
|
-
});
|
|
1138
|
-
return {
|
|
1139
|
-
success: res.ok,
|
|
1140
|
-
error: res.ok ? void 0 : await res.text()
|
|
1141
|
-
};
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
const err = error;
|
|
1144
|
-
log5.error("Webhook request failed", err);
|
|
1145
|
-
return {
|
|
1146
|
-
success: false,
|
|
1147
|
-
error: err.message
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1039
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/error/error.mjs
|
|
1040
|
+
var TypeBoxError = class extends Error {
|
|
1041
|
+
constructor(message) {
|
|
1042
|
+
super(message);
|
|
1150
1043
|
}
|
|
1151
1044
|
};
|
|
1152
1045
|
|
|
1153
|
-
//
|
|
1154
|
-
|
|
1155
|
-
var
|
|
1156
|
-
var
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1046
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
|
1047
|
+
var TransformKind = Symbol.for("TypeBox.Transform");
|
|
1048
|
+
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
|
1049
|
+
var OptionalKind = Symbol.for("TypeBox.Optional");
|
|
1050
|
+
var Hint = Symbol.for("TypeBox.Hint");
|
|
1051
|
+
var Kind = Symbol.for("TypeBox.Kind");
|
|
1052
|
+
|
|
1053
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
|
|
1054
|
+
function IsReadonly(value) {
|
|
1055
|
+
return IsObject(value) && value[ReadonlyKind] === "Readonly";
|
|
1161
1056
|
}
|
|
1162
|
-
function
|
|
1163
|
-
return
|
|
1057
|
+
function IsOptional(value) {
|
|
1058
|
+
return IsObject(value) && value[OptionalKind] === "Optional";
|
|
1164
1059
|
}
|
|
1165
|
-
function
|
|
1166
|
-
return
|
|
1060
|
+
function IsAny(value) {
|
|
1061
|
+
return IsKindOf(value, "Any");
|
|
1167
1062
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (!webhookUrl) {
|
|
1171
|
-
log6.warn("Slack webhook URL is required");
|
|
1172
|
-
return {
|
|
1173
|
-
success: false,
|
|
1174
|
-
error: "Slack webhook URL is required. Set SPFN_NOTIFICATION_SLACK_WEBHOOK_URL or pass webhookUrl."
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
let text3 = params.text;
|
|
1178
|
-
let blocks = params.blocks;
|
|
1179
|
-
if (params.template) {
|
|
1180
|
-
if (!hasTemplate(params.template)) {
|
|
1181
|
-
log6.warn(`Template not found: ${params.template}`);
|
|
1182
|
-
return {
|
|
1183
|
-
success: false,
|
|
1184
|
-
error: `Template not found: ${params.template}`
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
const rendered = renderTemplate(params.template, params.data || {}, "slack");
|
|
1188
|
-
if (rendered.slack) {
|
|
1189
|
-
text3 = rendered.slack.text;
|
|
1190
|
-
blocks = rendered.slack.blocks;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
if (!text3 && !blocks) {
|
|
1194
|
-
log6.warn("Slack message requires text or blocks");
|
|
1195
|
-
return {
|
|
1196
|
-
success: false,
|
|
1197
|
-
error: "Slack message requires text or blocks"
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
const internalParams = {
|
|
1201
|
-
webhookUrl,
|
|
1202
|
-
text: text3,
|
|
1203
|
-
blocks
|
|
1204
|
-
};
|
|
1205
|
-
const provider = getProvider3();
|
|
1206
|
-
let historyId;
|
|
1207
|
-
if (isHistoryEnabled()) {
|
|
1208
|
-
try {
|
|
1209
|
-
const record = await createNotificationRecord({
|
|
1210
|
-
channel: "slack",
|
|
1211
|
-
recipient: webhookUrl,
|
|
1212
|
-
templateName: params.template,
|
|
1213
|
-
templateData: params.data,
|
|
1214
|
-
content: text3,
|
|
1215
|
-
providerName: provider.name
|
|
1216
|
-
});
|
|
1217
|
-
historyId = record.id;
|
|
1218
|
-
} catch (error) {
|
|
1219
|
-
log6.warn("Failed to create notification history record", error);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
const result = await provider.send(internalParams);
|
|
1223
|
-
if (result.success) {
|
|
1224
|
-
log6.info("Slack message sent");
|
|
1225
|
-
} else {
|
|
1226
|
-
log6.error("Slack send failed", { error: result.error });
|
|
1227
|
-
}
|
|
1228
|
-
if (historyId && isHistoryEnabled()) {
|
|
1229
|
-
try {
|
|
1230
|
-
if (result.success) {
|
|
1231
|
-
await markNotificationSent(historyId, result.messageId);
|
|
1232
|
-
} else {
|
|
1233
|
-
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
1234
|
-
}
|
|
1235
|
-
} catch (error) {
|
|
1236
|
-
log6.warn("Failed to update notification history record", error);
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
return result;
|
|
1063
|
+
function IsArgument(value) {
|
|
1064
|
+
return IsKindOf(value, "Argument");
|
|
1240
1065
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
let successCount = 0;
|
|
1244
|
-
let failureCount = 0;
|
|
1245
|
-
for (const item of items) {
|
|
1246
|
-
const result = await sendSlack(item);
|
|
1247
|
-
results.push(result);
|
|
1248
|
-
if (result.success) {
|
|
1249
|
-
successCount++;
|
|
1250
|
-
} else {
|
|
1251
|
-
failureCount++;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
return { results, successCount, failureCount };
|
|
1066
|
+
function IsArray3(value) {
|
|
1067
|
+
return IsKindOf(value, "Array");
|
|
1255
1068
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
import { job } from "@spfn/core/job";
|
|
1259
|
-
|
|
1260
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
1261
|
-
var value_exports = {};
|
|
1262
|
-
__export(value_exports, {
|
|
1263
|
-
HasPropertyKey: () => HasPropertyKey,
|
|
1264
|
-
IsArray: () => IsArray,
|
|
1265
|
-
IsAsyncIterator: () => IsAsyncIterator,
|
|
1266
|
-
IsBigInt: () => IsBigInt,
|
|
1267
|
-
IsBoolean: () => IsBoolean,
|
|
1268
|
-
IsDate: () => IsDate,
|
|
1269
|
-
IsFunction: () => IsFunction,
|
|
1270
|
-
IsIterator: () => IsIterator,
|
|
1271
|
-
IsNull: () => IsNull,
|
|
1272
|
-
IsNumber: () => IsNumber,
|
|
1273
|
-
IsObject: () => IsObject,
|
|
1274
|
-
IsRegExp: () => IsRegExp,
|
|
1275
|
-
IsString: () => IsString,
|
|
1276
|
-
IsSymbol: () => IsSymbol,
|
|
1277
|
-
IsUint8Array: () => IsUint8Array,
|
|
1278
|
-
IsUndefined: () => IsUndefined
|
|
1279
|
-
});
|
|
1280
|
-
function HasPropertyKey(value, key) {
|
|
1281
|
-
return key in value;
|
|
1069
|
+
function IsAsyncIterator2(value) {
|
|
1070
|
+
return IsKindOf(value, "AsyncIterator");
|
|
1282
1071
|
}
|
|
1283
|
-
function
|
|
1284
|
-
return
|
|
1072
|
+
function IsBigInt2(value) {
|
|
1073
|
+
return IsKindOf(value, "BigInt");
|
|
1285
1074
|
}
|
|
1286
|
-
function
|
|
1287
|
-
return
|
|
1075
|
+
function IsBoolean2(value) {
|
|
1076
|
+
return IsKindOf(value, "Boolean");
|
|
1288
1077
|
}
|
|
1289
|
-
function
|
|
1290
|
-
return
|
|
1078
|
+
function IsComputed(value) {
|
|
1079
|
+
return IsKindOf(value, "Computed");
|
|
1291
1080
|
}
|
|
1292
|
-
function
|
|
1293
|
-
return
|
|
1081
|
+
function IsConstructor(value) {
|
|
1082
|
+
return IsKindOf(value, "Constructor");
|
|
1294
1083
|
}
|
|
1295
|
-
function
|
|
1296
|
-
return value
|
|
1084
|
+
function IsDate2(value) {
|
|
1085
|
+
return IsKindOf(value, "Date");
|
|
1297
1086
|
}
|
|
1298
|
-
function
|
|
1299
|
-
return
|
|
1087
|
+
function IsFunction2(value) {
|
|
1088
|
+
return IsKindOf(value, "Function");
|
|
1300
1089
|
}
|
|
1301
|
-
function
|
|
1302
|
-
return
|
|
1090
|
+
function IsInteger(value) {
|
|
1091
|
+
return IsKindOf(value, "Integer");
|
|
1303
1092
|
}
|
|
1304
|
-
function
|
|
1305
|
-
return value
|
|
1093
|
+
function IsIntersect(value) {
|
|
1094
|
+
return IsKindOf(value, "Intersect");
|
|
1306
1095
|
}
|
|
1307
|
-
function
|
|
1308
|
-
return
|
|
1096
|
+
function IsIterator2(value) {
|
|
1097
|
+
return IsKindOf(value, "Iterator");
|
|
1309
1098
|
}
|
|
1310
|
-
function
|
|
1311
|
-
return
|
|
1099
|
+
function IsKindOf(value, kind) {
|
|
1100
|
+
return IsObject(value) && Kind in value && value[Kind] === kind;
|
|
1312
1101
|
}
|
|
1313
|
-
function
|
|
1314
|
-
return value
|
|
1102
|
+
function IsLiteralValue(value) {
|
|
1103
|
+
return IsBoolean(value) || IsNumber(value) || IsString(value);
|
|
1315
1104
|
}
|
|
1316
|
-
function
|
|
1317
|
-
return
|
|
1105
|
+
function IsLiteral(value) {
|
|
1106
|
+
return IsKindOf(value, "Literal");
|
|
1318
1107
|
}
|
|
1319
|
-
function
|
|
1320
|
-
return
|
|
1108
|
+
function IsMappedKey(value) {
|
|
1109
|
+
return IsKindOf(value, "MappedKey");
|
|
1321
1110
|
}
|
|
1322
|
-
function
|
|
1323
|
-
return value
|
|
1111
|
+
function IsMappedResult(value) {
|
|
1112
|
+
return IsKindOf(value, "MappedResult");
|
|
1324
1113
|
}
|
|
1325
|
-
function
|
|
1326
|
-
return value
|
|
1114
|
+
function IsNever(value) {
|
|
1115
|
+
return IsKindOf(value, "Never");
|
|
1327
1116
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
function ArrayType(value) {
|
|
1331
|
-
return value.map((value2) => Visit(value2));
|
|
1117
|
+
function IsNot(value) {
|
|
1118
|
+
return IsKindOf(value, "Not");
|
|
1332
1119
|
}
|
|
1333
|
-
function
|
|
1334
|
-
return
|
|
1120
|
+
function IsNull2(value) {
|
|
1121
|
+
return IsKindOf(value, "Null");
|
|
1335
1122
|
}
|
|
1336
|
-
function
|
|
1337
|
-
return
|
|
1123
|
+
function IsNumber3(value) {
|
|
1124
|
+
return IsKindOf(value, "Number");
|
|
1338
1125
|
}
|
|
1339
|
-
function
|
|
1340
|
-
return
|
|
1126
|
+
function IsObject3(value) {
|
|
1127
|
+
return IsKindOf(value, "Object");
|
|
1341
1128
|
}
|
|
1342
|
-
function
|
|
1343
|
-
|
|
1344
|
-
for (const key of Object.getOwnPropertyNames(value)) {
|
|
1345
|
-
result[key] = Visit(value[key]);
|
|
1346
|
-
}
|
|
1347
|
-
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
1348
|
-
result[key] = Visit(value[key]);
|
|
1349
|
-
}
|
|
1350
|
-
return result;
|
|
1129
|
+
function IsPromise(value) {
|
|
1130
|
+
return IsKindOf(value, "Promise");
|
|
1351
1131
|
}
|
|
1352
|
-
function
|
|
1353
|
-
return
|
|
1132
|
+
function IsRecord(value) {
|
|
1133
|
+
return IsKindOf(value, "Record");
|
|
1354
1134
|
}
|
|
1355
|
-
function
|
|
1356
|
-
return
|
|
1135
|
+
function IsRef(value) {
|
|
1136
|
+
return IsKindOf(value, "Ref");
|
|
1357
1137
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
function CloneType(schema, options) {
|
|
1361
|
-
return options === void 0 ? Clone(schema) : Clone({ ...options, ...schema });
|
|
1138
|
+
function IsRegExp2(value) {
|
|
1139
|
+
return IsKindOf(value, "RegExp");
|
|
1362
1140
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
function IsObject2(value) {
|
|
1366
|
-
return value !== null && typeof value === "object";
|
|
1141
|
+
function IsString2(value) {
|
|
1142
|
+
return IsKindOf(value, "String");
|
|
1367
1143
|
}
|
|
1368
|
-
function
|
|
1369
|
-
return
|
|
1144
|
+
function IsSymbol2(value) {
|
|
1145
|
+
return IsKindOf(value, "Symbol");
|
|
1370
1146
|
}
|
|
1371
|
-
function
|
|
1372
|
-
return value
|
|
1147
|
+
function IsTemplateLiteral(value) {
|
|
1148
|
+
return IsKindOf(value, "TemplateLiteral");
|
|
1373
1149
|
}
|
|
1374
|
-
function
|
|
1375
|
-
return
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/system/policy.mjs
|
|
1379
|
-
var TypeSystemPolicy;
|
|
1380
|
-
(function(TypeSystemPolicy2) {
|
|
1381
|
-
TypeSystemPolicy2.InstanceMode = "default";
|
|
1382
|
-
TypeSystemPolicy2.ExactOptionalPropertyTypes = false;
|
|
1383
|
-
TypeSystemPolicy2.AllowArrayObject = false;
|
|
1384
|
-
TypeSystemPolicy2.AllowNaN = false;
|
|
1385
|
-
TypeSystemPolicy2.AllowNullVoid = false;
|
|
1386
|
-
function IsExactOptionalProperty(value, key) {
|
|
1387
|
-
return TypeSystemPolicy2.ExactOptionalPropertyTypes ? key in value : value[key] !== void 0;
|
|
1388
|
-
}
|
|
1389
|
-
TypeSystemPolicy2.IsExactOptionalProperty = IsExactOptionalProperty;
|
|
1390
|
-
function IsObjectLike(value) {
|
|
1391
|
-
const isObject = IsObject2(value);
|
|
1392
|
-
return TypeSystemPolicy2.AllowArrayObject ? isObject : isObject && !IsArray2(value);
|
|
1393
|
-
}
|
|
1394
|
-
TypeSystemPolicy2.IsObjectLike = IsObjectLike;
|
|
1395
|
-
function IsRecordLike(value) {
|
|
1396
|
-
return IsObjectLike(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
1397
|
-
}
|
|
1398
|
-
TypeSystemPolicy2.IsRecordLike = IsRecordLike;
|
|
1399
|
-
function IsNumberLike(value) {
|
|
1400
|
-
return TypeSystemPolicy2.AllowNaN ? IsNumber2(value) : Number.isFinite(value);
|
|
1401
|
-
}
|
|
1402
|
-
TypeSystemPolicy2.IsNumberLike = IsNumberLike;
|
|
1403
|
-
function IsVoidLike(value) {
|
|
1404
|
-
const isUndefined = IsUndefined2(value);
|
|
1405
|
-
return TypeSystemPolicy2.AllowNullVoid ? isUndefined || value === null : isUndefined;
|
|
1406
|
-
}
|
|
1407
|
-
TypeSystemPolicy2.IsVoidLike = IsVoidLike;
|
|
1408
|
-
})(TypeSystemPolicy || (TypeSystemPolicy = {}));
|
|
1409
|
-
|
|
1410
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/create/immutable.mjs
|
|
1411
|
-
function ImmutableArray(value) {
|
|
1412
|
-
return globalThis.Object.freeze(value).map((value2) => Immutable(value2));
|
|
1413
|
-
}
|
|
1414
|
-
function ImmutableDate(value) {
|
|
1415
|
-
return value;
|
|
1416
|
-
}
|
|
1417
|
-
function ImmutableUint8Array(value) {
|
|
1418
|
-
return value;
|
|
1419
|
-
}
|
|
1420
|
-
function ImmutableRegExp(value) {
|
|
1421
|
-
return value;
|
|
1422
|
-
}
|
|
1423
|
-
function ImmutableObject(value) {
|
|
1424
|
-
const result = {};
|
|
1425
|
-
for (const key of Object.getOwnPropertyNames(value)) {
|
|
1426
|
-
result[key] = Immutable(value[key]);
|
|
1427
|
-
}
|
|
1428
|
-
for (const key of Object.getOwnPropertySymbols(value)) {
|
|
1429
|
-
result[key] = Immutable(value[key]);
|
|
1430
|
-
}
|
|
1431
|
-
return globalThis.Object.freeze(result);
|
|
1432
|
-
}
|
|
1433
|
-
function Immutable(value) {
|
|
1434
|
-
return IsArray(value) ? ImmutableArray(value) : IsDate(value) ? ImmutableDate(value) : IsUint8Array(value) ? ImmutableUint8Array(value) : IsRegExp(value) ? ImmutableRegExp(value) : IsObject(value) ? ImmutableObject(value) : value;
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/create/type.mjs
|
|
1438
|
-
function CreateType(schema, options) {
|
|
1439
|
-
const result = options !== void 0 ? { ...options, ...schema } : schema;
|
|
1440
|
-
switch (TypeSystemPolicy.InstanceMode) {
|
|
1441
|
-
case "freeze":
|
|
1442
|
-
return Immutable(result);
|
|
1443
|
-
case "clone":
|
|
1444
|
-
return Clone(result);
|
|
1445
|
-
default:
|
|
1446
|
-
return result;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/error/error.mjs
|
|
1451
|
-
var TypeBoxError = class extends Error {
|
|
1452
|
-
constructor(message) {
|
|
1453
|
-
super(message);
|
|
1454
|
-
}
|
|
1455
|
-
};
|
|
1456
|
-
|
|
1457
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
|
1458
|
-
var TransformKind = Symbol.for("TypeBox.Transform");
|
|
1459
|
-
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
|
1460
|
-
var OptionalKind = Symbol.for("TypeBox.Optional");
|
|
1461
|
-
var Hint = Symbol.for("TypeBox.Hint");
|
|
1462
|
-
var Kind = Symbol.for("TypeBox.Kind");
|
|
1463
|
-
|
|
1464
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
|
|
1465
|
-
function IsReadonly(value) {
|
|
1466
|
-
return IsObject(value) && value[ReadonlyKind] === "Readonly";
|
|
1467
|
-
}
|
|
1468
|
-
function IsOptional(value) {
|
|
1469
|
-
return IsObject(value) && value[OptionalKind] === "Optional";
|
|
1470
|
-
}
|
|
1471
|
-
function IsAny(value) {
|
|
1472
|
-
return IsKindOf(value, "Any");
|
|
1473
|
-
}
|
|
1474
|
-
function IsArgument(value) {
|
|
1475
|
-
return IsKindOf(value, "Argument");
|
|
1476
|
-
}
|
|
1477
|
-
function IsArray3(value) {
|
|
1478
|
-
return IsKindOf(value, "Array");
|
|
1479
|
-
}
|
|
1480
|
-
function IsAsyncIterator2(value) {
|
|
1481
|
-
return IsKindOf(value, "AsyncIterator");
|
|
1482
|
-
}
|
|
1483
|
-
function IsBigInt2(value) {
|
|
1484
|
-
return IsKindOf(value, "BigInt");
|
|
1485
|
-
}
|
|
1486
|
-
function IsBoolean2(value) {
|
|
1487
|
-
return IsKindOf(value, "Boolean");
|
|
1488
|
-
}
|
|
1489
|
-
function IsComputed(value) {
|
|
1490
|
-
return IsKindOf(value, "Computed");
|
|
1491
|
-
}
|
|
1492
|
-
function IsConstructor(value) {
|
|
1493
|
-
return IsKindOf(value, "Constructor");
|
|
1494
|
-
}
|
|
1495
|
-
function IsDate2(value) {
|
|
1496
|
-
return IsKindOf(value, "Date");
|
|
1497
|
-
}
|
|
1498
|
-
function IsFunction2(value) {
|
|
1499
|
-
return IsKindOf(value, "Function");
|
|
1500
|
-
}
|
|
1501
|
-
function IsInteger(value) {
|
|
1502
|
-
return IsKindOf(value, "Integer");
|
|
1503
|
-
}
|
|
1504
|
-
function IsIntersect(value) {
|
|
1505
|
-
return IsKindOf(value, "Intersect");
|
|
1506
|
-
}
|
|
1507
|
-
function IsIterator2(value) {
|
|
1508
|
-
return IsKindOf(value, "Iterator");
|
|
1509
|
-
}
|
|
1510
|
-
function IsKindOf(value, kind) {
|
|
1511
|
-
return IsObject(value) && Kind in value && value[Kind] === kind;
|
|
1512
|
-
}
|
|
1513
|
-
function IsLiteralValue(value) {
|
|
1514
|
-
return IsBoolean(value) || IsNumber(value) || IsString(value);
|
|
1515
|
-
}
|
|
1516
|
-
function IsLiteral(value) {
|
|
1517
|
-
return IsKindOf(value, "Literal");
|
|
1518
|
-
}
|
|
1519
|
-
function IsMappedKey(value) {
|
|
1520
|
-
return IsKindOf(value, "MappedKey");
|
|
1521
|
-
}
|
|
1522
|
-
function IsMappedResult(value) {
|
|
1523
|
-
return IsKindOf(value, "MappedResult");
|
|
1524
|
-
}
|
|
1525
|
-
function IsNever(value) {
|
|
1526
|
-
return IsKindOf(value, "Never");
|
|
1527
|
-
}
|
|
1528
|
-
function IsNot(value) {
|
|
1529
|
-
return IsKindOf(value, "Not");
|
|
1530
|
-
}
|
|
1531
|
-
function IsNull2(value) {
|
|
1532
|
-
return IsKindOf(value, "Null");
|
|
1533
|
-
}
|
|
1534
|
-
function IsNumber3(value) {
|
|
1535
|
-
return IsKindOf(value, "Number");
|
|
1536
|
-
}
|
|
1537
|
-
function IsObject3(value) {
|
|
1538
|
-
return IsKindOf(value, "Object");
|
|
1539
|
-
}
|
|
1540
|
-
function IsPromise(value) {
|
|
1541
|
-
return IsKindOf(value, "Promise");
|
|
1542
|
-
}
|
|
1543
|
-
function IsRecord(value) {
|
|
1544
|
-
return IsKindOf(value, "Record");
|
|
1545
|
-
}
|
|
1546
|
-
function IsRef(value) {
|
|
1547
|
-
return IsKindOf(value, "Ref");
|
|
1548
|
-
}
|
|
1549
|
-
function IsRegExp2(value) {
|
|
1550
|
-
return IsKindOf(value, "RegExp");
|
|
1551
|
-
}
|
|
1552
|
-
function IsString2(value) {
|
|
1553
|
-
return IsKindOf(value, "String");
|
|
1554
|
-
}
|
|
1555
|
-
function IsSymbol2(value) {
|
|
1556
|
-
return IsKindOf(value, "Symbol");
|
|
1557
|
-
}
|
|
1558
|
-
function IsTemplateLiteral(value) {
|
|
1559
|
-
return IsKindOf(value, "TemplateLiteral");
|
|
1560
|
-
}
|
|
1561
|
-
function IsThis(value) {
|
|
1562
|
-
return IsKindOf(value, "This");
|
|
1150
|
+
function IsThis(value) {
|
|
1151
|
+
return IsKindOf(value, "This");
|
|
1563
1152
|
}
|
|
1564
1153
|
function IsTransform(value) {
|
|
1565
1154
|
return IsObject(value) && TransformKind in value;
|
|
@@ -3737,138 +3326,1023 @@ function Recursive(callback, options = {}) {
|
|
|
3737
3326
|
return CreateType({ [Hint]: "Recursive", ...thisType }, options);
|
|
3738
3327
|
}
|
|
3739
3328
|
|
|
3740
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/regexp/regexp.mjs
|
|
3741
|
-
function RegExp2(unresolved, options) {
|
|
3742
|
-
const expr = IsString(unresolved) ? new globalThis.RegExp(unresolved) : unresolved;
|
|
3743
|
-
return CreateType({ [Kind]: "RegExp", type: "RegExp", source: expr.source, flags: expr.flags }, options);
|
|
3744
|
-
}
|
|
3329
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/regexp/regexp.mjs
|
|
3330
|
+
function RegExp2(unresolved, options) {
|
|
3331
|
+
const expr = IsString(unresolved) ? new globalThis.RegExp(unresolved) : unresolved;
|
|
3332
|
+
return CreateType({ [Kind]: "RegExp", type: "RegExp", source: expr.source, flags: expr.flags }, options);
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/rest/rest.mjs
|
|
3336
|
+
function RestResolve(T) {
|
|
3337
|
+
return IsIntersect(T) ? T.allOf : IsUnion(T) ? T.anyOf : IsTuple(T) ? T.items ?? [] : [];
|
|
3338
|
+
}
|
|
3339
|
+
function Rest(T) {
|
|
3340
|
+
return RestResolve(T);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/return-type/return-type.mjs
|
|
3344
|
+
function ReturnType(schema, options) {
|
|
3345
|
+
return IsFunction2(schema) ? CreateType(schema.returns, options) : Never(options);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/transform/transform.mjs
|
|
3349
|
+
var TransformDecodeBuilder = class {
|
|
3350
|
+
constructor(schema) {
|
|
3351
|
+
this.schema = schema;
|
|
3352
|
+
}
|
|
3353
|
+
Decode(decode) {
|
|
3354
|
+
return new TransformEncodeBuilder(this.schema, decode);
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
var TransformEncodeBuilder = class {
|
|
3358
|
+
constructor(schema, decode) {
|
|
3359
|
+
this.schema = schema;
|
|
3360
|
+
this.decode = decode;
|
|
3361
|
+
}
|
|
3362
|
+
EncodeTransform(encode, schema) {
|
|
3363
|
+
const Encode = (value) => schema[TransformKind].Encode(encode(value));
|
|
3364
|
+
const Decode = (value) => this.decode(schema[TransformKind].Decode(value));
|
|
3365
|
+
const Codec = { Encode, Decode };
|
|
3366
|
+
return { ...schema, [TransformKind]: Codec };
|
|
3367
|
+
}
|
|
3368
|
+
EncodeSchema(encode, schema) {
|
|
3369
|
+
const Codec = { Decode: this.decode, Encode: encode };
|
|
3370
|
+
return { ...schema, [TransformKind]: Codec };
|
|
3371
|
+
}
|
|
3372
|
+
Encode(encode) {
|
|
3373
|
+
return IsTransform(this.schema) ? this.EncodeTransform(encode, this.schema) : this.EncodeSchema(encode, this.schema);
|
|
3374
|
+
}
|
|
3375
|
+
};
|
|
3376
|
+
function Transform(schema) {
|
|
3377
|
+
return new TransformDecodeBuilder(schema);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/unsafe/unsafe.mjs
|
|
3381
|
+
function Unsafe(options = {}) {
|
|
3382
|
+
return CreateType({ [Kind]: options[Kind] ?? "Unsafe" }, options);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/void/void.mjs
|
|
3386
|
+
function Void(options) {
|
|
3387
|
+
return CreateType({ [Kind]: "Void", type: "void" }, options);
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/type/type.mjs
|
|
3391
|
+
var type_exports2 = {};
|
|
3392
|
+
__export(type_exports2, {
|
|
3393
|
+
Any: () => Any,
|
|
3394
|
+
Argument: () => Argument,
|
|
3395
|
+
Array: () => Array2,
|
|
3396
|
+
AsyncIterator: () => AsyncIterator,
|
|
3397
|
+
Awaited: () => Awaited,
|
|
3398
|
+
BigInt: () => BigInt,
|
|
3399
|
+
Boolean: () => Boolean,
|
|
3400
|
+
Capitalize: () => Capitalize,
|
|
3401
|
+
Composite: () => Composite,
|
|
3402
|
+
Const: () => Const,
|
|
3403
|
+
Constructor: () => Constructor,
|
|
3404
|
+
ConstructorParameters: () => ConstructorParameters,
|
|
3405
|
+
Date: () => Date2,
|
|
3406
|
+
Enum: () => Enum,
|
|
3407
|
+
Exclude: () => Exclude,
|
|
3408
|
+
Extends: () => Extends,
|
|
3409
|
+
Extract: () => Extract,
|
|
3410
|
+
Function: () => Function,
|
|
3411
|
+
Index: () => Index,
|
|
3412
|
+
InstanceType: () => InstanceType,
|
|
3413
|
+
Instantiate: () => Instantiate,
|
|
3414
|
+
Integer: () => Integer,
|
|
3415
|
+
Intersect: () => Intersect,
|
|
3416
|
+
Iterator: () => Iterator,
|
|
3417
|
+
KeyOf: () => KeyOf,
|
|
3418
|
+
Literal: () => Literal,
|
|
3419
|
+
Lowercase: () => Lowercase,
|
|
3420
|
+
Mapped: () => Mapped,
|
|
3421
|
+
Module: () => Module,
|
|
3422
|
+
Never: () => Never,
|
|
3423
|
+
Not: () => Not,
|
|
3424
|
+
Null: () => Null,
|
|
3425
|
+
Number: () => Number2,
|
|
3426
|
+
Object: () => Object2,
|
|
3427
|
+
Omit: () => Omit,
|
|
3428
|
+
Optional: () => Optional,
|
|
3429
|
+
Parameters: () => Parameters,
|
|
3430
|
+
Partial: () => Partial,
|
|
3431
|
+
Pick: () => Pick,
|
|
3432
|
+
Promise: () => Promise2,
|
|
3433
|
+
Readonly: () => Readonly,
|
|
3434
|
+
ReadonlyOptional: () => ReadonlyOptional,
|
|
3435
|
+
Record: () => Record,
|
|
3436
|
+
Recursive: () => Recursive,
|
|
3437
|
+
Ref: () => Ref,
|
|
3438
|
+
RegExp: () => RegExp2,
|
|
3439
|
+
Required: () => Required,
|
|
3440
|
+
Rest: () => Rest,
|
|
3441
|
+
ReturnType: () => ReturnType,
|
|
3442
|
+
String: () => String2,
|
|
3443
|
+
Symbol: () => Symbol2,
|
|
3444
|
+
TemplateLiteral: () => TemplateLiteral,
|
|
3445
|
+
Transform: () => Transform,
|
|
3446
|
+
Tuple: () => Tuple,
|
|
3447
|
+
Uint8Array: () => Uint8Array2,
|
|
3448
|
+
Uncapitalize: () => Uncapitalize,
|
|
3449
|
+
Undefined: () => Undefined,
|
|
3450
|
+
Union: () => Union,
|
|
3451
|
+
Unknown: () => Unknown,
|
|
3452
|
+
Unsafe: () => Unsafe,
|
|
3453
|
+
Uppercase: () => Uppercase,
|
|
3454
|
+
Void: () => Void
|
|
3455
|
+
});
|
|
3456
|
+
|
|
3457
|
+
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/type/index.mjs
|
|
3458
|
+
var Type = type_exports2;
|
|
3459
|
+
|
|
3460
|
+
// src/jobs/send-bulk-email-item.ts
|
|
3461
|
+
var SendBulkEmailItemInput = Type.Object({
|
|
3462
|
+
notificationId: Type.Number(),
|
|
3463
|
+
to: Type.Array(Type.String()),
|
|
3464
|
+
from: Type.String(),
|
|
3465
|
+
replyTo: Type.Optional(Type.String()),
|
|
3466
|
+
subject: Type.String(),
|
|
3467
|
+
text: Type.Optional(Type.String()),
|
|
3468
|
+
html: Type.Optional(Type.String())
|
|
3469
|
+
});
|
|
3470
|
+
var sendBulkEmailItemJob = job("notification.send-bulk-email-item").input(SendBulkEmailItemInput).options({
|
|
3471
|
+
retryLimit: 3,
|
|
3472
|
+
retryDelay: 5e3,
|
|
3473
|
+
batchSize: 50
|
|
3474
|
+
}).handler(async (input) => {
|
|
3475
|
+
const { notificationId, ...emailParams } = input;
|
|
3476
|
+
const result = await awsSesProvider.send(emailParams);
|
|
3477
|
+
if (result.success) {
|
|
3478
|
+
await markNotificationSent(notificationId, result.messageId);
|
|
3479
|
+
} else {
|
|
3480
|
+
await markNotificationFailed(notificationId, result.error || "Unknown error");
|
|
3481
|
+
throw new Error(result.error || "Failed to send email");
|
|
3482
|
+
}
|
|
3483
|
+
});
|
|
3484
|
+
|
|
3485
|
+
// src/channels/email/index.ts
|
|
3486
|
+
import { logger as logger2 } from "@spfn/core/logger";
|
|
3487
|
+
var log2 = logger2.child("@spfn/notification:email");
|
|
3488
|
+
var providers = {
|
|
3489
|
+
"aws-ses": awsSesProvider
|
|
3490
|
+
};
|
|
3491
|
+
function registerEmailProvider(provider) {
|
|
3492
|
+
providers[provider.name] = provider;
|
|
3493
|
+
}
|
|
3494
|
+
function getProvider() {
|
|
3495
|
+
const providerName = env.SPFN_NOTIFICATION_EMAIL_PROVIDER || "aws-ses";
|
|
3496
|
+
const provider = providers[providerName];
|
|
3497
|
+
if (!provider) {
|
|
3498
|
+
throw new Error(`Email provider not found: ${providerName}`);
|
|
3499
|
+
}
|
|
3500
|
+
return provider;
|
|
3501
|
+
}
|
|
3502
|
+
async function sendEmail(params) {
|
|
3503
|
+
const recipients = Array.isArray(params.to) ? params.to : [params.to];
|
|
3504
|
+
let subject = params.subject;
|
|
3505
|
+
let text3 = params.text;
|
|
3506
|
+
let html = params.html;
|
|
3507
|
+
if (params.template) {
|
|
3508
|
+
if (!hasTemplate(params.template)) {
|
|
3509
|
+
log2.warn(`Template not found: ${params.template}`);
|
|
3510
|
+
return {
|
|
3511
|
+
success: false,
|
|
3512
|
+
error: `Template not found: ${params.template}`
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
const rendered = renderTemplate(params.template, params.data || {}, "email");
|
|
3516
|
+
if (rendered.email) {
|
|
3517
|
+
subject = rendered.email.subject;
|
|
3518
|
+
text3 = rendered.email.text;
|
|
3519
|
+
html = rendered.email.html;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
if (!subject) {
|
|
3523
|
+
log2.warn("Email subject is required", { to: recipients });
|
|
3524
|
+
return {
|
|
3525
|
+
success: false,
|
|
3526
|
+
error: "Email subject is required"
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
if (!text3 && !html) {
|
|
3530
|
+
log2.warn("Email content (text or html) is required", { to: recipients, subject });
|
|
3531
|
+
return {
|
|
3532
|
+
success: false,
|
|
3533
|
+
error: "Email content (text or html) is required"
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
const internalParams = {
|
|
3537
|
+
to: recipients,
|
|
3538
|
+
from: params.from || getEmailFrom(),
|
|
3539
|
+
replyTo: params.replyTo || getEmailReplyTo(),
|
|
3540
|
+
subject,
|
|
3541
|
+
text: text3,
|
|
3542
|
+
html
|
|
3543
|
+
};
|
|
3544
|
+
const provider = getProvider();
|
|
3545
|
+
let historyId;
|
|
3546
|
+
if (isHistoryEnabled()) {
|
|
3547
|
+
try {
|
|
3548
|
+
const record = await createNotificationRecord({
|
|
3549
|
+
channel: "email",
|
|
3550
|
+
recipient: recipients.join(","),
|
|
3551
|
+
templateName: params.template,
|
|
3552
|
+
templateData: params.data,
|
|
3553
|
+
subject,
|
|
3554
|
+
content: text3,
|
|
3555
|
+
providerName: provider.name
|
|
3556
|
+
});
|
|
3557
|
+
historyId = record.id;
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
log2.warn("Failed to create notification history record", error);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
const shouldTrack = params.tracking ?? isTrackingEnabled();
|
|
3563
|
+
const trackingBaseUrl = getTrackingBaseUrl();
|
|
3564
|
+
if (shouldTrack && historyId && internalParams.html && trackingBaseUrl) {
|
|
3565
|
+
try {
|
|
3566
|
+
const { html: trackedHtml } = processTrackingHtml(internalParams.html, {
|
|
3567
|
+
notificationId: historyId,
|
|
3568
|
+
baseUrl: trackingBaseUrl
|
|
3569
|
+
});
|
|
3570
|
+
internalParams.html = trackedHtml;
|
|
3571
|
+
} catch (error) {
|
|
3572
|
+
log2.warn("Failed to apply tracking to email HTML", error);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
const result = await provider.send(internalParams);
|
|
3576
|
+
if (result.success) {
|
|
3577
|
+
log2.info("Email sent", { to: recipients, subject, messageId: result.messageId });
|
|
3578
|
+
} else {
|
|
3579
|
+
log2.error("Email send failed", { to: recipients, subject, error: result.error });
|
|
3580
|
+
}
|
|
3581
|
+
if (historyId && isHistoryEnabled()) {
|
|
3582
|
+
try {
|
|
3583
|
+
if (result.success) {
|
|
3584
|
+
await markNotificationSent(historyId, result.messageId);
|
|
3585
|
+
} else {
|
|
3586
|
+
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
3587
|
+
}
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
log2.warn("Failed to update notification history record", error);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
return result;
|
|
3593
|
+
}
|
|
3594
|
+
function prepareEmailItems(items) {
|
|
3595
|
+
const prepared = [];
|
|
3596
|
+
const earlyFailures = [];
|
|
3597
|
+
for (let i = 0; i < items.length; i++) {
|
|
3598
|
+
const item = items[i];
|
|
3599
|
+
const recipients = Array.isArray(item.to) ? item.to : [item.to];
|
|
3600
|
+
let subject = item.subject;
|
|
3601
|
+
let text3 = item.text;
|
|
3602
|
+
let html = item.html;
|
|
3603
|
+
if (item.template) {
|
|
3604
|
+
if (!hasTemplate(item.template)) {
|
|
3605
|
+
earlyFailures.push({ index: i, result: { success: false, error: `Template not found: ${item.template}` } });
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
const rendered = renderTemplate(item.template, item.data || {}, "email");
|
|
3609
|
+
if (rendered.email) {
|
|
3610
|
+
subject = rendered.email.subject;
|
|
3611
|
+
text3 = rendered.email.text;
|
|
3612
|
+
html = rendered.email.html;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
if (!subject) {
|
|
3616
|
+
earlyFailures.push({ index: i, result: { success: false, error: "Email subject is required" } });
|
|
3617
|
+
continue;
|
|
3618
|
+
}
|
|
3619
|
+
if (!text3 && !html) {
|
|
3620
|
+
earlyFailures.push({ index: i, result: { success: false, error: "Email content (text or html) is required" } });
|
|
3621
|
+
continue;
|
|
3622
|
+
}
|
|
3623
|
+
prepared.push({
|
|
3624
|
+
index: i,
|
|
3625
|
+
params: {
|
|
3626
|
+
to: recipients,
|
|
3627
|
+
from: item.from || getEmailFrom(),
|
|
3628
|
+
replyTo: item.replyTo || getEmailReplyTo(),
|
|
3629
|
+
subject,
|
|
3630
|
+
text: text3,
|
|
3631
|
+
html
|
|
3632
|
+
},
|
|
3633
|
+
recipients,
|
|
3634
|
+
template: item.template,
|
|
3635
|
+
data: item.data,
|
|
3636
|
+
subject,
|
|
3637
|
+
text: text3,
|
|
3638
|
+
tracking: item.tracking
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
return { prepared, earlyFailures };
|
|
3642
|
+
}
|
|
3643
|
+
async function sendEmailBulk(items, options) {
|
|
3644
|
+
if (items.length === 0) {
|
|
3645
|
+
return { results: [], successCount: 0, failureCount: 0, batchId: "" };
|
|
3646
|
+
}
|
|
3647
|
+
const batchId = crypto.randomUUID();
|
|
3648
|
+
const provider = getProvider();
|
|
3649
|
+
const { prepared, earlyFailures } = prepareEmailItems(items);
|
|
3650
|
+
let historyRecords = [];
|
|
3651
|
+
if (isHistoryEnabled() && prepared.length > 0) {
|
|
3652
|
+
try {
|
|
3653
|
+
historyRecords = await createNotificationRecords(
|
|
3654
|
+
prepared.map((p) => ({
|
|
3655
|
+
channel: "email",
|
|
3656
|
+
recipient: p.recipients.join(","),
|
|
3657
|
+
templateName: p.template,
|
|
3658
|
+
templateData: p.data,
|
|
3659
|
+
subject: p.subject,
|
|
3660
|
+
content: p.text,
|
|
3661
|
+
providerName: provider.name,
|
|
3662
|
+
batchId
|
|
3663
|
+
}))
|
|
3664
|
+
);
|
|
3665
|
+
} catch (error) {
|
|
3666
|
+
log2.warn("Failed to batch create notification history records", error);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
const shouldTrackGlobal = isTrackingEnabled();
|
|
3670
|
+
const trackingBaseUrl = getTrackingBaseUrl();
|
|
3671
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
3672
|
+
const p = prepared[i];
|
|
3673
|
+
const historyId = historyRecords[i]?.id;
|
|
3674
|
+
const shouldTrack = p.tracking ?? shouldTrackGlobal;
|
|
3675
|
+
if (shouldTrack && historyId && p.params.html && trackingBaseUrl) {
|
|
3676
|
+
try {
|
|
3677
|
+
const { html } = processTrackingHtml(p.params.html, {
|
|
3678
|
+
notificationId: historyId,
|
|
3679
|
+
baseUrl: trackingBaseUrl
|
|
3680
|
+
});
|
|
3681
|
+
p.params.html = html;
|
|
3682
|
+
} catch (error) {
|
|
3683
|
+
log2.warn("Failed to apply tracking to email HTML", error);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
if (options?.distributed) {
|
|
3688
|
+
const jobInputs = prepared.map((p, i) => ({
|
|
3689
|
+
notificationId: historyRecords[i]?.id ?? 0,
|
|
3690
|
+
to: p.params.to,
|
|
3691
|
+
from: p.params.from,
|
|
3692
|
+
replyTo: p.params.replyTo,
|
|
3693
|
+
subject: p.params.subject,
|
|
3694
|
+
text: p.params.text,
|
|
3695
|
+
html: p.params.html
|
|
3696
|
+
}));
|
|
3697
|
+
await sendBulkEmailItemJob.sendBatch(jobInputs);
|
|
3698
|
+
log2.info("Bulk email enqueued for distributed processing", {
|
|
3699
|
+
batchId,
|
|
3700
|
+
total: items.length,
|
|
3701
|
+
enqueued: prepared.length,
|
|
3702
|
+
earlyFailures: earlyFailures.length
|
|
3703
|
+
});
|
|
3704
|
+
const results2 = new Array(items.length);
|
|
3705
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
3706
|
+
results2[index3] = result;
|
|
3707
|
+
}
|
|
3708
|
+
for (const p of prepared) {
|
|
3709
|
+
results2[p.index] = { success: true, messageId: `pending:${batchId}` };
|
|
3710
|
+
}
|
|
3711
|
+
return {
|
|
3712
|
+
results: results2,
|
|
3713
|
+
successCount: prepared.length,
|
|
3714
|
+
failureCount: earlyFailures.length,
|
|
3715
|
+
batchId
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
const concurrency = options?.concurrency ?? 10;
|
|
3719
|
+
const sendResults = await runWithConcurrency(
|
|
3720
|
+
prepared,
|
|
3721
|
+
(p) => provider.send(p.params),
|
|
3722
|
+
concurrency
|
|
3723
|
+
);
|
|
3724
|
+
const results = new Array(items.length);
|
|
3725
|
+
let successCount = 0;
|
|
3726
|
+
let failureCount = earlyFailures.length;
|
|
3727
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
3728
|
+
results[index3] = result;
|
|
3729
|
+
}
|
|
3730
|
+
const historyUpdates = [];
|
|
3731
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
3732
|
+
const { index: index3, recipients, subject } = prepared[i];
|
|
3733
|
+
const result = sendResults[i];
|
|
3734
|
+
results[index3] = result;
|
|
3735
|
+
if (result.success) {
|
|
3736
|
+
successCount++;
|
|
3737
|
+
log2.info("Email sent", { to: recipients, subject, messageId: result.messageId });
|
|
3738
|
+
} else {
|
|
3739
|
+
failureCount++;
|
|
3740
|
+
log2.error("Email send failed", { to: recipients, subject, error: result.error });
|
|
3741
|
+
}
|
|
3742
|
+
const historyId = historyRecords[i]?.id;
|
|
3743
|
+
if (historyId && isHistoryEnabled()) {
|
|
3744
|
+
const promise = result.success ? markNotificationSent(historyId, result.messageId) : markNotificationFailed(historyId, result.error || "Unknown error");
|
|
3745
|
+
historyUpdates.push(
|
|
3746
|
+
promise.catch((err) => log2.warn("Failed to update notification history", err))
|
|
3747
|
+
);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
await Promise.all(historyUpdates);
|
|
3751
|
+
return { results, successCount, failureCount, batchId };
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
// src/channels/sms/providers/aws-sns.ts
|
|
3755
|
+
import { logger as logger3 } from "@spfn/core/logger";
|
|
3756
|
+
var log3 = logger3.child("@spfn/notification:sns");
|
|
3757
|
+
var snsClient = null;
|
|
3758
|
+
async function getSNSClient() {
|
|
3759
|
+
if (snsClient) {
|
|
3760
|
+
return snsClient;
|
|
3761
|
+
}
|
|
3762
|
+
try {
|
|
3763
|
+
const { SNSClient } = await import("@aws-sdk/client-sns");
|
|
3764
|
+
snsClient = new SNSClient({
|
|
3765
|
+
region: env.AWS_REGION,
|
|
3766
|
+
credentials: env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY ? {
|
|
3767
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
3768
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY
|
|
3769
|
+
} : void 0
|
|
3770
|
+
});
|
|
3771
|
+
log3.debug("SNS client created", { region: env.AWS_REGION });
|
|
3772
|
+
return snsClient;
|
|
3773
|
+
} catch {
|
|
3774
|
+
throw new Error(
|
|
3775
|
+
"@aws-sdk/client-sns is not installed. Please install it: pnpm add @aws-sdk/client-sns"
|
|
3776
|
+
);
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
var awsSnsProvider = {
|
|
3780
|
+
name: "aws-sns",
|
|
3781
|
+
async send(params) {
|
|
3782
|
+
try {
|
|
3783
|
+
const client = await getSNSClient();
|
|
3784
|
+
const { PublishCommand } = await import("@aws-sdk/client-sns");
|
|
3785
|
+
const command = new PublishCommand({
|
|
3786
|
+
PhoneNumber: params.to,
|
|
3787
|
+
Message: params.message,
|
|
3788
|
+
MessageAttributes: {
|
|
3789
|
+
"AWS.SNS.SMS.SMSType": {
|
|
3790
|
+
DataType: "String",
|
|
3791
|
+
StringValue: "Transactional"
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
const response = await client.send(command);
|
|
3796
|
+
return {
|
|
3797
|
+
success: true,
|
|
3798
|
+
messageId: response.MessageId
|
|
3799
|
+
};
|
|
3800
|
+
} catch (error) {
|
|
3801
|
+
const err = error;
|
|
3802
|
+
log3.error("SNS send failed", err, { to: params.to });
|
|
3803
|
+
return {
|
|
3804
|
+
success: false,
|
|
3805
|
+
error: err.message
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
|
|
3811
|
+
// src/jobs/send-bulk-sms-item.ts
|
|
3812
|
+
import { job as job2 } from "@spfn/core/job";
|
|
3813
|
+
var SendBulkSmsItemInput = Type.Object({
|
|
3814
|
+
notificationId: Type.Number(),
|
|
3815
|
+
to: Type.String(),
|
|
3816
|
+
message: Type.String()
|
|
3817
|
+
});
|
|
3818
|
+
var sendBulkSmsItemJob = job2("notification.send-bulk-sms-item").input(SendBulkSmsItemInput).options({
|
|
3819
|
+
retryLimit: 3,
|
|
3820
|
+
retryDelay: 5e3,
|
|
3821
|
+
batchSize: 50
|
|
3822
|
+
}).handler(async (input) => {
|
|
3823
|
+
const { notificationId, ...smsParams } = input;
|
|
3824
|
+
const result = await awsSnsProvider.send(smsParams);
|
|
3825
|
+
if (result.success) {
|
|
3826
|
+
await markNotificationSent(notificationId, result.messageId);
|
|
3827
|
+
} else {
|
|
3828
|
+
await markNotificationFailed(notificationId, result.error || "Unknown error");
|
|
3829
|
+
throw new Error(result.error || "Failed to send SMS");
|
|
3830
|
+
}
|
|
3831
|
+
});
|
|
3832
|
+
|
|
3833
|
+
// src/channels/sms/utils.ts
|
|
3834
|
+
function normalizePhoneNumber(phone, defaultCountryCode) {
|
|
3835
|
+
let normalized = phone.replace(/[^\d+]/g, "");
|
|
3836
|
+
if (!normalized.startsWith("+")) {
|
|
3837
|
+
const countryCode = defaultCountryCode ?? getSmsDefaultCountryCode();
|
|
3838
|
+
if (normalized.startsWith("0")) {
|
|
3839
|
+
normalized = normalized.slice(1);
|
|
3840
|
+
}
|
|
3841
|
+
normalized = countryCode + normalized;
|
|
3842
|
+
}
|
|
3843
|
+
return normalized;
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
// src/channels/sms/index.ts
|
|
3847
|
+
import { logger as logger4 } from "@spfn/core/logger";
|
|
3848
|
+
var log4 = logger4.child("@spfn/notification:sms");
|
|
3849
|
+
var providers2 = {
|
|
3850
|
+
"aws-sns": awsSnsProvider
|
|
3851
|
+
};
|
|
3852
|
+
function registerSMSProvider(provider) {
|
|
3853
|
+
providers2[provider.name] = provider;
|
|
3854
|
+
}
|
|
3855
|
+
function getProvider2() {
|
|
3856
|
+
const providerName = env.SPFN_NOTIFICATION_SMS_PROVIDER || "aws-sns";
|
|
3857
|
+
const provider = providers2[providerName];
|
|
3858
|
+
if (!provider) {
|
|
3859
|
+
throw new Error(`SMS provider not found: ${providerName}`);
|
|
3860
|
+
}
|
|
3861
|
+
return provider;
|
|
3862
|
+
}
|
|
3863
|
+
async function sendSMS(params) {
|
|
3864
|
+
const recipients = Array.isArray(params.to) ? params.to : [params.to];
|
|
3865
|
+
let message = params.message;
|
|
3866
|
+
if (params.template) {
|
|
3867
|
+
if (!hasTemplate(params.template)) {
|
|
3868
|
+
log4.warn(`Template not found: ${params.template}`);
|
|
3869
|
+
return {
|
|
3870
|
+
success: false,
|
|
3871
|
+
error: `Template not found: ${params.template}`
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
const rendered = renderTemplate(params.template, params.data || {}, "sms");
|
|
3875
|
+
if (rendered.sms) {
|
|
3876
|
+
message = rendered.sms.message;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
if (!message) {
|
|
3880
|
+
log4.warn("SMS message is required", { to: recipients });
|
|
3881
|
+
return {
|
|
3882
|
+
success: false,
|
|
3883
|
+
error: "SMS message is required"
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
const provider = getProvider2();
|
|
3887
|
+
const results = [];
|
|
3888
|
+
for (const recipient of recipients) {
|
|
3889
|
+
const normalizedPhone = normalizePhoneNumber(recipient);
|
|
3890
|
+
const internalParams = {
|
|
3891
|
+
to: normalizedPhone,
|
|
3892
|
+
message
|
|
3893
|
+
};
|
|
3894
|
+
let historyId;
|
|
3895
|
+
if (isHistoryEnabled()) {
|
|
3896
|
+
try {
|
|
3897
|
+
const record = await createNotificationRecord({
|
|
3898
|
+
channel: "sms",
|
|
3899
|
+
recipient: normalizedPhone,
|
|
3900
|
+
templateName: params.template,
|
|
3901
|
+
templateData: params.data,
|
|
3902
|
+
content: message,
|
|
3903
|
+
providerName: provider.name
|
|
3904
|
+
});
|
|
3905
|
+
historyId = record.id;
|
|
3906
|
+
} catch (error) {
|
|
3907
|
+
log4.warn("Failed to create notification history record", error);
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
const result = await provider.send(internalParams);
|
|
3911
|
+
if (result.success) {
|
|
3912
|
+
log4.info("SMS sent", { to: normalizedPhone, messageId: result.messageId });
|
|
3913
|
+
} else {
|
|
3914
|
+
log4.error("SMS send failed", { to: normalizedPhone, error: result.error });
|
|
3915
|
+
}
|
|
3916
|
+
if (historyId && isHistoryEnabled()) {
|
|
3917
|
+
try {
|
|
3918
|
+
if (result.success) {
|
|
3919
|
+
await markNotificationSent(historyId, result.messageId);
|
|
3920
|
+
} else {
|
|
3921
|
+
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
3922
|
+
}
|
|
3923
|
+
} catch (error) {
|
|
3924
|
+
log4.warn("Failed to update notification history record", error);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
results.push(result);
|
|
3928
|
+
}
|
|
3929
|
+
const allSuccess = results.every((r) => r.success);
|
|
3930
|
+
const messageIds = results.filter((r) => r.messageId).map((r) => r.messageId).join(",");
|
|
3931
|
+
const errors = results.filter((r) => r.error).map((r) => r.error).join("; ");
|
|
3932
|
+
return {
|
|
3933
|
+
success: allSuccess,
|
|
3934
|
+
messageId: messageIds || void 0,
|
|
3935
|
+
error: errors || void 0
|
|
3936
|
+
};
|
|
3937
|
+
}
|
|
3938
|
+
async function sendSMSBulk(items, options) {
|
|
3939
|
+
if (items.length === 0) {
|
|
3940
|
+
return { results: [], successCount: 0, failureCount: 0, batchId: "" };
|
|
3941
|
+
}
|
|
3942
|
+
const batchId = crypto.randomUUID();
|
|
3943
|
+
const provider = getProvider2();
|
|
3944
|
+
const prepared = [];
|
|
3945
|
+
const earlyFailures = [];
|
|
3946
|
+
for (let i = 0; i < items.length; i++) {
|
|
3947
|
+
const item = items[i];
|
|
3948
|
+
const recipients = Array.isArray(item.to) ? item.to : [item.to];
|
|
3949
|
+
let message = item.message;
|
|
3950
|
+
if (item.template) {
|
|
3951
|
+
if (!hasTemplate(item.template)) {
|
|
3952
|
+
earlyFailures.push({ index: i, result: { success: false, error: `Template not found: ${item.template}` } });
|
|
3953
|
+
continue;
|
|
3954
|
+
}
|
|
3955
|
+
const rendered = renderTemplate(item.template, item.data || {}, "sms");
|
|
3956
|
+
if (rendered.sms) {
|
|
3957
|
+
message = rendered.sms.message;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
if (!message) {
|
|
3961
|
+
earlyFailures.push({ index: i, result: { success: false, error: "SMS message is required" } });
|
|
3962
|
+
continue;
|
|
3963
|
+
}
|
|
3964
|
+
for (const recipient of recipients) {
|
|
3965
|
+
prepared.push({
|
|
3966
|
+
index: i,
|
|
3967
|
+
phone: normalizePhoneNumber(recipient),
|
|
3968
|
+
message,
|
|
3969
|
+
template: item.template,
|
|
3970
|
+
data: item.data
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
let historyRecords = [];
|
|
3975
|
+
if (isHistoryEnabled() && prepared.length > 0) {
|
|
3976
|
+
try {
|
|
3977
|
+
historyRecords = await createNotificationRecords(
|
|
3978
|
+
prepared.map((p) => ({
|
|
3979
|
+
channel: "sms",
|
|
3980
|
+
recipient: p.phone,
|
|
3981
|
+
templateName: p.template,
|
|
3982
|
+
templateData: p.data,
|
|
3983
|
+
content: p.message,
|
|
3984
|
+
providerName: provider.name,
|
|
3985
|
+
batchId
|
|
3986
|
+
}))
|
|
3987
|
+
);
|
|
3988
|
+
} catch (error) {
|
|
3989
|
+
log4.warn("Failed to batch create notification history records", error);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
if (options?.distributed) {
|
|
3993
|
+
const jobInputs = prepared.map((p, i) => ({
|
|
3994
|
+
notificationId: historyRecords[i]?.id ?? 0,
|
|
3995
|
+
to: p.phone,
|
|
3996
|
+
message: p.message
|
|
3997
|
+
}));
|
|
3998
|
+
await sendBulkSmsItemJob.sendBatch(jobInputs);
|
|
3999
|
+
log4.info("Bulk SMS enqueued for distributed processing", {
|
|
4000
|
+
batchId,
|
|
4001
|
+
total: items.length,
|
|
4002
|
+
enqueued: prepared.length,
|
|
4003
|
+
earlyFailures: earlyFailures.length
|
|
4004
|
+
});
|
|
4005
|
+
const results2 = new Array(items.length);
|
|
4006
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
4007
|
+
results2[index3] = result;
|
|
4008
|
+
}
|
|
4009
|
+
const pendingMap = /* @__PURE__ */ new Map();
|
|
4010
|
+
for (const p of prepared) {
|
|
4011
|
+
pendingMap.set(p.index, (pendingMap.get(p.index) ?? 0) + 1);
|
|
4012
|
+
}
|
|
4013
|
+
for (const [index3] of pendingMap) {
|
|
4014
|
+
if (!results2[index3]) {
|
|
4015
|
+
results2[index3] = { success: true, messageId: `pending:${batchId}` };
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
return {
|
|
4019
|
+
results: results2,
|
|
4020
|
+
successCount: pendingMap.size,
|
|
4021
|
+
failureCount: earlyFailures.length,
|
|
4022
|
+
batchId
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
4025
|
+
const concurrency = options?.concurrency ?? 10;
|
|
4026
|
+
const sendResults = await runWithConcurrency(
|
|
4027
|
+
prepared,
|
|
4028
|
+
(p) => provider.send({ to: p.phone, message: p.message }),
|
|
4029
|
+
concurrency
|
|
4030
|
+
);
|
|
4031
|
+
const resultsMap = /* @__PURE__ */ new Map();
|
|
4032
|
+
const historyUpdates = [];
|
|
4033
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
4034
|
+
const { index: index3, phone } = prepared[i];
|
|
4035
|
+
const result = sendResults[i];
|
|
4036
|
+
if (!resultsMap.has(index3)) {
|
|
4037
|
+
resultsMap.set(index3, []);
|
|
4038
|
+
}
|
|
4039
|
+
resultsMap.get(index3).push(result);
|
|
4040
|
+
if (result.success) {
|
|
4041
|
+
log4.info("SMS sent", { to: phone, messageId: result.messageId });
|
|
4042
|
+
} else {
|
|
4043
|
+
log4.error("SMS send failed", { to: phone, error: result.error });
|
|
4044
|
+
}
|
|
4045
|
+
const historyId = historyRecords[i]?.id;
|
|
4046
|
+
if (historyId && isHistoryEnabled()) {
|
|
4047
|
+
const promise = result.success ? markNotificationSent(historyId, result.messageId) : markNotificationFailed(historyId, result.error || "Unknown error");
|
|
4048
|
+
historyUpdates.push(
|
|
4049
|
+
promise.catch((err) => log4.warn("Failed to update notification history", err))
|
|
4050
|
+
);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
await Promise.all(historyUpdates);
|
|
4054
|
+
const results = new Array(items.length);
|
|
4055
|
+
let successCount = 0;
|
|
4056
|
+
let failureCount = earlyFailures.length;
|
|
4057
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
4058
|
+
results[index3] = result;
|
|
4059
|
+
}
|
|
4060
|
+
for (const [index3, itemResults] of resultsMap) {
|
|
4061
|
+
const allSuccess = itemResults.every((r) => r.success);
|
|
4062
|
+
const messageIds = itemResults.filter((r) => r.messageId).map((r) => r.messageId).join(",");
|
|
4063
|
+
const errors = itemResults.filter((r) => r.error).map((r) => r.error).join("; ");
|
|
4064
|
+
results[index3] = {
|
|
4065
|
+
success: allSuccess,
|
|
4066
|
+
messageId: messageIds || void 0,
|
|
4067
|
+
error: errors || void 0
|
|
4068
|
+
};
|
|
4069
|
+
if (allSuccess) {
|
|
4070
|
+
successCount++;
|
|
4071
|
+
} else {
|
|
4072
|
+
failureCount++;
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
return { results, successCount, failureCount, batchId };
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
// src/channels/slack/providers/webhook.ts
|
|
4079
|
+
import { logger as logger5 } from "@spfn/core/logger";
|
|
4080
|
+
var log5 = logger5.child("@spfn/notification:slack-webhook");
|
|
4081
|
+
var webhookProvider = {
|
|
4082
|
+
name: "webhook",
|
|
4083
|
+
async send(params) {
|
|
4084
|
+
try {
|
|
4085
|
+
const res = await fetch(params.webhookUrl, {
|
|
4086
|
+
method: "POST",
|
|
4087
|
+
headers: { "Content-Type": "application/json" },
|
|
4088
|
+
body: JSON.stringify({
|
|
4089
|
+
text: params.text,
|
|
4090
|
+
blocks: params.blocks
|
|
4091
|
+
})
|
|
4092
|
+
});
|
|
4093
|
+
return {
|
|
4094
|
+
success: res.ok,
|
|
4095
|
+
error: res.ok ? void 0 : await res.text()
|
|
4096
|
+
};
|
|
4097
|
+
} catch (error) {
|
|
4098
|
+
const err = error;
|
|
4099
|
+
log5.error("Webhook request failed", err);
|
|
4100
|
+
return {
|
|
4101
|
+
success: false,
|
|
4102
|
+
error: err.message
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
};
|
|
4107
|
+
|
|
4108
|
+
// src/jobs/send-bulk-slack-item.ts
|
|
4109
|
+
import { job as job3 } from "@spfn/core/job";
|
|
4110
|
+
var SendBulkSlackItemInput = Type.Object({
|
|
4111
|
+
notificationId: Type.Number(),
|
|
4112
|
+
webhookUrl: Type.String(),
|
|
4113
|
+
text: Type.Optional(Type.String()),
|
|
4114
|
+
blocks: Type.Optional(Type.Array(Type.Unknown()))
|
|
4115
|
+
});
|
|
4116
|
+
var sendBulkSlackItemJob = job3("notification.send-bulk-slack-item").input(SendBulkSlackItemInput).options({
|
|
4117
|
+
retryLimit: 3,
|
|
4118
|
+
retryDelay: 5e3,
|
|
4119
|
+
batchSize: 50
|
|
4120
|
+
}).handler(async (input) => {
|
|
4121
|
+
const { notificationId, ...slackParams } = input;
|
|
4122
|
+
const result = await webhookProvider.send(slackParams);
|
|
4123
|
+
if (result.success) {
|
|
4124
|
+
await markNotificationSent(notificationId, result.messageId);
|
|
4125
|
+
} else {
|
|
4126
|
+
await markNotificationFailed(notificationId, result.error || "Unknown error");
|
|
4127
|
+
throw new Error(result.error || "Failed to send Slack message");
|
|
4128
|
+
}
|
|
4129
|
+
});
|
|
3745
4130
|
|
|
3746
|
-
//
|
|
3747
|
-
|
|
3748
|
-
|
|
4131
|
+
// src/channels/slack/index.ts
|
|
4132
|
+
import { logger as logger6 } from "@spfn/core/logger";
|
|
4133
|
+
var log6 = logger6.child("@spfn/notification:slack");
|
|
4134
|
+
var providers3 = {
|
|
4135
|
+
"webhook": webhookProvider
|
|
4136
|
+
};
|
|
4137
|
+
function registerSlackProvider(provider) {
|
|
4138
|
+
providers3[provider.name] = provider;
|
|
3749
4139
|
}
|
|
3750
|
-
function
|
|
3751
|
-
return
|
|
4140
|
+
function getProvider3() {
|
|
4141
|
+
return providers3["webhook"];
|
|
3752
4142
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
function ReturnType(schema, options) {
|
|
3756
|
-
return IsFunction2(schema) ? CreateType(schema.returns, options) : Never(options);
|
|
4143
|
+
function resolveWebhookUrl(params) {
|
|
4144
|
+
return params.webhookUrl || env.SPFN_NOTIFICATION_SLACK_WEBHOOK_URL;
|
|
3757
4145
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
4146
|
+
async function sendSlack(params) {
|
|
4147
|
+
const webhookUrl = resolveWebhookUrl(params);
|
|
4148
|
+
if (!webhookUrl) {
|
|
4149
|
+
log6.warn("Slack webhook URL is required");
|
|
4150
|
+
return {
|
|
4151
|
+
success: false,
|
|
4152
|
+
error: "Slack webhook URL is required. Set SPFN_NOTIFICATION_SLACK_WEBHOOK_URL or pass webhookUrl."
|
|
4153
|
+
};
|
|
3763
4154
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
4155
|
+
let text3 = params.text;
|
|
4156
|
+
let blocks = params.blocks;
|
|
4157
|
+
if (params.template) {
|
|
4158
|
+
if (!hasTemplate(params.template)) {
|
|
4159
|
+
log6.warn(`Template not found: ${params.template}`);
|
|
4160
|
+
return {
|
|
4161
|
+
success: false,
|
|
4162
|
+
error: `Template not found: ${params.template}`
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
const rendered = renderTemplate(params.template, params.data || {}, "slack");
|
|
4166
|
+
if (rendered.slack) {
|
|
4167
|
+
text3 = rendered.slack.text;
|
|
4168
|
+
blocks = rendered.slack.blocks;
|
|
4169
|
+
}
|
|
3766
4170
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
4171
|
+
if (!text3 && !blocks) {
|
|
4172
|
+
log6.warn("Slack message requires text or blocks");
|
|
4173
|
+
return {
|
|
4174
|
+
success: false,
|
|
4175
|
+
error: "Slack message requires text or blocks"
|
|
4176
|
+
};
|
|
3772
4177
|
}
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
4178
|
+
const internalParams = {
|
|
4179
|
+
webhookUrl,
|
|
4180
|
+
text: text3,
|
|
4181
|
+
blocks
|
|
4182
|
+
};
|
|
4183
|
+
const provider = getProvider3();
|
|
4184
|
+
let historyId;
|
|
4185
|
+
if (isHistoryEnabled()) {
|
|
4186
|
+
try {
|
|
4187
|
+
const record = await createNotificationRecord({
|
|
4188
|
+
channel: "slack",
|
|
4189
|
+
recipient: webhookUrl,
|
|
4190
|
+
templateName: params.template,
|
|
4191
|
+
templateData: params.data,
|
|
4192
|
+
content: text3,
|
|
4193
|
+
providerName: provider.name
|
|
4194
|
+
});
|
|
4195
|
+
historyId = record.id;
|
|
4196
|
+
} catch (error) {
|
|
4197
|
+
log6.warn("Failed to create notification history record", error);
|
|
4198
|
+
}
|
|
3778
4199
|
}
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
4200
|
+
const result = await provider.send(internalParams);
|
|
4201
|
+
if (result.success) {
|
|
4202
|
+
log6.info("Slack message sent");
|
|
4203
|
+
} else {
|
|
4204
|
+
log6.error("Slack send failed", { error: result.error });
|
|
3782
4205
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
4206
|
+
if (historyId && isHistoryEnabled()) {
|
|
4207
|
+
try {
|
|
4208
|
+
if (result.success) {
|
|
4209
|
+
await markNotificationSent(historyId, result.messageId);
|
|
4210
|
+
} else {
|
|
4211
|
+
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
4212
|
+
}
|
|
4213
|
+
} catch (error) {
|
|
4214
|
+
log6.warn("Failed to update notification history record", error);
|
|
4215
|
+
}
|
|
3785
4216
|
}
|
|
3786
|
-
|
|
3787
|
-
function Transform(schema) {
|
|
3788
|
-
return new TransformDecodeBuilder(schema);
|
|
3789
|
-
}
|
|
3790
|
-
|
|
3791
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/unsafe/unsafe.mjs
|
|
3792
|
-
function Unsafe(options = {}) {
|
|
3793
|
-
return CreateType({ [Kind]: options[Kind] ?? "Unsafe" }, options);
|
|
4217
|
+
return result;
|
|
3794
4218
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
4219
|
+
async function sendSlackBulk(items, options) {
|
|
4220
|
+
if (items.length === 0) {
|
|
4221
|
+
return { results: [], successCount: 0, failureCount: 0, batchId: "" };
|
|
4222
|
+
}
|
|
4223
|
+
const batchId = crypto.randomUUID();
|
|
4224
|
+
const provider = getProvider3();
|
|
4225
|
+
const prepared = [];
|
|
4226
|
+
const earlyFailures = [];
|
|
4227
|
+
for (let i = 0; i < items.length; i++) {
|
|
4228
|
+
const item = items[i];
|
|
4229
|
+
const webhookUrl = resolveWebhookUrl(item);
|
|
4230
|
+
if (!webhookUrl) {
|
|
4231
|
+
earlyFailures.push({ index: i, result: { success: false, error: "Slack webhook URL is required. Set SPFN_NOTIFICATION_SLACK_WEBHOOK_URL or pass webhookUrl." } });
|
|
4232
|
+
continue;
|
|
4233
|
+
}
|
|
4234
|
+
let text3 = item.text;
|
|
4235
|
+
let blocks = item.blocks;
|
|
4236
|
+
if (item.template) {
|
|
4237
|
+
if (!hasTemplate(item.template)) {
|
|
4238
|
+
earlyFailures.push({ index: i, result: { success: false, error: `Template not found: ${item.template}` } });
|
|
4239
|
+
continue;
|
|
4240
|
+
}
|
|
4241
|
+
const rendered = renderTemplate(item.template, item.data || {}, "slack");
|
|
4242
|
+
if (rendered.slack) {
|
|
4243
|
+
text3 = rendered.slack.text;
|
|
4244
|
+
blocks = rendered.slack.blocks;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
if (!text3 && !blocks) {
|
|
4248
|
+
earlyFailures.push({ index: i, result: { success: false, error: "Slack message requires text or blocks" } });
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
prepared.push({
|
|
4252
|
+
index: i,
|
|
4253
|
+
params: { webhookUrl, text: text3, blocks },
|
|
4254
|
+
webhookUrl,
|
|
4255
|
+
template: item.template,
|
|
4256
|
+
data: item.data,
|
|
4257
|
+
text: text3
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
let historyRecords = [];
|
|
4261
|
+
if (isHistoryEnabled() && prepared.length > 0) {
|
|
4262
|
+
try {
|
|
4263
|
+
historyRecords = await createNotificationRecords(
|
|
4264
|
+
prepared.map((p) => ({
|
|
4265
|
+
channel: "slack",
|
|
4266
|
+
recipient: p.webhookUrl,
|
|
4267
|
+
templateName: p.template,
|
|
4268
|
+
templateData: p.data,
|
|
4269
|
+
content: p.text,
|
|
4270
|
+
providerName: provider.name,
|
|
4271
|
+
batchId
|
|
4272
|
+
}))
|
|
4273
|
+
);
|
|
4274
|
+
} catch (error) {
|
|
4275
|
+
log6.warn("Failed to batch create notification history records", error);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
if (options?.distributed) {
|
|
4279
|
+
const jobInputs = prepared.map((p, i) => ({
|
|
4280
|
+
notificationId: historyRecords[i]?.id ?? 0,
|
|
4281
|
+
webhookUrl: p.webhookUrl,
|
|
4282
|
+
text: p.text,
|
|
4283
|
+
blocks: p.params.blocks
|
|
4284
|
+
}));
|
|
4285
|
+
await sendBulkSlackItemJob.sendBatch(jobInputs);
|
|
4286
|
+
log6.info("Bulk Slack enqueued for distributed processing", {
|
|
4287
|
+
batchId,
|
|
4288
|
+
total: items.length,
|
|
4289
|
+
enqueued: prepared.length,
|
|
4290
|
+
earlyFailures: earlyFailures.length
|
|
4291
|
+
});
|
|
4292
|
+
const results2 = new Array(items.length);
|
|
4293
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
4294
|
+
results2[index3] = result;
|
|
4295
|
+
}
|
|
4296
|
+
for (const p of prepared) {
|
|
4297
|
+
if (!results2[p.index]) {
|
|
4298
|
+
results2[p.index] = { success: true, messageId: `pending:${batchId}` };
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return {
|
|
4302
|
+
results: results2,
|
|
4303
|
+
successCount: prepared.length,
|
|
4304
|
+
failureCount: earlyFailures.length,
|
|
4305
|
+
batchId
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4308
|
+
const concurrency = options?.concurrency ?? 10;
|
|
4309
|
+
const sendResults = await runWithConcurrency(
|
|
4310
|
+
prepared,
|
|
4311
|
+
(p) => provider.send(p.params),
|
|
4312
|
+
concurrency
|
|
4313
|
+
);
|
|
4314
|
+
const results = new Array(items.length);
|
|
4315
|
+
let successCount = 0;
|
|
4316
|
+
let failureCount = earlyFailures.length;
|
|
4317
|
+
for (const { index: index3, result } of earlyFailures) {
|
|
4318
|
+
results[index3] = result;
|
|
4319
|
+
}
|
|
4320
|
+
const historyUpdates = [];
|
|
4321
|
+
for (let i = 0; i < prepared.length; i++) {
|
|
4322
|
+
const { index: index3 } = prepared[i];
|
|
4323
|
+
const result = sendResults[i];
|
|
4324
|
+
results[index3] = result;
|
|
4325
|
+
if (result.success) {
|
|
4326
|
+
successCount++;
|
|
4327
|
+
log6.info("Slack message sent");
|
|
4328
|
+
} else {
|
|
4329
|
+
failureCount++;
|
|
4330
|
+
log6.error("Slack send failed", { error: result.error });
|
|
4331
|
+
}
|
|
4332
|
+
const historyId = historyRecords[i]?.id;
|
|
4333
|
+
if (historyId && isHistoryEnabled()) {
|
|
4334
|
+
const promise = result.success ? markNotificationSent(historyId, result.messageId) : markNotificationFailed(historyId, result.error || "Unknown error");
|
|
4335
|
+
historyUpdates.push(
|
|
4336
|
+
promise.catch((err) => log6.warn("Failed to update notification history", err))
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
await Promise.all(historyUpdates);
|
|
4341
|
+
return { results, successCount, failureCount, batchId };
|
|
3799
4342
|
}
|
|
3800
4343
|
|
|
3801
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/type/type.mjs
|
|
3802
|
-
var type_exports2 = {};
|
|
3803
|
-
__export(type_exports2, {
|
|
3804
|
-
Any: () => Any,
|
|
3805
|
-
Argument: () => Argument,
|
|
3806
|
-
Array: () => Array2,
|
|
3807
|
-
AsyncIterator: () => AsyncIterator,
|
|
3808
|
-
Awaited: () => Awaited,
|
|
3809
|
-
BigInt: () => BigInt,
|
|
3810
|
-
Boolean: () => Boolean,
|
|
3811
|
-
Capitalize: () => Capitalize,
|
|
3812
|
-
Composite: () => Composite,
|
|
3813
|
-
Const: () => Const,
|
|
3814
|
-
Constructor: () => Constructor,
|
|
3815
|
-
ConstructorParameters: () => ConstructorParameters,
|
|
3816
|
-
Date: () => Date2,
|
|
3817
|
-
Enum: () => Enum,
|
|
3818
|
-
Exclude: () => Exclude,
|
|
3819
|
-
Extends: () => Extends,
|
|
3820
|
-
Extract: () => Extract,
|
|
3821
|
-
Function: () => Function,
|
|
3822
|
-
Index: () => Index,
|
|
3823
|
-
InstanceType: () => InstanceType,
|
|
3824
|
-
Instantiate: () => Instantiate,
|
|
3825
|
-
Integer: () => Integer,
|
|
3826
|
-
Intersect: () => Intersect,
|
|
3827
|
-
Iterator: () => Iterator,
|
|
3828
|
-
KeyOf: () => KeyOf,
|
|
3829
|
-
Literal: () => Literal,
|
|
3830
|
-
Lowercase: () => Lowercase,
|
|
3831
|
-
Mapped: () => Mapped,
|
|
3832
|
-
Module: () => Module,
|
|
3833
|
-
Never: () => Never,
|
|
3834
|
-
Not: () => Not,
|
|
3835
|
-
Null: () => Null,
|
|
3836
|
-
Number: () => Number2,
|
|
3837
|
-
Object: () => Object2,
|
|
3838
|
-
Omit: () => Omit,
|
|
3839
|
-
Optional: () => Optional,
|
|
3840
|
-
Parameters: () => Parameters,
|
|
3841
|
-
Partial: () => Partial,
|
|
3842
|
-
Pick: () => Pick,
|
|
3843
|
-
Promise: () => Promise2,
|
|
3844
|
-
Readonly: () => Readonly,
|
|
3845
|
-
ReadonlyOptional: () => ReadonlyOptional,
|
|
3846
|
-
Record: () => Record,
|
|
3847
|
-
Recursive: () => Recursive,
|
|
3848
|
-
Ref: () => Ref,
|
|
3849
|
-
RegExp: () => RegExp2,
|
|
3850
|
-
Required: () => Required,
|
|
3851
|
-
Rest: () => Rest,
|
|
3852
|
-
ReturnType: () => ReturnType,
|
|
3853
|
-
String: () => String2,
|
|
3854
|
-
Symbol: () => Symbol2,
|
|
3855
|
-
TemplateLiteral: () => TemplateLiteral,
|
|
3856
|
-
Transform: () => Transform,
|
|
3857
|
-
Tuple: () => Tuple,
|
|
3858
|
-
Uint8Array: () => Uint8Array2,
|
|
3859
|
-
Uncapitalize: () => Uncapitalize,
|
|
3860
|
-
Undefined: () => Undefined,
|
|
3861
|
-
Union: () => Union,
|
|
3862
|
-
Unknown: () => Unknown,
|
|
3863
|
-
Unsafe: () => Unsafe,
|
|
3864
|
-
Uppercase: () => Uppercase,
|
|
3865
|
-
Void: () => Void
|
|
3866
|
-
});
|
|
3867
|
-
|
|
3868
|
-
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/type/index.mjs
|
|
3869
|
-
var Type = type_exports2;
|
|
3870
|
-
|
|
3871
4344
|
// src/jobs/send-scheduled-email.ts
|
|
4345
|
+
import { job as job4 } from "@spfn/core/job";
|
|
3872
4346
|
var SendScheduledEmailInput = Type.Object({
|
|
3873
4347
|
notificationId: Type.Number(),
|
|
3874
4348
|
to: Type.Union([Type.String(), Type.Array(Type.String())]),
|
|
@@ -3880,7 +4354,7 @@ var SendScheduledEmailInput = Type.Object({
|
|
|
3880
4354
|
from: Type.Optional(Type.String()),
|
|
3881
4355
|
replyTo: Type.Optional(Type.String())
|
|
3882
4356
|
});
|
|
3883
|
-
var sendScheduledEmailJob =
|
|
4357
|
+
var sendScheduledEmailJob = job4("notification.send-scheduled-email").input(SendScheduledEmailInput).options({
|
|
3884
4358
|
retryLimit: 3,
|
|
3885
4359
|
retryDelay: 5e3
|
|
3886
4360
|
}).handler(async (input) => {
|
|
@@ -3896,7 +4370,7 @@ var sendScheduledEmailJob = job("notification.send-scheduled-email").input(SendS
|
|
|
3896
4370
|
});
|
|
3897
4371
|
|
|
3898
4372
|
// src/jobs/send-scheduled-sms.ts
|
|
3899
|
-
import { job as
|
|
4373
|
+
import { job as job5 } from "@spfn/core/job";
|
|
3900
4374
|
var SendScheduledSmsInput = Type.Object({
|
|
3901
4375
|
notificationId: Type.Number(),
|
|
3902
4376
|
to: Type.Union([Type.String(), Type.Array(Type.String())]),
|
|
@@ -3904,7 +4378,7 @@ var SendScheduledSmsInput = Type.Object({
|
|
|
3904
4378
|
template: Type.Optional(Type.String()),
|
|
3905
4379
|
data: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
3906
4380
|
});
|
|
3907
|
-
var sendScheduledSmsJob =
|
|
4381
|
+
var sendScheduledSmsJob = job5("notification.send-scheduled-sms").input(SendScheduledSmsInput).options({
|
|
3908
4382
|
retryLimit: 3,
|
|
3909
4383
|
retryDelay: 5e3
|
|
3910
4384
|
}).handler(async (input) => {
|
|
@@ -4118,7 +4592,10 @@ async function cancelNotificationsByReference(referenceType, referenceId) {
|
|
|
4118
4592
|
import { defineJobRouter } from "@spfn/core/job";
|
|
4119
4593
|
var notificationJobRouter = defineJobRouter({
|
|
4120
4594
|
sendScheduledEmail: sendScheduledEmailJob,
|
|
4121
|
-
sendScheduledSms: sendScheduledSmsJob
|
|
4595
|
+
sendScheduledSms: sendScheduledSmsJob,
|
|
4596
|
+
sendBulkEmailItem: sendBulkEmailItemJob,
|
|
4597
|
+
sendBulkSmsItem: sendBulkSmsItemJob,
|
|
4598
|
+
sendBulkSlackItem: sendBulkSlackItemJob
|
|
4122
4599
|
});
|
|
4123
4600
|
|
|
4124
4601
|
// src/integrations/error-slack.ts
|
|
@@ -4466,6 +4943,9 @@ export {
|
|
|
4466
4943
|
renderTemplate,
|
|
4467
4944
|
scheduleEmail,
|
|
4468
4945
|
scheduleSMS,
|
|
4946
|
+
sendBulkEmailItemJob,
|
|
4947
|
+
sendBulkSlackItemJob,
|
|
4948
|
+
sendBulkSmsItemJob,
|
|
4469
4949
|
sendEmail,
|
|
4470
4950
|
sendEmailBulk,
|
|
4471
4951
|
sendSMS,
|