@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,451 @@
1
+ /**
2
+ * Speculation Manager
3
+ *
4
+ * Client-side prefetching and prerendering for instant navigation.
5
+ * Uses the Speculation Rules API when available, with fallback to link prefetch.
6
+ */
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ export interface SpeculationOptions {
13
+ /** Maximum paths to prefetch */
14
+ maxPrefetch?: number;
15
+
16
+ /** Maximum paths to prerender (more expensive) */
17
+ maxPrerender?: number;
18
+
19
+ /** Prefetch on hover delay (ms) */
20
+ hoverDelay?: number;
21
+
22
+ /** Prefetch on viewport intersection */
23
+ prefetchOnVisible?: boolean;
24
+
25
+ /** Intersection observer threshold */
26
+ visibilityThreshold?: number;
27
+
28
+ /** Cache duration for prefetched resources (ms) */
29
+ cacheDuration?: number;
30
+
31
+ /** Callback when speculation occurs */
32
+ onSpeculate?: (path: string, type: 'prefetch' | 'prerender') => void;
33
+ }
34
+
35
+ export interface SpeculationState {
36
+ prefetched: Set<string>;
37
+ prerendered: Set<string>;
38
+ pending: Set<string>;
39
+ }
40
+
41
+ // ============================================================================
42
+ // Feature Detection
43
+ // ============================================================================
44
+
45
+ function supportsSpeculationRules(): boolean {
46
+ if (typeof document === 'undefined') return false;
47
+ return (
48
+ 'supports' in HTMLScriptElement &&
49
+ HTMLScriptElement.supports?.('speculationrules')
50
+ );
51
+ }
52
+
53
+ function supportsLinkPrefetch(): boolean {
54
+ if (typeof document === 'undefined') return false;
55
+ const link = document.createElement('link');
56
+ return link.relList?.supports?.('prefetch') ?? false;
57
+ }
58
+
59
+ // ============================================================================
60
+ // Speculation Rules API
61
+ // ============================================================================
62
+
63
+ /**
64
+ * Add speculation rules to the document
65
+ */
66
+ function addSpeculationRules(
67
+ prefetch: string[],
68
+ prerender: string[],
69
+ ): HTMLScriptElement | null {
70
+ if (!supportsSpeculationRules()) return null;
71
+
72
+ const rules: Record<string, Array<{ urls: string[] }>> = {};
73
+
74
+ if (prefetch.length > 0) {
75
+ rules.prefetch = [{ urls: prefetch }];
76
+ }
77
+
78
+ if (prerender.length > 0) {
79
+ rules.prerender = [{ urls: prerender }];
80
+ }
81
+
82
+ if (Object.keys(rules).length === 0) return null;
83
+
84
+ const script = document.createElement('script');
85
+ script.type = 'speculationrules';
86
+ script.textContent = JSON.stringify(rules);
87
+ document.head.appendChild(script);
88
+
89
+ return script;
90
+ }
91
+
92
+ /**
93
+ * Remove speculation rules script
94
+ */
95
+ function removeSpeculationRules(script: HTMLScriptElement): void {
96
+ script.remove();
97
+ }
98
+
99
+ // ============================================================================
100
+ // Link Prefetch Fallback
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Prefetch a path using <link rel="prefetch">
105
+ */
106
+ function linkPrefetch(path: string): HTMLLinkElement | null {
107
+ if (!supportsLinkPrefetch()) return null;
108
+
109
+ // Check if already prefetched
110
+ const existing = document.querySelector(
111
+ `link[rel="prefetch"][href="${path}"]`,
112
+ );
113
+ if (existing) return existing as HTMLLinkElement;
114
+
115
+ const link = document.createElement('link');
116
+ link.rel = 'prefetch';
117
+ link.href = path;
118
+ document.head.appendChild(link);
119
+
120
+ return link;
121
+ }
122
+
123
+ /**
124
+ * Remove prefetch link
125
+ */
126
+ function removePrefetch(link: HTMLLinkElement): void {
127
+ link.remove();
128
+ }
129
+
130
+ // ============================================================================
131
+ // Speculation Manager
132
+ // ============================================================================
133
+
134
+ export class SpeculationManager {
135
+ private options: Required<SpeculationOptions>;
136
+ private state: SpeculationState;
137
+ private observers: Map<Element, IntersectionObserver> = new Map();
138
+ private hoverTimers: Map<Element, ReturnType<typeof setTimeout>> = new Map();
139
+ private speculationScript: HTMLScriptElement | null = null;
140
+ private prefetchLinks: Map<string, HTMLLinkElement> = new Map();
141
+
142
+ constructor(options: SpeculationOptions = {}) {
143
+ this.options = {
144
+ maxPrefetch: options.maxPrefetch ?? 5,
145
+ maxPrerender: options.maxPrerender ?? 1,
146
+ hoverDelay: options.hoverDelay ?? 100,
147
+ prefetchOnVisible: options.prefetchOnVisible ?? true,
148
+ visibilityThreshold: options.visibilityThreshold ?? 0.1,
149
+ cacheDuration: options.cacheDuration ?? 5 * 60 * 1000, // 5 minutes
150
+ onSpeculate: options.onSpeculate ?? (() => {}),
151
+ };
152
+
153
+ this.state = {
154
+ prefetched: new Set(),
155
+ prerendered: new Set(),
156
+ pending: new Set(),
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Initialize speculation from server hints
162
+ */
163
+ initFromHints(prefetch: string[] = [], prerender: string[] = []): void {
164
+ // Filter out already handled paths
165
+ const newPrefetch = prefetch
166
+ .filter(
167
+ (p) => !this.state.prefetched.has(p) && !this.state.prerendered.has(p),
168
+ )
169
+ .slice(0, this.options.maxPrefetch);
170
+
171
+ const newPrerender = prerender
172
+ .filter((p) => !this.state.prerendered.has(p))
173
+ .slice(0, this.options.maxPrerender);
174
+
175
+ // Try Speculation Rules API first
176
+ if (supportsSpeculationRules()) {
177
+ this.speculationScript = addSpeculationRules(newPrefetch, newPrerender);
178
+
179
+ newPrefetch.forEach((p) => {
180
+ this.state.prefetched.add(p);
181
+ this.options.onSpeculate(p, 'prefetch');
182
+ });
183
+
184
+ newPrerender.forEach((p) => {
185
+ this.state.prerendered.add(p);
186
+ this.options.onSpeculate(p, 'prerender');
187
+ });
188
+ } else {
189
+ // Fallback to link prefetch (no prerender fallback)
190
+ newPrefetch.forEach((path) => {
191
+ const link = linkPrefetch(path);
192
+ if (link) {
193
+ this.prefetchLinks.set(path, link);
194
+ this.state.prefetched.add(path);
195
+ this.options.onSpeculate(path, 'prefetch');
196
+ }
197
+ });
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Prefetch a specific path
203
+ */
204
+ prefetch(path: string): boolean {
205
+ if (this.state.prefetched.has(path) || this.state.prerendered.has(path)) {
206
+ return false;
207
+ }
208
+
209
+ if (this.state.prefetched.size >= this.options.maxPrefetch) {
210
+ return false;
211
+ }
212
+
213
+ if (supportsSpeculationRules()) {
214
+ // Update speculation rules
215
+ const allPrefetch = [...this.state.prefetched, path];
216
+ const allPrerender = [...this.state.prerendered];
217
+
218
+ if (this.speculationScript) {
219
+ removeSpeculationRules(this.speculationScript);
220
+ }
221
+
222
+ this.speculationScript = addSpeculationRules(allPrefetch, allPrerender);
223
+ } else {
224
+ const link = linkPrefetch(path);
225
+ if (link) {
226
+ this.prefetchLinks.set(path, link);
227
+ }
228
+ }
229
+
230
+ this.state.prefetched.add(path);
231
+ this.options.onSpeculate(path, 'prefetch');
232
+ return true;
233
+ }
234
+
235
+ /**
236
+ * Watch an element for hover to prefetch its href
237
+ */
238
+ watchHover(element: HTMLAnchorElement): () => void {
239
+ const path = new URL(element.href, window.location.href).pathname;
240
+
241
+ const handleMouseEnter = () => {
242
+ if (this.state.prefetched.has(path) || this.state.pending.has(path)) {
243
+ return;
244
+ }
245
+
246
+ this.state.pending.add(path);
247
+
248
+ const timer = setTimeout(() => {
249
+ this.prefetch(path);
250
+ this.state.pending.delete(path);
251
+ }, this.options.hoverDelay);
252
+
253
+ this.hoverTimers.set(element, timer);
254
+ };
255
+
256
+ const handleMouseLeave = () => {
257
+ const timer = this.hoverTimers.get(element);
258
+ if (timer) {
259
+ clearTimeout(timer);
260
+ this.hoverTimers.delete(element);
261
+ }
262
+ this.state.pending.delete(path);
263
+ };
264
+
265
+ element.addEventListener('mouseenter', handleMouseEnter);
266
+ element.addEventListener('mouseleave', handleMouseLeave);
267
+
268
+ return () => {
269
+ element.removeEventListener('mouseenter', handleMouseEnter);
270
+ element.removeEventListener('mouseleave', handleMouseLeave);
271
+ handleMouseLeave();
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Watch an element for visibility to prefetch its href
277
+ */
278
+ watchVisible(element: HTMLAnchorElement): () => void {
279
+ if (!this.options.prefetchOnVisible) {
280
+ return () => {};
281
+ }
282
+
283
+ const path = new URL(element.href, window.location.href).pathname;
284
+
285
+ const observer = new IntersectionObserver(
286
+ (entries) => {
287
+ entries.forEach((entry) => {
288
+ if (entry.isIntersecting) {
289
+ this.prefetch(path);
290
+ observer.disconnect();
291
+ this.observers.delete(element);
292
+ }
293
+ });
294
+ },
295
+ { threshold: this.options.visibilityThreshold },
296
+ );
297
+
298
+ observer.observe(element);
299
+ this.observers.set(element, observer);
300
+
301
+ return () => {
302
+ observer.disconnect();
303
+ this.observers.delete(element);
304
+ };
305
+ }
306
+
307
+ /**
308
+ * Auto-watch all internal links on the page
309
+ */
310
+ watchAllLinks(): () => void {
311
+ const links = document.querySelectorAll('a[href^="/"]');
312
+ const cleanups: Array<() => void> = [];
313
+
314
+ links.forEach((link) => {
315
+ if (link instanceof HTMLAnchorElement) {
316
+ cleanups.push(this.watchHover(link));
317
+ cleanups.push(this.watchVisible(link));
318
+ }
319
+ });
320
+
321
+ return () => {
322
+ cleanups.forEach((cleanup) => cleanup());
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Clear all speculation state
328
+ */
329
+ clear(): void {
330
+ // Clear speculation rules
331
+ if (this.speculationScript) {
332
+ removeSpeculationRules(this.speculationScript);
333
+ this.speculationScript = null;
334
+ }
335
+
336
+ // Clear prefetch links
337
+ this.prefetchLinks.forEach((link) => removePrefetch(link));
338
+ this.prefetchLinks.clear();
339
+
340
+ // Clear observers
341
+ this.observers.forEach((observer) => observer.disconnect());
342
+ this.observers.clear();
343
+
344
+ // Clear timers
345
+ this.hoverTimers.forEach((timer) => clearTimeout(timer));
346
+ this.hoverTimers.clear();
347
+
348
+ // Reset state
349
+ this.state.prefetched.clear();
350
+ this.state.prerendered.clear();
351
+ this.state.pending.clear();
352
+ }
353
+
354
+ /**
355
+ * Get current speculation state
356
+ */
357
+ getState(): Readonly<SpeculationState> {
358
+ return {
359
+ prefetched: new Set(this.state.prefetched),
360
+ prerendered: new Set(this.state.prerendered),
361
+ pending: new Set(this.state.pending),
362
+ };
363
+ }
364
+ }
365
+
366
+ // ============================================================================
367
+ // React Hook
368
+ // ============================================================================
369
+
370
+ /**
371
+ * React hook for speculation management
372
+ * Usage: const speculation = useSpeculation({ maxPrefetch: 5 });
373
+ */
374
+ export function createSpeculationHook(
375
+ useState: <T>(initial: T) => [T, (v: T) => void],
376
+ useEffect: (effect: () => void | (() => void), deps: unknown[]) => void,
377
+ useRef: <T>(initial: T) => { current: T },
378
+ ) {
379
+ return function useSpeculation(options: SpeculationOptions = {}) {
380
+ const managerRef = useRef<SpeculationManager | null>(null);
381
+ const [state, setState] = useState<SpeculationState>({
382
+ prefetched: new Set(),
383
+ prerendered: new Set(),
384
+ pending: new Set(),
385
+ });
386
+
387
+ useEffect(() => {
388
+ managerRef.current = new SpeculationManager({
389
+ ...options,
390
+ onSpeculate: (path, type) => {
391
+ options.onSpeculate?.(path, type);
392
+ setState(managerRef.current!.getState());
393
+ },
394
+ });
395
+
396
+ // Auto-watch all links
397
+ const cleanup = managerRef.current.watchAllLinks();
398
+
399
+ return () => {
400
+ cleanup();
401
+ managerRef.current?.clear();
402
+ };
403
+ }, []);
404
+
405
+ return {
406
+ state,
407
+ prefetch: (path: string) => managerRef.current?.prefetch(path),
408
+ initFromHints: (prefetch: string[], prerender: string[]) =>
409
+ managerRef.current?.initFromHints(prefetch, prerender),
410
+ clear: () => managerRef.current?.clear(),
411
+ };
412
+ };
413
+ }
414
+
415
+ // ============================================================================
416
+ // Auto-init for non-React usage
417
+ // ============================================================================
418
+
419
+ /**
420
+ * Auto-initialize speculation from window.__AEON_SPECULATION__ hints
421
+ */
422
+ export function autoInitSpeculation(): SpeculationManager | null {
423
+ if (typeof window === 'undefined') return null;
424
+
425
+ // @ts-expect-error - Global hint injection
426
+ const hints = window.__AEON_SPECULATION__ as
427
+ | { prefetch?: string[]; prerender?: string[] }
428
+ | undefined;
429
+
430
+ const manager = new SpeculationManager();
431
+
432
+ if (hints) {
433
+ manager.initFromHints(hints.prefetch || [], hints.prerender || []);
434
+ }
435
+
436
+ // Watch all links
437
+ manager.watchAllLinks();
438
+
439
+ return manager;
440
+ }
441
+
442
+ // ============================================================================
443
+ // Exports
444
+ // ============================================================================
445
+
446
+ export {
447
+ supportsSpeculationRules,
448
+ supportsLinkPrefetch,
449
+ addSpeculationRules,
450
+ linkPrefetch,
451
+ };