@affectively/aeon-pages 1.3.0
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/CHANGELOG.md +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Flux vs Next.js Benchmark Tests
|
|
3
|
+
*
|
|
4
|
+
* Measures:
|
|
5
|
+
* - Build time
|
|
6
|
+
* - Bundle size
|
|
7
|
+
* - Cold start time
|
|
8
|
+
* - Request throughput
|
|
9
|
+
* - Memory usage
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
13
|
+
import { mkdir, writeFile, rm, readdir, stat } from 'fs/promises';
|
|
14
|
+
import { join, resolve } from 'path';
|
|
15
|
+
import { build } from '../../cli/src/commands/build';
|
|
16
|
+
|
|
17
|
+
const BENCHMARK_DIR = resolve(process.cwd(), '.benchmark-test');
|
|
18
|
+
const AEON_APP_DIR = join(BENCHMARK_DIR, 'aeon-app');
|
|
19
|
+
const NEXTJS_APP_DIR = join(BENCHMARK_DIR, 'nextjs-app');
|
|
20
|
+
|
|
21
|
+
interface BenchmarkResult {
|
|
22
|
+
name: string;
|
|
23
|
+
buildTimeMs: number;
|
|
24
|
+
bundleSizeKb: number;
|
|
25
|
+
coldStartMs: number;
|
|
26
|
+
requestsPerSecond: number;
|
|
27
|
+
memoryUsageMb: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const results: BenchmarkResult[] = [];
|
|
31
|
+
|
|
32
|
+
// Helper to measure execution time
|
|
33
|
+
async function measureTime<T>(fn: () => Promise<T>): Promise<[T, number]> {
|
|
34
|
+
const start = performance.now();
|
|
35
|
+
const result = await fn();
|
|
36
|
+
const elapsed = performance.now() - start;
|
|
37
|
+
return [result, elapsed];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Helper to get directory size recursively
|
|
41
|
+
async function getDirectorySize(dir: string): Promise<number> {
|
|
42
|
+
let totalSize = 0;
|
|
43
|
+
try {
|
|
44
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const fullPath = join(dir, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
totalSize += await getDirectorySize(fullPath);
|
|
49
|
+
} else {
|
|
50
|
+
const stats = await stat(fullPath);
|
|
51
|
+
totalSize += stats.size;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Directory doesn't exist
|
|
56
|
+
}
|
|
57
|
+
return totalSize;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper to simulate cold start
|
|
61
|
+
async function simulateColdStart(
|
|
62
|
+
setupFn: () => Promise<void>,
|
|
63
|
+
): Promise<number> {
|
|
64
|
+
const start = performance.now();
|
|
65
|
+
await setupFn();
|
|
66
|
+
return performance.now() - start;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Helper to measure request throughput
|
|
70
|
+
async function measureThroughput(
|
|
71
|
+
handler: (req: Request) => Promise<Response>,
|
|
72
|
+
durationMs: number = 1000,
|
|
73
|
+
): Promise<number> {
|
|
74
|
+
let requestCount = 0;
|
|
75
|
+
const start = performance.now();
|
|
76
|
+
const testRequest = new Request('http://localhost:3000/');
|
|
77
|
+
|
|
78
|
+
while (performance.now() - start < durationMs) {
|
|
79
|
+
await handler(testRequest);
|
|
80
|
+
requestCount++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const elapsed = performance.now() - start;
|
|
84
|
+
return (requestCount / elapsed) * 1000; // requests per second
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe('Aeon Flux vs Next.js Benchmarks', () => {
|
|
88
|
+
beforeAll(async () => {
|
|
89
|
+
// Clean up and create test directories
|
|
90
|
+
await rm(BENCHMARK_DIR, { recursive: true, force: true });
|
|
91
|
+
await mkdir(AEON_APP_DIR, { recursive: true });
|
|
92
|
+
await mkdir(NEXTJS_APP_DIR, { recursive: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
afterAll(async () => {
|
|
96
|
+
await rm(BENCHMARK_DIR, { recursive: true, force: true });
|
|
97
|
+
|
|
98
|
+
// Print benchmark results
|
|
99
|
+
console.log('\n');
|
|
100
|
+
console.log(
|
|
101
|
+
'═══════════════════════════════════════════════════════════════',
|
|
102
|
+
);
|
|
103
|
+
console.log(
|
|
104
|
+
' BENCHMARK RESULTS ',
|
|
105
|
+
);
|
|
106
|
+
console.log(
|
|
107
|
+
'═══════════════════════════════════════════════════════════════',
|
|
108
|
+
);
|
|
109
|
+
console.log('');
|
|
110
|
+
|
|
111
|
+
const headers = ['Metric', 'Aeon Flux', 'Next.js (est.)', 'Improvement'];
|
|
112
|
+
const rows: string[][] = [];
|
|
113
|
+
|
|
114
|
+
const aeonResult = results.find((r) => r.name === 'Aeon Flux');
|
|
115
|
+
const nextResult = results.find((r) => r.name === 'Next.js (estimated)');
|
|
116
|
+
|
|
117
|
+
if (aeonResult && nextResult) {
|
|
118
|
+
rows.push([
|
|
119
|
+
'Build Time',
|
|
120
|
+
`${aeonResult.buildTimeMs.toFixed(0)}ms`,
|
|
121
|
+
`${nextResult.buildTimeMs.toFixed(0)}ms`,
|
|
122
|
+
`${(nextResult.buildTimeMs / aeonResult.buildTimeMs).toFixed(1)}x faster`,
|
|
123
|
+
]);
|
|
124
|
+
rows.push([
|
|
125
|
+
'Bundle Size',
|
|
126
|
+
`${aeonResult.bundleSizeKb.toFixed(1)}KB`,
|
|
127
|
+
`${nextResult.bundleSizeKb.toFixed(1)}KB`,
|
|
128
|
+
`${(nextResult.bundleSizeKb / aeonResult.bundleSizeKb).toFixed(1)}x smaller`,
|
|
129
|
+
]);
|
|
130
|
+
rows.push([
|
|
131
|
+
'Cold Start',
|
|
132
|
+
`${aeonResult.coldStartMs.toFixed(1)}ms`,
|
|
133
|
+
`${nextResult.coldStartMs.toFixed(1)}ms`,
|
|
134
|
+
`${(nextResult.coldStartMs / aeonResult.coldStartMs).toFixed(1)}x faster`,
|
|
135
|
+
]);
|
|
136
|
+
rows.push([
|
|
137
|
+
'Requests/sec',
|
|
138
|
+
`${aeonResult.requestsPerSecond.toFixed(0)}`,
|
|
139
|
+
`${nextResult.requestsPerSecond.toFixed(0)}`,
|
|
140
|
+
`${(aeonResult.requestsPerSecond / nextResult.requestsPerSecond).toFixed(1)}x higher`,
|
|
141
|
+
]);
|
|
142
|
+
rows.push([
|
|
143
|
+
'Memory Usage',
|
|
144
|
+
`${aeonResult.memoryUsageMb.toFixed(1)}MB`,
|
|
145
|
+
`${nextResult.memoryUsageMb.toFixed(1)}MB`,
|
|
146
|
+
`${(nextResult.memoryUsageMb / aeonResult.memoryUsageMb).toFixed(1)}x lower`,
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Print table
|
|
151
|
+
const colWidths = headers.map((h, i) =>
|
|
152
|
+
Math.max(h.length, ...rows.map((r) => r[i]?.length || 0)),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log(
|
|
156
|
+
'│ ' + headers.map((h, i) => h.padEnd(colWidths[i])).join(' │ ') + ' │',
|
|
157
|
+
);
|
|
158
|
+
console.log('├─' + colWidths.map((w) => '─'.repeat(w)).join('─┼─') + '─┤');
|
|
159
|
+
for (const row of rows) {
|
|
160
|
+
console.log(
|
|
161
|
+
'│ ' + row.map((c, i) => c.padEnd(colWidths[i])).join(' │ ') + ' │',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log(
|
|
166
|
+
'═══════════════════════════════════════════════════════════════',
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Aeon Flux', () => {
|
|
171
|
+
const pagesDir = join(AEON_APP_DIR, 'pages');
|
|
172
|
+
const outputDir = join(AEON_APP_DIR, '.aeon');
|
|
173
|
+
|
|
174
|
+
beforeAll(async () => {
|
|
175
|
+
// Create Aeon Flux app structure
|
|
176
|
+
await mkdir(pagesDir, { recursive: true });
|
|
177
|
+
await mkdir(join(pagesDir, 'blog', '[slug]'), { recursive: true });
|
|
178
|
+
await mkdir(join(pagesDir, 'about'), { recursive: true });
|
|
179
|
+
|
|
180
|
+
// Home page
|
|
181
|
+
await writeFile(
|
|
182
|
+
join(pagesDir, 'page.tsx'),
|
|
183
|
+
`'use aeon';
|
|
184
|
+
|
|
185
|
+
export default function Home() {
|
|
186
|
+
return (
|
|
187
|
+
<div className="container mx-auto p-4">
|
|
188
|
+
<h1 className="text-4xl font-bold">Welcome to Aeon Flux</h1>
|
|
189
|
+
<p className="mt-4">The CMS is the website.</p>
|
|
190
|
+
<nav className="mt-8">
|
|
191
|
+
<a href="/about" className="text-blue-500 hover:underline">About</a>
|
|
192
|
+
<a href="/blog/hello-world" className="ml-4 text-blue-500 hover:underline">Blog</a>
|
|
193
|
+
</nav>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}`,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// About page
|
|
200
|
+
await writeFile(
|
|
201
|
+
join(pagesDir, 'about', 'page.tsx'),
|
|
202
|
+
`'use aeon';
|
|
203
|
+
|
|
204
|
+
export default function About() {
|
|
205
|
+
return (
|
|
206
|
+
<div className="container mx-auto p-4">
|
|
207
|
+
<h1 className="text-3xl font-bold">About Us</h1>
|
|
208
|
+
<p className="mt-4">Aeon Flux is a collaborative page framework.</p>
|
|
209
|
+
<ul className="mt-4 list-disc list-inside">
|
|
210
|
+
<li>Real-time collaboration</li>
|
|
211
|
+
<li>CRDT-based sync</li>
|
|
212
|
+
<li>Edge-native runtime</li>
|
|
213
|
+
<li>~20KB WASM core</li>
|
|
214
|
+
</ul>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}`,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Dynamic blog page
|
|
221
|
+
await writeFile(
|
|
222
|
+
join(pagesDir, 'blog', '[slug]', 'page.tsx'),
|
|
223
|
+
`'use aeon';
|
|
224
|
+
|
|
225
|
+
export default function BlogPost({ params }) {
|
|
226
|
+
return (
|
|
227
|
+
<article className="container mx-auto p-4">
|
|
228
|
+
<h1 className="text-3xl font-bold">Blog Post</h1>
|
|
229
|
+
<p className="mt-2 text-gray-500">Slug: {params?.slug}</p>
|
|
230
|
+
<div className="mt-8 prose">
|
|
231
|
+
<p>This is a blog post with collaborative editing.</p>
|
|
232
|
+
<p>Multiple users can edit simultaneously.</p>
|
|
233
|
+
</div>
|
|
234
|
+
</article>
|
|
235
|
+
);
|
|
236
|
+
}`,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Config
|
|
240
|
+
await writeFile(
|
|
241
|
+
join(AEON_APP_DIR, 'aeon.config.ts'),
|
|
242
|
+
`export default {
|
|
243
|
+
pagesDir: './pages',
|
|
244
|
+
runtime: 'cloudflare',
|
|
245
|
+
output: { dir: '.aeon' },
|
|
246
|
+
};`,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('build time', async () => {
|
|
251
|
+
const originalCwd = process.cwd();
|
|
252
|
+
process.chdir(AEON_APP_DIR);
|
|
253
|
+
|
|
254
|
+
const [, buildTime] = await measureTime(() => build({}));
|
|
255
|
+
|
|
256
|
+
process.chdir(originalCwd);
|
|
257
|
+
|
|
258
|
+
console.log(` Aeon Flux build time: ${buildTime.toFixed(2)}ms`);
|
|
259
|
+
expect(buildTime).toBeLessThan(500); // Should build in under 500ms
|
|
260
|
+
|
|
261
|
+
// Store for comparison
|
|
262
|
+
const existingResult = results.find((r) => r.name === 'Aeon Flux');
|
|
263
|
+
if (existingResult) {
|
|
264
|
+
existingResult.buildTimeMs = buildTime;
|
|
265
|
+
} else {
|
|
266
|
+
results.push({
|
|
267
|
+
name: 'Aeon Flux',
|
|
268
|
+
buildTimeMs: buildTime,
|
|
269
|
+
bundleSizeKb: 0,
|
|
270
|
+
coldStartMs: 0,
|
|
271
|
+
requestsPerSecond: 0,
|
|
272
|
+
memoryUsageMb: 0,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('bundle size', async () => {
|
|
278
|
+
const totalSize = await getDirectorySize(outputDir);
|
|
279
|
+
const sizeKb = totalSize / 1024;
|
|
280
|
+
|
|
281
|
+
console.log(` Aeon Flux bundle size: ${sizeKb.toFixed(2)}KB`);
|
|
282
|
+
expect(sizeKb).toBeLessThan(100); // Should be under 100KB
|
|
283
|
+
|
|
284
|
+
const existingResult = results.find((r) => r.name === 'Aeon Flux');
|
|
285
|
+
if (existingResult) {
|
|
286
|
+
existingResult.bundleSizeKb = sizeKb;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('cold start simulation', async () => {
|
|
291
|
+
const coldStart = await simulateColdStart(async () => {
|
|
292
|
+
// Simulate importing and initializing the router
|
|
293
|
+
const { AeonRouter } = await import('../../runtime/src/router');
|
|
294
|
+
const router = new AeonRouter({ routesDir: pagesDir });
|
|
295
|
+
await router.scan();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
console.log(` Aeon Flux cold start: ${coldStart.toFixed(2)}ms`);
|
|
299
|
+
expect(coldStart).toBeLessThan(100); // Should start in under 100ms
|
|
300
|
+
|
|
301
|
+
const existingResult = results.find((r) => r.name === 'Aeon Flux');
|
|
302
|
+
if (existingResult) {
|
|
303
|
+
existingResult.coldStartMs = coldStart;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('request throughput', async () => {
|
|
308
|
+
const { AeonRouter } = await import('../../runtime/src/router');
|
|
309
|
+
const router = new AeonRouter({ routesDir: pagesDir });
|
|
310
|
+
await router.scan();
|
|
311
|
+
|
|
312
|
+
// Simple request handler
|
|
313
|
+
const handler = async (req: Request): Promise<Response> => {
|
|
314
|
+
const url = new URL(req.url);
|
|
315
|
+
const match = router.match(url.pathname);
|
|
316
|
+
|
|
317
|
+
if (!match) {
|
|
318
|
+
return new Response('Not Found', { status: 404 });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Simulate minimal response
|
|
322
|
+
return new Response(
|
|
323
|
+
`<html><body><div>Page: ${match.route.pattern}</div></body></html>`,
|
|
324
|
+
{ headers: { 'Content-Type': 'text/html' } },
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const rps = await measureThroughput(handler, 1000);
|
|
329
|
+
|
|
330
|
+
console.log(` Aeon Flux requests/sec: ${rps.toFixed(0)}`);
|
|
331
|
+
expect(rps).toBeGreaterThan(10000); // Should handle 10k+ req/sec
|
|
332
|
+
|
|
333
|
+
const existingResult = results.find((r) => r.name === 'Aeon Flux');
|
|
334
|
+
if (existingResult) {
|
|
335
|
+
existingResult.requestsPerSecond = rps;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('memory usage', async () => {
|
|
340
|
+
// Force GC if available
|
|
341
|
+
if (typeof Bun !== 'undefined' && Bun.gc) {
|
|
342
|
+
Bun.gc(true);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const memoryBefore = process.memoryUsage().heapUsed;
|
|
346
|
+
|
|
347
|
+
// Load router and routes
|
|
348
|
+
const { AeonRouter } = await import('../../runtime/src/router');
|
|
349
|
+
const router = new AeonRouter({ routesDir: pagesDir });
|
|
350
|
+
await router.scan();
|
|
351
|
+
|
|
352
|
+
// Simulate some requests
|
|
353
|
+
for (let i = 0; i < 100; i++) {
|
|
354
|
+
router.match('/');
|
|
355
|
+
router.match('/about');
|
|
356
|
+
router.match('/blog/test-post');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const memoryAfter = process.memoryUsage().heapUsed;
|
|
360
|
+
const memoryUsedMb = (memoryAfter - memoryBefore) / 1024 / 1024;
|
|
361
|
+
|
|
362
|
+
console.log(` Aeon Flux memory usage: ${memoryUsedMb.toFixed(2)}MB`);
|
|
363
|
+
expect(memoryUsedMb).toBeLessThan(10); // Should use less than 10MB
|
|
364
|
+
|
|
365
|
+
const existingResult = results.find((r) => r.name === 'Aeon Flux');
|
|
366
|
+
if (existingResult) {
|
|
367
|
+
existingResult.memoryUsageMb = Math.max(0.5, memoryUsedMb); // Min 0.5MB for display
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('Next.js (estimated baseline)', () => {
|
|
373
|
+
/**
|
|
374
|
+
* Next.js baseline estimates based on typical production builds.
|
|
375
|
+
* These are conservative estimates from real-world Next.js apps.
|
|
376
|
+
*
|
|
377
|
+
* Sources:
|
|
378
|
+
* - Next.js GitHub issues on build time
|
|
379
|
+
* - Vercel documentation on bundle sizes
|
|
380
|
+
* - Community benchmarks
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
test('build time (estimated)', async () => {
|
|
384
|
+
// Next.js typical build time for a simple 3-page app: 2-5 seconds
|
|
385
|
+
// We'll use 3000ms as a conservative estimate
|
|
386
|
+
const estimatedBuildTime = 3000;
|
|
387
|
+
|
|
388
|
+
console.log(` Next.js build time (est.): ${estimatedBuildTime}ms`);
|
|
389
|
+
|
|
390
|
+
results.push({
|
|
391
|
+
name: 'Next.js (estimated)',
|
|
392
|
+
buildTimeMs: estimatedBuildTime,
|
|
393
|
+
bundleSizeKb: 0,
|
|
394
|
+
coldStartMs: 0,
|
|
395
|
+
requestsPerSecond: 0,
|
|
396
|
+
memoryUsageMb: 0,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(estimatedBuildTime).toBeGreaterThan(0);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('bundle size (estimated)', async () => {
|
|
403
|
+
// Next.js runtime + React + minimal app: ~200-500KB
|
|
404
|
+
// We'll use 350KB as estimate (gzipped would be smaller)
|
|
405
|
+
const estimatedSizeKb = 350;
|
|
406
|
+
|
|
407
|
+
console.log(` Next.js bundle size (est.): ${estimatedSizeKb}KB`);
|
|
408
|
+
|
|
409
|
+
const existingResult = results.find(
|
|
410
|
+
(r) => r.name === 'Next.js (estimated)',
|
|
411
|
+
);
|
|
412
|
+
if (existingResult) {
|
|
413
|
+
existingResult.bundleSizeKb = estimatedSizeKb;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
expect(estimatedSizeKb).toBeGreaterThan(0);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('cold start (estimated)', async () => {
|
|
420
|
+
// Next.js cold start on serverless: 500-2000ms
|
|
421
|
+
// Edge runtime is faster but still ~200-500ms
|
|
422
|
+
const estimatedColdStart = 800;
|
|
423
|
+
|
|
424
|
+
console.log(` Next.js cold start (est.): ${estimatedColdStart}ms`);
|
|
425
|
+
|
|
426
|
+
const existingResult = results.find(
|
|
427
|
+
(r) => r.name === 'Next.js (estimated)',
|
|
428
|
+
);
|
|
429
|
+
if (existingResult) {
|
|
430
|
+
existingResult.coldStartMs = estimatedColdStart;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
expect(estimatedColdStart).toBeGreaterThan(0);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('request throughput (estimated)', async () => {
|
|
437
|
+
// Next.js with SSR: typically 500-2000 req/sec
|
|
438
|
+
// Static/ISR can be higher, but SSR is limited by React rendering
|
|
439
|
+
const estimatedRps = 1500;
|
|
440
|
+
|
|
441
|
+
console.log(` Next.js requests/sec (est.): ${estimatedRps}`);
|
|
442
|
+
|
|
443
|
+
const existingResult = results.find(
|
|
444
|
+
(r) => r.name === 'Next.js (estimated)',
|
|
445
|
+
);
|
|
446
|
+
if (existingResult) {
|
|
447
|
+
existingResult.requestsPerSecond = estimatedRps;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
expect(estimatedRps).toBeGreaterThan(0);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test('memory usage (estimated)', async () => {
|
|
454
|
+
// Next.js with React typically uses 50-150MB
|
|
455
|
+
// We'll estimate 80MB for a simple app
|
|
456
|
+
const estimatedMemoryMb = 80;
|
|
457
|
+
|
|
458
|
+
console.log(` Next.js memory usage (est.): ${estimatedMemoryMb}MB`);
|
|
459
|
+
|
|
460
|
+
const existingResult = results.find(
|
|
461
|
+
(r) => r.name === 'Next.js (estimated)',
|
|
462
|
+
);
|
|
463
|
+
if (existingResult) {
|
|
464
|
+
existingResult.memoryUsageMb = estimatedMemoryMb;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
expect(estimatedMemoryMb).toBeGreaterThan(0);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('Performance characteristics', () => {
|
|
472
|
+
test('Aeon Flux builds faster than Next.js estimate', () => {
|
|
473
|
+
const aeon = results.find((r) => r.name === 'Aeon Flux');
|
|
474
|
+
const next = results.find((r) => r.name === 'Next.js (estimated)');
|
|
475
|
+
|
|
476
|
+
if (aeon && next) {
|
|
477
|
+
const improvement = next.buildTimeMs / aeon.buildTimeMs;
|
|
478
|
+
console.log(` Build speed improvement: ${improvement.toFixed(1)}x`);
|
|
479
|
+
expect(improvement).toBeGreaterThan(5); // At least 5x faster
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test('Aeon Flux bundle is smaller than Next.js estimate', () => {
|
|
484
|
+
const aeon = results.find((r) => r.name === 'Aeon Flux');
|
|
485
|
+
const next = results.find((r) => r.name === 'Next.js (estimated)');
|
|
486
|
+
|
|
487
|
+
if (aeon && next) {
|
|
488
|
+
const improvement = next.bundleSizeKb / aeon.bundleSizeKb;
|
|
489
|
+
console.log(` Bundle size improvement: ${improvement.toFixed(1)}x`);
|
|
490
|
+
expect(improvement).toBeGreaterThan(3); // At least 3x smaller
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test('Aeon Flux cold starts faster than Next.js estimate', () => {
|
|
495
|
+
const aeon = results.find((r) => r.name === 'Aeon Flux');
|
|
496
|
+
const next = results.find((r) => r.name === 'Next.js (estimated)');
|
|
497
|
+
|
|
498
|
+
if (aeon && next) {
|
|
499
|
+
const improvement = next.coldStartMs / aeon.coldStartMs;
|
|
500
|
+
console.log(` Cold start improvement: ${improvement.toFixed(1)}x`);
|
|
501
|
+
expect(improvement).toBeGreaterThan(5); // At least 5x faster
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('Aeon Flux handles more requests than Next.js estimate', () => {
|
|
506
|
+
const aeon = results.find((r) => r.name === 'Aeon Flux');
|
|
507
|
+
const next = results.find((r) => r.name === 'Next.js (estimated)');
|
|
508
|
+
|
|
509
|
+
if (aeon && next) {
|
|
510
|
+
const improvement = aeon.requestsPerSecond / next.requestsPerSecond;
|
|
511
|
+
console.log(` Throughput improvement: ${improvement.toFixed(1)}x`);
|
|
512
|
+
expect(improvement).toBeGreaterThan(5); // At least 5x more throughput
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test('Aeon Flux uses less memory than Next.js estimate', () => {
|
|
517
|
+
const aeon = results.find((r) => r.name === 'Aeon Flux');
|
|
518
|
+
const next = results.find((r) => r.name === 'Next.js (estimated)');
|
|
519
|
+
|
|
520
|
+
if (aeon && next) {
|
|
521
|
+
const improvement = next.memoryUsageMb / aeon.memoryUsageMb;
|
|
522
|
+
console.log(` Memory improvement: ${improvement.toFixed(1)}x`);
|
|
523
|
+
expect(improvement).toBeGreaterThan(5); // At least 5x less memory
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe('Micro-benchmarks', () => {
|
|
530
|
+
test('route matching performance', async () => {
|
|
531
|
+
const { AeonRouter } = await import('../../runtime/src/router');
|
|
532
|
+
const router = new AeonRouter({ routesDir: './pages' });
|
|
533
|
+
|
|
534
|
+
// Add various route patterns
|
|
535
|
+
router.addRoute({
|
|
536
|
+
pattern: '/',
|
|
537
|
+
sessionId: 'index',
|
|
538
|
+
componentId: 'index',
|
|
539
|
+
isAeon: true,
|
|
540
|
+
});
|
|
541
|
+
router.addRoute({
|
|
542
|
+
pattern: '/about',
|
|
543
|
+
sessionId: 'about',
|
|
544
|
+
componentId: 'about',
|
|
545
|
+
isAeon: true,
|
|
546
|
+
});
|
|
547
|
+
router.addRoute({
|
|
548
|
+
pattern: '/blog/[slug]',
|
|
549
|
+
sessionId: 'blog-$slug',
|
|
550
|
+
componentId: 'blog-slug',
|
|
551
|
+
isAeon: true,
|
|
552
|
+
});
|
|
553
|
+
router.addRoute({
|
|
554
|
+
pattern: '/docs/[[...path]]',
|
|
555
|
+
sessionId: 'docs-$path',
|
|
556
|
+
componentId: 'docs',
|
|
557
|
+
isAeon: true,
|
|
558
|
+
});
|
|
559
|
+
router.addRoute({
|
|
560
|
+
pattern: '/api/[...rest]',
|
|
561
|
+
sessionId: 'api-$rest',
|
|
562
|
+
componentId: 'api',
|
|
563
|
+
isAeon: false,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const iterations = 100000;
|
|
567
|
+
const paths = [
|
|
568
|
+
'/',
|
|
569
|
+
'/about',
|
|
570
|
+
'/blog/hello-world',
|
|
571
|
+
'/docs/getting-started/installation',
|
|
572
|
+
'/api/users/123',
|
|
573
|
+
];
|
|
574
|
+
|
|
575
|
+
const start = performance.now();
|
|
576
|
+
for (let i = 0; i < iterations; i++) {
|
|
577
|
+
for (const path of paths) {
|
|
578
|
+
router.match(path);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const elapsed = performance.now() - start;
|
|
582
|
+
|
|
583
|
+
const totalMatches = iterations * paths.length;
|
|
584
|
+
const matchesPerSecond = (totalMatches / elapsed) * 1000;
|
|
585
|
+
const microsecondsPerMatch = (elapsed / totalMatches) * 1000;
|
|
586
|
+
|
|
587
|
+
console.log(` Route matching: ${matchesPerSecond.toFixed(0)} matches/sec`);
|
|
588
|
+
console.log(` Time per match: ${microsecondsPerMatch.toFixed(3)}μs`);
|
|
589
|
+
|
|
590
|
+
expect(matchesPerSecond).toBeGreaterThan(500000); // 500k+ matches/sec
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test('JSX parsing performance', async () => {
|
|
594
|
+
const jsxContent = `'use aeon';
|
|
595
|
+
|
|
596
|
+
export default function ComplexPage() {
|
|
597
|
+
return (
|
|
598
|
+
<div className="container mx-auto p-4">
|
|
599
|
+
<header className="mb-8">
|
|
600
|
+
<nav className="flex gap-4">
|
|
601
|
+
<a href="/">Home</a>
|
|
602
|
+
<a href="/about">About</a>
|
|
603
|
+
<a href="/blog">Blog</a>
|
|
604
|
+
</nav>
|
|
605
|
+
</header>
|
|
606
|
+
<main>
|
|
607
|
+
<h1 className="text-4xl font-bold">Welcome</h1>
|
|
608
|
+
<p className="mt-4 text-gray-600">This is a complex page with many elements.</p>
|
|
609
|
+
<section className="mt-8 grid grid-cols-3 gap-4">
|
|
610
|
+
<div className="p-4 bg-gray-100 rounded">Card 1</div>
|
|
611
|
+
<div className="p-4 bg-gray-100 rounded">Card 2</div>
|
|
612
|
+
<div className="p-4 bg-gray-100 rounded">Card 3</div>
|
|
613
|
+
</section>
|
|
614
|
+
</main>
|
|
615
|
+
<footer className="mt-8 pt-4 border-t">
|
|
616
|
+
<p>© 2024 Aeon Flux</p>
|
|
617
|
+
</footer>
|
|
618
|
+
</div>
|
|
619
|
+
);
|
|
620
|
+
}`;
|
|
621
|
+
|
|
622
|
+
// Simple JSX parser (same as build.ts)
|
|
623
|
+
const parseJSX = (content: string) => {
|
|
624
|
+
const returnMatch = content.match(/return\s*\(\s*([\s\S]*?)\s*\);?\s*\}/);
|
|
625
|
+
if (!returnMatch) return { type: 'div', children: ['Page content'] };
|
|
626
|
+
|
|
627
|
+
const jsx = returnMatch[1];
|
|
628
|
+
const rootMatch = jsx.match(/<(\w+)/);
|
|
629
|
+
const rootType = rootMatch?.[1] || 'div';
|
|
630
|
+
|
|
631
|
+
const textContent = jsx
|
|
632
|
+
.replace(/<[^>]+>/g, '')
|
|
633
|
+
.replace(/\{[^}]+\}/g, '')
|
|
634
|
+
.trim()
|
|
635
|
+
.slice(0, 100);
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
type: rootType,
|
|
639
|
+
props: { className: 'aeon-page' },
|
|
640
|
+
children: textContent ? [textContent] : ['Page content'],
|
|
641
|
+
};
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const iterations = 10000;
|
|
645
|
+
const start = performance.now();
|
|
646
|
+
|
|
647
|
+
for (let i = 0; i < iterations; i++) {
|
|
648
|
+
parseJSX(jsxContent);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const elapsed = performance.now() - start;
|
|
652
|
+
const parsesPerSecond = (iterations / elapsed) * 1000;
|
|
653
|
+
const microsecondsPerParse = (elapsed / iterations) * 1000;
|
|
654
|
+
|
|
655
|
+
console.log(` JSX parsing: ${parsesPerSecond.toFixed(0)} parses/sec`);
|
|
656
|
+
console.log(` Time per parse: ${microsecondsPerParse.toFixed(3)}μs`);
|
|
657
|
+
|
|
658
|
+
expect(parsesPerSecond).toBeGreaterThan(50000); // 50k+ parses/sec
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test('session ID generation performance', async () => {
|
|
662
|
+
const routeToSessionId = (route: string): string => {
|
|
663
|
+
return route.replace(/^\/|\/$/g, '').replace(/\//g, '-') || 'index';
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const routes = [
|
|
667
|
+
'/',
|
|
668
|
+
'/about',
|
|
669
|
+
'/blog/hello-world',
|
|
670
|
+
'/users/123/posts/456',
|
|
671
|
+
'/docs/api/reference/authentication',
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
const iterations = 100000;
|
|
675
|
+
const start = performance.now();
|
|
676
|
+
|
|
677
|
+
for (let i = 0; i < iterations; i++) {
|
|
678
|
+
for (const route of routes) {
|
|
679
|
+
routeToSessionId(route);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const elapsed = performance.now() - start;
|
|
684
|
+
const totalOps = iterations * routes.length;
|
|
685
|
+
const opsPerSecond = (totalOps / elapsed) * 1000;
|
|
686
|
+
|
|
687
|
+
console.log(` Session ID generation: ${opsPerSecond.toFixed(0)} ops/sec`);
|
|
688
|
+
|
|
689
|
+
expect(opsPerSecond).toBeGreaterThan(1000000); // 1M+ ops/sec
|
|
690
|
+
});
|
|
691
|
+
});
|