@facteurjs/core 1.0.0-beta.3 → 2.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 (189) hide show
  1. package/README.md +27 -0
  2. package/dist/api/handlers/{notifications.js → notifications.mjs} +39 -16
  3. package/dist/api/handlers/preferences.mjs +73 -0
  4. package/dist/api/handlers/utils.mjs +23 -0
  5. package/dist/api/types.d.mts +52 -0
  6. package/dist/api/types.mjs +1 -0
  7. package/dist/api.d.mts +22 -0
  8. package/dist/api.mjs +25 -0
  9. package/dist/channels/aws-sns/{channel.d.ts → channel.d.mts} +3 -3
  10. package/dist/channels/aws-sns/{channel.js → channel.mjs} +2 -3
  11. package/dist/channels/aws-sns/types.mjs +1 -0
  12. package/dist/channels/aws-sns.d.mts +3 -0
  13. package/dist/channels/aws-sns.mjs +4 -0
  14. package/dist/channels/discord/{channel.d.ts → channel.d.mts} +5 -5
  15. package/dist/channels/discord/{channel.js → channel.mjs} +1 -1
  16. package/dist/channels/discord/{message.d.ts → message.d.mts} +2 -2
  17. package/dist/channels/discord/{message.js → message.mjs} +1 -1
  18. package/dist/channels/discord/{types.d.ts → types.d.mts} +1 -1
  19. package/dist/channels/discord/types.mjs +1 -0
  20. package/dist/channels/discord.d.mts +3 -0
  21. package/dist/channels/discord.mjs +4 -0
  22. package/dist/channels/expo/{channel.d.ts → channel.d.mts} +5 -3
  23. package/dist/channels/expo/channel.mjs +69 -0
  24. package/dist/channels/expo/types.mjs +1 -0
  25. package/dist/channels/expo.d.mts +3 -0
  26. package/dist/channels/expo.mjs +4 -0
  27. package/dist/channels/fcm/{channel.d.ts → channel.d.mts} +5 -5
  28. package/dist/channels/fcm/channel.mjs +68 -0
  29. package/dist/channels/fcm/types.mjs +1 -0
  30. package/dist/channels/fcm.d.mts +3 -0
  31. package/dist/channels/fcm.mjs +4 -0
  32. package/dist/channels/slack/{channel.d.ts → channel.d.mts} +5 -5
  33. package/dist/channels/slack/{channel.js → channel.mjs} +2 -2
  34. package/dist/channels/slack/{message.d.ts → message.d.mts} +1 -1
  35. package/dist/channels/slack/{message.js → message.mjs} +1 -1
  36. package/dist/channels/slack/{types.d.ts → types.d.mts} +1 -1
  37. package/dist/channels/slack/types.mjs +1 -0
  38. package/dist/channels/slack.d.mts +3 -0
  39. package/dist/channels/slack.mjs +4 -0
  40. package/dist/channels/socketio/{channel.d.ts → channel.d.mts} +3 -3
  41. package/dist/channels/socketio/{channel.js → channel.mjs} +2 -3
  42. package/dist/channels/socketio/types.mjs +1 -0
  43. package/dist/channels/socketio.d.mts +3 -0
  44. package/dist/channels/socketio.mjs +4 -0
  45. package/dist/channels/transmit/{channel.d.ts → channel.d.mts} +3 -3
  46. package/dist/channels/transmit/{channel.js → channel.mjs} +2 -3
  47. package/dist/channels/transmit/types.mjs +1 -0
  48. package/dist/channels/transmit.d.mts +3 -0
  49. package/dist/channels/transmit.mjs +4 -0
  50. package/dist/channels/twilio/{channel.d.ts → channel.d.mts} +3 -3
  51. package/dist/channels/twilio/{channel.js → channel.mjs} +3 -6
  52. package/dist/channels/twilio/types.mjs +1 -0
  53. package/dist/channels/twilio.d.mts +4 -0
  54. package/dist/channels/twilio.mjs +4 -0
  55. package/dist/channels/webhook/{exceptions.d.ts → exceptions.d.mts} +1 -1
  56. package/dist/channels/webhook/{provider.d.ts → provider.d.mts} +3 -3
  57. package/dist/channels/webhook/{provider.js → provider.mjs} +6 -8
  58. package/dist/channels/webhook/types.mjs +1 -0
  59. package/dist/channels/webhook.d.mts +4 -0
  60. package/dist/channels/webhook.mjs +5 -0
  61. package/dist/channels/webpush/{channel.d.ts → channel.d.mts} +3 -3
  62. package/dist/channels/webpush/{channel.js → channel.mjs} +2 -3
  63. package/dist/channels/webpush/types.mjs +1 -0
  64. package/dist/channels/webpush.d.mts +3 -0
  65. package/dist/channels/webpush.mjs +4 -0
  66. package/dist/database/adapters/{knex.d.ts → knex.d.mts} +1 -1
  67. package/dist/database/adapters/{knex.js → knex.mjs} +19 -15
  68. package/dist/database/adapters/{kysely.d.ts → kysely.d.mts} +1 -1
  69. package/dist/database/adapters/{kysely.js → kysely.mjs} +2 -4
  70. package/dist/database/{channel.d.ts → channel.d.mts} +3 -3
  71. package/dist/database/{channel.js → channel.mjs} +4 -6
  72. package/dist/database/{database.d.ts → database.d.mts} +3 -3
  73. package/dist/database/{database.js → database.mjs} +15 -12
  74. package/dist/database/{message.d.ts → message.d.mts} +2 -2
  75. package/dist/database/{types.d.ts → types.d.mts} +3 -3
  76. package/dist/database/types.mjs +1 -0
  77. package/dist/database.d.mts +3 -0
  78. package/dist/database.mjs +4 -0
  79. package/dist/errors/{duplicate_notification_exception.js → duplicate_notification_exception.mjs} +1 -2
  80. package/dist/errors/{http_error.d.ts → http_error.d.mts} +0 -3
  81. package/dist/errors/{index.d.ts → index.d.mts} +1 -1
  82. package/dist/errors/{index.js → index.mjs} +2 -3
  83. package/dist/events/{events.d.ts → events.d.mts} +2 -2
  84. package/dist/facteur.d.mts +45 -0
  85. package/dist/facteur.mjs +167 -0
  86. package/dist/{fake.d.ts → fake.d.mts} +3 -3
  87. package/dist/{fake.js → fake.mjs} +2 -3
  88. package/dist/{index.d.ts → index.d.mts} +3 -3
  89. package/dist/{index.js → index.mjs} +2 -2
  90. package/dist/notifications/batching_sender.mjs +148 -0
  91. package/dist/notifications/{channel_resolver.js → channel_resolver.mjs} +40 -31
  92. package/dist/notifications/notification_builder.mjs +77 -0
  93. package/dist/notifications/{notification_discoverer.d.ts → notification_discoverer.d.mts} +1 -1
  94. package/dist/notifications/{notification_discoverer.js → notification_discoverer.mjs} +9 -14
  95. package/dist/notifications/notification_sender.mjs +356 -0
  96. package/dist/notifications/orchestration_sender.mjs +92 -0
  97. package/dist/{options.d.ts → options.d.mts} +7 -6
  98. package/dist/{options.js → options.mjs} +5 -4
  99. package/dist/types/builder.d.mts +122 -0
  100. package/dist/types/channel.d.mts +56 -0
  101. package/dist/types/{events.d.ts → events.d.mts} +1 -1
  102. package/dist/types/{extend.d.ts → extend.d.mts} +2 -2
  103. package/dist/types/{notifications.d.ts → notifications.d.mts} +3 -3
  104. package/dist/types/{options.d.ts → options.d.mts} +94 -11
  105. package/dist/types/{preferences.d.ts → preferences.d.mts} +1 -1
  106. package/dist/types.d.mts +9 -0
  107. package/dist/types.mjs +4 -0
  108. package/dist/utils/chunk.mjs +28 -0
  109. package/package.json +69 -54
  110. package/dist/api/handlers/preferences.js +0 -43
  111. package/dist/api/index.d.ts +0 -16
  112. package/dist/api/index.js +0 -21
  113. package/dist/api/types.d.ts +0 -22
  114. package/dist/api/types.js +0 -0
  115. package/dist/channels/aws-sns/index.d.ts +0 -3
  116. package/dist/channels/aws-sns/index.js +0 -4
  117. package/dist/channels/aws-sns/types.js +0 -0
  118. package/dist/channels/discord/index.d.ts +0 -3
  119. package/dist/channels/discord/index.js +0 -4
  120. package/dist/channels/discord/types.js +0 -0
  121. package/dist/channels/expo/channel.js +0 -38
  122. package/dist/channels/expo/index.d.ts +0 -3
  123. package/dist/channels/expo/index.js +0 -4
  124. package/dist/channels/expo/types.js +0 -0
  125. package/dist/channels/fcm/channel.js +0 -44
  126. package/dist/channels/fcm/index.d.ts +0 -3
  127. package/dist/channels/fcm/index.js +0 -4
  128. package/dist/channels/fcm/types.js +0 -0
  129. package/dist/channels/slack/index.d.ts +0 -3
  130. package/dist/channels/slack/index.js +0 -4
  131. package/dist/channels/slack/types.js +0 -0
  132. package/dist/channels/socketio/index.d.ts +0 -3
  133. package/dist/channels/socketio/index.js +0 -4
  134. package/dist/channels/socketio/types.js +0 -0
  135. package/dist/channels/transmit/index.d.ts +0 -3
  136. package/dist/channels/transmit/index.js +0 -4
  137. package/dist/channels/transmit/types.js +0 -0
  138. package/dist/channels/twilio/index.d.ts +0 -4
  139. package/dist/channels/twilio/index.js +0 -4
  140. package/dist/channels/twilio/types.js +0 -0
  141. package/dist/channels/webhook/index.d.ts +0 -4
  142. package/dist/channels/webhook/index.js +0 -5
  143. package/dist/channels/webhook/types.js +0 -0
  144. package/dist/channels/webpush/index.d.ts +0 -3
  145. package/dist/channels/webpush/index.js +0 -4
  146. package/dist/channels/webpush/types.js +0 -0
  147. package/dist/database/index.d.ts +0 -3
  148. package/dist/database/index.js +0 -4
  149. package/dist/database/types.js +0 -0
  150. package/dist/facteur.d.ts +0 -37
  151. package/dist/facteur.js +0 -100
  152. package/dist/notifications/notification_sender.js +0 -211
  153. package/dist/types/channel.d.ts +0 -18
  154. package/dist/types/index.d.ts +0 -8
  155. package/dist/types/index.js +0 -4
  156. /package/dist/channels/aws-sns/{message.d.ts → message.d.mts} +0 -0
  157. /package/dist/channels/aws-sns/{message.js → message.mjs} +0 -0
  158. /package/dist/channels/aws-sns/{types.d.ts → types.d.mts} +0 -0
  159. /package/dist/channels/expo/{message.d.ts → message.d.mts} +0 -0
  160. /package/dist/channels/expo/{message.js → message.mjs} +0 -0
  161. /package/dist/channels/expo/{types.d.ts → types.d.mts} +0 -0
  162. /package/dist/channels/fcm/{message.d.ts → message.d.mts} +0 -0
  163. /package/dist/channels/fcm/{message.js → message.mjs} +0 -0
  164. /package/dist/channels/fcm/{types.d.ts → types.d.mts} +0 -0
  165. /package/dist/channels/socketio/{message.d.ts → message.d.mts} +0 -0
  166. /package/dist/channels/socketio/{message.js → message.mjs} +0 -0
  167. /package/dist/channels/socketio/{types.d.ts → types.d.mts} +0 -0
  168. /package/dist/channels/transmit/{message.d.ts → message.d.mts} +0 -0
  169. /package/dist/channels/transmit/{message.js → message.mjs} +0 -0
  170. /package/dist/channels/transmit/{types.d.ts → types.d.mts} +0 -0
  171. /package/dist/channels/twilio/{message.d.ts → message.d.mts} +0 -0
  172. /package/dist/channels/twilio/{message.js → message.mjs} +0 -0
  173. /package/dist/channels/twilio/{types.d.ts → types.d.mts} +0 -0
  174. /package/dist/channels/webhook/{exceptions.js → exceptions.mjs} +0 -0
  175. /package/dist/channels/webhook/{message.d.ts → message.d.mts} +0 -0
  176. /package/dist/channels/webhook/{message.js → message.mjs} +0 -0
  177. /package/dist/channels/webhook/{types.d.ts → types.d.mts} +0 -0
  178. /package/dist/channels/webpush/{message.d.ts → message.d.mts} +0 -0
  179. /package/dist/channels/webpush/{message.js → message.mjs} +0 -0
  180. /package/dist/channels/webpush/{types.d.ts → types.d.mts} +0 -0
  181. /package/dist/database/{message.js → message.mjs} +0 -0
  182. /package/dist/{debug.js → debug.mjs} +0 -0
  183. /package/dist/errors/{duplicate_notification_exception.d.ts → duplicate_notification_exception.d.mts} +0 -0
  184. /package/dist/errors/{http_error.js → http_error.mjs} +0 -0
  185. /package/dist/events/{events.js → events.mjs} +0 -0
  186. /package/dist/{helpers.js → helpers.mjs} +0 -0
  187. /package/dist/types/{channel.js → channel.mjs} +0 -0
  188. /package/dist/types/{notifications.js → notifications.mjs} +0 -0
  189. /package/dist/types/{queue.d.ts → queue.d.mts} +0 -0
