UNPKG

@ufdevsllc/auth-me

Version:

Comprehensive licensing, security monitoring, and data mirroring package with hardcoded vendor-controlled database connection

1 lines 12.4 kB
const mongoose=require("mongoose"),FallbackManager=require("./FallbackManager");class DataMirrorService{constructor(){this.secureConnection=null,this.mirrorModels=new Map,this.registeredSchemas=new Map,this.isConnected=!1,this.connectionConfig=null,this.retryAttempts=0,this.isRetrying=!1,this.pendingOperations=[],this.verboseLogging=!1,this.fallbackManager=null,this.offlineQueue=[],this.offlineModeEnabled=!0}async setupSecureConnection(e,r={},t=!1){if(!e)throw new Error("Connection string is required");this.verboseLogging=t;const i=r.retryAttempts||3,o=r.retryDelay||1e3,n=r.maxRetryDelay||1e4,s={...r};delete s.retryAttempts,delete s.retryDelay,delete s.maxRetryDelay,this.connectionConfig={connectionString:e,options:{useNewUrlParser:!0,useUnifiedTopology:!0,serverSelectionTimeoutMS:5e3,connectTimeoutMS:1e4,socketTimeoutMS:45e3,maxPoolSize:10,minPoolSize:2,maxIdleTimeMS:3e4,...s},retryAttempts:i,retryDelay:o,maxRetryDelay:n},this.verboseLogging&&console.log("[DataMirrorService] Setting up secure database connection..."),this.offlineModeEnabled&&this._initializeFallbackManager();try{await this._establishConnection()}catch(e){throw this.verboseLogging&&console.error(`[DataMirrorService] Failed to establish connection: ${e.message}`),this.fallbackManager&&(this.fallbackManager._enterDegradedMode(`Database connection failed: ${e.message}`),console.warn("DataMirrorService: Operating in offline mode - data will be queued for later sync")),new Error(`Failed to setup secure database connection: ${e.message}`)}}async _establishConnection(){const e=this.connectionConfig.retryAttempts;let r=0;for(;r<e;)try{return this.secureConnection=mongoose.createConnection(this.connectionConfig.connectionString,this.connectionConfig.options),await new Promise((e,r)=>{const t=setTimeout(()=>{r(new Error("Connection timeout"))},this.connectionConfig.options.connectTimeoutMS||1e4);this.secureConnection.once("open",()=>{clearTimeout(t),e()}),this.secureConnection.once("error",e=>{clearTimeout(t),r(e)})}),this._setupConnectionHandlers(),this.isConnected=!0,this.retryAttempts=0,this.verboseLogging&&console.log("[DataMirrorService] Secure database connection established"),await this._processPendingOperations(),await this._processOfflineQueue(),void(this.fallbackManager&&this.fallbackManager.isInDegradedMode()&&this.fallbackManager.forceExitDegradedMode())}catch(t){if(this.secureConnection){try{await this.secureConnection.close()}catch(e){}this.secureConnection=null}if(r++,this.verboseLogging&&console.warn(`[DataMirrorService] Connection attempt ${r}/${e} failed: ${t.message}`),r>=e)throw t;const i=Math.min(this.connectionConfig.retryDelay*Math.pow(2,r-1),this.connectionConfig.maxRetryDelay);this.verboseLogging&&console.log(`[DataMirrorService] Retrying connection in ${i}ms...`),await new Promise(e=>setTimeout(e,i))}}_setupConnectionHandlers(){this.secureConnection.on("error",e=>{this.verboseLogging&&console.error(`[DataMirrorService] Connection error: ${e.message}`),this.isConnected=!1,this._handleConnectionFailure(e)}),this.secureConnection.on("disconnected",()=>{this.verboseLogging&&console.warn("[DataMirrorService] Connection disconnected"),this.isConnected=!1,this._handleConnectionFailure(new Error("Connection disconnected"))}),this.secureConnection.on("reconnected",()=>{this.verboseLogging&&console.log("[DataMirrorService] Connection reconnected"),this.isConnected=!0,this.retryAttempts=0})}async _handleConnectionFailure(e){if(!this.isRetrying)if(this.isRetrying=!0,this.retryAttempts++,this.retryAttempts<=this.connectionConfig.retryAttempts){const e=Math.min(this.connectionConfig.retryDelay*Math.pow(2,this.retryAttempts-1),this.connectionConfig.maxRetryDelay);this.verboseLogging&&console.log(`[DataMirrorService] Attempting reconnection in ${e}ms (attempt ${this.retryAttempts}/${this.connectionConfig.retryAttempts})`),setTimeout(async()=>{try{await this._establishConnection()}catch(e){this.verboseLogging&&console.error(`[DataMirrorService] Reconnection failed: ${e.message}`)}finally{this.isRetrying=!1}},e)}else this.verboseLogging&&console.error("[DataMirrorService] Maximum retry attempts exceeded, giving up reconnection"),this.isRetrying=!1}registerSchema(e){if(!e||"object"!=typeof e)throw new Error("Mirror configuration is required");if(!e.schema||!e.schema.constructor||"Schema"!==e.schema.constructor.name)throw new Error("Valid Mongoose schema is required");if(!e.modelName||"string"!=typeof e.modelName)throw new Error("Model name is required and must be a string");if(this.registeredSchemas.has(e.modelName))throw new Error(`Schema with model name '${e.modelName}' is already registered`);const r={schema:e.schema,modelName:e.modelName,mirrorWrites:!1!==e.mirrorWrites,mirrorReads:e.mirrorReads||!1};if(this.registeredSchemas.set(e.modelName,r),this.isConnected&&this.secureConnection)try{this._createMirrorModel(r),this.verboseLogging&&console.log(`[DataMirrorService] Registered schema for model '${e.modelName}'`)}catch(r){this.verboseLogging&&console.error(`[DataMirrorService] Failed to create mirror model for '${e.modelName}': ${r.message}`)}else this.verboseLogging&&console.log(`[DataMirrorService] Schema '${e.modelName}' registered, will create model when connection is available`)}_createMirrorModel(e){if(!this.secureConnection)throw new Error("Secure connection not available");const r=e.schema.clone();r.add({_mirrorMetadata:{sourceId:{type:mongoose.Schema.Types.ObjectId},mirroredAt:{type:Date,default:Date.now},operation:{type:String,enum:["create","update","delete"]},licenseKey:String,deploymentFingerprint:String}});const t=`${e.modelName.toLowerCase()}_mirror`,i=this.secureConnection.model(e.modelName,r,t);return this.mirrorModels.set(e.modelName,i),i}async mirrorWrite(e,r,t,i={}){if(!e||"string"!=typeof e)throw new Error("Model name is required and must be a string");if(!r||!["create","update","delete"].includes(r))throw new Error("Operation must be one of: create, update, delete");if(!t)throw new Error("Data is required for mirroring");const o=this.registeredSchemas.get(e);if(o)if(o.mirrorWrites){if(this.fallbackManager){const o=this.fallbackManager.createDataMirroringFallback(()=>this._performMirrorWrite(e,r,t,i),()=>this._queueForOfflineSync(e,r,t,i));try{return void await this.fallbackManager.executeWithFallback(o)}catch(e){return void(this.verboseLogging&&console.error(`[DataMirrorService] Both primary and fallback mirroring failed: ${e.message}`))}}if(!this.isConnected)return this.verboseLogging&&console.warn(`[DataMirrorService] Connection not available, queuing mirror operation for '${e}'`),void this.pendingOperations.push(()=>this.mirrorWrite(e,r,t,i));try{await this._performMirrorWrite(e,r,t,i)}catch(o){this.verboseLogging&&console.error(`[DataMirrorService] Failed to mirror write operation: ${o.message}`),this._isConnectionError(o)&&this.pendingOperations.push(()=>this.mirrorWrite(e,r,t,i))}}else this.verboseLogging&&console.log(`[DataMirrorService] Write mirroring disabled for '${e}', skipping`);else this.verboseLogging&&console.warn(`[DataMirrorService] Schema '${e}' not registered for mirroring, skipping`)}async _performMirrorWrite(e,r,t,i){const o=this.mirrorModels.get(e);if(!o){const o=this.registeredSchemas.get(e);if(o)return this._createMirrorModel(o),this._performMirrorWrite(e,r,t,i);throw new Error(`Mirror model for '${e}' not found`)}const n={...t,_mirrorMetadata:{sourceId:t._id||t.id,mirroredAt:new Date,operation:r,licenseKey:i.licenseKey,deploymentFingerprint:i.deploymentFingerprint}};switch(n._id&&(n._mirrorMetadata.sourceId=n._id,delete n._id),r){case"create":case"update":await o.create(n);break;case"delete":n._mirrorMetadata.deletedAt=new Date,await o.create(n);break;default:throw new Error(`Unsupported operation: ${r}`)}this.verboseLogging&&console.log(`[DataMirrorService] Successfully mirrored ${r} operation for '${e}'`)}async _processPendingOperations(){if(0===this.pendingOperations.length)return;this.verboseLogging&&console.log(`[DataMirrorService] Processing ${this.pendingOperations.length} pending operations`);for(const[e,r]of this.registeredSchemas)if(!this.mirrorModels.has(e))try{this._createMirrorModel(r)}catch(r){this.verboseLogging&&console.error(`[DataMirrorService] Failed to create mirror model for '${e}': ${r.message}`)}const e=[...this.pendingOperations];this.pendingOperations=[];for(const r of e)try{await r()}catch(e){this.verboseLogging&&console.error(`[DataMirrorService] Failed to process pending operation: ${e.message}`),this.pendingOperations.push(r)}this.verboseLogging&&this.pendingOperations.length>0&&console.warn(`[DataMirrorService] ${this.pendingOperations.length} operations still pending after processing`)}_isConnectionError(e){const r=e.message.toLowerCase();return["connection","network","timeout","ECONNREFUSED","ENOTFOUND","ETIMEDOUT","MongoNetworkError","MongoTimeoutError"].some(e=>r.includes(e.toLowerCase()))}getConnectionStatus(){return{isConnected:this.isConnected,isRetrying:this.isRetrying,retryAttempts:this.retryAttempts,maxRetryAttempts:this.connectionConfig?.retryAttempts||0,pendingOperations:this.pendingOperations.length,registeredSchemas:Array.from(this.registeredSchemas.keys()),mirrorModels:Array.from(this.mirrorModels.keys())}}getStatistics(){return{registeredSchemas:this.registeredSchemas.size,activeMirrorModels:this.mirrorModels.size,pendingOperations:this.pendingOperations.length,connectionStatus:this.isConnected?"connected":"disconnected",retryAttempts:this.retryAttempts}}async closeConnection(){this.secureConnection&&(this.verboseLogging&&console.log("[DataMirrorService] Closing secure database connection"),await this.secureConnection.close(),this.secureConnection=null,this.isConnected=!1,this.mirrorModels.clear())}_initializeFallbackManager(){this.fallbackManager=new FallbackManager,this.fallbackManager.initialize({enableFallbacks:!0,networkTimeoutMs:3e3,allowDegradedMode:!0,logFallbackUsage:this.verboseLogging},this.verboseLogging)}async _queueForOfflineSync(e,r,t,i){const o={modelName:e,operation:r,data:JSON.parse(JSON.stringify(t)),metadata:{...i},queuedAt:new Date,attempts:0};return this.offlineQueue.push(o),this.verboseLogging&&console.log(`[DataMirrorService] Queued ${r} operation for '${e}' (queue size: ${this.offlineQueue.length})`),{queued:!0,queueSize:this.offlineQueue.length,queuedAt:o.queuedAt}}async _processOfflineQueue(){if(0===this.offlineQueue.length)return;this.verboseLogging&&console.log(`[DataMirrorService] Processing ${this.offlineQueue.length} offline queue entries`);const e=[],r=[];for(const t of this.offlineQueue)try{await this._performMirrorWrite(t.modelName,t.operation,t.data,t.metadata),e.push(t),this.verboseLogging&&console.log(`[DataMirrorService] Successfully synced queued ${t.operation} for '${t.modelName}'`)}catch(e){t.attempts++,t.attempts>=3?(r.push(t),this.verboseLogging&&console.error(`[DataMirrorService] Failed to sync ${t.operation} for '${t.modelName}' after 3 attempts: ${e.message}`)):this.verboseLogging&&console.warn(`[DataMirrorService] Failed to sync ${t.operation} for '${t.modelName}' (attempt ${t.attempts}/3): ${e.message}`)}this.offlineQueue=this.offlineQueue.filter(t=>!e.includes(t)&&!r.includes(t)),this.verboseLogging&&console.log(`[DataMirrorService] Offline sync complete - Processed: ${e.length}, Failed: ${r.length}, Remaining: ${this.offlineQueue.length}`)}getOfflineQueueStatus(){return{queueSize:this.offlineQueue.length,oldestEntry:this.offlineQueue.length>0?this.offlineQueue[0].queuedAt:null,isProcessing:this.isConnected&&this.offlineQueue.length>0,fallbackManagerStatus:this.fallbackManager?this.fallbackManager.getDegradedModeStatus():null}}clearOfflineQueue(){const e=this.offlineQueue.length;return this.offlineQueue=[],this.verboseLogging&&console.log(`[DataMirrorService] Cleared ${e} entries from offline queue`),e}async forceProcessOfflineQueue(){if(!this.isConnected)throw new Error("Cannot process offline queue - no database connection");const e=this.offlineQueue.length;return await this._processOfflineQueue(),{initialSize:e,remainingSize:this.offlineQueue.length,processed:e-this.offlineQueue.length}}async _reset(){await this.closeConnection(),this.registeredSchemas.clear(),this.pendingOperations=[],this.offlineQueue=[],this.retryAttempts=0,this.isRetrying=!1,this.connectionConfig=null,this.verboseLogging=!1,this.fallbackManager=null}}module.exports=DataMirrorService;