bun-crumb 0.11.0 → 0.12.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
@@ -9,11 +9,11 @@
9
9
 
10
10
  ### Features
11
11
 
12
- - Only Bun is supported
12
+ - Only Bun is supported
13
13
 
14
- - No classes
14
+ - No classes
15
15
 
16
- - Uses Bun’s HTTP API
16
+ - Uses `Bun.serve` routes
17
17
 
18
18
  ### Installation
19
19
 
@@ -34,59 +34,158 @@ const products: Product[] = [];
34
34
 
35
35
  createRoute({
36
36
  url: '/products',
37
+
37
38
  method: 'GET',
39
+
38
40
  handler: (request, response: RouteResponse<{ body: Product[] }>) => {
39
41
  return response.send(
40
42
  [
41
- { title: 'cookie', price: 100, id: 1 },
42
- { title: 'bread', price: 1600, id: 2 },
43
+ { title: 'bananas', price: 0.57, id: 1 },
44
+
45
+ { title: 'bread', price: 0.89, id: 2 },
46
+
47
+ { title: 'milk', price: 0.83, id: 3 },
43
48
  ],
44
- { status: 200 }
49
+
50
+ { status: 200 },
45
51
  );
46
52
  },
47
53
  });
48
54
  ```
49
55
 
50
- &nbsp;
56
+ ### Benchmarks
57
+
58
+ - Machine:
59
+ - windows 11
60
+ - intel core i5 10300H
61
+ - 16gb ram
62
+
63
+ - Load testing tool: grafana/k6
64
+
65
+ - Bun version: 1.3.6
66
+
67
+ <details>
68
+ <summary>k6 script</summary>
69
+
70
+ ```bash
71
+ sleep 1 | k6 run script.js
72
+ ```
51
73
 
52
- Middleware / Pre-handlers
74
+ ```javascript
75
+ import { PORT } from './constants.js';
76
+
77
+ import http from 'k6/http';
78
+
79
+ export const options = {
80
+ scenarios: {
81
+ load: {
82
+ executor: 'constant-arrival-rate',
83
+
84
+ rate: 16000,
85
+ timeUnit: '1s',
86
+
87
+ duration: '30s',
88
+ preAllocatedVUs: 1000,
89
+
90
+ maxVUs: 3000,
91
+ },
92
+ },
93
+ };
94
+
95
+ export default () => {
96
+ http.post(
97
+ 'http://localhost:' + PORT + '/',
98
+ JSON.stringify({ key: 'value' }),
99
+ { headers: { 'Content-Type': 'application/json' } },
100
+ );
101
+ };
102
+ ```
103
+
104
+ </details>
105
+
106
+ <details>
107
+ <summary>Pure Bun server code</summary>
53
108
 
54
109
  ```typescript