@@ -1,5 +1,5 @@
1
- import { FacteurConfiguration } from "./options.js";
2
- import { Facteur } from "../facteur.js";
1
+ import { Facteur } from "../facteur.mjs";
2
+ import { FacteurConfiguration } from "./options.mjs";
3
3
 
4
4
  //#region src/types/extend.d.ts
5
5
 
@@ -1,6 +1,6 @@
1
- import { Identifier } from "../database/types.js";
2
- import { ChannelName, NotificationChannels } from "./extend.js";
3
- import { ExtractChannelTargets, MessageCtx, NotificationOptions } from "./options.js";
1
+ import { ChannelName, NotificationChannels } from "./extend.mjs";
2
+ import { ExtractChannelTargets, MessageCtx, NotificationOptions } from "./options.mjs";
3
+ import { Identifier } from "../database/types.mjs";
4
4
  import { Awaitable } from "@julr/utils/types";
5
5
 
6
6
  //#region src/types/notifications.d.ts
@@ -1,12 +1,13 @@
1
- import { DatabaseAdapter, Identifier } from "../database/types.js";
2
- import { Channel } from "./channel.js";
3
- import { Emitter } from "./events.js";
4
- import { QueueAdapter } from "./queue.js";
5
- import { DefaultPreferences } from "./preferences.js";
6
- import { ChannelName, NotificationChannels } from "./extend.js";
7
- import { Notifiable, Notification, NotificationClass } from "./notifications.js";
1
+ import { Channel } from "./channel.mjs";
2
+ import { QueueAdapter } from "./queue.mjs";
3
+ import { DefaultPreferences } from "./preferences.mjs";
4
+ import { ChannelName, NotificationChannels } from "./extend.mjs";
5
+ import { Emitter } from "./events.mjs";
6
+ import { Notifiable, Notification, NotificationClass } from "./notifications.mjs";
7
+ import { DatabaseAdapter, Identifier } from "../database/types.mjs";
8
8
  import { Logger } from "@julr/utils/logger";
