@techiev2/vajra 1.2.0 → 1.3.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/examples/api.js +20 -7
- package/index.js +7 -10
- package/package.json +2 -2
- package/tests/tests.js +28 -25
package/examples/api.js
CHANGED
|
@@ -5,26 +5,39 @@ 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, log } = Vajra.create();
|
|
8
|
+
const { get, post, put, use, start, setProperty, log } = Vajra.create();
|
|
9
9
|
|
|
10
10
|
setProperty({ viewRoot: `${import.meta.dirname}/views` })
|
|
11
11
|
|
|
12
|
-
use((req, res, next) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
12
|
+
// use((req, res, next) => {
|
|
13
|
+
// log(`${req.method} ${req.url}`)
|
|
14
|
+
// next();
|
|
15
|
+
// });
|
|
16
16
|
|
|
17
|
-
get('/', (
|
|
17
|
+
get('/', ({ query = {} }, res) => {
|
|
18
|
+
const { version } = query
|
|
18
19
|
res.cookie('session', 'abc');
|
|
19
20
|
res.cookie('theme', 'dark');
|
|
20
21
|
res.cookie('user', 'test');
|
|
21
|
-
res.writeMessage('Hello from Vajra ⚡');
|
|
22
|
+
res.writeMessage(version ? `Hello from Vajra (v${version}) ⚡` : 'Hello from Vajra ⚡');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
get('/query', ({ query, params }, res) => {
|
|
26
|
+
return res.json({ query, params, now: new Date().getTime() })
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
post('/upload', (req, res) => {
|
|
25
30
|
res.json({ received: true, filesCount: req.files.length, files: req.files, body: req.body });
|
|
26
31
|
});
|
|
27
32
|
|
|
33
|
+
post('/post', ({ body, query, params }, res) => {
|
|
34
|
+
return res.json({ query, params, body, now: new Date().getTime() })
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
put('/users/:id', ({ params, query, body }, res) => {
|
|
38
|
+
return res.json({ params, query, body, now: new Date().getTime() })
|
|
39
|
+
})
|
|
40
|
+
|
|
28
41
|
start({ port: 4002 }, () => {
|
|
29
42
|
console.log('Ready at http://localhost:4002');
|
|
30
43
|
});
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'node:http'
|
|
2
|
-
import { readFile, access } from 'fs/promises'
|
|
2
|
+
import { readFile, access } from 'node:fs/promises'
|
|
3
3
|
|
|
4
4
|
const BLOCK_MATCHER=/{{\s*#\s*(?<grpStart>\w+)\s*}}\s*(?<block>.*?)\s*{{\s*\/\s*(?<grpEnd>\w+)\s*}}/gmsi; const INNER_BLOCK_MATCHER = /{\s*(.*?)\s*}/gmsi
|
|
5
5
|
const LOOP_MATCHER=/({\s*\w+@\s*})/gmis
|
|
@@ -88,12 +88,11 @@ export default class Vajra {
|
|
|
88
88
|
req.cookies = Object.fromEntries((req.headers.Cookie || req.headers.cookie || '').split(/;\s*/).map((k) => k.split('=')).map(([k, v]) => [k.trim(), decodeURIComponent(v).trim()]))
|
|
89
89
|
})
|
|
90
90
|
async function handleRoute() {
|
|
91
|
-
let
|
|
91
|
+
let _url = req.url.split('?')[0]; if (_url.endsWith('/')) _url = _url.split('/').slice(0, -1).join('/')
|
|
92
|
+
let match_; const directHandler = (Vajra.#straightRoutes[_url] || Vajra.#straightRoutes[`${_url}/`] || {})[req.method.toLowerCase()]
|
|
92
93
|
if (directHandler) { try { await directHandler(req, res); if (!res.sent && !res.writableEnded) res.end() } catch (error) { return default_500(req, res, error) }; return }
|
|
93
94
|
Object.entries(Vajra.#routes).map(([route, handler]) => {
|
|
94
|
-
if (match_) { return }
|
|
95
|
-
if (route === '/' && route !== req.url.split('?')[0]) { return } // FIXME: The case of a bare '/' is not handled right due to the pattern addition.
|
|
96
|
-
const match = new RegExp(route).exec(req.url); if (!!match && handler[req.method.toLowerCase()]) { match_ = handler; Object.assign(req.params, match.groups); return }
|
|
95
|
+
if (match_) { return }; const match = new RegExp(route).exec(req.url); if (!!match && handler[req.method.toLowerCase()]) { match_ = handler; Object.assign(req.params, match.groups); return }
|
|
97
96
|
})
|
|
98
97
|
if (!match_) { return default_404(req, res) }
|
|
99
98
|
if (!match_[req.method.toLowerCase()]) { return default_405(req, res) }
|
|
@@ -106,13 +105,11 @@ export default class Vajra {
|
|
|
106
105
|
const paramMatcher = /.*?(?<param>\:[a-zA-Z]{1,})/g; let pathMatcherStr = path
|
|
107
106
|
path.matchAll(paramMatcher).forEach(match => pathMatcherStr = pathMatcherStr.replace(match.groups.param, `{0,1}(?<${match.groups.param.slice(1)}>\\w{1,})`))
|
|
108
107
|
if (path !== '/' && pathMatcherStr.endsWith('/')) { pathMatcherStr = pathMatcherStr.replace(/(\/)$/, '/?') }
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return defaults
|
|
108
|
+
if (!paramMatcher.exec(path)?.groups) { Vajra.#straightRoutes[pathMatcherStr] = Object.assign(Vajra.#straightRoutes[pathMatcherStr] || {}, { [method]: handler }); return }
|
|
109
|
+
Vajra.#routes[pathMatcherStr] = Object.assign(Vajra.#routes[pathMatcherStr] || {}, {[method]: handler}); return defaults
|
|
112
110
|
}
|
|
113
111
|
function use(fn) {
|
|
114
|
-
if (typeof fn !== "function") { throw new Error(`${fn} is not a function. Can't use as middleware`) }
|
|
115
|
-
Vajra.#middlewares.push(fn); return defaults
|
|
112
|
+
if (typeof fn !== "function") { throw new Error(`${fn} is not a function. Can't use as middleware`) }; Vajra.#middlewares.push(fn); return defaults
|
|
116
113
|
}
|
|
117
114
|
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)
|
|
118
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techiev2/vajra",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
"test": "tests"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "tests/tests.js"
|
|
41
|
+
"test": "node --watch tests/tests.js"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/tests/tests.js
CHANGED
|
@@ -1,55 +1,58 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { encode } from 'node:querystring';
|
|
3
|
+
import pkg from '../package.json' with {type: 'json'}
|
|
3
4
|
import { suite, test } from 'node:test';
|
|
4
5
|
|
|
5
|
-
async function
|
|
6
|
+
async function getResponse(url, method = 'GET', body) {
|
|
6
7
|
return (await fetch(url, !!body ?
|
|
7
8
|
{ method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) }
|
|
8
9
|
: { method, headers: {'Content-Type': 'application/json'} }
|
|
9
|
-
))
|
|
10
|
+
))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function getJSON(url, method = 'GET', body) {
|
|
14
|
+
return (await getResponse(url, method, body)).json()
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
suite('Test HTTP API at port
|
|
13
|
-
const BASE_URL = 'http://localhost:
|
|
17
|
+
suite('Test HTTP API at port 4002', () => {
|
|
18
|
+
const BASE_URL = 'http://localhost:4002'
|
|
14
19
|
suite('Test HTTP GET', () => {
|
|
15
20
|
test('Basic HTTP GET to respond with a now and empty query/params', async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
assert.deepEqual((await (await getResponse(BASE_URL)).text()), 'Hello from Vajra ⚡')
|
|
22
|
+
})
|
|
23
|
+
test('Basic HTTP GET to respond with custom message given version query param', async () => {
|
|
24
|
+
const { version } = pkg
|
|
25
|
+
assert.deepEqual((await (await getResponse(`${BASE_URL}?${encode({ version })}`)).text()), `Hello from Vajra (v${version}) ⚡`)
|
|
20
26
|
})
|
|
21
27
|
test('Basic HTTP GET to respond with a now, and query params', async () => {
|
|
22
28
|
const query = { id: 1, user: 'test' }
|
|
23
|
-
const { now, query: res_query, params: res_params } = await getJSON(`${BASE_URL}?${encode(query)}`)
|
|
29
|
+
const { now, query: res_query, params: res_params } = await getJSON(`${BASE_URL}/query?${encode(query)}`)
|
|
24
30
|
assert.equal(!!now, true)
|
|
25
31
|
assert.deepEqual(res_query, query)
|
|
26
32
|
assert.deepEqual(res_params, {})
|
|
27
33
|
})
|
|
28
34
|
})
|
|
29
35
|
suite('Test HTTP POST', () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// const { now, query: res_query, body: res_body } =
|
|
36
|
+
test('Basic HTTP POST to respond with a now and body', async () => {
|
|
37
|
+
const body = { name: 'user' }
|
|
38
|
+
const json = await getJSON(`${BASE_URL}/post`, 'POST', body)
|
|
39
|
+
const { now, body: res_body } = json
|
|
40
|
+
assert.equal(!!now, true)
|
|
41
|
+
assert.deepEqual(res_body, body)
|
|
42
|
+
})
|
|
43
|
+
test('Basic HTTP POST with a param (unmapped route) should return a 404', async (done) => {
|
|
44
|
+
const query = { test: 'random' }
|
|
40
45
|
const body = { name: 'test' }
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
// assert.equal(!!now, true)
|
|
44
|
-
// assert.deepEqual(res_query, query)
|
|
45
|
-
// assert.deepEqual(res_body, body)
|
|
46
|
+
const response = await getResponse(`${BASE_URL}/post/1/?${encode(query)}`, 'POST', body)
|
|
47
|
+
assert.equal(response.status, 404)
|
|
46
48
|
})
|
|
47
49
|
})
|
|
48
50
|
suite('Test HTTP PUT', () => {
|
|
49
51
|
test('Basic HTTP PUT to respond with a now, query, params, and body', async () => {
|
|
50
52
|
const params = { id : 1 }
|
|
51
53
|
const body = { name: 'user' }
|
|
52
|
-
const
|
|
54
|
+
const json = await getJSON(`${BASE_URL}/users/${params.id}`, 'PUT', body)
|
|
55
|
+
const { now, params: res_params, body: res_body} = json
|
|
53
56
|
assert.equal(!!now, true)
|
|
54
57
|
assert.deepEqual(res_params, params)
|
|
55
58
|
assert.deepEqual(res_body, body)
|