Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add CBOR helper #822

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-pears-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/cbor': major
---

Initial release
25 changes: 25 additions & 0 deletions .github/workflows/ci-cbor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ci-cbor
on:
push:
branches: [main]
paths:
- 'packages/cbor/**'
pull_request:
branches: ['*']
paths:
- 'packages/cbor/**'

jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/cbor
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"build:effect-validator": "yarn workspace @hono/effect-validator build",
"build:conform-validator": "yarn workspace @hono/conform-validator build",
"build:casbin": "yarn workspace @hono/casbin build",
"build:cbor": "yarn workspace @hono/cbor build",
"build": "run-p 'build:*'",
"lint": "eslint 'packages/**/*.{ts,tsx}'",
"lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'",
Expand Down
31 changes: 31 additions & 0 deletions packages/cbor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# CBOR helper for Hono

This package is a CBOR helper for Hono.

## Usage

```ts
import { Hono } from 'hono'
import { parseCborFromHonoRequest, renderCborWithContext } from '@hono/cbor'

const app = new Hono()

// Render CBOR as `Content-Type: application/cbor`.
app.get('/', async (c) => {
return renderCborWithContext(c, { message: 'Hello CBOR!' })
})

// Parse the request body of type `application/cbor`.
app.post('/', async (c) => {
const body = await parseCborFromHonoRequest(c.req)
// ...
})
```

## Author

3w36zj6 <https://github.com/3w36zj6>

## License

MIT
48 changes: 48 additions & 0 deletions packages/cbor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@hono/cbor",
"version": "1.0.0",
"description": "CBOR helper for Hono",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"test": "vitest --run",
"build": "tsup ./src/index.ts ./src/helper/index.ts --format esm,cjs --dts",
"publint": "publint",
"release": "yarn build && yarn test && yarn publint && yarn publish"
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/honojs/middleware.git"
},
"homepage": "https://github.com/honojs/middleware",
"author": "3w36zj6",
"peerDependencies": {
"hono": ">=4.6.9"
},
"devDependencies": {
"hono": "^4.6.9",
"tsup": "^8.1.0",
"typescript": "^5.5.3",
"vitest": "^2.0.1"
},
"dependencies": {
"cbor2": "^1.7.1"
}
}
46 changes: 46 additions & 0 deletions packages/cbor/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Hono } from 'hono'
import { parseCborFromHonoRequest, renderCborWithContext } from '.'
import { encode, decode } from 'cbor2'

const testObject = {
message: 'Hello CBOR!',
}

describe('CBOR helper', () => {
const app = new Hono()

// Return the test object as CBOR
app.get('/', async (c) => {
return renderCborWithContext(c, testObject)
})

// Receive a CBOR object, decode it to JSON, and return the JSON response
app.post('/', async (c) => {
return c.json(await parseCborFromHonoRequest(c.req))
})

test('Should be encoded to CBOR', async () => {
const response = await app.request('/')
expect(response.headers.get('Content-Type')).toBe('application/cbor')

const readResult = await response.body?.getReader().read()
const encodedObject = readResult?.value
expect(encodedObject).toBeDefined()

if (encodedObject !== undefined) {
const actualObject = decode(encodedObject)
expect(actualObject).toEqual(testObject)
}
})

test('Should be decoded from CBOR', async () => {
const response = await app.request('/', {
method: 'POST',
headers: {
contentType: 'application/cbor',
},
body: encode(testObject),
})
expect(await response.json()).toEqual(testObject)
})
})
59 changes: 59 additions & 0 deletions packages/cbor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { decode, encode } from 'cbor2'
import { HonoRequest, type Context } from 'hono'
import { ResponseHeader } from 'hono/utils/headers'
import { StatusCode } from 'hono/utils/http-status'
import { BaseMime } from 'hono/utils/mime'

type HeaderRecord =
| Record<'Content-Type', BaseMime>
| Record<ResponseHeader, string | string[]>
| Record<string, string | string[]>

/**
* Render CBOR as `Content-Type:application/cbor` with the `Context` object.
*
* @param c - The `Context` object for Hono.
* @param object - The object to render as CBOR.
* @param arg - The status code or response init object.
* @param headers - The headers to set in the response.
* @returns The response object with the rendered CBOR.
*
* @example
* ```ts
* app.get('/api', (c) => {
* return renderCborWithContext(c, { message: 'Hello CBOR!' })
* })
* ```
*/
export const renderCborWithContext = (
c: Context,
object: any,
arg?: StatusCode | ResponseInit,
headers?: HeaderRecord
) => {
const encodedObject = encode(object)
const body = encodedObject.buffer
c.header('Content-Type', 'application/cbor')
return typeof arg === 'number' ? c.newResponse(body, arg, headers) : c.newResponse(body, arg)
}

/**
* Parse the request body of type `application/cbor` from the `HonoRequest` object.
*
* @template T - The type of the parsed object.
* @param req - The `HonoRequest` object to parse.
* @returns A promise that resolves to the parsed object.
*
* @example
* ```ts
* app.post('/entry', (c) => {
* const body = await parseCborFromHonoRequest(c.req)
* // ...
* })
* ```
*/
export const parseCborFromHonoRequest = async <T = any>(req: HonoRequest): Promise<T> => {
const requestArrayBuffer = await req.arrayBuffer()
const encodedObject = new Uint8Array(requestArrayBuffer)
return decode(encodedObject) as T
}
12 changes: 12 additions & 0 deletions packages/cbor/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"exactOptionalPropertyTypes": true,
"module": "ESNext",
"moduleResolution": "Bundler",
},
"include": [
"src/**/*.ts"
],
}
8 changes: 8 additions & 0 deletions packages/cbor/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
globals: true,
},
})
28 changes: 28 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2468,6 +2468,20 @@ __metadata:
languageName: unknown
linkType: soft

"@hono/cbor@workspace:packages/cbor":
version: 0.0.0-use.local
resolution: "@hono/cbor@workspace:packages/cbor"
dependencies:
cbor2: "npm:^1.7.1"
hono: "npm:^4.6.9"
tsup: "npm:^8.1.0"
typescript: "npm:^5.5.3"
vitest: "npm:^2.0.1"
peerDependencies:
hono: ">=4.6.9"
languageName: unknown
linkType: soft

"@hono/class-validator@workspace:packages/class-validator":
version: 0.0.0-use.local
resolution: "@hono/class-validator@workspace:packages/class-validator"
Expand Down Expand Up @@ -7195,6 +7209,13 @@ __metadata:
languageName: node
linkType: hard

"cbor2@npm:^1.7.1":
version: 1.7.1
resolution: "cbor2@npm:1.7.1"
checksum: a124fcbfcdd3e464318d64042c5d511432fedb3a9454649a26b3e606cbc4ba8bf6172deddddd58fe7aa22466294cde3b2f1556e4369d71529fb6c8eb4c40b024
languageName: node
linkType: hard

"ccount@npm:^2.0.0":
version: 2.0.1
resolution: "ccount@npm:2.0.1"
Expand Down Expand Up @@ -11466,6 +11487,13 @@ __metadata:
languageName: node
linkType: hard

"hono@npm:^4.6.9":
version: 4.6.9
resolution: "hono@npm:4.6.9"
checksum: 492971f111ccec8745df449ef8e28d8bc2a4bf29bb9545c2697df2f3252c2a393e66969e6aa3f0fffdff4322461b59805482734bc12d9c9c3b3dcbe10b13d8a0
languageName: node
linkType: hard

"hosted-git-info@npm:^2.1.4":
version: 2.8.9
resolution: "hosted-git-info@npm:2.8.9"
Expand Down