9
9
  import { Awaitable } from "@julr/utils/types";
10
+ import { Duration } from "@julr/tenace/types";
10
11
 
11
12
  //#region src/types/options.d.ts
12
13
 
@@ -58,6 +59,11 @@ interface FacteurConfiguration<KnownChannels extends Record<string, Channel> = R
58
59
  * If not provided, the default constructor will be used.
59
60
  */
60
61
  notificationResolver?: NotificationResolver;
62
+ /**
63
+ * Global retry configuration for channel sends.
64
+ * Can be overridden per-channel or per-send.
65
+ */
66
+ retry?: RetryConfig<KnownChannels>;
61
67
  }
62
68
  /**
63
69
  * Resolver function type for notifications
@@ -144,11 +150,11 @@ type IsAnonymousNotification<T> = T extends NotificationClass<infer N, any> ? (N
144
150
  /**
145
151
  * Options for the `send` method
146
152
  */
147
- type SendOptions<TNotificationClass extends NotificationClass<any, any>> = IsAnonymousNotification<TNotificationClass> extends true ? CommonSendOptions<TNotificationClass> & {
153
+ type SendOptions<TNotificationClass extends NotificationClass<any, any>> = IsAnonymousNotification<TNotificationClass> extends true ? CommonSendOptions<TNotificationClass> & RetryOptions & {
148
154
  notification: TNotificationClass;
149
155
  via: { [K in ChannelName]?: ProviderTarget<any, K> };
150
156
  tenantId?: Identifier;
151
- } : CommonSendOptions<TNotificationClass> & {
157
+ } : CommonSendOptions<TNotificationClass> & BulkSendOptions & {
152
158
  notification: TNotificationClass;
153
159
  to: Arrayable<NonNullable<ExtractNotifiable<TNotificationClass>>>;
154
160
  via?: ChannelSpecificConfig<ExtractNotifiable<TNotificationClass>>;
@@ -156,6 +162,83 @@ type SendOptions<TNotificationClass extends NotificationClass<any, any>> = IsAno
156
162
  };
157
163
  type ChannelSpecificConfig<N extends Notifiable> = { [K in ChannelName]?: boolean | ProviderTarget<N, K> };
158
164
  type ExtractChannelTargets<T> = T extends Channel<any, any, any, infer U> ? U : never;
159
- type ProviderTarget<_N extends Notifiable, K extends ChannelName> = ExtractChannelTargets<NotificationChannels[K]>;
165
+ type ProviderTarget<_N extends Notifiable, K$1 extends ChannelName> = ExtractChannelTargets<NotificationChannels[K$1]>;
166
+ /**
167
+ * Retry options for channel sends
168
+ */
169
+ interface RetryOptions {
170
+ /**
171
+ * Number of retry attempts when a channel send fails.
172
+ * Uses exponential backoff with jitter.
173
+ * @default 0 (no retries)
174
+ */
175
+ retries?: number;
176
+ /**
177
+ * Timeout for each channel send operation.
178
+ * Accepts milliseconds or duration strings like '30s', '1m'.
179
+ */
180
+ timeout?: Duration;
181
+ }
182
+ /**
183
+ * Global retry configuration with per-channel overrides
184
+ */
185
+ interface RetryConfig<KnownChannels extends Record<string, Channel> = Record<string, Channel>> extends RetryOptions {
186
+ /**
187
+ * Per-channel retry overrides
188
+ */
189
+ channels?: { [K in keyof KnownChannels]?: RetryOptions };
190
+ }
191
+ /**
192
+ * Options for bulk sending operations
193
+ */
194
+ interface BulkSendOptions extends RetryOptions {
195
+ /**
196
+ * Number of recipients to process per chunk.
197
+ * Useful for memory management with large recipient lists.
198
+ * @default Infinity (no chunking)
199
+ */
200
+ chunkSize?: number;
201
+ /**
202
+ * Maximum number of concurrent sends.
203
+ * @default 10
204
+ */
205
+ concurrency?: number;
206
+ /**
207
+ * If true, continues sending even if some recipients fail (Promise.allSettled behavior).
208
+ * If false, stops on first error (Promise.all behavior).
209
+ * @default false
210
+ */
211
+ continueOnError?: boolean;
212
+ /**
213
+ * Number of retry attempts per recipient.
214
+ * Uses exponential backoff with jitter.
215
+ * @default 0 (no retries)
216
+ */
217
+ retries?: number;
218
+ /**
219
+ * Timeout per recipient send operation.
220
+ * Accepts milliseconds or duration strings like '30s', '1m'.
221
+ */
222
+ timeout?: Duration;
223
+ /**
224
+ * Callback invoked after each recipient is processed.
225
+ */
226
+ onProgress?: (completed: number, total: number) => void;
227
+ /**
228
+ * Disable driver-level batching even if the channel supports it.
229
+ * When true, each message is sent individually.
230
+ * @default false
231
+ */
232
+ disableDriverBatch?: boolean;
233
+ /**
234
+ * Enable batch mode which groups messages by channel and uses batch APIs when available.
235
+ * This mode has different semantics:
236
+ * - Retries and timeouts apply at the batch level, not per-recipient
237
+ * - Progress callback is called per chunk, not per recipient
238
+ * - More efficient for channels that support batch APIs (FCM, Expo)
239
+ * @default false
240
+ */
241
+ useDriverBatching?: boolean;
242
+ }
160
243
  //#endregion
161
- export { Arrayable, ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, SendOptions };
244
+ export { Arrayable, BulkSendOptions, ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, RetryConfig, RetryOptions, SendOptions };
@@ -1,4 +1,4 @@
1
- import { Channel } from "./channel.js";
1
+ import { Channel } from "./channel.mjs";
2
2
 
3
3
  //#region src/types/preferences.d.ts
4
4
  type ChannelPreferences<KnownChannels extends Record<string, Channel>> = Record<keyof KnownChannels, boolean>;
@@ -0,0 +1,9 @@
1
+ import { BatchConfig, BatchSendResult, Channel, ChannelSendParams, kTargetSymbol } from "./types/channel.mjs";
2
+ import { QueueAdapter, QueueItemOptions } from "./types/queue.mjs";
3
+ import { DefaultPreferences, ResolvedDefaultPreferences } from "./types/preferences.mjs";
4
+ import { BuilderOptions, BuilderState, HasRequiredParams, InternalSendOptions, IsAnonymous, NotificationBuilder } from "./types/builder.mjs";
5
+ import { ChannelName, DatabaseContent, InferChannelsFromConfig, NotificationChannels } from "./types/extend.mjs";
6
+ import { Emitter, FacteurEvents } from "./types/events.mjs";
7
+ import { Arrayable, BulkSendOptions, ChannelSpecificConfig, CommonSendOptions, DeliverByOptions, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, MessageCtx, NotificationOptions, NotificationParams, NotificationResolver, ProviderTarget, RetryConfig, RetryOptions, SendOptions } from "./types/options.mjs";
8
+ import { ChannelSendResult, Notifiable, NotifiableTargets, Notification, NotificationClass, NotificationSendResult } from "./types/notifications.mjs";
9
+ export { Arrayable, BatchConfig, BatchSendResult, BuilderOptions, BuilderState, BulkSendOptions, Channel, ChannelName, ChannelSendParams, ChannelSendResult, ChannelSpecificConfig, CommonSendOptions, DatabaseContent, DefaultPreferences, DeliverByOptions, Emitter, ExtractChannelTargets, ExtractNotifiable, ExtractParams, FacteurConfiguration, FacteurEvents, HasRequiredParams, InferChannelsFromConfig, InternalSendOptions, IsAnonymous, MessageCtx, Notifiable, NotifiableTargets, Notification, NotificationBuilder, NotificationChannels, NotificationClass, NotificationOptions, NotificationParams, NotificationResolver, NotificationSendResult, ProviderTarget, QueueAdapter, QueueItemOptions, ResolvedDefaultPreferences, RetryConfig, RetryOptions, SendOptions, kTargetSymbol };
package/dist/types.mjs ADDED
@@ -0,0 +1,4 @@
1
+ import { kTargetSymbol } from "./types/channel.mjs";
2
+ import { Notification } from "./types/notifications.mjs";
3
+
4
+ export { Notification, kTargetSymbol };
@@ -0,0 +1,28 @@
1
+ //#region src/utils/chunk.ts
2
+ /**
3
+ * Split an array into chunks of a given size
4
+ */
5
+ function chunk(array, size) {
6
+ if (size <= 0) throw new Error("Chunk size must be greater than 0");
7
+ if (size === Infinity) return [array];
8
+ const chunks = [];
9
+ for (let i = 0; i < array.length; i += size) chunks.push(array.slice(i, i + size));
10
+ return chunks;
11
+ }
12
+ /**
13
+ * Collect items from an async iterable into an array
14
+ */
15
+ async function collect(iterable) {
16
+ const items = [];
17
+ for await (const item of iterable) items.push(item);
18
+ return items;
19
+ }
20
+ /**
21
+ * Check if a value is an async iterable
22
+ */
23
+ function isAsyncIterable(value) {
24
+ return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
25
+ }
26
+
27
+ //#endregion
28
+ export { chunk, collect, isAsyncIterable };
package/package.json CHANGED
@@ -1,52 +1,83 @@
1
1
  {
2
2
  "name": "@facteurjs/core",
3
- "type": "module",
4
- "version": "1.0.0-beta.3",
5
- "description": "Core package for facteur",
3
+ "version": "2.0.0-beta.0",
4
+ "description": "Framework-agnostic notification system for Node.js with support for multiple channels (email, SMS, push, webhooks, etc.)",
5
+ "keywords": [
6
+ "discord",
7
+ "email",
8
+ "expo",
9
+ "fcm",
10
+ "firebase",
11
+ "nodejs",
12
+ "notification-system",
13
+ "notifications",
14
+ "push-notifications",
15
+ "slack",
16
+ "sms",
17
+ "twilio",
18
+ "webhooks",
19
+ "webpush"
20
+ ],
21
+ "license": "MIT",
6
22
  "author": "Julien Ripouteau <julien@ripouteau.com>",
7
- "license": "ISC",
8
- "keywords": [],
9
23
  "files": [
10
24
  "dist"
11
25
  ],
26
+ "type": "module",
27
+ "main": "./dist/index.mjs",
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.mts",
12
30
  "exports": {
13
- ".": "./dist/index.js",
14
- "./api": "./dist/api/index.js",
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",
18
- "./channels/discord": "./dist/channels/discord/index.js",
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",
22
- "./channels/fcm": "./dist/channels/fcm/index.js",
23
- "./channels/fcm/types": "./dist/channels/fcm/types.js",
24
- "./channels/slack": "./dist/channels/slack/index.js",
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",
28
- "./channels/transmit": "./dist/channels/transmit/index.js",
29
- "./channels/transmit/types": "./dist/channels/transmit/types.js",
30
- "./channels/twilio": "./dist/channels/twilio/index.js",
31
- "./channels/twilio/types": "./dist/channels/twilio/types.js",
32
- "./channels/webhook": "./dist/channels/webhook/index.js",
33
- "./channels/webhook/types": "./dist/channels/webhook/types.js",
34
- "./channels/webpush": "./dist/channels/webpush/index.js",
35
- "./channels/webpush/types": "./dist/channels/webpush/types.js",
36
- "./database/adapters/knex": "./dist/database/adapters/knex.js",
37
- "./database/adapters/kysely": "./dist/database/adapters/kysely.js",
38
- "./database": "./dist/database/index.js",
39
- "./database/types": "./dist/database/types.js",
40
- "./types": "./dist/types/index.js",
31
+ ".": "./dist/index.mjs",
32
+ "./api": "./dist/api.mjs",
33
+ "./api/types": "./dist/api/types.mjs",
34
+ "./channels/aws-sns": "./dist/channels/aws-sns.mjs",
35
+ "./channels/aws-sns/types": "./dist/channels/aws-sns/types.mjs",
36
+ "./channels/discord": "./dist/channels/discord.mjs",
37
+ "./channels/discord/types": "./dist/channels/discord/types.mjs",
38
+ "./channels/expo": "./dist/channels/expo.mjs",
39
+ "./channels/expo/types": "./dist/channels/expo/types.mjs",
40
+ "./channels/fcm": "./dist/channels/fcm.mjs",
41
+ "./channels/fcm/types": "./dist/channels/fcm/types.mjs",
42
+ "./channels/slack": "./dist/channels/slack.mjs",
43
+ "./channels/slack/types": "./dist/channels/slack/types.mjs",
44
+ "./channels/socketio": "./dist/channels/socketio.mjs",
45
+ "./channels/socketio/types": "./dist/channels/socketio/types.mjs",
46
+ "./channels/transmit": "./dist/channels/transmit.mjs",
47
+ "./channels/transmit/types": "./dist/channels/transmit/types.mjs",
48
+ "./channels/twilio": "./dist/channels/twilio.mjs",
49
+ "./channels/twilio/types": "./dist/channels/twilio/types.mjs",
50
+ "./channels/webhook": "./dist/channels/webhook.mjs",
51
+ "./channels/webhook/types": "./dist/channels/webhook/types.mjs",
52
+ "./channels/webpush": "./dist/channels/webpush.mjs",
53
+ "./channels/webpush/types": "./dist/channels/webpush/types.mjs",
54
+ "./database": "./dist/database.mjs",
55
+ "./database/adapters/knex": "./dist/database/adapters/knex.mjs",
56
+ "./database/adapters/kysely": "./dist/database/adapters/kysely.mjs",
57
+ "./database/types": "./dist/database/types.mjs",
58
+ "./types": "./dist/types.mjs",
41
59
  "./package.json": "./package.json"
42
60
  },
43
- "main": "./dist/index.js",
44
- "module": "./dist/index.js",
45
- "types": "./dist/index.d.ts",
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
46
64
  "dependencies": {
65
+ "@julr/tenace": "1.0.0-next.0",
47
66
  "@julr/utils": "1.9.0",
48
- "@poppinss/exception": "^1.2.2",
49
- "ky": "^1.8.2"
67
+ "@poppinss/exception": "^1.2.3",
68
+ "ky": "^1.14.2"
69
+ },
70
+ "devDependencies": {
71
+ "@aws-sdk/client-sns": "^3.958.0",
72
+ "@boringnode/transmit": "^0.3.0",
73
+ "@types/web-push": "^3.6.4",
74
+ "expo-server-sdk": "^4.0.0",
75
+ "firebase-admin": "^13.6.0",
76
+ "knex": "^3.1.0",
77
+ "kysely": "^0.28.9",
78
+ "socket.io": "^4.8.3",
79
+ "twilio": "^5.11.1",
80
+ "web-push": "^3.6.7"
50
81
  },
51
82
  "peerDependencies": {
52
83
  "@aws-sdk/client-sns": "^3.856.0",
@@ -88,22 +119,6 @@
88
119
  "optional": true
89
120
  }
90
121
  },
91
- "devDependencies": {
92
- "@aws-sdk/client-sns": "^3.856.0",
93
- "@boringnode/transmit": "^0.3.0",
94
- "@types/web-push": "^3.6.4",
95
- "expo-server-sdk": "^3.15.0",
96
- "firebase-admin": "^13.4.0",
97
- "knex": "^3.1.0",
98
- "kysely": "^0.28.3",
99
- "p-event": "^6.0.1",
100
- "socket.io": "^4.8.1",
101
- "twilio": "^5.8.0",
102
- "web-push": "^3.6.7"
103
- },
104
- "publishConfig": {
105
- "access": "public"
106
- },
107
122
  "scripts": {
108
123
  "build": "tsdown",
109
124
  "stub": "tsdown",
@@ -1,43 +0,0 @@
1
- import { defineRoute } from "../index.js";
2
-
3
- //#region src/api/handlers/preferences.ts
4
- const getPreferencesRoute = defineRoute(({ facteur }) => ({
5
- method: "get",
6
- route: "/notifications/notifiable/:notifiableId/preferences",
7
- handler: async (request) => {
8
- const userId = request.params.notifiableId;
9
- const preferences = await facteur.db.getPreferences({
10
- notifiableId: userId,
11
- tenantId: request.query.tenantId
12
- });
13
- return {
14
- status: 200,
15
- body: preferences || {}
16
- };
17
- }
18
- }));
19
- const updatePreferencesRoute = defineRoute(({ facteur }) => ({
20
- method: "post",
21
- route: "/notifications/notifiable/:notifiableId/preferences",
22
- handler: async (request) => {
23
- const userId = request.params.notifiableId;
24
- const preferences = request.body.preferences;
25
- if (!preferences) return {
26
- status: 400,
27
- body: { error: "Preferences are required" }
28
- };
29
- await facteur.db.updatePreferences({
30
- notifiableId: userId,
31
- tenantId: request.body.tenantId,
32
- notificationName: request.body.notificationName,
33
- channelPreferences: preferences
34
- });
35
- return {
36
- status: 204,
37
- body: {}
38
- };
39
- }
40
- }));
41
-
42
- //#endregion
43
- export { getPreferencesRoute, updatePreferencesRoute };
@@ -1,16 +0,0 @@
1
- import { Facteur } from "../facteur.js";
2
- import { RouteDefinition, ServerAdapter } from "./types.js";
3
-
4
- //#region src/api/index.d.ts
5
- declare function defineRoute(routeDefinition: (options: {
6
- facteur: Facteur<any, any>;
7
- }) => RouteDefinition): (options: {
8
- facteur: Facteur<any, any>;
9
- }) => RouteDefinition;
10
- declare const routes: (facteur: Facteur<any, any>) => RouteDefinition[];
11
- declare function createFacteurServer(options: {
12
- adapter: ServerAdapter;
13
- facteur: Facteur<any, any>;
14
- }): void;
15
- //#endregion
16
- export { createFacteurServer, defineRoute, routes };
package/dist/api/index.js DELETED
@@ -1,21 +0,0 @@
1
- import { getPreferencesRoute, updatePreferencesRoute } from "./handlers/preferences.js";
2
- import { getNotificationRoute, markAllNotificationsAsRoute, markNotificationAsRoute } from "./handlers/notifications.js";
3
-
4
- //#region src/api/index.ts
5
- function defineRoute(routeDefinition) {
6
- return routeDefinition;
7
- }
8
- const routes = (facteur) => [
9
- getNotificationRoute({ facteur }),
10
- markNotificationAsRoute({ facteur }),
11
- markAllNotificationsAsRoute({ facteur }),
12
- getPreferencesRoute({ facteur }),
13
- updatePreferencesRoute({ facteur })
14
- ];
15
- function createFacteurServer(options) {
16
- const { adapter, facteur } = options;
17
- adapter.setRoutes(routes(facteur));
18
- }
19
-
20
- //#endregion
21
- export { createFacteurServer, defineRoute, routes };
@@ -1,22 +0,0 @@
1
- //#region src/api/types.d.ts
2
- type HTTPMethod = 'get' | 'post' | 'put' | 'patch';
3
- interface HTTPRequest {
4
- body: Record<string, any>;
5
- params: Record<string, any>;
6
- query: Record<string, any>;
7
- headers: Record<string, string | undefined>;
8
- }
9
- interface HTTPResponse {
10
- status: number;
11
- body: Record<string, any>;
12
- }
13
- interface RouteDefinition {
14
- method: HTTPMethod;
15
- route: string;
16
- handler: (request: HTTPRequest) => Promise<HTTPResponse>;
17
- }
18
- interface ServerAdapter {
19
- setRoutes(routes: RouteDefinition[]): void;
20
- }
21
- //#endregion
22
- export { HTTPMethod, HTTPRequest, HTTPResponse, RouteDefinition, ServerAdapter };
package/dist/api/types.js DELETED
File without changes
@@ -1,3 +0,0 @@
1
- import { AwsSnsMessage } from "./message.js";
2
- import { AwsSnsChannel, awsSnsChannel } from "./channel.js";
3
- export { AwsSnsChannel, AwsSnsMessage, awsSnsChannel };
@@ -1,4 +0,0 @@
1
- import { AwsSnsMessage } from "./message.js";
2
- import { AwsSnsChannel, awsSnsChannel } from "./channel.js";
3
-
4
- export { AwsSnsChannel, AwsSnsMessage, awsSnsChannel };
File without changes
@@ -1,3 +0,0 @@
1
- import { DiscordMessage } from "./message.js";
2
- import { DiscordProvider, discordWebhookChannel } from "./channel.js";
3
- export { DiscordMessage, DiscordProvider, discordWebhookChannel };
@@ -1,4 +0,0 @@
1
- import { DiscordMessage } from "./message.js";
2
- import { DiscordProvider, discordWebhookChannel } from "./channel.js";
3
-
4
- export { DiscordMessage, DiscordProvider, discordWebhookChannel };
File without changes
@@ -1,38 +0,0 @@
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 };
@@ -1,3 +0,0 @@
1
- import { ExpoMessage } from "./message.js";
2
- import { ExpoChannel, expoChannel } from "./channel.js";
3
- export { ExpoChannel, ExpoMessage, expoChannel };
@@ -1,4 +0,0 @@
1
- import { ExpoMessage } from "./message.js";
2
- import { ExpoChannel, expoChannel } from "./channel.js";
3
-
4
- export { ExpoChannel, ExpoMessage, expoChannel };
File without changes
@@ -1,44 +0,0 @@
1
- import { errors } from "../../errors/index.js";
2
- import { kTargetSymbol } from "../../types/channel.js";
3
- import "../../types/index.js";
4
- import { cert, initializeApp } from "firebase-admin/app";
5
- import { getMessaging } from "firebase-admin/messaging";
6
-
7
- //#region src/channels/fcm/channel.ts
8
- function fcmChannel(config) {
9
- return new FcmChannel(config);
10
- }
11
- var FcmChannel = class {
12
- name = "fcm";
13
- [kTargetSymbol] = null;
14
- messaging;
15
- constructor(config) {
16
- this.config = config;
17
- const app = initializeApp({
18
- ...config,
19
- ...config.serviceAccountKeyPath ? { credential: cert(config.serviceAccountKeyPath) } : {}
20
- });
21
- this.messaging = getMessaging(app);
22
- }
23
- #resolveTargets(options) {
24
- if (options.targets) return options.targets;
25
- throw new errors.E_UNAVAILABLE_TARGETS(["FCM"]);
26
- }
27
- #handleError(error) {
28
- if (error.code === "messaging/registration-token-not-registered") throw new Error(`FCM token is no longer valid: ${error.message}`);
29
- if (error.code === "messaging/invalid-argument") throw new Error(`Invalid FCM message format: ${error.message}`);
30
- throw error;
31
- }
32
- async send(options) {
33
- const message = options.message.serialize();
34
- const targets = this.#resolveTargets(options);
35
- if (this.config.debugToken) message.token = this.config.debugToken;
36
- if (targets.token) message.token = this.config.debugToken || targets.token;
37
- else if (targets.topic) message.topic = targets.topic;
38
- else if (targets.condition) message.condition = targets.condition;
39
- return await this.messaging.send(message).catch((error) => this.#handleError(error));
40
- }
41
- };
42
-
43
- //#endregion
44
- export { FcmChannel, fcmChannel };
@@ -1,3 +0,0 @@
1
- import { FcmMessage } from "./message.js";
2
- import { FcmChannel, fcmChannel } from "./channel.js";
3
- export { FcmChannel, FcmMessage, fcmChannel };
@@ -1,4 +0,0 @@
1
- import { FcmMessage } from "./message.js";
2
- import { FcmChannel, fcmChannel } from "./channel.js";
3
-
4
- export { FcmChannel, FcmMessage, fcmChannel };
File without changes
@@ -1,3 +0,0 @@
1
- import { SlackMessage } from "./message.js";
2
- import { slackWebhookChannel } from "./channel.js";
3
- export { SlackMessage, slackWebhookChannel };
@@ -1,4 +0,0 @@
1
- import { SlackMessage } from "./message.js";
2
- import { slackWebhookChannel } from "./channel.js";
3
-
4
- export { SlackMessage, slackWebhookChannel };
File without changes
@@ -1,3 +0,0 @@
1
- import { SocketIoMessage } from "./message.js";
2
- import { SocketIOChannel, socketIoChannel } from "./channel.js";
3
- export { SocketIOChannel, SocketIoMessage, socketIoChannel };
@@ -1,4 +0,0 @@
1
- import { SocketIoMessage } from "./message.js";
2
- import { SocketIOChannel, socketIoChannel } from "./channel.js";
3
-
4
- export { SocketIOChannel, SocketIoMessage, socketIoChannel };
File without changes
@@ -1,3 +0,0 @@
1
- import { TransmitMessage } from "./message.js";
2
- import { TransmitChannel, transmitChannel } from "./channel.js";
3
- export { TransmitChannel, TransmitMessage, transmitChannel };
@@ -1,4 +0,0 @@
1
- import { TransmitMessage } from "./message.js";
2
- import { TransmitChannel, transmitChannel } from "./channel.js";
3
-
4
- export { TransmitChannel, TransmitMessage, transmitChannel };
File without changes