@facteurjs/core 1.0.0-beta.0

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 (111) hide show
  1. package/dist/api/handlers/notifications.js +77 -0
  2. package/dist/api/handlers/preferences.js +43 -0
  3. package/dist/api/index.d.ts +16 -0
  4. package/dist/api/index.js +21 -0
  5. package/dist/api/types.d.ts +22 -0
  6. package/dist/api/types.js +0 -0
  7. package/dist/channels/discord/channel.d.ts +18 -0
  8. package/dist/channels/discord/channel.js +15 -0
  9. package/dist/channels/discord/index.d.ts +3 -0
  10. package/dist/channels/discord/index.js +4 -0
  11. package/dist/channels/discord/message.d.ts +147 -0
  12. package/dist/channels/discord/message.js +176 -0
  13. package/dist/channels/discord/types.d.ts +52 -0
  14. package/dist/channels/discord/types.js +0 -0
  15. package/dist/channels/fcm/channel.d.ts +22 -0
  16. package/dist/channels/fcm/channel.js +44 -0
  17. package/dist/channels/fcm/index.d.ts +3 -0
  18. package/dist/channels/fcm/index.js +4 -0
  19. package/dist/channels/fcm/message.d.ts +64 -0
  20. package/dist/channels/fcm/message.js +122 -0
  21. package/dist/channels/fcm/types.d.ts +29 -0
  22. package/dist/channels/fcm/types.js +0 -0
  23. package/dist/channels/slack/channel.d.ts +18 -0
  24. package/dist/channels/slack/channel.js +15 -0
  25. package/dist/channels/slack/index.d.ts +3 -0
  26. package/dist/channels/slack/index.js +4 -0
  27. package/dist/channels/slack/message.d.ts +209 -0
  28. package/dist/channels/slack/message.js +390 -0
  29. package/dist/channels/slack/types.d.ts +7 -0
  30. package/dist/channels/slack/types.js +0 -0
  31. package/dist/channels/transmit/channel.d.ts +21 -0
  32. package/dist/channels/transmit/channel.js +27 -0
  33. package/dist/channels/transmit/index.d.ts +3 -0
  34. package/dist/channels/transmit/index.js +4 -0
  35. package/dist/channels/transmit/message.d.ts +11 -0
  36. package/dist/channels/transmit/message.js +17 -0
  37. package/dist/channels/transmit/types.d.ts +11 -0
  38. package/dist/channels/transmit/types.js +0 -0
  39. package/dist/channels/twilio/channel.d.ts +21 -0
  40. package/dist/channels/twilio/channel.js +56 -0
  41. package/dist/channels/twilio/index.d.ts +4 -0
  42. package/dist/channels/twilio/index.js +4 -0
  43. package/dist/channels/twilio/message.d.ts +86 -0
  44. package/dist/channels/twilio/message.js +152 -0
  45. package/dist/channels/twilio/types.d.ts +51 -0
  46. package/dist/channels/twilio/types.js +0 -0
  47. package/dist/channels/webhook/exceptions.d.ts +18 -0
  48. package/dist/channels/webhook/exceptions.js +24 -0
  49. package/dist/channels/webhook/index.d.ts +4 -0
  50. package/dist/channels/webhook/index.js +5 -0
  51. package/dist/channels/webhook/message.d.ts +24 -0
  52. package/dist/channels/webhook/message.js +40 -0
  53. package/dist/channels/webhook/provider.d.ts +19 -0
  54. package/dist/channels/webhook/provider.js +63 -0
  55. package/dist/channels/webhook/types.d.ts +15 -0
  56. package/dist/channels/webhook/types.js +0 -0
  57. package/dist/channels/webpush/channel.d.ts +26 -0
  58. package/dist/channels/webpush/channel.js +55 -0
  59. package/dist/channels/webpush/index.d.ts +3 -0
  60. package/dist/channels/webpush/index.js +4 -0
  61. package/dist/channels/webpush/message.d.ts +90 -0
  62. package/dist/channels/webpush/message.js +174 -0
  63. package/dist/channels/webpush/types.d.ts +50 -0
  64. package/dist/channels/webpush/types.js +0 -0
  65. package/dist/database/adapters/knex.d.ts +6 -0
  66. package/dist/database/adapters/knex.js +116 -0
  67. package/dist/database/adapters/kysely.d.ts +6 -0
  68. package/dist/database/adapters/kysely.js +101 -0
  69. package/dist/database/channel.d.ts +24 -0
  70. package/dist/database/channel.js +42 -0
  71. package/dist/database/database.d.ts +18 -0
  72. package/dist/database/database.js +89 -0
  73. package/dist/database/index.d.ts +3 -0
  74. package/dist/database/index.js +4 -0
  75. package/dist/database/message.d.ts +26 -0
  76. package/dist/database/message.js +51 -0
  77. package/dist/database/types.d.ts +147 -0
  78. package/dist/database/types.js +0 -0
  79. package/dist/debug.js +7 -0
  80. package/dist/errors/duplicate_notification_exception.d.ts +13 -0
  81. package/dist/errors/duplicate_notification_exception.js +21 -0
  82. package/dist/errors/http_error.d.ts +16 -0
  83. package/dist/errors/http_error.js +30 -0
  84. package/dist/errors/index.d.ts +38 -0
  85. package/dist/errors/index.js +39 -0
  86. package/dist/events/events.d.ts +90 -0
  87. package/dist/events/events.js +83 -0
  88. package/dist/facteur.d.ts +37 -0
  89. package/dist/facteur.js +83 -0
  90. package/dist/fake.d.ts +47 -0
  91. package/dist/fake.js +85 -0
  92. package/dist/index.d.ts +15 -0
  93. package/dist/index.js +16 -0
  94. package/dist/notifications/channel_resolver.js +91 -0
  95. package/dist/notifications/notification_discoverer.d.ts +40 -0
  96. package/dist/notifications/notification_discoverer.js +113 -0
  97. package/dist/notifications/notification_sender.js +210 -0
  98. package/dist/options.d.ts +22 -0
  99. package/dist/options.js +57 -0
  100. package/dist/types/channel.d.ts +18 -0
  101. package/dist/types/channel.js +5 -0
  102. package/dist/types/events.d.ts +27 -0
  103. package/dist/types/extend.d.ts +25 -0
  104. package/dist/types/index.d.ts +8 -0
  105. package/dist/types/index.js +4 -0
  106. package/dist/types/notifications.d.ts +60 -0
  107. package/dist/types/notifications.js +38 -0
  108. package/dist/types/options.d.ts +157 -0
  109. package/dist/types/preferences.d.ts +40 -0
  110. package/dist/types/queue.d.ts +11 -0
  111. package/package.json +65 -0
