@etus/bhono-app 0.1.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 (688) hide show
  1. package/dist/cli.d.ts +13 -0
  2. package/dist/cli.js +46 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +26 -0
  6. package/dist/cli.test.js.map +1 -0
  7. package/dist/generator.d.ts +14 -0
  8. package/dist/generator.js +142 -0
  9. package/dist/generator.js.map +1 -0
  10. package/dist/generator.test.d.ts +1 -0
  11. package/dist/generator.test.js +127 -0
  12. package/dist/generator.test.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +97 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +25 -0
  17. package/dist/prompts.js +83 -0
  18. package/dist/prompts.js.map +1 -0
  19. package/dist/prompts.test.d.ts +1 -0
  20. package/dist/prompts.test.js +24 -0
  21. package/dist/prompts.test.js.map +1 -0
  22. package/dist/providers/cloudflare.d.ts +37 -0
  23. package/dist/providers/cloudflare.js +61 -0
  24. package/dist/providers/cloudflare.js.map +1 -0
  25. package/dist/providers/cloudflare.test.d.ts +1 -0
  26. package/dist/providers/cloudflare.test.js +29 -0
  27. package/dist/providers/cloudflare.test.js.map +1 -0
  28. package/dist/providers/github.d.ts +16 -0
  29. package/dist/providers/github.js +57 -0
  30. package/dist/providers/github.js.map +1 -0
  31. package/dist/providers/github.test.d.ts +1 -0
  32. package/dist/providers/github.test.js +16 -0
  33. package/dist/providers/github.test.js.map +1 -0
  34. package/dist/templates.d.ts +8 -0
  35. package/dist/templates.js +88 -0
  36. package/dist/templates.js.map +1 -0
  37. package/dist/templates.test.d.ts +1 -0
  38. package/dist/templates.test.js +26 -0
  39. package/dist/templates.test.js.map +1 -0
  40. package/package.json +36 -0
  41. package/templates/base/.claude/agents/architect-review.md +160 -0
  42. package/templates/base/.claude/agents/backend-architect.md +308 -0
  43. package/templates/base/.claude/agents/code-reviewer.md +170 -0
  44. package/templates/base/.claude/agents/performance-engineer.md +166 -0
  45. package/templates/base/.claude/agents/test-automator.md +219 -0
  46. package/templates/base/.claude/commands/check-skill-rules.md +53 -0
  47. package/templates/base/.claude/commands/claude-md.md +250 -0
  48. package/templates/base/.claude/commands/code-prompt.md +212 -0
  49. package/templates/base/.claude/commands/explain-code.md +194 -0
  50. package/templates/base/.claude/commands/init-projec.md +89 -0
  51. package/templates/base/.claude/commands/linear/README.md +297 -0
  52. package/templates/base/.claude/commands/linear/create-issue.md +190 -0
  53. package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
  54. package/templates/base/.claude/commands/linear/process-triage.md +399 -0
  55. package/templates/base/.claude/commands/linear/setup.md +180 -0
  56. package/templates/base/.claude/commands/prime.md +9 -0
  57. package/templates/base/.claude/commands/review-gap.md +10 -0
  58. package/templates/base/.claude/commands/setup-aa.md +311 -0
  59. package/templates/base/.claude/commands/ship.md +262 -0
  60. package/templates/base/.claude/commands/tools.md +3 -0
  61. package/templates/base/.claude/docs/claude-progress.txt +107 -0
  62. package/templates/base/.claude/hooks/package-lock.json +556 -0
  63. package/templates/base/.claude/hooks/package.json +16 -0
  64. package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
  65. package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
  66. package/templates/base/.claude/hooks/tsconfig.json +19 -0
  67. package/templates/base/.claude/scripts/check-updates.sh +85 -0
  68. package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
  69. package/templates/base/.claude/scripts/setup-project.sh +177 -0
  70. package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
  71. package/templates/base/.claude/settings.json +113 -0
  72. package/templates/base/.claude/settings.local.json +11 -0
  73. package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
  74. package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
  75. package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
  76. package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
  77. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
  78. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
  79. package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
  80. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
  81. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
  82. package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
  83. package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
  84. package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
  85. package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
  86. package/templates/base/.claude/skills/dev-browser/package.json +27 -0
  87. package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
  88. package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
  89. package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
  90. package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
  91. package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
  92. package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  93. package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
  94. package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
  95. package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
  96. package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
  97. package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
  98. package/templates/base/.claude/skills/linear/SKILL.md +440 -0
  99. package/templates/base/.claude/skills/linear/examples.md +262 -0
  100. package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
  101. package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
  102. package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
  103. package/templates/base/.claude/skills/linear/package-lock.json +698 -0
  104. package/templates/base/.claude/skills/linear/package.json +27 -0
  105. package/templates/base/.claude/skills/linear/reference.md +263 -0
  106. package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
  107. package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
  108. package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
  109. package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
  110. package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
  111. package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
  112. package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
  113. package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
  114. package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
  115. package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
  116. package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
  117. package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
  118. package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
  119. package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
  120. package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
  121. package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
  122. package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
  123. package/templates/base/.claude/skills/linear/templates/README.md +203 -0
  124. package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
  125. package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
  126. package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
  127. package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
  128. package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
  129. package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
  130. package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
  131. package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
  132. package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
  133. package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
  134. package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
  135. package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
  136. package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
  137. package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
  138. package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
  139. package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
  140. package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
  141. package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
  142. package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
  143. package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
  144. package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
  145. package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
  146. package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
  147. package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
  148. package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
  149. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
  150. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
  151. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
  152. package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
  153. package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
  154. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
  155. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
  156. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
  157. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
  158. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
  159. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
  160. package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
  161. package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
  162. package/templates/base/.claude/skills/skill-rules.json +184 -0
  163. package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
  164. package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
  165. package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
  166. package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
  167. package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
  168. package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
  169. package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
  170. package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
  171. package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
  172. package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
  173. package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
  174. package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
  175. package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
  176. package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
  177. package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
  178. package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
  179. package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
  180. package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
  181. package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
  182. package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
  183. package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
  184. package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
  185. package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
  186. package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
  187. package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
  188. package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
  189. package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
  190. package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
  191. package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
  192. package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
  193. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
  194. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
  195. package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
  196. package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
  197. package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
  198. package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
  199. package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
  200. package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
  201. package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
  202. package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
  203. package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
  204. package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
  205. package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
  206. package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
  207. package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
  208. package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
  209. package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
  210. package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
  211. package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
  212. package/templates/base/.dev.vars.example +15 -0
  213. package/templates/base/.env.example +29 -0
  214. package/templates/base/.github/workflows/test.yml +127 -0
  215. package/templates/base/.nycrc.json +16 -0
  216. package/templates/base/CLAUDE.md +218 -0
  217. package/templates/base/README.md +670 -0
  218. package/templates/base/auth-setup-error.png +0 -0
  219. package/templates/base/config/drizzle.config.ts +10 -0
  220. package/templates/base/config/eslint.config.js +364 -0
  221. package/templates/base/config/wrangler.json +76 -0
  222. package/templates/base/docs/app_spec.txt +879 -0
  223. package/templates/base/docs/app_spec_template.md +681 -0
  224. package/templates/base/docs/architecture/README.md +8 -0
  225. package/templates/base/docs/architecture/data-requirements.md +109 -0
  226. package/templates/base/docs/architecture/erd.md +91 -0
  227. package/templates/base/docs/features/feature_list.json +3128 -0
  228. package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
  229. package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
  230. package/templates/base/docs/testing.md +188 -0
  231. package/templates/base/index.html +16 -0
  232. package/templates/base/package.json +115 -0
  233. package/templates/base/playwright.config.ts +158 -0
  234. package/templates/base/pnpm-lock.yaml +8175 -0
  235. package/templates/base/scripts/capture-prod-session.ts +250 -0
  236. package/templates/base/scripts/generate-openapi.ts +23 -0
  237. package/templates/base/scripts/init.sh +121 -0
  238. package/templates/base/src/client/__tests__/button.test.tsx +30 -0
  239. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  240. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  241. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  242. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  243. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  244. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  245. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  246. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  247. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  248. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  249. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  250. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  251. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  252. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  253. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  254. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  255. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  256. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  257. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  258. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  259. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  260. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  261. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  262. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  263. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  264. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  265. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  266. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  267. package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
  268. package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
  269. package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
  270. package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
  271. package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
  272. package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
  273. package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
  274. package/templates/base/src/client/__tests__/setup.ts +32 -0
  275. package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
  276. package/templates/base/src/client/api/.gitkeep +0 -0
  277. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  278. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  279. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  280. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  281. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  282. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  283. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  284. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  285. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  286. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  287. package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
  288. package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
  289. package/templates/base/src/client/components/error-boundary.tsx +68 -0
  290. package/templates/base/src/client/components/icons.tsx +106 -0
  291. package/templates/base/src/client/components/layout/.gitkeep +0 -0
  292. package/templates/base/src/client/components/sidebar.tsx +267 -0
  293. package/templates/base/src/client/components/ui/.gitkeep +0 -0
  294. package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
  295. package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
  296. package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
  297. package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
  298. package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
  299. package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
  300. package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
  301. package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
  302. package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
  303. package/templates/base/src/client/components/ui/avatar.tsx +101 -0
  304. package/templates/base/src/client/components/ui/badge.tsx +46 -0
  305. package/templates/base/src/client/components/ui/button.tsx +72 -0
  306. package/templates/base/src/client/components/ui/card.tsx +86 -0
  307. package/templates/base/src/client/components/ui/dialog.tsx +140 -0
  308. package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
  309. package/templates/base/src/client/components/ui/form.tsx +172 -0
  310. package/templates/base/src/client/components/ui/input.tsx +24 -0
  311. package/templates/base/src/client/components/ui/label.tsx +22 -0
  312. package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
  313. package/templates/base/src/client/components/ui/separator.tsx +33 -0
  314. package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
  315. package/templates/base/src/client/components/ui/sonner.tsx +29 -0
  316. package/templates/base/src/client/components/ui/tabs.tsx +121 -0
  317. package/templates/base/src/client/hooks/.gitkeep +0 -0
  318. package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
  319. package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
  320. package/templates/base/src/client/hooks/use-auth.ts +53 -0
  321. package/templates/base/src/client/hooks/use-theme.tsx +78 -0
  322. package/templates/base/src/client/index.css +881 -0
  323. package/templates/base/src/client/lib/query-client.ts +11 -0
  324. package/templates/base/src/client/lib/utils.ts +7 -0
  325. package/templates/base/src/client/main.tsx +26 -0
  326. package/templates/base/src/client/routeTree.gen.ts +258 -0
  327. package/templates/base/src/client/router.ts +15 -0
  328. package/templates/base/src/client/routes/$.tsx +77 -0
  329. package/templates/base/src/client/routes/.gitkeep +0 -0
  330. package/templates/base/src/client/routes/__root.tsx +34 -0
  331. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  332. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  333. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  334. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  335. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  336. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  337. package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
  338. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  339. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  340. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  341. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  342. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  343. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  344. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  345. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  346. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  347. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  348. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  349. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  350. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  351. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  352. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  353. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  354. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  355. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  356. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  357. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  358. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  359. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  360. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  361. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  362. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  363. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  364. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  365. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  366. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  367. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  368. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  369. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  370. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  371. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  372. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  373. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  374. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  375. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  376. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  377. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  378. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  379. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  380. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  381. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  382. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  383. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  384. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  385. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  386. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  387. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  388. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  389. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  390. package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
  391. package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
  392. package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
  393. package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
  394. package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
  395. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
  396. package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
  397. package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
  398. package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
  399. package/templates/base/src/client/routes/_authenticated.tsx +69 -0
  400. package/templates/base/src/client/routes/index.tsx +155 -0
  401. package/templates/base/src/client/routes/invite.$token.tsx +191 -0
  402. package/templates/base/src/client/routes/login.tsx +92 -0
  403. package/templates/base/src/server/__tests__/fixtures.ts +461 -0
  404. package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
  405. package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
  406. package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
  407. package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
  408. package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
  409. package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
  410. package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
  411. package/templates/base/src/server/__tests__/setup.ts +281 -0
  412. package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
  413. package/templates/base/src/server/auth/guards.ts +92 -0
  414. package/templates/base/src/server/auth/permissions.test.ts +45 -0
  415. package/templates/base/src/server/auth/permissions.ts +139 -0
  416. package/templates/base/src/server/auth/roles.test.ts +169 -0
  417. package/templates/base/src/server/auth/roles.ts +141 -0
  418. package/templates/base/src/server/db/client.ts +12 -0
  419. package/templates/base/src/server/db/schema/accounts.ts +20 -0
  420. package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
  421. package/templates/base/src/server/db/schema/index.ts +7 -0
  422. package/templates/base/src/server/db/schema/invitations.ts +30 -0
  423. package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
  424. package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
  425. package/templates/base/src/server/db/schema/users.ts +33 -0
  426. package/templates/base/src/server/db/seed.ts +267 -0
  427. package/templates/base/src/server/env.test.ts +84 -0
  428. package/templates/base/src/server/env.ts +78 -0
  429. package/templates/base/src/server/index.ts +82 -0
  430. package/templates/base/src/server/lib/audit.ts +73 -0
  431. package/templates/base/src/server/lib/audited-db.test.ts +107 -0
  432. package/templates/base/src/server/lib/audited-db.ts +154 -0
  433. package/templates/base/src/server/lib/email.test.ts +116 -0
  434. package/templates/base/src/server/lib/email.ts +82 -0
  435. package/templates/base/src/server/lib/errors.test.ts +49 -0
  436. package/templates/base/src/server/lib/errors.ts +64 -0
  437. package/templates/base/src/server/lib/oauth.test.ts +238 -0
  438. package/templates/base/src/server/lib/oauth.ts +113 -0
  439. package/templates/base/src/server/lib/pagination.test.ts +52 -0
  440. package/templates/base/src/server/lib/pagination.ts +32 -0
  441. package/templates/base/src/server/lib/password.test.ts +151 -0
  442. package/templates/base/src/server/lib/password.ts +151 -0
  443. package/templates/base/src/server/lib/providers.test.ts +105 -0
  444. package/templates/base/src/server/lib/providers.ts +62 -0
  445. package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
  446. package/templates/base/src/server/lib/r2-storage.ts +107 -0
  447. package/templates/base/src/server/lib/schema-helpers.ts +16 -0
  448. package/templates/base/src/server/lib/session.test.ts +758 -0
  449. package/templates/base/src/server/lib/session.ts +267 -0
  450. package/templates/base/src/server/lib/tokens.test.ts +208 -0
  451. package/templates/base/src/server/lib/tokens.ts +65 -0
  452. package/templates/base/src/server/lib/transaction.test.ts +45 -0
  453. package/templates/base/src/server/lib/transaction.ts +24 -0
  454. package/templates/base/src/server/middleware/account.test.ts +201 -0
  455. package/templates/base/src/server/middleware/account.ts +66 -0
  456. package/templates/base/src/server/middleware/auth.test.ts +345 -0
  457. package/templates/base/src/server/middleware/auth.ts +146 -0
  458. package/templates/base/src/server/middleware/cors.test.ts +88 -0
  459. package/templates/base/src/server/middleware/cors.ts +26 -0
  460. package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
  461. package/templates/base/src/server/middleware/error-handler.ts +43 -0
  462. package/templates/base/src/server/middleware/index.ts +8 -0
  463. package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
  464. package/templates/base/src/server/middleware/rate-limit.ts +294 -0
  465. package/templates/base/src/server/middleware/request-context.test.ts +175 -0
  466. package/templates/base/src/server/middleware/request-context.ts +28 -0
  467. package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
  468. package/templates/base/src/server/middleware/request-logger.ts +50 -0
  469. package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
  470. package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
  471. package/templates/base/src/server/routes/accounts/index.ts +55 -0
  472. package/templates/base/src/server/routes/accounts/routes.ts +205 -0
  473. package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
  474. package/templates/base/src/server/routes/api.ts +37 -0
  475. package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
  476. package/templates/base/src/server/routes/audits/handlers.ts +37 -0
  477. package/templates/base/src/server/routes/audits/index.ts +14 -0
  478. package/templates/base/src/server/routes/audits/routes.ts +29 -0
  479. package/templates/base/src/server/routes/audits/schemas.ts +43 -0
  480. package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
  481. package/templates/base/src/server/routes/auth/handlers.ts +222 -0
  482. package/templates/base/src/server/routes/auth/index.ts +37 -0
  483. package/templates/base/src/server/routes/auth/routes.ts +136 -0
  484. package/templates/base/src/server/routes/auth/schemas.ts +48 -0
  485. package/templates/base/src/server/routes/auth/test-login.ts +156 -0
  486. package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
  487. package/templates/base/src/server/routes/health/handlers.ts +83 -0
  488. package/templates/base/src/server/routes/health/index.ts +13 -0
  489. package/templates/base/src/server/routes/health/routes.ts +90 -0
  490. package/templates/base/src/server/routes/index.ts +53 -0
  491. package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
  492. package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
  493. package/templates/base/src/server/routes/invitations/index.ts +25 -0
  494. package/templates/base/src/server/routes/invitations/routes.ts +69 -0
  495. package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
  496. package/templates/base/src/server/routes/openapi.ts +14 -0
  497. package/templates/base/src/server/routes/schemas.ts +53 -0
  498. package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
  499. package/templates/base/src/server/routes/storage/handlers.ts +100 -0
  500. package/templates/base/src/server/routes/storage/index.ts +42 -0
  501. package/templates/base/src/server/routes/storage/routes.ts +91 -0
  502. package/templates/base/src/server/routes/storage/schemas.ts +56 -0
  503. package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
  504. package/templates/base/src/server/routes/users/handlers.ts +228 -0
  505. package/templates/base/src/server/routes/users/index.ts +67 -0
  506. package/templates/base/src/server/routes/users/routes.ts +265 -0
  507. package/templates/base/src/server/routes/users/schemas.ts +67 -0
  508. package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
  509. package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
  510. package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
  511. package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
  512. package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
  513. package/templates/base/src/server/services/accounts.ts +269 -0
  514. package/templates/base/src/server/services/audits.ts +82 -0
  515. package/templates/base/src/server/services/auth.ts +225 -0
  516. package/templates/base/src/server/services/index.ts +6 -0
  517. package/templates/base/src/server/services/invitations.ts +306 -0
  518. package/templates/base/src/server/services/users.ts +350 -0
  519. package/templates/base/src/server/types/auth.ts +36 -0
  520. package/templates/base/src/server/types/index.ts +117 -0
  521. package/templates/base/src/shared/schemas/.gitkeep +0 -0
  522. package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
  523. package/templates/base/src/shared/schemas/account.ts +15 -0
  524. package/templates/base/src/shared/schemas/index.ts +6 -0
  525. package/templates/base/src/shared/schemas/invitation.ts +9 -0
  526. package/templates/base/src/shared/schemas/profile.ts +10 -0
  527. package/templates/base/src/shared/schemas/user.ts +16 -0
  528. package/templates/base/src/shared/schemas/webhook.ts +12 -0
  529. package/templates/base/src/shared/types/.gitkeep +0 -0
  530. package/templates/base/src/shared/types/account.ts +12 -0
  531. package/templates/base/src/shared/types/api.ts +1399 -0
  532. package/templates/base/src/shared/types/auth.ts +24 -0
  533. package/templates/base/src/shared/types/index.ts +5 -0
  534. package/templates/base/src/shared/types/user.ts +31 -0
  535. package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
  536. package/templates/base/src/test/vitest.d.ts +19 -0
  537. package/templates/base/tests/e2e/README.md +141 -0
  538. package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
  539. package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
  540. package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
  541. package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
  542. package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
  543. package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
  544. package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
  545. package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
  546. package/templates/base/tests/e2e/auth.setup.ts +115 -0
  547. package/templates/base/tests/e2e/auth.spec.ts +146 -0
  548. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
  549. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
  550. package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
  551. package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
  552. package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
  553. package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
  554. package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
  555. package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
  556. package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
  557. package/templates/base/tests/e2e/fixtures.ts +266 -0
  558. package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
  559. package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
  560. package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
  561. package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
  562. package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
  563. package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
  564. package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
  565. package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
  566. package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
  567. package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
  568. package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
  569. package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
  570. package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
  571. package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
  572. package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
  573. package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
  574. package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
  575. package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
  576. package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
  577. package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
  578. package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
  579. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
  580. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
  581. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
  582. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
  583. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
  584. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
  585. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
  586. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
  587. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
  588. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
  589. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
  590. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
  591. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
  592. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
  593. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
  594. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  595. package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
  596. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  597. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
  598. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
  599. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
  600. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
  601. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
  602. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  603. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
  604. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
  605. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  606. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  607. package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
  608. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
  609. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
  610. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  611. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
  612. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  613. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
  614. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
  615. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  616. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
  617. package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
  618. package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
  619. package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
  620. package/templates/base/tests/integration/audits/list.test.ts +1133 -0
  621. package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
  622. package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
  623. package/templates/base/tests/integration/auth/logout.test.ts +524 -0
  624. package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
  625. package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
  626. package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
  627. package/templates/base/tests/integration/auth/session.test.ts +520 -0
  628. package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
  629. package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
  630. package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
  631. package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
  632. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
  633. package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
  634. package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
  635. package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
  636. package/templates/base/tests/integration/db/constraints.test.ts +695 -0
  637. package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
  638. package/templates/base/tests/integration/fixtures/index.ts +232 -0
  639. package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
  640. package/templates/base/tests/integration/fixtures/users.ts +144 -0
  641. package/templates/base/tests/integration/health/health.test.ts +351 -0
  642. package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
  643. package/templates/base/tests/integration/invitations/email.test.ts +506 -0
  644. package/templates/base/tests/integration/lib/email.test.ts +174 -0
  645. package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
  646. package/templates/base/tests/integration/lib/password.test.ts +192 -0
  647. package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
  648. package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
  649. package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
  650. package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
  651. package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
  652. package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
  653. package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
  654. package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
  655. package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
  656. package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
  657. package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
  658. package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
  659. package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
  660. package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
  661. package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
  662. package/templates/base/tests/integration/setup.ts +834 -0
  663. package/templates/base/tests/integration/smoke.test.ts +288 -0
  664. package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
  665. package/templates/base/tests/integration/storage/validation.test.ts +746 -0
  666. package/templates/base/tests/integration/users/crud.test.ts +1297 -0
  667. package/templates/base/tests/integration/users/list.test.ts +698 -0
  668. package/templates/base/tests/integration/vitest.config.ts +80 -0
  669. package/templates/base/tsconfig.app.json +18 -0
  670. package/templates/base/tsconfig.json +39 -0
  671. package/templates/base/tsconfig.node.json +16 -0
  672. package/templates/base/tsconfig.server.json +26 -0
  673. package/templates/base/tsconfig.tsbuildinfo +1 -0
  674. package/templates/base/vite.config.ts +46 -0
  675. package/templates/base/vitest.config.browser.ts +47 -0
  676. package/templates/base/vitest.config.frontend.ts +47 -0
  677. package/templates/base/vitest.config.ts +82 -0
  678. package/templates/base/vitest.workspace.ts +22 -0
  679. package/templates/modules/audit-logs/.gitkeep +0 -0
  680. package/templates/modules/billing/.gitkeep +0 -0
  681. package/templates/modules/invitations/.gitkeep +0 -0
  682. package/templates/modules/storage/.gitkeep +0 -0
  683. package/templates/modules/webhooks/.gitkeep +0 -0
  684. package/templates/providers/auth-email/.gitkeep +0 -0
  685. package/templates/providers/auth-github/.gitkeep +0 -0
  686. package/templates/providers/auth-google/.gitkeep +0 -0
  687. package/templates/providers/email-resend/.gitkeep +0 -0
  688. package/templates/providers/email-sendgrid/.gitkeep +0 -0
