{"_id":"@asla/hono-decorator","_rev":"3-f2034ae9ffddb82efe7e5a5e30ea8a12","name":"@asla/hono-decorator","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1-1":{"name":"@asla/hono-decorator","version":"0.0.1-1","keywords":["hono","decorator","typescript"],"author":{"url":"https://github.com/eavidy","name":"Eaviyi"},"license":"MIT","_id":"@asla/hono-decorator@0.0.1-1","maintainers":[{"name":"undasnow","email":"eavidy@qq.com"}],"homepage":"https://github.com/asnowc/yoursql#readme","bugs":{"url":"https://github.com/asnowc/yoursql/issues"},"dist":{"shasum":"892f27339f7a159b9c577635b762b320380e4f45","tarball":"https://registry.npmjs.org/@asla/hono-decorator/-/hono-decorator-0.0.1-1.tgz","fileCount":31,"integrity":"sha512-NhJU01rFaxZutY1sOGtq5COYejCDo0RBuECbS7CGPZ1pPIKbToudEJ5j43f2LTikqk9Z+SOn5klCod3lnXasxg==","signatures":[{"sig":"MEUCIFjvzfLtL4Z4i1qegSGGysBr4RaIaUJkJCNbrIqruwe7AiEA1LpSwrgMQyA2vvaB+O/R1Zc9C5x3RrnOdiMvP5R5Yr4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42266},"type":"module","engines":{"node":">=18"},"exports":{".":"./dist/mod.js"},"gitHead":"715023842d68cc5338888078c6db2933608058f4","scripts":{},"_npmUser":{"name":"undasnow","email":"eavidy@qq.com"},"repository":{"url":"git+https://github.com/asnowc/yoursql.git","type":"git"},"_npmVersion":"10.9.2","description":"A ECMA decorator for Hono framework.","directories":{},"_nodeVersion":"22.14.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.4.1","devDependencies":{"vitest":"^3.0.8"},"peerDependencies":{"hono":"^4.7.2"},"_npmOperationalInternal":{"tmp":"tmp/hono-decorator_0.0.1-1_1744960837851_0.3534597697430497","host":"s3://npm-registry-packages-npm-production"}},"0.0.1":{"name":"@asla/hono-decorator","version":"0.0.1","keywords":["hono","decorator","typescript"],"author":{"url":"https://github.com/eavidy","name":"Eaviyi"},"license":"MIT","_id":"@asla/hono-decorator@0.0.1","maintainers":[{"name":"undasnow","email":"eavidy@qq.com"}],"homepage":"https://github.com/asnowc/hono-decorator#readme","bugs":{"url":"https://github.com/asnowc/hono-decorator/issues"},"dist":{"shasum":"70206d3d2dc43500fc23dc2e139f12271315e7b4","tarball":"https://registry.npmjs.org/@asla/hono-decorator/-/hono-decorator-0.0.1.tgz","fileCount":31,"integrity":"sha512-Uua+xSkUzY1rxItcytD20wBzoaJmrjjPtrctA0Szmh/4Yh9sjtGjJifW0/hcQ4ETOYebvwT0HPQy86pB3uUGvA==","signatures":[{"sig":"MEYCIQCK6T0WdSGbkzfEINp6Dc3ykjv/yIRdMMNiJLLdndSvnAIhALZsy1EZH2S8J6o5XFPaP4ZUp6USOBMu/1D21JODjG/k","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@asla%2fhono-decorator@0.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":42342},"type":"module","engines":{"node":">=18"},"exports":{".":"./dist/mod.js"},"gitHead":"b396a0807a5ac6d95800c2e20cafda8bc2a13d52","scripts":{},"_npmUser":{"name":"undasnow","email":"eavidy@qq.com"},"repository":{"url":"git+https://github.com/asnowc/hono-decorator.git","type":"git"},"_npmVersion":"10.9.2","description":"A ECMA decorator for Hono framework.","directories":{},"_nodeVersion":"22.14.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"_hasShrinkwrap":false,"packageManager":"pnpm@10.4.1","devDependencies":{"vitest":"^3.0.8"},"peerDependencies":{"hono":"^4.7.2"},"_npmOperationalInternal":{"tmp":"tmp/hono-decorator_0.0.1_1744961577951_0.9083523696661346","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@asla/hono-decorator","version":"0.0.2","type":"module","scripts":{},"keywords":["hono","decorator","typescript"],"description":"A ECMA decorator for Hono framework.","exports":{".":"./dist/mod.js"},"license":"MIT","packageManager":"pnpm@10.4.1","devDependencies":{"typescript":"^6.0.3","vitest":"^4.1.7","vite":"^7.3.3","@deno/vite-plugin":"^2.0.2","hono":"4.7.2"},"peerDependencies":{"hono":"^4.7.2"},"repository":{"type":"git","url":"git+https://github.com/asnowc/hono-decorator.git"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"author":{"name":"Eaviyi","url":"https://github.com/eavidy"},"engines":{"node":">=18"},"gitHead":"1fd5b3ab976160cf83542dc9c62c1d531042a69d","_id":"@asla/hono-decorator@0.0.2","bugs":{"url":"https://github.com/asnowc/hono-decorator/issues"},"homepage":"https://github.com/asnowc/hono-decorator#readme","_nodeVersion":"26.2.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-LI7Ztyiw6DaC+Mgjki4D2ve9mMGb4DP6mSnfwhSzQN6XSgpfjRUz1x41WpYar4kl3IB6w7qmei0VIHOIcGxE+w==","shasum":"a97e07f461053067a18fe75d68a3a2ca6b87b89f","tarball":"https://registry.npmjs.org/@asla/hono-decorator/-/hono-decorator-0.0.2.tgz","fileCount":31,"unpackedSize":45509,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@asla%2fhono-decorator@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID3ygUIHuSrP8XV3cO+BSJ/RlL5bWFpxcpzQTGH/duZNAiAck32O6eLqTiTIGL9AHT+Qy4uQ4l2jj6mJvKpXiUJKKQ=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:c3adae2e-3b1b-4e5b-96d4-4b6e036b919d"}},"directories":{},"maintainers":[{"name":"undasnow","email":"eavidy@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hono-decorator_0.0.2_1780202086782_0.576155178301007"},"_hasShrinkwrap":false}},"time":{"created":"2025-04-18T07:20:37.773Z","modified":"2026-05-31T04:34:47.211Z","0.0.1-1":"2025-04-18T07:20:38.055Z","0.0.1":"2025-04-18T07:32:58.133Z","0.0.2":"2026-05-31T04:34:46.945Z"},"bugs":{"url":"https://github.com/asnowc/hono-decorator/issues"},"author":{"name":"Eaviyi","url":"https://github.com/eavidy"},"license":"MIT","homepage":"https://github.com/asnowc/hono-decorator#readme","keywords":["hono","decorator","typescript"],"repository":{"type":"git","url":"git+https://github.com/asnowc/hono-decorator.git"},"description":"A ECMA decorator for Hono framework.","maintainers":[{"name":"undasnow","email":"eavidy@qq.com"}],"readme":"## 描述\n\n`@asla/hono-decorator` 允许您使用 [ECMA 装饰器](https://github.com/tc39/proposal-decorators) 定义路由、中间件等.\n\nECMA 装饰器，目前处以 Stage 3。在未来，它将成为 JavaScript 语法标准。而现在，我们可以通过 TypeScript\n使用该语法我们可以利用装饰器和装饰器元数据，实现类似 Nest 的装饰器功能。\\\n由于 Stage 3 的装饰器不包括参数装饰器，这里只考虑使用装饰器进行路由定义，不考虑依赖注入。\n\n`@asla/hono-decorator` **目前这是实验性的**，它需要 ECMA 装饰器语和 ECMA 装饰器元数据语法。\\\n现在如果你想尝试，需要 TypeScript 5.2 及以上。并删除 `tsconfig.json` 的 `\"experimentalDecorators\": true` 配置或将它设置为 false\\\n参考 [TypeScript ECMA 装饰器](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators) 和\n[TypeScript ECMA 装饰器元数据](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#decorator-metadata)\n\n**一个简单的示例**\n\n```ts\nimport { Context, Hono } from \"hono\";\nimport { applyController, Controller, Get, Post, ToResponse, Use } from \"@asla/hono-decorator\";\nimport { compress } from \"hono/compress\";\nimport { bodyLimit } from \"hono/body-limit\";\nimport { cors } from \"hono/cors\";\n\n@Use(cors({ origin: \"*\" }))\n@Controller({ basePath: \"/api\" })\nclass TestController {\n  @Use(compress())\n  @Use(bodyLimit({ maxSize: 1024 }))\n  @Post(\"/test1\")\n  method1(ctx: Context) {\n    return ctx.json({ ok: 1 });\n  }\n\n  @Get(\"/test2\")\n  method2 = () => {};\n\n  @ToResponse((data, ctx) => {\n    data.body; // string\n    data.title; // string\n\n    //@ts-expect-error Field \"content\" does not exist\n    data.content;\n\n    return ctx.html(\n      `<html>\n        <head>\n          <title>${data.title}</title>\n        </head>\n        <body>\n        ${data.body}\n        </body>\n      </html>`\n    );\n  })\n  @Get(\"/test3\")\n  method3(ctx: Context) {\n    return {\n      title: \"123\",\n      body: \"abc\",\n    };\n  }\n}\nconst hono = new Hono();\napplyController(hono, new TestController());\n// Apply more...\n\nawait hono.request(\"/api/test3\");\n```\n\n## 使用\n\nDeno `deno add jsr:@asla/hono-decorator`\nNode `npm install @asla/hono-decorator`\n\n## API 设计\n\n在应用装饰器后，实际上只是给这个类添加元数据，在调用 `applyController()`\n时，通过读取这个类的元数据，然后根据元数据设置路由、中间件。\n\n### 端点装饰器\n\n端点装饰器为类添加了路由信息。它是所有装饰器的基础。在应用其他装饰器前，必须应用端点装饰器，且一个方法或属性只能应用一个端点装饰器\n\n```ts\nexport type EndpointDecoratorTarget = (...args: any[]) => any;\n/**\n * @typeParam T Constrains the type of decoration target\n */\nexport type EndpointDecorator<T extends EndpointDecoratorTarget = EndpointDecoratorTarget> = (\n  input: T | undefined,\n  context: ClassMethodDecoratorContext<unknown, T> | ClassFieldDecoratorContext<unknown, T>\n) => void;\n\nexport declare function Endpoint(path: string, method?: string): EndpointDecorator;\n\nexport function Post(path: string): EndpointDecorator {\n  return Endpoint(path, \"POST\");\n}\nexport function Get(path: string): EndpointDecorator {\n  return Endpoint(path, \"GET\");\n}\n\n// The same is true of other common methods such as Patch and Put\n```\n\n```ts\nclass Test {\n  @Get(\"/test1\")\n  @Use() // Throw: Before applying the middleware decorator, you must apply the endpoint decorator\n  method1() {}\n\n  @Get(\"/test2\") // Throw: The route cannot be configured twice\n  @Get(\"/test1\")\n  method2() {}\n}\n```\n\n### 控制器装饰器\n\n控制器装饰器可以定义一组路由的一些行为。它只能应用到类上面\n\n```ts\nexport type ControllerDecoratorTarget = new (...args: any[]) => any;\n\n/**\n * @typeParam T Constrains the type of decoration target\n */\nexport type ControllerDecorator<T extends ControllerDecoratorTarget = ControllerDecoratorTarget> = (\n  input: T,\n  context: ClassDecoratorContext<T>\n) => void;\n\nexport type ControllerOption = {\n  /** Inherit the decorator from the parent class */\n  extends?: boolean;\n  basePath?: string;\n};\n\nexport declare function Controller(option: ControllerOption): ControllerDecorator;\n```\n\n### 中间件装饰器\n\n```ts\nexport type MiddlewareDecoratorTarget = ControllerDecoratorTarget | EndpointDecoratorTarget;\nexport type MiddlewareDecorator<T extends MiddlewareDecoratorTarget = MiddlewareDecoratorTarget> = (\n  input: unknown,\n  context: ClassDecoratorContext | ClassMethodDecoratorContext | ClassFieldDecoratorContext\n) => void;\n```\n\n中间件装饰器可以装饰器在类、方法或属性，请求经过中间件的顺序由外到内（与装饰器调用的顺序相反，这样可以更直观感受请求到路由处理程序的过程）\n\n```ts\n@Use(A)\n@Use(B)\n@Use(C)\nclass Controller {\n  @Use(D)\n  @Use(E)\n  @Use(F)\n  @Get(\"/test\")\n  method() {}\n}\n```\n\n请求经过的顺序： A>B>C>D>E>F > method() >F>E>D>C>B>A\n\n### 转换装饰器\n\n转换装饰器可以将 Hono 的 Context 对象转换为控制器方法所需的参数，也可以将控制器方法返回的对象转换为 Response 对象\n\n```ts\nclass Controller {\n  @Get(\"/test1\")\n  method1(ctx: Context) {} //If the PipeInput decorator is not applied, the first argument is passed to Context\n\n  @ToArguments(function (ctx: Context) {\n    //The returned type is the same as the parameter for method2\n    // If types are inconsistent, typescript prompts an exception\n    return [1, \"abc\"];\n  })\n  //The type of data is the same as that returned by method2\n  // If types are inconsistent, typescript prompts an exception\n  @ToResponse((data, ctx: Context) => {\n    data.body; // string\n    data.title; // string\n\n    //@ts-expect-error content not exist\n    data.content;\n\n    return ctx.text(\"ok\");\n  })\n  @Get(\"/test2\")\n  method2(size: number, id: string) {\n    return {\n      title: \"123\",\n      body: \"abc\",\n    };\n  }\n}\n```\n\n### 自定义装饰器\n\n可以通过 `createMetadataDecoratorFactory` 创建自定义装饰器。实际上，除了 `Endpoint` 和 `Controller`,\n其他的装饰器都是通过 `createMetadataDecoratorFactory` 创建的。\n\n下面是一个示例。自定义了 Roles 装饰器。该装饰器可以装饰后，需要特定角色才能访问接口\n\n```ts\nimport { applyController, createMetadataDecoratorFactory, getEndpointContext, Post, Use } from \"@asla/hono-decorator\";\n\nconst Roles = createMetadataDecoratorFactory<Set<string>, string[]>(function (args, decoratorContext) {\n  if (decoratorContext.metadata) {\n    // 已设置，添加角色\n    for (const arg of args) {\n      decoratorContext.metadata.add(arg);\n    }\n  } else {\n    return new Set(args); // 设置数据\n  }\n});\nfunction includeRoles(match: Set<string>, input?: Set<string>) {\n  if (!input?.size) return false;\n  return match.intersection(input).size > 0;\n}\nconst RolesGuard: MiddlewareHandler = async function (ctx, next) {\n  const body = await ctx.req.json();\n  const currentRoles = new Set<string>(body);\n\n  const endpointContext = getEndpointContext(ctx);\n\n  let roles = endpointContext.getControllerMetadata<Set<string>>(Roles);\n  if (roles && !includeRoles(roles, currentRoles)) return ctx.body(null, 403);\n\n  roles = endpointContext.getEndpointMetadata<Set<string>>(Roles);\n  if (roles && !includeRoles(roles, currentRoles)) return ctx.body(null, 403);\n  return next();\n};\n\n@Roles(\"admin\")\n@Use(RolesGuard)\nclass Controller {\n  @Roles(\"root\", \"test\") // admin && (root || test)\n  @Post(\"/create\")\n  create(ctx: Context) {\n    return ctx.text(\"ok\");\n  }\n  @Post(\"/delete\") // admin\n  delete(ctx: Context) {\n    return ctx.text(\"ok\");\n  }\n}\n\nconst hono = new Hono();\napplyController(hono, new Controller());\n\nconst ADMIN = JSON.stringify([\"admin\"]);\nconst ROOT = JSON.stringify([\"root\"]);\nconst ADMIN_AND_ROOT = JSON.stringify([\"admin\", \"root\"]);\n\nawait hono.request(\"/delete\", { method: \"POST\", body: JSON.stringify([]) }); // 403;\nawait hono.request(\"/delete\", { method: \"POST\", body: ADMIN }); // 200;\n\nawait hono.request(\"/create\", { method: \"POST\", body: ADMIN }); // 403;\nawait hono.request(\"/create\", { method: \"POST\", body: ROOT }); // 403;\nawait hono.request(\"/create\", { method: \"POST\", body: ADMIN_AND_ROOT }); // 200;\n```\n\n### 继承\n\n如果子类控制器类声明了 `@Controller({ extends: true })`,\n那么子类会继承父类的路由与中间件等配置，否则会忽略父类的一切装饰器\n\n```ts\n@Use(bodyLimit({ maxSize: 1024 }))\n@Controller({ basePath: \"/animal\" })\nclass Animal {\n  constructor() {}\n  @Get(\"/eat\")\n  eat() {\n    return \"Animal eat\";\n  }\n  @Get(\"/speak\")\n  speak() {\n    return \"Animal speak\";\n  }\n}\n\n/**\n * Animal routing and middleware will not be applied\n * Add `/fly`\n */\nclass Bird extends Animal {\n  @Get(\"/fly\")\n  fly() {\n    return \"Bird fly\";\n  }\n}\n/**\n * 继承中间件和路由\n * Add `/animal/sleep`, `/animal/eat`, `/animal/speak`\n */\n@Controller({ extends: true })\nclass Dog extends Animal {\n  @Get(\"/sleep\")\n  sleep() {\n    return \"Dog sleep\";\n  }\n}\n```\n\n如果调用 `applyController(hono, new Bird())`, 将只添加`/fly` ，且 Animal 上定义的 中间件也不会生效 如果调用\n`applyController(hono, new Dog())`, 将只添加 `/animal/sleep`, `/animal/eat`, `/animal/speak`, 其这些请求都会经过 Animal\n上应用的 bodyLimit 装饰器\n\n我们也可以在子类修改父类的一下设定\n\n```ts\n/**\n * 继承中间件和路由，并修改一些设定\n * Add `/run`, `/eat`, `/speak`\n * Get `/eat` will response `Cat eat`\n * Get `/speak` will response `Cat speak`\n */\n@Controller({ extends: true, basePath: \"\" })\nclass Cat extends Animal {\n  @Get(\"/run\")\n  run() {\n    return \"Cat run\";\n  }\n  override eat() {\n    return \"Cat eat\";\n  }\n  @Get(\"/speak\")\n  catSpeak() {\n    return \"Cat speak\";\n  }\n}\n```\n\n这个例子，重写了 basePath、eat() 方法和 /speak 路由\n\n如果调用 `applyController(hono, new Cat())`, 将只添加 `/run`, `/eat`, `/speak` GET /eat 返回 `Cat eat` GET /speak 返回\n`Cat speak`\n","readmeFilename":"README.zh.md"}