@develit-services/notification 0.0.3 → 0.0.5

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.
Files changed (62) hide show
  1. package/dist/src.cjs +636 -0
  2. package/dist/src.d.cts +491 -0
  3. package/dist/src.d.mts +489 -0
  4. package/dist/src.d.ts +491 -0
  5. package/dist/src.mjs +625 -0
  6. package/package.json +6 -1
  7. package/@types/consts/audit-log.consts.ts +0 -7
  8. package/@types/consts/index.ts +0 -1
  9. package/@types/database/audit-log.types.ts +0 -7
  10. package/@types/database/index.ts +0 -1
  11. package/@types/email/IEmail.connector.ts +0 -21
  12. package/@types/email/IEmail.types.ts +0 -25
  13. package/@types/email/ecomail/ecomail.connector.ts +0 -139
  14. package/@types/email/ecomail/ecomail.types.ts +0 -27
  15. package/@types/email/ecomail/index.ts +0 -2
  16. package/@types/email/index.ts +0 -3
  17. package/@types/index.ts +0 -9
  18. package/@types/io/index.ts +0 -3
  19. package/@types/io/sendEmail.ts +0 -18
  20. package/@types/io/sendSlack.ts +0 -19
  21. package/@types/io/sendSms.ts +0 -20
  22. package/@types/pushNotification/IPushNotification.ts +0 -1
  23. package/@types/pushNotification/index.ts +0 -1
  24. package/@types/queue.ts +0 -19
  25. package/@types/service.ts +0 -30
  26. package/@types/slack/ISlack.types.ts +0 -3
  27. package/@types/slack/index.ts +0 -1
  28. package/@types/slack/slack.connector.ts +0 -27
  29. package/@types/sms/ISms.connector.ts +0 -22
  30. package/@types/sms/ISms.types.ts +0 -4
  31. package/@types/sms/index.ts +0 -3
  32. package/@types/sms/twilio/index.ts +0 -1
  33. package/@types/sms/twilio/twilio.connector.ts +0 -35
  34. package/CHANGELOG.md +0 -32
  35. package/build.config.ts +0 -29
  36. package/drizzle.config.ts +0 -3
  37. package/src/database/drizzle.ts +0 -6
  38. package/src/database/migrations/0000_funny_beast.sql +0 -12
  39. package/src/database/migrations/meta/0000_snapshot.json +0 -101
  40. package/src/database/migrations/meta/_journal.json +0 -13
  41. package/src/database/schema/audit-log.schema.ts +0 -13
  42. package/src/database/schema/index.ts +0 -1
  43. package/src/defineNotificationService.ts +0 -328
  44. package/src/defineNotificationWrangler.ts +0 -78
  45. package/src/index.ts +0 -12
  46. package/src/utils/connectors.ts +0 -1
  47. package/src/utils/database/command/create-audit-log.command.ts +0 -34
  48. package/src/utils/database/command/index.ts +0 -1
  49. package/src/utils/database/index.ts +0 -1
  50. package/src/utils/email.ts +0 -25
  51. package/src/utils/index.ts +0 -3
  52. package/src/utils/sms.ts +0 -25
  53. package/test/env.d.ts +0 -7
  54. package/test/integration/sendEmail.test.ts +0 -88
  55. package/test/setup/migrations.ts +0 -3
  56. package/test/unit/connectors/ecomail.connector.ts +0 -715
  57. package/test/unit/email.test.ts +0 -55
  58. package/tsconfig.json +0 -3
  59. package/vitest.config.ts +0 -31
  60. package/worker-configuration.d.ts +0 -25
  61. package/wrangler.jsonc +0 -209
  62. package/wrangler.ts +0 -106
