@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.
- package/dist/api/handlers/notifications.js +77 -0
- package/dist/api/handlers/preferences.js +43 -0
- package/dist/api/index.d.ts +16 -0
- package/dist/api/index.js +21 -0
- package/dist/api/types.d.ts +22 -0
- package/dist/api/types.js +0 -0
- package/dist/channels/discord/channel.d.ts +18 -0
- package/dist/channels/discord/channel.js +15 -0
- package/dist/channels/discord/index.d.ts +3 -0
- package/dist/channels/discord/index.js +4 -0
- package/dist/channels/discord/message.d.ts +147 -0
- package/dist/channels/discord/message.js +176 -0
- package/dist/channels/discord/types.d.ts +52 -0
- package/dist/channels/discord/types.js +0 -0
- package/dist/channels/fcm/channel.d.ts +22 -0
- package/dist/channels/fcm/channel.js +44 -0
- package/dist/channels/fcm/index.d.ts +3 -0
- package/dist/channels/fcm/index.js +4 -0
- package/dist/channels/fcm/message.d.ts +64 -0
- package/dist/channels/fcm/message.js +122 -0
- package/dist/channels/fcm/types.d.ts +29 -0
- package/dist/channels/fcm/types.js +0 -0
- package/dist/channels/slack/channel.d.ts +18 -0
- package/dist/channels/slack/channel.js +15 -0
- package/dist/channels/slack/index.d.ts +3 -0
- package/dist/channels/slack/index.js +4 -0
- package/dist/channels/slack/message.d.ts +209 -0
- package/dist/channels/slack/message.js +390 -0
- package/dist/channels/slack/types.d.ts +7 -0
- package/dist/channels/slack/types.js +0 -0
- package/dist/channels/transmit/channel.d.ts +21 -0
- package/dist/channels/transmit/channel.js +27 -0
- package/dist/channels/transmit/index.d.ts +3 -0
- package/dist/channels/transmit/index.js +4 -0
- package/dist/channels/transmit/message.d.ts +11 -0
- package/dist/channels/transmit/message.js +17 -0
- package/dist/channels/transmit/types.d.ts +11 -0
- package/dist/channels/transmit/types.js +0 -0
- package/dist/channels/twilio/channel.d.ts +21 -0
- package/dist/channels/twilio/channel.js +56 -0
- package/dist/channels/twilio/index.d.ts +4 -0
- package/dist/channels/twilio/index.js +4 -0
- package/dist/channels/twilio/message.d.ts +86 -0
- package/dist/channels/twilio/message.js +152 -0
- package/dist/channels/twilio/types.d.ts +51 -0
- package/dist/channels/twilio/types.js +0 -0
- package/dist/channels/webhook/exceptions.d.ts +18 -0
- package/dist/channels/webhook/exceptions.js +24 -0
- package/dist/channels/webhook/index.d.ts +4 -0
- package/dist/channels/webhook/index.js +5 -0
- package/dist/channels/webhook/message.d.ts +24 -0
- package/dist/channels/webhook/message.js +40 -0
- package/dist/channels/webhook/provider.d.ts +19 -0
- package/dist/channels/webhook/provider.js +63 -0
- package/dist/channels/webhook/types.d.ts +15 -0
- package/dist/channels/webhook/types.js +0 -0
- package/dist/channels/webpush/channel.d.ts +26 -0
- package/dist/channels/webpush/channel.js +55 -0
- package/dist/channels/webpush/index.d.ts +3 -0
- package/dist/channels/webpush/index.js +4 -0
- package/dist/channels/webpush/message.d.ts +90 -0
- package/dist/channels/webpush/message.js +174 -0
- package/dist/channels/webpush/types.d.ts +50 -0
- package/dist/channels/webpush/types.js +0 -0
- package/dist/database/adapters/knex.d.ts +6 -0
- package/dist/database/adapters/knex.js +116 -0
- package/dist/database/adapters/kysely.d.ts +6 -0
- package/dist/database/adapters/kysely.js +101 -0
- package/dist/database/channel.d.ts +24 -0
- package/dist/database/channel.js +42 -0
- package/dist/database/database.d.ts +18 -0
- package/dist/database/database.js +89 -0
- package/dist/database/index.d.ts +3 -0
- package/dist/database/index.js +4 -0
- package/dist/database/message.d.ts +26 -0
- package/dist/database/message.js +51 -0
- package/dist/database/types.d.ts +147 -0
- package/dist/database/types.js +0 -0
- package/dist/debug.js +7 -0
- package/dist/errors/duplicate_notification_exception.d.ts +13 -0
- package/dist/errors/duplicate_notification_exception.js +21 -0
- package/dist/errors/http_error.d.ts +16 -0
- package/dist/errors/http_error.js +30 -0
- package/dist/errors/index.d.ts +38 -0
- package/dist/errors/index.js +39 -0
- package/dist/events/events.d.ts +90 -0
- package/dist/events/events.js +83 -0
- package/dist/facteur.d.ts +37 -0
- package/dist/facteur.js +83 -0
- package/dist/fake.d.ts +47 -0
- package/dist/fake.js +85 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/notifications/channel_resolver.js +91 -0
- package/dist/notifications/notification_discoverer.d.ts +40 -0
- package/dist/notifications/notification_discoverer.js +113 -0
- package/dist/notifications/notification_sender.js +210 -0
- package/dist/options.d.ts +22 -0
- package/dist/options.js +57 -0
- package/dist/types/channel.d.ts +18 -0
- package/dist/types/channel.js +5 -0
- package/dist/types/events.d.ts +27 -0
- package/dist/types/extend.d.ts +25 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +4 -0
- package/dist/types/notifications.d.ts +60 -0
- package/dist/types/notifications.js +38 -0
- package/dist/types/options.d.ts +157 -0
- package/dist/types/preferences.d.ts +40 -0
- package/dist/types/queue.d.ts +11 -0
- 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,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,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,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 };
|