@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.
- package/README.md +438 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +88 -0
- package/examples/basic/components/OfflineIndicator.tsx +93 -0
- package/examples/basic/components/PresenceBar.tsx +68 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +73 -0
- package/package.json +90 -0
- package/packages/benchmarks/src/benchmark.test.ts +644 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +649 -0
- package/packages/cli/src/commands/build.ts +853 -0
- package/packages/cli/src/commands/dev.ts +463 -0
- package/packages/cli/src/commands/init.ts +395 -0
- package/packages/cli/src/commands/start.ts +289 -0
- package/packages/cli/src/index.ts +102 -0
- package/packages/directives/src/use-aeon.ts +266 -0
- package/packages/react/package.json +34 -0
- package/packages/react/src/Link.tsx +355 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +204 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +253 -0
- package/packages/react/src/hooks/useServiceWorker.ts +276 -0
- package/packages/react/src/hooks.ts +192 -0
- package/packages/react/src/index.ts +89 -0
- package/packages/react/src/provider.tsx +428 -0
- package/packages/runtime/package.json +70 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +453 -0
- package/packages/runtime/src/benchmark.ts +145 -0
- package/packages/runtime/src/cache.ts +287 -0
- package/packages/runtime/src/durable-object.ts +847 -0
- package/packages/runtime/src/index.ts +235 -0
- package/packages/runtime/src/navigation.test.ts +432 -0
- package/packages/runtime/src/navigation.ts +412 -0
- package/packages/runtime/src/nextjs-adapter.ts +254 -0
- package/packages/runtime/src/predictor.ts +368 -0
- package/packages/runtime/src/registry.ts +339 -0
- package/packages/runtime/src/router/context-extractor.ts +394 -0
- package/packages/runtime/src/router/esi-control-react.tsx +1172 -0
- package/packages/runtime/src/router/esi-control.ts +488 -0
- package/packages/runtime/src/router/esi-react.tsx +600 -0
- package/packages/runtime/src/router/esi.ts +595 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +272 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +544 -0
- package/packages/runtime/src/router/index.ts +158 -0
- package/packages/runtime/src/router/speculation.ts +442 -0
- package/packages/runtime/src/router/types.ts +514 -0
- package/packages/runtime/src/router.test.ts +466 -0
- package/packages/runtime/src/router.ts +285 -0
- package/packages/runtime/src/server.ts +446 -0
- package/packages/runtime/src/service-worker.ts +418 -0
- package/packages/runtime/src/speculation.test.ts +360 -0
- package/packages/runtime/src/speculation.ts +456 -0
- package/packages/runtime/src/storage.test.ts +1201 -0
- package/packages/runtime/src/storage.ts +1031 -0
- package/packages/runtime/src/tree-compiler.ts +252 -0
- package/packages/runtime/src/types.ts +444 -0
- package/packages/runtime/src/worker.ts +300 -0
- package/packages/runtime/tsconfig.json +19 -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 +328 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1267 -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 +73 -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 +189 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree → TSX Compiler
|
|
3
|
+
*
|
|
4
|
+
* Converts component trees from the visual editor back to TSX source code.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface TreeNode {
|
|
8
|
+
id: string;
|
|
9
|
+
type: string;
|
|
10
|
+
props?: Record<string, unknown>;
|
|
11
|
+
children?: string[] | TreeNode[];
|
|
12
|
+
text?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface CompilerOptions {
|
|
16
|
+
/** Route path for the page */
|
|
17
|
+
route: string;
|
|
18
|
+
/** Include 'use aeon' directive */
|
|
19
|
+
useAeon?: boolean;
|
|
20
|
+
/** Component imports to add */
|
|
21
|
+
imports?: Record<string, string>; // { 'Hero': '@/components/Hero' }
|
|
22
|
+
/** Whether to format output */
|
|
23
|
+
format?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compile a tree to TSX source code
|
|
28
|
+
*/
|
|
29
|
+
export function compileTreeToTSX(
|
|
30
|
+
tree: TreeNode | TreeNode[],
|
|
31
|
+
options: CompilerOptions
|
|
32
|
+
): string {
|
|
33
|
+
const { route, useAeon = true, imports = {}, format = true } = options;
|
|
34
|
+
|
|
35
|
+
// Collect all component types used
|
|
36
|
+
const usedComponents = new Set<string>();
|
|
37
|
+
collectComponents(tree, usedComponents);
|
|
38
|
+
|
|
39
|
+
// Build imports
|
|
40
|
+
const importLines: string[] = [];
|
|
41
|
+
|
|
42
|
+
// React import (if needed)
|
|
43
|
+
importLines.push("import type { FC } from 'react';");
|
|
44
|
+
|
|
45
|
+
// Component imports
|
|
46
|
+
for (const component of usedComponents) {
|
|
47
|
+
if (imports[component]) {
|
|
48
|
+
importLines.push(`import { ${component} } from '${imports[component]}';`);
|
|
49
|
+
} else if (!isHTMLElement(component)) {
|
|
50
|
+
// Default import path for unknown components
|
|
51
|
+
importLines.push(`import { ${component} } from '@/components/${component}';`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generate component name from route
|
|
56
|
+
const componentName = routeToComponentName(route);
|
|
57
|
+
|
|
58
|
+
// Generate JSX
|
|
59
|
+
const jsx = nodeToJSX(tree, 2);
|
|
60
|
+
|
|
61
|
+
// Assemble the file
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
|
|
64
|
+
if (useAeon) {
|
|
65
|
+
lines.push("'use aeon';");
|
|
66
|
+
lines.push('');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lines.push('/**');
|
|
70
|
+
lines.push(` * ${componentName}`);
|
|
71
|
+
lines.push(` * Route: ${route}`);
|
|
72
|
+
lines.push(' * ');
|
|
73
|
+
lines.push(' * @generated by aeon-flux visual editor');
|
|
74
|
+
lines.push(' */');
|
|
75
|
+
lines.push('');
|
|
76
|
+
|
|
77
|
+
lines.push(...importLines);
|
|
78
|
+
lines.push('');
|
|
79
|
+
|
|
80
|
+
lines.push(`const ${componentName}: FC = () => {`);
|
|
81
|
+
lines.push(' return (');
|
|
82
|
+
lines.push(jsx);
|
|
83
|
+
lines.push(' );');
|
|
84
|
+
lines.push('};');
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push(`export default ${componentName};`);
|
|
87
|
+
lines.push('');
|
|
88
|
+
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Collect all component types used in the tree
|
|
94
|
+
*/
|
|
95
|
+
function collectComponents(node: TreeNode | TreeNode[], set: Set<string>): void {
|
|
96
|
+
if (Array.isArray(node)) {
|
|
97
|
+
node.forEach(n => collectComponents(n, set));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (node.type && !isHTMLElement(node.type)) {
|
|
102
|
+
set.add(node.type);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (node.children) {
|
|
106
|
+
if (Array.isArray(node.children)) {
|
|
107
|
+
node.children.forEach(child => {
|
|
108
|
+
if (typeof child === 'object') {
|
|
109
|
+
collectComponents(child, set);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert a tree node to JSX string
|
|
118
|
+
*/
|
|
119
|
+
function nodeToJSX(node: TreeNode | TreeNode[], indent: number = 0): string {
|
|
120
|
+
const spaces = ' '.repeat(indent);
|
|
121
|
+
|
|
122
|
+
if (Array.isArray(node)) {
|
|
123
|
+
if (node.length === 0) return `${spaces}{null}`;
|
|
124
|
+
if (node.length === 1) return nodeToJSX(node[0], indent);
|
|
125
|
+
return `${spaces}<>\n${node.map(n => nodeToJSX(n, indent + 1)).join('\n')}\n${spaces}</>`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { type, props = {}, children, text } = node;
|
|
129
|
+
|
|
130
|
+
// Text node
|
|
131
|
+
if (type === 'text' || text) {
|
|
132
|
+
const content = text || props.content || props.text || '';
|
|
133
|
+
return `${spaces}${escapeJSX(String(content))}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Component/element name
|
|
137
|
+
const tagName = isHTMLElement(type) ? type.toLowerCase() : type;
|
|
138
|
+
|
|
139
|
+
// Props string
|
|
140
|
+
const propsStr = propsToString(props);
|
|
141
|
+
|
|
142
|
+
// No children - self-closing
|
|
143
|
+
if (!children || (Array.isArray(children) && children.length === 0)) {
|
|
144
|
+
return `${spaces}<${tagName}${propsStr} />`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// With children
|
|
148
|
+
const childrenJSX = Array.isArray(children)
|
|
149
|
+
? children.map(child => {
|
|
150
|
+
if (typeof child === 'string') {
|
|
151
|
+
return `${spaces} ${escapeJSX(child)}`;
|
|
152
|
+
}
|
|
153
|
+
return nodeToJSX(child, indent + 1);
|
|
154
|
+
}).join('\n')
|
|
155
|
+
: `${spaces} ${escapeJSX(String(children))}`;
|
|
156
|
+
|
|
157
|
+
return `${spaces}<${tagName}${propsStr}>\n${childrenJSX}\n${spaces}</${tagName}>`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convert props object to JSX props string
|
|
162
|
+
*/
|
|
163
|
+
function propsToString(props: Record<string, unknown>): string {
|
|
164
|
+
const entries = Object.entries(props).filter(([key]) =>
|
|
165
|
+
!['children', 'id', 'text', 'content'].includes(key)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (entries.length === 0) return '';
|
|
169
|
+
|
|
170
|
+
const propsArr = entries.map(([key, value]) => {
|
|
171
|
+
// Handle different value types
|
|
172
|
+
if (typeof value === 'string') {
|
|
173
|
+
return `${key}="${escapeAttr(value)}"`;
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === 'boolean') {
|
|
176
|
+
return value ? key : `${key}={false}`;
|
|
177
|
+
}
|
|
178
|
+
if (typeof value === 'number') {
|
|
179
|
+
return `${key}={${value}}`;
|
|
180
|
+
}
|
|
181
|
+
if (value === null || value === undefined) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
// Objects/arrays as JSX expressions
|
|
185
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
186
|
+
}).filter(Boolean);
|
|
187
|
+
|
|
188
|
+
return propsArr.length > 0 ? ' ' + propsArr.join(' ') : '';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Convert route to component name
|
|
193
|
+
*/
|
|
194
|
+
function routeToComponentName(route: string): string {
|
|
195
|
+
if (route === '/' || route === '') return 'IndexPage';
|
|
196
|
+
|
|
197
|
+
const parts = route
|
|
198
|
+
.replace(/^\/|\/$/g, '')
|
|
199
|
+
.split('/')
|
|
200
|
+
.map(part => {
|
|
201
|
+
// Handle dynamic segments
|
|
202
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
203
|
+
return 'Dynamic' + capitalize(part.slice(1, -1));
|
|
204
|
+
}
|
|
205
|
+
return capitalize(part);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return parts.join('') + 'Page';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Check if a type is an HTML element
|
|
213
|
+
*/
|
|
214
|
+
function isHTMLElement(type: string): boolean {
|
|
215
|
+
const htmlElements = [
|
|
216
|
+
'div', 'span', 'p', 'a', 'button', 'input', 'form',
|
|
217
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
218
|
+
'ul', 'ol', 'li', 'nav', 'header', 'footer', 'main', 'section', 'article', 'aside',
|
|
219
|
+
'img', 'video', 'audio', 'canvas', 'svg',
|
|
220
|
+
'table', 'thead', 'tbody', 'tr', 'td', 'th',
|
|
221
|
+
'label', 'select', 'option', 'textarea',
|
|
222
|
+
'strong', 'em', 'code', 'pre', 'blockquote',
|
|
223
|
+
];
|
|
224
|
+
return htmlElements.includes(type.toLowerCase());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Capitalize first letter
|
|
229
|
+
*/
|
|
230
|
+
function capitalize(str: string): string {
|
|
231
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Escape string for JSX content
|
|
236
|
+
*/
|
|
237
|
+
function escapeJSX(str: string): string {
|
|
238
|
+
return str
|
|
239
|
+
.replace(/\{/g, '{')
|
|
240
|
+
.replace(/\}/g, '}')
|
|
241
|
+
.replace(/</g, '<')
|
|
242
|
+
.replace(/>/g, '>');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Escape string for JSX attribute
|
|
247
|
+
*/
|
|
248
|
+
function escapeAttr(str: string): string {
|
|
249
|
+
return str
|
|
250
|
+
.replace(/"/g, '"')
|
|
251
|
+
.replace(/'/g, ''');
|
|
252
|
+
}
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aeon Pages Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface AeonConfig {
|
|
6
|
+
/** Directory containing pages (default: './pages') */
|
|
7
|
+
pagesDir: string;
|
|
8
|
+
|
|
9
|
+
/** Directory containing components (default: './components') */
|
|
10
|
+
componentsDir?: string;
|
|
11
|
+
|
|
12
|
+
/** Runtime target: 'bun' or 'cloudflare' */
|
|
13
|
+
runtime: 'bun' | 'cloudflare';
|
|
14
|
+
|
|
15
|
+
/** Server port (default: 3000) */
|
|
16
|
+
port?: number;
|
|
17
|
+
|
|
18
|
+
/** Aeon-specific configuration */
|
|
19
|
+
aeon?: AeonOptions;
|
|
20
|
+
|
|
21
|
+
/** Component configuration */
|
|
22
|
+
components?: ComponentOptions;
|
|
23
|
+
|
|
24
|
+
/** Next.js compatibility mode */
|
|
25
|
+
nextCompat?: boolean;
|
|
26
|
+
|
|
27
|
+
/** Build output configuration */
|
|
28
|
+
output?: OutputOptions;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AeonOptions {
|
|
32
|
+
/** Distributed sync configuration */
|
|
33
|
+
sync?: SyncOptions;
|
|
34
|
+
|
|
35
|
+
/** Schema versioning configuration */
|
|
36
|
+
versioning?: VersioningOptions;
|
|
37
|
+
|
|
38
|
+
/** Presence tracking configuration */
|
|
39
|
+
presence?: PresenceOptions;
|
|
40
|
+
|
|
41
|
+
/** Offline support configuration */
|
|
42
|
+
offline?: OfflineOptions;
|
|
43
|
+
|
|
44
|
+
/** Allow dynamic route creation for unclaimed paths */
|
|
45
|
+
dynamicRoutes?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SyncOptions {
|
|
49
|
+
/** Sync mode: 'distributed' for multi-node, 'local' for single-node */
|
|
50
|
+
mode: 'distributed' | 'local';
|
|
51
|
+
|
|
52
|
+
/** Number of replicas for distributed mode */
|
|
53
|
+
replicationFactor?: number;
|
|
54
|
+
|
|
55
|
+
/** Consistency level for reads/writes */
|
|
56
|
+
consistencyLevel?: 'strong' | 'eventual' | 'read-after-write';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface VersioningOptions {
|
|
60
|
+
/** Enable schema versioning */
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
|
|
63
|
+
/** Automatically run migrations */
|
|
64
|
+
autoMigrate?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface PresenceOptions {
|
|
68
|
+
/** Enable presence tracking */
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
|
|
71
|
+
/** Track cursor positions */
|
|
72
|
+
cursorTracking?: boolean;
|
|
73
|
+
|
|
74
|
+
/** Inactivity timeout in milliseconds */
|
|
75
|
+
inactivityTimeout?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface OfflineOptions {
|
|
79
|
+
/** Enable offline support */
|
|
80
|
+
enabled: boolean;
|
|
81
|
+
|
|
82
|
+
/** Maximum operations to queue offline */
|
|
83
|
+
maxQueueSize?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ComponentOptions {
|
|
87
|
+
/** Auto-discover components from componentsDir */
|
|
88
|
+
autoDiscover?: boolean;
|
|
89
|
+
|
|
90
|
+
/** Explicit list of components to include */
|
|
91
|
+
include?: string[];
|
|
92
|
+
|
|
93
|
+
/** Components to exclude from auto-discovery */
|
|
94
|
+
exclude?: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface OutputOptions {
|
|
98
|
+
/** Output directory for built assets */
|
|
99
|
+
dir?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Route definition */
|
|
103
|
+
export interface RouteDefinition {
|
|
104
|
+
/** Pattern like "/blog/[slug]" */
|
|
105
|
+
pattern: string;
|
|
106
|
+
|
|
107
|
+
/** Session ID template */
|
|
108
|
+
sessionId: string;
|
|
109
|
+
|
|
110
|
+
/** Component ID */
|
|
111
|
+
componentId: string;
|
|
112
|
+
|
|
113
|
+
/** Layout wrapper */
|
|
114
|
+
layout?: string;
|
|
115
|
+
|
|
116
|
+
/** Whether this route uses 'use aeon' */
|
|
117
|
+
isAeon: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Route match result */
|
|
121
|
+
export interface RouteMatch {
|
|
122
|
+
/** The matched route */
|
|
123
|
+
route: RouteDefinition;
|
|
124
|
+
|
|
125
|
+
/** Extracted parameters */
|
|
126
|
+
params: Record<string, string>;
|
|
127
|
+
|
|
128
|
+
/** Resolved session ID */
|
|
129
|
+
sessionId: string;
|
|
130
|
+
|
|
131
|
+
/** Component ID shorthand */
|
|
132
|
+
componentId: string;
|
|
133
|
+
|
|
134
|
+
/** Is this an Aeon page? */
|
|
135
|
+
isAeon: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Route metadata for registry */
|
|
139
|
+
export interface RouteMetadata {
|
|
140
|
+
createdAt: string;
|
|
141
|
+
createdBy: string;
|
|
142
|
+
updatedAt?: string;
|
|
143
|
+
updatedBy?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Route operation for sync */
|
|
147
|
+
export interface RouteOperation {
|
|
148
|
+
type: 'route-add' | 'route-update' | 'route-remove';
|
|
149
|
+
path: string;
|
|
150
|
+
component?: string;
|
|
151
|
+
metadata?: RouteMetadata;
|
|
152
|
+
timestamp: string;
|
|
153
|
+
nodeId: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Serialized component tree */
|
|
157
|
+
export interface SerializedComponent {
|
|
158
|
+
type: string;
|
|
159
|
+
props?: Record<string, unknown>;
|
|
160
|
+
children?: (SerializedComponent | string)[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Page session stored in Aeon */
|
|
164
|
+
export interface PageSession {
|
|
165
|
+
/** Route this session serves */
|
|
166
|
+
route: string;
|
|
167
|
+
|
|
168
|
+
/** Current component state */
|
|
169
|
+
tree: SerializedComponent;
|
|
170
|
+
|
|
171
|
+
/** Page data */
|
|
172
|
+
data: Record<string, unknown>;
|
|
173
|
+
|
|
174
|
+
/** Schema version */
|
|
175
|
+
schema: { version: string };
|
|
176
|
+
|
|
177
|
+
/** Active presence info */
|
|
178
|
+
presence: PresenceInfo[];
|
|
179
|
+
|
|
180
|
+
/** Session version number (increments on each edit) */
|
|
181
|
+
version?: number;
|
|
182
|
+
|
|
183
|
+
/** Last modified timestamp */
|
|
184
|
+
updatedAt?: string;
|
|
185
|
+
|
|
186
|
+
/** Last modified by user/agent ID */
|
|
187
|
+
updatedBy?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Webhook configuration for sync callbacks */
|
|
191
|
+
export interface WebhookConfig {
|
|
192
|
+
/** URL to call when session changes */
|
|
193
|
+
url: string;
|
|
194
|
+
|
|
195
|
+
/** Secret for HMAC signature verification */
|
|
196
|
+
secret?: string;
|
|
197
|
+
|
|
198
|
+
/** Events to trigger webhook: 'edit', 'publish', 'all' */
|
|
199
|
+
events: ('edit' | 'publish' | 'merge' | 'all')[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Webhook payload sent to callback URLs */
|
|
203
|
+
export interface WebhookPayload {
|
|
204
|
+
/** Event type */
|
|
205
|
+
event: 'session.updated' | 'session.published' | 'session.merged' | 'github.push';
|
|
206
|
+
|
|
207
|
+
/** Session ID */
|
|
208
|
+
sessionId: string;
|
|
209
|
+
|
|
210
|
+
/** Route */
|
|
211
|
+
route: string;
|
|
212
|
+
|
|
213
|
+
/** Session version */
|
|
214
|
+
version: number;
|
|
215
|
+
|
|
216
|
+
/** Timestamp */
|
|
217
|
+
timestamp: string;
|
|
218
|
+
|
|
219
|
+
/** GitHub PR number (for publish/merge events) */
|
|
220
|
+
prNumber?: number;
|
|
221
|
+
|
|
222
|
+
/** User who triggered the event */
|
|
223
|
+
triggeredBy?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Presence info for a user/agent */
|
|
227
|
+
export interface PresenceInfo {
|
|
228
|
+
/** User or agent ID */
|
|
229
|
+
userId: string;
|
|
230
|
+
|
|
231
|
+
/** User or agent role */
|
|
232
|
+
role: 'user' | 'assistant' | 'monitor' | 'admin';
|
|
233
|
+
|
|
234
|
+
/** Cursor position */
|
|
235
|
+
cursor?: { x: number; y: number };
|
|
236
|
+
|
|
237
|
+
/** Currently editing element path */
|
|
238
|
+
editing?: string;
|
|
239
|
+
|
|
240
|
+
/** Online/away/offline status */
|
|
241
|
+
status: 'online' | 'away' | 'offline';
|
|
242
|
+
|
|
243
|
+
/** Last activity timestamp */
|
|
244
|
+
lastActivity: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** UCAN capability for Aeon pages */
|
|
248
|
+
export interface AeonCapability {
|
|
249
|
+
can: 'aeon:read' | 'aeon:write' | 'aeon:admin' | 'aeon:*';
|
|
250
|
+
with: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Alias for PresenceInfo - used by react package */
|
|
254
|
+
export type PresenceUser = PresenceInfo;
|
|
255
|
+
|
|
256
|
+
// =============================================================================
|
|
257
|
+
// API ROUTES - Server-side request handling
|
|
258
|
+
// =============================================================================
|
|
259
|
+
|
|
260
|
+
/** HTTP methods supported for API routes */
|
|
261
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
262
|
+
|
|
263
|
+
/** Cloudflare Workers environment bindings */
|
|
264
|
+
export interface AeonEnv {
|
|
265
|
+
/** Durable Object namespace for page sessions */
|
|
266
|
+
PAGE_SESSIONS?: DurableObjectNamespace;
|
|
267
|
+
/** Durable Object namespace for routes registry */
|
|
268
|
+
ROUTES_REGISTRY?: DurableObjectNamespace;
|
|
269
|
+
/** D1 Database binding */
|
|
270
|
+
DB?: D1Database;
|
|
271
|
+
/** Workers KV namespace for caching */
|
|
272
|
+
CACHE?: KVNamespace;
|
|
273
|
+
/** Workers AI binding */
|
|
274
|
+
AI?: Ai;
|
|
275
|
+
/** Environment name (development, staging, production) */
|
|
276
|
+
ENVIRONMENT?: string;
|
|
277
|
+
/** Allow additional custom bindings */
|
|
278
|
+
[key: string]: unknown;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** D1 Database interface */
|
|
282
|
+
export interface D1Database {
|
|
283
|
+
prepare(query: string): D1PreparedStatement;
|
|
284
|
+
exec(query: string): Promise<D1ExecResult>;
|
|
285
|
+
batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
|
|
286
|
+
dump(): Promise<ArrayBuffer>;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface D1PreparedStatement {
|
|
290
|
+
bind(...values: unknown[]): D1PreparedStatement;
|
|
291
|
+
run(): Promise<D1Result>;
|
|
292
|
+
first<T = unknown>(colName?: string): Promise<T | null>;
|
|
293
|
+
all<T = unknown>(): Promise<D1Result<T>>;
|
|
294
|
+
raw<T = unknown>(): Promise<T[]>;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface D1Result<T = unknown> {
|
|
298
|
+
results?: T[];
|
|
299
|
+
success: boolean;
|
|
300
|
+
error?: string;
|
|
301
|
+
meta?: {
|
|
302
|
+
duration: number;
|
|
303
|
+
changes: number;
|
|
304
|
+
last_row_id: number;
|
|
305
|
+
served_by: string;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export interface D1ExecResult {
|
|
310
|
+
count: number;
|
|
311
|
+
duration: number;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** KV Namespace interface */
|
|
315
|
+
export interface KVNamespace {
|
|
316
|
+
get(key: string, options?: { type?: 'text' | 'json' | 'arrayBuffer' | 'stream' }): Promise<string | null>;
|
|
317
|
+
getWithMetadata<T = unknown>(key: string): Promise<{ value: string | null; metadata: T | null }>;
|
|
318
|
+
put(key: string, value: string | ArrayBuffer | ReadableStream, options?: { expirationTtl?: number; metadata?: unknown }): Promise<void>;
|
|
319
|
+
delete(key: string): Promise<void>;
|
|
320
|
+
list(options?: { prefix?: string; limit?: number; cursor?: string }): Promise<{ keys: { name: string }[]; list_complete: boolean; cursor?: string }>;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Durable Object Namespace interface */
|
|
324
|
+
export interface DurableObjectNamespace {
|
|
325
|
+
idFromName(name: string): DurableObjectId;
|
|
326
|
+
idFromString(id: string): DurableObjectId;
|
|
327
|
+
newUniqueId(): DurableObjectId;
|
|
328
|
+
get(id: DurableObjectId): DurableObjectStub;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface DurableObjectId {
|
|
332
|
+
toString(): string;
|
|
333
|
+
equals(other: DurableObjectId): boolean;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface DurableObjectStub {
|
|
337
|
+
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
|
338
|
+
id: DurableObjectId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Workers AI interface */
|
|
342
|
+
export interface Ai {
|
|
343
|
+
run<T = unknown>(model: string, inputs: unknown, options?: { gateway?: { id: string } }): Promise<T>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Context passed to API route handlers */
|
|
347
|
+
export interface AeonContext<E extends AeonEnv = AeonEnv> {
|
|
348
|
+
/** The incoming request */
|
|
349
|
+
request: Request;
|
|
350
|
+
/** Environment bindings (D1, KV, AI, Durable Objects, etc.) */
|
|
351
|
+
env: E;
|
|
352
|
+
/** Extracted URL parameters from dynamic routes */
|
|
353
|
+
params: Record<string, string>;
|
|
354
|
+
/** The request URL parsed */
|
|
355
|
+
url: URL;
|
|
356
|
+
/** Execution context for waitUntil, etc. */
|
|
357
|
+
ctx: ExecutionContext;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Execution context for Cloudflare Workers */
|
|
361
|
+
export interface ExecutionContext {
|
|
362
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
363
|
+
passThroughOnException(): void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** API route handler function */
|
|
367
|
+
export type ApiRouteHandler<E extends AeonEnv = AeonEnv> = (
|
|
368
|
+
context: AeonContext<E>
|
|
369
|
+
) => Response | Promise<Response>;
|
|
370
|
+
|
|
371
|
+
/** API route module - exports handlers for each HTTP method */
|
|
372
|
+
export interface ApiRouteModule<E extends AeonEnv = AeonEnv> {
|
|
373
|
+
GET?: ApiRouteHandler<E>;
|
|
374
|
+
POST?: ApiRouteHandler<E>;
|
|
375
|
+
PUT?: ApiRouteHandler<E>;
|
|
376
|
+
PATCH?: ApiRouteHandler<E>;
|
|
377
|
+
DELETE?: ApiRouteHandler<E>;
|
|
378
|
+
HEAD?: ApiRouteHandler<E>;
|
|
379
|
+
OPTIONS?: ApiRouteHandler<E>;
|
|
380
|
+
/** Catch-all handler for any method */
|
|
381
|
+
default?: ApiRouteHandler<E>;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Registered API route */
|
|
385
|
+
export interface ApiRoute {
|
|
386
|
+
/** Route pattern (e.g., "/api/chat", "/api/users/[id]") */
|
|
387
|
+
pattern: string;
|
|
388
|
+
/** Parsed path segments for matching */
|
|
389
|
+
segments: ApiRouteSegment[];
|
|
390
|
+
/** The route module with handlers */
|
|
391
|
+
module: ApiRouteModule;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Segment of an API route pattern */
|
|
395
|
+
export interface ApiRouteSegment {
|
|
396
|
+
/** The segment text */
|
|
397
|
+
value: string;
|
|
398
|
+
/** Is this a dynamic parameter? */
|
|
399
|
+
isDynamic: boolean;
|
|
400
|
+
/** Is this a catch-all segment? */
|
|
401
|
+
isCatchAll: boolean;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** API route match result */
|
|
405
|
+
export interface ApiRouteMatch {
|
|
406
|
+
/** The matched route */
|
|
407
|
+
route: ApiRoute;
|
|
408
|
+
/** Extracted parameters */
|
|
409
|
+
params: Record<string, string>;
|
|
410
|
+
/** The handler for the request method */
|
|
411
|
+
handler: ApiRouteHandler;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Server route module - for page-level server logic */
|
|
415
|
+
export interface ServerRouteModule<E extends AeonEnv = AeonEnv> {
|
|
416
|
+
/** Called before page render - can return data or redirect */
|
|
417
|
+
loader?: (context: AeonContext<E>) => Promise<ServerLoaderResult>;
|
|
418
|
+
/** Called on form submission or POST to the page */
|
|
419
|
+
action?: (context: AeonContext<E>) => Promise<ServerActionResult>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Result from a server loader */
|
|
423
|
+
export interface ServerLoaderResult {
|
|
424
|
+
/** Data to pass to the page */
|
|
425
|
+
data?: Record<string, unknown>;
|
|
426
|
+
/** Redirect to another URL */
|
|
427
|
+
redirect?: string;
|
|
428
|
+
/** Response status code */
|
|
429
|
+
status?: number;
|
|
430
|
+
/** Additional headers */
|
|
431
|
+
headers?: Record<string, string>;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Result from a server action */
|
|
435
|
+
export interface ServerActionResult {
|
|
436
|
+
/** Data to return */
|
|
437
|
+
data?: Record<string, unknown>;
|
|
438
|
+
/** Errors to display */
|
|
439
|
+
errors?: Record<string, string>;
|
|
440
|
+
/** Redirect after action */
|
|
441
|
+
redirect?: string;
|
|
442
|
+
/** Response status code */
|
|
443
|
+
status?: number;
|
|
444
|
+
}
|