@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,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Router - TypeScript wrapper for WASM router
|
|
3
|
+
*
|
|
4
|
+
* Handles route matching with Next.js-style patterns:
|
|
5
|
+
* - Static: /about
|
|
6
|
+
* - Dynamic: /blog/[slug]
|
|
7
|
+
* - Catch-all: /api/[...path]
|
|
8
|
+
* - Optional catch-all: /docs/[[...slug]]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RouteDefinition, RouteMatch } from './types';
|
|
12
|
+
import { readdir, stat } from 'fs/promises';
|
|
13
|
+
import { join, relative } from 'path';
|
|
14
|
+
|
|
15
|
+
interface RouterOptions {
|
|
16
|
+
routesDir: string;
|
|
17
|
+
componentsDir?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ParsedRoute {
|
|
21
|
+
pattern: string;
|
|
22
|
+
segments: Segment[];
|
|
23
|
+
definition: RouteDefinition;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Segment =
|
|
27
|
+
| { type: 'static'; value: string }
|
|
28
|
+
| { type: 'dynamic'; name: string }
|
|
29
|
+
| { type: 'catchAll'; name: string }
|
|
30
|
+
| { type: 'optionalCatchAll'; name: string };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Aeon Router - file-based routing with dynamic segment support
|
|
34
|
+
*/
|
|
35
|
+
export class AeonRouter {
|
|
36
|
+
private routes: ParsedRoute[] = [];
|
|
37
|
+
private routesDir: string;
|
|
38
|
+
private componentsDir?: string;
|
|
39
|
+
|
|
40
|
+
constructor(options: RouterOptions) {
|
|
41
|
+
this.routesDir = options.routesDir;
|
|
42
|
+
this.componentsDir = options.componentsDir;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Scan the routes directory and build the route table
|
|
47
|
+
*/
|
|
48
|
+
async scan(): Promise<void> {
|
|
49
|
+
this.routes = [];
|
|
50
|
+
await this.scanDirectory(this.routesDir, '');
|
|
51
|
+
this.sortRoutes();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reload routes (for hot reload)
|
|
56
|
+
*/
|
|
57
|
+
async reload(): Promise<void> {
|
|
58
|
+
await this.scan();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Match a URL path to a route
|
|
63
|
+
*/
|
|
64
|
+
match(path: string): RouteMatch | null {
|
|
65
|
+
const pathSegments = path
|
|
66
|
+
.replace(/^\/|\/$/g, '')
|
|
67
|
+
.split('/')
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
|
|
70
|
+
for (const parsed of this.routes) {
|
|
71
|
+
const params = this.matchSegments(parsed.segments, pathSegments);
|
|
72
|
+
if (params !== null) {
|
|
73
|
+
const sessionId = this.resolveSessionId(
|
|
74
|
+
parsed.definition.sessionId,
|
|
75
|
+
params,
|
|
76
|
+
);
|
|
77
|
+
return {
|
|
78
|
+
route: parsed.definition,
|
|
79
|
+
params,
|
|
80
|
+
sessionId,
|
|
81
|
+
componentId: parsed.definition.componentId,
|
|
82
|
+
isAeon: parsed.definition.isAeon,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a route exists
|
|
92
|
+
*/
|
|
93
|
+
hasRoute(path: string): boolean {
|
|
94
|
+
return this.match(path) !== null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all registered routes
|
|
99
|
+
*/
|
|
100
|
+
getRoutes(): RouteDefinition[] {
|
|
101
|
+
return this.routes.map((r) => r.definition);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Add a route manually (for dynamic creation)
|
|
106
|
+
*/
|
|
107
|
+
addRoute(definition: RouteDefinition): void {
|
|
108
|
+
const segments = this.parsePattern(definition.pattern);
|
|
109
|
+
this.routes.push({ pattern: definition.pattern, segments, definition });
|
|
110
|
+
this.sortRoutes();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Private methods
|
|
114
|
+
|
|
115
|
+
private async scanDirectory(dir: string, prefix: string): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
118
|
+
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const fullPath = join(dir, entry.name);
|
|
121
|
+
|
|
122
|
+
if (entry.isDirectory()) {
|
|
123
|
+
// Skip route groups in URL but process their contents
|
|
124
|
+
const isRouteGroup =
|
|
125
|
+
entry.name.startsWith('(') && entry.name.endsWith(')');
|
|
126
|
+
const newPrefix = isRouteGroup ? prefix : `${prefix}/${entry.name}`;
|
|
127
|
+
await this.scanDirectory(fullPath, newPrefix);
|
|
128
|
+
} else if (entry.name === 'page.tsx' || entry.name === 'page.ts') {
|
|
129
|
+
// Found a page file
|
|
130
|
+
const route = await this.createRouteFromFile(fullPath, prefix);
|
|
131
|
+
if (route) {
|
|
132
|
+
this.routes.push(route);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`[aeon] Error scanning directory ${dir}:`, error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async createRouteFromFile(
|
|
142
|
+
filePath: string,
|
|
143
|
+
prefix: string,
|
|
144
|
+
): Promise<ParsedRoute | null> {
|
|
145
|
+
try {
|
|
146
|
+
// Read file to check for 'use aeon' directive
|
|
147
|
+
const file = Bun.file(filePath);
|
|
148
|
+
const content = await file.text();
|
|
149
|
+
const isAeon =
|
|
150
|
+
content.includes("'use aeon'") || content.includes('"use aeon"');
|
|
151
|
+
|
|
152
|
+
// Convert file path to route pattern
|
|
153
|
+
const pattern = prefix || '/';
|
|
154
|
+
const segments = this.parsePattern(pattern);
|
|
155
|
+
|
|
156
|
+
// Generate session ID template
|
|
157
|
+
const sessionId = this.generateSessionId(pattern);
|
|
158
|
+
|
|
159
|
+
// Component ID from file path
|
|
160
|
+
const componentId =
|
|
161
|
+
relative(this.routesDir, filePath)
|
|
162
|
+
.replace(/\.(tsx?|jsx?)$/, '')
|
|
163
|
+
.replace(/\//g, '-')
|
|
164
|
+
.replace(/page$/, '')
|
|
165
|
+
.replace(/-$/, '') || 'index';
|
|
166
|
+
|
|
167
|
+
const definition: RouteDefinition = {
|
|
168
|
+
pattern,
|
|
169
|
+
sessionId,
|
|
170
|
+
componentId,
|
|
171
|
+
isAeon,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return { pattern, segments, definition };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`[aeon] Error reading file ${filePath}:`, error);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private parsePattern(pattern: string): Segment[] {
|
|
182
|
+
return pattern
|
|
183
|
+
.replace(/^\/|\/$/g, '')
|
|
184
|
+
.split('/')
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
.filter((s) => !(s.startsWith('(') && s.endsWith(')'))) // Skip route groups
|
|
187
|
+
.map((s): Segment => {
|
|
188
|
+
if (s.startsWith('[[...') && s.endsWith(']]')) {
|
|
189
|
+
return { type: 'optionalCatchAll', name: s.slice(5, -2) };
|
|
190
|
+
}
|
|
191
|
+
if (s.startsWith('[...') && s.endsWith(']')) {
|
|
192
|
+
return { type: 'catchAll', name: s.slice(4, -1) };
|
|
193
|
+
}
|
|
194
|
+
if (s.startsWith('[') && s.endsWith(']')) {
|
|
195
|
+
return { type: 'dynamic', name: s.slice(1, -1) };
|
|
196
|
+
}
|
|
197
|
+
return { type: 'static', value: s };
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private matchSegments(
|
|
202
|
+
routeSegments: Segment[],
|
|
203
|
+
pathSegments: string[],
|
|
204
|
+
): Record<string, string> | null {
|
|
205
|
+
const params: Record<string, string> = {};
|
|
206
|
+
let pathIdx = 0;
|
|
207
|
+
|
|
208
|
+
for (const segment of routeSegments) {
|
|
209
|
+
switch (segment.type) {
|
|
210
|
+
case 'static':
|
|
211
|
+
if (
|
|
212
|
+
pathIdx >= pathSegments.length ||
|
|
213
|
+
pathSegments[pathIdx] !== segment.value
|
|
214
|
+
) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
pathIdx++;
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'dynamic':
|
|
221
|
+
if (pathIdx >= pathSegments.length) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
params[segment.name] = pathSegments[pathIdx];
|
|
225
|
+
pathIdx++;
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case 'catchAll':
|
|
229
|
+
if (pathIdx >= pathSegments.length) {
|
|
230
|
+
return null; // Must match at least one segment
|
|
231
|
+
}
|
|
232
|
+
params[segment.name] = pathSegments.slice(pathIdx).join('/');
|
|
233
|
+
pathIdx = pathSegments.length;
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case 'optionalCatchAll':
|
|
237
|
+
if (pathIdx < pathSegments.length) {
|
|
238
|
+
params[segment.name] = pathSegments.slice(pathIdx).join('/');
|
|
239
|
+
pathIdx = pathSegments.length;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// All path segments must be consumed
|
|
246
|
+
return pathIdx === pathSegments.length ? params : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private generateSessionId(pattern: string): string {
|
|
250
|
+
return (
|
|
251
|
+
pattern
|
|
252
|
+
.replace(/^\/|\/$/g, '')
|
|
253
|
+
.replace(/\[\.\.\.(\w+)\]/g, '$$$1')
|
|
254
|
+
.replace(/\[\[\.\.\.(\w+)\]\]/g, '$$$1')
|
|
255
|
+
.replace(/\[(\w+)\]/g, '$$$1')
|
|
256
|
+
.replace(/\//g, '-') || 'index'
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private resolveSessionId(
|
|
261
|
+
template: string,
|
|
262
|
+
params: Record<string, string>,
|
|
263
|
+
): string {
|
|
264
|
+
let result = template;
|
|
265
|
+
for (const [key, value] of Object.entries(params)) {
|
|
266
|
+
result = result.replace(`$${key}`, value);
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private sortRoutes(): void {
|
|
272
|
+
// Sort by specificity: static > dynamic > catch-all
|
|
273
|
+
this.routes.sort((a, b) => {
|
|
274
|
+
const scoreA = this.routeSpecificity(a.segments);
|
|
275
|
+
const scoreB = this.routeSpecificity(b.segments);
|
|
276
|
+
return scoreB - scoreA;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private routeSpecificity(segments: Segment[]): number {
|
|
281
|
+
let score = 0;
|
|
282
|
+
for (let i = 0; i < segments.length; i++) {
|
|
283
|
+
const positionWeight = 1000 - i;
|
|
284
|
+
const segment = segments[i];
|
|
285
|
+
switch (segment.type) {
|
|
286
|
+
case 'static':
|
|
287
|
+
score += positionWeight * 10;
|
|
288
|
+
break;
|
|
289
|
+
case 'dynamic':
|
|
290
|
+
score += positionWeight * 5;
|
|
291
|
+
break;
|
|
292
|
+
case 'catchAll':
|
|
293
|
+
score += 1;
|
|
294
|
+
break;
|
|
295
|
+
case 'optionalCatchAll':
|
|
296
|
+
score += 0;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return score;
|
|
301
|
+
}
|
|
302
|
+
}
|