@lunora/notify 1.0.0-alpha.22 → 1.0.0-alpha.24

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/index.mjs CHANGED
@@ -1 +1 @@
1
- import{FCM_ENV_KEYS as e,WEB_PUSH_ENV_KEYS as t,fcmFromEnv as i,webPushFromEnv as f}from"./packem_shared/FCM_ENV_KEYS-BbhPScGH.mjs";import{defineNotify as n,isNotifyDefinition as u}from"./packem_shared/defineNotify-CUi2k7pP.mjs";import{createNotify as p}from"./packem_shared/createNotify-qHVmV2yH.mjs";import{buildEngine as d,routingPushProvider as x}from"./packem_shared/buildEngine-oDWs9Pom.mjs";import{enqueuePushBroadcast as c,runPushBroadcastJob as P}from"./packem_shared/enqueuePushBroadcast-tf1LqgzA.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-D5VVGBxm.mjs";import{memorySubscriptionStore as N}from"./packem_shared/memorySubscriptionStore-B60C_I7v.mjs";import{fcmId as g,isGoneError as y,normalizeRegisterInput as v,targetOf as B,webPushId as F}from"./packem_shared/fcmId-ggkSOUHd.mjs";export{e as FCM_ENV_KEYS,t as WEB_PUSH_ENV_KEYS,d as buildEngine,p as createNotify,b as d1SubscriptionStore,n as defineNotify,c as enqueuePushBroadcast,i as fcmFromEnv,g as fcmId,y as isGoneError,u as isNotifyDefinition,N as memorySubscriptionStore,v as normalizeRegisterInput,x as routingPushProvider,P as runPushBroadcastJob,B as targetOf,f as webPushFromEnv,F as webPushId};
1
+ import{FCM_ENV_KEYS as e,WEB_PUSH_ENV_KEYS as t,fcmFromEnv as i,webPushFromEnv as f}from"./packem_shared/FCM_ENV_KEYS-BbhPScGH.mjs";import{defineNotify as n,isNotifyDefinition as u}from"./packem_shared/defineNotify-CUi2k7pP.mjs";import{createNotify as p}from"./packem_shared/createNotify-BDE42tvH.mjs";import{buildEngine as d,routingPushProvider as x}from"./packem_shared/buildEngine-oDWs9Pom.mjs";import{enqueuePushBroadcast as c,runPushBroadcastJob as P}from"./packem_shared/enqueuePushBroadcast-tf1LqgzA.mjs";import{d1SubscriptionStore as b}from"./packem_shared/d1SubscriptionStore-0dh-msWM.mjs";import{memorySubscriptionStore as N}from"./packem_shared/memorySubscriptionStore-FY8yANEM.mjs";import{fcmId as g,isGoneError as y,normalizeRegisterInput as v,targetOf as B,webPushId as F}from"./packem_shared/fcmId-Dwh_R5xe.mjs";export{e as FCM_ENV_KEYS,t as WEB_PUSH_ENV_KEYS,d as buildEngine,p as createNotify,b as d1SubscriptionStore,n as defineNotify,c as enqueuePushBroadcast,i as fcmFromEnv,g as fcmId,y as isGoneError,u as isNotifyDefinition,N as memorySubscriptionStore,v as normalizeRegisterInput,x as routingPushProvider,P as runPushBroadcastJob,B as targetOf,f as webPushFromEnv,F as webPushId};
@@ -1 +1 @@
1
- import{LunoraError as v}from"@lunora/errors";import{buildEngine as F}from"./buildEngine-oDWs9Pom.mjs";import{memorySubscriptionStore as R}from"./memorySubscriptionStore-B60C_I7v.mjs";import{normalizeRegisterInput as z,targetOf as B,isGoneError as L}from"./fcmId-ggkSOUHd.mjs";const W=250,w=(s,l)=>typeof s=="function"?s(l):s,S=s=>s.successful?void 0:s.errorMessages.join("; "),U=(s,l)=>s.successful?"accepted":L(l)?"gone":"failed",j=async(s,l,d)=>{const u=Array.from({length:s.length});let h=0;const g=async()=>{for(;h<s.length;){const f=h;h+=1,u[f]=await d(s[f])}};return await Promise.all(Array.from({length:Math.min(l,s.length)},()=>g())),u},G=(s,l)=>({allowedPushOrigins:s.allowedPushOrigins,chat:w(s.chat,l),fcm:w(s.fcm,l),inApp:w(s.inApp,l),webhook:w(s.webhook,l),webPush:w(s.webPush,l)}),x=new WeakMap,Q=(s,l)=>{let d=x.get(s);d===void 0&&(d=new WeakMap,x.set(s,d));let u=d.get(l);return u===void 0&&(u={warnedNoPushOriginAllowlist:!1,warnedNoStore:!1},d.set(l,u)),u},Y=(s,l,d={})=>{const u=Q(s,l);let h;d.engine===void 0?(u.engine??=F(G(s,l)),h=u.engine):h=d.engine,u.store??=s.store?.(l);let{store:g}=u;g===void 0&&(u.fallbackStore??=R(),!d.silent&&!u.warnedNoStore&&(u.warnedNoStore=!0,console.warn("@lunora/notify: no `store` configured — using a non-durable in-memory subscription store. Configure `store: (env) => d1SubscriptionStore(env.DB)` for production.")),g=u.fallbackStore);const f=g,C=Math.max(1,d.concurrency??10),b=Math.max(1,d.broadcastPageSize??W),{log:M,metrics:P}=d,p=(e,t,r,n=1)=>{P?.count("notify.send",n,{channel:e,provider:t??e,status:r})},y=(e,t,r)=>{M?.warn(`notify ${e} delivery failed`,{channel:e,provider:t??e,status:"failed",...r})},A=(e,t)=>{P?.count("notify.skipped",1,{channel:e,reason:t})},I=()=>{const e=s.allowedPushOrigins!==void 0&&s.allowedPushOrigins.length>0;d.silent||e||u.warnedNoPushOriginAllowlist||(u.warnedNoPushOriginAllowlist=!0,console.warn("@lunora/notify: Web Push registered without `allowedPushOrigins` — endpoints are guarded by a string classifier at register time and a best-effort DNS re-check at send time, both of which are defeatable. Set `allowedPushOrigins` to the exact push-service origins for a hard guarantee."))},T=async e=>(await f.list(e)).map(({keys:r,token:n,...a})=>a),_=async e=>{if(typeof e!="string")return e;const t=await f.get(e);if(t===void 0)throw new v("BAD_REQUEST",`@lunora/notify: no registered subscription with id "${e}"`);return t},E=async(e,t,r)=>{let n,a,o;try{n=await h.sendToChannel("push",{...t,to:B(e)}),a=S(n),o=U(n,a)}catch(c){o="failed",a=c instanceof Error?c.message:String(c)}try{o==="accepted"?await f.markStatus(e.id,"ok"):o==="gone"?await f.delete(e.id):await f.markStatus(e.id,"failed",a)}catch{}return o==="failed"&&y("push",e.kind,{error:a,subscriptionId:e.id,userId:e.userId??null}),r&&p("push",e.kind,o),{error:a,receipt:n,status:o}},D=async(e,t)=>{const r=await j(t,C,async o=>{const{error:c,status:i}=await E(o,e,!1);return{error:c,kind:o.kind,status:i,subscription:o}}),n=new Map;for(const{kind:o,status:c}of r){const i=`${o} ${c}`,m=n.get(i);m===void 0?n.set(i,{count:1,kind:o,status:c}):m.count+=1}for(const{count:o,kind:c,status:i}of n.values())p("push",c,i,o);const a=r.map(({error:o,status:c,subscription:i})=>c==="accepted"?{id:i.id,status:"ok"}:c==="gone"?{error:o,id:i.id,status:"expired"}:{error:o,id:i.id,status:"failed"});return{failed:a.filter(o=>o.status==="failed").length,outcomes:a,pruned:a.filter(o=>o.status==="expired").length,sent:a.filter(o=>o.status==="ok").length,total:a.length}},O=async(e,t)=>{if(t?.limit!==void 0&&t.limit<=0)return{nextCursor:void 0,result:{failed:0,outcomes:[],pruned:0,sent:0,total:0}};const r=t?.limit!==void 0&&t.limit>0?Math.trunc(t.limit):void 0,n=r===void 0?b:Math.min(r,b),a=await f.list({after:t?.after,kind:t?.kind,limit:n+1,userId:t?.userId}),o=t?.after===void 0?a:a.filter($=>$.id>t.after),c=o.length>n,i=c?o.slice(0,n):o;i.length===0&&t?.after===void 0&&A("push","no-subscriptions-matched");const m=await D(e,i);return{nextCursor:c?i[i.length-1]?.id:void 0,result:m}},N={broadcast:async(e,t)=>{const r={failed:0,outcomes:[],pruned:0,sent:0,total:0};let n=t?.after;const a=t?.limit;if(a!==void 0&&a<=0)return r;for(;;){const o=a===void 0?{...t,after:n}:{...t,after:n,limit:a-r.total},{nextCursor:c,result:i}=await O(e,o);if(r.failed+=i.failed,r.pruned+=i.pruned,r.sent+=i.sent,r.total+=i.total,r.outcomes.push(...i.outcomes),a!==void 0&&r.total>=a||c===void 0||c===n)break;n=c}return r},broadcastPage:O,list:e=>T(e),register:e=>("token"in e||I(),f.put(z(e,void 0,{allowedPushOrigins:s.allowedPushOrigins}))),send:async(e,t)=>{const{error:r,receipt:n}=await E(await _(e),t,!0);if(n===void 0)throw new v("INTERNAL",`@lunora/notify: push send failed: ${r??"unknown error"}`);return n},unregister:e=>f.delete(e)},k=async(e,t)=>{if(h.getProvider(e)===void 0)throw A(e,"channel-not-configured"),new v("BAD_REQUEST",`@lunora/notify: the "${e}" channel is not configured in defineNotify(...)`);const r=await h.sendToChannel(e,t),n=r.successful?"accepted":"failed";return p(e,r.provider,n),n==="failed"&&y(e,r.provider,{error:S(r)}),r};return{notify:{chat:e=>k("chat",e),inApp:e=>k("inapp",e),push:N,send:async e=>{const t=await h.send(e);for(const r of t){const n=r.channel??"unknown",a=r.successful?"accepted":"failed";p(n,r.provider,a),a==="failed"&&y(n,r.provider,{error:S(r)})}return t},webhook:e=>k("webhook",e)},push:N}};export{Y as createNotify};
1
+ import{LunoraError as v}from"@lunora/errors";import{buildEngine as F}from"./buildEngine-oDWs9Pom.mjs";import{memorySubscriptionStore as R}from"./memorySubscriptionStore-FY8yANEM.mjs";import{normalizeRegisterInput as z,targetOf as B,isGoneError as L}from"./fcmId-Dwh_R5xe.mjs";const W=250,w=(s,l)=>typeof s=="function"?s(l):s,S=s=>s.successful?void 0:s.errorMessages.join("; "),U=(s,l)=>s.successful?"accepted":L(l)?"gone":"failed",j=async(s,l,d)=>{const u=Array.from({length:s.length});let h=0;const g=async()=>{for(;h<s.length;){const f=h;h+=1,u[f]=await d(s[f])}};return await Promise.all(Array.from({length:Math.min(l,s.length)},()=>g())),u},G=(s,l)=>({allowedPushOrigins:s.allowedPushOrigins,chat:w(s.chat,l),fcm:w(s.fcm,l),inApp:w(s.inApp,l),webhook:w(s.webhook,l),webPush:w(s.webPush,l)}),x=new WeakMap,Q=(s,l)=>{let d=x.get(s);d===void 0&&(d=new WeakMap,x.set(s,d));let u=d.get(l);return u===void 0&&(u={warnedNoPushOriginAllowlist:!1,warnedNoStore:!1},d.set(l,u)),u},Y=(s,l,d={})=>{const u=Q(s,l);let h;d.engine===void 0?(u.engine??=F(G(s,l)),h=u.engine):h=d.engine,u.store??=s.store?.(l);let{store:g}=u;g===void 0&&(u.fallbackStore??=R(),!d.silent&&!u.warnedNoStore&&(u.warnedNoStore=!0,console.warn("@lunora/notify: no `store` configured — using a non-durable in-memory subscription store. Configure `store: (env) => d1SubscriptionStore(env.DB)` for production.")),g=u.fallbackStore);const f=g,C=Math.max(1,d.concurrency??10),b=Math.max(1,d.broadcastPageSize??W),{log:M,metrics:P}=d,p=(e,t,r,n=1)=>{P?.count("notify.send",n,{channel:e,provider:t??e,status:r})},y=(e,t,r)=>{M?.warn(`notify ${e} delivery failed`,{channel:e,provider:t??e,status:"failed",...r})},A=(e,t)=>{P?.count("notify.skipped",1,{channel:e,reason:t})},I=()=>{const e=s.allowedPushOrigins!==void 0&&s.allowedPushOrigins.length>0;d.silent||e||u.warnedNoPushOriginAllowlist||(u.warnedNoPushOriginAllowlist=!0,console.warn("@lunora/notify: Web Push registered without `allowedPushOrigins` — endpoints are guarded by a string classifier at register time and a best-effort DNS re-check at send time, both of which are defeatable. Set `allowedPushOrigins` to the exact push-service origins for a hard guarantee."))},T=async e=>(await f.list(e)).map(({keys:r,token:n,...a})=>a),_=async e=>{if(typeof e!="string")return e;const t=await f.get(e);if(t===void 0)throw new v("BAD_REQUEST",`@lunora/notify: no registered subscription with id "${e}"`);return t},E=async(e,t,r)=>{let n,a,o;try{n=await h.sendToChannel("push",{...t,to:B(e)}),a=S(n),o=U(n,a)}catch(c){o="failed",a=c instanceof Error?c.message:String(c)}try{o==="accepted"?await f.markStatus(e.id,"ok"):o==="gone"?await f.delete(e.id):await f.markStatus(e.id,"failed",a)}catch{}return o==="failed"&&y("push",e.kind,{error:a,subscriptionId:e.id,userId:e.userId??null}),r&&p("push",e.kind,o),{error:a,receipt:n,status:o}},D=async(e,t)=>{const r=await j(t,C,async o=>{const{error:c,status:i}=await E(o,e,!1);return{error:c,kind:o.kind,status:i,subscription:o}}),n=new Map;for(const{kind:o,status:c}of r){const i=`${o} ${c}`,m=n.get(i);m===void 0?n.set(i,{count:1,kind:o,status:c}):m.count+=1}for(const{count:o,kind:c,status:i}of n.values())p("push",c,i,o);const a=r.map(({error:o,status:c,subscription:i})=>c==="accepted"?{id:i.id,status:"ok"}:c==="gone"?{error:o,id:i.id,status:"expired"}:{error:o,id:i.id,status:"failed"});return{failed:a.filter(o=>o.status==="failed").length,outcomes:a,pruned:a.filter(o=>o.status==="expired").length,sent:a.filter(o=>o.status==="ok").length,total:a.length}},O=async(e,t)=>{if(t?.limit!==void 0&&t.limit<=0)return{nextCursor:void 0,result:{failed:0,outcomes:[],pruned:0,sent:0,total:0}};const r=t?.limit!==void 0&&t.limit>0?Math.trunc(t.limit):void 0,n=r===void 0?b:Math.min(r,b),a=await f.list({after:t?.after,kind:t?.kind,limit:n+1,userId:t?.userId}),o=t?.after===void 0?a:a.filter($=>$.id>t.after),c=o.length>n,i=c?o.slice(0,n):o;i.length===0&&t?.after===void 0&&A("push","no-subscriptions-matched");const m=await D(e,i);return{nextCursor:c?i[i.length-1]?.id:void 0,result:m}},N={broadcast:async(e,t)=>{const r={failed:0,outcomes:[],pruned:0,sent:0,total:0};let n=t?.after;const a=t?.limit;if(a!==void 0&&a<=0)return r;for(;;){const o=a===void 0?{...t,after:n}:{...t,after:n,limit:a-r.total},{nextCursor:c,result:i}=await O(e,o);if(r.failed+=i.failed,r.pruned+=i.pruned,r.sent+=i.sent,r.total+=i.total,r.outcomes.push(...i.outcomes),a!==void 0&&r.total>=a||c===void 0||c===n)break;n=c}return r},broadcastPage:O,list:e=>T(e),register:e=>("token"in e||I(),f.put(z(e,void 0,{allowedPushOrigins:s.allowedPushOrigins}))),send:async(e,t)=>{const{error:r,receipt:n}=await E(await _(e),t,!0);if(n===void 0)throw new v("INTERNAL",`@lunora/notify: push send failed: ${r??"unknown error"}`);return n},unregister:e=>f.delete(e)},k=async(e,t)=>{if(h.getProvider(e)===void 0)throw A(e,"channel-not-configured"),new v("BAD_REQUEST",`@lunora/notify: the "${e}" channel is not configured in defineNotify(...)`);const r=await h.sendToChannel(e,t),n=r.successful?"accepted":"failed";return p(e,r.provider,n),n==="failed"&&y(e,r.provider,{error:S(r)}),r};return{notify:{chat:e=>k("chat",e),inApp:e=>k("inapp",e),push:N,send:async e=>{const t=await h.send(e);for(const r of t){const n=r.channel??"unknown",a=r.successful?"accepted":"failed";p(n,r.provider,a),a==="failed"&&y(n,r.provider,{error:S(r)})}return t},webhook:e=>k("webhook",e)},push:N}};export{Y as createNotify};
@@ -1 +1 @@
1
- import{LunoraError as p}from"@lunora/errors";import{legacyIdFor as S}from"./fcmId-ggkSOUHd.mjs";const E=e=>{const i={createdAt:e.created_at,id:e.id,kind:e.kind,lastSeenAt:e.last_seen_at,userId:e.user_id};if(e.endpoint!==null&&(i.endpoint=e.endpoint),e.p256dh!==null&&e.auth!==null&&(i.keys={auth:e.auth,p256dh:e.p256dh}),e.token!==null&&(i.token=e.token),e.last_status!==null&&(i.lastStatus=e.last_status),e.last_error!==null&&(i.lastError=e.last_error),e.metadata!==null)try{i.metadata=JSON.parse(e.metadata)}catch{}return i},c=/^[A-Za-z_]\w*$/u,O=(e,i={})=>{const n=i.tableName??"lunora_push_subscriptions";if(!c.test(n))throw new p("BAD_REQUEST",`@lunora/notify: d1SubscriptionStore tableName "${n}" is not a bare SQL identifier`);let r;const l=()=>(r===void 0&&(r=e.prepare(`CREATE TABLE IF NOT EXISTS ${n} (id TEXT PRIMARY KEY, kind TEXT NOT NULL, endpoint TEXT, p256dh TEXT, auth TEXT, token TEXT, user_id TEXT, metadata TEXT, created_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, last_status TEXT, last_error TEXT)`).run().then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_user_id_idx ON ${n} (user_id)`).run()).then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_kind_idx ON ${n} (kind)`).run()).then(()=>{}),r.catch(()=>{r=void 0})),r),s=async t=>{await l();const a=await e.prepare(`SELECT * FROM ${n} WHERE id = ?1`).bind(t).first();return a===null?void 0:E(a)};return{delete:async t=>{await l(),await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(t).run()},get:s,list:async t=>{await l();const a=[],d=[];t?.kind!==void 0&&(d.push(t.kind),a.push(`kind = ?${d.length.toString()}`)),t?.userId!==void 0&&(t.userId===null?a.push("user_id IS NULL"):(d.push(t.userId),a.push(`user_id = ?${d.length.toString()}`))),t?.after!==void 0&&(d.push(t.after),a.push(`id > ?${d.length.toString()}`));const T=a.length===0?"":` WHERE ${a.join(" AND ")}`,o=" ORDER BY id ASC";let u="";t?.limit!==void 0&&t.limit>0&&(d.push(Math.trunc(t.limit)),u=` LIMIT ?${d.length.toString()}`);const{results:_}=await e.prepare(`SELECT * FROM ${n}${T}${o}${u}`).bind(...d).all();return _.map(h=>E(h))},markStatus:async(t,a,d)=>{await l(),await e.prepare(`UPDATE ${n} SET last_status = ?2, last_error = ?3, last_seen_at = ?4 WHERE id = ?1`).bind(t,a,d??null,Date.now()).run()},put:async t=>{await l(),await e.prepare(`INSERT INTO ${n} (id, kind, endpoint, p256dh, auth, token, user_id, metadata, created_at, last_seen_at, last_status, last_error) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) ON CONFLICT(id) DO UPDATE SET kind = ?2, endpoint = ?3, p256dh = ?4, auth = ?5, token = ?6, user_id = ?7, metadata = ?8, last_seen_at = ?10`).bind(t.id,t.kind,t.endpoint??null,t.keys?.p256dh??null,t.keys?.auth??null,t.token??null,t.userId??null,t.metadata===void 0?null:JSON.stringify(t.metadata),t.createdAt,t.lastSeenAt,t.lastStatus??null,t.lastError??null).run();const a=S(t);return a!==void 0&&a!==t.id&&await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(a).run(),await s(t.id)??t}}};export{O as d1SubscriptionStore};
1
+ import{LunoraError as p}from"@lunora/errors";import{legacyIdFor as S}from"./fcmId-Dwh_R5xe.mjs";const E=e=>{const i={createdAt:e.created_at,id:e.id,kind:e.kind,lastSeenAt:e.last_seen_at,userId:e.user_id};if(e.endpoint!==null&&(i.endpoint=e.endpoint),e.p256dh!==null&&e.auth!==null&&(i.keys={auth:e.auth,p256dh:e.p256dh}),e.token!==null&&(i.token=e.token),e.last_status!==null&&(i.lastStatus=e.last_status),e.last_error!==null&&(i.lastError=e.last_error),e.metadata!==null)try{i.metadata=JSON.parse(e.metadata)}catch{}return i},c=/^[A-Za-z_]\w*$/u,O=(e,i={})=>{const n=i.tableName??"lunora_push_subscriptions";if(!c.test(n))throw new p("BAD_REQUEST",`@lunora/notify: d1SubscriptionStore tableName "${n}" is not a bare SQL identifier`);let r;const l=()=>(r===void 0&&(r=e.prepare(`CREATE TABLE IF NOT EXISTS ${n} (id TEXT PRIMARY KEY, kind TEXT NOT NULL, endpoint TEXT, p256dh TEXT, auth TEXT, token TEXT, user_id TEXT, metadata TEXT, created_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, last_status TEXT, last_error TEXT)`).run().then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_user_id_idx ON ${n} (user_id)`).run()).then(()=>e.prepare(`CREATE INDEX IF NOT EXISTS ${n}_kind_idx ON ${n} (kind)`).run()).then(()=>{}),r.catch(()=>{r=void 0})),r),s=async t=>{await l();const a=await e.prepare(`SELECT * FROM ${n} WHERE id = ?1`).bind(t).first();return a===null?void 0:E(a)};return{delete:async t=>{await l(),await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(t).run()},get:s,list:async t=>{await l();const a=[],d=[];t?.kind!==void 0&&(d.push(t.kind),a.push(`kind = ?${d.length.toString()}`)),t?.userId!==void 0&&(t.userId===null?a.push("user_id IS NULL"):(d.push(t.userId),a.push(`user_id = ?${d.length.toString()}`))),t?.after!==void 0&&(d.push(t.after),a.push(`id > ?${d.length.toString()}`));const T=a.length===0?"":` WHERE ${a.join(" AND ")}`,o=" ORDER BY id ASC";let u="";t?.limit!==void 0&&t.limit>0&&(d.push(Math.trunc(t.limit)),u=` LIMIT ?${d.length.toString()}`);const{results:_}=await e.prepare(`SELECT * FROM ${n}${T}${o}${u}`).bind(...d).all();return _.map(h=>E(h))},markStatus:async(t,a,d)=>{await l(),await e.prepare(`UPDATE ${n} SET last_status = ?2, last_error = ?3, last_seen_at = ?4 WHERE id = ?1`).bind(t,a,d??null,Date.now()).run()},put:async t=>{await l(),await e.prepare(`INSERT INTO ${n} (id, kind, endpoint, p256dh, auth, token, user_id, metadata, created_at, last_seen_at, last_status, last_error) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12) ON CONFLICT(id) DO UPDATE SET kind = ?2, endpoint = ?3, p256dh = ?4, auth = ?5, token = ?6, user_id = ?7, metadata = ?8, last_seen_at = ?10`).bind(t.id,t.kind,t.endpoint??null,t.keys?.p256dh??null,t.keys?.auth??null,t.token??null,t.userId??null,t.metadata===void 0?null:JSON.stringify(t.metadata),t.createdAt,t.lastSeenAt,t.lastStatus??null,t.lastError??null).run();const a=S(t);return a!==void 0&&a!==t.id&&await e.prepare(`DELETE FROM ${n} WHERE id = ?1`).bind(a).run(),await s(t.id)??t}}};export{O as d1SubscriptionStore};
@@ -0,0 +1 @@
1
+ import{LunoraError as s}from"@lunora/errors";import{i as A}from"./ssrf-host-BCpHorGa.mjs";const b=2166136261,_=16777619,m=(t,e=b)=>{let n=e;for(let o=0;o<t.length;o+=1)n^=t.codePointAt(o)??0,n=Math.imul(n,_);return(n>>>0).toString(16).padStart(8,"0")},f=4096,h=t=>{if(t===void 0)return;const e=typeof t=="object"&&t!==null?Object.getPrototypeOf(t):void 0;if(!(typeof t=="object"&&t!==null&&!Array.isArray(t)&&(e===Object.prototype||e===null)))throw new s("BAD_REQUEST","@lunora/notify: register() `metadata` must be a plain object");let o;try{o=JSON.stringify(t)}catch(i){throw new s("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is not JSON-serialisable: ${i instanceof Error?i.message:String(i)}`)}const r=new TextEncoder().encode(o).length;if(r>f)throw new s("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is ${r.toString()} bytes, exceeding the ${f.toString()}-byte cap`);return t},d=t=>t.toString(16).padStart(4,"0"),p=t=>{let e=8997,n=33826,o=40164,r=52210;for(let i=0;i<t.length;i+=1){const a=t.codePointAt(i)??0;e^=a&65535,n^=a>>>16&65535;const c=e*435,y=n*435,E=o*435+e*256,w=r*435+n*256,l=y+(c>>>16),u=E+(l>>>16),S=w+(u>>>16);e=c&65535,n=l&65535,o=u&65535,r=S&65535}return d(r)+d(o)+d(n)+d(e)},T=t=>`wp2_${p(t)}`,O=t=>`fcm2_${p(t)}`,g=t=>m(t),k=t=>`wp_${g(t)}`,v=t=>`fcm_${g(t)}`,U=t=>t.kind==="fcm"?t.token===void 0?void 0:v(t.token):t.endpoint===void 0?void 0:k(t.endpoint),R=t=>{if(typeof t!="string")return t??{};try{return JSON.parse(t)}catch(e){throw new s("BAD_REQUEST",`@lunora/notify: register() web-push subscription is not valid JSON: ${e instanceof Error?e.message:String(e)}`)}},B=(t,e)=>{let n;try{n=new URL(t)}catch{throw new s("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must be an absolute https URL (got "${t}")`)}if(n.protocol!=="https:")throw new s("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must use https (got "${n.protocol}")`);if(e!==void 0&&e.length>0){if(!e.includes(n.origin))throw new s("FORBIDDEN",`@lunora/notify: register() web-push endpoint origin "${n.origin}" is not in the configured allowedPushOrigins allowlist`);return}if(A(n.hostname))throw new s("FORBIDDEN",`@lunora/notify: register() web-push endpoint host "${n.hostname}" is a private/internal address; configure allowedPushOrigins to permit a specific origin`)},x=(t,e=Date.now(),n={})=>{if("token"in t){const{token:c}=t;if(typeof c!="string"||c==="")throw new s("BAD_REQUEST","@lunora/notify: register() fcm input requires a non-empty `token`");return{createdAt:e,id:O(c),kind:"fcm",lastSeenAt:e,metadata:h(t.metadata),token:c,userId:t.userId??null}}const o=R(t.subscription),{endpoint:r}=o,i=o.keys?.p256dh,a=o.keys?.auth;if(typeof r!="string"||r===""||typeof i!="string"||typeof a!="string")throw new s("BAD_REQUEST","@lunora/notify: register() web-push subscription requires `endpoint` and `keys.{p256dh, auth}`");return B(r,n.allowedPushOrigins),{createdAt:e,endpoint:r,id:T(r),keys:{auth:a,p256dh:i},kind:"web-push",lastSeenAt:e,metadata:h(t.metadata),userId:t.userId??null}},F=t=>t.kind==="fcm"?t.token??"":JSON.stringify({endpoint:t.endpoint,keys:t.keys}),D=/\bhttp\s*4(?:04|10)\b/iu,I=/\b(?:unregistered|not[\s-]?registered|registration-token-not-registered)\b/iu,N=/\bsubscription (?:is )?(?:gone|expired|no longer valid)\b/iu,Q=t=>t===void 0?!1:D.test(t)||I.test(t)||N.test(t);export{O as fcmId,Q as isGoneError,v as legacyFcmId,U as legacyIdFor,k as legacyWebPushId,x as normalizeRegisterInput,F as targetOf,T as webPushId};
@@ -1 +1 @@
1
- import{legacyIdFor as n}from"./fcmId-ggkSOUHd.mjs";const i=(t,e)=>t.id<e.id?-1:t.id>e.id?1:0,a=(t,e)=>e===void 0?!0:!(e.kind!==void 0&&t.kind!==e.kind||e.userId!==void 0&&(t.userId??null)!==e.userId),c=()=>{const t=new Map;return{delete:e=>(t.delete(e),Promise.resolve()),get:e=>Promise.resolve(t.get(e)),list:e=>{const r=[];for(const d of t.values())a(d,e)&&r.push(d);r.sort(i);const o=e?.after===void 0?r:r.filter(d=>d.id>e.after),s=e?.limit!==void 0&&e.limit>0?o.slice(0,Math.trunc(e.limit)):o;return Promise.resolve(s)},markStatus:(e,r,o)=>{const s=t.get(e);return s!==void 0&&t.set(e,{...s,lastError:o,lastSeenAt:Date.now(),lastStatus:r}),Promise.resolve()},put:e=>{const r=n(e);r!==void 0&&r!==e.id&&t.delete(r);const o=t.get(e.id),s=o===void 0?e:{...o,...e,createdAt:o.createdAt};return t.set(s.id,s),Promise.resolve(s)}}};export{c as memorySubscriptionStore};
1
+ import{legacyIdFor as n}from"./fcmId-Dwh_R5xe.mjs";const i=(t,e)=>t.id<e.id?-1:t.id>e.id?1:0,a=(t,e)=>e===void 0?!0:!(e.kind!==void 0&&t.kind!==e.kind||e.userId!==void 0&&(t.userId??null)!==e.userId),c=()=>{const t=new Map;return{delete:e=>(t.delete(e),Promise.resolve()),get:e=>Promise.resolve(t.get(e)),list:e=>{const r=[];for(const d of t.values())a(d,e)&&r.push(d);r.sort(i);const o=e?.after===void 0?r:r.filter(d=>d.id>e.after),s=e?.limit!==void 0&&e.limit>0?o.slice(0,Math.trunc(e.limit)):o;return Promise.resolve(s)},markStatus:(e,r,o)=>{const s=t.get(e);return s!==void 0&&t.set(e,{...s,lastError:o,lastSeenAt:Date.now(),lastStatus:r}),Promise.resolve()},put:e=>{const r=n(e);r!==void 0&&r!==e.id&&t.delete(r);const o=t.get(e.id),s=o===void 0?e:{...o,...e,createdAt:o.createdAt};return t.set(s.id,s),Promise.resolve(s)}}};export{c as memorySubscriptionStore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/notify",
3
- "version": "1.0.0-alpha.22",
3
+ "version": "1.0.0-alpha.24",
4
4
  "description": "Multi-channel notifications for Lunora — ctx.notify / ctx.push over @visulima/notification: edge-safe Web Push + FCM, plus chat, in-app inbox and webhook channels, with subscription storage and queue-backed fan-out",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -49,7 +49,7 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@lunora/errors": "1.0.0-alpha.22",
52
+ "@lunora/errors": "1.0.0-alpha.23",
53
53
  "@visulima/notification": "1.0.12"
54
54
  },
