bro-framework 3.0.0 → 3.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/bin/bro.js CHANGED
@@ -32,7 +32,7 @@ async function bootstrap() {
32
32
  }
33
33
  }
34
34
 
35
- const routesDir = globalConfig.routesDir || path.resolve(process.cwd(), 'api');
35
+ const routesDir = globalConfig.routesDir || path.resolve(process.cwd(), 'routes');
36
36
  const localeDir = globalConfig.locale?.directory || path.resolve(process.cwd(), 'locale');
37
37
  const tasksDir = globalConfig.tasksDir || path.resolve(process.cwd(), 'tasks');
38
38
  const port = globalConfig.server?.port || process.env.PORT || 3000;
@@ -138,15 +138,92 @@ async function bootstrap() {
138
138
  const configPath = path.resolve(process.cwd(), 'bro.config.js');
139
139
  const envPath = path.resolve(process.cwd(), '.env.example');
140
140
  if (!fs.existsSync(configPath)) {
141
- fs.writeFileSync(configPath, `export default {
142
- server: { port: 3000, cors: false },
143
- auth: { jwtSecret: process.env.JWT_SECRET }
144
- };
141
+ fs.writeFileSync(configPath, `import { defineConfig } from 'bro-framework';
142
+
143
+ export default defineConfig({
144
+ // Server Settings
145
+ server: {
146
+ port: 5000,
147
+ cors: process.env.NODE_ENV === 'production' ? ['https://yourdomain.com'] : true, // Set to true to allow all, or pass a CORS options object
148
+ helmet: true // Enable security headers
149
+ },
150
+
151
+ // Authentication Settings
152
+ auth: {
153
+ jwtSecret: process.env.JWT_SECRET || 'dev_secret_please_change', // Must be at least 32 characters in production
154
+ expiresIn: '7d',
155
+ //apiKey: process.env.API_KEY || ['dev_key_1', 'dev_key_2'] // Supports array for zero-downtime rotation
156
+ },
157
+
158
+ // Trust reverse proxy IP headers (Nginx/Cloudflare)
159
+ trustProxy: true,
160
+
161
+ // Observability & Telemetry
162
+ observability: {
163
+ // Output structured JSON logs with request IDs and execution timing (ideal for CloudWatch/Datadog)
164
+ // Options: 'json' | 'pretty' (default: 'pretty' in dev, 'json' in production)
165
+ logging: 'json',
166
+
167
+ // Enable OpenTelemetry W3C trace propagation and HTTP span generation
168
+ openTelemetry: true
169
+ }
170
+
171
+ // Optional file-based API translations
172
+ // Add locale/en.js, locale/fr.js, etc.
173
+ locale: {
174
+ defaultLocale: 'en'
175
+ },
176
+
177
+ // API Documentation (Scalar UI)
178
+ docs: process.env.NODE_ENV !== 'production', // Set to false to disable completely, or true to force in prod
179
+
180
+ // Rate Limiting
181
+ rateLimit: {
182
+ windowMs: 15 * 60 * 1000, // 15 minutes
183
+ max: 100 // limit each IP to 100 requests per windowMs
184
+ },
185
+
186
+ // Redis Configuration (Auto-scales WebSockets, distributed caches & rate-limiting)
187
+ redisUrl: process.env.REDIS_URL, // e.g., 'redis://localhost:6379'
188
+
189
+ // WebSockets Setup
190
+ sockets: async (io, db) => {
191
+ io.on('connection', (socket) => {
192
+ console.log('Client connected:', socket.id);
193
+ });
194
+ },
195
+
196
+ // Database Context Injection
197
+ // This instance will be injected into every route's ctx.db (if defined)
198
+ db: async () => {
199
+ // If you use a database, set up your connection here
200
+ // and return the connection instance or an object of your models.
201
+ // Could be MongoDB, MySQL, etc. (your choice)
202
+ // --- MONGOOSE EXAMPLE ---
203
+ // import mongoose from 'mongoose';
204
+
205
+ // await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/bro_database');
206
+ // console.log("Connected to MongoDB");
207
+
208
+ // You can return mongoose itself, or an object of your models
209
+ // to access them instantly in your routes without importing them!
210
+ // Example: return { User, Post };
211
+
212
+ // return mongoose.connection;
213
+ // --------------------------
214
+ return null;
215
+ },
216
+
217
+ // Graceful Teardown Hook
218
+ onShutdown: async (db) => {
219
+ // Close application-owned database resources gracefully here
220
+ }
221
+ });
145
222
  `);
146
223
  console.log('[bro.js] Created bro.config.js');
147
224
  }
148
225
  if (!fs.existsSync(envPath)) {
149
- fs.writeFileSync(envPath, 'JWT_SECRET=\n');
226
+ fs.writeFileSync(envPath, 'JWT_SECRET=your_jwt_secret_here\nNODE_ENV=development\nREDIS_URL=redis://localhost:6379\n');
150
227
  console.log('[bro.js] Created .env.example');
151
228
  }
152
229
  } else if (command === 'doctor') {
@@ -160,7 +237,7 @@ async function bootstrap() {
160
237
  if (config.server?.cors === true) console.error('✗ Permissive CORS is enabled (cors: true). Use an array of allowed origins.');
161
238
  else console.log('✓ CORS is strict.');
162
239
 
163
- if (['dev_secret_please_change', 'bro_default_secret_key', 'your_jwt_secret_here'].includes(config.auth?.jwtSecret)) {
240
+ if (['dev_secret_please_change', 'bro_default_secret_key', 'your_jwt_secret_here', ''].includes(config.auth?.jwtSecret?.trim())) {
164
241
  console.error('✗ Hardcoded insecure JWT secret detected.');
165
242
  } else {
166
243
  console.log('✓ Secrets look ok.');
@@ -168,9 +245,17 @@ async function bootstrap() {
168
245
  });
169
246
  }
170
247
  } else if (command === 'sdk' || command === 'client' || command === 'generate-client') {
171
- const sdkOutPath = process.argv[3] || './client.ts';
172
- const routesDir = path.resolve(process.cwd(), 'api');
173
- generateSDK(routesDir, sdkOutPath).then(() => {
248
+ const sdkOutPath = process.argv[3] || './client.ts';
249
+ const configPath = path.resolve(process.cwd(), 'bro.config.js');
250
+ let globalConfig = {};
251
+ if (fs.existsSync(configPath)) {
252
+ try {
253
+ const configModule = await import(configPath);
254
+ globalConfig = configModule.default || configModule;
255
+ } catch (e) {}
256
+ }
257
+ const routesDir = globalConfig.routesDir || path.resolve(process.cwd(), 'routes');
258
+ generateSDK(routesDir, sdkOutPath).then(() => {
174
259
  console.log(`\x1b[32m✓ SDK successfully generated at ${sdkOutPath}\x1b[0m`);
175
260
  }).catch(err => {
176
261
  console.error('\x1b[31m✗ Failed to generate SDK:\x1b[0m', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bro-framework",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "The No-BS Backend Framework for Node.js",
5
5
  "repository": {
6
6
  "type": "git",
package/src/dashboard.js CHANGED
@@ -8,7 +8,8 @@ import path from 'path';
8
8
  */
9
9
  export function startDashboard(globalConfig, routes) {
10
10
  const host = '127.0.0.1'; // Strict local binding
11
- const port = (globalConfig.port || 5000) + 1;
11
+ const basePort = globalConfig.server?.port || globalConfig.port || process.env.PORT || 3000;
12
+ const port = Number(basePort) + 1;
12
13
 
13
14
  const server = http.createServer((req, res) => {
14
15
  // Restrict access to localhost strictly
package/src/engine.js CHANGED
@@ -92,6 +92,12 @@ export async function executeRequest(routeConfig, requestData, globalConfig, ctx
92
92
 
93
93
  try {
94
94
  const ctx = {
95
+ req: requestData.originalUrl,
96
+ method: requestData.method,
97
+ ip: requestData.ip,
98
+ headers: requestData.headers,
99
+ locale: requestData.locale,
100
+ requestId: reqId,
95
101
  ...ctxExtras,
96
102
  env: globalConfig.envData || process.env,
97
103
  body: requestData.body,
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /// <reference types="node" />
1
+ /// <reference types="node" />
2
2
  import { z, ZodTypeAny } from 'zod';
3
3
 
4
4
  type InferZod<T> = T extends ZodTypeAny ? z.infer<T> : any;
@@ -15,12 +15,12 @@ export interface UploadedFile {
15
15
  buffer?: Buffer;
16
16
  }
17
17
 
18
- export interface AppContext<Env = any, Db = any, User = any> {
19
- env: Env;
20
- db: Db;
21
- user: User;
22
- }
23
-
18
+ export interface AppContext<Env = any, Db = any, User = any> {
19
+ env: Env;
20
+ db: Db;
21
+ user: User;
22
+ }
23
+
24
24
  export interface BroContext<Body = any, Params = any, Query = any, App extends AppContext = AppContext> {
25
25
  env?: App['env'];
26
26
  jwt?: { sign: (payload: any, options?: any) => string };
@@ -36,10 +36,11 @@ export interface BroContext<Body = any, Params = any, Query = any, App extends A
36
36
  t: (key: string, values?: Record<string, unknown>) => string;
37
37
  error?: any;
38
38
  redis?: any;
39
+ requestId?: string;
39
40
  }
40
41
 
41
42
  export interface RouteConfig<Body = any, Params = any, Query = any, Response = any, App extends AppContext = AppContext> {
42
- auth?: boolean | string[] | 'api-key';
43
+ auth?: boolean | string[] | 'api-key';
43
44
  operationId?: string;
44
45
  upload?: boolean | { limits?: any, fields?: { name: string, maxCount?: number }[], single?: string, array?: string, fileFilter?: any, storage?: any };
45
46
  body?: Body;
@@ -66,24 +67,40 @@ export function loadLocale(directory: string, options?: { defaultLocale?: string
66
67
  translate: (locale: string, key: string, values?: Record<string, unknown>) => string;
67
68
  }>;
68
69
 
69
-
70
- export interface BroPlugin {
71
- name: string;
72
- version: string;
73
- order?: number;
74
- onInit?: (globalConfig: BroConfig, app: any) => void | Promise<void>;
75
- onContext?: (ctx: BroContext) => any | Promise<any>;
76
- onRequest?: (req: any, res: any) => void | Promise<void>;
77
- onError?: (err: any, req: any, res: any) => void | Promise<void>;
78
- onShutdown?: () => void | Promise<void>;
79
- }
80
- export interface BroConfig {
81
- validateResponse?: boolean | 'strict' | 'warn';
70
+
71
+ export interface BroPlugin {
72
+ name: string;
73
+ version: string;
74
+ order?: number;
75
+ onInit?: (globalConfig: BroConfig, app: any) => void | Promise<void>;
76
+ onContext?: (ctx: BroContext) => any | Promise<any>;
77
+ onRequest?: (req: any, res: any) => void | Promise<void>;
78
+ onError?: (err: any, req: any, res: any) => void | Promise<void>;
79
+ onShutdown?: () => void | Promise<void>;
80
+ }
81
+ export interface BroConfig {
82
+ routesDir?: string;
83
+ tasksDir?: string;
84
+ logger?: { level?: string; [key: string]: any };
85
+ plugins?: BroPlugin[];
86
+ fixtures?: Record<string, any>;
87
+ stores?: Record<string, any>;
88
+ health?: boolean | { dbCheck?: (db: any) => Promise<any> };
89
+ locales?: Record<string, any>;
90
+ defaultLocale?: string;
91
+ envData?: any;
92
+ redis?: any;
93
+ port?: number;
94
+ jwtSecret?: string;
95
+ trustProxy?: boolean;
96
+ validateResponse?: boolean | 'strict' | 'warn';
82
97
  env?: ZodTypeAny;
83
98
  server?: {
84
99
  port?: number;
85
100
  cors?: boolean | object;
86
101
  helmet?: boolean | object;
102
+ timeoutMs?: number;
103
+ headersTimeoutMs?: number;
87
104
  };
88
105
  locale?: {
89
106
  directory?: string;
package/src/next.d.ts CHANGED
@@ -12,9 +12,33 @@ export interface UploadedFile {
12
12
  }
13
13
 
14
14
  export interface NextBroGlobalConfig<TEnv = any, TDb = any, TUser = any> {
15
- env?: ZodTypeAny;
15
+ routesDir?: string;
16
+ tasksDir?: string;
17
+ logger?: { level?: string; [key: string]: any };
18
+ plugins?: any[];
19
+ fixtures?: Record<string, any>;
20
+ stores?: Record<string, any>;
21
+ health?: boolean | { dbCheck?: (db: any) => Promise<any> };
16
22
  locales?: Record<string, any>;
17
23
  defaultLocale?: string;
24
+ envData?: any;
25
+ redis?: any;
26
+ port?: number;
27
+ jwtSecret?: string;
28
+ trustProxy?: boolean;
29
+ validateResponse?: boolean | 'strict' | 'warn';
30
+ env?: ZodTypeAny;
31
+ server?: {
32
+ port?: number;
33
+ cors?: boolean | object;
34
+ helmet?: boolean | object;
35
+ timeoutMs?: number;
36
+ headersTimeoutMs?: number;
37
+ };
38
+ locale?: {
39
+ directory?: string;
40
+ defaultLocale?: string;
41
+ };
18
42
  redisUrl?: string;
19
43
  rateLimit?: { windowMs: number; max: number; };
20
44
  auth?: {
@@ -22,7 +46,18 @@ export interface NextBroGlobalConfig<TEnv = any, TDb = any, TUser = any> {
22
46
  apiKey?: string | string[];
23
47
  expiresIn?: string | number;
24
48
  };
49
+ docs?: boolean | { auth?: { user: string; pass: string } };
50
+ upload?: {
51
+ limits?: {
52
+ fileSize?: number;
53
+ files?: number;
54
+ fields?: number;
55
+ [key: string]: any;
56
+ };
57
+ };
25
58
  db?: TDb | Promise<TDb> | (() => TDb | Promise<TDb>) | { init: () => TDb | Promise<TDb> };
59
+ sockets?: (io: any, db: any) => Promise<void> | void;
60
+ onShutdown?: (db: any) => Promise<void> | void;
26
61
  }
27
62
 
28
63
  export interface NextRouteContext<TBody = any, TQuery = any, TParams = any, TEnv = any, TDb = any, TUser = any> {
@@ -37,6 +72,11 @@ export interface NextRouteContext<TBody = any, TQuery = any, TParams = any, TEnv
37
72
  file?: UploadedFile;
38
73
  files?: Record<string, UploadedFile[]>;
39
74
  locale: string;
75
+ method: string;
76
+ ip: string;
77
+ headers: Record<string, string>;
78
+ requestId: string;
79
+ logger: any;
40
80
  t: (key: string, values?: any) => string;
41
81
  user?: TUser;
42
82
  jwt: { sign: (payload: any, opts?: any) => string };
@@ -52,6 +92,7 @@ export interface NextRouteConfig<TBody = any, TQuery = any, TParams = any, TEnv
52
92
  rateLimit?: { windowMs: number; max: number; } | false;
53
93
  response?: ZodTypeAny;
54
94
  summary?: string;
95
+ operationId?: string;
55
96
  upload?: any;
56
97
  handler: (ctx: NextRouteContext<TBody, TQuery, TParams, TEnv, TDb, TUser>) => Promise<any> | any;
57
98
  }