@stacksjs/notifications 0.75.226 → 0.75.230

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/README.md CHANGED
@@ -46,8 +46,15 @@ Absent preferences allow a channel. A global opt-out wins over a category
46
46
  opt-in. `getNotificationPreferences`, `setNotificationPreference` and
47
47
  `bulkSetPreferences` provide persistent preferences; writes serialize on the
48
48
  user row, and bulk changes are atomic. `options.ignorePreferences: true`
49
- explicitly bypasses preferences and SMS opt-outs. Preference lookup failures
50
- currently log and allow sends, so this filter is not a fail-closed consent gate.
49
+ explicitly bypasses preferences only. Preference lookup failures currently log
50
+ and allow sends, so this filter is not a fail-closed consent gate.
51
+
52
+ A phone that replied STOP (`sms_opt_outs`) is never texted, `ignorePreferences`
53
+ or not. Only `options.ignoreOptOut: true` sends past STOP, and only for messages
54
+ the law requires (a mandated security or safety notice). It does not skip
55
+ preferences. Before this split `ignorePreferences` also bypassed STOP; callers
56
+ that relied on it must add `ignoreOptOut` for legally required messages, or let
57
+ the send fail.
51
58
 
52
59
  The database inbox and `notification_deliveries` tracking log are separate.
53
60
  Apply their model-driven migrations before relying on persistence. Inspect the
