@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,465 @@
1
+ /**
2
+ * Aeon Pages API Routes
3
+ *
4
+ * Server-side route handling for API endpoints.
5
+ * Enables full-stack functionality without a separate backend.
6
+ */
7
+
8
+ import type {
9
+ AeonEnv,
10
+ AeonContext,
11
+ ApiRoute,
12
+ ApiRouteMatch,
13
+ ApiRouteModule,
14
+ ApiRouteHandler,
15
+ ApiRouteSegment,
16
+ HttpMethod,
17
+ ExecutionContext,
18
+ } from './types';
19
+
20
+ /**
21
+ * API Router - matches requests to registered API routes
22
+ */
23
+ export class ApiRouter<E extends AeonEnv = AeonEnv> {
24
+ private routes: ApiRoute[] = [];
25
+
26
+ /**
27
+ * Register an API route
28
+ */
29
+ register(pattern: string, module: ApiRouteModule<E>): void {
30
+ const segments = this.parsePattern(pattern);
31
+ this.routes.push({ pattern, segments, module: module as ApiRouteModule });
32
+ }
33
+
34
+ /**
35
+ * Register multiple routes from a route map
36
+ */
37
+ registerAll(routes: Record<string, ApiRouteModule<E>>): void {
38
+ for (const [pattern, module] of Object.entries(routes)) {
39
+ this.register(pattern, module);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Match a request to a route
45
+ */
46
+ match(request: Request): ApiRouteMatch | null {
47
+ const url = new URL(request.url);
48
+ const method = request.method.toUpperCase() as HttpMethod;
49
+ const pathSegments = url.pathname.split('/').filter(Boolean);
50
+
51
+ for (const route of this.routes) {
52
+ const params = this.matchSegments(route.segments, pathSegments);
53
+ if (params !== null) {
54
+ const handler = this.getHandler(route.module, method);
55
+ if (handler) {
56
+ return { route, params, handler };
57
+ }
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Handle an API request
66
+ */
67
+ async handle(
68
+ request: Request,
69
+ env: E,
70
+ ctx: ExecutionContext,
71
+ ): Promise<Response | null> {
72
+ const match = this.match(request);
73
+ if (!match) {
74
+ return null;
75
+ }
76
+
77
+ const url = new URL(request.url);
78
+ const context: AeonContext<E> = {
79
+ request,
80
+ env,
81
+ params: match.params,
82
+ url,
83
+ ctx,
84
+ };
85
+
86
+ try {
87
+ return await match.handler(context);
88
+ } catch (error) {
89
+ console.error('API route error:', error);
90
+ return new Response(
91
+ JSON.stringify({
92
+ error: 'Internal server error',
93
+ message: error instanceof Error ? error.message : 'Unknown error',
94
+ }),
95
+ {
96
+ status: 500,
97
+ headers: { 'Content-Type': 'application/json' },
98
+ },
99
+ );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Parse a route pattern into segments
105
+ */
106
+ private parsePattern(pattern: string): ApiRouteSegment[] {
107
+ return pattern
108
+ .split('/')
109
+ .filter(Boolean)
110
+ .map((segment) => {
111
+ // Catch-all: [...slug]
112
+ if (segment.startsWith('[...') && segment.endsWith(']')) {
113
+ return {
114
+ value: segment.slice(4, -1),
115
+ isDynamic: true,
116
+ isCatchAll: true,
117
+ };
118
+ }
119
+ // Dynamic: [id]
120
+ if (segment.startsWith('[') && segment.endsWith(']')) {
121
+ return {
122
+ value: segment.slice(1, -1),
123
+ isDynamic: true,
124
+ isCatchAll: false,
125
+ };
126
+ }
127
+ // Static
128
+ return {
129
+ value: segment,
130
+ isDynamic: false,
131
+ isCatchAll: false,
132
+ };
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Match path segments against route segments
138
+ */
139
+ private matchSegments(
140
+ routeSegments: ApiRouteSegment[],
141
+ pathSegments: string[],
142
+ ): Record<string, string> | null {
143
+ const params: Record<string, string> = {};
144
+ let pathIndex = 0;
145
+
146
+ for (let i = 0; i < routeSegments.length; i++) {
147
+ const routeSegment = routeSegments[i];
148
+
149
+ if (routeSegment.isCatchAll) {
150
+ // Catch-all consumes remaining segments
151
+ params[routeSegment.value] = pathSegments.slice(pathIndex).join('/');
152
+ return params;
153
+ }
154
+
155
+ if (pathIndex >= pathSegments.length) {
156
+ // No more path segments but route expects more
157
+ return null;
158
+ }
159
+
160
+ if (routeSegment.isDynamic) {
161
+ // Dynamic segment - capture value
162
+ params[routeSegment.value] = pathSegments[pathIndex];
163
+ pathIndex++;
164
+ } else {
165
+ // Static segment - must match exactly
166
+ if (routeSegment.value !== pathSegments[pathIndex]) {
167
+ return null;
168
+ }
169
+ pathIndex++;
170
+ }
171
+ }
172
+
173
+ // All path segments must be consumed
174
+ if (pathIndex !== pathSegments.length) {
175
+ return null;
176
+ }
177
+
178
+ return params;
179
+ }
180
+
181
+ /**
182
+ * Get the handler for a given HTTP method
183
+ */
184
+ private getHandler(
185
+ module: ApiRouteModule,
186
+ method: HttpMethod,
187
+ ): ApiRouteHandler | null {
188
+ const handler = module[method];
189
+ if (handler) {
190
+ return handler;
191
+ }
192
+ // Fall back to default handler
193
+ if (module.default) {
194
+ return module.default;
195
+ }
196
+ return null;
197
+ }
198
+
199
+ /**
200
+ * Get all registered routes (for debugging/introspection)
201
+ */
202
+ getRoutes(): ApiRoute[] {
203
+ return [...this.routes];
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Create a new API router instance
209
+ */
210
+ export function createApiRouter<E extends AeonEnv = AeonEnv>(): ApiRouter<E> {
211
+ return new ApiRouter<E>();
212
+ }
213
+
214
+ /**
215
+ * Helper to create a JSON response
216
+ */
217
+ export function json<T>(data: T, init?: ResponseInit): Response {
218
+ return new Response(JSON.stringify(data), {
219
+ ...init,
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ ...init?.headers,
223
+ },
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Helper to create a redirect response
229
+ */
230
+ export function redirect(
231
+ url: string,
232
+ status: 301 | 302 | 303 | 307 | 308 = 302,
233
+ ): Response {
234
+ return new Response(null, {
235
+ status,
236
+ headers: { Location: url },
237
+ });
238
+ }
239
+
240
+ /**
241
+ * Helper to create an error response
242
+ */
243
+ export function error(message: string, status = 500): Response {
244
+ return new Response(JSON.stringify({ error: message }), {
245
+ status,
246
+ headers: { 'Content-Type': 'application/json' },
247
+ });
248
+ }
249
+
250
+ /**
251
+ * Helper to create a not found response
252
+ */
253
+ export function notFound(message = 'Not found'): Response {
254
+ return error(message, 404);
255
+ }
256
+
257
+ /**
258
+ * Helper to create a bad request response
259
+ */
260
+ export function badRequest(message = 'Bad request'): Response {
261
+ return error(message, 400);
262
+ }
263
+
264
+ /**
265
+ * Helper to create an unauthorized response
266
+ */
267
+ export function unauthorized(message = 'Unauthorized'): Response {
268
+ return error(message, 401);
269
+ }
270
+
271
+ /**
272
+ * Helper to create a forbidden response
273
+ */
274
+ export function forbidden(message = 'Forbidden'): Response {
275
+ return error(message, 403);
276
+ }
277
+
278
+ // =============================================================================
279
+ // MIDDLEWARE SUPPORT
280
+ // =============================================================================
281
+
282
+ /** Middleware function type */
283
+ export type Middleware<E extends AeonEnv = AeonEnv> = (
284
+ context: AeonContext<E>,
285
+ next: () => Promise<Response>,
286
+ ) => Response | Promise<Response>;
287
+
288
+ /**
289
+ * Compose multiple middleware into a single handler
290
+ */
291
+ export function composeMiddleware<E extends AeonEnv = AeonEnv>(
292
+ ...middlewares: Middleware<E>[]
293
+ ): (handler: ApiRouteHandler<E>) => ApiRouteHandler<E> {
294
+ return (handler: ApiRouteHandler<E>): ApiRouteHandler<E> => {
295
+ return async (context: AeonContext<E>): Promise<Response> => {
296
+ let index = 0;
297
+
298
+ const next = async (): Promise<Response> => {
299
+ if (index < middlewares.length) {
300
+ const middleware = middlewares[index++];
301
+ return middleware(context, next);
302
+ }
303
+ return handler(context);
304
+ };
305
+
306
+ return next();
307
+ };
308
+ };
309
+ }
310
+
311
+ /**
312
+ * CORS middleware factory
313
+ */
314
+ export function cors(options?: {
315
+ origin?: string | string[] | ((origin: string) => boolean);
316
+ methods?: string[];
317
+ headers?: string[];
318
+ credentials?: boolean;
319
+ maxAge?: number;
320
+ }): Middleware {
321
+ const opts = {
322
+ origin: '*',
323
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
324
+ headers: ['Content-Type', 'Authorization'],
325
+ credentials: false,
326
+ maxAge: 86400,
327
+ ...options,
328
+ };
329
+
330
+ return async (context, next) => {
331
+ const requestOrigin = context.request.headers.get('Origin') || '';
332
+
333
+ // Determine allowed origin
334
+ let allowedOrigin = '*';
335
+ if (typeof opts.origin === 'string') {
336
+ allowedOrigin = opts.origin;
337
+ } else if (Array.isArray(opts.origin)) {
338
+ if (opts.origin.includes(requestOrigin)) {
339
+ allowedOrigin = requestOrigin;
340
+ }
341
+ } else if (typeof opts.origin === 'function') {
342
+ if (opts.origin(requestOrigin)) {
343
+ allowedOrigin = requestOrigin;
344
+ }
345
+ }
346
+
347
+ const corsHeaders: Record<string, string> = {
348
+ 'Access-Control-Allow-Origin': allowedOrigin,
349
+ 'Access-Control-Allow-Methods': opts.methods.join(', '),
350
+ 'Access-Control-Allow-Headers': opts.headers.join(', '),
351
+ };
352
+
353
+ if (opts.credentials) {
354
+ corsHeaders['Access-Control-Allow-Credentials'] = 'true';
355
+ }
356
+
357
+ // Handle preflight
358
+ if (context.request.method === 'OPTIONS') {
359
+ return new Response(null, {
360
+ status: 204,
361
+ headers: {
362
+ ...corsHeaders,
363
+ 'Access-Control-Max-Age': String(opts.maxAge),
364
+ },
365
+ });
366
+ }
367
+
368
+ const response = await next();
369
+
370
+ // Add CORS headers to response
371
+ const newHeaders = new Headers(response.headers);
372
+ for (const [key, value] of Object.entries(corsHeaders)) {
373
+ newHeaders.set(key, value);
374
+ }
375
+
376
+ return new Response(response.body, {
377
+ status: response.status,
378
+ statusText: response.statusText,
379
+ headers: newHeaders,
380
+ });
381
+ };
382
+ }
383
+
384
+ /**
385
+ * Auth middleware factory - validates Authorization header
386
+ */
387
+ export function requireAuth<E extends AeonEnv = AeonEnv>(
388
+ validate: (
389
+ token: string,
390
+ context: AeonContext<E>,
391
+ ) => boolean | Promise<boolean>,
392
+ ): Middleware<E> {
393
+ return async (context, next) => {
394
+ const authHeader = context.request.headers.get('Authorization');
395
+ if (!authHeader) {
396
+ return unauthorized('Missing Authorization header');
397
+ }
398
+
399
+ const token = authHeader.replace(/^Bearer\s+/i, '');
400
+ const isValid = await validate(token, context);
401
+
402
+ if (!isValid) {
403
+ return unauthorized('Invalid token');
404
+ }
405
+
406
+ return next();
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Rate limiting middleware (uses KV for distributed rate limiting)
412
+ */
413
+ export function rateLimit<E extends AeonEnv = AeonEnv>(options: {
414
+ /** KV namespace key in env */
415
+ kvKey?: keyof E;
416
+ /** Requests per window */
417
+ limit: number;
418
+ /** Window size in seconds */
419
+ window: number;
420
+ /** Function to extract client identifier (default: IP) */
421
+ keyGenerator?: (context: AeonContext<E>) => string;
422
+ }): Middleware<E> {
423
+ return async (context, next) => {
424
+ const kv = options.kvKey
425
+ ? (context.env[options.kvKey] as unknown)
426
+ : context.env.CACHE;
427
+ if (!kv || typeof (kv as Record<string, unknown>).get !== 'function') {
428
+ // No KV available, skip rate limiting
429
+ return next();
430
+ }
431
+
432
+ const kvNamespace = kv as {
433
+ get: (key: string) => Promise<string | null>;
434
+ put: (
435
+ key: string,
436
+ value: string,
437
+ options?: { expirationTtl?: number },
438
+ ) => Promise<void>;
439
+ };
440
+ const clientKey = options.keyGenerator
441
+ ? options.keyGenerator(context)
442
+ : context.request.headers.get('CF-Connecting-IP') || 'unknown';
443
+
444
+ const rateLimitKey = `ratelimit:${clientKey}`;
445
+ const current = await kvNamespace.get(rateLimitKey);
446
+ const count = current ? parseInt(current, 10) : 0;
447
+
448
+ if (count >= options.limit) {
449
+ return new Response(JSON.stringify({ error: 'Too many requests' }), {
450
+ status: 429,
451
+ headers: {
452
+ 'Content-Type': 'application/json',
453
+ 'Retry-After': String(options.window),
454
+ },
455
+ });
456
+ }
457
+
458
+ // Increment counter
459
+ await kvNamespace.put(rateLimitKey, String(count + 1), {
460
+ expirationTtl: options.window,
461
+ });
462
+
463
+ return next();
464
+ };
465
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Aeon Flux Benchmarks
3
+ *
4
+ * Run: bun run src/benchmark.ts
5
+ */
6
+
7
+ import { HeuristicAdapter } from './router/heuristic-adapter';
8
+ import { compileTreeToTSX } from './tree-compiler';
9
+ import type { UserContext, ComponentTree, ComponentNode } from './router/types';
10
+
11
+ // Mock data
12
+ function createMockContext(): UserContext {
13
+ return {
14
+ tier: 'pro',
15
+ recentPages: ['/', '/chat', '/settings', '/chat', '/', '/tools'],
16
+ dwellTimes: new Map([
17
+ ['/', 5000],
18
+ ['/chat', 12000],
19
+ ]),
20
+ clickPatterns: ['nav-chat', 'btn-send', 'nav-home'],
21
+ preferences: { theme: 'dark' },
22
+ viewport: { width: 1920, height: 1080 },
23
+ connection: 'fast',
24
+ reducedMotion: false,
25
+ localHour: 14,
26
+ timezone: 'America/New_York',
27
+ isNewSession: false,
28
+ };
29
+ }
30
+
31
+ function createMockTree(nodeCount: number): ComponentTree {
32
+ const nodes = new Map<string, ComponentNode>();
33
+
34
+ nodes.set('root', { id: 'root', type: 'Page', children: [] });
35
+
36
+ for (let i = 0; i < nodeCount; i++) {
37
+ const id = `node-${i}`;
38
+ nodes.set(id, {
39
+ id,
40
+ type: i % 3 === 0 ? 'Section' : i % 3 === 1 ? 'Text' : 'Button',
41
+ props: { title: `Node ${i}`, className: 'test-class' },
42
+ });
43
+ (nodes.get('root')!.children as string[]).push(id);
44
+ }
45
+
46
+ return {
47
+ rootId: 'root',
48
+ nodes,
49
+ getNode: (id) => nodes.get(id),
50
+ getChildren: (id) => {
51
+ const node = nodes.get(id);
52
+ if (!node?.children) return [];
53
+ return (node.children as string[]).map((cid) => nodes.get(cid)!);
54
+ },
55
+ getSchema: () => ({
56
+ rootId: 'root',
57
+ nodeCount: nodes.size,
58
+ nodeTypes: ['Page', 'Section', 'Text', 'Button'],
59
+ depth: 2,
60
+ }),
61
+ clone: () => createMockTree(nodeCount),
62
+ };
63
+ }
64
+
65
+ function createMockTreeForCompiler(nodeCount: number) {
66
+ const children = [];
67
+ for (let i = 0; i < nodeCount; i++) {
68
+ children.push({
69
+ id: `node-${i}`,
70
+ type: i % 3 === 0 ? 'Section' : i % 3 === 1 ? 'Text' : 'Button',
71
+ props: { title: `Node ${i}`, className: 'test-class' },
72
+ children:
73
+ i % 5 === 0
74
+ ? [{ id: `nested-${i}`, type: 'Span', text: 'Nested content' }]
75
+ : [],
76
+ });
77
+ }
78
+ return { id: 'root', type: 'Page', children };
79
+ }
80
+
81
+ async function benchmark(
82
+ name: string,
83
+ fn: () => unknown | Promise<unknown>,
84
+ iterations: number = 1000,
85
+ ) {
86
+ // Warmup
87
+ for (let i = 0; i < 10; i++) await fn();
88
+
89
+ const start = performance.now();
90
+ for (let i = 0; i < iterations; i++) {
91
+ await fn();
92
+ }
93
+ const elapsed = performance.now() - start;
94
+
95
+ const avgMs = elapsed / iterations;
96
+ const opsPerSec = Math.round(1000 / avgMs);
97
+
98
+ console.log(
99
+ `${name.padEnd(40)} ${avgMs.toFixed(3).padStart(8)}ms ${opsPerSec.toLocaleString().padStart(8)} ops/sec`,
100
+ );
101
+
102
+ return { name, avgMs, opsPerSec };
103
+ }
104
+
105
+ async function main() {
106
+ console.log('\nšŸš€ Aeon Flux Benchmarks\n');
107
+ console.log('='.repeat(70));
108
+
109
+ const adapter = new HeuristicAdapter({
110
+ defaultPaths: ['/', '/chat', '/settings', '/tools', '/about'],
111
+ });
112
+ const context = createMockContext();
113
+
114
+ // Router benchmarks
115
+ console.log('\nšŸ“ HeuristicAdapter.route()\n');
116
+
117
+ for (const nodeCount of [10, 50, 100, 500]) {
118
+ const tree = createMockTree(nodeCount);
119
+ await benchmark(` route() with ${nodeCount} nodes`, () =>
120
+ adapter.route('/', context, tree),
121
+ );
122
+ }
123
+
124
+ // Speculation benchmarks
125
+ console.log('\nšŸ”® HeuristicAdapter.speculate()\n');
126
+
127
+ await benchmark(' speculate() - empty history', () =>
128
+ adapter.speculate('/', { ...context, recentPages: [] }),
129
+ );
130
+
131
+ await benchmark(' speculate() - 6 page history', () =>
132
+ adapter.speculate('/', context),
133
+ );
134
+
135
+ await benchmark(' speculate() - 50 page history', () =>
136
+ adapter.speculate('/', {
137
+ ...context,
138
+ recentPages: Array(50)
139
+ .fill('/')
140
+ .map((_, i) => `/${i % 5}`),
141
+ }),
142
+ );
143
+
144
+ // Tree compiler benchmarks
145
+ console.log('\n🌳 Tree → TSX Compiler\n');
146
+
147
+ for (const nodeCount of [10, 50, 100, 500]) {
148
+ const tree = createMockTreeForCompiler(nodeCount);
149
+ await benchmark(
150
+ ` compile ${nodeCount} nodes → TSX`,
151
+ () => compileTreeToTSX(tree, { route: '/test' }),
152
+ 100,
153
+ );
154
+ }
155
+
156
+ // Personalization benchmarks
157
+ console.log('\nšŸŽØ personalizeTree()\n');
158
+
159
+ for (const nodeCount of [10, 50, 100, 500]) {
160
+ const tree = createMockTree(nodeCount);
161
+ const decision = await adapter.route('/', context, tree);
162
+ await benchmark(` personalize ${nodeCount} nodes`, () =>
163
+ adapter.personalizeTree(tree, decision),
164
+ );
165
+ }
166
+
167
+ console.log('\n' + '='.repeat(70));
168
+ console.log('āœ… Benchmarks complete\n');
169
+ }
170
+
171
+ main().catch(console.error);