@rx-ted/packages-honest-plugins 1.0.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.
Files changed (266) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +45 -0
  3. package/api-doc/.turbo/turbo-build.log +1 -0
  4. package/api-doc/.turbo/turbo-test.log +11 -0
  5. package/api-doc/.turbo/turbo-typecheck.log +1 -0
  6. package/api-doc/CHANGELOG.md +22 -0
  7. package/api-doc/README.md +80 -0
  8. package/api-doc/dist/api-doc.plugin.d.ts +53 -0
  9. package/api-doc/dist/api-doc.plugin.js +452 -0
  10. package/api-doc/dist/index.d.ts +5 -0
  11. package/api-doc/dist/index.js +3 -0
  12. package/api-doc/dist/interfaces/renderer.interface.d.ts +7 -0
  13. package/api-doc/dist/interfaces/renderer.interface.js +1 -0
  14. package/api-doc/dist/renderers/scalar.renderer.d.ts +10 -0
  15. package/api-doc/dist/renderers/scalar.renderer.js +76 -0
  16. package/api-doc/dist/renderers/swagger.renderer.d.ts +10 -0
  17. package/api-doc/dist/renderers/swagger.renderer.js +83 -0
  18. package/api-doc/node_modules/.bin/esbuild +22 -0
  19. package/api-doc/node_modules/.bin/jiti +22 -0
  20. package/api-doc/node_modules/.bin/sass +22 -0
  21. package/api-doc/node_modules/.bin/tsx +22 -0
  22. package/api-doc/node_modules/.bin/vite +22 -0
  23. package/api-doc/node_modules/.bin/vitest +22 -0
  24. package/api-doc/node_modules/.bin/yaml +22 -0
  25. package/api-doc/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  26. package/api-doc/package.json +48 -0
  27. package/api-doc/src/api-doc.plugin.ts +554 -0
  28. package/api-doc/src/index.ts +5 -0
  29. package/api-doc/src/interfaces/renderer.interface.ts +5 -0
  30. package/api-doc/src/plugin.test.ts +740 -0
  31. package/api-doc/src/renderers/scalar.renderer.ts +82 -0
  32. package/api-doc/src/renderers/swagger.renderer.ts +89 -0
  33. package/api-doc/tsconfig.json +21 -0
  34. package/api-doc/vitest.config.ts +13 -0
  35. package/cache/.turbo/turbo-build.log +1 -0
  36. package/cache/.turbo/turbo-test.log +16 -0
  37. package/cache/CHANGELOG.md +23 -0
  38. package/cache/README.md +79 -0
  39. package/cache/dist/cache-service.d.ts +8 -0
  40. package/cache/dist/cache-service.js +21 -0
  41. package/cache/dist/cacheable.d.ts +2 -0
  42. package/cache/dist/cacheable.js +10 -0
  43. package/cache/dist/cloudflare/driver.d.ts +23 -0
  44. package/cache/dist/cloudflare/driver.js +102 -0
  45. package/cache/dist/cloudflare/index.d.ts +3 -0
  46. package/cache/dist/cloudflare/index.js +2 -0
  47. package/cache/dist/cloudflare/plugin.d.ts +19 -0
  48. package/cache/dist/cloudflare/plugin.js +58 -0
  49. package/cache/dist/index.d.ts +11 -0
  50. package/cache/dist/index.js +14 -0
  51. package/cache/dist/local/driver.d.ts +2 -0
  52. package/cache/dist/local/driver.js +98 -0
  53. package/cache/dist/local/index.d.ts +2 -0
  54. package/cache/dist/local/index.js +2 -0
  55. package/cache/dist/local/plugin.d.ts +14 -0
  56. package/cache/dist/local/plugin.js +24 -0
  57. package/cache/dist/redis/driver.d.ts +10 -0
  58. package/cache/dist/redis/driver.js +102 -0
  59. package/cache/dist/redis/index.d.ts +4 -0
  60. package/cache/dist/redis/index.js +2 -0
  61. package/cache/dist/redis/plugin.d.ts +25 -0
  62. package/cache/dist/redis/plugin.js +61 -0
  63. package/cache/dist/resolve.d.ts +24 -0
  64. package/cache/dist/resolve.js +114 -0
  65. package/cache/dist/types.d.ts +18 -0
  66. package/cache/dist/types.js +1 -0
  67. package/cache/node_modules/.bin/esbuild +22 -0
  68. package/cache/node_modules/.bin/jiti +22 -0
  69. package/cache/node_modules/.bin/sass +22 -0
  70. package/cache/node_modules/.bin/tsx +22 -0
  71. package/cache/node_modules/.bin/vite +22 -0
  72. package/cache/node_modules/.bin/vitest +22 -0
  73. package/cache/node_modules/.bin/yaml +22 -0
  74. package/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  75. package/cache/package.json +46 -0
  76. package/cache/src/cache-service.test.ts +16 -0
  77. package/cache/src/cache-service.ts +15 -0
  78. package/cache/src/cacheable.test.ts +78 -0
  79. package/cache/src/cacheable.ts +16 -0
  80. package/cache/src/cloudflare/driver.test.ts +188 -0
  81. package/cache/src/cloudflare/driver.ts +118 -0
  82. package/cache/src/cloudflare/index.ts +3 -0
  83. package/cache/src/cloudflare/plugin.ts +71 -0
  84. package/cache/src/index.test.ts +68 -0
  85. package/cache/src/index.ts +20 -0
  86. package/cache/src/local/driver.ts +117 -0
  87. package/cache/src/local/index.ts +2 -0
  88. package/cache/src/local/plugin.ts +34 -0
  89. package/cache/src/redis/driver.test.ts +71 -0
  90. package/cache/src/redis/driver.ts +124 -0
  91. package/cache/src/redis/index.ts +4 -0
  92. package/cache/src/redis/plugin.ts +84 -0
  93. package/cache/src/resolve.test.ts +257 -0
  94. package/cache/src/resolve.ts +140 -0
  95. package/cache/src/types.ts +14 -0
  96. package/cache/tsconfig.json +21 -0
  97. package/cache/vitest.config.ts +13 -0
  98. package/db/.turbo/turbo-build.log +1 -0
  99. package/db/.turbo/turbo-test.log +21 -0
  100. package/db/CHANGELOG.md +26 -0
  101. package/db/README.md +99 -0
  102. package/db/dist/constants.d.ts +1 -0
  103. package/db/dist/constants.js +1 -0
  104. package/db/dist/d1/index.d.ts +3 -0
  105. package/db/dist/d1/index.js +2 -0
  106. package/db/dist/d1/plugin.d.ts +18 -0
  107. package/db/dist/d1/plugin.js +104 -0
  108. package/db/dist/db-service.d.ts +7 -0
  109. package/db/dist/db-service.js +21 -0
  110. package/db/dist/index.d.ts +15 -0
  111. package/db/dist/index.js +20 -0
  112. package/db/dist/mysql/client.d.ts +36 -0
  113. package/db/dist/mysql/client.js +84 -0
  114. package/db/dist/mysql/driver.d.ts +18 -0
  115. package/db/dist/mysql/driver.js +107 -0
  116. package/db/dist/mysql/index.d.ts +11 -0
  117. package/db/dist/mysql/index.js +11 -0
  118. package/db/dist/mysql/plugin.d.ts +51 -0
  119. package/db/dist/mysql/plugin.js +94 -0
  120. package/db/dist/mysql/types.d.ts +38 -0
  121. package/db/dist/mysql/types.js +1 -0
  122. package/db/dist/postgres/index.d.ts +2 -0
  123. package/db/dist/postgres/index.js +3 -0
  124. package/db/dist/repository.d.ts +14 -0
  125. package/db/dist/repository.js +79 -0
  126. package/db/dist/resolve.d.ts +6 -0
  127. package/db/dist/resolve.js +81 -0
  128. package/db/dist/schema-builder/column-builder.d.ts +26 -0
  129. package/db/dist/schema-builder/column-builder.js +53 -0
  130. package/db/dist/schema-builder/compile-d1.d.ts +5 -0
  131. package/db/dist/schema-builder/compile-d1.js +67 -0
  132. package/db/dist/schema-builder/compile-mysql.d.ts +12 -0
  133. package/db/dist/schema-builder/compile-mysql.js +78 -0
  134. package/db/dist/schema-builder/index.d.ts +8 -0
  135. package/db/dist/schema-builder/index.js +22 -0
  136. package/db/dist/schema-builder/table-builder.d.ts +16 -0
  137. package/db/dist/schema-builder/table-builder.js +38 -0
  138. package/db/dist/schema-builder/types.d.ts +26 -0
  139. package/db/dist/schema-builder/types.js +1 -0
  140. package/db/dist/schema-builder/zod-bridge.d.ts +24 -0
  141. package/db/dist/schema-builder/zod-bridge.js +53 -0
  142. package/db/dist/sqlite/index.d.ts +4 -0
  143. package/db/dist/sqlite/index.js +2 -0
  144. package/db/dist/sqlite/plugin.d.ts +17 -0
  145. package/db/dist/sqlite/plugin.js +33 -0
  146. package/db/dist/types.d.ts +42 -0
  147. package/db/dist/types.js +1 -0
  148. package/db/node_modules/.bin/esbuild +22 -0
  149. package/db/node_modules/.bin/jiti +22 -0
  150. package/db/node_modules/.bin/sass +22 -0
  151. package/db/node_modules/.bin/tsx +22 -0
  152. package/db/node_modules/.bin/vite +22 -0
  153. package/db/node_modules/.bin/vitest +22 -0
  154. package/db/node_modules/.bin/yaml +22 -0
  155. package/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  156. package/db/package.json +59 -0
  157. package/db/src/constants.test.ts +8 -0
  158. package/db/src/constants.ts +1 -0
  159. package/db/src/d1/index.ts +13 -0
  160. package/db/src/d1/plugin.ts +135 -0
  161. package/db/src/db-service.ts +14 -0
  162. package/db/src/index.test.ts +63 -0
  163. package/db/src/index.ts +57 -0
  164. package/db/src/mysql/client.ts +112 -0
  165. package/db/src/mysql/driver.test.ts +190 -0
  166. package/db/src/mysql/driver.ts +116 -0
  167. package/db/src/mysql/index.ts +51 -0
  168. package/db/src/mysql/plugin.test.ts +228 -0
  169. package/db/src/mysql/plugin.ts +128 -0
  170. package/db/src/mysql/types.ts +32 -0
  171. package/db/src/postgres/index.ts +15 -0
  172. package/db/src/repository.test.ts +195 -0
  173. package/db/src/repository.ts +95 -0
  174. package/db/src/resolve.ts +105 -0
  175. package/db/src/schema-builder/column-builder.test.ts +55 -0
  176. package/db/src/schema-builder/column-builder.ts +87 -0
  177. package/db/src/schema-builder/compile-d1.test.ts +60 -0
  178. package/db/src/schema-builder/compile-d1.ts +70 -0
  179. package/db/src/schema-builder/compile-mysql.test.ts +64 -0
  180. package/db/src/schema-builder/compile-mysql.ts +102 -0
  181. package/db/src/schema-builder/index.test.ts +36 -0
  182. package/db/src/schema-builder/index.ts +41 -0
  183. package/db/src/schema-builder/table-builder.test.ts +55 -0
  184. package/db/src/schema-builder/table-builder.ts +50 -0
  185. package/db/src/schema-builder/types.test.ts +68 -0
  186. package/db/src/schema-builder/types.ts +30 -0
  187. package/db/src/schema-builder/zod-bridge.ts +89 -0
  188. package/db/src/sqlite/index.ts +14 -0
  189. package/db/src/sqlite/plugin.ts +46 -0
  190. package/db/src/types.ts +45 -0
  191. package/db/tsconfig.json +21 -0
  192. package/db/vitest.config.ts +13 -0
  193. package/mail/.turbo/turbo-build.log +1 -0
  194. package/mail/.turbo/turbo-test.log +12 -0
  195. package/mail/.turbo/turbo-typecheck.log +1 -0
  196. package/mail/CHANGELOG.md +25 -0
  197. package/mail/README.md +214 -0
  198. package/mail/dist/index.d.ts +4 -0
  199. package/mail/dist/index.js +2 -0
  200. package/mail/dist/plugin.d.ts +64 -0
  201. package/mail/dist/plugin.js +232 -0
  202. package/mail/dist/providers/brevo.provider.d.ts +15 -0
  203. package/mail/dist/providers/brevo.provider.js +75 -0
  204. package/mail/dist/providers/custom.provider.d.ts +9 -0
  205. package/mail/dist/providers/custom.provider.js +22 -0
  206. package/mail/dist/providers/resend.provider.d.ts +16 -0
  207. package/mail/dist/providers/resend.provider.js +77 -0
  208. package/mail/dist/providers/smtp.provider.d.ts +21 -0
  209. package/mail/dist/providers/smtp.provider.js +86 -0
  210. package/mail/dist/rate-limiter.d.ts +16 -0
  211. package/mail/dist/rate-limiter.js +49 -0
  212. package/mail/dist/types.d.ts +68 -0
  213. package/mail/dist/types.js +6 -0
  214. package/mail/node_modules/.bin/esbuild +22 -0
  215. package/mail/node_modules/.bin/jiti +22 -0
  216. package/mail/node_modules/.bin/sass +22 -0
  217. package/mail/node_modules/.bin/tsx +22 -0
  218. package/mail/node_modules/.bin/vite +22 -0
  219. package/mail/node_modules/.bin/vitest +22 -0
  220. package/mail/node_modules/.bin/yaml +22 -0
  221. package/mail/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  222. package/mail/package.json +52 -0
  223. package/mail/src/index.ts +4 -0
  224. package/mail/src/plugin.test.ts +219 -0
  225. package/mail/src/plugin.ts +313 -0
  226. package/mail/src/providers/brevo.provider.ts +78 -0
  227. package/mail/src/providers/custom.provider.ts +29 -0
  228. package/mail/src/providers/resend.provider.ts +74 -0
  229. package/mail/src/providers/smtp.provider.ts +85 -0
  230. package/mail/src/rate-limiter.test.ts +81 -0
  231. package/mail/src/rate-limiter.ts +54 -0
  232. package/mail/src/types.ts +81 -0
  233. package/mail/tsconfig.json +21 -0
  234. package/mail/vitest.config.ts +13 -0
  235. package/package.json +36 -0
  236. package/s3/.turbo/turbo-build.log +1 -0
  237. package/s3/.turbo/turbo-test.log +11 -0
  238. package/s3/CHANGELOG.md +20 -0
  239. package/s3/README.md +90 -0
  240. package/s3/dist/index.d.ts +6 -0
  241. package/s3/dist/index.js +4 -0
  242. package/s3/dist/s3-service.d.ts +7 -0
  243. package/s3/dist/s3-service.js +21 -0
  244. package/s3/dist/s3.driver.d.ts +6 -0
  245. package/s3/dist/s3.driver.js +221 -0
  246. package/s3/dist/s3.plugin.d.ts +24 -0
  247. package/s3/dist/s3.plugin.js +58 -0
  248. package/s3/dist/types.d.ts +30 -0
  249. package/s3/dist/types.js +1 -0
  250. package/s3/node_modules/.bin/esbuild +22 -0
  251. package/s3/node_modules/.bin/jiti +22 -0
  252. package/s3/node_modules/.bin/sass +22 -0
  253. package/s3/node_modules/.bin/tsx +22 -0
  254. package/s3/node_modules/.bin/vite +22 -0
  255. package/s3/node_modules/.bin/vitest +22 -0
  256. package/s3/node_modules/.bin/yaml +22 -0
  257. package/s3/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  258. package/s3/package.json +47 -0
  259. package/s3/src/index.test.ts +20 -0
  260. package/s3/src/index.ts +13 -0
  261. package/s3/src/s3-service.ts +14 -0
  262. package/s3/src/s3.driver.ts +259 -0
  263. package/s3/src/s3.plugin.ts +80 -0
  264. package/s3/src/types.ts +37 -0
  265. package/s3/tsconfig.json +21 -0
  266. package/s3/vitest.config.ts +13 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @rx-ted/packages-honest-plugins
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 11bbae7: Remove defunct packages (web-admin, auth, http-client, search) and introduce barrel packages for consolidated exports. Migrate all internal imports from scoped plugin names (`@rx-ted/packages-honest-plugins-db`) to path-based barrel imports (`@rx-ted/packages-honest-plugins/db`). Reset all package versions to 1.0.0 with consolidated changelogs. Clean up obsolete documentation across the monorepo.
8
+ - Updated dependencies [11bbae7]
9
+ - @rx-ted/packages-honest-plugins-db@1.0.1
10
+ - @rx-ted/packages-honest-plugins-cache@1.0.1
11
+ - @rx-ted/packages-honest-plugins-mail@1.0.1
12
+ - @rx-ted/packages-honest-plugins-s3@1.0.1
13
+ - @rx-ted/packages-honest-plugins-api-doc@1.0.1
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @rx-ted/packages-honest-plugins
2
+
3
+ HonestJS 插件集合 — db, cache, mail, s3, api-doc。
4
+
5
+ ## 插件列表
6
+
7
+ | 插件 | 子包 | 用途 |
8
+ | --- | --- | --- |
9
+ | `db` | `@rx-ted/packages-honest-plugins-db` | 数据库(Drizzle ORM + MySQL/SQLite/D1/PostgreSQL) |
10
+ | `cache` | `@rx-ted/packages-honest-plugins-cache` | 缓存(Redis/KV/Local) |
11
+ | `mail` | `@rx-ted/packages-honest-plugins-mail` | 邮件(Resend/Brevo/SMTP) |
12
+ | `s3` | `@rx-ted/packages-honest-plugins-s3` | 对象存储(AWS S3) |
13
+ | `api-doc` | `@rx-ted/packages-honest-plugins-api-doc` | OpenAPI 文档 + Scalar UI |
14
+
15
+ ## 导入方式
16
+
17
+ ```ts
18
+ // 通过 barrel 导入
19
+ import { DbService } from '@rx-ted/packages-honest-plugins/db'
20
+ import { CacheService } from '@rx-ted/packages-honest-plugins/cache'
21
+ import { MailPlugin } from '@rx-ted/packages-honest-plugins/mail'
22
+ import { S3Plugin } from '@rx-ted/packages-honest-plugins/s3'
23
+ import { ApiDocPlugin } from '@rx-ted/packages-honest-plugins/api-doc'
24
+ ```
25
+
26
+ ## 子包结构
27
+
28
+ ```
29
+ packages/honest-plugins/
30
+ ├── db/ @rx-ted/packages-honest-plugins-db (private)
31
+ ├── cache/ @rx-ted/packages-honest-plugins-cache (private)
32
+ ├── mail/ @rx-ted/packages-honest-plugins-mail (private)
33
+ ├── s3/ @rx-ted/packages-honest-plugins-s3 (private)
34
+ └── api-doc/ @rx-ted/packages-honest-plugins-api-doc (private)
35
+ ```
36
+
37
+ 子包为 `private: true`,仅通过 barrel 包 `@rx-ted/packages-honest-plugins` 发布。
38
+
39
+ ## 开发
40
+
41
+ ```bash
42
+ pnpm build # 构建所有插件
43
+ pnpm test # 运行所有测试
44
+ pnpm typecheck # 类型检查
45
+ ```
@@ -0,0 +1 @@
1
+ $ tsc -p tsconfig.json
@@ -0,0 +1,11 @@
1
+ $ vitest run
2
+
3
+  RUN  v4.1.8 /Users/ben/projects/app/packages/honest-plugins/api-doc
4
+
5
+ ✓ src/plugin.test.ts (45 tests) 296ms
6
+
7
+  Test Files  1 passed (1)
8
+  Tests  45 passed (45)
9
+  Start at  14:17:11
10
+  Duration  513ms (transform 112ms, setup 0ms, import 159ms, tests 296ms, environment 0ms)
11
+
@@ -0,0 +1 @@
1
+ $ tsc -p tsconfig.json --noEmit
@@ -0,0 +1,22 @@
1
+ # @rx-ted/packages-honest-plugins-api-doc
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 11bbae7: Remove defunct packages (web-admin, auth, http-client, search) and introduce barrel packages for consolidated exports. Migrate all internal imports from scoped plugin names (`@rx-ted/packages-honest-plugins-db`) to path-based barrel imports (`@rx-ted/packages-honest-plugins/db`). Reset all package versions to 1.0.0 with consolidated changelogs. Clean up obsolete documentation across the monorepo.
8
+ - Updated dependencies [11bbae7]
9
+ - @rx-ted/packages-core@1.0.1
10
+ - @rx-ted/packages-honest@1.0.1
11
+
12
+ ## 1.0.0
13
+
14
+ Initial consolidated release.
15
+
16
+ ### Features
17
+
18
+ - API documentation plugin for @rx-ted/packages-honest
19
+ - OpenAPI spec generation
20
+ - NestJS-compatible decorators: `@Headers`, `@Ip`, `@Session`, `@HostParam`, `@Next`
21
+ - `@Redirect(url, statusCode?)` method decorator
22
+ - DESIGN.md documentation
@@ -0,0 +1,80 @@
1
+ # @rx-ted/packages-honest-plugins-api-doc
2
+
3
+ OpenAPI documentation plugin for [@rx-ted/packages-honest](https://github.com/rx-ted/honest). Generates OpenAPI specs from Honest route metadata with Scalar and Swagger UI renderers.
4
+
5
+ ## Features
6
+
7
+ - **Auto-generate**: OpenAPI spec from Honest decorator metadata
8
+ - **Scalar UI**: modern interactive API documentation
9
+ - **Swagger UI**: classic Swagger documentation
10
+ - **Plugin-based**: integrates with Honest's plugin lifecycle
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @rx-ted/packages-honest-plugins-api-doc
16
+ ```
17
+
18
+ Peer dependencies: `@rx-ted/packages-honest`, `hono`, `reflect-metadata`
19
+
20
+ ## Usage
21
+
22
+ ### Register as a plugin
23
+
24
+ ```ts
25
+ import { ApiDocPlugin } from '@rx-ted/packages-honest-plugins/api-doc';
26
+
27
+ const plugin = new ApiDocPlugin({
28
+ title: 'My API',
29
+ version: '1.0.0',
30
+ renderer: 'scalar', // 'scalar' | 'swagger'
31
+ });
32
+
33
+ const { hono } = await Application.create(AppModule, {
34
+ plugins: [plugin],
35
+ });
36
+ ```
37
+
38
+ ### Renderers
39
+
40
+ ```ts
41
+ import { ScalarRenderer } from '@rx-ted/packages-honest-plugins/api-doc';
42
+ import { SwaggerRenderer } from '@rx-ted/packages-honest-plugins/api-doc';
43
+
44
+ // Scalar UI (default)
45
+ const scalar = new ScalarRenderer({ title: 'API Docs' });
46
+
47
+ // Swagger UI
48
+ const swagger = new SwaggerRenderer({ title: 'API Docs' });
49
+ ```
50
+
51
+ ### Endpoints
52
+
53
+ Once registered, the plugin adds:
54
+
55
+ - `GET /openapi.json` — OpenAPI spec
56
+ - `GET /docs` — UI rendering (Scalar or Swagger)
57
+
58
+ ## Types
59
+
60
+ ```ts
61
+ interface ApiDocPluginOptions {
62
+ title?: string;
63
+ version?: string;
64
+ description?: string;
65
+ renderer?: 'scalar' | 'swagger';
66
+ specPath?: string;
67
+ }
68
+
69
+ interface IDocRenderer {
70
+ render(spec: Record<string, unknown>): string;
71
+ }
72
+ ```
73
+
74
+ ## Scripts
75
+
76
+ | Script | Description |
77
+ |--------|-------------|
78
+ | `pnpm test` | Run tests |
79
+ | `pnpm build` | Build |
80
+ | `pnpm typecheck` | Type check |
@@ -0,0 +1,53 @@
1
+ import { type ILogger } from '@rx-ted/packages-core';
2
+ import type { ApiDocSecurityScheme, ApiDocSecurityRequirement, Application, HonestOptions, IPlugin } from '@rx-ted/packages-honest';
3
+ import type { Context, Hono } from 'hono';
4
+ import type { IDocRenderer } from './interfaces/renderer.interface';
5
+ type RendererName = 'scalar' | 'swagger';
6
+ export interface ApiDocPluginOptions {
7
+ specUrl?: string;
8
+ uiRoute?: string;
9
+ uiTitle?: string;
10
+ defaultRenderer?: RendererName;
11
+ renderers?: IDocRenderer[];
12
+ onRequest?: (c: Context) => void | Promise<void>;
13
+ securitySchemes?: Record<string, ApiDocSecurityScheme>;
14
+ security?: ApiDocSecurityRequirement[];
15
+ exclude?: string[];
16
+ excludeMethods?: string[];
17
+ excludeTags?: string[];
18
+ }
19
+ export declare function normalizeRoute(input: string): string;
20
+ export declare class ApiDocPlugin implements IPlugin {
21
+ readonly name = "api-doc-plugin";
22
+ readonly version = "1.0.0";
23
+ logger?: ILogger;
24
+ private readonly specUrl;
25
+ private readonly uiRoute;
26
+ private readonly uiTitle;
27
+ private readonly defaultRendererName;
28
+ private readonly onRequest?;
29
+ private readonly securitySchemes;
30
+ private readonly defaultSecurity?;
31
+ private readonly exclude?;
32
+ private readonly excludeMethods?;
33
+ private readonly excludeTags?;
34
+ private renderers;
35
+ private spec;
36
+ private routingPrefix?;
37
+ private routingVersion?;
38
+ private app?;
39
+ constructor(options?: ApiDocPluginOptions);
40
+ private applyPrefixToUrl;
41
+ setRoutingOptions(options?: HonestOptions['routing']): void;
42
+ registerRenderer(renderer: IDocRenderer): void;
43
+ getRenderer(name: string): IDocRenderer | undefined;
44
+ beforeModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
45
+ afterModulesRegistered: (app: Application, hono: Hono) => Promise<void>;
46
+ private generateSpec;
47
+ private convertHonoPathToOpenAPI;
48
+ private isPathExcluded;
49
+ private toOpenAPISchema;
50
+ private zodToOpenAPI;
51
+ private runHook;
52
+ }
53
+ export {};
@@ -0,0 +1,452 @@
1
+ import { ENV_SYMBOL } from '@rx-ted/packages-core';
2
+ import { ComponentManager, VERSION_NEUTRAL, normalizePath, resolvePluginLogger, } from '@rx-ted/packages-honest';
3
+ import { ScalarRenderer } from './renderers/scalar.renderer';
4
+ import { SwaggerRenderer } from './renderers/swagger.renderer';
5
+ export function normalizeRoute(input) {
6
+ const trimmed = input.trim();
7
+ if (!trimmed)
8
+ return '/';
9
+ let normalized = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
10
+ if (normalized.length > 1) {
11
+ normalized = normalized.replace(/\/+$/g, '');
12
+ }
13
+ return normalized || '/';
14
+ }
15
+ const DEFAULT_SECURITY_SCHEMES = {
16
+ bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
17
+ basicAuth: { type: 'http', scheme: 'basic' },
18
+ apiKey: { type: 'apiKey', in: 'header', name: 'X-API-Key' },
19
+ cookieAuth: { type: 'apiKey', in: 'cookie', name: 'token' },
20
+ };
21
+ export class ApiDocPlugin {
22
+ name = 'api-doc-plugin';
23
+ version = '1.0.0';
24
+ logger;
25
+ specUrl = '/openapi.json';
26
+ uiRoute = '/docs';
27
+ uiTitle = 'API Documentation';
28
+ defaultRendererName = 'scalar';
29
+ onRequest;
30
+ securitySchemes = {
31
+ ...DEFAULT_SECURITY_SCHEMES,
32
+ };
33
+ defaultSecurity;
34
+ exclude;
35
+ excludeMethods;
36
+ excludeTags;
37
+ renderers = new Map();
38
+ spec = null;
39
+ routingPrefix;
40
+ routingVersion;
41
+ app;
42
+ constructor(options = {}) {
43
+ if (options.specUrl !== undefined)
44
+ this.specUrl = options.specUrl;
45
+ if (options.uiRoute !== undefined)
46
+ this.uiRoute = normalizeRoute(options.uiRoute);
47
+ if (options.uiTitle !== undefined)
48
+ this.uiTitle = options.uiTitle;
49
+ if (options.defaultRenderer !== undefined)
50
+ this.defaultRendererName = options.defaultRenderer;
51
+ if (options.onRequest !== undefined)
52
+ this.onRequest = options.onRequest;
53
+ if (options.securitySchemes !== undefined)
54
+ this.securitySchemes = options.securitySchemes;
55
+ if (options.security !== undefined)
56
+ this.defaultSecurity = options.security;
57
+ if (options.exclude !== undefined)
58
+ this.exclude = options.exclude;
59
+ if (options.excludeMethods !== undefined)
60
+ this.excludeMethods = options.excludeMethods;
61
+ if (options.excludeTags !== undefined)
62
+ this.excludeTags = options.excludeTags;
63
+ if (options.renderers !== undefined) {
64
+ options.renderers.forEach((r) => this.renderers.set(r.name, r));
65
+ }
66
+ else {
67
+ [new ScalarRenderer(), new SwaggerRenderer()].forEach((r) => this.renderers.set(r.name, r));
68
+ }
69
+ }
70
+ applyPrefixToUrl(url) {
71
+ if (!this.routingPrefix && !this.routingVersion) {
72
+ return url;
73
+ }
74
+ const prefix = this.routingPrefix ? normalizePath(`/${this.routingPrefix}`) : '';
75
+ let versionSegment = '';
76
+ if (this.routingVersion !== undefined &&
77
+ this.routingVersion !== null &&
78
+ this.routingVersion !== VERSION_NEUTRAL &&
79
+ !Array.isArray(this.routingVersion)) {
80
+ versionSegment = `/v${this.routingVersion}`;
81
+ }
82
+ const base = `${prefix}${versionSegment}`;
83
+ if (!base)
84
+ return url;
85
+ const urlPath = url.startsWith('/') ? url : `/${url}`;
86
+ return normalizePath(`${base}${urlPath}`);
87
+ }
88
+ setRoutingOptions(options) {
89
+ if (options) {
90
+ this.routingPrefix = options.prefix;
91
+ this.routingVersion = options.version;
92
+ }
93
+ }
94
+ registerRenderer(renderer) {
95
+ this.renderers.set(renderer.name, renderer);
96
+ }
97
+ getRenderer(name) {
98
+ return this.renderers.get(name);
99
+ }
100
+ beforeModulesRegistered = async (app, hono) => {
101
+ this.logger ??= resolvePluginLogger(this.name);
102
+ if (!ComponentManager.hasPlugin(ENV_SYMBOL)) {
103
+ this.logger.warn('ApiDoc disabled (env not configured)');
104
+ return;
105
+ }
106
+ const envInstance = ComponentManager.getPlugin(ENV_SYMBOL);
107
+ if (!envInstance.DEBUG) {
108
+ this.logger.warn('ApiDoc disabled in production');
109
+ return;
110
+ }
111
+ try {
112
+ const routingOptions = app.getRoutingOptions();
113
+ if (routingOptions) {
114
+ this.setRoutingOptions(routingOptions);
115
+ }
116
+ }
117
+ catch {
118
+ // Ignore if options are not accessible
119
+ }
120
+ const specUrlWithPrefix = this.applyPrefixToUrl(this.specUrl);
121
+ const uiRouteWithPrefix = this.applyPrefixToUrl(this.uiRoute);
122
+ // Register callback to print doc URLs when server starts
123
+ if (app.registerOnStartCallback) {
124
+ app.registerOnStartCallback((port) => {
125
+ const base = `http://localhost:${port}`;
126
+ this.logger?.info(`Docs: ${base}${uiRouteWithPrefix}`);
127
+ this.logger?.info(`OpenAPI JSON: ${base}${specUrlWithPrefix}`);
128
+ });
129
+ }
130
+ hono.get(specUrlWithPrefix, async (c) => {
131
+ await this.runHook(c);
132
+ if (!this.spec) {
133
+ return c.json({ status: 500, code: 'ERROR', data: null, error: 'API spec not generated yet' }, 500);
134
+ }
135
+ return c.json({ status: 200, code: 'OK', data: this.spec });
136
+ });
137
+ hono.get(uiRouteWithPrefix, async (c) => {
138
+ await this.runHook(c);
139
+ const renderer = this.renderers.get(this.defaultRendererName);
140
+ if (!renderer) {
141
+ return c.json({ error: `Default renderer '${this.defaultRendererName}' not found` }, 500);
142
+ }
143
+ return c.html(renderer.renderHtml({
144
+ specUrl: specUrlWithPrefix,
145
+ uiTitle: this.uiTitle,
146
+ }));
147
+ });
148
+ hono.get(`${uiRouteWithPrefix}/:renderer`, async (c) => {
149
+ await this.runHook(c);
150
+ const rendererName = c.req.param('renderer');
151
+ if (!rendererName)
152
+ return c.json({
153
+ error: `Renderer '${rendererName}' not found`,
154
+ available: Array.from(this.renderers.keys()),
155
+ }, 404);
156
+ const renderer = this.renderers.get(rendererName);
157
+ if (!renderer) {
158
+ return c.json({
159
+ error: `Renderer '${rendererName}' not found`,
160
+ available: Array.from(this.renderers.keys()),
161
+ }, 404);
162
+ }
163
+ return c.html(renderer.renderHtml({
164
+ specUrl: specUrlWithPrefix,
165
+ uiTitle: this.uiTitle,
166
+ }));
167
+ });
168
+ };
169
+ afterModulesRegistered = async (app, hono) => {
170
+ this.app = app;
171
+ this.spec = this.generateSpec();
172
+ };
173
+ generateSpec() {
174
+ const paths = {};
175
+ const tags = [];
176
+ if (!this.app) {
177
+ return {
178
+ openapi: '3.0.0',
179
+ info: {
180
+ title: this.uiTitle,
181
+ version: '1.0.0',
182
+ description: 'API Documentation',
183
+ },
184
+ paths: {},
185
+ };
186
+ }
187
+ const routes = this.app.getRoutes();
188
+ const controllerTags = new Map();
189
+ // Collect all controller tags first
190
+ for (const route of routes) {
191
+ if (route.controllerClass) {
192
+ const tag = Reflect.getMetadata('api:controller:tag', route.controllerClass);
193
+ if (tag && !controllerTags.has(route.controller)) {
194
+ controllerTags.set(route.controller, tag);
195
+ tags.push({
196
+ name: tag.name,
197
+ description: tag.description ?? `${tag.name} endpoints`,
198
+ });
199
+ }
200
+ }
201
+ }
202
+ for (const route of routes) {
203
+ let fullPath = route.fullPath;
204
+ if (!fullPath)
205
+ continue;
206
+ // Skip excluded paths
207
+ if (this.exclude && this.isPathExcluded(fullPath, this.exclude)) {
208
+ continue;
209
+ }
210
+ // Skip excluded methods
211
+ if (this.excludeMethods?.map((m) => m.toLowerCase()).includes(route.method.toLowerCase())) {
212
+ continue;
213
+ }
214
+ // Convert Hono :param syntax to OpenAPI {param} syntax
215
+ fullPath = this.convertHonoPathToOpenAPI(fullPath);
216
+ const pathItem = paths[fullPath] ?? {};
217
+ const apiDoc = route.controllerClass
218
+ ? Reflect.getMetadata('api:method:apiDoc', route.controllerClass.prototype, route.handler)
219
+ : undefined;
220
+ const controllerTag = controllerTags.get(route.controller);
221
+ // Skip excluded tags
222
+ const tagName = apiDoc?.tags ?? (controllerTag ? [controllerTag.name] : ['Default']);
223
+ if (this.excludeTags && tagName.some((tag) => this.excludeTags.includes(tag))) {
224
+ continue;
225
+ }
226
+ const operation = {
227
+ ...(apiDoc?.summary && { summary: apiDoc.summary }),
228
+ ...(apiDoc?.description && { description: apiDoc.description }),
229
+ tags: tagName,
230
+ // Add security requirements
231
+ ...(apiDoc?.security
232
+ ? { security: apiDoc.security }
233
+ : this.defaultSecurity
234
+ ? { security: this.defaultSecurity }
235
+ : this.securitySchemes
236
+ ? { security: Object.keys(this.securitySchemes).map((n) => ({ [n]: [] })) }
237
+ : {}),
238
+ };
239
+ // Handle parameters (path params from Zod schema + query params)
240
+ const parameters = [];
241
+ // Add path parameters from apiDoc.request.params
242
+ if (apiDoc?.request?.params) {
243
+ const paramsSchema = this.toOpenAPISchema(apiDoc.request.params);
244
+ if (paramsSchema.properties) {
245
+ for (const [name, schema] of Object.entries(paramsSchema.properties)) {
246
+ parameters.push({
247
+ name,
248
+ in: 'path',
249
+ required: true,
250
+ schema,
251
+ });
252
+ }
253
+ }
254
+ }
255
+ // Add query parameters from apiDoc.request.query
256
+ if (apiDoc?.request?.query) {
257
+ const querySchema = this.toOpenAPISchema(apiDoc.request.query);
258
+ if (querySchema.properties) {
259
+ for (const [name, schema] of Object.entries(querySchema.properties)) {
260
+ parameters.push({
261
+ name,
262
+ in: 'query',
263
+ required: querySchema.required?.includes(name) ?? false,
264
+ schema,
265
+ });
266
+ }
267
+ }
268
+ }
269
+ if (parameters.length > 0) {
270
+ operation.parameters = parameters;
271
+ }
272
+ if (apiDoc?.request?.body) {
273
+ operation.requestBody = {
274
+ required: true,
275
+ content: {
276
+ 'application/json': {
277
+ schema: this.toOpenAPISchema(apiDoc.request.body),
278
+ },
279
+ },
280
+ };
281
+ }
282
+ if (apiDoc?.responses) {
283
+ operation.responses = {};
284
+ for (const [statusStr, response] of Object.entries(apiDoc.responses)) {
285
+ const status = parseInt(statusStr, 10);
286
+ operation.responses[status] = {
287
+ description: response.description ?? 'Success',
288
+ ...(response.schema && {
289
+ content: {
290
+ 'application/json': {
291
+ schema: this.toOpenAPISchema(response.schema),
292
+ },
293
+ },
294
+ }),
295
+ };
296
+ }
297
+ }
298
+ pathItem[route.method] = operation;
299
+ paths[fullPath] = pathItem;
300
+ }
301
+ return {
302
+ openapi: '3.0.0',
303
+ info: {
304
+ title: this.uiTitle,
305
+ version: '1.0.0',
306
+ description: 'API Documentation',
307
+ },
308
+ paths,
309
+ ...(tags.length > 0 && { tags }),
310
+ components: {
311
+ securitySchemes: this.securitySchemes,
312
+ },
313
+ };
314
+ }
315
+ convertHonoPathToOpenAPI(honoPath) {
316
+ // Convert Hono :param syntax to OpenAPI {param} syntax
317
+ return honoPath.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
318
+ }
319
+ isPathExcluded(path, excludePatterns) {
320
+ return excludePatterns.some((pattern) => {
321
+ // Convert pattern to regex (support wildcard *)
322
+ const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`);
323
+ return regex.test(path);
324
+ });
325
+ }
326
+ toOpenAPISchema(schema) {
327
+ if (!schema)
328
+ return { type: 'object' };
329
+ if (schema._def) {
330
+ return this.zodToOpenAPI(schema);
331
+ }
332
+ if (schema.type || schema.properties) {
333
+ return schema;
334
+ }
335
+ return { type: 'object' };
336
+ }
337
+ zodToOpenAPI(schema) {
338
+ if (!schema?._def)
339
+ return { type: 'object' };
340
+ const def = schema._def;
341
+ // Handle Zod 4.x new structure (type, innerType, etc.)
342
+ // Zod 4.x uses def.type property instead of typeName
343
+ const zodType = def.type || def.typeName;
344
+ switch (zodType) {
345
+ case 'string':
346
+ case 'ZodString': {
347
+ const stringMeta = { type: 'string' };
348
+ // Zod 3.x: checks in def.checks
349
+ if (def.checks) {
350
+ for (const check of def.checks) {
351
+ if (check.kind === 'min')
352
+ stringMeta.minLength = check.value;
353
+ if (check.kind === 'max')
354
+ stringMeta.maxLength = check.value;
355
+ if (check.kind === 'regex')
356
+ stringMeta.pattern = check.regex.source;
357
+ }
358
+ }
359
+ // Zod 4.x: instance properties
360
+ if (schema.minLength != null)
361
+ stringMeta.minLength = schema.minLength;
362
+ if (schema.maxLength != null)
363
+ stringMeta.maxLength = schema.maxLength;
364
+ return stringMeta;
365
+ }
366
+ case 'number':
367
+ case 'ZodNumber': {
368
+ const numberMeta = { type: 'number' };
369
+ // Zod 3.x: checks in def.checks
370
+ if (def.checks) {
371
+ for (const check of def.checks) {
372
+ if (check.kind === 'min')
373
+ numberMeta.minimum = check.value;
374
+ if (check.kind === 'max')
375
+ numberMeta.maximum = check.value;
376
+ }
377
+ }
378
+ // Zod 4.x: instance properties
379
+ if (schema.minValue != null && Number.isFinite(schema.minValue))
380
+ numberMeta.minimum = schema.minValue;
381
+ if (schema.maxValue != null && Number.isFinite(schema.maxValue))
382
+ numberMeta.maximum = schema.maxValue;
383
+ return numberMeta;
384
+ }
385
+ case 'boolean':
386
+ case 'ZodBoolean':
387
+ return { type: 'boolean' };
388
+ case 'array':
389
+ case 'ZodArray': {
390
+ // Zod 4.x: def.element, Zod 3.x: def.type (the schema, not a string)
391
+ const elementSchema = def.element ||
392
+ (typeof def.type !== 'string' ? def.type : null) ||
393
+ (def.typeName ? def : null);
394
+ return {
395
+ type: 'array',
396
+ items: elementSchema ? this.zodToOpenAPI(elementSchema) : { type: 'object' },
397
+ };
398
+ }
399
+ case 'object':
400
+ case 'ZodObject': {
401
+ const properties = {};
402
+ const required = [];
403
+ // Zod 4.x: def.shape() or def.shape
404
+ // Zod 3.x: def.shape()
405
+ const shape = typeof def.shape === 'function' ? def.shape() : def.shape || {};
406
+ for (const [key, value] of Object.entries(shape)) {
407
+ const field = value;
408
+ properties[key] = this.zodToOpenAPI(field);
409
+ // Check if field is required (not optional)
410
+ const fieldDef = field?._def;
411
+ const fieldType = fieldDef?.type || fieldDef?.typeName;
412
+ if (fieldType !== 'optional' && fieldType !== 'ZodOptional') {
413
+ required.push(key);
414
+ }
415
+ }
416
+ return {
417
+ type: 'object',
418
+ properties,
419
+ ...(required.length > 0 && { required }),
420
+ };
421
+ }
422
+ case 'optional':
423
+ case 'ZodOptional':
424
+ // Zod 4.x: def.innerType, Zod 3.x: def.innerType or def.type
425
+ return this.zodToOpenAPI(def.innerType ?? def.type);
426
+ case 'enum':
427
+ case 'ZodEnum':
428
+ return {
429
+ type: 'string',
430
+ enum: def.values || (def.entries ? Object.values(def.entries) : []),
431
+ };
432
+ case 'null':
433
+ case 'ZodNull':
434
+ return { type: 'null' };
435
+ case 'union':
436
+ case 'ZodUnion':
437
+ if (def.options) {
438
+ return {
439
+ oneOf: def.options.map((opt) => this.zodToOpenAPI(opt)),
440
+ };
441
+ }
442
+ return { type: 'object' };
443
+ default:
444
+ return { type: 'object' };
445
+ }
446
+ }
447
+ async runHook(c) {
448
+ if (this.onRequest) {
449
+ await this.onRequest(c);
450
+ }
451
+ }
452
+ }