alepha 0.13.2 → 0.13.4

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 (91) hide show
  1. package/README.md +7 -7
  2. package/dist/api-files/index.browser.js +80 -0
  3. package/dist/api-files/index.browser.js.map +1 -0
  4. package/dist/api-files/index.d.ts +175 -175
  5. package/dist/api-jobs/index.browser.js +56 -0
  6. package/dist/api-jobs/index.browser.js.map +1 -0
  7. package/dist/api-jobs/index.d.ts +156 -156
  8. package/dist/api-notifications/index.browser.js +382 -0
  9. package/dist/api-notifications/index.browser.js.map +1 -0
  10. package/dist/api-notifications/index.d.ts +221 -166
  11. package/dist/api-notifications/index.js +107 -55
  12. package/dist/api-notifications/index.js.map +1 -1
  13. package/dist/api-parameters/index.browser.js +29 -0
  14. package/dist/api-parameters/index.browser.js.map +1 -0
  15. package/dist/api-users/index.d.ts +16 -3
  16. package/dist/api-users/index.js +75 -28
  17. package/dist/api-users/index.js.map +1 -1
  18. package/dist/api-verifications/index.browser.js +52 -0
  19. package/dist/api-verifications/index.browser.js.map +1 -0
  20. package/dist/api-verifications/index.d.ts +120 -98
  21. package/dist/api-verifications/index.js +1 -1
  22. package/dist/api-verifications/index.js.map +1 -1
  23. package/dist/batch/index.js +0 -5
  24. package/dist/batch/index.js.map +1 -1
  25. package/dist/bucket/index.js +7 -5
  26. package/dist/bucket/index.js.map +1 -1
  27. package/dist/cli/{dist-Dl9Vl7Ur.js → dist-lGnqsKpu.js} +11 -15
  28. package/dist/cli/dist-lGnqsKpu.js.map +1 -0
  29. package/dist/cli/index.d.ts +26 -45
  30. package/dist/cli/index.js +40 -58
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/command/index.d.ts +1 -0
  33. package/dist/command/index.js +9 -0
  34. package/dist/command/index.js.map +1 -1
  35. package/dist/core/index.browser.js +5 -1
  36. package/dist/core/index.browser.js.map +1 -1
  37. package/dist/core/index.d.ts +221 -219
  38. package/dist/core/index.js +5 -1
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/index.native.js +5 -1
  41. package/dist/core/index.native.js.map +1 -1
  42. package/dist/email/index.d.ts +4 -4
  43. package/dist/email/index.js +5 -0
  44. package/dist/email/index.js.map +1 -1
  45. package/dist/orm/index.d.ts +19 -19
  46. package/dist/orm/index.js +3 -3
  47. package/dist/orm/index.js.map +1 -1
  48. package/dist/redis/index.d.ts +10 -10
  49. package/dist/security/index.d.ts +28 -28
  50. package/dist/security/index.js +3 -3
  51. package/dist/security/index.js.map +1 -1
  52. package/dist/server/index.d.ts +9 -9
  53. package/dist/server-auth/index.d.ts +152 -152
  54. package/dist/server-cookies/index.js +2 -2
  55. package/dist/server-cookies/index.js.map +1 -1
  56. package/dist/server-links/index.d.ts +33 -33
  57. package/dist/server-security/index.d.ts +9 -9
  58. package/dist/server-static/index.js +18 -2
  59. package/dist/server-static/index.js.map +1 -1
  60. package/package.json +16 -6
  61. package/src/api-files/index.browser.ts +17 -0
  62. package/src/api-jobs/index.browser.ts +15 -0
  63. package/src/api-notifications/controllers/NotificationController.ts +26 -1
  64. package/src/api-notifications/index.browser.ts +17 -0
  65. package/src/api-notifications/index.ts +1 -0
  66. package/src/api-notifications/schemas/notificationQuerySchema.ts +13 -0
  67. package/src/api-notifications/services/NotificationService.ts +45 -2
  68. package/src/api-parameters/index.browser.ts +12 -0
  69. package/src/api-users/atoms/realmAuthSettingsAtom.ts +3 -1
  70. package/src/api-users/controllers/UserController.ts +21 -1
  71. package/src/api-users/primitives/$userRealm.ts +33 -10
  72. package/src/api-users/providers/UserRealmProvider.ts +1 -0
  73. package/src/api-users/services/SessionService.ts +2 -0
  74. package/src/api-users/services/UserService.ts +56 -16
  75. package/src/api-verifications/index.browser.ts +15 -0
  76. package/src/api-verifications/index.ts +1 -0
  77. package/src/batch/providers/BatchProvider.ts +0 -7
  78. package/src/bucket/index.ts +7 -5
  79. package/src/cli/apps/AlephaCli.ts +27 -1
  80. package/src/cli/apps/AlephaPackageBuilderCli.ts +3 -0
  81. package/src/cli/commands/CoreCommands.ts +6 -2
  82. package/src/cli/commands/ViteCommands.ts +2 -1
  83. package/src/cli/services/ProjectUtils.ts +40 -75
  84. package/src/command/helpers/Asker.ts +10 -0
  85. package/src/core/Alepha.ts +14 -0
  86. package/src/core/primitives/$module.ts +1 -1
  87. package/src/email/index.ts +13 -5
  88. package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -3
  89. package/src/server-cookies/providers/ServerCookiesProvider.ts +2 -1
  90. package/src/server-static/providers/ServerStaticProvider.ts +18 -3
  91. package/dist/cli/dist-Dl9Vl7Ur.js.map +0 -1
