Skip to content

Commit 1be6c52

Browse files
authored
Merge pull request #103 from peacprotocol/release/v0.9.13.2
feat(bridge): Bridge Bootstrap, local dev sidecar
2 parents 3861f96 + 2a138e2 commit 1be6c52

File tree

25 files changed

+1835
-20
lines changed

25 files changed

+1835
-20
lines changed

apps/api/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ if (import.meta.url === `file://${process.argv[1]}`) {
6060

6161
// Start server
6262
const port = parseInt(process.env.PORT || '3000');
63-
console.log(`🚀 PEAC Verify API v0.9.13.1 starting on port ${port}`);
63+
console.log(`PEAC Verify API v0.9.13.1 starting on port ${port}`);
6464

6565
serve({
6666
fetch: app.fetch,
6767
port,
6868
});
6969

70-
console.log(`Server running at http://localhost:${port}`);
71-
console.log(`📋 Health check: http://localhost:${port}/health`);
72-
console.log(`🔍 Verify endpoint: POST http://localhost:${port}/verify`);
70+
console.log(`Server running at http://localhost:${port}`);
71+
console.log(`Health check: http://localhost:${port}/health`);
72+
console.log(`Verify endpoint: POST http://localhost:${port}/verify`);
7373
}

apps/bridge/bin/bridge.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env node
2+
import('../dist/server.js').then((m) => m.startBridge?.());

apps/bridge/package.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "@peac/app-bridge",
3+
"version": "0.9.13.2",
4+
"description": "PEAC Protocol Bridge - Local development sidecar",
5+
"type": "module",
6+
"main": "dist/server.js",
7+
"private": true,
8+
"bin": {
9+
"peac-bridge": "./bin/bridge.js"
10+
},
11+
"exports": {
12+
".": {
13+
"import": "./dist/server.js"
14+
}
15+
},
16+
"files": [
17+
"dist/**/*",
18+
"bin/**/*"
19+
],
20+
"scripts": {
21+
"build": "tsup",
22+
"dev": "tsx watch src/server.ts",
23+
"start": "node dist/server.js",
24+
"pretest": "pnpm build",
25+
"test": "node --test src/*.test.js",
26+
"test:perf": "node scripts/perf-test.js",
27+
"perf": "npm run test:perf",
28+
"lint": "echo 'Lint temporarily disabled due to workspace dependencies - run from root'",
29+
"typecheck": "tsc --noEmit",
30+
"clean": "rm -rf dist"
31+
},
32+
"dependencies": {
33+
"@peac/core": "workspace:^0.9.12.1",
34+
"@peac/disc": "workspace:^0.9.12.1",
35+
"@peac/receipts": "workspace:^0.9.12.1",
36+
"@peac/pay402": "workspace:^0.9.12.1",
37+
"hono": "^4.9.7",
38+
"@hono/node-server": "^1.8.2",
39+
"zod": "^3.22.0"
40+
},
41+
"devDependencies": {
42+
"@types/node": "^20.0.0",
43+
"typescript": "^5.0.0",
44+
"tsx": "^4.0.0",
45+
"tsup": "^8.0.0"
46+
},
47+
"keywords": [
48+
"peac",
49+
"bridge",
50+
"sidecar",
51+
"adapter",
52+
"local-development"
53+
],
54+
"author": "PEAC Protocol",
55+
"license": "Apache-2.0",
56+
"engines": {
57+
"node": ">=18.18.0"
58+
}
59+
}

