apiforgejs 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiforgejs",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "API observability & intelligence SDK for Express.js — local-first, privacy-first",
5
5
  "main": "src/index.js",
6
6
  "keywords": [
package/src/database.js CHANGED
@@ -229,6 +229,19 @@ class ApiForgeDatabase {
229
229
  this._begin.run();
230
230
  try {
231
231
  for (const r of routes) stmt.run(r.route, r.method);
232
+ // Remove routes no longer in the router that never had traffic
233
+ if (routes.length > 0) {
234
+ const keys = routes.map(r => `${r.route}|${r.method}`);
235
+ const ph = keys.map(() => '?').join(', ');
236
+ this.db.prepare(`
237
+ DELETE FROM known_routes
238
+ WHERE route || '|' || method NOT IN (${ph})
239
+ AND NOT EXISTS (
240
+ SELECT 1 FROM api_metrics m
241
+ WHERE m.route = known_routes.route AND m.method = known_routes.method
242
+ )
243
+ `).run(...keys);
244
+ }
232
245
  this._commit.run();
233
246
  } catch (err) {
234
247
  this._rollback.run();
@@ -258,13 +271,17 @@ class ApiForgeDatabase {
258
271
 
259
272
  getReleases() {
260
273
  return this.db.prepare(`
261
- SELECT release_tag,
262
- MIN(bucket_ts) as release_ts,
263
- COUNT(DISTINCT route || '|' || method) as routes_affected
264
- FROM api_metrics
265
- WHERE release_tag IS NOT NULL AND release_tag != ''
266
- GROUP BY release_tag
267
- ORDER BY release_ts DESC
274
+ WITH release_times AS (
275
+ SELECT release_tag, MIN(bucket_ts) AS release_ts
276
+ FROM api_metrics
277
+ WHERE release_tag IS NOT NULL AND release_tag != ''
278
+ GROUP BY release_tag
279
+ )
280
+ SELECT rt.release_tag,
281
+ rt.release_ts,
282
+ (SELECT COUNT(*) FROM known_routes WHERE first_seen <= rt.release_ts + 60) AS routes_affected
283
+ FROM release_times rt
284
+ ORDER BY rt.release_ts DESC
268
285
  LIMIT 20
269
286
  `).all();
270
287
  }
package/src/ui.html CHANGED
@@ -421,6 +421,7 @@ function mapInsights(insights) {
421
421
  function mapReleases(releases) {
422
422
  return (releases || []).map(r => ({
423
423
  tag: r.release_tag,
424
+ ts: r.release_ts,
424
425
  summary: `${r.routes_affected || 0} route${r.routes_affected !== 1 ? 's' : ''} recorded`,
425
426
  age: formatAge(r.release_ts),
426
427
  by: 'local',
@@ -739,11 +740,18 @@ function Overview({ timeRange, setRoute, setParams, lastUpdated }) {
739
740
  const globalCalls = chartData?.calls || Array(fallbackPts).fill(0);
740
741
  const xLabelsFinal = xLabels.length > 0 ? xLabels : Array.from({length:globalP90.length}, (_,i) => `${i}`);
741
742
 
742
- const releaseMarkers = (RELEASES || []).slice(0,2).map((r, i) => ({
743
- idx: Math.min(Math.floor(globalP90.length * (0.45 + i * 0.35)), globalP90.length - 1),
744
- label: r.tag,
745
- color: i === 0 ? '#b91c1c' : '#15803d',
746
- }));
743
+ const MARKER_COLORS = ['#b91c1c','#15803d','#2563eb','#b45309','#7c3aed'];
744
+ const nowTs = Date.now() / 1000;
745
+ const releaseMarkers = globalTs && globalTs.length > 0
746
+ ? [...(RELEASES || [])]
747
+ .filter(r => r.ts != null && r.ts >= nowTs - hours * 3600)
748
+ .reverse()
749
+ .map((r, i) => {
750
+ const idx = globalTs.reduce((best, b, j) =>
751
+ Math.abs(b.bucket_ts - r.ts) < Math.abs(globalTs[best].bucket_ts - r.ts) ? j : best, 0);
752
+ return { idx, label: r.tag, color: MARKER_COLORS[i % MARKER_COLORS.length] };
753
+ })
754
+ : [];
747
755
 
748
756
  const topSlow = [...ENDPOINTS].filter(e => !e.untracked && e.base_p90 > 0).sort((a,b) => b.base_p90-a.base_p90).slice(0,5);
749
757
  const topCalled = [...ENDPOINTS].sort((a,b) => b.calls24h-a.calls24h).slice(0,5);