@facteurjs/core 1.0.0-beta.0 → 1.0.0-beta.2

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 (40) hide show
  1. package/dist/api/handlers/notifications.js +2 -1
  2. package/dist/channels/aws-sns/channel.d.ts +21 -0
  3. package/dist/channels/aws-sns/channel.js +42 -0
  4. package/dist/channels/aws-sns/index.d.ts +3 -0
  5. package/dist/channels/aws-sns/index.js +4 -0
  6. package/dist/channels/aws-sns/message.d.ts +23 -0
  7. package/dist/channels/aws-sns/message.js +29 -0
  8. package/dist/channels/aws-sns/types.d.ts +35 -0
  9. package/dist/channels/aws-sns/types.js +0 -0
  10. package/dist/channels/expo/channel.d.ts +23 -0
  11. package/dist/channels/expo/channel.js +38 -0
  12. package/dist/channels/expo/index.d.ts +3 -0
  13. package/dist/channels/expo/index.js +4 -0
  14. package/dist/channels/expo/message.d.ts +70 -0
  15. package/dist/channels/expo/message.js +130 -0
  16. package/dist/channels/expo/types.d.ts +9 -0
  17. package/dist/channels/expo/types.js +0 -0
  18. package/dist/channels/socketio/channel.d.ts +23 -0
  19. package/dist/channels/socketio/channel.js +29 -0
  20. package/dist/channels/socketio/index.d.ts +3 -0
  21. package/dist/channels/socketio/index.js +4 -0
  22. package/dist/channels/socketio/message.d.ts +11 -0
  23. package/dist/channels/socketio/message.js +17 -0
  24. package/dist/channels/socketio/types.d.ts +21 -0
  25. package/dist/channels/socketio/types.js +0 -0
  26. package/dist/database/adapters/knex.js +3 -2
  27. package/dist/database/adapters/kysely.js +1 -1
  28. package/dist/database/database.js +1 -0
  29. package/dist/database/types.d.ts +2 -0
  30. package/dist/facteur.js +18 -1
  31. package/dist/fake.d.ts +1 -1
  32. package/dist/fake.js +6 -2
  33. package/dist/helpers.js +8 -0
  34. package/dist/notifications/channel_resolver.js +5 -5
  35. package/dist/notifications/notification_sender.js +8 -7
  36. package/dist/types/channel.d.ts +1 -1
  37. package/dist/types/index.d.ts +2 -2
  38. package/dist/types/notifications.js +1 -1
  39. package/dist/types/options.d.ts +8 -4
  40. package/package.json +50 -1
