@kichu12348/bunify 1.0.4 → 1.0.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/core.ts +119 -109
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kichu12348/bunify",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kichu12348/bunify.git"
package/src/core.ts CHANGED
@@ -22,10 +22,10 @@ export type BunifyRequest<T extends RouteSchema = RouteSchema, D = {}> = {
22
22
 
23
23
  type Reply = ReturnType<typeof createReply>;
24
24
 
25
- export type BunifyReply<T extends RouteSchema = RouteSchema> = Reply & {
26
- send: (content: T["Reply"] | Response) => Response;
27
- json: (data: T["Reply"]) => Response;
28
- };
25
+ // export type BunifyReply<T extends RouteSchema = RouteSchema> = Reply & {
26
+ // send: (content: T["Reply"] | Response) => Response;
27
+ // json: (data: T["Reply"]) => Response;
28
+ // };
29
29
 
30
30
  export type Handler<T extends RouteSchema = RouteSchema, D = {}> = (
31
31
  request: BunifyRequest<T, D>,
@@ -68,59 +68,67 @@ interface RegisterOptions {
68
68
  prefix?: string;
69
69
  }
70
70
 
71
- function createReply() {
72
- let status = 200;
73
- const headers = new Headers();
74
- let body: any = null;
75
- let sent = false;
76
-
77
- return {
78
- get sent() {
79
- return sent;
80
- },
81
-
82
- status(code: number) {
83
- status = code;
84
- return this;
85
- },
86
- code(code: number) {
87
- return this.status(code);
88
- },
89
- headers(key: string, value: string) {
90
- headers.set(key, value);
91
- return this;
92
- },
93
- json(data: any) {
94
- return this.headers("Content-Type", "application/json").send(data);
95
- },
96
- redirect(url: string) {
97
- return this.status(302).headers("Location", url).send(null);
98
- },
99
- html(content: string) {
100
- return this.headers("Content-Type", "text/html").send(content);
101
- },
102
- send(content: any) {
103
- if (sent) {
104
- throw new Error("Reply has already been sent.");
105
- }
106
- sent = true;
107
- if (content instanceof Response) return content;
71
+ // Look at how lean this is! No closures, no heavy standard objects.
72
+ export class BunifyReply<T extends RouteSchema = RouteSchema> {
73
+ private _status: number = 200;
74
+ private _headers: Record<string, string> = {}; // Fast, plain object
75
+ private _body: any = null;
76
+ public sent: boolean = false;
108
77
 
109
- if (typeof content === "object") {
110
- headers.set("Content-Type", "application/json");
111
- content = JSON.stringify(content);
112
- }
78
+ status(code: number) {
79
+ this._status = code;
80
+ return this;
81
+ }
113
82
 
114
- body = content;
115
- return this.build();
116
- },
83
+ code(code: number) {
84
+ return this.status(code);
85
+ }
117
86
 
118
- build() {
119
- if (body instanceof Response) return body;
87
+ headers(key: string, value: string) {
88
+ this._headers[key] = value;
89
+ return this;
90
+ }
120
91
 
121
- return new Response(body, { status, headers });
122
- },
123
- };
92
+ json(data: T["Reply"]) {
93
+ return this.headers("Content-Type", "application/json").send(data);
94
+ }
95
+
96
+ redirect(url: string) {
97
+ return this.status(302).headers("Location", url).send(null);
98
+ }
99
+
100
+ html(content: string) {
101
+ return this.headers("Content-Type", "text/html").send(content);
102
+ }
103
+
104
+ send(content: T["Reply"] | Response | null) {
105
+ if (this.sent) {
106
+ throw new Error("Reply has already been sent.");
107
+ }
108
+ this.sent = true;
109
+
110
+ if (content instanceof Response) return content;
111
+
112
+ if (typeof content === "object" && content !== null) {
113
+ this._headers["Content-Type"] = "application/json";
114
+ content = JSON.stringify(content) as any;
115
+ }
116
+
117
+ this._body = content;
118
+ return this.build();
119
+ }
120
+
121
+ build() {
122
+ if (this._body instanceof Response) return this._body;
123
+ return new Response(this._body, {
124
+ status: this._status,
125
+ headers: this._headers,
126
+ });
127
+ }
128
+ }
129
+
130
+ function createReply() {
131
+ return new BunifyReply();
124
132
  }
125
133
 
126
134
  export class Bunify<Decorators extends Record<string, any> = {}> {
@@ -156,14 +164,26 @@ export class Bunify<Decorators extends Record<string, any> = {}> {
156
164
  handler,
157
165
  );
158
166
  const wrappedHandler = async (req: Bun.BunRequest) => {
159
- const url = new URL(req.url);
160
-
167
+ let parsedQuery: any = null;
161
168
  const request: BunifyRequest<T, Decorators> = {
162
169
  raw: req,
163
- query: Object.fromEntries(url.searchParams.entries()) as BunifyRequest<
164
- T,
165
- Decorators
166
- >["query"],
170
+ // Lazily parse query parameters when accessed for the first time to optimize performance
171
+ get query() {
172
+ if (parsedQuery !== null) return parsedQuery;
173
+
174
+ const urlStr = req.url;
175
+ const queryStart = urlStr.indexOf("?", 8);
176
+
177
+ if (queryStart === -1) {
178
+ parsedQuery = {};
179
+ return parsedQuery;
180
+ }
181
+
182
+ const queryString = urlStr.substring(queryStart + 1);
183
+ parsedQuery = Object.fromEntries(new URLSearchParams(queryString));
184
+
185
+ return parsedQuery;
186
+ },
167
187
  params: (req.params || {}) as BunifyRequest<T, Decorators>["params"],
168
188
  json: () => req.json(),
169
189
  text: () => req.text(),
@@ -207,23 +227,15 @@ export class Bunify<Decorators extends Record<string, any> = {}> {
207
227
  request: BunifyRequest<T, Decorators>,
208
228
  reply: BunifyReply<T>,
209
229
  ) => {
210
- let index = -1;
211
- const next = async (i: number): Promise<any> => {
212
- if (i <= index) {
213
- throw new Error("next() called multiple times");
214
- }
215
- index = i;
216
-
217
- if (i === middlewares.length) {
218
- return handler(request, reply);
230
+ for (let i = 0; i < middlewares.length; i++) {
231
+ const fn = middlewares[i];
232
+ if (fn) {
233
+ await fn(request, reply, async () => {});
234
+ if (reply.sent) return;
219
235
  }
220
-
221
- const mw = middlewares[i];
222
- if (mw) {
223
- return mw(request, reply, () => next(i + 1));
224
- }
225
- };
226
- return next(0);
236
+ }
237
+ const res = await handler(request, reply);
238
+ if (res !== undefined && !reply.sent) return reply.send(res);
227
239
  };
228
240
  }
229
241
 
@@ -236,25 +248,18 @@ export class Bunify<Decorators extends Record<string, any> = {}> {
236
248
  return { handler, middlewares };
237
249
  }
238
250
 
239
- private runHooks<T extends RouteSchema>(
251
+ private async runHooks<T extends RouteSchema>(
240
252
  hooks: Middleware<T, Decorators>[],
241
253
  request: BunifyRequest<T, Decorators>,
242
254
  reply: BunifyReply<T>,
243
255
  ) {
244
- let index = -1;
245
-
246
- const next = async (i: number): Promise<any> => {
247
- if (i <= index) {
248
- throw new Error("next() called multiple times in hooks");
249
- }
250
- index = i;
251
-
256
+ for (let i = 0; i < hooks.length; i++) {
252
257
  const fn = hooks[i];
253
- if (!fn) return;
254
-
255
- return fn(request, reply, () => next(i + 1));
256
- };
257
- return next(0);
258
+ if (fn) {
259
+ await fn(request, reply, async () => {});
260
+ if (reply.sent) return;
261
+ }
262
+ }
258
263
  }
259
264
 
260
265
  private async runPipeline<T extends RouteSchema>(
@@ -262,29 +267,34 @@ export class Bunify<Decorators extends Record<string, any> = {}> {
262
267
  reply: BunifyReply<T>,
263
268
  handler: Handler<T, Decorators>,
264
269
  ) {
265
- let res = await this.runHooks(
266
- this.hooks.onRequest as Middleware<T, Decorators>[],
267
- request,
268
- reply,
269
- );
270
- if (reply.sent) return;
271
-
272
- res = await this.runHooks(
273
- this.hooks.preHandler as Middleware<T, Decorators>[],
274
- request,
275
- reply,
276
- );
277
- if (reply.sent) return;
270
+ if (this.hooks.onRequest.length > 0) {
271
+ await this.runHooks(
272
+ this.hooks.onRequest as Middleware<T, Decorators>[],
273
+ request,
274
+ reply,
275
+ );
276
+ if (reply.sent) return;
277
+ }
278
278
 
279
- res = await handler(request, reply);
280
- if (reply.sent) return res;
279
+ if (this.hooks.preHandler.length > 0) {
280
+ await this.runHooks(
281
+ this.hooks.preHandler as Middleware<T, Decorators>[],
282
+ request,
283
+ reply,
284
+ );
285
+ if (reply.sent) return;
286
+ }
281
287
 
282
- await this.runHooks(
283
- this.hooks.onResponse as Middleware<T, Decorators>[],
284
- request,
285
- reply,
286
- );
288
+ const res = await handler(request, reply);
289
+ if (res !== undefined && !reply.sent) return reply.send(res);
287
290
 
291
+ if (this.hooks.onResponse.length > 0) {
292
+ await this.runHooks(
293
+ this.hooks.onResponse as Middleware<T, Decorators>[],
294
+ request,
295
+ reply,
296
+ );
297
+ }
288
298
  return res;
289
299
  }
290
300