55
55
  "engines": {
@@ -1 +0,0 @@
1
- import{LunoraError as s}from"@lunora/errors";import{i as S}from"./ssrf-host-BCpHorGa.mjs";const f=4096,h=t=>{if(t===void 0)return;const e=typeof t=="object"&&t!==null?Object.getPrototypeOf(t):void 0;if(!(typeof t=="object"&&t!==null&&!Array.isArray(t)&&(e===Object.prototype||e===null)))throw new s("BAD_REQUEST","@lunora/notify: register() `metadata` must be a plain object");let r;try{r=JSON.stringify(t)}catch(i){throw new s("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is not JSON-serialisable: ${i instanceof Error?i.message:String(i)}`)}const o=new TextEncoder().encode(r).length;if(o>f)throw new s("BAD_REQUEST",`@lunora/notify: register() \`metadata\` is ${o.toString()} bytes, exceeding the ${f.toString()}-byte cap`);return t},d=t=>t.toString(16).padStart(4,"0"),p=t=>{let e=8997,n=33826,r=40164,o=52210;for(let i=0;i<t.length;i+=1){const a=t.codePointAt(i)??0;e^=a&65535,n^=a>>>16&65535;const c=e*435,y=n*435,E=r*435+e*256,w=o*435+n*256,l=y+(c>>>16),u=E+(l>>>16),b=w+(u>>>16);e=c&65535,n=l&65535,r=u&65535,o=b&65535}return d(o)+d(r)+d(n)+d(e)},A=t=>`wp2_${p(t)}`,_=t=>`fcm2_${p(t)}`,g=t=>{let e=2166136261;for(let n=0;n<t.length;n+=1)e^=t.codePointAt(n)??0,e=Math.imul(e,16777619);return(e>>>0).toString(16).padStart(8,"0")},m=t=>`wp_${g(t)}`,T=t=>`fcm_${g(t)}`,I=t=>t.kind==="fcm"?t.token===void 0?void 0:T(t.token):t.endpoint===void 0?void 0:m(t.endpoint),k=t=>{if(typeof t!="string")return t??{};try{return JSON.parse(t)}catch(e){throw new s("BAD_REQUEST",`@lunora/notify: register() web-push subscription is not valid JSON: ${e instanceof Error?e.message:String(e)}`)}},O=(t,e)=>{let n;try{n=new URL(t)}catch{throw new s("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must be an absolute https URL (got "${t}")`)}if(n.protocol!=="https:")throw new s("BAD_REQUEST",`@lunora/notify: register() web-push \`endpoint\` must use https (got "${n.protocol}")`);if(e!==void 0&&e.length>0){if(!e.includes(n.origin))throw new s("FORBIDDEN",`@lunora/notify: register() web-push endpoint origin "${n.origin}" is not in the configured allowedPushOrigins allowlist`);return}if(S(n.hostname))throw new s("FORBIDDEN",`@lunora/notify: register() web-push endpoint host "${n.hostname}" is a private/internal address; configure allowedPushOrigins to permit a specific origin`)},N=(t,e=Date.now(),n={})=>{if("token"in t){const{token:c}=t;if(typeof c!="string"||c==="")throw new s("BAD_REQUEST","@lunora/notify: register() fcm input requires a non-empty `token`");return{createdAt:e,id:_(c),kind:"fcm",lastSeenAt:e,metadata:h(t.metadata),token:c,userId:t.userId??null}}const r=k(t.subscription),{endpoint:o}=r,i=r.keys?.p256dh,a=r.keys?.auth;if(typeof o!="string"||o===""||typeof i!="string"||typeof a!="string")throw new s("BAD_REQUEST","@lunora/notify: register() web-push subscription requires `endpoint` and `keys.{p256dh, auth}`");return O(o,n.allowedPushOrigins),{createdAt:e,endpoint:o,id:A(o),keys:{auth:a,p256dh:i},kind:"web-push",lastSeenAt:e,metadata:h(t.metadata),userId:t.userId??null}},$=t=>t.kind==="fcm"?t.token??"":JSON.stringify({endpoint:t.endpoint,keys:t.keys}),v=/\bhttp\s*4(?:04|10)\b/iu,R=/\b(?:unregistered|not[\s-]?registered|registration-token-not-registered)\b/iu,D=/\bsubscription (?:is )?(?:gone|expired|no longer valid)\b/iu,U=t=>t===void 0?!1:v.test(t)||R.test(t)||D.test(t);export{_ as fcmId,U as isGoneError,T as legacyFcmId,I as legacyIdFor,m as legacyWebPushId,N as normalizeRegisterInput,$ as targetOf,A as webPushId};