hono 0.2.0 → 0.2.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
@@ -23,12 +23,12 @@ app.fire()
23
23
  **Hono is fastest** compared to other routers for Cloudflare Workers.
24
24
 
25
25
  ```plain
26
- hono x 748,188 ops/sec ±5.40% (77 runs sampled)
27
- itty-router x 158,817 ops/sec ±3.62% (87 runs sampled)
28
- sunder x 332,339 ops/sec ±1.11% (95 runs sampled)
29
- worktop x 205,906 ops/sec ±4.43% (83 runs sampled)
26
+ hono x 708,671 ops/sec ±2.58% (58 runs sampled)
27
+ itty-router x 159,610 ops/sec ±2.86% (87 runs sampled)
28
+ sunder x 322,846 ops/sec ±2.24% (86 runs sampled)
29
+ worktop x 223,625 ops/sec ±2.01% (95 runs sampled)
30
30
  Fastest is hono
31
- ✨ Done in 52.79s.
31
+ ✨ Done in 57.83s.
32
32
  ```
33
33
 
34
34
  ## Hono in 1 minute
@@ -114,6 +114,16 @@ app
114
114
  .put(() => {...})
115
115
  ```
116
116
 
117
+ ### Custom 404 Response
118
+
119
+ You can customize 404 Not Found response:
120
+
121
+ ```js
122
+ app.get('*', (c) => {
123
+ return c.text('Custom 404 Error', 404)
124
+ })
125
+ ```
126
+
117
127
  ## async/await
118
128
 
119
129
  ```js
