@africode/core 5.0.0 → 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.
@@ -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
- console.warn(`Scheduling FIU report in ${hours} hours for transaction ${report.transactionId}`);
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
- console.error('NIDA verification failed:', error);
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
- console.warn(`Suspicious transaction detected: ${transaction.id}`);
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
- console.warn(`Updated KYC for user ${userId}:`, verification);
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
- console.error('[AfriCode] Hook error in onValidationError:', e);
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
- console.error('[AfriCode] Hook error in onSecurityViolation:', e);
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
- console.error('[AfriCode] Hook error in onRateLimit:', e);
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
- console.error('[AfriCode] Hook error in onDatabaseError:', e);
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
- console.error('[AfriCode] Hook error in onRequest:', e);
250
+ frameworkLog('error', '[AfriCode] Hook error in onRequest:', e);
249
251
  }
250
252
  }
251
253
  }
@@ -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
- console.log(`[HMR] Initializing enhanced hot reload on port ${this.options.port}`);
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
- console.log(`[HMR] Watching ${this.options.watchPaths.length} paths for changes...`);
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
- console.log(`[HMR] Client connected from ${ws.remoteAddress}`);
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
- console.log(`[HMR] Unknown message type: ${data.type}`);
101
+ frameworkLog('log', `[HMR] Unknown message type: ${data.type}`);
102
+ break;
101
103
  }
102
104
  } catch (error) {
103
- console.error('[HMR] Error parsing client message:', error);
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
- console.log('[HMR] Client disconnected');
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
- console.log(`[HMR] Watching directory: ${dirPath}`);
156
+ frameworkLog('log', `[HMR] Watching directory: ${dirPath}`);
155
157
 
156
158
  } catch (error) {
157
- console.error(`[HMR] Failed to watch ${dirPath}:`, error.message);
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
- console.log(`[HMR] File changed: ${relativePath}`);
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
- console.log(`[HMR] Core module changed, triggering full reload: ${relativePath}`);
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
- console.log(`[HMR] Config changed, server restart required: ${relativePath}`);
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
- console.error('[HMR] Failed to send to client:', error);
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
- console.log('[HMR] Shutting down...');
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
- console.log('[HMR] Shutdown complete');
402
+ frameworkLog('log', '[HMR] Shutdown complete');
401
403
  }
402
404
  }
403
405
 
@@ -1,290 +1,50 @@
1
1
  /**
2
- * File-Based Router for Bun Runtime
3
- * Automatic mapping of /pages directory to URLs
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 { readdirSync, statSync } from 'fs';
8
- import { join, extname, dirname, relative } from 'path';
9
- import { fileURLToPath } from 'url';
6
+ import { FileSystemRouter } from './router.js';
7
+ import { join } from 'path';
10
8
 
11
9
  export class FileBasedRouter {
12
- constructor(options = {}) {
13
- this.options = {
14
- pagesDir: options.pagesDir || 'pages',
15
- apiDir: options.apiDir || 'pages/api',
16
- layoutsDir: options.layoutsDir || 'layouts',
17
- middlewareDir: options.middlewareDir || 'middleware',
18
- ...options
19
- };
20
-
21
- this.routes = new Map();
22
- this.apiRoutes = new Map();
23
- this.layouts = new Map();
24
- this.middleware = new Map();
25
-
26
- this._scanRoutes();
27
- }
28
-
29
- /**
30
- * Scan pages directory and build route mappings
31
- */
32
- _scanRoutes() {
33
- const pagesPath = join(process.cwd(), this.options.pagesDir);
34
-
35
- try {
36
- this._scanDirectory(pagesPath, '');
37
- } catch (error) {
38
- console.warn('[Router] Pages directory not found:', pagesPath);
39
- }
40
- }
41
-
42
- /**
43
- * Recursively scan directory for route files
44
- */
45
- _scanDirectory(dirPath, routePrefix) {
46
- const items = readdirSync(dirPath);
47
-
48
- for (const item of items) {
49
- const itemPath = join(dirPath, item);
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
- console.log(`[HMR] Client connected from ${ws.remoteAddress}`);
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
- console.log(`[HMR] Client disconnected`);
48
+ frameworkLog('log', `[HMR] Client disconnected`);
48
49
  }
49
50
  });
50
51
 
51
52
  // Start file watching
52
53
  this.watchDirectory(process.cwd());
53
- console.log(`[HMR] Watching for changes...`);
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
- console.error(`[HMR] Failed to watch ${dir}:`, err.message);
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
- console.log(`[HMR] File changed: ${normalizedPath}`);
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
- console.log(`[HMR] Broadcasted update to ${successCount} client(s)`);
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
- console.log(`[HMR] Server stopped`);
186
+ frameworkLog('log', `[HMR] Server stopped`);
186
187
  }
187
188
  }
188
189