@spfn/notification 0.1.0-beta.1 → 0.1.0-beta.2
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/{index-D_XTwHmO.d.ts → index-BAe1ZBYQ.d.ts} +45 -1
- package/dist/index.d.ts +1 -1
- package/dist/server.d.ts +78 -3
- package/dist/server.js +218 -0
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -119,6 +119,50 @@ interface InternalSendSMSParams {
|
|
|
119
119
|
message: string;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* @spfn/notification - Slack Channel Types
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parameters for sending Slack message
|
|
128
|
+
*/
|
|
129
|
+
interface SendSlackParams {
|
|
130
|
+
/**
|
|
131
|
+
* Webhook URL (overrides env/config default)
|
|
132
|
+
*/
|
|
133
|
+
webhookUrl?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Plain text message
|
|
136
|
+
*/
|
|
137
|
+
text?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Slack Block Kit blocks
|
|
140
|
+
*/
|
|
141
|
+
blocks?: unknown[];
|
|
142
|
+
/**
|
|
143
|
+
* Template name
|
|
144
|
+
*/
|
|
145
|
+
template?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Template data for variable substitution
|
|
148
|
+
*/
|
|
149
|
+
data?: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Slack provider interface
|
|
153
|
+
*/
|
|
154
|
+
interface SlackProvider extends ChannelProvider<InternalSendSlackParams> {
|
|
155
|
+
name: 'webhook' | string;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Internal send Slack params (after template rendering)
|
|
159
|
+
*/
|
|
160
|
+
interface InternalSendSlackParams {
|
|
161
|
+
webhookUrl: string;
|
|
162
|
+
text?: string;
|
|
163
|
+
blocks?: unknown[];
|
|
164
|
+
}
|
|
165
|
+
|
|
122
166
|
/**
|
|
123
167
|
* @spfn/notification - Template Types
|
|
124
168
|
*/
|
|
@@ -167,4 +211,4 @@ interface RenderedTemplate {
|
|
|
167
211
|
*/
|
|
168
212
|
type TemplateData = Record<string, unknown>;
|
|
169
213
|
|
|
170
|
-
export type { EmailProvider as E, NotificationChannel as N, RenderedTemplate as R, SendEmailParams as S, TemplateDefinition as T, SendResult as a, SendSMSParams as b, SMSProvider as c,
|
|
214
|
+
export type { EmailProvider as E, NotificationChannel as N, RenderedTemplate as R, SendEmailParams as S, TemplateDefinition as T, SendResult as a, SendSMSParams as b, SMSProvider as c, SendSlackParams as d, SlackProvider as e, TemplateData as f, EmailTemplateContent as g, SmsTemplateContent as h, SlackTemplateContent as i };
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { g as EmailTemplateContent, N as NotificationChannel, S as SendEmailParams, a as SendResult, b as SendSMSParams, d as SendSlackParams, i as SlackTemplateContent, h as SmsTemplateContent, f as TemplateData, T as TemplateDefinition } from './index-BAe1ZBYQ.js';
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { NotificationConfig, configureNotification, getAppName, getEmailFrom, getEmailReplyTo, getNotificationConfig, getSmsDefaultCountryCode } from './config/index.js';
|
|
2
|
-
import { S as SendEmailParams, a as SendResult, E as EmailProvider, b as SendSMSParams, c as SMSProvider, T as TemplateDefinition,
|
|
3
|
-
export {
|
|
2
|
+
import { S as SendEmailParams, a as SendResult, E as EmailProvider, b as SendSMSParams, c as SMSProvider, d as SendSlackParams, e as SlackProvider, T as TemplateDefinition, f as TemplateData, N as NotificationChannel$1, R as RenderedTemplate } from './index-BAe1ZBYQ.js';
|
|
3
|
+
export { g as EmailTemplateContent, i as SlackTemplateContent, h as SmsTemplateContent } from './index-BAe1ZBYQ.js';
|
|
4
4
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
5
5
|
import * as _spfn_core_job from '@spfn/core/job';
|
|
6
6
|
import '@spfn/core/env';
|
|
@@ -47,6 +47,27 @@ declare function sendSMSBulk(items: SendSMSParams[]): Promise<{
|
|
|
47
47
|
failureCount: number;
|
|
48
48
|
}>;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @spfn/notification - Slack Channel
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register custom Slack provider
|
|
56
|
+
*/
|
|
57
|
+
declare function registerSlackProvider(provider: SlackProvider): void;
|
|
58
|
+
/**
|
|
59
|
+
* Send Slack message
|
|
60
|
+
*/
|
|
61
|
+
declare function sendSlack(params: SendSlackParams): Promise<SendResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Send bulk Slack messages
|
|
64
|
+
*/
|
|
65
|
+
declare function sendSlackBulk(items: SendSlackParams[]): Promise<{
|
|
66
|
+
results: SendResult[];
|
|
67
|
+
successCount: number;
|
|
68
|
+
failureCount: number;
|
|
69
|
+
}>;
|
|
70
|
+
|
|
50
71
|
/**
|
|
51
72
|
* @spfn/notification - Schedule Service
|
|
52
73
|
*
|
|
@@ -680,4 +701,58 @@ declare const notificationJobRouter: _spfn_core_job.JobRouter<{
|
|
|
680
701
|
}, void>;
|
|
681
702
|
}>;
|
|
682
703
|
|
|
683
|
-
|
|
704
|
+
/**
|
|
705
|
+
* @spfn/notification - Error → Slack Integration
|
|
706
|
+
*
|
|
707
|
+
* Factory for creating an onError callback that sends error notifications to Slack.
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* ```typescript
|
|
711
|
+
* import { createServer } from '@spfn/core/server';
|
|
712
|
+
* import { createErrorSlackNotifier } from '@spfn/notification/server';
|
|
713
|
+
*
|
|
714
|
+
* await createServer({
|
|
715
|
+
* middleware: {
|
|
716
|
+
* onError: createErrorSlackNotifier({ minStatusCode: 500 }),
|
|
717
|
+
* },
|
|
718
|
+
* });
|
|
719
|
+
* ```
|
|
720
|
+
*/
|
|
721
|
+
interface ErrorContext {
|
|
722
|
+
statusCode: number;
|
|
723
|
+
path: string;
|
|
724
|
+
method: string;
|
|
725
|
+
requestId?: string;
|
|
726
|
+
timestamp: string;
|
|
727
|
+
userId?: string;
|
|
728
|
+
request: {
|
|
729
|
+
headers: Record<string, string>;
|
|
730
|
+
query: Record<string, string>;
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
interface ErrorSlackOptions {
|
|
734
|
+
/**
|
|
735
|
+
* Minimum status code to trigger notification
|
|
736
|
+
* @default 500
|
|
737
|
+
*/
|
|
738
|
+
minStatusCode?: number;
|
|
739
|
+
/**
|
|
740
|
+
* Webhook URL override (defaults to env/config)
|
|
741
|
+
*/
|
|
742
|
+
webhookUrl?: string;
|
|
743
|
+
/**
|
|
744
|
+
* Custom message formatter
|
|
745
|
+
*/
|
|
746
|
+
formatMessage?: (err: Error, ctx: ErrorContext) => {
|
|
747
|
+
text?: string;
|
|
748
|
+
blocks?: unknown[];
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Create an onError callback that sends Slack notifications
|
|
753
|
+
*
|
|
754
|
+
* Returns a function matching ErrorHandler's onError signature.
|
|
755
|
+
*/
|
|
756
|
+
declare function createErrorSlackNotifier(options?: ErrorSlackOptions): (err: Error, ctx: ErrorContext) => Promise<void>;
|
|
757
|
+
|
|
758
|
+
export { type CancelResult, EmailProvider, type ErrorSlackOptions, type FindNotificationsOptions, NOTIFICATION_CHANNELS, NOTIFICATION_STATUSES, type NewNotification, type Notification, NotificationChannel$1 as NotificationChannel, type NotificationStats, type NotificationStatus, SMSProvider, type ScheduleOptions, type ScheduleResult, SendEmailParams, SendResult, SendSMSParams, SendSlackParams, SlackProvider, TemplateData, TemplateDefinition, cancelNotification, cancelNotificationsByReference, cancelScheduledNotification, countNotifications, createErrorSlackNotifier, createNotificationRecord, createScheduledNotification, findNotificationByJobId, findNotifications, findScheduledNotifications, getNotificationStats, getTemplate, getTemplateNames, hasTemplate, markNotificationFailed, markNotificationPending, markNotificationSent, notificationJobRouter, notifications, registerBuiltinTemplates, registerEmailProvider, registerFilter, registerSMSProvider, registerSlackProvider, registerTemplate, renderTemplate, scheduleEmail, scheduleSMS, sendEmail, sendEmailBulk, sendSMS, sendSMSBulk, sendScheduledEmailJob, sendScheduledSmsJob, sendSlack, sendSlackBulk, updateNotificationJobId };
|
package/dist/server.js
CHANGED
|
@@ -892,6 +892,125 @@ async function sendSMSBulk(items) {
|
|
|
892
892
|
return { results, successCount, failureCount };
|
|
893
893
|
}
|
|
894
894
|
|
|
895
|
+
// src/channels/slack/providers/webhook.ts
|
|
896
|
+
var webhookProvider = {
|
|
897
|
+
name: "webhook",
|
|
898
|
+
async send(params) {
|
|
899
|
+
try {
|
|
900
|
+
const res = await fetch(params.webhookUrl, {
|
|
901
|
+
method: "POST",
|
|
902
|
+
headers: { "Content-Type": "application/json" },
|
|
903
|
+
body: JSON.stringify({
|
|
904
|
+
text: params.text,
|
|
905
|
+
blocks: params.blocks
|
|
906
|
+
})
|
|
907
|
+
});
|
|
908
|
+
return {
|
|
909
|
+
success: res.ok,
|
|
910
|
+
error: res.ok ? void 0 : await res.text()
|
|
911
|
+
};
|
|
912
|
+
} catch (error) {
|
|
913
|
+
const err = error;
|
|
914
|
+
return {
|
|
915
|
+
success: false,
|
|
916
|
+
error: err.message
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// src/channels/slack/index.ts
|
|
923
|
+
var providers3 = {
|
|
924
|
+
"webhook": webhookProvider
|
|
925
|
+
};
|
|
926
|
+
function registerSlackProvider(provider) {
|
|
927
|
+
providers3[provider.name] = provider;
|
|
928
|
+
}
|
|
929
|
+
function getProvider3() {
|
|
930
|
+
return providers3["webhook"];
|
|
931
|
+
}
|
|
932
|
+
function resolveWebhookUrl(params) {
|
|
933
|
+
return params.webhookUrl || env.SPFN_NOTIFICATION_SLACK_WEBHOOK_URL;
|
|
934
|
+
}
|
|
935
|
+
async function sendSlack(params) {
|
|
936
|
+
const webhookUrl = resolveWebhookUrl(params);
|
|
937
|
+
if (!webhookUrl) {
|
|
938
|
+
return {
|
|
939
|
+
success: false,
|
|
940
|
+
error: "Slack webhook URL is required. Set SPFN_NOTIFICATION_SLACK_WEBHOOK_URL or pass webhookUrl."
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
let text2 = params.text;
|
|
944
|
+
let blocks = params.blocks;
|
|
945
|
+
if (params.template) {
|
|
946
|
+
if (!hasTemplate(params.template)) {
|
|
947
|
+
return {
|
|
948
|
+
success: false,
|
|
949
|
+
error: `Template not found: ${params.template}`
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const rendered = renderTemplate(params.template, params.data || {}, "slack");
|
|
953
|
+
if (rendered.slack) {
|
|
954
|
+
text2 = rendered.slack.text;
|
|
955
|
+
blocks = rendered.slack.blocks;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (!text2 && !blocks) {
|
|
959
|
+
return {
|
|
960
|
+
success: false,
|
|
961
|
+
error: "Slack message requires text or blocks"
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
const internalParams = {
|
|
965
|
+
webhookUrl,
|
|
966
|
+
text: text2,
|
|
967
|
+
blocks
|
|
968
|
+
};
|
|
969
|
+
const provider = getProvider3();
|
|
970
|
+
let historyId;
|
|
971
|
+
if (isHistoryEnabled()) {
|
|
972
|
+
try {
|
|
973
|
+
const record = await createNotificationRecord({
|
|
974
|
+
channel: "slack",
|
|
975
|
+
recipient: webhookUrl,
|
|
976
|
+
templateName: params.template,
|
|
977
|
+
templateData: params.data,
|
|
978
|
+
content: text2,
|
|
979
|
+
providerName: provider.name
|
|
980
|
+
});
|
|
981
|
+
historyId = record.id;
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const result = await provider.send(internalParams);
|
|
986
|
+
if (historyId && isHistoryEnabled()) {
|
|
987
|
+
try {
|
|
988
|
+
if (result.success) {
|
|
989
|
+
await markNotificationSent(historyId, result.messageId);
|
|
990
|
+
} else {
|
|
991
|
+
await markNotificationFailed(historyId, result.error || "Unknown error");
|
|
992
|
+
}
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return result;
|
|
997
|
+
}
|
|
998
|
+
async function sendSlackBulk(items) {
|
|
999
|
+
const results = [];
|
|
1000
|
+
let successCount = 0;
|
|
1001
|
+
let failureCount = 0;
|
|
1002
|
+
for (const item of items) {
|
|
1003
|
+
const result = await sendSlack(item);
|
|
1004
|
+
results.push(result);
|
|
1005
|
+
if (result.success) {
|
|
1006
|
+
successCount++;
|
|
1007
|
+
} else {
|
|
1008
|
+
failureCount++;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return { results, successCount, failureCount };
|
|
1012
|
+
}
|
|
1013
|
+
|
|
895
1014
|
// src/jobs/send-scheduled-email.ts
|
|
896
1015
|
import { job } from "@spfn/core/job";
|
|
897
1016
|
|
|
@@ -3759,6 +3878,101 @@ var notificationJobRouter = defineJobRouter({
|
|
|
3759
3878
|
sendScheduledSms: sendScheduledSmsJob
|
|
3760
3879
|
});
|
|
3761
3880
|
|
|
3881
|
+
// src/integrations/error-slack.ts
|
|
3882
|
+
function formatHeaders(headers) {
|
|
3883
|
+
const entries = Object.entries(headers);
|
|
3884
|
+
if (entries.length === 0) {
|
|
3885
|
+
return "(none)";
|
|
3886
|
+
}
|
|
3887
|
+
return entries.map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
3888
|
+
}
|
|
3889
|
+
function formatQuery(query) {
|
|
3890
|
+
const entries = Object.entries(query);
|
|
3891
|
+
if (entries.length === 0) {
|
|
3892
|
+
return "(none)";
|
|
3893
|
+
}
|
|
3894
|
+
return entries.map(([k, v]) => `${k}=${v}`).join("\n");
|
|
3895
|
+
}
|
|
3896
|
+
function shortStack(err, maxLines = 3) {
|
|
3897
|
+
if (!err.stack) {
|
|
3898
|
+
return "(no stack)";
|
|
3899
|
+
}
|
|
3900
|
+
const lines = err.stack.split("\n").slice(1);
|
|
3901
|
+
return lines.slice(0, maxLines).map((line) => line.trim()).join("\n");
|
|
3902
|
+
}
|
|
3903
|
+
function defaultFormat(err, ctx) {
|
|
3904
|
+
const emoji = ctx.statusCode >= 500 ? ":rotating_light:" : ":warning:";
|
|
3905
|
+
const title = `${emoji} *${err.name || "Error"}* \u2014 ${ctx.statusCode}`;
|
|
3906
|
+
const fields = [
|
|
3907
|
+
{ type: "mrkdwn", text: `*Method*
|
|
3908
|
+
${ctx.method}` },
|
|
3909
|
+
{ type: "mrkdwn", text: `*Path*
|
|
3910
|
+
${ctx.path}` },
|
|
3911
|
+
{ type: "mrkdwn", text: `*User*
|
|
3912
|
+
${ctx.userId ?? "(anonymous)"}` },
|
|
3913
|
+
{ type: "mrkdwn", text: `*Request ID*
|
|
3914
|
+
${ctx.requestId ?? "(none)"}` }
|
|
3915
|
+
];
|
|
3916
|
+
const blocks = [
|
|
3917
|
+
// Title
|
|
3918
|
+
{
|
|
3919
|
+
type: "header",
|
|
3920
|
+
text: { type: "plain_text", text: `${err.name || "Error"} \u2014 ${ctx.statusCode}`, emoji: true }
|
|
3921
|
+
},
|
|
3922
|
+
// Error message
|
|
3923
|
+
{
|
|
3924
|
+
type: "section",
|
|
3925
|
+
text: { type: "mrkdwn", text: `> ${err.message}` }
|
|
3926
|
+
},
|
|
3927
|
+
// Fields: method, path, user, requestId
|
|
3928
|
+
{
|
|
3929
|
+
type: "section",
|
|
3930
|
+
fields
|
|
3931
|
+
},
|
|
3932
|
+
// Timestamp
|
|
3933
|
+
{
|
|
3934
|
+
type: "context",
|
|
3935
|
+
elements: [
|
|
3936
|
+
{ type: "mrkdwn", text: `*Time:* ${ctx.timestamp}` }
|
|
3937
|
+
]
|
|
3938
|
+
},
|
|
3939
|
+
{ type: "divider" },
|
|
3940
|
+
// Headers
|
|
3941
|
+
{
|
|
3942
|
+
type: "section",
|
|
3943
|
+
text: { type: "mrkdwn", text: `*Request Headers*
|
|
3944
|
+
\`\`\`${formatHeaders(ctx.request.headers)}\`\`\`` }
|
|
3945
|
+
},
|
|
3946
|
+
// Query
|
|
3947
|
+
{
|
|
3948
|
+
type: "section",
|
|
3949
|
+
text: { type: "mrkdwn", text: `*Query Params*
|
|
3950
|
+
\`\`\`${formatQuery(ctx.request.query)}\`\`\`` }
|
|
3951
|
+
},
|
|
3952
|
+
{ type: "divider" },
|
|
3953
|
+
// Stack trace
|
|
3954
|
+
{
|
|
3955
|
+
type: "section",
|
|
3956
|
+
text: { type: "mrkdwn", text: `*Stack Trace*
|
|
3957
|
+
\`\`\`${shortStack(err)}\`\`\`` }
|
|
3958
|
+
}
|
|
3959
|
+
];
|
|
3960
|
+
return { text: title, blocks };
|
|
3961
|
+
}
|
|
3962
|
+
function createErrorSlackNotifier(options = {}) {
|
|
3963
|
+
const { minStatusCode = 500 } = options;
|
|
3964
|
+
return async (err, ctx) => {
|
|
3965
|
+
if (ctx.statusCode < minStatusCode) {
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3968
|
+
const message = options.formatMessage?.(err, ctx) ?? defaultFormat(err, ctx);
|
|
3969
|
+
await sendSlack({
|
|
3970
|
+
...message,
|
|
3971
|
+
webhookUrl: options.webhookUrl
|
|
3972
|
+
});
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3762
3976
|
// src/server.ts
|
|
3763
3977
|
registerBuiltinTemplates();
|
|
3764
3978
|
export {
|
|
@@ -3769,6 +3983,7 @@ export {
|
|
|
3769
3983
|
cancelScheduledNotification,
|
|
3770
3984
|
configureNotification,
|
|
3771
3985
|
countNotifications,
|
|
3986
|
+
createErrorSlackNotifier,
|
|
3772
3987
|
createNotificationRecord,
|
|
3773
3988
|
createScheduledNotification,
|
|
3774
3989
|
findNotificationByJobId,
|
|
@@ -3792,6 +4007,7 @@ export {
|
|
|
3792
4007
|
registerEmailProvider,
|
|
3793
4008
|
registerFilter,
|
|
3794
4009
|
registerSMSProvider,
|
|
4010
|
+
registerSlackProvider,
|
|
3795
4011
|
registerTemplate,
|
|
3796
4012
|
renderTemplate,
|
|
3797
4013
|
scheduleEmail,
|
|
@@ -3802,6 +4018,8 @@ export {
|
|
|
3802
4018
|
sendSMSBulk,
|
|
3803
4019
|
sendScheduledEmailJob,
|
|
3804
4020
|
sendScheduledSmsJob,
|
|
4021
|
+
sendSlack,
|
|
4022
|
+
sendSlackBulk,
|
|
3805
4023
|
updateNotificationJobId
|
|
3806
4024
|
};
|
|
3807
4025
|
//# sourceMappingURL=server.js.map
|