@hpkv/zustand-multiplayer
Version:
A multiplayer middleware for Zustand using HPKV
8 lines (7 loc) • 21.8 kB
JavaScript
/**
* @license @hpkv/zustand-multiplayer v0.6.1
* Copyright (c) 2025 HPKV Team
* This source code is licensed under the MIT license.
*/
import{ConnectionState as e,WebsocketTokenManager as t,HPKVClientFactory as n}from"@hpkv/websocket-client";class s{constructor(e=1e3,t=3e5){this.data=new Map,this.maxSize=e,this.ttl=t}get(e){const t=this.data.get(e);if(t){if(Date.now()<=t.expires)return t.value;this.data.delete(e)}}set(e,t){if(this.data.size>=this.maxSize){const e=this.data.keys().next().value;void 0!==e&&this.data.delete(e)}this.data.set(e,{value:t,expires:Date.now()+this.ttl})}clear(){this.data.clear()}get size(){return this.data.size}}class i{constructor(){this.storageKeyCache=new s}static getInstance(){return i.instance||(i.instance=new i),i.instance}clearAll(){this.storageKeyCache.clear()}}class r{constructor(e,t){this.cacheManager=i.getInstance(),this.namespacedPrefix=void 0!==t?`${e}-${t}`:e}createStorageKey(e){const t=e.join(":"),n=`${this.namespacedPrefix}:${t}`,s=this.cacheManager.storageKeyCache.get(n);if(void 0!==s)return s;const i=`${this.namespacedPrefix}:${t}`;return this.cacheManager.storageKeyCache.set(n,i),i}parseStorageKey(e){const t=this.namespacedPrefix+":";let n=e;e.startsWith(t)&&(n=e.substring(t.length));const s=n.split(":");return{segments:s,depth:s.length,isNested:s.length>1}}getFullKey(e){return`${this.namespacedPrefix}:${e}`}getKeyWithoutPrefix(e){const t=this.namespacedPrefix+":";return e.startsWith(t)?e.substring(t.length):e}getNamespace(){return this.namespacedPrefix}getNamespaceRange(){return{start:this.namespacedPrefix+":",end:this.namespacedPrefix+":ÿ"}}clearCache(){this.cacheManager.storageKeyCache.clear()}}function a(){if("undefined"!=typeof crypto&&crypto.getRandomValues){const e=new Uint8Array(16);crypto.getRandomValues(e);const t=Array.from(e,e=>e.toString(36)).join("").substring(0,15);return`client_${Date.now()}_${t}`}return`client_${Date.now()}_${Math.random().toString(36).substring(2,15)}`}function o(e){return e instanceof Error?e:Error(e+"")}function c(e){return new Promise(t=>setTimeout(t,e))}function h(e){e&&clearTimeout(e)}function l(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function u(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}class g{calculateDiff(e,t){if(void 0===e||!u(e)||!u(t))return{type:"full",data:t};const n=this.calculateObjectDiff(e,t);return Object.keys(n).length>0?{type:"diff",data:n}:{type:"full",data:t}}calculateObjectDiff(e,t){const n={},s=new Set([...Object.keys(e),...Object.keys(t)]);for(const i of s){const s=this.getDiffForKey(i,e,t);void 0!==s&&(n[i]=s)}return n}getDiffForKey(e,t,n){const s=e in t,i=e in n;if(s&&!i)return null;if(!s&&i)return n[e];const r=t[e],a=n[e];if(!this.isDeepEqual(r,a)){if(u(r)&&u(a)){const e=this.calculateObjectDiff(r,a);return Object.keys(e).length>0?e:void 0}return a}}isDeepEqual(e,t){if(e===t)return!0;if(null===e||null===t)return e===t;if(typeof e!=typeof t)return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>this.isDeepEqual(e,t[n]));if("object"==typeof e&&"object"==typeof t){const n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(n=>n in t&&this.isDeepEqual(e[n],t[n]))}return!1}}class d{constructor(e=2){this.zFactor=e}buildStateUpdate(e,t,n){const s=e.split("."),i=null==t;return s.length>this.zFactor&&!i?this.buildMergeUpdate(s,t,n):this.buildDirectUpdate(n,s,t,i)}extractPaths(e,t,n,s=0){if(s>=n||!u(e))return[{path:t,value:e}];const i=[];for(const[r,a]of Object.entries(e)){const e=[...t,r];null!==a&&u(a)?i.push(...this.extractPaths(a,e,n,s+1)):i.push({path:e,value:a})}return i}mergeObjects(e,t){const n={...e};for(const[e,s]of Object.entries(t))null==s?delete n[e]:n[e]=s;return n}buildDirectUpdate(e,t,n,s){const i=t[0];if(1===t.length)return{[i]:n};const r=this.cloneValue(e[i])||{};return this.applyNestedUpdate(r,t.slice(1),n,s),{[i]:r}}setNestedValue(e,t,n){if(0===t.length)return;let s=e;for(let e=0;t.length-1>e;e++){const n=t[e];u(s[n])||(s[n]={}),s=s[n]}s[t[t.length-1]]=n}buildMergeUpdate(e,t,n){const s=this.getNestedValue(n,e);if(u(s)&&u(t)){const i=this.mergeObjects(s,t);return this.buildDirectUpdate(n,e,i,!1)}return this.buildDirectUpdate(n,e,t,!1)}getNestedValue(e,t){var n;let s=e[t[0]];for(let e=1;this.zFactor>=e&&t.length>e;e++)Object.isExtensible(s)||(s={...s}),s[n=t[e]]??(s[n]={}),s=s[t[e]];return s}cloneValue(e){return u(e)?JSON.parse(JSON.stringify(e)):{}}applyNestedUpdate(e,t,n,s){const i=this.navigateToParent(e,t),r=t[t.length-1];s?(delete i.current[r],this.cleanupEmptyParents(i.path)):i.current[r]=n}navigateToParent(e,t){let n=e;const s=[];for(let e=0;t.length-1>e;e++){const i=t[e];u(n[i])||(n[i]={}),s.push({obj:n,key:i}),n=n[i]}return{current:n,path:s}}cleanupEmptyParents(e){for(let t=e.length-1;t>=0;t--){const{obj:n,key:s}=e[t],i=n[s];if(!u(i)||0!==Object.keys(i).length)break;delete n[s]}}}class p{constructor(e,t,n,s,i){this.client=e,this.options=t,this.api=n,this.performanceMonitor=s,this.logger=i,this.cleanupFunctions=[],this.isHydrating=!1,this.hasHydrated=!1,this.keyManager=new r(t.namespace,t.zFactor),this.diffManager=new g,this.merger=new d(t.zFactor??2),this.setupEventListeners(),this.logger.debug("Orchestrator initialized",{operation:"orchestrator-init",namespace:t.namespace,zFactor:t.zFactor??2})}setupEventListeners(){const e=this.client.addConnectionListener(e=>{this.handleConnectionStateChange(e)});this.cleanupFunctions.push(e);const t=this.client.addChangeListener(e=>{this.handleRemoteChange(e)});this.cleanupFunctions.push(t)}async handleConnectionStateChange(t){this.logger.info("Connection state changed to "+t),t===e.DISCONNECTED&&(this.hasHydrated=!1,this.updateMultiplayerState({hasHydrated:!1})),this.updateMultiplayerState({connectionState:t}),t!==e.CONNECTED||this.hasHydrated||await this.hydrate()}async handleLocalStateChange(e,t){const n=this.api.getState();this.api.setState(e,t);const s=this.api.getState();await this.syncToRemote(s,n)}handleRemoteChange(e){const t=this.parseStorageKey(e.key);this.logger.debug("Received remote state update",{path:t});const n=this.api.getState(),s=this.merger.buildStateUpdate(t,e.value,n);this.api.setState(s,!1)}async syncToRemote(e,t){const n=this.buildSyncOperations(e,t,this.options.sync??[]);if(n.length>0){this.logger.debug(`Syncing ${n.length} state updates to remote storage`);try{await Promise.all(n),this.updateMultiplayerState({performanceMetrics:this.performanceMonitor.getMetrics()})}catch(e){throw this.logger.error("Failed to sync changes to remote storage",e),e}}}buildSyncOperations(e,t,n){const s=[],i=this.options.zFactor??2;for(const r of n){const n=this.detectFieldChanges(r,e,t,i);s.push(...n.deletions,...n.updates)}return s}detectFieldChanges(e,t,n,s){const i=e+"",r=t[e],a=n[e];if(this.shouldSkipField(i,r,a))return{deletions:[],updates:[]};const o=this.extractFieldPaths(a,i,s),c=this.extractFieldPaths(r,i,s);return this.comparePathsForChanges(o,c)}shouldSkipField(e,t,n){return t===n||"function"==typeof t||"multiplayer"===e}extractFieldPaths(e,t,n){return void 0!==e?this.merger.extractPaths(e,[t],n):[]}comparePathsForChanges(e,t){const n=new Map(e.map(e=>[e.path.join("."),e.value])),s=new Map(t.map(e=>[e.path.join("."),e.value])),i={deletions:[],updates:[]};for(const[e,t]of n)s.has(e)||i.deletions.push(this.client.removeItem(this.createStorageKey(e)));for(const[e,t]of s){const s=n.get(e);if(s!==t){const n=this.diffManager.calculateDiff(s,t);i.updates.push(this.client.setItem(this.createStorageKey(e),n.data))}}return i}async hydrate(){if(!this.isHydrating){this.isHydrating=!0;try{const e=await this.loadRemoteState();this.api.setState(e,!1),this.completeHydration()}catch(e){throw this.logger.error("State hydration failed",e),e}finally{this.isHydrating=!1}}}async loadRemoteState(){const e=await this.client.getAllItems(),t={};for(const[n,s]of e.entries()){const e=this.parseStorageKey(n).split(".");this.merger.setNestedValue(t,e,s)}return t}completeHydration(){this.hasHydrated=!0,this.updateMultiplayerState({hasHydrated:!0,performanceMetrics:this.performanceMonitor.getMetrics()})}createStorageKey(e){const t=e.split(".");return this.keyManager.createStorageKey(t)}parseStorageKey(e){return this.keyManager.parseStorageKey(e).segments.join(".")}updateMultiplayerState(e){this.api.setState(t=>{const n=t;return{...t,multiplayer:{...n.multiplayer,...e}}},!1)}async clearStorage(){this.logger.info("Clearing store..."),await this.client.clear()}async connect(){try{await this.client.ensureConnection(),this.client.getConnectionStatus()?.connectionState===e.CONNECTED&&this.updateMultiplayerState({connectionState:e.CONNECTED})}catch(e){throw this.logger.error("Failed to connect to remote storage",e),e}}async disconnect(){await this.client.close(),this.updateMultiplayerState({connectionState:e.DISCONNECTED})}destroy(){return this.cleanup(),Promise.resolve()}getConnectionStatus(){return this.client.getConnectionStatus()}getMetrics(){return this.performanceMonitor.getMetrics()}cleanup(){this.cleanupFunctions.forEach(e=>e()),this.cleanupFunctions.length=0,this.performanceMonitor.cleanup?.()}}var f;!function(e){e[e.DEBUG=0]="DEBUG",e[e.INFO=1]="INFO",e[e.WARN=2]="WARN",e[e.ERROR=3]="ERROR",e[e.NONE=4]="NONE"}(f||(f={}));class y{constructor(e=f.INFO){this.level=e,this.level=f.DEBUG>e||e>f.NONE?f.INFO:e}shouldLog(e){return e>=this.level}formatMessage(e,t,n){return`${new Date(n.timestamp).toISOString()} ${e} ${n.clientId?`[${n.clientId}]`:""}${n.operation?`(${n.operation})`:""}: ${t}`}debug(e,t={}){this.shouldLog(f.DEBUG)&&console.debug(this.formatMessage("DEBUG",e,{timestamp:Date.now(),...t}))}info(e,t={}){this.shouldLog(f.INFO)&&console.info(this.formatMessage("INFO",e,{timestamp:Date.now(),...t}))}warn(e,t={}){this.shouldLog(f.WARN)&&console.warn(this.formatMessage("WARN",e,{timestamp:Date.now(),...t}))}error(e,t,n={}){this.shouldLog(f.ERROR)&&(console.error(this.formatMessage("ERROR",t?`${e}: ${t.message}`:e,{timestamp:Date.now(),...n})),t?.stack&&console.error(t.stack))}setLevel(e){this.level=e}getLevel(){return this.level}}function m(e=f.INFO){return new y(e)}class k{constructor(){this.metrics={averageSyncTime:0},this.syncTimes=[]}recordSyncTime(e){this.syncTimes.push(e),this.syncTimes.length>5&&this.syncTimes.shift(),this.metrics.averageSyncTime=this.syncTimes.length>0?this.syncTimes.reduce((e,t)=>e+t,0)/this.syncTimes.length:0}getMetrics(){return{...this.metrics}}cleanup(){this.syncTimes.length=0,this.metrics.averageSyncTime=0}}class w{constructor(e,n){this.tokenManager=new t(e,n)}async generateTokenForStore(e,t){return await this.tokenManager.generateToken({subscribePatterns:[...t],accessPattern:`^${l(e)}:.*$`})}async processTokenRequest(e){try{let t;if("string"==typeof e)try{t=JSON.parse(e)}catch{throw Error("Invalid request: Could not parse request data")}else t=e;const{namespace:n,subscribedKeysAndPatterns:s}=t;if(!n||"string"!=typeof n)throw Error("Invalid request: namespace is required and must be a string");return{namespace:n,token:await this.generateTokenForStore(n,s??[])}}catch(e){throw e instanceof Error?e:Error("Unknown error during token generation")}}}class b{constructor(){this.tokenData=null,this.isRefreshing=!1}set(e,t){this.tokenData={token:e,expiresAt:t}}get(){return this.tokenData}clear(){this.tokenData&&(this.tokenData.token=Array(this.tokenData.token.length).fill(0).map(()=>Math.random().toString(36).charAt(0)).join(""),this.tokenData.token="",this.tokenData=null)}isValid(){return null!==this.tokenData&&Date.now()<this.tokenData.expiresAt}setRefreshing(e){this.isRefreshing=e}getRefreshing(){return this.isRefreshing}}class C{constructor(e){this.secureTokenCache=new b,this.tokenRefreshTimer=null,this.tokenRefreshPromise=null,this.options=e}setTokenRefreshCallback(e){this.onTokenRefresh=e}async generateToken(){if(this.secureTokenCache.isValid())return this.secureTokenCache.get().token;if(this.tokenRefreshPromise)return this.tokenRefreshPromise;this.secureTokenCache.setRefreshing(!0),this.tokenRefreshPromise=this.performTokenGeneration();try{return await this.tokenRefreshPromise}finally{this.secureTokenCache.setRefreshing(!1),this.tokenRefreshPromise=null}}async performTokenGeneration(){let e;if(this.secureTokenCache.clear(),this.clearTokenRefreshTimer(),this.options.apiKey){const t=new w(this.options.apiKey,this.options.apiBaseUrl),n=this.options.subscribedKeys.map(e=>this.options.keyManager.getFullKey(e));e=await t.generateTokenForStore(this.options.keyManager.getNamespace(),n)}else{if(!this.options.tokenGenerationUrl)throw Error("either apiKey or tokenGenerationUrl are required");e=await this.fetchToken()}const t=Date.now()+72e5;this.secureTokenCache.set(e,t);const n=t-9e5-Date.now();return n>0&&(this.tokenRefreshTimer=setTimeout(()=>{this.refreshToken().catch(()=>{})},n)),e}async refreshToken(){this.secureTokenCache.getRefreshing()||this.onTokenRefresh&&await this.onTokenRefresh()}clearTokenRefreshTimer(){h(this.tokenRefreshTimer),this.tokenRefreshTimer=null}async fetchToken(){return this.options.retryManager.executeWithRetry(async()=>{const e=await fetch(this.options.tokenGenerationUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({namespace:this.options.keyManager.getNamespace(),subscribedKeysAndPatterns:this.options.subscribedKeys.map(e=>this.options.keyManager.getFullKey(e))})});if(!e.ok)throw Error(`Failed to get token: ${e.status} ${e.statusText}`);return(await e.json()).token},"fetchToken")}clear(){this.clearTokenRefreshTimer(),this.secureTokenCache.clear()}getCurrentToken(){if(this.secureTokenCache.isValid()){const e=this.secureTokenCache.get();return e?.token??null}return null}isRefreshing(){return this.secureTokenCache.getRefreshing()}}class S{constructor(e){this.config=e}async executeWithRetry(e,t){let n;for(let t=0;this.config.maxRetries>=t;t++)try{return await e()}catch(e){if(n=e,t===this.config.maxRetries)break;const s=Math.min(this.config.baseDelay*Math.pow(this.config.backoffFactor,t),this.config.maxDelay);await c(s)}throw n??Error(t+" failed")}}class E{constructor(e,t,n,s){if(this.options=e,this.subscribedKeys=t,this.logger=n,this.performanceMonitor=s,this.client=null,this.subscriptionId=null,this.connectionPromise=null,this.changeListeners=new Set,this.connectionListeners=new Set,this.cleanupCallbacks=new Set,this.isDestroyed=!1,!e.namespace)throw Error("namespace is required");this.clientId=a(),this.retryManager=new S({maxRetries:3,baseDelay:1e3,maxDelay:5e3,backoffFactor:2}),this.keyManager=new r(e.namespace,e.zFactor),this.tokenManager=new C({namespace:e.namespace,apiBaseUrl:e.apiBaseUrl,apiKey:e.apiKey,tokenGenerationUrl:e.tokenGenerationUrl,subscribedKeys:this.subscribedKeys,keyManager:this.keyManager,retryManager:this.retryManager,logger:this.logger,clientId:this.clientId}),this.tokenManager.setTokenRefreshCallback(()=>this.handleTokenRefresh())}getClientId(){return this.clientId}addChangeListener(e){return this.changeListeners.add(e),()=>this.changeListeners.delete(e)}addConnectionListener(e){return this.connectionListeners.add(e),()=>this.connectionListeners.delete(e)}async ensureConnection(){if(this.isDestroyed)throw Error("Storage has been destroyed");if(this.connectionPromise)return this.connectionPromise;const t=this.client?.getConnectionStats();return this.client&&t?.connectionState===e.CONNECTED?void 0:(this.connectionPromise=this.connectInternal().finally(()=>{this.connectionPromise=null}),this.connectionPromise)}getConnectionStatus(){return this.client?.getConnectionStats()??null}async getAllItems(){if(await this.ensureConnection(),!this.client)throw Error("No connection available");const e=new Map,t=this.keyManager.getNamespaceRange();let n=t.start,s=!0;for(;s;){const i=await this.client.range(n,t.end);if(i.records){for(const t of i.records)try{const n=JSON.parse(t.value);e.set(t.key,n.value)}catch{e.set(t.key,t.value)}i.truncated&&i.records.length>0?n=i.records[i.records.length-1].key+"\0":s=!1}else s=!1}return e}async setItem(e,t){if(await this.ensureConnection(),!this.client)throw Error("No connection available");const n={value:t,clientId:this.clientId,timestamp:Date.now()},s=Date.now(),i=await this.client.set(e,JSON.stringify(n),!0),r=Date.now()-s;if(!i.success)throw Error("Failed to store item in the database: "+i.code);this.performanceMonitor.recordSyncTime(r)}async removeItem(e){await this.ensureConnection();const t=await(this.client?.delete(e));if(!t?.success)throw Error("Failed to remove item: "+t?.code)}async clear(){const e=await this.getAllItems(),t=Array.from(e.keys()).map(e=>this.removeItem(e));await Promise.all(t)}async close(){this.client&&(await this.client.disconnect(),this.notifyConnectionListeners(e.DISCONNECTED)),this.cleanup()}async destroy(){this.isDestroyed=!0,await this.close()}async connectInternal(){return this.retryManager.executeWithRetry(async()=>{const e=await this.tokenManager.generateToken();this.client=n.createSubscriptionClient(e,this.options.apiBaseUrl,{maxReconnectAttempts:5,maxDelayBetweenReconnects:6e4,jitterMs:1e3,initialDelayBetweenReconnects:500,throttling:{enabled:void 0!==this.options.rateLimit,rateLimit:this.options.rateLimit}}),this.setupClientEventHandlers(),await this.client.connect(),this.setupSubscriptions()},"connectInternal")}setupClientEventHandlers(){if(!this.client)return;const t={connected:()=>this.notifyConnectionListeners(e.CONNECTED),disconnected:()=>this.notifyConnectionListeners(e.DISCONNECTED),reconnecting:()=>this.notifyConnectionListeners(e.RECONNECTING),reconnectFailed:()=>this.notifyConnectionListeners(e.DISCONNECTED),error:()=>{this.logger.debug("WebSocket connection error occurred",{operation:"connection-error",clientId:this.clientId}),this.notifyConnectionListeners(e.DISCONNECTED)}};Object.entries(t).forEach(([e,t])=>{this.client.on(e,t)}),this.cleanupCallbacks.add(()=>{this.client&&Object.entries(t).forEach(([e,t])=>{this.client.off(e,t)})})}setupSubscriptions(){this.client&&(this.subscriptionId=this.client.subscribe(e=>{if(!e.key||void 0===e.value)return;const t=this.keyManager.getKeyWithoutPrefix(e.key);let n=e.value;try{if("string"==typeof e.value){const t=JSON.parse(e.value);if(t.clientId===this.getClientId())return;n=t.value}}catch{n=e.value}this.notifyChangeListeners({key:t,value:n,timestamp:e.timestamp})}),this.cleanupCallbacks.add(()=>{this.client&&this.subscriptionId&&(this.client.unsubscribe(this.subscriptionId),this.subscriptionId=null)}))}async handleTokenRefresh(){this.client&&(await this.client.disconnect(),this.client.destroy(),this.client=null),await this.ensureConnection()}notifyChangeListeners(e){this.changeListeners.forEach(t=>{t(e)})}notifyConnectionListeners(e){this.connectionListeners.forEach(t=>{t(e)})}cleanup(){this.cleanupCallbacks.forEach(e=>e()),this.cleanupCallbacks.clear(),this.changeListeners.clear(),this.connectionListeners.clear()}}const T=(t,n)=>{const s=function(e){const t=function(e){const t=[];try{!function(e){if(!e.apiKey&&!e.tokenGenerationUrl)throw Error("Either apiKey or tokenGenerationUrl must be provided for authentication")}(e),function(e){if(!e||"string"!=typeof e)throw Error("Namespace must be a non-empty string");if(1>e.length||e.length>100)throw Error("Namespace must be between 1 and 100 characters");if(/[^a-zA-Z0-9_-]/.test(e))throw Error("Namespace can only contain alphanumeric characters, underscores, and hyphens")}(e.namespace),function(e){if(!e||"string"!=typeof e)throw Error("API base URL must be a non-empty string");try{const t=new URL(e);if(!["http:","https:"].includes(t.protocol))throw Error("API base URL must use HTTP or HTTPS protocol")}catch(e){throw Error("Invalid API base URL format : "+o(e).message)}}(e.apiBaseUrl),function(e){if(e&&!Array.isArray(e))throw Error("sync must be an array")}(e.sync),e.zFactor=function(e){if(null==e)return 2;if("number"!=typeof e||!Number.isInteger(e))throw Error("Z-factor must be an integer");if(0>e||e>10)throw Error("Z-factor must be between 0 and 10");return e}(e.zFactor)}catch(e){t.push(e instanceof Error?e.message:e+"")}return{isValid:0===t.length,errors:t,warnings:[]}}(e);if(!t.isValid)throw Error("Configuration validation failed: "+t.errors.join(", "));return t.warnings.length>0&&void 0!==console&&console.warn("Zustand Multiplayer Configuration Warnings:",t.warnings),e}(n);return(n,i,r)=>{const a=n;let o;const c=(e,t)=>{o.handleLocalStateChange(e,t)};r.multiplayer={reHydrate:()=>o.hydrate(),clearStorage:()=>o.clearStorage(),disconnect:()=>o.disconnect(),connect:()=>o.connect(),destroy:()=>o.destroy(),getConnectionStatus:()=>o.getConnectionStatus(),getMetrics:()=>o.getMetrics()},r.setState=c;const h=t(c,i,r),l=Object.keys(C=h).filter(e=>"function"!=typeof C[e]),u=function(e,t){const n=Math.min(Math.max(0,e.zFactor??2),10);return{sync:e.sync??t,logLevel:f.INFO,...e,zFactor:n}}(s,l),g=m(u.logLevel),d=function(e){const t=new Set;return e.forEach(e=>{const n=e+"";t.add(n),t.add(n+":*")}),Array.from(t)}(u.sync),y={namespace:u.namespace,apiBaseUrl:u.apiBaseUrl,apiKey:u.apiKey,tokenGenerationUrl:u.tokenGenerationUrl,rateLimit:u.rateLimit,zFactor:u.zFactor},w=new k,b=new E(y,d,g,w);var C;o=new p(b,u,{...r,setState:a},w,g);const S={connectionState:b?.getConnectionStatus()?.connectionState??e.DISCONNECTED,hasHydrated:!1,performanceMetrics:o.getMetrics()};return async function(e,t){try{await e.connect(),await e.hydrate(),t.info("Multiplayer store initialized successfully",{operation:"store-initialization"})}catch(e){t.error("Store initialization failed",e,{operation:"store-initialization"})}}(o,g),{...h,multiplayer:S}}};export{f as LogLevel,y as Logger,w as TokenHelper,h as clearTimeoutSafely,c as createDelay,m as createLogger,l as escapeRegExp,a as generateClientId,u as isPlainObject,T as multiplayer,o as normalizeError};
//# sourceMappingURL=index.min.mjs.map