@mesh-kit/server 2.0.7 → 2.0.9
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.js +28 -28
- package/dist/index.mjs +27 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,29 +1,4 @@
|
|
|
1
|
-
"use strict";var Ee=Object.create;var _=Object.defineProperty;var Se=Object.getOwnPropertyDescriptor;var Ie=Object.getOwnPropertyNames;var Te=Object.getPrototypeOf,Oe=Object.prototype.hasOwnProperty;var Ne=(c,t)=>{for(var e in t)_(c,e,{get:t[e],enumerable:!0})},ee=(c,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Ie(t))!Oe.call(c,s)&&s!==e&&_(c,s,{get:()=>t[s],enumerable:!(r=Se(t,s))||r.enumerable});return c};var te=(c,t,e)=>(e=c!=null?Ee(Te(c)):{},ee(t||!c||!c.__esModule?_(e,"default",{value:c,enumerable:!0}):e,c)),$e=c=>ee(_({},"__esModule",{value:!0}),c);var ke={};Ne(ke,{Connection:()=>w,ConnectionManager:()=>R,MeshContext:()=>T,MeshServer:()=>W,MessageStream:()=>y,PersistenceManager:()=>$,PostgreSQLPersistenceAdapter:()=>N,PresenceManager:()=>E,RecordManager:()=>S,RoomManager:()=>I,SQLitePersistenceAdapter:()=>O});module.exports=$e(ke);var ve=require("uuid"),Ce=require("ws");var se=class extends Error{constructor(c,t,e){super(c),typeof t=="string"&&(this.code=t),this.name=typeof e=="string"?e:"CodeError"}},H=(c=>(c[c.NONE=0]="NONE",c[c.ERROR=1]="ERROR",c[c.WARN=2]="WARN",c[c.INFO=3]="INFO",c[c.DEBUG=4]="DEBUG",c))(H||{}),re=typeof window<"u"&&typeof window.document<"u",ne=class{constructor(c){this.config={level:c?.level??3,prefix:c?.prefix??"[mesh]",styling:c?.styling??re}}configure(c){this.config={...this.config,...c}}info(...c){this.config.level>=3&&this.log("log",...c)}warn(...c){this.config.level>=2&&this.log("warn",...c)}error(...c){this.config.level>=1&&this.log("error",...c)}debug(...c){this.config.level>=4&&this.log("debug",...c)}log(c,...t){if(this.config.styling&&re){let e={prefix:"background: #000; color: #FFA07A; padding: 2px 4px; border-radius: 2px;",reset:""};console[c](`%c${this.config.prefix}%c`,e.prefix,e.reset,...t)}else console[c](this.config.prefix,...t)}},De=new ne({level:1,styling:!0}),l=new ne({level:1,styling:!1});function v(c,t){if(!g(c)||!g(t))return t;let e={...c};for(let r in t)t.hasOwnProperty(r)&&(g(t[r])&&g(c[r])?e[r]=v(c[r],t[r]):e[r]=t[r]);return e}function g(c){return c!==null&&typeof c=="object"&&!Array.isArray(c)}function F(c){try{return JSON.parse(c)}catch{return{command:"",payload:{}}}}function ie(c){return JSON.stringify(c)}var f=(c=>(c[c.ONLINE=3]="ONLINE",c[c.CONNECTING=2]="CONNECTING",c[c.RECONNECTING=1]="RECONNECTING",c[c.OFFLINE=0]="OFFLINE",c))(f||{});var ce=require("events"),de=require("ws");var k=class{constructor(){this.start=0;this.end=0;this.ms=0}onRequest(){this.start=Date.now()}onResponse(){this.end=Date.now(),this.ms=this.end-this.start}};var K=class{};var oe=require("crypto"),Y=[];for(let c=0;c<256;c++)Y[c]=(c+256).toString(16).substring(1);function xe(c,t){let e=`000000${c}`;return e.substring(e.length-t)}var Ae=32;function ae(c){let t=c.len||16,e="",r=0,s=1679616,n=c.init+Math.ceil(s/2);function i(){return n>=s&&(n=0),n++,(n-1).toString(16)}return()=>{if(!e||r===256){let h=new Uint8Array(t);(0,oe.getRandomValues)(h),e=Array.from(h,u=>Y[u]).join("").substring(0,t),r=0}let o=Date.now().toString(36),a=xe(i(),6),d=Y[r++],p=parseInt(d,16)%Ae;return`conn-${o}${a}${d}${e}${p}`}}var Le=ae({init:Date.now(),len:4}),w=class extends ce.EventEmitter{constructor(e,r,s,n){super();this.alive=!0;this.missedPongs=0;this.status=f.ONLINE;this.socket=e,this.id=Le(),this.remoteAddress=r.socket.remoteAddress,this.connectionOptions=s,this.server=n,this.applyListeners(),this.startIntervals()}get isDead(){return!this.socket||this.socket.readyState!==de.WebSocket.OPEN}startIntervals(){this.latency=new k,this.ping=new K,this.latency.interval=setInterval(()=>{this.alive&&(typeof this.latency.ms=="number"&&this.send({command:"latency",payload:this.latency.ms}),this.latency.onRequest(),this.send({command:"latency:request",payload:{}}))},this.connectionOptions.latencyInterval),this.ping.interval=setInterval(()=>{if(this.alive)this.missedPongs=0;else{this.missedPongs++;let e=this.connectionOptions.maxMissedPongs??1;if(this.missedPongs>e){l.info(`Closing connection (${this.id}) due to missed pongs`),this.close(),this.server.cleanupConnection(this);return}}this.alive=!1,this.send({command:"ping",payload:{}})},this.connectionOptions.pingInterval)}stopIntervals(){clearInterval(this.latency.interval),clearInterval(this.ping.interval)}applyListeners(){this.socket.on("close",()=>{l.info("Client's socket closed:",this.id),this.status=f.OFFLINE,this.emit("close")}),this.socket.on("error",e=>{this.emit("error",e)}),this.socket.on("message",e=>{try{let r=F(e.toString());if(r.command==="latency:response"){this.latency.onResponse();return}else if(r.command==="pong"){this.alive=!0,this.missedPongs=0,this.emit("pong",this.id);return}this.emit("message",e)}catch(r){this.emit("error",r)}})}send(e){if(this.isDead)return!1;try{return this.socket.send(ie(e)),!0}catch(r){return this.emit("error",r),!1}}async close(){if(this.isDead)return!1;try{return await new Promise((e,r)=>{this.socket.once("close",e),this.socket.once("error",r),this.socket.close()}),!0}catch(e){return this.emit("error",e),!1}}};var C="mesh:pubsub:",M="mesh:record-updates",le="mesh:record:",he="mesh:record-version:";var P="mesh:connections",_e="mesh:connections:",R=class{constructor(t){this.localConnections={};this.redis=t.redis,this.instanceId=t.instanceId,this.roomManager=t.roomManager}getLocalConnections(){return Object.values(this.localConnections)}getLocalConnection(t){return this.localConnections[t]??null}async registerConnection(t){this.localConnections[t.id]=t;let e=this.redis.pipeline();e.hset(P,t.id,this.instanceId),e.sadd(this.getInstanceConnectionsKey(this.instanceId),t.id),await e.exec()}getInstanceConnectionsKey(t){return`${_e}${t}`}async deregisterConnection(t){let e=await this.getInstanceIdForConnection(t);if(!e)return;let r=this.redis.pipeline();r.hdel(P,t.id),r.srem(this.getInstanceConnectionsKey(e),t.id),await r.exec()}async getInstanceIdForConnection(t){return this.redis.hget(P,t.id)}async getInstanceIdsForConnections(t){if(t.length===0)return{};let e=await this.redis.hmget(P,...t),r={};return t.forEach((s,n)=>{r[s]=e[n]??null}),r}async getAllConnectionIds(){return this.redis.hkeys(P)}async getLocalConnectionIds(){return this.redis.smembers(this.getInstanceConnectionsKey(this.instanceId))}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let o=await this.getMetadata(t);n==="merge"?g(o)&&g(e)?s={...o,...e}:s=e:n==="deepMerge"&&(g(o)&&g(e)?s=v(o,e):s=e)}let i=this.redis.pipeline();i.hset(P,t.id,JSON.stringify(s)),await i.exec()}async getMetadata(t){let e=await this.redis.hget(P,t.id);return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.getAllConnectionIds(),e=await this.getInstanceIdsForConnections(t),r=[];return t.forEach(s=>{try{let n=e[s]?JSON.parse(e[s]):null;r.push({id:s,metadata:n})}catch(n){console.error(`Failed to parse metadata for connection ${s}:`,n),r.push({id:s,metadata:null})}}),r}async getAllMetadataForRoom(t){let e=await this.roomManager.getRoomConnectionIds(t),r=await this.getInstanceIdsForConnections(e),s=[];return e.forEach(n=>{try{let i=r[n]?JSON.parse(r[n]):null;s.push({id:n,metadata:i})}catch(i){console.error(`Failed to parse metadata for connection ${n}:`,i),s.push({id:n,metadata:null})}}),s}async cleanupConnection(t){await this.deregisterConnection(t)}};var E=class{constructor(t){this.PRESENCE_KEY_PATTERN=/^mesh:presence:room:(.+):conn:(.+)$/;this.PRESENCE_STATE_KEY_PATTERN=/^mesh:presence:state:(.+):conn:(.+)$/;this.trackedRooms=[];this.roomGuards=new Map;this.roomTTLs=new Map;this.defaultTTL=0;this.redis=t.redis,this.roomManager=t.roomManager,this.redisManager=t.redisManager,this.presenceExpirationEventsEnabled=t.enableExpirationEvents??!0,this.presenceExpirationEventsEnabled&&this.subscribeToExpirationEvents()}getExpiredEventsPattern(){return`__keyevent@${this.redis.options?.db??0}__:expired`}subscribeToExpirationEvents(){let{subClient:t}=this.redisManager,e=this.getExpiredEventsPattern();t.psubscribe(e),t.on("pmessage",(r,s,n)=>{(this.PRESENCE_KEY_PATTERN.test(n)||this.PRESENCE_STATE_KEY_PATTERN.test(n))&&this.handleExpiredKey(n)})}async handleExpiredKey(t){try{let e=t.match(this.PRESENCE_KEY_PATTERN);if(e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.markOffline(s,r);return}if(e=t.match(this.PRESENCE_STATE_KEY_PATTERN),e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.publishPresenceStateUpdate(r,s,null)}}catch(e){console.error("[PresenceManager] Failed to handle expired key:",e)}}trackRoom(t,e){this.trackedRooms.push(t),typeof e=="function"?this.roomGuards.set(t,e):e&&typeof e=="object"&&(e.guard&&this.roomGuards.set(t,e.guard),e.ttl&&typeof e.ttl=="number"&&this.roomTTLs.set(t,e.ttl))}async isRoomTracked(t,e){let r=this.trackedRooms.find(s=>typeof s=="string"?s===t:s.test(t));if(!r)return!1;if(e){let s=this.roomGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}}return!0}getRoomTTL(t){let e=this.trackedRooms.find(r=>typeof r=="string"?r===t:r.test(t));if(e){let r=this.roomTTLs.get(e);if(r!==void 0)return r}return this.defaultTTL}presenceRoomKey(t){return`mesh:presence:room:${t}`}presenceConnectionKey(t,e){return`mesh:presence:room:${t}:conn:${e}`}presenceStateKey(t,e){return`mesh:presence:state:${t}:conn:${e}`}async markOnline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.getRoomTTL(e),i=this.redis.pipeline();if(i.sadd(r,t),n>0){let o=Math.max(1,Math.floor(n/1e3));i.set(s,"","EX",o)}else i.set(s,"");await i.exec(),await this.publishPresenceUpdate(e,t,"join")}async markOffline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.presenceStateKey(e,t),i=this.redis.pipeline();i.srem(r,t),i.del(s),i.del(n),await i.exec(),await this.publishPresenceUpdate(e,t,"leave")}async refreshPresence(t,e){let r=this.presenceConnectionKey(e,t),s=this.getRoomTTL(e);if(s>0){let n=Math.max(1,Math.floor(s/1e3));await this.redis.set(r,"","EX",n)}else await this.redis.set(r,"")}async getPresentConnections(t){return this.redis.smembers(this.presenceRoomKey(t))}async publishPresenceUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:r,connectionId:e,roomName:t,timestamp:Date.now()});await this.redis.publish(s,n)}async publishPresenceState(t,e,r,s,n){let i=this.presenceStateKey(e,t),o=JSON.stringify(r),a=this.redis.pipeline();s&&s>0?a.set(i,o,"PX",s):a.set(i,o),await a.exec(),!n&&await this.publishPresenceStateUpdate(e,t,r)}async clearPresenceState(t,e){let r=this.presenceStateKey(e,t);await this.redis.del(r),await this.publishPresenceStateUpdate(e,t,null)}async getPresenceState(t,e){let r=this.presenceStateKey(e,t),s=await this.redis.get(r);if(!s)return null;try{return JSON.parse(s)}catch(n){return console.error(`[PresenceManager] Failed to parse presence state: ${n}`),null}}async getAllPresenceStates(t){let e=new Map,r=await this.getPresentConnections(t);if(r.length===0)return e;let s=this.redis.pipeline();for(let i of r)s.get(this.presenceStateKey(t,i));let n=await s.exec();if(!n)return e;for(let i=0;i<r.length;i++){let o=r[i];if(!o)continue;let[a,d]=n[i]||[];if(!(a||!d))try{let p=JSON.parse(d);e.set(o,p)}catch(p){console.error(`[PresenceManager] Failed to parse presence state: ${p}`)}}return e}async publishPresenceStateUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:"state",connectionId:e,roomName:t,state:r,timestamp:Date.now()});await this.redis.publish(s,n)}async cleanupConnection(t){let e=t.id,r=await this.roomManager.getRoomsForConnection(e);for(let s of r)await this.isRoomTracked(s)&&await this.markOffline(e,s)}async cleanup(){let{subClient:t}=this.redisManager;if(t&&t.status!=="end"){let e=this.getExpiredEventsPattern();await new Promise(r=>{t.punsubscribe(e,()=>r())})}}};var pe=te(require("fast-json-patch"));var S=class{constructor(t){this.recordUpdateCallbacks=[];this.recordRemovedCallbacks=[];this.redis=t.redis,this.server=t.server}getServer(){return this.server}getRedis(){return this.redis}recordKey(t){return`${le}${t}`}recordVersionKey(t){return`${he}${t}`}async getRecord(t){let e=await this.redis.get(this.recordKey(t));return e?JSON.parse(e):null}async getVersion(t){let e=await this.redis.get(this.recordVersionKey(t));return e?parseInt(e,10):0}async getRecordAndVersion(t){let e=this.redis.pipeline();e.get(this.recordKey(t)),e.get(this.recordVersionKey(t));let r=await e.exec(),s=r?.[0]?.[1],n=r?.[1]?.[1],i=s?JSON.parse(s):null,o=n?parseInt(n,10):0;return{record:i,version:o}}async publishUpdate(t,e,r="replace"){let s=this.recordKey(t),n=this.recordVersionKey(t),{record:i,version:o}=await this.getRecordAndVersion(t),a;r==="merge"?g(i)&&g(e)?a={...i,...e}:a=e:r==="deepMerge"&&g(i)&&g(e)?a=v(i,e):a=e;let d=pe.default.compare(i??{},a??{});if(d.length===0)return null;let p=o+1,h=this.redis.pipeline();return h.set(s,JSON.stringify(a)),h.set(n,p.toString()),await h.exec(),this.recordUpdateCallbacks.length>0&&Promise.all(this.recordUpdateCallbacks.map(async u=>{try{await u({recordId:t,value:a})}catch(b){console.error(`Error in record update callback for ${t}:`,b)}})).catch(u=>{console.error(`Error in record update callbacks for ${t}:`,u)}),{patch:d,version:p,finalValue:a}}async deleteRecord(t){let{record:e,version:r}=await this.getRecordAndVersion(t);if(!e)return null;let s=this.redis.pipeline();return s.del(this.recordKey(t)),s.del(this.recordVersionKey(t)),await s.exec(),this.recordRemovedCallbacks.length>0&&Promise.all(this.recordRemovedCallbacks.map(async n=>{try{await n({recordId:t,value:e})}catch(i){console.error(`Error in record removed callback for ${t}:`,i)}})).catch(n=>{console.error(`Error in record removed callbacks for ${t}:`,n)}),{version:r}}onRecordUpdate(t){return this.recordUpdateCallbacks.push(t),()=>{this.recordUpdateCallbacks=this.recordUpdateCallbacks.filter(e=>e!==t)}}onRecordRemoved(t){return this.recordRemovedCallbacks.push(t),()=>{this.recordRemovedCallbacks=this.recordRemovedCallbacks.filter(e=>e!==t)}}};var I=class{constructor(t){this.redis=t.redis}roomKey(t){return`mesh:room:${t}`}connectionsRoomKey(t){return`mesh:connection:${t}:rooms`}roomMetadataKey(t){return`mesh:roommeta:${t}`}async getRoomConnectionIds(t){return this.redis.smembers(this.roomKey(t))}async connectionIsInRoom(t,e){let r=typeof e=="string"?e:e.id;return!!await this.redis.sismember(this.roomKey(t),r)}async addToRoom(t,e){let r=typeof e=="string"?e:e.id;await this.redis.sadd(this.roomKey(t),r),await this.redis.sadd(this.connectionsRoomKey(r),t)}async getRoomsForConnection(t){let e=typeof t=="string"?t:t.id;return await this.redis.smembers(this.connectionsRoomKey(e))}async getAllRooms(){return(await this.redis.keys("mesh:room:*")).map(e=>e.replace("mesh:room:",""))}async removeFromRoom(t,e){let r=typeof e=="string"?e:e.id,s=this.redis.pipeline();s.srem(this.roomKey(t),r),s.srem(this.connectionsRoomKey(r),t),await s.exec()}async removeFromAllRooms(t){let e=typeof t=="string"?t:t.id,r=await this.redis.smembers(this.connectionsRoomKey(e)),s=this.redis.pipeline();for(let n of r)s.srem(this.roomKey(n),e);s.del(this.connectionsRoomKey(e)),await s.exec()}async clearRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),await r.exec()}async deleteRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),r.del(this.roomMetadataKey(t)),await r.exec()}async cleanupConnection(t){let e=await this.redis.smembers(this.connectionsRoomKey(t.id)),r=this.redis.pipeline();for(let s of e)r.srem(this.roomKey(s),t.id);r.del(this.connectionsRoomKey(t.id)),await r.exec()}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let i=await this.getMetadata(t);n==="merge"?g(i)&&g(e)?s={...i,...e}:s=e:n==="deepMerge"&&(g(i)&&g(e)?s=v(i,e):s=e)}await this.redis.hset(this.roomMetadataKey(t),"data",JSON.stringify(s))}async getMetadata(t){let e=await this.redis.hget(this.roomMetadataKey(t),"data");return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.redis.keys("mesh:roommeta:*"),e=[];if(t.length===0)return e;let r=this.redis.pipeline();t.forEach(n=>r.hget(n,"data"));let s=await r.exec();return t.forEach((n,i)=>{let o=n.replace("mesh:roommeta:",""),a=s?.[i]?.[1];if(a)try{let d=JSON.parse(a);e.push({id:o,metadata:d})}catch(d){console.error(`Failed to parse metadata for room ${o}:`,d)}}),e}};var D=class{constructor(t){this.connectionManager=t.connectionManager,this.roomManager=t.roomManager,this.instanceId=t.instanceId,this.pubClient=t.pubClient,this.getPubSubChannel=t.getPubSubChannel,this.emitError=t.emitError}async broadcast(t,e,r){let s={command:t,payload:e};try{if(r){let n=r.map(({id:a})=>a),i=await this.connectionManager.getAllConnectionIds(),o=n.filter(a=>i.includes(a));await this.publishOrSend(o,s)}else{let n=await this.connectionManager.getAllConnectionIds();await this.publishOrSend(n,s)}}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoom(t,e,r){let s=await this.roomManager.getRoomConnectionIds(t);try{await this.publishOrSend(s,{command:e,payload:r})}catch(n){this.emitError(new Error(`Failed to broadcast command "${e}": ${n}`))}}async broadcastExclude(t,e,r){let s=new Set((Array.isArray(r)?r:[r]).map(({id:n})=>n));try{let n=(await this.connectionManager.getAllConnectionIds()).filter(i=>!s.has(i));await this.publishOrSend(n,{command:t,payload:e})}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoomExclude(t,e,r,s){let n=new Set((Array.isArray(s)?s:[s]).map(({id:i})=>i));try{let i=(await this.roomManager.getRoomConnectionIds(t)).filter(o=>!n.has(o));await this.publishOrSend(i,{command:e,payload:r})}catch(i){this.emitError(new Error(`Failed to broadcast command "${e}": ${i}`))}}async publishOrSend(t,e){if(t.length===0)return;let r=await this.connectionManager.getInstanceIdsForConnections(t),s={};for(let n of t){let i=r[n];i&&(s[i]||(s[i]=[]),s[i].push(n))}for(let[n,i]of Object.entries(s))if(i.length!==0)if(n===this.instanceId)i.forEach(o=>{let a=this.connectionManager.getLocalConnection(o);a&&!a.isDead&&a.send(e)});else{let a=JSON.stringify({targetConnectionIds:i,command:e});try{await this.pubClient.publish(this.getPubSubChannel(n),a)}catch(d){this.emitError(new Error(`Failed to publish command "${e.command}": ${d}`))}}}};var me=require("events"),y=class c extends me.EventEmitter{static getInstance(){return c.instance||(c.instance=new c),c.instance}constructor(){super(),this.setMaxListeners(100)}publishMessage(t,e,r){let s=Date.now();this.emit("message",{channel:t,message:e,instanceId:r,timestamp:s})}subscribeToMessages(t){this.on("message",t)}unsubscribeFromMessages(t){this.off("message",t)}};var U=class{constructor(t){this.exposedChannels=[];this.channelGuards=new Map;this.channelSubscriptions={};this.redis=t.redis,this.pubClient=t.pubClient,this.subClient=t.subClient,this.messageStream=y.getInstance()}setPersistenceManager(t){this.persistenceManager=t}exposeChannel(t,e){this.exposedChannels.push(t),e&&this.channelGuards.set(t,e)}async isChannelExposed(t,e){let r=this.exposedChannels.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.channelGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}async writeChannel(t,e,r=0,s){let n=parseInt(r,10);!isNaN(n)&&n>0&&(await this.pubClient.rpush(`mesh:history:${t}`,e),await this.pubClient.ltrim(`mesh:history:${t}`,-n,-1)),this.messageStream.publishMessage(t,e,s),await this.pubClient.publish(t,e)}addSubscription(t,e){this.channelSubscriptions[t]||(this.channelSubscriptions[t]=new Set),this.channelSubscriptions[t].add(e)}removeSubscription(t,e){return this.channelSubscriptions[t]?(this.channelSubscriptions[t].delete(e),this.channelSubscriptions[t].size===0&&delete this.channelSubscriptions[t],!0):!1}getSubscribers(t){return this.channelSubscriptions[t]}async subscribeToRedisChannel(t){return new Promise((e,r)=>{this.subClient.subscribe(t,s=>{s?r(s):e()})})}async unsubscribeFromRedisChannel(t){return new Promise((e,r)=>{this.subClient.unsubscribe(t,s=>{s?r(s):e()})})}async getChannelHistory(t,e,r){if(this.persistenceManager&&r!==void 0)try{return(await this.persistenceManager.getMessages(t,r,e)).map(i=>i.message)}catch{let i=`mesh:history:${t}`;return this.redis.lrange(i,0,e-1)}let s=`mesh:history:${t}`;return this.redis.lrange(s,0,e-1)}async getPersistedMessages(t,e,r){if(!this.persistenceManager)throw new Error("Persistence not enabled");return this.persistenceManager.getMessages(t,e,r)}cleanupConnection(t){for(let e in this.channelSubscriptions)this.removeSubscription(e,t)}};var T=class{constructor(t,e,r,s){this.server=t,this.command=e,this.connection=r,this.payload=s}};var z=class{constructor(){this.commands={};this.globalMiddlewares=[];this.middlewares={}}exposeCommand(t,e,r=[]){this.commands[t]=e,r.length>0&&this.useMiddlewareWithCommand(t,r)}useMiddleware(...t){this.globalMiddlewares.push(...t)}useMiddlewareWithCommand(t,e){e.length&&(this.middlewares[t]=this.middlewares[t]||[],this.middlewares[t]=e.concat(this.middlewares[t]))}async runCommand(t,e,r,s,n){let i=new T(n,e,s,r);try{if(!this.commands[e])throw new se(`Command "${e}" not found`,"ENOTFOUND","CommandError");if(this.globalMiddlewares.length)for(let a of this.globalMiddlewares)await a(i);if(this.middlewares[e])for(let a of this.middlewares[e])await a(i);let o=await this.commands[e](i);s.send({id:t,command:e,payload:o})}catch(o){let a=o instanceof Error?{error:o.message,code:o.code||"ESERVER",name:o.name||"Error"}:{error:String(o),code:"EUNKNOWN",name:"UnknownError"};s.send({id:t,command:e,payload:a})}}getCommands(){return this.commands}hasCommand(t){return!!this.commands[t]}};var B=class{constructor(t){this.collectionManager=null;this.collectionUpdateTimeouts=new Map;this.collectionMaxDelayTimeouts=new Map;this.pendingCollectionUpdates=new Map;this.COLLECTION_UPDATE_DEBOUNCE_MS=50;this.COLLECTION_MAX_DELAY_MS=200;this.subClient=t.subClient,this.pubClient=t.pubClient,this.instanceId=t.instanceId,this.connectionManager=t.connectionManager,this.recordManager=t.recordManager,this.recordSubscriptions=t.recordSubscriptions,this.getChannelSubscriptions=t.getChannelSubscriptions,this.emitError=t.emitError,this.collectionManager=t.collectionManager||null}subscribeToInstanceChannel(){let t=`${C}${this.instanceId}`;return this._subscriptionPromise=new Promise((e,r)=>{this.subClient.subscribe(t,M,"mesh:collection:record-change"),this.subClient.psubscribe("mesh:presence:updates:*",s=>{if(s){this.emitError(new Error(`Failed to subscribe to channels/patterns: ${JSON.stringify({cause:s})}`)),r(s);return}e()})}),this.setupMessageHandlers(),this._subscriptionPromise}setupMessageHandlers(){this.subClient.on("message",async(t,e)=>{if(t.startsWith(C))this.handleInstancePubSubMessage(t,e);else if(t===M)this.handleRecordUpdatePubSubMessage(e);else if(t==="mesh:collection:record-change")this.handleCollectionRecordChange(e);else{let r=this.getChannelSubscriptions(t);if(r)for(let s of r)s.isDead||s.send({command:"mesh/subscription-message",payload:{channel:t,message:e}})}}),this.subClient.on("pmessage",async(t,e,r)=>{if(t==="mesh:presence:updates:*"){let s=this.getChannelSubscriptions(e);if(s)try{let n=JSON.parse(r);s.forEach(i=>{i.isDead?s.delete(i):i.send({command:"mesh/presence-update",payload:n})})}catch{this.emitError(new Error(`Failed to parse presence update: ${r}`))}}})}handleInstancePubSubMessage(t,e){try{let r=JSON.parse(e);if(!r||!Array.isArray(r.targetConnectionIds)||!r.command||typeof r.command.command!="string")throw new Error("Invalid message format");let{targetConnectionIds:s,command:n}=r;s.forEach(i=>{let o=this.connectionManager.getLocalConnection(i);o&&!o.isDead&&o.send(n)})}catch{this.emitError(new Error(`Failed to parse message: ${e}`))}}handleRecordUpdatePubSubMessage(t){try{let e=JSON.parse(t),{recordId:r,newValue:s,patch:n,version:i,deleted:o}=e;if(!r||typeof i!="number")throw new Error("Invalid record update message format");let a=this.recordSubscriptions.get(r);if(!a)return;a.forEach((d,p)=>{let h=this.connectionManager.getLocalConnection(p);h&&!h.isDead?o?h.send({command:"mesh/record-deleted",payload:{recordId:r,version:i}}):d==="patch"&&n?h.send({command:"mesh/record-update",payload:{recordId:r,patch:n,version:i}}):d==="full"&&s!==void 0&&h.send({command:"mesh/record-update",payload:{recordId:r,full:s,version:i}}):(!h||h.isDead)&&(a.delete(p),a.size===0&&this.recordSubscriptions.delete(r))}),o&&this.recordSubscriptions.delete(r)}catch{this.emitError(new Error(`Failed to parse record update message: ${t}`))}}async handleCollectionRecordChange(t){if(!this.collectionManager)return;let e=this.collectionManager.getCollectionSubscriptions(),r=new Set;for(let[s]of e.entries())r.add(s);for(let s of r){let n=this.collectionUpdateTimeouts.get(s);n&&clearTimeout(n),this.pendingCollectionUpdates.has(s)||this.pendingCollectionUpdates.set(s,new Set),this.pendingCollectionUpdates.get(s).add(t);let i=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_UPDATE_DEBOUNCE_MS);if(this.collectionUpdateTimeouts.set(s,i),!this.collectionMaxDelayTimeouts.has(s)){let o=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_MAX_DELAY_MS);this.collectionMaxDelayTimeouts.set(s,o)}}}async processCollectionUpdates(t){let e=this.pendingCollectionUpdates.get(t);if(!e||e.size===0)return;let r=this.collectionUpdateTimeouts.get(t),s=this.collectionMaxDelayTimeouts.get(t);if(r&&(clearTimeout(r),this.collectionUpdateTimeouts.delete(t)),s&&(clearTimeout(s),this.collectionMaxDelayTimeouts.delete(t)),this.pendingCollectionUpdates.delete(t),!this.collectionManager)return;let n=this.collectionManager.getCollectionSubscriptions().get(t);if(!(!n||n.size===0))for(let[i,{version:o}]of n.entries())try{let a=this.connectionManager.getLocalConnection(i);if(!a||a.isDead)continue;let d=await this.collectionManager.resolveCollection(t,a),p=d.map(m=>m.id),h=`mesh:collection:${t}:${i}`,u=await this.pubClient.get(h),b=u?JSON.parse(u):[],x=p.filter(m=>!b.includes(m)),j=d.filter(m=>x.includes(m.id)),Me=b.filter(m=>!p.includes(m)),L=[];for(let m of Me)try{let A=await this.recordManager.getRecord(m);L.push(A||{id:m})}catch{L.push({id:m})}let Z=[];for(let m of e)b.includes(m)&&!p.includes(m)&&Z.push(m);let Pe=j.length>0||L.length>0,we=Z.length>0;if(Pe||we){let m=o+1;this.collectionManager.updateSubscriptionVersion(t,i,m),await this.pubClient.set(h,JSON.stringify(p)),a.send({command:"mesh/collection-diff",payload:{collectionId:t,added:j,removed:L,version:m}})}for(let m of e)if(p.includes(m))try{let{record:A,version:Re}=await this.recordManager.getRecordAndVersion(m);A&&a.send({command:"mesh/record-update",payload:{recordId:m,version:Re,full:A}})}catch{l.info(`Record ${m} not found during collection update (likely deleted).`)}}catch(a){this.emitError(new Error(`Error processing collection ${t} for connection ${i}: ${a}`))}}getSubscriptionPromise(){return this._subscriptionPromise}getPubSubChannel(t){return`${C}${t}`}async cleanup(){for(let t of this.collectionUpdateTimeouts.values())clearTimeout(t);this.collectionUpdateTimeouts.clear();for(let t of this.collectionMaxDelayTimeouts.values())clearTimeout(t);if(this.collectionMaxDelayTimeouts.clear(),this.pendingCollectionUpdates.clear(),this.subClient&&this.subClient.status!=="end"){let t=`${C}${this.instanceId}`;await Promise.all([new Promise(e=>{this.subClient.unsubscribe(t,M,"mesh:collection:record-change",()=>e())}),new Promise(e=>{this.subClient.punsubscribe("mesh:presence:updates:*",()=>e())})])}}};var J=class{constructor(t){this.persistenceManager=null;this.exposedRecords=[];this.exposedWritableRecords=[];this.recordGuards=new Map;this.writableRecordGuards=new Map;this.recordSubscriptions=new Map;this.pubClient=t.pubClient,this.recordManager=t.recordManager,this.emitError=t.emitError,this.persistenceManager=t.persistenceManager||null}setPersistenceManager(t){this.persistenceManager=t}exposeRecord(t,e){this.exposedRecords.push(t),e&&this.recordGuards.set(t,e)}exposeWritableRecord(t,e){this.exposedWritableRecords.push(t),e&&this.writableRecordGuards.set(t,e)}async isRecordExposed(t,e){let r=this.exposedRecords.find(i=>typeof i=="string"?i===t:i.test(t)),s=!1;if(r){let i=this.recordGuards.get(r);if(i)try{s=await Promise.resolve(i(e,t))}catch{s=!1}else s=!0}return!!(s||this.exposedWritableRecords.find(i=>typeof i=="string"?i===t:i.test(t)))}async isRecordWritable(t,e){let r=this.exposedWritableRecords.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.writableRecordGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}addSubscription(t,e,r){this.recordSubscriptions.has(t)||this.recordSubscriptions.set(t,new Map),this.recordSubscriptions.get(t).set(e,r)}removeSubscription(t,e){let r=this.recordSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.recordSubscriptions.delete(t),!0):!1}getSubscribers(t){return this.recordSubscriptions.get(t)}async writeRecord(t,e,r){let s=await this.recordManager.publishUpdate(t,e,r?.strategy||"replace");if(!s)return;let{patch:n,version:i,finalValue:o}=s;this.persistenceManager&&this.persistenceManager.handleRecordUpdate(t,o,i);let a={recordId:t,newValue:o,patch:n,version:i};try{await this.pubClient.publish(M,JSON.stringify(a))}catch(d){this.emitError(new Error(`Failed to publish record update for "${t}": ${d}`))}}cleanupConnection(t){let e=t.id;this.recordSubscriptions.forEach((r,s)=>{r.has(e)&&(r.delete(e),r.size===0&&this.recordSubscriptions.delete(s))})}async publishRecordDeletion(t,e){let r={recordId:t,deleted:!0,version:e};try{await this.pubClient.publish(M,JSON.stringify(r))}catch(s){this.emitError(new Error(`Failed to publish record deletion for "${t}": ${s}`))}}getRecordSubscriptions(){return this.recordSubscriptions}};var ge=require("ioredis"),q=class{constructor(){this._redis=null;this._pubClient=null;this._subClient=null;this._isShuttingDown=!1}initialize(t,e){this._redis=new ge.Redis({retryStrategy:r=>this._isShuttingDown||r>10?null:Math.min(1e3*Math.pow(2,r),3e4),...t}),this._redis.on("error",r=>{e(new Error(`Redis error: ${r}`))}),this._pubClient=this._redis.duplicate(),this._subClient=this._redis.duplicate()}get redis(){if(!this._redis)throw new Error("Redis not initialized");return this._redis}get pubClient(){if(!this._pubClient)throw new Error("Redis pub client not initialized");return this._pubClient}get subClient(){if(!this._subClient)throw new Error("Redis sub client not initialized");return this._subClient}disconnect(){this._isShuttingDown=!0,this._pubClient&&(this._pubClient.disconnect(),this._pubClient=null),this._subClient&&(this._subClient.disconnect(),this._subClient=null),this._redis&&(this._redis.disconnect(),this._redis=null)}get isShuttingDown(){return this._isShuttingDown}set isShuttingDown(t){this._isShuttingDown=t}async enableKeyspaceNotifications(){let t=await this.redis.config("GET","notify-keyspace-events"),r=(Array.isArray(t)&&t.length>1?t[1]:"")||"";r.includes("E")||(r+="E"),r.includes("x")||(r+="x"),await this.redis.config("SET","notify-keyspace-events",r)}};var X=class{constructor(t){this.heartbeatInterval=null;this.heartbeatTTL=120;this.heartbeatFrequency=15e3;this.cleanupInterval=null;this.cleanupFrequency=6e4;this.cleanupLockTTL=10;this.redis=t.redis,this.instanceId=t.instanceId}async start(){await this.registerInstance(),await this.updateHeartbeat(),this.heartbeatInterval=setInterval(()=>this.updateHeartbeat(),this.heartbeatFrequency),this.cleanupInterval=setInterval(()=>this.performCleanup(),this.cleanupFrequency)}async stop(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),await this.deregisterInstance()}async registerInstance(){await this.redis.sadd("mesh:instances",this.instanceId)}async deregisterInstance(){await this.redis.srem("mesh:instances",this.instanceId),await this.redis.del(this.getHeartbeatKey())}async updateHeartbeat(){let t=this.getHeartbeatKey();await this.redis.set(t,Date.now().toString(),"EX",this.heartbeatTTL)}getHeartbeatKey(){return`mesh:instance:${this.instanceId}:heartbeat`}async acquireCleanupLock(){return await this.redis.set("mesh:cleanup:lock",this.instanceId,"EX",this.cleanupLockTTL,"NX")==="OK"}async releaseCleanupLock(){await this.redis.eval(`
|
|
2
|
-
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
3
|
-
return redis.call("del", KEYS[1])
|
|
4
|
-
else
|
|
5
|
-
return 0
|
|
6
|
-
end
|
|
7
|
-
`,1,"mesh:cleanup:lock",this.instanceId)}async performCleanup(){try{if(!await this.acquireCleanupLock())return;let e=await this.redis.smembers("mesh:instances"),r=await this.redis.hgetall("mesh:connections"),s=new Set([...e,...Object.values(r)]);for(let n of s){if(n===this.instanceId)continue;let i=`mesh:instance:${n}:heartbeat`;await this.redis.get(i)||(console.log(`Found dead instance: ${n}`),await this.cleanupDeadInstance(n))}}catch(t){console.error("Error during cleanup:",t)}finally{await this.releaseCleanupLock()}}async cleanupDeadInstance(t){try{let e=`mesh:connections:${t}`,r=await this.redis.smembers(e);for(let n of r)await this.cleanupConnection(n);let s=await this.redis.hgetall("mesh:connections");for(let[n,i]of Object.entries(s))i===t&&await this.cleanupConnection(n);await this.redis.srem("mesh:instances",t),await this.redis.del(e),console.log(`Cleaned up dead instance: ${t}`)}catch(e){console.error(`Error cleaning up instance ${t}:`,e)}}async deleteMatchingKeys(t){let e=this.redis.scanStream({match:t}),r=this.redis.pipeline();return e.on("data",s=>{for(let n of s)r.del(n)}),new Promise((s,n)=>{e.on("end",async()=>{await r.exec(),s()}),e.on("error",n)})}async cleanupConnection(t){try{let e=`mesh:connection:${t}:rooms`,r=await this.redis.smembers(e),s=this.redis.pipeline();for(let n of r)s.srem(`mesh:room:${n}`,t),s.srem(`mesh:presence:room:${n}`,t),s.del(`mesh:presence:room:${n}:conn:${t}`),s.del(`mesh:presence:state:${n}:conn:${t}`);s.del(e),s.hdel("mesh:connections",t),await this.deleteMatchingKeys(`mesh:collection:*:${t}`),await s.exec(),console.log(`Cleaned up stale connection: ${t}`)}catch(e){console.error(`Error cleaning up connection ${t}:`,e)}}};var G=class{constructor(t){this.exposedCollections=[];this.collectionSubscriptions=new Map;this.redis=t.redis,this.emitError=t.emitError}exposeCollection(t,e){this.exposedCollections.push({pattern:t,resolver:e})}async isCollectionExposed(t,e){return!!this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t))}async resolveCollection(t,e){let r=this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t));if(!r)throw new Error(`Collection "${t}" is not exposed`);try{return await Promise.resolve(r.resolver(e,t))}catch(s){throw this.emitError(new Error(`Failed to resolve collection "${t}": ${s}`)),s}}async addSubscription(t,e,r){this.collectionSubscriptions.has(t)||this.collectionSubscriptions.set(t,new Map);let s=await this.resolveCollection(t,r),n=s.map(o=>o.id),i=1;return this.collectionSubscriptions.get(t).set(e,{version:i}),await this.redis.set(`mesh:collection:${t}:${e}`,JSON.stringify(n)),{ids:n,records:s,version:i}}async removeSubscription(t,e){let r=this.collectionSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.collectionSubscriptions.delete(t),await this.redis.del(`mesh:collection:${t}:${e}`),!0):!1}async publishRecordChange(t){try{await this.redis.publish("mesh:collection:record-change",t)}catch(e){this.emitError(new Error(`Failed to publish record change for ${t}: ${e}`))}}async cleanupConnection(t){let e=t.id,r=[];this.collectionSubscriptions.forEach((s,n)=>{s.has(e)&&(s.delete(e),s.size===0&&this.collectionSubscriptions.delete(n),r.push(this.redis.del(`mesh:collection:${n}:${e}`).then(()=>{}).catch(i=>{this.emitError(new Error(`Failed to clean up collection subscription for "${n}": ${i}`))})))}),await Promise.all(r)}async listRecordsMatching(t,e){try{let r="mesh:record:",s=[],n="0";do{let d=await this.redis.scan(n,"MATCH",`${r}${t}`,"COUNT",e?.scanCount??100);n=d[0],s.push(...d[1])}while(n!=="0");if(s.length===0)return[];let i=await this.redis.mget(s),o=s.map(d=>d.substring(r.length)),a=i.map((d,p)=>{if(d===null)return null;try{let h=JSON.parse(d),u=o[p];return h.id===u?h:{...h,id:u}}catch(h){return this.emitError(new Error(`Failed to parse record for processing: ${d} - ${h.message}`)),null}}).filter(d=>d!==null);if(e?.map&&(a=a.map(e.map)),e?.sort&&a.sort(e.sort),e?.slice){let{start:d,count:p}=e.slice;a=a.slice(d,d+p)}return a}catch(r){return this.emitError(new Error(`Failed to list records matching "${t}": ${r.message}`)),[]}}getCollectionSubscriptions(){return this.collectionSubscriptions}updateSubscriptionVersion(t,e,r){let s=this.collectionSubscriptions.get(t);s?.has(e)&&s.set(e,{version:r})}};var ye=require("events"),be=require("uuid");var ue=te(require("sqlite3"));function V(c){return c.replace(/\^/g,"").replace(/\$/g,"").replace(/\./g,"_").replace(/\*/g,"%").replace(/\+/g,"%").replace(/\?/g,"_").replace(/\\\\/g,"\\").replace(/\\\./g,".").replace(/\\\*/g,"*").replace(/\\\+/g,"+").replace(/\\\?/g,"?")}var{Database:Fe}=ue.default,O=class{constructor(t={}){this.db=null;this.initialized=!1;this.options={filename:":memory:",...t}}async initialize(){if(!this.initialized)return new Promise((t,e)=>{try{this.db=new Fe(this.options.filename,r=>{if(r){l.error("Failed to open SQLite database:",r),e(r);return}this.createTables().then(()=>{this.initialized=!0,t()}).catch(e)})}catch(r){l.error("Error initializing SQLite database:",r),e(r)}})}async createTables(){if(!this.db)throw new Error("Database not initialized");return new Promise((t,e)=>{this.db.run(`CREATE TABLE IF NOT EXISTS channel_messages (
|
|
8
|
-
id TEXT PRIMARY KEY,
|
|
9
|
-
channel TEXT NOT NULL,
|
|
10
|
-
message TEXT NOT NULL,
|
|
11
|
-
instance_id TEXT NOT NULL,
|
|
12
|
-
timestamp INTEGER NOT NULL,
|
|
13
|
-
metadata TEXT
|
|
14
|
-
)`,r=>{if(r){e(r);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_channel_timestamp ON channel_messages (channel, timestamp)",s=>{if(s){e(s);return}this.db.run(`CREATE TABLE IF NOT EXISTS records (
|
|
15
|
-
record_id TEXT PRIMARY KEY,
|
|
16
|
-
version INTEGER NOT NULL,
|
|
17
|
-
value TEXT NOT NULL,
|
|
18
|
-
timestamp INTEGER NOT NULL
|
|
19
|
-
)`,n=>{if(n){e(n);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_records_timestamp ON records (timestamp)",i=>{if(i){e(i);return}t()})})})})})}async storeMessages(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT INTO channel_messages
|
|
20
|
-
(id, channel, message, instance_id, timestamp, metadata)
|
|
21
|
-
VALUES (?, ?, ?, ?, ?, ?)`);try{for(let i of t){let o=i.metadata?JSON.stringify(i.metadata):null;n.run(i.id,i.channel,i.message,i.instanceId,i.timestamp,o)}n.finalize(),s.run("COMMIT",i=>{i?r(i):e()})}catch(i){s.run("ROLLBACK"),r(i)}})})}async getMessages(t,e,r=50){if(!this.db)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = ?",n=[t];if(e!==void 0)if(typeof e=="number")s+=" AND timestamp > ?",n.push(e);else{let i=await new Promise((o,a)=>{this.db.get("SELECT timestamp FROM channel_messages WHERE id = ?",[e],(d,p)=>{if(d){a(d);return}o(p?p.timestamp:0)})});s+=" AND timestamp > ?",n.push(i)}return s+=" ORDER BY timestamp ASC LIMIT ?",n.push(r),new Promise((i,o)=>{this.db.all(s,n,(a,d)=>{if(a){o(a);return}let p=d.map(h=>({id:h.id,channel:h.channel,message:h.message,instanceId:h.instance_id,timestamp:h.timestamp,metadata:h.metadata?JSON.parse(h.metadata):void 0}));i(p)})})}async close(){if(this.db)return new Promise((t,e)=>{this.db.close(r=>{if(r){e(r);return}this.db=null,this.initialized=!1,t()})})}async storeRecords(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return l.debug(`SQLite: Storing ${t.length} records`),new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT OR REPLACE INTO records
|
|
22
|
-
(record_id, version, value, timestamp)
|
|
23
|
-
VALUES (?, ?, ?, ?)`);try{for(let i of t)n.run(i.recordId,i.version,i.value,i.timestamp,o=>{o&&l.error(`Error storing record ${i.recordId}:`,o)});n.finalize(),s.run("COMMIT",i=>{i?(l.error("Error committing transaction:",i),s.run("ROLLBACK"),r(i)):e()})}catch(i){l.error("Error in storeRecords:",i),s.run("ROLLBACK"),r(i)}})})}async getRecords(t){if(!this.db)throw new Error("Database not initialized");let e=V(t);return l.debug(`SQLite: Getting records matching pattern: ${t} (SQL: ${e})`),new Promise((r,s)=>{this.db.all(`SELECT record_id, version, value, timestamp
|
|
24
|
-
FROM records
|
|
25
|
-
WHERE record_id LIKE ?
|
|
26
|
-
ORDER BY timestamp DESC`,[e],(n,i)=>{if(n){l.error("Error getting records:",n),s(n);return}l.debug(`SQLite: Found ${i.length} records matching pattern ${t}`);let o=i.map(a=>({recordId:a.record_id,version:a.version,value:a.value,timestamp:a.timestamp}));r(o)})})}};var fe=require("pg");var N=class{constructor(t={}){this.pool=null;this.initialized=!1;this.options={host:"localhost",port:5432,database:"mesh_test",user:"mesh",password:"mesh_password",max:10,...t}}async initialize(){if(!this.initialized)try{this.pool=new fe.Pool(this.options.connectionString?{connectionString:this.options.connectionString,max:this.options.max}:{host:this.options.host,port:this.options.port,database:this.options.database,user:this.options.user,password:this.options.password,ssl:this.options.ssl,max:this.options.max}),await this.createTables(),this.initialized=!0}catch(t){throw l.error("Error initializing PostgreSQL database:",t),t}}async createTables(){if(!this.pool)throw new Error("Database not initialized");let t=await this.pool.connect();try{await t.query(`
|
|
1
|
+
"use strict";var Le=Object.create;var L=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Fe=Object.getOwnPropertyNames;var ke=Object.getPrototypeOf,Ke=Object.prototype.hasOwnProperty;var _=(a,t)=>()=>(a&&(t=a(a=0)),t);var H=(a,t)=>{for(var e in t)L(a,e,{get:t[e],enumerable:!0})},ie=(a,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Fe(t))!Ke.call(a,s)&&s!==e&&L(a,s,{get:()=>t[s],enumerable:!(r=_e(t,s))||r.enumerable});return a};var oe=(a,t,e)=>(e=a!=null?Le(ke(a)):{},ie(t||!a||!a.__esModule?L(e,"default",{value:a,enumerable:!0}):e,a)),Y=a=>ie(L({},"__esModule",{value:!0}),a);function C(a,t){if(!g(a)||!g(t))return t;let e={...a};for(let r in t)t.hasOwnProperty(r)&&(g(t[r])&&g(a[r])?e[r]=C(a[r],t[r]):e[r]=t[r]);return e}function g(a){return a!==null&&typeof a=="object"&&!Array.isArray(a)}function F(a){try{return JSON.parse(a)}catch{return{command:"",payload:{}}}}function le(a){return JSON.stringify(a)}var ce,Q,ae,de,Ge,l,y,f=_(()=>{"use strict";ce=class extends Error{constructor(a,t,e){super(a),typeof t=="string"&&(this.code=t),this.name=typeof e=="string"?e:"CodeError"}},Q=(a=>(a[a.NONE=0]="NONE",a[a.ERROR=1]="ERROR",a[a.WARN=2]="WARN",a[a.INFO=3]="INFO",a[a.DEBUG=4]="DEBUG",a))(Q||{}),ae=typeof window<"u"&&typeof window.document<"u",de=class{constructor(a){this.config={level:a?.level??3,prefix:a?.prefix??"[mesh]",styling:a?.styling??ae}}configure(a){this.config={...this.config,...a}}info(...a){this.config.level>=3&&this.log("log",...a)}warn(...a){this.config.level>=2&&this.log("warn",...a)}error(...a){this.config.level>=1&&this.log("error",...a)}debug(...a){this.config.level>=4&&this.log("debug",...a)}log(a,...t){if(this.config.styling&&ae){let e={prefix:"background: #000; color: #FFA07A; padding: 2px 4px; border-radius: 2px;",reset:""};console[a](`%c${this.config.prefix}%c`,e.prefix,e.reset,...t)}else console[a](this.config.prefix,...t)}},Ge=new de({level:1,styling:!0}),l=new de({level:1,styling:!1});y=(a=>(a[a.ONLINE=3]="ONLINE",a[a.CONNECTING=2]="CONNECTING",a[a.RECONNECTING=1]="RECONNECTING",a[a.OFFLINE=0]="OFFLINE",a))(y||{})});function V(a){return a.replace(/\^/g,"").replace(/\$/g,"").replace(/\./g,"_").replace(/\*/g,"%").replace(/\+/g,"%").replace(/\?/g,"_").replace(/\\\\/g,"\\").replace(/\\\./g,".").replace(/\\\*/g,"*").replace(/\\\+/g,"+").replace(/\\\?/g,"?")}var Z=_(()=>{"use strict"});var Me={};H(Me,{PostgreSQLPersistenceAdapter:()=>ee});var Ce,ee,Pe=_(()=>{"use strict";Ce=require("pg");Z();f();ee=class{constructor(t={}){this.pool=null;this.initialized=!1;this.options={host:"localhost",port:5432,database:"mesh_test",user:"mesh",password:"mesh_password",max:10,...t}}async initialize(){if(!this.initialized)try{this.pool=new Ce.Pool(this.options.connectionString?{connectionString:this.options.connectionString,max:this.options.max}:{host:this.options.host,port:this.options.port,database:this.options.database,user:this.options.user,password:this.options.password,ssl:this.options.ssl,max:this.options.max}),await this.createTables(),this.initialized=!0}catch(t){throw l.error("Error initializing PostgreSQL database:",t),t}}async createTables(){if(!this.pool)throw new Error("Database not initialized");let t=await this.pool.connect();try{await t.query(`
|
|
27
2
|
CREATE TABLE IF NOT EXISTS channel_messages (
|
|
28
3
|
id TEXT PRIMARY KEY,
|
|
29
4
|
channel TEXT NOT NULL,
|
|
@@ -47,10 +22,35 @@
|
|
|
47
22
|
ON records (timestamp)
|
|
48
23
|
`)}finally{t.release()}}async storeMessages(t){if(!this.pool)throw new Error("Database not initialized");if(t.length===0)return;let e=await this.pool.connect();try{await e.query("BEGIN");for(let r of t)await e.query(`INSERT INTO channel_messages
|
|
49
24
|
(id, channel, message, instance_id, timestamp, metadata)
|
|
50
|
-
VALUES ($1, $2, $3, $4, $5, $6)`,[r.id,r.channel,r.message,r.instanceId,r.timestamp,r.metadata||null]);await e.query("COMMIT")}catch(r){throw await e.query("ROLLBACK"),r}finally{e.release()}}async getMessages(t,e,r=50){if(!this.pool)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = $1",n=[t],i=2;if(e!==void 0)if(typeof e=="number")s+=` AND timestamp > $${i}`,n.push(e),i++;else{let d=(await this.pool.query("SELECT timestamp FROM channel_messages WHERE id = $1",[e])).rows[0]?.timestamp||0;s+=` AND timestamp > $${i}`,n.push(d),i++}return s+=` ORDER BY timestamp ASC LIMIT $${i}`,n.push(r),(await this.pool.query(s,n)).rows.map(
|
|
25
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,[r.id,r.channel,r.message,r.instanceId,r.timestamp,r.metadata||null]);await e.query("COMMIT")}catch(r){throw await e.query("ROLLBACK"),r}finally{e.release()}}async getMessages(t,e,r=50){if(!this.pool)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = $1",n=[t],i=2;if(e!==void 0)if(typeof e=="number")s+=` AND timestamp > $${i}`,n.push(e),i++;else{let d=(await this.pool.query("SELECT timestamp FROM channel_messages WHERE id = $1",[e])).rows[0]?.timestamp||0;s+=` AND timestamp > $${i}`,n.push(d),i++}return s+=` ORDER BY timestamp ASC LIMIT $${i}`,n.push(r),(await this.pool.query(s,n)).rows.map(c=>({id:c.id,channel:c.channel,message:c.message,instanceId:c.instance_id,timestamp:parseInt(c.timestamp),metadata:c.metadata}))}async storeRecords(t){if(!this.pool)throw new Error("Database not initialized");if(t.length===0)return;l.debug(`PostgreSQL: Storing ${t.length} records`);let e=await this.pool.connect();try{await e.query("BEGIN");for(let r of t)await e.query(`INSERT INTO records (record_id, version, value, timestamp)
|
|
51
26
|
VALUES ($1, $2, $3, $4)
|
|
52
27
|
ON CONFLICT (record_id)
|
|
53
28
|
DO UPDATE SET version = $2, value = $3, timestamp = $4`,[r.recordId,r.version,r.value,r.timestamp]);await e.query("COMMIT")}catch(r){throw await e.query("ROLLBACK"),l.error("Error in storeRecords:",r),r}finally{e.release()}}async getRecords(t){if(!this.pool)throw new Error("Database not initialized");let e=V(t);l.debug(`PostgreSQL: Getting records matching pattern: ${t} (SQL: ${e})`);let r=await this.pool.query(`SELECT record_id, version, value, timestamp
|
|
54
29
|
FROM records
|
|
55
30
|
WHERE record_id LIKE $1
|
|
56
|
-
ORDER BY timestamp DESC`,[e]);return l.debug(`PostgreSQL: Found ${r.rows.length} records matching pattern ${t}`),r.rows.map(s=>({recordId:s.record_id,version:s.version,value:s.value,timestamp:parseInt(s.timestamp)}))}async close(){this.pool&&(await this.pool.end(),this.pool=null,this.initialized=!1)}};var $=class extends ye.EventEmitter{constructor(e){super();this.channelPatterns=[];this.recordPatterns=[];this.messageBuffer=new Map;this.recordBuffer=new Map;this.flushTimers=new Map;this.recordFlushTimer=null;this.isShuttingDown=!1;this.initialized=!1;this.recordManager=null;this.pendingRecordUpdates=[];let{defaultAdapterOptions:r={},adapterType:s="sqlite"}=e;s==="postgres"?this.defaultAdapter=new N(r):this.defaultAdapter=new O(r),this.messageStream=y.getInstance()}setRecordManager(e){this.recordManager=e}async ready(){return this.initialized?Promise.resolve():new Promise(e=>{this.once("initialized",e)})}async processPendingRecordUpdates(){if(this.pendingRecordUpdates.length===0)return;l.info(`Processing ${this.pendingRecordUpdates.length} pending record updates`);let e=[...this.pendingRecordUpdates];this.pendingRecordUpdates=[];for(let{recordId:r,value:s,version:n}of e)this.handleRecordUpdate(r,s,n)}async initialize(){if(!this.initialized)try{await this.defaultAdapter.initialize(),this.messageStream.subscribeToMessages(this.handleStreamMessage.bind(this)),this.initialized=!0,await this.processPendingRecordUpdates(),this.emit("initialized")}catch(e){throw l.error("Failed to initialize persistence manager:",e),e}}async restorePersistedRecords(){if(!this.recordManager){l.warn("Cannot restore persisted records: record manager not available");return}let e=this.recordManager.getRedis();if(!e){l.warn("Cannot restore records: Redis not available");return}try{if(l.info("Restoring persisted records..."),this.recordPatterns.length===0){l.info("No record patterns to restore");return}l.info(`Found ${this.recordPatterns.length} record patterns to restore`);for(let r of this.recordPatterns){l.info(`Config keys: ${Object.keys(r).join(", ")}`),l.info(`Config.hooks: ${typeof r.hooks}, Config.adapter: ${typeof r.adapter}`),l.info(`Config.pattern: ${r.pattern}`);let{adapter:s,hooks:n}=r,i=n?"(custom hooks)":s?.restorePattern;try{let o=[];if(n?o=await n.restore():s&&(o=(s.adapter.getRecords?await s.adapter.getRecords(s.restorePattern):[]).map(d=>({recordId:d.recordId,value:typeof d.value=="string"?JSON.parse(d.value):d.value,version:d.version}))),o.length>0){l.info(`Restoring ${o.length} records for pattern ${i}`);for(let a of o)try{let{recordId:d,value:p,version:h}=a,u=this.recordManager.recordKey(d),b=this.recordManager.recordVersionKey(d),x=e.pipeline();x.set(u,JSON.stringify(p)),x.set(b,h.toString()),await x.exec(),l.debug(`Restored record ${d} (version ${h})`)}catch(d){l.error(`Failed to restore record ${a.recordId}: ${d}`)}}else l.debug(`No records found for pattern ${i}`)}catch(o){l.error(`Error restoring records for pattern ${i}: ${o}`)}}l.info("Finished restoring persisted records")}catch(r){l.error("Failed to restore persisted records:",r)}}handleStreamMessage(e){let{channel:r,message:s,instanceId:n,timestamp:i}=e;this.handleChannelMessage(r,s,n,i)}enableChannelPersistence(e,r={}){let s={historyLimit:r.historyLimit??50,filter:r.filter??(()=>!0),adapter:r.adapter??this.defaultAdapter,flushInterval:r.flushInterval??500,maxBufferSize:r.maxBufferSize??100};s.adapter!==this.defaultAdapter&&!this.isShuttingDown&&s.adapter.initialize().catch(n=>{l.error(`Failed to initialize adapter for pattern ${e}:`,n)}),this.channelPatterns.push({pattern:e,options:s})}enableRecordPersistence(e){l.info(`enableRecordPersistence called with config keys: ${Object.keys(e).join(", ")}`),l.info(`config.hooks type: ${typeof e.hooks}, config.adapter type: ${typeof e.adapter}`);let{pattern:r,adapter:s,hooks:n,flushInterval:i,maxBufferSize:o}=e;if(s&&n)throw new Error("Cannot use both adapter and hooks. Choose one.");let a;if(s){let d=s.adapter??this.defaultAdapter;a={adapter:d,restorePattern:s.restorePattern},d!==this.defaultAdapter&&!this.isShuttingDown&&d.initialize().catch(p=>{l.error(`Failed to initialize adapter for record pattern ${r}:`,p)})}this.recordPatterns.push({pattern:r,adapter:a,hooks:n,flushInterval:i??500,maxBufferSize:o??100})}getChannelPersistenceOptions(e){for(let{pattern:r,options:s}of this.channelPatterns)if(typeof r=="string"&&r===e||r instanceof RegExp&&r.test(e))return s}getRecordPersistenceConfig(e){for(let r of this.recordPatterns){let{pattern:s}=r;if(typeof s=="string"&&s===e||s instanceof RegExp&&s.test(e))return r}}handleChannelMessage(e,r,s,n){if(!this.initialized||this.isShuttingDown)return;let i=this.getChannelPersistenceOptions(e);if(!i||!i.filter(r,e))return;let o={id:(0,be.v4)(),channel:e,message:r,instanceId:s,timestamp:n||Date.now()};if(this.messageBuffer.has(e)||this.messageBuffer.set(e,[]),this.messageBuffer.get(e).push(o),this.messageBuffer.get(e).length>=i.maxBufferSize){this.flushChannel(e);return}if(!this.flushTimers.has(e)){let a=setTimeout(()=>{this.flushChannel(e)},i.flushInterval);a.unref&&a.unref(),this.flushTimers.set(e,a)}}async flushChannel(e){if(!this.messageBuffer.has(e))return;this.flushTimers.has(e)&&(clearTimeout(this.flushTimers.get(e)),this.flushTimers.delete(e));let r=this.messageBuffer.get(e);if(r.length===0)return;this.messageBuffer.set(e,[]);let s=this.getChannelPersistenceOptions(e);if(s)try{await s.adapter.storeMessages(r),this.emit("flushed",{channel:e,count:r.length}),l.debug(`Flushed ${r.length} messages for channel ${e}`)}catch(n){if(l.error(`Failed to flush messages for channel ${e}:`,n),!this.isShuttingDown){let i=this.messageBuffer.get(e)||[];if(this.messageBuffer.set(e,[...r,...i]),!this.flushTimers.has(e)){let o=setTimeout(()=>{this.flushChannel(e)},1e3);o.unref&&o.unref(),this.flushTimers.set(e,o)}}}}async flushAll(){let e=Array.from(this.messageBuffer.keys());for(let r of e)await this.flushChannel(r)}async getMessages(e,r,s){if(!this.initialized)throw new Error("Persistence manager not initialized");let n=this.getChannelPersistenceOptions(e);if(!n)throw new Error(`Channel ${e} does not have persistence enabled`);return await this.flushChannel(e),n.adapter.getMessages(e,r,s||n.historyLimit)}handleRecordUpdate(e,r,s){if(this.isShuttingDown)return;if(!this.initialized){this.pendingRecordUpdates.push({recordId:e,value:r,version:s}),l.debug(`Buffered record update for ${e} (pending initialization)`);return}let n=this.getRecordPersistenceConfig(e);if(!n)return;let i={recordId:e,value:JSON.stringify(r),version:s,timestamp:Date.now()};if(this.recordBuffer.set(e,i),l.debug(`Added record ${e} to buffer, buffer size: ${this.recordBuffer.size}`),this.recordBuffer.size>=n.maxBufferSize){l.debug(`Buffer size ${this.recordBuffer.size} exceeds limit ${n.maxBufferSize}, flushing records`),this.flushRecords();return}this.recordFlushTimer||(l.debug(`Scheduling record flush in ${n.flushInterval}ms`),this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},n.flushInterval),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}async flushRecords(){if(this.recordBuffer.size===0)return;l.debug(`Flushing ${this.recordBuffer.size} records to storage`),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null);let e=Array.from(this.recordBuffer.values());this.recordBuffer.clear();let r=new Map,s=new Map;for(let i of e){let o=this.getRecordPersistenceConfig(i.recordId);o&&(o.hooks?(s.has(o.hooks.persist)||s.set(o.hooks.persist,[]),s.get(o.hooks.persist).push(i)):o.adapter&&(r.has(o.adapter.adapter)||r.set(o.adapter.adapter,[]),r.get(o.adapter.adapter).push(i)))}let n=(i,o)=>{if(l.error("Failed to flush records:",o),!this.isShuttingDown){for(let a of i)this.recordBuffer.set(a.recordId,a);this.recordFlushTimer||(this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},1e3),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}};for(let[i,o]of s.entries())try{let a=o.map(d=>({recordId:d.recordId,value:JSON.parse(d.value),version:d.version}));l.debug(`Storing ${o.length} records with custom persist hook`),await i(a),this.emit("recordsFlushed",{count:o.length})}catch(a){n(o,a)}for(let[i,o]of r.entries())try{i.storeRecords?(l.debug(`Storing ${o.length} records with adapter`),await i.storeRecords(o),this.emit("recordsFlushed",{count:o.length})):l.warn("Adapter does not support storing records")}catch(a){n(o,a)}}async getPersistedRecords(e){if(!this.initialized)throw new Error("Persistence manager not initialized");await this.flushRecords();try{let r=this.defaultAdapter;if(r.getRecords)return await r.getRecords(e)}catch(r){l.error(`Failed to get persisted records for pattern ${e}:`,r)}return[]}async shutdown(){if(this.isShuttingDown)return;this.isShuttingDown=!0,this.messageStream.unsubscribeFromMessages(this.handleStreamMessage.bind(this));for(let r of this.flushTimers.values())clearTimeout(r);this.flushTimers.clear(),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null),await this.flushAll(),await this.flushRecords();let e=new Set([this.defaultAdapter]);for(let{options:r}of this.channelPatterns)e.add(r.adapter);for(let r of this.recordPatterns)r.adapter&&e.add(r.adapter.adapter);for(let r of e)try{await r.close()}catch(s){l.error("Error closing persistence adapter:",s)}this.initialized=!1}};var Q=new WeakMap,W=class extends Ce.WebSocketServer{constructor(e){let r={...e};e.authenticateConnection&&(r.verifyClient=(s,n)=>{Promise.resolve().then(()=>e.authenticateConnection(s.req)).then(i=>{i!=null?(Q.set(s.req,i),n(!0)):n(!1,401,"Unauthorized")}).catch(i=>{let o=i?.code??401,a=i?.message??"Unauthorized";n(!1,o,a)})});super(r);this.persistenceManager=null;this.status=f.OFFLINE;this._listening=!1;this.authenticateConnection=e.authenticateConnection,this.instanceId=(0,ve.v4)(),this.serverOptions={...e,pingInterval:e.pingInterval??3e4,latencyInterval:e.latencyInterval??5e3,maxMissedPongs:e.maxMissedPongs??1,logLevel:e.logLevel??H.ERROR},l.configure({level:this.serverOptions.logLevel,styling:!1}),this.redisManager=new q,this.redisManager.initialize(e.redisOptions,s=>this.emit("error",s)),this.instanceManager=new X({redis:this.redisManager.redis,instanceId:this.instanceId}),this.roomManager=new I({redis:this.redisManager.redis}),this.recordManager=new S({redis:this.redisManager.redis,server:this}),this.connectionManager=new R({redis:this.redisManager.pubClient,instanceId:this.instanceId,roomManager:this.roomManager}),this.presenceManager=new E({redis:this.redisManager.redis,roomManager:this.roomManager,redisManager:this.redisManager,enableExpirationEvents:this.serverOptions.enablePresenceExpirationEvents}),this.serverOptions.enablePresenceExpirationEvents&&this.redisManager.enableKeyspaceNotifications().catch(s=>this.emit("error",new Error(`Failed to enable keyspace notifications: ${s}`))),this.commandManager=new z,this.persistenceManager=new $({defaultAdapterOptions:this.serverOptions.persistenceOptions,adapterType:this.serverOptions.persistenceAdapter}),this.persistenceManager.initialize().catch(s=>{this.emit("error",new Error(`Failed to initialize persistence manager: ${s}`))}),this.channelManager=new U({redis:this.redisManager.redis,pubClient:this.redisManager.pubClient,subClient:this.redisManager.subClient}),this.channelManager.setPersistenceManager(this.persistenceManager),this.recordSubscriptionManager=new J({pubClient:this.redisManager.pubClient,recordManager:this.recordManager,emitError:s=>this.emit("error",s),persistenceManager:this.persistenceManager}),this.collectionManager=new G({redis:this.redisManager.redis,emitError:s=>this.emit("error",s)}),this.persistenceManager&&this.persistenceManager.setRecordManager(this.recordManager),this.recordManager.onRecordUpdate(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record update for collection check: ${n}`))}}),this.recordManager.onRecordRemoved(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record removal for collection check: ${n}`))}}),this.pubSubManager=new B({subClient:this.redisManager.subClient,instanceId:this.instanceId,connectionManager:this.connectionManager,recordManager:this.recordManager,recordSubscriptions:this.recordSubscriptionManager.getRecordSubscriptions(),getChannelSubscriptions:this.channelManager.getSubscribers.bind(this.channelManager),emitError:s=>this.emit("error",s),collectionManager:this.collectionManager,pubClient:this.redisManager.pubClient}),this.broadcastManager=new D({connectionManager:this.connectionManager,roomManager:this.roomManager,instanceId:this.instanceId,pubClient:this.redisManager.pubClient,getPubSubChannel:s=>`${C}${s}`,emitError:s=>this.emit("error",s)}),this.on("listening",()=>{this.listening=!0,this.instanceManager.start()}),this.on("error",s=>{l.error(`Error: ${s}`)}),this.on("close",()=>{this.listening=!1}),this.pubSubManager.subscribeToInstanceChannel(),this.registerBuiltinCommands(),this.registerRecordCommands(),this.applyListeners()}get listening(){return this._listening}set listening(e){this._listening=e,this.status=e?f.ONLINE:f.OFFLINE}get port(){return this.address().port}async ready(){let e=this.listening?Promise.resolve():new Promise(s=>this.once("listening",s)),r=this.persistenceManager?this.persistenceManager.ready():Promise.resolve();await Promise.all([e,this.pubSubManager.getSubscriptionPromise(),r]),this.persistenceManager&&await this.persistenceManager.restorePersistedRecords()}applyListeners(){this.on("connection",async(e,r)=>{let s=new w(e,r,this.serverOptions,this);s.on("message",n=>{try{let i=n.toString(),o=F(i);o.id!==void 0&&!["latency:response","pong"].includes(o.command)&&this.commandManager.runCommand(o.id,o.command,o.payload,s,this)}catch(i){this.emit("error",i)}});try{await this.connectionManager.registerConnection(s);let n=Q.get(r);n&&(Q.delete(r),await this.connectionManager.setMetadata(s,n)),s.send({command:"mesh/assign-id",payload:s.id})}catch{s.close();return}this.emit("connected",s),s.on("close",async()=>{await this.cleanupConnection(s),this.emit("disconnected",s)}),s.on("error",n=>{this.emit("clientError",n,s)}),s.on("pong",async n=>{try{let i=await this.roomManager.getRoomsForConnection(n);for(let o of i)await this.presenceManager.isRoomTracked(o)&&await this.presenceManager.refreshPresence(n,o)}catch(i){this.emit("error",new Error(`Failed to refresh presence: ${i}`))}})})}exposeCommand(e,r,s=[]){this.commandManager.exposeCommand(e,r,s)}useMiddleware(...e){this.commandManager.useMiddleware(...e)}useMiddlewareWithCommand(e,r){this.commandManager.useMiddlewareWithCommand(e,r)}exposeChannel(e,r){this.channelManager.exposeChannel(e,r)}async writeChannel(e,r,s=0){return this.channelManager.writeChannel(e,r,s,this.instanceId)}enableChannelPersistence(e,r={}){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableChannelPersistence(e,r)}enableRecordPersistence(e){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableRecordPersistence(e)}exposeRecord(e,r){this.recordSubscriptionManager.exposeRecord(e,r)}exposeWritableRecord(e,r){this.recordSubscriptionManager.exposeWritableRecord(e,r)}async writeRecord(e,r,s){return this.recordSubscriptionManager.writeRecord(e,r,s)}async getRecord(e){return this.recordManager.getRecord(e)}async deleteRecord(e){let r=await this.recordManager.deleteRecord(e);r&&await this.recordSubscriptionManager.publishRecordDeletion(e,r.version)}async listRecordsMatching(e,r){return this.collectionManager.listRecordsMatching(e,r)}exposeCollection(e,r){this.collectionManager.exposeCollection(e,r)}async isInRoom(e,r){let s=typeof r=="string"?r:r.id;return this.roomManager.connectionIsInRoom(e,s)}async addToRoom(e,r){let s=typeof r=="string"?r:r.id;await this.roomManager.addToRoom(e,r),await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOnline(s,e)}async removeFromRoom(e,r){let s=typeof r=="string"?r:r.id;return await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOffline(s,e),this.roomManager.removeFromRoom(e,r)}async removeFromAllRooms(e){return this.roomManager.removeFromAllRooms(e)}async clearRoom(e){return this.roomManager.clearRoom(e)}async deleteRoom(e){return this.roomManager.deleteRoom(e)}async getRoomMembers(e){return this.roomManager.getRoomConnectionIds(e)}async getRoomMembersWithMetadata(e){let r=await this.roomManager.getRoomConnectionIds(e);return Promise.all(r.map(async s=>{try{let n=this.connectionManager.getLocalConnection(s),i;if(n)i=await this.connectionManager.getMetadata(n);else{let o=await this.redisManager.redis.hget("mesh:connections",s);i=o?JSON.parse(o):null}return{id:s,metadata:i}}catch{return{id:s,metadata:null}}}))}async getAllRooms(){return this.roomManager.getAllRooms()}async broadcast(e,r,s){return this.broadcastManager.broadcast(e,r,s)}async broadcastRoom(e,r,s){return this.broadcastManager.broadcastRoom(e,r,s)}async broadcastExclude(e,r,s){return this.broadcastManager.broadcastExclude(e,r,s)}async broadcastRoomExclude(e,r,s,n){return this.broadcastManager.broadcastRoomExclude(e,r,s,n)}trackPresence(e,r){this.presenceManager.trackRoom(e,r)}registerBuiltinCommands(){this.exposeCommand("mesh/noop",async()=>!0),this.exposeCommand("mesh/subscribe-channel",async e=>{let{channel:r,historyLimit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.channelManager.getSubscribers(r)||await this.channelManager.subscribeToRedisChannel(r),this.channelManager.addSubscription(r,e.connection),{success:!0,history:s&&s>0?await this.channelManager.getChannelHistory(r,s,n):[]}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/unsubscribe-channel",async e=>{let{channel:r}=e.payload,s=this.channelManager.removeSubscription(r,e.connection);return s&&!this.channelManager.getSubscribers(r)&&await this.channelManager.unsubscribeFromRedisChannel(r),s}),this.exposeCommand("mesh/get-channel-history",async e=>{let{channel:r,limit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.persistenceManager?.getChannelPersistenceOptions(r)?{success:!0,history:(await this.persistenceManager.getMessages(r,n,s||this.persistenceManager.getChannelPersistenceOptions(r)?.historyLimit)).map(o=>o.message)}:{success:!0,history:await this.channelManager.getChannelHistory(r,s||50,n)}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/join-room",async e=>{let{roomName:r}=e.payload;return await this.addToRoom(r,e.connection),{success:!0,present:await this.getRoomMembersWithMetadata(r)}}),this.exposeCommand("mesh/leave-room",async e=>{let{roomName:r}=e.payload;return await this.removeFromRoom(r,e.connection),{success:!0}}),this.exposeCommand("mesh/get-connection-metadata",async e=>{let{connectionId:r}=e.payload,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/get-my-connection-metadata",async e=>{let r=e.connection.id,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/set-my-connection-metadata",async e=>{let{metadata:r,options:s}=e.payload,n=e.connection.id,i=this.connectionManager.getLocalConnection(n);if(i)try{return await this.connectionManager.setMetadata(i,r,s),{success:!0}}catch{return{success:!1}}else return{success:!1}}),this.exposeCommand("mesh/get-room-metadata",async e=>{let{roomName:r}=e.payload;return{metadata:await this.roomManager.getMetadata(r)}})}registerRecordCommands(){this.exposeCommand("mesh/subscribe-record",async e=>{let{recordId:r,mode:s="full"}=e.payload,n=e.connection.id;if(!await this.recordSubscriptionManager.isRecordExposed(r,e.connection))return{success:!1};try{let{record:i,version:o}=await this.recordManager.getRecordAndVersion(r);return this.recordSubscriptionManager.addSubscription(r,n,s),{success:!0,record:i,version:o}}catch(i){return console.error(`Failed to subscribe to record ${r}:`,i),{success:!1}}}),this.exposeCommand("mesh/unsubscribe-record",async e=>{let{recordId:r}=e.payload,s=e.connection.id;return this.recordSubscriptionManager.removeSubscription(r,s)}),this.exposeCommand("mesh/publish-record-update",async e=>{let{recordId:r,newValue:s,options:n}=e.payload;if(!await this.recordSubscriptionManager.isRecordWritable(r,e.connection))throw new Error(`Record "${r}" is not writable by this connection.`);try{return await this.writeRecord(r,s,n),{success:!0}}catch(i){throw new Error(`Failed to publish update for record "${r}": ${i.message}`)}}),this.exposeCommand("mesh/subscribe-presence",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=`mesh:presence:updates:${r}`;this.channelManager.addSubscription(s,e.connection),(!this.channelManager.getSubscribers(s)||this.channelManager.getSubscribers(s)?.size===1)&&await this.channelManager.subscribeToRedisChannel(s);let n=await this.getRoomMembersWithMetadata(r),i=await this.presenceManager.getAllPresenceStates(r),o={};return i.forEach((a,d)=>{o[d]=a}),{success:!0,present:n,states:o}}catch(s){return console.error(`Failed to subscribe to presence for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/unsubscribe-presence",async e=>{let{roomName:r}=e.payload,s=`mesh:presence:updates:${r}`;return this.channelManager.removeSubscription(s,e.connection)}),this.exposeCommand("mesh/publish-presence-state",async e=>{let{roomName:r,state:s,expireAfter:n,silent:i}=e.payload,o=e.connection.id;if(!s||!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,o))return!1;try{return await this.presenceManager.publishPresenceState(o,r,s,n,i),!0}catch(a){return console.error(`Failed to publish presence state for room ${r}:`,a),!1}}),this.exposeCommand("mesh/clear-presence-state",async e=>{let{roomName:r}=e.payload,s=e.connection.id;if(!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,s))return!1;try{return await this.presenceManager.clearPresenceState(s,r),!0}catch(n){return console.error(`Failed to clear presence state for room ${r}:`,n),!1}}),this.exposeCommand("mesh/get-presence-state",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=await this.presenceManager.getPresentConnections(r),n=await this.presenceManager.getAllPresenceStates(r),i={};return n.forEach((o,a)=>{i[a]=o}),{success:!0,present:s,states:i}}catch(s){return console.error(`Failed to get presence state for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/subscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;if(!await this.collectionManager.isCollectionExposed(r,e.connection))return{success:!1,ids:[],records:[],version:0};try{let{ids:n,records:i,version:o}=await this.collectionManager.addSubscription(r,s,e.connection),a=i.map(d=>({id:d.id,record:d}));return{success:!0,ids:n,records:a,version:o}}catch(n){return console.error(`Failed to subscribe to collection ${r}:`,n),{success:!1,ids:[],records:[],version:0}}}),this.exposeCommand("mesh/unsubscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;return this.collectionManager.removeSubscription(r,s)})}async cleanupConnection(e){l.info("Cleaning up connection:",e.id),e.stopIntervals();try{await this.presenceManager.cleanupConnection(e),await this.connectionManager.cleanupConnection(e),await this.roomManager.cleanupConnection(e),this.recordSubscriptionManager.cleanupConnection(e),this.channelManager.cleanupConnection(e),await this.collectionManager.cleanupConnection(e)}catch(r){this.emit("error",new Error(`Failed to clean up connection: ${r}`))}}async close(e){this.redisManager.isShuttingDown=!0;let r=Object.values(this.connectionManager.getLocalConnections());if(await Promise.all(r.map(async s=>{s.isDead||await s.close(),await this.cleanupConnection(s)})),await new Promise((s,n)=>{super.close(i=>{i?n(i):s()})}),this.persistenceManager)try{await this.persistenceManager.shutdown()}catch(s){l.error("Error shutting down persistence manager:",s)}await this.instanceManager.stop(),await this.pubSubManager.cleanup(),await this.presenceManager.cleanup(),this.redisManager.disconnect(),this.listening=!1,this.removeAllListeners(),e&&e()}onConnection(e){return this.on("connected",e),this}onDisconnection(e){return this.on("disconnected",e),this}onRecordUpdate(e){return this.recordManager.onRecordUpdate(e)}onRecordRemoved(e){return this.recordManager.onRecordRemoved(e)}};0&&(module.exports={Connection,ConnectionManager,MeshContext,MeshServer,MessageStream,PersistenceManager,PostgreSQLPersistenceAdapter,PresenceManager,RecordManager,RoomManager,SQLitePersistenceAdapter});
|
|
31
|
+
ORDER BY timestamp DESC`,[e]);return l.debug(`PostgreSQL: Found ${r.rows.length} records matching pattern ${t}`),r.rows.map(s=>({recordId:s.record_id,version:s.version,value:s.value,timestamp:parseInt(s.timestamp)}))}async close(){this.pool&&(await this.pool.end(),this.pool=null,this.initialized=!1)}}});var Re={};H(Re,{SQLitePersistenceAdapter:()=>te});var we,qe,te,Ee=_(()=>{"use strict";we=oe(require("sqlite3"));Z();f();({Database:qe}=we.default),te=class{constructor(t={}){this.db=null;this.initialized=!1;this.options={filename:":memory:",...t}}async initialize(){if(!this.initialized)return new Promise((t,e)=>{try{this.db=new qe(this.options.filename,r=>{if(r){l.error("Failed to open SQLite database:",r),e(r);return}this.createTables().then(()=>{this.initialized=!0,t()}).catch(e)})}catch(r){l.error("Error initializing SQLite database:",r),e(r)}})}async createTables(){if(!this.db)throw new Error("Database not initialized");return new Promise((t,e)=>{this.db.run(`CREATE TABLE IF NOT EXISTS channel_messages (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
channel TEXT NOT NULL,
|
|
34
|
+
message TEXT NOT NULL,
|
|
35
|
+
instance_id TEXT NOT NULL,
|
|
36
|
+
timestamp INTEGER NOT NULL,
|
|
37
|
+
metadata TEXT
|
|
38
|
+
)`,r=>{if(r){e(r);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_channel_timestamp ON channel_messages (channel, timestamp)",s=>{if(s){e(s);return}this.db.run(`CREATE TABLE IF NOT EXISTS records (
|
|
39
|
+
record_id TEXT PRIMARY KEY,
|
|
40
|
+
version INTEGER NOT NULL,
|
|
41
|
+
value TEXT NOT NULL,
|
|
42
|
+
timestamp INTEGER NOT NULL
|
|
43
|
+
)`,n=>{if(n){e(n);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_records_timestamp ON records (timestamp)",i=>{if(i){e(i);return}t()})})})})})}async storeMessages(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT INTO channel_messages
|
|
44
|
+
(id, channel, message, instance_id, timestamp, metadata)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, ?)`);try{for(let i of t){let o=i.metadata?JSON.stringify(i.metadata):null;n.run(i.id,i.channel,i.message,i.instanceId,i.timestamp,o)}n.finalize(),s.run("COMMIT",i=>{i?r(i):e()})}catch(i){s.run("ROLLBACK"),r(i)}})})}async getMessages(t,e,r=50){if(!this.db)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = ?",n=[t];if(e!==void 0)if(typeof e=="number")s+=" AND timestamp > ?",n.push(e);else{let i=await new Promise((o,c)=>{this.db.get("SELECT timestamp FROM channel_messages WHERE id = ?",[e],(d,p)=>{if(d){c(d);return}o(p?p.timestamp:0)})});s+=" AND timestamp > ?",n.push(i)}return s+=" ORDER BY timestamp ASC LIMIT ?",n.push(r),new Promise((i,o)=>{this.db.all(s,n,(c,d)=>{if(c){o(c);return}let p=d.map(h=>({id:h.id,channel:h.channel,message:h.message,instanceId:h.instance_id,timestamp:h.timestamp,metadata:h.metadata?JSON.parse(h.metadata):void 0}));i(p)})})}async close(){if(this.db)return new Promise((t,e)=>{this.db.close(r=>{if(r){e(r);return}this.db=null,this.initialized=!1,t()})})}async storeRecords(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return l.debug(`SQLite: Storing ${t.length} records`),new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT OR REPLACE INTO records
|
|
46
|
+
(record_id, version, value, timestamp)
|
|
47
|
+
VALUES (?, ?, ?, ?)`);try{for(let i of t)n.run(i.recordId,i.version,i.value,i.timestamp,o=>{o&&l.error(`Error storing record ${i.recordId}:`,o)});n.finalize(),s.run("COMMIT",i=>{i?(l.error("Error committing transaction:",i),s.run("ROLLBACK"),r(i)):e()})}catch(i){l.error("Error in storeRecords:",i),s.run("ROLLBACK"),r(i)}})})}async getRecords(t){if(!this.db)throw new Error("Database not initialized");let e=V(t);return l.debug(`SQLite: Getting records matching pattern: ${t} (SQL: ${e})`),new Promise((r,s)=>{this.db.all(`SELECT record_id, version, value, timestamp
|
|
48
|
+
FROM records
|
|
49
|
+
WHERE record_id LIKE ?
|
|
50
|
+
ORDER BY timestamp DESC`,[e],(n,i)=>{if(n){l.error("Error getting records:",n),s(n);return}l.debug(`SQLite: Found ${i.length} records matching pattern ${t}`);let o=i.map(c=>({recordId:c.record_id,version:c.version,value:c.value,timestamp:c.timestamp}));r(o)})})}}});var Je={};H(Je,{Connection:()=>R,ConnectionManager:()=>E,MeshContext:()=>O,MeshServer:()=>W,MessageStream:()=>b,PersistenceManager:()=>N,PresenceManager:()=>S,RecordManager:()=>I,RoomManager:()=>T});module.exports=Y(Je);var Te=require("uuid"),Oe=require("ws");f();var me=require("events"),ge=require("ws");f();f();var k=class{constructor(){this.start=0;this.end=0;this.ms=0}onRequest(){this.start=Date.now()}onResponse(){this.end=Date.now(),this.ms=this.end-this.start}};var K=class{};var he=require("crypto"),j=[];for(let a=0;a<256;a++)j[a]=(a+256).toString(16).substring(1);function De(a,t){let e=`000000${a}`;return e.substring(e.length-t)}var Ue=32;function pe(a){let t=a.len||16,e="",r=0,s=1679616,n=a.init+Math.ceil(s/2);function i(){return n>=s&&(n=0),n++,(n-1).toString(16)}return()=>{if(!e||r===256){let h=new Uint8Array(t);(0,he.getRandomValues)(h),e=Array.from(h,u=>j[u]).join("").substring(0,t),r=0}let o=Date.now().toString(36),c=De(i(),6),d=j[r++],p=parseInt(d,16)%Ue;return`conn-${o}${c}${d}${e}${p}`}}var ze=pe({init:Date.now(),len:4}),R=class extends me.EventEmitter{constructor(e,r,s,n){super();this.alive=!0;this.missedPongs=0;this.status=y.ONLINE;this.socket=e,this.id=ze(),this.remoteAddress=r.socket.remoteAddress,this.connectionOptions=s,this.server=n,this.applyListeners(),this.startIntervals()}get isDead(){return!this.socket||this.socket.readyState!==ge.WebSocket.OPEN}startIntervals(){this.latency=new k,this.ping=new K,this.latency.interval=setInterval(()=>{this.alive&&(typeof this.latency.ms=="number"&&this.send({command:"latency",payload:this.latency.ms}),this.latency.onRequest(),this.send({command:"latency:request",payload:{}}))},this.connectionOptions.latencyInterval),this.ping.interval=setInterval(()=>{if(this.alive)this.missedPongs=0;else{this.missedPongs++;let e=this.connectionOptions.maxMissedPongs??1;if(this.missedPongs>e){l.info(`Closing connection (${this.id}) due to missed pongs`),this.close(),this.server.cleanupConnection(this);return}}this.alive=!1,this.send({command:"ping",payload:{}})},this.connectionOptions.pingInterval)}stopIntervals(){clearInterval(this.latency.interval),clearInterval(this.ping.interval)}applyListeners(){this.socket.on("close",()=>{l.info("Client's socket closed:",this.id),this.status=y.OFFLINE,this.emit("close")}),this.socket.on("error",e=>{this.emit("error",e)}),this.socket.on("message",e=>{try{let r=F(e.toString());if(r.command==="latency:response"){this.latency.onResponse();return}else if(r.command==="pong"){this.alive=!0,this.missedPongs=0,this.emit("pong",this.id);return}this.emit("message",e)}catch(r){this.emit("error",r)}})}send(e){if(this.isDead)return!1;try{return this.socket.send(le(e)),!0}catch(r){return this.emit("error",r),!1}}async close(){if(this.isDead)return!1;try{return await new Promise((e,r)=>{this.socket.once("close",e),this.socket.once("error",r),this.socket.close()}),!0}catch(e){return this.emit("error",e),!1}}};var M="mesh:pubsub:",P="mesh:record-updates",ue="mesh:record:",fe="mesh:record-version:";f();var w="mesh:connections",Be="mesh:connections:",E=class{constructor(t){this.localConnections={};this.redis=t.redis,this.instanceId=t.instanceId,this.roomManager=t.roomManager}getLocalConnections(){return Object.values(this.localConnections)}getLocalConnection(t){return this.localConnections[t]??null}async registerConnection(t){this.localConnections[t.id]=t;let e=this.redis.pipeline();e.hset(w,t.id,this.instanceId),e.sadd(this.getInstanceConnectionsKey(this.instanceId),t.id),await e.exec()}getInstanceConnectionsKey(t){return`${Be}${t}`}async deregisterConnection(t){let e=await this.getInstanceIdForConnection(t);if(!e)return;let r=this.redis.pipeline();r.hdel(w,t.id),r.srem(this.getInstanceConnectionsKey(e),t.id),await r.exec()}async getInstanceIdForConnection(t){return this.redis.hget(w,t.id)}async getInstanceIdsForConnections(t){if(t.length===0)return{};let e=await this.redis.hmget(w,...t),r={};return t.forEach((s,n)=>{r[s]=e[n]??null}),r}async getAllConnectionIds(){return this.redis.hkeys(w)}async getLocalConnectionIds(){return this.redis.smembers(this.getInstanceConnectionsKey(this.instanceId))}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let o=await this.getMetadata(t);n==="merge"?g(o)&&g(e)?s={...o,...e}:s=e:n==="deepMerge"&&(g(o)&&g(e)?s=C(o,e):s=e)}let i=this.redis.pipeline();i.hset(w,t.id,JSON.stringify(s)),await i.exec()}async getMetadata(t){let e=await this.redis.hget(w,t.id);return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.getAllConnectionIds(),e=await this.getInstanceIdsForConnections(t),r=[];return t.forEach(s=>{try{let n=e[s]?JSON.parse(e[s]):null;r.push({id:s,metadata:n})}catch(n){console.error(`Failed to parse metadata for connection ${s}:`,n),r.push({id:s,metadata:null})}}),r}async getAllMetadataForRoom(t){let e=await this.roomManager.getRoomConnectionIds(t),r=await this.getInstanceIdsForConnections(e),s=[];return e.forEach(n=>{try{let i=r[n]?JSON.parse(r[n]):null;s.push({id:n,metadata:i})}catch(i){console.error(`Failed to parse metadata for connection ${n}:`,i),s.push({id:n,metadata:null})}}),s}async cleanupConnection(t){await this.deregisterConnection(t)}};var S=class{constructor(t){this.PRESENCE_KEY_PATTERN=/^mesh:presence:room:(.+):conn:(.+)$/;this.PRESENCE_STATE_KEY_PATTERN=/^mesh:presence:state:(.+):conn:(.+)$/;this.trackedRooms=[];this.roomGuards=new Map;this.roomTTLs=new Map;this.defaultTTL=0;this.redis=t.redis,this.roomManager=t.roomManager,this.redisManager=t.redisManager,this.presenceExpirationEventsEnabled=t.enableExpirationEvents??!0,this.presenceExpirationEventsEnabled&&this.subscribeToExpirationEvents()}getExpiredEventsPattern(){return`__keyevent@${this.redis.options?.db??0}__:expired`}subscribeToExpirationEvents(){let{subClient:t}=this.redisManager,e=this.getExpiredEventsPattern();t.psubscribe(e),t.on("pmessage",(r,s,n)=>{(this.PRESENCE_KEY_PATTERN.test(n)||this.PRESENCE_STATE_KEY_PATTERN.test(n))&&this.handleExpiredKey(n)})}async handleExpiredKey(t){try{let e=t.match(this.PRESENCE_KEY_PATTERN);if(e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.markOffline(s,r);return}if(e=t.match(this.PRESENCE_STATE_KEY_PATTERN),e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.publishPresenceStateUpdate(r,s,null)}}catch(e){console.error("[PresenceManager] Failed to handle expired key:",e)}}trackRoom(t,e){this.trackedRooms.push(t),typeof e=="function"?this.roomGuards.set(t,e):e&&typeof e=="object"&&(e.guard&&this.roomGuards.set(t,e.guard),e.ttl&&typeof e.ttl=="number"&&this.roomTTLs.set(t,e.ttl))}async isRoomTracked(t,e){let r=this.trackedRooms.find(s=>typeof s=="string"?s===t:s.test(t));if(!r)return!1;if(e){let s=this.roomGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}}return!0}getRoomTTL(t){let e=this.trackedRooms.find(r=>typeof r=="string"?r===t:r.test(t));if(e){let r=this.roomTTLs.get(e);if(r!==void 0)return r}return this.defaultTTL}presenceRoomKey(t){return`mesh:presence:room:${t}`}presenceConnectionKey(t,e){return`mesh:presence:room:${t}:conn:${e}`}presenceStateKey(t,e){return`mesh:presence:state:${t}:conn:${e}`}async markOnline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.getRoomTTL(e),i=this.redis.pipeline();if(i.sadd(r,t),n>0){let o=Math.max(1,Math.floor(n/1e3));i.set(s,"","EX",o)}else i.set(s,"");await i.exec(),await this.publishPresenceUpdate(e,t,"join")}async markOffline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.presenceStateKey(e,t),i=this.redis.pipeline();i.srem(r,t),i.del(s),i.del(n),await i.exec(),await this.publishPresenceUpdate(e,t,"leave")}async refreshPresence(t,e){let r=this.presenceConnectionKey(e,t),s=this.getRoomTTL(e);if(s>0){let n=Math.max(1,Math.floor(s/1e3));await this.redis.set(r,"","EX",n)}else await this.redis.set(r,"")}async getPresentConnections(t){return this.redis.smembers(this.presenceRoomKey(t))}async publishPresenceUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:r,connectionId:e,roomName:t,timestamp:Date.now()});await this.redis.publish(s,n)}async publishPresenceState(t,e,r,s,n){let i=this.presenceStateKey(e,t),o=JSON.stringify(r),c=this.redis.pipeline();s&&s>0?c.set(i,o,"PX",s):c.set(i,o),await c.exec(),!n&&await this.publishPresenceStateUpdate(e,t,r)}async clearPresenceState(t,e){let r=this.presenceStateKey(e,t);await this.redis.del(r),await this.publishPresenceStateUpdate(e,t,null)}async getPresenceState(t,e){let r=this.presenceStateKey(e,t),s=await this.redis.get(r);if(!s)return null;try{return JSON.parse(s)}catch(n){return console.error(`[PresenceManager] Failed to parse presence state: ${n}`),null}}async getAllPresenceStates(t){let e=new Map,r=await this.getPresentConnections(t);if(r.length===0)return e;let s=this.redis.pipeline();for(let i of r)s.get(this.presenceStateKey(t,i));let n=await s.exec();if(!n)return e;for(let i=0;i<r.length;i++){let o=r[i];if(!o)continue;let[c,d]=n[i]||[];if(!(c||!d))try{let p=JSON.parse(d);e.set(o,p)}catch(p){console.error(`[PresenceManager] Failed to parse presence state: ${p}`)}}return e}async publishPresenceStateUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:"state",connectionId:e,roomName:t,state:r,timestamp:Date.now()});await this.redis.publish(s,n)}async cleanupConnection(t){let e=t.id,r=await this.roomManager.getRoomsForConnection(e);for(let s of r)await this.isRoomTracked(s)&&await this.markOffline(e,s)}async cleanup(){let{subClient:t}=this.redisManager;if(t&&t.status!=="end"){let e=this.getExpiredEventsPattern();await new Promise(r=>{t.punsubscribe(e,()=>r())})}}};var ye=oe(require("fast-json-patch"));f();var I=class{constructor(t){this.recordUpdateCallbacks=[];this.recordRemovedCallbacks=[];this.redis=t.redis,this.server=t.server}getServer(){return this.server}getRedis(){return this.redis}recordKey(t){return`${ue}${t}`}recordVersionKey(t){return`${fe}${t}`}async getRecord(t){let e=await this.redis.get(this.recordKey(t));return e?JSON.parse(e):null}async getVersion(t){let e=await this.redis.get(this.recordVersionKey(t));return e?parseInt(e,10):0}async getRecordAndVersion(t){let e=this.redis.pipeline();e.get(this.recordKey(t)),e.get(this.recordVersionKey(t));let r=await e.exec(),s=r?.[0]?.[1],n=r?.[1]?.[1],i=s?JSON.parse(s):null,o=n?parseInt(n,10):0;return{record:i,version:o}}async publishUpdate(t,e,r="replace"){let s=this.recordKey(t),n=this.recordVersionKey(t),{record:i,version:o}=await this.getRecordAndVersion(t),c;r==="merge"?g(i)&&g(e)?c={...i,...e}:c=e:r==="deepMerge"&&g(i)&&g(e)?c=C(i,e):c=e;let d=ye.default.compare(i??{},c??{});if(d.length===0)return null;let p=o+1,h=this.redis.pipeline();return h.set(s,JSON.stringify(c)),h.set(n,p.toString()),await h.exec(),this.recordUpdateCallbacks.length>0&&Promise.all(this.recordUpdateCallbacks.map(async u=>{try{await u({recordId:t,value:c})}catch(v){console.error(`Error in record update callback for ${t}:`,v)}})).catch(u=>{console.error(`Error in record update callbacks for ${t}:`,u)}),{patch:d,version:p,finalValue:c}}async deleteRecord(t){let{record:e,version:r}=await this.getRecordAndVersion(t);if(!e)return null;let s=this.redis.pipeline();return s.del(this.recordKey(t)),s.del(this.recordVersionKey(t)),await s.exec(),this.recordRemovedCallbacks.length>0&&Promise.all(this.recordRemovedCallbacks.map(async n=>{try{await n({recordId:t,value:e})}catch(i){console.error(`Error in record removed callback for ${t}:`,i)}})).catch(n=>{console.error(`Error in record removed callbacks for ${t}:`,n)}),{version:r}}onRecordUpdate(t){return this.recordUpdateCallbacks.push(t),()=>{this.recordUpdateCallbacks=this.recordUpdateCallbacks.filter(e=>e!==t)}}onRecordRemoved(t){return this.recordRemovedCallbacks.push(t),()=>{this.recordRemovedCallbacks=this.recordRemovedCallbacks.filter(e=>e!==t)}}};f();var T=class{constructor(t){this.redis=t.redis}roomKey(t){return`mesh:room:${t}`}connectionsRoomKey(t){return`mesh:connection:${t}:rooms`}roomMetadataKey(t){return`mesh:roommeta:${t}`}async getRoomConnectionIds(t){return this.redis.smembers(this.roomKey(t))}async connectionIsInRoom(t,e){let r=typeof e=="string"?e:e.id;return!!await this.redis.sismember(this.roomKey(t),r)}async addToRoom(t,e){let r=typeof e=="string"?e:e.id;await this.redis.sadd(this.roomKey(t),r),await this.redis.sadd(this.connectionsRoomKey(r),t)}async getRoomsForConnection(t){let e=typeof t=="string"?t:t.id;return await this.redis.smembers(this.connectionsRoomKey(e))}async getAllRooms(){return(await this.redis.keys("mesh:room:*")).map(e=>e.replace("mesh:room:",""))}async removeFromRoom(t,e){let r=typeof e=="string"?e:e.id,s=this.redis.pipeline();s.srem(this.roomKey(t),r),s.srem(this.connectionsRoomKey(r),t),await s.exec()}async removeFromAllRooms(t){let e=typeof t=="string"?t:t.id,r=await this.redis.smembers(this.connectionsRoomKey(e)),s=this.redis.pipeline();for(let n of r)s.srem(this.roomKey(n),e);s.del(this.connectionsRoomKey(e)),await s.exec()}async clearRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),await r.exec()}async deleteRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),r.del(this.roomMetadataKey(t)),await r.exec()}async cleanupConnection(t){let e=await this.redis.smembers(this.connectionsRoomKey(t.id)),r=this.redis.pipeline();for(let s of e)r.srem(this.roomKey(s),t.id);r.del(this.connectionsRoomKey(t.id)),await r.exec()}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let i=await this.getMetadata(t);n==="merge"?g(i)&&g(e)?s={...i,...e}:s=e:n==="deepMerge"&&(g(i)&&g(e)?s=C(i,e):s=e)}await this.redis.hset(this.roomMetadataKey(t),"data",JSON.stringify(s))}async getMetadata(t){let e=await this.redis.hget(this.roomMetadataKey(t),"data");return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.redis.keys("mesh:roommeta:*"),e=[];if(t.length===0)return e;let r=this.redis.pipeline();t.forEach(n=>r.hget(n,"data"));let s=await r.exec();return t.forEach((n,i)=>{let o=n.replace("mesh:roommeta:",""),c=s?.[i]?.[1];if(c)try{let d=JSON.parse(c);e.push({id:o,metadata:d})}catch(d){console.error(`Failed to parse metadata for room ${o}:`,d)}}),e}};var D=class{constructor(t){this.connectionManager=t.connectionManager,this.roomManager=t.roomManager,this.instanceId=t.instanceId,this.pubClient=t.pubClient,this.getPubSubChannel=t.getPubSubChannel,this.emitError=t.emitError}async broadcast(t,e,r){let s={command:t,payload:e};try{if(r){let n=r.map(({id:c})=>c),i=await this.connectionManager.getAllConnectionIds(),o=n.filter(c=>i.includes(c));await this.publishOrSend(o,s)}else{let n=await this.connectionManager.getAllConnectionIds();await this.publishOrSend(n,s)}}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoom(t,e,r){let s=await this.roomManager.getRoomConnectionIds(t);try{await this.publishOrSend(s,{command:e,payload:r})}catch(n){this.emitError(new Error(`Failed to broadcast command "${e}": ${n}`))}}async broadcastExclude(t,e,r){let s=new Set((Array.isArray(r)?r:[r]).map(({id:n})=>n));try{let n=(await this.connectionManager.getAllConnectionIds()).filter(i=>!s.has(i));await this.publishOrSend(n,{command:t,payload:e})}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoomExclude(t,e,r,s){let n=new Set((Array.isArray(s)?s:[s]).map(({id:i})=>i));try{let i=(await this.roomManager.getRoomConnectionIds(t)).filter(o=>!n.has(o));await this.publishOrSend(i,{command:e,payload:r})}catch(i){this.emitError(new Error(`Failed to broadcast command "${e}": ${i}`))}}async publishOrSend(t,e){if(t.length===0)return;let r=await this.connectionManager.getInstanceIdsForConnections(t),s={};for(let n of t){let i=r[n];i&&(s[i]||(s[i]=[]),s[i].push(n))}for(let[n,i]of Object.entries(s))if(i.length!==0)if(n===this.instanceId)i.forEach(o=>{let c=this.connectionManager.getLocalConnection(o);c&&!c.isDead&&c.send(e)});else{let c=JSON.stringify({targetConnectionIds:i,command:e});try{await this.pubClient.publish(this.getPubSubChannel(n),c)}catch(d){this.emitError(new Error(`Failed to publish command "${e.command}": ${d}`))}}}};var be=require("events"),b=class a extends be.EventEmitter{static getInstance(){return a.instance||(a.instance=new a),a.instance}constructor(){super(),this.setMaxListeners(100)}publishMessage(t,e,r){let s=Date.now();this.emit("message",{channel:t,message:e,instanceId:r,timestamp:s})}subscribeToMessages(t){this.on("message",t)}unsubscribeFromMessages(t){this.off("message",t)}};var U=class{constructor(t){this.exposedChannels=[];this.channelGuards=new Map;this.channelSubscriptions={};this.redis=t.redis,this.pubClient=t.pubClient,this.subClient=t.subClient,this.messageStream=b.getInstance()}setPersistenceManager(t){this.persistenceManager=t}exposeChannel(t,e){this.exposedChannels.push(t),e&&this.channelGuards.set(t,e)}async isChannelExposed(t,e){let r=this.exposedChannels.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.channelGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}async writeChannel(t,e,r=0,s){let n=parseInt(r,10);!isNaN(n)&&n>0&&(await this.pubClient.rpush(`mesh:history:${t}`,e),await this.pubClient.ltrim(`mesh:history:${t}`,-n,-1)),this.messageStream.publishMessage(t,e,s),await this.pubClient.publish(t,e)}addSubscription(t,e){this.channelSubscriptions[t]||(this.channelSubscriptions[t]=new Set),this.channelSubscriptions[t].add(e)}removeSubscription(t,e){return this.channelSubscriptions[t]?(this.channelSubscriptions[t].delete(e),this.channelSubscriptions[t].size===0&&delete this.channelSubscriptions[t],!0):!1}getSubscribers(t){return this.channelSubscriptions[t]}async subscribeToRedisChannel(t){return new Promise((e,r)=>{this.subClient.subscribe(t,s=>{s?r(s):e()})})}async unsubscribeFromRedisChannel(t){return new Promise((e,r)=>{this.subClient.unsubscribe(t,s=>{s?r(s):e()})})}async getChannelHistory(t,e,r){if(this.persistenceManager&&r!==void 0)try{return(await this.persistenceManager.getMessages(t,r,e)).map(i=>i.message)}catch{let i=`mesh:history:${t}`;return this.redis.lrange(i,0,e-1)}let s=`mesh:history:${t}`;return this.redis.lrange(s,0,e-1)}async getPersistedMessages(t,e,r){if(!this.persistenceManager)throw new Error("Persistence not enabled");return this.persistenceManager.getMessages(t,e,r)}cleanupConnection(t){for(let e in this.channelSubscriptions)this.removeSubscription(e,t)}};var O=class{constructor(t,e,r,s){this.server=t,this.command=e,this.connection=r,this.payload=s}};f();var z=class{constructor(){this.commands={};this.globalMiddlewares=[];this.middlewares={}}exposeCommand(t,e,r=[]){this.commands[t]=e,r.length>0&&this.useMiddlewareWithCommand(t,r)}useMiddleware(...t){this.globalMiddlewares.push(...t)}useMiddlewareWithCommand(t,e){e.length&&(this.middlewares[t]=this.middlewares[t]||[],this.middlewares[t]=e.concat(this.middlewares[t]))}async runCommand(t,e,r,s,n){let i=new O(n,e,s,r);try{if(!this.commands[e])throw new ce(`Command "${e}" not found`,"ENOTFOUND","CommandError");if(this.globalMiddlewares.length)for(let c of this.globalMiddlewares)await c(i);if(this.middlewares[e])for(let c of this.middlewares[e])await c(i);let o=await this.commands[e](i);s.send({id:t,command:e,payload:o})}catch(o){let c=o instanceof Error?{error:o.message,code:o.code||"ESERVER",name:o.name||"Error"}:{error:String(o),code:"EUNKNOWN",name:"UnknownError"};s.send({id:t,command:e,payload:c})}}getCommands(){return this.commands}hasCommand(t){return!!this.commands[t]}};f();var B=class{constructor(t){this.collectionManager=null;this.collectionUpdateTimeouts=new Map;this.collectionMaxDelayTimeouts=new Map;this.pendingCollectionUpdates=new Map;this.COLLECTION_UPDATE_DEBOUNCE_MS=50;this.COLLECTION_MAX_DELAY_MS=200;this.subClient=t.subClient,this.pubClient=t.pubClient,this.instanceId=t.instanceId,this.connectionManager=t.connectionManager,this.recordManager=t.recordManager,this.recordSubscriptions=t.recordSubscriptions,this.getChannelSubscriptions=t.getChannelSubscriptions,this.emitError=t.emitError,this.collectionManager=t.collectionManager||null}subscribeToInstanceChannel(){let t=`${M}${this.instanceId}`;return this._subscriptionPromise=new Promise((e,r)=>{this.subClient.subscribe(t,P,"mesh:collection:record-change"),this.subClient.psubscribe("mesh:presence:updates:*",s=>{if(s){this.emitError(new Error(`Failed to subscribe to channels/patterns: ${JSON.stringify({cause:s})}`)),r(s);return}e()})}),this.setupMessageHandlers(),this._subscriptionPromise}setupMessageHandlers(){this.subClient.on("message",async(t,e)=>{if(t.startsWith(M))this.handleInstancePubSubMessage(t,e);else if(t===P)this.handleRecordUpdatePubSubMessage(e);else if(t==="mesh:collection:record-change")this.handleCollectionRecordChange(e);else{let r=this.getChannelSubscriptions(t);if(r)for(let s of r)s.isDead||s.send({command:"mesh/subscription-message",payload:{channel:t,message:e}})}}),this.subClient.on("pmessage",async(t,e,r)=>{if(t==="mesh:presence:updates:*"){let s=this.getChannelSubscriptions(e);if(s)try{let n=JSON.parse(r);s.forEach(i=>{i.isDead?s.delete(i):i.send({command:"mesh/presence-update",payload:n})})}catch{this.emitError(new Error(`Failed to parse presence update: ${r}`))}}})}handleInstancePubSubMessage(t,e){try{let r=JSON.parse(e);if(!r||!Array.isArray(r.targetConnectionIds)||!r.command||typeof r.command.command!="string")throw new Error("Invalid message format");let{targetConnectionIds:s,command:n}=r;s.forEach(i=>{let o=this.connectionManager.getLocalConnection(i);o&&!o.isDead&&o.send(n)})}catch{this.emitError(new Error(`Failed to parse message: ${e}`))}}handleRecordUpdatePubSubMessage(t){try{let e=JSON.parse(t),{recordId:r,newValue:s,patch:n,version:i,deleted:o}=e;if(!r||typeof i!="number")throw new Error("Invalid record update message format");let c=this.recordSubscriptions.get(r);if(!c)return;c.forEach((d,p)=>{let h=this.connectionManager.getLocalConnection(p);h&&!h.isDead?o?h.send({command:"mesh/record-deleted",payload:{recordId:r,version:i}}):d==="patch"&&n?h.send({command:"mesh/record-update",payload:{recordId:r,patch:n,version:i}}):d==="full"&&s!==void 0&&h.send({command:"mesh/record-update",payload:{recordId:r,full:s,version:i}}):(!h||h.isDead)&&(c.delete(p),c.size===0&&this.recordSubscriptions.delete(r))}),o&&this.recordSubscriptions.delete(r)}catch{this.emitError(new Error(`Failed to parse record update message: ${t}`))}}async handleCollectionRecordChange(t){if(!this.collectionManager)return;let e=this.collectionManager.getCollectionSubscriptions(),r=new Set;for(let[s]of e.entries())r.add(s);for(let s of r){let n=this.collectionUpdateTimeouts.get(s);n&&clearTimeout(n),this.pendingCollectionUpdates.has(s)||this.pendingCollectionUpdates.set(s,new Set),this.pendingCollectionUpdates.get(s).add(t);let i=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_UPDATE_DEBOUNCE_MS);if(this.collectionUpdateTimeouts.set(s,i),!this.collectionMaxDelayTimeouts.has(s)){let o=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_MAX_DELAY_MS);this.collectionMaxDelayTimeouts.set(s,o)}}}async processCollectionUpdates(t){let e=this.pendingCollectionUpdates.get(t);if(!e||e.size===0)return;let r=this.collectionUpdateTimeouts.get(t),s=this.collectionMaxDelayTimeouts.get(t);if(r&&(clearTimeout(r),this.collectionUpdateTimeouts.delete(t)),s&&(clearTimeout(s),this.collectionMaxDelayTimeouts.delete(t)),this.pendingCollectionUpdates.delete(t),!this.collectionManager)return;let n=this.collectionManager.getCollectionSubscriptions().get(t);if(!(!n||n.size===0))for(let[i,{version:o}]of n.entries())try{let c=this.connectionManager.getLocalConnection(i);if(!c||c.isDead)continue;let d=await this.collectionManager.resolveCollection(t,c),p=d.map(m=>m.id),h=`mesh:collection:${t}:${i}`,u=await this.pubClient.get(h),v=u?JSON.parse(u):[],$=p.filter(m=>!v.includes(m)),se=d.filter(m=>$.includes(m.id)),Ne=v.filter(m=>!p.includes(m)),A=[];for(let m of Ne)try{let x=await this.recordManager.getRecord(m);A.push(x||{id:m})}catch{A.push({id:m})}let ne=[];for(let m of e)v.includes(m)&&!p.includes(m)&&ne.push(m);let $e=se.length>0||A.length>0,xe=ne.length>0;if($e||xe){let m=o+1;this.collectionManager.updateSubscriptionVersion(t,i,m),await this.pubClient.set(h,JSON.stringify(p)),c.send({command:"mesh/collection-diff",payload:{collectionId:t,added:se,removed:A,version:m}})}for(let m of e)if(p.includes(m))try{let{record:x,version:Ae}=await this.recordManager.getRecordAndVersion(m);x&&c.send({command:"mesh/record-update",payload:{recordId:m,version:Ae,full:x}})}catch{l.info(`Record ${m} not found during collection update (likely deleted).`)}}catch(c){this.emitError(new Error(`Error processing collection ${t} for connection ${i}: ${c}`))}}getSubscriptionPromise(){return this._subscriptionPromise}getPubSubChannel(t){return`${M}${t}`}async cleanup(){for(let t of this.collectionUpdateTimeouts.values())clearTimeout(t);this.collectionUpdateTimeouts.clear();for(let t of this.collectionMaxDelayTimeouts.values())clearTimeout(t);if(this.collectionMaxDelayTimeouts.clear(),this.pendingCollectionUpdates.clear(),this.subClient&&this.subClient.status!=="end"){let t=`${M}${this.instanceId}`;await Promise.all([new Promise(e=>{this.subClient.unsubscribe(t,P,"mesh:collection:record-change",()=>e())}),new Promise(e=>{this.subClient.punsubscribe("mesh:presence:updates:*",()=>e())})])}}};var q=class{constructor(t){this.persistenceManager=null;this.exposedRecords=[];this.exposedWritableRecords=[];this.recordGuards=new Map;this.writableRecordGuards=new Map;this.recordSubscriptions=new Map;this.pubClient=t.pubClient,this.recordManager=t.recordManager,this.emitError=t.emitError,this.persistenceManager=t.persistenceManager||null}setPersistenceManager(t){this.persistenceManager=t}exposeRecord(t,e){this.exposedRecords.push(t),e&&this.recordGuards.set(t,e)}exposeWritableRecord(t,e){this.exposedWritableRecords.push(t),e&&this.writableRecordGuards.set(t,e)}async isRecordExposed(t,e){let r=this.exposedRecords.find(i=>typeof i=="string"?i===t:i.test(t)),s=!1;if(r){let i=this.recordGuards.get(r);if(i)try{s=await Promise.resolve(i(e,t))}catch{s=!1}else s=!0}return!!(s||this.exposedWritableRecords.find(i=>typeof i=="string"?i===t:i.test(t)))}async isRecordWritable(t,e){let r=this.exposedWritableRecords.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.writableRecordGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}addSubscription(t,e,r){this.recordSubscriptions.has(t)||this.recordSubscriptions.set(t,new Map),this.recordSubscriptions.get(t).set(e,r)}removeSubscription(t,e){let r=this.recordSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.recordSubscriptions.delete(t),!0):!1}getSubscribers(t){return this.recordSubscriptions.get(t)}async writeRecord(t,e,r){let s=await this.recordManager.publishUpdate(t,e,r?.strategy||"replace");if(!s)return;let{patch:n,version:i,finalValue:o}=s;this.persistenceManager&&this.persistenceManager.handleRecordUpdate(t,o,i);let c={recordId:t,newValue:o,patch:n,version:i};try{await this.pubClient.publish(P,JSON.stringify(c))}catch(d){this.emitError(new Error(`Failed to publish record update for "${t}": ${d}`))}}cleanupConnection(t){let e=t.id;this.recordSubscriptions.forEach((r,s)=>{r.has(e)&&(r.delete(e),r.size===0&&this.recordSubscriptions.delete(s))})}async publishRecordDeletion(t,e){let r={recordId:t,deleted:!0,version:e};try{await this.pubClient.publish(P,JSON.stringify(r))}catch(s){this.emitError(new Error(`Failed to publish record deletion for "${t}": ${s}`))}}getRecordSubscriptions(){return this.recordSubscriptions}};var ve=require("ioredis"),J=class{constructor(){this._redis=null;this._pubClient=null;this._subClient=null;this._isShuttingDown=!1}initialize(t,e){this._redis=new ve.Redis({retryStrategy:r=>this._isShuttingDown||r>10?null:Math.min(1e3*Math.pow(2,r),3e4),...t}),this._redis.on("error",r=>{e(new Error(`Redis error: ${r}`))}),this._pubClient=this._redis.duplicate(),this._subClient=this._redis.duplicate()}get redis(){if(!this._redis)throw new Error("Redis not initialized");return this._redis}get pubClient(){if(!this._pubClient)throw new Error("Redis pub client not initialized");return this._pubClient}get subClient(){if(!this._subClient)throw new Error("Redis sub client not initialized");return this._subClient}disconnect(){this._isShuttingDown=!0,this._pubClient&&(this._pubClient.disconnect(),this._pubClient=null),this._subClient&&(this._subClient.disconnect(),this._subClient=null),this._redis&&(this._redis.disconnect(),this._redis=null)}get isShuttingDown(){return this._isShuttingDown}set isShuttingDown(t){this._isShuttingDown=t}async enableKeyspaceNotifications(){let t=await this.redis.config("GET","notify-keyspace-events"),r=(Array.isArray(t)&&t.length>1?t[1]:"")||"";r.includes("E")||(r+="E"),r.includes("x")||(r+="x"),await this.redis.config("SET","notify-keyspace-events",r)}};var X=class{constructor(t){this.heartbeatInterval=null;this.heartbeatTTL=120;this.heartbeatFrequency=15e3;this.cleanupInterval=null;this.cleanupFrequency=6e4;this.cleanupLockTTL=10;this.redis=t.redis,this.instanceId=t.instanceId}async start(){await this.registerInstance(),await this.updateHeartbeat(),this.heartbeatInterval=setInterval(()=>this.updateHeartbeat(),this.heartbeatFrequency),this.cleanupInterval=setInterval(()=>this.performCleanup(),this.cleanupFrequency)}async stop(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),await this.deregisterInstance()}async registerInstance(){await this.redis.sadd("mesh:instances",this.instanceId)}async deregisterInstance(){await this.redis.srem("mesh:instances",this.instanceId),await this.redis.del(this.getHeartbeatKey())}async updateHeartbeat(){let t=this.getHeartbeatKey();await this.redis.set(t,Date.now().toString(),"EX",this.heartbeatTTL)}getHeartbeatKey(){return`mesh:instance:${this.instanceId}:heartbeat`}async acquireCleanupLock(){return await this.redis.set("mesh:cleanup:lock",this.instanceId,"EX",this.cleanupLockTTL,"NX")==="OK"}async releaseCleanupLock(){await this.redis.eval(`
|
|
51
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
52
|
+
return redis.call("del", KEYS[1])
|
|
53
|
+
else
|
|
54
|
+
return 0
|
|
55
|
+
end
|
|
56
|
+
`,1,"mesh:cleanup:lock",this.instanceId)}async performCleanup(){try{if(!await this.acquireCleanupLock())return;let e=await this.redis.smembers("mesh:instances"),r=await this.redis.hgetall("mesh:connections"),s=new Set([...e,...Object.values(r)]);for(let n of s){if(n===this.instanceId)continue;let i=`mesh:instance:${n}:heartbeat`;await this.redis.get(i)||(console.log(`Found dead instance: ${n}`),await this.cleanupDeadInstance(n))}}catch(t){console.error("Error during cleanup:",t)}finally{await this.releaseCleanupLock()}}async cleanupDeadInstance(t){try{let e=`mesh:connections:${t}`,r=await this.redis.smembers(e);for(let n of r)await this.cleanupConnection(n);let s=await this.redis.hgetall("mesh:connections");for(let[n,i]of Object.entries(s))i===t&&await this.cleanupConnection(n);await this.redis.srem("mesh:instances",t),await this.redis.del(e),console.log(`Cleaned up dead instance: ${t}`)}catch(e){console.error(`Error cleaning up instance ${t}:`,e)}}async deleteMatchingKeys(t){let e=this.redis.scanStream({match:t}),r=this.redis.pipeline();return e.on("data",s=>{for(let n of s)r.del(n)}),new Promise((s,n)=>{e.on("end",async()=>{await r.exec(),s()}),e.on("error",n)})}async cleanupConnection(t){try{let e=`mesh:connection:${t}:rooms`,r=await this.redis.smembers(e),s=this.redis.pipeline();for(let n of r)s.srem(`mesh:room:${n}`,t),s.srem(`mesh:presence:room:${n}`,t),s.del(`mesh:presence:room:${n}:conn:${t}`),s.del(`mesh:presence:state:${n}:conn:${t}`);s.del(e),s.hdel("mesh:connections",t),await this.deleteMatchingKeys(`mesh:collection:*:${t}`),await s.exec(),console.log(`Cleaned up stale connection: ${t}`)}catch(e){console.error(`Error cleaning up connection ${t}:`,e)}}};var G=class{constructor(t){this.exposedCollections=[];this.collectionSubscriptions=new Map;this.redis=t.redis,this.emitError=t.emitError}exposeCollection(t,e){this.exposedCollections.push({pattern:t,resolver:e})}async isCollectionExposed(t,e){return!!this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t))}async resolveCollection(t,e){let r=this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t));if(!r)throw new Error(`Collection "${t}" is not exposed`);try{return await Promise.resolve(r.resolver(e,t))}catch(s){throw this.emitError(new Error(`Failed to resolve collection "${t}": ${s}`)),s}}async addSubscription(t,e,r){this.collectionSubscriptions.has(t)||this.collectionSubscriptions.set(t,new Map);let s=await this.resolveCollection(t,r),n=s.map(o=>o.id),i=1;return this.collectionSubscriptions.get(t).set(e,{version:i}),await this.redis.set(`mesh:collection:${t}:${e}`,JSON.stringify(n)),{ids:n,records:s,version:i}}async removeSubscription(t,e){let r=this.collectionSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.collectionSubscriptions.delete(t),await this.redis.del(`mesh:collection:${t}:${e}`),!0):!1}async publishRecordChange(t){try{await this.redis.publish("mesh:collection:record-change",t)}catch(e){this.emitError(new Error(`Failed to publish record change for ${t}: ${e}`))}}async cleanupConnection(t){let e=t.id,r=[];this.collectionSubscriptions.forEach((s,n)=>{s.has(e)&&(s.delete(e),s.size===0&&this.collectionSubscriptions.delete(n),r.push(this.redis.del(`mesh:collection:${n}:${e}`).then(()=>{}).catch(i=>{this.emitError(new Error(`Failed to clean up collection subscription for "${n}": ${i}`))})))}),await Promise.all(r)}async listRecordsMatching(t,e){try{let r="mesh:record:",s=[],n="0";do{let d=await this.redis.scan(n,"MATCH",`${r}${t}`,"COUNT",e?.scanCount??100);n=d[0],s.push(...d[1])}while(n!=="0");if(s.length===0)return[];let i=await this.redis.mget(s),o=s.map(d=>d.substring(r.length)),c=i.map((d,p)=>{if(d===null)return null;try{let h=JSON.parse(d),u=o[p];return h.id===u?h:{...h,id:u}}catch(h){return this.emitError(new Error(`Failed to parse record for processing: ${d} - ${h.message}`)),null}}).filter(d=>d!==null);if(e?.map&&(c=c.map(e.map)),e?.sort&&c.sort(e.sort),e?.slice){let{start:d,count:p}=e.slice;c=c.slice(d,d+p)}return c}catch(r){return this.emitError(new Error(`Failed to list records matching "${t}": ${r.message}`)),[]}}getCollectionSubscriptions(){return this.collectionSubscriptions}updateSubscriptionVersion(t,e,r){let s=this.collectionSubscriptions.get(t);s?.has(e)&&s.set(e,{version:r})}};var Se=require("events"),Ie=require("uuid");f();var N=class extends Se.EventEmitter{constructor(e){super();this.channelPatterns=[];this.recordPatterns=[];this.messageBuffer=new Map;this.recordBuffer=new Map;this.flushTimers=new Map;this.recordFlushTimer=null;this.isShuttingDown=!1;this.initialized=!1;this.recordManager=null;this.pendingRecordUpdates=[];let{defaultAdapterOptions:r={},adapterType:s="sqlite"}=e;if(s==="postgres"){let{PostgreSQLPersistenceAdapter:n}=(Pe(),Y(Me));this.defaultAdapter=new n(r)}else{let{SQLitePersistenceAdapter:n}=(Ee(),Y(Re));this.defaultAdapter=new n(r)}this.messageStream=b.getInstance()}setRecordManager(e){this.recordManager=e}async ready(){return this.initialized?Promise.resolve():new Promise(e=>{this.once("initialized",e)})}async processPendingRecordUpdates(){if(this.pendingRecordUpdates.length===0)return;l.info(`Processing ${this.pendingRecordUpdates.length} pending record updates`);let e=[...this.pendingRecordUpdates];this.pendingRecordUpdates=[];for(let{recordId:r,value:s,version:n}of e)this.handleRecordUpdate(r,s,n)}async initialize(){if(!this.initialized)try{await this.defaultAdapter.initialize(),this.messageStream.subscribeToMessages(this.handleStreamMessage.bind(this)),this.initialized=!0,await this.processPendingRecordUpdates(),this.emit("initialized")}catch(e){throw l.error("Failed to initialize persistence manager:",e),e}}async restorePersistedRecords(){if(!this.recordManager){l.warn("Cannot restore persisted records: record manager not available");return}let e=this.recordManager.getRedis();if(!e){l.warn("Cannot restore records: Redis not available");return}try{if(l.info("Restoring persisted records..."),this.recordPatterns.length===0){l.info("No record patterns to restore");return}l.info(`Found ${this.recordPatterns.length} record patterns to restore`);for(let r of this.recordPatterns){l.info(`Config keys: ${Object.keys(r).join(", ")}`),l.info(`Config.hooks: ${typeof r.hooks}, Config.adapter: ${typeof r.adapter}`),l.info(`Config.pattern: ${r.pattern}`);let{adapter:s,hooks:n}=r,i=n?"(custom hooks)":s?.restorePattern;try{let o=[];if(n?o=await n.restore():s&&(o=(s.adapter.getRecords?await s.adapter.getRecords(s.restorePattern):[]).map(d=>({recordId:d.recordId,value:typeof d.value=="string"?JSON.parse(d.value):d.value,version:d.version}))),o.length>0){l.info(`Restoring ${o.length} records for pattern ${i}`);for(let c of o)try{let{recordId:d,value:p,version:h}=c,u=this.recordManager.recordKey(d),v=this.recordManager.recordVersionKey(d),$=e.pipeline();$.set(u,JSON.stringify(p)),$.set(v,h.toString()),await $.exec(),l.debug(`Restored record ${d} (version ${h})`)}catch(d){l.error(`Failed to restore record ${c.recordId}: ${d}`)}}else l.debug(`No records found for pattern ${i}`)}catch(o){l.error(`Error restoring records for pattern ${i}: ${o}`)}}l.info("Finished restoring persisted records")}catch(r){l.error("Failed to restore persisted records:",r)}}handleStreamMessage(e){let{channel:r,message:s,instanceId:n,timestamp:i}=e;this.handleChannelMessage(r,s,n,i)}enableChannelPersistence(e,r={}){let s={historyLimit:r.historyLimit??50,filter:r.filter??(()=>!0),adapter:r.adapter??this.defaultAdapter,flushInterval:r.flushInterval??500,maxBufferSize:r.maxBufferSize??100};s.adapter!==this.defaultAdapter&&!this.isShuttingDown&&s.adapter.initialize().catch(n=>{l.error(`Failed to initialize adapter for pattern ${e}:`,n)}),this.channelPatterns.push({pattern:e,options:s})}enableRecordPersistence(e){l.info(`enableRecordPersistence called with config keys: ${Object.keys(e).join(", ")}`),l.info(`config.hooks type: ${typeof e.hooks}, config.adapter type: ${typeof e.adapter}`);let{pattern:r,adapter:s,hooks:n,flushInterval:i,maxBufferSize:o}=e;if(s&&n)throw new Error("Cannot use both adapter and hooks. Choose one.");let c;if(s){let d=s.adapter??this.defaultAdapter;c={adapter:d,restorePattern:s.restorePattern},d!==this.defaultAdapter&&!this.isShuttingDown&&d.initialize().catch(p=>{l.error(`Failed to initialize adapter for record pattern ${r}:`,p)})}this.recordPatterns.push({pattern:r,adapter:c,hooks:n,flushInterval:i??500,maxBufferSize:o??100})}getChannelPersistenceOptions(e){for(let{pattern:r,options:s}of this.channelPatterns)if(typeof r=="string"&&r===e||r instanceof RegExp&&r.test(e))return s}getRecordPersistenceConfig(e){for(let r of this.recordPatterns){let{pattern:s}=r;if(typeof s=="string"&&s===e||s instanceof RegExp&&s.test(e))return r}}handleChannelMessage(e,r,s,n){if(!this.initialized||this.isShuttingDown)return;let i=this.getChannelPersistenceOptions(e);if(!i||!i.filter(r,e))return;let o={id:(0,Ie.v4)(),channel:e,message:r,instanceId:s,timestamp:n||Date.now()};if(this.messageBuffer.has(e)||this.messageBuffer.set(e,[]),this.messageBuffer.get(e).push(o),this.messageBuffer.get(e).length>=i.maxBufferSize){this.flushChannel(e);return}if(!this.flushTimers.has(e)){let c=setTimeout(()=>{this.flushChannel(e)},i.flushInterval);c.unref&&c.unref(),this.flushTimers.set(e,c)}}async flushChannel(e){if(!this.messageBuffer.has(e))return;this.flushTimers.has(e)&&(clearTimeout(this.flushTimers.get(e)),this.flushTimers.delete(e));let r=this.messageBuffer.get(e);if(r.length===0)return;this.messageBuffer.set(e,[]);let s=this.getChannelPersistenceOptions(e);if(s)try{await s.adapter.storeMessages(r),this.emit("flushed",{channel:e,count:r.length}),l.debug(`Flushed ${r.length} messages for channel ${e}`)}catch(n){if(l.error(`Failed to flush messages for channel ${e}:`,n),!this.isShuttingDown){let i=this.messageBuffer.get(e)||[];if(this.messageBuffer.set(e,[...r,...i]),!this.flushTimers.has(e)){let o=setTimeout(()=>{this.flushChannel(e)},1e3);o.unref&&o.unref(),this.flushTimers.set(e,o)}}}}async flushAll(){let e=Array.from(this.messageBuffer.keys());for(let r of e)await this.flushChannel(r)}async getMessages(e,r,s){if(!this.initialized)throw new Error("Persistence manager not initialized");let n=this.getChannelPersistenceOptions(e);if(!n)throw new Error(`Channel ${e} does not have persistence enabled`);return await this.flushChannel(e),n.adapter.getMessages(e,r,s||n.historyLimit)}handleRecordUpdate(e,r,s){if(this.isShuttingDown)return;if(!this.initialized){this.pendingRecordUpdates.push({recordId:e,value:r,version:s}),l.debug(`Buffered record update for ${e} (pending initialization)`);return}let n=this.getRecordPersistenceConfig(e);if(!n)return;let i={recordId:e,value:JSON.stringify(r),version:s,timestamp:Date.now()};if(this.recordBuffer.set(e,i),l.debug(`Added record ${e} to buffer, buffer size: ${this.recordBuffer.size}`),this.recordBuffer.size>=n.maxBufferSize){l.debug(`Buffer size ${this.recordBuffer.size} exceeds limit ${n.maxBufferSize}, flushing records`),this.flushRecords();return}this.recordFlushTimer||(l.debug(`Scheduling record flush in ${n.flushInterval}ms`),this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},n.flushInterval),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}async flushRecords(){if(this.recordBuffer.size===0)return;l.debug(`Flushing ${this.recordBuffer.size} records to storage`),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null);let e=Array.from(this.recordBuffer.values());this.recordBuffer.clear();let r=new Map,s=new Map;for(let i of e){let o=this.getRecordPersistenceConfig(i.recordId);o&&(o.hooks?(s.has(o.hooks.persist)||s.set(o.hooks.persist,[]),s.get(o.hooks.persist).push(i)):o.adapter&&(r.has(o.adapter.adapter)||r.set(o.adapter.adapter,[]),r.get(o.adapter.adapter).push(i)))}let n=(i,o)=>{if(l.error("Failed to flush records:",o),!this.isShuttingDown){for(let c of i)this.recordBuffer.set(c.recordId,c);this.recordFlushTimer||(this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},1e3),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}};for(let[i,o]of s.entries())try{let c=o.map(d=>({recordId:d.recordId,value:JSON.parse(d.value),version:d.version}));l.debug(`Storing ${o.length} records with custom persist hook`),await i(c),this.emit("recordsFlushed",{count:o.length})}catch(c){n(o,c)}for(let[i,o]of r.entries())try{i.storeRecords?(l.debug(`Storing ${o.length} records with adapter`),await i.storeRecords(o),this.emit("recordsFlushed",{count:o.length})):l.warn("Adapter does not support storing records")}catch(c){n(o,c)}}async getPersistedRecords(e){if(!this.initialized)throw new Error("Persistence manager not initialized");await this.flushRecords();try{let r=this.defaultAdapter;if(r.getRecords)return await r.getRecords(e)}catch(r){l.error(`Failed to get persisted records for pattern ${e}:`,r)}return[]}async shutdown(){if(this.isShuttingDown)return;this.isShuttingDown=!0,this.messageStream.unsubscribeFromMessages(this.handleStreamMessage.bind(this));for(let r of this.flushTimers.values())clearTimeout(r);this.flushTimers.clear(),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null),await this.flushAll(),await this.flushRecords();let e=new Set([this.defaultAdapter]);for(let{options:r}of this.channelPatterns)e.add(r.adapter);for(let r of this.recordPatterns)r.adapter&&e.add(r.adapter.adapter);for(let r of e)try{await r.close()}catch(s){l.error("Error closing persistence adapter:",s)}this.initialized=!1}};var re=new WeakMap,W=class extends Oe.WebSocketServer{constructor(e){let r={...e};e.authenticateConnection&&(r.verifyClient=(s,n)=>{Promise.resolve().then(()=>e.authenticateConnection(s.req)).then(i=>{i!=null?(re.set(s.req,i),n(!0)):n(!1,401,"Unauthorized")}).catch(i=>{let o=i?.code??401,c=i?.message??"Unauthorized";n(!1,o,c)})});super(r);this.persistenceManager=null;this.status=y.OFFLINE;this._listening=!1;this.authenticateConnection=e.authenticateConnection,this.instanceId=(0,Te.v4)(),this.serverOptions={...e,pingInterval:e.pingInterval??3e4,latencyInterval:e.latencyInterval??5e3,maxMissedPongs:e.maxMissedPongs??1,logLevel:e.logLevel??Q.ERROR},l.configure({level:this.serverOptions.logLevel,styling:!1}),this.redisManager=new J,this.redisManager.initialize(e.redisOptions,s=>this.emit("error",s)),this.instanceManager=new X({redis:this.redisManager.redis,instanceId:this.instanceId}),this.roomManager=new T({redis:this.redisManager.redis}),this.recordManager=new I({redis:this.redisManager.redis,server:this}),this.connectionManager=new E({redis:this.redisManager.pubClient,instanceId:this.instanceId,roomManager:this.roomManager}),this.presenceManager=new S({redis:this.redisManager.redis,roomManager:this.roomManager,redisManager:this.redisManager,enableExpirationEvents:this.serverOptions.enablePresenceExpirationEvents}),this.serverOptions.enablePresenceExpirationEvents&&this.redisManager.enableKeyspaceNotifications().catch(s=>this.emit("error",new Error(`Failed to enable keyspace notifications: ${s}`))),this.commandManager=new z,this.persistenceManager=new N({defaultAdapterOptions:this.serverOptions.persistenceOptions,adapterType:this.serverOptions.persistenceAdapter}),this.persistenceManager.initialize().catch(s=>{this.emit("error",new Error(`Failed to initialize persistence manager: ${s}`))}),this.channelManager=new U({redis:this.redisManager.redis,pubClient:this.redisManager.pubClient,subClient:this.redisManager.subClient}),this.channelManager.setPersistenceManager(this.persistenceManager),this.recordSubscriptionManager=new q({pubClient:this.redisManager.pubClient,recordManager:this.recordManager,emitError:s=>this.emit("error",s),persistenceManager:this.persistenceManager}),this.collectionManager=new G({redis:this.redisManager.redis,emitError:s=>this.emit("error",s)}),this.persistenceManager&&this.persistenceManager.setRecordManager(this.recordManager),this.recordManager.onRecordUpdate(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record update for collection check: ${n}`))}}),this.recordManager.onRecordRemoved(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record removal for collection check: ${n}`))}}),this.pubSubManager=new B({subClient:this.redisManager.subClient,instanceId:this.instanceId,connectionManager:this.connectionManager,recordManager:this.recordManager,recordSubscriptions:this.recordSubscriptionManager.getRecordSubscriptions(),getChannelSubscriptions:this.channelManager.getSubscribers.bind(this.channelManager),emitError:s=>this.emit("error",s),collectionManager:this.collectionManager,pubClient:this.redisManager.pubClient}),this.broadcastManager=new D({connectionManager:this.connectionManager,roomManager:this.roomManager,instanceId:this.instanceId,pubClient:this.redisManager.pubClient,getPubSubChannel:s=>`${M}${s}`,emitError:s=>this.emit("error",s)}),this.on("listening",()=>{this.listening=!0,this.instanceManager.start()}),this.on("error",s=>{l.error(`Error: ${s}`)}),this.on("close",()=>{this.listening=!1}),this.pubSubManager.subscribeToInstanceChannel(),this.registerBuiltinCommands(),this.registerRecordCommands(),this.applyListeners()}get listening(){return this._listening}set listening(e){this._listening=e,this.status=e?y.ONLINE:y.OFFLINE}get port(){return this.address().port}async ready(){let e=this.listening?Promise.resolve():new Promise(s=>this.once("listening",s)),r=this.persistenceManager?this.persistenceManager.ready():Promise.resolve();await Promise.all([e,this.pubSubManager.getSubscriptionPromise(),r]),this.persistenceManager&&await this.persistenceManager.restorePersistedRecords()}applyListeners(){this.on("connection",async(e,r)=>{let s=new R(e,r,this.serverOptions,this);s.on("message",n=>{try{let i=n.toString(),o=F(i);o.id!==void 0&&!["latency:response","pong"].includes(o.command)&&this.commandManager.runCommand(o.id,o.command,o.payload,s,this)}catch(i){this.emit("error",i)}});try{await this.connectionManager.registerConnection(s);let n=re.get(r);n&&(re.delete(r),await this.connectionManager.setMetadata(s,n)),s.send({command:"mesh/assign-id",payload:s.id})}catch{s.close();return}this.emit("connected",s),s.on("close",async()=>{await this.cleanupConnection(s),this.emit("disconnected",s)}),s.on("error",n=>{this.emit("clientError",n,s)}),s.on("pong",async n=>{try{let i=await this.roomManager.getRoomsForConnection(n);for(let o of i)await this.presenceManager.isRoomTracked(o)&&await this.presenceManager.refreshPresence(n,o)}catch(i){this.emit("error",new Error(`Failed to refresh presence: ${i}`))}})})}exposeCommand(e,r,s=[]){this.commandManager.exposeCommand(e,r,s)}useMiddleware(...e){this.commandManager.useMiddleware(...e)}useMiddlewareWithCommand(e,r){this.commandManager.useMiddlewareWithCommand(e,r)}exposeChannel(e,r){this.channelManager.exposeChannel(e,r)}async writeChannel(e,r,s=0){return this.channelManager.writeChannel(e,r,s,this.instanceId)}enableChannelPersistence(e,r={}){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableChannelPersistence(e,r)}enableRecordPersistence(e){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableRecordPersistence(e)}exposeRecord(e,r){this.recordSubscriptionManager.exposeRecord(e,r)}exposeWritableRecord(e,r){this.recordSubscriptionManager.exposeWritableRecord(e,r)}async writeRecord(e,r,s){return this.recordSubscriptionManager.writeRecord(e,r,s)}async getRecord(e){return this.recordManager.getRecord(e)}async deleteRecord(e){let r=await this.recordManager.deleteRecord(e);r&&await this.recordSubscriptionManager.publishRecordDeletion(e,r.version)}async listRecordsMatching(e,r){return this.collectionManager.listRecordsMatching(e,r)}exposeCollection(e,r){this.collectionManager.exposeCollection(e,r)}async isInRoom(e,r){let s=typeof r=="string"?r:r.id;return this.roomManager.connectionIsInRoom(e,s)}async addToRoom(e,r){let s=typeof r=="string"?r:r.id;await this.roomManager.addToRoom(e,r),await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOnline(s,e)}async removeFromRoom(e,r){let s=typeof r=="string"?r:r.id;return await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOffline(s,e),this.roomManager.removeFromRoom(e,r)}async removeFromAllRooms(e){return this.roomManager.removeFromAllRooms(e)}async clearRoom(e){return this.roomManager.clearRoom(e)}async deleteRoom(e){return this.roomManager.deleteRoom(e)}async getRoomMembers(e){return this.roomManager.getRoomConnectionIds(e)}async getRoomMembersWithMetadata(e){let r=await this.roomManager.getRoomConnectionIds(e);return Promise.all(r.map(async s=>{try{let n=this.connectionManager.getLocalConnection(s),i;if(n)i=await this.connectionManager.getMetadata(n);else{let o=await this.redisManager.redis.hget("mesh:connections",s);i=o?JSON.parse(o):null}return{id:s,metadata:i}}catch{return{id:s,metadata:null}}}))}async getAllRooms(){return this.roomManager.getAllRooms()}async broadcast(e,r,s){return this.broadcastManager.broadcast(e,r,s)}async broadcastRoom(e,r,s){return this.broadcastManager.broadcastRoom(e,r,s)}async broadcastExclude(e,r,s){return this.broadcastManager.broadcastExclude(e,r,s)}async broadcastRoomExclude(e,r,s,n){return this.broadcastManager.broadcastRoomExclude(e,r,s,n)}trackPresence(e,r){this.presenceManager.trackRoom(e,r)}registerBuiltinCommands(){this.exposeCommand("mesh/noop",async()=>!0),this.exposeCommand("mesh/subscribe-channel",async e=>{let{channel:r,historyLimit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.channelManager.getSubscribers(r)||await this.channelManager.subscribeToRedisChannel(r),this.channelManager.addSubscription(r,e.connection),{success:!0,history:s&&s>0?await this.channelManager.getChannelHistory(r,s,n):[]}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/unsubscribe-channel",async e=>{let{channel:r}=e.payload,s=this.channelManager.removeSubscription(r,e.connection);return s&&!this.channelManager.getSubscribers(r)&&await this.channelManager.unsubscribeFromRedisChannel(r),s}),this.exposeCommand("mesh/get-channel-history",async e=>{let{channel:r,limit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.persistenceManager?.getChannelPersistenceOptions(r)?{success:!0,history:(await this.persistenceManager.getMessages(r,n,s||this.persistenceManager.getChannelPersistenceOptions(r)?.historyLimit)).map(o=>o.message)}:{success:!0,history:await this.channelManager.getChannelHistory(r,s||50,n)}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/join-room",async e=>{let{roomName:r}=e.payload;return await this.addToRoom(r,e.connection),{success:!0,present:await this.getRoomMembersWithMetadata(r)}}),this.exposeCommand("mesh/leave-room",async e=>{let{roomName:r}=e.payload;return await this.removeFromRoom(r,e.connection),{success:!0}}),this.exposeCommand("mesh/get-connection-metadata",async e=>{let{connectionId:r}=e.payload,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/get-my-connection-metadata",async e=>{let r=e.connection.id,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/set-my-connection-metadata",async e=>{let{metadata:r,options:s}=e.payload,n=e.connection.id,i=this.connectionManager.getLocalConnection(n);if(i)try{return await this.connectionManager.setMetadata(i,r,s),{success:!0}}catch{return{success:!1}}else return{success:!1}}),this.exposeCommand("mesh/get-room-metadata",async e=>{let{roomName:r}=e.payload;return{metadata:await this.roomManager.getMetadata(r)}})}registerRecordCommands(){this.exposeCommand("mesh/subscribe-record",async e=>{let{recordId:r,mode:s="full"}=e.payload,n=e.connection.id;if(!await this.recordSubscriptionManager.isRecordExposed(r,e.connection))return{success:!1};try{let{record:i,version:o}=await this.recordManager.getRecordAndVersion(r);return this.recordSubscriptionManager.addSubscription(r,n,s),{success:!0,record:i,version:o}}catch(i){return console.error(`Failed to subscribe to record ${r}:`,i),{success:!1}}}),this.exposeCommand("mesh/unsubscribe-record",async e=>{let{recordId:r}=e.payload,s=e.connection.id;return this.recordSubscriptionManager.removeSubscription(r,s)}),this.exposeCommand("mesh/publish-record-update",async e=>{let{recordId:r,newValue:s,options:n}=e.payload;if(!await this.recordSubscriptionManager.isRecordWritable(r,e.connection))throw new Error(`Record "${r}" is not writable by this connection.`);try{return await this.writeRecord(r,s,n),{success:!0}}catch(i){throw new Error(`Failed to publish update for record "${r}": ${i.message}`)}}),this.exposeCommand("mesh/subscribe-presence",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=`mesh:presence:updates:${r}`;this.channelManager.addSubscription(s,e.connection),(!this.channelManager.getSubscribers(s)||this.channelManager.getSubscribers(s)?.size===1)&&await this.channelManager.subscribeToRedisChannel(s);let n=await this.getRoomMembersWithMetadata(r),i=await this.presenceManager.getAllPresenceStates(r),o={};return i.forEach((c,d)=>{o[d]=c}),{success:!0,present:n,states:o}}catch(s){return console.error(`Failed to subscribe to presence for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/unsubscribe-presence",async e=>{let{roomName:r}=e.payload,s=`mesh:presence:updates:${r}`;return this.channelManager.removeSubscription(s,e.connection)}),this.exposeCommand("mesh/publish-presence-state",async e=>{let{roomName:r,state:s,expireAfter:n,silent:i}=e.payload,o=e.connection.id;if(!s||!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,o))return!1;try{return await this.presenceManager.publishPresenceState(o,r,s,n,i),!0}catch(c){return console.error(`Failed to publish presence state for room ${r}:`,c),!1}}),this.exposeCommand("mesh/clear-presence-state",async e=>{let{roomName:r}=e.payload,s=e.connection.id;if(!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,s))return!1;try{return await this.presenceManager.clearPresenceState(s,r),!0}catch(n){return console.error(`Failed to clear presence state for room ${r}:`,n),!1}}),this.exposeCommand("mesh/get-presence-state",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=await this.presenceManager.getPresentConnections(r),n=await this.presenceManager.getAllPresenceStates(r),i={};return n.forEach((o,c)=>{i[c]=o}),{success:!0,present:s,states:i}}catch(s){return console.error(`Failed to get presence state for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/subscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;if(!await this.collectionManager.isCollectionExposed(r,e.connection))return{success:!1,ids:[],records:[],version:0};try{let{ids:n,records:i,version:o}=await this.collectionManager.addSubscription(r,s,e.connection),c=i.map(d=>({id:d.id,record:d}));return{success:!0,ids:n,records:c,version:o}}catch(n){return console.error(`Failed to subscribe to collection ${r}:`,n),{success:!1,ids:[],records:[],version:0}}}),this.exposeCommand("mesh/unsubscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;return this.collectionManager.removeSubscription(r,s)})}async cleanupConnection(e){l.info("Cleaning up connection:",e.id),e.stopIntervals();try{await this.presenceManager.cleanupConnection(e),await this.connectionManager.cleanupConnection(e),await this.roomManager.cleanupConnection(e),this.recordSubscriptionManager.cleanupConnection(e),this.channelManager.cleanupConnection(e),await this.collectionManager.cleanupConnection(e)}catch(r){this.emit("error",new Error(`Failed to clean up connection: ${r}`))}}async close(e){this.redisManager.isShuttingDown=!0;let r=Object.values(this.connectionManager.getLocalConnections());if(await Promise.all(r.map(async s=>{s.isDead||await s.close(),await this.cleanupConnection(s)})),await new Promise((s,n)=>{super.close(i=>{i?n(i):s()})}),this.persistenceManager)try{await this.persistenceManager.shutdown()}catch(s){l.error("Error shutting down persistence manager:",s)}await this.instanceManager.stop(),await this.pubSubManager.cleanup(),await this.presenceManager.cleanup(),this.redisManager.disconnect(),this.listening=!1,this.removeAllListeners(),e&&e()}onConnection(e){return this.on("connected",e),this}onDisconnection(e){return this.on("disconnected",e),this}onRecordUpdate(e){return this.recordManager.onRecordUpdate(e)}onRecordRemoved(e){return this.recordManager.onRecordRemoved(e)}};0&&(module.exports={Connection,ConnectionManager,MeshContext,MeshServer,MessageStream,PersistenceManager,PresenceManager,RecordManager,RoomManager});
|
package/dist/index.mjs
CHANGED
|
@@ -1,29 +1,4 @@
|
|
|
1
|
-
import{v4 as Ee}from"uuid";import{WebSocketServer as Se}from"ws";var ee=class extends Error{constructor(c,t,e){super(c),typeof t=="string"&&(this.code=t),this.name=typeof e=="string"?e:"CodeError"}},V=(c=>(c[c.NONE=0]="NONE",c[c.ERROR=1]="ERROR",c[c.WARN=2]="WARN",c[c.INFO=3]="INFO",c[c.DEBUG=4]="DEBUG",c))(V||{}),Z=typeof window<"u"&&typeof window.document<"u",te=class{constructor(c){this.config={level:c?.level??3,prefix:c?.prefix??"[mesh]",styling:c?.styling??Z}}configure(c){this.config={...this.config,...c}}info(...c){this.config.level>=3&&this.log("log",...c)}warn(...c){this.config.level>=2&&this.log("warn",...c)}error(...c){this.config.level>=1&&this.log("error",...c)}debug(...c){this.config.level>=4&&this.log("debug",...c)}log(c,...t){if(this.config.styling&&Z){let e={prefix:"background: #000; color: #FFA07A; padding: 2px 4px; border-radius: 2px;",reset:""};console[c](`%c${this.config.prefix}%c`,e.prefix,e.reset,...t)}else console[c](this.config.prefix,...t)}},Ie=new te({level:1,styling:!0}),l=new te({level:1,styling:!1});function b(c,t){if(!g(c)||!g(t))return t;let e={...c};for(let r in t)t.hasOwnProperty(r)&&(g(t[r])&&g(c[r])?e[r]=b(c[r],t[r]):e[r]=t[r]);return e}function g(c){return c!==null&&typeof c=="object"&&!Array.isArray(c)}function _(c){try{return JSON.parse(c)}catch{return{command:"",payload:{}}}}function re(c){return JSON.stringify(c)}var f=(c=>(c[c.ONLINE=3]="ONLINE",c[c.CONNECTING=2]="CONNECTING",c[c.RECONNECTING=1]="RECONNECTING",c[c.OFFLINE=0]="OFFLINE",c))(f||{});import{EventEmitter as me}from"events";import{WebSocket as ge}from"ws";var F=class{constructor(){this.start=0;this.end=0;this.ms=0}onRequest(){this.start=Date.now()}onResponse(){this.end=Date.now(),this.ms=this.end-this.start}};var k=class{};import{getRandomValues as le}from"crypto";var W=[];for(let c=0;c<256;c++)W[c]=(c+256).toString(16).substring(1);function he(c,t){let e=`000000${c}`;return e.substring(e.length-t)}var pe=32;function se(c){let t=c.len||16,e="",r=0,s=1679616,n=c.init+Math.ceil(s/2);function i(){return n>=s&&(n=0),n++,(n-1).toString(16)}return()=>{if(!e||r===256){let h=new Uint8Array(t);le(h),e=Array.from(h,u=>W[u]).join("").substring(0,t),r=0}let o=Date.now().toString(36),a=he(i(),6),d=W[r++],p=parseInt(d,16)%pe;return`conn-${o}${a}${d}${e}${p}`}}var ue=se({init:Date.now(),len:4}),E=class extends me{constructor(e,r,s,n){super();this.alive=!0;this.missedPongs=0;this.status=f.ONLINE;this.socket=e,this.id=ue(),this.remoteAddress=r.socket.remoteAddress,this.connectionOptions=s,this.server=n,this.applyListeners(),this.startIntervals()}get isDead(){return!this.socket||this.socket.readyState!==ge.OPEN}startIntervals(){this.latency=new F,this.ping=new k,this.latency.interval=setInterval(()=>{this.alive&&(typeof this.latency.ms=="number"&&this.send({command:"latency",payload:this.latency.ms}),this.latency.onRequest(),this.send({command:"latency:request",payload:{}}))},this.connectionOptions.latencyInterval),this.ping.interval=setInterval(()=>{if(this.alive)this.missedPongs=0;else{this.missedPongs++;let e=this.connectionOptions.maxMissedPongs??1;if(this.missedPongs>e){l.info(`Closing connection (${this.id}) due to missed pongs`),this.close(),this.server.cleanupConnection(this);return}}this.alive=!1,this.send({command:"ping",payload:{}})},this.connectionOptions.pingInterval)}stopIntervals(){clearInterval(this.latency.interval),clearInterval(this.ping.interval)}applyListeners(){this.socket.on("close",()=>{l.info("Client's socket closed:",this.id),this.status=f.OFFLINE,this.emit("close")}),this.socket.on("error",e=>{this.emit("error",e)}),this.socket.on("message",e=>{try{let r=_(e.toString());if(r.command==="latency:response"){this.latency.onResponse();return}else if(r.command==="pong"){this.alive=!0,this.missedPongs=0,this.emit("pong",this.id);return}this.emit("message",e)}catch(r){this.emit("error",r)}})}send(e){if(this.isDead)return!1;try{return this.socket.send(re(e)),!0}catch(r){return this.emit("error",r),!1}}async close(){if(this.isDead)return!1;try{return await new Promise((e,r)=>{this.socket.once("close",e),this.socket.once("error",r),this.socket.close()}),!0}catch(e){return this.emit("error",e),!1}}};var v="mesh:pubsub:",C="mesh:record-updates",ne="mesh:record:",ie="mesh:record-version:";var M="mesh:connections",fe="mesh:connections:",S=class{constructor(t){this.localConnections={};this.redis=t.redis,this.instanceId=t.instanceId,this.roomManager=t.roomManager}getLocalConnections(){return Object.values(this.localConnections)}getLocalConnection(t){return this.localConnections[t]??null}async registerConnection(t){this.localConnections[t.id]=t;let e=this.redis.pipeline();e.hset(M,t.id,this.instanceId),e.sadd(this.getInstanceConnectionsKey(this.instanceId),t.id),await e.exec()}getInstanceConnectionsKey(t){return`${fe}${t}`}async deregisterConnection(t){let e=await this.getInstanceIdForConnection(t);if(!e)return;let r=this.redis.pipeline();r.hdel(M,t.id),r.srem(this.getInstanceConnectionsKey(e),t.id),await r.exec()}async getInstanceIdForConnection(t){return this.redis.hget(M,t.id)}async getInstanceIdsForConnections(t){if(t.length===0)return{};let e=await this.redis.hmget(M,...t),r={};return t.forEach((s,n)=>{r[s]=e[n]??null}),r}async getAllConnectionIds(){return this.redis.hkeys(M)}async getLocalConnectionIds(){return this.redis.smembers(this.getInstanceConnectionsKey(this.instanceId))}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let o=await this.getMetadata(t);n==="merge"?g(o)&&g(e)?s={...o,...e}:s=e:n==="deepMerge"&&(g(o)&&g(e)?s=b(o,e):s=e)}let i=this.redis.pipeline();i.hset(M,t.id,JSON.stringify(s)),await i.exec()}async getMetadata(t){let e=await this.redis.hget(M,t.id);return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.getAllConnectionIds(),e=await this.getInstanceIdsForConnections(t),r=[];return t.forEach(s=>{try{let n=e[s]?JSON.parse(e[s]):null;r.push({id:s,metadata:n})}catch(n){console.error(`Failed to parse metadata for connection ${s}:`,n),r.push({id:s,metadata:null})}}),r}async getAllMetadataForRoom(t){let e=await this.roomManager.getRoomConnectionIds(t),r=await this.getInstanceIdsForConnections(e),s=[];return e.forEach(n=>{try{let i=r[n]?JSON.parse(r[n]):null;s.push({id:n,metadata:i})}catch(i){console.error(`Failed to parse metadata for connection ${n}:`,i),s.push({id:n,metadata:null})}}),s}async cleanupConnection(t){await this.deregisterConnection(t)}};var I=class{constructor(t){this.PRESENCE_KEY_PATTERN=/^mesh:presence:room:(.+):conn:(.+)$/;this.PRESENCE_STATE_KEY_PATTERN=/^mesh:presence:state:(.+):conn:(.+)$/;this.trackedRooms=[];this.roomGuards=new Map;this.roomTTLs=new Map;this.defaultTTL=0;this.redis=t.redis,this.roomManager=t.roomManager,this.redisManager=t.redisManager,this.presenceExpirationEventsEnabled=t.enableExpirationEvents??!0,this.presenceExpirationEventsEnabled&&this.subscribeToExpirationEvents()}getExpiredEventsPattern(){return`__keyevent@${this.redis.options?.db??0}__:expired`}subscribeToExpirationEvents(){let{subClient:t}=this.redisManager,e=this.getExpiredEventsPattern();t.psubscribe(e),t.on("pmessage",(r,s,n)=>{(this.PRESENCE_KEY_PATTERN.test(n)||this.PRESENCE_STATE_KEY_PATTERN.test(n))&&this.handleExpiredKey(n)})}async handleExpiredKey(t){try{let e=t.match(this.PRESENCE_KEY_PATTERN);if(e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.markOffline(s,r);return}if(e=t.match(this.PRESENCE_STATE_KEY_PATTERN),e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.publishPresenceStateUpdate(r,s,null)}}catch(e){console.error("[PresenceManager] Failed to handle expired key:",e)}}trackRoom(t,e){this.trackedRooms.push(t),typeof e=="function"?this.roomGuards.set(t,e):e&&typeof e=="object"&&(e.guard&&this.roomGuards.set(t,e.guard),e.ttl&&typeof e.ttl=="number"&&this.roomTTLs.set(t,e.ttl))}async isRoomTracked(t,e){let r=this.trackedRooms.find(s=>typeof s=="string"?s===t:s.test(t));if(!r)return!1;if(e){let s=this.roomGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}}return!0}getRoomTTL(t){let e=this.trackedRooms.find(r=>typeof r=="string"?r===t:r.test(t));if(e){let r=this.roomTTLs.get(e);if(r!==void 0)return r}return this.defaultTTL}presenceRoomKey(t){return`mesh:presence:room:${t}`}presenceConnectionKey(t,e){return`mesh:presence:room:${t}:conn:${e}`}presenceStateKey(t,e){return`mesh:presence:state:${t}:conn:${e}`}async markOnline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.getRoomTTL(e),i=this.redis.pipeline();if(i.sadd(r,t),n>0){let o=Math.max(1,Math.floor(n/1e3));i.set(s,"","EX",o)}else i.set(s,"");await i.exec(),await this.publishPresenceUpdate(e,t,"join")}async markOffline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.presenceStateKey(e,t),i=this.redis.pipeline();i.srem(r,t),i.del(s),i.del(n),await i.exec(),await this.publishPresenceUpdate(e,t,"leave")}async refreshPresence(t,e){let r=this.presenceConnectionKey(e,t),s=this.getRoomTTL(e);if(s>0){let n=Math.max(1,Math.floor(s/1e3));await this.redis.set(r,"","EX",n)}else await this.redis.set(r,"")}async getPresentConnections(t){return this.redis.smembers(this.presenceRoomKey(t))}async publishPresenceUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:r,connectionId:e,roomName:t,timestamp:Date.now()});await this.redis.publish(s,n)}async publishPresenceState(t,e,r,s,n){let i=this.presenceStateKey(e,t),o=JSON.stringify(r),a=this.redis.pipeline();s&&s>0?a.set(i,o,"PX",s):a.set(i,o),await a.exec(),!n&&await this.publishPresenceStateUpdate(e,t,r)}async clearPresenceState(t,e){let r=this.presenceStateKey(e,t);await this.redis.del(r),await this.publishPresenceStateUpdate(e,t,null)}async getPresenceState(t,e){let r=this.presenceStateKey(e,t),s=await this.redis.get(r);if(!s)return null;try{return JSON.parse(s)}catch(n){return console.error(`[PresenceManager] Failed to parse presence state: ${n}`),null}}async getAllPresenceStates(t){let e=new Map,r=await this.getPresentConnections(t);if(r.length===0)return e;let s=this.redis.pipeline();for(let i of r)s.get(this.presenceStateKey(t,i));let n=await s.exec();if(!n)return e;for(let i=0;i<r.length;i++){let o=r[i];if(!o)continue;let[a,d]=n[i]||[];if(!(a||!d))try{let p=JSON.parse(d);e.set(o,p)}catch(p){console.error(`[PresenceManager] Failed to parse presence state: ${p}`)}}return e}async publishPresenceStateUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:"state",connectionId:e,roomName:t,state:r,timestamp:Date.now()});await this.redis.publish(s,n)}async cleanupConnection(t){let e=t.id,r=await this.roomManager.getRoomsForConnection(e);for(let s of r)await this.isRoomTracked(s)&&await this.markOffline(e,s)}async cleanup(){let{subClient:t}=this.redisManager;if(t&&t.status!=="end"){let e=this.getExpiredEventsPattern();await new Promise(r=>{t.punsubscribe(e,()=>r())})}}};import ye from"fast-json-patch";var T=class{constructor(t){this.recordUpdateCallbacks=[];this.recordRemovedCallbacks=[];this.redis=t.redis,this.server=t.server}getServer(){return this.server}getRedis(){return this.redis}recordKey(t){return`${ne}${t}`}recordVersionKey(t){return`${ie}${t}`}async getRecord(t){let e=await this.redis.get(this.recordKey(t));return e?JSON.parse(e):null}async getVersion(t){let e=await this.redis.get(this.recordVersionKey(t));return e?parseInt(e,10):0}async getRecordAndVersion(t){let e=this.redis.pipeline();e.get(this.recordKey(t)),e.get(this.recordVersionKey(t));let r=await e.exec(),s=r?.[0]?.[1],n=r?.[1]?.[1],i=s?JSON.parse(s):null,o=n?parseInt(n,10):0;return{record:i,version:o}}async publishUpdate(t,e,r="replace"){let s=this.recordKey(t),n=this.recordVersionKey(t),{record:i,version:o}=await this.getRecordAndVersion(t),a;r==="merge"?g(i)&&g(e)?a={...i,...e}:a=e:r==="deepMerge"&&g(i)&&g(e)?a=b(i,e):a=e;let d=ye.compare(i??{},a??{});if(d.length===0)return null;let p=o+1,h=this.redis.pipeline();return h.set(s,JSON.stringify(a)),h.set(n,p.toString()),await h.exec(),this.recordUpdateCallbacks.length>0&&Promise.all(this.recordUpdateCallbacks.map(async u=>{try{await u({recordId:t,value:a})}catch(y){console.error(`Error in record update callback for ${t}:`,y)}})).catch(u=>{console.error(`Error in record update callbacks for ${t}:`,u)}),{patch:d,version:p,finalValue:a}}async deleteRecord(t){let{record:e,version:r}=await this.getRecordAndVersion(t);if(!e)return null;let s=this.redis.pipeline();return s.del(this.recordKey(t)),s.del(this.recordVersionKey(t)),await s.exec(),this.recordRemovedCallbacks.length>0&&Promise.all(this.recordRemovedCallbacks.map(async n=>{try{await n({recordId:t,value:e})}catch(i){console.error(`Error in record removed callback for ${t}:`,i)}})).catch(n=>{console.error(`Error in record removed callbacks for ${t}:`,n)}),{version:r}}onRecordUpdate(t){return this.recordUpdateCallbacks.push(t),()=>{this.recordUpdateCallbacks=this.recordUpdateCallbacks.filter(e=>e!==t)}}onRecordRemoved(t){return this.recordRemovedCallbacks.push(t),()=>{this.recordRemovedCallbacks=this.recordRemovedCallbacks.filter(e=>e!==t)}}};var O=class{constructor(t){this.redis=t.redis}roomKey(t){return`mesh:room:${t}`}connectionsRoomKey(t){return`mesh:connection:${t}:rooms`}roomMetadataKey(t){return`mesh:roommeta:${t}`}async getRoomConnectionIds(t){return this.redis.smembers(this.roomKey(t))}async connectionIsInRoom(t,e){let r=typeof e=="string"?e:e.id;return!!await this.redis.sismember(this.roomKey(t),r)}async addToRoom(t,e){let r=typeof e=="string"?e:e.id;await this.redis.sadd(this.roomKey(t),r),await this.redis.sadd(this.connectionsRoomKey(r),t)}async getRoomsForConnection(t){let e=typeof t=="string"?t:t.id;return await this.redis.smembers(this.connectionsRoomKey(e))}async getAllRooms(){return(await this.redis.keys("mesh:room:*")).map(e=>e.replace("mesh:room:",""))}async removeFromRoom(t,e){let r=typeof e=="string"?e:e.id,s=this.redis.pipeline();s.srem(this.roomKey(t),r),s.srem(this.connectionsRoomKey(r),t),await s.exec()}async removeFromAllRooms(t){let e=typeof t=="string"?t:t.id,r=await this.redis.smembers(this.connectionsRoomKey(e)),s=this.redis.pipeline();for(let n of r)s.srem(this.roomKey(n),e);s.del(this.connectionsRoomKey(e)),await s.exec()}async clearRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),await r.exec()}async deleteRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),r.del(this.roomMetadataKey(t)),await r.exec()}async cleanupConnection(t){let e=await this.redis.smembers(this.connectionsRoomKey(t.id)),r=this.redis.pipeline();for(let s of e)r.srem(this.roomKey(s),t.id);r.del(this.connectionsRoomKey(t.id)),await r.exec()}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let i=await this.getMetadata(t);n==="merge"?g(i)&&g(e)?s={...i,...e}:s=e:n==="deepMerge"&&(g(i)&&g(e)?s=b(i,e):s=e)}await this.redis.hset(this.roomMetadataKey(t),"data",JSON.stringify(s))}async getMetadata(t){let e=await this.redis.hget(this.roomMetadataKey(t),"data");return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.redis.keys("mesh:roommeta:*"),e=[];if(t.length===0)return e;let r=this.redis.pipeline();t.forEach(n=>r.hget(n,"data"));let s=await r.exec();return t.forEach((n,i)=>{let o=n.replace("mesh:roommeta:",""),a=s?.[i]?.[1];if(a)try{let d=JSON.parse(a);e.push({id:o,metadata:d})}catch(d){console.error(`Failed to parse metadata for room ${o}:`,d)}}),e}};var K=class{constructor(t){this.connectionManager=t.connectionManager,this.roomManager=t.roomManager,this.instanceId=t.instanceId,this.pubClient=t.pubClient,this.getPubSubChannel=t.getPubSubChannel,this.emitError=t.emitError}async broadcast(t,e,r){let s={command:t,payload:e};try{if(r){let n=r.map(({id:a})=>a),i=await this.connectionManager.getAllConnectionIds(),o=n.filter(a=>i.includes(a));await this.publishOrSend(o,s)}else{let n=await this.connectionManager.getAllConnectionIds();await this.publishOrSend(n,s)}}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoom(t,e,r){let s=await this.roomManager.getRoomConnectionIds(t);try{await this.publishOrSend(s,{command:e,payload:r})}catch(n){this.emitError(new Error(`Failed to broadcast command "${e}": ${n}`))}}async broadcastExclude(t,e,r){let s=new Set((Array.isArray(r)?r:[r]).map(({id:n})=>n));try{let n=(await this.connectionManager.getAllConnectionIds()).filter(i=>!s.has(i));await this.publishOrSend(n,{command:t,payload:e})}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoomExclude(t,e,r,s){let n=new Set((Array.isArray(s)?s:[s]).map(({id:i})=>i));try{let i=(await this.roomManager.getRoomConnectionIds(t)).filter(o=>!n.has(o));await this.publishOrSend(i,{command:e,payload:r})}catch(i){this.emitError(new Error(`Failed to broadcast command "${e}": ${i}`))}}async publishOrSend(t,e){if(t.length===0)return;let r=await this.connectionManager.getInstanceIdsForConnections(t),s={};for(let n of t){let i=r[n];i&&(s[i]||(s[i]=[]),s[i].push(n))}for(let[n,i]of Object.entries(s))if(i.length!==0)if(n===this.instanceId)i.forEach(o=>{let a=this.connectionManager.getLocalConnection(o);a&&!a.isDead&&a.send(e)});else{let a=JSON.stringify({targetConnectionIds:i,command:e});try{await this.pubClient.publish(this.getPubSubChannel(n),a)}catch(d){this.emitError(new Error(`Failed to publish command "${e.command}": ${d}`))}}}};import{EventEmitter as be}from"events";var P=class c extends be{static getInstance(){return c.instance||(c.instance=new c),c.instance}constructor(){super(),this.setMaxListeners(100)}publishMessage(t,e,r){let s=Date.now();this.emit("message",{channel:t,message:e,instanceId:r,timestamp:s})}subscribeToMessages(t){this.on("message",t)}unsubscribeFromMessages(t){this.off("message",t)}};var D=class{constructor(t){this.exposedChannels=[];this.channelGuards=new Map;this.channelSubscriptions={};this.redis=t.redis,this.pubClient=t.pubClient,this.subClient=t.subClient,this.messageStream=P.getInstance()}setPersistenceManager(t){this.persistenceManager=t}exposeChannel(t,e){this.exposedChannels.push(t),e&&this.channelGuards.set(t,e)}async isChannelExposed(t,e){let r=this.exposedChannels.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.channelGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}async writeChannel(t,e,r=0,s){let n=parseInt(r,10);!isNaN(n)&&n>0&&(await this.pubClient.rpush(`mesh:history:${t}`,e),await this.pubClient.ltrim(`mesh:history:${t}`,-n,-1)),this.messageStream.publishMessage(t,e,s),await this.pubClient.publish(t,e)}addSubscription(t,e){this.channelSubscriptions[t]||(this.channelSubscriptions[t]=new Set),this.channelSubscriptions[t].add(e)}removeSubscription(t,e){return this.channelSubscriptions[t]?(this.channelSubscriptions[t].delete(e),this.channelSubscriptions[t].size===0&&delete this.channelSubscriptions[t],!0):!1}getSubscribers(t){return this.channelSubscriptions[t]}async subscribeToRedisChannel(t){return new Promise((e,r)=>{this.subClient.subscribe(t,s=>{s?r(s):e()})})}async unsubscribeFromRedisChannel(t){return new Promise((e,r)=>{this.subClient.unsubscribe(t,s=>{s?r(s):e()})})}async getChannelHistory(t,e,r){if(this.persistenceManager&&r!==void 0)try{return(await this.persistenceManager.getMessages(t,r,e)).map(i=>i.message)}catch{let i=`mesh:history:${t}`;return this.redis.lrange(i,0,e-1)}let s=`mesh:history:${t}`;return this.redis.lrange(s,0,e-1)}async getPersistedMessages(t,e,r){if(!this.persistenceManager)throw new Error("Persistence not enabled");return this.persistenceManager.getMessages(t,e,r)}cleanupConnection(t){for(let e in this.channelSubscriptions)this.removeSubscription(e,t)}};var N=class{constructor(t,e,r,s){this.server=t,this.command=e,this.connection=r,this.payload=s}};var U=class{constructor(){this.commands={};this.globalMiddlewares=[];this.middlewares={}}exposeCommand(t,e,r=[]){this.commands[t]=e,r.length>0&&this.useMiddlewareWithCommand(t,r)}useMiddleware(...t){this.globalMiddlewares.push(...t)}useMiddlewareWithCommand(t,e){e.length&&(this.middlewares[t]=this.middlewares[t]||[],this.middlewares[t]=e.concat(this.middlewares[t]))}async runCommand(t,e,r,s,n){let i=new N(n,e,s,r);try{if(!this.commands[e])throw new ee(`Command "${e}" not found`,"ENOTFOUND","CommandError");if(this.globalMiddlewares.length)for(let a of this.globalMiddlewares)await a(i);if(this.middlewares[e])for(let a of this.middlewares[e])await a(i);let o=await this.commands[e](i);s.send({id:t,command:e,payload:o})}catch(o){let a=o instanceof Error?{error:o.message,code:o.code||"ESERVER",name:o.name||"Error"}:{error:String(o),code:"EUNKNOWN",name:"UnknownError"};s.send({id:t,command:e,payload:a})}}getCommands(){return this.commands}hasCommand(t){return!!this.commands[t]}};var z=class{constructor(t){this.collectionManager=null;this.collectionUpdateTimeouts=new Map;this.collectionMaxDelayTimeouts=new Map;this.pendingCollectionUpdates=new Map;this.COLLECTION_UPDATE_DEBOUNCE_MS=50;this.COLLECTION_MAX_DELAY_MS=200;this.subClient=t.subClient,this.pubClient=t.pubClient,this.instanceId=t.instanceId,this.connectionManager=t.connectionManager,this.recordManager=t.recordManager,this.recordSubscriptions=t.recordSubscriptions,this.getChannelSubscriptions=t.getChannelSubscriptions,this.emitError=t.emitError,this.collectionManager=t.collectionManager||null}subscribeToInstanceChannel(){let t=`${v}${this.instanceId}`;return this._subscriptionPromise=new Promise((e,r)=>{this.subClient.subscribe(t,C,"mesh:collection:record-change"),this.subClient.psubscribe("mesh:presence:updates:*",s=>{if(s){this.emitError(new Error(`Failed to subscribe to channels/patterns: ${JSON.stringify({cause:s})}`)),r(s);return}e()})}),this.setupMessageHandlers(),this._subscriptionPromise}setupMessageHandlers(){this.subClient.on("message",async(t,e)=>{if(t.startsWith(v))this.handleInstancePubSubMessage(t,e);else if(t===C)this.handleRecordUpdatePubSubMessage(e);else if(t==="mesh:collection:record-change")this.handleCollectionRecordChange(e);else{let r=this.getChannelSubscriptions(t);if(r)for(let s of r)s.isDead||s.send({command:"mesh/subscription-message",payload:{channel:t,message:e}})}}),this.subClient.on("pmessage",async(t,e,r)=>{if(t==="mesh:presence:updates:*"){let s=this.getChannelSubscriptions(e);if(s)try{let n=JSON.parse(r);s.forEach(i=>{i.isDead?s.delete(i):i.send({command:"mesh/presence-update",payload:n})})}catch{this.emitError(new Error(`Failed to parse presence update: ${r}`))}}})}handleInstancePubSubMessage(t,e){try{let r=JSON.parse(e);if(!r||!Array.isArray(r.targetConnectionIds)||!r.command||typeof r.command.command!="string")throw new Error("Invalid message format");let{targetConnectionIds:s,command:n}=r;s.forEach(i=>{let o=this.connectionManager.getLocalConnection(i);o&&!o.isDead&&o.send(n)})}catch{this.emitError(new Error(`Failed to parse message: ${e}`))}}handleRecordUpdatePubSubMessage(t){try{let e=JSON.parse(t),{recordId:r,newValue:s,patch:n,version:i,deleted:o}=e;if(!r||typeof i!="number")throw new Error("Invalid record update message format");let a=this.recordSubscriptions.get(r);if(!a)return;a.forEach((d,p)=>{let h=this.connectionManager.getLocalConnection(p);h&&!h.isDead?o?h.send({command:"mesh/record-deleted",payload:{recordId:r,version:i}}):d==="patch"&&n?h.send({command:"mesh/record-update",payload:{recordId:r,patch:n,version:i}}):d==="full"&&s!==void 0&&h.send({command:"mesh/record-update",payload:{recordId:r,full:s,version:i}}):(!h||h.isDead)&&(a.delete(p),a.size===0&&this.recordSubscriptions.delete(r))}),o&&this.recordSubscriptions.delete(r)}catch{this.emitError(new Error(`Failed to parse record update message: ${t}`))}}async handleCollectionRecordChange(t){if(!this.collectionManager)return;let e=this.collectionManager.getCollectionSubscriptions(),r=new Set;for(let[s]of e.entries())r.add(s);for(let s of r){let n=this.collectionUpdateTimeouts.get(s);n&&clearTimeout(n),this.pendingCollectionUpdates.has(s)||this.pendingCollectionUpdates.set(s,new Set),this.pendingCollectionUpdates.get(s).add(t);let i=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_UPDATE_DEBOUNCE_MS);if(this.collectionUpdateTimeouts.set(s,i),!this.collectionMaxDelayTimeouts.has(s)){let o=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_MAX_DELAY_MS);this.collectionMaxDelayTimeouts.set(s,o)}}}async processCollectionUpdates(t){let e=this.pendingCollectionUpdates.get(t);if(!e||e.size===0)return;let r=this.collectionUpdateTimeouts.get(t),s=this.collectionMaxDelayTimeouts.get(t);if(r&&(clearTimeout(r),this.collectionUpdateTimeouts.delete(t)),s&&(clearTimeout(s),this.collectionMaxDelayTimeouts.delete(t)),this.pendingCollectionUpdates.delete(t),!this.collectionManager)return;let n=this.collectionManager.getCollectionSubscriptions().get(t);if(!(!n||n.size===0))for(let[i,{version:o}]of n.entries())try{let a=this.connectionManager.getLocalConnection(i);if(!a||a.isDead)continue;let d=await this.collectionManager.resolveCollection(t,a),p=d.map(m=>m.id),h=`mesh:collection:${t}:${i}`,u=await this.pubClient.get(h),y=u?JSON.parse(u):[],w=p.filter(m=>!y.includes(m)),Q=d.filter(m=>w.includes(m.id)),oe=y.filter(m=>!p.includes(m)),L=[];for(let m of oe)try{let R=await this.recordManager.getRecord(m);L.push(R||{id:m})}catch{L.push({id:m})}let j=[];for(let m of e)y.includes(m)&&!p.includes(m)&&j.push(m);let ae=Q.length>0||L.length>0,ce=j.length>0;if(ae||ce){let m=o+1;this.collectionManager.updateSubscriptionVersion(t,i,m),await this.pubClient.set(h,JSON.stringify(p)),a.send({command:"mesh/collection-diff",payload:{collectionId:t,added:Q,removed:L,version:m}})}for(let m of e)if(p.includes(m))try{let{record:R,version:de}=await this.recordManager.getRecordAndVersion(m);R&&a.send({command:"mesh/record-update",payload:{recordId:m,version:de,full:R}})}catch{l.info(`Record ${m} not found during collection update (likely deleted).`)}}catch(a){this.emitError(new Error(`Error processing collection ${t} for connection ${i}: ${a}`))}}getSubscriptionPromise(){return this._subscriptionPromise}getPubSubChannel(t){return`${v}${t}`}async cleanup(){for(let t of this.collectionUpdateTimeouts.values())clearTimeout(t);this.collectionUpdateTimeouts.clear();for(let t of this.collectionMaxDelayTimeouts.values())clearTimeout(t);if(this.collectionMaxDelayTimeouts.clear(),this.pendingCollectionUpdates.clear(),this.subClient&&this.subClient.status!=="end"){let t=`${v}${this.instanceId}`;await Promise.all([new Promise(e=>{this.subClient.unsubscribe(t,C,"mesh:collection:record-change",()=>e())}),new Promise(e=>{this.subClient.punsubscribe("mesh:presence:updates:*",()=>e())})])}}};var B=class{constructor(t){this.persistenceManager=null;this.exposedRecords=[];this.exposedWritableRecords=[];this.recordGuards=new Map;this.writableRecordGuards=new Map;this.recordSubscriptions=new Map;this.pubClient=t.pubClient,this.recordManager=t.recordManager,this.emitError=t.emitError,this.persistenceManager=t.persistenceManager||null}setPersistenceManager(t){this.persistenceManager=t}exposeRecord(t,e){this.exposedRecords.push(t),e&&this.recordGuards.set(t,e)}exposeWritableRecord(t,e){this.exposedWritableRecords.push(t),e&&this.writableRecordGuards.set(t,e)}async isRecordExposed(t,e){let r=this.exposedRecords.find(i=>typeof i=="string"?i===t:i.test(t)),s=!1;if(r){let i=this.recordGuards.get(r);if(i)try{s=await Promise.resolve(i(e,t))}catch{s=!1}else s=!0}return!!(s||this.exposedWritableRecords.find(i=>typeof i=="string"?i===t:i.test(t)))}async isRecordWritable(t,e){let r=this.exposedWritableRecords.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.writableRecordGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}addSubscription(t,e,r){this.recordSubscriptions.has(t)||this.recordSubscriptions.set(t,new Map),this.recordSubscriptions.get(t).set(e,r)}removeSubscription(t,e){let r=this.recordSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.recordSubscriptions.delete(t),!0):!1}getSubscribers(t){return this.recordSubscriptions.get(t)}async writeRecord(t,e,r){let s=await this.recordManager.publishUpdate(t,e,r?.strategy||"replace");if(!s)return;let{patch:n,version:i,finalValue:o}=s;this.persistenceManager&&this.persistenceManager.handleRecordUpdate(t,o,i);let a={recordId:t,newValue:o,patch:n,version:i};try{await this.pubClient.publish(C,JSON.stringify(a))}catch(d){this.emitError(new Error(`Failed to publish record update for "${t}": ${d}`))}}cleanupConnection(t){let e=t.id;this.recordSubscriptions.forEach((r,s)=>{r.has(e)&&(r.delete(e),r.size===0&&this.recordSubscriptions.delete(s))})}async publishRecordDeletion(t,e){let r={recordId:t,deleted:!0,version:e};try{await this.pubClient.publish(C,JSON.stringify(r))}catch(s){this.emitError(new Error(`Failed to publish record deletion for "${t}": ${s}`))}}getRecordSubscriptions(){return this.recordSubscriptions}};import{Redis as ve}from"ioredis";var J=class{constructor(){this._redis=null;this._pubClient=null;this._subClient=null;this._isShuttingDown=!1}initialize(t,e){this._redis=new ve({retryStrategy:r=>this._isShuttingDown||r>10?null:Math.min(1e3*Math.pow(2,r),3e4),...t}),this._redis.on("error",r=>{e(new Error(`Redis error: ${r}`))}),this._pubClient=this._redis.duplicate(),this._subClient=this._redis.duplicate()}get redis(){if(!this._redis)throw new Error("Redis not initialized");return this._redis}get pubClient(){if(!this._pubClient)throw new Error("Redis pub client not initialized");return this._pubClient}get subClient(){if(!this._subClient)throw new Error("Redis sub client not initialized");return this._subClient}disconnect(){this._isShuttingDown=!0,this._pubClient&&(this._pubClient.disconnect(),this._pubClient=null),this._subClient&&(this._subClient.disconnect(),this._subClient=null),this._redis&&(this._redis.disconnect(),this._redis=null)}get isShuttingDown(){return this._isShuttingDown}set isShuttingDown(t){this._isShuttingDown=t}async enableKeyspaceNotifications(){let t=await this.redis.config("GET","notify-keyspace-events"),r=(Array.isArray(t)&&t.length>1?t[1]:"")||"";r.includes("E")||(r+="E"),r.includes("x")||(r+="x"),await this.redis.config("SET","notify-keyspace-events",r)}};var q=class{constructor(t){this.heartbeatInterval=null;this.heartbeatTTL=120;this.heartbeatFrequency=15e3;this.cleanupInterval=null;this.cleanupFrequency=6e4;this.cleanupLockTTL=10;this.redis=t.redis,this.instanceId=t.instanceId}async start(){await this.registerInstance(),await this.updateHeartbeat(),this.heartbeatInterval=setInterval(()=>this.updateHeartbeat(),this.heartbeatFrequency),this.cleanupInterval=setInterval(()=>this.performCleanup(),this.cleanupFrequency)}async stop(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),await this.deregisterInstance()}async registerInstance(){await this.redis.sadd("mesh:instances",this.instanceId)}async deregisterInstance(){await this.redis.srem("mesh:instances",this.instanceId),await this.redis.del(this.getHeartbeatKey())}async updateHeartbeat(){let t=this.getHeartbeatKey();await this.redis.set(t,Date.now().toString(),"EX",this.heartbeatTTL)}getHeartbeatKey(){return`mesh:instance:${this.instanceId}:heartbeat`}async acquireCleanupLock(){return await this.redis.set("mesh:cleanup:lock",this.instanceId,"EX",this.cleanupLockTTL,"NX")==="OK"}async releaseCleanupLock(){await this.redis.eval(`
|
|
2
|
-
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
3
|
-
return redis.call("del", KEYS[1])
|
|
4
|
-
else
|
|
5
|
-
return 0
|
|
6
|
-
end
|
|
7
|
-
`,1,"mesh:cleanup:lock",this.instanceId)}async performCleanup(){try{if(!await this.acquireCleanupLock())return;let e=await this.redis.smembers("mesh:instances"),r=await this.redis.hgetall("mesh:connections"),s=new Set([...e,...Object.values(r)]);for(let n of s){if(n===this.instanceId)continue;let i=`mesh:instance:${n}:heartbeat`;await this.redis.get(i)||(console.log(`Found dead instance: ${n}`),await this.cleanupDeadInstance(n))}}catch(t){console.error("Error during cleanup:",t)}finally{await this.releaseCleanupLock()}}async cleanupDeadInstance(t){try{let e=`mesh:connections:${t}`,r=await this.redis.smembers(e);for(let n of r)await this.cleanupConnection(n);let s=await this.redis.hgetall("mesh:connections");for(let[n,i]of Object.entries(s))i===t&&await this.cleanupConnection(n);await this.redis.srem("mesh:instances",t),await this.redis.del(e),console.log(`Cleaned up dead instance: ${t}`)}catch(e){console.error(`Error cleaning up instance ${t}:`,e)}}async deleteMatchingKeys(t){let e=this.redis.scanStream({match:t}),r=this.redis.pipeline();return e.on("data",s=>{for(let n of s)r.del(n)}),new Promise((s,n)=>{e.on("end",async()=>{await r.exec(),s()}),e.on("error",n)})}async cleanupConnection(t){try{let e=`mesh:connection:${t}:rooms`,r=await this.redis.smembers(e),s=this.redis.pipeline();for(let n of r)s.srem(`mesh:room:${n}`,t),s.srem(`mesh:presence:room:${n}`,t),s.del(`mesh:presence:room:${n}:conn:${t}`),s.del(`mesh:presence:state:${n}:conn:${t}`);s.del(e),s.hdel("mesh:connections",t),await this.deleteMatchingKeys(`mesh:collection:*:${t}`),await s.exec(),console.log(`Cleaned up stale connection: ${t}`)}catch(e){console.error(`Error cleaning up connection ${t}:`,e)}}};var X=class{constructor(t){this.exposedCollections=[];this.collectionSubscriptions=new Map;this.redis=t.redis,this.emitError=t.emitError}exposeCollection(t,e){this.exposedCollections.push({pattern:t,resolver:e})}async isCollectionExposed(t,e){return!!this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t))}async resolveCollection(t,e){let r=this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t));if(!r)throw new Error(`Collection "${t}" is not exposed`);try{return await Promise.resolve(r.resolver(e,t))}catch(s){throw this.emitError(new Error(`Failed to resolve collection "${t}": ${s}`)),s}}async addSubscription(t,e,r){this.collectionSubscriptions.has(t)||this.collectionSubscriptions.set(t,new Map);let s=await this.resolveCollection(t,r),n=s.map(o=>o.id),i=1;return this.collectionSubscriptions.get(t).set(e,{version:i}),await this.redis.set(`mesh:collection:${t}:${e}`,JSON.stringify(n)),{ids:n,records:s,version:i}}async removeSubscription(t,e){let r=this.collectionSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.collectionSubscriptions.delete(t),await this.redis.del(`mesh:collection:${t}:${e}`),!0):!1}async publishRecordChange(t){try{await this.redis.publish("mesh:collection:record-change",t)}catch(e){this.emitError(new Error(`Failed to publish record change for ${t}: ${e}`))}}async cleanupConnection(t){let e=t.id,r=[];this.collectionSubscriptions.forEach((s,n)=>{s.has(e)&&(s.delete(e),s.size===0&&this.collectionSubscriptions.delete(n),r.push(this.redis.del(`mesh:collection:${n}:${e}`).then(()=>{}).catch(i=>{this.emitError(new Error(`Failed to clean up collection subscription for "${n}": ${i}`))})))}),await Promise.all(r)}async listRecordsMatching(t,e){try{let r="mesh:record:",s=[],n="0";do{let d=await this.redis.scan(n,"MATCH",`${r}${t}`,"COUNT",e?.scanCount??100);n=d[0],s.push(...d[1])}while(n!=="0");if(s.length===0)return[];let i=await this.redis.mget(s),o=s.map(d=>d.substring(r.length)),a=i.map((d,p)=>{if(d===null)return null;try{let h=JSON.parse(d),u=o[p];return h.id===u?h:{...h,id:u}}catch(h){return this.emitError(new Error(`Failed to parse record for processing: ${d} - ${h.message}`)),null}}).filter(d=>d!==null);if(e?.map&&(a=a.map(e.map)),e?.sort&&a.sort(e.sort),e?.slice){let{start:d,count:p}=e.slice;a=a.slice(d,d+p)}return a}catch(r){return this.emitError(new Error(`Failed to list records matching "${t}": ${r.message}`)),[]}}getCollectionSubscriptions(){return this.collectionSubscriptions}updateSubscriptionVersion(t,e,r){let s=this.collectionSubscriptions.get(t);s?.has(e)&&s.set(e,{version:r})}};import{EventEmitter as we}from"events";import{v4 as Re}from"uuid";import Ce from"sqlite3";function G(c){return c.replace(/\^/g,"").replace(/\$/g,"").replace(/\./g,"_").replace(/\*/g,"%").replace(/\+/g,"%").replace(/\?/g,"_").replace(/\\\\/g,"\\").replace(/\\\./g,".").replace(/\\\*/g,"*").replace(/\\\+/g,"+").replace(/\\\?/g,"?")}var{Database:Me}=Ce,$=class{constructor(t={}){this.db=null;this.initialized=!1;this.options={filename:":memory:",...t}}async initialize(){if(!this.initialized)return new Promise((t,e)=>{try{this.db=new Me(this.options.filename,r=>{if(r){l.error("Failed to open SQLite database:",r),e(r);return}this.createTables().then(()=>{this.initialized=!0,t()}).catch(e)})}catch(r){l.error("Error initializing SQLite database:",r),e(r)}})}async createTables(){if(!this.db)throw new Error("Database not initialized");return new Promise((t,e)=>{this.db.run(`CREATE TABLE IF NOT EXISTS channel_messages (
|
|
8
|
-
id TEXT PRIMARY KEY,
|
|
9
|
-
channel TEXT NOT NULL,
|
|
10
|
-
message TEXT NOT NULL,
|
|
11
|
-
instance_id TEXT NOT NULL,
|
|
12
|
-
timestamp INTEGER NOT NULL,
|
|
13
|
-
metadata TEXT
|
|
14
|
-
)`,r=>{if(r){e(r);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_channel_timestamp ON channel_messages (channel, timestamp)",s=>{if(s){e(s);return}this.db.run(`CREATE TABLE IF NOT EXISTS records (
|
|
15
|
-
record_id TEXT PRIMARY KEY,
|
|
16
|
-
version INTEGER NOT NULL,
|
|
17
|
-
value TEXT NOT NULL,
|
|
18
|
-
timestamp INTEGER NOT NULL
|
|
19
|
-
)`,n=>{if(n){e(n);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_records_timestamp ON records (timestamp)",i=>{if(i){e(i);return}t()})})})})})}async storeMessages(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT INTO channel_messages
|
|
20
|
-
(id, channel, message, instance_id, timestamp, metadata)
|
|
21
|
-
VALUES (?, ?, ?, ?, ?, ?)`);try{for(let i of t){let o=i.metadata?JSON.stringify(i.metadata):null;n.run(i.id,i.channel,i.message,i.instanceId,i.timestamp,o)}n.finalize(),s.run("COMMIT",i=>{i?r(i):e()})}catch(i){s.run("ROLLBACK"),r(i)}})})}async getMessages(t,e,r=50){if(!this.db)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = ?",n=[t];if(e!==void 0)if(typeof e=="number")s+=" AND timestamp > ?",n.push(e);else{let i=await new Promise((o,a)=>{this.db.get("SELECT timestamp FROM channel_messages WHERE id = ?",[e],(d,p)=>{if(d){a(d);return}o(p?p.timestamp:0)})});s+=" AND timestamp > ?",n.push(i)}return s+=" ORDER BY timestamp ASC LIMIT ?",n.push(r),new Promise((i,o)=>{this.db.all(s,n,(a,d)=>{if(a){o(a);return}let p=d.map(h=>({id:h.id,channel:h.channel,message:h.message,instanceId:h.instance_id,timestamp:h.timestamp,metadata:h.metadata?JSON.parse(h.metadata):void 0}));i(p)})})}async close(){if(this.db)return new Promise((t,e)=>{this.db.close(r=>{if(r){e(r);return}this.db=null,this.initialized=!1,t()})})}async storeRecords(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return l.debug(`SQLite: Storing ${t.length} records`),new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT OR REPLACE INTO records
|
|
22
|
-
(record_id, version, value, timestamp)
|
|
23
|
-
VALUES (?, ?, ?, ?)`);try{for(let i of t)n.run(i.recordId,i.version,i.value,i.timestamp,o=>{o&&l.error(`Error storing record ${i.recordId}:`,o)});n.finalize(),s.run("COMMIT",i=>{i?(l.error("Error committing transaction:",i),s.run("ROLLBACK"),r(i)):e()})}catch(i){l.error("Error in storeRecords:",i),s.run("ROLLBACK"),r(i)}})})}async getRecords(t){if(!this.db)throw new Error("Database not initialized");let e=G(t);return l.debug(`SQLite: Getting records matching pattern: ${t} (SQL: ${e})`),new Promise((r,s)=>{this.db.all(`SELECT record_id, version, value, timestamp
|
|
24
|
-
FROM records
|
|
25
|
-
WHERE record_id LIKE ?
|
|
26
|
-
ORDER BY timestamp DESC`,[e],(n,i)=>{if(n){l.error("Error getting records:",n),s(n);return}l.debug(`SQLite: Found ${i.length} records matching pattern ${t}`);let o=i.map(a=>({recordId:a.record_id,version:a.version,value:a.value,timestamp:a.timestamp}));r(o)})})}};import{Pool as Pe}from"pg";var x=class{constructor(t={}){this.pool=null;this.initialized=!1;this.options={host:"localhost",port:5432,database:"mesh_test",user:"mesh",password:"mesh_password",max:10,...t}}async initialize(){if(!this.initialized)try{this.pool=new Pe(this.options.connectionString?{connectionString:this.options.connectionString,max:this.options.max}:{host:this.options.host,port:this.options.port,database:this.options.database,user:this.options.user,password:this.options.password,ssl:this.options.ssl,max:this.options.max}),await this.createTables(),this.initialized=!0}catch(t){throw l.error("Error initializing PostgreSQL database:",t),t}}async createTables(){if(!this.pool)throw new Error("Database not initialized");let t=await this.pool.connect();try{await t.query(`
|
|
1
|
+
var V=Object.defineProperty;var Ce=Object.getOwnPropertyDescriptor;var Me=Object.getOwnPropertyNames;var Pe=Object.prototype.hasOwnProperty;var L=(c,t)=>()=>(c&&(t=c(c=0)),t);var se=(c,t)=>{for(var e in t)V(c,e,{get:t[e],enumerable:!0})},we=(c,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Me(t))!Pe.call(c,s)&&s!==e&&V(c,s,{get:()=>t[s],enumerable:!(r=Ce(t,s))||r.enumerable});return c};var ne=c=>we(V({},"__esModule",{value:!0}),c);function v(c,t){if(!g(c)||!g(t))return t;let e={...c};for(let r in t)t.hasOwnProperty(r)&&(g(t[r])&&g(c[r])?e[r]=v(c[r],t[r]):e[r]=t[r]);return e}function g(c){return c!==null&&typeof c=="object"&&!Array.isArray(c)}function _(c){try{return JSON.parse(c)}catch{return{command:"",payload:{}}}}function ce(c){return JSON.stringify(c)}var oe,W,ie,ae,Be,l,y,f=L(()=>{"use strict";oe=class extends Error{constructor(c,t,e){super(c),typeof t=="string"&&(this.code=t),this.name=typeof e=="string"?e:"CodeError"}},W=(c=>(c[c.NONE=0]="NONE",c[c.ERROR=1]="ERROR",c[c.WARN=2]="WARN",c[c.INFO=3]="INFO",c[c.DEBUG=4]="DEBUG",c))(W||{}),ie=typeof window<"u"&&typeof window.document<"u",ae=class{constructor(c){this.config={level:c?.level??3,prefix:c?.prefix??"[mesh]",styling:c?.styling??ie}}configure(c){this.config={...this.config,...c}}info(...c){this.config.level>=3&&this.log("log",...c)}warn(...c){this.config.level>=2&&this.log("warn",...c)}error(...c){this.config.level>=1&&this.log("error",...c)}debug(...c){this.config.level>=4&&this.log("debug",...c)}log(c,...t){if(this.config.styling&&ie){let e={prefix:"background: #000; color: #FFA07A; padding: 2px 4px; border-radius: 2px;",reset:""};console[c](`%c${this.config.prefix}%c`,e.prefix,e.reset,...t)}else console[c](this.config.prefix,...t)}},Be=new ae({level:1,styling:!0}),l=new ae({level:1,styling:!1});y=(c=>(c[c.ONLINE=3]="ONLINE",c[c.CONNECTING=2]="CONNECTING",c[c.RECONNECTING=1]="RECONNECTING",c[c.OFFLINE=0]="OFFLINE",c))(y||{})});function G(c){return c.replace(/\^/g,"").replace(/\$/g,"").replace(/\./g,"_").replace(/\*/g,"%").replace(/\+/g,"%").replace(/\?/g,"_").replace(/\\\\/g,"\\").replace(/\\\./g,".").replace(/\\\*/g,"*").replace(/\\\+/g,"+").replace(/\\\?/g,"?")}var Y=L(()=>{"use strict"});var pe={};se(pe,{PostgreSQLPersistenceAdapter:()=>Q});import{Pool as Le}from"pg";var Q,me=L(()=>{"use strict";Y();f();Q=class{constructor(t={}){this.pool=null;this.initialized=!1;this.options={host:"localhost",port:5432,database:"mesh_test",user:"mesh",password:"mesh_password",max:10,...t}}async initialize(){if(!this.initialized)try{this.pool=new Le(this.options.connectionString?{connectionString:this.options.connectionString,max:this.options.max}:{host:this.options.host,port:this.options.port,database:this.options.database,user:this.options.user,password:this.options.password,ssl:this.options.ssl,max:this.options.max}),await this.createTables(),this.initialized=!0}catch(t){throw l.error("Error initializing PostgreSQL database:",t),t}}async createTables(){if(!this.pool)throw new Error("Database not initialized");let t=await this.pool.connect();try{await t.query(`
|
|
27
2
|
CREATE TABLE IF NOT EXISTS channel_messages (
|
|
28
3
|
id TEXT PRIMARY KEY,
|
|
29
4
|
channel TEXT NOT NULL,
|
|
@@ -53,4 +28,29 @@ import{v4 as Ee}from"uuid";import{WebSocketServer as Se}from"ws";var ee=class ex
|
|
|
53
28
|
DO UPDATE SET version = $2, value = $3, timestamp = $4`,[r.recordId,r.version,r.value,r.timestamp]);await e.query("COMMIT")}catch(r){throw await e.query("ROLLBACK"),l.error("Error in storeRecords:",r),r}finally{e.release()}}async getRecords(t){if(!this.pool)throw new Error("Database not initialized");let e=G(t);l.debug(`PostgreSQL: Getting records matching pattern: ${t} (SQL: ${e})`);let r=await this.pool.query(`SELECT record_id, version, value, timestamp
|
|
54
29
|
FROM records
|
|
55
30
|
WHERE record_id LIKE $1
|
|
56
|
-
ORDER BY timestamp DESC`,[e]);return l.debug(`PostgreSQL: Found ${r.rows.length} records matching pattern ${t}`),r.rows.map(s=>({recordId:s.record_id,version:s.version,value:s.value,timestamp:parseInt(s.timestamp)}))}async close(){this.pool&&(await this.pool.end(),this.pool=null,this.initialized=!1)}};var A=class extends we{constructor(e){super();this.channelPatterns=[];this.recordPatterns=[];this.messageBuffer=new Map;this.recordBuffer=new Map;this.flushTimers=new Map;this.recordFlushTimer=null;this.isShuttingDown=!1;this.initialized=!1;this.recordManager=null;this.pendingRecordUpdates=[];let{defaultAdapterOptions:r={},adapterType:s="sqlite"}=e;s==="postgres"?this.defaultAdapter=new x(r):this.defaultAdapter=new $(r),this.messageStream=P.getInstance()}setRecordManager(e){this.recordManager=e}async ready(){return this.initialized?Promise.resolve():new Promise(e=>{this.once("initialized",e)})}async processPendingRecordUpdates(){if(this.pendingRecordUpdates.length===0)return;l.info(`Processing ${this.pendingRecordUpdates.length} pending record updates`);let e=[...this.pendingRecordUpdates];this.pendingRecordUpdates=[];for(let{recordId:r,value:s,version:n}of e)this.handleRecordUpdate(r,s,n)}async initialize(){if(!this.initialized)try{await this.defaultAdapter.initialize(),this.messageStream.subscribeToMessages(this.handleStreamMessage.bind(this)),this.initialized=!0,await this.processPendingRecordUpdates(),this.emit("initialized")}catch(e){throw l.error("Failed to initialize persistence manager:",e),e}}async restorePersistedRecords(){if(!this.recordManager){l.warn("Cannot restore persisted records: record manager not available");return}let e=this.recordManager.getRedis();if(!e){l.warn("Cannot restore records: Redis not available");return}try{if(l.info("Restoring persisted records..."),this.recordPatterns.length===0){l.info("No record patterns to restore");return}l.info(`Found ${this.recordPatterns.length} record patterns to restore`);for(let r of this.recordPatterns){l.info(`Config keys: ${Object.keys(r).join(", ")}`),l.info(`Config.hooks: ${typeof r.hooks}, Config.adapter: ${typeof r.adapter}`),l.info(`Config.pattern: ${r.pattern}`);let{adapter:s,hooks:n}=r,i=n?"(custom hooks)":s?.restorePattern;try{let o=[];if(n?o=await n.restore():s&&(o=(s.adapter.getRecords?await s.adapter.getRecords(s.restorePattern):[]).map(d=>({recordId:d.recordId,value:typeof d.value=="string"?JSON.parse(d.value):d.value,version:d.version}))),o.length>0){l.info(`Restoring ${o.length} records for pattern ${i}`);for(let a of o)try{let{recordId:d,value:p,version:h}=a,u=this.recordManager.recordKey(d),y=this.recordManager.recordVersionKey(d),w=e.pipeline();w.set(u,JSON.stringify(p)),w.set(y,h.toString()),await w.exec(),l.debug(`Restored record ${d} (version ${h})`)}catch(d){l.error(`Failed to restore record ${a.recordId}: ${d}`)}}else l.debug(`No records found for pattern ${i}`)}catch(o){l.error(`Error restoring records for pattern ${i}: ${o}`)}}l.info("Finished restoring persisted records")}catch(r){l.error("Failed to restore persisted records:",r)}}handleStreamMessage(e){let{channel:r,message:s,instanceId:n,timestamp:i}=e;this.handleChannelMessage(r,s,n,i)}enableChannelPersistence(e,r={}){let s={historyLimit:r.historyLimit??50,filter:r.filter??(()=>!0),adapter:r.adapter??this.defaultAdapter,flushInterval:r.flushInterval??500,maxBufferSize:r.maxBufferSize??100};s.adapter!==this.defaultAdapter&&!this.isShuttingDown&&s.adapter.initialize().catch(n=>{l.error(`Failed to initialize adapter for pattern ${e}:`,n)}),this.channelPatterns.push({pattern:e,options:s})}enableRecordPersistence(e){l.info(`enableRecordPersistence called with config keys: ${Object.keys(e).join(", ")}`),l.info(`config.hooks type: ${typeof e.hooks}, config.adapter type: ${typeof e.adapter}`);let{pattern:r,adapter:s,hooks:n,flushInterval:i,maxBufferSize:o}=e;if(s&&n)throw new Error("Cannot use both adapter and hooks. Choose one.");let a;if(s){let d=s.adapter??this.defaultAdapter;a={adapter:d,restorePattern:s.restorePattern},d!==this.defaultAdapter&&!this.isShuttingDown&&d.initialize().catch(p=>{l.error(`Failed to initialize adapter for record pattern ${r}:`,p)})}this.recordPatterns.push({pattern:r,adapter:a,hooks:n,flushInterval:i??500,maxBufferSize:o??100})}getChannelPersistenceOptions(e){for(let{pattern:r,options:s}of this.channelPatterns)if(typeof r=="string"&&r===e||r instanceof RegExp&&r.test(e))return s}getRecordPersistenceConfig(e){for(let r of this.recordPatterns){let{pattern:s}=r;if(typeof s=="string"&&s===e||s instanceof RegExp&&s.test(e))return r}}handleChannelMessage(e,r,s,n){if(!this.initialized||this.isShuttingDown)return;let i=this.getChannelPersistenceOptions(e);if(!i||!i.filter(r,e))return;let o={id:Re(),channel:e,message:r,instanceId:s,timestamp:n||Date.now()};if(this.messageBuffer.has(e)||this.messageBuffer.set(e,[]),this.messageBuffer.get(e).push(o),this.messageBuffer.get(e).length>=i.maxBufferSize){this.flushChannel(e);return}if(!this.flushTimers.has(e)){let a=setTimeout(()=>{this.flushChannel(e)},i.flushInterval);a.unref&&a.unref(),this.flushTimers.set(e,a)}}async flushChannel(e){if(!this.messageBuffer.has(e))return;this.flushTimers.has(e)&&(clearTimeout(this.flushTimers.get(e)),this.flushTimers.delete(e));let r=this.messageBuffer.get(e);if(r.length===0)return;this.messageBuffer.set(e,[]);let s=this.getChannelPersistenceOptions(e);if(s)try{await s.adapter.storeMessages(r),this.emit("flushed",{channel:e,count:r.length}),l.debug(`Flushed ${r.length} messages for channel ${e}`)}catch(n){if(l.error(`Failed to flush messages for channel ${e}:`,n),!this.isShuttingDown){let i=this.messageBuffer.get(e)||[];if(this.messageBuffer.set(e,[...r,...i]),!this.flushTimers.has(e)){let o=setTimeout(()=>{this.flushChannel(e)},1e3);o.unref&&o.unref(),this.flushTimers.set(e,o)}}}}async flushAll(){let e=Array.from(this.messageBuffer.keys());for(let r of e)await this.flushChannel(r)}async getMessages(e,r,s){if(!this.initialized)throw new Error("Persistence manager not initialized");let n=this.getChannelPersistenceOptions(e);if(!n)throw new Error(`Channel ${e} does not have persistence enabled`);return await this.flushChannel(e),n.adapter.getMessages(e,r,s||n.historyLimit)}handleRecordUpdate(e,r,s){if(this.isShuttingDown)return;if(!this.initialized){this.pendingRecordUpdates.push({recordId:e,value:r,version:s}),l.debug(`Buffered record update for ${e} (pending initialization)`);return}let n=this.getRecordPersistenceConfig(e);if(!n)return;let i={recordId:e,value:JSON.stringify(r),version:s,timestamp:Date.now()};if(this.recordBuffer.set(e,i),l.debug(`Added record ${e} to buffer, buffer size: ${this.recordBuffer.size}`),this.recordBuffer.size>=n.maxBufferSize){l.debug(`Buffer size ${this.recordBuffer.size} exceeds limit ${n.maxBufferSize}, flushing records`),this.flushRecords();return}this.recordFlushTimer||(l.debug(`Scheduling record flush in ${n.flushInterval}ms`),this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},n.flushInterval),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}async flushRecords(){if(this.recordBuffer.size===0)return;l.debug(`Flushing ${this.recordBuffer.size} records to storage`),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null);let e=Array.from(this.recordBuffer.values());this.recordBuffer.clear();let r=new Map,s=new Map;for(let i of e){let o=this.getRecordPersistenceConfig(i.recordId);o&&(o.hooks?(s.has(o.hooks.persist)||s.set(o.hooks.persist,[]),s.get(o.hooks.persist).push(i)):o.adapter&&(r.has(o.adapter.adapter)||r.set(o.adapter.adapter,[]),r.get(o.adapter.adapter).push(i)))}let n=(i,o)=>{if(l.error("Failed to flush records:",o),!this.isShuttingDown){for(let a of i)this.recordBuffer.set(a.recordId,a);this.recordFlushTimer||(this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},1e3),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}};for(let[i,o]of s.entries())try{let a=o.map(d=>({recordId:d.recordId,value:JSON.parse(d.value),version:d.version}));l.debug(`Storing ${o.length} records with custom persist hook`),await i(a),this.emit("recordsFlushed",{count:o.length})}catch(a){n(o,a)}for(let[i,o]of r.entries())try{i.storeRecords?(l.debug(`Storing ${o.length} records with adapter`),await i.storeRecords(o),this.emit("recordsFlushed",{count:o.length})):l.warn("Adapter does not support storing records")}catch(a){n(o,a)}}async getPersistedRecords(e){if(!this.initialized)throw new Error("Persistence manager not initialized");await this.flushRecords();try{let r=this.defaultAdapter;if(r.getRecords)return await r.getRecords(e)}catch(r){l.error(`Failed to get persisted records for pattern ${e}:`,r)}return[]}async shutdown(){if(this.isShuttingDown)return;this.isShuttingDown=!0,this.messageStream.unsubscribeFromMessages(this.handleStreamMessage.bind(this));for(let r of this.flushTimers.values())clearTimeout(r);this.flushTimers.clear(),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null),await this.flushAll(),await this.flushRecords();let e=new Set([this.defaultAdapter]);for(let{options:r}of this.channelPatterns)e.add(r.adapter);for(let r of this.recordPatterns)r.adapter&&e.add(r.adapter.adapter);for(let r of e)try{await r.close()}catch(s){l.error("Error closing persistence adapter:",s)}this.initialized=!1}};var H=new WeakMap,Y=class extends Se{constructor(e){let r={...e};e.authenticateConnection&&(r.verifyClient=(s,n)=>{Promise.resolve().then(()=>e.authenticateConnection(s.req)).then(i=>{i!=null?(H.set(s.req,i),n(!0)):n(!1,401,"Unauthorized")}).catch(i=>{let o=i?.code??401,a=i?.message??"Unauthorized";n(!1,o,a)})});super(r);this.persistenceManager=null;this.status=f.OFFLINE;this._listening=!1;this.authenticateConnection=e.authenticateConnection,this.instanceId=Ee(),this.serverOptions={...e,pingInterval:e.pingInterval??3e4,latencyInterval:e.latencyInterval??5e3,maxMissedPongs:e.maxMissedPongs??1,logLevel:e.logLevel??V.ERROR},l.configure({level:this.serverOptions.logLevel,styling:!1}),this.redisManager=new J,this.redisManager.initialize(e.redisOptions,s=>this.emit("error",s)),this.instanceManager=new q({redis:this.redisManager.redis,instanceId:this.instanceId}),this.roomManager=new O({redis:this.redisManager.redis}),this.recordManager=new T({redis:this.redisManager.redis,server:this}),this.connectionManager=new S({redis:this.redisManager.pubClient,instanceId:this.instanceId,roomManager:this.roomManager}),this.presenceManager=new I({redis:this.redisManager.redis,roomManager:this.roomManager,redisManager:this.redisManager,enableExpirationEvents:this.serverOptions.enablePresenceExpirationEvents}),this.serverOptions.enablePresenceExpirationEvents&&this.redisManager.enableKeyspaceNotifications().catch(s=>this.emit("error",new Error(`Failed to enable keyspace notifications: ${s}`))),this.commandManager=new U,this.persistenceManager=new A({defaultAdapterOptions:this.serverOptions.persistenceOptions,adapterType:this.serverOptions.persistenceAdapter}),this.persistenceManager.initialize().catch(s=>{this.emit("error",new Error(`Failed to initialize persistence manager: ${s}`))}),this.channelManager=new D({redis:this.redisManager.redis,pubClient:this.redisManager.pubClient,subClient:this.redisManager.subClient}),this.channelManager.setPersistenceManager(this.persistenceManager),this.recordSubscriptionManager=new B({pubClient:this.redisManager.pubClient,recordManager:this.recordManager,emitError:s=>this.emit("error",s),persistenceManager:this.persistenceManager}),this.collectionManager=new X({redis:this.redisManager.redis,emitError:s=>this.emit("error",s)}),this.persistenceManager&&this.persistenceManager.setRecordManager(this.recordManager),this.recordManager.onRecordUpdate(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record update for collection check: ${n}`))}}),this.recordManager.onRecordRemoved(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record removal for collection check: ${n}`))}}),this.pubSubManager=new z({subClient:this.redisManager.subClient,instanceId:this.instanceId,connectionManager:this.connectionManager,recordManager:this.recordManager,recordSubscriptions:this.recordSubscriptionManager.getRecordSubscriptions(),getChannelSubscriptions:this.channelManager.getSubscribers.bind(this.channelManager),emitError:s=>this.emit("error",s),collectionManager:this.collectionManager,pubClient:this.redisManager.pubClient}),this.broadcastManager=new K({connectionManager:this.connectionManager,roomManager:this.roomManager,instanceId:this.instanceId,pubClient:this.redisManager.pubClient,getPubSubChannel:s=>`${v}${s}`,emitError:s=>this.emit("error",s)}),this.on("listening",()=>{this.listening=!0,this.instanceManager.start()}),this.on("error",s=>{l.error(`Error: ${s}`)}),this.on("close",()=>{this.listening=!1}),this.pubSubManager.subscribeToInstanceChannel(),this.registerBuiltinCommands(),this.registerRecordCommands(),this.applyListeners()}get listening(){return this._listening}set listening(e){this._listening=e,this.status=e?f.ONLINE:f.OFFLINE}get port(){return this.address().port}async ready(){let e=this.listening?Promise.resolve():new Promise(s=>this.once("listening",s)),r=this.persistenceManager?this.persistenceManager.ready():Promise.resolve();await Promise.all([e,this.pubSubManager.getSubscriptionPromise(),r]),this.persistenceManager&&await this.persistenceManager.restorePersistedRecords()}applyListeners(){this.on("connection",async(e,r)=>{let s=new E(e,r,this.serverOptions,this);s.on("message",n=>{try{let i=n.toString(),o=_(i);o.id!==void 0&&!["latency:response","pong"].includes(o.command)&&this.commandManager.runCommand(o.id,o.command,o.payload,s,this)}catch(i){this.emit("error",i)}});try{await this.connectionManager.registerConnection(s);let n=H.get(r);n&&(H.delete(r),await this.connectionManager.setMetadata(s,n)),s.send({command:"mesh/assign-id",payload:s.id})}catch{s.close();return}this.emit("connected",s),s.on("close",async()=>{await this.cleanupConnection(s),this.emit("disconnected",s)}),s.on("error",n=>{this.emit("clientError",n,s)}),s.on("pong",async n=>{try{let i=await this.roomManager.getRoomsForConnection(n);for(let o of i)await this.presenceManager.isRoomTracked(o)&&await this.presenceManager.refreshPresence(n,o)}catch(i){this.emit("error",new Error(`Failed to refresh presence: ${i}`))}})})}exposeCommand(e,r,s=[]){this.commandManager.exposeCommand(e,r,s)}useMiddleware(...e){this.commandManager.useMiddleware(...e)}useMiddlewareWithCommand(e,r){this.commandManager.useMiddlewareWithCommand(e,r)}exposeChannel(e,r){this.channelManager.exposeChannel(e,r)}async writeChannel(e,r,s=0){return this.channelManager.writeChannel(e,r,s,this.instanceId)}enableChannelPersistence(e,r={}){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableChannelPersistence(e,r)}enableRecordPersistence(e){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableRecordPersistence(e)}exposeRecord(e,r){this.recordSubscriptionManager.exposeRecord(e,r)}exposeWritableRecord(e,r){this.recordSubscriptionManager.exposeWritableRecord(e,r)}async writeRecord(e,r,s){return this.recordSubscriptionManager.writeRecord(e,r,s)}async getRecord(e){return this.recordManager.getRecord(e)}async deleteRecord(e){let r=await this.recordManager.deleteRecord(e);r&&await this.recordSubscriptionManager.publishRecordDeletion(e,r.version)}async listRecordsMatching(e,r){return this.collectionManager.listRecordsMatching(e,r)}exposeCollection(e,r){this.collectionManager.exposeCollection(e,r)}async isInRoom(e,r){let s=typeof r=="string"?r:r.id;return this.roomManager.connectionIsInRoom(e,s)}async addToRoom(e,r){let s=typeof r=="string"?r:r.id;await this.roomManager.addToRoom(e,r),await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOnline(s,e)}async removeFromRoom(e,r){let s=typeof r=="string"?r:r.id;return await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOffline(s,e),this.roomManager.removeFromRoom(e,r)}async removeFromAllRooms(e){return this.roomManager.removeFromAllRooms(e)}async clearRoom(e){return this.roomManager.clearRoom(e)}async deleteRoom(e){return this.roomManager.deleteRoom(e)}async getRoomMembers(e){return this.roomManager.getRoomConnectionIds(e)}async getRoomMembersWithMetadata(e){let r=await this.roomManager.getRoomConnectionIds(e);return Promise.all(r.map(async s=>{try{let n=this.connectionManager.getLocalConnection(s),i;if(n)i=await this.connectionManager.getMetadata(n);else{let o=await this.redisManager.redis.hget("mesh:connections",s);i=o?JSON.parse(o):null}return{id:s,metadata:i}}catch{return{id:s,metadata:null}}}))}async getAllRooms(){return this.roomManager.getAllRooms()}async broadcast(e,r,s){return this.broadcastManager.broadcast(e,r,s)}async broadcastRoom(e,r,s){return this.broadcastManager.broadcastRoom(e,r,s)}async broadcastExclude(e,r,s){return this.broadcastManager.broadcastExclude(e,r,s)}async broadcastRoomExclude(e,r,s,n){return this.broadcastManager.broadcastRoomExclude(e,r,s,n)}trackPresence(e,r){this.presenceManager.trackRoom(e,r)}registerBuiltinCommands(){this.exposeCommand("mesh/noop",async()=>!0),this.exposeCommand("mesh/subscribe-channel",async e=>{let{channel:r,historyLimit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.channelManager.getSubscribers(r)||await this.channelManager.subscribeToRedisChannel(r),this.channelManager.addSubscription(r,e.connection),{success:!0,history:s&&s>0?await this.channelManager.getChannelHistory(r,s,n):[]}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/unsubscribe-channel",async e=>{let{channel:r}=e.payload,s=this.channelManager.removeSubscription(r,e.connection);return s&&!this.channelManager.getSubscribers(r)&&await this.channelManager.unsubscribeFromRedisChannel(r),s}),this.exposeCommand("mesh/get-channel-history",async e=>{let{channel:r,limit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.persistenceManager?.getChannelPersistenceOptions(r)?{success:!0,history:(await this.persistenceManager.getMessages(r,n,s||this.persistenceManager.getChannelPersistenceOptions(r)?.historyLimit)).map(o=>o.message)}:{success:!0,history:await this.channelManager.getChannelHistory(r,s||50,n)}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/join-room",async e=>{let{roomName:r}=e.payload;return await this.addToRoom(r,e.connection),{success:!0,present:await this.getRoomMembersWithMetadata(r)}}),this.exposeCommand("mesh/leave-room",async e=>{let{roomName:r}=e.payload;return await this.removeFromRoom(r,e.connection),{success:!0}}),this.exposeCommand("mesh/get-connection-metadata",async e=>{let{connectionId:r}=e.payload,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/get-my-connection-metadata",async e=>{let r=e.connection.id,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/set-my-connection-metadata",async e=>{let{metadata:r,options:s}=e.payload,n=e.connection.id,i=this.connectionManager.getLocalConnection(n);if(i)try{return await this.connectionManager.setMetadata(i,r,s),{success:!0}}catch{return{success:!1}}else return{success:!1}}),this.exposeCommand("mesh/get-room-metadata",async e=>{let{roomName:r}=e.payload;return{metadata:await this.roomManager.getMetadata(r)}})}registerRecordCommands(){this.exposeCommand("mesh/subscribe-record",async e=>{let{recordId:r,mode:s="full"}=e.payload,n=e.connection.id;if(!await this.recordSubscriptionManager.isRecordExposed(r,e.connection))return{success:!1};try{let{record:i,version:o}=await this.recordManager.getRecordAndVersion(r);return this.recordSubscriptionManager.addSubscription(r,n,s),{success:!0,record:i,version:o}}catch(i){return console.error(`Failed to subscribe to record ${r}:`,i),{success:!1}}}),this.exposeCommand("mesh/unsubscribe-record",async e=>{let{recordId:r}=e.payload,s=e.connection.id;return this.recordSubscriptionManager.removeSubscription(r,s)}),this.exposeCommand("mesh/publish-record-update",async e=>{let{recordId:r,newValue:s,options:n}=e.payload;if(!await this.recordSubscriptionManager.isRecordWritable(r,e.connection))throw new Error(`Record "${r}" is not writable by this connection.`);try{return await this.writeRecord(r,s,n),{success:!0}}catch(i){throw new Error(`Failed to publish update for record "${r}": ${i.message}`)}}),this.exposeCommand("mesh/subscribe-presence",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=`mesh:presence:updates:${r}`;this.channelManager.addSubscription(s,e.connection),(!this.channelManager.getSubscribers(s)||this.channelManager.getSubscribers(s)?.size===1)&&await this.channelManager.subscribeToRedisChannel(s);let n=await this.getRoomMembersWithMetadata(r),i=await this.presenceManager.getAllPresenceStates(r),o={};return i.forEach((a,d)=>{o[d]=a}),{success:!0,present:n,states:o}}catch(s){return console.error(`Failed to subscribe to presence for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/unsubscribe-presence",async e=>{let{roomName:r}=e.payload,s=`mesh:presence:updates:${r}`;return this.channelManager.removeSubscription(s,e.connection)}),this.exposeCommand("mesh/publish-presence-state",async e=>{let{roomName:r,state:s,expireAfter:n,silent:i}=e.payload,o=e.connection.id;if(!s||!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,o))return!1;try{return await this.presenceManager.publishPresenceState(o,r,s,n,i),!0}catch(a){return console.error(`Failed to publish presence state for room ${r}:`,a),!1}}),this.exposeCommand("mesh/clear-presence-state",async e=>{let{roomName:r}=e.payload,s=e.connection.id;if(!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,s))return!1;try{return await this.presenceManager.clearPresenceState(s,r),!0}catch(n){return console.error(`Failed to clear presence state for room ${r}:`,n),!1}}),this.exposeCommand("mesh/get-presence-state",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=await this.presenceManager.getPresentConnections(r),n=await this.presenceManager.getAllPresenceStates(r),i={};return n.forEach((o,a)=>{i[a]=o}),{success:!0,present:s,states:i}}catch(s){return console.error(`Failed to get presence state for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/subscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;if(!await this.collectionManager.isCollectionExposed(r,e.connection))return{success:!1,ids:[],records:[],version:0};try{let{ids:n,records:i,version:o}=await this.collectionManager.addSubscription(r,s,e.connection),a=i.map(d=>({id:d.id,record:d}));return{success:!0,ids:n,records:a,version:o}}catch(n){return console.error(`Failed to subscribe to collection ${r}:`,n),{success:!1,ids:[],records:[],version:0}}}),this.exposeCommand("mesh/unsubscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;return this.collectionManager.removeSubscription(r,s)})}async cleanupConnection(e){l.info("Cleaning up connection:",e.id),e.stopIntervals();try{await this.presenceManager.cleanupConnection(e),await this.connectionManager.cleanupConnection(e),await this.roomManager.cleanupConnection(e),this.recordSubscriptionManager.cleanupConnection(e),this.channelManager.cleanupConnection(e),await this.collectionManager.cleanupConnection(e)}catch(r){this.emit("error",new Error(`Failed to clean up connection: ${r}`))}}async close(e){this.redisManager.isShuttingDown=!0;let r=Object.values(this.connectionManager.getLocalConnections());if(await Promise.all(r.map(async s=>{s.isDead||await s.close(),await this.cleanupConnection(s)})),await new Promise((s,n)=>{super.close(i=>{i?n(i):s()})}),this.persistenceManager)try{await this.persistenceManager.shutdown()}catch(s){l.error("Error shutting down persistence manager:",s)}await this.instanceManager.stop(),await this.pubSubManager.cleanup(),await this.presenceManager.cleanup(),this.redisManager.disconnect(),this.listening=!1,this.removeAllListeners(),e&&e()}onConnection(e){return this.on("connected",e),this}onDisconnection(e){return this.on("disconnected",e),this}onRecordUpdate(e){return this.recordManager.onRecordUpdate(e)}onRecordRemoved(e){return this.recordManager.onRecordRemoved(e)}};export{E as Connection,S as ConnectionManager,N as MeshContext,Y as MeshServer,P as MessageStream,A as PersistenceManager,x as PostgreSQLPersistenceAdapter,I as PresenceManager,T as RecordManager,O as RoomManager,$ as SQLitePersistenceAdapter};
|
|
31
|
+
ORDER BY timestamp DESC`,[e]);return l.debug(`PostgreSQL: Found ${r.rows.length} records matching pattern ${t}`),r.rows.map(s=>({recordId:s.record_id,version:s.version,value:s.value,timestamp:parseInt(s.timestamp)}))}async close(){this.pool&&(await this.pool.end(),this.pool=null,this.initialized=!1)}}});var ge={};se(ge,{SQLitePersistenceAdapter:()=>j});import _e from"sqlite3";var Fe,j,ue=L(()=>{"use strict";Y();f();({Database:Fe}=_e),j=class{constructor(t={}){this.db=null;this.initialized=!1;this.options={filename:":memory:",...t}}async initialize(){if(!this.initialized)return new Promise((t,e)=>{try{this.db=new Fe(this.options.filename,r=>{if(r){l.error("Failed to open SQLite database:",r),e(r);return}this.createTables().then(()=>{this.initialized=!0,t()}).catch(e)})}catch(r){l.error("Error initializing SQLite database:",r),e(r)}})}async createTables(){if(!this.db)throw new Error("Database not initialized");return new Promise((t,e)=>{this.db.run(`CREATE TABLE IF NOT EXISTS channel_messages (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
channel TEXT NOT NULL,
|
|
34
|
+
message TEXT NOT NULL,
|
|
35
|
+
instance_id TEXT NOT NULL,
|
|
36
|
+
timestamp INTEGER NOT NULL,
|
|
37
|
+
metadata TEXT
|
|
38
|
+
)`,r=>{if(r){e(r);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_channel_timestamp ON channel_messages (channel, timestamp)",s=>{if(s){e(s);return}this.db.run(`CREATE TABLE IF NOT EXISTS records (
|
|
39
|
+
record_id TEXT PRIMARY KEY,
|
|
40
|
+
version INTEGER NOT NULL,
|
|
41
|
+
value TEXT NOT NULL,
|
|
42
|
+
timestamp INTEGER NOT NULL
|
|
43
|
+
)`,n=>{if(n){e(n);return}this.db.run("CREATE INDEX IF NOT EXISTS idx_records_timestamp ON records (timestamp)",i=>{if(i){e(i);return}t()})})})})})}async storeMessages(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT INTO channel_messages
|
|
44
|
+
(id, channel, message, instance_id, timestamp, metadata)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, ?)`);try{for(let i of t){let o=i.metadata?JSON.stringify(i.metadata):null;n.run(i.id,i.channel,i.message,i.instanceId,i.timestamp,o)}n.finalize(),s.run("COMMIT",i=>{i?r(i):e()})}catch(i){s.run("ROLLBACK"),r(i)}})})}async getMessages(t,e,r=50){if(!this.db)throw new Error("Database not initialized");let s="SELECT * FROM channel_messages WHERE channel = ?",n=[t];if(e!==void 0)if(typeof e=="number")s+=" AND timestamp > ?",n.push(e);else{let i=await new Promise((o,a)=>{this.db.get("SELECT timestamp FROM channel_messages WHERE id = ?",[e],(d,p)=>{if(d){a(d);return}o(p?p.timestamp:0)})});s+=" AND timestamp > ?",n.push(i)}return s+=" ORDER BY timestamp ASC LIMIT ?",n.push(r),new Promise((i,o)=>{this.db.all(s,n,(a,d)=>{if(a){o(a);return}let p=d.map(h=>({id:h.id,channel:h.channel,message:h.message,instanceId:h.instance_id,timestamp:h.timestamp,metadata:h.metadata?JSON.parse(h.metadata):void 0}));i(p)})})}async close(){if(this.db)return new Promise((t,e)=>{this.db.close(r=>{if(r){e(r);return}this.db=null,this.initialized=!1,t()})})}async storeRecords(t){if(!this.db)throw new Error("Database not initialized");if(t.length!==0)return l.debug(`SQLite: Storing ${t.length} records`),new Promise((e,r)=>{let s=this.db;s.serialize(()=>{s.run("BEGIN TRANSACTION");let n=s.prepare(`INSERT OR REPLACE INTO records
|
|
46
|
+
(record_id, version, value, timestamp)
|
|
47
|
+
VALUES (?, ?, ?, ?)`);try{for(let i of t)n.run(i.recordId,i.version,i.value,i.timestamp,o=>{o&&l.error(`Error storing record ${i.recordId}:`,o)});n.finalize(),s.run("COMMIT",i=>{i?(l.error("Error committing transaction:",i),s.run("ROLLBACK"),r(i)):e()})}catch(i){l.error("Error in storeRecords:",i),s.run("ROLLBACK"),r(i)}})})}async getRecords(t){if(!this.db)throw new Error("Database not initialized");let e=G(t);return l.debug(`SQLite: Getting records matching pattern: ${t} (SQL: ${e})`),new Promise((r,s)=>{this.db.all(`SELECT record_id, version, value, timestamp
|
|
48
|
+
FROM records
|
|
49
|
+
WHERE record_id LIKE ?
|
|
50
|
+
ORDER BY timestamp DESC`,[e],(n,i)=>{if(n){l.error("Error getting records:",n),s(n);return}l.debug(`SQLite: Found ${i.length} records matching pattern ${t}`);let o=i.map(a=>({recordId:a.record_id,version:a.version,value:a.value,timestamp:a.timestamp}));r(o)})})}}});f();import{v4 as De}from"uuid";import{WebSocketServer as Ue}from"ws";f();f();import{EventEmitter as Ie}from"events";import{WebSocket as Te}from"ws";var F=class{constructor(){this.start=0;this.end=0;this.ms=0}onRequest(){this.start=Date.now()}onResponse(){this.end=Date.now(),this.ms=this.end-this.start}};var k=class{};import{getRandomValues as Re}from"crypto";var H=[];for(let c=0;c<256;c++)H[c]=(c+256).toString(16).substring(1);function Ee(c,t){let e=`000000${c}`;return e.substring(e.length-t)}var Se=32;function de(c){let t=c.len||16,e="",r=0,s=1679616,n=c.init+Math.ceil(s/2);function i(){return n>=s&&(n=0),n++,(n-1).toString(16)}return()=>{if(!e||r===256){let h=new Uint8Array(t);Re(h),e=Array.from(h,u=>H[u]).join("").substring(0,t),r=0}let o=Date.now().toString(36),a=Ee(i(),6),d=H[r++],p=parseInt(d,16)%Se;return`conn-${o}${a}${d}${e}${p}`}}var Oe=de({init:Date.now(),len:4}),S=class extends Ie{constructor(e,r,s,n){super();this.alive=!0;this.missedPongs=0;this.status=y.ONLINE;this.socket=e,this.id=Oe(),this.remoteAddress=r.socket.remoteAddress,this.connectionOptions=s,this.server=n,this.applyListeners(),this.startIntervals()}get isDead(){return!this.socket||this.socket.readyState!==Te.OPEN}startIntervals(){this.latency=new F,this.ping=new k,this.latency.interval=setInterval(()=>{this.alive&&(typeof this.latency.ms=="number"&&this.send({command:"latency",payload:this.latency.ms}),this.latency.onRequest(),this.send({command:"latency:request",payload:{}}))},this.connectionOptions.latencyInterval),this.ping.interval=setInterval(()=>{if(this.alive)this.missedPongs=0;else{this.missedPongs++;let e=this.connectionOptions.maxMissedPongs??1;if(this.missedPongs>e){l.info(`Closing connection (${this.id}) due to missed pongs`),this.close(),this.server.cleanupConnection(this);return}}this.alive=!1,this.send({command:"ping",payload:{}})},this.connectionOptions.pingInterval)}stopIntervals(){clearInterval(this.latency.interval),clearInterval(this.ping.interval)}applyListeners(){this.socket.on("close",()=>{l.info("Client's socket closed:",this.id),this.status=y.OFFLINE,this.emit("close")}),this.socket.on("error",e=>{this.emit("error",e)}),this.socket.on("message",e=>{try{let r=_(e.toString());if(r.command==="latency:response"){this.latency.onResponse();return}else if(r.command==="pong"){this.alive=!0,this.missedPongs=0,this.emit("pong",this.id);return}this.emit("message",e)}catch(r){this.emit("error",r)}})}send(e){if(this.isDead)return!1;try{return this.socket.send(ce(e)),!0}catch(r){return this.emit("error",r),!1}}async close(){if(this.isDead)return!1;try{return await new Promise((e,r)=>{this.socket.once("close",e),this.socket.once("error",r),this.socket.close()}),!0}catch(e){return this.emit("error",e),!1}}};var C="mesh:pubsub:",M="mesh:record-updates",le="mesh:record:",he="mesh:record-version:";f();var P="mesh:connections",Ne="mesh:connections:",I=class{constructor(t){this.localConnections={};this.redis=t.redis,this.instanceId=t.instanceId,this.roomManager=t.roomManager}getLocalConnections(){return Object.values(this.localConnections)}getLocalConnection(t){return this.localConnections[t]??null}async registerConnection(t){this.localConnections[t.id]=t;let e=this.redis.pipeline();e.hset(P,t.id,this.instanceId),e.sadd(this.getInstanceConnectionsKey(this.instanceId),t.id),await e.exec()}getInstanceConnectionsKey(t){return`${Ne}${t}`}async deregisterConnection(t){let e=await this.getInstanceIdForConnection(t);if(!e)return;let r=this.redis.pipeline();r.hdel(P,t.id),r.srem(this.getInstanceConnectionsKey(e),t.id),await r.exec()}async getInstanceIdForConnection(t){return this.redis.hget(P,t.id)}async getInstanceIdsForConnections(t){if(t.length===0)return{};let e=await this.redis.hmget(P,...t),r={};return t.forEach((s,n)=>{r[s]=e[n]??null}),r}async getAllConnectionIds(){return this.redis.hkeys(P)}async getLocalConnectionIds(){return this.redis.smembers(this.getInstanceConnectionsKey(this.instanceId))}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let o=await this.getMetadata(t);n==="merge"?g(o)&&g(e)?s={...o,...e}:s=e:n==="deepMerge"&&(g(o)&&g(e)?s=v(o,e):s=e)}let i=this.redis.pipeline();i.hset(P,t.id,JSON.stringify(s)),await i.exec()}async getMetadata(t){let e=await this.redis.hget(P,t.id);return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.getAllConnectionIds(),e=await this.getInstanceIdsForConnections(t),r=[];return t.forEach(s=>{try{let n=e[s]?JSON.parse(e[s]):null;r.push({id:s,metadata:n})}catch(n){console.error(`Failed to parse metadata for connection ${s}:`,n),r.push({id:s,metadata:null})}}),r}async getAllMetadataForRoom(t){let e=await this.roomManager.getRoomConnectionIds(t),r=await this.getInstanceIdsForConnections(e),s=[];return e.forEach(n=>{try{let i=r[n]?JSON.parse(r[n]):null;s.push({id:n,metadata:i})}catch(i){console.error(`Failed to parse metadata for connection ${n}:`,i),s.push({id:n,metadata:null})}}),s}async cleanupConnection(t){await this.deregisterConnection(t)}};var T=class{constructor(t){this.PRESENCE_KEY_PATTERN=/^mesh:presence:room:(.+):conn:(.+)$/;this.PRESENCE_STATE_KEY_PATTERN=/^mesh:presence:state:(.+):conn:(.+)$/;this.trackedRooms=[];this.roomGuards=new Map;this.roomTTLs=new Map;this.defaultTTL=0;this.redis=t.redis,this.roomManager=t.roomManager,this.redisManager=t.redisManager,this.presenceExpirationEventsEnabled=t.enableExpirationEvents??!0,this.presenceExpirationEventsEnabled&&this.subscribeToExpirationEvents()}getExpiredEventsPattern(){return`__keyevent@${this.redis.options?.db??0}__:expired`}subscribeToExpirationEvents(){let{subClient:t}=this.redisManager,e=this.getExpiredEventsPattern();t.psubscribe(e),t.on("pmessage",(r,s,n)=>{(this.PRESENCE_KEY_PATTERN.test(n)||this.PRESENCE_STATE_KEY_PATTERN.test(n))&&this.handleExpiredKey(n)})}async handleExpiredKey(t){try{let e=t.match(this.PRESENCE_KEY_PATTERN);if(e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.markOffline(s,r);return}if(e=t.match(this.PRESENCE_STATE_KEY_PATTERN),e&&e[1]&&e[2]){let r=e[1],s=e[2];await this.publishPresenceStateUpdate(r,s,null)}}catch(e){console.error("[PresenceManager] Failed to handle expired key:",e)}}trackRoom(t,e){this.trackedRooms.push(t),typeof e=="function"?this.roomGuards.set(t,e):e&&typeof e=="object"&&(e.guard&&this.roomGuards.set(t,e.guard),e.ttl&&typeof e.ttl=="number"&&this.roomTTLs.set(t,e.ttl))}async isRoomTracked(t,e){let r=this.trackedRooms.find(s=>typeof s=="string"?s===t:s.test(t));if(!r)return!1;if(e){let s=this.roomGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}}return!0}getRoomTTL(t){let e=this.trackedRooms.find(r=>typeof r=="string"?r===t:r.test(t));if(e){let r=this.roomTTLs.get(e);if(r!==void 0)return r}return this.defaultTTL}presenceRoomKey(t){return`mesh:presence:room:${t}`}presenceConnectionKey(t,e){return`mesh:presence:room:${t}:conn:${e}`}presenceStateKey(t,e){return`mesh:presence:state:${t}:conn:${e}`}async markOnline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.getRoomTTL(e),i=this.redis.pipeline();if(i.sadd(r,t),n>0){let o=Math.max(1,Math.floor(n/1e3));i.set(s,"","EX",o)}else i.set(s,"");await i.exec(),await this.publishPresenceUpdate(e,t,"join")}async markOffline(t,e){let r=this.presenceRoomKey(e),s=this.presenceConnectionKey(e,t),n=this.presenceStateKey(e,t),i=this.redis.pipeline();i.srem(r,t),i.del(s),i.del(n),await i.exec(),await this.publishPresenceUpdate(e,t,"leave")}async refreshPresence(t,e){let r=this.presenceConnectionKey(e,t),s=this.getRoomTTL(e);if(s>0){let n=Math.max(1,Math.floor(s/1e3));await this.redis.set(r,"","EX",n)}else await this.redis.set(r,"")}async getPresentConnections(t){return this.redis.smembers(this.presenceRoomKey(t))}async publishPresenceUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:r,connectionId:e,roomName:t,timestamp:Date.now()});await this.redis.publish(s,n)}async publishPresenceState(t,e,r,s,n){let i=this.presenceStateKey(e,t),o=JSON.stringify(r),a=this.redis.pipeline();s&&s>0?a.set(i,o,"PX",s):a.set(i,o),await a.exec(),!n&&await this.publishPresenceStateUpdate(e,t,r)}async clearPresenceState(t,e){let r=this.presenceStateKey(e,t);await this.redis.del(r),await this.publishPresenceStateUpdate(e,t,null)}async getPresenceState(t,e){let r=this.presenceStateKey(e,t),s=await this.redis.get(r);if(!s)return null;try{return JSON.parse(s)}catch(n){return console.error(`[PresenceManager] Failed to parse presence state: ${n}`),null}}async getAllPresenceStates(t){let e=new Map,r=await this.getPresentConnections(t);if(r.length===0)return e;let s=this.redis.pipeline();for(let i of r)s.get(this.presenceStateKey(t,i));let n=await s.exec();if(!n)return e;for(let i=0;i<r.length;i++){let o=r[i];if(!o)continue;let[a,d]=n[i]||[];if(!(a||!d))try{let p=JSON.parse(d);e.set(o,p)}catch(p){console.error(`[PresenceManager] Failed to parse presence state: ${p}`)}}return e}async publishPresenceStateUpdate(t,e,r){let s=`mesh:presence:updates:${t}`,n=JSON.stringify({type:"state",connectionId:e,roomName:t,state:r,timestamp:Date.now()});await this.redis.publish(s,n)}async cleanupConnection(t){let e=t.id,r=await this.roomManager.getRoomsForConnection(e);for(let s of r)await this.isRoomTracked(s)&&await this.markOffline(e,s)}async cleanup(){let{subClient:t}=this.redisManager;if(t&&t.status!=="end"){let e=this.getExpiredEventsPattern();await new Promise(r=>{t.punsubscribe(e,()=>r())})}}};f();import $e from"fast-json-patch";var O=class{constructor(t){this.recordUpdateCallbacks=[];this.recordRemovedCallbacks=[];this.redis=t.redis,this.server=t.server}getServer(){return this.server}getRedis(){return this.redis}recordKey(t){return`${le}${t}`}recordVersionKey(t){return`${he}${t}`}async getRecord(t){let e=await this.redis.get(this.recordKey(t));return e?JSON.parse(e):null}async getVersion(t){let e=await this.redis.get(this.recordVersionKey(t));return e?parseInt(e,10):0}async getRecordAndVersion(t){let e=this.redis.pipeline();e.get(this.recordKey(t)),e.get(this.recordVersionKey(t));let r=await e.exec(),s=r?.[0]?.[1],n=r?.[1]?.[1],i=s?JSON.parse(s):null,o=n?parseInt(n,10):0;return{record:i,version:o}}async publishUpdate(t,e,r="replace"){let s=this.recordKey(t),n=this.recordVersionKey(t),{record:i,version:o}=await this.getRecordAndVersion(t),a;r==="merge"?g(i)&&g(e)?a={...i,...e}:a=e:r==="deepMerge"&&g(i)&&g(e)?a=v(i,e):a=e;let d=$e.compare(i??{},a??{});if(d.length===0)return null;let p=o+1,h=this.redis.pipeline();return h.set(s,JSON.stringify(a)),h.set(n,p.toString()),await h.exec(),this.recordUpdateCallbacks.length>0&&Promise.all(this.recordUpdateCallbacks.map(async u=>{try{await u({recordId:t,value:a})}catch(b){console.error(`Error in record update callback for ${t}:`,b)}})).catch(u=>{console.error(`Error in record update callbacks for ${t}:`,u)}),{patch:d,version:p,finalValue:a}}async deleteRecord(t){let{record:e,version:r}=await this.getRecordAndVersion(t);if(!e)return null;let s=this.redis.pipeline();return s.del(this.recordKey(t)),s.del(this.recordVersionKey(t)),await s.exec(),this.recordRemovedCallbacks.length>0&&Promise.all(this.recordRemovedCallbacks.map(async n=>{try{await n({recordId:t,value:e})}catch(i){console.error(`Error in record removed callback for ${t}:`,i)}})).catch(n=>{console.error(`Error in record removed callbacks for ${t}:`,n)}),{version:r}}onRecordUpdate(t){return this.recordUpdateCallbacks.push(t),()=>{this.recordUpdateCallbacks=this.recordUpdateCallbacks.filter(e=>e!==t)}}onRecordRemoved(t){return this.recordRemovedCallbacks.push(t),()=>{this.recordRemovedCallbacks=this.recordRemovedCallbacks.filter(e=>e!==t)}}};f();var N=class{constructor(t){this.redis=t.redis}roomKey(t){return`mesh:room:${t}`}connectionsRoomKey(t){return`mesh:connection:${t}:rooms`}roomMetadataKey(t){return`mesh:roommeta:${t}`}async getRoomConnectionIds(t){return this.redis.smembers(this.roomKey(t))}async connectionIsInRoom(t,e){let r=typeof e=="string"?e:e.id;return!!await this.redis.sismember(this.roomKey(t),r)}async addToRoom(t,e){let r=typeof e=="string"?e:e.id;await this.redis.sadd(this.roomKey(t),r),await this.redis.sadd(this.connectionsRoomKey(r),t)}async getRoomsForConnection(t){let e=typeof t=="string"?t:t.id;return await this.redis.smembers(this.connectionsRoomKey(e))}async getAllRooms(){return(await this.redis.keys("mesh:room:*")).map(e=>e.replace("mesh:room:",""))}async removeFromRoom(t,e){let r=typeof e=="string"?e:e.id,s=this.redis.pipeline();s.srem(this.roomKey(t),r),s.srem(this.connectionsRoomKey(r),t),await s.exec()}async removeFromAllRooms(t){let e=typeof t=="string"?t:t.id,r=await this.redis.smembers(this.connectionsRoomKey(e)),s=this.redis.pipeline();for(let n of r)s.srem(this.roomKey(n),e);s.del(this.connectionsRoomKey(e)),await s.exec()}async clearRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),await r.exec()}async deleteRoom(t){let e=await this.getRoomConnectionIds(t),r=this.redis.pipeline();for(let s of e)r.srem(this.connectionsRoomKey(s),t);r.del(this.roomKey(t)),r.del(this.roomMetadataKey(t)),await r.exec()}async cleanupConnection(t){let e=await this.redis.smembers(this.connectionsRoomKey(t.id)),r=this.redis.pipeline();for(let s of e)r.srem(this.roomKey(s),t.id);r.del(this.connectionsRoomKey(t.id)),await r.exec()}async setMetadata(t,e,r){let s,n=r?.strategy||"replace";if(n==="replace")s=e;else{let i=await this.getMetadata(t);n==="merge"?g(i)&&g(e)?s={...i,...e}:s=e:n==="deepMerge"&&(g(i)&&g(e)?s=v(i,e):s=e)}await this.redis.hset(this.roomMetadataKey(t),"data",JSON.stringify(s))}async getMetadata(t){let e=await this.redis.hget(this.roomMetadataKey(t),"data");return e?JSON.parse(e):null}async getAllMetadata(){let t=await this.redis.keys("mesh:roommeta:*"),e=[];if(t.length===0)return e;let r=this.redis.pipeline();t.forEach(n=>r.hget(n,"data"));let s=await r.exec();return t.forEach((n,i)=>{let o=n.replace("mesh:roommeta:",""),a=s?.[i]?.[1];if(a)try{let d=JSON.parse(a);e.push({id:o,metadata:d})}catch(d){console.error(`Failed to parse metadata for room ${o}:`,d)}}),e}};var K=class{constructor(t){this.connectionManager=t.connectionManager,this.roomManager=t.roomManager,this.instanceId=t.instanceId,this.pubClient=t.pubClient,this.getPubSubChannel=t.getPubSubChannel,this.emitError=t.emitError}async broadcast(t,e,r){let s={command:t,payload:e};try{if(r){let n=r.map(({id:a})=>a),i=await this.connectionManager.getAllConnectionIds(),o=n.filter(a=>i.includes(a));await this.publishOrSend(o,s)}else{let n=await this.connectionManager.getAllConnectionIds();await this.publishOrSend(n,s)}}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoom(t,e,r){let s=await this.roomManager.getRoomConnectionIds(t);try{await this.publishOrSend(s,{command:e,payload:r})}catch(n){this.emitError(new Error(`Failed to broadcast command "${e}": ${n}`))}}async broadcastExclude(t,e,r){let s=new Set((Array.isArray(r)?r:[r]).map(({id:n})=>n));try{let n=(await this.connectionManager.getAllConnectionIds()).filter(i=>!s.has(i));await this.publishOrSend(n,{command:t,payload:e})}catch(n){this.emitError(new Error(`Failed to broadcast command "${t}": ${n}`))}}async broadcastRoomExclude(t,e,r,s){let n=new Set((Array.isArray(s)?s:[s]).map(({id:i})=>i));try{let i=(await this.roomManager.getRoomConnectionIds(t)).filter(o=>!n.has(o));await this.publishOrSend(i,{command:e,payload:r})}catch(i){this.emitError(new Error(`Failed to broadcast command "${e}": ${i}`))}}async publishOrSend(t,e){if(t.length===0)return;let r=await this.connectionManager.getInstanceIdsForConnections(t),s={};for(let n of t){let i=r[n];i&&(s[i]||(s[i]=[]),s[i].push(n))}for(let[n,i]of Object.entries(s))if(i.length!==0)if(n===this.instanceId)i.forEach(o=>{let a=this.connectionManager.getLocalConnection(o);a&&!a.isDead&&a.send(e)});else{let a=JSON.stringify({targetConnectionIds:i,command:e});try{await this.pubClient.publish(this.getPubSubChannel(n),a)}catch(d){this.emitError(new Error(`Failed to publish command "${e.command}": ${d}`))}}}};import{EventEmitter as xe}from"events";var w=class c extends xe{static getInstance(){return c.instance||(c.instance=new c),c.instance}constructor(){super(),this.setMaxListeners(100)}publishMessage(t,e,r){let s=Date.now();this.emit("message",{channel:t,message:e,instanceId:r,timestamp:s})}subscribeToMessages(t){this.on("message",t)}unsubscribeFromMessages(t){this.off("message",t)}};var D=class{constructor(t){this.exposedChannels=[];this.channelGuards=new Map;this.channelSubscriptions={};this.redis=t.redis,this.pubClient=t.pubClient,this.subClient=t.subClient,this.messageStream=w.getInstance()}setPersistenceManager(t){this.persistenceManager=t}exposeChannel(t,e){this.exposedChannels.push(t),e&&this.channelGuards.set(t,e)}async isChannelExposed(t,e){let r=this.exposedChannels.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.channelGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}async writeChannel(t,e,r=0,s){let n=parseInt(r,10);!isNaN(n)&&n>0&&(await this.pubClient.rpush(`mesh:history:${t}`,e),await this.pubClient.ltrim(`mesh:history:${t}`,-n,-1)),this.messageStream.publishMessage(t,e,s),await this.pubClient.publish(t,e)}addSubscription(t,e){this.channelSubscriptions[t]||(this.channelSubscriptions[t]=new Set),this.channelSubscriptions[t].add(e)}removeSubscription(t,e){return this.channelSubscriptions[t]?(this.channelSubscriptions[t].delete(e),this.channelSubscriptions[t].size===0&&delete this.channelSubscriptions[t],!0):!1}getSubscribers(t){return this.channelSubscriptions[t]}async subscribeToRedisChannel(t){return new Promise((e,r)=>{this.subClient.subscribe(t,s=>{s?r(s):e()})})}async unsubscribeFromRedisChannel(t){return new Promise((e,r)=>{this.subClient.unsubscribe(t,s=>{s?r(s):e()})})}async getChannelHistory(t,e,r){if(this.persistenceManager&&r!==void 0)try{return(await this.persistenceManager.getMessages(t,r,e)).map(i=>i.message)}catch{let i=`mesh:history:${t}`;return this.redis.lrange(i,0,e-1)}let s=`mesh:history:${t}`;return this.redis.lrange(s,0,e-1)}async getPersistedMessages(t,e,r){if(!this.persistenceManager)throw new Error("Persistence not enabled");return this.persistenceManager.getMessages(t,e,r)}cleanupConnection(t){for(let e in this.channelSubscriptions)this.removeSubscription(e,t)}};var $=class{constructor(t,e,r,s){this.server=t,this.command=e,this.connection=r,this.payload=s}};f();var U=class{constructor(){this.commands={};this.globalMiddlewares=[];this.middlewares={}}exposeCommand(t,e,r=[]){this.commands[t]=e,r.length>0&&this.useMiddlewareWithCommand(t,r)}useMiddleware(...t){this.globalMiddlewares.push(...t)}useMiddlewareWithCommand(t,e){e.length&&(this.middlewares[t]=this.middlewares[t]||[],this.middlewares[t]=e.concat(this.middlewares[t]))}async runCommand(t,e,r,s,n){let i=new $(n,e,s,r);try{if(!this.commands[e])throw new oe(`Command "${e}" not found`,"ENOTFOUND","CommandError");if(this.globalMiddlewares.length)for(let a of this.globalMiddlewares)await a(i);if(this.middlewares[e])for(let a of this.middlewares[e])await a(i);let o=await this.commands[e](i);s.send({id:t,command:e,payload:o})}catch(o){let a=o instanceof Error?{error:o.message,code:o.code||"ESERVER",name:o.name||"Error"}:{error:String(o),code:"EUNKNOWN",name:"UnknownError"};s.send({id:t,command:e,payload:a})}}getCommands(){return this.commands}hasCommand(t){return!!this.commands[t]}};f();var z=class{constructor(t){this.collectionManager=null;this.collectionUpdateTimeouts=new Map;this.collectionMaxDelayTimeouts=new Map;this.pendingCollectionUpdates=new Map;this.COLLECTION_UPDATE_DEBOUNCE_MS=50;this.COLLECTION_MAX_DELAY_MS=200;this.subClient=t.subClient,this.pubClient=t.pubClient,this.instanceId=t.instanceId,this.connectionManager=t.connectionManager,this.recordManager=t.recordManager,this.recordSubscriptions=t.recordSubscriptions,this.getChannelSubscriptions=t.getChannelSubscriptions,this.emitError=t.emitError,this.collectionManager=t.collectionManager||null}subscribeToInstanceChannel(){let t=`${C}${this.instanceId}`;return this._subscriptionPromise=new Promise((e,r)=>{this.subClient.subscribe(t,M,"mesh:collection:record-change"),this.subClient.psubscribe("mesh:presence:updates:*",s=>{if(s){this.emitError(new Error(`Failed to subscribe to channels/patterns: ${JSON.stringify({cause:s})}`)),r(s);return}e()})}),this.setupMessageHandlers(),this._subscriptionPromise}setupMessageHandlers(){this.subClient.on("message",async(t,e)=>{if(t.startsWith(C))this.handleInstancePubSubMessage(t,e);else if(t===M)this.handleRecordUpdatePubSubMessage(e);else if(t==="mesh:collection:record-change")this.handleCollectionRecordChange(e);else{let r=this.getChannelSubscriptions(t);if(r)for(let s of r)s.isDead||s.send({command:"mesh/subscription-message",payload:{channel:t,message:e}})}}),this.subClient.on("pmessage",async(t,e,r)=>{if(t==="mesh:presence:updates:*"){let s=this.getChannelSubscriptions(e);if(s)try{let n=JSON.parse(r);s.forEach(i=>{i.isDead?s.delete(i):i.send({command:"mesh/presence-update",payload:n})})}catch{this.emitError(new Error(`Failed to parse presence update: ${r}`))}}})}handleInstancePubSubMessage(t,e){try{let r=JSON.parse(e);if(!r||!Array.isArray(r.targetConnectionIds)||!r.command||typeof r.command.command!="string")throw new Error("Invalid message format");let{targetConnectionIds:s,command:n}=r;s.forEach(i=>{let o=this.connectionManager.getLocalConnection(i);o&&!o.isDead&&o.send(n)})}catch{this.emitError(new Error(`Failed to parse message: ${e}`))}}handleRecordUpdatePubSubMessage(t){try{let e=JSON.parse(t),{recordId:r,newValue:s,patch:n,version:i,deleted:o}=e;if(!r||typeof i!="number")throw new Error("Invalid record update message format");let a=this.recordSubscriptions.get(r);if(!a)return;a.forEach((d,p)=>{let h=this.connectionManager.getLocalConnection(p);h&&!h.isDead?o?h.send({command:"mesh/record-deleted",payload:{recordId:r,version:i}}):d==="patch"&&n?h.send({command:"mesh/record-update",payload:{recordId:r,patch:n,version:i}}):d==="full"&&s!==void 0&&h.send({command:"mesh/record-update",payload:{recordId:r,full:s,version:i}}):(!h||h.isDead)&&(a.delete(p),a.size===0&&this.recordSubscriptions.delete(r))}),o&&this.recordSubscriptions.delete(r)}catch{this.emitError(new Error(`Failed to parse record update message: ${t}`))}}async handleCollectionRecordChange(t){if(!this.collectionManager)return;let e=this.collectionManager.getCollectionSubscriptions(),r=new Set;for(let[s]of e.entries())r.add(s);for(let s of r){let n=this.collectionUpdateTimeouts.get(s);n&&clearTimeout(n),this.pendingCollectionUpdates.has(s)||this.pendingCollectionUpdates.set(s,new Set),this.pendingCollectionUpdates.get(s).add(t);let i=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_UPDATE_DEBOUNCE_MS);if(this.collectionUpdateTimeouts.set(s,i),!this.collectionMaxDelayTimeouts.has(s)){let o=setTimeout(async()=>{await this.processCollectionUpdates(s)},this.COLLECTION_MAX_DELAY_MS);this.collectionMaxDelayTimeouts.set(s,o)}}}async processCollectionUpdates(t){let e=this.pendingCollectionUpdates.get(t);if(!e||e.size===0)return;let r=this.collectionUpdateTimeouts.get(t),s=this.collectionMaxDelayTimeouts.get(t);if(r&&(clearTimeout(r),this.collectionUpdateTimeouts.delete(t)),s&&(clearTimeout(s),this.collectionMaxDelayTimeouts.delete(t)),this.pendingCollectionUpdates.delete(t),!this.collectionManager)return;let n=this.collectionManager.getCollectionSubscriptions().get(t);if(!(!n||n.size===0))for(let[i,{version:o}]of n.entries())try{let a=this.connectionManager.getLocalConnection(i);if(!a||a.isDead)continue;let d=await this.collectionManager.resolveCollection(t,a),p=d.map(m=>m.id),h=`mesh:collection:${t}:${i}`,u=await this.pubClient.get(h),b=u?JSON.parse(u):[],R=p.filter(m=>!b.includes(m)),te=d.filter(m=>R.includes(m.id)),fe=b.filter(m=>!p.includes(m)),A=[];for(let m of fe)try{let E=await this.recordManager.getRecord(m);A.push(E||{id:m})}catch{A.push({id:m})}let re=[];for(let m of e)b.includes(m)&&!p.includes(m)&&re.push(m);let ye=te.length>0||A.length>0,be=re.length>0;if(ye||be){let m=o+1;this.collectionManager.updateSubscriptionVersion(t,i,m),await this.pubClient.set(h,JSON.stringify(p)),a.send({command:"mesh/collection-diff",payload:{collectionId:t,added:te,removed:A,version:m}})}for(let m of e)if(p.includes(m))try{let{record:E,version:ve}=await this.recordManager.getRecordAndVersion(m);E&&a.send({command:"mesh/record-update",payload:{recordId:m,version:ve,full:E}})}catch{l.info(`Record ${m} not found during collection update (likely deleted).`)}}catch(a){this.emitError(new Error(`Error processing collection ${t} for connection ${i}: ${a}`))}}getSubscriptionPromise(){return this._subscriptionPromise}getPubSubChannel(t){return`${C}${t}`}async cleanup(){for(let t of this.collectionUpdateTimeouts.values())clearTimeout(t);this.collectionUpdateTimeouts.clear();for(let t of this.collectionMaxDelayTimeouts.values())clearTimeout(t);if(this.collectionMaxDelayTimeouts.clear(),this.pendingCollectionUpdates.clear(),this.subClient&&this.subClient.status!=="end"){let t=`${C}${this.instanceId}`;await Promise.all([new Promise(e=>{this.subClient.unsubscribe(t,M,"mesh:collection:record-change",()=>e())}),new Promise(e=>{this.subClient.punsubscribe("mesh:presence:updates:*",()=>e())})])}}};var B=class{constructor(t){this.persistenceManager=null;this.exposedRecords=[];this.exposedWritableRecords=[];this.recordGuards=new Map;this.writableRecordGuards=new Map;this.recordSubscriptions=new Map;this.pubClient=t.pubClient,this.recordManager=t.recordManager,this.emitError=t.emitError,this.persistenceManager=t.persistenceManager||null}setPersistenceManager(t){this.persistenceManager=t}exposeRecord(t,e){this.exposedRecords.push(t),e&&this.recordGuards.set(t,e)}exposeWritableRecord(t,e){this.exposedWritableRecords.push(t),e&&this.writableRecordGuards.set(t,e)}async isRecordExposed(t,e){let r=this.exposedRecords.find(i=>typeof i=="string"?i===t:i.test(t)),s=!1;if(r){let i=this.recordGuards.get(r);if(i)try{s=await Promise.resolve(i(e,t))}catch{s=!1}else s=!0}return!!(s||this.exposedWritableRecords.find(i=>typeof i=="string"?i===t:i.test(t)))}async isRecordWritable(t,e){let r=this.exposedWritableRecords.find(n=>typeof n=="string"?n===t:n.test(t));if(!r)return!1;let s=this.writableRecordGuards.get(r);if(s)try{return await Promise.resolve(s(e,t))}catch{return!1}return!0}addSubscription(t,e,r){this.recordSubscriptions.has(t)||this.recordSubscriptions.set(t,new Map),this.recordSubscriptions.get(t).set(e,r)}removeSubscription(t,e){let r=this.recordSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.recordSubscriptions.delete(t),!0):!1}getSubscribers(t){return this.recordSubscriptions.get(t)}async writeRecord(t,e,r){let s=await this.recordManager.publishUpdate(t,e,r?.strategy||"replace");if(!s)return;let{patch:n,version:i,finalValue:o}=s;this.persistenceManager&&this.persistenceManager.handleRecordUpdate(t,o,i);let a={recordId:t,newValue:o,patch:n,version:i};try{await this.pubClient.publish(M,JSON.stringify(a))}catch(d){this.emitError(new Error(`Failed to publish record update for "${t}": ${d}`))}}cleanupConnection(t){let e=t.id;this.recordSubscriptions.forEach((r,s)=>{r.has(e)&&(r.delete(e),r.size===0&&this.recordSubscriptions.delete(s))})}async publishRecordDeletion(t,e){let r={recordId:t,deleted:!0,version:e};try{await this.pubClient.publish(M,JSON.stringify(r))}catch(s){this.emitError(new Error(`Failed to publish record deletion for "${t}": ${s}`))}}getRecordSubscriptions(){return this.recordSubscriptions}};import{Redis as Ae}from"ioredis";var q=class{constructor(){this._redis=null;this._pubClient=null;this._subClient=null;this._isShuttingDown=!1}initialize(t,e){this._redis=new Ae({retryStrategy:r=>this._isShuttingDown||r>10?null:Math.min(1e3*Math.pow(2,r),3e4),...t}),this._redis.on("error",r=>{e(new Error(`Redis error: ${r}`))}),this._pubClient=this._redis.duplicate(),this._subClient=this._redis.duplicate()}get redis(){if(!this._redis)throw new Error("Redis not initialized");return this._redis}get pubClient(){if(!this._pubClient)throw new Error("Redis pub client not initialized");return this._pubClient}get subClient(){if(!this._subClient)throw new Error("Redis sub client not initialized");return this._subClient}disconnect(){this._isShuttingDown=!0,this._pubClient&&(this._pubClient.disconnect(),this._pubClient=null),this._subClient&&(this._subClient.disconnect(),this._subClient=null),this._redis&&(this._redis.disconnect(),this._redis=null)}get isShuttingDown(){return this._isShuttingDown}set isShuttingDown(t){this._isShuttingDown=t}async enableKeyspaceNotifications(){let t=await this.redis.config("GET","notify-keyspace-events"),r=(Array.isArray(t)&&t.length>1?t[1]:"")||"";r.includes("E")||(r+="E"),r.includes("x")||(r+="x"),await this.redis.config("SET","notify-keyspace-events",r)}};var J=class{constructor(t){this.heartbeatInterval=null;this.heartbeatTTL=120;this.heartbeatFrequency=15e3;this.cleanupInterval=null;this.cleanupFrequency=6e4;this.cleanupLockTTL=10;this.redis=t.redis,this.instanceId=t.instanceId}async start(){await this.registerInstance(),await this.updateHeartbeat(),this.heartbeatInterval=setInterval(()=>this.updateHeartbeat(),this.heartbeatFrequency),this.cleanupInterval=setInterval(()=>this.performCleanup(),this.cleanupFrequency)}async stop(){this.heartbeatInterval&&(clearInterval(this.heartbeatInterval),this.heartbeatInterval=null),this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null),await this.deregisterInstance()}async registerInstance(){await this.redis.sadd("mesh:instances",this.instanceId)}async deregisterInstance(){await this.redis.srem("mesh:instances",this.instanceId),await this.redis.del(this.getHeartbeatKey())}async updateHeartbeat(){let t=this.getHeartbeatKey();await this.redis.set(t,Date.now().toString(),"EX",this.heartbeatTTL)}getHeartbeatKey(){return`mesh:instance:${this.instanceId}:heartbeat`}async acquireCleanupLock(){return await this.redis.set("mesh:cleanup:lock",this.instanceId,"EX",this.cleanupLockTTL,"NX")==="OK"}async releaseCleanupLock(){await this.redis.eval(`
|
|
51
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
52
|
+
return redis.call("del", KEYS[1])
|
|
53
|
+
else
|
|
54
|
+
return 0
|
|
55
|
+
end
|
|
56
|
+
`,1,"mesh:cleanup:lock",this.instanceId)}async performCleanup(){try{if(!await this.acquireCleanupLock())return;let e=await this.redis.smembers("mesh:instances"),r=await this.redis.hgetall("mesh:connections"),s=new Set([...e,...Object.values(r)]);for(let n of s){if(n===this.instanceId)continue;let i=`mesh:instance:${n}:heartbeat`;await this.redis.get(i)||(console.log(`Found dead instance: ${n}`),await this.cleanupDeadInstance(n))}}catch(t){console.error("Error during cleanup:",t)}finally{await this.releaseCleanupLock()}}async cleanupDeadInstance(t){try{let e=`mesh:connections:${t}`,r=await this.redis.smembers(e);for(let n of r)await this.cleanupConnection(n);let s=await this.redis.hgetall("mesh:connections");for(let[n,i]of Object.entries(s))i===t&&await this.cleanupConnection(n);await this.redis.srem("mesh:instances",t),await this.redis.del(e),console.log(`Cleaned up dead instance: ${t}`)}catch(e){console.error(`Error cleaning up instance ${t}:`,e)}}async deleteMatchingKeys(t){let e=this.redis.scanStream({match:t}),r=this.redis.pipeline();return e.on("data",s=>{for(let n of s)r.del(n)}),new Promise((s,n)=>{e.on("end",async()=>{await r.exec(),s()}),e.on("error",n)})}async cleanupConnection(t){try{let e=`mesh:connection:${t}:rooms`,r=await this.redis.smembers(e),s=this.redis.pipeline();for(let n of r)s.srem(`mesh:room:${n}`,t),s.srem(`mesh:presence:room:${n}`,t),s.del(`mesh:presence:room:${n}:conn:${t}`),s.del(`mesh:presence:state:${n}:conn:${t}`);s.del(e),s.hdel("mesh:connections",t),await this.deleteMatchingKeys(`mesh:collection:*:${t}`),await s.exec(),console.log(`Cleaned up stale connection: ${t}`)}catch(e){console.error(`Error cleaning up connection ${t}:`,e)}}};var X=class{constructor(t){this.exposedCollections=[];this.collectionSubscriptions=new Map;this.redis=t.redis,this.emitError=t.emitError}exposeCollection(t,e){this.exposedCollections.push({pattern:t,resolver:e})}async isCollectionExposed(t,e){return!!this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t))}async resolveCollection(t,e){let r=this.exposedCollections.find(s=>typeof s.pattern=="string"?s.pattern===t:s.pattern.test(t));if(!r)throw new Error(`Collection "${t}" is not exposed`);try{return await Promise.resolve(r.resolver(e,t))}catch(s){throw this.emitError(new Error(`Failed to resolve collection "${t}": ${s}`)),s}}async addSubscription(t,e,r){this.collectionSubscriptions.has(t)||this.collectionSubscriptions.set(t,new Map);let s=await this.resolveCollection(t,r),n=s.map(o=>o.id),i=1;return this.collectionSubscriptions.get(t).set(e,{version:i}),await this.redis.set(`mesh:collection:${t}:${e}`,JSON.stringify(n)),{ids:n,records:s,version:i}}async removeSubscription(t,e){let r=this.collectionSubscriptions.get(t);return r?.has(e)?(r.delete(e),r.size===0&&this.collectionSubscriptions.delete(t),await this.redis.del(`mesh:collection:${t}:${e}`),!0):!1}async publishRecordChange(t){try{await this.redis.publish("mesh:collection:record-change",t)}catch(e){this.emitError(new Error(`Failed to publish record change for ${t}: ${e}`))}}async cleanupConnection(t){let e=t.id,r=[];this.collectionSubscriptions.forEach((s,n)=>{s.has(e)&&(s.delete(e),s.size===0&&this.collectionSubscriptions.delete(n),r.push(this.redis.del(`mesh:collection:${n}:${e}`).then(()=>{}).catch(i=>{this.emitError(new Error(`Failed to clean up collection subscription for "${n}": ${i}`))})))}),await Promise.all(r)}async listRecordsMatching(t,e){try{let r="mesh:record:",s=[],n="0";do{let d=await this.redis.scan(n,"MATCH",`${r}${t}`,"COUNT",e?.scanCount??100);n=d[0],s.push(...d[1])}while(n!=="0");if(s.length===0)return[];let i=await this.redis.mget(s),o=s.map(d=>d.substring(r.length)),a=i.map((d,p)=>{if(d===null)return null;try{let h=JSON.parse(d),u=o[p];return h.id===u?h:{...h,id:u}}catch(h){return this.emitError(new Error(`Failed to parse record for processing: ${d} - ${h.message}`)),null}}).filter(d=>d!==null);if(e?.map&&(a=a.map(e.map)),e?.sort&&a.sort(e.sort),e?.slice){let{start:d,count:p}=e.slice;a=a.slice(d,d+p)}return a}catch(r){return this.emitError(new Error(`Failed to list records matching "${t}": ${r.message}`)),[]}}getCollectionSubscriptions(){return this.collectionSubscriptions}updateSubscriptionVersion(t,e,r){let s=this.collectionSubscriptions.get(t);s?.has(e)&&s.set(e,{version:r})}};f();import{EventEmitter as ke}from"events";import{v4 as Ke}from"uuid";var x=class extends ke{constructor(e){super();this.channelPatterns=[];this.recordPatterns=[];this.messageBuffer=new Map;this.recordBuffer=new Map;this.flushTimers=new Map;this.recordFlushTimer=null;this.isShuttingDown=!1;this.initialized=!1;this.recordManager=null;this.pendingRecordUpdates=[];let{defaultAdapterOptions:r={},adapterType:s="sqlite"}=e;if(s==="postgres"){let{PostgreSQLPersistenceAdapter:n}=(me(),ne(pe));this.defaultAdapter=new n(r)}else{let{SQLitePersistenceAdapter:n}=(ue(),ne(ge));this.defaultAdapter=new n(r)}this.messageStream=w.getInstance()}setRecordManager(e){this.recordManager=e}async ready(){return this.initialized?Promise.resolve():new Promise(e=>{this.once("initialized",e)})}async processPendingRecordUpdates(){if(this.pendingRecordUpdates.length===0)return;l.info(`Processing ${this.pendingRecordUpdates.length} pending record updates`);let e=[...this.pendingRecordUpdates];this.pendingRecordUpdates=[];for(let{recordId:r,value:s,version:n}of e)this.handleRecordUpdate(r,s,n)}async initialize(){if(!this.initialized)try{await this.defaultAdapter.initialize(),this.messageStream.subscribeToMessages(this.handleStreamMessage.bind(this)),this.initialized=!0,await this.processPendingRecordUpdates(),this.emit("initialized")}catch(e){throw l.error("Failed to initialize persistence manager:",e),e}}async restorePersistedRecords(){if(!this.recordManager){l.warn("Cannot restore persisted records: record manager not available");return}let e=this.recordManager.getRedis();if(!e){l.warn("Cannot restore records: Redis not available");return}try{if(l.info("Restoring persisted records..."),this.recordPatterns.length===0){l.info("No record patterns to restore");return}l.info(`Found ${this.recordPatterns.length} record patterns to restore`);for(let r of this.recordPatterns){l.info(`Config keys: ${Object.keys(r).join(", ")}`),l.info(`Config.hooks: ${typeof r.hooks}, Config.adapter: ${typeof r.adapter}`),l.info(`Config.pattern: ${r.pattern}`);let{adapter:s,hooks:n}=r,i=n?"(custom hooks)":s?.restorePattern;try{let o=[];if(n?o=await n.restore():s&&(o=(s.adapter.getRecords?await s.adapter.getRecords(s.restorePattern):[]).map(d=>({recordId:d.recordId,value:typeof d.value=="string"?JSON.parse(d.value):d.value,version:d.version}))),o.length>0){l.info(`Restoring ${o.length} records for pattern ${i}`);for(let a of o)try{let{recordId:d,value:p,version:h}=a,u=this.recordManager.recordKey(d),b=this.recordManager.recordVersionKey(d),R=e.pipeline();R.set(u,JSON.stringify(p)),R.set(b,h.toString()),await R.exec(),l.debug(`Restored record ${d} (version ${h})`)}catch(d){l.error(`Failed to restore record ${a.recordId}: ${d}`)}}else l.debug(`No records found for pattern ${i}`)}catch(o){l.error(`Error restoring records for pattern ${i}: ${o}`)}}l.info("Finished restoring persisted records")}catch(r){l.error("Failed to restore persisted records:",r)}}handleStreamMessage(e){let{channel:r,message:s,instanceId:n,timestamp:i}=e;this.handleChannelMessage(r,s,n,i)}enableChannelPersistence(e,r={}){let s={historyLimit:r.historyLimit??50,filter:r.filter??(()=>!0),adapter:r.adapter??this.defaultAdapter,flushInterval:r.flushInterval??500,maxBufferSize:r.maxBufferSize??100};s.adapter!==this.defaultAdapter&&!this.isShuttingDown&&s.adapter.initialize().catch(n=>{l.error(`Failed to initialize adapter for pattern ${e}:`,n)}),this.channelPatterns.push({pattern:e,options:s})}enableRecordPersistence(e){l.info(`enableRecordPersistence called with config keys: ${Object.keys(e).join(", ")}`),l.info(`config.hooks type: ${typeof e.hooks}, config.adapter type: ${typeof e.adapter}`);let{pattern:r,adapter:s,hooks:n,flushInterval:i,maxBufferSize:o}=e;if(s&&n)throw new Error("Cannot use both adapter and hooks. Choose one.");let a;if(s){let d=s.adapter??this.defaultAdapter;a={adapter:d,restorePattern:s.restorePattern},d!==this.defaultAdapter&&!this.isShuttingDown&&d.initialize().catch(p=>{l.error(`Failed to initialize adapter for record pattern ${r}:`,p)})}this.recordPatterns.push({pattern:r,adapter:a,hooks:n,flushInterval:i??500,maxBufferSize:o??100})}getChannelPersistenceOptions(e){for(let{pattern:r,options:s}of this.channelPatterns)if(typeof r=="string"&&r===e||r instanceof RegExp&&r.test(e))return s}getRecordPersistenceConfig(e){for(let r of this.recordPatterns){let{pattern:s}=r;if(typeof s=="string"&&s===e||s instanceof RegExp&&s.test(e))return r}}handleChannelMessage(e,r,s,n){if(!this.initialized||this.isShuttingDown)return;let i=this.getChannelPersistenceOptions(e);if(!i||!i.filter(r,e))return;let o={id:Ke(),channel:e,message:r,instanceId:s,timestamp:n||Date.now()};if(this.messageBuffer.has(e)||this.messageBuffer.set(e,[]),this.messageBuffer.get(e).push(o),this.messageBuffer.get(e).length>=i.maxBufferSize){this.flushChannel(e);return}if(!this.flushTimers.has(e)){let a=setTimeout(()=>{this.flushChannel(e)},i.flushInterval);a.unref&&a.unref(),this.flushTimers.set(e,a)}}async flushChannel(e){if(!this.messageBuffer.has(e))return;this.flushTimers.has(e)&&(clearTimeout(this.flushTimers.get(e)),this.flushTimers.delete(e));let r=this.messageBuffer.get(e);if(r.length===0)return;this.messageBuffer.set(e,[]);let s=this.getChannelPersistenceOptions(e);if(s)try{await s.adapter.storeMessages(r),this.emit("flushed",{channel:e,count:r.length}),l.debug(`Flushed ${r.length} messages for channel ${e}`)}catch(n){if(l.error(`Failed to flush messages for channel ${e}:`,n),!this.isShuttingDown){let i=this.messageBuffer.get(e)||[];if(this.messageBuffer.set(e,[...r,...i]),!this.flushTimers.has(e)){let o=setTimeout(()=>{this.flushChannel(e)},1e3);o.unref&&o.unref(),this.flushTimers.set(e,o)}}}}async flushAll(){let e=Array.from(this.messageBuffer.keys());for(let r of e)await this.flushChannel(r)}async getMessages(e,r,s){if(!this.initialized)throw new Error("Persistence manager not initialized");let n=this.getChannelPersistenceOptions(e);if(!n)throw new Error(`Channel ${e} does not have persistence enabled`);return await this.flushChannel(e),n.adapter.getMessages(e,r,s||n.historyLimit)}handleRecordUpdate(e,r,s){if(this.isShuttingDown)return;if(!this.initialized){this.pendingRecordUpdates.push({recordId:e,value:r,version:s}),l.debug(`Buffered record update for ${e} (pending initialization)`);return}let n=this.getRecordPersistenceConfig(e);if(!n)return;let i={recordId:e,value:JSON.stringify(r),version:s,timestamp:Date.now()};if(this.recordBuffer.set(e,i),l.debug(`Added record ${e} to buffer, buffer size: ${this.recordBuffer.size}`),this.recordBuffer.size>=n.maxBufferSize){l.debug(`Buffer size ${this.recordBuffer.size} exceeds limit ${n.maxBufferSize}, flushing records`),this.flushRecords();return}this.recordFlushTimer||(l.debug(`Scheduling record flush in ${n.flushInterval}ms`),this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},n.flushInterval),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}async flushRecords(){if(this.recordBuffer.size===0)return;l.debug(`Flushing ${this.recordBuffer.size} records to storage`),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null);let e=Array.from(this.recordBuffer.values());this.recordBuffer.clear();let r=new Map,s=new Map;for(let i of e){let o=this.getRecordPersistenceConfig(i.recordId);o&&(o.hooks?(s.has(o.hooks.persist)||s.set(o.hooks.persist,[]),s.get(o.hooks.persist).push(i)):o.adapter&&(r.has(o.adapter.adapter)||r.set(o.adapter.adapter,[]),r.get(o.adapter.adapter).push(i)))}let n=(i,o)=>{if(l.error("Failed to flush records:",o),!this.isShuttingDown){for(let a of i)this.recordBuffer.set(a.recordId,a);this.recordFlushTimer||(this.recordFlushTimer=setTimeout(()=>{this.flushRecords()},1e3),this.recordFlushTimer.unref&&this.recordFlushTimer.unref())}};for(let[i,o]of s.entries())try{let a=o.map(d=>({recordId:d.recordId,value:JSON.parse(d.value),version:d.version}));l.debug(`Storing ${o.length} records with custom persist hook`),await i(a),this.emit("recordsFlushed",{count:o.length})}catch(a){n(o,a)}for(let[i,o]of r.entries())try{i.storeRecords?(l.debug(`Storing ${o.length} records with adapter`),await i.storeRecords(o),this.emit("recordsFlushed",{count:o.length})):l.warn("Adapter does not support storing records")}catch(a){n(o,a)}}async getPersistedRecords(e){if(!this.initialized)throw new Error("Persistence manager not initialized");await this.flushRecords();try{let r=this.defaultAdapter;if(r.getRecords)return await r.getRecords(e)}catch(r){l.error(`Failed to get persisted records for pattern ${e}:`,r)}return[]}async shutdown(){if(this.isShuttingDown)return;this.isShuttingDown=!0,this.messageStream.unsubscribeFromMessages(this.handleStreamMessage.bind(this));for(let r of this.flushTimers.values())clearTimeout(r);this.flushTimers.clear(),this.recordFlushTimer&&(clearTimeout(this.recordFlushTimer),this.recordFlushTimer=null),await this.flushAll(),await this.flushRecords();let e=new Set([this.defaultAdapter]);for(let{options:r}of this.channelPatterns)e.add(r.adapter);for(let r of this.recordPatterns)r.adapter&&e.add(r.adapter.adapter);for(let r of e)try{await r.close()}catch(s){l.error("Error closing persistence adapter:",s)}this.initialized=!1}};var Z=new WeakMap,ee=class extends Ue{constructor(e){let r={...e};e.authenticateConnection&&(r.verifyClient=(s,n)=>{Promise.resolve().then(()=>e.authenticateConnection(s.req)).then(i=>{i!=null?(Z.set(s.req,i),n(!0)):n(!1,401,"Unauthorized")}).catch(i=>{let o=i?.code??401,a=i?.message??"Unauthorized";n(!1,o,a)})});super(r);this.persistenceManager=null;this.status=y.OFFLINE;this._listening=!1;this.authenticateConnection=e.authenticateConnection,this.instanceId=De(),this.serverOptions={...e,pingInterval:e.pingInterval??3e4,latencyInterval:e.latencyInterval??5e3,maxMissedPongs:e.maxMissedPongs??1,logLevel:e.logLevel??W.ERROR},l.configure({level:this.serverOptions.logLevel,styling:!1}),this.redisManager=new q,this.redisManager.initialize(e.redisOptions,s=>this.emit("error",s)),this.instanceManager=new J({redis:this.redisManager.redis,instanceId:this.instanceId}),this.roomManager=new N({redis:this.redisManager.redis}),this.recordManager=new O({redis:this.redisManager.redis,server:this}),this.connectionManager=new I({redis:this.redisManager.pubClient,instanceId:this.instanceId,roomManager:this.roomManager}),this.presenceManager=new T({redis:this.redisManager.redis,roomManager:this.roomManager,redisManager:this.redisManager,enableExpirationEvents:this.serverOptions.enablePresenceExpirationEvents}),this.serverOptions.enablePresenceExpirationEvents&&this.redisManager.enableKeyspaceNotifications().catch(s=>this.emit("error",new Error(`Failed to enable keyspace notifications: ${s}`))),this.commandManager=new U,this.persistenceManager=new x({defaultAdapterOptions:this.serverOptions.persistenceOptions,adapterType:this.serverOptions.persistenceAdapter}),this.persistenceManager.initialize().catch(s=>{this.emit("error",new Error(`Failed to initialize persistence manager: ${s}`))}),this.channelManager=new D({redis:this.redisManager.redis,pubClient:this.redisManager.pubClient,subClient:this.redisManager.subClient}),this.channelManager.setPersistenceManager(this.persistenceManager),this.recordSubscriptionManager=new B({pubClient:this.redisManager.pubClient,recordManager:this.recordManager,emitError:s=>this.emit("error",s),persistenceManager:this.persistenceManager}),this.collectionManager=new X({redis:this.redisManager.redis,emitError:s=>this.emit("error",s)}),this.persistenceManager&&this.persistenceManager.setRecordManager(this.recordManager),this.recordManager.onRecordUpdate(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record update for collection check: ${n}`))}}),this.recordManager.onRecordRemoved(async({recordId:s})=>{try{await this.collectionManager.publishRecordChange(s)}catch(n){this.emit("error",new Error(`Failed to publish record removal for collection check: ${n}`))}}),this.pubSubManager=new z({subClient:this.redisManager.subClient,instanceId:this.instanceId,connectionManager:this.connectionManager,recordManager:this.recordManager,recordSubscriptions:this.recordSubscriptionManager.getRecordSubscriptions(),getChannelSubscriptions:this.channelManager.getSubscribers.bind(this.channelManager),emitError:s=>this.emit("error",s),collectionManager:this.collectionManager,pubClient:this.redisManager.pubClient}),this.broadcastManager=new K({connectionManager:this.connectionManager,roomManager:this.roomManager,instanceId:this.instanceId,pubClient:this.redisManager.pubClient,getPubSubChannel:s=>`${C}${s}`,emitError:s=>this.emit("error",s)}),this.on("listening",()=>{this.listening=!0,this.instanceManager.start()}),this.on("error",s=>{l.error(`Error: ${s}`)}),this.on("close",()=>{this.listening=!1}),this.pubSubManager.subscribeToInstanceChannel(),this.registerBuiltinCommands(),this.registerRecordCommands(),this.applyListeners()}get listening(){return this._listening}set listening(e){this._listening=e,this.status=e?y.ONLINE:y.OFFLINE}get port(){return this.address().port}async ready(){let e=this.listening?Promise.resolve():new Promise(s=>this.once("listening",s)),r=this.persistenceManager?this.persistenceManager.ready():Promise.resolve();await Promise.all([e,this.pubSubManager.getSubscriptionPromise(),r]),this.persistenceManager&&await this.persistenceManager.restorePersistedRecords()}applyListeners(){this.on("connection",async(e,r)=>{let s=new S(e,r,this.serverOptions,this);s.on("message",n=>{try{let i=n.toString(),o=_(i);o.id!==void 0&&!["latency:response","pong"].includes(o.command)&&this.commandManager.runCommand(o.id,o.command,o.payload,s,this)}catch(i){this.emit("error",i)}});try{await this.connectionManager.registerConnection(s);let n=Z.get(r);n&&(Z.delete(r),await this.connectionManager.setMetadata(s,n)),s.send({command:"mesh/assign-id",payload:s.id})}catch{s.close();return}this.emit("connected",s),s.on("close",async()=>{await this.cleanupConnection(s),this.emit("disconnected",s)}),s.on("error",n=>{this.emit("clientError",n,s)}),s.on("pong",async n=>{try{let i=await this.roomManager.getRoomsForConnection(n);for(let o of i)await this.presenceManager.isRoomTracked(o)&&await this.presenceManager.refreshPresence(n,o)}catch(i){this.emit("error",new Error(`Failed to refresh presence: ${i}`))}})})}exposeCommand(e,r,s=[]){this.commandManager.exposeCommand(e,r,s)}useMiddleware(...e){this.commandManager.useMiddleware(...e)}useMiddlewareWithCommand(e,r){this.commandManager.useMiddlewareWithCommand(e,r)}exposeChannel(e,r){this.channelManager.exposeChannel(e,r)}async writeChannel(e,r,s=0){return this.channelManager.writeChannel(e,r,s,this.instanceId)}enableChannelPersistence(e,r={}){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableChannelPersistence(e,r)}enableRecordPersistence(e){if(!this.persistenceManager)throw new Error("Persistence not enabled. Initialize the persistence manager first.");this.persistenceManager.enableRecordPersistence(e)}exposeRecord(e,r){this.recordSubscriptionManager.exposeRecord(e,r)}exposeWritableRecord(e,r){this.recordSubscriptionManager.exposeWritableRecord(e,r)}async writeRecord(e,r,s){return this.recordSubscriptionManager.writeRecord(e,r,s)}async getRecord(e){return this.recordManager.getRecord(e)}async deleteRecord(e){let r=await this.recordManager.deleteRecord(e);r&&await this.recordSubscriptionManager.publishRecordDeletion(e,r.version)}async listRecordsMatching(e,r){return this.collectionManager.listRecordsMatching(e,r)}exposeCollection(e,r){this.collectionManager.exposeCollection(e,r)}async isInRoom(e,r){let s=typeof r=="string"?r:r.id;return this.roomManager.connectionIsInRoom(e,s)}async addToRoom(e,r){let s=typeof r=="string"?r:r.id;await this.roomManager.addToRoom(e,r),await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOnline(s,e)}async removeFromRoom(e,r){let s=typeof r=="string"?r:r.id;return await this.presenceManager.isRoomTracked(e)&&await this.presenceManager.markOffline(s,e),this.roomManager.removeFromRoom(e,r)}async removeFromAllRooms(e){return this.roomManager.removeFromAllRooms(e)}async clearRoom(e){return this.roomManager.clearRoom(e)}async deleteRoom(e){return this.roomManager.deleteRoom(e)}async getRoomMembers(e){return this.roomManager.getRoomConnectionIds(e)}async getRoomMembersWithMetadata(e){let r=await this.roomManager.getRoomConnectionIds(e);return Promise.all(r.map(async s=>{try{let n=this.connectionManager.getLocalConnection(s),i;if(n)i=await this.connectionManager.getMetadata(n);else{let o=await this.redisManager.redis.hget("mesh:connections",s);i=o?JSON.parse(o):null}return{id:s,metadata:i}}catch{return{id:s,metadata:null}}}))}async getAllRooms(){return this.roomManager.getAllRooms()}async broadcast(e,r,s){return this.broadcastManager.broadcast(e,r,s)}async broadcastRoom(e,r,s){return this.broadcastManager.broadcastRoom(e,r,s)}async broadcastExclude(e,r,s){return this.broadcastManager.broadcastExclude(e,r,s)}async broadcastRoomExclude(e,r,s,n){return this.broadcastManager.broadcastRoomExclude(e,r,s,n)}trackPresence(e,r){this.presenceManager.trackRoom(e,r)}registerBuiltinCommands(){this.exposeCommand("mesh/noop",async()=>!0),this.exposeCommand("mesh/subscribe-channel",async e=>{let{channel:r,historyLimit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.channelManager.getSubscribers(r)||await this.channelManager.subscribeToRedisChannel(r),this.channelManager.addSubscription(r,e.connection),{success:!0,history:s&&s>0?await this.channelManager.getChannelHistory(r,s,n):[]}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/unsubscribe-channel",async e=>{let{channel:r}=e.payload,s=this.channelManager.removeSubscription(r,e.connection);return s&&!this.channelManager.getSubscribers(r)&&await this.channelManager.unsubscribeFromRedisChannel(r),s}),this.exposeCommand("mesh/get-channel-history",async e=>{let{channel:r,limit:s,since:n}=e.payload;if(!await this.channelManager.isChannelExposed(r,e.connection))return{success:!1,history:[]};try{return this.persistenceManager?.getChannelPersistenceOptions(r)?{success:!0,history:(await this.persistenceManager.getMessages(r,n,s||this.persistenceManager.getChannelPersistenceOptions(r)?.historyLimit)).map(o=>o.message)}:{success:!0,history:await this.channelManager.getChannelHistory(r,s||50,n)}}catch{return{success:!1,history:[]}}}),this.exposeCommand("mesh/join-room",async e=>{let{roomName:r}=e.payload;return await this.addToRoom(r,e.connection),{success:!0,present:await this.getRoomMembersWithMetadata(r)}}),this.exposeCommand("mesh/leave-room",async e=>{let{roomName:r}=e.payload;return await this.removeFromRoom(r,e.connection),{success:!0}}),this.exposeCommand("mesh/get-connection-metadata",async e=>{let{connectionId:r}=e.payload,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/get-my-connection-metadata",async e=>{let r=e.connection.id,s=this.connectionManager.getLocalConnection(r);if(s)return{metadata:await this.connectionManager.getMetadata(s)};{let n=await this.redisManager.redis.hget("mesh:connections",r);return{metadata:n?JSON.parse(n):null}}}),this.exposeCommand("mesh/set-my-connection-metadata",async e=>{let{metadata:r,options:s}=e.payload,n=e.connection.id,i=this.connectionManager.getLocalConnection(n);if(i)try{return await this.connectionManager.setMetadata(i,r,s),{success:!0}}catch{return{success:!1}}else return{success:!1}}),this.exposeCommand("mesh/get-room-metadata",async e=>{let{roomName:r}=e.payload;return{metadata:await this.roomManager.getMetadata(r)}})}registerRecordCommands(){this.exposeCommand("mesh/subscribe-record",async e=>{let{recordId:r,mode:s="full"}=e.payload,n=e.connection.id;if(!await this.recordSubscriptionManager.isRecordExposed(r,e.connection))return{success:!1};try{let{record:i,version:o}=await this.recordManager.getRecordAndVersion(r);return this.recordSubscriptionManager.addSubscription(r,n,s),{success:!0,record:i,version:o}}catch(i){return console.error(`Failed to subscribe to record ${r}:`,i),{success:!1}}}),this.exposeCommand("mesh/unsubscribe-record",async e=>{let{recordId:r}=e.payload,s=e.connection.id;return this.recordSubscriptionManager.removeSubscription(r,s)}),this.exposeCommand("mesh/publish-record-update",async e=>{let{recordId:r,newValue:s,options:n}=e.payload;if(!await this.recordSubscriptionManager.isRecordWritable(r,e.connection))throw new Error(`Record "${r}" is not writable by this connection.`);try{return await this.writeRecord(r,s,n),{success:!0}}catch(i){throw new Error(`Failed to publish update for record "${r}": ${i.message}`)}}),this.exposeCommand("mesh/subscribe-presence",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=`mesh:presence:updates:${r}`;this.channelManager.addSubscription(s,e.connection),(!this.channelManager.getSubscribers(s)||this.channelManager.getSubscribers(s)?.size===1)&&await this.channelManager.subscribeToRedisChannel(s);let n=await this.getRoomMembersWithMetadata(r),i=await this.presenceManager.getAllPresenceStates(r),o={};return i.forEach((a,d)=>{o[d]=a}),{success:!0,present:n,states:o}}catch(s){return console.error(`Failed to subscribe to presence for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/unsubscribe-presence",async e=>{let{roomName:r}=e.payload,s=`mesh:presence:updates:${r}`;return this.channelManager.removeSubscription(s,e.connection)}),this.exposeCommand("mesh/publish-presence-state",async e=>{let{roomName:r,state:s,expireAfter:n,silent:i}=e.payload,o=e.connection.id;if(!s||!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,o))return!1;try{return await this.presenceManager.publishPresenceState(o,r,s,n,i),!0}catch(a){return console.error(`Failed to publish presence state for room ${r}:`,a),!1}}),this.exposeCommand("mesh/clear-presence-state",async e=>{let{roomName:r}=e.payload,s=e.connection.id;if(!await this.presenceManager.isRoomTracked(r,e.connection)||!await this.isInRoom(r,s))return!1;try{return await this.presenceManager.clearPresenceState(s,r),!0}catch(n){return console.error(`Failed to clear presence state for room ${r}:`,n),!1}}),this.exposeCommand("mesh/get-presence-state",async e=>{let{roomName:r}=e.payload;if(!await this.presenceManager.isRoomTracked(r,e.connection))return{success:!1,present:[]};try{let s=await this.presenceManager.getPresentConnections(r),n=await this.presenceManager.getAllPresenceStates(r),i={};return n.forEach((o,a)=>{i[a]=o}),{success:!0,present:s,states:i}}catch(s){return console.error(`Failed to get presence state for room ${r}:`,s),{success:!1,present:[]}}}),this.exposeCommand("mesh/subscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;if(!await this.collectionManager.isCollectionExposed(r,e.connection))return{success:!1,ids:[],records:[],version:0};try{let{ids:n,records:i,version:o}=await this.collectionManager.addSubscription(r,s,e.connection),a=i.map(d=>({id:d.id,record:d}));return{success:!0,ids:n,records:a,version:o}}catch(n){return console.error(`Failed to subscribe to collection ${r}:`,n),{success:!1,ids:[],records:[],version:0}}}),this.exposeCommand("mesh/unsubscribe-collection",async e=>{let{collectionId:r}=e.payload,s=e.connection.id;return this.collectionManager.removeSubscription(r,s)})}async cleanupConnection(e){l.info("Cleaning up connection:",e.id),e.stopIntervals();try{await this.presenceManager.cleanupConnection(e),await this.connectionManager.cleanupConnection(e),await this.roomManager.cleanupConnection(e),this.recordSubscriptionManager.cleanupConnection(e),this.channelManager.cleanupConnection(e),await this.collectionManager.cleanupConnection(e)}catch(r){this.emit("error",new Error(`Failed to clean up connection: ${r}`))}}async close(e){this.redisManager.isShuttingDown=!0;let r=Object.values(this.connectionManager.getLocalConnections());if(await Promise.all(r.map(async s=>{s.isDead||await s.close(),await this.cleanupConnection(s)})),await new Promise((s,n)=>{super.close(i=>{i?n(i):s()})}),this.persistenceManager)try{await this.persistenceManager.shutdown()}catch(s){l.error("Error shutting down persistence manager:",s)}await this.instanceManager.stop(),await this.pubSubManager.cleanup(),await this.presenceManager.cleanup(),this.redisManager.disconnect(),this.listening=!1,this.removeAllListeners(),e&&e()}onConnection(e){return this.on("connected",e),this}onDisconnection(e){return this.on("disconnected",e),this}onRecordUpdate(e){return this.recordManager.onRecordUpdate(e)}onRecordRemoved(e){return this.recordManager.onRecordRemoved(e)}};export{S as Connection,I as ConnectionManager,$ as MeshContext,ee as MeshServer,w as MessageStream,x as PersistenceManager,T as PresenceManager,O as RecordManager,N as RoomManager};
|