nucleus-core-ts 0.9.946 → 0.9.947

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 CHANGED
@@ -1 +1 @@
1
- 0.9.946
1
+ 0.9.947
package/dist/index.js CHANGED
@@ -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();constructor(config){this.config=config}recordRequest(params){if(!this.config?.enabled)return;if(this.requestCount++,this.config.metrics?.responseTime!==!1){if(this.responseTimes.push(params.responseTimeMs),this.responseTimes.length>1e4)this.responseTimes=this.responseTimes.slice(-5000)}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&&params.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++}collect(){if(!this.config?.enabled)return null;let elapsed=(Date.now()-this.lastCollectTime)/1000/60,perMinute=elapsed>0?Math.round(this.requestCount/elapsed):0,blockedPerMinute=elapsed>0?Math.round(this.rateLimitBlocks/elapsed):0,sortedTimes=[...this.responseTimes].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:this.requestCount>0?Math.round(this.errorCount/this.requestCount*100*100)/100:0,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.lastCollectTime=Date.now()}getRequestsPerMinute(){let elapsed=(Date.now()-this.lastCollectTime)/1000/60;return elapsed>0?Math.round(this.requestCount/elapsed):0}getErrorRate(){return this.requestCount>0?this.errorCount/this.requestCount*100:0}getRateLimitBlocksPerMinute(){let elapsed=(Date.now()-this.lastCollectTime)/1000/60;return elapsed>0?Math.round(this.rateLimitBlocks/elapsed):0}getAvgResponseTime(){if(this.responseTimes.length===0)return 0;return this.responseTimes.reduce((a,b)=>a+b,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 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(params.responseTimeMs),this.responseTimes.length>1e4)this.responseTimes=this.responseTimes.slice(-5000)}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&&params.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}collect(){if(!this.config?.enabled)return null;let perMinute=this.windowed((b)=>b.requests),blockedPerMinute=this.windowed((b)=>b.blocks),sortedTimes=[...this.responseTimes].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.responseTimes.length===0)return 0;return this.responseTimes.reduce((a,b)=>a+b,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
@@ -10,7 +10,34 @@ export declare class ApplicationCollector {
10
10
  private byStatus;
11
11
  private byErrorType;
12
12
  private lastCollectTime;
13
+ /**
14
+ * The last sixty seconds, one bucket each.
15
+ *
16
+ * ## What this replaces, and why it is not a detail
17
+ *
18
+ * `perMinute` used to be `requestCount / minutesSinceBoot` — the lifetime
19
+ * AVERAGE, presented as the current rate, on a chart labelled "requests per
20
+ * minute" and in the alert thresholds. `reset()` exists to make that
21
+ * arithmetic correct and is called from nowhere, so the divisor kept growing
22
+ * for as long as the pod lived.
23
+ *
24
+ * What that does to a real number: measured on 6 August 2026, a load run
25
+ * driving roughly fifty API requests a SECOND was recorded as a peak of 273
26
+ * per MINUTE, in the same snapshot that reported 370 cumulative hits on the
27
+ * busiest endpoint alone — two figures that cannot both be true. A pod up for
28
+ * a week would have flattened a genuine incident into nothing at all, which
29
+ * is the same failure in the direction that matters: the alert that never
30
+ * fires.
31
+ *
32
+ * A rolling window costs sixty small objects and answers the question the
33
+ * metric's name promises.
34
+ */
35
+ private buckets;
13
36
  constructor(config: MonitoringConfig['application']);
37
+ /** The bucket for `now`, recycled if this slot belongs to an expired second. */
38
+ private bucketFor;
39
+ /** Sums the buckets still inside the window. */
40
+ private windowed;
14
41
  recordRequest(params: {
15
42
  endpoint: string;
16
43
  method: string;
@@ -21,8 +48,21 @@ export declare class ApplicationCollector {
21
48
  }): void;
22
49
  recordRateLimitBlock(): void;
23
50
  collect(): ApplicationMetrics | null;
51
+ /**
52
+ * Clears the cumulative counters.
53
+ *
54
+ * Kept for callers that want a fresh baseline, but nothing on the boot path
55
+ * calls it — and that used to be a silent bug rather than a choice, because
56
+ * the rate arithmetic depended on it. The rates no longer do: they read the
57
+ * rolling window, which expires on its own.
58
+ */
24
59
  reset(): void;
60
+ /** Requests in the last sixty seconds. */
25
61
  getRequestsPerMinute(): number;
62
+ /**
63
+ * Error rate over the last minute — the same window the alert thresholds
64
+ * should be judging, and the same one `collect()` reports.
65
+ */
26
66
  getErrorRate(): number;
27
67
  getRateLimitBlocksPerMinute(): number;
28
68
  getAvgResponseTime(): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.946",
3
+ "version": "0.9.947",
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",