@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. 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
+ });