@affectively/aeon-flux 0.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 (72) hide show
  1. package/README.md +438 -0
  2. package/examples/basic/aeon.config.ts +39 -0
  3. package/examples/basic/components/Cursor.tsx +88 -0
  4. package/examples/basic/components/OfflineIndicator.tsx +93 -0
  5. package/examples/basic/components/PresenceBar.tsx +68 -0
  6. package/examples/basic/package.json +20 -0
  7. package/examples/basic/pages/index.tsx +73 -0
  8. package/package.json +90 -0
  9. package/packages/benchmarks/src/benchmark.test.ts +644 -0
  10. package/packages/cli/package.json +43 -0
  11. package/packages/cli/src/commands/build.test.ts +649 -0
  12. package/packages/cli/src/commands/build.ts +853 -0
  13. package/packages/cli/src/commands/dev.ts +463 -0
  14. package/packages/cli/src/commands/init.ts +395 -0
  15. package/packages/cli/src/commands/start.ts +289 -0
  16. package/packages/cli/src/index.ts +102 -0
  17. package/packages/directives/src/use-aeon.ts +266 -0
  18. package/packages/react/package.json +34 -0
  19. package/packages/react/src/Link.tsx +355 -0
  20. package/packages/react/src/hooks/useAeonNavigation.ts +204 -0
  21. package/packages/react/src/hooks/usePilotNavigation.ts +253 -0
  22. package/packages/react/src/hooks/useServiceWorker.ts +276 -0
  23. package/packages/react/src/hooks.ts +192 -0
  24. package/packages/react/src/index.ts +89 -0
  25. package/packages/react/src/provider.tsx +428 -0
  26. package/packages/runtime/package.json +70 -0
  27. package/packages/runtime/schema.sql +40 -0
  28. package/packages/runtime/src/api-routes.ts +453 -0
  29. package/packages/runtime/src/benchmark.ts +145 -0
  30. package/packages/runtime/src/cache.ts +287 -0
  31. package/packages/runtime/src/durable-object.ts +847 -0
  32. package/packages/runtime/src/index.ts +235 -0
  33. package/packages/runtime/src/navigation.test.ts +432 -0
  34. package/packages/runtime/src/navigation.ts +412 -0
  35. package/packages/runtime/src/nextjs-adapter.ts +254 -0
  36. package/packages/runtime/src/predictor.ts +368 -0
  37. package/packages/runtime/src/registry.ts +339 -0
  38. package/packages/runtime/src/router/context-extractor.ts +394 -0
  39. package/packages/runtime/src/router/esi-control-react.tsx +1172 -0
  40. package/packages/runtime/src/router/esi-control.ts +488 -0
  41. package/packages/runtime/src/router/esi-react.tsx +600 -0
  42. package/packages/runtime/src/router/esi.ts +595 -0
  43. package/packages/runtime/src/router/heuristic-adapter.test.ts +272 -0
  44. package/packages/runtime/src/router/heuristic-adapter.ts +544 -0
  45. package/packages/runtime/src/router/index.ts +158 -0
  46. package/packages/runtime/src/router/speculation.ts +442 -0
  47. package/packages/runtime/src/router/types.ts +514 -0
  48. package/packages/runtime/src/router.test.ts +466 -0
  49. package/packages/runtime/src/router.ts +285 -0
  50. package/packages/runtime/src/server.ts +446 -0
  51. package/packages/runtime/src/service-worker.ts +418 -0
  52. package/packages/runtime/src/speculation.test.ts +360 -0
  53. package/packages/runtime/src/speculation.ts +456 -0
  54. package/packages/runtime/src/storage.test.ts +1201 -0
  55. package/packages/runtime/src/storage.ts +1031 -0
  56. package/packages/runtime/src/tree-compiler.ts +252 -0
  57. package/packages/runtime/src/types.ts +444 -0
  58. package/packages/runtime/src/worker.ts +300 -0
  59. package/packages/runtime/tsconfig.json +19 -0
  60. package/packages/runtime/wrangler.toml +41 -0
  61. package/packages/runtime-wasm/Cargo.lock +436 -0
  62. package/packages/runtime-wasm/Cargo.toml +29 -0
  63. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +328 -0
  64. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1267 -0
  65. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  66. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +73 -0
  67. package/packages/runtime-wasm/pkg/package.json +21 -0
  68. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  69. package/packages/runtime-wasm/src/lib.rs +189 -0
  70. package/packages/runtime-wasm/src/render.rs +629 -0
  71. package/packages/runtime-wasm/src/router.rs +298 -0
  72. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Aeon Pages Cloudflare Worker