@@ -0,0 +1,174 @@
1
+ //#region src/channels/webpush/message.ts
2
+ var WebpushMessage = class WebpushMessage {
3
+ #title;
4
+ #body;
5
+ #icon;
6
+ #image;
7
+ #badge;
8
+ #tag;
9
+ #data;
10
+ #actions;
11
+ #url;
12
+ #requireInteraction;
13
+ #silent;
14
+ #renotify;
15
+ #timestamp;
16
+ #vibrate;
17
+ #dir;
18
+ #lang;
19
+ /**
20
+ * Creates a new instance of WebpushMessage
21
+ */
22
+ static create() {
23
+ return new WebpushMessage();
24
+ }
25
+ /**
26
+ * Sets the notification title
27
+ */
28
+ setTitle(title) {
29
+ this.#title = title;
30
+ return this;
31
+ }
32
+ /**
33
+ * Sets the notification body
34
+ */
35
+ setBody(body) {
36
+ this.#body = body;
37
+ return this;
38
+ }
39
+ /**
40
+ * Sets the notification icon
41
+ */
42
+ setIcon(icon) {
43
+ this.#icon = icon;
44
+ return this;
45
+ }
46
+ /**
47
+ * Sets the notification image
48
+ */
49
+ setImage(image) {
50
+ this.#image = image;
51
+ return this;
52
+ }
53
+ /**
54
+ * Sets the notification badge
55
+ */
56
+ setBadge(badge) {
57
+ this.#badge = badge;
58
+ return this;
59
+ }
60
+ /**
61
+ * Sets the notification tag
62
+ */
63
+ setTag(tag) {
64
+ this.#tag = tag;
65
+ return this;
66
+ }
67
+ /**
68
+ * Sets the notification data
69
+ */
70
+ setData(data) {
71
+ this.#data = data;
72
+ return this;
73
+ }
74
+ /**
75
+ * Sets the notification actions
76
+ */
77
+ setActions(actions) {
78
+ this.#actions = actions;
79
+ return this;
80
+ }
81
+ /**
82
+ * Adds a single action
83
+ */
84
+ addAction(options) {
85
+ if (!this.#actions) this.#actions = [];
86
+ this.#actions.push(options);
87
+ return this;
88
+ }
89
+ /**
90
+ * Sets the URL to open when notification is clicked
91
+ */
92
+ setUrl(url) {
93
+ this.#url = url;
94
+ return this;
95
+ }
96
+ /**
97
+ * Sets whether the notification requires interaction
98
+ */
99
+ setRequireInteraction(requireInteraction) {
100
+ this.#requireInteraction = requireInteraction;
101
+ return this;
102
+ }
103
+ /**
104
+ * Sets whether the notification should be silent
105
+ */
106
+ setSilent(silent) {
107
+ this.#silent = silent;
108
+ return this;
109
+ }
110
+ /**
111
+ * Sets whether to renotify when replacing an existing notification
112
+ */
113
+ setRenotify(renotify) {
114
+ this.#renotify = renotify;
115
+ return this;
116
+ }
117
+ /**
118
+ * Sets the notification timestamp
119
+ */
120
+ setTimestamp(timestamp) {
121
+ this.#timestamp = timestamp;
122
+ return this;
123
+ }
124
+ /**
125
+ * Sets the vibration pattern
126
+ */
127
+ setVibrate(vibrate) {
128
+ this.#vibrate = vibrate;
129
+ return this;
130
+ }
131
+ /**
132
+ * Sets the text direction
133
+ */
134
+ setDir(dir) {
135
+ this.#dir = dir;
136
+ return this;
137
+ }
138
+ /**
139
+ * Sets the notification language
140
+ */
141
+ setLang(lang) {
142
+ this.#lang = lang;
143
+ return this;
144
+ }
145
+ /**
146
+ * Serializes the message to web push format
147
+ */
148
+ serialize() {
149
+ const notification = {};
150
+ if (this.#title) notification.title = this.#title;
151
+ if (this.#body) notification.body = this.#body;
152
+ if (this.#icon) notification.icon = this.#icon;
153
+ if (this.#image) notification.image = this.#image;
154
+ if (this.#badge) notification.badge = this.#badge;
155
+ if (this.#tag) notification.tag = this.#tag;
156
+ if (this.#data !== void 0) notification.data = this.#data;
157
+ if (this.#actions) notification.actions = this.#actions;
158
+ if (this.#url) notification.data = {
159
+ ...notification.data,
160
+ url: this.#url
161
+ };
162
+ if (this.#requireInteraction !== void 0) notification.requireInteraction = this.#requireInteraction;
163
+ if (this.#silent !== void 0) notification.silent = this.#silent;
164
+ if (this.#renotify !== void 0) notification.renotify = this.#renotify;
165
+ if (this.#timestamp !== void 0) notification.timestamp = this.#timestamp;
166
+ if (this.#vibrate) notification.vibrate = this.#vibrate;
167
+ if (this.#dir) notification.dir = this.#dir;
168
+ if (this.#lang) notification.lang = this.#lang;
169
+ return JSON.stringify(notification);
170
+ }
171
+ };
172
+
173
+ //#endregion
174
+ export { WebpushMessage };
@@ -0,0 +1,50 @@
1
+ //#region src/channels/webpush/types.d.ts
2
+ interface WebpushConfig {
3
+ /**
4
+ * VAPID subject (mailto: or https:// URL)
5
+ */
6
+ vapidSubject: string;
7
+ /**
8
+ * VAPID public key (URL Safe Base64 encoded)
9
+ */
10
+ vapidPublicKey: string;
11
+ /**
12
+ * VAPID private key (URL Safe Base64 encoded)
13
+ */
14
+ vapidPrivateKey: string;
15
+ /**
16
+ * GCM API Key (optional, for legacy support)
17
+ */
18
+ gcmApiKey?: string;
19
+ /**
20
+ * Default TTL in seconds (default: 4 weeks)
21
+ */
22
+ ttl?: number;
23
+ /**
24
+ * Default urgency level
25
+ */
26
+ urgency?: 'very-low' | 'low' | 'normal' | 'high';
27
+ /**
28
+ * HTTP proxy configuration
29
+ */
30
+ proxy?: string;
31
+ /**
32
+ * Request timeout in milliseconds
33
+ */
34
+ timeout?: number;
35
+ }
36
+ interface WebpushTargets {
37
+ /**
38
+ * Push subscription object from the browser
39
+ */
40
+ subscription: {
41
+ endpoint: string;
42
+ keys: {
43
+ p256dh: string;
44
+ auth: string;
45
+ };
46
+ expirationTime?: number | null;
47
+ };
48
+ }
49
+ //#endregion
50
+ export { WebpushConfig, WebpushTargets };
File without changes
@@ -0,0 +1,6 @@
1
+ import { DatabaseAdapter, KnexConfig } from "../types.js";
2
+
3
+ //#region src/database/adapters/knex.d.ts
4
+ declare function knexAdapter(config: KnexConfig): DatabaseAdapter;
5
+ //#endregion
6
+ export { knexAdapter };
@@ -0,0 +1,116 @@
1
+ //#region src/database/adapters/knex.ts
2
+ function knexAdapter(config) {
3
+ return new KnexAdapter(config);
4
+ }
5
+ var KnexAdapter = class {
6
+ #tableName = "notifications";
7
+ #preferencesTableName = "notification_preferences";
8
+ #connection;
9
+ constructor(config) {
10
+ this.#connection = config.connection;
11
+ this.#tableName = config.tableNames?.notifications || "notifications";
12
+ this.#preferencesTableName = config.tableNames?.preferences || "notification_preferences";
13
+ }
14
+ setTableName(tableName) {
15
+ this.#tableName = tableName;
16
+ }
17
+ async save(options) {
18
+ await this.#connection.table(this.#tableName).insert({
19
+ notifiable_id: options.notifiableId,
20
+ tenant_id: options.tenantId || null,
21
+ type: options.type,
22
+ content: JSON.stringify(options.content),
23
+ status: options.status,
24
+ created_at: options.createdAt || /* @__PURE__ */ new Date(),
25
+ updated_at: options.updatedAt || /* @__PURE__ */ new Date(),
26
+ tags: options.tags ? JSON.stringify(options.tags) : null
27
+ });
28
+ }
29
+ async getNotifications(options) {
30
+ const page = options.page || 1;
31
+ const limit = Math.min(options.limit || 10, 100);
32
+ const offset = (page - 1) * limit;
33
+ let query = this.#connection.table(this.#tableName).where("notifiable_id", options.notifiableId);
34
+ if (options.tenantId) query = query.where("tenant_id", options.tenantId);
35
+ if (options.status) query = query.where("status", options.status);
36
+ const results = await query.orderBy("created_at", "desc").limit(limit).offset(offset).select("*");
37
+ return results.map((row) => ({
38
+ id: row.id,
39
+ notifiableId: row.notifiable_id,
40
+ tenantId: row.tenant_id,
41
+ type: row.type,
42
+ content: typeof row.content === "string" ? JSON.parse(row.content) : row.content,
43
+ status: row.status,
44
+ tags: row.tags ? typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags : void 0,
45
+ readAt: row.read_at,
46
+ seenAt: row.seen_at,
47
+ createdAt: row.created_at,
48
+ updatedAt: row.updated_at
49
+ }));
50
+ }
51
+ async updateNotification(options) {
52
+ const updateData = {
53
+ status: options.status,
54
+ updated_at: /* @__PURE__ */ new Date()
55
+ };
56
+ if (options.status === "read") updateData.read_at = /* @__PURE__ */ new Date();
57
+ else if (options.status === "seen") updateData.seen_at = /* @__PURE__ */ new Date();
58
+ await this.#connection.table(this.#tableName).where("id", options.id).update(updateData);
59
+ }
60
+ async updateAllNotifications(options) {
61
+ const updateData = {
62
+ status: options.status,
63
+ updated_at: /* @__PURE__ */ new Date()
64
+ };
65
+ if (options.status === "read") updateData.read_at = /* @__PURE__ */ new Date();
66
+ else if (options.status === "seen") updateData.seen_at = /* @__PURE__ */ new Date();
67
+ let query = this.#connection.table(this.#tableName).where("notifiable_id", options.notifiableId);
68
+ if (options.tenantId) query = query.where("tenant_id", options.tenantId);
69
+ await query.update(updateData);
70
+ }
71
+ async pruneNotifications(options) {
72
+ let query = this.#connection.table(this.#tableName);
73
+ if (options.notifiableId) query = query.where("notifiable_id", options.notifiableId);
74
+ if (options.tenantId) query = query.where("tenant_id", options.tenantId);
75
+ if (options.olderThan) query = query.where("created_at", "<", options.olderThan);
76
+ await query.del();
77
+ }
78
+ async getPreferences(options) {
79
+ const results = await this.#connection.table(this.#preferencesTableName).where("user_id", options.notifiableId).andWhere((builder) => {
80
+ if (options.tenantId) builder.where("tenant_id", options.tenantId);
81
+ else builder.whereNull("tenant_id");
82
+ }).select("*");
83
+ return results.map((row) => ({
84
+ id: row.id,
85
+ user_id: row.user_id,
86
+ tenant_id: row.tenant_id,
87
+ notification_name: row.notification_name,
88
+ channels: typeof row.channels === "string" ? JSON.parse(row.channels) : row.channels,
89
+ created_at: row.created_at,
90
+ updated_at: row.updated_at
91
+ }));
92
+ }
93
+ async updatePreferences(options) {
94
+ await this.#connection.transaction(async (trx) => {
95
+ const existing = await trx.table(this.#preferencesTableName).where("user_id", options.notifiableId).andWhere("notification_name", options.notificationName).andWhere((builder) => {
96
+ if (options.tenantId) builder.where("tenant_id", options.tenantId);
97
+ else builder.whereNull("tenant_id");
98
+ }).first();
99
+ if (existing) await trx.table(this.#preferencesTableName).where("id", existing.id).update({
100
+ channels: JSON.stringify(options.channelPreferences),
101
+ updated_at: /* @__PURE__ */ new Date()
102
+ });
103
+ else await trx.table(this.#preferencesTableName).insert({
104
+ user_id: options.notifiableId,
105
+ tenant_id: options.tenantId || null,
106
+ notification_name: options.notificationName,
107
+ channels: JSON.stringify(options.channelPreferences),
108
+ created_at: /* @__PURE__ */ new Date(),
109
+ updated_at: /* @__PURE__ */ new Date()
110
+ });
111
+ });
112
+ }
113
+ };
114
+
115
+ //#endregion
116
+ export { knexAdapter };
@@ -0,0 +1,6 @@
1
+ import { DatabaseAdapter, KyselyConfig } from "../types.js";
2
+
3
+ //#region src/database/adapters/kysely.d.ts
4
+ declare function kyselyAdapter(config: KyselyConfig): DatabaseAdapter;
5
+ //#endregion
6
+ export { kyselyAdapter };
@@ -0,0 +1,101 @@
1
+ //#region src/database/adapters/kysely.ts
2
+ function kyselyAdapter(config) {
3
+ return new KyselyAdapter(config);
4
+ }
5
+ var KyselyAdapter = class {
6
+ #tableName;
7
+ #preferencesTableName = "notification_preferences";
8
+ #connection;
9
+ constructor(config) {
10
+ this.#connection = config.connection;
11
+ this.#tableName = config.tableNames?.notifications || "notifications";
12
+ this.#preferencesTableName = config.tableNames?.preferences || "notification_preferences";
13
+ }
14
+ setTableName(tableName) {
15
+ this.#tableName = tableName;
16
+ }
17
+ async save(options) {
18
+ await this.#connection.insertInto(this.#tableName).values({
19
+ notifiable_id: options.notifiableId,
20
+ tenant_id: options.tenantId || null,
21
+ type: options.type,
22
+ content: JSON.stringify(options.content),
23
+ status: options.status,
24
+ created_at: options.createdAt || /* @__PURE__ */ new Date(),
25
+ updated_at: options.updatedAt || /* @__PURE__ */ new Date(),
26
+ tags: options.tags ? JSON.stringify(options.tags) : null
27
+ }).execute();
28
+ }
29
+ async getNotifications(options) {
30
+ const page = options.page || 1;
31
+ const limit = Math.min(options.limit || 10, 100);
32
+ const offset = (page - 1) * limit;
33
+ const results = await this.#connection.selectFrom(this.#tableName).selectAll().where("notifiable_id", "=", options.notifiableId).$if(!!options.tenantId, (qb) => qb.where("tenant_id", "=", options.tenantId)).$if(!!options.status, (qb) => qb.where("status", "=", options.status)).orderBy("created_at", "desc").limit(limit).offset(offset).execute();
34
+ return results.map((row) => ({
35
+ id: row.id,
36
+ notifiableId: row.notifiable_id,
37
+ tenantId: row.tenant_id,
38
+ type: row.type,
39
+ content: typeof row.content === "string" ? JSON.parse(row.content) : row.content,
40
+ status: row.status,
41
+ tags: row.tags ? typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags : void 0,
42
+ readAt: row.read_at,
43
+ seenAt: row.seen_at,
44
+ createdAt: row.created_at,
45
+ updatedAt: row.updated_at
46
+ }));
47
+ }
48
+ async updateNotification(options) {
49
+ const updateData = {
50
+ status: options.status,
51
+ updated_at: /* @__PURE__ */ new Date()
52
+ };
53
+ if (options.status === "read") updateData.read_at = /* @__PURE__ */ new Date();
54
+ else if (options.status === "seen") updateData.seen_at = /* @__PURE__ */ new Date();
55
+ await this.#connection.updateTable(this.#tableName).set(updateData).where("id", "=", options.id).execute();
56
+ }
57
+ async updateAllNotifications(options) {
58
+ const updateData = {
59
+ status: options.status,
60
+ updated_at: /* @__PURE__ */ new Date()
61
+ };
62
+ if (options.status === "read") updateData.read_at = /* @__PURE__ */ new Date();
63
+ else if (options.status === "seen") updateData.seen_at = /* @__PURE__ */ new Date();
64
+ await this.#connection.updateTable(this.#tableName).set(updateData).where("notifiable_id", "=", options.notifiableId).$if(!!options.tenantId, (qb) => qb.where("tenant_id", "=", options.tenantId)).execute();
65
+ }
66
+ async pruneNotifications(options) {
67
+ await this.#connection.deleteFrom(this.#tableName).$if(!!options.notifiableId, (qb) => qb.where("notifiable_id", "=", options.notifiableId)).$if(!!options.tenantId, (qb) => qb.where("tenant_id", "=", options.tenantId)).$if(!!options.olderThan, (qb) => qb.where("created_at", "<", options.olderThan)).execute();
68
+ }
69
+ async getPreferences(options) {
70
+ const results = await this.#connection.selectFrom(this.#preferencesTableName).selectAll().where("user_id", "=", options.notifiableId).$if(!!options.tenantId, (qb) => qb.where("tenant_id", "=", options.tenantId)).$if(!options.tenantId, (qb) => qb.where("tenant_id", "is", null)).execute();
71
+ return results.map((row) => ({
72
+ id: row.id,
73
+ user_id: row.user_id,
74
+ tenant_id: row.tenant_id,
75
+ notification_name: row.notification_name,
76
+ channels: typeof row.channels === "string" ? JSON.parse(row.channels) : row.channels,
77
+ created_at: row.created_at,
78
+ updated_at: row.updated_at
79
+ }));
80
+ }
81
+ async updatePreferences(options) {
82
+ await this.#connection.transaction().execute(async (trx) => {
83
+ const existing = await trx.selectFrom(this.#preferencesTableName).selectAll().where("user_id", "=", options.notifiableId).where("notification_name", "=", options.notificationName).$if(!!options.tenantId, (qb) => qb.where("tenant_id", "=", options.tenantId)).$if(!options.tenantId, (qb) => qb.where("tenant_id", "is", null)).executeTakeFirst();
84
+ if (existing) await trx.updateTable(this.#preferencesTableName).set({
85
+ channels: JSON.stringify(options.channelPreferences),
86
+ updated_at: /* @__PURE__ */ new Date()
87
+ }).where("id", "=", existing.id).execute();
88
+ else await trx.insertInto(this.#preferencesTableName).values({
89
+ user_id: options.notifiableId,
90
+ tenant_id: options.tenantId || null,
91
+ notification_name: options.notificationName,
92
+ channels: JSON.stringify(options.channelPreferences),
93
+ created_at: /* @__PURE__ */ new Date(),
94
+ updated_at: /* @__PURE__ */ new Date()
95
+ }).execute();
96
+ });
97
+ }
98
+ };
99
+
100
+ //#endregion
101
+ export { kyselyAdapter };
@@ -0,0 +1,24 @@
1
+ import { DatabaseConfig, Identifier } from "./types.js";
2
+ import { Channel, ChannelSendParams, kTargetSymbol } from "../types/channel.js";
3
+ import { DatabaseMessage } from "./message.js";
4
+
5
+ //#region src/database/channel.d.ts
6
+ declare function databaseChannel(options: DatabaseConfig): DatabaseChannel;
7
+ type DatabaseTargets = {
8
+ notifiableId: Identifier;
9
+ tenantId?: Identifier;
10
+ };
11
+ declare class DatabaseChannel implements Channel<DatabaseConfig, DatabaseMessage, any, DatabaseTargets> {
12
+ #private;
13
+ name: "database";
14
+ [kTargetSymbol]: DatabaseTargets;
15
+ constructor(config: DatabaseConfig);
16
+ send(options: ChannelSendParams<DatabaseMessage, DatabaseTargets>): Promise<void>;
17
+ }
18
+ declare module '@facteurjs/core/types' {
19
+ interface Notification {
20
+ asDatabaseMessage(): DatabaseMessage;
21
+ }
22
+ }
23
+ //#endregion
24
+ export { DatabaseChannel, databaseChannel };
@@ -0,0 +1,42 @@
1
+ import { errors } from "../errors/index.js";
2
+ import { kTargetSymbol } from "../types/channel.js";
3
+ import "../types/index.js";
4
+ import { DatabaseMessage } from "./message.js";
5
+
6
+ //#region src/database/channel.ts
7
+ function databaseChannel(options) {
8
+ return new DatabaseChannel(options);
9
+ }
10
+ var DatabaseChannel = class {
11
+ name = "database";
12
+ #adapter;
13
+ [kTargetSymbol] = null;
14
+ constructor(config) {
15
+ this.#adapter = config.adapter;
16
+ }
17
+ #resolveTargets(options) {
18
+ if (options.targets) return options.targets;
19
+ throw new errors.E_UNAVAILABLE_TARGETS(["DATABASE"]);
20
+ }
21
+ async send(options) {
22
+ const message = options.message.serialize();
23
+ const targets = this.#resolveTargets(options);
24
+ const notifiableId = message.notifiableId || targets.notifiableId;
25
+ const tenantId = message.tenantId || targets.tenantId || options.tenantId;
26
+ if (!notifiableId) throw new Error("No notifiableId provided");
27
+ const result = await this.#adapter.save({
28
+ notifiableId,
29
+ tenantId,
30
+ content: message.content,
31
+ type: message.type,
32
+ status: message.status,
33
+ tags: message.tags,
34
+ createdAt: /* @__PURE__ */ new Date(),
35
+ updatedAt: /* @__PURE__ */ new Date()
36
+ });
37
+ return result;
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ export { DatabaseChannel, databaseChannel };
@@ -0,0 +1,18 @@
1
+ import { DatabaseAdapter, GetNotificationsParams, GetPreferencesParams, Notification, Preferences, UpdateAllNotificationsParams, UpdateNotificationParams, UpdatePreferencesParams } from "./types.js";
2
+ import { FacteurOptions } from "../options.js";
3
+ import { NotificationDiscoverer } from "../notifications/notification_discoverer.js";
4
+
5
+ //#region src/database/database.d.ts
6
+ declare class FacteurDatabase {
7
+ #private;
8
+ private options;
9
+ private discoverer;
10
+ constructor(options: FacteurOptions<Record<string, any>, DatabaseAdapter>, discoverer: NotificationDiscoverer);
11
+ getNotifications(options: GetNotificationsParams): Promise<Notification[]> | undefined;
12
+ updateNotification(option: UpdateNotificationParams): Promise<void> | undefined;
13
+ updateAllNotifications(options: UpdateAllNotificationsParams): Promise<void> | undefined;
14
+ getPreferences(options: GetPreferencesParams): Promise<Preferences>;
15
+ updatePreferences(options: UpdatePreferencesParams): Promise<void> | undefined;
16
+ }
17
+ //#endregion
18
+ export { FacteurDatabase };
@@ -0,0 +1,89 @@
1
+ //#region src/database/database.ts
2
+ var FacteurDatabase = class {
3
+ constructor(options, discoverer) {
4
+ this.options = options;
5
+ this.discoverer = discoverer;
6
+ }
7
+ getNotifications(options) {
8
+ const page = options.page || 1;
9
+ const limit = Math.min(options.limit || 10, 100);
10
+ return this.options.databaseAdapter?.getNotifications({
11
+ page,
12
+ limit,
13
+ status: options.status,
14
+ tenantId: options.tenantId,
15
+ notifiableId: options.notifiableId
16
+ });
17
+ }
18
+ updateNotification(option) {
19
+ return this.options.databaseAdapter?.updateNotification({
20
+ id: option.id,
21
+ status: option.status
22
+ });
23
+ }
24
+ updateAllNotifications(options) {
25
+ return this.options.databaseAdapter?.updateAllNotifications({
26
+ notifiableId: options.notifiableId,
27
+ tenantId: options.tenantId,
28
+ status: options.status
29
+ });
30
+ }
31
+ async #createEmptyPreferences(tenantId) {
32
+ const globalChannels = this.options.defaultPreferences.global.channels;
33
+ const notificationIdentities = await this.discoverer.getNotificationIdentities();
34
+ const notificationPreferences = notificationIdentities.map((identity) => ({
35
+ notification: {
36
+ name: identity.name,
37
+ identifier: identity.identifier
38
+ },
39
+ channels: { ...globalChannels }
40
+ }));
41
+ const preferences = { global: {
42
+ global: { channels: { ...globalChannels } },
43
+ notifications: [...notificationPreferences]
44
+ } };
45
+ if (tenantId) preferences.tenants = { [tenantId]: {
46
+ global: { channels: { ...globalChannels } },
47
+ notifications: [...notificationPreferences]
48
+ } };
49
+ return preferences;
50
+ }
51
+ #processPreferenceRow(row, preferences, tenantId) {
52
+ const channels = row.channels;
53
+ const notificationName = row.notification_name;
54
+ if (tenantId && preferences.tenants) this.#updateTenantPreferences(preferences.tenants[tenantId], notificationName, channels);
55
+ else this.#updateGlobalPreferences(preferences.global, notificationName, channels);
56
+ }
57
+ async getPreferences(options) {
58
+ const rawPreferences = await this.options.databaseAdapter?.getPreferences(options);
59
+ if (!rawPreferences) return await this.#createEmptyPreferences(options.tenantId);
60
+ const preferences = await this.#createEmptyPreferences(options.tenantId);
61
+ for (const row of rawPreferences) this.#processPreferenceRow(row, preferences, options.tenantId);
62
+ return preferences;
63
+ }
64
+ #updateTenantPreferences(tenantPrefs, notificationName, channels) {
65
+ if (!notificationName) Object.assign(tenantPrefs.global.channels, channels);
66
+ else this.#updateNotificationPreferences(tenantPrefs.notifications, notificationName, channels);
67
+ }
68
+ #updateGlobalPreferences(globalPrefs, notificationName, channels) {
69
+ if (!notificationName) Object.assign(globalPrefs.global.channels, channels);
70
+ else this.#updateNotificationPreferences(globalPrefs.notifications, notificationName, channels);
71
+ }
72
+ #updateNotificationPreferences(notifications, notificationName, channels) {
73
+ let notifPref = notifications.find((n) => n.notification.name === notificationName);
74
+ if (!notifPref) {
75
+ notifPref = {
76
+ notification: { name: notificationName },
77
+ channels: {}
78
+ };
79
+ notifications.push(notifPref);
80
+ }
81
+ Object.assign(notifPref.channels, channels);
82
+ }
83
+ updatePreferences(options) {
84
+ return this.options.databaseAdapter?.updatePreferences(options);
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ export { FacteurDatabase };
@@ -0,0 +1,3 @@
1
+ import { DatabaseMessage } from "./message.js";
2
+ import { DatabaseChannel, databaseChannel } from "./channel.js";
3
+ export { DatabaseChannel, DatabaseMessage, databaseChannel };
@@ -0,0 +1,4 @@
1
+ import { DatabaseMessage } from "./message.js";
2
+ import { DatabaseChannel, databaseChannel } from "./channel.js";
3
+
4
+ export { DatabaseChannel, DatabaseMessage, databaseChannel };
@@ -0,0 +1,26 @@
1
+ import { NotificationStatus } from "./types.js";
2
+ import { DatabaseContent } from "../types/extend.js";
3
+
4
+ //#region src/database/message.d.ts
5
+ declare class DatabaseMessage {
6
+ #private;
7
+ static create(): DatabaseMessage;
8
+ setType(type: string): this;
9
+ setTags(tags: string[]): this;
10
+ setNotifiableId(notifiableId: string | number): this;
11
+ setTenantId(tenantId: string | number): this;
12
+ setStatus(status: NotificationStatus): this;
13
+ setContent(content: DatabaseContent): this;
14
+ serialize(): {
15
+ type: string;
16
+ content: any;
17
+ notifiableId: string | number | undefined;
18
+ tenantId: string | number | undefined;
19
+ status: NotificationStatus;
20
+ tags: string[];
21
+ createdAt: Date;
22
+ updatedAt: Date;
23
+ };
24
+ }
25
+ //#endregion
26
+ export { DatabaseMessage };