bro-framework 2.4.0 → 2.4.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.
Files changed (3) hide show
  1. package/README.md +112 -3
  2. package/bin/bro.js +20 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,8 +27,11 @@
27
27
  ## Table of Contents
28
28
 
29
29
  - [Getting Started](#getting-started)
30
+ - [Configuration](#configuration)
30
31
  - [The Core Experience](#the-core-experience)
31
32
  - [Deep-Dive Features](#deep-dive-features)
33
+ - [Route Reference](#route-reference)
34
+ - [Context Reference](#context-reference)
32
35
  - [Architecture & Request Lifecycle](#architecture--request-lifecycle)
33
36
  - [Tech Stack Breakdown](#tech-stack-breakdown)
34
37
  - [CLI Reference](#cli-reference)
@@ -48,6 +51,38 @@ npm run dev
48
51
 
49
52
  That's it! Your zero-boilerplate backend is now running with hot-reloading enabled.
50
53
 
54
+ ## Configuration
55
+
56
+ Configuration lives in `bro.config.js`:
57
+
58
+ ```javascript
59
+ import { defineConfig } from 'bro-framework';
60
+
61
+ export default defineConfig({
62
+ server: { port: 5000, cors: true, helmet: true },
63
+ auth: {
64
+ jwtSecret: process.env.JWT_SECRET,
65
+ expiresIn: '7d',
66
+ apiKey: process.env.API_KEY
67
+ },
68
+ locale: { directory: './locale', defaultLocale: 'en' },
69
+ rateLimit: { windowMs: 15 * 60 * 1000, max: 100 },
70
+ docs: process.env.NODE_ENV !== 'production',
71
+ redisUrl: process.env.REDIS_URL,
72
+ db: async () => {
73
+ // Initialize database connection here
74
+ },
75
+ sockets: async (io, db) => {
76
+ // Setup Socket.IO event listeners here
77
+ },
78
+ onShutdown: async (db) => {
79
+ // Close application-owned database resources here.
80
+ }
81
+ });
82
+ ```
83
+
84
+ `cors: false` disables HTTP CORS middleware. Helmet is enabled by default and can be disabled with `helmet: false`. Redis is optional; when configured it powers distributed rate limiting, route caching, and Socket.IO scaling. In `NODE_ENV=test`, bro.js can inject `ioredis-mock`; install it in the consuming project's development dependencies.
85
+
51
86
  ---
52
87
 
53
88
  ## The Core Experience
@@ -68,6 +103,13 @@ export default defineRoute({
68
103
  title: z.string().min(5),
69
104
  content: z.string()
70
105
  }),
106
+ query: z.object({
107
+ draft: z.coerce.boolean().default(false)
108
+ }),
109
+ response: z.object({
110
+ success: z.boolean(),
111
+ updated: z.number()
112
+ }),
71
113
  handler: async ({ body, params, user, db, io }) => {
72
114
  // 1. Data is already validated. user is already authenticated.
73
115
 
@@ -89,6 +131,8 @@ export default defineRoute({
89
131
  });
90
132
  ```
91
133
 
134
+ Validation schemas are flat and must be declared directly as `body`, `params`, and `query`. The deprecated nested `schema: { body, params, query }` form is rejected during route loading. The optional `response` schema documents the successful JSON response in OpenAPI; it does not runtime-validate handler output.
135
+
92
136
  ---
93
137
 
94
138
  ## Deep-Dive Features
@@ -101,22 +145,47 @@ Powered by Zod. Attach a schema to `body`, `query`, or `params` directly in your
101
145
 
102
146
  ### Zero-Config Auth (JWTs, RBAC, API Keys)
103
147
  Add `auth: true` to your route config. `bro.js` will intercept the request, extract the `Authorization: Bearer <token>` header, verify the signature using your `jwtSecret`, and inject the decoded payload directly into `ctx.user`.
104
- You can also use Role-Based Access Control by passing an array of roles (e.g. `auth: ['admin']`) or enforce strict service-to-service communication by using `auth: 'api-key'`. API Keys fully support zero-downtime rotation by accepting an array of valid keys in `bro.config.js`.
148
+ You can also use Role-Based Access Control by passing an array of roles (e.g. `auth: ['admin']`) or enforce service-to-service communication with `auth: 'api-key'`. API keys are read from `auth.apiKey` or `API_KEY` and support zero-downtime rotation with an array:
149
+
150
+ ```javascript
151
+ auth: {
152
+ apiKey: ['current-key', 'next-key']
153
+ }
154
+ ```
155
+
156
+ Clients send the selected key in the `x-api-key` header. API-key routes are represented as `apiKeyAuth` operations in OpenAPI.
105
157
 
106
158
  ### Context Injection
107
159
  Stop importing singleton database connections and socket instances into every file. Define your `db` and `sockets` setup once in `bro.config.js`. `bro.js` orchestrates the initialization and injects both instances directly into the `ctx` object for every request handler.
108
160
 
161
+ ### Redis Caching and Lifecycle
162
+ Set `redisUrl` to enable distributed rate limiting, route caching, and Socket.IO pub/sub scaling. Add `cache: 60` to a route to cache its JSON response for 60 seconds. Cache keys include the request URL, resolved locale, and authorization/API-key identity; do not cache responses with dimensions that are not represented in the key.
163
+
164
+ The programmatic `createServer()` API returns `shutdown()`. It stops scheduled tasks, closes Socket.IO, closes Redis clients, runs the optional `onShutdown(db)` hook, and closes the HTTP server. The CLI calls it automatically on `SIGINT` and `SIGTERM`.
165
+
109
166
  ### Zero-YAML Live Documentation
110
167
  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.
111
168
 
112
169
  ### The Frontend SDK Generator
113
- 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)`).
170
+ Tired of writing frontend `fetch` wrappers? Run `bro sdk`. The CLI parses your backend routes and compiles a JavaScript `bro-sdk.js` file. It includes token and locale headers, query serialization, URL-encoded dynamic parameters, and deep tree traversal (e.g., `api.users.id("123").get()`). Configure it with `setBaseURL()`, `setTokenKey()`, and `setLocale()`.
114
171
 
115
172
  ### Background Task Scheduler
116
173
  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.
117
174
 
118
175
  ### Zero-Boilerplate File Uploads
119
- Add `upload: true` to a route. `bro.js` automatically hooks into `multer`, parses the `multipart/form-data` payload in memory, and injects the files directly into `ctx.files`. It also natively supports granular file limits, restricting max sizes, parts, and fielding counts instantly to protect your RAM. (Note: Use the `storage` configuration for heavy production disk writing to prevent memory exhaustion).
176
+ Add `upload: true` to a route. bro.js uses Multer to parse `multipart/form-data` and inject files into the context. Use `single`, `array`, or `fields` for explicit field handling:
177
+
178
+ ```javascript
179
+ export default defineRoute({
180
+ upload: {
181
+ single: 'avatar',
182
+ limits: { fileSize: 5 * 1024 * 1024 }
183
+ },
184
+ handler: ({ file }) => ({ name: file?.originalname })
185
+ });
186
+ ```
187
+
188
+ `ctx.file` is used by `single()`. `ctx.files` is an array for `array()` or a field-to-array object for `fields()`. Defaults include limits for file size, file count, fields, parts, and field size. Use `storage` for production disk/object-storage integration instead of retaining large buffers in memory.
120
189
 
121
190
  ### File-Based Locale
122
191
  Create a `locale/` folder with one translation file per locale, such as `locale/en.js` and `locale/fr.js`. Export a plain object from each file, then use `t()` in any route:
@@ -133,6 +202,46 @@ export default defineRoute({
133
202
 
134
203
  The locale is negotiated dynamically using RFC 9110 `Accept-Language` headers, supporting full region fallback and custom defaults, and the generated SDK can securely set it via `setLocale('fr')`.
135
204
 
205
+ ## Route Reference
206
+
207
+ | Option | Type | Purpose |
208
+ | :--- | :--- | :--- |
209
+ | `auth` | `boolean \| string[] \| 'api-key'` | JWT, role, or API-key protection. |
210
+ | `body` | Zod schema | Validates JSON request bodies. |
211
+ | `params` | Zod schema | Validates URL parameters. |
212
+ | `query` | Zod schema | Validates query-string values. |
213
+ | `response` | Zod schema | Documents successful JSON output in OpenAPI. |
214
+ | `cache` | number | Redis response-cache duration in seconds. |
215
+ | `rateLimit` | `{ windowMs, max }` | Route-specific request limiting. |
216
+ | `upload` | boolean or options | Enables Multer parsing and limits. |
217
+ | `summary` | string | OpenAPI operation summary. |
218
+
219
+ Dynamic route files use bracket parameters such as `routes/users/[id].get.js`. Nested dynamic directories are supported. HTTP method suffixes are `get`, `post`, `put`, `delete`, `patch`, `options`, and `head`.
220
+
221
+ ## Context Reference
222
+
223
+ Handlers receive:
224
+
225
+ | Property | Description |
226
+ | :--- | :--- |
227
+ | `body` | Validated body data. |
228
+ | `params` | Validated path parameters. |
229
+ | `query` | Validated query data. |
230
+ | `user` | Decoded JWT payload when JWT auth is used. |
231
+ | `db` | Value returned by `config.db`. |
232
+ | `io` | Socket.IO server instance. |
233
+ | `redis` | Redis client when Redis is enabled or test mode is active. |
234
+ | `file` | Single uploaded file from `upload.single()`. |
235
+ | `files` | Array or field map from `array()`/`fields()`. |
236
+ | `locale` | Resolved request locale. |
237
+ | `t` | Translation function, `t(key, values)`. |
238
+ | `jwt` | Configured JWT signing helper. |
239
+ | `env` | Parsed environment data when configured, otherwise `process.env`. |
240
+
241
+ ## Testing
242
+
243
+ For Redis-backed integration tests without an external Redis server, install `ioredis-mock` in the consuming project and run with `NODE_ENV=test`. bro.js injects a mock Redis client and exercises cache, rate-limit, Socket.IO adapter, and shutdown paths.
244
+
136
245
  ---
137
246
 
138
247
  ## Architecture & Request Lifecycle
package/bin/bro.js CHANGED
@@ -29,13 +29,15 @@ export default defineConfig({
29
29
  // Server Settings
30
30
  server: {
31
31
  port: 5000,
32
- cors: true // Set to true to allow all, or pass a CORS options object
32
+ cors: true, // Set to true to allow all, or pass a CORS options object
33
+ helmet: true // Enable security headers
33
34
  },
34
35
 
35
36
  // Authentication Settings
36
37
  auth: {
37
38
  jwtSecret: 'dev_secret_please_change',
38
- expiresIn: '7d'
39
+ expiresIn: '7d',
40
+ apiKey: process.env.API_KEY || ['dev_key_1', 'dev_key_2'] // Supports array for zero-downtime rotation
39
41
  },
40
42
 
41
43
  // Optional file-based API translations
@@ -53,6 +55,9 @@ export default defineConfig({
53
55
  max: 100 // limit each IP to 100 requests per windowMs
54
56
  },
55
57
 
58
+ // Redis Configuration (Auto-scales WebSockets, distributed caches & rate-limiting)
59
+ redisUrl: process.env.REDIS_URL, // e.g., 'redis://localhost:6379'
60
+
56
61
  // WebSockets Setup
57
62
  sockets: async (io, db) => {
58
63
  io.on('connection', (socket) => {
@@ -79,6 +84,11 @@ export default defineConfig({
79
84
  // return mongoose.connection;
80
85
  // --------------------------
81
86
  return null;
87
+ },
88
+
89
+ // Graceful Teardown Hook
90
+ onShutdown: async (db) => {
91
+ // Close application-owned database resources gracefully here
82
92
  }
83
93
  });
84
94
  `;
@@ -264,6 +274,12 @@ async function bootstrap() {
264
274
 
265
275
  if (command === 'dev' || command === 'start') {
266
276
  bootstrap();
267
- } else {
268
- console.log(`Usage: bro dev | bro start | bro init`);
277
+ } else if (!['sdk', 'generate-client', 'client', 'init'].includes(command)) {
278
+ console.log(`\n ${colors.bold}${colors.green}bro.js CLI${colors.reset}\n`);
279
+ console.log(` ${colors.bold}Usage:${colors.reset} bro <command>\n`);
280
+ console.log(` ${colors.bold}Commands:${colors.reset}`);
281
+ console.log(` ${colors.cyan}dev${colors.reset} Start the development server with hot-reload`);
282
+ console.log(` ${colors.cyan}start${colors.reset} Start the production server gracefully`);
283
+ console.log(` ${colors.cyan}init${colors.reset} Scaffold a new bro.config.js workspace`);
284
+ console.log(` ${colors.cyan}sdk${colors.reset} Generate a typed frontend client\n`);
269
285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bro-framework",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "The No-BS Backend Framework for Node.js",
5
5
  "repository": {
6
6
  "type": "git",