bro-framework 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,7 +109,7 @@ Stop importing singleton database connections and socket instances into every fi
109
109
  If you've ever hand-written OpenAPI YAML, you know the pain. `bro.js` parses your Zod schemas and automatically serves a stunning, interactive [Scalar](https://scalar.com/) API playground at `/docs`. It's highly secure: by default, these internal docs are disabled in production mode.
110
110
 
111
111
  ### The Frontend SDK Generator
112
- Tired of writing frontend `fetch` wrappers? Run `bro sdk`. The CLI will parse your backend routes and compile a `bro-client.js` file for your frontend. It features built-in token management, request stringification, and type-safe deep tree traversal (e.g., `api.users.id("123").post(data)`).
112
+ Tired of writing frontend `fetch` wrappers? Run `bro sdk`. The CLI will parse your backend routes and compile a `bro-sdk.js` file for your frontend. It features built-in token management, request stringification, and type-safe deep tree traversal (e.g., `api.users.id("123").post(data)`).
113
113
 
114
114
  ### Background Task Scheduler
115
115
  Don't spin up a separate worker server. Drop a JavaScript file anywhere in the `tasks/` folder, export a cron string (e.g., `"0 0 * * *"`), and an async handler. `bro.js` natively schedules it as a background worker with full access to your injected database and WebSocket contexts.
package/bin/bro.js CHANGED
@@ -9,7 +9,7 @@ register();
9
9
 
10
10
  import { createServer } from '../src/server.js';
11
11
  import { colors, printBanner, printRoute, printHotReload } from '../src/logger.js';
12
- import { scanTasks, stopTasks } from '../src/tasks.js';
12
+ import { scanTasks } from '../src/tasks.js';
13
13
  import { generateSDK } from '../src/sdk.js';
14
14
  import dotenv from 'dotenv';
15
15
  import chokidar from 'chokidar';
@@ -204,7 +204,7 @@ async function bootstrap() {
204
204
  console.log(`[bro.js] Server running in production on port ${port}`);
205
205
  }
206
206
 
207
- await scanTasks({ db, io });
207
+ let taskManager = await scanTasks({ db, io });
208
208
 
209
209
  if (command === 'dev') {
210
210
  const printCurrentRoutes = (routesToPrint) => {
@@ -240,7 +240,7 @@ async function bootstrap() {
240
240
 
241
241
  const handleShutdown = async (signal) => {
242
242
  console.log(`\n[bro.js] Received ${signal}. Shutting down gracefully...`);
243
- stopTasks();
243
+ if (taskManager) taskManager.stopAll();
244
244
  if (io) io.close();
245
245
  server.close(() => {
246
246
  console.log('[bro.js] HTTP server closed.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bro-framework",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "The No-BS Backend Framework for Node.js",
5
5
  "repository": {
6
6
  "type": "git",
package/src/logger.js CHANGED
@@ -1,3 +1,7 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
1
5
  export const colors = {
2
6
  reset: "\x1b[0m",
3
7
  bold: "\x1b[1m",
@@ -25,7 +29,16 @@ export function formatMethod(method) {
25
29
 
26
30
  export function printBanner(port, durationMs) {
27
31
  const time = durationMs.toFixed(0);
28
- const version = "2.0.1";
32
+
33
+ let version = "2.2.0";
34
+ try {
35
+ const __filename = fileURLToPath(import.meta.url);
36
+ const __dirname = path.dirname(__filename);
37
+ const pkgPath = path.join(__dirname, '..', 'package.json');
38
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
39
+ version = pkg.version;
40
+ } catch(e) {}
41
+
29
42
  const innerWidth = 47;
30
43
 
31
44
  const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
package/src/router.js CHANGED
@@ -43,6 +43,12 @@ export function parseRouteFile(filePath, routesDir) {
43
43
  if (parts.length < 2) return null;
44
44
 
45
45
  const method = parts.pop().toLowerCase();
46
+
47
+ const allowedMethods = new Set(['get', 'post', 'put', 'delete', 'patch', 'options', 'head']);
48
+ if (!allowedMethods.has(method)) {
49
+ throw new Error(`Invalid HTTP method "${method}" in file: ${filePath}`);
50
+ }
51
+
46
52
  const namePart = parts.join('.');
47
53
 
48
54
  let routePath = '/' + path.dirname(relativePath).replace(/\\/g, '/');
@@ -52,6 +58,15 @@ export function parseRouteFile(filePath, routesDir) {
52
58
  routePath += `/${namePart}`;
53
59
  }
54
60
 
61
+ const bracketRegex = /\[(.*?)\]/g;
62
+ let match;
63
+ while ((match = bracketRegex.exec(routePath)) !== null) {
64
+ const paramName = match[1];
65
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(paramName)) {
66
+ throw new Error(`Invalid dynamic parameter "[${paramName}]" in file: ${filePath}. Must be a valid JavaScript identifier.`);
67
+ }
68
+ }
69
+
55
70
  routePath = routePath.replace(/\[(.*?)\]/g, ':$1');
56
71
 
57
72
  if (routePath === '') routePath = '/';
package/src/sdk.js CHANGED
@@ -98,6 +98,10 @@ function generateApiObject(endpoints) {
98
98
 
99
99
  if (!current[name]) {
100
100
  current[name] = { _isParam: isParam, _methods: {}, _children: {}, _path: pathAcc };
101
+ } else {
102
+ if (current[name]._isParam !== isParam) {
103
+ throw new Error(`SDK Collision: Route segment "${name}" conflicts between static and dynamic parameters at path "${pathAcc}"`);
104
+ }
101
105
  }
102
106
 
103
107
  if (i === parts.length - 1) {
package/src/tasks.js CHANGED
@@ -5,21 +5,24 @@ import cron from 'node-cron';
5
5
  import { colors } from './logger.js';
6
6
  import { scanDir } from './router.js';
7
7
 
8
- let taskHandles = [];
9
-
10
- export function stopTasks() {
11
- taskHandles.forEach(t => t.stop());
12
- taskHandles = [];
8
+ export class TaskManager {
9
+ constructor() {
10
+ this.taskHandles = [];
11
+ }
12
+
13
+ stopAll() {
14
+ this.taskHandles.forEach(t => t.stop());
15
+ this.taskHandles = [];
16
+ }
13
17
  }
14
18
 
15
19
  export async function scanTasks(ctx) {
20
+ const manager = new TaskManager();
16
21
  const tasksDir = path.join(process.cwd(), 'tasks');
17
- if (!fs.existsSync(tasksDir)) return;
22
+ if (!fs.existsSync(tasksDir)) return manager;
18
23
 
19
- stopTasks();
20
-
21
24
  const files = scanDir(tasksDir);
22
- if (files.length === 0) return;
25
+ if (files.length === 0) return manager;
23
26
 
24
27
  let count = 0;
25
28
  for (const file of files) {
@@ -35,7 +38,7 @@ export async function scanTasks(ctx) {
35
38
  console.error(`\n ${colors.red}❌ Task Error (${file}):${colors.reset}`, err);
36
39
  }
37
40
  });
38
- taskHandles.push(task);
41
+ manager.taskHandles.push(task);
39
42
  count++;
40
43
  }
41
44
  } catch (err) {
@@ -46,4 +49,6 @@ export async function scanTasks(ctx) {
46
49
  if (count > 0) {
47
50
  console.log(` ${colors.dim}├──${colors.reset} ${colors.cyan}Scheduled ${count} background task(s)${colors.reset}`);
48
51
  }
52
+
53
+ return manager;
49
54
  }