55
- import { createRoute, type RouteResponse } from 'bun-crumb';
110
+ import { serve, stdout } from 'bun';
111
+
112
+ import { PORT } from './constants';
113
+
114
+ serve({
115
+ routes: {
116
+ '/': {
117
+ POST: (request) => {
118
+ return request.json().then(
119
+ (body) =>
120
+ new Response(JSON.stringify(body), {
121
+ headers: { 'Content-Type': 'application/json' },
122
+ }),
123
+ );
124
+ },
125
+ },
126
+ },
127
+ port: PORT,
128
+ });
129
+ stdout.write('Bun is running\n');
130
+ ```
56
131
 
57
- type Product = { title: string; price: number; id: number };
132
+ </details>
58
133
 
59
- const products: Product[] = [];
134
+ <details>
135
+ <summary>Fastify server code</summary>
60
136
 
61
- createRoute({
62
- url: '/products',
137
+ ```typescript
138
+ import Fastify from 'fastify';
139
+
140
+ import { PORT } from './constants';
141
+
142
+ const fastify = Fastify({ logger: false });
143
+
144
+ fastify.route({
145
+ url: '/',
63
146
  method: 'POST',
64
- preHandler: (request, response) => {
65
- if (products.find((product) => product.id === request.body.id)) {
66
- return response.send(
67
- { message: 'Product with this id already exists' },
68
- { status: 409 }
69
- );
70
- }
147
+ handler: (request, response) => {
148
+ return response.send(request.body);
71
149
  },
72
- handler: (request, response: RouteResponse<{ body: Product }>) => {
73
- products.push(body);
150
+ });
74
151
 
75
- return response.send(body, { status: 201 });
76
- },
152
+ fastify.listen({ port: PORT }, () => {
153
+ Bun.stdout.write('Fastify is running\n');
77
154
  });
78
155
  ```
79
156
 
80
- &nbsp;
157
+ </details>
158
+
159
+ <details>
160
+ <summary>Bun Crumb server code</summary>
81
161
 
82
- Setting Headers and Status
162
+ ```bash
163
+ sleep 1 | k6 run script.js
164
+ ```
83
165
 
84
166
  ```typescript
85
- import { createRoute } from 'bun-crumb';
167
+ import { PORT } from './constants';
168
+
169
+ import { listen, createRoute } from 'bun-crumb';
86
170
 
171
+ //
87
172
  createRoute({
88
- url: '/auth',
173
+ url: '/',
174
+
89
175
  method: 'POST',
90
- handler: (request, response) => {},
176
+
177
+ handler: (request, response) => {
178
+ return request.handleBody().then(response.send);
179
+ },
91
180
  });
181
+
182
+ listen({ port: PORT });
92
183
  ```
184
+
185
+ </details>
186
+
187
+ | Library | Total requests | RPS | Requests failed | Avg. request duration |
188
+ | --------- | -------------- | -------------- | --------------- | --------------------- |
189
+ | Pure Bun | 482681 | 15958.069946/s | 0.00% (0) | 12.48ms |
190
+ | Bun Crumb | 474870 | 15789.504029/s | 0.02% (101) | 23.08ms |
191
+ | Fastify | 364525 | 12060.63486/s | 0.27% (1002) | 237.68ms |
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Multiplier between `bytes` and `megabytes`
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * const hugeBytes = 211_813_564;
7
+ * console.log(`Megabytes: `, hugeBytes / MB_MULTIPLIER);
8
+ * ```
9
+ */
10
+ export declare const MB_MULTIPLIER = 1048582;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export const PORT: 59957;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ export namespace options {
2
+ namespace scenarios {
3
+ namespace load {
4
+ let executor: string;
5
+ let rate: number;
6
+ let timeUnit: string;
7
+ let duration: string;
8
+ let preAllocatedVUs: number;
9
+ let maxVUs: number;
10
+ }
11
+ }
12
+ }
13
+ declare function _default(): void;
14
+ export default _default;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -13,12 +13,15 @@ import { BunRequest, CookieInit } from 'bun';
13
13
  *
14
14
  *
15
15
  *
16
+ *
17
+ *
16
18
  * declare module 'crumb-bun' {
17
19
  * interface Schema {
18
20
  * zod: ZodType;
19
21
  * }
20
22
  * }
21
23
  * ```
24
+ *
22
25
  * &nbsp;
23
26
  * ```json
24
27
  * // tsconfig.json
@@ -118,7 +121,7 @@ interface RouteResponse<T extends {
118
121
  } = {
119
122
  body: unknown;
120
123
  }> {
121
- send: (data: T['body'], options?: ResponseOptions) => void;
124
+ send: (data?: T['body'], options?: ResponseOptions) => void;
122
125
  /**
123
126
  * Sets `Location` header to provided `url` and `response.status` to provided `status`
124
127
  *
@@ -166,7 +169,7 @@ interface RouteResponse<T extends {
166
169
  setHeader: (name: Header['name'], value: Header['value']) => void;
167
170
  /**
168
171
  *
169
- * @param options `Bun.Cookie` options parametr
172
+ * @param options `Bun.Cookie` options parameter
170
173
  *
171
174
  *
172
175
  *
@@ -212,7 +215,8 @@ interface ListenOptions {
212
215
  }
213
216
 
214
217
  /**
215
- * Starts serving http server.
218
+ *
219
+ * Starts to serve http server.
216
220
  *
217
221
  *
218
222
  * @param {ListenOption} options - options
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{serve as e,Cookie as t}from"bun";class s extends Error{status;constructor(e,t){super(t),this.status=e,this.name="HttpError"}}const n=new Map,o=(e,n)=>o=>{const r=o.headers.get("Content-Type")??"text/plain",a=o;return a.handleBody=()=>((e,t,n,o)=>{const r={"application/json":e=>e.json().catch(()=>{throw new s(400,"Bad Request")}).then(e=>{if(n&&o&&!o(e,n))throw new s(400,"Request does not match schema");return e}),"text/plain":e=>e.text().catch(e=>{throw new s(400,e)}).then(e=>{if(n&&o&&!o(e,n))throw new s(400,"Request does not match schema");return e})};return t in r?r[t](e):Promise.reject(new s(415,"Unsupported media type"))})(o,r,e.schema,n),a.query=new URLSearchParams(o.url.split("?")[1]||""),Promise.resolve(((e,s)=>{let n=200,o="",r="";const a=new Headers,c={send:(e,t)=>{"object"==typeof e?(a.has("Content-Type")||a.set("Content-Type","application/json"),r=JSON.stringify(e)):"string"==typeof e?(a.has("Content-Type")||a.set("Content-Type","text/plain"),r=e):r=e,t&&(n=t.status,o=t.statusText)},redirect:(e,t)=>{r="",n=t||302,a.set("Location",e)},setHeader:(e,t)=>{a.set(e,t)},setCookie:e=>{a.append("Set-Cookie",new t(e).toString())}};return Promise.resolve(s.handler(e,c)).then(()=>new Response(r,{headers:a,status:n,statusText:o}))})(a,e)).then(e=>e).catch(e=>e instanceof s?new Response(e.message,{status:e.status}):new Response("Internal server error",{status:500}))},r=(e,t)=>{const s={};for(const n in e)Object.hasOwn(e,n)&&(s[n]=o(e[n],t));return s},a=(e,t)=>{const s={};for(const n of e)s[n[0]]=r(n[1],t);return e.clear(),s},c=t=>{e({port:t?.port,hostname:t?.hostname,development:t?.development??!1,routes:a(n,t?.schemaValidator)})},p=e=>{const t=n.get(e.url);t?t[e.method]=e:n.set(e.url,{[e.method]:e})};export{p as createRoute,c as listen};
1
+ import{serve as e,stdout as t,Cookie as n}from"bun";class s extends Error{status;constructor(e,t){super(t),this.status=e,this.name="HttpError"}}const r=new Map,o=(e,t)=>r=>{const o=r.headers.get("Content-Type")??"text/plain",a=r;return a.handleBody=()=>((e,t,n,r)=>{const o={"application/json":e=>e.json().catch(()=>{throw new s(400,"Bad Request")}).then(e=>{if(n&&r&&!r(e,n))throw new s(400,"Request does not match schema");return e}),"text/plain":e=>e.text().catch(e=>{throw new s(400,e)}).then(e=>{if(n&&r&&!r(e,n))throw new s(400,"Request does not match schema");return e})};return t in o?o[t](e):Promise.reject(new s(415,"Unsupported media type"))})(r,o,e.schema,t),a.query=new URLSearchParams(r.url.split("?")[1]||""),Promise.resolve(((e,t)=>{let s=200,r="",o="";const a=new Headers,c={send:(e,t)=>{"object"==typeof e?(a.has("Content-Type")||a.set("Content-Type","application/json"),o=JSON.stringify(e)):"string"==typeof e?(a.has("Content-Type")||a.set("Content-Type","text/plain"),o=e):o=e,t&&(s=t.status,r=t.statusText)},redirect:(e,t)=>{o="",s=t||302,a.set("Location",e)},setHeader:(e,t)=>{a.set(e,t)},setCookie:e=>{a.append("Set-Cookie",new n(e).toString())}};return Promise.resolve(t.handler(e,c)).then(()=>new Response(o,{headers:a,status:s,statusText:r}))})(a,e)).then(e=>e).catch(e=>e instanceof s?new Response(e.message,{status:e.status}):new Response("Internal server error",{status:500}))},a=(e,t)=>{const n={};for(const s in e)Object.hasOwn(e,s)&&(n[s]=o(e[s],t));return n},c=(e,t)=>{const n={};for(const s of e)n[s[0]]=a(s[1],t);return e.clear(),n},h=n=>{const s=e({port:n?.port,hostname:n?.hostname,development:n?.development??!1,routes:c(r,n?.schemaValidator)});t.write("Server is running on "+s.url.href+"\n")},i=e=>{const t=r.get(e.url);t?t[e.method]=e:r.set(e.url,{[e.method]:e})};export{i as createRoute,h as listen};
package/dist/server.d.ts CHANGED
@@ -25,6 +25,10 @@ export declare const _routes: Routes;
25
25
  * @param {Validate} schemaValidator schema validator function that receives `data` and `schema` arguments.
26
26
  *
27
27
  * @returns {Promise<unknown>} Promise with body
28
+ *
29
+ *
30
+ *
31
+ *
28
32
  */
29
33
  export declare const handleBody: (request: BunRequest, contentType: string, schema?: SchemaData, schemaValidator?: Validate) => Promise<unknown>;
30
34
  /**
@@ -90,7 +94,8 @@ export declare const prepareRoute: (route: Route, schemaValidator?: Validate) =>
90
94
  */
91
95
  export declare const prepareRoutes: (routes: Routes, schemaValidator?: Validate) => PreparedRoutes;
92
96
  /**
93
- * Starts serving http server.
97
+ *
98
+ * Starts to serve http server.
94
99
  *
95
100
  *
96
101
  * @param {ListenOption} options - options
@@ -60,7 +60,7 @@ export interface RouteResponse<T extends {
60
60
  } = {
61
61
  body: unknown;
62
62
  }> {
63
- send: (data: T['body'], options?: ResponseOptions) => void;
63
+ send: (data?: T['body'], options?: ResponseOptions) => void;
64
64
  /**
65
65
  * Sets `Location` header to provided `url` and `response.status` to provided `status`
66
66
  *
@@ -108,7 +108,7 @@ export interface RouteResponse<T extends {
108
108
  setHeader: (name: Header['name'], value: Header['value']) => void;
109
109
  /**
110
110
  *
111
- * @param options `Bun.Cookie` options parametr
111
+ * @param options `Bun.Cookie` options parameter
112
112
  *
113
113
  *
114
114
  *
@@ -11,12 +11,15 @@
11
11
  *
12
12
  *
13
13
  *
14
+ *
15
+ *
14
16
  * declare module 'crumb-bun' {
15
17
  * interface Schema {
16
18
  * zod: ZodType;
17
19
  * }
18
20
  * }
19
21
  * ```
22
+ *
20
23
  * &nbsp;
21
24
  * ```json
22
25
  * // tsconfig.json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-crumb",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "author": "marigold",
5
5
  "module": "dist/index.js",
6
6
  "license": "UNLICENSED",