bun-crumb 0.3.0 → 0.6.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.md CHANGED
@@ -1,90 +1,92 @@
1
- # <div align='center'> <a> **Bun Crumb** </a> </div>
2
-
3
- <div align='center'>
4
- [![CI](https://github.com/a-marigold/crumb/actions/workflows/ci.yaml/badge.svg)](https://github.com/a-marigold/crumb/actions) [![bun](https://img.shields.io/badge/Bun-000?logo=bun&logoColor=fff)](https://bun.com) [![npm](https://img.shields.io/npm/v/bun-crumb)](https://npmjs.com/package/bun-crumb) [![Status](https://img.shields.io/badge/BETA-darkgreen?style=for-the-badge)](https://npmjs.com/package/bun-crumb)
5
-
6
- </div>
7
-
8
- ### Features
9
-
10
- - Only Bun is supported
11
-
12
- - No classes
13
-
14
- - Uses Bun’s HTTP API
15
-
16
- ### Installation
17
-
18
- ```bash
19
- bun add bun-crumb
20
- ```
21
-
22
- ### Usage
23
-
24
- Handling Requests
25
-
26
- ```typescript
27
- import { createRoute, type RouteResponse } from 'bun-crumb';
28
-
29
- type Product = { title: string; price: number; id: number };
30
-
31
- const products: Product[] = [];
32
-
33
- createRoute({
34
- url: '/products',
35
- method: 'GET',
36
- handler: (request, response: RouteResponse<{ body: Product[] }>) => {
37
- return response.send(
38
- [
39
- { title: 'cookie', price: 100, id: 1 },
40
- { title: 'bread', price: 1600, id: 2 },
41
- ],
42
- { status: 200 }
43
- );
44
- },
45
- });
46
- ```
47
-
48
- &nbsp;
49
-
50
- Middleware / Pre-handlers
51
-
52
- ```typescript
53
- import { createRoute, type RouteResponse } from 'bun-crumb';
54
-
55
- type Product = { title: string; price: number; id: number };
56
-
57
- const products: Product[] = [];
58
-
59
- createRoute({
60
- url: '/products',
61
- method: 'POST',
62
- preHandler: (request, response) => {
63
- if (products.find((product) => product.id === request.body.id)) {
64
- return response.send(
65
- { message: 'Product with this id already exists' },
66
- { status: 409 }
67
- );
68
- }
69
- },
70
- handler: (request, response: RouteResponse<{ body: Product }>) => {
71
- products.push(body);
72
-
73
- return response.send(body, { status: 201 });
74
- },
75
- });
76
- ```
77
-
78
- &nbsp;
79
-
80
- Setting Headers and Status
81
-
82
- ```typescript
83
- import { createRoute } from 'bun-crumb';
84
-
85
- createRoute({
86
- url: '/auth',
87
- method: 'POST',
88
- handler: (request, response) => {},
89
- });
90
- ```
1
+ # <div align='center'> <a> **Bun Crumb** </a> </div>
2
+
3
+ <div align='center'>
4
+
5
+ [![CI](https://github.com/a-marigold/crumb/actions/workflows/ci.yaml/badge.svg)](https://github.com/a-marigold/bun-crumb/actions) [![Status](https://img.shields.io/badge/BETA-darkgreen?style=for-the-badge)](https://npmjs.com/package/bun-crumb)
6
+ [![bun](https://img.shields.io/badge/Bun-000?logo=bun&logoColor=fff)](https://bun.com) [![npm](https://img.shields.io/npm/v/bun-crumb)](https://npmjs.com/package/bun-crumb)
7
+
8
+ </div>
9
+
10
+ ### Features
11
+
12
+ - Only Bun is supported
13
+
14
+ - No classes
15
+
16
+ - Uses Bun’s HTTP API
17
+
18
+ ### Installation
19
+
20
+ ```bash
21
+ bun add bun-crumb
22
+ ```
23
+
24
+ ### Usage
25
+
26
+ Handling Requests
27
+
28
+ ```typescript
29
+ import { createRoute, type RouteResponse } from 'bun-crumb';
30
+
31
+ type Product = { title: string; price: number; id: number };
32
+
33
+ const products: Product[] = [];
34
+
35
+ createRoute({
36
+ url: '/products',
37
+ method: 'GET',
38
+ handler: (request, response: RouteResponse<{ body: Product[] }>) => {
39
+ return response.send(
40
+ [
41
+ { title: 'cookie', price: 100, id: 1 },
42
+ { title: 'bread', price: 1600, id: 2 },
43
+ ],
44
+ { status: 200 }
45
+ );
46
+ },
47
+ });
48
+ ```
49
+
50
+ &nbsp;
51
+
52
+ Middleware / Pre-handlers
53
+
54
+ ```typescript
55
+ import { createRoute, type RouteResponse } from 'bun-crumb';
56
+
57
+ type Product = { title: string; price: number; id: number };
58
+
59
+ const products: Product[] = [];
60
+
61
+ createRoute({
62
+ url: '/products',
63
+ 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
+ }
71
+ },
72
+ handler: (request, response: RouteResponse<{ body: Product }>) => {
73
+ products.push(body);
74
+
75
+ return response.send(body, { status: 201 });
76
+ },
77
+ });
78
+ ```
79
+
80
+ &nbsp;
81
+
82
+ Setting Headers and Status
83
+
84
+ ```typescript
85
+ import { createRoute } from 'bun-crumb';
86
+
87
+ createRoute({
88
+ url: '/auth',
89
+ method: 'POST',
90
+ handler: (request, response) => {},
91
+ });
92
+ ```
package/dist/index.d.ts CHANGED
@@ -94,11 +94,14 @@ type RouteOptions = {
94
94
  url: string;
95
95
  method: HttpMethod;
96
96
  schema?: SchemaData;
97
- onRequest?: RouteHandler;
98
- preHandler?: RouteHandler;
99
97
  handler: RouteHandler;
100
98
  };
101
99
 
100
+ /**
101
+ *
102
+ *
103
+ * Type of `options` parameter in `listen` function
104
+ */
102
105
  interface ListenOptions {
103
106
  /**
104
107
  * Server port to listen
@@ -106,7 +109,6 @@ interface ListenOptions {
106
109
  port?: number | string;
107
110
  /**
108
111
  * Server hostname to listen
109
- *
110
112
  * @example `localhost`, `0.0.0.0`
111
113
  *
112
114
  */
@@ -123,90 +125,6 @@ interface ListenOptions {
123
125
  schemaValidator?: Validate;
124
126
  }
125
127
 
126
- type PreparedRoute = Partial<Record<HttpMethod, WrappedRouteCallback>>;
127
- type WrappedRouteCallback = (request: BunRequest) => Promise<Response>;
128
- /**
129
- * Used straight as Bun.serve `routes` object.
130
- */
131
- type PreparedRoutes = Record<RouteOptions['url'], PreparedRoute>;
132
- type Routes = Map<RouteOptions['url'], Route>;
133
- /**
134
- * An internal Map with routes of app. Do not use it in user code to prevent undefined errors
135
- */
136
- declare const _routes: Routes;
137
- /**
138
- * Runtime function that used in request.
139
- * Parses body to supported content type (json, plain text) and validates it with route schema.
140
- *
141
- * @param {BunRequest} request incoming bun request.
142
- * @param {string} contentType request `Content-Type` header value.
143
- * @param {Schema} schema json or any schema with declared `Schema` type.
144
- * @param {Validate} schemaValidator schema validator function that receives `data` and `schema` arguments.
145
- *
146
- * @returns {Promise<unknown>} Promise with body
147
- */
148
- declare const handleBody: (request: BunRequest, contentType: string, schema?: SchemaData, schemaValidator?: Validate) => Promise<unknown>;
149
- /**
150
- * Internal `server` function.
151
- * Creates a function with handler and all route hooks.
152
- *
153
- * The created function can be used as a callback for route in Bun.serve `routes` object.
154
- *
155
- *
156
- *
157
- *
158
- *
159
- * @param routeOptions options of route
160
- * @returns {WrappedRouteCallback} Function that is ready to be used in Bun.serve `routes`
161
- */
162
- declare const wrapRouteCallback: (routeOptions: RouteOptions, schemaValidator?: Validate) => WrappedRouteCallback;
163
- /**
164
- * Internal `server` function.
165
- * Prepares a route to be used in Bun.serve `routes` object.
166
- *
167
- * @param {Route} route
168
- *
169
- * @returns {PreparedRoute} Route object with `GET` or other http method keys with wrapped route callbacks.
170
- *
171
- * @example
172
- *
173
- * ```typescript
174
- * prepareRoute({
175
- * GET: {
176
- * url: '/products',
177
- * method: 'GET',
178
- * handler: (request, response) => {},
179
- * },
180
- * POST: {
181
- * url: '/products/:id',
182
- * method: 'POST',
183
- * handler: (request, response) => {},
184
- * },
185
- * });
186
- * // Output will be:
187
- * ({
188
- * GET: (request: BunRequest) => {
189
- * // ...code
190
- * return new Response();
191
- * },
192
- * POST: (request: BunRequest) => {
193
- * // ...code
194
- * return new Response();
195
- * },
196
- * })
197
- * ```
198
- *
199
- */
200
- declare const prepareRoute: (route: Route, schemaValidator?: Validate) => PreparedRoute;
201
- /**
202
- * Internal server function.
203
- * Calls `prepareRoute` for every route of `routes` Map and returns prepared routes to use in Bun.serve `routes`.
204
- *
205
- * @param {Routes} routes Map with routes to prepare.
206
- *
207
- * @returns {PreparedRoutes} An object that is used straight in Bun.serve `routes` object.
208
- */
209
- declare const prepareRoutes: (routes: Routes, schemaValidator?: Validate) => PreparedRoutes;
210
128
  /**
211
129
  * Starts serving http server.
212
130
  *
@@ -282,5 +200,5 @@ declare const listen: (options: ListenOptions) => void;
282
200
  */
283
201
  declare const createRoute: (routeOptions: RouteOptions) => void;
284
202
 
285
- export { _routes, createRoute, handleBody, listen, prepareRoute, prepareRoutes, wrapRouteCallback };
286
- export type { Header, Headers, HttpMethod, ListenOptions, ResponseOptions, Route, RouteHandler, RouteOptions, RouteRequest, RouteResponse, Routes, Schema, SchemaData, Validate, WrappedRouteCallback };
203
+ export { createRoute, listen };
204
+ export type { Header, Headers, HttpMethod, ListenOptions, ResponseOptions, Route, RouteHandler, RouteOptions, RouteRequest, RouteResponse, Schema, SchemaData, Validate };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{serve as t}from"bun";class e extends Error{status;constructor(t,e){super(e),this.status=t,this.name="HttpError"}}const n=new Map,s=(t,n,s,o)=>{const r={"application/json":t=>t.json().catch(t=>{throw new e(400,t)}).then(t=>{if(s&&o&&!o(t,s))throw new e(400,"Request does not match schema");return t}),"text/plain":t=>t.text().catch(t=>{throw new e(400,t)}).then(t=>{if(s&&o&&!o(t,s))throw new e(400,"Request does not match schema");return t})};return n in r?r[n](t):Promise.reject(new e(415,"Unsupported media type"))},o=(t,n)=>o=>{const r=o.headers.get("Content-Type")??"text/plain",a=o;return a.handleBody=()=>s(o,r,t.schema,n).then(t=>t),Promise.resolve(((t,e)=>{let n,s,o=null;const r={},a={setHeader:(t,e)=>{r[t]=e},send:(t,e)=>{"object"==typeof t?r["Content-Type"]="application/json":"string"==typeof t&&(r["Content-Type"]="text/plain"),o=t,n=e?.status,s=e?.statusText}};return Promise.all([e.onRequest?.(t,a),e.preHandler?.(t,a),e.handler(t,a)]).then(()=>new Response(null==o?null:JSON.stringify(o),{headers:r,status:n,statusText:s}))})(a,t)).then(t=>t).catch(t=>t instanceof e?new Response(t.message,{status:t.status}):new Response("Internal server error",{status:500}))},r=(t,e)=>{const n={};for(const s in t)Object.hasOwn(t,s)&&(n[s]=o(t[s],e));return n},a=(t,e)=>{const n={};for(const s of t)n[s[0]]=r(s[1],e);return t.clear(),n},c=e=>{t({port:e.port,hostname:e.hostname,development:e.development??!1,routes:a(n,e?.schemaValidator)})},h=t=>{const e=n.get(t.url);e?e[t.method]=t:n.set(t.url,{[t.method]:t})};export{n as _routes,h as createRoute,s as handleBody,c as listen,r as prepareRoute,a as prepareRoutes,o as wrapRouteCallback};
1
+ import{serve as t}from"bun";class e extends Error{status;constructor(t,e){super(e),this.status=t,this.name="HttpError"}}const s=new Map,n=(t,s)=>n=>{const o=n.headers.get("Content-Type")??"text/plain",r=n;return r.handleBody=()=>((t,s,n,o)=>{const r={"application/json":t=>t.json().catch(()=>{throw new e(400,"Bad Request")}).then(t=>{if(n&&o&&!o(t,n))throw new e(400,"Request does not match schema");return t}),"text/plain":t=>t.text().catch(t=>{throw new e(400,t)}).then(t=>{if(n&&o&&!o(t,n))throw new e(400,"Request does not match schema");return t})};return s in r?r[s](t):Promise.reject(new e(415,"Unsupported media type"))})(n,o,t.schema,s),Promise.resolve(((t,e)=>{let s=200,n="",o="";const r={},a={setHeader:(t,e)=>{r[t]=e},send:(t,e)=>{"object"==typeof t?(r["Content-Type"]="application/json",o=JSON.stringify(t)):"string"==typeof t&&(r["Content-Type"]="text/plain",o=t),e&&(s=e.status,n=e.statusText)}};return Promise.resolve(e.handler(t,a)).then(()=>new Response(o,{headers:r,status:s,statusText:n}))})(r,t)).then(t=>t).catch(t=>t instanceof e?new Response(t.message,{status:t.status}):new Response("Internal server error",{status:500}))},o=(t,e)=>{const s={};for(const o in t)Object.hasOwn(t,o)&&(s[o]=n(t[o],e));return s},r=(t,e)=>{const s={};for(const n of t)s[n[0]]=o(n[1],e);return t.clear(),s},a=e=>{t({port:e.port,hostname:e.hostname,development:e.development??!1,routes:r(s,e?.schemaValidator)})},c=t=>{const e=s.get(t.url);e?e[t.method]=t:s.set(t.url,{[t.method]:t})};export{c as createRoute,a as listen};
@@ -54,7 +54,5 @@ export type RouteOptions = {
54
54
  url: string;
55
55
  method: HttpMethod;
56
56
  schema?: SchemaData;
57
- onRequest?: RouteHandler;
58
- preHandler?: RouteHandler;
59
57
  handler: RouteHandler;
60
58
  };
@@ -1,4 +1,9 @@
1
1
  import type { Validate } from './schema';
2
+ /**
3
+ *
4
+ *
5
+ * Type of `options` parameter in `listen` function
6
+ */
2
7
  export interface ListenOptions {
3
8
  /**
4
9
  * Server port to listen
@@ -6,7 +11,6 @@ export interface ListenOptions {
6
11
  port?: number | string;
7
12
  /**
8
13
  * Server hostname to listen
9
- *
10
14
  * @example `localhost`, `0.0.0.0`
11
15
  *
12
16
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-crumb",
3
- "version": "0.3.0",
3
+ "version": "0.6.0",
4
4
  "author": "marigold",
5
5
  "module": "dist/index.js",
6
6
  "license": "UNLICENSED",