@@ -1,328 +0,0 @@
1
- import {
2
- type IRPCResponse,
3
- action,
4
- cloudflareQueue,
5
- createInternalError,
6
- develitWorker,
7
- useResult,
8
- uuidv4,
9
- } from '@develit-io/backend-sdk'
10
- import {
11
- type IEmailConnector,
12
- type ISmsConnector,
13
- type NotificationQueueMessage,
14
- type SendEmailInput,
15
- type SendEmailOutput,
16
- type SendSlackInput,
17
- type SendSlackOutput,
18
- type SendSmsInput,
19
- type SendSmsOutput,
20
- sendEmailInputSchema,
21
- sendSlackInputSchema,
22
- sendSmsInputSchema,
23
- } from '../@types'
24
- import { tables } from './database/drizzle'
25
- import {
26
- createAuditLogCommand,
27
- initiateEmailConnector,
28
- initiateSmsConnector,
29
- } from './utils'
30
- import { WorkerEntrypoint } from 'cloudflare:workers'
31
- import { type DrizzleD1Database, drizzle } from 'drizzle-orm/d1'
32
- import { SlackConnector } from '../@types/slack/slack.connector'
33
-
34
- export class NotificationServiceBase extends develitWorker(
35
- WorkerEntrypoint<NotificationEnv>,
36
- ) {
37
- readonly name: string
38
-
39
- readonly slackConnector = new SlackConnector(this.env.SLACK_WEBHOOKS)
40
- protected emailConnector!: IEmailConnector
41
- protected smsConnector!: ISmsConnector
42
-
43
- readonly db: DrizzleD1Database<typeof tables>
44
-
45
- constructor(ctx: ExecutionContext, env: NotificationEnv) {
46
- super(ctx, env)
47
- this.name = 'notification-service'
48
- this.db = drizzle(this.env.NOTIFICATION_D1, { schema: tables })
49
- }
50
-
51
- @cloudflareQueue({ baseDelay: 60 })
52
- async queue(batch: MessageBatch<NotificationQueueMessage>): Promise<void> {
53
- for (const message of batch.messages) {
54
- this.logInput({ message })
55
-
56
- let notificationAction
57
-
58
- const { type, metadata, payload } = message.body
59
-
60
- if (type === 'email') {
61
- const [emailConnector, error] = await useResult(
62
- initiateEmailConnector(
63
- this.env.EMAIL_PROVIDER,
64
- this.env.EMAIL_API_KEY,
65
- this.env.EMAIL_SMTP_HOST,
66
- this.env.EMAIL_SENDER,
67
- ),
68
- )
69
-
70
- if (error) {
71
- this.logError({ error })
72
- message.retry()
73
- continue
74
- }
75
-
76
- this.emailConnector = emailConnector!
77
- }
78
-
79
- if (type === 'sms') {
80
- const [smsConnector, error] = await useResult(
81
- initiateSmsConnector(
82
- this.env.SMS_PROVIDER,
83
- this.env.SMS_ACCOUNT_ID,
84
- this.env.SMS_AUTH_TOKEN,
85
- this.env.SMS_SERVICE_ID,
86
- ),
87
- )
88
-
89
- if (error) {
90
- this.logError({ error })
91
- message.retry()
92
- continue
93
- }
94
-
95
- this.smsConnector = smsConnector!
96
- }
97
-
98
- switch (type) {
99
- case 'email':
100
- notificationAction = async () =>
101
- this._sendEmail({
102
- email: payload.email!,
103
- metadata,
104
- })
105
- break
106
- case 'sms':
107
- notificationAction = async () =>
108
- this._sendSms({
109
- sms: payload.sms!,
110
- metadata,
111
- })
112
- break
113
- case 'pushNotification':
114
- notificationAction = async () => this._sendPushNotification()
115
- break
116
- case 'slack':
117
- notificationAction = async () =>
118
- this.sendSlackNotification({
119
- slack: message.body.payload.slack!,
120
- metadata,
121
- })
122
- break
123
- default:
124
- this.logError({ error: `Unknown notification type: ${type}` })
125
- message.retry()
126
- continue
127
- }
128
-
129
- const { error, message: errorMessage } = await notificationAction()
130
-
131
- if (error) {
132
- this.logError({ error: errorMessage })
133
- message.retry()
134
- continue
135
- }
136
-
137
- message.ack()
138
- }
139
- }
140
-
141
- @action('private-send-email')
142
- protected async _sendEmail(
143
- input: SendEmailInput,
144
- ): Promise<IRPCResponse<SendEmailOutput>> {
145
- return this.handleAction(
146
- { data: input, schema: sendEmailInputSchema },
147
- { successMessage: 'Email sent.' },
148
- async (params) => {
149
- const {
150
- email,
151
- metadata: {
152
- ip,
153
- userAgent,
154
- initiator: { service, userId },
155
- },
156
- } = params!
157
-
158
- if (!this.emailConnector)
159
- this.emailConnector = await initiateEmailConnector(
160
- this.env.EMAIL_PROVIDER,
161
- this.env.EMAIL_API_KEY,
162
- this.env.EMAIL_SMTP_HOST,
163
- this.env.EMAIL_SENDER,
164
- )
165
-
166
- const response = await this.emailConnector.sendEmail(email)
167
-
168
- if (!response?.ok) {
169
- throw createInternalError(null, {
170
- message: `Could not send email: ${JSON.stringify(await response?.json())}`,
171
- status: 500,
172
- })
173
- }
174
-
175
- const { command } = await createAuditLogCommand({
176
- db: this.db,
177
- auditLog: {
178
- id: uuidv4(),
179
- event: 'EMAIL',
180
- ip,
181
- initiatorService: service,
182
- initiatorUserId: userId,
183
- userAgent,
184
- description: JSON.stringify(input),
185
- },
186
- })
187
-
188
- await this.db.batch([command])
189
-
190
- return {}
191
- },
192
- )
193
- }
194
-
195
- @action('private-send-sms')
196
- protected async _sendSms(
197
- input: SendSmsInput,
198
- ): Promise<IRPCResponse<SendSmsOutput>> {
199
- return this.handleAction(
200
- { data: input, schema: sendSmsInputSchema },
201
- { successMessage: 'Sms sent.' },
202
- async (params) => {
203
- const {
204
- sms: { message, to },
205
- metadata: {
206
- ip,
207
- userAgent,
208
- initiator: { service, userId },
209
- },
210
- } = params!
211
-
212
- await this.smsConnector.sendSms({ message, to })
213
-
214
- const { command } = await createAuditLogCommand({
215
- db: this.db,
216
- auditLog: {
217
- id: uuidv4(),
218
- event: 'SMS',
219
- ip,
220
- userAgent,
221
- initiatorService: service,
222
- initiatorUserId: userId,
223
- description: JSON.stringify(input),
224
- },
225
- })
226
-
227
- await this.db.batch([command])
228
-
229
- return {}
230
- },
231
- )
232
- }
233
-
234
- @action('public-send-email')
235
- async sendEmail(
236
- input: SendEmailInput,
237
- ): Promise<IRPCResponse<SendEmailOutput>> {
238
- return this.handleAction(
239
- { data: input, schema: sendEmailInputSchema },
240
- { successMessage: 'Email sent.' },
241
- async (params) => {
242
- const { email, metadata } = params!
243
-
244
- await this.pushToQueue<NotificationQueueMessage>(
245
- this.env.NOTIFICATIONS_QUEUE,
246
- {
247
- type: 'email',
248
- payload: {
249
- email,
250
- },
251
- metadata,
252
- },
253
- )
254
-
255
- return {}
256
- },
257
- )
258
- }
259
-
260
- @action('public-send-sms')
261
- async sendSms(input: SendSmsInput): Promise<IRPCResponse<SendSmsOutput>> {
262
- return this.handleAction(
263
- { data: input, schema: sendSmsInputSchema },
264
- { successMessage: 'SMS sent.' },
265
- async (params) => {
266
- const {
267
- sms: { message, to },
268
- metadata,
269
- } = params!
270
-
271
- await this.pushToQueue<NotificationQueueMessage>(
272
- this.env.NOTIFICATIONS_QUEUE,
273
- {
274
- type: 'sms',
275
- payload: {
276
- sms: {
277
- message,
278
- to,
279
- },
280
- },
281
- metadata,
282
- },
283
- )
284
-
285
- return {}
286
- },
287
- )
288
- }
289
-
290
- @action('send-push-notification')
291
- protected async _sendPushNotification(): Promise<IRPCResponse<{}>> {
292
- this.logInput({})
293
-
294
- this.logError({ error: 'Method not implemented.' })
295
- throw new Error('Method not implemented.')
296
- }
297
-
298
- @action('send-slack-notification')
299
- async sendSlackNotification(
300
- input: SendSlackInput,
301
- ): Promise<IRPCResponse<SendSlackOutput>> {
302
- return this.handleAction(
303
- {
304
- data: input,
305
- schema: sendSlackInputSchema,
306
- },
307
- { successMessage: 'Slack sent.' },
308
- async (params) => {
309
- const { slack } = params!
310
-
311
- await this.slackConnector.sendNotificationToAllSlack(slack.message)
312
-
313
- return {}
314
- },
315
- )
316
- }
317
- }
318
-
319
- export function defineNotificationService(): new (
320
- ctx: ExecutionContext,
321
- env: NotificationEnv,
322
- ) => NotificationServiceBase {
323
- return class NotificationService extends NotificationServiceBase {
324
- constructor(ctx: ExecutionContext, env: NotificationEnv) {
325
- super(ctx, env)
326
- }
327
- }
328
- }
@@ -1,78 +0,0 @@
1
- // defineNotificationServiceWrangler.ts
2
- /** biome-ignore-all lint/suspicious/noExplicitAny: allow any */
3
- import type { NotificationServiceWranglerConfig } from '../@types'
4
-
5
- export function defineNotificationServiceWrangler(
6
- config: NotificationServiceWranglerConfig,
7
- ) {
8
- const { name, envs } = config
9
-
10
- const base = {
11
- name,
12
- main: './src/index.ts',
13
- compatibility_date: '2025-06-04',
14
- compatibility_flags: ['nodejs_compat'],
15
- d1_databases: [
16
- {
17
- binding: 'NOTIFICATION_D1',
18
- database_name: name,
19
- database_id: envs.local.d1.id,
20
- migrations_dir: './src/database/migrations',
21
- },
22
- ],
23
- queues: {
24
- producers: [
25
- {
26
- binding: 'NOTIFICATIONS_QUEUE',
27
- queue: `${name}`,
28
- },
29
- ],
30
- consumers: [
31
- {
32
- queue: `${name}`,
33
- max_batch_size: 1,
34
- max_batch_timeout: 5,
35
- dead_letter_queue: `${name}-dlq`,
36
- },
37
- ],
38
- },
39
- env: {} as Record<string, any>,
40
- }
41
-
42
- for (const [envName, envCfg] of Object.entries(envs).filter(
43
- ([key]) => key !== 'local',
44
- )) {
45
- base.env[envName] = {
46
- vars: {
47
- ...envCfg.vars,
48
- ENVIRONMENT: envName,
49
- },
50
- d1_databases: [
51
- {
52
- binding: 'NOTIFICATION_D1',
53
- database_name: `${name}-${envName}`,
54
- database_id: envCfg.d1.id,
55
- migrations_dir: './src/database/migrations',
56
- },
57
- ],
58
- queues: {
59
- producers: [
60
- {
61
- binding: 'NOTIFICATIONS_QUEUE',
62
- queue: `${name}-${envName}`,
63
- },
64
- ],
65
- consumers: [
66
- {
67
- queue: `${name}-${envName}`,
68
- max_batch_size: envCfg.queue?.max_batch_size ?? 1,
69
- max_batch_timeout: envCfg.queue?.max_batch_timeout ?? 5,
70
- dead_letter_queue: `${name}-dlq-${envName}`,
71
- },
72
- ],
73
- },
74
- }
75
- }
76
-
77
- return base
78
- }
package/src/index.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineNotificationService } from './defineNotificationService'
2
- import { defineNotificationServiceWrangler } from './defineNotificationWrangler'
3
- import type { WorkerEntrypoint } from 'cloudflare:workers'
4
-
5
- const NotificationService = defineNotificationService()
6
-
7
- export default NotificationService as new (
8
- ctx: ExecutionContext,
9
- env: NotificationEnv,
10
- ) => WorkerEntrypoint<NotificationEnv>
11
-
12
- export { defineNotificationService, defineNotificationServiceWrangler }
@@ -1 +0,0 @@
1
- export const initiateConnector = () => {}
@@ -1,34 +0,0 @@
1
- import { uuidv4 } from '@develit-io/backend-sdk'
2
- import type { AuditLogInsertType } from '../../../../@types'
3
- import { tables } from '../../../database/drizzle'
4
- import type { DrizzleD1Database } from 'drizzle-orm/d1'
5
-
6
- export const createAuditLogCommand = async ({
7
- db,
8
- auditLog: {
9
- event,
10
- description,
11
- initiatorService,
12
- initiatorUserId,
13
- ip,
14
- userAgent,
15
- },
16
- }: {
17
- db: DrizzleD1Database<typeof tables>
18
- auditLog: AuditLogInsertType
19
- }) => {
20
- const command = db.insert(tables.auditLog).values({
21
- id: uuidv4(),
22
- createdAt: new Date(),
23
- event,
24
- description,
25
- ip,
26
- userAgent,
27
- initiatorService,
28
- initiatorUserId,
29
- })
30
-
31
- return {
32
- command,
33
- }
34
- }
@@ -1 +0,0 @@
1
- export * from './create-audit-log.command'
@@ -1 +0,0 @@
1
- export * from './command'
@@ -1,25 +0,0 @@
1
- import { createInternalError } from '@develit-io/backend-sdk'
2
- import {
3
- EcomailConnector,
4
- type IContact,
5
- type IEmailConnector,
6
- } from '../../@types'
7
-
8
- export const initiateEmailConnector = async (
9
- provider: string,
10
- apiKey: string,
11
- smtpHost: string,
12
- sender: IContact,
13
- ): Promise<IEmailConnector> => {
14
- const connector = [EcomailConnector].find(
15
- (conn) => conn.providerName === provider,
16
- )
17
-
18
- if (!connector)
19
- throw createInternalError(null, {
20
- message: 'Unsupported email provider',
21
- status: 404,
22
- })
23
-
24
- return new connector({ API_KEY: apiKey, SMTP_HOST: smtpHost, SENDER: sender })
25
- }
@@ -1,3 +0,0 @@
1
- export * from './email'
2
- export * from './database'
3
- export * from './sms'
package/src/utils/sms.ts DELETED
@@ -1,25 +0,0 @@
1
- import { createInternalError } from '@develit-io/backend-sdk'
2
- import { type ISmsConnector, TwilioConnector } from '../../@types'
3
-
4
- export const initiateSmsConnector = async (
5
- provider: string,
6
- accountId: string,
7
- authToken: string,
8
- serviceId: string,
9
- ): Promise<ISmsConnector> => {
10
- const connector = [TwilioConnector].find(
11
- (conn) => conn.providerName === provider,
12
- )
13
-
14
- if (!connector)
15
- throw createInternalError(null, {
16
- message: 'Unsupported sms provider',
17
- status: 404,
18
- })
19
-
20
- return new connector({
21
- ACCOUNT_ID: accountId,
22
- AUTH_TOKEN: authToken,
23
- SERVICE_ID: serviceId,
24
- })
25
- }
package/test/env.d.ts DELETED
@@ -1,7 +0,0 @@
1
- declare module 'cloudflare:test' {
2
- interface ProvidedEnv extends NotificationEnv {
3
- MIGRATIONS: D1Migration[]
4
- }
5
-
6
- export const SELF: Service<import('../src/index').default>
7
- }
@@ -1,88 +0,0 @@
1
- // import { SELF } from 'cloudflare:test'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- describe('integration placeholder', () => {
5
- it('should pass', () => {
6
- expect(true).toBe(true)
7
- })
8
- })
9
-
10
- // // class MockEmailConnector extends IEmailConnector {
11
- // // constructor() {
12
- // // super({
13
- // // API_KEY: 'mock_api_key',
14
- // // SMTP_HOST: 'mock_smtp_host',
15
- // // SENDER: { email: 'sender@example.com', name: 'Sender Name' } as IContact,
16
- // // })
17
- // // }
18
-
19
- // // async sendEmail(email: IEmail): Promise<Response | null> {
20
- // // // Mock implementation for success case
21
-
22
- // // return Promise.resolve(new Response('Email sent.', { status: 200 }))
23
- // // }
24
- // // }
25
-
26
- // describe('sendEmail', () => {
27
- // // beforeEach(() => {
28
- // // vi.resetModules()
29
- // // vi.doUnmock('../../@types/email/IEmail.connector')
30
- // // })
31
- // describe('should return a successful response when', () => {
32
- // it('the input is valid', async () => {
33
- // const { status, message, data, error } = await SELF.sendEmail({
34
- // email: {
35
- // to: 'anna@example.com',
36
- // replyTo: 'anna@example.com',
37
- // cc: 'manager@example.com',
38
- // bcc: 'admin@example.com',
39
- // from: 'ben@example.com',
40
- // subject: 'Meeting Reminder',
41
- // text: 'Hi! Meeting tomorrow!',
42
- // html: undefined,
43
- // templateId: 0,
44
- // templateVariables: {},
45
- // },
46
- // metadata: {
47
- // userAgent: 'v8',
48
- // initiator: {
49
- // service: 'test',
50
- // },
51
- // },
52
- // })
53
-
54
- // expect(error).toBeFalsy()
55
- // expect(status).toEqual(200)
56
- // expect(message).toEqual('Email sent.')
57
- // expect(data).toEqual({})
58
- // })
59
- // })
60
-
61
- // // describe('should return an error when', () => {
62
- // // it('the input is valid', async () => {
63
- // // vi.doMock('../../@types/email/IEmail.connector', () => ({
64
- // // sendEmail: vi.fn().mockRejectedValueOnce(new Error('Error mock')),
65
- // // }))
66
-
67
- // // const { status, message, data, error } = await SELF.sendEmail({
68
- // // email: {
69
- // // to: 'anna@example.com',
70
- // // replyTo: 'anna@example.com',
71
- // // cc: 'manager@example.com',
72
- // // bcc: 'admin@example.com',
73
- // // from: 'ben@example.com',
74
- // // subject: 'Meeting Reminder',
75
- // // text: 'Hi! Meeting tomorrow!',
76
- // // html: undefined,
77
- // // templateId: 0,
78
- // // templateVariables: {},
79
- // // },
80
- // // })
81
-
82
- // // expect(error).toBeTruthy()
83
- // // expect(status).toEqual(500)
84
- // // expect(message).toEqual('')
85
- // // expect(data).toEqual(null)
86
- // // })
87
- // // })
88
- // })
@@ -1,3 +0,0 @@
1
- import { applyD1Migrations, env } from 'cloudflare:test'
2
-
3
- await applyD1Migrations(env.NOTIFICATION_D1, env.MIGRATIONS)