@stacksjs/notifications 0.75.108 → 0.75.110

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.
@@ -44,6 +44,7 @@ export declare interface CreateNotificationOptions {
44
44
  }
45
45
  /** Shape of a row-insert result across the supported drivers. */
46
46
  declare interface InsertResultLike {
47
+ id?: number | bigint
47
48
  insertId?: number | bigint
48
49
  lastInsertRowid?: number | bigint
49
50
  lastInsertId?: number | bigint
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @bun
2
- import{log as P}from"@stacksjs/cli";import{notification as W}from"@stacksjs/config";import{mail as B}from"@stacksjs/email";import{log as k}from"@stacksjs/cli";var l={async send(e){let t=e.event??"notification",n=e.channel;if(!n)n=e.userId!==void 0?`private-user.${e.userId}`:"notifications";try{let r=await import("@stacksjs/realtime").catch(()=>null),i=r?.emit,a=r?.getServer;if(!i||!a)return{delivered:!1,channel:n,event:t,reason:"@stacksjs/realtime is not available"};if(!a())return{delivered:!1,channel:n,event:t,reason:"realtime server not running"};let s=e.private??e.userId!==void 0;return i(n,t,e.data,{private:s}),k.info(`[notifications] broadcast to '${n}' (${t})`),{delivered:!0,channel:n,event:t}}catch(r){let i=r instanceof Error?r.message:String(r);return k.warn(`[notifications] broadcast failed: ${i}`),{delivered:!1,channel:n,event:t,reason:i}}}};import*as D from"@stacksjs/chat";import{log as L}from"@stacksjs/cli";import{db as f,sqlDateTime as h}from"@stacksjs/database/runtime";var d={async send(e){let t=h(),r=await f.insertInto("notifications").values({user_id:e.userId,type:e.type,data:JSON.stringify(e.data),read_at:null,created_at:t,updated_at:t}).execute(),i=Array.isArray(r)?r[0]:r,a=Number(i?.insertId??i?.lastInsertRowid??i?.lastInsertId??0);return L.info(`Database notification sent to user ${e.userId}: ${e.type}`),{id:a,user_id:e.userId,type:e.type,data:JSON.stringify(e.data),read_at:null,created_at:t,updated_at:t}},async getUserNotifications(e){return await f.selectFrom("notifications").selectAll().where("user_id","=",e).orderBy("created_at","desc").execute()},async getUnreadNotifications(e){return await f.selectFrom("notifications").selectAll().where("user_id","=",e).whereNull("read_at").orderBy("created_at","desc").execute()},async markAsRead(e){await f.updateTable("notifications").set({read_at:h()}).where("id","=",e).execute()},async markAllAsRead(e){await f.updateTable("notifications").set({read_at:h()}).where("user_id","=",e).whereNull("read_at").execute()},async unreadCount(e){let t=await f.selectFrom("notifications").select(f.fn.countAll().as("count")).where("user_id","=",e).whereNull("read_at").executeTakeFirst();return Number(t?.count??0)},async deleteNotification(e){await f.deleteFrom("notifications").where("id","=",e).execute()},async deleteAllNotifications(e){await f.deleteFrom("notifications").where("user_id","=",e).execute()}};import*as R from"@stacksjs/push";import*as E from"@stacksjs/sms";import{log as U}from"@stacksjs/cli";import{config as T}from"@stacksjs/config";import{db as H,sqlDateTime as _}from"@stacksjs/database/runtime";function I(e,t){switch(t){case"email":return e.email||"";case"sms":return e.phone||"";case"push":return Array.isArray(e.pushTokens)?e.pushTokens.join(", "):e.pushTokens||"";case"broadcast":return e.broadcastChannel||(e.userId?`private-user.${e.userId}`:"notifications");case"database":return e.userId?`User #${e.userId}`:"";case"chat":return"Configured chat destination"}}function b(e,t,n,r,i=_()){return{userId:e.userId,channel:n.channel,recipient:I(e,n.channel),subject:t.subject,body:t.body,status:n.success?"sent":"failed",error:n.error?.message,metadata:{...t.data,...r.category?{category:r.category}:{}},sentAt:i}}function J(e){let t=T.notification?.tracking;if(t?.enabled===!1)return!1;let n=t?.channels;if(!n||n.length===0)return!0;return n.includes(e)}async function g(e){if(!J(e.channel))return;let t=_();try{await H.insertInto("notification_deliveries").values({user_id:e.userId??null,channel:e.channel,recipient:e.recipient,subject:e.subject??null,body:T.notification?.tracking?.body===!1?"":e.body,status:e.status,error:e.error??null,metadata:e.metadata?JSON.stringify(e.metadata):null,sent_at:e.sentAt,created_at:t,updated_at:t}).execute()}catch(n){let r=n instanceof Error?n.message:String(n);U.warn(`[notifications] could not record ${e.channel} delivery: ${r}`)}}import{templateByName as Q}from"@stacksjs/email";var z=/https?:\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]]/g;function C(e){return String(e??"").replace(/\r\n?/g,`
2
+ import{log as k}from"@stacksjs/cli";import{notification as Y}from"@stacksjs/config";import{mail as j}from"@stacksjs/email";import{log as D}from"@stacksjs/cli";var d={async send(e){let t=e.event??"notification",n=e.channel;if(!n)n=e.userId!==void 0?`private-user.${e.userId}`:"notifications";try{let r=await import("@stacksjs/realtime").catch(()=>null),i=r?.emit,a=r?.getServer;if(!i||!a)return{delivered:!1,channel:n,event:t,reason:"@stacksjs/realtime is not available"};if(!a())return{delivered:!1,channel:n,event:t,reason:"realtime server not running"};let s=e.private??e.userId!==void 0;return i(n,t,e.data,{private:s}),D.info(`[notifications] broadcast to '${n}' (${t})`),{delivered:!0,channel:n,event:t}}catch(r){let i=r instanceof Error?r.message:String(r);return D.warn(`[notifications] broadcast failed: ${i}`),{delivered:!1,channel:n,event:t,reason:i}}}};import*as R from"@stacksjs/chat";import{log as U}from"@stacksjs/cli";import{db as u,sqlDateTime as h}from"@stacksjs/database/runtime";var m={async send(e){let t=h(),r=await u.insertInto("notifications").values({user_id:e.userId,type:e.type,data:JSON.stringify(e.data),read_at:null,created_at:t,updated_at:t}).returning("id").execute(),i=Array.isArray(r)?r[0]:r,a=Number(i?.id??i?.insertId??i?.lastInsertRowid??i?.lastInsertId??0);if(!Number.isSafeInteger(a)||a<=0)throw Error("Database notification did not return a generated id");return U.info(`Database notification sent to user ${e.userId}: ${e.type}`),{id:a,user_id:e.userId,type:e.type,data:JSON.stringify(e.data),read_at:null,created_at:t,updated_at:t}},async getUserNotifications(e){return(await u.selectFrom("notifications").selectAll().where("user_id","=",e).orderBy("created_at","desc").execute()).map((n)=>({...n,id:Number(n.id),user_id:Number(n.user_id)}))},async getUnreadNotifications(e){return(await u.selectFrom("notifications").selectAll().where("user_id","=",e).whereNull("read_at").orderBy("created_at","desc").execute()).map((n)=>({...n,id:Number(n.id),user_id:Number(n.user_id)}))},async markAsRead(e){await u.updateTable("notifications").set({read_at:h()}).where("id","=",e).whereNull("read_at").execute()},async markAllAsRead(e){await u.updateTable("notifications").set({read_at:h()}).where("user_id","=",e).whereNull("read_at").execute()},async unreadCount(e){let t=await u.selectFrom("notifications").select(u.fn.countAll().as("count")).where("user_id","=",e).whereNull("read_at").executeTakeFirst();return Number(t?.count??0)},async deleteNotification(e){await u.deleteFrom("notifications").where("id","=",e).execute()},async deleteAllNotifications(e){await u.deleteFrom("notifications").where("user_id","=",e).execute()}};import*as E from"@stacksjs/push";import*as _ from"@stacksjs/sms";import{log as H}from"@stacksjs/cli";import{config as T}from"@stacksjs/config";import{db as J,sqlDateTime as C}from"@stacksjs/database/runtime";function I(e,t){switch(t){case"email":return e.email||"";case"sms":return e.phone||"";case"push":return Array.isArray(e.pushTokens)?e.pushTokens.join(", "):e.pushTokens||"";case"broadcast":return e.broadcastChannel||(e.userId?`private-user.${e.userId}`:"notifications");case"database":return e.userId?`User #${e.userId}`:"";case"chat":return"Configured chat destination"}}function b(e,t,n,r,i=C()){return{userId:e.userId,channel:n.channel,recipient:I(e,n.channel),subject:t.subject,body:t.body,status:n.success?"sent":"failed",error:n.error?.message,metadata:{...t.data,...r.category?{category:r.category}:{}},sentAt:i}}function Q(e){let t=T.notification?.tracking;if(t?.enabled===!1)return!1;let n=t?.channels;if(!n||n.length===0)return!0;return n.includes(e)}async function g(e){if(!Q(e.channel))return;let t=C();try{await J.insertInto("notification_deliveries").values({user_id:e.userId??null,channel:e.channel,recipient:e.recipient,subject:e.subject??null,body:T.notification?.tracking?.body===!1?"":e.body,status:e.status,error:e.error??null,metadata:e.metadata?JSON.stringify(e.metadata):null,sent_at:e.sentAt,created_at:t,updated_at:t}).execute()}catch(n){let r=n instanceof Error?n.message:String(n);H.warn(`[notifications] could not record ${e.channel} delivery: ${r}`)}}import{templateByName as z}from"@stacksjs/email";var G=/https?:\/\/[^\s<>"']+[^\s<>"'.,;:!?)\]]/g;function S(e){return String(e??"").replace(/\r\n?/g,`
3
3
  `).split(/\n\s*\n/).map((t)=>t.trim()).filter(Boolean).map((t)=>{let n=[];return t.split(`
4
- `).forEach((r,i)=>{if(i>0)n.push({br:!0});let a=0;for(let s of r.matchAll(z)){if(s.index>a)n.push({text:r.slice(a,s.index)});n.push({url:s[0]}),a=s.index+s[0].length}if(a<r.length)n.push({text:r.slice(a)})}),n})}function y(e){return String(e??"").replace(/[&<>"']/g,(t)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}async function w(e){try{let{html:n}=await Q("notification",{subject:e.subject??"",variables:{subject:e.subject??"",paragraphs:C(e.body),preheader:String(e.body??"").replace(/\s+/g," ").trim().slice(0,140),...e.action?.url?{actionUrl:e.action.url,actionLabel:e.action.label||"Open"}:{}}});if(n)return n}catch{}let t=e.action?.url?`<p><a href="${y(e.action.url)}">${y(e.action.label||e.action.url)}</a></p>`:"";return`<p>${y(e.body)}</p>${t}`}function N(e){return e.action?.url?`${e.body}
4
+ `).forEach((r,i)=>{if(i>0)n.push({br:!0});let a=0;for(let s of r.matchAll(G)){if(s.index>a)n.push({text:r.slice(a,s.index)});n.push({url:s[0]}),a=s.index+s[0].length}if(a<r.length)n.push({text:r.slice(a)})}),n})}function y(e){return String(e??"").replace(/[&<>"']/g,(t)=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}async function w(e){try{let{html:n}=await z("notification",{subject:e.subject??"",variables:{subject:e.subject??"",paragraphs:S(e.body),preheader:String(e.body??"").replace(/\s+/g," ").trim().slice(0,140),...e.action?.url?{actionUrl:e.action.url,actionLabel:e.action.label||"Open"}:{}}});if(n)return n}catch{}let t=e.action?.url?`<p><a href="${y(e.action.url)}">${y(e.action.label||e.action.url)}</a></p>`:"";return`<p>${y(e.body)}</p>${t}`}function N(e){return e.action?.url?`${e.body}
5
5
 
6
- ${e.action.label||"Open"}: ${e.action.url}`:e.body}import{log as G}from"@stacksjs/cli";import{db as m,sqlDateTime as K}from"@stacksjs/database/runtime";var p="notification_preferences";function A(e,t){return t===null?e.whereNull("category"):e.where("category","=",t)}async function v(e,t){let n=new Map;try{let r=m.selectFrom(p).select(["channel","enabled","category"]).where("user_id","=",e);if(t!==void 0)r=r.where("category","=",t);let i=await r.execute();for(let a of i)n.set(a.channel,a.enabled===!0||a.enabled===1)}catch(r){G.debug?.(`[notifications] preferences lookup failed (table missing?): ${r.message}`)}return n}async function S(e,t,n,r){let i=K(),a=r??null,s=m.selectFrom(p).select(["id"]).where("user_id","=",e).where("channel","=",t),o=await A(s,a).executeTakeFirst();if(o?.id){await m.updateTable(p).set({enabled:n,updated_at:i}).where("id","=",o.id).execute();return}await m.insertInto(p).values({user_id:e,channel:t,enabled:n,category:a,created_at:i,updated_at:i}).execute()}async function V(e,t){for(let n of t)await S(e,n.channel,n.enabled,n.category)}async function x(e,t,n){let r=await v(e,n);if(n!==void 0){let i=await v(e,void 0);for(let[a,s]of i)if(!r.has(a))r.set(a,s)}return t.filter((i)=>{let a=r.get(i);return a===void 0?!0:a===!0})}var X=W;function Y(e){if(!e.success)throw Error(e.message||`Email delivery failed through ${e.provider||"the configured provider"}.`)}function O(e){return D[e||"slack"]}function F(e){if(e)try{return B.use(e)}catch(t){P.warn(`[notifications] email driver '${e}' not registered - falling back to default mail singleton (${t.message})`)}return B}function q(e){return E[e||"twilio"]}function Z(){return R}function ee(){return d}function _e(){return l}function te(e,t){let n=e||X?.default||"email",r=t;switch(n){case"email":return F(r);case"chat":return O(r);case"sms":return q(r);case"database":return ee();default:throw Error(`Notification type "${n}" is not supported`)}}function j(e){return!!e&&typeof e==="object"&&"send"in e&&typeof e.send==="function"}async function Ie(e,t,n=["email"],r={}){let i=n;if(e.userId&&!r.ignorePreferences)try{i=await x(e.userId,n,r.category)}catch(o){P.warn(`[notify] preferences filter failed, sending all channels: ${o.message}`)}let s=(await Promise.allSettled(i.map(async(o)=>{switch(o){case"email":{if(!e.email)throw Error("[notify] email channel requires recipient.email");let u=await F().send({to:e.email,subject:t.subject??"",text:N(t),html:await w(t)});Y(u);break}case"sms":{if(!e.phone)throw Error("[notify] sms channel requires recipient.phone");if(r.ignorePreferences!==!0){let{isPhoneOptedOut:u}=await import("@stacksjs/sms");if(await u(e.phone))throw Error("[notify] sms recipient has opted out (STOP)")}let c=q();if(!j(c))throw Error("[notify] sms channel is not configured: no usable SMS driver with a send() method");await c.send({to:e.phone,body:t.body});break}case"chat":{let c=O();if(!j(c))throw Error("[notify] chat channel is not configured: no usable chat driver with a send() method");await c.send({body:t.body});break}case"database":{if(!e.userId)throw Error("[notify] database channel requires recipient.userId");await d.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");await Z().send(e.pushTokens,{title:t.subject,body:t.body,data:t.data});break}case"broadcast":{await l.send({channel:e.broadcastChannel,userId:e.userId,event:t.subject??"notification",data:{body:t.body,...t.data}});break}default:throw Error(`Unsupported notification channel: ${o}`)}}))).map((o,c)=>{let u=i[c];if(!u)throw Error(`Missing notification channel for result ${c}`);if(o.status==="rejected"){let M=o.reason instanceof Error?o.reason.message:String(o.reason);P.warn(`[notify] ${u} channel failed: ${M}`)}return{channel:u,success:o.status==="fulfilled",error:o.status==="rejected"?o.reason:void 0}});return await Promise.all(s.map((o)=>g(b(e,t,o,r)))),s}function Ce(){return te()}export{l as BroadcastNotificationDriver,d as DatabaseNotificationDriver,V as bulkSetPreferences,C as emailParagraphs,Y as ensureSuccessfulEmailResult,x as filterChannelsByPreferences,v as getNotificationPreferences,b as makeDeliveryRecord,Ce as notification,w as notificationEmailHtml,N as notificationEmailText,Ie as notify,g as recordNotificationDelivery,I as resolveDeliveryRecipient,S as setNotificationPreference,_e as useBroadcast,O as useChat,ee as useDatabase,F as useEmail,te as useNotification,Z as usePush,q as useSMS,A as wherePreferenceCategory};
6
+ ${e.action.label||"Open"}: ${e.action.url}`:e.body}import{log as K}from"@stacksjs/cli";import{db as l,lockRow as A,sqlDateTime as V}from"@stacksjs/database/runtime";var p="notification_preferences";function x(e,t){return t===null?e.whereNull("category"):e.where("category","=",t)}async function v(e,t){let n=new Map;try{let i=await x(l.selectFrom(p).select(["channel","enabled","category"]).where("user_id","=",e),t??null).execute();for(let a of i)n.set(a.channel,a.enabled===!0||a.enabled===1)}catch(r){K.debug?.(`[notifications] preferences lookup failed (table missing?): ${r.message}`)}return n}async function W(e,t,n,r){await l.transaction(async(i)=>{await A(i,"users",{id:e}),await B(e,t,n,r)})}async function B(e,t,n,r){if(!Number.isSafeInteger(e)||e<=0||typeof n!=="boolean")throw Error("A notification preference needs a valid user id and enabled boolean");let i=V(),a=r??null,s=l.selectFrom(p).select(["id"]).where("user_id","=",e).where("channel","=",t),o=await x(s,a).executeTakeFirst();if(o?.id){await l.updateTable(p).set({enabled:n,updated_at:i}).where("id","=",o.id).execute();return}await l.insertInto(p).values({user_id:e,channel:t,enabled:n,category:a,created_at:i,updated_at:i}).execute()}async function X(e,t){await l.transaction(async(n)=>{await A(n,"users",{id:e});for(let r of t)await B(e,r.channel,r.enabled,r.category)})}async function P(e,t,n){let r=await v(e,n);if(n!==void 0){let i=await v(e,void 0);for(let[a,s]of i)if(!s||!r.has(a))r.set(a,s)}return t.filter((i)=>{let a=r.get(i);return a===void 0?!0:a===!0})}var Z=Y;function ee(e){if(!e.success)throw Error(e.message||`Email delivery failed through ${e.provider||"the configured provider"}.`)}function q(e){return R[e||"slack"]}function F(e){if(e)try{return j.use(e)}catch(t){k.warn(`[notifications] email driver '${e}' not registered - falling back to default mail singleton (${t.message})`)}return j}function M(e){return _[e||"twilio"]}function te(){return E}function ne(){return m}function Ie(){return d}function re(e,t){let n=e||Z?.default||"email",r=t;switch(n){case"email":return F(r);case"chat":return q(r);case"sms":return M(r);case"database":return ne();default:throw Error(`Notification type "${n}" is not supported`)}}function O(e){return!!e&&typeof e==="object"&&"send"in e&&typeof e.send==="function"}async function Se(e,t,n=["email"],r={}){let i=n;if(e.userId&&!r.ignorePreferences)try{i=await P(e.userId,n,r.category)}catch(o){k.warn(`[notify] preferences filter failed, sending all channels: ${o.message}`)}let s=(await Promise.allSettled(i.map(async(o)=>{switch(o){case"email":{if(!e.email)throw Error("[notify] email channel requires recipient.email");let f=await F().send({to:e.email,subject:t.subject??"",text:N(t),html:await w(t)});ee(f);break}case"sms":{if(!e.phone)throw Error("[notify] sms channel requires recipient.phone");if(r.ignorePreferences!==!0){let{isPhoneOptedOut:f}=await import("@stacksjs/sms");if(await f(e.phone))throw Error("[notify] sms recipient has opted out (STOP)")}let c=M();if(!O(c))throw Error("[notify] sms channel is not configured: no usable SMS driver with a send() method");await c.send({to:e.phone,body:t.body});break}case"chat":{let c=q();if(!O(c))throw Error("[notify] chat channel is not configured: no usable chat driver with a send() method");await c.send({body:t.body});break}case"database":{if(!e.userId)throw Error("[notify] database channel requires recipient.userId");await m.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");await te().send(e.pushTokens,{title:t.subject,body:t.body,data:t.data});break}case"broadcast":{await d.send({channel:e.broadcastChannel,userId:e.userId,event:t.subject??"notification",data:{body:t.body,...t.data}});break}default:throw Error(`Unsupported notification channel: ${o}`)}}))).map((o,c)=>{let f=i[c];if(!f)throw Error(`Missing notification channel for result ${c}`);if(o.status==="rejected"){let L=o.reason instanceof Error?o.reason.message:String(o.reason);k.warn(`[notify] ${f} channel failed: ${L}`)}return{channel:f,success:o.status==="fulfilled",error:o.status==="rejected"?o.reason:void 0}});return await Promise.all(s.map((o)=>g(b(e,t,o,r)))),s}function Ae(){return re()}export{d as BroadcastNotificationDriver,m as DatabaseNotificationDriver,X as bulkSetPreferences,S as emailParagraphs,ee as ensureSuccessfulEmailResult,P as filterChannelsByPreferences,v as getNotificationPreferences,b as makeDeliveryRecord,Ae as notification,w as notificationEmailHtml,N as notificationEmailText,Se as notify,g as recordNotificationDelivery,I as resolveDeliveryRecipient,W as setNotificationPreference,Ie as useBroadcast,q as useChat,ne as useDatabase,F as useEmail,re as useNotification,te as usePush,M as useSMS,x as wherePreferenceCategory};
@@ -2,6 +2,8 @@ import type { NotificationChannel } from './index';
2
2
  export declare function wherePreferenceCategory<T extends PreferenceCategoryQuery<T>>(query: T, category: string | null): T;
3
3
  /**
4
4
  * Load all notification preferences for a user as a Map keyed by channel.
5
+ * Without a category, returns global preferences only. Category-specific
6
+ * preferences are read only when that category is explicitly requested.
5
7
  * Returns `Map<channel, enabled>` — channels with no row recorded are
6
8
  * absent from the Map (callers should treat absent = enabled by default).
7
9
  *
@@ -35,6 +37,7 @@ export declare function setNotificationPreference(userId: number, channel: Prefe
35
37
  * Bulk upsert preferences for a user — convenient for "save preferences"
36
38
  * forms that submit the user's full opt-in/out matrix in one POST.
37
39
  *
40
+ * Saves the entire matrix inside one transaction and one user lock.
38
41
  * Iterates one upsert per entry rather than one mega-query because the
39
42
  * underlying conflict logic is keyed on `(user_id, channel, category)`
40
43
  * and a single SQL statement can't express that across N rows portably.
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.108",
5
+ "version": "0.75.110",
6
6
  "description": "The Stacks notifications integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -64,18 +64,18 @@
64
64
  "prepublishOnly": "bun run build"
65
65
  },
66
66
  "dependencies": {
67
- "@stacksjs/chat": "0.75.108",
68
- "@stacksjs/cli": "0.75.108",
69
- "@stacksjs/config": "0.75.108",
70
- "@stacksjs/database": "0.75.108",
71
- "@stacksjs/email": "0.75.108",
72
- "@stacksjs/push": "0.75.108",
73
- "@stacksjs/realtime": "0.75.108",
74
- "@stacksjs/sms": "0.75.108"
67
+ "@stacksjs/chat": "0.75.110",
68
+ "@stacksjs/cli": "0.75.110",
69
+ "@stacksjs/config": "0.75.110",
70
+ "@stacksjs/database": "0.75.110",
71
+ "@stacksjs/email": "0.75.110",
72
+ "@stacksjs/push": "0.75.110",
73
+ "@stacksjs/realtime": "0.75.110",
74
+ "@stacksjs/sms": "0.75.110"
75
75
  },
76
76
  "devDependencies": {
77
- "@stacksjs/error-handling": "0.75.108",
78
- "@stacksjs/types": "0.75.108",
77
+ "@stacksjs/error-handling": "0.75.110",
78
+ "@stacksjs/types": "0.75.110",
79
79
  "better-dx": "^0.2.24"
80
80
  }
81
81
  }