@contractspec/module.notifications 3.7.16 → 3.7.17

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 (46) hide show
  1. package/dist/browser/channels/index.js +1 -314
  2. package/dist/browser/contracts/index.js +1 -356
  3. package/dist/browser/entities/index.js +1 -239
  4. package/dist/browser/i18n/catalogs/en.js +1 -68
  5. package/dist/browser/i18n/catalogs/es.js +1 -68
  6. package/dist/browser/i18n/catalogs/fr.js +1 -68
  7. package/dist/browser/i18n/catalogs/index.js +1 -169
  8. package/dist/browser/i18n/index.js +1 -229
  9. package/dist/browser/i18n/keys.js +1 -37
  10. package/dist/browser/i18n/locale.js +1 -29
  11. package/dist/browser/i18n/messages.js +1 -190
  12. package/dist/browser/index.js +7 -1126
  13. package/dist/browser/notifications.capability.js +1 -35
  14. package/dist/browser/notifications.feature.js +1 -50
  15. package/dist/browser/templates/index.js +7 -235
  16. package/dist/channels/index.js +1 -314
  17. package/dist/contracts/index.js +1 -356
  18. package/dist/entities/index.js +1 -239
  19. package/dist/i18n/catalogs/en.js +1 -68
  20. package/dist/i18n/catalogs/es.js +1 -68
  21. package/dist/i18n/catalogs/fr.js +1 -68
  22. package/dist/i18n/catalogs/index.js +1 -169
  23. package/dist/i18n/index.js +1 -229
  24. package/dist/i18n/keys.js +1 -37
  25. package/dist/i18n/locale.js +1 -29
  26. package/dist/i18n/messages.js +1 -190
  27. package/dist/index.js +7 -1126
  28. package/dist/node/channels/index.js +1 -314
  29. package/dist/node/contracts/index.js +1 -356
  30. package/dist/node/entities/index.js +1 -239
  31. package/dist/node/i18n/catalogs/en.js +1 -68
  32. package/dist/node/i18n/catalogs/es.js +1 -68
  33. package/dist/node/i18n/catalogs/fr.js +1 -68
  34. package/dist/node/i18n/catalogs/index.js +1 -169
  35. package/dist/node/i18n/index.js +1 -229
  36. package/dist/node/i18n/keys.js +1 -37
  37. package/dist/node/i18n/locale.js +1 -29
  38. package/dist/node/i18n/messages.js +1 -190
  39. package/dist/node/index.js +7 -1126
  40. package/dist/node/notifications.capability.js +1 -35
  41. package/dist/node/notifications.feature.js +1 -50
  42. package/dist/node/templates/index.js +7 -235
  43. package/dist/notifications.capability.js +1 -35
  44. package/dist/notifications.feature.js +1 -50
  45. package/dist/templates/index.js +7 -235
  46. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1,1145 +1,26 @@
1
1
  // @bun