package/dist/index.d.ts CHANGED
@@ -67,7 +67,9 @@ export declare function useNotification(typeParam?: string, driverParam?: string
67
67
  *
68
68
  * For transactional notifications that must always go out (password
69
69
  * resets, security alerts, billing failures), pass
70
- * `{ ignorePreferences: true }`.
70
+ * `{ ignorePreferences: true }`. That skips preferences only: a phone that
71
+ * texted STOP is still never texted, unless `{ ignoreOptOut: true }` marks a
72
+ * message the law requires you to send.
71
73
  *
72
74
  * @example
73
75
  * ```ts
@@ -86,6 +88,7 @@ export declare function notification(): ReturnType<typeof useNotification>;
86
88
  /** Optional flags accepted by {@link notify}. */
87
89
  export declare interface NotifyOptions {
88
90
  ignorePreferences?: boolean
91
+ ignoreOptOut?: boolean
89
92
  category?: string
90
93
  }
91
94
  /** Minimal transport contract used by `notify()` — anything that exposes a
package/dist/index.js CHANGED
@@ -46,4 +46,4 @@ ${e.action.label||t.action}: ${e.action.url}`:e.body;if(e.unsubscribe?.url)n+=`
46
46
  ${e.unsubscribe.notice||t.unsubscribeNotice}
47
47
  ${t.unsubscribe}: ${e.unsubscribe.url}`;return n}function ie(e){if(!e?.url)return{};return{"List-Unsubscribe":`<${e.oneClickUrl||e.url}>`,...e.oneClickUrl?{"List-Unsubscribe-Post":"List-Unsubscribe=One-Click"}:{}}}async function R(e,t){let n={...t.headers,...ie(t.unsubscribe)};return{to:e,subject:t.subject??"",text:ne(t),html:await te(t),...Object.keys(n).length?{headers:n}:{}}}var re=30;function oe(e){let t=String(e??"").replace(/\s+/g," ").trim().slice(0,re).trim();return t?`${t}: `:""}function D(e,t){let n=String(e.body??""),i=oe(e.smsSender);if(i&&!n.trimStart().toLowerCase().startsWith(i.trim().toLowerCase()))n=`${i}${n.trimStart()}`;if(e.smsOptOut===!1)return n;let r=typeof e.smsOptOut==="string"?e.smsOptOut.trim():t==="marketing"?b(e.locale,e.strings).smsOptOut:"";if(!r||n.toLowerCase().includes(r.toLowerCase()))return n;return`${n.trimEnd()}
48
48
 
49
- ${r}`}import{log as $e}from"@stacksjs/cli";import{db as m,lockRow as ae,sqlDateTime as je}from"@stacksjs/database/runtime";var x="notification_preferences";function C(e,t){return t===null?e.whereNull("category"):e.where("category","=",t)}async function A(e,t){let n=new Map;try{let r=await C(m.selectFrom(x).select(["channel","enabled","category"]).where("user_id","=",e),t??null).execute();for(let o of r)n.set(o.channel,o.enabled===!0||o.enabled===1)}catch(i){$e.debug?.(`[notifications] preferences lookup failed (table missing?): ${i.message}`)}return n}async function O(e,t,n,i){await m.transaction(async(r)=>{await ae(r,"users",{id:e}),await se(e,t,n,i)})}async function se(e,t,n,i){if(!Number.isSafeInteger(e)||e<=0||typeof n!=="boolean")throw Error("A notification preference needs a valid user id and enabled boolean");let r=je(),o=i??null,s=m.selectFrom(x).select(["id"]).where("user_id","=",e).where("channel","=",t),a=await C(s,o).executeTakeFirst();if(a?.id){await m.updateTable(x).set({enabled:n,updated_at:r}).where("id","=",a.id).execute();return}await m.insertInto(x).values({user_id:e,channel:t,enabled:n,category:o,created_at:r,updated_at:r}).execute()}async function Ie(e,t){await m.transaction(async(n)=>{await ae(n,"users",{id:e});for(let i of t)await se(e,i.channel,i.enabled,i.category)})}async function U(e,t,n){let i=await A(e,n);if(n!==void 0){let r=await A(e,void 0);for(let[o,s]of r)if(!s||!i.has(o))i.set(o,s)}return t.filter((r)=>{let o=i.get(r);return o===void 0?!0:o===!0})}import{localDateTime as ce,zonedTimeToUtc as Le}from"@stacksjs/datetime";var Fe=/^([01]\d|2[0-3]):([0-5]\d)$/;function p(e,t){let n=Fe.exec(String(e));if(!n)throw RangeError(`Quiet hours ${t} must be a time like 21:00`);return Number(n[1])*60+Number(n[2])}function Be(e,t){let n=new Date(`${e}T00:00:00Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function j(e,t){if(!t)return!1;let n=p(t.start,"start"),i=p(t.end,"end");if(n===i)return!1;let r=ce(e,t.timeZone),o=p(r.time,"time");return n<i?o>=n&&o<i:o>=n||o<i}function ue(e,t){if(!t||!j(e,t))return e;let n=p(t.start,"start"),i=ce(e,t.timeZone),r=p(i.time,"time"),o=n>p(t.end,"end")&&r>=n?Be(i.date,1):i.date;return Le(`${o}T${t.end}`,i.timeZone)}function Me(e){let t=Number(e.minutes??0);if(!Number.isFinite(t)||t<0)throw RangeError("A wait step needs zero or more minutes");return t}function le(e,t,n,i={}){if(!Number.isSafeInteger(t)||t<0)throw RangeError("A sequence position must be a whole number of zero or more");let r=i.respectsQuietHours??((s)=>s.type!=="wait"),o=[];for(let s=t;s<e.length;s++){let a=e[s];if(a.type==="wait"){let c=Me(a);if(c>0)return{run:o,next:{index:s+1,at:new Date(n.getTime()+c*60000)}};continue}if(r(a)&&j(n,i.quietHours))return{run:o,next:{index:s,at:ue(n,i.quietHours)}};o.push({index:s,step:a})}return{run:o,next:null}}function qe(e,t,n={}){let i=[],r=0,o=t;for(let s=0;s<=e.length*2+1;s++){let a=le(e,r,o,n);for(let c of a.run)i.push({...c,at:o});if(!a.next)break;r=a.next.index,o=a.next.at}return i}import k from"process";import{createHmac as ze,timingSafeEqual as He}from"crypto";import{db as Ke}from"@stacksjs/database/runtime";var fe=["email","sms"];function Ye(){let e=k.env.APP_KEY;if((!e||e.length<16)&&(k.env.APP_ENV==="production"||k.env.NODE_ENV==="production"))throw Error("[notifications/unsubscribe] APP_KEY is missing or too short (need at least 16 characters). Cannot sign unsubscribe links.");return e||"stacks-default-key-dev-only-do-not-use-prod"}function de(e){return ze("sha256",Ye()).update(e).digest("hex")}function me(e,t,n="marketing"){if(!Number.isSafeInteger(e)||e<=0)throw Error("An unsubscribe token needs a positive user id");if(!fe.includes(t))throw Error(`Unsupported unsubscribe channel: ${t}`);if(!n||n.includes(":"))throw Error("An unsubscribe category must be non-empty and contain no colon");let i=`${e}:${t}:${n}`;return Buffer.from(`${i}:${de(i)}`).toString("base64url")}function pe(e){try{let t=Buffer.from(String(e??""),"base64url").toString("utf8");if(!e||Buffer.from(t).toString("base64url")!==e)return null;let n=t.split(":");if(n.length!==4)return null;let[i,r,o,s]=n;if(!i||!o||!s||!fe.includes(r))return null;let a=de(`${i}:${r}:${o}`);if(s.length!==a.length||!He(Buffer.from(s),Buffer.from(a)))return null;let c=Number(i);return Number.isSafeInteger(c)&&c>0&&String(c)===i?{userId:c,channel:r,category:o}:null}catch{return null}}function Qe(e,t={}){let n=String(t.baseUrl||k.env.APP_URL||"http://localhost").replace(/\/$/,""),i=me(e,t.channel??"email",t.category??"marketing"),r=(o)=>o.startsWith("/")?o:`/${o}`;return{url:`${n}${r(t.pagePath??"/unsubscribe")}?token=${i}`,oneClickUrl:`${n}${r(t.endpointPath??"/api/notifications/unsubscribe")}?token=${i}`,...t.preferencesPath===!1?{}:{preferencesUrl:`${n}${r(t.preferencesPath??"/settings")}`},...t.notice?{notice:t.notice}:{}}}async function Ve(e){let t=new URL(e.url,"http://localhost").searchParams.get("token");if(t)return t;let n=await e.json?.().catch(()=>({}));return String(n?.token||"")}async function Ge(e){let t=pe(e);if(!t)return null;if(await Ke.selectFrom("users").select(["id"]).where("id","=",t.userId).executeTakeFirst())await O(t.userId,t.channel,!1,t.category);return t}var Ze=We;function Je(e){if(!e.success)throw Error(e.message||`Email delivery failed through ${e.provider||"the configured provider"}.`)}function Vt(e){if(!e.length)throw Error("No notification channels were delivered");let t=e.filter((n)=>!n.success);if(t.length){let n=t.map((i)=>i.error instanceof Error?i.error:Error(`Notification delivery failed through ${i.channel}`));if(n.length===1)throw n[0];throw AggregateError(n,n.map((i)=>i.message).join("; "))}}function I(e){if(!e.success)throw Error(e.error||e.message||"Notification provider rejected delivery")}function ge(e){let t=e||"slack";if(!["slack","discord","teams"].includes(t))throw Error(`Unsupported chat driver: ${t}`);return B[t]}function he(e){if(e)try{return be.use(e)}catch(t){L.warn(`[notifications] email driver '${e}' not registered - falling back to default mail singleton (${t.message})`)}return be}function ye(e){if(!e)return N;if(e!=="twilio"&&e!=="vonage")throw Error(`Unsupported SMS driver: ${e}`);let t=e;return{send:async(n)=>(await N.init(),N.getDriver(t).send(n))}}function Xe(){return H}function et(){return y}function Gt(){return h}function tt(e,t){let n=e||Ze?.default||"email",i=t;switch(n){case"email":return he(i);case"chat":return ge(i);case"sms":return ye(i);case"database":return et();default:throw Error(`Notification type "${n}" is not supported`)}}async function Wt(e,t,n=["email"],i={}){let r=[...new Set(n)];if(e.userId&&!i.ignorePreferences)try{r=await U(e.userId,r,i.category)}catch(a){L.warn(`[notify] preferences filter failed, sending all channels: ${a.message}`)}let s=(await Promise.allSettled(r.map(async(a)=>{switch(a){case"email":{if(!e.email)throw Error("[notify] email channel requires recipient.email");let l=await he().send(await R(e.email,t));Je(l);break}case"sms":{if(!e.phone)throw Error("[notify] sms channel requires recipient.phone");if(i.ignorePreferences!==!0){let{isPhoneOptedOut:l}=await import("@stacksjs/sms");if(await l(e.phone))throw Error("[notify] sms recipient has opted out (STOP)")}let c=ye();I(await c.send({to:e.phone,body:D(t,i.category)}));break}case"chat":{if(!e.chatRecipient||Array.isArray(e.chatRecipient)&&!e.chatRecipient.length)throw Error("[notify] chat channel requires recipient.chatRecipient");let c=ge();I(await c.send({to:e.chatRecipient,content:t.body,subject:t.subject,data:t.data}));break}case"database":{if(!e.userId)throw Error("[notify] database channel requires recipient.userId");await y.send({userId:e.userId,type:t.subject||"notification",data:{body:t.body,...t.data}});break}case"push":{if(!e.pushTokens||Array.isArray(e.pushTokens)&&e.pushTokens.length===0)throw Error("[notify] push channel requires recipient.pushTokens");let c=Xe();I(await c.send(e.pushTokens,{title:t.subject,body:t.body,data:t.data},{driver:e.pushDriver}));break}case"broadcast":{let c=await h.send({channel:e.broadcastChannel,userId:e.userId,event:t.subject??"notification",data:{body:t.body,...t.data}});if(!c.delivered)throw Error(c.reason||"Broadcast notification was not delivered");break}default:throw Error(`Unsupported notification channel: ${a}`)}}))).map((a,c)=>{let l=r[c];if(!l)throw Error(`Missing notification channel for result ${c}`);if(a.status==="rejected"){let g=a.reason instanceof Error?a.reason.message:String(a.reason);L.warn(`[notify] ${l} channel failed: ${g}`)}return{channel:l,success:a.status==="fulfilled",error:a.status==="rejected"?a.reason instanceof Error?a.reason:Error(String(a.reason)):void 0}});return await Promise.all(s.map((a)=>P(_(e,t,a,i)))),s}function Zt(){return tt()}export{h as BroadcastNotificationDriver,Oe as DEFAULT_SMS_OPT_OUT,Ce as DEFAULT_UNSUBSCRIBE_NOTICE,y as DatabaseNotificationDriver,f as NOTIFICATION_FRAME_STRINGS,re as SMS_SENDER_MAX_LENGTH,V as accountNotificationTemplates,le as advanceSequence,Ie as bulkSetPreferences,G as commerceNotificationTemplates,Re as createNotificationTemplates,Pe as defineNotificationTemplates,ee as emailParagraphs,Je as ensureSuccessfulEmailResult,Vt as ensureSuccessfulNotificationResults,U as filterChannelsByPreferences,A as getNotificationPreferences,w as interpolateNotificationText,j as isQuietTime,ie as listUnsubscribeHeaders,_ as makeDeliveryRecord,ue as nextSendableTime,Zt as notification,z as notificationChannel,te as notificationEmailHtml,R as notificationEmailMessage,ne as notificationEmailText,b as notificationFrameStrings,E as notificationLanguageTag,D as notificationSmsBody,J as notificationTemplateVariables,Qe as notificationUnsubscribeLinks,me as notificationUnsubscribeToken,Wt as notify,pe as parseNotificationUnsubscribeToken,P as recordNotificationDelivery,X as renderNotificationTemplate,Q as resolveDeliveryRecipient,T as resolveNotificationLocale,qe as sequenceTimeline,O as setNotificationPreference,oe as smsSenderPrefix,W as subscriptionNotificationTemplates,Ve as unsubscribeTokenFromRequest,Ge as unsubscribeWithToken,Gt as useBroadcast,ge as useChat,et as useDatabase,he as useEmail,tt as useNotification,Xe as usePush,ye as useSMS,C as wherePreferenceCategory};
49
+ ${r}`}import{log as $e}from"@stacksjs/cli";import{db as m,lockRow as ae,sqlDateTime as je}from"@stacksjs/database/runtime";var x="notification_preferences";function C(e,t){return t===null?e.whereNull("category"):e.where("category","=",t)}async function A(e,t){let n=new Map;try{let r=await C(m.selectFrom(x).select(["channel","enabled","category"]).where("user_id","=",e),t??null).execute();for(let o of r)n.set(o.channel,o.enabled===!0||o.enabled===1)}catch(i){$e.debug?.(`[notifications] preferences lookup failed (table missing?): ${i.message}`)}return n}async function O(e,t,n,i){await m.transaction(async(r)=>{await ae(r,"users",{id:e}),await se(e,t,n,i)})}async function se(e,t,n,i){if(!Number.isSafeInteger(e)||e<=0||typeof n!=="boolean")throw Error("A notification preference needs a valid user id and enabled boolean");let r=je(),o=i??null,s=m.selectFrom(x).select(["id"]).where("user_id","=",e).where("channel","=",t),a=await C(s,o).executeTakeFirst();if(a?.id){await m.updateTable(x).set({enabled:n,updated_at:r}).where("id","=",a.id).execute();return}await m.insertInto(x).values({user_id:e,channel:t,enabled:n,category:o,created_at:r,updated_at:r}).execute()}async function Ie(e,t){await m.transaction(async(n)=>{await ae(n,"users",{id:e});for(let i of t)await se(e,i.channel,i.enabled,i.category)})}async function U(e,t,n){let i=await A(e,n);if(n!==void 0){let r=await A(e,void 0);for(let[o,s]of r)if(!s||!i.has(o))i.set(o,s)}return t.filter((r)=>{let o=i.get(r);return o===void 0?!0:o===!0})}import{localDateTime as ce,zonedTimeToUtc as Le}from"@stacksjs/datetime";var Fe=/^([01]\d|2[0-3]):([0-5]\d)$/;function p(e,t){let n=Fe.exec(String(e));if(!n)throw RangeError(`Quiet hours ${t} must be a time like 21:00`);return Number(n[1])*60+Number(n[2])}function Be(e,t){let n=new Date(`${e}T00:00:00Z`);return n.setUTCDate(n.getUTCDate()+t),n.toISOString().slice(0,10)}function j(e,t){if(!t)return!1;let n=p(t.start,"start"),i=p(t.end,"end");if(n===i)return!1;let r=ce(e,t.timeZone),o=p(r.time,"time");return n<i?o>=n&&o<i:o>=n||o<i}function ue(e,t){if(!t||!j(e,t))return e;let n=p(t.start,"start"),i=ce(e,t.timeZone),r=p(i.time,"time"),o=n>p(t.end,"end")&&r>=n?Be(i.date,1):i.date;return Le(`${o}T${t.end}`,i.timeZone)}function Me(e){let t=Number(e.minutes??0);if(!Number.isFinite(t)||t<0)throw RangeError("A wait step needs zero or more minutes");return t}function le(e,t,n,i={}){if(!Number.isSafeInteger(t)||t<0)throw RangeError("A sequence position must be a whole number of zero or more");let r=i.respectsQuietHours??((s)=>s.type!=="wait"),o=[];for(let s=t;s<e.length;s++){let a=e[s];if(a.type==="wait"){let c=Me(a);if(c>0)return{run:o,next:{index:s+1,at:new Date(n.getTime()+c*60000)}};continue}if(r(a)&&j(n,i.quietHours))return{run:o,next:{index:s,at:ue(n,i.quietHours)}};o.push({index:s,step:a})}return{run:o,next:null}}function qe(e,t,n={}){let i=[],r=0,o=t;for(let s=0;s<=e.length*2+1;s++){let a=le(e,r,o,n);for(let c of a.run)i.push({...c,at:o});if(!a.next)break;r=a.next.index,o=a.next.at}return i}import k from"process";import{createHmac as ze,timingSafeEqual as He}from"crypto";import{db as Ke}from"@stacksjs/database/runtime";var fe=["email","sms"];function Ye(){let e=k.env.APP_KEY;if((!e||e.length<16)&&(k.env.APP_ENV==="production"||k.env.NODE_ENV==="production"))throw Error("[notifications/unsubscribe] APP_KEY is missing or too short (need at least 16 characters). Cannot sign unsubscribe links.");return e||"stacks-default-key-dev-only-do-not-use-prod"}function de(e){return ze("sha256",Ye()).update(e).digest("hex")}function me(e,t,n="marketing"){if(!Number.isSafeInteger(e)||e<=0)throw Error("An unsubscribe token needs a positive user id");if(!fe.includes(t))throw Error(`Unsupported unsubscribe channel: ${t}`);if(!n||n.includes(":"))throw Error("An unsubscribe category must be non-empty and contain no colon");let i=`${e}:${t}:${n}`;return Buffer.from(`${i}:${de(i)}`).toString("base64url")}function pe(e){try{let t=Buffer.from(String(e??""),"base64url").toString("utf8");if(!e||Buffer.from(t).toString("base64url")!==e)return null;let n=t.split(":");if(n.length!==4)return null;let[i,r,o,s]=n;if(!i||!o||!s||!fe.includes(r))return null;let a=de(`${i}:${r}:${o}`);if(s.length!==a.length||!He(Buffer.from(s),Buffer.from(a)))return null;let c=Number(i);return Number.isSafeInteger(c)&&c>0&&String(c)===i?{userId:c,channel:r,category:o}:null}catch{return null}}function Qe(e,t={}){let n=String(t.baseUrl||k.env.APP_URL||"http://localhost").replace(/\/$/,""),i=me(e,t.channel??"email",t.category??"marketing"),r=(o)=>o.startsWith("/")?o:`/${o}`;return{url:`${n}${r(t.pagePath??"/unsubscribe")}?token=${i}`,oneClickUrl:`${n}${r(t.endpointPath??"/api/notifications/unsubscribe")}?token=${i}`,...t.preferencesPath===!1?{}:{preferencesUrl:`${n}${r(t.preferencesPath??"/settings")}`},...t.notice?{notice:t.notice}:{}}}async function Ve(e){let t=new URL(e.url,"http://localhost").searchParams.get("token");if(t)return t;let n=await e.json?.().catch(()=>({}));return String(n?.token||"")}async function Ge(e){let t=pe(e);if(!t)return null;if(await Ke.selectFrom("users").select(["id"]).where("id","=",t.userId).executeTakeFirst())await O(t.userId,t.channel,!1,t.category);return t}var Ze=We;function Je(e){if(!e.success)throw Error(e.message||`Email delivery failed through ${e.provider||"the configured provider"}.`)}function Vt(e){if(!e.length)throw Error("No notification channels were delivered");let t=e.filter((n)=>!n.success);if(t.length){let n=t.map((i)=>i.error instanceof Error?i.error:Error(`Notification delivery failed through ${i.channel}`));if(n.length===1)throw n[0];throw AggregateError(n,n.map((i)=>i.message).join("; "))}}function I(e){if(!e.success)throw Error(e.error||e.message||"Notification provider rejected delivery")}function ge(e){let t=e||"slack";if(!["slack","discord","teams"].includes(t))throw Error(`Unsupported chat driver: ${t}`);return B[t]}function he(e){if(e)try{return be.use(e)}catch(t){L.warn(`[notifications] email driver '${e}' not registered - falling back to default mail singleton (${t.message})`)}return be}function ye(e){if(!e)return N;if(e!=="twilio"&&e!=="vonage")throw Error(`Unsupported SMS driver: ${e}`);let t=e;return{send:async(n)=>(await N.init(),N.getDriver(t).send(n))}}function Xe(){return H}function et(){return y}function Gt(){return h}function tt(e,t){let n=e||Ze?.default||"email",i=t;switch(n){case"email":return he(i);case"chat":return ge(i);case"sms":return ye(i);case"database":return et();default:throw Error(`Notification type "${n}" is not supported`)}}async function Wt(e,t,n=["email"],i={}){let r=[...new Set(n)];if(e.userId&&!i.ignorePreferences)try{r=await U(e.userId,r,i.category)}catch(a){L.warn(`[notify] preferences filter failed, sending all channels: ${a.message}`)}let s=(await Promise.allSettled(r.map(async(a)=>{switch(a){case"email":{if(!e.email)throw Error("[notify] email channel requires recipient.email");let l=await he().send(await R(e.email,t));Je(l);break}case"sms":{if(!e.phone)throw Error("[notify] sms channel requires recipient.phone");if(i.ignoreOptOut!==!0){let{isPhoneOptedOut:l}=await import("@stacksjs/sms");if(await l(e.phone))throw Error("[notify] sms recipient has opted out (STOP)")}let c=ye();I(await c.send({to:e.phone,body:D(t,i.category)}));break}case"chat":{if(!e.chatRecipient||Array.isArray(e.chatRecipient)&&!e.chatRecipient.length)throw Error("[notify] chat channel requires recipient.chatRecipient");let c=ge();I(await c.send({to:e.chatRecipient,content:t.body,subject:t.subject,data:t.data}));break}case"database":{if(!e.userId)throw Error("[notify] database channel requires recipient.userId");await y.send({userId:e.userId,type:t.subject||"notification",data:{body:t.body,...t.data}});break}case"push":{if(!e.pushTokens||Array.isArray(e.pushTokens)&&e.pushTokens.length===0)throw Error("[notify] push channel requires recipient.pushTokens");let c=Xe();I(await c.send(e.pushTokens,{title:t.subject,body:t.body,data:t.data},{driver:e.pushDriver}));break}case"broadcast":{let c=await h.send({channel:e.broadcastChannel,userId:e.userId,event:t.subject??"notification",data:{body:t.body,...t.data}});if(!c.delivered)throw Error(c.reason||"Broadcast notification was not delivered");break}default:throw Error(`Unsupported notification channel: ${a}`)}}))).map((a,c)=>{let l=r[c];if(!l)throw Error(`Missing notification channel for result ${c}`);if(a.status==="rejected"){let g=a.reason instanceof Error?a.reason.message:String(a.reason);L.warn(`[notify] ${l} channel failed: ${g}`)}return{channel:l,success:a.status==="fulfilled",error:a.status==="rejected"?a.reason instanceof Error?a.reason:Error(String(a.reason)):void 0}});return await Promise.all(s.map((a)=>P(_(e,t,a,i)))),s}function Zt(){return tt()}export{h as BroadcastNotificationDriver,Oe as DEFAULT_SMS_OPT_OUT,Ce as DEFAULT_UNSUBSCRIBE_NOTICE,y as DatabaseNotificationDriver,f as NOTIFICATION_FRAME_STRINGS,re as SMS_SENDER_MAX_LENGTH,V as accountNotificationTemplates,le as advanceSequence,Ie as bulkSetPreferences,G as commerceNotificationTemplates,Re as createNotificationTemplates,Pe as defineNotificationTemplates,ee as emailParagraphs,Je as ensureSuccessfulEmailResult,Vt as ensureSuccessfulNotificationResults,U as filterChannelsByPreferences,A as getNotificationPreferences,w as interpolateNotificationText,j as isQuietTime,ie as listUnsubscribeHeaders,_ as makeDeliveryRecord,ue as nextSendableTime,Zt as notification,z as notificationChannel,te as notificationEmailHtml,R as notificationEmailMessage,ne as notificationEmailText,b as notificationFrameStrings,E as notificationLanguageTag,D as notificationSmsBody,J as notificationTemplateVariables,Qe as notificationUnsubscribeLinks,me as notificationUnsubscribeToken,Wt as notify,pe as parseNotificationUnsubscribeToken,P as recordNotificationDelivery,X as renderNotificationTemplate,Q as resolveDeliveryRecipient,T as resolveNotificationLocale,qe as sequenceTimeline,O as setNotificationPreference,oe as smsSenderPrefix,W as subscriptionNotificationTemplates,Ve as unsubscribeTokenFromRequest,Ge as unsubscribeWithToken,Gt as useBroadcast,ge as useChat,et as useDatabase,he as useEmail,tt as useNotification,Xe as usePush,ye as useSMS,C as wherePreferenceCategory};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/notifications",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.226",
5
+ "version": "0.75.230",
6
6
  "description": "The Stacks notifications integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -64,19 +64,19 @@
64
64
  "prepublishOnly": "bun run build"
65
65
  },
