Skip to content

Commit

Permalink
feat(tsyringe): add @hono/tsyringe middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
elct9620 committed Nov 15, 2024
1 parent b0320d9 commit aced1ba
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/eleven-islands-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/tsyringe': minor
---

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

jobs:
ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./packages/tsyringe
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
57 changes: 57 additions & 0 deletions packages/tsyringe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# tsyringe middleware for Hono

The [tsyringe](https://github.com/microsoft/tsyringe) middleware provides a way to use dependency injection in [Hono](https://hono.dev/).

## Usage

```ts
import "reflect-metadata" // tsyringe requires reflect-metadata or polyfill
import { container, inject, injectable } from 'tsyringe'
import { tsyringe } from '@hono/tsyringe'
import { Hono } from 'hono'

@injectable()
class Hello {
constructor(@inject('name') private name: string) {}

greet() {
return `Hello, ${this.name}!`
}
}

const app = new Hono()

app.use('*', tsyringe((container) => {
container.register('name', { useValue: 'world' })
}))

app.get('/', (c) => {
const hello = container.resolve(Hello)
return c.text(hello.greet())
})

export default app
```

### With providers

```ts
const app = new Hono()

app.use('/tenant/:name/*', async (c, next) => {
await tsyringe((container) => {
// Allowing to inject `c.var` or `c.req.param` in the providers
const tenantName = c.req.param('name')

container.register(Config, { useFactory: () => new Config(tenantName) })
})(c, next)
})
```

## Author

Aotokitsuruya <https://github.com/elct9620>

## License

MIT
51 changes: 51 additions & 0 deletions packages/tsyringe/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@hono/tsyringe",
"version": "0.1.0",
"description": "The tsyringe dependency injection middleware for Hono",
"type": "module",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"test": "vitest --run",
"build": "tsup ./src/index.ts --format esm,cjs --dts",
"publint": "publint",
"release": "yarn build && yarn test && yarn publint && yarn publish"
},
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"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",
"peerDependencies": {
"hono": ">=4.*",
"tsyringe": ">=4.*"
},
"devDependencies": {
"hono": "^4.4.12",
"prettier": "^3.3.3",
"reflect-metadata": "^0.2.2",
"tsup": "^8.1.0",
"tsyringe": "^4.8.0",
"vitest": "^1.6.0"
}
}
57 changes: 57 additions & 0 deletions packages/tsyringe/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'reflect-metadata'
import { injectable, inject } from 'tsyringe'
import { Hono } from 'hono'
import { tsyringe } from '../src'

class Config {
constructor(public readonly tenantName: string) {}
}

@injectable()
class TenantService {
constructor(@inject(Config) public readonly config: Config) {}

get message() {
return `Hello, ${this.config.tenantName}!`
}
}

describe('tsyringe middleware', () => {
const app = new Hono()

app.use(
'/hello/*',
tsyringe((container) => container.register('foo', { useValue: 'Hello!' }))
)
app.get('/hello/foo', (c) => {
const message = c.var.resolve<string>('foo')
return c.text(message)
})

app.use('/tenant/:name/*', async (c, next) => {
await tsyringe((container) => {
const tenantName = c.req.param('name')

container.register(Config, { useFactory: () => new Config(tenantName) })
})(c, next)
})

app.get('/tenant/:name/message', (c) => {
const tenantService = c.var.resolve(TenantService)
return c.text(tenantService.message)
})

it('Should be hello message', async () => {
const res = await app.request('http://localhost/hello/foo')
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello!')
})

it('Should be tenant message', async () => {
const res = await app.request('http://localhost/tenant/foo/message')
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello, foo!')
})
})
19 changes: 19 additions & 0 deletions packages/tsyringe/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { container, DependencyContainer, InjectionToken } from 'tsyringe'
import type { Context, MiddlewareHandler } from 'hono'
import { createMiddleware } from 'hono/factory'

declare module 'hono' {
interface ContextVariableMap {
resolve: <T>(token: InjectionToken<T>) => T
}
}

export type Provider = (container: DependencyContainer) => void
export const tsyringe = (...providers: Provider[]): MiddlewareHandler => {
return createMiddleware(async (c, next) => {
const childContainer = container.createChildContainer()
providers.forEach((provider) => provider(childContainer))
c.set('resolve', <T>(token: InjectionToken<T>) => childContainer.resolve(token))
await next()
})
}
12 changes: 12 additions & 0 deletions packages/tsyringe/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*.ts"
],
}
8 changes: 8 additions & 0 deletions packages/tsyringe/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,
},
})
36 changes: 35 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2849,6 +2849,22 @@ __metadata:
languageName: unknown
linkType: soft

"@hono/tsyringe@workspace:packages/tsyringe":
version: 0.0.0-use.local
resolution: "@hono/tsyringe@workspace:packages/tsyringe"
dependencies:
hono: "npm:^4.4.12"
prettier: "npm:^3.3.3"
reflect-metadata: "npm:^0.2.2"
tsup: "npm:^8.1.0"
tsyringe: "npm:^4.8.0"
vitest: "npm:^1.6.0"
peerDependencies:
hono: ">=4.*"
tsyringe: ">=4.*"
languageName: unknown
linkType: soft

"@hono/typebox-validator@workspace:packages/typebox-validator":
version: 0.0.0-use.local
resolution: "@hono/typebox-validator@workspace:packages/typebox-validator"
Expand Down Expand Up @@ -16790,6 +16806,15 @@ __metadata:
languageName: node
linkType: hard

"prettier@npm:^3.3.3":
version: 3.3.3
resolution: "prettier@npm:3.3.3"
bin:
prettier: bin/prettier.cjs
checksum: b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
languageName: node
linkType: hard

"pretty-format@npm:^28.0.0, pretty-format@npm:^28.1.3":
version: 28.1.3
resolution: "pretty-format@npm:28.1.3"
Expand Down Expand Up @@ -19489,7 +19514,7 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^1.8.1, tslib@npm:^1.9.0":
"tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: 69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2
Expand Down Expand Up @@ -19797,6 +19822,15 @@ __metadata:
languageName: node
linkType: hard

"tsyringe@npm:^4.8.0":
version: 4.8.0
resolution: "tsyringe@npm:4.8.0"
dependencies:
tslib: "npm:^1.9.3"
checksum: e13810e8ff39c4093acd0649bc5db3c164825827631e1522cd9d5ca8694a018447fa1c24f059ea54e93b1020767b1131b9dc9ce598dabfc9aa41c11544bbfe19
languageName: node
linkType: hard

"tty-table@npm:^4.1.5":
version: 4.2.3
resolution: "tty-table@npm:4.2.3"
Expand Down

0 comments on commit aced1ba

Please sign in to comment.