@stacksjs/notifications 0.70.88 → 0.70.91
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/drivers/broadcast.d.ts +24 -0
- package/dist/drivers/chat.d.ts +1 -0
- package/dist/drivers/database.d.ts +50 -0
- package/dist/drivers/email.d.ts +1 -0
- package/dist/drivers/index.d.ts +6 -0
- package/dist/drivers/push.d.ts +1 -0
- package/dist/drivers/sms.d.ts +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +2 -0
- package/dist/preferences.d.ts +106 -0
- package/package.json +9 -9
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** @defaultValue `{ send: () => Promise<unknown> }` */
|
|
2
|
+
export declare const BroadcastNotificationDriver: {
|
|
3
|
+
/**
|
|
4
|
+
* Emit the notification over the realtime channel. Resolves with
|
|
5
|
+
* `delivered: false` (and a reason) when no realtime server is
|
|
6
|
+
* available rather than throwing — fire-and-forget is the right shape
|
|
7
|
+
* for a notification channel that ships transports best-effort.
|
|
8
|
+
*/
|
|
9
|
+
send: (options: BroadcastNotificationOptions) => Promise<BroadcastNotificationResult>
|
|
10
|
+
};
|
|
11
|
+
/** Options accepted by the broadcast driver. */
|
|
12
|
+
export declare interface BroadcastNotificationOptions {
|
|
13
|
+
channel?: string
|
|
14
|
+
userId?: number
|
|
15
|
+
event?: string
|
|
16
|
+
data?: Record<string, unknown>
|
|
17
|
+
private?: boolean
|
|
18
|
+
}
|
|
19
|
+
export declare interface BroadcastNotificationResult {
|
|
20
|
+
delivered: boolean
|
|
21
|
+
channel: string
|
|
22
|
+
event: string
|
|
23
|
+
reason?: string
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as chat from '@stacksjs/chat';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare function useDatabase(): typeof DatabaseNotificationDriver;
|
|
2
|
+
/**
|
|
3
|
+
* @defaultValue
|
|
4
|
+
* ```ts
|
|
5
|
+
* {
|
|
6
|
+
* send: () => unknown,
|
|
7
|
+
* getUserNotifications: () => unknown,
|
|
8
|
+
* getUnreadNotifications: () => unknown,
|
|
9
|
+
* markAsRead: () => unknown,
|
|
10
|
+
* markAllAsRead: () => unknown,
|
|
11
|
+
* unreadCount: () => unknown,
|
|
12
|
+
* deleteNotification: () => unknown,
|
|
13
|
+
* deleteAllNotifications: () => unknown
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const DatabaseNotificationDriver: {
|
|
18
|
+
send: (options: CreateNotificationOptions) => Promise<DatabaseNotification>;
|
|
19
|
+
getUserNotifications: (userId: number) => Promise<DatabaseNotification[]>;
|
|
20
|
+
getUnreadNotifications: (userId: number) => Promise<DatabaseNotification[]>;
|
|
21
|
+
markAsRead: (id: number) => Promise<void>;
|
|
22
|
+
markAllAsRead: (userId: number) => Promise<void>;
|
|
23
|
+
unreadCount: (userId: number) => Promise<number>;
|
|
24
|
+
deleteNotification: (id: number) => Promise<void>;
|
|
25
|
+
deleteAllNotifications: (userId: number) => Promise<void>
|
|
26
|
+
};
|
|
27
|
+
// `./database-schema.d.ts` augments `@stacksjs/database`'s
|
|
28
|
+
// `DatabaseSchema` with the `notifications` + `notification_preferences`
|
|
29
|
+
// tables so the chain calls below type-check without per-call `as any`
|
|
30
|
+
// (Notif-3 follow-up to #1923 / #1937).
|
|
31
|
+
export declare interface DatabaseNotification {
|
|
32
|
+
id: number
|
|
33
|
+
user_id: number
|
|
34
|
+
type: string
|
|
35
|
+
data: string
|
|
36
|
+
read_at: string | null
|
|
37
|
+
created_at: string
|
|
38
|
+
updated_at: string | null
|
|
39
|
+
}
|
|
40
|
+
export declare interface CreateNotificationOptions {
|
|
41
|
+
userId: number
|
|
42
|
+
type: string
|
|
43
|
+
data: Record<string, unknown>
|
|
44
|
+
}
|
|
45
|
+
/** Shape of a row-insert result across the supported drivers. */
|
|
46
|
+
declare interface InsertResultLike {
|
|
47
|
+
insertId?: number | bigint
|
|
48
|
+
lastInsertRowid?: number | bigint
|
|
49
|
+
lastInsertId?: number | bigint
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as email from '@stacksjs/email';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as push from '@stacksjs/push';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as sms from '@stacksjs/sms';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BroadcastNotificationDriver } from './drivers/broadcast';
|
|
2
|
+
import { chat, email, push, sms } from './drivers/index';
|
|
3
|
+
import { DatabaseNotificationDriver } from './drivers/database';
|
|
4
|
+
import { filterChannelsByPreferences } from './preferences';
|
|
5
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
6
|
+
export type { BroadcastNotificationOptions, BroadcastNotificationResult } from './drivers/broadcast';
|
|
7
|
+
export type { CreateNotificationOptions, DatabaseNotification } from './drivers/database';
|
|
8
|
+
export type { NotificationPreferenceRow, PreferenceChannel } from './preferences';
|
|
9
|
+
export declare function useChat(driver?: string): typeof chat[keyof typeof chat];
|
|
10
|
+
/**
|
|
11
|
+
* Return the email transport — by default the @stacksjs/email Mail
|
|
12
|
+
* singleton, which lazy-resolves its driver from `config.email.default`
|
|
13
|
+
* (so `MAIL_MAILER=log` in dev / `ses` in prod just works). Pass an
|
|
14
|
+
* explicit driver name to scope to a specific transport — useful in
|
|
15
|
+
* tests that want to assert against `LogEmailDriver` specifically without
|
|
16
|
+
* touching the global config.
|
|
17
|
+
*
|
|
18
|
+
* Earlier this function returned the *driver namespace* (e.g.
|
|
19
|
+
* `{ SESDriver, default }`), which has no `send`. The `'send' in driver`
|
|
20
|
+
* guard in `notify()` was always false, so the email channel silently
|
|
21
|
+
* no-op'd for every booking confirmation, host alert, etc.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useEmail(driver?: string): EmailTransport;
|
|
24
|
+
export declare function useSMS(driver?: string): typeof sms[keyof typeof sms];
|
|
25
|
+
/**
|
|
26
|
+
* Push transport — returns the `@stacksjs/push` namespace whose
|
|
27
|
+
* `send(to, notification, options)` dispatches to Expo or FCM based on
|
|
28
|
+
* `options.driver`. `notify()` calls it with the driver omitted so the
|
|
29
|
+
* push package's own default kicks in (`expo`). Apps that need to
|
|
30
|
+
* force FCM can call `push.send(...)` directly with `{ driver: 'fcm' }`.
|
|
31
|
+
*
|
|
32
|
+
* Previously the `'push'` switch arm in `notify()` threw a hardcoded
|
|
33
|
+
* "not yet wired into a default driver" error even though both drivers
|
|
34
|
+
* exist (stacksjs/stacks#1874 F-1) — this helper is the missing link.
|
|
35
|
+
*/
|
|
36
|
+
export declare function usePush(): typeof push;
|
|
37
|
+
export declare function useDatabase(): typeof DatabaseNotificationDriver;
|
|
38
|
+
/**
|
|
39
|
+
* Broadcast transport — returns the `BroadcastNotificationDriver` which
|
|
40
|
+
* fans out a notification payload over the realtime WebSocket layer
|
|
41
|
+
* (`@stacksjs/realtime` `emit()`). See {@link BroadcastNotificationDriver}
|
|
42
|
+
* for channel-naming rules. stacksjs/stacks#669.
|
|
43
|
+
*/
|
|
44
|
+
export declare function useBroadcast(): typeof BroadcastNotificationDriver;
|
|
45
|
+
export declare function useNotification(typeParam?: string, driverParam?: string): ReturnType<typeof useEmail> | ReturnType<typeof useChat> | ReturnType<typeof useSMS> | typeof DatabaseNotificationDriver;
|
|
46
|
+
export declare function notify(recipient: NotificationRecipient, payload: NotificationPayload, channels?: NotificationChannel[], options?: NotifyOptions): Promise<NotifyResult[]>;
|
|
47
|
+
export declare function notification(): ReturnType<typeof useNotification>;
|
|
48
|
+
/** Optional flags accepted by {@link notify}. */
|
|
49
|
+
export declare interface NotifyOptions {
|
|
50
|
+
ignorePreferences?: boolean
|
|
51
|
+
category?: string
|
|
52
|
+
}
|
|
53
|
+
/** Minimal transport contract used by `notify()` — anything that exposes a
|
|
54
|
+
* `send(EmailMessage)` returning an `EmailResult` works (the @stacksjs/email
|
|
55
|
+
* Mail singleton, a per-test LogEmailDriver, a custom mock, etc.). */
|
|
56
|
+
export declare interface EmailTransport {
|
|
57
|
+
send: (message: EmailMessage) => Promise<EmailResult>
|
|
58
|
+
}
|
|
59
|
+
export declare interface NotificationPayload {
|
|
60
|
+
subject?: string
|
|
61
|
+
body: string
|
|
62
|
+
data?: Record<string, unknown>
|
|
63
|
+
}
|
|
64
|
+
export declare interface NotificationRecipient {
|
|
65
|
+
email?: string
|
|
66
|
+
phone?: string
|
|
67
|
+
userId?: number
|
|
68
|
+
pushTokens?: string | string[]
|
|
69
|
+
broadcastChannel?: string
|
|
70
|
+
}
|
|
71
|
+
export declare interface NotifyResult {
|
|
72
|
+
channel: NotificationChannel
|
|
73
|
+
success: boolean
|
|
74
|
+
error?: Error
|
|
75
|
+
}
|
|
76
|
+
export type NotificationChannel = 'email' | 'sms' | 'chat' | 'database' | 'push' | 'broadcast';
|
|
77
|
+
export { BroadcastNotificationDriver } from './drivers/broadcast';
|
|
78
|
+
export { DatabaseNotificationDriver } from './drivers/database';
|
|
79
|
+
export {
|
|
80
|
+
bulkSetPreferences,
|
|
81
|
+
filterChannelsByPreferences,
|
|
82
|
+
getNotificationPreferences,
|
|
83
|
+
setNotificationPreference,
|
|
84
|
+
} from './preferences';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var P=import.meta.require;import{log as _}from"@stacksjs/cli";import{notification as k}from"@stacksjs/config";import{mail as T}from"@stacksjs/email";import{log as q}from"@stacksjs/cli";var V={async send(z){let F=z.event??"notification",J=z.channel;if(!J)J=z.userId!==void 0?`private-user-${z.userId}`:"notifications";try{let G=await import("@stacksjs/realtime").catch(()=>null),K=G?.emit,O=G?.getServer;if(!K||!O)return{delivered:!1,channel:J,event:F,reason:"@stacksjs/realtime is not available"};if(!O())return{delivered:!1,channel:J,event:F,reason:"realtime server not running"};let Q=z.private??z.userId!==void 0;return K(J,F,z.data,{private:Q}),q.info(`[notifications] broadcast to '${J}' (${F})`),{delivered:!0,channel:J,event:F}}catch(G){let K=G instanceof Error?G.message:String(G);return q.warn(`[notifications] broadcast failed: ${K}`),{delivered:!1,channel:J,event:F,reason:K}}}};import*as x from"@stacksjs/chat";import{log as D}from"@stacksjs/cli";import{db as U}from"@stacksjs/database";var W={async send(z){let F=new Date().toISOString(),G=await U.insertInto("notifications").values({user_id:z.userId,type:z.type,data:JSON.stringify(z.data),read_at:null,created_at:F,updated_at:F}).execute(),K=Array.isArray(G)?G[0]:G,O=Number(K?.insertId??K?.lastInsertRowid??K?.lastInsertId??0);return D.info(`Database notification sent to user ${z.userId}: ${z.type}`),{id:O,user_id:z.userId,type:z.type,data:JSON.stringify(z.data),read_at:null,created_at:F,updated_at:F}},async getUserNotifications(z){return await U.selectFrom("notifications").selectAll().where("user_id","=",z).orderBy("created_at","desc").execute()},async getUnreadNotifications(z){return await U.selectFrom("notifications").selectAll().where("user_id","=",z).where("read_at","is",null).orderBy("created_at","desc").execute()},async markAsRead(z){await U.updateTable("notifications").set({read_at:new Date().toISOString()}).where("id","=",z).execute()},async markAllAsRead(z){await U.updateTable("notifications").set({read_at:new Date().toISOString()}).where("user_id","=",z).where("read_at","is",null).execute()},async unreadCount(z){let F=await U.selectFrom("notifications").select(U.fn.countAll().as("count")).where("user_id","=",z).where("read_at","is",null).executeTakeFirst();return Number(F?.count??0)},async deleteNotification(z){await U.deleteFrom("notifications").where("id","=",z).execute()},async deleteAllNotifications(z){await U.deleteFrom("notifications").where("user_id","=",z).execute()}};import*as M from"@stacksjs/push";import*as A from"@stacksjs/sms";import{log as S}from"@stacksjs/cli";import{db as X}from"@stacksjs/database";var Y="notification_preferences";async function $(z,F){let J=new Map;try{let G=X.selectFrom(Y).select(["channel","enabled","category"]).where("user_id","=",z);if(F!==void 0)G=G.where("category","=",F);let K=await G.execute();for(let O of K)J.set(O.channel,O.enabled===!0||O.enabled===1)}catch(G){S.debug?.(`[notifications] preferences lookup failed (table missing?): ${G.message}`)}return J}async function L(z,F,J,G){let K=new Date().toISOString(),O=G??null,Q=await X.selectFrom(Y).select(["id"]).where("user_id","=",z).where("channel","=",F).where("category",O===null?"is":"=",O).executeTakeFirst();if(Q?.id){await X.updateTable(Y).set({enabled:J,updated_at:K}).where("id","=",Q.id).execute();return}await X.insertInto(Y).values({user_id:z,channel:F,enabled:J,category:O,created_at:K,updated_at:K}).execute()}async function w(z,F){for(let J of F)await L(z,J.channel,J.enabled,J.category)}async function H(z,F,J){let G=await $(z,J);if(J!==void 0){let K=await $(z,void 0);for(let[O,Q]of K)if(!G.has(O))G.set(O,Q)}return F.filter((K)=>{let O=G.get(K);return O===void 0?!0:O===!0})}var m=k;function E(z){return x[z||"slack"]}function B(z){if(z)try{return T.use(z)}catch(F){_.warn(`[notifications] email driver '${z}' not registered \u2014 falling back to default mail singleton (${F.message})`)}return T}function j(z){return A[z||"twilio"]}function I(){return M}function g(){return W}function Kz(){return V}function b(z,F){let J=z||m?.default||"email",G=F;switch(J){case"email":return B(G);case"chat":return E(G);case"sms":return j(G);case"database":return g();default:throw Error(`Notification type "${J}" is not supported`)}}function C(z){return!!z&&typeof z==="object"&&"send"in z&&typeof z.send==="function"}async function Oz(z,F,J=["email"],G={}){let K=J;if(z.userId&&!G.ignorePreferences)try{K=await H(z.userId,J,G.category)}catch(Q){_.warn(`[notify] preferences filter failed, sending all channels: ${Q.message}`)}return(await Promise.allSettled(K.map(async(Q)=>{switch(Q){case"email":{if(!z.email)throw Error("[notify] email channel requires recipient.email");await B().send({to:z.email,subject:F.subject??"",text:F.body,html:`<p>${f(F.body)}</p>`});break}case"sms":{if(!z.phone)throw Error("[notify] sms channel requires recipient.phone");let R=j();if(!C(R))throw Error("[notify] sms channel is not configured: no usable SMS driver with a send() method");await R.send({to:z.phone,body:F.body});break}case"chat":{let R=E();if(!C(R))throw Error("[notify] chat channel is not configured: no usable chat driver with a send() method");await R.send({body:F.body});break}case"database":{if(!z.userId)throw Error("[notify] database channel requires recipient.userId");await W.send({userId:z.userId,type:F.subject||"notification",data:{body:F.body,...F.data}});break}case"push":{if(!z.pushTokens||Array.isArray(z.pushTokens)&&z.pushTokens.length===0)throw Error("[notify] push channel requires recipient.pushTokens");await I().send(z.pushTokens,{title:F.subject,body:F.body,data:F.data});break}case"broadcast":{await V.send({channel:z.broadcastChannel,userId:z.userId,event:F.subject??"notification",data:{body:F.body,...F.data}});break}default:throw Error(`Unsupported notification channel: ${Q}`)}}))).map((Q,R)=>{let Z=K[R];if(!Z)throw Error(`Missing notification channel for result ${R}`);if(Q.status==="rejected"){let N=Q.reason instanceof Error?Q.reason.message:String(Q.reason);_.warn(`[notify] ${Z} channel failed: ${N}`)}return{channel:Z,success:Q.status==="fulfilled",error:Q.status==="rejected"?Q.reason:void 0}})}function Qz(){return b()}function f(z){return String(z??"").replace(/[&<>"']/g,(F)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[F])}export{j as useSMS,I as usePush,b as useNotification,B as useEmail,g as useDatabase,E as useChat,Kz as useBroadcast,L as setNotificationPreference,Oz as notify,Qz as notification,$ as getNotificationPreferences,H as filterChannelsByPreferences,w as bulkSetPreferences,W as DatabaseNotificationDriver,V as BroadcastNotificationDriver};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { NotificationChannel } from './index';
|
|
2
|
+
/**
|
|
3
|
+
* Load all notification preferences for a user as a Map keyed by channel.
|
|
4
|
+
* Returns `Map<channel, enabled>` — channels with no row recorded are
|
|
5
|
+
* absent from the Map (callers should treat absent = enabled by default).
|
|
6
|
+
*
|
|
7
|
+
* If the `notification_preferences` table doesn't exist yet (apps that
|
|
8
|
+
* haven't applied the migration) this returns an empty Map and logs a
|
|
9
|
+
* one-line debug message rather than throwing — the user-facing notify()
|
|
10
|
+
* flow shouldn't 500 because of an opt-in feature.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const prefs = await getNotificationPreferences(user.id)
|
|
15
|
+
* if (prefs.get('email') === false) console.log('user opted out of email')
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function getNotificationPreferences(userId: number, category?: string): Promise<Map<PreferenceChannel, boolean>>;
|
|
19
|
+
/**
|
|
20
|
+
* Upsert a single preference row for `(user_id, channel, category)`.
|
|
21
|
+
* The combination is treated as the natural key — calling this twice with
|
|
22
|
+
* the same triple just toggles `enabled` on the existing row.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // user opts out of marketing emails
|
|
27
|
+
* await setNotificationPreference(user.id, 'email', false, 'marketing')
|
|
28
|
+
* // ...later, opts back in
|
|
29
|
+
* await setNotificationPreference(user.id, 'email', true, 'marketing')
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function setNotificationPreference(userId: number, channel: PreferenceChannel, enabled: boolean, category?: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Bulk upsert preferences for a user — convenient for "save preferences"
|
|
35
|
+
* forms that submit the user's full opt-in/out matrix in one POST.
|
|
36
|
+
*
|
|
37
|
+
* Iterates one upsert per entry rather than one mega-query because the
|
|
38
|
+
* underlying conflict logic is keyed on `(user_id, channel, category)`
|
|
39
|
+
* and a single SQL statement can't express that across N rows portably.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* await bulkSetPreferences(user.id, [
|
|
44
|
+
* { channel: 'email', enabled: false, category: 'marketing' },
|
|
45
|
+
* { channel: 'sms', enabled: true },
|
|
46
|
+
* { channel: 'push', enabled: false },
|
|
47
|
+
* ])
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function bulkSetPreferences(userId: number, prefs: Array<{ channel: PreferenceChannel, enabled: boolean, category?: string }>): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Filter a list of channels through a user's preferences.
|
|
53
|
+
*
|
|
54
|
+
* Returns the subset the user is opted *in* for. Channels with no row
|
|
55
|
+
* recorded fall through as enabled (default-allow) so that introducing
|
|
56
|
+
* the preferences feature doesn't suddenly break notifications for users
|
|
57
|
+
* who never visited the preferences page.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const allowed = await filterChannelsByPreferences(user.id, ['email', 'sms', 'push'])
|
|
62
|
+
* // -> ['email', 'push'] (user disabled sms)
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function filterChannelsByPreferences<T extends PreferenceChannel>(userId: number, channels: T[], category?: string): Promise<T[]>;
|
|
66
|
+
/**
|
|
67
|
+
* `notification_preferences` table — user-level opt-out per channel
|
|
68
|
+
* (and optionally per category, e.g. `marketing` vs `system`).
|
|
69
|
+
*
|
|
70
|
+
* The migration is intentionally NOT shipped here — apps maintain their
|
|
71
|
+
* own migration directory and column conventions. Generate the migration
|
|
72
|
+
* yourself; the schema below is what the runtime expects.
|
|
73
|
+
*
|
|
74
|
+
* ```sql
|
|
75
|
+
* CREATE TABLE notification_preferences (
|
|
76
|
+
* id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
|
+
* user_id INTEGER NOT NULL,
|
|
78
|
+
* channel TEXT NOT NULL, -- email | sms | chat | database | push
|
|
79
|
+
* enabled INTEGER NOT NULL DEFAULT 1, -- boolean (0/1)
|
|
80
|
+
* category TEXT, -- optional, e.g. 'marketing'
|
|
81
|
+
* created_at TEXT NOT NULL,
|
|
82
|
+
* updated_at TEXT NOT NULL,
|
|
83
|
+
* UNIQUE (user_id, channel, category)
|
|
84
|
+
* );
|
|
85
|
+
*
|
|
86
|
+
* CREATE INDEX idx_notification_preferences_user
|
|
87
|
+
* ON notification_preferences (user_id);
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Postgres / MySQL equivalents: use `BOOLEAN` for `enabled`, `BIGINT` for
|
|
91
|
+
* IDs, and `TIMESTAMP` for the timestamp columns. The `UNIQUE (user_id,
|
|
92
|
+
* channel, category)` constraint is what makes the upsert below safe; if
|
|
93
|
+
* your DB doesn't allow `NULL` to participate in `UNIQUE`, treat
|
|
94
|
+
* `category IS NULL` as a sentinel "global" category instead.
|
|
95
|
+
*/
|
|
96
|
+
export declare interface NotificationPreferenceRow {
|
|
97
|
+
id: number
|
|
98
|
+
user_id: number
|
|
99
|
+
channel: NotificationChannel | 'push'
|
|
100
|
+
enabled: boolean | 0 | 1
|
|
101
|
+
category?: string | null
|
|
102
|
+
created_at: string
|
|
103
|
+
updated_at: string
|
|
104
|
+
}
|
|
105
|
+
/** All channels that can be filtered through preferences. */
|
|
106
|
+
export type PreferenceChannel = NotificationChannel | 'push';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/notifications",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.91",
|
|
6
6
|
"description": "The Stacks notifications integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"prepublishOnly": "bun run build"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@stacksjs/chat": "0.70.
|
|
63
|
-
"@stacksjs/cli": "0.70.
|
|
64
|
-
"@stacksjs/config": "0.70.
|
|
62
|
+
"@stacksjs/chat": "0.70.91",
|
|
63
|
+
"@stacksjs/cli": "0.70.91",
|
|
64
|
+
"@stacksjs/config": "0.70.91",
|
|
65
65
|
"better-dx": "^0.2.16",
|
|
66
|
-
"@stacksjs/email": "0.70.
|
|
67
|
-
"@stacksjs/error-handling": "0.70.
|
|
68
|
-
"@stacksjs/push": "0.70.
|
|
69
|
-
"@stacksjs/sms": "0.70.
|
|
70
|
-
"@stacksjs/types": "0.70.
|
|
66
|
+
"@stacksjs/email": "0.70.91",
|
|
67
|
+
"@stacksjs/error-handling": "0.70.91",
|
|
68
|
+
"@stacksjs/push": "0.70.91",
|
|
69
|
+
"@stacksjs/sms": "0.70.91",
|
|
70
|
+
"@stacksjs/types": "0.70.91"
|
|
71
71
|
}
|
|
72
72
|
}
|