@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/dist/server.js CHANGED
@@ -138,19 +138,19 @@ async function getSESClient() {
138
138
  return sesClient;
139
139
  }
140
140
  try {
141
- const { SESClient } = await import("@aws-sdk/client-ses");
142
- sesClient = new SESClient({
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-ses is not installed. Please install it: pnpm add @aws-sdk/client-ses"
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-ses");
162
+ const { SendEmailCommand } = await import("@aws-sdk/client-sesv2");
163
163
  const command = new SendEmailCommand({
164
- Source: params.from,
164
+ FromEmailAddress: params.from,
165
165
  Destination: {
166
166
  ToAddresses: params.to
167
167
  },
168
168
  ReplyToAddresses: params.replyTo ? [params.replyTo] : void 0,
169
- Message: {
170
- Subject: {
171
- Charset: "UTF-8",
172
- Data: params.subject
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
- ...params.text && {
182
- Text: {
183
- Charset: "UTF-8",
184
- Data: params.text
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/email/index.ts
821
- import { logger as logger2 } from "@spfn/core/logger";
822
- var log2 = logger2.child("@spfn/notification:email");
823
- var providers = {
824
- "aws-ses": awsSesProvider
825
- };
826
- function registerEmailProvider(provider) {
827
- providers[provider.name] = provider;
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
- return provider;
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
- async function sendEmail(params) {
838
- const recipients = Array.isArray(params.to) ? params.to : [params.to];
839
- let subject = params.subject;
840
- let text3 = params.text;
841
- let html = params.html;
842
- if (params.template) {
843
- if (!hasTemplate(params.template)) {
844
- log2.warn(`Template not found: ${params.template}`);
845
- return {
846
- success: false,
847
- error: `Template not found: ${params.template}`
848
- };
849
- }
850
- const rendered = renderTemplate(params.template, params.data || {}, "email");
851
- if (rendered.email) {
852
- subject = rendered.email.subject;
853
- text3 = rendered.email.text;
854
- html = rendered.email.html;
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
- if (!subject) {
858
- log2.warn("Email subject is required", { to: recipients });
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
- if (!text3 && !html) {
865
- log2.warn("Email content (text or html) is required", { to: recipients, subject });
866
- return {
867
- success: false,
868
- error: "Email content (text or html) is required"
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
- const internalParams = {
872
- to: recipients,
873
- from: params.from || getEmailFrom(),
874
- replyTo: params.replyTo || getEmailReplyTo(),
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
- const shouldTrack = params.tracking ?? isTrackingEnabled();
898
- const trackingBaseUrl = getTrackingBaseUrl();
899
- if (shouldTrack && historyId && internalParams.html && trackingBaseUrl) {
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
- const result = await provider.send(internalParams);
911
- if (result.success) {
912
- log2.info("Email sent", { to: recipients, subject, messageId: result.messageId });
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
- if (historyId && isHistoryEnabled()) {
917
- try {
918
- if (result.success) {
919
- await markNotificationSent(historyId, result.messageId);
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
- return result;
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
- async function sendEmailBulk(items) {
930
- const results = [];
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
- // src/channels/sms/index.ts
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 getProvider2() {
1025
- const providerName = env.SPFN_NOTIFICATION_SMS_PROVIDER || "aws-sns";
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
- async function sendSMS(params) {
1033
- const recipients = Array.isArray(params.to) ? params.to : [params.to];
1034
- let message = params.message;
1035
- if (params.template) {
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 provider = getProvider2();
1056
- const results = [];
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
- const allSuccess = results.every((r) => r.success);
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
- async function sendSMSBulk(items) {
1108
- const results = [];
1109
- let successCount = 0;
1110
- let failureCount = 0;
1111
- for (const item of items) {
1112
- const result = await sendSMS(item);
1113
- results.push(result);
1114
- if (result.success) {
1115
- successCount++;
1116
- } else {
1117
- failureCount++;
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
- // src/channels/slack/providers/webhook.ts
1124
- import { logger as logger5 } from "@spfn/core/logger";
1125
- var log5 = logger5.child("@spfn/notification:slack-webhook");
1126
- var webhookProvider = {
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
- // src/channels/slack/index.ts
1154
- import { logger as logger6 } from "@spfn/core/logger";
1155
- var log6 = logger6.child("@spfn/notification:slack");
1156
- var providers3 = {
1157
- "webhook": webhookProvider
1158
- };
1159
- function registerSlackProvider(provider) {
1160
- providers3[provider.name] = provider;
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 getProvider3() {
1163
- return providers3["webhook"];
1057
+ function IsOptional(value) {
1058
+ return IsObject(value) && value[OptionalKind] === "Optional";
1164
1059
  }
1165
- function resolveWebhookUrl(params) {
1166
- return params.webhookUrl || env.SPFN_NOTIFICATION_SLACK_WEBHOOK_URL;
1060
+ function IsAny(value) {
1061
+ return IsKindOf(value, "Any");
1167
1062
  }
1168
- async function sendSlack(params) {
1169
- const webhookUrl = resolveWebhookUrl(params);
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
- async function sendSlackBulk(items) {
1242
- const results = [];
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
- // src/jobs/send-scheduled-email.ts
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 IsAsyncIterator(value) {
1284
- return IsObject(value) && !IsArray(value) && !IsUint8Array(value) && Symbol.asyncIterator in value;
1072
+ function IsBigInt2(value) {
1073
+ return IsKindOf(value, "BigInt");
1285
1074
  }
1286
- function IsArray(value) {
1287
- return Array.isArray(value);
1075
+ function IsBoolean2(value) {
1076
+ return IsKindOf(value, "Boolean");
1288
1077
  }
1289
- function IsBigInt(value) {
1290
- return typeof value === "bigint";
1078
+ function IsComputed(value) {
1079
+ return IsKindOf(value, "Computed");
1291
1080
  }
1292
- function IsBoolean(value) {
1293
- return typeof value === "boolean";
1081
+ function IsConstructor(value) {
1082
+ return IsKindOf(value, "Constructor");
1294
1083
  }
1295
- function IsDate(value) {
1296
- return value instanceof globalThis.Date;
1084
+ function IsDate2(value) {
1085
+ return IsKindOf(value, "Date");
1297
1086
  }
1298
- function IsFunction(value) {
1299
- return typeof value === "function";
1087
+ function IsFunction2(value) {
1088
+ return IsKindOf(value, "Function");
1300
1089
  }
1301
- function IsIterator(value) {
1302
- return IsObject(value) && !IsArray(value) && !IsUint8Array(value) && Symbol.iterator in value;
1090
+ function IsInteger(value) {
1091
+ return IsKindOf(value, "Integer");
1303
1092
  }
1304
- function IsNull(value) {
1305
- return value === null;
1093
+ function IsIntersect(value) {
1094
+ return IsKindOf(value, "Intersect");
1306
1095
  }
1307
- function IsNumber(value) {
1308
- return typeof value === "number";
1096
+ function IsIterator2(value) {
1097
+ return IsKindOf(value, "Iterator");
1309
1098
  }
1310
- function IsObject(value) {
1311
- return typeof value === "object" && value !== null;
1099
+ function IsKindOf(value, kind) {
1100
+ return IsObject(value) && Kind in value && value[Kind] === kind;
1312
1101
  }
1313
- function IsRegExp(value) {
1314
- return value instanceof globalThis.RegExp;
1102
+ function IsLiteralValue(value) {
1103
+ return IsBoolean(value) || IsNumber(value) || IsString(value);
1315
1104
  }
1316
- function IsString(value) {
1317
- return typeof value === "string";
1105
+ function IsLiteral(value) {
1106
+ return IsKindOf(value, "Literal");
1318
1107
  }
1319
- function IsSymbol(value) {
1320
- return typeof value === "symbol";
1108
+ function IsMappedKey(value) {
1109
+ return IsKindOf(value, "MappedKey");
1321
1110
  }
1322
- function IsUint8Array(value) {
1323
- return value instanceof globalThis.Uint8Array;
1111
+ function IsMappedResult(value) {
1112
+ return IsKindOf(value, "MappedResult");
1324
1113
  }
1325
- function IsUndefined(value) {
1326
- return value === void 0;
1114
+ function IsNever(value) {
1115
+ return IsKindOf(value, "Never");
1327
1116
  }
1328
-
1329
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/clone/value.mjs
1330
- function ArrayType(value) {
1331
- return value.map((value2) => Visit(value2));
1117
+ function IsNot(value) {
1118
+ return IsKindOf(value, "Not");
1332
1119
  }
1333
- function DateType(value) {
1334
- return new Date(value.getTime());
1120
+ function IsNull2(value) {
1121
+ return IsKindOf(value, "Null");
1335
1122
  }
1336
- function Uint8ArrayType(value) {
1337
- return new Uint8Array(value);
1123
+ function IsNumber3(value) {
1124
+ return IsKindOf(value, "Number");
1338
1125
  }
1339
- function RegExpType(value) {
1340
- return new RegExp(value.source, value.flags);
1126
+ function IsObject3(value) {
1127
+ return IsKindOf(value, "Object");
1341
1128
  }
1342
- function ObjectType(value) {
1343
- const result = {};
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 Visit(value) {
1353
- return IsArray(value) ? ArrayType(value) : IsDate(value) ? DateType(value) : IsUint8Array(value) ? Uint8ArrayType(value) : IsRegExp(value) ? RegExpType(value) : IsObject(value) ? ObjectType(value) : value;
1132
+ function IsRecord(value) {
1133
+ return IsKindOf(value, "Record");
1354
1134
  }
1355
- function Clone(value) {
1356
- return Visit(value);
1135
+ function IsRef(value) {
1136
+ return IsKindOf(value, "Ref");
1357
1137
  }
1358
-
1359
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/clone/type.mjs
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
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/value/guard/guard.mjs
1365
- function IsObject2(value) {
1366
- return value !== null && typeof value === "object";
1141
+ function IsString2(value) {
1142
+ return IsKindOf(value, "String");
1367
1143
  }
1368
- function IsArray2(value) {
1369
- return globalThis.Array.isArray(value) && !globalThis.ArrayBuffer.isView(value);
1144
+ function IsSymbol2(value) {
1145
+ return IsKindOf(value, "Symbol");
1370
1146
  }
1371
- function IsUndefined2(value) {
1372
- return value === void 0;
1147
+ function IsTemplateLiteral(value) {
1148
+ return IsKindOf(value, "TemplateLiteral");
1373
1149
  }
1374
- function IsNumber2(value) {
1375
- return typeof value === "number";
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
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/rest/rest.mjs
3747
- function RestResolve(T) {
3748
- return IsIntersect(T) ? T.allOf : IsUnion(T) ? T.anyOf : IsTuple(T) ? T.items ?? [] : [];
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 Rest(T) {
3751
- return RestResolve(T);
4140
+ function getProvider3() {
4141
+ return providers3["webhook"];
3752
4142
  }
3753
-
3754
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/return-type/return-type.mjs
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
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/transform/transform.mjs
3760
- var TransformDecodeBuilder = class {
3761
- constructor(schema) {
3762
- this.schema = schema;
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
- Decode(decode) {
3765
- return new TransformEncodeBuilder(this.schema, decode);
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
- var TransformEncodeBuilder = class {
3769
- constructor(schema, decode) {
3770
- this.schema = schema;
3771
- this.decode = decode;
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
- EncodeTransform(encode, schema) {
3774
- const Encode = (value) => schema[TransformKind].Encode(encode(value));
3775
- const Decode = (value) => this.decode(schema[TransformKind].Decode(value));
3776
- const Codec = { Encode, Decode };
3777
- return { ...schema, [TransformKind]: Codec };
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
- EncodeSchema(encode, schema) {
3780
- const Codec = { Decode: this.decode, Encode: encode };
3781
- return { ...schema, [TransformKind]: Codec };
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
- Encode(encode) {
3784
- return IsTransform(this.schema) ? this.EncodeTransform(encode, this.schema) : this.EncodeSchema(encode, this.schema);
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
- // ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/void/void.mjs
3797
- function Void(options) {
3798
- return CreateType({ [Kind]: "Void", type: "void" }, options);
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 = job("notification.send-scheduled-email").input(SendScheduledEmailInput).options({
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 job2 } from "@spfn/core/job";
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 = job2("notification.send-scheduled-sms").input(SendScheduledSmsInput).options({
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,