nucleus-core-ts 0.9.948 → 0.9.949
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/.build-ok +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.949
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ and limitations under the License.
|
|
|
34
34
|
`)&&privateKey.includes("\\n"))privateKey=privateKey.replace(/\\n/g,`
|
|
35
35
|
`);let auth=new google.auth.JWT({email:credentials.client_email,key:privateKey,scopes:["https://www.googleapis.com/auth/gmail.send"],subject:this.fromEmail});this.gmail=google.gmail({version:"v1",auth}),this.initialized=!0,this.logger.info("[GmailService] Initialized",{serviceAccount:credentials.client_email,fromEmail:this.fromEmail})}catch(error){this.logger.error("[GmailService] Initialization failed",{error})}}createEmailMessage(payload){let{to,subject,html,text,from,replyTo,attachments}=payload,toAddresses=this.sanitizeHeaderValue(Array.isArray(to)?to.map((address)=>this.sanitizeHeaderValue(address)).join(", "):to),fromAddress=this.sanitizeHeaderValue(from||`${this.fromName} <${this.fromEmail}>`),encodedSubject=this.encodeHeaderValue(subject);if(!attachments||attachments.length===0){let messageParts2=[`From: ${fromAddress}`,`To: ${toAddresses}`,`Subject: ${encodedSubject}`,"MIME-Version: 1.0","Content-Type: text/html; charset=utf-8"];if(replyTo)messageParts2.push(`Reply-To: ${this.sanitizeHeaderValue(replyTo)}`);return messageParts2.push("",html||text||""),Buffer.from(messageParts2.join(`
|
|
36
36
|
`)).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}let boundary=`----Nucleus_${Date.now()}_${Math.random().toString(36).substring(7)}`,messageParts=[`From: ${fromAddress}`,`To: ${toAddresses}`,`Subject: ${encodedSubject}`,"MIME-Version: 1.0",`Content-Type: multipart/mixed; boundary="${boundary}"`];if(replyTo)messageParts.push(`Reply-To: ${this.sanitizeHeaderValue(replyTo)}`);messageParts.push("",`--${boundary}`),messageParts.push("Content-Type: text/html; charset=utf-8","Content-Transfer-Encoding: base64","",Buffer.from(html||text||"").toString("base64"),"");for(let attachment of attachments)messageParts.push(`--${boundary}`,`Content-Type: ${attachment.contentType}`,`Content-Disposition: attachment; filename="${attachment.filename}"`,"Content-Transfer-Encoding: base64","",attachment.content.toString("base64"),"");return messageParts.push(`--${boundary}--`),Buffer.from(messageParts.join(`\r
|
|
37
|
-
`)).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async sendEmail(payload){if(!this.gmail)return this.logger.error("[GmailService] Not initialized"),{success:!1,error:"Gmail service not initialized"};try{let encodedMessage=this.createEmailMessage(payload);return await this.gmail.users.messages.send({userId:"me",requestBody:{raw:encodedMessage}}),this.logger.info("[GmailService] Email sent",{to:payload.to,subject:payload.subject}),{success:!0}}catch(error){let message=error instanceof Error?error.message:"Unknown error";return this.logger.error("[GmailService] Send failed",{error:message,to:payload.to}),{success:!1,error:message}}}emailWrapper(content,appName){return`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><meta name="color-scheme" content="light dark"><meta name="supported-color-schemes" content="light dark"><title>${appName}</title><style type="text/css">:root{color-scheme:light dark;supported-color-schemes:light dark;}@media(prefers-color-scheme:dark){.em-bg{background-color:#18181b!important;}.em-card{background-color:#27272a!important;border-color:#3f3f46!important;}.em-h{color:#fafafa!important;}.em-t{color:#d4d4d8!important;}.em-m{color:#a1a1aa!important;}.em-hr{background-color:#3f3f46!important;}.em-btn{background-color:#fafafa!important;}.em-btn-text{color:#18181b!important;}.em-link{color:#60a5fa!important;}.em-lb{background-color:#3f3f46!important;border-color:#52525b!important;}.em-lb-t{color:#a1a1aa!important;}.em-sn{background-color:#3f3f46!important;border-color:#52525b!important;color:#a1a1aa!important;}}</style></head><body style="margin:0;padding:0;background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;-webkit-font-smoothing:antialiased;"><table role="presentation" class="em-bg" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f4f5;"><tr><td align="center" style="padding:40px 16px;"><table role="presentation" width="480" cellpadding="0" cellspacing="0" style="max-width:480px;width:100%;"><tr><td align="center" style="padding-bottom:24px;"><span class="em-m" style="font-size:14px;font-weight:600;color:#71717a;letter-spacing:-0.2px;">${appName}</span></td></tr><tr><td class="em-card" style="background-color:#ffffff;border:1px solid #e4e4e7;border-radius:12px;overflow:hidden;">${content}</td></tr><tr><td align="center" style="padding-top:24px;"><p class="em-m" style="margin:0;font-size:12px;color:#a1a1aa;line-height:1.6;">This email was sent by <strong>${appName}</strong>.<br>Please do not reply to this email.</p></td></tr></table></td></tr></table></body></html>`}emailButton(href,label){return`<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr><td align="center" style="padding:28px 32px;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-btn" align="center" style="background-color:#18181b;border-radius:8px;"><a class="em-btn-text" href="${href}" target="_blank" style="display:inline-block;padding:12px 32px;font-size:14px;font-weight:600;color:#fafafa;text-decoration:none;letter-spacing:-0.1px;">${label}</a></td></tr></table></td></tr></table>`}emailDivider(){return'<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:0 32px;"><div class="em-hr" style="height:1px;background-color:#e4e4e7;"></div></td></tr></table>'}emailLinkFallback(link,notice){let html=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:4px 32px 32px 32px;"><p class="em-m" style="margin:0 0 8px 0;font-size:11px;font-weight:600;color:#a1a1aa;text-transform:uppercase;letter-spacing:0.5px;">Or copy this link</p><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td class="em-lb" style="background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:8px;padding:12px 14px;"><a class="em-link" href="${link}" style="font-size:12px;color:#2563eb;word-break:break-all;line-height:1.5;text-decoration:none;">${link}</a></td></tr></table>`;if(notice)html+=`<p class="em-m" style="margin:16px 0 0 0;font-size:12px;color:#a1a1aa;">${notice}</p>`;return html+="</td></tr></table>",html}async sendWelcomeEmail(email,name,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Welcome to ${appName}</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Your account has been created successfully</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 32px 32px;"><p class="em-t" style="margin:0 0 16px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Welcome aboard! You now have full access to <strong>${appName}</strong>. We're excited to have you.</p></td></tr></table>`;return this.sendEmail({to:email,subject:`Welcome to ${appName}`,html:this.emailWrapper(content,appName)})}async sendVerificationEmail(email,name,verificationLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Verify your email</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Confirm your address to get started</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0 0 4px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Please verify your email address by clicking the button below.</p></td></tr><tr><td>${this.emailButton(verificationLink,"Verify Email Address")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(verificationLink)}`;return this.sendEmail({to:email,subject:`Verify your email \u2014 ${appName}`,html:this.emailWrapper(content,appName)})}async sendPasswordResetEmail(email,name,resetLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Reset your password</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">We received a request to reset your password</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0 0 4px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Click the button below to choose a new password. If you didn't make this request, you can safely ignore this email.</p></td></tr><tr><td>${this.emailButton(resetLink,"Reset Password")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(resetLink,"This link expires in 1 hour and can only be used once.")}`;return this.sendEmail({to:email,subject:`Reset your password \u2014 ${appName}`,html:this.emailWrapper(content,appName)})}async sendMagicLinkEmail(email,magicLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Sign in to ${appName}</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">No password needed \u2014 just click to sign in</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Click the button below to securely sign in to your <strong class="em-h" style="color:#18181b;">${appName}</strong> account.</p></td></tr><tr><td>${this.emailButton(magicLink,"Sign In")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(magicLink,"This link expires in 15 minutes and can only be used once.")}`;return this.sendEmail({to:email,subject:`Sign in to ${appName}`,html:this.emailWrapper(content,appName)})}async sendAlertEmail(to,subject,html){return this.sendEmail({to,subject,html})}async sendInvitationEmail(email,inviteLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">You're Invited</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Join <strong class="em-h" style="color:#18181b;">${appName}</strong> and start collaborating</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:0 32px 8px 32px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">1</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Click the button below</td></tr></table></td></tr><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">2</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Choose a secure password</td></tr></table></td></tr><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">3</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Start collaborating</td></tr></table></td></tr></table></td></tr></table>${this.emailButton(inviteLink,"Accept Invitation")}${this.emailDivider()}${this.emailLinkFallback(inviteLink,"This invitation expires in 7 days.")}`;return this.sendEmail({to:email,subject:`You're invited to join ${appName}`,html:this.emailWrapper(content,appName)})}}var init_Gmail=()=>{};async function deliver(service,payload,what){let recipients=list(payload.to);if(!service?.isAvailable())return{sent:!1,level:"info",message:`${what}: not sent \u2014 no email provider is configured or available`,rejected:[]};if(recipients.length===0)return{sent:!1,level:"warn",message:`${what}: not sent \u2014 no recipient`,rejected:[]};let result;try{result=await service.sendEmail(payload)}catch(error){return{sent:!1,level:"warn",message:`${what}: NOT sent \u2014 ${error instanceof Error?error.message:String(error)}`,rejected:[]}}let rejected=(result.rejected??[]).filter(Boolean);if(result.success===!1)return{sent:!1,level:"warn",message:`${what}: NOT sent \u2014 ${result.error??"the provider refused it"}`,rejected};if(rejected.length>0)return{sent:!0,level:"warn",message:`${what}: sent to ${recipients.length-rejected.length} of ${recipients.length} \u2014 refused: ${rejected.join(", ")}`,rejected};return{sent:!0,level:"info",message:`${what}: sent`,rejected:[]}}var list=(to)=>(Array.isArray(to)?to:[to]).filter(Boolean);class AlertService{logger;getEmailService;config;appId;state={lastAlertTime:{},activeAlerts:new Map};cooldownMs;constructor(deps){this.logger=deps.logger,this.getEmailService=deps.getEmailService,this.config=deps.config,this.appId=deps.appId,this.cooldownMs=this.parseTimeToMs(deps.config.alerts?.cooldown||"5m")}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 300000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 300000}}async checkAndAlert(snapshot){if(!this.config.alerts?.enabled)return[];let alerts=[],thresholds=this.config.alerts.thresholds||{};if(snapshot.system){if(thresholds.cpuPercent&&snapshot.system.cpu.usage>thresholds.cpuPercent){let alert=this.createAlert("cpu_high",snapshot.system.cpu.usage,thresholds.cpuPercent,`CPU usage is ${snapshot.system.cpu.usage}% (threshold: ${thresholds.cpuPercent}%)`);if(alert)alerts.push(alert)}if(thresholds.memoryPercent&&snapshot.system.memory.usagePercent>thresholds.memoryPercent){let alert=this.createAlert("memory_high",snapshot.system.memory.usagePercent,thresholds.memoryPercent,`Memory usage is ${snapshot.system.memory.usagePercent}% (threshold: ${thresholds.memoryPercent}%)`);if(alert)alerts.push(alert)}if(thresholds.diskPercent&&snapshot.system.disk.usagePercent>thresholds.diskPercent){let alert=this.createAlert("disk_high",snapshot.system.disk.usagePercent,thresholds.diskPercent,`Disk usage is ${snapshot.system.disk.usagePercent}% (threshold: ${thresholds.diskPercent}%)`);if(alert)alerts.push(alert)}}if(snapshot.application){if(thresholds.errorRatePercent&&snapshot.application.errors.rate>thresholds.errorRatePercent){let alert=this.createAlert("error_rate_high",snapshot.application.errors.rate,thresholds.errorRatePercent,`Error rate is ${snapshot.application.errors.rate}% (threshold: ${thresholds.errorRatePercent}%)`);if(alert)alerts.push(alert)}if(thresholds.responseTimeMs&&snapshot.application.responseTime.p95>thresholds.responseTimeMs){let alert=this.createAlert("response_time_high",snapshot.application.responseTime.p95,thresholds.responseTimeMs,`P95 response time is ${snapshot.application.responseTime.p95}ms (threshold: ${thresholds.responseTimeMs}ms)`);if(alert)alerts.push(alert)}if(thresholds.rateLimitBlocksPerMinute&&snapshot.application.rateLimits.blockedPerMinute>thresholds.rateLimitBlocksPerMinute){let alert=this.createAlert("rate_limit_blocks_high",snapshot.application.rateLimits.blockedPerMinute,thresholds.rateLimitBlocksPerMinute,`Rate limit blocks: ${snapshot.application.rateLimits.blockedPerMinute}/min (threshold: ${thresholds.rateLimitBlocksPerMinute}/min)`);if(alert)alerts.push(alert)}}if(alerts.length>0)await this.sendAlerts(alerts);return alerts}createAlert(type,value,threshold,message){let now=Date.now(),lastAlert=this.state.lastAlertTime[type]||0;if(now-lastAlert<this.cooldownMs)return null;let severity=this.getSeverity(type,value,threshold),alert={id:`${type}-${now}`,type,severity,message,value,threshold,timestamp:now,acknowledged:!1};return this.state.lastAlertTime[type]=now,this.state.activeAlerts.set(type,alert),this.logger.warn(`[Alert] ${severity.toUpperCase()}: ${message}`),alert}getSeverity(_type,value,threshold){return value/threshold>1.5?"critical":"warning"}async sendAlerts(alerts){if(!this.config.alerts?.email?.enabled)return;let emailService=this.getEmailService?.();if(!emailService||!emailService.isAvailable())return;let recipients=this.config.alerts.email.recipients||[];if(recipients.length===0)return;let subject=alerts.filter((a)=>a.severity==="critical").length>0?`\uD83D\uDEA8 CRITICAL Alert - ${this.appId}`:`\u26A0\uFE0F Warning Alert - ${this.appId}`,html=this.buildEmailHtml(alerts);for(let recipient of recipients){let report=await deliver(emailService,{to:recipient,subject,html},"[Alert]");if(report.level==="warn")this.logger.error(report.message);else this.logger.info(report.message)}}buildEmailHtml(alerts){let alertRows=alerts.map((alert)=>`
|
|
37
|
+
`)).toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async sendEmail(payload){if(!this.gmail)return this.logger.error("[GmailService] Not initialized"),{success:!1,error:"Gmail service not initialized"};try{let encodedMessage=this.createEmailMessage(payload);return await this.gmail.users.messages.send({userId:"me",requestBody:{raw:encodedMessage}}),this.logger.info("[GmailService] Email sent",{to:payload.to,subject:payload.subject}),{success:!0}}catch(error){let message=error instanceof Error?error.message:"Unknown error";return this.logger.error("[GmailService] Send failed",{error:message,to:payload.to}),{success:!1,error:message}}}emailWrapper(content,appName){return`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><meta name="color-scheme" content="light dark"><meta name="supported-color-schemes" content="light dark"><title>${appName}</title><style type="text/css">:root{color-scheme:light dark;supported-color-schemes:light dark;}@media(prefers-color-scheme:dark){.em-bg{background-color:#18181b!important;}.em-card{background-color:#27272a!important;border-color:#3f3f46!important;}.em-h{color:#fafafa!important;}.em-t{color:#d4d4d8!important;}.em-m{color:#a1a1aa!important;}.em-hr{background-color:#3f3f46!important;}.em-btn{background-color:#fafafa!important;}.em-btn-text{color:#18181b!important;}.em-link{color:#60a5fa!important;}.em-lb{background-color:#3f3f46!important;border-color:#52525b!important;}.em-lb-t{color:#a1a1aa!important;}.em-sn{background-color:#3f3f46!important;border-color:#52525b!important;color:#a1a1aa!important;}}</style></head><body style="margin:0;padding:0;background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;-webkit-font-smoothing:antialiased;"><table role="presentation" class="em-bg" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f4f5;"><tr><td align="center" style="padding:40px 16px;"><table role="presentation" width="480" cellpadding="0" cellspacing="0" style="max-width:480px;width:100%;"><tr><td align="center" style="padding-bottom:24px;"><span class="em-m" style="font-size:14px;font-weight:600;color:#71717a;letter-spacing:-0.2px;">${appName}</span></td></tr><tr><td class="em-card" style="background-color:#ffffff;border:1px solid #e4e4e7;border-radius:12px;overflow:hidden;">${content}</td></tr><tr><td align="center" style="padding-top:24px;"><p class="em-m" style="margin:0;font-size:12px;color:#a1a1aa;line-height:1.6;">This email was sent by <strong>${appName}</strong>.<br>Please do not reply to this email.</p></td></tr></table></td></tr></table></body></html>`}emailButton(href,label){return`<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr><td align="center" style="padding:28px 32px;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-btn" align="center" style="background-color:#18181b;border-radius:8px;"><a class="em-btn-text" href="${href}" target="_blank" style="display:inline-block;padding:12px 32px;font-size:14px;font-weight:600;color:#fafafa;text-decoration:none;letter-spacing:-0.1px;">${label}</a></td></tr></table></td></tr></table>`}emailDivider(){return'<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:0 32px;"><div class="em-hr" style="height:1px;background-color:#e4e4e7;"></div></td></tr></table>'}emailLinkFallback(link,notice){let html=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:4px 32px 32px 32px;"><p class="em-m" style="margin:0 0 8px 0;font-size:11px;font-weight:600;color:#a1a1aa;text-transform:uppercase;letter-spacing:0.5px;">Or copy this link</p><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td class="em-lb" style="background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:8px;padding:12px 14px;"><a class="em-link" href="${link}" style="font-size:12px;color:#2563eb;word-break:break-all;line-height:1.5;text-decoration:none;">${link}</a></td></tr></table>`;if(notice)html+=`<p class="em-m" style="margin:16px 0 0 0;font-size:12px;color:#a1a1aa;">${notice}</p>`;return html+="</td></tr></table>",html}async sendWelcomeEmail(email,name,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Welcome to ${appName}</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Your account has been created successfully</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 32px 32px;"><p class="em-t" style="margin:0 0 16px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Welcome aboard! You now have full access to <strong>${appName}</strong>. We're excited to have you.</p></td></tr></table>`;return this.sendEmail({to:email,subject:`Welcome to ${appName}`,html:this.emailWrapper(content,appName)})}async sendVerificationEmail(email,name,verificationLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Verify your email</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Confirm your address to get started</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0 0 4px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Please verify your email address by clicking the button below.</p></td></tr><tr><td>${this.emailButton(verificationLink,"Verify Email Address")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(verificationLink)}`;return this.sendEmail({to:email,subject:`Verify your email \u2014 ${appName}`,html:this.emailWrapper(content,appName)})}async sendPasswordResetEmail(email,name,resetLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Reset your password</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">We received a request to reset your password</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0 0 4px 0;font-size:15px;color:#3f3f46;line-height:1.7;">Hello <strong class="em-h" style="color:#18181b;">${name}</strong>,</p><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Click the button below to choose a new password. If you didn't make this request, you can safely ignore this email.</p></td></tr><tr><td>${this.emailButton(resetLink,"Reset Password")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(resetLink,"This link expires in 1 hour and can only be used once.")}`;return this.sendEmail({to:email,subject:`Reset your password \u2014 ${appName}`,html:this.emailWrapper(content,appName)})}async sendMagicLinkEmail(email,magicLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">Sign in to ${appName}</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">No password needed \u2014 just click to sign in</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:24px 32px 0 32px;"><p class="em-t" style="margin:0;font-size:15px;color:#3f3f46;line-height:1.7;">Click the button below to securely sign in to your <strong class="em-h" style="color:#18181b;">${appName}</strong> account.</p></td></tr><tr><td>${this.emailButton(magicLink,"Sign In")}</td></tr></table>${this.emailDivider()}${this.emailLinkFallback(magicLink,"This link expires in 15 minutes and can only be used once.")}`;return this.sendEmail({to:email,subject:`Sign in to ${appName}`,html:this.emailWrapper(content,appName)})}async sendAlertEmail(to,subject,html){return this.sendEmail({to,subject,html})}async sendInvitationEmail(email,inviteLink,appName="Vorion"){let content=`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:40px 32px 24px 32px;text-align:center;"><h1 class="em-h" style="margin:0;font-size:24px;font-weight:700;color:#18181b;letter-spacing:-0.5px;">You're Invited</h1><p class="em-t" style="margin:8px 0 0 0;font-size:15px;color:#52525b;line-height:1.6;">Join <strong class="em-h" style="color:#18181b;">${appName}</strong> and start collaborating</p></td></tr></table>${this.emailDivider()}<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:0 32px 8px 32px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">1</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Click the button below</td></tr></table></td></tr><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">2</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Choose a secure password</td></tr></table></td></tr><tr><td style="padding:8px 0;"><table role="presentation" cellpadding="0" cellspacing="0"><tr><td class="em-sn" style="width:28px;height:28px;background-color:#f4f4f5;border:1px solid #e4e4e7;border-radius:50%;text-align:center;line-height:28px;font-size:12px;font-weight:600;color:#71717a;">3</td><td class="em-t" style="padding-left:14px;font-size:14px;color:#52525b;">Start collaborating</td></tr></table></td></tr></table></td></tr></table>${this.emailButton(inviteLink,"Accept Invitation")}${this.emailDivider()}${this.emailLinkFallback(inviteLink,"This invitation expires in 7 days.")}`;return this.sendEmail({to:email,subject:`You're invited to join ${appName}`,html:this.emailWrapper(content,appName)})}}var init_Gmail=()=>{};async function deliver(service,payload,what){let recipients=list(payload.to);if(!service?.isAvailable())return{sent:!1,level:"info",message:`${what}: not sent \u2014 no email provider is configured or available`,rejected:[]};if(recipients.length===0)return{sent:!1,level:"warn",message:`${what}: not sent \u2014 no recipient`,rejected:[]};let result;try{result=await service.sendEmail(payload)}catch(error){return{sent:!1,level:"warn",message:`${what}: NOT sent \u2014 ${error instanceof Error?error.message:String(error)}`,rejected:[]}}let rejected=(result.rejected??[]).filter(Boolean);if(result.success===!1)return{sent:!1,level:"warn",message:`${what}: NOT sent \u2014 ${result.error??"the provider refused it"}`,rejected};if(rejected.length>0)return{sent:!0,level:"warn",message:`${what}: sent to ${recipients.length-rejected.length} of ${recipients.length} \u2014 refused: ${rejected.join(", ")}`,rejected};return{sent:!0,level:"info",message:`${what}: sent`,rejected:[]}}var list=(to)=>(Array.isArray(to)?to:[to]).filter(Boolean);class AlertService{logger;getEmailService;config;appId;state={lastAlertTime:{},activeAlerts:new Map};cooldownMs;constructor(deps){this.logger=deps.logger,this.getEmailService=deps.getEmailService,this.config=deps.config,this.appId=deps.appId,this.cooldownMs=this.parseTimeToMs(deps.config.alerts?.cooldown||"5m")}parseTimeToMs(time){let match=time.match(/^(\d+)(ms|s|m|h|d)$/);if(!match||!match[1]||!match[2])return 300000;let value=parseInt(match[1],10);switch(match[2]){case"ms":return value;case"s":return value*1000;case"m":return value*60*1000;case"h":return value*60*60*1000;case"d":return value*24*60*60*1000;default:return 300000}}async checkAndAlert(snapshot){if(!this.config.alerts?.enabled)return[];let alerts=[],thresholds=this.config.alerts.thresholds||{};if(snapshot.system){if(thresholds.cpuPercent&&snapshot.system.cpu.usage>thresholds.cpuPercent){let alert=this.createAlert("cpu_high",snapshot.system.cpu.usage,thresholds.cpuPercent,`CPU usage is ${snapshot.system.cpu.usage}% (threshold: ${thresholds.cpuPercent}%)`);if(alert)alerts.push(alert)}if(thresholds.memoryPercent&&snapshot.system.memory.usagePercent>thresholds.memoryPercent){let alert=this.createAlert("memory_high",snapshot.system.memory.usagePercent,thresholds.memoryPercent,`Memory usage is ${snapshot.system.memory.usagePercent}% (threshold: ${thresholds.memoryPercent}%)`);if(alert)alerts.push(alert)}if(thresholds.diskPercent&&snapshot.system.disk.usagePercent>thresholds.diskPercent){let alert=this.createAlert("disk_high",snapshot.system.disk.usagePercent,thresholds.diskPercent,`Disk usage is ${snapshot.system.disk.usagePercent}% (threshold: ${thresholds.diskPercent}%)`);if(alert)alerts.push(alert)}}if(snapshot.application){let enoughTraffic=snapshot.application.requests.perMinute>=MIN_REQUESTS_FOR_RATE_ALERT;if(enoughTraffic&&thresholds.errorRatePercent&&snapshot.application.errors.rate>thresholds.errorRatePercent){let alert=this.createAlert("error_rate_high",snapshot.application.errors.rate,thresholds.errorRatePercent,`Error rate is ${snapshot.application.errors.rate}% (threshold: ${thresholds.errorRatePercent}%)`);if(alert)alerts.push(alert)}if(enoughTraffic&&thresholds.responseTimeMs&&snapshot.application.responseTime.p95>thresholds.responseTimeMs){let alert=this.createAlert("response_time_high",snapshot.application.responseTime.p95,thresholds.responseTimeMs,`P95 response time is ${snapshot.application.responseTime.p95}ms (threshold: ${thresholds.responseTimeMs}ms)`);if(alert)alerts.push(alert)}if(thresholds.rateLimitBlocksPerMinute&&snapshot.application.rateLimits.blockedPerMinute>thresholds.rateLimitBlocksPerMinute){let alert=this.createAlert("rate_limit_blocks_high",snapshot.application.rateLimits.blockedPerMinute,thresholds.rateLimitBlocksPerMinute,`Rate limit blocks: ${snapshot.application.rateLimits.blockedPerMinute}/min (threshold: ${thresholds.rateLimitBlocksPerMinute}/min)`);if(alert)alerts.push(alert)}}if(alerts.length>0)await this.sendAlerts(alerts);return alerts}createAlert(type,value,threshold,message){let now=Date.now(),lastAlert=this.state.lastAlertTime[type]||0;if(now-lastAlert<this.cooldownMs)return null;let severity=this.getSeverity(type,value,threshold),alert={id:`${type}-${now}`,type,severity,message,value,threshold,timestamp:now,acknowledged:!1};return this.state.lastAlertTime[type]=now,this.state.activeAlerts.set(type,alert),this.logger.warn(`[Alert] ${severity.toUpperCase()}: ${message}`),alert}getSeverity(_type,value,threshold){return value/threshold>1.5?"critical":"warning"}async sendAlerts(alerts){if(!this.config.alerts?.email?.enabled)return;let emailService=this.getEmailService?.();if(!emailService||!emailService.isAvailable())return;let recipients=this.config.alerts.email.recipients||[];if(recipients.length===0)return;let subject=alerts.filter((a)=>a.severity==="critical").length>0?`\uD83D\uDEA8 CRITICAL Alert - ${this.appId}`:`\u26A0\uFE0F Warning Alert - ${this.appId}`,html=this.buildEmailHtml(alerts);for(let recipient of recipients){let report=await deliver(emailService,{to:recipient,subject,html},"[Alert]");if(report.level==="warn")this.logger.error(report.message);else this.logger.info(report.message)}}buildEmailHtml(alerts){let alertRows=alerts.map((alert)=>`
|
|
38
38
|
<tr style="background-color: ${alert.severity==="critical"?"#fee2e2":"#fef3c7"};">
|
|
39
39
|
<td style="padding: 12px; border: 1px solid #e5e7eb;">${alert.severity.toUpperCase()}</td>
|
|
40
40
|
<td style="padding: 12px; border: 1px solid #e5e7eb;">${alert.type}</td>
|
|
@@ -71,7 +71,7 @@ and limitations under the License.
|
|
|
71
71
|
</div>
|
|
72
72
|
</body>
|
|
73
73
|
</html>
|
|
74
|
-
`}getActiveAlerts(){return Array.from(this.state.activeAlerts.values())}acknowledgeAlert(alertId){for(let[_type,alert]of this.state.activeAlerts)if(alert.id===alertId)return alert.acknowledged=!0,!0;return!1}clearAlert(type){this.state.activeAlerts.delete(type)}}var init_AlertService=()=>{};class ApplicationCollector{config;requestCount=0;responseTimes=[];errorCount=0;rateLimitBlocks=0;byEndpoint={};byMethod={};byStatus={};byErrorType={};lastCollectTime=Date.now();buckets=[];constructor(config){this.config=config}bucketFor(second){let index=second%60,bucket=this.buckets[index];if(!bucket)return bucket={second,requests:0,errors:0,blocks:0},this.buckets[index]=bucket,bucket;if(bucket.second!==second)bucket.second=second,bucket.requests=0,bucket.errors=0,bucket.blocks=0;return bucket}windowed(pick){let oldest=Math.floor(Date.now()/1000)-60,total=0;for(let bucket of this.buckets)if(bucket&&bucket.second>oldest)total+=pick(bucket);return total}recordRequest(params){if(!this.config?.enabled)return;this.requestCount++;let bucket=this.bucketFor(Math.floor(Date.now()/1000));if(bucket.requests+=1,params.isError)bucket.errors+=1;if(this.config.metrics?.responseTime!==!1){if(this.responseTimes.push({at:Date.now(),ms:params.responseTimeMs}),this.responseTimes.length>50000)this.pruneResponseTimes()}if(this.config.metrics?.requests!==!1)this.byEndpoint[params.endpoint]=(this.byEndpoint[params.endpoint]||0)+1,this.byMethod[params.method]=(this.byMethod[params.method]||0)+1,this.byStatus[String(params.status)]=(this.byStatus[String(params.status)]||0)+1;if(this.config.metrics?.errors!==!1&¶ms.isError){if(this.errorCount++,params.errorType)this.byErrorType[params.errorType]=(this.byErrorType[params.errorType]||0)+1}}recordRateLimitBlock(){if(!this.config?.enabled||this.config.metrics?.rateLimits===!1)return;this.rateLimitBlocks++,this.bucketFor(Math.floor(Date.now()/1000)).blocks+=1}pruneResponseTimes(){let oldest=Date.now()-60000,kept=this.responseTimes.filter((sample)=>sample.at>oldest);if(kept.length>50000)kept=kept.slice(-50000);this.responseTimes=kept}collect(){if(!this.config?.enabled)return null;let perMinute=this.windowed((b)=>b.requests),blockedPerMinute=this.windowed((b)=>b.blocks);this.pruneResponseTimes();let sortedTimes=this.responseTimes.map((s)=>s.ms).sort((a,b)=>a-b),len=sortedTimes.length;return{requests:{total:this.requestCount,perMinute,byEndpoint:{...this.byEndpoint},byMethod:{...this.byMethod},byStatus:{...this.byStatus}},responseTime:{avg:len>0?Math.round(sortedTimes.reduce((a,b)=>a+b,0)/len*100)/100:0,min:len>0?sortedTimes[0]??0:0,max:len>0?sortedTimes[len-1]??0:0,p50:len>0?sortedTimes[Math.floor(len*0.5)]??0:0,p95:len>0?sortedTimes[Math.floor(len*0.95)]??0:0,p99:len>0?sortedTimes[Math.floor(len*0.99)]??0:0},errors:{total:this.errorCount,rate:(()=>{let requests=perMinute;if(requests===0)return 0;let errors=this.windowed((b)=>b.errors);return Math.round(errors/requests*100*100)/100})(),byType:{...this.byErrorType}},rateLimits:{blocked:this.rateLimitBlocks,blockedPerMinute}}}reset(){this.requestCount=0,this.responseTimes=[],this.errorCount=0,this.rateLimitBlocks=0,this.byEndpoint={},this.byMethod={},this.byStatus={},this.byErrorType={},this.buckets=[],this.lastCollectTime=Date.now()}getRequestsPerMinute(){return this.windowed((b)=>b.requests)}getErrorRate(){let requests=this.windowed((b)=>b.requests);return requests>0?this.windowed((b)=>b.errors)/requests*100:0}getRateLimitBlocksPerMinute(){return this.windowed((b)=>b.blocks)}getAvgResponseTime(){if(this.pruneResponseTimes(),this.responseTimes.length===0)return 0;return this.responseTimes.reduce((total,s)=>total+s.ms,0)/this.responseTimes.length}}class DatabaseCollector{config;execute;lastTotals=null;activeTimeSupported=!0;constructor(config,execute){this.config=config,this.execute=execute}async collect(){if(!this.config.enabled)return null;let metrics={connections:{active:0,idle:0,total:0},queries:{total:0,avgTime:0,slowQueries:0}};if(this.config.metrics?.connections!==!1)metrics.connections=await this.collectConnections();if(this.config.metrics?.queryTime!==!1)metrics.queries=await this.collectQueries();return metrics}toNumber(value){let num=Number.parseFloat(String(value??0));return Number.isNaN(num)?0:num}async collectConnections(){try{let row=(await this.execute(`SELECT
|
|
74
|
+
`}getActiveAlerts(){return Array.from(this.state.activeAlerts.values())}acknowledgeAlert(alertId){for(let[_type,alert]of this.state.activeAlerts)if(alert.id===alertId)return alert.acknowledged=!0,!0;return!1}clearAlert(type){this.state.activeAlerts.delete(type)}}var MIN_REQUESTS_FOR_RATE_ALERT=20;var init_AlertService=()=>{};class ApplicationCollector{config;requestCount=0;responseTimes=[];errorCount=0;rateLimitBlocks=0;byEndpoint={};byMethod={};byStatus={};byErrorType={};lastCollectTime=Date.now();buckets=[];constructor(config){this.config=config}bucketFor(second){let index=second%60,bucket=this.buckets[index];if(!bucket)return bucket={second,requests:0,errors:0,blocks:0},this.buckets[index]=bucket,bucket;if(bucket.second!==second)bucket.second=second,bucket.requests=0,bucket.errors=0,bucket.blocks=0;return bucket}windowed(pick){let oldest=Math.floor(Date.now()/1000)-60,total=0;for(let bucket of this.buckets)if(bucket&&bucket.second>oldest)total+=pick(bucket);return total}recordRequest(params){if(!this.config?.enabled)return;this.requestCount++;let bucket=this.bucketFor(Math.floor(Date.now()/1000));if(bucket.requests+=1,params.isError)bucket.errors+=1;if(this.config.metrics?.responseTime!==!1){if(this.responseTimes.push({at:Date.now(),ms:params.responseTimeMs}),this.responseTimes.length>50000)this.pruneResponseTimes()}if(this.config.metrics?.requests!==!1)this.byEndpoint[params.endpoint]=(this.byEndpoint[params.endpoint]||0)+1,this.byMethod[params.method]=(this.byMethod[params.method]||0)+1,this.byStatus[String(params.status)]=(this.byStatus[String(params.status)]||0)+1;if(this.config.metrics?.errors!==!1&¶ms.isError){if(this.errorCount++,params.errorType)this.byErrorType[params.errorType]=(this.byErrorType[params.errorType]||0)+1}}recordRateLimitBlock(){if(!this.config?.enabled||this.config.metrics?.rateLimits===!1)return;this.rateLimitBlocks++,this.bucketFor(Math.floor(Date.now()/1000)).blocks+=1}pruneResponseTimes(){let oldest=Date.now()-60000,kept=this.responseTimes.filter((sample)=>sample.at>oldest);if(kept.length>50000)kept=kept.slice(-50000);this.responseTimes=kept}collect(){if(!this.config?.enabled)return null;let perMinute=this.windowed((b)=>b.requests),blockedPerMinute=this.windowed((b)=>b.blocks);this.pruneResponseTimes();let sortedTimes=this.responseTimes.map((s)=>s.ms).sort((a,b)=>a-b),len=sortedTimes.length;return{requests:{total:this.requestCount,perMinute,byEndpoint:{...this.byEndpoint},byMethod:{...this.byMethod},byStatus:{...this.byStatus}},responseTime:{avg:len>0?Math.round(sortedTimes.reduce((a,b)=>a+b,0)/len*100)/100:0,min:len>0?sortedTimes[0]??0:0,max:len>0?sortedTimes[len-1]??0:0,p50:len>0?sortedTimes[Math.floor(len*0.5)]??0:0,p95:len>0?sortedTimes[Math.floor(len*0.95)]??0:0,p99:len>0?sortedTimes[Math.floor(len*0.99)]??0:0},errors:{total:this.errorCount,rate:(()=>{let requests=perMinute;if(requests===0)return 0;let errors=this.windowed((b)=>b.errors);return Math.round(errors/requests*100*100)/100})(),byType:{...this.byErrorType}},rateLimits:{blocked:this.rateLimitBlocks,blockedPerMinute}}}reset(){this.requestCount=0,this.responseTimes=[],this.errorCount=0,this.rateLimitBlocks=0,this.byEndpoint={},this.byMethod={},this.byStatus={},this.byErrorType={},this.buckets=[],this.lastCollectTime=Date.now()}getRequestsPerMinute(){return this.windowed((b)=>b.requests)}getErrorRate(){let requests=this.windowed((b)=>b.requests);return requests>0?this.windowed((b)=>b.errors)/requests*100:0}getRateLimitBlocksPerMinute(){return this.windowed((b)=>b.blocks)}getAvgResponseTime(){if(this.pruneResponseTimes(),this.responseTimes.length===0)return 0;return this.responseTimes.reduce((total,s)=>total+s.ms,0)/this.responseTimes.length}}class DatabaseCollector{config;execute;lastTotals=null;activeTimeSupported=!0;constructor(config,execute){this.config=config,this.execute=execute}async collect(){if(!this.config.enabled)return null;let metrics={connections:{active:0,idle:0,total:0},queries:{total:0,avgTime:0,slowQueries:0}};if(this.config.metrics?.connections!==!1)metrics.connections=await this.collectConnections();if(this.config.metrics?.queryTime!==!1)metrics.queries=await this.collectQueries();return metrics}toNumber(value){let num=Number.parseFloat(String(value??0));return Number.isNaN(num)?0:num}async collectConnections(){try{let row=(await this.execute(`SELECT
|
|
75
75
|
count(*) FILTER (WHERE state = 'active') AS active,
|
|
76
76
|
count(*) FILTER (WHERE state = 'idle') AS idle,
|
|
77
77
|
count(*) AS total
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.949",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|