3
+ *
4
+ * Entry point for Cloudflare Workers deployment.
5
+ * Handles routing to Durable Objects, API routes, and static assets.
6
+ */
7
+
8
+ // Export Durable Object classes
9
+ export { AeonPageSession, AeonRoutesRegistry } from './durable-object';
10
+
11
+ // Import API router
12
+ import { ApiRouter, createApiRouter } from './api-routes';
13
+ import type {
14
+ AeonEnv,
15
+ ApiRouteModule,
16
+ ExecutionContext,
17
+ DurableObjectNamespace,
18
+ } from './types';
19
+
20
+ // =============================================================================
21
+ // WORKER FACTORY
22
+ // =============================================================================
23
+
24
+ /** Options for creating an Aeon worker */
25
+ export interface AeonWorkerOptions<E extends AeonEnv = AeonEnv> {
26
+ /** API routes to register */
27
+ apiRoutes?: Record<string, ApiRouteModule<E>>;
28
+
29
+ /** CORS configuration */
30
+ cors?: {
31
+ origin?: string | string[];
32
+ methods?: string[];
33
+ headers?: string[];
34
+ credentials?: boolean;
35
+ };
36
+
37
+ /** Custom fetch handler to run before Aeon routing */
38
+ onRequest?: (request: Request, env: E, ctx: ExecutionContext) => Promise<Response | null>;
39
+
40
+ /** Custom 404 handler */
41
+ notFound?: (request: Request, env: E) => Response | Promise<Response>;
42
+ }
43
+
44
+ /**
45
+ * Create an Aeon worker with API route support
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * import { createAeonWorker } from '@affectively/aeon-pages-runtime/worker';
50
+ *
51
+ * export default createAeonWorker({
52
+ * apiRoutes: {
53
+ * '/api/chat': {
54
+ * POST: async ({ request, env }) => {
55
+ * const body = await request.json();
56
+ * const result = await env.AI.run('@cf/meta/llama-3-8b-instruct', { ... });
57
+ * return Response.json({ result });
58
+ * },
59
+ * },
60
+ * '/api/users/[id]': {
61
+ * GET: async ({ params, env }) => {
62
+ * const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?')
63
+ * .bind(params.id)
64
+ * .first();
65
+ * return Response.json(user);
66
+ * },
67
+ * },
68
+ * },
69
+ * });
70
+ * ```
71
+ */
72
+ export function createAeonWorker<E extends AeonEnv = AeonEnv>(
73
+ options: AeonWorkerOptions<E> = {}
74
+ ): ExportedHandler<E> {
75
+ // Create API router and register routes
76
+ const apiRouter = createApiRouter<E>();
77
+ if (options.apiRoutes) {
78
+ apiRouter.registerAll(options.apiRoutes);
79
+ }
80
+
81
+ // Build CORS headers
82
+ const corsConfig = {
83
+ origin: '*',
84
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
85
+ headers: ['Content-Type', 'Authorization'],
86
+ credentials: false,
87
+ ...options.cors,
88
+ };
89
+
90
+ const getCorsHeaders = (requestOrigin?: string | null): Record<string, string> => {
91
+ let allowedOrigin = '*';
92
+ if (typeof corsConfig.origin === 'string') {
93
+ allowedOrigin = corsConfig.origin;
94
+ } else if (Array.isArray(corsConfig.origin) && requestOrigin) {
95
+ if (corsConfig.origin.includes(requestOrigin)) {
96
+ allowedOrigin = requestOrigin;
97
+ }
98
+ }
99
+
100
+ const headers: Record<string, string> = {
101
+ 'Access-Control-Allow-Origin': allowedOrigin,
102
+ 'Access-Control-Allow-Methods': corsConfig.methods.join(', '),
103
+ 'Access-Control-Allow-Headers': corsConfig.headers.join(', '),
104
+ };
105
+
106
+ if (corsConfig.credentials) {
107
+ headers['Access-Control-Allow-Credentials'] = 'true';
108
+ }
109
+
110
+ return headers;
111
+ };
112
+
113
+ return {
114
+ async fetch(request: Request, env: E, ctx: ExecutionContext): Promise<Response> {
115
+ const url = new URL(request.url);
116
+ const corsHeaders = getCorsHeaders(request.headers.get('Origin'));
117
+
118
+ // Handle preflight
119
+ if (request.method === 'OPTIONS') {
120
+ return new Response(null, {
121
+ status: 204,
122
+ headers: {
123
+ ...corsHeaders,
124
+ 'Access-Control-Max-Age': '86400',
125
+ },
126
+ });
127
+ }
128
+
129
+ try {
130
+ // Custom onRequest handler
131
+ if (options.onRequest) {
132
+ const customResponse = await options.onRequest(request, env, ctx);
133
+ if (customResponse) {
134
+ return addCorsHeaders(customResponse, corsHeaders);
135
+ }
136
+ }
137
+
138
+ // API routes - /api/*
139
+ if (url.pathname.startsWith('/api/')) {
140
+ const response = await apiRouter.handle(request, env, ctx);
141
+ if (response) {
142
+ return addCorsHeaders(response, corsHeaders);
143
+ }
144
+ // No matching API route - fall through to 404
145
+ }
146
+
147
+ // Session routes - /session/*
148
+ if (url.pathname.startsWith('/session/')) {
149
+ return handleSessionRequest(request, env as unknown as BaseEnv, corsHeaders);
150
+ }
151
+
152
+ // Routes registry - /routes
153
+ if (url.pathname.startsWith('/routes')) {
154
+ return handleRoutesRequest(request, env as unknown as BaseEnv, corsHeaders);
155
+ }
156
+
157
+ // Health check
158
+ if (url.pathname === '/health') {
159
+ return new Response(
160
+ JSON.stringify({ status: 'ok', env: env.ENVIRONMENT }),
161
+ { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
162
+ );
163
+ }
164
+
165
+ // Custom 404 handler
166
+ if (options.notFound) {
167
+ const notFoundResponse = await options.notFound(request, env);
168
+ return addCorsHeaders(notFoundResponse, corsHeaders);
169
+ }
170
+
171
+ return new Response('Not found', { status: 404, headers: corsHeaders });
172
+ } catch (error) {
173
+ console.error('Worker error:', error);
174
+ return new Response(
175
+ JSON.stringify({
176
+ error: 'Internal server error',
177
+ message: error instanceof Error ? error.message : 'Unknown error',
178
+ }),
179
+ { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
180
+ );
181
+ }
182
+ },
183
+ };
184
+ }
185
+
186
+ /** Add CORS headers to a response */
187
+ function addCorsHeaders(response: Response, corsHeaders: Record<string, string>): Response {
188
+ const newHeaders = new Headers(response.headers);
189
+ for (const [key, value] of Object.entries(corsHeaders)) {
190
+ if (!newHeaders.has(key)) {
191
+ newHeaders.set(key, value);
192
+ }
193
+ }
194
+ return new Response(response.body, {
195
+ status: response.status,
196
+ statusText: response.statusText,
197
+ headers: newHeaders,
198
+ });
199
+ }
200
+
201
+ /** Cloudflare Worker exported handler interface */
202
+ interface ExportedHandler<E = unknown> {
203
+ fetch(request: Request, env: E, ctx: ExecutionContext): Promise<Response>;
204
+ }
205
+
206
+ // =============================================================================
207
+ // LEGACY DEFAULT EXPORT (for backwards compatibility)
208
+ // =============================================================================
209
+
210
+ interface BaseEnv {
211
+ PAGE_SESSIONS: DurableObjectNamespace;
212
+ ROUTES_REGISTRY: DurableObjectNamespace;
213
+ ENVIRONMENT?: string;
214
+ }
215
+
216
+ export default createAeonWorker();
217
+
218
+ /**
219
+ * Handle session requests - route to PageSession Durable Object
220
+ */
221
+ async function handleSessionRequest(
222
+ request: Request,
223
+ env: BaseEnv,
224
+ corsHeaders: Record<string, string>
225
+ ): Promise<Response> {
226
+ const url = new URL(request.url);
227
+
228
+ // Extract session ID from path: /session/:sessionId/...
229
+ const pathParts = url.pathname.split('/').filter(Boolean);
230
+ const sessionId = pathParts[1];
231
+
232
+ if (!sessionId) {
233
+ return new Response('Session ID required', { status: 400, headers: corsHeaders });
234
+ }
235
+
236
+ // Get or create the Durable Object instance
237
+ const id = env.PAGE_SESSIONS.idFromName(sessionId);
238
+ const stub = env.PAGE_SESSIONS.get(id);
239
+
240
+ // Forward the request to the DO
241
+ const doUrl = new URL(request.url);
242
+ doUrl.pathname = '/' + pathParts.slice(2).join('/') || '/session';
243
+
244
+ const doRequest = new Request(doUrl.toString(), {
245
+ method: request.method,
246
+ headers: request.headers,
247
+ body: request.body,
248
+ });
249
+
250
+ const response = await stub.fetch(doRequest);
251
+
252
+ // Add CORS headers to response
253
+ const newHeaders = new Headers(response.headers);
254
+ Object.entries(corsHeaders).forEach(([key, value]) => {
255
+ newHeaders.set(key, value);
256
+ });
257
+
258
+ return new Response(response.body, {
259
+ status: response.status,
260
+ statusText: response.statusText,
261
+ headers: newHeaders,
262
+ });
263
+ }
264
+
265
+ /**
266
+ * Handle routes registry requests - route to singleton RoutesRegistry DO
267
+ */
268
+ async function handleRoutesRequest(
269
+ request: Request,
270
+ env: BaseEnv,
271
+ corsHeaders: Record<string, string>
272
+ ): Promise<Response> {
273
+ // Use a singleton DO for routes registry
274
+ const id = env.ROUTES_REGISTRY.idFromName('__routes__');
275
+ const stub = env.ROUTES_REGISTRY.get(id);
276
+
277
+ const url = new URL(request.url);
278
+ const doUrl = new URL(request.url);
279
+ doUrl.pathname = url.pathname.replace('/routes', '') || '/routes';
280
+
281
+ const doRequest = new Request(doUrl.toString(), {
282
+ method: request.method,
283
+ headers: request.headers,
284
+ body: request.body,
285
+ });
286
+
287
+ const response = await stub.fetch(doRequest);
288
+
289
+ // Add CORS headers
290
+ const newHeaders = new Headers(response.headers);
291
+ Object.entries(corsHeaders).forEach(([key, value]) => {
292
+ newHeaders.set(key, value);
293
+ });
294
+
295
+ return new Response(response.body, {
296
+ status: response.status,
297
+ statusText: response.statusText,
298
+ headers: newHeaders,
299
+ });
300
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "declarationDir": "./dist",
8
+ "emitDeclarationOnly": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "jsx": "react-jsx",
13
+ "outDir": "./dist",
14
+ "rootDir": "./src",
15
+ "types": ["bun"]
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
19
+ }
@@ -0,0 +1,41 @@
1
+ name = "aeon-flux"
2
+ main = "src/worker.ts"
3
+ compatibility_date = "2024-01-01"
4
+
5
+ # Durable Objects
6
+ [durable_objects]
7
+ bindings = [
8
+ { name = "PAGE_SESSIONS", class_name = "AeonPageSession" },
9
+ { name = "ROUTES_REGISTRY", class_name = "AeonRoutesRegistry" }
10
+ ]
11
+
12
+ [[migrations]]
13
+ tag = "v1"
14
+ new_classes = ["AeonPageSession", "AeonRoutesRegistry"]
15
+
16
+ # D1 Database - uncomment after creating:
17
+ # wrangler d1 create aeon-flux
18
+ # [[d1_databases]]
19
+ # binding = "DB"
20
+ # database_name = "aeon-flux"
21
+ # database_id = "YOUR_DATABASE_ID"
22
+
23
+ # KV for caching - uncomment after creating:
24
+ # wrangler kv:namespace create CACHE
25
+ # [[kv_namespaces]]
26
+ # binding = "CACHE"
27
+ # id = "YOUR_KV_ID"
28
+
29
+ # Environment variables
30
+ [vars]
31
+ ENVIRONMENT = "production"
32
+ GITHUB_REPO = "buley/emotions"
33
+ GITHUB_TREE_PATH = "apps/edge-web-app/pages"
34
+ GITHUB_BASE_BRANCH = "staging"
35
+ GITHUB_DEV_BRANCH = "development"
36
+ GITHUB_AUTO_MERGE = "false"
37
+
38
+ # Dev settings
39
+ [dev]
40
+ port = 8787
41
+ local_protocol = "http"