2
- var __defProp = Object.defineProperty;
3
- var __returnValue = (v) => v;
4
- function __exportSetter(name, newValue) {
5
- this[name] = __returnValue.bind(null, newValue);
6
- }
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true,
12
- configurable: true,
13
- set: __exportSetter.bind(all, name)
14
- });
15
- };
16
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
-
18
- // src/i18n/catalogs/en.ts
19
- import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
20
- var enMessages;
21
- var init_en = __esm(() => {
22
- enMessages = defineTranslation({
23
- meta: {
24
- key: "notifications.messages",
25
- version: "1.0.0",
26
- domain: "notifications",
27
- description: "Template and channel strings for the notifications module",
28
- owners: ["platform"],
29
- stability: "experimental"
30
- },
31
- locale: "en",
32
- fallback: "en",
33
- messages: {
34
- "template.welcome.name": {
35
- value: "Welcome",
36
- description: "Welcome template display name"
37
- },
38
- "template.welcome.description": {
39
- value: "Sent when a user signs up.",
40
- description: "Welcome template description"
41
- },
42
- "template.orgInvite.name": {
43
- value: "Organization Invitation",
44
- description: "Org invite template display name"
45
- },
46
- "template.orgInvite.description": {
47
- value: "Sent when a user is invited to an organization.",
48
- description: "Org invite template description"
49
- },
50
- "template.mention.name": {
51
- value: "Mention",
52
- description: "Mention template display name"
53
- },
54
- "template.mention.description": {
55
- value: "Sent when a user is mentioned.",
56
- description: "Mention template description"
57
- },
58
- "channel.webhook.noUrl": {
59
- value: "No webhook URL configured",
60
- description: "Error when webhook channel has no URL"
61
- }
62
- }
63
- });
64
- });
65
-
66
- // src/i18n/catalogs/es.ts
67
- import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
68
- var esMessages;
69
- var init_es = __esm(() => {
70
- esMessages = defineTranslation2({
71
- meta: {
72
- key: "notifications.messages",
73
- version: "1.0.0",
74
- domain: "notifications",
75
- description: "Template and channel strings (Spanish)",
76
- owners: ["platform"],
77
- stability: "experimental"
78
- },
79
- locale: "es",
80
- fallback: "en",
81
- messages: {
82
- "template.welcome.name": {
83
- value: "Bienvenida",
84
- description: "Welcome template display name"
85
- },
86
- "template.welcome.description": {
87
- value: "Enviado cuando un usuario se registra.",
88
- description: "Welcome template description"
89
- },
90
- "template.orgInvite.name": {
91
- value: "Invitaci\xF3n a la organizaci\xF3n",
92
- description: "Org invite template display name"
93
- },
94
- "template.orgInvite.description": {
95
- value: "Enviado cuando un usuario es invitado a una organizaci\xF3n.",
96
- description: "Org invite template description"
97
- },
98
- "template.mention.name": {
99
- value: "Menci\xF3n",
100
- description: "Mention template display name"
101
- },
102
- "template.mention.description": {
103
- value: "Enviado cuando un usuario es mencionado.",
104
- description: "Mention template description"
105
- },
106
- "channel.webhook.noUrl": {
107
- value: "No se ha configurado una URL de webhook",
108
- description: "Error when webhook channel has no URL"
109
- }
110
- }
111
- });
112
- });
113
-
114
- // src/i18n/catalogs/fr.ts
115
- import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
116
- var frMessages;
117
- var init_fr = __esm(() => {
118
- frMessages = defineTranslation3({
119
- meta: {
120
- key: "notifications.messages",
121
- version: "1.0.0",
122
- domain: "notifications",
123
- description: "Template and channel strings (French)",
124
- owners: ["platform"],
125
- stability: "experimental"
126
- },
127
- locale: "fr",
128
- fallback: "en",
129
- messages: {
130
- "template.welcome.name": {
131
- value: "Bienvenue",
132
- description: "Welcome template display name"
133
- },
134
- "template.welcome.description": {
135
- value: "Envoy\xE9 lorsqu'un utilisateur s'inscrit.",
136
- description: "Welcome template description"
137
- },
138
- "template.orgInvite.name": {
139
- value: "Invitation \xE0 l'organisation",
140
- description: "Org invite template display name"
141
- },
142
- "template.orgInvite.description": {
143
- value: "Envoy\xE9 lorsqu'un utilisateur est invit\xE9 \xE0 une organisation.",
144
- description: "Org invite template description"
145
- },
146
- "template.mention.name": {
147
- value: "Mention",
148
- description: "Mention template display name"
149
- },
150
- "template.mention.description": {
151
- value: "Envoy\xE9 lorsqu'un utilisateur est mentionn\xE9.",
152
- description: "Mention template description"
153
- },
154
- "channel.webhook.noUrl": {
155
- value: "Aucune URL de webhook configur\xE9e",
156
- description: "Error when webhook channel has no URL"
157
- }
158
- }
159
- });
160
- });
161
-
162
- // src/i18n/messages.ts
163
- var exports_messages = {};
164
- __export(exports_messages, {
165
- resetI18nRegistry: () => resetI18nRegistry,
166
- getDefaultI18n: () => getDefaultI18n,
167
- createNotificationsI18n: () => createNotificationsI18n
168
- });
169
- import {
170
- createI18nFactory
171
- } from "@contractspec/lib.contracts-spec/translations";
172
- var factory, createNotificationsI18n, getDefaultI18n, resetI18nRegistry;
173
- var init_messages = __esm(() => {
174
- init_en();
175
- init_es();
176
- init_fr();
177
- factory = createI18nFactory({
178
- specKey: "notifications.messages",
179
- catalogs: [enMessages, frMessages, esMessages]
180
- });
181
- createNotificationsI18n = factory.create;
182
- getDefaultI18n = factory.getDefault;
183
- resetI18nRegistry = factory.resetRegistry;
184
- });
185
-
186
- // src/channels/index.ts
187
- class InAppChannel {
188
- channelId = "IN_APP";
189
- async send(_notification) {
190
- return {
191
- success: true,
192
- responseMessage: "Stored in database"
193
- };
194
- }
195
- async isAvailable() {
196
- return true;
197
- }
198
- }
199
-
200
- class ConsoleChannel {
201
- channelId = "CONSOLE";
202
- async send(notification) {
203
- console.log(`\uD83D\uDCEC [${notification.id}] ${notification.title}`);
204
- console.log(` ${notification.body}`);
205
- if (notification.actionUrl) {
206
- console.log(` Action: ${notification.actionUrl}`);
207
- }
208
- return {
209
- success: true,
210
- responseMessage: "Logged to console"
211
- };
212
- }
213
- async isAvailable() {
214
- return true;
215
- }
216
- }
217
-
218
- class EmailChannel {
219
- channelId = "EMAIL";
220
- async isAvailable() {
221
- return true;
222
- }
223
- }
224
-
225
- class PushChannel {
226
- channelId = "PUSH";
227
- async isAvailable() {
228
- return true;
229
- }
230
- }
231
-
232
- class WebhookChannel {
233
- channelId = "WEBHOOK";
234
- locale;
235
- constructor(options) {
236
- this.locale = options?.locale;
237
- }
238
- async send(notification) {
239
- if (!notification.webhook?.url) {
240
- const { createNotificationsI18n: createNotificationsI18n2 } = await Promise.resolve().then(() => (init_messages(), exports_messages));
241
- const i18n = createNotificationsI18n2(this.locale);
242
- return {
243
- success: false,
244
- responseMessage: i18n.t("channel.webhook.noUrl")
245
- };
246
- }
247
- try {
248
- const response = await fetch(notification.webhook.url, {
249
- method: "POST",
250
- headers: {
251
- "Content-Type": "application/json",
252
- ...notification.webhook.headers
253
- },
254
- body: JSON.stringify({
255
- id: notification.id,
256
- title: notification.title,
257
- body: notification.body,
258
- actionUrl: notification.actionUrl,
259
- metadata: notification.metadata
260
- })
261
- });
262
- return {
263
- success: response.ok,
264
- responseCode: String(response.status),
265
- responseMessage: response.statusText
266
- };
267
- } catch (error) {
268
- return {
269
- success: false,
270
- responseMessage: error instanceof Error ? error.message : "Unknown error"
271
- };
272
- }
273
- }
274
- async isAvailable() {
275
- return true;
276
- }
277
- }
278
-
279
- class ChannelRegistry {
280
- channels = new Map;
281
- register(channel) {
282
- this.channels.set(channel.channelId, channel);
283
- }
284
- get(channelId) {
285
- return this.channels.get(channelId);
286
- }
287
- getAll() {
288
- return Array.from(this.channels.values());
289
- }
290
- async getAvailable() {
291
- const available = [];
292
- for (const channel of this.channels.values()) {
293
- if (await channel.isAvailable()) {
294
- available.push(channel);
295
- }
296
- }
297
- return available;
298
- }
299
- }
300
- function createChannelRegistry() {
301
- const registry = new ChannelRegistry;
302
- registry.register(new InAppChannel);
303
- registry.register(new ConsoleChannel);
304
- registry.register(new WebhookChannel);
305
- return registry;
306
- }
307
-
308
- // src/contracts/index.ts
309
- import { defineCommand, defineQuery } from "@contractspec/lib.contracts-spec";
310
- import {
311
- defineEnum,
312
- defineSchemaModel,
313
- ScalarTypeEnum
314
- } from "@contractspec/lib.schema";
315
- var OWNERS = ["platform.notifications"];
316
- var NotificationStatusSchemaEnum = defineEnum("NotificationStatus", [
317
- "PENDING",
318
- "SENT",
319
- "DELIVERED",
320
- "READ",
321
- "FAILED",
322
- "CANCELLED"
323
- ]);
324
- var NotificationChannelSchemaEnum = defineEnum("NotificationChannel", [
325
- "EMAIL",
326
- "IN_APP",
327
- "PUSH",
328
- "WEBHOOK"
329
- ]);
330
- var NotificationFilterEnum = defineEnum("NotificationFilter", [
331
- "unread",
332
- "read",
333
- "all"
334
- ]);
335
- var NotificationModel = defineSchemaModel({
336
- name: "Notification",
337
- description: "A notification sent to a user",
338
- fields: {
339
- id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
340
- userId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
341
- title: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
342
- body: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
343
- type: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
344
- status: { type: NotificationStatusSchemaEnum, isOptional: false },
345
- channels: {
346
- type: NotificationChannelSchemaEnum,
347
- isArray: true,
348
- isOptional: false
349
- },
350
- actionUrl: { type: ScalarTypeEnum.URL(), isOptional: true },
351
- readAt: { type: ScalarTypeEnum.DateTime(), isOptional: true },
352
- createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
353
- }
354
- });
355
- var NotificationPreferenceModel = defineSchemaModel({
356
- name: "NotificationPreference",
357
- description: "User notification preferences",
358
- fields: {
359
- userId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
360
- globalEnabled: { type: ScalarTypeEnum.Boolean(), isOptional: false },
361
- channelPreferences: {
362
- type: ScalarTypeEnum.JSONObject(),
363
- isOptional: false
364
- },
365
- typePreferences: { type: ScalarTypeEnum.JSONObject(), isOptional: false },
366
- quietHoursStart: {
367
- type: ScalarTypeEnum.String_unsecure(),
368
- isOptional: true
369
- },
370
- quietHoursEnd: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
371
- timezone: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
372
- digestEnabled: { type: ScalarTypeEnum.Boolean(), isOptional: false },
373
- digestFrequency: {
374
- type: ScalarTypeEnum.String_unsecure(),
375
- isOptional: true
376
- }
377
- }
378
- });
379
- var SendNotificationInputModel = defineSchemaModel({
380
- name: "SendNotificationInput",
381
- description: "Input for sending a notification",
382
- fields: {
383
- userId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
384
- templateId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
385
- title: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
386
- body: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
387
- type: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
388
- channels: {
389
- type: NotificationChannelSchemaEnum,
390
- isArray: true,
391
- isOptional: true
392
- },
393
- actionUrl: { type: ScalarTypeEnum.URL(), isOptional: true },
394
- variables: { type: ScalarTypeEnum.JSONObject(), isOptional: true },
395
- metadata: { type: ScalarTypeEnum.JSONObject(), isOptional: true }
396
- }
397
- });
398
- var ListNotificationsInputModel = defineSchemaModel({
399
- name: "ListNotificationsInput",
400
- description: "Input for listing notifications",
401
- fields: {
402
- status: { type: NotificationFilterEnum, isOptional: true },
403
- type: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
404
- limit: {
405
- type: ScalarTypeEnum.Int_unsecure(),
406
- isOptional: true,
407
- defaultValue: 20
408
- },
409
- offset: {
410
- type: ScalarTypeEnum.Int_unsecure(),
411
- isOptional: true,
412
- defaultValue: 0
413
- }
414
- }
415
- });
416
- var ListNotificationsOutputModel = defineSchemaModel({
417
- name: "ListNotificationsOutput",
418
- description: "Output for listing notifications",
419
- fields: {
420
- notifications: {
421
- type: NotificationModel,
422
- isArray: true,
423
- isOptional: false
424
- },
425
- total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
426
- unreadCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
427
- }
428
- });
429
- var UpdatePreferencesInputModel = defineSchemaModel({
430
- name: "UpdateNotificationPreferencesInput",
431
- description: "Input for updating notification preferences",
432
- fields: {
433
- globalEnabled: { type: ScalarTypeEnum.Boolean(), isOptional: true },
434
- channelPreferences: { type: ScalarTypeEnum.JSONObject(), isOptional: true },
435
- typePreferences: { type: ScalarTypeEnum.JSONObject(), isOptional: true },
436
- quietHoursStart: {
437
- type: ScalarTypeEnum.String_unsecure(),
438
- isOptional: true
439
- },
440
- quietHoursEnd: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
441
- timezone: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
442
- digestEnabled: { type: ScalarTypeEnum.Boolean(), isOptional: true },
443
- digestFrequency: {
444
- type: ScalarTypeEnum.String_unsecure(),
445
- isOptional: true
446
- }
447
- }
448
- });
449
- var SendNotificationContract = defineCommand({
450
- meta: {
451
- key: "notifications.send",
452
- version: "1.0.0",
453
- stability: "stable",
454
- owners: [...OWNERS],
455
- tags: ["notifications", "send"],
456
- description: "Send a notification to a user.",
457
- goal: "Deliver notifications across multiple channels.",
458
- context: "Called by services when events require user notification."
459
- },
460
- io: {
461
- input: SendNotificationInputModel,
462
- output: NotificationModel,
463
- errors: {
464
- USER_NOT_FOUND: {
465
- description: "Target user does not exist",
466
- http: 404,
467
- gqlCode: "USER_NOT_FOUND",
468
- when: "User ID is invalid"
469
- },
470
- TEMPLATE_NOT_FOUND: {
471
- description: "Notification template does not exist",
472
- http: 404,
473
- gqlCode: "TEMPLATE_NOT_FOUND",
474
- when: "Template ID is invalid"
475
- }
476
- }
477
- },
478
- policy: {
479
- auth: "user"
480
- },
481
- sideEffects: {
482
- emits: [
483
- {
484
- key: "notification.sent",
485
- version: "1.0.0",
486
- when: "Notification is sent",
487
- payload: NotificationModel
488
- }
489
- ]
490
- }
491
- });
492
- var ListNotificationsContract = defineQuery({
493
- meta: {
494
- key: "notifications.list",
495
- version: "1.0.0",
496
- stability: "stable",
497
- owners: [...OWNERS],
498
- tags: ["notifications", "list"],
499
- description: "List notifications for the current user.",
500
- goal: "Show user their notifications.",
501
- context: "Notification center UI."
502
- },
503
- io: {
504
- input: ListNotificationsInputModel,
505
- output: ListNotificationsOutputModel
506
- },
507
- policy: {
508
- auth: "user"
509
- }
510
- });
511
- var MarkNotificationReadContract = defineCommand({
512
- meta: {
513
- key: "notifications.markRead",
514
- version: "1.0.0",
515
- stability: "stable",
516
- owners: [...OWNERS],
517
- tags: ["notifications", "read"],
518
- description: "Mark a notification as read.",
519
- goal: "Track which notifications user has seen.",
520
- context: "User clicks on a notification."
521
- },
522
- io: {
523
- input: defineSchemaModel({
524
- name: "MarkNotificationReadInput",
525
- fields: {
526
- notificationId: {
527
- type: ScalarTypeEnum.String_unsecure(),
528
- isOptional: false
529
- }
530
- }
531
- }),
532
- output: NotificationModel
533
- },
534
- policy: {
535
- auth: "user"
536
- }
537
- });
538
- var MarkAllNotificationsReadContract = defineCommand({
539
- meta: {
540
- key: "notifications.markAllRead",
541
- version: "1.0.0",
542
- stability: "stable",
543
- owners: [...OWNERS],
544
- tags: ["notifications", "read"],
545
- description: "Mark all notifications as read.",
546
- goal: "Clear notification badge.",
547
- context: 'User clicks "mark all as read".'
548
- },
549
- io: {
550
- input: null,
551
- output: defineSchemaModel({
552
- name: "MarkAllNotificationsReadOutput",
553
- fields: {
554
- markedCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false }
555
- }
556
- })
557
- },
558
- policy: {
559
- auth: "user"
560
- }
561
- });
562
- var GetNotificationPreferencesContract = defineQuery({
563
- meta: {
564
- key: "notifications.preferences.get",
565
- version: "1.0.0",
566
- stability: "stable",
567
- owners: [...OWNERS],
568
- tags: ["notifications", "preferences", "get"],
569
- description: "Get notification preferences for current user.",
570
- goal: "Show user their notification settings.",
571
- context: "Notification settings page."
572
- },
573
- io: {
574
- input: null,
575
- output: NotificationPreferenceModel
576
- },
577
- policy: {
578
- auth: "user"
579
- }
580
- });
581
- var UpdateNotificationPreferencesContract = defineCommand({
582
- meta: {
583
- key: "notifications.preferences.update",
584
- version: "1.0.0",
585
- stability: "stable",
586
- owners: [...OWNERS],
587
- tags: ["notifications", "preferences", "update"],
588
- description: "Update notification preferences.",
589
- goal: "Allow user to control notification delivery.",
590
- context: "Notification settings page."
591
- },
592
- io: {
593
- input: UpdatePreferencesInputModel,
594
- output: NotificationPreferenceModel
595
- },
596
- policy: {
597
- auth: "user"
598
- }
599
- });
600
- var DeleteNotificationContract = defineCommand({
601
- meta: {
602
- key: "notifications.delete",
603
- version: "1.0.0",
604
- stability: "stable",
605
- owners: [...OWNERS],
606
- tags: ["notifications", "delete"],
607
- description: "Delete a notification.",
608
- goal: "Allow user to remove unwanted notifications.",
609
- context: "User dismisses a notification."
610
- },
611
- io: {
612
- input: defineSchemaModel({
613
- name: "DeleteNotificationInput",
614
- fields: {
615
- notificationId: {
616
- type: ScalarTypeEnum.String_unsecure(),
617
- isOptional: false
618
- }
619
- }
620
- }),
621
- output: defineSchemaModel({
622
- name: "DeleteNotificationOutput",
623
- fields: {
624
- success: { type: ScalarTypeEnum.Boolean(), isOptional: false }
625
- }
626
- })
627
- },
628
- policy: {
629
- auth: "user"
630
- }
631
- });
632
-
633
- // src/entities/index.ts
634
- import {
635
- defineEntity,
636
- defineEntityEnum,
637
- field,
638
- index
639
- } from "@contractspec/lib.schema";
640
- var NotificationStatusEnum = defineEntityEnum({
641
- name: "NotificationStatus",
642
- values: [
643
- "PENDING",
644
- "SENT",
645
- "DELIVERED",
646
- "READ",
647
- "FAILED",
648
- "CANCELLED"
649
- ],
650
- schema: "lssm_notifications",
651
- description: "Status of a notification."
652
- });
653
- var NotificationChannelEnum = defineEntityEnum({
654
- name: "NotificationChannel",
655
- values: ["EMAIL", "IN_APP", "PUSH", "WEBHOOK", "SMS"],
656
- schema: "lssm_notifications",
657
- description: "Delivery channel for notifications."
658
- });
659
- var NotificationEntity = defineEntity({
660
- name: "Notification",
661
- description: "An individual notification to be delivered to a user.",
662
- schema: "lssm_notifications",
663
- map: "notification",
664
- fields: {
665
- id: field.id({ description: "Unique notification ID" }),
666
- userId: field.foreignKey({ description: "Target user ID" }),
667
- orgId: field.string({
668
- isOptional: true,
669
- description: "Organization context"
670
- }),
671
- templateId: field.string({
672
- isOptional: true,
673
- description: "Template used"
674
- }),
675
- title: field.string({ description: "Notification title" }),
676
- body: field.string({ description: "Notification body" }),
677
- actionUrl: field.string({ isOptional: true, description: "Action URL" }),
678
- imageUrl: field.string({ isOptional: true, description: "Image URL" }),
679
- type: field.string({
680
- description: "Notification type (e.g., mention, update)"
681
- }),
682
- category: field.string({
683
- isOptional: true,
684
- description: "Notification category"
685
- }),
686
- priority: field.enum("NotificationPriority", { default: "NORMAL" }),
687
- channels: field.string({
688
- isArray: true,
689
- description: "Target delivery channels"
690
- }),
691
- status: field.enum("NotificationStatus", { default: "PENDING" }),
692
- sentAt: field.dateTime({ isOptional: true }),
693
- deliveredAt: field.dateTime({ isOptional: true }),
694
- readAt: field.dateTime({ isOptional: true }),
695
- metadata: field.json({
696
- isOptional: true,
697
- description: "Additional metadata"
698
- }),
699
- variables: field.json({
700
- isOptional: true,
701
- description: "Template variables used"
702
- }),
703
- triggeredBy: field.string({
704
- isOptional: true,
705
- description: "Event/action that triggered"
706
- }),
707
- sourceId: field.string({
708
- isOptional: true,
709
- description: "Source entity ID"
710
- }),
711
- sourceType: field.string({
712
- isOptional: true,
713
- description: "Source entity type"
714
- }),
715
- createdAt: field.createdAt(),
716
- updatedAt: field.updatedAt(),
717
- expiresAt: field.dateTime({
718
- isOptional: true,
719
- description: "Notification expiry"
720
- }),
721
- deliveryLogs: field.hasMany("DeliveryLog")
722
- },
723
- indexes: [
724
- index.on(["userId", "status", "createdAt"]),
725
- index.on(["userId", "readAt"]),
726
- index.on(["orgId", "createdAt"]),
727
- index.on(["type", "createdAt"])
728
- ],
729
- enums: [NotificationStatusEnum, NotificationChannelEnum]
730
- });
731
- var NotificationPriorityEnum = defineEntityEnum({
732
- name: "NotificationPriority",
733
- values: ["LOW", "NORMAL", "HIGH", "URGENT"],
734
- schema: "lssm_notifications",
735
- description: "Priority level of a notification."
736
- });
737
- var NotificationTemplateEntity = defineEntity({
738
- name: "NotificationTemplate",
739
- description: "Reusable notification template.",
740
- schema: "lssm_notifications",
741
- map: "notification_template",
742
- fields: {
743
- id: field.id(),
744
- templateId: field.string({
745
- isUnique: true,
746
- description: "Template identifier"
747
- }),
748
- name: field.string({ description: "Template display name" }),
749
- description: field.string({ isOptional: true }),
750
- emailSubject: field.string({ isOptional: true }),
751
- emailBody: field.string({ isOptional: true }),
752
- inAppTitle: field.string({ isOptional: true }),
753
- inAppBody: field.string({ isOptional: true }),
754
- pushTitle: field.string({ isOptional: true }),
755
- pushBody: field.string({ isOptional: true }),
756
- defaultChannels: field.string({ isArray: true }),
757
- category: field.string({ isOptional: true }),
758
- priority: field.enum("NotificationPriority", { default: "NORMAL" }),
759
- variablesSchema: field.json({
760
- isOptional: true,
761
- description: "JSON schema for variables"
762
- }),
763
- enabled: field.boolean({ default: true }),
764
- createdAt: field.createdAt(),
765
- updatedAt: field.updatedAt()
766
- },
767
- enums: [NotificationPriorityEnum]
768
- });
769
- var NotificationPreferenceEntity = defineEntity({
770
- name: "NotificationPreference",
771
- description: "User notification preferences by type and channel.",
772
- schema: "lssm_notifications",
773
- map: "notification_preference",
774
- fields: {
775
- id: field.id(),
776
- userId: field.foreignKey(),
777
- globalEnabled: field.boolean({ default: true }),
778
- quietHoursStart: field.string({
779
- isOptional: true,
780
- description: "Quiet hours start (HH:MM)"
781
- }),
782
- quietHoursEnd: field.string({
783
- isOptional: true,
784
- description: "Quiet hours end (HH:MM)"
785
- }),
786
- timezone: field.string({ default: '"UTC"' }),
787
- channelPreferences: field.json({
788
- description: "Channel-level preferences"
789
- }),
790
- typePreferences: field.json({ description: "Type-level preferences" }),
791
- digestEnabled: field.boolean({ default: false }),
792
- digestFrequency: field.string({
793
- isOptional: true,
794
- description: "daily, weekly, etc."
795
- }),
796
- digestTime: field.string({
797
- isOptional: true,
798
- description: "Digest send time (HH:MM)"
799
- }),
800
- createdAt: field.createdAt(),
801
- updatedAt: field.updatedAt()
802
- },
803
- indexes: [index.unique(["userId"])]
804
- });
805
- var DeliveryLogEntity = defineEntity({
806
- name: "DeliveryLog",
807
- description: "Log of notification delivery attempts.",
808
- schema: "lssm_notifications",
809
- map: "delivery_log",
810
- fields: {
811
- id: field.id(),
812
- notificationId: field.foreignKey(),
813
- channel: field.enum("NotificationChannel"),
814
- status: field.enum("NotificationStatus"),
815
- attemptedAt: field.dateTime(),
816
- deliveredAt: field.dateTime({ isOptional: true }),
817
- responseCode: field.string({ isOptional: true }),
818
- responseMessage: field.string({ isOptional: true }),
819
- externalId: field.string({
820
- isOptional: true,
821
- description: "Provider message ID"
822
- }),
823
- metadata: field.json({ isOptional: true }),
824
- notification: field.belongsTo("Notification", ["notificationId"], ["id"], {
825
- onDelete: "Cascade"
826
- })
827
- },
828
- indexes: [index.on(["notificationId", "channel"])]
829
- });
830
- var notificationEntities = [
831
- NotificationEntity,
832
- NotificationTemplateEntity,
833
- NotificationPreferenceEntity,
834
- DeliveryLogEntity
835
- ];
836
- var notificationsSchemaContribution = {
837
- moduleId: "@contractspec/module.notifications",
838
- entities: notificationEntities,
839
- enums: [
840
- NotificationStatusEnum,
841
- NotificationChannelEnum,
842
- NotificationPriorityEnum
843
- ]
844
- };
845
-
846
- // src/notifications.feature.ts
847
- import { defineFeature } from "@contractspec/lib.contracts-spec";
848
- var NotificationsFeature = defineFeature({
849
- meta: {
850
- key: "notifications",
851
- title: "Notifications",
852
- description: "Multi-channel notification delivery with preference management",
853
- domain: "platform",
854
- version: "1.0.0",
855
- owners: ["@platform.notifications"],
856
- tags: ["notifications", "email", "push", "in-app"],
857
- stability: "stable"
858
- },
859
- operations: [
860
- { key: "notifications.send", version: "1.0.0" },
861
- { key: "notifications.markRead", version: "1.0.0" },
862
- { key: "notifications.markAllRead", version: "1.0.0" },
863
- { key: "notifications.delete", version: "1.0.0" },
864
- { key: "notifications.list", version: "1.0.0" },
865
- { key: "notifications.preferences.update", version: "1.0.0" },
866
- { key: "notifications.preferences.get", version: "1.0.0" }
867
- ],
868
- events: [],
869
- presentations: [],
870
- opToPresentation: [],
871
- presentationsTargets: [],
872
- capabilities: {
873
- provides: [{ key: "notifications", version: "1.0.0" }],
874
- requires: [{ key: "identity", version: "1.0.0" }]
875
- }
876
- });
877
-
878
- // src/templates/index.ts
879
- function defineTemplate(def) {
880
- return def;
881
- }
882
- function renderTemplate(content, variables) {
883
- return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
884
- const value = variables[key];
885
- if (value === undefined || value === null) {
886
- return match;
887
- }
888
- return String(value);
889
- });
890
- }
891
- function renderNotificationTemplate(template, channel, variables, locale) {
892
- const channelContent = (locale && template.localeChannels?.[locale]?.[channel]) ?? template.channels[channel];
893
- if (!channelContent) {
894
- return null;
895
- }
896
- const title = channelContent.title ? renderTemplate(channelContent.title, variables) : template.name;
897
- const body = renderTemplate(channelContent.body, variables);
898
- const actionUrl = channelContent.actionUrl ? renderTemplate(channelContent.actionUrl, variables) : undefined;
899
- const result = {
900
- title,
901
- body,
902
- actionUrl
903
- };
904
- if (channel === "email" && channelContent.subject) {
905
- result.email = {
906
- subject: renderTemplate(channelContent.subject, variables),
907
- html: body,
908
- text: stripHtml(body)
909
- };
910
- }
911
- return result;
912
- }
913
- function stripHtml(html) {
914
- return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
915
- }
916
-
917
- class TemplateRegistry {
918
- templates = new Map;
919
- register(template) {
920
- this.templates.set(template.id, template);
921
- }
922
- get(templateId) {
923
- return this.templates.get(templateId);
924
- }
925
- getAll() {
926
- return Array.from(this.templates.values());
927
- }
928
- getByCategory(category) {
929
- return this.getAll().filter((t) => t.category === category);
930
- }
931
- }
932
- function createTemplateRegistry() {
933
- return new TemplateRegistry;
934
- }
935
- var WelcomeTemplate = defineTemplate({
936
- id: "welcome",
937
- name: "Welcome",
938
- description: "Sent when a user signs up.",
939
- category: "onboarding",
940
- variables: [
941
- { name: "name", type: "string", required: true },
942
- { name: "appName", type: "string", default: "ContractSpec" },
943
- { name: "actionUrl", type: "url" }
944
- ],
945
- defaultChannels: ["EMAIL", "IN_APP"],
946
- channels: {
947
- email: {
948
- subject: "Welcome to {{appName}}, {{name}}!",
949
- body: `
2
+ var m=Object.defineProperty;var r=(q)=>q;function d(q,D){this[q]=r.bind(null,D)}var f=(q,D)=>{for(var J in D)m(q,J,{get:D[J],enumerable:!0,configurable:!0,set:d.bind(D,J)})};var _=(q,D)=>()=>(q&&(D=q(q=0)),D);import{defineTranslation as o}from"@contractspec/lib.contracts-spec/translations";var L;var k=_(()=>{L=o({meta:{key:"notifications.messages",version:"1.0.0",domain:"notifications",description:"Template and channel strings for the notifications module",owners:["platform"],stability:"experimental"},locale:"en",fallback:"en",messages:{"template.welcome.name":{value:"Welcome",description:"Welcome template display name"},"template.welcome.description":{value:"Sent when a user signs up.",description:"Welcome template description"},"template.orgInvite.name":{value:"Organization Invitation",description:"Org invite template display name"},"template.orgInvite.description":{value:"Sent when a user is invited to an organization.",description:"Org invite template description"},"template.mention.name":{value:"Mention",description:"Mention template display name"},"template.mention.description":{value:"Sent when a user is mentioned.",description:"Mention template description"},"channel.webhook.noUrl":{value:"No webhook URL configured",description:"Error when webhook channel has no URL"}}})});import{defineTranslation as c}from"@contractspec/lib.contracts-spec/translations";var P;var O=_(()=>{P=c({meta:{key:"notifications.messages",version:"1.0.0",domain:"notifications",description:"Template and channel strings (Spanish)",owners:["platform"],stability:"experimental"},locale:"es",fallback:"en",messages:{"template.welcome.name":{value:"Bienvenida",description:"Welcome template display name"},"template.welcome.description":{value:"Enviado cuando un usuario se registra.",description:"Welcome template description"},"template.orgInvite.name":{value:"Invitaci\xF3n a la organizaci\xF3n",description:"Org invite template display name"},"template.orgInvite.description":{value:"Enviado cuando un usuario es invitado a una organizaci\xF3n.",description:"Org invite template description"},"template.mention.name":{value:"Menci\xF3n",description:"Mention template display name"},"template.mention.description":{value:"Enviado cuando un usuario es mencionado.",description:"Mention template description"},"channel.webhook.noUrl":{value:"No se ha configurado una URL de webhook",description:"Error when webhook channel has no URL"}}})});import{defineTranslation as l}from"@contractspec/lib.contracts-spec/translations";var W;var I=_(()=>{W=l({meta:{key:"notifications.messages",version:"1.0.0",domain:"notifications",description:"Template and channel strings (French)",owners:["platform"],stability:"experimental"},locale:"fr",fallback:"en",messages:{"template.welcome.name":{value:"Bienvenue",description:"Welcome template display name"},"template.welcome.description":{value:"Envoy\xE9 lorsqu'un utilisateur s'inscrit.",description:"Welcome template description"},"template.orgInvite.name":{value:"Invitation \xE0 l'organisation",description:"Org invite template display name"},"template.orgInvite.description":{value:"Envoy\xE9 lorsqu'un utilisateur est invit\xE9 \xE0 une organisation.",description:"Org invite template description"},"template.mention.name":{value:"Mention",description:"Mention template display name"},"template.mention.description":{value:"Envoy\xE9 lorsqu'un utilisateur est mentionn\xE9.",description:"Mention template description"},"channel.webhook.noUrl":{value:"Aucune URL de webhook configur\xE9e",description:"Error when webhook channel has no URL"}}})});var R={};f(R,{resetI18nRegistry:()=>n,getDefaultI18n:()=>i,createNotificationsI18n:()=>a});import{createI18nFactory as t}from"@contractspec/lib.contracts-spec/translations";var H,a,i,n;var v=_(()=>{k();O();I();H=t({specKey:"notifications.messages",catalogs:[L,W,P]}),a=H.create,i=H.getDefault,n=H.resetRegistry});class g{channelId="IN_APP";async send(q){return{success:!0,responseMessage:"Stored in database"}}async isAvailable(){return!0}}class M{channelId="CONSOLE";async send(q){if(console.log(`\uD83D\uDCEC [${q.id}] ${q.title}`),console.log(` ${q.body}`),q.actionUrl)console.log(` Action: ${q.actionUrl}`);return{success:!0,responseMessage:"Logged to console"}}async isAvailable(){return!0}}class e{channelId="EMAIL";async isAvailable(){return!0}}class jj{channelId="PUSH";async isAvailable(){return!0}}class b{channelId="WEBHOOK";locale;constructor(q){this.locale=q?.locale}async send(q){if(!q.webhook?.url){let{createNotificationsI18n:D}=await Promise.resolve().then(() => (v(),R));return{success:!1,responseMessage:D(this.locale).t("channel.webhook.noUrl")}}try{let D=await fetch(q.webhook.url,{method:"POST",headers:{"Content-Type":"application/json",...q.webhook.headers},body:JSON.stringify({id:q.id,title:q.title,body:q.body,actionUrl:q.actionUrl,metadata:q.metadata})});return{success:D.ok,responseCode:String(D.status),responseMessage:D.statusText}}catch(D){return{success:!1,responseMessage:D instanceof Error?D.message:"Unknown error"}}}async isAvailable(){return!0}}class C{channels=new Map;register(q){this.channels.set(q.channelId,q)}get(q){return this.channels.get(q)}getAll(){return Array.from(this.channels.values())}async getAvailable(){let q=[];for(let D of this.channels.values())if(await D.isAvailable())q.push(D);return q}}function Wj(){let q=new C;return q.register(new g),q.register(new M),q.register(new b),q}import{defineCommand as Y,defineQuery as N}from"@contractspec/lib.contracts-spec";import{defineEnum as A,defineSchemaModel as K,ScalarTypeEnum as B}from"@contractspec/lib.schema";var V=["platform.notifications"],qj=A("NotificationStatus",["PENDING","SENT","DELIVERED","READ","FAILED","CANCELLED"]),T=A("NotificationChannel",["EMAIL","IN_APP","PUSH","WEBHOOK"]),Bj=A("NotificationFilter",["unread","read","all"]),$=K({name:"Notification",description:"A notification sent to a user",fields:{id:{type:B.String_unsecure(),isOptional:!1},userId:{type:B.String_unsecure(),isOptional:!1},title:{type:B.String_unsecure(),isOptional:!1},body:{type:B.String_unsecure(),isOptional:!1},type:{type:B.String_unsecure(),isOptional:!1},status:{type:qj,isOptional:!1},channels:{type:T,isArray:!0,isOptional:!1},actionUrl:{type:B.URL(),isOptional:!0},readAt:{type:B.DateTime(),isOptional:!0},createdAt:{type:B.DateTime(),isOptional:!1}}}),h=K({name:"NotificationPreference",description:"User notification preferences",fields:{userId:{type:B.String_unsecure(),isOptional:!1},globalEnabled:{type:B.Boolean(),isOptional:!1},channelPreferences:{type:B.JSONObject(),isOptional:!1},typePreferences:{type:B.JSONObject(),isOptional:!1},quietHoursStart:{type:B.String_unsecure(),isOptional:!0},quietHoursEnd:{type:B.String_unsecure(),isOptional:!0},timezone:{type:B.String_unsecure(),isOptional:!1},digestEnabled:{type:B.Boolean(),isOptional:!1},digestFrequency:{type:B.String_unsecure(),isOptional:!0}}}),Dj=K({name:"SendNotificationInput",description:"Input for sending a notification",fields:{userId:{type:B.String_unsecure(),isOptional:!1},templateId:{type:B.String_unsecure(),isOptional:!0},title:{type:B.String_unsecure(),isOptional:!0},body:{type:B.String_unsecure(),isOptional:!0},type:{type:B.String_unsecure(),isOptional:!1},channels:{type:T,isArray:!0,isOptional:!0},actionUrl:{type:B.URL(),isOptional:!0},variables:{type:B.JSONObject(),isOptional:!0},metadata:{type:B.JSONObject(),isOptional:!0}}}),Gj=K({name:"ListNotificationsInput",description:"Input for listing notifications",fields:{status:{type:Bj,isOptional:!0},type:{type:B.String_unsecure(),isOptional:!0},limit:{type:B.Int_unsecure(),isOptional:!0,defaultValue:20},offset:{type:B.Int_unsecure(),isOptional:!0,defaultValue:0}}}),Jj=K({name:"ListNotificationsOutput",description:"Output for listing notifications",fields:{notifications:{type:$,isArray:!0,isOptional:!1},total:{type:B.Int_unsecure(),isOptional:!1},unreadCount:{type:B.Int_unsecure(),isOptional:!1}}}),Kj=K({name:"UpdateNotificationPreferencesInput",description:"Input for updating notification preferences",fields:{globalEnabled:{type:B.Boolean(),isOptional:!0},channelPreferences:{type:B.JSONObject(),isOptional:!0},typePreferences:{type:B.JSONObject(),isOptional:!0},quietHoursStart:{type:B.String_unsecure(),isOptional:!0},quietHoursEnd:{type:B.String_unsecure(),isOptional:!0},timezone:{type:B.String_unsecure(),isOptional:!0},digestEnabled:{type:B.Boolean(),isOptional:!0},digestFrequency:{type:B.String_unsecure(),isOptional:!0}}}),gj=Y({meta:{key:"notifications.send",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","send"],description:"Send a notification to a user.",goal:"Deliver notifications across multiple channels.",context:"Called by services when events require user notification."},io:{input:Dj,output:$,errors:{USER_NOT_FOUND:{description:"Target user does not exist",http:404,gqlCode:"USER_NOT_FOUND",when:"User ID is invalid"},TEMPLATE_NOT_FOUND:{description:"Notification template does not exist",http:404,gqlCode:"TEMPLATE_NOT_FOUND",when:"Template ID is invalid"}}},policy:{auth:"user"},sideEffects:{emits:[{key:"notification.sent",version:"1.0.0",when:"Notification is sent",payload:$}]}}),Mj=N({meta:{key:"notifications.list",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","list"],description:"List notifications for the current user.",goal:"Show user their notifications.",context:"Notification center UI."},io:{input:Gj,output:Jj},policy:{auth:"user"}}),bj=Y({meta:{key:"notifications.markRead",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","read"],description:"Mark a notification as read.",goal:"Track which notifications user has seen.",context:"User clicks on a notification."},io:{input:K({name:"MarkNotificationReadInput",fields:{notificationId:{type:B.String_unsecure(),isOptional:!1}}}),output:$},policy:{auth:"user"}}),Cj=Y({meta:{key:"notifications.markAllRead",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","read"],description:"Mark all notifications as read.",goal:"Clear notification badge.",context:'User clicks "mark all as read".'},io:{input:null,output:K({name:"MarkAllNotificationsReadOutput",fields:{markedCount:{type:B.Int_unsecure(),isOptional:!1}}})},policy:{auth:"user"}}),Nj=N({meta:{key:"notifications.preferences.get",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","preferences","get"],description:"Get notification preferences for current user.",goal:"Show user their notification settings.",context:"Notification settings page."},io:{input:null,output:h},policy:{auth:"user"}}),Tj=Y({meta:{key:"notifications.preferences.update",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","preferences","update"],description:"Update notification preferences.",goal:"Allow user to control notification delivery.",context:"Notification settings page."},io:{input:Kj,output:h},policy:{auth:"user"}}),hj=Y({meta:{key:"notifications.delete",version:"1.0.0",stability:"stable",owners:[...V],tags:["notifications","delete"],description:"Delete a notification.",goal:"Allow user to remove unwanted notifications.",context:"User dismisses a notification."},io:{input:K({name:"DeleteNotificationInput",fields:{notificationId:{type:B.String_unsecure(),isOptional:!1}}}),output:K({name:"DeleteNotificationOutput",fields:{success:{type:B.Boolean(),isOptional:!1}}})},policy:{auth:"user"}});import{defineEntity as w,defineEntityEnum as Q,field as j,index as X}from"@contractspec/lib.schema";var S=Q({name:"NotificationStatus",values:["PENDING","SENT","DELIVERED","READ","FAILED","CANCELLED"],schema:"lssm_notifications",description:"Status of a notification."}),y=Q({name:"NotificationChannel",values:["EMAIL","IN_APP","PUSH","WEBHOOK","SMS"],schema:"lssm_notifications",description:"Delivery channel for notifications."}),Vj=w({name:"Notification",description:"An individual notification to be delivered to a user.",schema:"lssm_notifications",map:"notification",fields:{id:j.id({description:"Unique notification ID"}),userId:j.foreignKey({description:"Target user ID"}),orgId:j.string({isOptional:!0,description:"Organization context"}),templateId:j.string({isOptional:!0,description:"Template used"}),title:j.string({description:"Notification title"}),body:j.string({description:"Notification body"}),actionUrl:j.string({isOptional:!0,description:"Action URL"}),imageUrl:j.string({isOptional:!0,description:"Image URL"}),type:j.string({description:"Notification type (e.g., mention, update)"}),category:j.string({isOptional:!0,description:"Notification category"}),priority:j.enum("NotificationPriority",{default:"NORMAL"}),channels:j.string({isArray:!0,description:"Target delivery channels"}),status:j.enum("NotificationStatus",{default:"PENDING"}),sentAt:j.dateTime({isOptional:!0}),deliveredAt:j.dateTime({isOptional:!0}),readAt:j.dateTime({isOptional:!0}),metadata:j.json({isOptional:!0,description:"Additional metadata"}),variables:j.json({isOptional:!0,description:"Template variables used"}),triggeredBy:j.string({isOptional:!0,description:"Event/action that triggered"}),sourceId:j.string({isOptional:!0,description:"Source entity ID"}),sourceType:j.string({isOptional:!0,description:"Source entity type"}),createdAt:j.createdAt(),updatedAt:j.updatedAt(),expiresAt:j.dateTime({isOptional:!0,description:"Notification expiry"}),deliveryLogs:j.hasMany("DeliveryLog")},indexes:[X.on(["userId","status","createdAt"]),X.on(["userId","readAt"]),X.on(["orgId","createdAt"]),X.on(["type","createdAt"])],enums:[S,y]}),E=Q({name:"NotificationPriority",values:["LOW","NORMAL","HIGH","URGENT"],schema:"lssm_notifications",description:"Priority level of a notification."}),Xj=w({name:"NotificationTemplate",description:"Reusable notification template.",schema:"lssm_notifications",map:"notification_template",fields:{id:j.id(),templateId:j.string({isUnique:!0,description:"Template identifier"}),name:j.string({description:"Template display name"}),description:j.string({isOptional:!0}),emailSubject:j.string({isOptional:!0}),emailBody:j.string({isOptional:!0}),inAppTitle:j.string({isOptional:!0}),inAppBody:j.string({isOptional:!0}),pushTitle:j.string({isOptional:!0}),pushBody:j.string({isOptional:!0}),defaultChannels:j.string({isArray:!0}),category:j.string({isOptional:!0}),priority:j.enum("NotificationPriority",{default:"NORMAL"}),variablesSchema:j.json({isOptional:!0,description:"JSON schema for variables"}),enabled:j.boolean({default:!0}),createdAt:j.createdAt(),updatedAt:j.updatedAt()},enums:[E]}),Yj=w({name:"NotificationPreference",description:"User notification preferences by type and channel.",schema:"lssm_notifications",map:"notification_preference",fields:{id:j.id(),userId:j.foreignKey(),globalEnabled:j.boolean({default:!0}),quietHoursStart:j.string({isOptional:!0,description:"Quiet hours start (HH:MM)"}),quietHoursEnd:j.string({isOptional:!0,description:"Quiet hours end (HH:MM)"}),timezone:j.string({default:'"UTC"'}),channelPreferences:j.json({description:"Channel-level preferences"}),typePreferences:j.json({description:"Type-level preferences"}),digestEnabled:j.boolean({default:!1}),digestFrequency:j.string({isOptional:!0,description:"daily, weekly, etc."}),digestTime:j.string({isOptional:!0,description:"Digest send time (HH:MM)"}),createdAt:j.createdAt(),updatedAt:j.updatedAt()},indexes:[X.unique(["userId"])]}),Zj=w({name:"DeliveryLog",description:"Log of notification delivery attempts.",schema:"lssm_notifications",map:"delivery_log",fields:{id:j.id(),notificationId:j.foreignKey(),channel:j.enum("NotificationChannel"),status:j.enum("NotificationStatus"),attemptedAt:j.dateTime(),deliveredAt:j.dateTime({isOptional:!0}),responseCode:j.string({isOptional:!0}),responseMessage:j.string({isOptional:!0}),externalId:j.string({isOptional:!0,description:"Provider message ID"}),metadata:j.json({isOptional:!0}),notification:j.belongsTo("Notification",["notificationId"],["id"],{onDelete:"Cascade"})},indexes:[X.on(["notificationId","channel"])]}),_j=[Vj,Xj,Yj,Zj],Ej={moduleId:"@contractspec/module.notifications",entities:_j,enums:[S,y,E]};import{defineFeature as $j}from"@contractspec/lib.contracts-spec";var sj=$j({meta:{key:"notifications",title:"Notifications",description:"Multi-channel notification delivery with preference management",domain:"platform",version:"1.0.0",owners:["@platform.notifications"],tags:["notifications","email","push","in-app"],stability:"stable"},operations:[{key:"notifications.send",version:"1.0.0"},{key:"notifications.markRead",version:"1.0.0"},{key:"notifications.markAllRead",version:"1.0.0"},{key:"notifications.delete",version:"1.0.0"},{key:"notifications.list",version:"1.0.0"},{key:"notifications.preferences.update",version:"1.0.0"},{key:"notifications.preferences.get",version:"1.0.0"}],events:[],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{provides:[{key:"notifications",version:"1.0.0"}],requires:[{key:"identity",version:"1.0.0"}]}});function U(q){return q}function x(q,D){return q.replace(/\{\{(\w+)\}\}/g,(J,Z)=>{let G=D[Z];if(G===void 0||G===null)return J;return String(G)})}function rj(q,D,J,Z){let G=(Z&&q.localeChannels?.[Z]?.[D])??q.channels[D];if(!G)return null;let p=G.title?x(G.title,J):q.name,z=x(G.body,J),s=G.actionUrl?x(G.actionUrl,J):void 0,F={title:p,body:z,actionUrl:s};if(D==="email"&&G.subject)F.email={subject:x(G.subject,J),html:z,text:wj(z)};return F}function wj(q){return q.replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim()}class u{templates=new Map;register(q){this.templates.set(q.id,q)}get(q){return this.templates.get(q)}getAll(){return Array.from(this.templates.values())}getByCategory(q){return this.getAll().filter((D)=>D.category===q)}}function dj(){return new u}var fj=U({id:"welcome",name:"Welcome",description:"Sent when a user signs up.",category:"onboarding",variables:[{name:"name",type:"string",required:!0},{name:"appName",type:"string",default:"ContractSpec"},{name:"actionUrl",type:"url"}],defaultChannels:["EMAIL","IN_APP"],channels:{email:{subject:"Welcome to {{appName}}, {{name}}!",body:`
950
3
  <h1>Welcome, {{name}}!</h1>
951
4
  <p>Thanks for joining {{appName}}. We're excited to have you on board.</p>
952
5
  <p><a href="{{actionUrl}}">Get started now</a></p>
953
- `
954
- },
955
- inApp: {
956
- title: "Welcome to {{appName}}!",
957
- body: "Thanks for joining. Click to complete your profile.",
958
- actionUrl: "{{actionUrl}}"
959
- }
960
- },
961
- localeChannels: {
962
- fr: {
963
- email: {
964
- subject: "Bienvenue sur {{appName}}, {{name}}\u202F!",
965
- body: `
6
+ `},inApp:{title:"Welcome to {{appName}}!",body:"Thanks for joining. Click to complete your profile.",actionUrl:"{{actionUrl}}"}},localeChannels:{fr:{email:{subject:"Bienvenue sur {{appName}}, {{name}}\u202F!",body:`
966
7
  <h1>Bienvenue, {{name}}\u202F!</h1>
967
8
  <p>Merci d\u2019avoir rejoint {{appName}}. Nous sommes ravis de vous compter parmi nous.</p>
968
9
  <p><a href="{{actionUrl}}">Commencer maintenant</a></p>
969
- `
970
- },
971
- inApp: {
972
- title: "Bienvenue sur {{appName}}\u202F!",
973
- body: "Merci de nous avoir rejoint. Cliquez pour compl\xE9ter votre profil.",
974
- actionUrl: "{{actionUrl}}"
975
- }
976
- },
977
- es: {
978
- email: {
979
- subject: "\xA1Bienvenido a {{appName}}, {{name}}!",
980
- body: `
10
+ `},inApp:{title:"Bienvenue sur {{appName}}\u202F!",body:"Merci de nous avoir rejoint. Cliquez pour compl\xE9ter votre profil.",actionUrl:"{{actionUrl}}"}},es:{email:{subject:"\xA1Bienvenido a {{appName}}, {{name}}!",body:`
981
11
  <h1>\xA1Bienvenido, {{name}}!</h1>
982
12
  <p>Gracias por unirte a {{appName}}. Estamos encantados de tenerte.</p>
983
13
  <p><a href="{{actionUrl}}">Comenzar ahora</a></p>
984
- `
985
- },
986
- inApp: {
987
- title: "\xA1Bienvenido a {{appName}}!",
988
- body: "Gracias por unirte. Haz clic para completar tu perfil.",
989
- actionUrl: "{{actionUrl}}"
990
- }
991
- }
992
- }
993
- });
994
- var OrgInviteTemplate = defineTemplate({
995
- id: "org-invite",
996
- name: "Organization Invitation",
997
- description: "Sent when a user is invited to an organization.",
998
- category: "organization",
999
- variables: [
1000
- { name: "inviterName", type: "string", required: true },
1001
- { name: "orgName", type: "string", required: true },
1002
- { name: "role", type: "string", default: "member" },
1003
- { name: "actionUrl", type: "url", required: true }
1004
- ],
1005
- defaultChannels: ["EMAIL"],
1006
- channels: {
1007
- email: {
1008
- subject: "{{inviterName}} invited you to join {{orgName}}",
1009
- body: `
14
+ `},inApp:{title:"\xA1Bienvenido a {{appName}}!",body:"Gracias por unirte. Haz clic para completar tu perfil.",actionUrl:"{{actionUrl}}"}}}}),oj=U({id:"org-invite",name:"Organization Invitation",description:"Sent when a user is invited to an organization.",category:"organization",variables:[{name:"inviterName",type:"string",required:!0},{name:"orgName",type:"string",required:!0},{name:"role",type:"string",default:"member"},{name:"actionUrl",type:"url",required:!0}],defaultChannels:["EMAIL"],channels:{email:{subject:"{{inviterName}} invited you to join {{orgName}}",body:`
1010
15
  <h1>You've been invited!</h1>
1011
16
  <p>{{inviterName}} has invited you to join <strong>{{orgName}}</strong> as a {{role}}.</p>
1012
17
  <p><a href="{{actionUrl}}">Accept invitation</a></p>
1013
- `
1014
- },
1015
- inApp: {
1016
- title: "Invitation to {{orgName}}",
1017
- body: "{{inviterName}} invited you to join as {{role}}.",
1018
- actionUrl: "{{actionUrl}}",
1019
- actionText: "Accept"
1020
- }
1021
- },
1022
- localeChannels: {
1023
- fr: {
1024
- email: {
1025
- subject: "{{inviterName}} vous invite \xE0 rejoindre {{orgName}}",
1026
- body: `
18
+ `},inApp:{title:"Invitation to {{orgName}}",body:"{{inviterName}} invited you to join as {{role}}.",actionUrl:"{{actionUrl}}",actionText:"Accept"}},localeChannels:{fr:{email:{subject:"{{inviterName}} vous invite \xE0 rejoindre {{orgName}}",body:`
1027
19
  <h1>Vous \xEAtes invit\xE9\u202F!</h1>
1028
20
  <p>{{inviterName}} vous a invit\xE9 \xE0 rejoindre <strong>{{orgName}}</strong> en tant que {{role}}.</p>
1029
21
  <p><a href="{{actionUrl}}">Accepter l\u2019invitation</a></p>
1030
- `
1031
- },
1032
- inApp: {
1033
- title: "Invitation \xE0 {{orgName}}",
1034
- body: "{{inviterName}} vous a invit\xE9 \xE0 rejoindre en tant que {{role}}.",
1035
- actionUrl: "{{actionUrl}}",
1036
- actionText: "Accepter"
1037
- }
1038
- },
1039
- es: {
1040
- email: {
1041
- subject: "{{inviterName}} te invit\xF3 a unirte a {{orgName}}",
1042
- body: `
22
+ `},inApp:{title:"Invitation \xE0 {{orgName}}",body:"{{inviterName}} vous a invit\xE9 \xE0 rejoindre en tant que {{role}}.",actionUrl:"{{actionUrl}}",actionText:"Accepter"}},es:{email:{subject:"{{inviterName}} te invit\xF3 a unirte a {{orgName}}",body:`
1043
23
  <h1>\xA1Has sido invitado!</h1>
1044
24
  <p>{{inviterName}} te ha invitado a unirte a <strong>{{orgName}}</strong> como {{role}}.</p>
1045
25
  <p><a href="{{actionUrl}}">Aceptar invitaci\xF3n</a></p>
1046
- `
1047
- },
1048
- inApp: {
1049
- title: "Invitaci\xF3n a {{orgName}}",
1050
- body: "{{inviterName}} te invit\xF3 a unirte como {{role}}.",
1051
- actionUrl: "{{actionUrl}}",
1052
- actionText: "Aceptar"
1053
- }
1054
- }
1055
- }
1056
- });
1057
- var MentionTemplate = defineTemplate({
1058
- id: "mention",
1059
- name: "Mention",
1060
- description: "Sent when a user is mentioned.",
1061
- category: "social",
1062
- variables: [
1063
- { name: "mentionerName", type: "string", required: true },
1064
- { name: "context", type: "string", required: true },
1065
- { name: "preview", type: "string" },
1066
- { name: "actionUrl", type: "url", required: true }
1067
- ],
1068
- defaultChannels: ["IN_APP", "PUSH"],
1069
- channels: {
1070
- inApp: {
1071
- title: "{{mentionerName}} mentioned you",
1072
- body: 'In {{context}}: "{{preview}}"',
1073
- actionUrl: "{{actionUrl}}"
1074
- },
1075
- push: {
1076
- title: "{{mentionerName}} mentioned you",
1077
- body: "{{preview}}"
1078
- }
1079
- },
1080
- localeChannels: {
1081
- fr: {
1082
- inApp: {
1083
- title: "{{mentionerName}} vous a mentionn\xE9",
1084
- body: "Dans {{context}}\u202F: \xAB\u202F{{preview}}\u202F\xBB",
1085
- actionUrl: "{{actionUrl}}"
1086
- },
1087
- push: {
1088
- title: "{{mentionerName}} vous a mentionn\xE9",
1089
- body: "{{preview}}"
1090
- }
1091
- },
1092
- es: {
1093
- inApp: {
1094
- title: "{{mentionerName}} te mencion\xF3",
1095
- body: 'En {{context}}: "{{preview}}"',
1096
- actionUrl: "{{actionUrl}}"
1097
- },
1098
- push: {
1099
- title: "{{mentionerName}} te mencion\xF3",
1100
- body: "{{preview}}"
1101
- }
1102
- }
1103
- }
1104
- });
1105
- export {
1106
- renderTemplate,
1107
- renderNotificationTemplate,
1108
- notificationsSchemaContribution,
1109
- notificationEntities,
1110
- defineTemplate,
1111
- createTemplateRegistry,
1112
- createChannelRegistry,
1113
- WelcomeTemplate,
1114
- WebhookChannel,
1115
- UpdatePreferencesInputModel,
1116
- UpdateNotificationPreferencesContract,
1117
- TemplateRegistry,
1118
- SendNotificationInputModel,
1119
- SendNotificationContract,
1120
- PushChannel,
1121
- OrgInviteTemplate,
1122
- NotificationsFeature,
1123
- NotificationTemplateEntity,
1124
- NotificationStatusEnum,
1125
- NotificationPriorityEnum,
1126
- NotificationPreferenceModel,
1127
- NotificationPreferenceEntity,
1128
- NotificationModel,
1129
- NotificationFilterEnum,
1130
- NotificationEntity,
1131
- NotificationChannelEnum,
1132
- MentionTemplate,
1133
- MarkNotificationReadContract,
1134
- MarkAllNotificationsReadContract,
1135
- ListNotificationsOutputModel,
1136
- ListNotificationsInputModel,
1137
- ListNotificationsContract,
1138
- InAppChannel,
1139
- GetNotificationPreferencesContract,
1140
- EmailChannel,
1141
- DeliveryLogEntity,
1142
- DeleteNotificationContract,
1143
- ConsoleChannel,
1144
- ChannelRegistry
1145
- };
26
+ `},inApp:{title:"Invitaci\xF3n a {{orgName}}",body:"{{inviterName}} te invit\xF3 a unirte como {{role}}.",actionUrl:"{{actionUrl}}",actionText:"Aceptar"}}}}),cj=U({id:"mention",name:"Mention",description:"Sent when a user is mentioned.",category:"social",variables:[{name:"mentionerName",type:"string",required:!0},{name:"context",type:"string",required:!0},{name:"preview",type:"string"},{name:"actionUrl",type:"url",required:!0}],defaultChannels:["IN_APP","PUSH"],channels:{inApp:{title:"{{mentionerName}} mentioned you",body:'In {{context}}: "{{preview}}"',actionUrl:"{{actionUrl}}"},push:{title:"{{mentionerName}} mentioned you",body:"{{preview}}"}},localeChannels:{fr:{inApp:{title:"{{mentionerName}} vous a mentionn\xE9",body:"Dans {{context}}\u202F: \xAB\u202F{{preview}}\u202F\xBB",actionUrl:"{{actionUrl}}"},push:{title:"{{mentionerName}} vous a mentionn\xE9",body:"{{preview}}"}},es:{inApp:{title:"{{mentionerName}} te mencion\xF3",body:'En {{context}}: "{{preview}}"',actionUrl:"{{actionUrl}}"},push:{title:"{{mentionerName}} te mencion\xF3",body:"{{preview}}"}}}});export{x as renderTemplate,rj as renderNotificationTemplate,Ej as notificationsSchemaContribution,_j as notificationEntities,U as defineTemplate,dj as createTemplateRegistry,Wj as createChannelRegistry,fj as WelcomeTemplate,b as WebhookChannel,Kj as UpdatePreferencesInputModel,Tj as UpdateNotificationPreferencesContract,u as TemplateRegistry,Dj as SendNotificationInputModel,gj as SendNotificationContract,jj as PushChannel,oj as OrgInviteTemplate,sj as NotificationsFeature,Xj as NotificationTemplateEntity,S as NotificationStatusEnum,E as NotificationPriorityEnum,h as NotificationPreferenceModel,Yj as NotificationPreferenceEntity,$ as NotificationModel,Bj as NotificationFilterEnum,Vj as NotificationEntity,y as NotificationChannelEnum,cj as MentionTemplate,bj as MarkNotificationReadContract,Cj as MarkAllNotificationsReadContract,Jj as ListNotificationsOutputModel,Gj as ListNotificationsInputModel,Mj as ListNotificationsContract,g as InAppChannel,Nj as GetNotificationPreferencesContract,e as EmailChannel,Zj as DeliveryLogEntity,hj as DeleteNotificationContract,M as ConsoleChannel,C as ChannelRegistry};