@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/compliance.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { frameworkLog } from './logging.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* NIDA Identity Infrastructure Integration
|
|
@@ -532,7 +533,7 @@ export class AMLComplianceEngine {
|
|
|
532
533
|
*/
|
|
533
534
|
async _scheduleReporting(report, hours) {
|
|
534
535
|
// Implementation would use job queue system
|
|
535
|
-
|
|
536
|
+
frameworkLog('warn', `Scheduling FIU report in ${hours} hours for transaction ${report.transactionId}`);
|
|
536
537
|
}
|
|
537
538
|
}
|
|
538
539
|
|
|
@@ -571,7 +572,7 @@ export class ComplianceMiddleware {
|
|
|
571
572
|
await this._updateUserKYC(userId, verification);
|
|
572
573
|
}
|
|
573
574
|
} catch (error) {
|
|
574
|
-
|
|
575
|
+
frameworkLog('error', 'NIDA verification failed:', error);
|
|
575
576
|
// Allow request to continue but log failure
|
|
576
577
|
}
|
|
577
578
|
}
|
|
@@ -590,7 +591,7 @@ export class ComplianceMiddleware {
|
|
|
590
591
|
const evaluation = await this.aml.evaluateTransaction(transaction, customer);
|
|
591
592
|
|
|
592
593
|
if (evaluation.suspicious) {
|
|
593
|
-
|
|
594
|
+
frameworkLog('warn', `Suspicious transaction detected: ${transaction.id}`);
|
|
594
595
|
// Could block transaction or flag for review
|
|
595
596
|
}
|
|
596
597
|
|
|
@@ -611,7 +612,7 @@ export class ComplianceMiddleware {
|
|
|
611
612
|
*/
|
|
612
613
|
async _updateUserKYC(userId, verification) {
|
|
613
614
|
// Implementation would update user database
|
|
614
|
-
|
|
615
|
+
frameworkLog('warn', `Updated KYC for user ${userId}:`, verification);
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
/**
|
|
@@ -625,4 +626,4 @@ export class ComplianceMiddleware {
|
|
|
625
626
|
address: { country: 'TZ' }
|
|
626
627
|
};
|
|
627
628
|
}
|
|
628
|
-
}
|
|
629
|
+
}
|
package/core/config.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @module core/config
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { frameworkLog } from './logging.js';
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* @typedef {Object} AfriCodeConfig
|
|
18
20
|
*
|
|
@@ -192,7 +194,7 @@ export function emitValidationError(error) {
|
|
|
192
194
|
const hook = _config.hooks?.onValidationError;
|
|
193
195
|
if (typeof hook === 'function') {
|
|
194
196
|
try { hook(error); } catch (e) {
|
|
195
|
-
|
|
197
|
+
frameworkLog('error', '[AfriCode] Hook error in onValidationError:', e);
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
}
|
|
@@ -205,7 +207,7 @@ export function emitSecurityViolation(error) {
|
|
|
205
207
|
const hook = _config.hooks?.onSecurityViolation;
|
|
206
208
|
if (typeof hook === 'function') {
|
|
207
209
|
try { hook(error); } catch (e) {
|
|
208
|
-
|
|
210
|
+
frameworkLog('error', '[AfriCode] Hook error in onSecurityViolation:', e);
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
}
|
|
@@ -219,7 +221,7 @@ export function emitRateLimit(key, request = null) {
|
|
|
219
221
|
const hook = _config.hooks?.onRateLimit;
|
|
220
222
|
if (typeof hook === 'function') {
|
|
221
223
|
try { hook(key, request); } catch (e) {
|
|
222
|
-
|
|
224
|
+
frameworkLog('error', '[AfriCode] Hook error in onRateLimit:', e);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
}
|
|
@@ -232,7 +234,7 @@ export function emitDatabaseError(error) {
|
|
|
232
234
|
const hook = _config.hooks?.onDatabaseError;
|
|
233
235
|
if (typeof hook === 'function') {
|
|
234
236
|
try { hook(error); } catch (e) {
|
|
235
|
-
|
|
237
|
+
frameworkLog('error', '[AfriCode] Hook error in onDatabaseError:', e);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
}
|
|
@@ -245,7 +247,7 @@ export function emitRequest(request) {
|
|
|
245
247
|
const hook = _config.hooks?.onRequest;
|
|
246
248
|
if (typeof hook === 'function') {
|
|
247
249
|
try { hook(request); } catch (e) {
|
|
248
|
-
|
|
250
|
+
frameworkLog('error', '[AfriCode] Hook error in onRequest:', e);
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
253
|
}
|
package/core/enhanced-hmr.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { watch } from 'node:fs';
|
|
14
14
|
import { join, extname, dirname } from 'node:path';
|
|
15
|
+
import { frameworkLog } from './logging.js';
|
|
15
16
|
|
|
16
17
|
export class EnhancedHMRMiddleware {
|
|
17
18
|
constructor(options = {}) {
|
|
@@ -44,7 +45,7 @@ export class EnhancedHMRMiddleware {
|
|
|
44
45
|
* Initialize HMR server with Bun.serve integration
|
|
45
46
|
*/
|
|
46
47
|
async initialize(devServer) {
|
|
47
|
-
|
|
48
|
+
frameworkLog('log', `[HMR] Initializing enhanced hot reload on port ${this.options.port}`);
|
|
48
49
|
|
|
49
50
|
// Setup WebSocket upgrade handler
|
|
50
51
|
devServer.upgrade({
|
|
@@ -56,14 +57,14 @@ export class EnhancedHMRMiddleware {
|
|
|
56
57
|
// Start file watching
|
|
57
58
|
await this.startFileWatching();
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
frameworkLog('log', `[HMR] Watching ${this.options.watchPaths.length} paths for changes...`);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* Handle new client connection
|
|
64
65
|
*/
|
|
65
66
|
handleClientConnect(ws) {
|
|
66
|
-
|
|
67
|
+
frameworkLog('log', `[HMR] Client connected from ${ws.remoteAddress}`);
|
|
67
68
|
this.clients.add(ws);
|
|
68
69
|
|
|
69
70
|
// Send welcome message with current state
|
|
@@ -97,10 +98,11 @@ export class EnhancedHMRMiddleware {
|
|
|
97
98
|
break;
|
|
98
99
|
|
|
99
100
|
default:
|
|
100
|
-
|
|
101
|
+
frameworkLog('log', `[HMR] Unknown message type: ${data.type}`);
|
|
102
|
+
break;
|
|
101
103
|
}
|
|
102
104
|
} catch (error) {
|
|
103
|
-
|
|
105
|
+
frameworkLog('error', '[HMR] Error parsing client message:', error);
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -108,7 +110,7 @@ export class EnhancedHMRMiddleware {
|
|
|
108
110
|
* Handle client disconnection
|
|
109
111
|
*/
|
|
110
112
|
handleClientDisconnect(ws) {
|
|
111
|
-
|
|
113
|
+
frameworkLog('log', '[HMR] Client disconnected');
|
|
112
114
|
this.clients.delete(ws);
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -151,10 +153,10 @@ export class EnhancedHMRMiddleware {
|
|
|
151
153
|
});
|
|
152
154
|
|
|
153
155
|
this.watchers.set(dirPath, watcher);
|
|
154
|
-
|
|
156
|
+
frameworkLog('log', `[HMR] Watching directory: ${dirPath}`);
|
|
155
157
|
|
|
156
158
|
} catch (error) {
|
|
157
|
-
|
|
159
|
+
frameworkLog('error', `[HMR] Failed to watch ${dirPath}:`, error.message);
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -181,7 +183,7 @@ export class EnhancedHMRMiddleware {
|
|
|
181
183
|
const ext = extname(filename);
|
|
182
184
|
const relativePath = this.getRelativePath(fullPath);
|
|
183
185
|
|
|
184
|
-
|
|
186
|
+
frameworkLog('log', `[HMR] File changed: ${relativePath}`);
|
|
185
187
|
|
|
186
188
|
// Determine change type and reload strategy
|
|
187
189
|
const changeType = this.classifyChange(filename, ext);
|
|
@@ -269,7 +271,7 @@ export class EnhancedHMRMiddleware {
|
|
|
269
271
|
* Handle core module changes (full reload required)
|
|
270
272
|
*/
|
|
271
273
|
handleCoreChange(relativePath) {
|
|
272
|
-
|
|
274
|
+
frameworkLog('log', `[HMR] Core module changed, triggering full reload: ${relativePath}`);
|
|
273
275
|
|
|
274
276
|
this.broadcast({
|
|
275
277
|
type: 'core-update',
|
|
@@ -297,7 +299,7 @@ export class EnhancedHMRMiddleware {
|
|
|
297
299
|
* Handle config changes (server restart)
|
|
298
300
|
*/
|
|
299
301
|
handleConfigChange(relativePath) {
|
|
300
|
-
|
|
302
|
+
frameworkLog('log', `[HMR] Config changed, server restart required: ${relativePath}`);
|
|
301
303
|
|
|
302
304
|
this.broadcast({
|
|
303
305
|
type: 'config-update',
|
|
@@ -329,7 +331,7 @@ export class EnhancedHMRMiddleware {
|
|
|
329
331
|
try {
|
|
330
332
|
client.send(messageStr);
|
|
331
333
|
} catch (error) {
|
|
332
|
-
|
|
334
|
+
frameworkLog('error', '[HMR] Failed to send to client:', error);
|
|
333
335
|
this.clients.delete(client);
|
|
334
336
|
}
|
|
335
337
|
}
|
|
@@ -375,7 +377,7 @@ export class EnhancedHMRMiddleware {
|
|
|
375
377
|
* Cleanup watchers on shutdown
|
|
376
378
|
*/
|
|
377
379
|
async shutdown() {
|
|
378
|
-
|
|
380
|
+
frameworkLog('log', '[HMR] Shutting down...');
|
|
379
381
|
|
|
380
382
|
// Close all watchers
|
|
381
383
|
for (const [path, watcher] of this.watchers) {
|
|
@@ -397,7 +399,7 @@ export class EnhancedHMRMiddleware {
|
|
|
397
399
|
this.sessions.clear();
|
|
398
400
|
this.dbConnections.clear();
|
|
399
401
|
|
|
400
|
-
|
|
402
|
+
frameworkLog('log', '[HMR] Shutdown complete');
|
|
401
403
|
}
|
|
402
404
|
}
|
|
403
405
|
|
package/core/file-router.js
CHANGED
|
@@ -1,290 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Supports dynamic routes, nested layouts, and API routes
|
|
2
|
+
* Compatibility router for legacy runtime entry points.
|
|
3
|
+
* The canonical route scanner lives in core/router.js.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
import { join
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
6
|
+
import { FileSystemRouter } from './router.js';
|
|
7
|
+
import { join } from 'path';
|
|
10
8
|
|
|
11
9
|
export class FileBasedRouter {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const stat = statSync(itemPath);
|
|
51
|
-
|
|
52
|
-
if (stat.isDirectory()) {
|
|
53
|
-
// Handle nested directories
|
|
54
|
-
const subRoute = routePrefix + '/' + item;
|
|
55
|
-
this._scanDirectory(itemPath, subRoute);
|
|
56
|
-
} else if (stat.isFile()) {
|
|
57
|
-
// Handle route files
|
|
58
|
-
this._processRouteFile(itemPath, item, routePrefix);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Process individual route file
|
|
65
|
-
*/
|
|
66
|
-
_processRouteFile(filePath, filename, routePrefix) {
|
|
67
|
-
const ext = extname(filename);
|
|
68
|
-
const name = filename.replace(ext, '');
|
|
69
|
-
|
|
70
|
-
// Skip non-route files
|
|
71
|
-
if (!['.js', '.ts', '.mjs'].includes(ext)) return;
|
|
72
|
-
|
|
73
|
-
// Handle API routes
|
|
74
|
-
if (filePath.includes('/api/') || routePrefix.includes('/api')) {
|
|
75
|
-
this._addApiRoute(filePath, filename, routePrefix);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Handle page routes
|
|
80
|
-
this._addPageRoute(filePath, filename, routePrefix);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Add API route mapping
|
|
85
|
-
*/
|
|
86
|
-
_addApiRoute(filePath, filename, routePrefix) {
|
|
87
|
-
let routePath = routePrefix + '/' + filename.replace(/\.(js|ts|mjs)$/, '');
|
|
88
|
-
|
|
89
|
-
// Handle dynamic routes
|
|
90
|
-
routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1');
|
|
91
|
-
|
|
92
|
-
// Handle catch-all routes
|
|
93
|
-
routePath = routePath.replace(/\[\.\.\.([^\]]+)\]/g, '*$1');
|
|
94
|
-
|
|
95
|
-
this.apiRoutes.set(routePath, {
|
|
96
|
-
filePath,
|
|
97
|
-
handler: null, // Lazy load
|
|
98
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
console.log(`[Router] API Route: ${routePath} -> ${relative(process.cwd(), filePath)}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Add page route mapping
|
|
106
|
-
*/
|
|
107
|
-
_addPageRoute(filePath, filename, routePrefix) {
|
|
108
|
-
let routePath = routePrefix;
|
|
109
|
-
|
|
110
|
-
// Handle index files
|
|
111
|
-
if (filename === 'index.js' || filename === 'index.ts' || filename === 'index.mjs') {
|
|
112
|
-
// Keep routePrefix as is
|
|
113
|
-
} else {
|
|
114
|
-
routePath += '/' + filename.replace(/\.(js|ts|mjs)$/, '');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Handle dynamic routes
|
|
118
|
-
routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1');
|
|
119
|
-
|
|
120
|
-
// Handle catch-all routes
|
|
121
|
-
routePath = routePath.replace(/\[\.\.\.([^\]]+)\]/g, '*$1');
|
|
122
|
-
|
|
123
|
-
// Ensure root route
|
|
124
|
-
if (routePath === '') routePath = '/';
|
|
125
|
-
|
|
126
|
-
this.routes.set(routePath, {
|
|
127
|
-
filePath,
|
|
128
|
-
component: null, // Lazy load
|
|
129
|
-
layout: this._findLayoutForRoute(routePath)
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
console.log(`[Router] Page Route: ${routePath} -> ${relative(process.cwd(), filePath)}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Find appropriate layout for route
|
|
137
|
-
*/
|
|
138
|
-
_findLayoutForRoute(routePath) {
|
|
139
|
-
// Check for nested layouts
|
|
140
|
-
const segments = routePath.split('/').filter(Boolean);
|
|
141
|
-
let layoutPath = null;
|
|
142
|
-
|
|
143
|
-
// Walk up the route hierarchy
|
|
144
|
-
for (let i = segments.length; i >= 0; i--) {
|
|
145
|
-
const testPath = '/' + segments.slice(0, i).join('/');
|
|
146
|
-
const layoutFile = join(process.cwd(), this.options.layoutsDir, testPath, 'layout.js');
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
statSync(layoutFile);
|
|
150
|
-
layoutPath = layoutFile;
|
|
151
|
-
break;
|
|
152
|
-
} catch {
|
|
153
|
-
// Continue searching
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Fallback to root layout
|
|
158
|
-
if (!layoutPath) {
|
|
159
|
-
const rootLayout = join(process.cwd(), this.options.layoutsDir, 'layout.js');
|
|
160
|
-
try {
|
|
161
|
-
statSync(rootLayout);
|
|
162
|
-
layoutPath = rootLayout;
|
|
163
|
-
} catch {
|
|
164
|
-
// No layout found
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return layoutPath;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Match incoming request to route
|
|
173
|
-
*/
|
|
174
|
-
matchRoute(pathname, method = 'GET') {
|
|
175
|
-
// Check API routes first
|
|
176
|
-
for (const [routePath, route] of this.apiRoutes) {
|
|
177
|
-
const params = this._matchRoutePattern(routePath, pathname);
|
|
178
|
-
if (params && route.methods.includes(method)) {
|
|
179
|
-
return {
|
|
180
|
-
type: 'api',
|
|
181
|
-
route: routePath,
|
|
182
|
-
filePath: route.filePath,
|
|
183
|
-
params,
|
|
184
|
-
handler: route.handler
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check page routes
|
|
190
|
-
for (const [routePath, route] of this.routes) {
|
|
191
|
-
const params = this._matchRoutePattern(routePath, pathname);
|
|
192
|
-
if (params) {
|
|
193
|
-
return {
|
|
194
|
-
type: 'page',
|
|
195
|
-
route: routePath,
|
|
196
|
-
filePath: route.filePath,
|
|
197
|
-
params,
|
|
198
|
-
component: route.component,
|
|
199
|
-
layout: route.layout
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Match route pattern with pathname
|
|
209
|
-
*/
|
|
210
|
-
_matchRoutePattern(pattern, pathname) {
|
|
211
|
-
const patternParts = pattern.split('/').filter(Boolean);
|
|
212
|
-
const pathParts = pathname.split('/').filter(Boolean);
|
|
213
|
-
|
|
214
|
-
if (pattern === '/' && pathname === '/') {
|
|
215
|
-
return {};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (patternParts.length !== pathParts.length && !pattern.includes('*')) {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const params = {};
|
|
223
|
-
|
|
224
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
225
|
-
const patternPart = patternParts[i];
|
|
226
|
-
const pathPart = pathParts[i];
|
|
227
|
-
|
|
228
|
-
if (patternPart.startsWith(':')) {
|
|
229
|
-
// Dynamic parameter
|
|
230
|
-
const paramName = patternPart.slice(1);
|
|
231
|
-
params[paramName] = pathPart;
|
|
232
|
-
} else if (patternPart.startsWith('*')) {
|
|
233
|
-
// Catch-all parameter
|
|
234
|
-
const paramName = patternPart.slice(1);
|
|
235
|
-
params[paramName] = pathParts.slice(i).join('/');
|
|
236
|
-
return params; // Catch-all consumes rest
|
|
237
|
-
} else if (patternPart !== pathPart) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return params;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Load route handler/component lazily
|
|
247
|
-
*/
|
|
248
|
-
async loadRoute(routeMatch) {
|
|
249
|
-
if (routeMatch.type === 'api') {
|
|
250
|
-
if (!routeMatch.handler) {
|
|
251
|
-
const module = await import(routeMatch.filePath);
|
|
252
|
-
routeMatch.handler = module.default || module;
|
|
253
|
-
this.apiRoutes.get(routeMatch.route).handler = routeMatch.handler;
|
|
254
|
-
}
|
|
255
|
-
return routeMatch.handler;
|
|
256
|
-
} else {
|
|
257
|
-
if (!routeMatch.component) {
|
|
258
|
-
const module = await import(routeMatch.filePath);
|
|
259
|
-
routeMatch.component = module.default || module;
|
|
260
|
-
this.routes.get(routeMatch.route).component = routeMatch.component;
|
|
261
|
-
}
|
|
262
|
-
return routeMatch.component;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get all registered routes
|
|
268
|
-
*/
|
|
269
|
-
getRoutes() {
|
|
270
|
-
return {
|
|
271
|
-
pages: Array.from(this.routes.keys()),
|
|
272
|
-
apis: Array.from(this.apiRoutes.keys())
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Clear route cache (for hot reloading)
|
|
278
|
-
*/
|
|
279
|
-
clearCache() {
|
|
280
|
-
for (const route of this.apiRoutes.values()) {
|
|
281
|
-
route.handler = null;
|
|
282
|
-
}
|
|
283
|
-
for (const route of this.routes.values()) {
|
|
284
|
-
route.component = null;
|
|
285
|
-
}
|
|
286
|
-
console.log('[Router] Route cache cleared');
|
|
287
|
-
}
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.router = new FileSystemRouter(join(process.cwd(), options.pagesDir || 'pages'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
matchRoute(pathname) {
|
|
15
|
+
const resolved = this.router.resolve(pathname);
|
|
16
|
+
if (!resolved) return null;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
type: resolved.isApi ? "api" : "page",
|
|
20
|
+
route: pathname,
|
|
21
|
+
filePath: resolved.filePath,
|
|
22
|
+
params: resolved.params || {},
|
|
23
|
+
isDynamic: Boolean(resolved.isDynamic),
|
|
24
|
+
isApi: Boolean(resolved.isApi),
|
|
25
|
+
handler: null,
|
|
26
|
+
component: null
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async loadRoute(routeMatch) {
|
|
31
|
+
const module = await import(routeMatch.filePath);
|
|
32
|
+
if (routeMatch.type === "api") {
|
|
33
|
+
routeMatch.handler = module.default || module;
|
|
34
|
+
return routeMatch.handler;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
routeMatch.component = module.default || module;
|
|
38
|
+
return routeMatch.component;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getRoutes() {
|
|
42
|
+
return this.router.getRoutes();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
clearCache() {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
288
48
|
}
|
|
289
49
|
|
|
290
50
|
export default FileBasedRouter;
|
package/core/hmr.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import * as nodePath from 'node:path';
|
|
12
12
|
import { watch } from 'node:fs';
|
|
13
|
+
import { frameworkLog } from './logging.js';
|
|
13
14
|
|
|
14
15
|
export class HMRServer {
|
|
15
16
|
constructor(port = 3001) {
|
|
@@ -28,7 +29,7 @@ export class HMRServer {
|
|
|
28
29
|
// Upgrade HTTP connections to WebSocket
|
|
29
30
|
devServer.upgrade({
|
|
30
31
|
open(ws) {
|
|
31
|
-
|
|
32
|
+
frameworkLog('log', `[HMR] Client connected from ${ws.remoteAddress}`);
|
|
32
33
|
this.clients.add(ws);
|
|
33
34
|
},
|
|
34
35
|
message(ws, message) {
|
|
@@ -44,13 +45,13 @@ export class HMRServer {
|
|
|
44
45
|
},
|
|
45
46
|
close(ws) {
|
|
46
47
|
this.clients.delete(ws);
|
|
47
|
-
|
|
48
|
+
frameworkLog('log', `[HMR] Client disconnected`);
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
// Start file watching
|
|
52
53
|
this.watchDirectory(process.cwd());
|
|
53
|
-
|
|
54
|
+
frameworkLog('log', `[HMR] Watching for changes...`);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -94,7 +95,7 @@ export class HMRServer {
|
|
|
94
95
|
|
|
95
96
|
this.watchers.set(dir, watcher);
|
|
96
97
|
} catch (err) {
|
|
97
|
-
|
|
98
|
+
frameworkLog('error', `[HMR] Failed to watch ${dir}:`, err.message);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -111,7 +112,7 @@ export class HMRServer {
|
|
|
111
112
|
if (normalizedPath.includes('.test.')) return;
|
|
112
113
|
if (normalizedPath.includes('test/')) return;
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
frameworkLog('log', `[HMR] File changed: ${normalizedPath}`);
|
|
115
116
|
|
|
116
117
|
// Determine module type and extract clean path
|
|
117
118
|
let moduleType = 'unknown';
|
|
@@ -165,7 +166,7 @@ export class HMRServer {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
if (successCount > 0) {
|
|
168
|
-
|
|
169
|
+
frameworkLog('log', `[HMR] Broadcasted update to ${successCount} client(s)`);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -182,7 +183,7 @@ export class HMRServer {
|
|
|
182
183
|
}
|
|
183
184
|
this.watchers.clear();
|
|
184
185
|
this.clients.clear();
|
|
185
|
-
|
|
186
|
+
frameworkLog('log', `[HMR] Server stopped`);
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|