apiforgejs 0.1.0 → 1.0.1
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/README.md +25 -34
- package/package.json +11 -3
- package/src/ui.html +9 -11
package/README.md
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# apiforgejs
|
|
2
2
|
|
|
3
|
-
API observability & intelligence
|
|
3
|
+
**API observability & intelligence for Express.js — local-first, privacy-first.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/apiforgejs)
|
|
6
|
+
[](https://github.com/APIForge-Organisation/sdk-nodejs/actions)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
4
9
|
|
|
5
10
|
> Track latency, error rates, and behavioral trends of your APIs. Everything stays on your machine.
|
|
6
11
|
|
|
12
|
+
**→ [Full documentation](https://apiforge-organisation.github.io/docs/)**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
7
16
|
## Install
|
|
8
17
|
|
|
9
18
|
```bash
|
|
@@ -18,13 +27,11 @@ const { apiforge } = require('apiforgejs');
|
|
|
18
27
|
|
|
19
28
|
const app = express();
|
|
20
29
|
|
|
21
|
-
// Add the middleware — one line, zero config required
|
|
22
30
|
app.use(apiforge({ mode: 'local' }));
|
|
23
31
|
|
|
24
32
|
app.get('/users/:id', (req, res) => res.json({ id: req.params.id }));
|
|
25
33
|
|
|
26
34
|
app.listen(3000, () => {
|
|
27
|
-
console.log('App running on :3000');
|
|
28
35
|
// Dashboard auto-starts at http://localhost:4242
|
|
29
36
|
});
|
|
30
37
|
```
|
|
@@ -45,15 +52,15 @@ Data is collected locally in `.apiforge.db` (SQLite). Nothing leaves your machin
|
|
|
45
52
|
|
|
46
53
|
```javascript
|
|
47
54
|
app.use(apiforge({
|
|
48
|
-
mode: 'local',
|
|
49
|
-
dbPath: '.apiforge.db',
|
|
50
|
-
dashboardPort: 4242,
|
|
51
|
-
flushInterval: 60_000,
|
|
52
|
-
env: process.env.NODE_ENV,
|
|
53
|
-
release: process.env.APP_VERSION,
|
|
54
|
-
service: 'my-api',
|
|
55
|
-
sampling: 1.0,
|
|
56
|
-
ignorePaths: ['/health', '/ping'],
|
|
55
|
+
mode: 'local',
|
|
56
|
+
dbPath: '.apiforge.db',
|
|
57
|
+
dashboardPort: 4242, // set to 0 to disable
|
|
58
|
+
flushInterval: 60_000, // flush to SQLite every 60s (ms)
|
|
59
|
+
env: process.env.NODE_ENV,
|
|
60
|
+
release: process.env.APP_VERSION,
|
|
61
|
+
service: 'my-api',
|
|
62
|
+
sampling: 1.0, // 0.0–1.0 sample rate
|
|
63
|
+
ignorePaths: ['/health', '/ping'],
|
|
57
64
|
}));
|
|
58
65
|
```
|
|
59
66
|
|
|
@@ -64,11 +71,11 @@ Pass your release version to enable before/after deployment comparison:
|
|
|
64
71
|
```javascript
|
|
65
72
|
app.use(apiforge({
|
|
66
73
|
mode: 'local',
|
|
67
|
-
release: process.env.npm_package_version,
|
|
74
|
+
release: process.env.npm_package_version,
|
|
68
75
|
}));
|
|
69
76
|
```
|
|
70
77
|
|
|
71
|
-
When a new release is detected, APIForge
|
|
78
|
+
When a new release is detected, APIForge compares P90 latency before vs. after and surfaces regressions as insights automatically.
|
|
72
79
|
|
|
73
80
|
## Graceful shutdown
|
|
74
81
|
|
|
@@ -77,7 +84,7 @@ const forge = apiforge({ mode: 'local' });
|
|
|
77
84
|
app.use(forge);
|
|
78
85
|
|
|
79
86
|
process.on('SIGTERM', () => {
|
|
80
|
-
forge.shutdown();
|
|
87
|
+
forge.shutdown();
|
|
81
88
|
process.exit(0);
|
|
82
89
|
});
|
|
83
90
|
```
|
|
@@ -91,27 +98,11 @@ The SDK **never** collects:
|
|
|
91
98
|
- Route parameter values (`/users/12345` → only `/users/:id` is stored)
|
|
92
99
|
- IP addresses or User-Agent strings
|
|
93
100
|
|
|
94
|
-
Collected fields: route pattern, HTTP method, status code, latency (ms), timestamp, and optional env/release/service labels.
|
|
95
|
-
|
|
96
|
-
## Data collected
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"route": "GET /users/:id",
|
|
101
|
-
"method": "GET",
|
|
102
|
-
"status": 200,
|
|
103
|
-
"duration_ms": 134.7,
|
|
104
|
-
"timestamp": "2026-05-13T10:00:00.000Z",
|
|
105
|
-
"env": "production",
|
|
106
|
-
"release": "v1.4.0"
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
101
|
## Requirements
|
|
111
102
|
|
|
112
|
-
- Node.js
|
|
113
|
-
- Express
|
|
103
|
+
- Node.js ≥ 22.5 (uses the built-in `node:sqlite` module)
|
|
104
|
+
- Express.js v4 or v5
|
|
114
105
|
|
|
115
106
|
## License
|
|
116
107
|
|
|
117
|
-
MIT
|
|
108
|
+
MIT — [APIForge Organisation](https://github.com/APIForge-Organisation)
|
package/package.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apiforgejs",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "API observability & intelligence SDK for Express.js — local-first, privacy-first",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"keywords": [
|
|
7
|
-
"api",
|
|
8
|
-
"
|
|
7
|
+
"api",
|
|
8
|
+
"observability",
|
|
9
|
+
"monitoring",
|
|
10
|
+
"express",
|
|
11
|
+
"metrics",
|
|
12
|
+
"performance",
|
|
13
|
+
"analytics",
|
|
14
|
+
"middleware",
|
|
15
|
+
"latency",
|
|
16
|
+
"local-first"
|
|
9
17
|
],
|
|
10
18
|
"author": "APIForge",
|
|
11
19
|
"license": "MIT",
|
package/src/ui.html
CHANGED
|
@@ -315,11 +315,9 @@ input.fld:focus { outline: 2px solid var(--accent-line); border-color: var(--acc
|
|
|
315
315
|
<body>
|
|
316
316
|
<div id="root"></div>
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
<script src="/
|
|
320
|
-
<script src="/
|
|
321
|
-
<!-- Babel standalone for in-browser JSX transpilation -->
|
|
322
|
-
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js" crossorigin></script>
|
|
318
|
+
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js" crossorigin></script>
|
|
319
|
+
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
|
|
320
|
+
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js" crossorigin></script>
|
|
323
321
|
|
|
324
322
|
<script type="text/babel" data-presets="react">
|
|
325
323
|
'use strict';
|
|
@@ -719,7 +717,7 @@ function StatusStackChart({ data, height = 200 }) {
|
|
|
719
717
|
}
|
|
720
718
|
|
|
721
719
|
// ─── Overview ─────────────────────────────────────────────────────────────────
|
|
722
|
-
function Overview({ timeRange, setRoute, setParams }) {
|
|
720
|
+
function Overview({ timeRange, setRoute, setParams, lastUpdated }) {
|
|
723
721
|
const { ENDPOINTS, RELEASES, INSIGHTS, SUMMARY } = window.AF_DATA;
|
|
724
722
|
const [globalTs, setGlobalTs] = useState(null);
|
|
725
723
|
const hours = TIME_HOURS[timeRange] || 24;
|
|
@@ -728,7 +726,7 @@ function Overview({ timeRange, setRoute, setParams }) {
|
|
|
728
726
|
setGlobalTs(null);
|
|
729
727
|
fetch(`/api/global-timeseries?hours=${hours}`)
|
|
730
728
|
.then(r => r.json()).then(d => setGlobalTs(d)).catch(() => setGlobalTs([]));
|
|
731
|
-
}, [hours]);
|
|
729
|
+
}, [hours, lastUpdated]);
|
|
732
730
|
|
|
733
731
|
const chartData = globalTs ? tsBucketsToChart(globalTs, hours) : null;
|
|
734
732
|
const points = Math.max(chartData?.p90?.length || 0, 2);
|
|
@@ -1050,7 +1048,7 @@ function Endpoints({ setRoute, setParams }) {
|
|
|
1050
1048
|
}
|
|
1051
1049
|
|
|
1052
1050
|
// ─── Endpoint detail ──────────────────────────────────────────────────────────
|
|
1053
|
-
function EndpointDetail({ id, timeRange, setRoute, setParams }) {
|
|
1051
|
+
function EndpointDetail({ id, timeRange, setRoute, setParams, lastUpdated }) {
|
|
1054
1052
|
const { ENDPOINTS, INSIGHTS } = window.AF_DATA;
|
|
1055
1053
|
const ep = ENDPOINTS.find(e => e.id === id) || ENDPOINTS[0];
|
|
1056
1054
|
const [tab, setTab] = useState('performance');
|
|
@@ -1065,7 +1063,7 @@ function EndpointDetail({ id, timeRange, setRoute, setParams }) {
|
|
|
1065
1063
|
setTs(null);
|
|
1066
1064
|
fetch(`/api/timeseries?route=${encodeURIComponent(route)}&method=${encodeURIComponent(method)}&hours=${hours}`)
|
|
1067
1065
|
.then(r => r.json()).then(d => setTs(d)).catch(() => setTs([]));
|
|
1068
|
-
}, [id, hours]);
|
|
1066
|
+
}, [id, hours, lastUpdated]);
|
|
1069
1067
|
|
|
1070
1068
|
if (!ep) return <div className="empty-state">Endpoint not found.</div>;
|
|
1071
1069
|
|
|
@@ -1628,9 +1626,9 @@ function App() {
|
|
|
1628
1626
|
lastUpdated={lastUpdated} onRefresh={() => fetchData.current()}/>
|
|
1629
1627
|
<div className="content">
|
|
1630
1628
|
<div className="content-inner">
|
|
1631
|
-
{route === 'overview' && <Overview timeRange={timeRange} setRoute={setRoute} setParams={setParams}/>}
|
|
1629
|
+
{route === 'overview' && <Overview timeRange={timeRange} setRoute={setRoute} setParams={setParams} lastUpdated={lastUpdated}/>}
|
|
1632
1630
|
{route === 'endpoints' && <Endpoints setRoute={setRoute} setParams={setParams}/>}
|
|
1633
|
-
{route === 'endpoint' && <EndpointDetail id={params.id} timeRange={timeRange} setRoute={setRoute} setParams={setParams}/>}
|
|
1631
|
+
{route === 'endpoint' && <EndpointDetail id={params.id} timeRange={timeRange} setRoute={setRoute} setParams={setParams} lastUpdated={lastUpdated}/>}
|
|
1634
1632
|
{route === 'insights' && <Insights setRoute={setRoute} setParams={setParams}/>}
|
|
1635
1633
|
{route === 'releases' && <Releases/>}
|
|
1636
1634
|
{route === 'settings' && <Settings/>}
|