@@ -165,19 +175,6 @@ app.use('/message/*', async (c, next) => {
165
175
  app.get('/message/hello', (c) => c.text('Hello Middleware!'))
166
176
  ```
167
177
 
168
- ### Custom 404 Response
169
-
170
- You can customize 404 Not Found response:
171
-
172
- ```js
173
- app.use('*', async (c, next) => {
174
- await next()
175
- if (c.res.status === 404) {
176
- c.res = new Response('Custom 404 Not Found', { status: 404 })
177
- }
178
- })
179
- ```
180
-
181
178
  ### Handling Error
182
179
 
183
180
  ```js
@@ -186,7 +183,7 @@ app.use('*', async (c, next) => {
186
183
  await next()
187
184
  } catch (err) {
188
185
  console.error(`${err}`)
189
- c.res = new Response('Custom Error Message', { status: 500 })
186
+ c.res = c.text('Custom Error Message', { status: 500 })
190
187
  }
191
188
  })
192
189
  ```
@@ -252,7 +249,9 @@ app.get('/welcome', (c) => {
252
249
  c.header('Content-Type', 'text/plain')
253
250
  c.status(201)
254
251
  c.statusText('201 Content Created')
252
+
255
253
  return c.body('Thank you for comming')
254
+
256
255
  /*
257
256
  Same as:
258
257
  return new Response('Thank you for comming', {
@@ -260,7 +259,8 @@ app.get('/welcome', (c) => {
260
259
  statusText: '201 Content Created',
261
260
  headers: {
262
261
  'X-Message': 'Hello',
263
- 'Content-Type': 'text/plain'
262
+ 'Content-Type': 'text/plain',
263
+ 'Content-Length: '22'
264
264
  }
265
265
  })
266
266
  */
package/dist/context.d.ts CHANGED
@@ -12,7 +12,7 @@ export declare class Context {
12
12
  private _headers;
13
13
  private _status;
14
14
  private _statusText;
15
- body: (body: BodyInit, init?: ResponseInit) => Response;
15
+ render: (template: string, params?: object, options?: object) => Promise<Response>;
16
16
  constructor(req: Request, opts?: {
17
17
  res: Response;
18
18
  env: Env;
@@ -21,7 +21,8 @@ export declare class Context {
21
21
  header(name: string, value: string): void;
22
22
  status(number: number): void;
23
23
  statusText(text: string): void;
24
- newResponse(body: BodyInit, init?: ResponseInit): Response;
24
+ newResponse(data: any, init?: ResponseInit): Response;
25
+ body(data: any, status?: number, headers?: Headers): Response;
25
26
  text(text: string, status?: number, headers?: Headers): Response;
26
27
  json(object: object, status?: number, headers?: Headers): Response;
27
28
  html(html: string, status?: number, headers?: Headers): Response;
package/dist/context.js CHANGED
@@ -11,7 +11,6 @@ class Context {
11
11
  this.event = opts.event;
12
12
  }
13
13
  this._headers = {};
14
- this.body = this.newResponse;
15
14
  }
16
15
  header(name, value) {
17
16
  this._headers[name] = value;
@@ -22,42 +21,41 @@ class Context {
22
21
  statusText(text) {
23
22
  this._statusText = text;
24
23
  }
25
- newResponse(body, init = {}) {
26
- init.status = this._status || init.status;
27
- init.statusText = this._statusText || init.statusText;
28
- init.headers = Object.assign(Object.assign({}, init.headers), this._headers);
29
- return new Response(body, init);
24
+ newResponse(data, init = {}) {
25
+ init.status = init.status || this._status;
26
+ init.statusText = init.statusText || this._statusText;
27
+ const Encoder = new TextEncoder();
28
+ const length = data ? data.bytelength || Encoder.encode(data).byteLength : 0;
29
+ init.headers = Object.assign(Object.assign(Object.assign({}, this._headers), init.headers), { 'Content-Length': String(length) });
30
+ return new Response(data, init);
30
31
  }
31
- text(text, status = 200, headers = {}) {
32
+ body(data, status = this._status, headers = this._headers) {
33
+ return this.newResponse(data, {
34
+ status: status,
35
+ headers: headers,
36
+ });
37
+ }
38
+ text(text, status = this._status, headers = {}) {
32
39
  if (typeof text !== 'string') {
33
40
  throw new TypeError('text method arg must be a string!');
34
41
  }
35
42
  headers['Content-Type'] = 'text/plain';
36
- return this.newResponse(text, {
37
- status: status,
38
- headers: headers,
39
- });
43
+ return this.body(text, status, headers);
40
44
  }
41
- json(object, status = 200, headers = {}) {
45
+ json(object, status = this._status, headers = {}) {
42
46
  if (typeof object !== 'object') {
43
47
  throw new TypeError('json method arg must be a object!');
44
48
  }
45
49
  const body = JSON.stringify(object);
46
50
  headers['Content-Type'] = 'application/json; charset=UTF-8';
47
- return this.newResponse(body, {
48
- status: status,
49
- headers: headers,
50
- });
51
+ return this.body(body, status, headers);
51
52
  }
52
- html(html, status = 200, headers = {}) {
53
+ html(html, status = this._status, headers = {}) {
53
54
  if (typeof html !== 'string') {
54
55
  throw new TypeError('html method arg must be a string!');
55
56
  }
56
57
  headers['Content-Type'] = 'text/html; charset=UTF-8';
57
- return this.newResponse(html, {
58
- status: status,
59
- headers: headers,
60
- });
58
+ return this.body(html, status, headers);
61
59
  }
62
60
  redirect(location, status = 302) {
63
61
  if (typeof location !== 'string') {
package/dist/hono.d.ts CHANGED
@@ -8,7 +8,6 @@ declare global {
8
8
  param: (key: string) => string;
9
9
  query: (key: string) => string | null;
10
10
  header: (name: string) => string;
11
- parsedBody: any;
12
11
  }
13
12
  }
14
13
  export declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
package/dist/hono.js CHANGED
@@ -137,11 +137,23 @@ class Hono {
137
137
  });
138
138
  }
139
139
  onError(err) {
140
- console.error(err);
141
- return new Response('Internal Server Error', { status: 500 });
140
+ console.error(`${err}`);
141
+ const message = 'Internal Server Error';
142
+ return new Response(message, {
143
+ status: 500,
144
+ headers: {
145
+ 'Content-Length': message.length.toString(),
146
+ },
147
+ });
142
148
  }
143
149
  notFound() {
144
- return new Response('Not Found', { status: 404 });
150
+ const message = 'Not Found';
151
+ return new Response('Not Found', {
152
+ status: 404,
153
+ headers: {
154
+ 'Content-Length': message.length.toString(),
155
+ },
156
+ });
145
157
  }
146
158
  }
147
159
  exports.Hono = Hono;
@@ -1,2 +1,7 @@
1
1
  import type { Context } from '../../context';
2
+ declare global {
3
+ interface Request {
4
+ parsedBody: any;
5
+ }
6
+ }
2
7
  export declare const bodyParse: () => (ctx: Context, next: Function) => Promise<void>;
@@ -3,7 +3,9 @@ declare global {
3
3
  interface Request {
4
4
  cookie: (name: string) => string;
5
5
  }
6
- interface Response {
6
+ }
7
+ declare module '../../context' {
8
+ interface Context {
7
9
  cookie: (name: string, value: string, options?: CookieOptions) => void;
8
10
  }
9
11
  }
@@ -9,12 +9,11 @@ const cookie = () => {
9
9
  const value = obj[name];
10
10
  return value;
11
11
  };
12
- await next();
13
- c.res.cookie = (name, value, options) => {
14
- console.log('c.res.cookie!');
15
- const cookie = serialize(name, value);
16
- c.res.headers.set('Set-Cookie', cookie);
12
+ c.cookie = (name, value, opt) => {
13
+ const cookie = serialize(name, value, opt);
14
+ c.header('Set-Cookie', cookie);
17
15
  };
16
+ await next();
18
17
  };
19
18
  };
20
19
  exports.cookie = cookie;
@@ -27,8 +26,29 @@ const parse = (cookie) => {
27
26
  }
28
27
  return parsedCookie;
29
28
  };
30
- const serialize = (name, value, options = {}) => {
29
+ const serialize = (name, value, opt = {}) => {
31
30
  value = encodeURIComponent(value);
32
- const cookie = `${name}=${value}`;
31
+ let cookie = `${name}=${value}`;
32
+ if (opt.maxAge) {
33
+ cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
34
+ }
35
+ if (opt.domain) {
36
+ cookie += '; Domain=' + opt.domain;
37
+ }
38
+ if (opt.path) {
39
+ cookie += '; Path=' + opt.path;
40
+ }
41
+ if (opt.expires) {
42
+ cookie += '; Expires=' + opt.expires.toUTCString();
43
+ }
44
+ if (opt.httpOnly) {
45
+ cookie += '; HttpOnly';
46
+ }
47
+ if (opt.secure) {
48
+ cookie += '; Secure';
49
+ }
50
+ if (opt.sameSite) {
51
+ cookie += `; SameSaite=${opt.sameSite}`;
52
+ }
33
53
  return cookie;
34
54
  };
@@ -45,6 +45,8 @@ const cors = (options) => {
45
45
  set('Access-Control-Allow-Headers', headers.join(','));
46
46
  set('Vary', 'Access-Control-Request-Headers');
47
47
  }
48
+ c.res.headers.delete('Content-Length');
49
+ c.res.headers.delete('Content-Type');
48
50
  c.res = new Response(null, {
49
51
  headers: c.res.headers,
50
52
  status: 204,
@@ -11,16 +11,5 @@ const defaultMiddleware = async (c, next) => {
11
11
  return c.req.headers.get(name);
12
12
  };
13
13
  await next();
14
- if (c.res.body) {
15
- // Do not clone Response, ex: c.res.clone().arrayBuffer()
16
- const buffer = await c.res.arrayBuffer();
17
- const res = new Response(buffer, {
18
- status: c.res.status,
19
- statusText: c.res.statusText,
20
- headers: c.res.headers,
21
- });
22
- res.headers.append('Content-Length', buffer.byteLength.toString());
23
- c.res = res;
24
- }
25
14
  };
26
15
  exports.defaultMiddleware = defaultMiddleware;
@@ -0,0 +1,2 @@
1
+ import type { Context } from '../../context';
2
+ export declare const mustache: () => (c: Context, next: Function) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.mustache = void 0;
23
+ const cloudflare_1 = require("../../utils/cloudflare");
24
+ const EXTENSION = '.mustache';
25
+ const mustache = () => {
26
+ return async (c, next) => {
27
+ let Mustache;
28
+ try {
29
+ Mustache = await Promise.resolve().then(() => __importStar(require('mustache')));
30
+ }
31
+ catch (e) {
32
+ console.error(`Mustache is not found! ${e}`);
33
+ throw new Error(`${e}`);
34
+ }
35
+ c.render = async (filename, view = {}, options) => {
36
+ const content = await (0, cloudflare_1.getContentFromKVAsset)(`${filename}${EXTENSION}`);
37
+ if (!content) {
38
+ throw new Error(`Template "${filename}${EXTENSION}" is not found`);
39
+ }
40
+ const partialArgs = {};
41
+ if (options) {
42
+ const partials = options;
43
+ for (const key of Object.keys(partials)) {
44
+ const partialContent = await (0, cloudflare_1.getContentFromKVAsset)(`${partials[key]}${EXTENSION}`);
45
+ if (!partialContent) {
46
+ throw new Error(`Partial Template "${partials[key]}${EXTENSION}" is not found`);
47
+ }
48
+ partialArgs[key] = partialContent;
49
+ }
50
+ }
51
+ const output = Mustache.render(content, view, partialArgs);
52
+ return c.html(output);
53
+ };
54
+ await next();
55
+ };
56
+ };
57
+ exports.mustache = mustache;
@@ -20,4 +20,6 @@ export declare class Middleware {
20
20
  credentials?: boolean;
21
21
  exposeHeaders?: string[];
22
22
  }) => (c: import("./context").Context, next: Function) => Promise<void>;
23
+ static cookie: () => (c: import("./context").Context, next: Function) => Promise<void>;
24
+ static mustache: () => (c: import("./context").Context, next: Function) => Promise<void>;
23
25
  }
@@ -7,6 +7,8 @@ const logger_1 = require("./middleware/logger/logger");
7
7
  const basic_auth_1 = require("./middleware/basic-auth/basic-auth");
8
8
  const body_parse_1 = require("./middleware/body-parse/body-parse");
9
9
  const cors_1 = require("./middleware/cors/cors");
10
+ const cookie_1 = require("./middleware/cookie/cookie");
11
+ const mustache_1 = require("./middleware/mustache/mustache");
10
12
  class Middleware {
11
13
  }
12
14
  exports.Middleware = Middleware;
@@ -16,3 +18,5 @@ Middleware.logger = logger_1.logger;
16
18
  Middleware.basicAuth = basic_auth_1.basicAuth;
17
19
  Middleware.bodyParse = body_parse_1.bodyParse;
18
20
  Middleware.cors = cors_1.cors;
21
+ Middleware.cookie = cookie_1.cookie;
22
+ Middleware.mustache = mustache_1.mustache;
@@ -0,0 +1 @@
1
+ export declare const getContentFromKVAsset: (path: string) => Promise<string>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getContentFromKVAsset = void 0;
4
+ const getContentFromKVAsset = async (path) => {
5
+ let ASSET_MANIFEST;
6
+ if (typeof __STATIC_CONTENT_MANIFEST === 'string') {
7
+ ASSET_MANIFEST = JSON.parse(__STATIC_CONTENT_MANIFEST);
8
+ }
9
+ else {
10
+ ASSET_MANIFEST = __STATIC_CONTENT_MANIFEST;
11
+ }
12
+ const ASSET_NAMESPACE = __STATIC_CONTENT;
13
+ const key = ASSET_MANIFEST[path] || path;
14
+ if (!key) {
15
+ return;
16
+ }
17
+ let content;
18
+ content = await ASSET_NAMESPACE.get(key, { type: 'text' });
19
+ if (content) {
20
+ content = content;
21
+ }
22
+ return content;
23
+ };
24
+ exports.getContentFromKVAsset = getContentFromKVAsset;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "[炎] Ultrafast web framework for Cloudflare Workers.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,6 +36,7 @@
36
36
  "devDependencies": {
37
37
  "@cloudflare/workers-types": "^3.3.0",
38
38
  "@types/jest": "^27.4.0",
39
+ "@types/mustache": "^4.1.2",
39
40
  "@types/node": "^17.0.8",
40
41
  "@typescript-eslint/eslint-plugin": "^5.9.0",
41
42
  "@typescript-eslint/parser": "^5.9.0",
@@ -51,6 +52,7 @@
51
52
  "form-data": "^4.0.0",
52
53
  "jest": "^27.4.5",
53
54
  "jest-environment-miniflare": "^2.0.0",
55
+ "mustache": "^4.2.0",
54
56
  "rimraf": "^3.0.2",
55
57
  "ts-jest": "^27.1.2",
56
58
  "typescript": "^4.5.4"