@techiev2/vajra 1.0.1 → 1.1.0

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 ADDED
@@ -0,0 +1,193 @@
1
+ # Vajra ⚡
2
+
3
+ ![Vajra Thunderbolt](assets/vajra.jpg) <!-- or use one of the above hosted URLs if you like -->
4
+
5
+
6
+ **Ultra-minimal, zero-dependency Node.js HTTP server**
7
+ Routing · Middleware · Multipart parsing · HTML templating
8
+ All in **111 lines** of pure JavaScript
9
+
10
+ ## Name Origin
11
+
12
+ Vajra draws from the Rigvedic thunderbolt weapon of Indra — crafted from the bones of Sage Dadhichi, symbolizing unbreakable strength through selfless sacrifice.
13
+
14
+ Like the Vajra, this server delivers maximum power in minimal form.
15
+
16
+
17
+ [![npm version](https://img.shields.io/npm/v/vajra.svg?style=flat-square)](https://www.npmjs.com/package/vajra)
18
+ [![npm downloads](https://img.shields.io/npm/dm/vajra.svg?style=flat-square)](https://www.npmjs.com/package/vajra)
19
+ [![Node.js version](https://img.shields.io/node/v/vajra.svg?style=flat-square)](https://nodejs.org)
20
+ [![License](https://img.shields.io/npm/l/vajra.svg?style=flat-square)](LICENSE)
21
+
22
+ ## Features
23
+
24
+ - Zero external dependencies
25
+ - Built-in routing with named parameters (`:id`)
26
+ - Global middleware support with `next()` chaining
27
+ - JSON, urlencoded, and **multipart/form-data** body parsing
28
+ - Fast HTML templating with loops, nested objects, and simple array headers
29
+ - Helper methods: `res.json()`, `res.html()`, `res.status()`, `res.writeMessage()`
30
+ - Payload size limiting with 413 responses
31
+ - Sensible defaults for 404/405/500
32
+
33
+ ## Performance (Apple M4, Node 20+)
34
+
35
+ | Test Case | Vajra | Express + Multer | Notes |
36
+ |------------------------------------------------|----------------|------------------|---------------------------|
37
+ | 1MB Multipart Upload (wrk -t16 -c600) | **~94–98k req/s** | ~72k req/s | +30% faster |
38
+ | Idle RSS | ~52–53 MB | ~44 MB | Zero deps vs extra packages |
39
+ | Peak RSS under load | ~228 MB | ~209 MB | Full buffering trade-off |
40
+ | Code size (source) | **111 lines** | ~2k+ lines | Hand-crafted minimalism |
41
+
42
+ ## Performance Benchmarks (wrk)
43
+
44
+ ![wrk multipart benchmarks on M4 and VPS](assets/wrk-benchmarks.png)
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ npm install vajra
50
+ ```
51
+
52
+
53
+ ## Quick Start
54
+ ```JavaScript
55
+ import Vajra from '../index.js';
56
+ import { encode } from 'node:querystring';
57
+
58
+ async function getUsers(query = {}) {
59
+ return (await fetch(`https://jsonplaceholder.typicode.com/users?${encode(query)}`)).json()
60
+ }
61
+
62
+ const { get, post, use, start, setProperty } = Vajra.create();
63
+
64
+ setProperty({ viewsRoot: `${import.meta.url}/views` })
65
+ // Or as a key-value pair
66
+ // setProperty('viewsRoot', `${import.meta.url}/views`)
67
+
68
+ use((req, res, next) => {
69
+ console.log(`${req.method} ${req.url}`);
70
+ next();
71
+ });
72
+
73
+ get('/', (req, res) => {
74
+ res.writeMessage('Hello from Vajra ⚡');
75
+ });
76
+
77
+ post('/upload', (req, res) => {
78
+ res.json({ received: true, filesCount: req.files.length, files: req.files, body: req.body });
79
+ });
80
+
81
+ start({ port: 4002 }, () => {
82
+ console.log('Ready at http://localhost:4002');
83
+ });
84
+
85
+ get('/api/users', async ({ query }, res) => {
86
+ const users = await getUsers(query)
87
+ return res.json({ users })
88
+ })
89
+
90
+ get('/web/users', async ({ query }, res) => {
91
+ const users = await getUsers(query)
92
+ const headers = Object.keys(users[0])
93
+ return res.html(`users.html`, { users, headers })
94
+ })
95
+ ```
96
+
97
+ ## HTML Templating
98
+ ```JavaScript
99
+ import Vajra from '../index.js';
100
+ const { get, post, use, start, setProperty } = Vajra.create();
101
+
102
+ get('/users', (req, res) => {
103
+ const data = {
104
+ users: [
105
+ { id: 1, name: 'Alice' },
106
+ { id: 2, name: 'Bob' }
107
+ ],
108
+ headers: ['ID', 'Name']
109
+ };
110
+
111
+ // If no view root is set, .html() expects the absolute path.
112
+ res.html('views/users.html', data);
113
+ });
114
+ ```
115
+
116
+ #### views/users.html
117
+ ```html
118
+ <table>
119
+ <thead>
120
+ {{# headers }}
121
+ <th>{{ header@ }}</th>
122
+ {{/ headers }}
123
+ </thead>
124
+ <tbody>
125
+ {{# users }}
126
+ <tr>
127
+ <td>{{ id }}</td>
128
+ <td>{{ name }}</td>
129
+ </tr>
130
+ {{/ users }}
131
+ </tbody>
132
+ </table>
133
+ ```
134
+
135
+ Supports:
136
+
137
+ - Loops ({{# array }} ... {{/ array }})
138
+ - Dot notation ({{ user.name }})
139
+ - Special header shorthand ({{ header@ }} for simple arrays)
140
+
141
+
142
+ ## Configuration
143
+ ```JavaScript
144
+ const app = vajra.create({
145
+ maxFileSize: 10 // in MB (default: 2)
146
+ });
147
+
148
+ // Set view root path
149
+ app.setProperty('viewRoot', './views');
150
+ ```
151
+
152
+
153
+ ## API
154
+
155
+ - `get/post/put/patch/delete/head/options(path, handler)`
156
+ - `use(middleware)`
157
+ - `start({ port, host }, callback?)`
158
+ - `setProperty(key, value)` or `setProperty({ key: value })`
159
+
160
+
161
+ #### Response helpers:
162
+
163
+ `res.status(code)`
164
+ `res.json(data)`
165
+ `res.writeMessage(text)`
166
+ `res.html(pathOrString, data)`
167
+
168
+
169
+ ## Philosophy
170
+
171
+ Vajra is built on the principle that minimalism can maximise outcomes.
172
+
173
+ Everything you need for real internal tools, admin panels, APIs, and prototypes — without the bloat.
174
+
175
+ No dependencies.
176
+ No build step.
177
+ Just copy `index.js` and go.
178
+
179
+
180
+ ## Benchmarks & Memory
181
+ Run under extreme multipart load (wrk -t16 -c600 -d30s 1MB payloads):
182
+
183
+ Throughput: ~95k req/s
184
+ Idle RSS: ~52 MB
185
+ Peak under load: ~228 MB (drops back on idle)
186
+
187
+ ## License
188
+ MIT
189
+
190
+ ## Credits
191
+ Hand-crafted by [[Sriram Velamur](https://linkedin.com/in/techiev2)/[@techiev2](https://x.com/techiev2)]
192
+
193
+ Inspired by the desire for a truly tiny, powerful, and dependency-free Node server.
package/README.md CHANGED
@@ -14,15 +14,16 @@ Vajra draws from the Rigvedic thunderbolt weapon of Indra — crafted from the b
14
14
  Like the Vajra, this server delivers maximum power in minimal form.
15
15
 
16
16
 
17
- [![npm version](https://img.shields.io/npm/v/vajra.svg?style=flat-square)](https://www.npmjs.com/package/vajra)
18
- [![npm downloads](https://img.shields.io/npm/dm/vajra.svg?style=flat-square)](https://www.npmjs.com/package/vajra)
19
- [![Node.js version](https://img.shields.io/node/v/vajra.svg?style=flat-square)](https://nodejs.org)
20
- [![License](https://img.shields.io/npm/l/vajra.svg?style=flat-square)](LICENSE)
17
+ [![npm version](https://img.shields.io/npm/v/@techiev2/vajra.svg?style=flat-square)](https://www.npmjs.com/package/@techiev2/vajra)
18
+ [![npm downloads](https://img.shields.io/npm/dm/vajra.svg?style=flat-square)](https://www.npmjs.com/package/@techiev2/vajra)
19
+ [![Node.js version](https://img.shields.io/node/v/@techiev2/vajra.svg?style=flat-square)](https://nodejs.org)
20
+ [![License](https://img.shields.io/npm/l/@techiev2/vajra.svg?style=flat-square)](LICENSE)
21
21
 
22
22
  ## Features
23
23
 
24
24
  - Zero external dependencies
25
25
  - Built-in routing with named parameters (`:id`)
26
+ - Asynchronous batched logging for performance
26
27
  - Global middleware support with `next()` chaining
27
28
  - JSON, urlencoded, and **multipart/form-data** body parsing
28
29
  - Fast HTML templating with loops, nested objects, and simple array headers
@@ -59,14 +60,17 @@ async function getUsers(query = {}) {
59
60
  return (await fetch(`https://jsonplaceholder.typicode.com/users?${encode(query)}`)).json()
60
61
  }
61
62
 
62
- const { get, post, use, start, setProperty } = Vajra.create();
63
+ const { get, post, use, start, setProperty, log } = Vajra.create();
63
64
 
64
65
  setProperty({ viewsRoot: `${import.meta.url}/views` })
65
66
  // Or as a key-value pair
66
67
  // setProperty('viewsRoot', `${import.meta.url}/views`)
67
68
 
68
69
  use((req, res, next) => {
69
- console.log(`${req.method} ${req.url}`);
70
+ // Vajra provides an async batched logger to provide a balance between 100% log coverage and performance.
71
+ // If you prefer blocking immediate logs, you can switch to console.log
72
+ // or any other library of your choice.
73
+ log(`${req.method} ${req.url}`);
70
74
  next();
71
75
  });
72
76
 
@@ -156,6 +160,7 @@ app.setProperty('viewRoot', './views');
156
160
  - `use(middleware)`
157
161
  - `start({ port, host }, callback?)`
158
162
  - `setProperty(key, value)` or `setProperty({ key: value })`
163
+ - `log(message)`
159
164
 
160
165
 
161
166
  #### Response helpers:
package/examples/api.js CHANGED
@@ -5,12 +5,12 @@ async function getUsers(query = {}) {
5
5
  return (await fetch(`https://jsonplaceholder.typicode.com/users?${encode(query)}`)).json()
6
6
  }
7
7
 
8
- const { get, post, use, start, setProperty } = Vajra.create();
8
+ const { get, post, use, start, setProperty, log } = Vajra.create();
9
9
 
10
10
  setProperty({ viewRoot: `${import.meta.dirname}/views` })
11
11
 
12
12
  use((req, res, next) => {
13
- console.log(`${req.method} ${req.url}`);
13
+ log(`${req.method} ${req.url}`)
14
14
  next();
15
15
  });
16
16
 
package/index.js CHANGED
@@ -25,8 +25,11 @@ const MAX_MB = 2; const MAX_FILE_SIZE = MAX_MB * 1024 * 1024
25
25
  export default class Vajra {
26
26
  static #app; static #routes = {}; static #middlewares = []; static #straightRoutes = {}; static #MAX_FILE_SIZE; static #onCreate; static #props = {}
27
27
  static create({ maxFileSize } = { maxFileSize: 2 }) {
28
- Vajra.#MAX_FILE_SIZE = !+MAX_FILE_SIZE ? +maxFileSize * 1024 * 1024 : MAX_FILE_SIZE
29
28
  Vajra.#app = createServer()
29
+ const _queue = []; const LOG_QUEUE_SIZE = 100; const logOut = () => { if (_queue.length) { process.stdout.write(`${_queue.join('').trim()}\n`) }; _queue.length = 0 };
30
+ const flushAndShutDown = () => { logOut(); Vajra.#app.close(() => { process.exit(0); }); }; 'SIGINT_SIGTERM_SIGABRT'.split('_').map((evt) => process.on(evt, flushAndShutDown));
31
+ process.on('exit', logOut); function log(message) { _queue.push(`${message}\n`); if (_queue.length >= LOG_QUEUE_SIZE) { logOut(); _queue.length = 0; } }
32
+ Vajra.#MAX_FILE_SIZE = !+MAX_FILE_SIZE ? +maxFileSize * 1024 * 1024 : MAX_FILE_SIZE
30
33
  Vajra.#app.on('request', async (req, res) => {
31
34
  res.sent = false;
32
35
  res.status = (/**@type{code} Number*/ code) => {
@@ -104,8 +107,6 @@ export default class Vajra {
104
107
  if (typeof fn !== "function") { throw new Error(`${fn} is not a function. Can't use as middleware`) }
105
108
  Vajra.#middlewares.push(fn); return defaults
106
109
  }
107
- const defaults = Object.freeze(
108
- Object.assign({}, { use, setProperty, start }, Object.fromEntries('get__post__put__patch__delete__head__options'.split('__').map((method) => [method, (...args) => register(method, ...args)]))
109
- )); return Object.assign({}, { start }, defaults)
110
+ const defaults = Object.freeze(Object.assign({}, { use, setProperty, start, log }, Object.fromEntries('get__post__put__patch__delete__head__options'.split('__').map((method) => [method, (...args) => register(method, ...args)])))); return Object.assign({}, { start }, defaults)
110
111
  }
111
- }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techiev2/vajra",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Blazing-fast, zero-dependency Node.js server with routing, middleware, multipart uploads, and templating. 111 lines · ~95k req/s · ~52 MB idle.",
5
5
  "keywords": [
6
6
  "http-server",