@@ -14,7 +14,8 @@ const getNotificationRoute = defineRoute(({ facteur }) => ({
14
14
  tenantId: request.query.tenantId,
15
15
  page: request.query.page,
16
16
  limit: request.query.limit,
17
- status: request.query.status
17
+ status: request.query.status,
18
+ tags: request.query.tags ? JSON.parse(request.query.tags) : void 0
18
19
  });
19
20
  return {
20
21
  status: 200,
@@ -0,0 +1,21 @@
1
+ import { Channel, ChannelSendParams, kTargetSymbol } from "../../types/channel.js";
2
+ import { AwsSnsMessage } from "./message.js";
3
+ import { AwsSnsConfig, AwsSnsTargets } from "./types.js";
4
+
5
+ //#region src/channels/aws-sns/channel.d.ts
6
+ declare function awsSnsChannel(config: AwsSnsConfig): AwsSnsChannel;
7
+ declare class AwsSnsChannel implements Channel<AwsSnsConfig, AwsSnsMessage, any, AwsSnsTargets> {
8
+ #private;
9
+ private config;
10
+ name: "awsSns";
11
+ [kTargetSymbol]: AwsSnsTargets;
12
+ constructor(config: AwsSnsConfig);
13
+ send(options: ChannelSendParams<AwsSnsMessage, AwsSnsTargets>): Promise<void>;
14
+ }
15
+ declare module '@facteurjs/core/types' {
16
+ interface Notification {
17
+ asAwsSnsMessage(): AwsSnsMessage;
18
+ }
19
+ }
20
+ //#endregion
21
+ export { AwsSnsChannel, awsSnsChannel };
@@ -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 { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
5
+
6
+ //#region src/channels/aws-sns/channel.ts
7
+ function awsSnsChannel(config) {
8
+ return new AwsSnsChannel(config);
9
+ }
10
+ var AwsSnsChannel = class {
11
+ name = "awsSns";
12
+ [kTargetSymbol] = null;
13
+ #client;
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.#client = new SNSClient({
17
+ region: this.config.region,
18
+ credentials: {
19
+ accessKeyId: this.config.accessKeyId,
20
+ secretAccessKey: this.config.secretAccessKey,
21
+ ...this.config.sessionToken && { sessionToken: this.config.sessionToken }
22
+ }
23
+ });
24
+ }
25
+ #resolveTargets(options) {
26
+ if (options.targets) return options.targets;
27
+ throw new errors.E_UNAVAILABLE_TARGETS(["AWS SNS"]);
28
+ }
29
+ async send(options) {
30
+ const message = options.message;
31
+ const targets = this.#resolveTargets(options);
32
+ const messageData = message.serialize();
33
+ const command = new PublishCommand({
34
+ PhoneNumber: targets.to,
35
+ Message: messageData.Message
36
+ });
37
+ await this.#client.send(command);
38
+ }
39
+ };
40
+
41
+ //#endregion
42
+ export { AwsSnsChannel, awsSnsChannel };
@@ -0,0 +1,3 @@
1
+ import { AwsSnsMessage } from "./message.js";
2
+ import { AwsSnsChannel, awsSnsChannel } from "./channel.js";
3
+ export { AwsSnsChannel, AwsSnsMessage, awsSnsChannel };
@@ -0,0 +1,4 @@
1
+ import { AwsSnsMessage } from "./message.js";
2
+ import { AwsSnsChannel, awsSnsChannel } from "./channel.js";
3
+
4
+ export { AwsSnsChannel, AwsSnsMessage, awsSnsChannel };
@@ -0,0 +1,23 @@
1
+ //#region src/channels/aws-sns/message.d.ts
2
+ /**
3
+ * AWS SNS SMS message
4
+ */
5
+ declare class AwsSnsMessage {
6
+ #private;
7
+ /**
8
+ * Creates a new instance of AwsSnsMessage
9
+ */
10
+ static create(): AwsSnsMessage;
11
+ /**
12
+ * Set the message content
13
+ */
14
+ setMessage(message: string): this;
15
+ /**
16
+ * Serialize the message to AWS SNS format
17
+ */
18
+ serialize(): {
19
+ Message: string;
20
+ };
21
+ }
22
+ //#endregion
23
+ export { AwsSnsMessage };
@@ -0,0 +1,29 @@
1
+ //#region src/channels/aws-sns/message.ts
2
+ /**
3
+ * AWS SNS SMS message
4
+ */
5
+ var AwsSnsMessage = class AwsSnsMessage {
6
+ #message = "";
7
+ /**
8
+ * Creates a new instance of AwsSnsMessage
9
+ */
10
+ static create() {
11
+ return new AwsSnsMessage();
12
+ }
13
+ /**
14
+ * Set the message content
15
+ */
16
+ setMessage(message) {
17
+ this.#message = message;
18
+ return this;
19
+ }
20
+ /**
21
+ * Serialize the message to AWS SNS format
22
+ */
23
+ serialize() {
24
+ return { Message: this.#message };
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ export { AwsSnsMessage };
@@ -0,0 +1,35 @@
1
+ //#region src/channels/aws-sns/types.d.ts
2
+ /**
3
+ * Configuration for AWS SNS SMS channel
4
+ */
5
+ interface AwsSnsConfig {
6
+ region: string;
7
+ accessKeyId: string;
8
+ secretAccessKey: string;
9
+ sessionToken?: string;
10
+ }
11
+ /**
12
+ * SMS message data for AWS SNS
13
+ */
14
+ interface AwsSnsSmsData {
15
+ to: string;
16
+ content: string;
17
+ }
18
+ /**
19
+ * AWS SNS targets
20
+ */
21
+ interface AwsSnsTargets {
22
+ to: string;
23
+ }
24
+ interface AwsSnsTargets {
25
+ /**
26
+ * The phone number to send the SMS to (in E.164 format)
27
+ */
28
+ to: string;
29
+ /**
30
+ * Override the sender ID for this specific message
31
+ */
32
+ senderId?: string;
33
+ }
34
+ //#endregion
35
+ export { AwsSnsConfig, AwsSnsSmsData, AwsSnsTargets };
File without changes
@@ -0,0 +1,23 @@
1
+ import { Channel, ChannelSendParams, kTargetSymbol } from "../../types/channel.js";
2
+ import { ExpoMessage } from "./message.js";
3
+ import { ExpoConfig, ExpoTargets } from "./types.js";
4
+
5
+ //#region src/channels/expo/channel.d.ts
6
+ declare function expoChannel(config?: ExpoConfig): ExpoChannel;
7
+ declare class ExpoChannel implements Channel<ExpoConfig, ExpoMessage, any, ExpoTargets> {
8
+ #private;
9
+ name: "expo";
10
+ [kTargetSymbol]: ExpoTargets;
11
+ constructor(config: ExpoConfig);
12
+ send(options: ChannelSendParams<ExpoMessage, ExpoTargets>): Promise<{
13
+ id: string;
14
+ status: "ok";
15
+ }>;
16
+ }
17
+ declare module '@facteurjs/core/types' {
18
+ interface Notification {
19
+ asExpoMessage(): ExpoMessage;
20
+ }
21
+ }
22
+ //#endregion
23
+ export { ExpoChannel, expoChannel };
@@ -0,0 +1,38 @@
1
+ import { errors } from "../../errors/index.js";
2
+ import { kTargetSymbol } from "../../types/channel.js";
3
+ import "../../types/index.js";
4
+ import { Expo } from "expo-server-sdk";
5
+
6
+ //#region src/channels/expo/channel.ts
7
+ function expoChannel(config = {}) {
8
+ return new ExpoChannel(config);
9
+ }
10
+ var ExpoChannel = class {
11
+ name = "expo";
12
+ [kTargetSymbol] = null;
13
+ #expo;
14
+ constructor(config) {
15
+ this.#expo = new Expo(config);
16
+ if (config.accessToken) this.#validateExpoToken(config.accessToken);
17
+ }
18
+ #validateExpoToken(token) {
19
+ if (!Expo.isExpoPushToken(token)) throw new Error(`Invalid Expo push token: ${token}`);
20
+ }
21
+ #resolveTargets(options) {
22
+ if (options.targets) return options.targets;
23
+ throw new errors.E_UNAVAILABLE_TARGETS(["Expo"]);
24
+ }
25
+ async send(options) {
26
+ const targets = this.#resolveTargets(options);
27
+ const message = options.message.serialize({ to: targets.expoToken });
28
+ const [ticket] = await this.#expo.sendPushNotificationsAsync([message]);
29
+ if (ticket?.status === "error") throw new Error(`Expo push notification failed: ${ticket.message}`);
30
+ return {
31
+ id: ticket?.id,
32
+ status: ticket?.status
33
+ };
34
+ }
35
+ };
36
+
37
+ //#endregion
38
+ export { ExpoChannel, expoChannel };
@@ -0,0 +1,3 @@
1
+ import { ExpoMessage } from "./message.js";
2
+ import { ExpoChannel, expoChannel } from "./channel.js";
3
+ export { ExpoChannel, ExpoMessage, expoChannel };
@@ -0,0 +1,4 @@
1
+ import { ExpoMessage } from "./message.js";
2
+ import { ExpoChannel, expoChannel } from "./channel.js";
3
+
4
+ export { ExpoChannel, ExpoMessage, expoChannel };
@@ -0,0 +1,70 @@
1
+ import { ExpoPushMessage } from "expo-server-sdk";
2
+
3
+ //#region src/channels/expo/message.d.ts
4
+
5
+ /**
6
+ * Expo push notification message
7
+ */
8
+ declare class ExpoMessage {
9
+ #private;
10
+ /**
11
+ * Creates a new instance of ExpoMessage
12
+ */
13
+ static create(): ExpoMessage;
14
+ /**
15
+ * Set the notification title
16
+ */
17
+ setTitle(title: string): this;
18
+ /**
19
+ * Set the notification body
20
+ */
21
+ setBody(body: string): this;
22
+ /**
23
+ * Set additional data payload
24
+ */
25
+ setData(data: any): this;
26
+ /**
27
+ * Set the sound to play. Use 'default' for default sound
28
+ */
29
+ setSound(sound: string): this;
30
+ /**
31
+ * Set the badge number for iOS
32
+ */
33
+ setBadge(badge: number): this;
34
+ /**
35
+ * Set the notification channel ID for Android
36
+ */
37
+ setChannelId(channelId: string): this;
38
+ /**
39
+ * Set the notification category ID
40
+ */
41
+ setCategoryId(categoryId: string): this;
42
+ /**
43
+ * Set the subtitle (iOS only)
44
+ */
45
+ setSubtitle(subtitle: string): this;
46
+ /**
47
+ * Set the delivery priority
48
+ */
49
+ setPriority(priority: 'default' | 'normal' | 'high'): this;
50
+ /**
51
+ * Set time to live in seconds
52
+ */
53
+ setTtl(ttl: number): this;
54
+ /**
55
+ * Set expiration timestamp
56
+ */
57
+ setExpiration(expiration: number): this;
58
+ /**
59
+ * Set whether notification can be intercepted by client app (iOS only)
60
+ */
61
+ setMutableContent(mutableContent: boolean): this;
62
+ /**
63
+ * Serialize the message to Expo format
64
+ */
65
+ serialize(options: {
66
+ to: string;
67
+ }): ExpoPushMessage;
68
+ }
69
+ //#endregion
70
+ export { ExpoMessage };
@@ -0,0 +1,130 @@
1
+ //#region src/channels/expo/message.ts
2
+ /**
3
+ * Expo push notification message
4
+ */
5
+ var ExpoMessage = class ExpoMessage {
6
+ #title;
7
+ #body;
8
+ #data;
9
+ #sound;
10
+ #badge;
11
+ #channelId;
12
+ #categoryId;
13
+ #subtitle;
14
+ #priority = "default";
15
+ #ttl;
16
+ #expiration;
17
+ #mutableContent;
18
+ /**
19
+ * Creates a new instance of ExpoMessage
20
+ */
21
+ static create() {
22
+ return new ExpoMessage();
23
+ }
24
+ /**
25
+ * Set the notification title
26
+ */
27
+ setTitle(title) {
28
+ this.#title = title;
29
+ return this;
30
+ }
31
+ /**
32
+ * Set the notification body
33
+ */
34
+ setBody(body) {
35
+ this.#body = body;
36
+ return this;
37
+ }
38
+ /**
39
+ * Set additional data payload
40
+ */
41
+ setData(data) {
42
+ this.#data = data;
43
+ return this;
44
+ }
45
+ /**
46
+ * Set the sound to play. Use 'default' for default sound
47
+ */
48
+ setSound(sound) {
49
+ this.#sound = sound;
50
+ return this;
51
+ }
52
+ /**
53
+ * Set the badge number for iOS
54
+ */
55
+ setBadge(badge) {
56
+ this.#badge = badge;
57
+ return this;
58
+ }
59
+ /**
60
+ * Set the notification channel ID for Android
61
+ */
62
+ setChannelId(channelId) {
63
+ this.#channelId = channelId;
64
+ return this;
65
+ }
66
+ /**
67
+ * Set the notification category ID
68
+ */
69
+ setCategoryId(categoryId) {
70
+ this.#categoryId = categoryId;
71
+ return this;
72
+ }
73
+ /**
74
+ * Set the subtitle (iOS only)
75
+ */
76
+ setSubtitle(subtitle) {
77
+ this.#subtitle = subtitle;
78
+ return this;
79
+ }
80
+ /**
81
+ * Set the delivery priority
82
+ */
83
+ setPriority(priority) {
84
+ this.#priority = priority;
85
+ return this;
86
+ }
87
+ /**
88
+ * Set time to live in seconds
89
+ */
90
+ setTtl(ttl) {
91
+ this.#ttl = ttl;
92
+ return this;
93
+ }
94
+ /**
95
+ * Set expiration timestamp
96
+ */
97
+ setExpiration(expiration) {
98
+ this.#expiration = expiration;
99
+ return this;
100
+ }
101
+ /**
102
+ * Set whether notification can be intercepted by client app (iOS only)
103
+ */
104
+ setMutableContent(mutableContent) {
105
+ this.#mutableContent = mutableContent;
106
+ return this;
107
+ }
108
+ /**
109
+ * Serialize the message to Expo format
110
+ */
111
+ serialize(options) {
112
+ const message = { to: options.to };
113
+ if (this.#title) message.title = this.#title;
114
+ if (this.#body) message.body = this.#body;
115
+ if (this.#data !== void 0) message.data = this.#data;
116
+ if (this.#sound) message.sound = this.#sound;
117
+ if (this.#badge !== void 0) message.badge = this.#badge;
118
+ if (this.#channelId) message.channelId = this.#channelId;
119
+ if (this.#categoryId) message.categoryId = this.#categoryId;
120
+ if (this.#subtitle) message.subtitle = this.#subtitle;
121
+ if (this.#priority !== "default") message.priority = this.#priority;
122
+ if (this.#ttl !== void 0) message.ttl = this.#ttl;
123
+ if (this.#expiration !== void 0) message.expiration = this.#expiration;
124
+ if (this.#mutableContent !== void 0) message.mutableContent = this.#mutableContent;
125
+ return message;
126
+ }
127
+ };
128
+
129
+ //#endregion
130
+ export { ExpoMessage };
@@ -0,0 +1,9 @@
1
+ import { ExpoClientOptions } from "expo-server-sdk";
2
+
3
+ //#region src/channels/expo/types.d.ts
4
+ interface ExpoConfig extends Partial<ExpoClientOptions> {}
5
+ interface ExpoTargets {
6
+ expoToken: string;
7
+ }
8
+ //#endregion
9
+ export { ExpoConfig, ExpoTargets };
File without changes
@@ -0,0 +1,23 @@
1
+ import { Channel, ChannelSendParams, kTargetSymbol } from "../../types/channel.js";
2
+ import { SocketIoMessage } from "./message.js";
3
+ import { SocketIOConfig, SocketIOTargets } from "./types.js";
4
+ import { Server } from "socket.io";
5
+
6
+ //#region src/channels/socketio/channel.d.ts
7
+ declare function socketIoChannel(config: SocketIOConfig): SocketIOChannel;
8
+ declare class SocketIOChannel implements Channel<SocketIOConfig, SocketIoMessage, any, SocketIOTargets> {
9
+ #private;
10
+ private config;
11
+ name: "socketIo";
12
+ [kTargetSymbol]: SocketIOTargets;
13
+ protected server: () => Server;
14
+ constructor(config: SocketIOConfig);
15
+ send(options: ChannelSendParams<SocketIoMessage, SocketIOTargets>): Promise<void>;
16
+ }
17
+ declare module '@facteurjs/core/types' {
18
+ interface Notification {
19
+ asSocketIoMessage(): SocketIoMessage;
20
+ }
21
+ }
22
+ //#endregion
23
+ export { SocketIOChannel, socketIoChannel };
@@ -0,0 +1,29 @@
1
+ import { errors } from "../../errors/index.js";
2
+ import { kTargetSymbol } from "../../types/channel.js";
3
+ import "../../types/index.js";
4
+
5
+ //#region src/channels/socketio/channel.ts
6
+ function socketIoChannel(config) {
7
+ return new SocketIOChannel(config);
8
+ }
9
+ var SocketIOChannel = class {
10
+ name = "socketIo";
11
+ [kTargetSymbol] = null;
12
+ server;
13
+ constructor(config) {
14
+ this.config = config;
15
+ this.server = typeof this.config.server === "function" ? this.config.server : () => this.config.server;
16
+ }
17
+ #resolveTargets(options) {
18
+ if (options.targets) return options.targets;
19
+ throw new errors.E_UNAVAILABLE_TARGETS(["SocketIO"]);
20
+ }
21
+ async send(options) {
22
+ const message = options.message.serialize();
23
+ const targets = this.#resolveTargets(options);
24
+ this.server().of(targets.namespace || "/").emit(targets.event, message.data);
25
+ }
26
+ };
27
+
28
+ //#endregion
29
+ export { SocketIOChannel, socketIoChannel };
@@ -0,0 +1,3 @@
1
+ import { SocketIoMessage } from "./message.js";
2
+ import { SocketIOChannel, socketIoChannel } from "./channel.js";
3
+ export { SocketIOChannel, SocketIoMessage, socketIoChannel };
@@ -0,0 +1,4 @@
1
+ import { SocketIoMessage } from "./message.js";
2
+ import { SocketIOChannel, socketIoChannel } from "./channel.js";
3
+
4
+ export { SocketIOChannel, SocketIoMessage, socketIoChannel };
@@ -0,0 +1,11 @@
1
+ //#region src/channels/socketio/message.d.ts
2
+ declare class SocketIoMessage {
3
+ #private;
4
+ static create(): SocketIoMessage;
5
+ setData(data: Record<string, any>): this;
6
+ serialize(): {
7
+ data: Record<string, any>;
8
+ };
9
+ }
10
+ //#endregion
11
+ export { SocketIoMessage };
@@ -0,0 +1,17 @@
1
+ //#region src/channels/socketio/message.ts
2
+ var SocketIoMessage = class SocketIoMessage {
3
+ #data = {};
4
+ static create() {
5
+ return new SocketIoMessage();
6
+ }
7
+ setData(data) {
8
+ this.#data = data;
9
+ return this;
10
+ }
11
+ serialize() {
12
+ return { data: this.#data };
13
+ }
14
+ };
15
+
16
+ //#endregion
17
+ export { SocketIoMessage };
@@ -0,0 +1,21 @@
1
+ import { Server } from "socket.io";
2
+
3
+ //#region src/channels/socketio/types.d.ts
4
+ interface SocketIOTargets {
5
+ /**
6
+ * Namespace to emit the event to
7
+ */
8
+ namespace?: string;
9
+ /**
10
+ * Event to emit
11
+ */
12
+ event: string;
13
+ }
14
+ interface SocketIOConfig {
15
+ /**
16
+ * Socket.IO server instance. Can be a function that will be resolved when needed
17
+ */
18
+ server: Server | (() => Server);
19
+ }
20
+ //#endregion
21
+ export { SocketIOConfig, SocketIOTargets };
File without changes
@@ -31,8 +31,9 @@ var KnexAdapter = class {
31
31
  const limit = Math.min(options.limit || 10, 100);
32
32
  const offset = (page - 1) * limit;
33
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);
34
+ if (options.tenantId) query.where("tenant_id", options.tenantId);
35
+ if (options.status) query.where("status", options.status);
36
+ if (options.tags) query.whereJsonSupersetOf("tags", JSON.stringify(options.tags));
36
37
  const results = await query.orderBy("created_at", "desc").limit(limit).offset(offset).select("*");
37
38
  return results.map((row) => ({
38
39
  id: row.id,
@@ -30,7 +30,7 @@ var KyselyAdapter = class {
30
30
  const page = options.page || 1;
31
31
  const limit = Math.min(options.limit || 10, 100);
32
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();
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)).$if(!!options.tags, (qb) => qb.where("tags", "@>", JSON.stringify(options.tags))).orderBy("created_at", "desc").limit(limit).offset(offset).execute();
34
34
  return results.map((row) => ({
35
35
  id: row.id,
36
36
  notifiableId: row.notifiable_id,
@@ -10,6 +10,7 @@ var FacteurDatabase = class {
10
10
  return this.options.databaseAdapter?.getNotifications({
11
11
  page,
12
12
  limit,
13
+ tags: options.tags,
13
14
  status: options.status,
14
15
  tenantId: options.tenantId,
15
16
  notifiableId: options.notifiableId
@@ -11,6 +11,7 @@ interface AdapterGetNotificationsParams {
11
11
  page?: number | undefined;
12
12
  status?: NotificationStatus | undefined;
13
13
  limit?: number;
14
+ tags?: string[] | undefined;
14
15
  }
15
16
  interface GetNotificationsParams {
16
17
  notifiableId: Identifier;
@@ -18,6 +19,7 @@ interface GetNotificationsParams {
18
19
  page?: number;
19
20
  limit?: number;
20
21
  status?: NotificationStatus;
22
+ tags?: string[];
21
23
  }
22
24
  interface Notification {
23
25
  id: Identifier;
package/dist/facteur.js CHANGED
@@ -62,8 +62,25 @@ var Facteur = class {
62
62
  * Send a notification
63
63
  */
64
64
  async send(options) {
65
+ if ("via" in options && !("to" in options)) return this.#sendToSingle(options);
66
+ const optionsWithTo = options;
67
+ const recipients = Array.isArray(optionsWithTo.to) ? optionsWithTo.to : [optionsWithTo.to];
68
+ const results = await Promise.all(recipients.map((recipient) => this.#sendToSingle({
69
+ ...optionsWithTo,
70
+ to: recipient
71
+ })));
72
+ return {
73
+ success: results.reduce((sum, result) => sum + result.success, 0),
74
+ failed: results.reduce((sum, result) => sum + result.failed, 0),
75
+ results: results.flatMap((result) => result.results)
76
+ };
77
+ }
78
+ /**
79
+ * Send notification to a single recipient
80
+ */
81
+ async #sendToSingle(options) {
65
82
  const notification = await this.#options.notificationResolver(options.notification, {
66
- notifiable: "notifiable" in options ? options.notifiable : void 0,
83
+ to: "to" in options ? options.to : void 0,
67
84
  params: options.params,
68
85
  tenantId: options.tenantId
69
86
  });
package/dist/fake.d.ts CHANGED
@@ -4,7 +4,7 @@ import { Notification, NotificationSendResult } from "./types/notifications.js";
4
4
  //#region src/fake.d.ts
5
5
  interface SentNotification<N extends Notification = Notification> {
6
6
  notification: N;
7
- notifiable: N extends Notification<infer TNotifiable, any> ? TNotifiable : never;
7
+ to: N extends Notification<infer TNotifiable, any> ? TNotifiable : never;
8
8
  params?: N extends Notification<any, infer P> ? P : never;
9
9
  via?: any;
10
10
  }
package/dist/fake.js CHANGED
@@ -7,10 +7,14 @@ var FacteurFake = class {
7
7
  * Record a notification as sent during fake mode
8
8
  */
9
9
  recordSent(options) {
10
- const notification = new options.notification();
10
+ const notification = new options.notification({
11
+ to: options.to,
12
+ params: options.params,
13
+ tenantId: options.tenantId
14
+ });
11
15
  this.#sentNotifications.push({
12
16
  notification,
13
- notifiable: options.notifiable,
17
+ to: options.to,
14
18
  params: options.params,
15
19
  via: options.via
16
20
  });
@@ -0,0 +1,8 @@
1
+ //#region src/helpers.ts
2
+ function capitalizeFirstLetter(str) {
3
+ if (!str) return str;
4
+ return str.charAt(0).toUpperCase() + str.slice(1);
5
+ }
6
+
7
+ //#endregion
8
+ export { capitalizeFirstLetter };
@@ -12,7 +12,7 @@ var ChannelResolver = class {
12
12
  * Resolve channels and targets provided by via
13
13
  */
14
14
  #resolveVia(options) {
15
- const notifiableTargets = options.notifiable?.notificationTargets?.();
15
+ const notifiableTargets = options.to?.notificationTargets?.();
16
16
  return mapEntries(options.via, (channelName, channelConfig) => {
17
17
  const shouldSend = typeof channelConfig === "boolean" ? channelConfig : true;
18
18
  const target = invoke(() => {
@@ -26,10 +26,10 @@ var ChannelResolver = class {
26
26
  });
27
27
  }
28
28
  async resolveChannels(options) {
29
- const { notification, notifiable, params, via, tenantId } = options;
29
+ const { notification, to, params, via, tenantId } = options;
30
30
  const notificationOptions = notification.options;
31
31
  const notificationIdentifier = notificationOptions.identifier || notification.name;
32
- const notifiableTargets = options.notifiable?.notificationTargets?.();
32
+ const notifiableTargets = options.to?.notificationTargets?.();
33
33
  /**
34
34
  * First, if via is provided it should override everything.
35
35
  */
@@ -38,7 +38,7 @@ var ChannelResolver = class {
38
38
  * Get preferences for the notifiable and tenant
39
39
  */
40
40
  const preferences = await this.#database?.getPreferences({
41
- notifiableId: notifiable.id,
41
+ notifiableId: to.id,
42
42
  tenantId
43
43
  });
44
44
  /**
@@ -48,7 +48,7 @@ var ChannelResolver = class {
48
48
  const shouldSend = invoke(() => {
49
49
  if (typeof deliverBy === "boolean") return deliverBy;
50
50
  return deliverBy.if({
51
- notifiable,
51
+ to,
52
52
  params,
53
53
  preferences
54
54
  });
@@ -2,7 +2,7 @@ import { errors } from "../errors/index.js";
2
2
  import debug_default from "../debug.js";
3
3
  import { facteurEvents } from "../events/events.js";
4
4
  import "./channel_resolver.js";
5
- import { capitalize } from "@julr/utils/string";
5
+ import { capitalizeFirstLetter } from "../helpers.js";
6
6
 
7
7
  //#region src/notifications/notification_sender.ts
8
8
  /**
@@ -96,11 +96,12 @@ var NotificationSender = class {
96
96
  * `as<ChannelName>Message` method
97
97
  */
98
98
  const channel = this.#getChannel(channelName);
99
- const channelMethodName = `as${capitalize(channelName)}Message`;
99
+ const capitalizedChannelName = capitalizeFirstLetter(channelName);
100
+ const channelMethodName = `as${capitalizedChannelName}Message`;
100
101
  const messageBuilder = options.notification[channelMethodName];
101
- if (typeof messageBuilder !== "function") throw new errors.E_MISSING_MESSAGE_METHOD([capitalize(channelName)]);
102
+ if (typeof messageBuilder !== "function") throw new errors.E_MISSING_MESSAGE_METHOD([capitalizedChannelName]);
102
103
  const messageContent = messageBuilder.call(options.notification, {
103
- notifiable: sendOptions.notifiable,
104
+ to: sendOptions.to,
104
105
  params: sendOptions.params,
105
106
  tenantId: sendOptions.tenantId
106
107
  });
@@ -115,7 +116,7 @@ var NotificationSender = class {
115
116
  tenantId: sendOptions.tenantId,
116
117
  message: messageContent,
117
118
  targets: channelConfig.target,
118
- notifiable: sendOptions.notifiable
119
+ to: sendOptions.to
119
120
  });
120
121
  debug_default(`Message sent via ${channelName}`);
121
122
  this.#emitMessageSent(options.notification, channelName, messageContent);
@@ -166,9 +167,9 @@ var NotificationSender = class {
166
167
  */
167
168
  async send(options, notification) {
168
169
  const { via, params, tenantId } = options;
169
- const notifiable = "notifiable" in options ? options.notifiable : void 0;
170
+ const to = "to" in options ? options.to : void 0;
170
171
  const resolvedChannels = await this.channelResolver.resolveChannels({
171
- notifiable,
172
+ to,
172
173
  params,
173
174
  tenantId,
174
175
  notification: options.notification,
@@ -3,7 +3,7 @@ import { Awaitable } from "@julr/utils/types";
3
3
 
4
4
  //#region src/types/channel.d.ts
5
5
  type ChannelSendParams<Message, Targets> = {
6
- notifiable?: any;
6
+ to?: any;
7
7
  message: Message;
8
8
  targets?: Targets;
9
9
  tenantId?: Identifier | undefined;
@@ -3,6 +3,6 @@ import { Emitter, FacteurEvents } from "./events.js";
3
3
  import { QueueAdapter, QueueItemOptions } from "./queue.js";
4
4
  import { DefaultPreferences, ResolvedDefaultPreferences } from "./preferences.js";
5
5
  import { ChannelName, DatabaseContent, InferChannelsFromConfig, NotificationChannels } from "./extend.js";
6
- import { ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, SendOptions } from "./options.js";
6
+ import { Arrayable, ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, SendOptions } from "./options.js";
7
7
  import { ChannelSendResult, Notifiable, NotifiableTargets, Notification, NotificationClass, NotificationSendResult } from "./notifications.js";
8
- export { Channel, ChannelName, ChannelSendParams, ChannelSendResult, ChannelSpecificConfig, CommonSendOptions, DatabaseContent, DefaultPreferences, DeliverByOptions, Emitter, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, FacteurEvents, InferChannelsFromConfig, MessageCtx, Notifiable, NotifiableTargets, Notification, NotificationChannels, NotificationClass, NotificationOptions, NotificationParams, NotificationResolver, NotificationSendResult, ProviderTarget, QueueAdapter, QueueItemOptions, ResolvedDefaultPreferences, SendOptions, kTargetSymbol };
8
+ export { Arrayable, Channel, ChannelName, ChannelSendParams, ChannelSendResult, ChannelSpecificConfig, CommonSendOptions, DatabaseContent, DefaultPreferences, DeliverByOptions, Emitter, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, FacteurEvents, InferChannelsFromConfig, MessageCtx, Notifiable, NotifiableTargets, Notification, NotificationChannels, NotificationClass, NotificationOptions, NotificationParams, NotificationResolver, NotificationSendResult, ProviderTarget, QueueAdapter, QueueItemOptions, ResolvedDefaultPreferences, SendOptions, kTargetSymbol };
@@ -7,7 +7,7 @@ var Notification = class {
7
7
  params;
8
8
  tenantId;
9
9
  constructor(ctx) {
10
- this.notifiable = ctx.notifiable;
10
+ this.notifiable = ctx.to;
11
11
  this.params = ctx.params;
12
12
  this.tenantId = ctx.tenantId;
13
13
  }
@@ -10,6 +10,10 @@ import { Awaitable } from "@julr/utils/types";
10
10
 
11
11
  //#region src/types/options.d.ts
12
12
 
13
+ /**
14
+ * Type for values that can be a single item or an array of items
15
+ */
16
+ type Arrayable<T> = T | T[];
13
17
  /**
14
18
  * Configuration options for the Facteur library
15
19
  */
@@ -92,7 +96,7 @@ interface NotificationOptions<N extends Notifiable = Notifiable> {
92
96
  }
93
97
  interface DeliverByOptions<N extends Notifiable = Notifiable> {
94
98
  if: (options: {
95
- notifiable: N;
99
+ to: N;
96
100
  params?: any;
97
101
  preferences?: Record<string, boolean | undefined>;
98
102
  }) => boolean;
@@ -101,7 +105,7 @@ interface DeliverByOptions<N extends Notifiable = Notifiable> {
101
105
  * Parameters received by the `as*Message` methods of a notification.
102
106
  */
103
107
  interface MessageCtx<N extends Notifiable | undefined = Notifiable, Params extends Record<string, any> = {}> {
104
- notifiable: N;
108
+ to: N;
105
109
  params: Params;
106
110
  tenantId?: Identifier | undefined;
107
111
  }
@@ -146,7 +150,7 @@ type SendOptions<TNotificationClass extends NotificationClass<any, any>> = IsAno
146
150
  tenantId?: Identifier;
147
151
  } : CommonSendOptions<TNotificationClass> & {
148
152
  notification: TNotificationClass;
149
- notifiable: NonNullable<ExtractNotifiable<TNotificationClass>>;
153
+ to: Arrayable<NonNullable<ExtractNotifiable<TNotificationClass>>>;
150
154
  via?: ChannelSpecificConfig<ExtractNotifiable<TNotificationClass>>;
151
155
  tenantId?: Identifier;
152
156
  };
@@ -154,4 +158,4 @@ type ChannelSpecificConfig<N extends Notifiable> = { [K in ChannelName]?: boolea
154
158
  type ExtractChannelTargets<T> = T extends Channel<any, any, any, infer U> ? U : never;
155
159
  type ProviderTarget<_N extends Notifiable, K extends ChannelName> = ExtractChannelTargets<NotificationChannels[K]>;
156
160
  //#endregion
157
- export { ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, SendOptions };
161
+ export { Arrayable, ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, SendOptions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@facteurjs/core",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.0",
4
+ "version": "1.0.0-beta.2",
5
5
  "description": "Core package for facteur",
6
6
  "author": "Julien Ripouteau <julien@ripouteau.com>",
7
7
  "license": "ISC",
@@ -13,12 +13,18 @@
13
13
  ".": "./dist/index.js",
14
14
  "./api": "./dist/api/index.js",
15
15
  "./api/types": "./dist/api/types.js",
16
+ "./channels/aws-sns": "./dist/channels/aws-sns/index.js",
17
+ "./channels/aws-sns/types": "./dist/channels/aws-sns/types.js",
16
18
  "./channels/discord": "./dist/channels/discord/index.js",
17
19
  "./channels/discord/types": "./dist/channels/discord/types.js",
20
+ "./channels/expo": "./dist/channels/expo/index.js",
21
+ "./channels/expo/types": "./dist/channels/expo/types.js",
18
22
  "./channels/fcm": "./dist/channels/fcm/index.js",
19
23
  "./channels/fcm/types": "./dist/channels/fcm/types.js",
20
24
  "./channels/slack": "./dist/channels/slack/index.js",
21
25
  "./channels/slack/types": "./dist/channels/slack/types.js",
26
+ "./channels/socketio": "./dist/channels/socketio/index.js",
27
+ "./channels/socketio/types": "./dist/channels/socketio/types.js",
22
28
  "./channels/transmit": "./dist/channels/transmit/index.js",
23
29
  "./channels/transmit/types": "./dist/channels/transmit/types.js",
24
30
  "./channels/twilio": "./dist/channels/twilio/index.js",
@@ -42,13 +48,56 @@
42
48
  "@poppinss/exception": "^1.2.2",
43
49
  "ky": "^1.8.2"
44
50
  },
51
+ "peerDependencies": {
52
+ "@aws-sdk/client-sns": "^3.856.0",
53
+ "@boringnode/transmit": "^0.3.0",
54
+ "expo-server-sdk": "^3.15.0",
55
+ "firebase-admin": "^13.4.0",
56
+ "knex": "^3.1.0",
57
+ "kysely": "^0.28.3",
58
+ "socket.io": "^4.8.1",
59
+ "twilio": "^5.8.0",
60
+ "web-push": "^3.6.7"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@aws-sdk/client-sns": {
64
+ "optional": true
65
+ },
66
+ "@boringnode/transmit": {
67
+ "optional": true
68
+ },
69
+ "expo-server-sdk": {
70
+ "optional": true
71
+ },
72
+ "firebase-admin": {
73
+ "optional": true
74
+ },
75
+ "knex": {
76
+ "optional": true
77
+ },
78
+ "kysely": {
79
+ "optional": true
80
+ },
81
+ "socket.io": {
82
+ "optional": true
83
+ },
84
+ "twilio": {
85
+ "optional": true
86
+ },
87
+ "web-push": {
88
+ "optional": true
89
+ }
90
+ },
45
91
  "devDependencies": {
92
+ "@aws-sdk/client-sns": "^3.856.0",
46
93
  "@boringnode/transmit": "^0.3.0",
47
94
  "@types/web-push": "^3.6.4",
95
+ "expo-server-sdk": "^3.15.0",
48
96
  "firebase-admin": "^13.4.0",
49
97
  "knex": "^3.1.0",
50
98
  "kysely": "^0.28.3",
51
99
  "p-event": "^6.0.1",
100
+ "socket.io": "^4.8.1",
52
101
  "twilio": "^5.8.0",
53
102
  "web-push": "^3.6.7"
54
103
  },