66
66
  "dependencies": {
67
- "@stacksjs/chat": "0.75.226",
68
- "@stacksjs/cli": "0.75.226",
69
- "@stacksjs/config": "0.75.226",
70
- "@stacksjs/database": "0.75.226",
71
- "@stacksjs/datetime": "0.75.226",
72
- "@stacksjs/email": "0.75.226",
73
- "@stacksjs/push": "0.75.226",
74
- "@stacksjs/realtime": "0.75.226",
75
- "@stacksjs/sms": "0.75.226"
67
+ "@stacksjs/chat": "0.75.230",
68
+ "@stacksjs/cli": "0.75.230",
69
+ "@stacksjs/config": "0.75.230",
70
+ "@stacksjs/database": "0.75.230",
71
+ "@stacksjs/datetime": "0.75.230",
72
+ "@stacksjs/email": "0.75.230",
73
+ "@stacksjs/push": "0.75.230",
74
+ "@stacksjs/realtime": "0.75.230",
75
+ "@stacksjs/sms": "0.75.230"
76
76
  },
77
77
  "devDependencies": {
78
- "@stacksjs/error-handling": "0.75.226",
79
- "@stacksjs/types": "0.75.226",
78
+ "@stacksjs/error-handling": "0.75.230",
79
+ "@stacksjs/types": "0.75.230",
80
80
  "better-dx": "^0.2.24"
81
81
  }
82
82
  }