@africode/core 5.0.1 → 5.0.2
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/COMPONENT_SCHEMA.json +103 -69
- package/components/base.d.ts +1 -1
- package/components/base.js +71 -21
- package/core/a2ui-schema-manager.js +9 -2
- package/core/a2ui.js +131 -43
- package/core/actions.js +27 -0
- package/core/bun-runtime.js +207 -724
- package/core/compliance.js +6 -5
- package/core/config.js +7 -5
- package/core/enhanced-hmr.js +16 -14
- package/core/file-router.js +42 -282
- package/core/hmr.js +8 -7
- package/core/html.d.ts +15 -101
- package/core/html.js +53 -129
- package/core/lipa-namba-journey.js +74 -12
- package/core/logging.js +14 -0
- package/core/middleware.js +82 -0
- package/core/nida-cig-middleware.js +13 -8
- package/core/plugins/index.js +345 -312
- package/core/request-identity.js +44 -0
- package/core/sdk.js +22 -0
- package/core/session-store.js +68 -0
- package/core/state.js +34 -0
- package/core/websocket.js +22 -20
- package/dist/africode.js +108 -112
- package/dist/africode.js.map +6 -6
- package/dist/build-info.json +3 -3
- package/dist/components.js +351 -351
- package/dist/components.js.map +6 -6
- package/package.json +3 -3
- package/scripts/generate-component-schema.js +1 -1
package/core/bun-runtime.js
CHANGED
|
@@ -1,799 +1,282 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bun-Native Runtime Engine
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Provides ultra-fast startup, native APIs, and hot reloading
|
|
6
|
-
* capabilities for high-performance fintech applications.
|
|
3
|
+
* Phase 1: Runtime Stability - Clean and predictable server
|
|
7
4
|
*
|
|
8
5
|
* @module core/bun-runtime
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { FileSystemRouter } from './router.js';
|
|
9
|
+
import { RawHtml } from './html.js';
|
|
10
|
+
import { getRequestIdentity } from './request-identity.js';
|
|
11
|
+
import { sessionStore } from './session-store.js';
|
|
12
|
+
import { actions } from './actions.js';
|
|
13
|
+
import { MiddlewareManager, loggerMiddleware } from './middleware.js';
|
|
13
14
|
import { EnhancedHMRMiddleware } from './enhanced-hmr.js';
|
|
15
|
+
import { Database as BunSQLiteDatabase } from 'bun:sqlite';
|
|
16
|
+
import { extname, join } from 'path';
|
|
14
17
|
|
|
15
|
-
/**
|
|
16
|
-
* Bun Runtime Configuration
|
|
17
|
-
*/
|
|
18
18
|
export const BUN_CONFIG = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
startupTimeout: 5000,
|
|
20
|
+
memoryLimit: '1gb',
|
|
21
|
+
gcStrategy: 'generational',
|
|
22
|
+
threadPoolSize: 4
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Bun HTTP Server with File-Based Routing
|
|
27
|
-
* Autonomous application lifecycle management
|
|
28
|
-
*/
|
|
29
25
|
export class BunHTTPServer {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
port: options.port || 3000,
|
|
33
|
-
pagesDir: options.pagesDir || 'pages',
|
|
34
|
-
publicDir: options.publicDir || 'public',
|
|
35
|
-
middleware: options.middleware || [],
|
|
36
|
-
enableHMR: options.enableHMR !== false,
|
|
37
|
-
...options
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
this.router = new FileBasedRouter({
|
|
41
|
-
pagesDir: this.options.pagesDir
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
this.server = null;
|
|
45
|
-
this.hmr = null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Start the HTTP server
|
|
50
|
-
*/
|
|
51
|
-
async start() {
|
|
52
|
-
console.log(`🚀 Starting Bun HTTP Server on port ${this.options.port}`);
|
|
53
|
-
|
|
54
|
-
// Initialize HMR if enabled
|
|
55
|
-
if (this.options.enableHMR) {
|
|
56
|
-
this.hmr = new EnhancedHMRMiddleware({
|
|
57
|
-
port: this.options.port + 1,
|
|
58
|
-
watchPaths: [this.options.pagesDir, 'components', 'core']
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Create Bun server with file-based routing
|
|
63
|
-
this.server = Bun.serve({
|
|
64
|
-
port: this.options.port,
|
|
65
|
-
async fetch(request) {
|
|
66
|
-
const url = new URL(request.url);
|
|
67
|
-
const pathname = url.pathname;
|
|
68
|
-
|
|
69
|
-
// Handle static files
|
|
70
|
-
if (pathname.startsWith('/public/') || pathname.startsWith('/static/')) {
|
|
71
|
-
return this._serveStaticFile(pathname);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Handle HMR WebSocket upgrade
|
|
75
|
-
if (pathname === '/hmr' && this.hmr) {
|
|
76
|
-
const upgrade = this.hmr.handleUpgrade(request);
|
|
77
|
-
if (upgrade) return upgrade;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Route to file-based handler
|
|
81
|
-
return this._handleRoute(request);
|
|
82
|
-
}.bind(this),
|
|
83
|
-
|
|
84
|
-
// WebSocket handling for HMR
|
|
85
|
-
websocket: this.hmr ? {
|
|
86
|
-
open: (ws) => this.hmr.handleClientConnect(ws),
|
|
87
|
-
message: (ws, message) => this.hmr.handleClientMessage(ws, message),
|
|
88
|
-
close: (ws) => this.hmr.handleClientDisconnect(ws)
|
|
89
|
-
} : undefined
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Start HMR watching
|
|
93
|
-
if (this.hmr) {
|
|
94
|
-
await this.hmr.initialize(this.server);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log(`📡 Server ready at http://localhost:${this.options.port}`);
|
|
98
|
-
console.log(`📊 Routes loaded:`, this.router.getRoutes());
|
|
99
|
-
}
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.router = new FileSystemRouter(join(process.cwd(), options.pagesDir || 'pages'));
|
|
100
28
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const url = new URL(request.url);
|
|
106
|
-
const pathname = url.pathname;
|
|
107
|
-
const method = request.method;
|
|
29
|
+
this.middleware = new MiddlewareManager();
|
|
30
|
+
this.port = options.port || 3000;
|
|
31
|
+
this.server = null;
|
|
32
|
+
}
|
|
108
33
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!routeMatch) {
|
|
114
|
-
return new Response('Not Found', { status: 404 });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Load route handler/component
|
|
118
|
-
const handler = await this.router.loadRoute(routeMatch);
|
|
119
|
-
|
|
120
|
-
if (routeMatch.type === 'api') {
|
|
121
|
-
// Handle API route
|
|
122
|
-
return this._handleApiRoute(handler, request, routeMatch.params);
|
|
123
|
-
} else {
|
|
124
|
-
// Handle page route
|
|
125
|
-
return this._handlePageRoute(handler, request, routeMatch);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.error('[Server] Route error:', error);
|
|
130
|
-
return new Response('Internal Server Error', { status: 500 });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
34
|
+
async start() {
|
|
35
|
+
// Register middlewares
|
|
36
|
+
this.middleware.use(loggerMiddleware());
|
|
133
37
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*/
|
|
137
|
-
async _handleApiRoute(handler, request, params) {
|
|
138
|
-
try {
|
|
139
|
-
// Execute API handler
|
|
140
|
-
const result = await handler(request, { params });
|
|
141
|
-
|
|
142
|
-
if (result instanceof Response) {
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Convert to JSON response
|
|
147
|
-
return new Response(JSON.stringify(result), {
|
|
148
|
-
headers: { 'Content-Type': 'application/json' }
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error('[Server] API error:', error);
|
|
153
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
154
|
-
status: 500,
|
|
155
|
-
headers: { 'Content-Type': 'application/json' }
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
38
|
+
this.server = Bun.serve({
|
|
39
|
+
port: this.port,
|
|
159
40
|
|
|
160
|
-
|
|
161
|
-
* Handle page route rendering
|
|
162
|
-
*/
|
|
163
|
-
async _handlePageRoute(component, request, routeMatch) {
|
|
41
|
+
fetch: async (req) => {
|
|
164
42
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return new Response(html, {
|
|
169
|
-
headers: { 'Content-Type': 'text/html' }
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('[Server] Page render error:', error);
|
|
174
|
-
return new Response('Render Error', { status: 500 });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Render page with layout
|
|
180
|
-
*/
|
|
181
|
-
async _renderPage(component, routeMatch) {
|
|
182
|
-
let content = '';
|
|
183
|
-
|
|
184
|
-
// Render component
|
|
185
|
-
if (typeof component === 'function') {
|
|
186
|
-
content = component(routeMatch.params || {});
|
|
187
|
-
} else if (component.render) {
|
|
188
|
-
content = component.render(routeMatch.params || {});
|
|
189
|
-
} else {
|
|
190
|
-
content = String(component);
|
|
191
|
-
}
|
|
43
|
+
return await this.handleRequest(req);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('Server Error:', err);
|
|
192
46
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const layoutModule = await import(routeMatch.layout);
|
|
197
|
-
const layout = layoutModule.default || layoutModule;
|
|
198
|
-
|
|
199
|
-
if (typeof layout === 'function') {
|
|
200
|
-
content = layout({ children: content, params: routeMatch.params });
|
|
201
|
-
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.warn('[Server] Layout load error:', error.message);
|
|
204
|
-
}
|
|
47
|
+
return new Response('Internal Server Error', {
|
|
48
|
+
status: 500,
|
|
49
|
+
});
|
|
205
50
|
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log('AfriCode running on http://localhost:' + this.port);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async handleRequest(req) {
|
|
58
|
+
const url = new URL(req.url);
|
|
59
|
+
const pathname = url.pathname;
|
|
60
|
+
const sessionId = getRequestIdentity(req);
|
|
61
|
+
const session = sessionStore.get(sessionId);
|
|
62
|
+
|
|
63
|
+
if (pathname.startsWith('/public')) {
|
|
64
|
+
const response = await this.serveStatic(pathname);
|
|
65
|
+
return this.attachSessionCookie(response, sessionId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let context = {
|
|
69
|
+
req,
|
|
70
|
+
url,
|
|
71
|
+
pathname,
|
|
72
|
+
params: {},
|
|
73
|
+
sessionId,
|
|
74
|
+
state: session,
|
|
75
|
+
actions,
|
|
76
|
+
sessionStore
|
|
77
|
+
};
|
|
206
78
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<head>
|
|
211
|
-
<meta charset="UTF-8">
|
|
212
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
213
|
-
<title>AfriCode App</title>
|
|
214
|
-
<link rel="stylesheet" href="/public/styles.css">
|
|
215
|
-
</head>
|
|
216
|
-
<body>
|
|
217
|
-
${content}
|
|
218
|
-
<script type="module" src="/public/app.js"></script>
|
|
219
|
-
</body>
|
|
220
|
-
</html>`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Serve static files
|
|
225
|
-
*/
|
|
226
|
-
_serveStaticFile(pathname) {
|
|
227
|
-
try {
|
|
228
|
-
const filePath = join(process.cwd(), pathname);
|
|
229
|
-
const file = Bun.file(filePath);
|
|
230
|
-
return new Response(file);
|
|
231
|
-
} catch {
|
|
232
|
-
return new Response('File Not Found', { status: 404 });
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Stop the server
|
|
238
|
-
*/
|
|
239
|
-
async stop() {
|
|
240
|
-
if (this.server) {
|
|
241
|
-
this.server.stop();
|
|
242
|
-
}
|
|
243
|
-
if (this.hmr) {
|
|
244
|
-
await this.hmr.shutdown();
|
|
245
|
-
}
|
|
246
|
-
console.log('🛑 Server stopped');
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Hot Reloading Engine
|
|
252
|
-
* Enables zero-downtime updates with state preservation
|
|
253
|
-
*/
|
|
254
|
-
export class HotReloadEngine {
|
|
255
|
-
constructor() {
|
|
256
|
-
this.modules = new Map();
|
|
257
|
-
this.state = createReactiveState({});
|
|
258
|
-
this.connections = new Set();
|
|
79
|
+
const mwResult = await this.middleware.run(context);
|
|
80
|
+
if (mwResult) {
|
|
81
|
+
return this.attachSessionCookie(mwResult, sessionId);
|
|
259
82
|
}
|
|
260
83
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
* @param {Function} moduleFactory - Module factory function
|
|
265
|
-
*/
|
|
266
|
-
initModule(moduleId, moduleFactory) {
|
|
267
|
-
const module = {
|
|
268
|
-
id: moduleId,
|
|
269
|
-
factory: moduleFactory,
|
|
270
|
-
instance: null,
|
|
271
|
-
lastModified: Date.now(),
|
|
272
|
-
dependencies: new Set()
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
this.modules.set(moduleId, module);
|
|
276
|
-
this._instantiateModule(module);
|
|
277
|
-
return module;
|
|
84
|
+
const route = this.router.resolve(pathname);
|
|
85
|
+
if (!route) {
|
|
86
|
+
return this.attachSessionCookie(new Response('Not Found', { status: 404 }), sessionId);
|
|
278
87
|
}
|
|
279
88
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
this.modules.delete(moduleId);
|
|
290
|
-
this._broadcastReload(moduleId, 'delete');
|
|
291
|
-
return;
|
|
89
|
+
try {
|
|
90
|
+
context = {
|
|
91
|
+
...context,
|
|
92
|
+
params: route.params || {},
|
|
93
|
+
route: {
|
|
94
|
+
pathname,
|
|
95
|
+
filePath: route.filePath,
|
|
96
|
+
isApi: route.isApi,
|
|
97
|
+
isDynamic: route.isDynamic
|
|
292
98
|
}
|
|
99
|
+
};
|
|
293
100
|
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
await this._reloadModule(moduleId);
|
|
302
|
-
}
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.warn('[HMR] File stat error:', error.message);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
101
|
+
if (!route.isApi && extname(route.filePath) === '.html') {
|
|
102
|
+
const html = await Bun.file(route.filePath).text();
|
|
103
|
+
const response = new Response(html, {
|
|
104
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
105
|
+
});
|
|
106
|
+
return this.attachSessionCookie(response, sessionId);
|
|
107
|
+
}
|
|
307
108
|
|
|
308
|
-
|
|
309
|
-
* Reload a module with state preservation
|
|
310
|
-
* @param {string} moduleId - Module to reload
|
|
311
|
-
*/
|
|
312
|
-
async _reloadModule(moduleId) {
|
|
313
|
-
const module = this.modules.get(moduleId);
|
|
314
|
-
if (!module) return;
|
|
109
|
+
const pageModule = await import(route.filePath);
|
|
315
110
|
|
|
316
|
-
|
|
317
|
-
const
|
|
111
|
+
if (route.isApi) {
|
|
112
|
+
const method = req.method.toUpperCase();
|
|
113
|
+
const handler = pageModule[method];
|
|
318
114
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
module.instance.dispose();
|
|
115
|
+
if (!handler) {
|
|
116
|
+
throw new Error("No " + method + " handler in API route");
|
|
322
117
|
}
|
|
323
118
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
delete require.cache[moduleId];
|
|
329
|
-
}
|
|
119
|
+
const result = await handler(context);
|
|
120
|
+
const response = this.normalizeResponse(result);
|
|
121
|
+
return this.attachSessionCookie(response, sessionId);
|
|
122
|
+
}
|
|
330
123
|
|
|
331
|
-
|
|
124
|
+
const handler = pageModule.default;
|
|
125
|
+
if (!handler) {
|
|
126
|
+
throw new Error('No default export in page');
|
|
127
|
+
}
|
|
332
128
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
} catch (error) {
|
|
340
|
-
console.error('[HMR] Module reload error:', error.message);
|
|
341
|
-
}
|
|
129
|
+
const result = await handler(context);
|
|
130
|
+
const response = this.normalizeResponse(result);
|
|
131
|
+
return this.attachSessionCookie(response, sessionId);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error('Route Error:', err);
|
|
134
|
+
return this.attachSessionCookie(new Response('Route Error', { status: 500 }), sessionId);
|
|
342
135
|
}
|
|
136
|
+
}
|
|
137
|
+
attachSessionCookie(response, sessionId) {
|
|
138
|
+
const cookie = 'afri_session=' + sessionId + '; Path=/; HttpOnly; SameSite=Strict';
|
|
343
139
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
async _instantiateModule(module) {
|
|
349
|
-
try {
|
|
350
|
-
module.instance = await module.factory();
|
|
351
|
-
module.lastModified = Date.now();
|
|
352
|
-
} catch (error) {
|
|
353
|
-
console.error('[HMR] Module instantiation error:', error.message);
|
|
354
|
-
module.instance = null;
|
|
355
|
-
}
|
|
356
|
-
return module;
|
|
140
|
+
if (response.headers.has('Set-Cookie')) {
|
|
141
|
+
response.headers.append('Set-Cookie', cookie);
|
|
142
|
+
} else {
|
|
143
|
+
response.headers.set('Set-Cookie', cookie);
|
|
357
144
|
}
|
|
358
145
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
* @param {string} filePath - File path
|
|
362
|
-
* @returns {string} Module ID
|
|
363
|
-
*/
|
|
364
|
-
_pathToModuleId(filePath) {
|
|
365
|
-
return filePath.replace(/\\/g, '/').replace(process.cwd() + '/', '');
|
|
366
|
-
}
|
|
146
|
+
return response;
|
|
147
|
+
}
|
|
367
148
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
* @param {string} eventType - Event type
|
|
372
|
-
*/
|
|
373
|
-
_broadcastReload(moduleId, eventType) {
|
|
374
|
-
const message = {
|
|
375
|
-
type: 'module-update',
|
|
376
|
-
moduleId,
|
|
377
|
-
eventType,
|
|
378
|
-
timestamp: Date.now()
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
for (const ws of this.connections) {
|
|
382
|
-
try {
|
|
383
|
-
ws.send(JSON.stringify(message));
|
|
384
|
-
} catch (error) {
|
|
385
|
-
this.connections.delete(ws);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
149
|
+
normalizeResponse(result) {
|
|
150
|
+
// ✅ Already a Response
|
|
151
|
+
if (result instanceof Response) return result;
|
|
390
152
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
153
|
+
// ✅ RawHtml (PRIMARY)
|
|
154
|
+
if (result instanceof RawHtml) {
|
|
155
|
+
return new Response(result.toString(), {
|
|
156
|
+
headers: { 'Content-Type': 'text/html' },
|
|
157
|
+
});
|
|
394
158
|
}
|
|
395
159
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
initModule(moduleId, moduleFactory) {
|
|
402
|
-
const module = {
|
|
403
|
-
id: moduleId,
|
|
404
|
-
factory: moduleFactory,
|
|
405
|
-
instance: null,
|
|
406
|
-
lastModified: Date.now(),
|
|
407
|
-
dependencies: new Set()
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
this.modules.set(moduleId, module);
|
|
411
|
-
this._instantiateModule(module);
|
|
412
|
-
|
|
413
|
-
return module;
|
|
160
|
+
// ✅ String fallback
|
|
161
|
+
if (typeof result === 'string') {
|
|
162
|
+
return new Response(result, {
|
|
163
|
+
headers: { 'Content-Type': 'text/html' },
|
|
164
|
+
});
|
|
414
165
|
}
|
|
415
166
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
* @param {string} filePath - Changed file path
|
|
419
|
-
* @param {string} eventType - Change type (create, update, delete)
|
|
420
|
-
*/
|
|
421
|
-
async handleFileChange(filePath, eventType) {
|
|
422
|
-
const moduleId = this._pathToModuleId(filePath);
|
|
423
|
-
|
|
424
|
-
if (eventType === 'delete') {
|
|
425
|
-
this.modules.delete(moduleId);
|
|
426
|
-
this._broadcastReload(moduleId, 'delete');
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Check if module needs reloading
|
|
431
|
-
const module = this.modules.get(moduleId);
|
|
432
|
-
if (!module) return;
|
|
433
|
-
|
|
434
|
-
const stats = await Bun.file(filePath).stat();
|
|
435
|
-
if (stats.mtime > module.lastModified) {
|
|
436
|
-
await this._reloadModule(moduleId);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Reload a module with state preservation
|
|
442
|
-
* @param {string} moduleId - Module to reload
|
|
443
|
-
*/
|
|
444
|
-
async _reloadModule(moduleId) {
|
|
445
|
-
const module = this.modules.get(moduleId);
|
|
446
|
-
if (!module) return;
|
|
447
|
-
|
|
448
|
-
// Preserve current state
|
|
449
|
-
const oldState = module.instance?.state || {};
|
|
167
|
+
// ❌ Invalid return type
|
|
168
|
+
console.error('Invalid render output:', result);
|
|
450
169
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
module.instance.dispose();
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Reload module
|
|
457
|
-
delete require.cache[moduleId]; // Clear Node.js cache if applicable
|
|
458
|
-
await this._instantiateModule(module);
|
|
459
|
-
|
|
460
|
-
// Restore state
|
|
461
|
-
if (module.instance?.restoreState) {
|
|
462
|
-
module.instance.restoreState(oldState);
|
|
463
|
-
}
|
|
170
|
+
return new Response('Invalid response type', { status: 500 });
|
|
171
|
+
}
|
|
464
172
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
this._broadcastReload(moduleId, 'update');
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Instantiate a module
|
|
474
|
-
* @param {Object} module - Module definition
|
|
475
|
-
*/
|
|
476
|
-
async _instantiateModule(module) {
|
|
477
|
-
try {
|
|
478
|
-
module.instance = await module.factory();
|
|
479
|
-
|
|
480
|
-
// Store state for hot reload
|
|
481
|
-
if (module.instance) {
|
|
482
|
-
module.instance.state = this.state[module.id] || {};
|
|
483
|
-
}
|
|
484
|
-
} catch (error) {
|
|
485
|
-
console.error(`Failed to instantiate module ${module.id}:`, error);
|
|
486
|
-
}
|
|
173
|
+
async serveStatic(pathname) {
|
|
174
|
+
// Basic path traversal protection
|
|
175
|
+
if (pathname.includes('..') || pathname.includes('\\')) {
|
|
176
|
+
return new Response('Forbidden', { status: 403 });
|
|
487
177
|
}
|
|
488
178
|
|
|
489
|
-
|
|
490
|
-
* Broadcast reload event to connected clients
|
|
491
|
-
* @param {string} moduleId - Reloaded module
|
|
492
|
-
* @param {string} type - Reload type
|
|
493
|
-
*/
|
|
494
|
-
_broadcastReload(moduleId, type) {
|
|
495
|
-
const message = {
|
|
496
|
-
type: 'hot-reload',
|
|
497
|
-
moduleId,
|
|
498
|
-
reloadType: type,
|
|
499
|
-
timestamp: Date.now()
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
for (const ws of this.connections) {
|
|
503
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
504
|
-
ws.send(JSON.stringify(message));
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
179
|
+
const filePath = join(process.cwd(), pathname);
|
|
508
180
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
* @param {WebSocket} ws - WebSocket connection
|
|
512
|
-
*/
|
|
513
|
-
addConnection(ws) {
|
|
514
|
-
this.connections.add(ws);
|
|
181
|
+
try {
|
|
182
|
+
const file = Bun.file(filePath);
|
|
515
183
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
184
|
+
if (!(await file.exists())) {
|
|
185
|
+
return new Response('Not Found', { status: 404 });
|
|
186
|
+
}
|
|
520
187
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
_pathToModuleId(filePath) {
|
|
526
|
-
return filePath.replace(/\\/g, '/').replace(/^.*\/src\//, '');
|
|
188
|
+
return new Response(file);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error('Static Error:', err);
|
|
191
|
+
return new Response('Static File Error', { status: 500 });
|
|
527
192
|
}
|
|
193
|
+
}
|
|
528
194
|
}
|
|
529
195
|
|
|
530
|
-
|
|
531
|
-
* Bun-Optimized HTTP Server
|
|
532
|
-
* Leveraging Bun.serve for maximum performance
|
|
533
|
-
*/
|
|
534
|
-
export class BunHTTPServer {
|
|
535
|
-
constructor(config = {}) {
|
|
536
|
-
this.config = {
|
|
537
|
-
port: config.port || 3000,
|
|
538
|
-
hostname: config.hostname || 'localhost',
|
|
539
|
-
...config
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
this.routes = new Map();
|
|
543
|
-
this.middlewares = [];
|
|
544
|
-
this.hotReload = new HotReloadEngine();
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Register a route handler
|
|
549
|
-
* @param {string} method - HTTP method
|
|
550
|
-
* @param {string} path - Route path
|
|
551
|
-
* @param {Function} handler - Route handler
|
|
552
|
-
*/
|
|
553
|
-
route(method, path, handler) {
|
|
554
|
-
const key = `${method}:${path}`;
|
|
555
|
-
this.routes.set(key, handler);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Add middleware
|
|
560
|
-
* @param {Function} middleware - Middleware function
|
|
561
|
-
*/
|
|
562
|
-
use(middleware) {
|
|
563
|
-
this.middlewares.push(middleware);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Start the server
|
|
568
|
-
*/
|
|
569
|
-
start() {
|
|
570
|
-
const server = Bun.serve({
|
|
571
|
-
port: this.config.port,
|
|
572
|
-
hostname: this.config.hostname,
|
|
573
|
-
|
|
574
|
-
async fetch(request) {
|
|
575
|
-
const url = new URL(request.url);
|
|
576
|
-
const method = request.method;
|
|
577
|
-
const path = url.pathname;
|
|
578
|
-
|
|
579
|
-
// Apply middlewares
|
|
580
|
-
let response = null;
|
|
581
|
-
for (const middleware of this.middlewares) {
|
|
582
|
-
response = await middleware(request, () => null);
|
|
583
|
-
if (response) break;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (!response) {
|
|
587
|
-
// Find route handler
|
|
588
|
-
const key = `${method}:${path}`;
|
|
589
|
-
const handler = this.routes.get(key);
|
|
590
|
-
|
|
591
|
-
if (handler) {
|
|
592
|
-
response = await handler(request);
|
|
593
|
-
} else {
|
|
594
|
-
response = new Response('Not Found', { status: 404 });
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
return response;
|
|
599
|
-
},
|
|
600
|
-
|
|
601
|
-
websocket: {
|
|
602
|
-
open: (ws) => {
|
|
603
|
-
this.hotReload.addConnection(ws);
|
|
604
|
-
},
|
|
605
|
-
|
|
606
|
-
message: (ws, message) => {
|
|
607
|
-
// Handle WebSocket messages
|
|
608
|
-
console.log('WebSocket message:', message);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
console.log(`🚀 Bun Server running on ${server.url}`);
|
|
614
|
-
return server;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Enable hot reloading
|
|
619
|
-
* @param {string[]} watchPaths - Paths to watch for changes
|
|
620
|
-
*/
|
|
621
|
-
enableHotReload(watchPaths = ['./src']) {
|
|
622
|
-
// File watcher using Bun's native file watching
|
|
623
|
-
console.log('Hot reload not yet implemented for this Bun version');
|
|
624
|
-
// TODO: Implement when Bun.watch is available
|
|
625
|
-
/*
|
|
626
|
-
for (const watchPath of watchPaths) {
|
|
627
|
-
const watcher = Bun.watch(watchPath, {
|
|
628
|
-
persistent: true
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
watcher.on('change', async (event, filename) => {
|
|
632
|
-
await this.hotReload.handleFileChange(filename, event);
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
*/
|
|
636
|
-
}
|
|
637
|
-
}
|
|
196
|
+
export class HotReloadEngine extends EnhancedHMRMiddleware {}
|
|
638
197
|
|
|
639
|
-
/**
|
|
640
|
-
* Bun-Native Database Connection
|
|
641
|
-
* Leveraging Bun.sql for type-safe SQLite operations
|
|
642
|
-
*/
|
|
643
198
|
export class BunDatabase {
|
|
644
|
-
|
|
645
|
-
|
|
199
|
+
constructor(dbPath = './afriCode.db') {
|
|
200
|
+
this.db = new BunSQLiteDatabase(dbPath, { create: true });
|
|
201
|
+
}
|
|
646
202
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
this.db.query('PRAGMA synchronous = NORMAL').run();
|
|
651
|
-
this.db.query('PRAGMA cache_size = 1000000').run();
|
|
652
|
-
} catch (e) {
|
|
653
|
-
// Ignore pragma errors in demo
|
|
654
|
-
console.warn('Database pragma setup failed:', e.message);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Execute a query with parameters
|
|
660
|
-
* @param {string} sql - SQL query
|
|
661
|
-
* @param {Array} params - Query parameters
|
|
662
|
-
*/
|
|
663
|
-
query(sql, params = []) {
|
|
664
|
-
try {
|
|
665
|
-
return this.db.query(sql).all(...params);
|
|
666
|
-
} catch (e) {
|
|
667
|
-
console.warn('Query failed:', sql, e.message);
|
|
668
|
-
return [];
|
|
669
|
-
}
|
|
670
|
-
}
|
|
203
|
+
query(sql, params = []) {
|
|
204
|
+
return this.db.query(sql).all(...params);
|
|
205
|
+
}
|
|
671
206
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
* @param {Array} params - Query parameters
|
|
676
|
-
*/
|
|
677
|
-
get(sql, params = []) {
|
|
678
|
-
try {
|
|
679
|
-
return this.db.query(sql).get(...params);
|
|
680
|
-
} catch (e) {
|
|
681
|
-
console.warn('Get query failed:', sql, e.message);
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
207
|
+
get(sql, params = []) {
|
|
208
|
+
return this.db.query(sql).get(...params);
|
|
209
|
+
}
|
|
685
210
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
* @param {Array} params - Query parameters
|
|
690
|
-
*/
|
|
691
|
-
run(sql, params = []) {
|
|
692
|
-
try {
|
|
693
|
-
return this.db.query(sql).run(...params);
|
|
694
|
-
} catch (e) {
|
|
695
|
-
console.warn('Run query failed:', sql, e.message);
|
|
696
|
-
return null;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
211
|
+
run(sql, params = []) {
|
|
212
|
+
return this.db.query(sql).run(...params);
|
|
213
|
+
}
|
|
699
214
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
*/
|
|
704
|
-
transaction(callback) {
|
|
705
|
-
return this.db.transaction(callback)();
|
|
706
|
-
}
|
|
215
|
+
transaction(callback) {
|
|
216
|
+
return this.db.transaction(callback)();
|
|
217
|
+
}
|
|
707
218
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
close() {
|
|
712
|
-
this.db.close();
|
|
713
|
-
}
|
|
219
|
+
close() {
|
|
220
|
+
this.db.close();
|
|
221
|
+
}
|
|
714
222
|
}
|
|
715
223
|
|
|
716
|
-
/**
|
|
717
|
-
* Performance Monitoring
|
|
718
|
-
* Real-time performance metrics using Bun's high-resolution timers
|
|
719
|
-
*/
|
|
720
224
|
export class PerformanceMonitor {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Record request timing
|
|
736
|
-
* @param {number} duration - Request duration in ms
|
|
737
|
-
*/
|
|
738
|
-
recordRequest(duration) {
|
|
739
|
-
this.requestTimes.push(duration);
|
|
225
|
+
constructor() {
|
|
226
|
+
this.metrics = {
|
|
227
|
+
startupTime: 0,
|
|
228
|
+
requestCount: 0,
|
|
229
|
+
averageResponseTime: 0,
|
|
230
|
+
memoryUsage: 0,
|
|
231
|
+
activeConnections: 0
|
|
232
|
+
};
|
|
233
|
+
this.requestTimes = [];
|
|
234
|
+
this.startTime = performance.now();
|
|
235
|
+
}
|
|
740
236
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
this.requestTimes.shift();
|
|
744
|
-
}
|
|
237
|
+
recordRequest(duration) {
|
|
238
|
+
this.requestTimes.push(duration);
|
|
745
239
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
this.requestTimes.reduce((a, b) => a + b, 0) / this.requestTimes.length;
|
|
240
|
+
if (this.requestTimes.length > 1000) {
|
|
241
|
+
this.requestTimes.shift();
|
|
749
242
|
}
|
|
750
243
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
756
|
-
this.metrics.memoryUsage = process.memoryUsage().heapUsed;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
244
|
+
this.metrics.requestCount++;
|
|
245
|
+
this.metrics.averageResponseTime =
|
|
246
|
+
this.requestTimes.reduce((a, b) => a + b, 0) / this.requestTimes.length;
|
|
247
|
+
}
|
|
759
248
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
getMetrics() {
|
|
764
|
-
this.updateMemoryUsage();
|
|
765
|
-
return { ...this.metrics };
|
|
249
|
+
updateMemoryUsage() {
|
|
250
|
+
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
251
|
+
this.metrics.memoryUsage = process.memoryUsage().heapUsed;
|
|
766
252
|
}
|
|
253
|
+
}
|
|
767
254
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
255
|
+
getMetrics() {
|
|
256
|
+
this.updateMemoryUsage();
|
|
257
|
+
return { ...this.metrics };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
startCollection(interval = 5000) {
|
|
261
|
+
setInterval(() => {
|
|
262
|
+
this.updateMemoryUsage();
|
|
263
|
+
}, interval);
|
|
264
|
+
}
|
|
777
265
|
}
|
|
778
266
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
monitor: new PerformanceMonitor(),
|
|
787
|
-
hotReload: new HotReloadEngine()
|
|
788
|
-
};
|
|
267
|
+
export async function initBunRuntime(config = {}) {
|
|
268
|
+
const runtime = {
|
|
269
|
+
server: new BunHTTPServer(config.server),
|
|
270
|
+
database: config.database ? new BunDatabase(config.database.path) : null,
|
|
271
|
+
monitor: new PerformanceMonitor(),
|
|
272
|
+
hotReload: config.hotReload ? new HotReloadEngine(config.hotReload) : null
|
|
273
|
+
};
|
|
789
274
|
|
|
790
|
-
|
|
791
|
-
runtime.monitor.startCollection();
|
|
275
|
+
runtime.monitor.startCollection();
|
|
792
276
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
277
|
+
if (runtime.hotReload && runtime.server) {
|
|
278
|
+
await runtime.hotReload.initialize(runtime.server);
|
|
279
|
+
}
|
|
797
280
|
|
|
798
|
-
|
|
799
|
-
}
|
|
281
|
+
return runtime;
|
|
282
|
+
}
|