@africode/core 5.0.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/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun-Native Runtime Engine
|
|
3
|
+
* Optimized runtime leveraging Bun's Zig-based architecture
|
|
4
|
+
*
|
|
5
|
+
* Provides ultra-fast startup, native APIs, and hot reloading
|
|
6
|
+
* capabilities for high-performance fintech applications.
|
|
7
|
+
*
|
|
8
|
+
* @module core/bun-runtime
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createReactiveState } from './state.js';
|
|
12
|
+
import { FileBasedRouter } from './file-router.js';
|
|
13
|
+
import { EnhancedHMRMiddleware } from './enhanced-hmr.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Bun Runtime Configuration
|
|
17
|
+
*/
|
|
18
|
+
export const BUN_CONFIG = {
|
|
19
|
+
startupTimeout: 5000, // 5ms target startup
|
|
20
|
+
memoryLimit: '1gb',
|
|
21
|
+
gcStrategy: 'generational',
|
|
22
|
+
threadPoolSize: 4
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Bun HTTP Server with File-Based Routing
|
|
27
|
+
* Autonomous application lifecycle management
|
|
28
|
+
*/
|
|
29
|
+
export class BunHTTPServer {
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
this.options = {
|
|
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
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Handle incoming requests with file-based routing
|
|
103
|
+
*/
|
|
104
|
+
async _handleRoute(request) {
|
|
105
|
+
const url = new URL(request.url);
|
|
106
|
+
const pathname = url.pathname;
|
|
107
|
+
const method = request.method;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Match route
|
|
111
|
+
const routeMatch = this.router.matchRoute(pathname, method);
|
|
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
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Handle API route execution
|
|
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
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handle page route rendering
|
|
162
|
+
*/
|
|
163
|
+
async _handlePageRoute(component, request, routeMatch) {
|
|
164
|
+
try {
|
|
165
|
+
// Render page component
|
|
166
|
+
const html = await this._renderPage(component, routeMatch);
|
|
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
|
+
}
|
|
192
|
+
|
|
193
|
+
// Apply layout if available
|
|
194
|
+
if (routeMatch.layout) {
|
|
195
|
+
try {
|
|
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
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Wrap in basic HTML structure
|
|
208
|
+
return `<!DOCTYPE html>
|
|
209
|
+
<html lang="en">
|
|
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();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Initialize hot reloading for a module
|
|
263
|
+
* @param {string} moduleId - Module identifier
|
|
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;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Handle file change event
|
|
282
|
+
* @param {string} filePath - Changed file path
|
|
283
|
+
* @param {string} eventType - Change type (create, update, delete)
|
|
284
|
+
*/
|
|
285
|
+
async handleFileChange(filePath, eventType) {
|
|
286
|
+
const moduleId = this._pathToModuleId(filePath);
|
|
287
|
+
|
|
288
|
+
if (eventType === 'delete') {
|
|
289
|
+
this.modules.delete(moduleId);
|
|
290
|
+
this._broadcastReload(moduleId, 'delete');
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check if module needs reloading
|
|
295
|
+
const module = this.modules.get(moduleId);
|
|
296
|
+
if (!module) return;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const stats = await Bun.file(filePath).stat();
|
|
300
|
+
if (stats.mtime > module.lastModified) {
|
|
301
|
+
await this._reloadModule(moduleId);
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.warn('[HMR] File stat error:', error.message);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
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;
|
|
315
|
+
|
|
316
|
+
// Preserve current state
|
|
317
|
+
const oldState = module.instance?.state || {};
|
|
318
|
+
|
|
319
|
+
// Dispose old instance
|
|
320
|
+
if (module.instance?.dispose) {
|
|
321
|
+
module.instance.dispose();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Reload module
|
|
325
|
+
try {
|
|
326
|
+
// Clear module cache if possible
|
|
327
|
+
if (typeof require !== 'undefined' && require.cache) {
|
|
328
|
+
delete require.cache[moduleId];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await this._instantiateModule(module);
|
|
332
|
+
|
|
333
|
+
// Restore state
|
|
334
|
+
if (module.instance?.restoreState) {
|
|
335
|
+
module.instance.restoreState(oldState);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
this._broadcastReload(moduleId, 'update');
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('[HMR] Module reload error:', error.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Instantiate a module
|
|
346
|
+
* @param {Object} module - Module object
|
|
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;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Convert file path to module ID
|
|
361
|
+
* @param {string} filePath - File path
|
|
362
|
+
* @returns {string} Module ID
|
|
363
|
+
*/
|
|
364
|
+
_pathToModuleId(filePath) {
|
|
365
|
+
return filePath.replace(/\\/g, '/').replace(process.cwd() + '/', '');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Broadcast reload event to connected clients
|
|
370
|
+
* @param {string} moduleId - Module ID
|
|
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
|
+
}
|
|
390
|
+
|
|
391
|
+
// Import join function for static file serving
|
|
392
|
+
import { join } from 'path';
|
|
393
|
+
this.connections = new Set();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Initialize hot reloading for a module
|
|
398
|
+
* @param {string} moduleId - Module identifier
|
|
399
|
+
* @param {Function} moduleFactory - Module factory function
|
|
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;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Handle file change event
|
|
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 || {};
|
|
450
|
+
|
|
451
|
+
// Dispose old instance
|
|
452
|
+
if (module.instance?.dispose) {
|
|
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
|
+
}
|
|
464
|
+
|
|
465
|
+
// Update timestamp
|
|
466
|
+
module.lastModified = Date.now();
|
|
467
|
+
|
|
468
|
+
// Broadcast reload to clients
|
|
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
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
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
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Add WebSocket connection for hot reload notifications
|
|
511
|
+
* @param {WebSocket} ws - WebSocket connection
|
|
512
|
+
*/
|
|
513
|
+
addConnection(ws) {
|
|
514
|
+
this.connections.add(ws);
|
|
515
|
+
|
|
516
|
+
ws.on('close', () => {
|
|
517
|
+
this.connections.delete(ws);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Convert file path to module ID
|
|
523
|
+
* @param {string} filePath - File path
|
|
524
|
+
*/
|
|
525
|
+
_pathToModuleId(filePath) {
|
|
526
|
+
return filePath.replace(/\\/g, '/').replace(/^.*\/src\//, '');
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
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
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Bun-Native Database Connection
|
|
641
|
+
* Leveraging Bun.sql for type-safe SQLite operations
|
|
642
|
+
*/
|
|
643
|
+
export class BunDatabase {
|
|
644
|
+
constructor(dbPath = './afriCode.db') {
|
|
645
|
+
this.db = new Bun.sql(`${dbPath}`);
|
|
646
|
+
|
|
647
|
+
// Enable WAL mode for better concurrency
|
|
648
|
+
try {
|
|
649
|
+
this.db.query('PRAGMA journal_mode = WAL').run();
|
|
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
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Execute a query and get first result
|
|
674
|
+
* @param {string} sql - SQL query
|
|
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
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Execute a mutation query
|
|
688
|
+
* @param {string} sql - SQL query
|
|
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
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Execute multiple queries in a transaction
|
|
702
|
+
* @param {Function} callback - Transaction callback
|
|
703
|
+
*/
|
|
704
|
+
transaction(callback) {
|
|
705
|
+
return this.db.transaction(callback)();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Close database connection
|
|
710
|
+
*/
|
|
711
|
+
close() {
|
|
712
|
+
this.db.close();
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Performance Monitoring
|
|
718
|
+
* Real-time performance metrics using Bun's high-resolution timers
|
|
719
|
+
*/
|
|
720
|
+
export class PerformanceMonitor {
|
|
721
|
+
constructor() {
|
|
722
|
+
this.metrics = createReactiveState({
|
|
723
|
+
startupTime: 0,
|
|
724
|
+
requestCount: 0,
|
|
725
|
+
averageResponseTime: 0,
|
|
726
|
+
memoryUsage: 0,
|
|
727
|
+
activeConnections: 0
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
this.requestTimes = [];
|
|
731
|
+
this.startTime = performance.now();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Record request timing
|
|
736
|
+
* @param {number} duration - Request duration in ms
|
|
737
|
+
*/
|
|
738
|
+
recordRequest(duration) {
|
|
739
|
+
this.requestTimes.push(duration);
|
|
740
|
+
|
|
741
|
+
// Keep only last 1000 requests for averaging
|
|
742
|
+
if (this.requestTimes.length > 1000) {
|
|
743
|
+
this.requestTimes.shift();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
this.metrics.requestCount++;
|
|
747
|
+
this.metrics.averageResponseTime =
|
|
748
|
+
this.requestTimes.reduce((a, b) => a + b, 0) / this.requestTimes.length;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Update memory usage
|
|
753
|
+
*/
|
|
754
|
+
updateMemoryUsage() {
|
|
755
|
+
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
756
|
+
this.metrics.memoryUsage = process.memoryUsage().heapUsed;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Get current metrics
|
|
762
|
+
*/
|
|
763
|
+
getMetrics() {
|
|
764
|
+
this.updateMemoryUsage();
|
|
765
|
+
return { ...this.metrics };
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Start periodic metrics collection
|
|
770
|
+
* @param {number} interval - Collection interval in ms
|
|
771
|
+
*/
|
|
772
|
+
startCollection(interval = 5000) {
|
|
773
|
+
setInterval(() => {
|
|
774
|
+
this.updateMemoryUsage();
|
|
775
|
+
}, interval);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Initialize Bun-native runtime
|
|
781
|
+
*/
|
|
782
|
+
export function initBunRuntime(config = {}) {
|
|
783
|
+
const runtime = {
|
|
784
|
+
server: new BunHTTPServer(config.server),
|
|
785
|
+
database: config.database ? new BunDatabase(config.database.path) : null,
|
|
786
|
+
monitor: new PerformanceMonitor(),
|
|
787
|
+
hotReload: new HotReloadEngine()
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// Start performance monitoring
|
|
791
|
+
runtime.monitor.startCollection();
|
|
792
|
+
|
|
793
|
+
// Enable hot reloading if in development
|
|
794
|
+
if (config.hotReload) {
|
|
795
|
+
runtime.server.enableHotReload(config.hotReload.watchPaths);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return runtime;
|
|
799
|
+
}
|