@@ -0,0 +1,1774 @@
1
+ # Hono Boilerplate Plan
2
+
3
+ This document outlines a plan to create a new backend boilerplate using Hono.js, preserving the core concepts from the NestJS boilerplate while leveraging Hono's lightweight, type-safe approach.
4
+
5
+ ## Key Hono Features to Leverage
6
+
7
+ | Feature | Benefit |
8
+ |---------|---------|
9
+ | `@hono/zod-openapi` | Schema validation + auto-generated OpenAPI docs in one |
10
+ | `OpenAPIHono` | Extended Hono class with built-in OpenAPI support |
11
+ | `createRoute()` | Type-safe route definitions with request/response schemas |
12
+ | RPC mode | End-to-end type safety if frontend consumes the API |
13
+ | `createFactory()` | Centralized type definitions, reduces boilerplate |
14
+ | `c.set()`/`c.get()` | Built-in request-scoped context (replaces nestjs-cls) |
15
+ | JWT helper | Native JWT verify/sign/decode with multiple algorithms |
16
+
17
+ ## Project Structure
18
+
19
+ ```
20
+ src/
21
+ ├── index.ts # Entry point with serve()
22
+ ├── app.ts # OpenAPIHono app factory
23
+ ├── env.ts # Zod schema for env validation
24
+
25
+ ├── middleware/
26
+ │ ├── auth.ts # JWT validation middleware
27
+ │ ├── account.ts # account-id header + membership check
28
+ │ ├── request-context.ts # Sets user, requestId, ip in context
29
+ │ └── error-handler.ts # defaultHook for validation errors
30
+
31
+ ├── routes/
32
+ │ ├── index.ts # app.route() aggregation + OpenAPI doc
33
+ │ ├── users/
34
+ │ │ ├── index.ts # OpenAPIHono sub-app
35
+ │ │ ├── routes.ts # createRoute() definitions
36
+ │ │ ├── handlers.ts # Handler implementations
37
+ │ │ └── schemas.ts # Zod schemas with .openapi()
38
+ │ └── accounts/
39
+ │ └── ...
40
+
41
+ ├── db/
42
+ │ ├── client.ts # Drizzle client
43
+ │ ├── schema/
44
+ │ │ ├── users.ts
45
+ │ │ ├── accounts.ts
46
+ │ │ ├── user-accounts.ts
47
+ │ │ └── audit-logs.ts
48
+ │ ├── migrations/
49
+ │ └── seed.ts
50
+
51
+ ├── services/
52
+ │ ├── users.ts # Business logic
53
+ │ ├── accounts.ts
54
+ │ ├── audit.ts
55
+ │ └── identity-provider.ts # Auth0 abstraction
56
+
57
+ ├── auth/
58
+ │ ├── permissions.ts # Permission enum + matrix
59
+ │ ├── roles.ts # Role enum + helpers
60
+ │ └── guards.ts # requireRole(), requirePermission()
61
+
62
+ ├── lib/
63
+ │ ├── pagination.ts # Pagination helpers
64
+ │ ├── errors.ts # Custom error classes
65
+ │ └── result.ts # Result<T, E> type (optional)
66
+
67
+ └── types/
68
+ └── index.ts # Shared types, Env definition
69
+ ```
70
+
71
+ ## Core Dependencies
72
+
73
+ ```json
74
+ {
75
+ "dependencies": {
76
+ "hono": "^4.x",
77
+ "@hono/node-server": "^1.x",
78
+ "@hono/zod-openapi": "^0.x",
79
+ "@hono/swagger-ui": "^0.x",
80
+ "drizzle-orm": "^0.x",
81
+ "better-sqlite3": "^11.x",
82
+ "zod": "^3.x"
83
+ },
84
+ "devDependencies": {
85
+ "drizzle-kit": "^0.x",
86
+ "typescript": "^5.x",
87
+ "@types/node": "^22.x",
88
+ "@types/better-sqlite3": "^7.x"
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Implementation Details
94
+
95
+ ### 1. App Factory with Typed Environment
96
+
97
+ ```typescript
98
+ // app.ts
99
+ import { OpenAPIHono } from '@hono/zod-openapi'
100
+ import { swaggerUI } from '@hono/swagger-ui'
101
+
102
+ export type Env = {
103
+ Variables: {
104
+ requestId: string
105
+ user: User | null
106
+ accountId: string
107
+ userRole: Role | null
108
+ }
109
+ }
110
+
111
+ export const createApp = () => {
112
+ const app = new OpenAPIHono<Env>({
113
+ defaultHook: (result, c) => {
114
+ if (!result.success) {
115
+ return c.json({
116
+ error: 'Validation Error',
117
+ details: result.error.flatten()
118
+ }, 400)
119
+ }
120
+ }
121
+ })
122
+
123
+ return app
124
+ }
125
+ ```
126
+
127
+ ### 2. Route Definitions with `createRoute()`
128
+
129
+ ```typescript
130
+ // routes/users/routes.ts
131
+ import { createRoute, z } from '@hono/zod-openapi'
132
+ import { UserSchema, CreateUserSchema } from './schemas'
133
+
134
+ export const listUsers = createRoute({
135
+ method: 'get',
136
+ path: '/users',
137
+ tags: ['Users'],
138
+ security: [{ Bearer: [] }],
139
+ request: {
140
+ query: z.object({
141
+ page: z.coerce.number().default(1),
142
+ limit: z.coerce.number().default(50),
143
+ }),
144
+ },
145
+ responses: {
146
+ 200: {
147
+ content: { 'application/json': { schema: PaginatedUsersSchema } },
148
+ description: 'List of users',
149
+ },
150
+ 401: { description: 'Unauthorized' },
151
+ },
152
+ })
153
+
154
+ export const createUser = createRoute({
155
+ method: 'post',
156
+ path: '/users',
157
+ tags: ['Users'],
158
+ middleware: [requireRole('ADMIN')], // Inline guard
159
+ request: {
160
+ body: { content: { 'application/json': { schema: CreateUserSchema } } },
161
+ },
162
+ responses: {
163
+ 201: {
164
+ content: { 'application/json': { schema: UserSchema } },
165
+ description: 'User created',
166
+ },
167
+ },
168
+ })
169
+ ```
170
+
171
+ ### 3. Schemas with OpenAPI Metadata
172
+
173
+ ```typescript
174
+ // routes/users/schemas.ts
175
+ import { z } from '@hono/zod-openapi'
176
+
177
+ export const UserSchema = z.object({
178
+ id: z.string().uuid().openapi({ example: '550e8400-e29b-41d4-a716-446655440000' }),
179
+ email: z.string().email().openapi({ example: 'user@example.com' }),
180
+ name: z.string().openapi({ example: 'John Doe' }),
181
+ status: z.enum(['active', 'inactive']).openapi({ example: 'active' }),
182
+ isSuperAdmin: z.boolean().openapi({ example: false }),
183
+ createdAt: z.string().datetime(),
184
+ }).openapi('User')
185
+
186
+ export const CreateUserSchema = z.object({
187
+ email: z.string().email(),
188
+ name: z.string().min(1).max(255),
189
+ accountIds: z.array(z.string().uuid()).optional(),
190
+ }).openapi('CreateUserInput')
191
+ ```
192
+
193
+ ### 4. Handlers with Typed Context
194
+
195
+ ```typescript
196
+ // routes/users/handlers.ts
197
+ import type { Context } from 'hono'
198
+ import type { Env } from '../../app'
199
+
200
+ export const handleListUsers = async (c: Context<Env>) => {
201
+ const { page, limit } = c.req.valid('query') // Typed from route schema
202
+ const user = c.get('user') // From middleware
203
+ const accountId = c.get('accountId')
204
+
205
+ const result = await usersService.findAll({ page, limit, accountId })
206
+ return c.json(result, 200)
207
+ }
208
+
209
+ export const handleCreateUser = async (c: Context<Env>) => {
210
+ const body = c.req.valid('json') // Typed as CreateUserSchema
211
+ const actor = c.get('user')
212
+
213
+ const user = await usersService.create(body, actor)
214
+ return c.json(user, 201)
215
+ }
216
+ ```
217
+
218
+ ### 5. Route Module Assembly
219
+
220
+ ```typescript
221
+ // routes/users/index.ts
222
+ import { OpenAPIHono } from '@hono/zod-openapi'
223
+ import { listUsers, createUser } from './routes'
224
+ import { handleListUsers, handleCreateUser } from './handlers'
225
+ import type { Env } from '../../app'
226
+
227
+ const users = new OpenAPIHono<Env>()
228
+
229
+ users.openapi(listUsers, handleListUsers)
230
+ users.openapi(createUser, handleCreateUser)
231
+
232
+ export { users }
233
+ ```
234
+
235
+ ### 6. Main App with Swagger UI
236
+
237
+ ```typescript
238
+ // routes/index.ts
239
+ import { OpenAPIHono } from '@hono/zod-openapi'
240
+ import { swaggerUI } from '@hono/swagger-ui'
241
+ import { users } from './users'
242
+ import { accounts } from './accounts'
243
+ import type { Env } from '../app'
244
+
245
+ const api = new OpenAPIHono<Env>()
246
+
247
+ api.route('/users', users)
248
+ api.route('/accounts', accounts)
249
+
250
+ // OpenAPI JSON endpoint
251
+ api.doc('/doc', {
252
+ openapi: '3.1.0',
253
+ info: { title: 'Etus API', version: '1.0.0' },
254
+ security: [{ Bearer: [] }],
255
+ })
256
+
257
+ // Swagger UI
258
+ api.get('/docs', swaggerUI({ url: '/doc' }))
259
+
260
+ export { api }
261
+ ```
262
+
263
+ ### 7. Drizzle Schema (Preserving Entity Model)
264
+
265
+ ```typescript
266
+ // db/schema/users.ts
267
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
268
+ import { sql } from 'drizzle-orm'
269
+
270
+ export const users = sqliteTable('users', {
271
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
272
+ email: text('email').notNull().unique(),
273
+ name: text('name').notNull(),
274
+ status: text('status', { enum: ['active', 'inactive'] }).default('active'),
275
+ providerIds: text('provider_ids', { mode: 'json' }).$type<string[]>().default([]),
276
+ isSuperAdmin: integer('is_super_admin', { mode: 'boolean' }).default(false),
277
+
278
+ // Soft delete + audit fields
279
+ createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`),
280
+ updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`),
281
+ deletedAt: text('deleted_at'),
282
+ createdById: text('created_by_id').references(() => users.id),
283
+ updatedById: text('updated_by_id').references(() => users.id),
284
+ deletedById: text('deleted_by_id').references(() => users.id),
285
+ })
286
+ ```
287
+
288
+ ```typescript
289
+ // db/schema/accounts.ts
290
+ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
291
+ import { sql } from 'drizzle-orm'
292
+
293
+ export const accounts = sqliteTable('accounts', {
294
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
295
+ name: text('name').notNull(),
296
+ description: text('description'),
297
+ domain: text('domain').unique(),
298
+
299
+ createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`),
300
+ updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`),
301
+ deletedAt: text('deleted_at'),
302
+ })
303
+ ```
304
+
305
+ ```typescript
306
+ // db/schema/user-accounts.ts
307
+ import { sqliteTable, text, primaryKey } from 'drizzle-orm/sqlite-core'
308
+ import { users } from './users'
309
+ import { accounts } from './accounts'
310
+
311
+ export const userAccounts = sqliteTable('user_accounts', {
312
+ userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
313
+ accountId: text('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
314
+ role: text('role', {
315
+ enum: ['ADMIN', 'MANAGER', 'EDITOR', 'AUTHOR', 'VIEWER', 'BILLING', 'ANALYTICS']
316
+ }).notNull(),
317
+ }, (table) => ({
318
+ pk: primaryKey({ columns: [table.userId, table.accountId] }),
319
+ }))
320
+ ```
321
+
322
+ ```typescript
323
+ // db/schema/audit-logs.ts
324
+ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
325
+ import { sql } from 'drizzle-orm'
326
+ import { users } from './users'
327
+ import { accounts } from './accounts'
328
+
329
+ export const auditLogs = sqliteTable('audit_logs', {
330
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
331
+ transactionId: text('transaction_id').notNull(),
332
+ accountId: text('account_id').references(() => accounts.id),
333
+ userId: text('user_id').references(() => users.id),
334
+ entity: text('entity').notNull(),
335
+ entityId: text('entity_id').notNull(),
336
+ action: text('action', { enum: ['INSERT', 'UPDATE', 'DELETE'] }).notNull(),
337
+ changes: text('changes', { mode: 'json' }).$type<Record<string, unknown>>(),
338
+ ipAddress: text('ip_address'),
339
+ userAgent: text('user_agent'),
340
+ timestamp: text('timestamp').default(sql`CURRENT_TIMESTAMP`),
341
+ })
342
+ ```
343
+
344
+ ### 8. Role Guard Middleware
345
+
346
+ ```typescript
347
+ // auth/guards.ts
348
+ import { createMiddleware } from 'hono/factory'
349
+ import type { Env } from '../app'
350
+ import { hasMinimumRole, Role } from './roles'
351
+
352
+ export const requireRole = (minRole: Role) => {
353
+ return createMiddleware<Env>(async (c, next) => {
354
+ const user = c.get('user')
355
+ const userRole = c.get('userRole')
356
+
357
+ if (!user) {
358
+ return c.json({ error: 'Unauthorized' }, 401)
359
+ }
360
+
361
+ // Super-admin bypass
362
+ if (user.isSuperAdmin) {
363
+ return next()
364
+ }
365
+
366
+ if (!userRole || !hasMinimumRole(userRole, minRole)) {
367
+ return c.json({ error: 'Forbidden' }, 403)
368
+ }
369
+
370
+ return next()
371
+ })
372
+ }
373
+
374
+ export const requirePermission = (permission: Permission) => {
375
+ return createMiddleware<Env>(async (c, next) => {
376
+ const user = c.get('user')
377
+ const userRole = c.get('userRole')
378
+
379
+ if (!user) {
380
+ return c.json({ error: 'Unauthorized' }, 401)
381
+ }
382
+
383
+ if (user.isSuperAdmin) {
384
+ return next()
385
+ }
386
+
387
+ if (!userRole || !hasPermission(userRole, permission)) {
388
+ return c.json({ error: 'Forbidden' }, 403)
389
+ }
390
+
391
+ return next()
392
+ })
393
+ }
394
+ ```
395
+
396
+ ### 9. Roles and Permissions
397
+
398
+ ```typescript
399
+ // auth/roles.ts
400
+ export const Role = {
401
+ ADMIN: 'ADMIN',
402
+ MANAGER: 'MANAGER',
403
+ EDITOR: 'EDITOR',
404
+ AUTHOR: 'AUTHOR',
405
+ VIEWER: 'VIEWER',
406
+ BILLING: 'BILLING',
407
+ ANALYTICS: 'ANALYTICS',
408
+ } as const
409
+
410
+ export type Role = typeof Role[keyof typeof Role]
411
+
412
+ const ROLE_HIERARCHY: Record<string, number> = {
413
+ ADMIN: 0,
414
+ MANAGER: 1,
415
+ EDITOR: 2,
416
+ AUTHOR: 3,
417
+ VIEWER: 4,
418
+ BILLING: -1, // Non-hierarchical
419
+ ANALYTICS: -1, // Non-hierarchical
420
+ }
421
+
422
+ export const hasMinimumRole = (userRole: Role, requiredRole: Role): boolean => {
423
+ const userLevel = ROLE_HIERARCHY[userRole]
424
+ const requiredLevel = ROLE_HIERARCHY[requiredRole]
425
+
426
+ // Non-hierarchical roles can only match exactly
427
+ if (userLevel === -1 || requiredLevel === -1) {
428
+ return userRole === requiredRole
429
+ }
430
+
431
+ return userLevel <= requiredLevel
432
+ }
433
+ ```
434
+
435
+ ```typescript
436
+ // auth/permissions.ts
437
+ export const Permission = {
438
+ MANAGE_SYSTEM_SETTINGS: 'MANAGE_SYSTEM_SETTINGS',
439
+ MANAGE_ALL_USERS: 'MANAGE_ALL_USERS',
440
+ MANAGE_TEAM_USERS: 'MANAGE_TEAM_USERS',
441
+ VIEW_ALL_USERS: 'VIEW_ALL_USERS',
442
+ CREATE_CONTENT: 'CREATE_CONTENT',
443
+ EDIT_ALL_CONTENT: 'EDIT_ALL_CONTENT',
444
+ EDIT_OWN_CONTENT: 'EDIT_OWN_CONTENT',
445
+ DELETE_CONTENT: 'DELETE_CONTENT',
446
+ PUBLISH_CONTENT: 'PUBLISH_CONTENT',
447
+ VIEW_CONTENT: 'VIEW_CONTENT',
448
+ VIEW_ANALYTICS: 'VIEW_ANALYTICS',
449
+ MANAGE_BILLING: 'MANAGE_BILLING',
450
+ VIEW_BILLING: 'VIEW_BILLING',
451
+ } as const
452
+
453
+ export type Permission = typeof Permission[keyof typeof Permission]
454
+
455
+ const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
456
+ ADMIN: [
457
+ Permission.MANAGE_SYSTEM_SETTINGS,
458
+ Permission.MANAGE_ALL_USERS,
459
+ Permission.CREATE_CONTENT,
460
+ Permission.EDIT_ALL_CONTENT,
461
+ Permission.DELETE_CONTENT,
462
+ Permission.PUBLISH_CONTENT,
463
+ Permission.VIEW_CONTENT,
464
+ Permission.VIEW_ANALYTICS,
465
+ Permission.MANAGE_BILLING,
466
+ Permission.VIEW_BILLING,
467
+ ],
468
+ MANAGER: [
469
+ Permission.MANAGE_TEAM_USERS,
470
+ Permission.VIEW_ALL_USERS,
471
+ Permission.CREATE_CONTENT,
472
+ Permission.EDIT_ALL_CONTENT,
473
+ Permission.PUBLISH_CONTENT,
474
+ Permission.VIEW_CONTENT,
475
+ Permission.VIEW_ANALYTICS,
476
+ ],
477
+ EDITOR: [
478
+ Permission.CREATE_CONTENT,
479
+ Permission.EDIT_ALL_CONTENT,
480
+ Permission.PUBLISH_CONTENT,
481
+ Permission.VIEW_CONTENT,
482
+ ],
483
+ AUTHOR: [
484
+ Permission.CREATE_CONTENT,
485
+ Permission.EDIT_OWN_CONTENT,
486
+ Permission.VIEW_CONTENT,
487
+ ],
488
+ VIEWER: [
489
+ Permission.VIEW_CONTENT,
490
+ ],
491
+ BILLING: [
492
+ Permission.MANAGE_BILLING,
493
+ Permission.VIEW_BILLING,
494
+ ],
495
+ ANALYTICS: [
496
+ Permission.VIEW_ANALYTICS,
497
+ ],
498
+ }
499
+
500
+ export const hasPermission = (role: Role, permission: Permission): boolean => {
501
+ return ROLE_PERMISSIONS[role]?.includes(permission) ?? false
502
+ }
503
+ ```
504
+
505
+ ### 10. JWT Auth Middleware
506
+
507
+ ```typescript
508
+ // middleware/auth.ts
509
+ import { createMiddleware } from 'hono/factory'
510
+ import { verify } from 'hono/jwt'
511
+ import type { Env } from '../app'
512
+
513
+ export const jwtAuth = createMiddleware<Env>(async (c, next) => {
514
+ const authHeader = c.req.header('Authorization')
515
+
516
+ if (!authHeader?.startsWith('Bearer ')) {
517
+ return c.json({ error: 'Missing or invalid Authorization header' }, 401)
518
+ }
519
+
520
+ const token = authHeader.slice(7)
521
+
522
+ try {
523
+ // For Auth0 with RS256, you'd use JWKS validation
524
+ // This is simplified for HS256
525
+ const payload = await verify(token, process.env.JWT_SECRET!)
526
+
527
+ c.set('user', {
528
+ id: payload.sub as string,
529
+ email: payload.email as string,
530
+ isSuperAdmin: payload.isSuperAdmin as boolean ?? false,
531
+ })
532
+
533
+ return next()
534
+ } catch {
535
+ return c.json({ error: 'Invalid token' }, 401)
536
+ }
537
+ })
538
+ ```
539
+
540
+ ### 11. Account Middleware
541
+
542
+ ```typescript
543
+ // middleware/account.ts
544
+ import { createMiddleware } from 'hono/factory'
545
+ import type { Env } from '../app'
546
+ import { db } from '../db/client'
547
+ import { userAccounts } from '../db/schema'
548
+ import { eq, and } from 'drizzle-orm'
549
+
550
+ export const accountMiddleware = createMiddleware<Env>(async (c, next) => {
551
+ const accountId = c.req.header('account-id')
552
+ const user = c.get('user')
553
+
554
+ if (!accountId) {
555
+ return c.json({ error: 'account-id header required' }, 400)
556
+ }
557
+
558
+ if (!user) {
559
+ return c.json({ error: 'Unauthorized' }, 401)
560
+ }
561
+
562
+ // Super-admin can access any account
563
+ if (user.isSuperAdmin) {
564
+ c.set('accountId', accountId)
565
+ c.set('userRole', 'ADMIN') // Treat as admin for super-admin
566
+ return next()
567
+ }
568
+
569
+ // Check user belongs to account
570
+ const [membership] = await db
571
+ .select()
572
+ .from(userAccounts)
573
+ .where(and(
574
+ eq(userAccounts.userId, user.id),
575
+ eq(userAccounts.accountId, accountId)
576
+ ))
577
+ .limit(1)
578
+
579
+ if (!membership) {
580
+ return c.json({ error: 'Forbidden: No access to this account' }, 403)
581
+ }
582
+
583
+ c.set('accountId', accountId)
584
+ c.set('userRole', membership.role)
585
+
586
+ return next()
587
+ })
588
+ ```
589
+
590
+ ### 12. Entry Point
591
+
592
+ ```typescript
593
+ // index.ts
594
+ import { serve } from '@hono/node-server'
595
+ import { createApp } from './app'
596
+ import { api } from './routes'
597
+ import { jwtAuth } from './middleware/auth'
598
+ import { accountMiddleware } from './middleware/account'
599
+ import { requestContext } from './middleware/request-context'
600
+
601
+ const app = createApp()
602
+
603
+ // Global middleware
604
+ app.use('*', requestContext)
605
+
606
+ // Protected routes
607
+ app.use('/users/*', jwtAuth, accountMiddleware)
608
+ app.use('/accounts/*', jwtAuth, accountMiddleware)
609
+
610
+ // Mount API routes
611
+ app.route('/', api)
612
+
613
+ // Health check (unprotected)
614
+ app.get('/health', (c) => c.json({ status: 'ok' }))
615
+
616
+ const port = parseInt(process.env.PORT ?? '3000')
617
+
618
+ console.log(`Server running on http://localhost:${port}`)
619
+ console.log(`API docs available at http://localhost:${port}/docs`)
620
+
621
+ serve({ fetch: app.fetch, port })
622
+ ```
623
+
624
+ ## What's Preserved from NestJS Boilerplate
625
+
626
+ | Concept | Status | Notes |
627
+ |---------|--------|-------|
628
+ | Entity model (User, Account, UserAccount, AuditLog) | Preserved | Same fields and relationships |
629
+ | Multi-tenant via `account-id` header | Preserved | Same pattern |
630
+ | Role hierarchy + permissions | Preserved | Same logic, lighter implementation |
631
+ | Super-admin bypass | Preserved | Same behavior |
632
+ | Soft deletes | Preserved | Drizzle columns instead of TypeORM |
633
+ | Request context | Preserved | `c.set()`/`c.get()` instead of CLS |
634
+ | JWT auth flow | Preserved | Hono's native JWT helper |
635
+ | OpenAPI docs | Preserved | `@hono/zod-openapi` + Swagger UI |
636
+ | Validation | Preserved | Zod instead of class-validator |
637
+ | Pagination contract | Preserved | Same query params and response shape |
638
+
639
+ ## What's Different
640
+
641
+ | Aspect | NestJS | Hono |
642
+ |--------|--------|------|
643
+ | Framework weight | Heavy (~50+ deps) | Lightweight (zero deps core) |
644
+ | Route definition | Decorators | `createRoute()` function |
645
+ | Dependency injection | Built-in DI container | Direct imports / factory functions |
646
+ | Validation | class-validator | Zod schemas |
647
+ | ORM | TypeORM | Drizzle ORM |
648
+ | Context passing | CLS (implicit) | `c.set()`/`c.get()` (explicit) |
649
+ | Modules | NestJS modules | File-based organization |
650
+ | Guards | Guard classes | Middleware functions |
651
+ | OpenAPI | @nestjs/swagger decorators | `@hono/zod-openapi` schemas |
652
+
653
+ ---
654
+
655
+ # Architecture & System Design Patterns Reference
656
+
657
+ This section documents all architectural patterns from the NestJS boilerplate that MUST be preserved in any Hono implementation. Use this as the definitive reference for system design decisions.
658
+
659
+ ---
660
+
661
+ ## 1. Entity Design Patterns
662
+
663
+ ### 1.1 Base Entity Hierarchy
664
+
665
+ The boilerplate uses a two-tier entity inheritance pattern for consistent temporal and audit fields:
666
+
667
+ ```
668
+ SoftDeleteEntity (base)
669
+ ├── createdAt: Date
670
+ ├── updatedAt: Date
671
+ └── deletedAt: Date | null
672
+
673
+ InteractiveEntity (extends SoftDeleteEntity)
674
+ ├── createdById: string | null
675
+ ├── updatedById: string | null
676
+ └── deletedById: string | null
677
+ ```
678
+
679
+ **Hono/Drizzle Implementation:**
680
+
681
+ ```typescript
682
+ // lib/schema-helpers.ts
683
+ import { text } from 'drizzle-orm/sqlite-core'
684
+ import { sql } from 'drizzle-orm'
685
+
686
+ export const softDeleteFields = {
687
+ createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`).notNull(),
688
+ updatedAt: text('updated_at').default(sql`CURRENT_TIMESTAMP`).notNull(),
689
+ deletedAt: text('deleted_at'),
690
+ }
691
+
692
+ export const interactiveFields = (usersTable: any) => ({
693
+ ...softDeleteFields,
694
+ createdById: text('created_by_id').references(() => usersTable.id),
695
+ updatedById: text('updated_by_id').references(() => usersTable.id),
696
+ deletedById: text('deleted_by_id').references(() => usersTable.id),
697
+ })
698
+ ```
699
+
700
+ **Rules:**
701
+ - ALL entities must include `softDeleteFields` at minimum
702
+ - Entities that track user actions must include `interactiveFields`
703
+ - NEVER use hard deletes; always set `deletedAt`
704
+ - `updatedAt` must be updated on every modification
705
+
706
+ ### 1.2 Soft Delete Pattern
707
+
708
+ **Implementation Requirements:**
709
+
710
+ 1. Every query MUST filter out soft-deleted records:
711
+ ```typescript
712
+ // Always include this in WHERE clauses
713
+ .where(isNull(table.deletedAt))
714
+ ```
715
+
716
+ 2. Delete operations set `deletedAt` instead of removing:
717
+ ```typescript
718
+ await db
719
+ .update(users)
720
+ .set({
721
+ deletedAt: new Date().toISOString(),
722
+ deletedById: actor.id
723
+ })
724
+ .where(eq(users.id, id))
725
+ ```
726
+
727
+ 3. Unique constraints must account for soft deletes:
728
+ ```typescript
729
+ // Use partial unique index (PostgreSQL) or application-level check
730
+ ```
731
+
732
+ ### 1.3 Entity Relationships
733
+
734
+ **User <-> Account (Many-to-Many via UserAccount):**
735
+
736
+ ```
737
+ User (1) --> (M) UserAccount (M) <-- (1) Account
738
+ |
739
+ └── role: Role (per-account role)
740
+ ```
741
+
742
+ **Key Characteristics:**
743
+ - A user can belong to multiple accounts
744
+ - Each user-account pair has its own role
745
+ - Composite primary key on (userId, accountId)
746
+ - CASCADE delete on both foreign keys
747
+
748
+ **Audit Log Relationships:**
749
+
750
+ ```
751
+ AuditLog
752
+ ├── accountId -> Account (which tenant)
753
+ ├── userId -> User (who did it)
754
+ ├── entity: string (what type)
755
+ ├── entityId: string (which record)
756
+ └── changes: JSON (what changed)
757
+ ```
758
+
759
+ ---
760
+
761
+ ## 2. Authentication & Authorization Architecture
762
+
763
+ ### 2.1 Two-Tier Privilege System
764
+
765
+ The system uses TWO independent authorization layers:
766
+
767
+ **Tier 1: Account-Level Roles (via UserAccount table)**
768
+ - Stored per user-account relationship
769
+ - Hierarchical: ADMIN > MANAGER > EDITOR > AUTHOR > VIEWER
770
+ - Non-hierarchical: BILLING, ANALYTICS (special access only)
771
+
772
+ **Tier 2: System Admin (isSuperAdmin flag on User)**
773
+ - Boolean flag on User entity
774
+ - Bypasses ALL role and permission checks
775
+ - Bypasses account-id validation
776
+ - Access to all accounts without UserAccount entries
777
+ - Should be granted to 1-2 platform administrators only
778
+
779
+ ### 2.2 Role Hierarchy
780
+
781
+ ```typescript
782
+ const ROLE_HIERARCHY: Record<Role, number> = {
783
+ ADMIN: 0, // Highest privilege
784
+ MANAGER: 1,
785
+ EDITOR: 2,
786
+ AUTHOR: 3,
787
+ VIEWER: 4, // Lowest privilege
788
+ BILLING: -1, // Non-hierarchical (special)
789
+ ANALYTICS: -1, // Non-hierarchical (special)
790
+ }
791
+ ```
792
+
793
+ **Hierarchy Rules:**
794
+ - Lower number = higher privilege
795
+ - ADMIN (0) has all permissions of MANAGER (1), EDITOR (2), etc.
796
+ - Non-hierarchical roles (-1) can ONLY be granted via `additionalRoles` option
797
+ - Non-hierarchical roles CANNOT access hierarchical endpoints
798
+
799
+ **Check Function:**
800
+
801
+ ```typescript
802
+ function hasMinimumRole(
803
+ userRole: Role,
804
+ minimumRole: Role,
805
+ additionalRoles: Role[] = []
806
+ ): boolean {
807
+ // Check additional roles first (for non-hierarchical access)
808
+ if (additionalRoles.includes(userRole)) {
809
+ return true
810
+ }
811
+
812
+ const userLevel = ROLE_HIERARCHY[userRole]
813
+ const requiredLevel = ROLE_HIERARCHY[minimumRole]
814
+
815
+ // Non-hierarchical roles can't access hierarchical endpoints
816
+ if (userLevel === -1 || requiredLevel === -1) {
817
+ return false
818
+ }
819
+
820
+ // Lower or equal level = higher or equal privilege
821
+ return userLevel <= requiredLevel
822
+ }
823
+ ```
824
+
825
+ ### 2.3 Permission Matrix
826
+
827
+ Permissions are statically mapped to roles:
828
+
829
+ ```typescript
830
+ const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
831
+ ADMIN: [
832
+ 'MANAGE_TENANT_SETTINGS',
833
+ 'MANAGE_ALL_USERS',
834
+ 'MANAGE_TEAM_USERS',
835
+ 'CREATE_CONTENT',
836
+ 'EDIT_OWN_CONTENT',
837
+ 'EDIT_ANY_CONTENT',
838
+ 'PUBLISH_CONTENT',
839
+ 'UNPUBLISH_DELETE_CONTENT',
840
+ 'MANAGE_ASSETS',
841
+ 'MANAGE_CATEGORIES_TAGS',
842
+ 'MANAGE_COMMENTS',
843
+ 'VIEW_ALL_CONTENT',
844
+ 'VIEW_OWN_CONTENT',
845
+ 'VIEW_PUBLISHED_CONTENT',
846
+ 'VIEW_ANALYTICS',
847
+ 'EXPORT_REPORTS',
848
+ ],
849
+ MANAGER: [
850
+ 'MANAGE_TEAM_USERS',
851
+ 'CREATE_CONTENT',
852
+ 'EDIT_ANY_CONTENT',
853
+ 'PUBLISH_CONTENT',
854
+ 'MANAGE_ASSETS',
855
+ 'MANAGE_CATEGORIES_TAGS',
856
+ 'VIEW_ALL_CONTENT',
857
+ 'VIEW_ANALYTICS',
858
+ ],
859
+ EDITOR: [
860
+ 'CREATE_CONTENT',
861
+ 'EDIT_ANY_CONTENT',
862
+ 'PUBLISH_CONTENT',
863
+ 'MANAGE_ASSETS',
864
+ 'VIEW_ALL_CONTENT',
865
+ ],
866
+ AUTHOR: [
867
+ 'CREATE_CONTENT',
868
+ 'EDIT_OWN_CONTENT',
869
+ 'VIEW_OWN_CONTENT',
870
+ 'VIEW_PUBLISHED_CONTENT',
871
+ ],
872
+ VIEWER: [
873
+ 'VIEW_PUBLISHED_CONTENT',
874
+ ],
875
+ BILLING: [
876
+ 'MANAGE_BILLING',
877
+ 'VIEW_BILLING',
878
+ ],
879
+ ANALYTICS: [
880
+ 'VIEW_ANALYTICS',
881
+ 'EXPORT_REPORTS',
882
+ ],
883
+ }
884
+ ```
885
+
886
+ **Helper Functions:**
887
+
888
+ ```typescript
889
+ function hasPermission(role: Role, permission: Permission): boolean {
890
+ return ROLE_PERMISSIONS[role]?.includes(permission) ?? false
891
+ }
892
+
893
+ function hasAnyPermission(role: Role, permissions: Permission[]): boolean {
894
+ return permissions.some(p => hasPermission(role, p))
895
+ }
896
+
897
+ function hasAllPermissions(role: Role, permissions: Permission[]): boolean {
898
+ return permissions.every(p => hasPermission(role, p))
899
+ }
900
+ ```
901
+
902
+ ### 2.4 JWT Validation Flow
903
+
904
+ ```
905
+ Request with Authorization: Bearer <token>
906
+ |
907
+ v
908
+ ┌─────────────────────────────────────┐
909
+ │ 1. Extract token from header │
910
+ └─────────────────────────────────────┘
911
+ |
912
+ v
913
+ ┌─────────────────────────────────────┐
914
+ │ 2. Validate signature via JWKS │
915
+ │ (RS256 with Auth0) │
916
+ └─────────────────────────────────────┘
917
+ |
918
+ v
919
+ ┌─────────────────────────────────────┐
920
+ │ 3. Verify claims: │
921
+ │ - iss (issuer) │
922
+ │ - aud (audience) │
923
+ │ - exp (expiration) │
924
+ └─────────────────────────────────────┘
925
+ |
926
+ v
927
+ ┌─────────────────────────────────────┐
928
+ │ 4. Extract user info from payload: │
929
+ │ - sub (provider ID) │
930
+ │ - email │
931
+ │ - custom claims (roles, perms) │
932
+ └─────────────────────────────────────┘
933
+ |
934
+ v
935
+ ┌─────────────────────────────────────┐
936
+ │ 5. Fetch user from DB by providerID │
937
+ │ (includes userAccounts) │
938
+ └─────────────────────────────────────┘
939
+ |
940
+ v
941
+ ┌─────────────────────────────────────┐
942
+ │ 6. Store user in request context │
943
+ └─────────────────────────────────────┘
944
+ ```
945
+
946
+ ### 2.5 Super-Admin Bypass Mechanism
947
+
948
+ **When isSuperAdmin is true:**
949
+ 1. Skip account-id header validation
950
+ 2. Skip role hierarchy checks
951
+ 3. Skip permission checks
952
+ 4. Grant access to all accounts
953
+ 5. Treat as ADMIN for audit purposes
954
+ 6. Log with 'SYSTEM_ADMIN' marker
955
+
956
+ **Implementation:**
957
+
958
+ ```typescript
959
+ // In account middleware
960
+ if (user.isSuperAdmin) {
961
+ c.set('accountId', accountId)
962
+ c.set('userRole', 'ADMIN') // Treat as admin
963
+ c.set('isSystemAdminAccess', true) // Mark for audit
964
+ return next()
965
+ }
966
+
967
+ // In role guard
968
+ if (user.isSuperAdmin) {
969
+ return next() // Bypass all checks
970
+ }
971
+ ```
972
+
973
+ ### 2.6 Middleware Execution Order
974
+
975
+ ```
976
+ Request
977
+ |
978
+ v
979
+ ┌─────────────────────────────────────┐
980
+ │ 1. requestContext middleware │
981
+ │ - Generate transactionId (UUIDv7)│
982
+ │ - Capture IP, userAgent │
983
+ └─────────────────────────────────────┘
984
+ |
985
+ v
986
+ ┌─────────────────────────────────────┐
987
+ │ 2. jwtAuth middleware │
988
+ │ - Validate JWT │
989
+ │ - Fetch user from DB │
990
+ │ - Set user in context │
991
+ └─────────────────────────────────────┘
992
+ |
993
+ v
994
+ ┌─────────────────────────────────────┐
995
+ │ 3. accountMiddleware │
996
+ │ - Validate account-id header │
997
+ │ - Check user-account membership │
998
+ │ - Set accountId, userRole │
999
+ └─────────────────────────────────────┘
1000
+ |
1001
+ v
1002
+ ┌─────────────────────────────────────┐
1003
+ │ 4. requireRole() middleware │
1004
+ │ - Check role hierarchy │
1005
+ │ - Return 403 if insufficient │
1006
+ └─────────────────────────────────────┘
1007
+ |
1008
+ v
1009
+ ┌─────────────────────────────────────┐
1010
+ │ 5. Route Handler │
1011
+ │ - Business logic │
1012
+ └─────────────────────────────────────┘
1013
+ |
1014
+ v
1015
+ ┌─────────────────────────────────────┐
1016
+ │ 6. Audit logging (on DB operations) │
1017
+ │ - Capture changes with context │
1018
+ └─────────────────────────────────────┘
1019
+ ```
1020
+
1021
+ ---
1022
+
1023
+ ## 3. Multi-Tenancy Implementation
1024
+
1025
+ ### 3.1 Account Isolation Strategy
1026
+
1027
+ **Core Principle:** Every database operation MUST be scoped to an accountId.
1028
+
1029
+ **Isolation Layers:**
1030
+
1031
+ 1. **Header Level:** `account-id` header required on all protected endpoints
1032
+ 2. **Middleware Level:** Validate user has access to requested account
1033
+ 3. **Query Level:** All queries filtered by accountId
1034
+ 4. **Audit Level:** All changes logged with account context
1035
+
1036
+ ### 3.2 Account-ID Header Flow
1037
+
1038
+ ```
1039
+ Client Request
1040
+ |
1041
+ ├── Header: account-id: <uuid>
1042
+ |
1043
+ v
1044
+ ┌─────────────────────────────────────┐
1045
+ │ accountMiddleware │
1046
+ │ │
1047
+ │ 1. Extract account-id header │
1048
+ │ 2. If missing -> 400 Bad Request │
1049
+ │ 3. If super-admin -> allow any │
1050
+ │ 4. Query userAccounts table │
1051
+ │ 5. If no membership -> 403 Forbidden│
1052
+ │ 6. Set accountId in context │
1053
+ │ 7. Set userRole from membership │
1054
+ └─────────────────────────────────────┘
1055
+ ```
1056
+
1057
+ ### 3.3 Query Filtering Pattern
1058
+
1059
+ **Every service method must:**
1060
+
1061
+ 1. Get accountId from context
1062
+ 2. Include accountId in WHERE clause
1063
+ 3. Filter out soft-deleted records
1064
+
1065
+ **Example:**
1066
+
1067
+ ```typescript
1068
+ async findAll(pagination: PaginationQuery) {
1069
+ const accountId = c.get('accountId')
1070
+ const user = c.get('user')
1071
+
1072
+ // Super-admin sees all
1073
+ if (user.isSuperAdmin) {
1074
+ return db
1075
+ .select()
1076
+ .from(entities)
1077
+ .where(isNull(entities.deletedAt))
1078
+ .limit(pagination.limit)
1079
+ .offset((pagination.page - 1) * pagination.limit)
1080
+ }
1081
+
1082
+ // Regular users filtered by account
1083
+ return db
1084
+ .select()
1085
+ .from(entities)
1086
+ .where(and(
1087
+ eq(entities.accountId, accountId),
1088
+ isNull(entities.deletedAt)
1089
+ ))
1090
+ .limit(pagination.limit)
1091
+ .offset((pagination.page - 1) * pagination.limit)
1092
+ }
1093
+ ```
1094
+
1095
+ ### 3.4 User-Account Relationship
1096
+
1097
+ **Schema:**
1098
+
1099
+ ```typescript
1100
+ userAccounts = sqliteTable('user_accounts', {
1101
+ userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
1102
+ accountId: text('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }),
1103
+ role: text('role', { enum: ROLES }).notNull().default('VIEWER'),
1104
+ }, (table) => ({
1105
+ pk: primaryKey({ columns: [table.userId, table.accountId] }),
1106
+ }))
1107
+ ```
1108
+
1109
+ **Key Points:**
1110
+ - Composite primary key prevents duplicate assignments
1111
+ - Role is stored PER relationship (user can be ADMIN in Account A, VIEWER in Account B)
1112
+ - CASCADE delete on both sides
1113
+ - No soft delete on junction table (delete means removal of access)
1114
+
1115
+ ---
1116
+
1117
+ ## 4. Audit System Design
1118
+
1119
+ ### 4.1 Audit Log Structure
1120
+
1121
+ ```typescript
1122
+ auditLogs = sqliteTable('audit_logs', {
1123
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
1124
+ transactionId: text('transaction_id').notNull(), // Groups related changes
1125
+ accountId: text('account_id').references(() => accounts.id),
1126
+ userId: text('user_id').references(() => users.id),
1127
+ entity: text('entity').notNull(), // e.g., "User", "Account"
1128
+ entityId: text('entity_id').notNull(), // Primary key of affected record
1129
+ action: text('action', { enum: ['INSERT', 'UPDATE', 'DELETE'] }).notNull(),
1130
+ changes: text('changes', { mode: 'json' }).$type<AuditChanges>(),
1131
+ ipAddress: text('ip_address'),
1132
+ userAgent: text('user_agent'),
1133
+ timestamp: text('timestamp').default(sql`CURRENT_TIMESTAMP`).notNull(),
1134
+ })
1135
+ ```
1136
+
1137
+ ### 4.2 What Gets Captured
1138
+
1139
+ | Field | Source | Description |
1140
+ |-------|--------|-------------|
1141
+ | transactionId | UUIDv7 from context | Groups all changes in one request |
1142
+ | accountId | Context | Which tenant was affected |
1143
+ | userId | Context | Who made the change |
1144
+ | entity | Table name | What type of record |
1145
+ | entityId | Record PK | Which specific record |
1146
+ | action | Operation type | INSERT, UPDATE, or DELETE |
1147
+ | changes | JSON diff | What changed (for UPDATE) or full record (for INSERT/DELETE) |
1148
+ | ipAddress | Request | Client IP for forensics |
1149
+ | userAgent | Request | Client info for forensics |
1150
+ | timestamp | Server | When it happened |
1151
+
1152
+ ### 4.3 Audit Logging Implementation
1153
+
1154
+ Since Hono/Drizzle doesn't have TypeORM-style subscribers, implement audit logging via service layer:
1155
+
1156
+ ```typescript
1157
+ // lib/audit.ts
1158
+ type AuditAction = 'INSERT' | 'UPDATE' | 'DELETE'
1159
+
1160
+ interface AuditContext {
1161
+ transactionId: string
1162
+ accountId: string
1163
+ userId: string
1164
+ ipAddress: string
1165
+ userAgent: string
1166
+ }
1167
+
1168
+ async function logAudit(
1169
+ ctx: AuditContext,
1170
+ entity: string,
1171
+ entityId: string,
1172
+ action: AuditAction,
1173
+ changes: Record<string, unknown>
1174
+ ) {
1175
+ await db.insert(auditLogs).values({
1176
+ transactionId: ctx.transactionId,
1177
+ accountId: ctx.accountId,
1178
+ userId: ctx.userId,
1179
+ entity,
1180
+ entityId,
1181
+ action,
1182
+ changes,
1183
+ ipAddress: ctx.ipAddress,
1184
+ userAgent: ctx.userAgent,
1185
+ })
1186
+ }
1187
+
1188
+ // Usage in service
1189
+ async function createUser(data: CreateUserInput, c: Context<Env>) {
1190
+ const [user] = await db.insert(users).values(data).returning()
1191
+
1192
+ await logAudit(
1193
+ getAuditContext(c),
1194
+ 'User',
1195
+ user.id,
1196
+ 'INSERT',
1197
+ user
1198
+ )
1199
+
1200
+ return user
1201
+ }
1202
+ ```
1203
+
1204
+ ### 4.4 Transaction ID Propagation
1205
+
1206
+ **UUIDv7 for sortability:**
1207
+
1208
+ ```typescript
1209
+ // middleware/request-context.ts
1210
+ import { uuidv7 } from 'uuidv7'
1211
+
1212
+ export const requestContext = createMiddleware<Env>(async (c, next) => {
1213
+ c.set('transactionId', uuidv7())
1214
+ c.set('ip', c.req.header('x-forwarded-for') || 'unknown')
1215
+ c.set('userAgent', c.req.header('user-agent') || 'unknown')
1216
+ await next()
1217
+ })
1218
+ ```
1219
+
1220
+ **Benefits of UUIDv7:**
1221
+ - Time-ordered (sortable)
1222
+ - Unique across requests
1223
+ - Can group related audit entries
1224
+ - Efficient for time-range queries
1225
+
1226
+ ---
1227
+
1228
+ ## 5. Request Context Management
1229
+
1230
+ ### 5.1 Context Variables
1231
+
1232
+ The following must be available throughout the request lifecycle:
1233
+
1234
+ ```typescript
1235
+ type Env = {
1236
+ Variables: {
1237
+ // Request tracking
1238
+ transactionId: string // UUIDv7 for audit correlation
1239
+ ip: string // Client IP
1240
+ userAgent: string // Client user agent
1241
+
1242
+ // Authentication
1243
+ user: User | null // Full user entity from DB
1244
+
1245
+ // Authorization
1246
+ accountId: string // Current account context
1247
+ userRole: Role | null // User's role in current account
1248
+ isSystemAdminAccess: boolean // Super-admin flag
1249
+ }
1250
+ }
1251
+ ```
1252
+
1253
+ ### 5.2 Context Flow
1254
+
1255
+ ```
1256
+ ┌─────────────────────────────────────────────────────────────┐
1257
+ │ requestContext middleware │
1258
+ │ Sets: transactionId, ip, userAgent │
1259
+ └─────────────────────────────────────────────────────────────┘
1260
+ |
1261
+ v
1262
+ ┌─────────────────────────────────────────────────────────────┐
1263
+ │ jwtAuth middleware │
1264
+ │ Sets: user (fetched from DB with userAccounts) │
1265
+ └─────────────────────────────────────────────────────────────┘
1266
+ |
1267
+ v
1268
+ ┌─────────────────────────────────────────────────────────────┐
1269
+ │ accountMiddleware │
1270
+ │ Sets: accountId, userRole, isSystemAdminAccess │
1271
+ └─────────────────────────────────────────────────────────────┘
1272
+ |
1273
+ v
1274
+ ┌─────────────────────────────────────────────────────────────┐
1275
+ │ Route Handler / Services │
1276
+ │ Reads: All context variables via c.get() │
1277
+ └─────────────────────────────────────────────────────────────┘
1278
+ |
1279
+ v
1280
+ ┌─────────────────────────────────────────────────────────────┐
1281
+ │ Audit Logging │
1282
+ │ Uses: transactionId, accountId, user.id, ip, userAgent │
1283
+ └─────────────────────────────────────────────────────────────┘
1284
+ ```
1285
+
1286
+ ### 5.3 Accessing Context in Handlers
1287
+
1288
+ ```typescript
1289
+ // In route handlers
1290
+ export const handleCreateUser = async (c: Context<Env>) => {
1291
+ const body = c.req.valid('json')
1292
+
1293
+ // Access all context
1294
+ const user = c.get('user') // Actor
1295
+ const accountId = c.get('accountId') // Tenant
1296
+ const transactionId = c.get('transactionId') // For audit
1297
+
1298
+ // Pass to service
1299
+ const result = await usersService.create(body, {
1300
+ actor: user,
1301
+ accountId,
1302
+ transactionId,
1303
+ ip: c.get('ip'),
1304
+ userAgent: c.get('userAgent'),
1305
+ })
1306
+
1307
+ return c.json(result, 201)
1308
+ }
1309
+ ```
1310
+
1311
+ ---
1312
+
1313
+ ## 6. API Design Patterns
1314
+
1315
+ ### 6.1 Standard Endpoint Structure
1316
+
1317
+ Every resource should follow this pattern:
1318
+
1319
+ | Method | Path | Role | Description |
1320
+ |--------|------|------|-------------|
1321
+ | GET | /{resource} | MANAGER+ | List with pagination |
1322
+ | GET | /{resource}/:id | MANAGER+ | Get single by ID |
1323
+ | POST | /{resource} | ADMIN+ | Create new |
1324
+ | PUT | /{resource}/:id | ADMIN+ | Update existing |
1325
+ | DELETE | /{resource}/:id | ADMIN+ | Soft delete |
1326
+
1327
+ ### 6.2 Pagination Contract
1328
+
1329
+ **Request Query Parameters:**
1330
+
1331
+ ```typescript
1332
+ const PaginationQuerySchema = z.object({
1333
+ page: z.coerce.number().min(1).default(1),
1334
+ limit: z.coerce.number().min(1).max(100).default(50),
1335
+ sortBy: z.string().optional(),
1336
+ sortOrder: z.enum(['ASC', 'DESC']).default('DESC'),
1337
+ query: z.string().optional(), // Search term
1338
+ })
1339
+ ```
1340
+
1341
+ **Response Shape:**
1342
+
1343
+ ```typescript
1344
+ const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
1345
+ z.object({
1346
+ data: z.array(itemSchema),
1347
+ meta: z.object({
1348
+ currentPage: z.number(),
1349
+ limit: z.number(),
1350
+ totalItems: z.number(),
1351
+ totalPages: z.number(),
1352
+ hasPreviousPage: z.boolean(),
1353
+ hasNextPage: z.boolean(),
1354
+ }),
1355
+ })
1356
+ ```
1357
+
1358
+ **Helper Function:**
1359
+
1360
+ ```typescript
1361
+ function createPaginationMeta(
1362
+ totalItems: number,
1363
+ page: number,
1364
+ limit: number
1365
+ ): PaginationMeta {
1366
+ const totalPages = Math.ceil(totalItems / limit)
1367
+ return {
1368
+ currentPage: page,
1369
+ limit,
1370
+ totalItems,
1371
+ totalPages,
1372
+ hasPreviousPage: page > 1,
1373
+ hasNextPage: page < totalPages,
1374
+ }
1375
+ }
1376
+ ```
1377
+
1378
+ ### 6.3 Error Response Format
1379
+
1380
+ **Standard Error Shape:**
1381
+
1382
+ ```typescript
1383
+ const ErrorResponseSchema = z.object({
1384
+ error: z.string(),
1385
+ message: z.string().optional(),
1386
+ details: z.unknown().optional(),
1387
+ statusCode: z.number(),
1388
+ })
1389
+ ```
1390
+
1391
+ **HTTP Status Codes:**
1392
+
1393
+ | Code | Usage |
1394
+ |------|-------|
1395
+ | 400 | Validation errors, malformed request |
1396
+ | 401 | Missing or invalid JWT |
1397
+ | 403 | Valid JWT but insufficient role/permission |
1398
+ | 404 | Resource not found |
1399
+ | 409 | Conflict (e.g., duplicate email) |
1400
+ | 500 | Unexpected server error |
1401
+
1402
+ **Error Handler:**
1403
+
1404
+ ```typescript
1405
+ app.onError((err, c) => {
1406
+ if (err instanceof HTTPException) {
1407
+ return c.json({
1408
+ error: err.message,
1409
+ statusCode: err.status,
1410
+ }, err.status)
1411
+ }
1412
+
1413
+ console.error(err)
1414
+ return c.json({
1415
+ error: 'Internal Server Error',
1416
+ statusCode: 500,
1417
+ }, 500)
1418
+ })
1419
+ ```
1420
+
1421
+ ### 6.4 OpenAPI Documentation
1422
+
1423
+ Every route must include:
1424
+
1425
+ ```typescript
1426
+ const createUserRoute = createRoute({
1427
+ method: 'post',
1428
+ path: '/users',
1429
+ tags: ['Users'],
1430
+ summary: 'Create a new user',
1431
+ description: 'Creates a new user and sends Auth0 invitation',
1432
+ security: [{ Bearer: [] }],
1433
+ request: {
1434
+ headers: z.object({
1435
+ 'account-id': z.string().uuid(),
1436
+ }),
1437
+ body: {
1438
+ content: {
1439
+ 'application/json': { schema: CreateUserSchema },
1440
+ },
1441
+ },
1442
+ },
1443
+ responses: {
1444
+ 201: {
1445
+ description: 'User created successfully',
1446
+ content: { 'application/json': { schema: UserSchema } },
1447
+ },
1448
+ 400: {
1449
+ description: 'Validation error',
1450
+ content: { 'application/json': { schema: ErrorResponseSchema } },
1451
+ },
1452
+ 401: { description: 'Unauthorized' },
1453
+ 403: { description: 'Forbidden - insufficient role' },
1454
+ 409: { description: 'User already exists' },
1455
+ },
1456
+ })
1457
+ ```
1458
+
1459
+ ---
1460
+
1461
+ ## 7. Service Layer Patterns
1462
+
1463
+ ### 7.1 Service Structure
1464
+
1465
+ Each service should:
1466
+ - Accept context as parameter (not read from global)
1467
+ - Handle business logic
1468
+ - Call other services when needed
1469
+ - Trigger audit logging
1470
+
1471
+ ```typescript
1472
+ // services/users.ts
1473
+ export function createUsersService(db: Database) {
1474
+ return {
1475
+ async findAll(ctx: ServiceContext, pagination: PaginationQuery) {
1476
+ // Implementation
1477
+ },
1478
+
1479
+ async findById(ctx: ServiceContext, id: string) {
1480
+ // Implementation
1481
+ },
1482
+
1483
+ async create(ctx: ServiceContext, data: CreateUserInput) {
1484
+ // Implementation with audit logging
1485
+ },
1486
+
1487
+ async update(ctx: ServiceContext, id: string, data: UpdateUserInput) {
1488
+ // Implementation with audit logging
1489
+ },
1490
+
1491
+ async delete(ctx: ServiceContext, id: string) {
1492
+ // Soft delete with audit logging
1493
+ },
1494
+ }
1495
+ }
1496
+ ```
1497
+
1498
+ ### 7.2 Service Context
1499
+
1500
+ ```typescript
1501
+ interface ServiceContext {
1502
+ accountId: string
1503
+ user: User
1504
+ transactionId: string
1505
+ ip: string
1506
+ userAgent: string
1507
+ }
1508
+
1509
+ function getServiceContext(c: Context<Env>): ServiceContext {
1510
+ return {
1511
+ accountId: c.get('accountId'),
1512
+ user: c.get('user')!,
1513
+ transactionId: c.get('transactionId'),
1514
+ ip: c.get('ip'),
1515
+ userAgent: c.get('userAgent'),
1516
+ }
1517
+ }
1518
+ ```
1519
+
1520
+ ### 7.3 External Service Integration
1521
+
1522
+ Abstract external services behind interfaces:
1523
+
1524
+ ```typescript
1525
+ // services/identity-provider.ts
1526
+ interface IdentityProvider {
1527
+ sendInvitation(email: string, name: string): Promise<{ userId: string }>
1528
+ getUserByEmail(email: string): Promise<ExternalUser | null>
1529
+ deleteUser(userId: string): Promise<void>
1530
+ }
1531
+
1532
+ // Implementation for Auth0
1533
+ export function createAuth0Provider(config: Auth0Config): IdentityProvider {
1534
+ return {
1535
+ async sendInvitation(email, name) {
1536
+ // Auth0 Management API call
1537
+ },
1538
+ async getUserByEmail(email) {
1539
+ // Auth0 Management API call
1540
+ },
1541
+ async deleteUser(userId) {
1542
+ // Auth0 Management API call
1543
+ },
1544
+ }
1545
+ }
1546
+ ```
1547
+
1548
+ ---
1549
+
1550
+ ## 8. Database Patterns
1551
+
1552
+ ### 8.1 Migration Strategy
1553
+
1554
+ **Rules:**
1555
+ - NEVER use auto-sync/synchronize in production
1556
+ - Every schema change requires a migration
1557
+ - Migrations must be reversible (up and down)
1558
+ - Test migrations on a copy of production data
1559
+
1560
+ **Drizzle Migration Commands:**
1561
+
1562
+ ```bash
1563
+ # Generate migration from schema changes
1564
+ pnpm drizzle-kit generate
1565
+
1566
+ # Apply migrations
1567
+ pnpm drizzle-kit migrate
1568
+
1569
+ # Drop all and recreate (dev only!)
1570
+ pnpm drizzle-kit push
1571
+ ```
1572
+
1573
+ ### 8.2 Seed Data
1574
+
1575
+ **Structure:**
1576
+
1577
+ ```typescript
1578
+ // db/seed.ts
1579
+ async function seed() {
1580
+ // 1. Create default account
1581
+ const [account] = await db.insert(accounts).values({
1582
+ name: 'Default',
1583
+ domain: 'default.local',
1584
+ }).returning()
1585
+
1586
+ // 2. Create super admin user
1587
+ const [superAdmin] = await db.insert(users).values({
1588
+ email: 'admin@example.com',
1589
+ name: 'Super Admin',
1590
+ isSuperAdmin: true,
1591
+ status: 'active',
1592
+ }).returning()
1593
+
1594
+ // 3. Create test users with different roles
1595
+ const testUsers = [
1596
+ { email: 'manager@example.com', role: 'MANAGER' },
1597
+ { email: 'editor@example.com', role: 'EDITOR' },
1598
+ { email: 'viewer@example.com', role: 'VIEWER' },
1599
+ ]
1600
+
1601
+ for (const { email, role } of testUsers) {
1602
+ const [user] = await db.insert(users).values({
1603
+ email,
1604
+ name: email.split('@')[0],
1605
+ status: 'active',
1606
+ }).returning()
1607
+
1608
+ await db.insert(userAccounts).values({
1609
+ userId: user.id,
1610
+ accountId: account.id,
1611
+ role,
1612
+ })
1613
+ }
1614
+ }
1615
+ ```
1616
+
1617
+ ### 8.3 Query Patterns
1618
+
1619
+ **Always Include:**
1620
+
1621
+ ```typescript
1622
+ // 1. Account filtering (for multi-tenant queries)
1623
+ .where(eq(table.accountId, ctx.accountId))
1624
+
1625
+ // 2. Soft delete filtering
1626
+ .where(isNull(table.deletedAt))
1627
+
1628
+ // 3. Combined
1629
+ .where(and(
1630
+ eq(table.accountId, ctx.accountId),
1631
+ isNull(table.deletedAt)
1632
+ ))
1633
+ ```
1634
+
1635
+ **Pagination:**
1636
+
1637
+ ```typescript
1638
+ async function findWithPagination<T>(
1639
+ query: SelectQueryBuilder<T>,
1640
+ page: number,
1641
+ limit: number
1642
+ ) {
1643
+ const offset = (page - 1) * limit
1644
+
1645
+ const [data, countResult] = await Promise.all([
1646
+ query.limit(limit).offset(offset),
1647
+ db.select({ count: sql<number>`count(*)` }).from(query.as('subquery')),
1648
+ ])
1649
+
1650
+ return {
1651
+ data,
1652
+ meta: createPaginationMeta(countResult[0].count, page, limit),
1653
+ }
1654
+ }
1655
+ ```
1656
+
1657
+ ---
1658
+
1659
+ ## 9. Security Patterns Summary
1660
+
1661
+ ### 9.1 Defense in Depth
1662
+
1663
+ | Layer | Protection | Implementation |
1664
+ |-------|------------|----------------|
1665
+ | Network | HTTPS only | Infrastructure |
1666
+ | Header | account-id required | accountMiddleware |
1667
+ | Token | JWT validation | jwtAuth middleware |
1668
+ | Membership | User-account check | accountMiddleware |
1669
+ | Role | Hierarchy check | requireRole middleware |
1670
+ | Permission | Granular check | requirePermission middleware |
1671
+ | Query | Account filtering | Service layer |
1672
+ | Audit | Change logging | Audit service |
1673
+
1674
+ ### 9.2 Input Validation
1675
+
1676
+ - ALL input validated via Zod schemas
1677
+ - Schemas defined once, used for validation AND OpenAPI docs
1678
+ - Whitelist approach (reject unknown fields)
1679
+ - Type coercion handled by Zod
1680
+
1681
+ ### 9.3 SQL Injection Prevention
1682
+
1683
+ - NEVER use string concatenation for queries
1684
+ - Always use Drizzle's parameterized queries
1685
+ - Use `sql` template literal for raw SQL
1686
+
1687
+ ```typescript
1688
+ // GOOD
1689
+ db.select().from(users).where(eq(users.email, email))
1690
+
1691
+ // GOOD (when raw SQL needed)
1692
+ db.execute(sql`SELECT * FROM users WHERE email = ${email}`)
1693
+
1694
+ // BAD - Never do this
1695
+ db.execute(`SELECT * FROM users WHERE email = '${email}'`)
1696
+ ```
1697
+
1698
+ ---
1699
+
1700
+ ## 10. Configuration & Environment
1701
+
1702
+ ### 10.1 Required Environment Variables
1703
+
1704
+ ```typescript
1705
+ // env.ts
1706
+ import { z } from 'zod'
1707
+
1708
+ const envSchema = z.object({
1709
+ // Server
1710
+ PORT: z.coerce.number().default(3000),
1711
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
1712
+
1713
+ // Database
1714
+ DATABASE_URL: z.string().default('db.sqlite'),
1715
+
1716
+ // Auth0
1717
+ JWKS_URI: z.string().url(),
1718
+ IDP_ISSUER: z.string().url(),
1719
+ IDP_AUDIENCE: z.string(),
1720
+ AUTH0_DOMAIN: z.string(),
1721
+ AUTH0_CLIENT_ID: z.string(),
1722
+ AUTH0_CLIENT_SECRET: z.string(),
1723
+ AUTH0_ROLES_CLAIM: z.string().default('https://app.example.com/roles'),
1724
+
1725
+ // Optional
1726
+ CORS_ORIGINS: z.string().default('*'),
1727
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
1728
+ })
1729
+
1730
+ export const env = envSchema.parse(process.env)
1731
+ ```
1732
+
1733
+ ### 10.2 Startup Validation
1734
+
1735
+ ```typescript
1736
+ // index.ts
1737
+ import { env } from './env' // Throws if invalid
1738
+
1739
+ // Only reached if env is valid
1740
+ console.log(`Starting server on port ${env.PORT}...`)
1741
+ ```
1742
+
1743
+ ---
1744
+
1745
+ ## 11. Quick Reference: Pattern Mapping
1746
+
1747
+ | NestJS Pattern | Hono Equivalent |
1748
+ |----------------|-----------------|
1749
+ | `@Controller()` | `new OpenAPIHono()` sub-app |
1750
+ | `@Get()`, `@Post()`, etc. | `createRoute()` + `app.openapi()` |
1751
+ | `@UseGuards(JwtAuthGuard)` | `jwtAuth` middleware |
1752
+ | `@MinRole(Role.ADMIN)` | `requireRole('ADMIN')` middleware |
1753
+ | `@Body() dto: CreateDto` | `c.req.valid('json')` with Zod schema |
1754
+ | `@Param('id')` | `c.req.valid('param').id` |
1755
+ | `@Query() query: PaginationDto` | `c.req.valid('query')` |
1756
+ | `ClsService.get('user')` | `c.get('user')` |
1757
+ | `@InjectRepository(User)` | Direct import of `db` client |
1758
+ | TypeORM `Repository` | Drizzle query builder |
1759
+ | `class-validator` decorators | Zod schema methods |
1760
+ | `@ApiProperty()` | `.openapi({ example: ... })` |
1761
+ | `@ApiTags('users')` | `tags: ['Users']` in route |
1762
+ | TypeORM subscriber | Manual audit logging in service |
1763
+
1764
+ ---
1765
+
1766
+ ## References
1767
+
1768
+ - [Hono Documentation](https://hono.dev/docs/)
1769
+ - [Zod OpenAPI Example](https://hono.dev/examples/zod-openapi)
1770
+ - [@hono/zod-openapi](https://www.npmjs.com/package/@hono/zod-openapi)
1771
+ - [Hono Best Practices](https://hono.dev/docs/guides/best-practices)
1772
+ - [Hono JWT Helper](https://hono.dev/docs/helpers/jwt)
1773
+ - [Hono Node.js Guide](https://hono.dev/docs/getting-started/nodejs)
1774
+ - [Drizzle ORM](https://orm.drizzle.team/)