@lowerdeck/hono 1.0.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/.turbo/turbo-build.log +14 -0
- package/.turbo/turbo-test.log +11 -0
- package/README.md +69 -0
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/hono.d.ts +5 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/package.json +36 -0
- package/src/context.ts +15 -0
- package/src/hono.ts +32 -0
- package/src/index.ts +3 -0
- package/src/validation.ts +59 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mmicrobundle[0m
|
|
3
|
+
No name was provided for external module '@lowerdeck/forwarded-for' in output.globals – guessing 'forwardedFor'
|
|
4
|
+
No name was provided for external module '@lowerdeck/error' in output.globals – guessing 'error'
|
|
5
|
+
No name was provided for external module 'hono/cors' in output.globals – guessing 'cors'
|
|
6
|
+
[34mBuild "@lowerdeck/hono" to dist:[39m
|
|
7
|
+
[32m821 B[39m: [37mindex.cjs[39m.gz
|
|
8
|
+
[32m707 B[39m: [37mindex.cjs[39m.br
|
|
9
|
+
[32m720 B[39m: [37mindex.modern.js[39m.gz
|
|
10
|
+
[32m626 B[39m: [37mindex.modern.js[39m.br
|
|
11
|
+
[32m805 B[39m: [37mindex.module.js[39m.gz
|
|
12
|
+
[32m705 B[39m: [37mindex.module.js[39m.br
|
|
13
|
+
[32m911 B[39m: [37mindex.umd.js[39m.gz
|
|
14
|
+
[32m782 B[39m: [37mindex.umd.js[39m.br
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
[0m[2m[35m$[0m [2m[1mvitest run --passWithNoTests[0m
|
|
3
|
+
[?25l
|
|
4
|
+
[1m[46m RUN [49m[22m [36mv3.2.4 [39m[90m/Users/tobias/code/metorial/metorial-enterprise/oss/src/packages/server/hono[39m
|
|
5
|
+
|
|
6
|
+
No test files found, exiting with code 0
|
|
7
|
+
|
|
8
|
+
[2minclude: [22m[33m**/*.{test,spec}.?(c|m)[jt]s?(x)[39m
|
|
9
|
+
[2mexclude: [22m[33m**/node_modules/**[2m, [22m**/dist/**[2m, [22m**/cypress/**[2m, [22m**/.{idea,git,cache,output,temp}/**[2m, [22m**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*[39m
|
|
10
|
+
|
|
11
|
+
[?25h
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# `@lowerdeck/hono`
|
|
2
|
+
|
|
3
|
+
Create Hono web applications with Metorial defaults. Includes error handling for ServiceError, 404 handling, and CORS configuration.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @lowerdeck/hono
|
|
9
|
+
yarn add @lowerdeck/hono
|
|
10
|
+
bun add @lowerdeck/hono
|
|
11
|
+
pnpm add @lowerdeck/hono
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { createHono, cors } from '@lowerdeck/hono';
|
|
18
|
+
|
|
19
|
+
// Create Hono app with defaults
|
|
20
|
+
const app = createHono('/api');
|
|
21
|
+
|
|
22
|
+
// Routes
|
|
23
|
+
app.get('/users/:id', async (c) => {
|
|
24
|
+
const id = c.req.param('id');
|
|
25
|
+
const user = await getUser(id);
|
|
26
|
+
return c.json(user);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ServiceError handling is automatic
|
|
30
|
+
import { createError } from '@lowerdeck/error';
|
|
31
|
+
|
|
32
|
+
const UserNotFound = createError({
|
|
33
|
+
statusCode: 404,
|
|
34
|
+
errorCode: 'USER_NOT_FOUND',
|
|
35
|
+
message: 'User not found'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.get('/users/:id', async (c) => {
|
|
39
|
+
const user = await getUser(id);
|
|
40
|
+
if (!user) {
|
|
41
|
+
throw UserNotFound(); // Automatically handled
|
|
42
|
+
}
|
|
43
|
+
return c.json(user);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Custom CORS configuration
|
|
47
|
+
app.use('/api/*', cors({
|
|
48
|
+
origin: 'https://example.com',
|
|
49
|
+
credentials: true
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
export default app;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Features
|
|
56
|
+
|
|
57
|
+
- Automatic ServiceError handling with proper HTTP status codes
|
|
58
|
+
- 404 handling for undefined routes
|
|
59
|
+
- X-Powered-By header set to Metorial
|
|
60
|
+
- Optional base path prefix
|
|
61
|
+
- CORS middleware included
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
This project is licensed under the Apache License 2.0.
|
|
66
|
+
|
|
67
|
+
<div align="center">
|
|
68
|
+
<sub>Built with ❤️ by <a href="https://metorial.com">Metorial</a></sub>
|
|
69
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,eAAO,IAAI,iBAAiB,GAAI,GAAG,OAAO;;;CAWzC,CAAC"}
|
package/dist/hono.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC;AAE9B,eAAO,IAAI,UAAU,GAAI,CAAC,SAAS,GAAG,EAAE,WAAW,MAAM,mDAyBxD,CAAC"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var r=require("@lowerdeck/forwarded-for"),e=require("@lowerdeck/error"),o=require("hono"),n=require("hono/cors");Object.defineProperty(exports,"cors",{enumerable:!0,get:function(){return n.cors}}),exports.createHono=function(r){var n=new o.Hono;return r&&(n=n.basePath(r)),n.use(function(r,e){try{return Promise.resolve(e()).then(function(){r.res.headers.set("X-Powered-By","Metorial")})}catch(r){return Promise.reject(r)}}),n.notFound(function(r){return r.json(e.notFoundError("endpoint",null).toResponse(),404)}),n.onError(function(r,o){return e.isServiceError(r)?o.json(r.toResponse(),r.data.status):(console.error(r),o.json(e.internalServerError().toResponse(),500))}),n},exports.useRequestContext=function(e){var o,n,t,s;return{ua:e.req.header("user-agent"),ip:null!=(o=r.parseForwardedFor(null!=(n=null!=(t=null!=(s=e.req.header("metorial-connecting-ip"))?s:e.req.header("cf-connecting-ip"))?t:e.req.header("x-forwarded-for"))?n:e.req.header("x-real-ip")))?o:"0.0.0.0"}},exports.useValidatedBody=function(r,o){try{var n,t=function(r){var t=o.validate(n);if(!t.success)throw new e.ServiceError(e.validationError({entity:"body",errors:t.errors}));return t.value},s=null!=(a=r.req.header("Content-Type"))&&a.includes("application/x-www-form-urlencoded")?Promise.resolve(r.req.text()).then(function(r){n=Object.fromEntries(new URLSearchParams(r))}):function(r,e){try{var o=r()}catch(r){return e()}return o&&o.then?o.then(void 0,e):o}(function(){return Promise.resolve(r.req.json()).then(function(r){n=r})},function(){throw new e.ServiceError(e.badRequestError({message:"Invalid JSON body"}))});return Promise.resolve(s&&s.then?s.then(t):t())}catch(r){return Promise.reject(r)}var a},exports.useValidatedQuery=function(r,o){try{var n;try{n=r.req.query()}catch(r){throw new e.ServiceError(e.badRequestError({message:"Invalid JSON body"}))}var t=o.validate(n);if(!t.success)throw new e.ServiceError(e.validationError({entity:"query",errors:t.errors}));return Promise.resolve(t.value)}catch(r){return Promise.reject(r)}};
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/hono.ts","../src/context.ts","../src/validation.ts"],"sourcesContent":["import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';\nimport { Context, Env, Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nexport { cors, type Context };\n\nexport let createHono = <E extends Env>(basePath?: string) => {\n let app = new Hono<E>();\n if (basePath) app = app.basePath(basePath);\n\n app.use(async (c, next) => {\n await next();\n\n c.res.headers.set('X-Powered-By', 'Metorial');\n });\n\n app.notFound(c => {\n return c.json(notFoundError('endpoint', null).toResponse(), 404);\n });\n\n app.onError((e, c) => {\n if (isServiceError(e)) {\n return c.json(e.toResponse(), e.data.status);\n }\n\n console.error(e);\n\n return c.json(internalServerError().toResponse(), 500);\n });\n\n return app;\n};\n","import { parseForwardedFor } from '@lowerdeck/forwarded-for';\nimport { Context } from 'hono';\n\nexport let useRequestContext = (c: Context) => {\n let ua = c.req.header('user-agent');\n let ip =\n parseForwardedFor(\n c.req.header('metorial-connecting-ip') ??\n c.req.header('cf-connecting-ip') ??\n c.req.header('x-forwarded-for') ??\n c.req.header('x-real-ip')\n ) ?? '0.0.0.0';\n\n return { ua, ip };\n};\n","import { badRequestError, ServiceError, validationError } from '@lowerdeck/error';\nimport { ValidationType } from '@lowerdeck/validation';\nimport { Context } from 'hono';\n\nexport let useValidatedBody = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n if (c.req.header('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries(new URLSearchParams(await c.req.text()));\n } else {\n try {\n body = await c.req.json();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'body',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n\nexport let useValidatedQuery = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n try {\n body = c.req.query();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'query',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n"],"names":["basePath","app","Hono","use","c","next","Promise","resolve","then","res","headers","set","e","reject","notFound","json","notFoundError","toResponse","onError","isServiceError","data","status","console","error","internalServerError","_parseForwardedFor","_ref","_ref2","_c$req$header","ua","req","header","ip","parseForwardedFor","v","_temp2","body","_result","val","validate","success","ServiceError","validationError","entity","errors","value","_temp","includes","text","_c$req$text","Object","fromEntries","URLSearchParams","_catch","_c$req$json","badRequestError","message","query"],"mappings":"wNAMwB,SAAgBA,GACtC,IAAIC,EAAM,IAAIC,EAAAA,KAuBd,OAtBIF,IAAUC,EAAMA,EAAID,SAASA,IAEjCC,EAAIE,IAAWC,SAAAA,EAAGC,GAAI,WAAIC,QAAAC,QAClBF,KAAMG,KAEZJ,WAAAA,EAAEK,IAAIC,QAAQC,IAAI,eAAgB,WAAY,EAChD,CAAC,MAAAC,UAAAN,QAAAO,OAAAD,EAAA,CAAA,GAEDX,EAAIa,SAAS,SAAAV,GACX,OAAOA,EAAEW,KAAKC,EAAAA,cAAc,WAAY,MAAMC,aAAc,IAC9D,GAEAhB,EAAIiB,QAAQ,SAACN,EAAGR,GACd,OAAIe,iBAAeP,GACVR,EAAEW,KAAKH,EAAEK,aAAcL,EAAEQ,KAAKC,SAGvCC,QAAQC,MAAMX,GAEPR,EAAEW,KAAKS,wBAAsBP,aAAc,KACpD,GAEOhB,CACT,4BC5B+B,SAACG,GAAcqB,IAAAA,EAAAC,EAAAC,EAAAC,EAU5C,MAAO,CAAEC,GATAzB,EAAE0B,IAAIC,OAAO,cASTC,GAFV,OANGP,EACJQ,EAAAA,kBAGmC,OAHlBP,EAEmB,OAFnBC,EACuBC,OADvBA,EACfxB,EAAE0B,IAAIC,OAAO,2BAAyBH,EACpCxB,EAAE0B,IAAIC,OAAO,qBAAmBJ,EAChCvB,EAAE0B,IAAIC,OAAO,oBAAkBL,EAC/BtB,EAAE0B,IAAIC,OAAO,eAChBN,EAAI,UAGT,2BCVW,SAA6BrB,EAAY8B,OAAoCC,IAClFC,EADkFD,WAAAE,GAiBtF,IAAIC,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAU,IAAAC,eACRC,EAAAA,gBAAgB,CACdC,OAAQ,OACRC,OAAQN,EAAIM,UAKlB,OAAON,EAAIO,KAAM,EA1BHC,EAEkB,OAFlBlB,EAEVxB,EAAE0B,IAAIC,OAAO,kBAAbH,EAA8BmB,SAAS,qCAAoCzC,QAAAC,QACzBH,EAAE0B,IAAIkB,QAAMxC,cAAAyC,GAAhEb,EAAOc,OAAOC,YAAY,IAAIC,gBAAeH,GAAsB,uFAAAI,CAE/D,WAAA,OAAA/C,QAAAC,QACWH,EAAE0B,IAAIf,QAAMP,KAAA,SAAA8C,GAAzBlB,EAAIkB,CAAsB,EAC5B,EAAC,WACC,MAAM,IAAIb,EAAYA,aACpBc,kBAAgB,CACdC,QAAS,sBAGf,GAAC,OAAAlD,QAAAC,QAAAuC,GAAAA,EAAAtC,KAAAsC,EAAAtC,KAAA2B,GAAAA,IAcL,CAAC,MAAAvB,UAAAN,QAAAO,OAAAD,GA3BegB,IAAAA,6BA6BY,SAAaxB,EAAY8B,OACnD,IAAIE,EAEJ,IACEA,EAAOhC,EAAE0B,IAAI2B,OACf,CAAE,MAAO7C,GACP,UAAU6B,EAAYA,aACpBc,kBAAgB,CACdC,QAAS,sBAGf,CAEA,IAAIlB,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAM,IAAIC,EAAAA,aACRC,EAAeA,gBAAC,CACdC,OAAQ,QACRC,OAAQN,EAAIM,UAKlB,OAAAtC,QAAAC,QAAO+B,EAAIO,MACb,CAAC,MAAAjC,GAAA,OAAAN,QAAAO,OAAAD,EAAA,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{parseForwardedFor as e}from"@lowerdeck/forwarded-for";import{notFoundError as r,isServiceError as o,internalServerError as t,ServiceError as n,badRequestError as a,validationError as s}from"@lowerdeck/error";import{Hono as l}from"hono";export{cors}from"hono/cors";let i=r=>{var o,t,n,a;return{ua:r.req.header("user-agent"),ip:null!=(o=e(null!=(t=null!=(n=null!=(a=r.req.header("metorial-connecting-ip"))?a:r.req.header("cf-connecting-ip"))?n:r.req.header("x-forwarded-for"))?t:r.req.header("x-real-ip")))?o:"0.0.0.0"}},d=e=>{let n=new l;return e&&(n=n.basePath(e)),n.use(async(e,r)=>{await r(),e.res.headers.set("X-Powered-By","Metorial")}),n.notFound(e=>e.json(r("endpoint",null).toResponse(),404)),n.onError((e,r)=>o(e)?r.json(e.toResponse(),e.data.status):(console.error(e),r.json(t().toResponse(),500))),n},c=async(e,r)=>{var o;let t;if(null!=(o=e.req.header("Content-Type"))&&o.includes("application/x-www-form-urlencoded"))t=Object.fromEntries(new URLSearchParams(await e.req.text()));else try{t=await e.req.json()}catch(e){throw new n(a({message:"Invalid JSON body"}))}let l=r.validate(t);if(!l.success)throw new n(s({entity:"body",errors:l.errors}));return l.value},u=async(e,r)=>{let o;try{o=e.req.query()}catch(e){throw new n(a({message:"Invalid JSON body"}))}let t=r.validate(o);if(!t.success)throw new n(s({entity:"query",errors:t.errors}));return t.value};export{d as createHono,i as useRequestContext,c as useValidatedBody,u as useValidatedQuery};
|
|
2
|
+
//# sourceMappingURL=index.modern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.modern.js","sources":["../src/context.ts","../src/hono.ts","../src/validation.ts"],"sourcesContent":["import { parseForwardedFor } from '@lowerdeck/forwarded-for';\nimport { Context } from 'hono';\n\nexport let useRequestContext = (c: Context) => {\n let ua = c.req.header('user-agent');\n let ip =\n parseForwardedFor(\n c.req.header('metorial-connecting-ip') ??\n c.req.header('cf-connecting-ip') ??\n c.req.header('x-forwarded-for') ??\n c.req.header('x-real-ip')\n ) ?? '0.0.0.0';\n\n return { ua, ip };\n};\n","import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';\nimport { Context, Env, Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nexport { cors, type Context };\n\nexport let createHono = <E extends Env>(basePath?: string) => {\n let app = new Hono<E>();\n if (basePath) app = app.basePath(basePath);\n\n app.use(async (c, next) => {\n await next();\n\n c.res.headers.set('X-Powered-By', 'Metorial');\n });\n\n app.notFound(c => {\n return c.json(notFoundError('endpoint', null).toResponse(), 404);\n });\n\n app.onError((e, c) => {\n if (isServiceError(e)) {\n return c.json(e.toResponse(), e.data.status);\n }\n\n console.error(e);\n\n return c.json(internalServerError().toResponse(), 500);\n });\n\n return app;\n};\n","import { badRequestError, ServiceError, validationError } from '@lowerdeck/error';\nimport { ValidationType } from '@lowerdeck/validation';\nimport { Context } from 'hono';\n\nexport let useValidatedBody = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n if (c.req.header('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries(new URLSearchParams(await c.req.text()));\n } else {\n try {\n body = await c.req.json();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'body',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n\nexport let useValidatedQuery = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n try {\n body = c.req.query();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'query',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n"],"names":["useRequestContext","c","_parseForwardedFor","_ref","_ref2","_c$req$header","ua","req","header","ip","parseForwardedFor","createHono","basePath","app","Hono","use","async","next","res","headers","set","notFound","json","notFoundError","toResponse","onError","e","isServiceError","data","status","console","error","internalServerError","useValidatedBody","v","body","includes","Object","fromEntries","URLSearchParams","text","ServiceError","badRequestError","message","val","validate","success","validationError","entity","errors","value","useValidatedQuery","query"],"mappings":"+QAGW,IAAAA,EAAqBC,QAAcC,EAAAC,EAAAC,EAAAC,EAU5C,MAAO,CAAEC,GATAL,EAAEM,IAAIC,OAAO,cASTC,UARPP,EACJQ,EAGmCP,OAHlBA,SAAAC,EACuB,OADvBC,EACfJ,EAAEM,IAAIC,OAAO,2BAAyBH,EACpCJ,EAAEM,IAAIC,OAAO,qBAAmBJ,EAChCH,EAAEM,IAAIC,OAAO,oBAAkBL,EAC/BF,EAAEM,IAAIC,OAAO,eAChBN,EAAI,YCLES,EAA6BC,IACtC,IAAIC,EAAM,IAAIC,EAuBd,OAtBIF,IAAUC,EAAMA,EAAID,SAASA,IAEjCC,EAAIE,IAAIC,MAAOf,EAAGgB,WACVA,IAENhB,EAAEiB,IAAIC,QAAQC,IAAI,eAAgB,cAGpCP,EAAIQ,SAASpB,GACJA,EAAEqB,KAAKC,EAAc,WAAY,MAAMC,aAAc,MAG9DX,EAAIY,QAAQ,CAACC,EAAGzB,IACV0B,EAAeD,GACVzB,EAAEqB,KAAKI,EAAEF,aAAcE,EAAEE,KAAKC,SAGvCC,QAAQC,MAAML,GAEPzB,EAAEqB,KAAKU,IAAsBR,aAAc,OAG7CX,GC1BEoB,EAAmBjB,MAAUf,EAAYiC,KAAoC,IAAA7B,EACtF,IAAI8B,EAEJ,GAAI9B,OAAJA,EAAIJ,EAAEM,IAAIC,OAAO,kBAAbH,EAA8B+B,SAAS,qCACzCD,EAAOE,OAAOC,YAAY,IAAIC,sBAAsBtC,EAAEM,IAAIiC,cAE1D,IACEL,QAAalC,EAAEM,IAAIe,MACrB,CAAE,MAAOI,GACP,UAAUe,EACRC,EAAgB,CACdC,QAAS,sBAGf,CAGF,IAAIC,EAAMV,EAAEW,SAASV,GACrB,IAAKS,EAAIE,QACP,MAAU,IAAAL,EACRM,EAAgB,CACdC,OAAQ,OACRC,OAAQL,EAAIK,UAKlB,OAAOL,EAAIM,OAGFC,EAAoBnC,MAAUf,EAAYiC,KACnD,IAAIC,EAEJ,IACEA,EAAOlC,EAAEM,IAAI6C,OACf,CAAE,MAAO1B,GACP,MAAM,IAAIe,EACRC,EAAgB,CACdC,QAAS,sBAGf,CAEA,IAAIC,EAAMV,EAAEW,SAASV,GACrB,IAAKS,EAAIE,QACP,MAAM,IAAIL,EACRM,EAAgB,CACdC,OAAQ,QACRC,OAAQL,EAAIK,UAKlB,OAAOL,EAAIM"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{parseForwardedFor as e}from"@lowerdeck/forwarded-for";import{notFoundError as r,isServiceError as n,internalServerError as o,ServiceError as t,validationError as s,badRequestError as a}from"@lowerdeck/error";import{Hono as i}from"hono";export{cors}from"hono/cors";var u=function(r){var n,o,t,s;return{ua:r.req.header("user-agent"),ip:null!=(n=e(null!=(o=null!=(t=null!=(s=r.req.header("metorial-connecting-ip"))?s:r.req.header("cf-connecting-ip"))?t:r.req.header("x-forwarded-for"))?o:r.req.header("x-real-ip")))?n:"0.0.0.0"}},c=function(e){var t=new i;return e&&(t=t.basePath(e)),t.use(function(e,r){try{return Promise.resolve(r()).then(function(){e.res.headers.set("X-Powered-By","Metorial")})}catch(e){return Promise.reject(e)}}),t.notFound(function(e){return e.json(r("endpoint",null).toResponse(),404)}),t.onError(function(e,r){return n(e)?r.json(e.toResponse(),e.data.status):(console.error(e),r.json(o().toResponse(),500))}),t},l=function(e,r){try{var n,o=function(e){var o=r.validate(n);if(!o.success)throw new t(s({entity:"body",errors:o.errors}));return o.value},i=null!=(u=e.req.header("Content-Type"))&&u.includes("application/x-www-form-urlencoded")?Promise.resolve(e.req.text()).then(function(e){n=Object.fromEntries(new URLSearchParams(e))}):function(e,r){try{var n=e()}catch(e){return r()}return n&&n.then?n.then(void 0,r):n}(function(){return Promise.resolve(e.req.json()).then(function(e){n=e})},function(){throw new t(a({message:"Invalid JSON body"}))});return Promise.resolve(i&&i.then?i.then(o):o())}catch(e){return Promise.reject(e)}var u},d=function(e,r){try{var n;try{n=e.req.query()}catch(e){throw new t(a({message:"Invalid JSON body"}))}var o=r.validate(n);if(!o.success)throw new t(s({entity:"query",errors:o.errors}));return Promise.resolve(o.value)}catch(e){return Promise.reject(e)}};export{c as createHono,u as useRequestContext,l as useValidatedBody,d as useValidatedQuery};
|
|
2
|
+
//# sourceMappingURL=index.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.module.js","sources":["../src/context.ts","../src/hono.ts","../src/validation.ts"],"sourcesContent":["import { parseForwardedFor } from '@lowerdeck/forwarded-for';\nimport { Context } from 'hono';\n\nexport let useRequestContext = (c: Context) => {\n let ua = c.req.header('user-agent');\n let ip =\n parseForwardedFor(\n c.req.header('metorial-connecting-ip') ??\n c.req.header('cf-connecting-ip') ??\n c.req.header('x-forwarded-for') ??\n c.req.header('x-real-ip')\n ) ?? '0.0.0.0';\n\n return { ua, ip };\n};\n","import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';\nimport { Context, Env, Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nexport { cors, type Context };\n\nexport let createHono = <E extends Env>(basePath?: string) => {\n let app = new Hono<E>();\n if (basePath) app = app.basePath(basePath);\n\n app.use(async (c, next) => {\n await next();\n\n c.res.headers.set('X-Powered-By', 'Metorial');\n });\n\n app.notFound(c => {\n return c.json(notFoundError('endpoint', null).toResponse(), 404);\n });\n\n app.onError((e, c) => {\n if (isServiceError(e)) {\n return c.json(e.toResponse(), e.data.status);\n }\n\n console.error(e);\n\n return c.json(internalServerError().toResponse(), 500);\n });\n\n return app;\n};\n","import { badRequestError, ServiceError, validationError } from '@lowerdeck/error';\nimport { ValidationType } from '@lowerdeck/validation';\nimport { Context } from 'hono';\n\nexport let useValidatedBody = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n if (c.req.header('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries(new URLSearchParams(await c.req.text()));\n } else {\n try {\n body = await c.req.json();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'body',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n\nexport let useValidatedQuery = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n try {\n body = c.req.query();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'query',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n"],"names":["useRequestContext","c","_parseForwardedFor","_ref","_ref2","_c$req$header","ua","req","header","ip","parseForwardedFor","createHono","basePath","app","Hono","use","next","Promise","resolve","then","res","headers","set","e","reject","notFound","json","notFoundError","toResponse","onError","isServiceError","data","status","console","error","internalServerError","useValidatedBody","v","_temp2","body","_result","val","validate","success","ServiceError","validationError","entity","errors","value","_temp","includes","text","_c$req$text","Object","fromEntries","URLSearchParams","_catch","_c$req$json","badRequestError","message","useValidatedQuery","query"],"mappings":"+QAGW,IAAAA,EAAoB,SAACC,GAAcC,IAAAA,EAAAC,EAAAC,EAAAC,EAU5C,MAAO,CAAEC,GATAL,EAAEM,IAAIC,OAAO,cASTC,GAFV,OANGP,EACJQ,EAGmC,OAHlBP,EAEmB,OAFnBC,EACuBC,OADvBA,EACfJ,EAAEM,IAAIC,OAAO,2BAAyBH,EACpCJ,EAAEM,IAAIC,OAAO,qBAAmBJ,EAChCH,EAAEM,IAAIC,OAAO,oBAAkBL,EAC/BF,EAAEM,IAAIC,OAAO,eAChBN,EAAI,UAGT,ECRWS,EAAa,SAAgBC,GACtC,IAAIC,EAAM,IAAIC,EAuBd,OAtBIF,IAAUC,EAAMA,EAAID,SAASA,IAEjCC,EAAIE,IAAWd,SAAAA,EAAGe,GAAI,WAAIC,QAAAC,QAClBF,KAAMG,KAEZlB,WAAAA,EAAEmB,IAAIC,QAAQC,IAAI,eAAgB,WAAY,EAChD,CAAC,MAAAC,UAAAN,QAAAO,OAAAD,EAAA,CAAA,GAEDV,EAAIY,SAAS,SAAAxB,GACX,OAAOA,EAAEyB,KAAKC,EAAc,WAAY,MAAMC,aAAc,IAC9D,GAEAf,EAAIgB,QAAQ,SAACN,EAAGtB,GACd,OAAI6B,EAAeP,GACVtB,EAAEyB,KAAKH,EAAEK,aAAcL,EAAEQ,KAAKC,SAGvCC,QAAQC,MAAMX,GAEPtB,EAAEyB,KAAKS,IAAsBP,aAAc,KACpD,GAEOf,CACT,EC3BWuB,EAAA,SAA6BnC,EAAYoC,OAAoCC,IAClFC,EADkFD,WAAAE,GAiBtF,IAAIC,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAU,IAAAC,EACRC,EAAgB,CACdC,OAAQ,OACRC,OAAQN,EAAIM,UAKlB,OAAON,EAAIO,KAAM,EA1BHC,EAEkB,OAFlB5C,EAEVJ,EAAEM,IAAIC,OAAO,kBAAbH,EAA8B6C,SAAS,qCAAoCjC,QAAAC,QACzBjB,EAAEM,IAAI4C,QAAMhC,cAAAiC,GAAhEb,EAAOc,OAAOC,YAAY,IAAIC,gBAAeH,GAAsB,uFAAAI,CAE/D,WAAA,OAAAvC,QAAAC,QACWjB,EAAEM,IAAImB,QAAMP,KAAA,SAAAsC,GAAzBlB,EAAIkB,CAAsB,EAC5B,EAAC,WACC,MAAM,IAAIb,EACRc,EAAgB,CACdC,QAAS,sBAGf,GAAC,OAAA1C,QAAAC,QAAA+B,GAAAA,EAAA9B,KAAA8B,EAAA9B,KAAAmB,GAAAA,IAcL,CAAC,MAAAf,UAAAN,QAAAO,OAAAD,GA3BelB,IAAAA,GA6BLuD,EAAiB,SAAa3D,EAAYoC,OACnD,IAAIE,EAEJ,IACEA,EAAOtC,EAAEM,IAAIsD,OACf,CAAE,MAAOtC,GACP,UAAUqB,EACRc,EAAgB,CACdC,QAAS,sBAGf,CAEA,IAAIlB,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAM,IAAIC,EACRC,EAAgB,CACdC,OAAQ,QACRC,OAAQN,EAAIM,UAKlB,OAAA9B,QAAAC,QAAOuB,EAAIO,MACb,CAAC,MAAAzB,GAAA,OAAAN,QAAAO,OAAAD,EAAA,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("@lowerdeck/forwarded-for"),require("@lowerdeck/error"),require("hono"),require("hono/cors")):"function"==typeof define&&define.amd?define(["exports","@lowerdeck/forwarded-for","@lowerdeck/error","hono","hono/cors"],r):r((e||self).hono={},e.forwardedFor,e.error,e.hono,e.cors)}(this,function(e,r,o,n,t){Object.defineProperty(e,"cors",{enumerable:!0,get:function(){return t.cors}}),e.createHono=function(e){var r=new n.Hono;return e&&(r=r.basePath(e)),r.use(function(e,r){try{return Promise.resolve(r()).then(function(){e.res.headers.set("X-Powered-By","Metorial")})}catch(e){return Promise.reject(e)}}),r.notFound(function(e){return e.json(o.notFoundError("endpoint",null).toResponse(),404)}),r.onError(function(e,r){return o.isServiceError(e)?r.json(e.toResponse(),e.data.status):(console.error(e),r.json(o.internalServerError().toResponse(),500))}),r},e.useRequestContext=function(e){var o,n,t,i;return{ua:e.req.header("user-agent"),ip:null!=(o=r.parseForwardedFor(null!=(n=null!=(t=null!=(i=e.req.header("metorial-connecting-ip"))?i:e.req.header("cf-connecting-ip"))?t:e.req.header("x-forwarded-for"))?n:e.req.header("x-real-ip")))?o:"0.0.0.0"}},e.useValidatedBody=function(e,r){try{var n,t=function(e){var t=r.validate(n);if(!t.success)throw new o.ServiceError(o.validationError({entity:"body",errors:t.errors}));return t.value},i=null!=(s=e.req.header("Content-Type"))&&s.includes("application/x-www-form-urlencoded")?Promise.resolve(e.req.text()).then(function(e){n=Object.fromEntries(new URLSearchParams(e))}):function(e,r){try{var o=e()}catch(e){return r()}return o&&o.then?o.then(void 0,r):o}(function(){return Promise.resolve(e.req.json()).then(function(e){n=e})},function(){throw new o.ServiceError(o.badRequestError({message:"Invalid JSON body"}))});return Promise.resolve(i&&i.then?i.then(t):t())}catch(e){return Promise.reject(e)}var s},e.useValidatedQuery=function(e,r){try{var n;try{n=e.req.query()}catch(e){throw new o.ServiceError(o.badRequestError({message:"Invalid JSON body"}))}var t=r.validate(n);if(!t.success)throw new o.ServiceError(o.validationError({entity:"query",errors:t.errors}));return Promise.resolve(t.value)}catch(e){return Promise.reject(e)}}});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/hono.ts","../src/context.ts","../src/validation.ts"],"sourcesContent":["import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';\nimport { Context, Env, Hono } from 'hono';\nimport { cors } from 'hono/cors';\n\nexport { cors, type Context };\n\nexport let createHono = <E extends Env>(basePath?: string) => {\n let app = new Hono<E>();\n if (basePath) app = app.basePath(basePath);\n\n app.use(async (c, next) => {\n await next();\n\n c.res.headers.set('X-Powered-By', 'Metorial');\n });\n\n app.notFound(c => {\n return c.json(notFoundError('endpoint', null).toResponse(), 404);\n });\n\n app.onError((e, c) => {\n if (isServiceError(e)) {\n return c.json(e.toResponse(), e.data.status);\n }\n\n console.error(e);\n\n return c.json(internalServerError().toResponse(), 500);\n });\n\n return app;\n};\n","import { parseForwardedFor } from '@lowerdeck/forwarded-for';\nimport { Context } from 'hono';\n\nexport let useRequestContext = (c: Context) => {\n let ua = c.req.header('user-agent');\n let ip =\n parseForwardedFor(\n c.req.header('metorial-connecting-ip') ??\n c.req.header('cf-connecting-ip') ??\n c.req.header('x-forwarded-for') ??\n c.req.header('x-real-ip')\n ) ?? '0.0.0.0';\n\n return { ua, ip };\n};\n","import { badRequestError, ServiceError, validationError } from '@lowerdeck/error';\nimport { ValidationType } from '@lowerdeck/validation';\nimport { Context } from 'hono';\n\nexport let useValidatedBody = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n if (c.req.header('Content-Type')?.includes('application/x-www-form-urlencoded')) {\n body = Object.fromEntries(new URLSearchParams(await c.req.text()));\n } else {\n try {\n body = await c.req.json();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'body',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n\nexport let useValidatedQuery = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {\n let body: any;\n\n try {\n body = c.req.query();\n } catch (e) {\n throw new ServiceError(\n badRequestError({\n message: 'Invalid JSON body'\n })\n );\n }\n\n let val = v.validate(body);\n if (!val.success) {\n throw new ServiceError(\n validationError({\n entity: 'query',\n errors: val.errors\n })\n );\n }\n\n return val.value;\n};\n"],"names":["basePath","app","Hono","use","c","next","Promise","resolve","then","res","headers","set","e","reject","notFound","json","notFoundError","toResponse","onError","isServiceError","data","status","console","error","internalServerError","_parseForwardedFor","_ref","_ref2","_c$req$header","ua","req","header","ip","parseForwardedFor","v","_temp2","body","_result","val","validate","success","ServiceError","validationError","entity","errors","value","_temp","includes","text","_c$req$text","Object","fromEntries","URLSearchParams","_catch","_c$req$json","badRequestError","message","query"],"mappings":"4gBAMwB,SAAgBA,GACtC,IAAIC,EAAM,IAAIC,EAAAA,KAuBd,OAtBIF,IAAUC,EAAMA,EAAID,SAASA,IAEjCC,EAAIE,IAAWC,SAAAA,EAAGC,GAAI,WAAIC,QAAAC,QAClBF,KAAMG,KAEZJ,WAAAA,EAAEK,IAAIC,QAAQC,IAAI,eAAgB,WAAY,EAChD,CAAC,MAAAC,UAAAN,QAAAO,OAAAD,EAAA,CAAA,GAEDX,EAAIa,SAAS,SAAAV,GACX,OAAOA,EAAEW,KAAKC,EAAAA,cAAc,WAAY,MAAMC,aAAc,IAC9D,GAEAhB,EAAIiB,QAAQ,SAACN,EAAGR,GACd,OAAIe,iBAAeP,GACVR,EAAEW,KAAKH,EAAEK,aAAcL,EAAEQ,KAAKC,SAGvCC,QAAQC,MAAMX,GAEPR,EAAEW,KAAKS,wBAAsBP,aAAc,KACpD,GAEOhB,CACT,sBC5B+B,SAACG,GAAcqB,IAAAA,EAAAC,EAAAC,EAAAC,EAU5C,MAAO,CAAEC,GATAzB,EAAE0B,IAAIC,OAAO,cASTC,GAFV,OANGP,EACJQ,EAAAA,kBAGmC,OAHlBP,EAEmB,OAFnBC,EACuBC,OADvBA,EACfxB,EAAE0B,IAAIC,OAAO,2BAAyBH,EACpCxB,EAAE0B,IAAIC,OAAO,qBAAmBJ,EAChCvB,EAAE0B,IAAIC,OAAO,oBAAkBL,EAC/BtB,EAAE0B,IAAIC,OAAO,eAChBN,EAAI,UAGT,qBCVW,SAA6BrB,EAAY8B,OAAoCC,IAClFC,EADkFD,WAAAE,GAiBtF,IAAIC,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAU,IAAAC,eACRC,EAAAA,gBAAgB,CACdC,OAAQ,OACRC,OAAQN,EAAIM,UAKlB,OAAON,EAAIO,KAAM,EA1BHC,EAEkB,OAFlBlB,EAEVxB,EAAE0B,IAAIC,OAAO,kBAAbH,EAA8BmB,SAAS,qCAAoCzC,QAAAC,QACzBH,EAAE0B,IAAIkB,QAAMxC,cAAAyC,GAAhEb,EAAOc,OAAOC,YAAY,IAAIC,gBAAeH,GAAsB,uFAAAI,CAE/D,WAAA,OAAA/C,QAAAC,QACWH,EAAE0B,IAAIf,QAAMP,KAAA,SAAA8C,GAAzBlB,EAAIkB,CAAsB,EAC5B,EAAC,WACC,MAAM,IAAIb,EAAYA,aACpBc,kBAAgB,CACdC,QAAS,sBAGf,GAAC,OAAAlD,QAAAC,QAAAuC,GAAAA,EAAAtC,KAAAsC,EAAAtC,KAAA2B,GAAAA,IAcL,CAAC,MAAAvB,UAAAN,QAAAO,OAAAD,GA3BegB,IAAAA,uBA6BY,SAAaxB,EAAY8B,OACnD,IAAIE,EAEJ,IACEA,EAAOhC,EAAE0B,IAAI2B,OACf,CAAE,MAAO7C,GACP,UAAU6B,EAAYA,aACpBc,kBAAgB,CACdC,QAAS,sBAGf,CAEA,IAAIlB,EAAMJ,EAAEK,SAASH,GACrB,IAAKE,EAAIE,QACP,MAAM,IAAIC,EAAAA,aACRC,EAAeA,gBAAC,CACdC,OAAQ,QACRC,OAAQN,EAAIM,UAKlB,OAAAtC,QAAAC,QAAO+B,EAAIO,MACb,CAAC,MAAAjC,GAAA,OAAAN,QAAAO,OAAAD,EAAA,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ValidationType } from '@lowerdeck/validation';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
export declare let useValidatedBody: <T>(c: Context, v: ValidationType<T>) => Promise<T>;
|
|
4
|
+
export declare let useValidatedQuery: <T>(c: Context, v: ValidationType<T>) => Promise<T>;
|
|
5
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,eAAO,IAAI,gBAAgB,GAAU,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CA4BnF,CAAC;AAEF,eAAO,IAAI,iBAAiB,GAAU,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAwBpF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lowerdeck/hono",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"author": "Tobias Herber",
|
|
8
|
+
"license": "Apache 2",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"source": "src/index.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"default": "./dist/index.modern.js"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.module.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"unpkg": "./dist/index.umd.js",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "vitest run --passWithNoTests",
|
|
21
|
+
"lint": "prettier src/**/*.ts --check",
|
|
22
|
+
"build": "microbundle"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"hono": "^4.5.5",
|
|
26
|
+
"@lowerdeck/forwarded-for": "^1.0.0",
|
|
27
|
+
"@lowerdeck/validation": "^1.0.0",
|
|
28
|
+
"@lowerdeck/error": "^1.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"microbundle": "^0.15.1",
|
|
32
|
+
"@lowerdeck/tsconfig": "^1.0.0",
|
|
33
|
+
"typescript": "5.8.2",
|
|
34
|
+
"vitest": "^3.1.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseForwardedFor } from '@lowerdeck/forwarded-for';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
|
|
4
|
+
export let useRequestContext = (c: Context) => {
|
|
5
|
+
let ua = c.req.header('user-agent');
|
|
6
|
+
let ip =
|
|
7
|
+
parseForwardedFor(
|
|
8
|
+
c.req.header('metorial-connecting-ip') ??
|
|
9
|
+
c.req.header('cf-connecting-ip') ??
|
|
10
|
+
c.req.header('x-forwarded-for') ??
|
|
11
|
+
c.req.header('x-real-ip')
|
|
12
|
+
) ?? '0.0.0.0';
|
|
13
|
+
|
|
14
|
+
return { ua, ip };
|
|
15
|
+
};
|
package/src/hono.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { internalServerError, isServiceError, notFoundError } from '@lowerdeck/error';
|
|
2
|
+
import { Context, Env, Hono } from 'hono';
|
|
3
|
+
import { cors } from 'hono/cors';
|
|
4
|
+
|
|
5
|
+
export { cors, type Context };
|
|
6
|
+
|
|
7
|
+
export let createHono = <E extends Env>(basePath?: string) => {
|
|
8
|
+
let app = new Hono<E>();
|
|
9
|
+
if (basePath) app = app.basePath(basePath);
|
|
10
|
+
|
|
11
|
+
app.use(async (c, next) => {
|
|
12
|
+
await next();
|
|
13
|
+
|
|
14
|
+
c.res.headers.set('X-Powered-By', 'Metorial');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.notFound(c => {
|
|
18
|
+
return c.json(notFoundError('endpoint', null).toResponse(), 404);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
app.onError((e, c) => {
|
|
22
|
+
if (isServiceError(e)) {
|
|
23
|
+
return c.json(e.toResponse(), e.data.status);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.error(e);
|
|
27
|
+
|
|
28
|
+
return c.json(internalServerError().toResponse(), 500);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return app;
|
|
32
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { badRequestError, ServiceError, validationError } from '@lowerdeck/error';
|
|
2
|
+
import { ValidationType } from '@lowerdeck/validation';
|
|
3
|
+
import { Context } from 'hono';
|
|
4
|
+
|
|
5
|
+
export let useValidatedBody = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {
|
|
6
|
+
let body: any;
|
|
7
|
+
|
|
8
|
+
if (c.req.header('Content-Type')?.includes('application/x-www-form-urlencoded')) {
|
|
9
|
+
body = Object.fromEntries(new URLSearchParams(await c.req.text()));
|
|
10
|
+
} else {
|
|
11
|
+
try {
|
|
12
|
+
body = await c.req.json();
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw new ServiceError(
|
|
15
|
+
badRequestError({
|
|
16
|
+
message: 'Invalid JSON body'
|
|
17
|
+
})
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let val = v.validate(body);
|
|
23
|
+
if (!val.success) {
|
|
24
|
+
throw new ServiceError(
|
|
25
|
+
validationError({
|
|
26
|
+
entity: 'body',
|
|
27
|
+
errors: val.errors
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return val.value;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export let useValidatedQuery = async <T>(c: Context, v: ValidationType<T>): Promise<T> => {
|
|
36
|
+
let body: any;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
body = c.req.query();
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new ServiceError(
|
|
42
|
+
badRequestError({
|
|
43
|
+
message: 'Invalid JSON body'
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let val = v.validate(body);
|
|
49
|
+
if (!val.success) {
|
|
50
|
+
throw new ServiceError(
|
|
51
|
+
validationError({
|
|
52
|
+
entity: 'query',
|
|
53
|
+
errors: val.errors
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return val.value;
|
|
59
|
+
};
|
package/tsconfig.json
ADDED