bro-framework 1.2.8 → 2.0.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
@@ -5,6 +5,7 @@
5
5
  <strong>The zero-boilerplate Node.js framework that actually has your back.</strong>
6
6
  </p>
7
7
  <p align="center">
8
+ <a href="https://www.npmjs.com/package/bro-framework"><img src="https://img.shields.io/npm/v/bro-framework.svg?style=flat-square&color=e11d48" alt="npm version"></a>
8
9
  <img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="License: MIT">
9
10
  <img src="https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg?style=flat-square" alt="Node.js: >= 18">
10
11
  <img src="https://img.shields.io/badge/Architecture-Pure%20ESM-orange.svg?style=flat-square" alt="Architecture: Pure ESM">
@@ -16,7 +17,6 @@
16
17
  <a href="https://brojs.yessindevs.me">Documentation Website</a>
17
18
  </p>
18
19
  </p>
19
-
20
20
  ---
21
21
 
22
22
  > "NestJS wants four decorators, three modules, and an existential crisis just to handle a GET request. Express makes you write the same 40 lines of CORS, JSON parsing, and auth middleware for every project. bro.js gives you file routing, auto-validation, JWT auth, WebSockets, and live docs out of the box. Be honest: you just want to return an object."
@@ -25,6 +25,7 @@
25
25
 
26
26
  ## Table of Contents
27
27
 