@@ -0,0 +1,382 @@
1
+ import { $env, $inject, $module, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
2
+ import { $entity, $repository, pageQuerySchema, pg } from "alepha/orm";
3
+ import { $action } from "alepha/server";
4
+ import { $batch } from "alepha/batch";
5
+ import { DateTimeProvider } from "alepha/datetime";
6
+ import { $logger } from "alepha/logger";
7
+ import { $queue } from "alepha/queue";
8
+ import { EmailProvider } from "alepha/email";
9
+ import { SmsProvider } from "alepha/sms";
10
+
11
+ //#region src/api-notifications/entities/notifications.ts
12
+ const notifications = $entity({
13
+ name: "notifications",
14
+ schema: t.object({
15
+ id: pg.primaryKey(t.uuid()),
16
+ version: pg.version(),
17
+ createdAt: pg.createdAt(),
18
+ updatedAt: pg.updatedAt(),
19
+ type: t.enum(["email", "sms"]),
20
+ template: t.text(),
21
+ category: t.optional(t.text({ description: "For grouping related notifications (e.g., 'authentication', 'marketing'). Contact can filter notifications by category." })),
22
+ critical: t.optional(t.boolean({ description: "Prioritize delivery of this notification. Set to true for important system alerts." })),
23
+ sensitive: t.optional(t.boolean({ description: "Message won't be logged or stored in plain text. Set to true when notification contains passwords or codes." })),
24
+ contact: t.text(),
25
+ variables: t.optional(t.record(t.text(), t.any())),
26
+ scheduledAt: t.optional(t.datetime({ description: "When set, the notification will be sent at or after this date/time." })),
27
+ sentAt: t.optional(t.datetime()),
28
+ error: t.optional(t.object({
29
+ at: t.datetime(),
30
+ name: t.text(),
31
+ message: t.text({ size: "rich" })
32
+ }))
33
+ })
34
+ });
35
+
36
+ //#endregion
37
+ //#region src/api-notifications/schemas/notificationQuerySchema.ts
38
+ const notificationQuerySchema = t.extend(pageQuerySchema, {
39
+ type: t.optional(t.enum(["email", "sms"])),
40
+ template: t.optional(t.string()),
41
+ contact: t.optional(t.string()),
42
+ category: t.optional(t.string()),
43
+ status: t.optional(t.enum([
44
+ "pending",
45
+ "sent",
46
+ "failed"
47
+ ]))
48
+ });
49
+
50
+ //#endregion
51
+ //#region src/api-notifications/primitives/$notification.ts
52
+ /**
53
+ * Creates a notification primitive for managing email/SMS notification templates.
54
+ *
55
+ * Provides type-safe, reusable notification templates with multi-language support,
56
+ * variable substitution, and categorization for different notification channels.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * class NotificationTemplates {
61
+ * welcomeEmail = $notification({
62
+ * name: "welcome-email",
63
+ * category: "onboarding",
64
+ * schema: t.object({ username: t.text(), activationLink: t.text() }),
65
+ * email: {
66
+ * subject: "Welcome to our platform!",
67
+ * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`
68
+ * }
69
+ * });
70
+ *
71
+ * async sendWelcome(user: User) {
72
+ * await this.welcomeEmail.push({
73
+ * variables: { username: user.name, activationLink: generateLink() },
74
+ * contact: user.email
75
+ * });
76
+ * }
77
+ * }
78
+ * ```
79
+ */
80
+ const $notification = (options) => createPrimitive(NotificationPrimitive, options);
81
+ var NotificationPrimitive = class extends Primitive {
82
+ notificationService = $inject(NotificationService);
83
+ get name() {
84
+ return this.options.name ?? `${this.config.propertyKey}`;
85
+ }
86
+ async push(options) {
87
+ if (this.options.email) await this.notificationService.createNotification({
88
+ ...options,
89
+ type: "email",
90
+ template: this.name
91
+ });
92
+ }
93
+ configure(options) {
94
+ Object.assign(this.options, options);
95
+ }
96
+ };
97
+ $notification[KIND] = NotificationPrimitive;
98
+
99
+ //#endregion
100
+ //#region src/api-notifications/services/NotificationSenderService.ts
101
+ var NotificationSenderService = class {
102
+ alepha = $inject(Alepha);
103
+ log = $logger();
104
+ notificationRepository = $repository(notifications);
105
+ dateTimeProvider = $inject(DateTimeProvider);
106
+ emailProvider = $inject(EmailProvider);
107
+ smsProvider = $inject(SmsProvider);
108
+ async send(notificationId) {
109
+ this.log.trace("Sending notification", { notificationId: typeof notificationId === "string" ? notificationId : notificationId.id });
110
+ const notification = typeof notificationId === "string" ? await this.notificationRepository.findById(notificationId) : notificationId;
111
+ if (notification.sentAt) {
112
+ this.log.debug("Notification already sent", {
113
+ notificationId: notification.id,
114
+ sentAt: notification.sentAt
115
+ });
116
+ return;
117
+ }
118
+ this.log.debug("Processing notification", {
119
+ id: notification.id,
120
+ type: notification.type,
121
+ template: notification.template,
122
+ contact: notification.contact
123
+ });
124
+ try {
125
+ if (notification.type === "email") {
126
+ await this.emailProvider.send(this.renderEmail(notification));
127
+ notification.sentAt = this.dateTimeProvider.nowISOString();
128
+ this.log.info("Email notification sent", {
129
+ id: notification.id,
130
+ template: notification.template,
131
+ contact: notification.contact
132
+ });
133
+ }
134
+ if (notification.type === "sms") {
135
+ await this.smsProvider.send(this.renderSms(notification));
136
+ notification.sentAt = this.dateTimeProvider.nowISOString();
137
+ this.log.info("SMS notification sent", {
138
+ id: notification.id,
139
+ template: notification.template,
140
+ contact: notification.contact
141
+ });
142
+ }
143
+ } catch (e) {
144
+ this.log.error("Failed to send notification", {
145
+ id: notification.id,
146
+ type: notification.type,
147
+ template: notification.template,
148
+ contact: notification.contact,
149
+ error: e
150
+ });
151
+ if (e instanceof Error) notification.error = {
152
+ at: this.dateTimeProvider.nowISOString(),
153
+ name: e.name,
154
+ message: e.message
155
+ };
156
+ } finally {
157
+ await this.notificationRepository.save(notification);
158
+ }
159
+ }
160
+ renderSms(notification) {
161
+ this.log.trace("Rendering SMS notification", {
162
+ id: notification.id,
163
+ template: notification.template
164
+ });
165
+ const { variables, contact, template } = this.load(notification);
166
+ const sms = template.options.sms;
167
+ if (!sms) {
168
+ this.log.error("Notification template has no SMS defined", {
169
+ id: notification.id,
170
+ template: notification.template
171
+ });
172
+ throw new AlephaError(`Notification template ${notification.template} has no sms defined`);
173
+ }
174
+ this.log.debug("Rendering SMS", {
175
+ template: notification.template,
176
+ contact
177
+ });
178
+ return {
179
+ to: contact,
180
+ message: typeof sms.message === "function" ? sms.message(variables) : sms.message
181
+ };
182
+ }
183
+ renderEmail(notification) {
184
+ this.log.trace("Rendering email notification", {
185
+ id: notification.id,
186
+ template: notification.template
187
+ });
188
+ const { variables, contact, template } = this.load(notification);
189
+ const email = template.options.email;
190
+ if (!email) {
191
+ this.log.error("Notification template has no email defined", {
192
+ id: notification.id,
193
+ template: notification.template
194
+ });
195
+ throw new AlephaError(`Notification template ${notification.template} has no email defined`);
196
+ }
197
+ this.log.debug("Rendering email", {
198
+ template: notification.template,
199
+ contact,
200
+ subject: email.subject
201
+ });
202
+ return {
203
+ to: contact,
204
+ subject: email.subject,
205
+ body: typeof email.body === "function" ? email.body(variables) : email.body
206
+ };
207
+ }
208
+ load(notification) {
209
+ const variables = notification.variables || {};
210
+ const contact = notification.contact;
211
+ const template = this.alepha.primitives($notification).find((it) => it.name === notification.template);
212
+ if (!template) {
213
+ this.log.error("Notification template not found", {
214
+ id: notification.id,
215
+ template: notification.template
216
+ });
217
+ throw new AlephaError(`No notification template found for ${notification.template}`);
218
+ }
219
+ return {
220
+ template,
221
+ variables,
222
+ contact
223
+ };
224
+ }
225
+ };
226
+
227
+ //#endregion
228
+ //#region src/api-notifications/queues/NotificationQueues.ts
229
+ var NotificationQueues = class {
230
+ notificationSenderService = $inject(NotificationSenderService);
231
+ processNotification = $queue({
232
+ description: "Queue for processing notifications",
233
+ schema: t.object({ notificationId: t.string({ format: "uuid" }) }),
234
+ handler: async (message) => {
235
+ await this.notificationSenderService.send(message.payload.notificationId);
236
+ }
237
+ });
238
+ };
239
+
240
+ //#endregion
241
+ //#region src/api-notifications/schemas/notificationCreateSchema.ts
242
+ const notificationCreateSchema = t.pick(notifications.schema, [
243
+ "type",
244
+ "contact",
245
+ "template",
246
+ "variables"
247
+ ]);
248
+
249
+ //#endregion
250
+ //#region src/api-notifications/services/NotificationService.ts
251
+ const notificationServiceEnvSchema = t.object({ NOTIFICATION_QUEUE: t.optional(t.boolean({ description: "If true, notifications will be queued instead of sent immediately" })) });
252
+ var NotificationService = class {
253
+ alepha = $inject(Alepha);
254
+ log = $logger();
255
+ env = $env(notificationServiceEnvSchema);
256
+ notificationRepository = $repository(notifications);
257
+ dateTimeProvider = $inject(DateTimeProvider);
258
+ notificationSenderService = $inject(NotificationSenderService);
259
+ notificationBatch = $batch({
260
+ maxSize: 100,
261
+ maxDuration: [15, "seconds"],
262
+ schema: notificationCreateSchema,
263
+ handler: async (notifications$1) => {
264
+ this.log.debug("Processing notification batch", {
265
+ size: notifications$1.length,
266
+ templates: [...new Set(notifications$1.map((n) => n.template))]
267
+ });
268
+ const entities = await this.notificationRepository.createMany(notifications$1);
269
+ await this.alepha.inject(NotificationQueues).processNotification.push(...entities.map((it) => ({ notificationId: it.id })));
270
+ this.log.info("Notification batch queued", {
271
+ count: entities.length,
272
+ ids: entities.map((it) => it.id)
273
+ });
274
+ }
275
+ });
276
+ async findNotificationById(id) {
277
+ this.log.trace("Finding notification by ID", { id });
278
+ return this.notificationRepository.findOne({ where: { id } });
279
+ }
280
+ async findNotifications(q = {}) {
281
+ this.log.trace("Finding notifications", { query: q });
282
+ q.sort ??= "-createdAt";
283
+ const where = this.notificationRepository.createQueryWhere();
284
+ if (q.type) where.type = { eq: q.type };
285
+ if (q.template) where.template = { like: `%${q.template}%` };
286
+ if (q.contact) where.contact = { like: `%${q.contact}%` };
287
+ if (q.category) where.category = { eq: q.category };
288
+ if (q.status) {
289
+ if (q.status === "sent") {
290
+ where.sentAt = { isNotNull: true };
291
+ where.error = { isNull: true };
292
+ } else if (q.status === "failed") where.error = { isNotNull: true };
293
+ else if (q.status === "pending") {
294
+ where.sentAt = { isNull: true };
295
+ where.error = { isNull: true };
296
+ }
297
+ }
298
+ return this.notificationRepository.paginate(q, { where }, { count: true });
299
+ }
300
+ /**
301
+ * Create a new notification.
302
+ */
303
+ async createNotification(entry) {
304
+ this.log.trace("Creating notification", {
305
+ template: entry.template,
306
+ type: entry.type,
307
+ contact: entry.contact
308
+ });
309
+ if (this.env.NOTIFICATION_QUEUE !== true || this.alepha.isServerless() || this.alepha.isTest()) {
310
+ this.log.debug("Sending notification immediately", {
311
+ template: entry.template,
312
+ type: entry.type,
313
+ contact: entry.contact
314
+ });
315
+ const notification = await this.notificationRepository.create(entry);
316
+ await this.notificationSenderService.send(notification);
317
+ return;
318
+ }
319
+ this.log.debug("Queuing notification to batch", {
320
+ template: entry.template,
321
+ type: entry.type,
322
+ contact: entry.contact
323
+ });
324
+ this.notificationBatch.push(entry).catch((e) => {
325
+ this.log.error("Failed to push notification to batch", {
326
+ template: entry.template,
327
+ type: entry.type,
328
+ contact: entry.contact,
329
+ error: e
330
+ });
331
+ });
332
+ }
333
+ };
334
+
335
+ //#endregion
336
+ //#region src/api-notifications/controllers/NotificationController.ts
337
+ var NotificationController = class {
338
+ url = "/notifications";
339
+ group = "notifications";
340
+ notificationService = $inject(NotificationService);
341
+ /**
342
+ * Find notifications with pagination and filtering.
343
+ */
344
+ findNotifications = $action({
345
+ path: this.url,
346
+ group: this.group,
347
+ description: "Find notifications with pagination and filtering",
348
+ schema: {
349
+ query: notificationQuerySchema,
350
+ response: pg.page(notifications.schema)
351
+ },
352
+ handler: ({ query }) => this.notificationService.findNotifications(query)
353
+ });
354
+ };
355
+
356
+ //#endregion
357
+ //#region src/api-notifications/schemas/notificationContactPreferencesSchema.ts
358
+ const notificationContactPreferencesSchema = t.object({
359
+ language: t.optional(t.text()),
360
+ exclude: t.array(t.text())
361
+ });
362
+
363
+ //#endregion
364
+ //#region src/api-notifications/schemas/notificationContactSchema.ts
365
+ const notificationContactSchema = t.object({
366
+ email: t.optional(t.email()),
367
+ phoneNumber: t.optional(t.e164()),
368
+ firstName: t.optional(t.shortText()),
369
+ lastName: t.optional(t.text({ size: "short" })),
370
+ language: t.optional(t.bcp47())
371
+ });
372
+
373
+ //#endregion
374
+ //#region src/api-notifications/index.browser.ts
375
+ const AlephaApiNotifications = $module({
376
+ name: "alepha.api.notifications",
377
+ services: []
378
+ });
379
+
380
+ //#endregion
381
+ export { AlephaApiNotifications, NotificationController, notificationContactPreferencesSchema, notificationContactSchema, notificationCreateSchema, notificationQuerySchema, notifications };
382
+ //# sourceMappingURL=index.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.js","names":["notifications"],"sources":["../../src/api-notifications/entities/notifications.ts","../../src/api-notifications/schemas/notificationQuerySchema.ts","../../src/api-notifications/primitives/$notification.ts","../../src/api-notifications/services/NotificationSenderService.ts","../../src/api-notifications/queues/NotificationQueues.ts","../../src/api-notifications/schemas/notificationCreateSchema.ts","../../src/api-notifications/services/NotificationService.ts","../../src/api-notifications/controllers/NotificationController.ts","../../src/api-notifications/schemas/notificationContactPreferencesSchema.ts","../../src/api-notifications/schemas/notificationContactSchema.ts","../../src/api-notifications/index.browser.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\nimport { $entity, pg } from \"alepha/orm\";\n\nexport const notifications = $entity({\n name: \"notifications\",\n schema: t.object({\n id: pg.primaryKey(t.uuid()),\n\n version: pg.version(),\n\n createdAt: pg.createdAt(),\n\n updatedAt: pg.updatedAt(),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n type: t.enum([\"email\", \"sms\"]),\n\n template: t.text(), // e.g. 'resetPassword'\n\n category: t.optional(\n t.text({\n description:\n \"For grouping related notifications (e.g., 'authentication', 'marketing'). Contact can filter notifications by category.\",\n }),\n ),\n\n critical: t.optional(\n t.boolean({\n description:\n \"Prioritize delivery of this notification. Set to true for important system alerts.\",\n }),\n ),\n\n sensitive: t.optional(\n t.boolean({\n description:\n \"Message won't be logged or stored in plain text. Set to true when notification contains passwords or codes.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n contact: t.text(), // e.g. email address or phone number or user ID or whatever\n\n variables: t.optional(t.record(t.text(), t.any())),\n\n scheduledAt: t.optional(\n t.datetime({\n description:\n \"When set, the notification will be sent at or after this date/time.\",\n }),\n ),\n\n // -----------------------------------------------------------------------------------------------------------------\n\n sentAt: t.optional(t.datetime()),\n\n error: t.optional(\n t.object({\n at: t.datetime(),\n name: t.text(),\n message: t.text({ size: \"rich\" }),\n }),\n ),\n\n // TODO: retryCount, lastRetryAt, etc.\n }),\n});\n\nexport type NotificationEntity = Static<typeof notifications.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const notificationQuerySchema = t.extend(pageQuerySchema, {\n type: t.optional(t.enum([\"email\", \"sms\"])),\n template: t.optional(t.string()),\n contact: t.optional(t.string()),\n category: t.optional(t.string()),\n status: t.optional(t.enum([\"pending\", \"sent\", \"failed\"])),\n});\n\nexport type NotificationQuery = Static<typeof notificationQuerySchema>;\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type StaticEncode,\n type TObject,\n} from \"alepha\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\n/**\n * Creates a notification primitive for managing email/SMS notification templates.\n *\n * Provides type-safe, reusable notification templates with multi-language support,\n * variable substitution, and categorization for different notification channels.\n *\n * @example\n * ```ts\n * class NotificationTemplates {\n * welcomeEmail = $notification({\n * name: \"welcome-email\",\n * category: \"onboarding\",\n * schema: t.object({ username: t.text(), activationLink: t.text() }),\n * email: {\n * subject: \"Welcome to our platform!\",\n * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`\n * }\n * });\n *\n * async sendWelcome(user: User) {\n * await this.welcomeEmail.push({\n * variables: { username: user.name, activationLink: generateLink() },\n * contact: user.email\n * });\n * }\n * }\n * ```\n */\nexport const $notification = <T extends TObject>(\n options: NotificationPrimitiveOptions<T>,\n) => createPrimitive(NotificationPrimitive<T>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPrimitiveOptions<T extends TObject>\n extends NotificationMessage<T> {\n name?: string;\n description?: string;\n category?: string;\n critical?: boolean;\n sensitive?: boolean;\n translations?: {\n // e.g., \"en\", \"fr\", even \"en-US\"\n [lang: string]: NotificationMessage<T>;\n };\n schema: T;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NotificationPrimitive<T extends TObject> extends Primitive<\n NotificationPrimitiveOptions<T>\n> {\n protected readonly notificationService = $inject(NotificationService);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n public async push(options: NotificationPushOptions<T>) {\n if (this.options.email) {\n await this.notificationService.createNotification({\n ...options,\n type: \"email\",\n template: this.name,\n });\n }\n }\n\n public configure(options: Partial<NotificationPrimitiveOptions<T>>) {\n Object.assign(this.options, options);\n }\n}\n\n$notification[KIND] = NotificationPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPushOptions<T extends TObject> {\n variables: StaticEncode<T>;\n contact: string;\n}\n\nexport interface NotificationMessage<T extends TObject> {\n email?: {\n subject: string;\n body: string | ((variables: Static<T>) => string);\n };\n sms?: {\n message: string | ((variables: Static<T>) => string);\n };\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { EmailProvider } from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { SmsProvider } from \"alepha/sms\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { $notification } from \"../primitives/$notification.ts\";\n\nexport class NotificationSenderService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly emailProvider = $inject(EmailProvider);\n protected readonly smsProvider = $inject(SmsProvider);\n\n public async send(notificationId: string | NotificationEntity) {\n this.log.trace(\"Sending notification\", {\n notificationId:\n typeof notificationId === \"string\" ? notificationId : notificationId.id,\n });\n\n const notification =\n typeof notificationId === \"string\"\n ? await this.notificationRepository.findById(notificationId)\n : notificationId;\n\n if (notification.sentAt) {\n this.log.debug(\"Notification already sent\", {\n notificationId: notification.id,\n sentAt: notification.sentAt,\n });\n return;\n }\n\n this.log.debug(\"Processing notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n });\n\n try {\n if (notification.type === \"email\") {\n await this.emailProvider.send(this.renderEmail(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"Email notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n if (notification.type === \"sms\") {\n await this.smsProvider.send(this.renderSms(notification));\n notification.sentAt = this.dateTimeProvider.nowISOString();\n this.log.info(\"SMS notification sent\", {\n id: notification.id,\n template: notification.template,\n contact: notification.contact,\n });\n }\n } catch (e) {\n this.log.error(\"Failed to send notification\", {\n id: notification.id,\n type: notification.type,\n template: notification.template,\n contact: notification.contact,\n error: e,\n });\n if (e instanceof Error) {\n notification.error = {\n at: this.dateTimeProvider.nowISOString(),\n name: e.name,\n message: e.message,\n };\n }\n } finally {\n await this.notificationRepository.save(notification);\n }\n }\n\n public renderSms(notification: NotificationEntity) {\n this.log.trace(\"Rendering SMS notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const sms = template.options.sms;\n if (!sms) {\n this.log.error(\"Notification template has no SMS defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no sms defined`,\n );\n }\n\n this.log.debug(\"Rendering SMS\", {\n template: notification.template,\n contact,\n });\n\n const message =\n typeof sms.message === \"function\"\n ? sms.message(variables as any)\n : sms.message;\n\n return {\n to: contact,\n message,\n };\n }\n\n public renderEmail(notification: NotificationEntity) {\n this.log.trace(\"Rendering email notification\", {\n id: notification.id,\n template: notification.template,\n });\n\n const { variables, contact, template } = this.load(notification);\n\n const email = template.options.email;\n if (!email) {\n this.log.error(\"Notification template has no email defined\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `Notification template ${notification.template} has no email defined`,\n );\n }\n\n this.log.debug(\"Rendering email\", {\n template: notification.template,\n contact,\n subject: email.subject,\n });\n\n const subject = email.subject;\n\n const body =\n typeof email.body === \"function\"\n ? email.body(variables as any)\n : email.body;\n\n return {\n to: contact,\n subject,\n body,\n };\n }\n\n protected load(notification: NotificationEntity) {\n const variables = notification.variables || {};\n const contact = notification.contact;\n const template = this.alepha\n .primitives($notification)\n .find((it) => it.name === notification.template);\n\n if (!template) {\n this.log.error(\"Notification template not found\", {\n id: notification.id,\n template: notification.template,\n });\n throw new AlephaError(\n `No notification template found for ${notification.template}`,\n );\n }\n\n return {\n template,\n variables,\n contact,\n };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $queue } from \"alepha/queue\";\nimport { NotificationSenderService } from \"../services/NotificationSenderService.ts\";\n\nexport class NotificationQueues {\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly processNotification = $queue({\n description: \"Queue for processing notifications\",\n schema: t.object({\n notificationId: t.string({ format: \"uuid\" }),\n }),\n handler: async (message) => {\n await this.notificationSenderService.send(message.payload.notificationId);\n },\n });\n}\n","import { type Static, t } from \"alepha\";\nimport { notifications } from \"../entities/notifications.ts\";\n\nexport const notificationCreateSchema = t.pick(notifications.schema, [\n \"type\",\n \"contact\",\n \"template\",\n \"variables\",\n]);\n\nexport type NotificationCreate = Static<typeof notificationCreateSchema>;\n","import { $env, $inject, Alepha, type Static, t } from \"alepha\";\nimport { $batch } from \"alepha/batch\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository, type Page } from \"alepha/orm\";\nimport {\n type NotificationEntity,\n notifications,\n} from \"../entities/notifications.ts\";\nimport { NotificationQueues } from \"../queues/NotificationQueues.ts\";\nimport {\n type NotificationCreate,\n notificationCreateSchema,\n} from \"../schemas/notificationCreateSchema.ts\";\nimport type { NotificationQuery } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationSenderService } from \"./NotificationSenderService.ts\";\n\nexport const notificationServiceEnvSchema = t.object({\n NOTIFICATION_QUEUE: t.optional(\n t.boolean({\n description:\n \"If true, notifications will be queued instead of sent immediately\",\n }),\n ),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof notificationServiceEnvSchema>> {}\n}\n\nexport class NotificationService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly env = $env(notificationServiceEnvSchema);\n protected readonly notificationRepository = $repository(notifications);\n protected readonly dateTimeProvider = $inject(DateTimeProvider);\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n\n public readonly notificationBatch = $batch({\n maxSize: 100,\n maxDuration: [15, \"seconds\"],\n schema: notificationCreateSchema,\n handler: async (notifications: NotificationCreate[]) => {\n this.log.debug(\"Processing notification batch\", {\n size: notifications.length,\n templates: [...new Set(notifications.map((n) => n.template))],\n });\n\n const entities =\n await this.notificationRepository.createMany(notifications);\n\n await this.alepha\n .inject(NotificationQueues)\n .processNotification.push(\n ...entities.map((it) => ({ notificationId: it.id })),\n );\n\n this.log.info(\"Notification batch queued\", {\n count: entities.length,\n ids: entities.map((it) => it.id),\n });\n },\n });\n\n public async findNotificationById(id: string) {\n this.log.trace(\"Finding notification by ID\", { id });\n return this.notificationRepository.findOne({ where: { id } });\n }\n\n public async findNotifications(\n q: NotificationQuery = {},\n ): Promise<Page<NotificationEntity>> {\n this.log.trace(\"Finding notifications\", { query: q });\n q.sort ??= \"-createdAt\";\n\n const where = this.notificationRepository.createQueryWhere();\n\n if (q.type) {\n where.type = { eq: q.type };\n }\n\n if (q.template) {\n where.template = { like: `%${q.template}%` };\n }\n\n if (q.contact) {\n where.contact = { like: `%${q.contact}%` };\n }\n\n if (q.category) {\n where.category = { eq: q.category };\n }\n\n if (q.status) {\n if (q.status === \"sent\") {\n where.sentAt = { isNotNull: true };\n where.error = { isNull: true };\n } else if (q.status === \"failed\") {\n where.error = { isNotNull: true };\n } else if (q.status === \"pending\") {\n where.sentAt = { isNull: true };\n where.error = { isNull: true };\n }\n }\n\n return this.notificationRepository.paginate(q, { where }, { count: true });\n }\n\n /**\n * Create a new notification.\n */\n public async createNotification(entry: NotificationCreate): Promise<void> {\n this.log.trace(\"Creating notification\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n if (\n this.env.NOTIFICATION_QUEUE !== true ||\n this.alepha.isServerless() ||\n this.alepha.isTest()\n ) {\n this.log.debug(\"Sending notification immediately\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n const notification = await this.notificationRepository.create(entry);\n await this.notificationSenderService.send(notification);\n return;\n }\n\n this.log.debug(\"Queuing notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n });\n\n this.notificationBatch.push(entry).catch((e) => {\n this.log.error(\"Failed to push notification to batch\", {\n template: entry.template,\n type: entry.type,\n contact: entry.contact,\n error: e,\n });\n });\n }\n}\n","import { $inject } from \"alepha\";\nimport { pg } from \"alepha/orm\";\nimport { $action } from \"alepha/server\";\nimport { notifications } from \"../entities/notifications.ts\";\nimport { notificationQuerySchema } from \"../schemas/notificationQuerySchema.ts\";\nimport { NotificationService } from \"../services/NotificationService.ts\";\n\nexport class NotificationController {\n protected readonly url = \"/notifications\";\n protected readonly group = \"notifications\";\n protected readonly notificationService = $inject(NotificationService);\n\n /**\n * Find notifications with pagination and filtering.\n */\n public readonly findNotifications = $action({\n path: this.url,\n group: this.group,\n description: \"Find notifications with pagination and filtering\",\n schema: {\n query: notificationQuerySchema,\n response: pg.page(notifications.schema),\n },\n handler: ({ query }) => this.notificationService.findNotifications(query),\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactPreferencesSchema = t.object({\n language: t.optional(t.text()),\n exclude: t.array(t.text()),\n});\n\nexport type NotificationContactPreferences = Static<\n typeof notificationContactPreferencesSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.shortText()),\n lastName: t.optional(t.text({ size: \"short\" })),\n language: t.optional(t.bcp47()),\n});\n\nexport type NotificationContact = Static<typeof notificationContactSchema>;\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/NotificationController.ts\";\nexport * from \"./entities/notifications.ts\";\nexport * from \"./schemas/notificationContactPreferencesSchema.ts\";\nexport * from \"./schemas/notificationContactSchema.ts\";\nexport * from \"./schemas/notificationCreateSchema.ts\";\nexport * from \"./schemas/notificationQuerySchema.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaApiNotifications = $module({\n name: \"alepha.api.notifications\",\n services: [],\n});\n"],"mappings":"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"}