@affectively/aeon-pages-runtime 0.1.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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Aeon Service Worker - Total Preload Strategy
3
+ *
4
+ * The Aeon architecture is recursive:
5
+ * - This service worker caches the ENTIRE site as an Aeon
6
+ * - Each page session is an Aeon entity within the site Aeon
7
+ * - Federation would cache multiple sites as Aeons of Aeons
8
+ *
9
+ * With 8.4KB framework + ~2-5KB per page session, we can preload EVERYTHING.
10
+ * A site with 100 pages = ~315KB total (smaller than one hero image!)
11
+ */
12
+ export {};
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Aeon Flux Storage Adapters
3
+ *
4
+ * Pluggable backend storage for routes and page content.
5
+ *
6
+ * Supported backends:
7
+ * - Dash (recommended for AFFECTIVELY ecosystem)
8
+ * - File system (default, development)
9
+ * - Cloudflare D1 (production, distributed)
10
+ * - Cloudflare Durable Objects (strong consistency)
11
+ * - Hybrid (D1 + Durable Objects)
12
+ * - Custom adapters
13
+ */
14
+ import type { RouteDefinition, PageSession, SerializedComponent } from './types';
15
+ /**
16
+ * Storage adapter interface - implement this for custom backends
17
+ */
18
+ export interface StorageAdapter {
19
+ /** Adapter name for logging */
20
+ name: string;
21
+ /** Initialize the storage (create tables, etc.) */
22
+ init(): Promise<void>;
23
+ /** Get a route by path */
24
+ getRoute(path: string): Promise<RouteDefinition | null>;
25
+ /** Get all routes */
26
+ getAllRoutes(): Promise<RouteDefinition[]>;
27
+ /** Save a route */
28
+ saveRoute(route: RouteDefinition): Promise<void>;
29
+ /** Delete a route */
30
+ deleteRoute(path: string): Promise<void>;
31
+ /** Get a page session */
32
+ getSession(sessionId: string): Promise<PageSession | null>;
33
+ /** Save a page session */
34
+ saveSession(session: PageSession): Promise<void>;
35
+ /** Get component tree for a session */
36
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
37
+ /** Save component tree for a session */
38
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
39
+ }
40
+ /**
41
+ * File system storage adapter (default for development)
42
+ */
43
+ export declare class FileStorageAdapter implements StorageAdapter {
44
+ name: string;
45
+ private pagesDir;
46
+ private dataDir;
47
+ constructor(options: {
48
+ pagesDir: string;
49
+ dataDir?: string;
50
+ });
51
+ init(): Promise<void>;
52
+ getRoute(path: string): Promise<RouteDefinition | null>;
53
+ getAllRoutes(): Promise<RouteDefinition[]>;
54
+ saveRoute(route: RouteDefinition): Promise<void>;
55
+ deleteRoute(path: string): Promise<void>;
56
+ getSession(sessionId: string): Promise<PageSession | null>;
57
+ saveSession(session: PageSession): Promise<void>;
58
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
59
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
60
+ private pathToKey;
61
+ }
62
+ /**
63
+ * Cloudflare D1 storage adapter (production, distributed)
64
+ */
65
+ export declare class D1StorageAdapter implements StorageAdapter {
66
+ name: string;
67
+ private db;
68
+ constructor(db: D1Database);
69
+ init(): Promise<void>;
70
+ getRoute(path: string): Promise<RouteDefinition | null>;
71
+ getAllRoutes(): Promise<RouteDefinition[]>;
72
+ saveRoute(route: RouteDefinition): Promise<void>;
73
+ deleteRoute(path: string): Promise<void>;
74
+ getSession(sessionId: string): Promise<PageSession | null>;
75
+ saveSession(session: PageSession): Promise<void>;
76
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
77
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
78
+ private routeToSessionId;
79
+ }
80
+ /**
81
+ * Cloudflare D1 database interface (matches Cloudflare Workers API)
82
+ */
83
+ interface D1Database {
84
+ prepare(query: string): D1PreparedStatement;
85
+ exec(query: string): Promise<void>;
86
+ }
87
+ interface D1PreparedStatement {
88
+ bind(...values: unknown[]): D1PreparedStatement;
89
+ first(): Promise<Record<string, unknown> | null>;
90
+ all(): Promise<{
91
+ results: Record<string, unknown>[];
92
+ }>;
93
+ run(): Promise<void>;
94
+ }
95
+ interface DurableObjectId {
96
+ toString(): string;
97
+ equals(other: DurableObjectId): boolean;
98
+ }
99
+ interface DurableObjectNamespace {
100
+ get(id: DurableObjectId): DurableObjectStub;
101
+ idFromName(name: string): DurableObjectId;
102
+ idFromString(id: string): DurableObjectId;
103
+ newUniqueId(): DurableObjectId;
104
+ }
105
+ interface DurableObjectStub {
106
+ id: DurableObjectId;
107
+ fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
108
+ }
109
+ /**
110
+ * Cloudflare Durable Objects storage adapter (strong consistency)
111
+ *
112
+ * Each Aeon session maps to a Durable Object instance, providing:
113
+ * - Strong consistency for real-time collaborative editing
114
+ * - Automatic coalescing of WebSocket connections
115
+ * - Sub-millisecond latency within the same colo
116
+ *
117
+ * Use in combination with D1 for read replicas and historical data.
118
+ */
119
+ export declare class DurableObjectStorageAdapter implements StorageAdapter {
120
+ name: string;
121
+ private namespace;
122
+ private routeCache;
123
+ constructor(namespace: DurableObjectNamespace);
124
+ init(): Promise<void>;
125
+ getRoute(path: string): Promise<RouteDefinition | null>;
126
+ getAllRoutes(): Promise<RouteDefinition[]>;
127
+ saveRoute(route: RouteDefinition): Promise<void>;
128
+ deleteRoute(path: string): Promise<void>;
129
+ getSession(sessionId: string): Promise<PageSession | null>;
130
+ saveSession(session: PageSession): Promise<void>;
131
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
132
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
133
+ /**
134
+ * Get a direct stub for WebSocket connections
135
+ * This allows real-time collaboration via Durable Object WebSockets
136
+ */
137
+ getSessionStub(sessionId: string): DurableObjectStub;
138
+ private routeToSessionId;
139
+ }
140
+ /**
141
+ * Hybrid storage adapter (D1 + Durable Objects)
142
+ *
143
+ * Combines the best of both:
144
+ * - Durable Objects for real-time collaborative editing (strong consistency)
145
+ * - D1 for read replicas and historical snapshots (eventual consistency)
146
+ *
147
+ * Write path: Durable Object → async propagate to D1
148
+ * Read path: Durable Object (real-time) or D1 (historical)
149
+ */
150
+ export declare class HybridStorageAdapter implements StorageAdapter {
151
+ name: string;
152
+ private do;
153
+ private d1;
154
+ constructor(options: {
155
+ namespace: DurableObjectNamespace;
156
+ db: D1Database;
157
+ });
158
+ init(): Promise<void>;
159
+ getRoute(path: string): Promise<RouteDefinition | null>;
160
+ getAllRoutes(): Promise<RouteDefinition[]>;
161
+ saveRoute(route: RouteDefinition): Promise<void>;
162
+ deleteRoute(path: string): Promise<void>;
163
+ getSession(sessionId: string): Promise<PageSession | null>;
164
+ saveSession(session: PageSession): Promise<void>;
165
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
166
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
167
+ /**
168
+ * Get historical snapshots from D1
169
+ */
170
+ getHistoricalSession(sessionId: string): Promise<PageSession | null>;
171
+ /**
172
+ * Get direct Durable Object stub for WebSocket connections
173
+ */
174
+ getSessionStub(sessionId: string): DurableObjectStub;
175
+ }
176
+ /**
177
+ * Dash client interface
178
+ *
179
+ * Dash is AFFECTIVELY's real-time sync system built on Aeon.
180
+ * It provides:
181
+ * - Real-time subscriptions via WebSocket
182
+ * - Automatic conflict resolution (CRDT-based)
183
+ * - Offline-first with sync on reconnect
184
+ * - Cross-platform (web, mobile, edge)
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * import { createDashClient } from '@affectively/dash';
189
+ *
190
+ * const dash = createDashClient({
191
+ * endpoint: 'wss://dash.affectively.com',
192
+ * auth: { token: 'your-auth-token' },
193
+ * });
194
+ *
195
+ * const storage = new DashStorageAdapter(dash);
196
+ * ```
197
+ */
198
+ interface DashClient {
199
+ /** Connect to Dash server */
200
+ connect(): Promise<void>;
201
+ /** Disconnect from Dash server */
202
+ disconnect(): Promise<void>;
203
+ /** Check connection status */
204
+ isConnected(): boolean;
205
+ /** Get a document by collection and id */
206
+ get<T>(collection: string, id: string): Promise<T | null>;
207
+ /** Query documents in a collection */
208
+ query<T>(collection: string, filter?: DashFilter): Promise<T[]>;
209
+ /** Set/update a document */
210
+ set<T>(collection: string, id: string, data: T): Promise<void>;
211
+ /** Delete a document */
212
+ delete(collection: string, id: string): Promise<void>;
213
+ /** Subscribe to real-time updates */
214
+ subscribe<T>(collection: string, filter: DashFilter | undefined, callback: (changes: DashChange<T>[]) => void): DashSubscription;
215
+ /** Batch operations */
216
+ batch(operations: DashOperation[]): Promise<void>;
217
+ }
218
+ interface DashFilter {
219
+ where?: Array<{
220
+ field: string;
221
+ op: '==' | '!=' | '<' | '<=' | '>' | '>=' | 'in' | 'contains';
222
+ value: unknown;
223
+ }>;
224
+ orderBy?: {
225
+ field: string;
226
+ direction: 'asc' | 'desc';
227
+ };
228
+ limit?: number;
229
+ offset?: number;
230
+ }
231
+ interface DashChange<T> {
232
+ type: 'added' | 'modified' | 'removed';
233
+ id: string;
234
+ data: T | null;
235
+ previousData?: T;
236
+ }
237
+ interface DashSubscription {
238
+ unsubscribe(): void;
239
+ }
240
+ interface DashOperation {
241
+ type: 'set' | 'delete';
242
+ collection: string;
243
+ id: string;
244
+ data?: unknown;
245
+ }
246
+ /**
247
+ * Dash storage adapter
248
+ *
249
+ * Uses AFFECTIVELY's Dash real-time sync system as the backend.
250
+ * This is the recommended adapter for the AFFECTIVELY ecosystem
251
+ * as it integrates seamlessly with other Dash-powered features.
252
+ *
253
+ * Features:
254
+ * - Real-time sync across all connected clients
255
+ * - CRDT-based conflict resolution (no data loss)
256
+ * - Offline-first with automatic sync on reconnect
257
+ * - Presence tracking built-in
258
+ * - Works with existing Dash infrastructure
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * import { createDashClient } from '@affectively/dash';
263
+ * import { DashStorageAdapter } from '@affectively/aeon-flux';
264
+ *
265
+ * const dash = createDashClient({
266
+ * endpoint: process.env.DASH_ENDPOINT,
267
+ * auth: { token: await getAuthToken() },
268
+ * });
269
+ *
270
+ * const storage = new DashStorageAdapter(dash, {
271
+ * routesCollection: 'aeon-routes',
272
+ * sessionsCollection: 'aeon-sessions',
273
+ * presenceCollection: 'aeon-presence',
274
+ * });
275
+ *
276
+ * // Use with Aeon Flux server
277
+ * const server = await createAeonServer({
278
+ * storage,
279
+ * // ...
280
+ * });
281
+ * ```
282
+ */
283
+ export declare class DashStorageAdapter implements StorageAdapter {
284
+ name: string;
285
+ private client;
286
+ private collections;
287
+ private subscriptions;
288
+ constructor(client: DashClient, options?: {
289
+ routesCollection?: string;
290
+ sessionsCollection?: string;
291
+ presenceCollection?: string;
292
+ });
293
+ init(): Promise<void>;
294
+ getRoute(path: string): Promise<RouteDefinition | null>;
295
+ getAllRoutes(): Promise<RouteDefinition[]>;
296
+ saveRoute(route: RouteDefinition): Promise<void>;
297
+ deleteRoute(path: string): Promise<void>;
298
+ getSession(sessionId: string): Promise<PageSession | null>;
299
+ saveSession(session: PageSession): Promise<void>;
300
+ getTree(sessionId: string): Promise<SerializedComponent | null>;
301
+ saveTree(sessionId: string, tree: SerializedComponent): Promise<void>;
302
+ /**
303
+ * Subscribe to real-time route changes
304
+ */
305
+ subscribeToRoutes(callback: (changes: DashChange<RouteDefinition>[]) => void): DashSubscription;
306
+ /**
307
+ * Subscribe to real-time session changes
308
+ */
309
+ subscribeToSession(sessionId: string, callback: (changes: DashChange<PageSession>[]) => void): DashSubscription;
310
+ /**
311
+ * Subscribe to presence updates for a session
312
+ */
313
+ subscribeToPresence(sessionId: string, callback: (changes: DashChange<PresenceRecord>[]) => void): DashSubscription;
314
+ /**
315
+ * Update presence for current user
316
+ */
317
+ updatePresence(sessionId: string, userId: string, presence: Partial<PresenceRecord>): Promise<void>;
318
+ /**
319
+ * Clean up subscriptions
320
+ */
321
+ destroy(): void;
322
+ private pathToId;
323
+ private routeToSessionId;
324
+ }
325
+ interface PresenceRecord {
326
+ sessionId: string;
327
+ userId: string;
328
+ role: 'user' | 'assistant' | 'monitor' | 'admin';
329
+ cursor?: {
330
+ x: number;
331
+ y: number;
332
+ };
333
+ editing?: string;
334
+ status: 'online' | 'away' | 'offline';
335
+ lastActivity: string;
336
+ }
337
+ /**
338
+ * Create a storage adapter based on configuration
339
+ */
340
+ export declare function createStorageAdapter(config: {
341
+ type: 'file' | 'd1' | 'durable-object' | 'hybrid' | 'dash' | 'custom';
342
+ pagesDir?: string;
343
+ dataDir?: string;
344
+ d1?: D1Database;
345
+ durableObjectNamespace?: DurableObjectNamespace;
346
+ dash?: DashClient;
347
+ dashCollections?: {
348
+ routes?: string;
349
+ sessions?: string;
350
+ presence?: string;
351
+ };
352
+ custom?: StorageAdapter;
353
+ }): StorageAdapter;
354
+ export {};
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Aeon Pages Type Definitions
3
+ */
4
+ export interface AeonConfig {
5
+ /** Directory containing pages (default: './pages') */
6
+ pagesDir: string;
7
+ /** Directory containing components (default: './components') */
8
+ componentsDir?: string;
9
+ /** Runtime target: 'bun' or 'cloudflare' */
10
+ runtime: 'bun' | 'cloudflare';
11
+ /** Server port (default: 3000) */
12
+ port?: number;
13
+ /** Aeon-specific configuration */
14
+ aeon?: AeonOptions;
15
+ /** Component configuration */
16
+ components?: ComponentOptions;
17
+ /** Next.js compatibility mode */
18
+ nextCompat?: boolean;
19
+ /** Build output configuration */
20
+ output?: OutputOptions;
21
+ }
22
+ export interface AeonOptions {
23
+ /** Distributed sync configuration */
24
+ sync?: SyncOptions;
25
+ /** Schema versioning configuration */
26
+ versioning?: VersioningOptions;
27
+ /** Presence tracking configuration */
28
+ presence?: PresenceOptions;
29
+ /** Offline support configuration */
30
+ offline?: OfflineOptions;
31
+ /** Allow dynamic route creation for unclaimed paths */
32
+ dynamicRoutes?: boolean;
33
+ }
34
+ export interface SyncOptions {
35
+ /** Sync mode: 'distributed' for multi-node, 'local' for single-node */
36
+ mode: 'distributed' | 'local';
37
+ /** Number of replicas for distributed mode */
38
+ replicationFactor?: number;
39
+ /** Consistency level for reads/writes */
40
+ consistencyLevel?: 'strong' | 'eventual' | 'read-after-write';
41
+ }
42
+ export interface VersioningOptions {
43
+ /** Enable schema versioning */
44
+ enabled: boolean;
45
+ /** Automatically run migrations */
46
+ autoMigrate?: boolean;
47
+ }
48
+ export interface PresenceOptions {
49
+ /** Enable presence tracking */
50
+ enabled: boolean;
51
+ /** Track cursor positions */
52
+ cursorTracking?: boolean;
53
+ /** Inactivity timeout in milliseconds */
54
+ inactivityTimeout?: number;
55
+ }
56
+ export interface OfflineOptions {
57
+ /** Enable offline support */
58
+ enabled: boolean;
59
+ /** Maximum operations to queue offline */
60
+ maxQueueSize?: number;
61
+ }
62
+ export interface ComponentOptions {
63
+ /** Auto-discover components from componentsDir */
64
+ autoDiscover?: boolean;
65
+ /** Explicit list of components to include */
66
+ include?: string[];
67
+ /** Components to exclude from auto-discovery */
68
+ exclude?: string[];
69
+ }
70
+ export interface OutputOptions {
71
+ /** Output directory for built assets */
72
+ dir?: string;
73
+ }
74
+ /** Route definition */
75
+ export interface RouteDefinition {
76
+ /** Pattern like "/blog/[slug]" */
77
+ pattern: string;
78
+ /** Session ID template */
79
+ sessionId: string;
80
+ /** Component ID */
81
+ componentId: string;
82
+ /** Layout wrapper */
83
+ layout?: string;
84
+ /** Whether this route uses 'use aeon' */
85
+ isAeon: boolean;
86
+ }
87
+ /** Route match result */
88
+ export interface RouteMatch {
89
+ /** The matched route */
90
+ route: RouteDefinition;
91
+ /** Extracted parameters */
92
+ params: Record<string, string>;
93
+ /** Resolved session ID */
94
+ sessionId: string;
95
+ /** Component ID shorthand */
96
+ componentId: string;
97
+ /** Is this an Aeon page? */
98
+ isAeon: boolean;
99
+ }
100
+ /** Route metadata for registry */
101
+ export interface RouteMetadata {
102
+ createdAt: string;
103
+ createdBy: string;
104
+ updatedAt?: string;
105
+ updatedBy?: string;
106
+ }
107
+ /** Route operation for sync */
108
+ export interface RouteOperation {
109
+ type: 'route-add' | 'route-update' | 'route-remove';
110
+ path: string;
111
+ component?: string;
112
+ metadata?: RouteMetadata;
113
+ timestamp: string;
114
+ nodeId: string;
115
+ }
116
+ /** Serialized component tree */
117
+ export interface SerializedComponent {
118
+ type: string;
119
+ props?: Record<string, unknown>;
120
+ children?: (SerializedComponent | string)[];
121
+ }
122
+ /** Page session stored in Aeon */
123
+ export interface PageSession {
124
+ /** Route this session serves */
125
+ route: string;
126
+ /** Current component state */
127
+ tree: SerializedComponent;
128
+ /** Page data */
129
+ data: Record<string, unknown>;
130
+ /** Schema version */
131
+ schema: {
132
+ version: string;
133
+ };
134
+ /** Active presence info */
135
+ presence: PresenceInfo[];
136
+ }
137
+ /** Presence info for a user/agent */
138
+ export interface PresenceInfo {
139
+ /** User or agent ID */
140
+ userId: string;
141
+ /** User or agent role */
142
+ role: 'user' | 'assistant' | 'monitor' | 'admin';
143
+ /** Cursor position */
144
+ cursor?: {
145
+ x: number;
146
+ y: number;
147
+ };
148
+ /** Currently editing element path */
149
+ editing?: string;
150
+ /** Online/away/offline status */
151
+ status: 'online' | 'away' | 'offline';
152
+ /** Last activity timestamp */
153
+ lastActivity: string;
154
+ }
155
+ /** UCAN capability for Aeon pages */
156
+ export interface AeonCapability {
157
+ can: 'aeon:read' | 'aeon:write' | 'aeon:admin' | 'aeon:*';
158
+ with: string;
159
+ }
160
+ /** Alias for PresenceInfo - used by react package */
161
+ export type PresenceUser = PresenceInfo;
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@affectively/aeon-pages-runtime",
3
+ "version": "0.1.0",
4
+ "description": "Bun/Cloudflare runtime for @affectively/aeon-pages",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./server": {
14
+ "import": "./dist/server.js",
15
+ "types": "./dist/server.d.ts"
16
+ },
17
+ "./router": {
18
+ "import": "./dist/router/index.js",
19
+ "types": "./dist/router/index.d.ts"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "bun build ./src/index.ts ./src/server.ts ./src/router/index.ts --outdir ./dist --target bun && tsc --declaration --emitDeclarationOnly",
24
+ "dev": "bun --watch ./src/index.ts",
25
+ "test": "bun test",
26
+ "prepublishOnly": "bun run build"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ],
32
+ "peerDependencies": {
33
+ "bun": ">=1.0.0",
34
+ "react": ">=18.0.0",
35
+ "zod": ">=3.0.0",
36
+ "@affectively/aeon": ">=0.1.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@affectively/aeon": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "typescript": "^5.7.0",
45
+ "@types/bun": "latest",
46
+ "@types/react": "^19.0.0",
47
+ "react": "^19.0.0",
48
+ "zod": "^3.24.0"
49
+ },
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/affectively/aeon-pages"
54
+ },
55
+ "keywords": [
56
+ "aeon",
57
+ "pages",
58
+ "framework",
59
+ "collaborative",
60
+ "bun",
61
+ "cloudflare"
62
+ ]
63
+ }