28
+ - [Getting Started](#getting-started)
28
29
  - [The Core Experience](#the-core-experience)
29
30
  - [Deep-Dive Features](#deep-dive-features)
30
31
  - [Architecture & Request Lifecycle](#architecture--request-lifecycle)
@@ -34,6 +35,20 @@
34
35
 
35
36
  ---
36
37
 
38
+ ## Getting Started
39
+
40
+ Bootstrapping a new `bro.js` project is incredibly simple. We recommend using our official scaffolding tool to set everything up instantly (with your choice of JavaScript or TypeScript):
41
+
42
+ ```bash
43
+ npx create-bro-framework@latest my-api
44
+ cd my-api
45
+ npm run dev
46
+ ```
47
+
48
+ That's it! Your zero-boilerplate backend is now running with hot-reloading enabled.
49
+
50
+ ---
51
+
37
52
  ## The Core Experience
38
53
 
39
54
  In `bro.js`, everything you need is handed to you instantly. No setup, no middleware wrangling, no manual `req/res` handling. You define your route, set your validation, and return an object.
package/bin/bro.js CHANGED
@@ -3,6 +3,10 @@
3
3
  import path from 'path';
4
4
  import fs from 'fs';
5
5
  import { pathToFileURL } from 'url';
6
+ import { register } from 'tsx/esm/api';
7
+
8
+ register();
9
+
6
10
  import { createServer } from '../src/server.js';
7
11
  import { colors, printBanner, printRoute, printHotReload } from '../src/logger.js';
8
12
  import { scanTasks } from '../src/tasks.js';
@@ -44,6 +48,13 @@ export default defineConfig({
44
48
  max: 100 // limit each IP to 100 requests per windowMs
45
49
  },
46
50
 
51
+ // WebSockets Setup
52
+ sockets: async (io, db) => {
53
+ io.on('connection', (socket) => {
54
+ console.log('Client connected:', socket.id);
55
+ });
56
+ },
57
+
47
58
  // Database Context Injection
48
59
  // This instance will be injected into every route's ctx.db (if defined)
49
60
  db: async () => {
@@ -63,13 +74,6 @@ export default defineConfig({
63
74
  // return mongoose.connection;
64
75
  // --------------------------
65
76
  return null;
66
- },
67
-
68
- // WebSockets Setup
69
- sockets: async (io, db) => {
70
- io.on('connection', (socket) => {
71
- console.log('Client connected:', socket.id);
72
- });
73
77
  }
74
78
  });
75
79
  `;
@@ -118,10 +122,10 @@ if (command === 'init') {
118
122
 
119
123
  if (['sdk', 'generate-client', 'client'].includes(command)) {
120
124
  generateSDK().then(() => {
121
- console.log(`\n ${colors.green} bro-client.js generated successfully!${colors.reset}\n`);
125
+ console.log(`\n ${colors.green} bro-client.js generated successfully!${colors.reset}\n`);
122
126
  process.exit(0);
123
127
  }).catch(err => {
124
- console.error(`\n ${colors.red} Error generating SDK:${colors.reset}`, err.message);
128
+ console.error(`\n ${colors.red} Error generating SDK:${colors.reset}`, err.message);
125
129
  process.exit(1);
126
130
  });
127
131
  }
@@ -171,7 +175,7 @@ async function bootstrap() {
171
175
  if (globalConfig.env) {
172
176
  const envResult = globalConfig.env.safeParse(process.env);
173
177
  if (!envResult.success) {
174
- console.error(`\n ${colors.red} Environment Validation Failed${colors.reset}`);
178
+ console.error(`\n ${colors.red} Environment Validation Failed${colors.reset}`);
175
179
  envResult.error.errors.forEach(err => {
176
180
  console.error(` ${colors.dim}-${colors.reset} ${colors.bold}${err.path.join('.')}${colors.reset}: ${err.message}`);
177
181
  });
@@ -218,7 +222,7 @@ async function bootstrap() {
218
222
  const watcher = chokidar.watch(routesDir, { ignoreInitial: true });
219
223
 
220
224
  watcher.on('all', async (event, filepath) => {
221
- if (!filepath.endsWith('.js')) return;
225
+ if (!filepath.endsWith('.js') && !filepath.endsWith('.ts')) return;
222
226
 
223
227
  try {
224
228
  const reloadStartTime = performance.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bro-framework",
3
- "version": "1.2.8",
3
+ "version": "2.0.1",
4
4
  "description": "The No-BS Backend Framework for Node.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,9 +10,13 @@
10
10
  "author": "Yessindevs",
11
11
  "license": "MIT",
12
12
  "main": "src/index.js",
13
+ "types": "src/index.d.ts",
13
14
  "type": "module",
14
15
  "exports": {
15
- ".": "./src/index.js"
16
+ ".": {
17
+ "types": "./src/index.d.ts",
18
+ "default": "./src/index.js"
19
+ }
16
20
  },
17
21
  "bin": {
18
22
  "bro": "./bin/bro.js"
@@ -32,6 +36,7 @@
32
36
  "multer": "^2.3.0",
33
37
  "node-cron": "^4.6.0",
34
38
  "socket.io": "^4.8.3",
39
+ "tsx": "^4.23.13",
35
40
  "zod": "^3.23.8",
36
41
  "zod-to-json-schema": "^3.25.2"
37
42
  },
package/src/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { ZodType } from 'zod';
1
+ import { z, ZodTypeAny } from 'zod';
2
2
 
3
- export interface BroContext {
4
- body?: any;
5
- params?: any;
6
- query?: any;
3
+ type InferZod<T> = T extends ZodTypeAny ? z.infer<T> : any;
4
+
5
+ export interface BroContext<Body = any, Params = any, Query = any> {
6
+ body: InferZod<Body>;
7
+ params: InferZod<Params>;
8
+ query: InferZod<Query>;
7
9
  user?: any;
8
10
  db?: any;
9
11
  io?: any;
@@ -11,24 +13,26 @@ export interface BroContext {
11
13
  error?: any;
12
14
  }
13
15
 
14
- export interface RouteConfig {
16
+ export interface RouteConfig<Body = any, Params = any, Query = any> {
15
17
  auth?: boolean;
16
18
  upload?: boolean;
17
- body?: ZodType<any, any, any>;
18
- params?: ZodType<any, any, any>;
19
- query?: ZodType<any, any, any>;
19
+ body?: Body;
20
+ params?: Params;
21
+ query?: Query;
20
22
  rateLimit?: {
21
23
  windowMs: number;
22
24
  max: number;
23
25
  };
24
26
  summary?: string;
25
- handler: (ctx: BroContext) => Promise<any> | any;
27
+ handler: (ctx: BroContext<Body, Params, Query>) => Promise<any> | any;
26
28
  }
27
29
 
28
- export function defineRoute(config: RouteConfig): RouteConfig;
30
+ export function defineRoute<Body = any, Params = any, Query = any>(
31
+ config: RouteConfig<Body, Params, Query>
32
+ ): RouteConfig<Body, Params, Query>;
29
33
 
30
34
  export interface BroConfig {
31
- env?: ZodType<any, any, any>;
35
+ env?: ZodTypeAny;
32
36
  server?: {
33
37
  port?: number;
34
38
  cors?: boolean | object;
package/src/logger.js CHANGED
@@ -25,17 +25,29 @@ export function formatMethod(method) {
25
25
 
26
26
  export function printBanner(port, durationMs) {
27
27
  const time = durationMs.toFixed(0);
28
- const version = "1.0.0";
29
-
28
+ const version = "2.0.1";
29
+ const innerWidth = 47;
30
+
31
+ const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
32
+
33
+ const formatLine = (content = "") => {
34
+ const visibleLength = stripAnsi(content).length;
35
+ const padding = Math.max(0, innerWidth - visibleLength);
36
+ return `${colors.green}│${colors.reset}${content}${" ".repeat(padding)}${colors.green}│${colors.reset}`;
37
+ };
38
+
39
+ const borderTop = `${colors.green}╭${"─".repeat(innerWidth)}╮${colors.reset}`;
40
+ const borderBottom = `${colors.green}╰${"─".repeat(innerWidth)}╯${colors.reset}`;
41
+
30
42
  console.log("");
31
- console.log(`${colors.green}╭───────────────────────────────────────────────╮${colors.reset}`);
32
- console.log(`${colors.green}│${colors.reset} ${colors.green}│${colors.reset}`);
33
- console.log(`${colors.green}│${colors.reset} ${colors.bold}bro.js${colors.reset} v${version} ${colors.green}│${colors.reset}`);
34
- console.log(`${colors.green}│${colors.reset} ${colors.green}│${colors.reset}`);
35
- console.log(`${colors.green}│${colors.reset} ➜ ${colors.bold}Local:${colors.reset} ${colors.cyan}http://localhost:${port}${colors.reset} ${colors.green}│${colors.reset}`);
36
- console.log(`${colors.green}│${colors.reset} ➜ ${colors.bold}Ready in:${colors.reset} ${colors.yellow}${time}ms${colors.reset} ${colors.green}│${colors.reset}`);
37
- console.log(`${colors.green}│${colors.reset} ${colors.green}│${colors.reset}`);
38
- console.log(`${colors.green}╰───────────────────────────────────────────────╯${colors.reset}`);
43
+ console.log(borderTop);
44
+ console.log(formatLine());
45
+ console.log(formatLine(` ${colors.bold}bro.js${colors.reset} v${version}`));
46
+ console.log(formatLine());
47
+ console.log(formatLine(` ➜ ${colors.bold}Local:${colors.reset} ${colors.cyan}http://localhost:${port}${colors.reset}`));
48
+ console.log(formatLine(` ➜ ${colors.bold}Ready in:${colors.reset} ${colors.yellow}${time}ms${colors.reset}`));
49
+ console.log(formatLine());
50
+ console.log(borderBottom);
39
51
  console.log("");
40
52
  }
41
53
 
package/src/router.js CHANGED
@@ -18,7 +18,7 @@ export function scanDir(dir, fileList = []) {
18
18
  const filePath = path.join(dir, file);
19
19
  if (fs.statSync(filePath).isDirectory()) {
20
20
  scanDir(filePath, fileList);
21
- } else if (filePath.endsWith('.js')) {
21
+ } else if (filePath.endsWith('.js') || filePath.endsWith('.ts')) {
22
22
  fileList.push(filePath);
23
23
  }
24
24
  }