apps/bridge/scripts/perf-test.js

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Bridge Performance Validation Script
5+
* Validates that bridge endpoints meet p95 < 5ms target
6+
*/
7+
8+
const { performance } = require('perf_hooks');
9+
const { spawn } = require('child_process');
10+
const { join } = require('path');
11+
12+
const BRIDGE_PORT = 31415;
13+
const METRICS_PORT = 31416;
14+
const TARGET_P95_MS = 5;
15+
const TEST_REQUESTS = 100;
16+
const WARMUP_REQUESTS = 10;
17+
18+
class PerformanceValidator {
19+
constructor() {
20+
this.bridgeProcess = null;
21+
this.results = {
22+
health: [],
23+
ready: [],
24+
enforce: [],
25+
verify: [],
26+
metrics: [],
27+
};
28+
}
29+
30+
async startBridge() {
31+
console.log('🚀 Starting bridge for performance testing...');
32+
33+
const cliPath = join(__dirname, '../../../packages/cli/bin/peac.js');
34+
this.bridgeProcess = spawn('node', [cliPath, 'bridge', 'start', '--port', BRIDGE_PORT], {
35+
stdio: 'pipe',
36+
env: { ...process.env, PEAC_ENABLE_METRICS: '1' },
37+
});
38+
39+
// Wait for bridge to start
40+
await new Promise((resolve) => setTimeout(resolve, 3000));
41+
42+
// Verify bridge is running
43+
try {
44+
const response = await fetch(`http://127.0.0.1:${BRIDGE_PORT}/health`);
45+
if (!response.ok) {
46+
throw new Error(`Bridge health check failed: ${response.status}`);
47+
}
48+
console.log('✅ Bridge started successfully');
49+
} catch (error) {
50+
throw new Error(`Failed to start bridge: ${error.message}`);
51+
}
52+
}
53+
54+
async stopBridge() {
55+
if (this.bridgeProcess) {
56+
console.log('🛑 Stopping bridge...');
57+
this.bridgeProcess.kill('SIGTERM');
58+
59+
// Wait for graceful shutdown
60+
await new Promise((resolve) => setTimeout(resolve, 2000));
61+
62+
// Force kill if still running
63+
try {
64+
this.bridgeProcess.kill('SIGKILL');
65+
} catch {
66+
// Process already stopped
67+
}
68+
69+
this.bridgeProcess = null;
70+
}
71+
}
72+
73+
async measureEndpoint(name, url, options = {}) {
74+
console.log(`📊 Testing ${name} endpoint...`);
75+
76+
const timings = [];
77+
78+
// Warmup
79+
for (let i = 0; i < WARMUP_REQUESTS; i++) {
80+
try {
81+
await fetch(url, options);
82+
} catch {
83+
// Ignore warmup errors
84+
}
85+
}
86+
87+
// Actual measurements
88+
for (let i = 0; i < TEST_REQUESTS; i++) {
89+
const start = performance.now();
90+
91+
try {
92+
const response = await fetch(url, options);
93+
const duration = performance.now() - start;
94+
95+
if (response.ok) {
96+
timings.push(duration);
97+
} else {
98+
console.warn(`Request ${i + 1} failed: ${response.status}`);
99+
}
100+
} catch (error) {
101+
console.warn(`Request ${i + 1} error: ${error.message}`);
102+
}
103+
104+
// Small delay between requests
105+
await new Promise((resolve) => setTimeout(resolve, 10));
106+
}
107+
108+
if (timings.length === 0) {
109+
throw new Error(`No successful requests for ${name}`);
110+
}
111+
112+
this.results[name] = timings;
113+
return this.calculateStats(timings);
114+
}
115+
116+
calculateStats(timings) {
117+
const sorted = [...timings].sort((a, b) => a - b);
118+
const len = sorted.length;
119+
120+
return {
121+
count: len,
122+
min: sorted[0],
123+
max: sorted[len - 1],
124+
avg: timings.reduce((a, b) => a + b, 0) / len,
125+
p50: sorted[Math.floor(len * 0.5)],
126+
p95: sorted[Math.floor(len * 0.95)],
127+
p99: sorted[Math.floor(len * 0.99)],
128+
};
129+
}
130+
131+
async runTests() {
132+
try {
133+
await this.startBridge();
134+
135+
// Test health endpoint
136+
const healthStats = await this.measureEndpoint(
137+
'health',
138+
`http://127.0.0.1:${BRIDGE_PORT}/health`
139+
);
140+
141+
// Test readiness endpoint
142+
const readyStats = await this.measureEndpoint(
143+
'ready',
144+
`http://127.0.0.1:${BRIDGE_PORT}/ready`,
145+
{
146+
headers: { Accept: 'application/peac+json' },
147+
}
148+
);
149+
150+
// Test enforce endpoint
151+
const enforceStats = await this.measureEndpoint(
152+
'enforce',
153+
`http://127.0.0.1:${BRIDGE_PORT}/enforce`,
154+
{
155+
method: 'POST',
156+
headers: { 'Content-Type': 'application/json' },
157+
body: JSON.stringify({
158+
resource: 'https://example.com',
159+
context: { agent: 'test' },
160+
}),
161+
}
162+
);
163+
164+
// Test verify endpoint
165+
const verifyStats = await this.measureEndpoint(
166+
'verify',
167+
`http://127.0.0.1:${BRIDGE_PORT}/verify`,
168+
{
169+
method: 'POST',
170+
headers: {
171+
'Content-Type': 'application/json',
172+
Accept: 'application/peac+json',
173+
},
174+
body: JSON.stringify({
175+
receipt: 'test.receipt.jws',
176+
resource: 'https://example.com',
177+
}),
178+
}
179+
);
180+
181+
// Test metrics endpoint
182+
const metricsStats = await this.measureEndpoint(
183+
'metrics',
184+
`http://127.0.0.1:${METRICS_PORT}/metrics`
185+
);
186+
187+
// Report results
188+
this.reportResults({
189+
health: healthStats,
190+
ready: readyStats,
191+
enforce: enforceStats,
192+
verify: verifyStats,
193+
metrics: metricsStats,
194+
});
195+
} finally {
196+
await this.stopBridge();
197+
}
198+
}
199+
200+
reportResults(stats) {
201+
console.log('\n📈 Performance Test Results');
202+
console.log('================================');
203+
204+
let allPassed = true;
205+
206+
Object.entries(stats).forEach(([endpoint, stat]) => {
207+
const passed = stat.p95 < TARGET_P95_MS;
208+
allPassed = allPassed && passed;
209+
210+
console.log(`\n${endpoint.toUpperCase()} endpoint:`);
211+
console.log(` Requests: ${stat.count}/${TEST_REQUESTS}`);
212+
console.log(` Min: ${stat.min.toFixed(2)} ms`);
213+
console.log(` Avg: ${stat.avg.toFixed(2)} ms`);
214+
console.log(` P50: ${stat.p50.toFixed(2)} ms`);
215+
console.log(` P95: ${stat.p95.toFixed(2)} ms (target: <${TARGET_P95_MS}ms)`);
216+
console.log(` P99: ${stat.p99.toFixed(2)} ms`);
217+
console.log(` Max: ${stat.max.toFixed(2)} ms`);
218+
console.log(` Status: ${passed ? '✅ PASS' : '❌ FAIL'}`);
219+
});
220+
221+
console.log('\n================================');
222+
if (allPassed) {
223+
console.log('🎉 All performance targets met!');
224+
console.log(`✅ All endpoints p95 < ${TARGET_P95_MS}ms`);
225+
process.exit(0);
226+
} else {
227+
console.log('❌ Performance targets not met');
228+
console.log(`Some endpoints exceeded p95 target of ${TARGET_P95_MS}ms`);
229+
process.exit(1);
230+
}
231+
}
232+
}
233+
234+
// CLI usage
235+
if (require.main === module) {
236+
const validator = new PerformanceValidator();
237+
238+
validator.runTests().catch((error) => {
239+
console.error('❌ Performance test failed:', error);
240+
validator.stopBridge().finally(() => {
241+
process.exit(1);
242+
});
243+
});
244+
245+
// Handle cleanup on exit
246+
process.on('SIGINT', () => {
247+
console.log('\n🛑 Cleaning up...');
248+
validator.stopBridge().finally(() => {
249+
process.exit(130);
250+
});
251+
});
252+
}
253+
254+
module.exports = PerformanceValidator;

0 commit comments

Comments
 (0)