create-fullstack-scaffold 0.1.0

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 (530) hide show
  1. package/README.md +154 -0
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +439 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/package.json +171 -0
  6. package/template/.agents/skills/writing-hookify-rules/SKILL.md +408 -0
  7. package/template/.ai-context.md +368 -0
  8. package/template/.claude/hookify.block-dangerous-commands.local.md +41 -0
  9. package/template/.claude/hookify.check-api-types.local.md +111 -0
  10. package/template/.claude/hookify.check-duplicate-types.local.md +287 -0
  11. package/template/.claude/hookify.check-test-assertions.local.md +93 -0
  12. package/template/.claude/hookify.remind-add-tests.local.md +209 -0
  13. package/template/.claude/hookify.remind-db-migration.local.md +58 -0
  14. package/template/.claude/hookify.remind-run-tests.local.md +52 -0
  15. package/template/.claude/hookify.warn-any-type.local.md +203 -0
  16. package/template/.claude/hookify.warn-console-log.local.md +73 -0
  17. package/template/.claude/rules/00-project-config.md +147 -0
  18. package/template/.claude/rules/01-file-types.md +127 -0
  19. package/template/.claude/rules/02-git-workflow.md +167 -0
  20. package/template/.claude/rules/10-api-type-inference.md +555 -0
  21. package/template/.claude/rules/20-server-api.md +349 -0
  22. package/template/.claude/rules/21-server-entrypoint.md +257 -0
  23. package/template/.claude/rules/30-client-components.md +314 -0
  24. package/template/.claude/rules/31-client-services.md +768 -0
  25. package/template/.claude/rules/32-client-state-zustand.md +324 -0
  26. package/template/.claude/rules/33-client-app-entry.md +276 -0
  27. package/template/.claude/rules/34-client-admin-module.md +445 -0
  28. package/template/.claude/rules/35-cli-module.md +259 -0
  29. package/template/.claude/rules/40-admin-module.md +495 -0
  30. package/template/.claude/rules/40-shared-types.md +450 -0
  31. package/template/.claude/rules/50-websocket.md +245 -0
  32. package/template/.claude/rules/51-sse.md +372 -0
  33. package/template/.claude/rules/52-cloudflare-realtime.md +384 -0
  34. package/template/.claude/rules/60-testing-standards.md +245 -0
  35. package/template/.claude/rules/61-hono-testing.md +200 -0
  36. package/template/.claude/scripts/README.md +187 -0
  37. package/template/.claude/scripts/check-duplicate-types.ts +188 -0
  38. package/template/.claude/scripts/post-edit-check-incremental.sh +61 -0
  39. package/template/.claude/scripts/post-edit-check.sh +22 -0
  40. package/template/.claude/scripts/pre-tool-use-check.sh +86 -0
  41. package/template/.claude/scripts/test-pre-tool-use.sh +73 -0
  42. package/template/.claude/scripts/validate-git-changes.sh +38 -0
  43. package/template/.claude/settings.json +32 -0
  44. package/template/.claude/skills/writing-hookify-rules/SKILL.md +408 -0
  45. package/template/.dev-context-compact.md +54 -0
  46. package/template/.dev-context.md +621 -0
  47. package/template/.dev-guide.md +243 -0
  48. package/template/.env.example +60 -0
  49. package/template/.eslintrc.routes.json +39 -0
  50. package/template/.husky/_/husky.sh +27 -0
  51. package/template/.husky/commit-msg +4 -0
  52. package/template/.husky/post-commit +4 -0
  53. package/template/.husky/pre-commit +19 -0
  54. package/template/.husky/pre-push +12 -0
  55. package/template/.prettierignore +22 -0
  56. package/template/.prettierrc +8 -0
  57. package/template/.sessions/3/2026-03-26T05-41-09-154Z_03bb597f-3898-4a2e-8c08-5746bbe8d95e.jsonl +18 -0
  58. package/template/.sessions/3/2026-03-26T05-48-30-052Z_12de4774-9cb3-494c-9852-bd1e6b0003c3.jsonl +6 -0
  59. package/template/.vscode/extensions.json +8 -0
  60. package/template/.vscode/settings.json +4 -0
  61. package/template/.workspaces/3/hello.py +1 -0
  62. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite +0 -0
  63. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-shm +0 -0
  64. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-wal +0 -0
  65. package/template/CLAUDE.md +296 -0
  66. package/template/DESIGN.md +260 -0
  67. package/template/DEV_CONTEXT_GUIDE.md +366 -0
  68. package/template/DEV_TOOLS_README.md +396 -0
  69. package/template/FRAMEWORK_HISTORY.md +34 -0
  70. package/template/PROJECT_SUMMARY.md +325 -0
  71. package/template/QUICKSTART.md +305 -0
  72. package/template/README.md +154 -0
  73. package/template/STRUCTURE.txt +81 -0
  74. package/template/admin.html +13 -0
  75. package/template/auth-inject.html +33 -0
  76. package/template/commitlint.config.js +12 -0
  77. package/template/docs/PERMISSION_ARCHITECTURE.md +279 -0
  78. package/template/docs/PERMISSION_EXAMPLES.md +230 -0
  79. package/template/docs/PERMISSION_SYSTEM.md +301 -0
  80. package/template/docs/SMART_TEST.md +171 -0
  81. package/template/docs/runtime-abstract.md +253 -0
  82. package/template/drizzle/0000_rainy_boomer.sql +101 -0
  83. package/template/drizzle/0001_add_todo_attachments.sql +12 -0
  84. package/template/drizzle/0002_chilly_magneto.sql +89 -0
  85. package/template/drizzle/meta/0000_snapshot.json +652 -0
  86. package/template/drizzle/meta/0001_snapshot.json +735 -0
  87. package/template/drizzle/meta/0002_snapshot.json +1198 -0
  88. package/template/drizzle/meta/_journal.json +27 -0
  89. package/template/drizzle.config.ts +13 -0
  90. package/template/eslint-rules/README-no-middleware-in-routes.md +154 -0
  91. package/template/eslint-rules/__tests__/enforce-valid-method.test.ts +251 -0
  92. package/template/eslint-rules/__tests__/layer-boundary.test.ts +102 -0
  93. package/template/eslint-rules/__tests__/module-boundary.test.ts +77 -0
  94. package/template/eslint-rules/__tests__/no-direct-zod-import-in-file-routes.test.ts +45 -0
  95. package/template/eslint-rules/__tests__/no-new-old-service-naming.test.ts +76 -0
  96. package/template/eslint-rules/e2e-test-location.js +153 -0
  97. package/template/eslint-rules/enforce-valid-method.js +217 -0
  98. package/template/eslint-rules/flat-routes-services.js +69 -0
  99. package/template/eslint-rules/framework-protect.js +186 -0
  100. package/template/eslint-rules/layer-boundary.js +164 -0
  101. package/template/eslint-rules/limit-type-complexity.js +130 -0
  102. package/template/eslint-rules/middleware-location.js +162 -0
  103. package/template/eslint-rules/module-boundary.js +125 -0
  104. package/template/eslint-rules/no-ambiguous-file-paths.js +64 -0
  105. package/template/eslint-rules/no-any-on-apiclient.js +173 -0
  106. package/template/eslint-rules/no-boolean-success.js +43 -0
  107. package/template/eslint-rules/no-deep-relative-imports.js +69 -0
  108. package/template/eslint-rules/no-direct-fetch.js +122 -0
  109. package/template/eslint-rules/no-direct-ws-sse.js +57 -0
  110. package/template/eslint-rules/no-direct-zod-import-in-file-routes.js +54 -0
  111. package/template/eslint-rules/no-disable-direct-fetch.js +57 -0
  112. package/template/eslint-rules/no-disable-type-safe-client.js +58 -0
  113. package/template/eslint-rules/no-inline-schema.js +220 -0
  114. package/template/eslint-rules/no-middleware-in-routes.js +81 -0
  115. package/template/eslint-rules/no-new-old-service-naming.js +123 -0
  116. package/template/eslint-rules/no-type-assertion-in-rpc.js +99 -0
  117. package/template/eslint-rules/no-type-assertion-on-shared-types.js +109 -0
  118. package/template/eslint-rules/no-util-functions-in-service.js +128 -0
  119. package/template/eslint-rules/prefer-shared-types.js +352 -0
  120. package/template/eslint-rules/protect-ws-sse-interface.js +66 -0
  121. package/template/eslint-rules/require-antd-generic-types.js +73 -0
  122. package/template/eslint-rules/require-file-openapi-props.js +114 -0
  123. package/template/eslint-rules/require-hono-chain-syntax.js +129 -0
  124. package/template/eslint-rules/require-nullable-for-optional.js +99 -0
  125. package/template/eslint-rules/require-response-helpers.js +140 -0
  126. package/template/eslint-rules/require-type-safe-test-client.js +106 -0
  127. package/template/eslint-rules/route-location.js +72 -0
  128. package/template/eslint.config.js +231 -0
  129. package/template/index.html +17 -0
  130. package/template/lint-scripts/DIRECTORY_RULES.md +335 -0
  131. package/template/lint-scripts/README.md +299 -0
  132. package/template/lint-scripts/TESTING.md +273 -0
  133. package/template/lint-scripts/ai-context-generator.ts +159 -0
  134. package/template/lint-scripts/check-framework-modify.sh +8 -0
  135. package/template/lint-scripts/check-menu-permissions.js +242 -0
  136. package/template/lint-scripts/check-refs.ts +165 -0
  137. package/template/lint-scripts/check-route-auth-complete.js +260 -0
  138. package/template/lint-scripts/check-route-auth.js +212 -0
  139. package/template/lint-scripts/check-route-permissions.js +210 -0
  140. package/template/lint-scripts/config/project.config.ts +358 -0
  141. package/template/lint-scripts/dev-context-analyzer.ts +707 -0
  142. package/template/lint-scripts/dev-guide-generator.ts +830 -0
  143. package/template/lint-scripts/dev-server.ts +58 -0
  144. package/template/lint-scripts/eslint-route-auth.js +158 -0
  145. package/template/lint-scripts/eslint-routes-permission.js +147 -0
  146. package/template/lint-scripts/fix-route-auth.js +245 -0
  147. package/template/lint-scripts/framework/check-modify.ts +152 -0
  148. package/template/lint-scripts/framework/hash-utils.ts +180 -0
  149. package/template/lint-scripts/framework/init-baseline.ts +84 -0
  150. package/template/lint-scripts/framework/update-baseline.ts +148 -0
  151. package/template/lint-scripts/post-commit-track.ts +66 -0
  152. package/template/lint-scripts/quick-test.sh +27 -0
  153. package/template/lint-scripts/smart-test.ts +268 -0
  154. package/template/lint-scripts/test-history.ts +360 -0
  155. package/template/lint-scripts/test-tracker.ts +350 -0
  156. package/template/lint-scripts/validate-all.ts +250 -0
  157. package/template/lint-scripts/validators/api-coverage.validator.ts +355 -0
  158. package/template/lint-scripts/validators/client-rpc.validator.ts +190 -0
  159. package/template/lint-scripts/validators/client-tests.validator.ts +155 -0
  160. package/template/lint-scripts/validators/console-log.validator.ts +122 -0
  161. package/template/lint-scripts/validators/directory-structure.validator.ts +181 -0
  162. package/template/lint-scripts/validators/imports.validator.ts +180 -0
  163. package/template/lint-scripts/validators/index.ts +271 -0
  164. package/template/lint-scripts/validators/md-refs.validator.ts +165 -0
  165. package/template/lint-scripts/validators/module-tests.validator.ts +112 -0
  166. package/template/lint-scripts/validators/sensitive.validator.ts +185 -0
  167. package/template/lint-scripts/validators/server-rpc.validator.ts +141 -0
  168. package/template/lint-scripts/validators/test-quality.validator.ts +257 -0
  169. package/template/lint-scripts/validators/todos.validator.ts +126 -0
  170. package/template/lint-scripts/watch-validator.ts +131 -0
  171. package/template/modules.config.ts +71 -0
  172. package/template/package-lock.json +14554 -0
  173. package/template/package.json +158 -0
  174. package/template/patches/@hono+zod-openapi+1.2.2.patch +67 -0
  175. package/template/patches/@hono+zod-validator+0.7.6.patch +13 -0
  176. package/template/patches/hono+4.12.16.patch +246 -0
  177. package/template/patches/typescript+5.8.3.patch +26 -0
  178. package/template/playwright.config.ts +60 -0
  179. package/template/pnpm-lock.yaml +7137 -0
  180. package/template/postcss.config.js +6 -0
  181. package/template/scripts/create-template.ts +1724 -0
  182. package/template/src/admin/App.tsx +59 -0
  183. package/template/src/admin/components/AccountSwitcher.tsx +104 -0
  184. package/template/src/admin/components/CaptchaModal.tsx +150 -0
  185. package/template/src/admin/components/NotificationDrawer.tsx +213 -0
  186. package/template/src/admin/components/PageHeader.tsx +45 -0
  187. package/template/src/admin/components/PermissionConfigEditor.tsx +237 -0
  188. package/template/src/admin/components/PermissionGuard.tsx +108 -0
  189. package/template/src/admin/components/PermissionTree.tsx +193 -0
  190. package/template/src/admin/components/ProtectedRoute.tsx +33 -0
  191. package/template/src/admin/components/StatsCard.tsx +44 -0
  192. package/template/src/admin/components/UserFormModal.tsx +67 -0
  193. package/template/src/admin/components/UserTable.tsx +72 -0
  194. package/template/src/admin/components/__tests__/PageHeader.test.tsx +48 -0
  195. package/template/src/admin/components/__tests__/PermissionConfigEditor.test.tsx +147 -0
  196. package/template/src/admin/components/__tests__/PermissionGuard.test.tsx +221 -0
  197. package/template/src/admin/components/__tests__/PermissionTree.test.tsx +187 -0
  198. package/template/src/admin/components/__tests__/ProtectedRoute.test.tsx +129 -0
  199. package/template/src/admin/components/__tests__/StatsCard.test.tsx +44 -0
  200. package/template/src/admin/components/__tests__/UserFormModal.test.tsx +125 -0
  201. package/template/src/admin/components/__tests__/UserTable.test.tsx +89 -0
  202. package/template/src/admin/components/index.ts +7 -0
  203. package/template/src/admin/hooks/__tests__/useAuditLogs.test.ts +109 -0
  204. package/template/src/admin/hooks/__tests__/usePermissions.test.tsx +327 -0
  205. package/template/src/admin/hooks/__tests__/usePermissionsBranches.test.ts +198 -0
  206. package/template/src/admin/hooks/__tests__/useRoles.test.ts +261 -0
  207. package/template/src/admin/hooks/useAdminNotifications.ts +156 -0
  208. package/template/src/admin/hooks/useAuditLogs.ts +44 -0
  209. package/template/src/admin/hooks/useConfig.ts +168 -0
  210. package/template/src/admin/hooks/usePermissions.ts +156 -0
  211. package/template/src/admin/hooks/useRoles.ts +120 -0
  212. package/template/src/admin/layouts/Header.tsx +93 -0
  213. package/template/src/admin/layouts/Layout.tsx +23 -0
  214. package/template/src/admin/layouts/Sidebar.tsx +120 -0
  215. package/template/src/admin/main.tsx +18 -0
  216. package/template/src/admin/pages/ContentPage.tsx +252 -0
  217. package/template/src/admin/pages/DashboardPage.tsx +141 -0
  218. package/template/src/admin/pages/DisputesPage.tsx +261 -0
  219. package/template/src/admin/pages/LoginPage.tsx +121 -0
  220. package/template/src/admin/pages/MediaTestPage.tsx +524 -0
  221. package/template/src/admin/pages/OrdersPage.tsx +302 -0
  222. package/template/src/admin/pages/PermissionsPage.tsx +95 -0
  223. package/template/src/admin/pages/RegisterPage.tsx +103 -0
  224. package/template/src/admin/pages/RolesPage.tsx +330 -0
  225. package/template/src/admin/pages/SettingsPage.tsx +73 -0
  226. package/template/src/admin/pages/SystemLogsPage.tsx +218 -0
  227. package/template/src/admin/pages/TestCaptchaPage.tsx +120 -0
  228. package/template/src/admin/pages/TicketsPage.tsx +292 -0
  229. package/template/src/admin/pages/UsersPage.tsx +282 -0
  230. package/template/src/admin/pages/__tests__/ContentPage.test.tsx +213 -0
  231. package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +153 -0
  232. package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +196 -0
  233. package/template/src/admin/pages/__tests__/LoginPage.test.tsx +112 -0
  234. package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +217 -0
  235. package/template/src/admin/pages/__tests__/PermissionsPage.test.tsx +119 -0
  236. package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +179 -0
  237. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +256 -0
  238. package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +66 -0
  239. package/template/src/admin/pages/__tests__/SystemLogsPage.test.tsx +125 -0
  240. package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +202 -0
  241. package/template/src/admin/pages/__tests__/UsersPage.test.tsx +229 -0
  242. package/template/src/admin/services/README.md +263 -0
  243. package/template/src/admin/services/TESTING.md +323 -0
  244. package/template/src/admin/services/__tests__/skipLoading.test.ts +146 -0
  245. package/template/src/admin/services/apiClient.ts +66 -0
  246. package/template/src/admin/services/requestInterceptor.ts +127 -0
  247. package/template/src/admin/services/types.ts +5 -0
  248. package/template/src/admin/stores/__tests__/adminStore.test.ts +184 -0
  249. package/template/src/admin/stores/__tests__/adminStoreBranches.test.ts +148 -0
  250. package/template/src/admin/stores/__tests__/captchaStore.test.ts +75 -0
  251. package/template/src/admin/stores/__tests__/captchaStoreBranches.test.ts +37 -0
  252. package/template/src/admin/stores/__tests__/loadingStore.test.ts +68 -0
  253. package/template/src/admin/stores/adminStore.ts +88 -0
  254. package/template/src/admin/stores/captchaStore.ts +47 -0
  255. package/template/src/admin/stores/loadingStore.ts +27 -0
  256. package/template/src/cli/index.ts +22 -0
  257. package/template/src/cli/modules/config/index.ts +132 -0
  258. package/template/src/cli/modules/index.ts +12 -0
  259. package/template/src/cli/modules/notification/index.ts +86 -0
  260. package/template/src/cli/modules/todo/index.ts +60 -0
  261. package/template/src/cli/rpc/client.ts +23 -0
  262. package/template/src/cli/rpc/index.ts +1 -0
  263. package/template/src/cli/utils/api.ts +17 -0
  264. package/template/src/cli/utils/auto-command.ts +229 -0
  265. package/template/src/cli/utils/index.ts +8 -0
  266. package/template/src/cli/utils/logger.ts +79 -0
  267. package/template/src/client/App.tsx +24 -0
  268. package/template/src/client/Layout.tsx +27 -0
  269. package/template/src/client/components/AuthButton.tsx +41 -0
  270. package/template/src/client/components/ConnectionStatus.tsx +75 -0
  271. package/template/src/client/components/EmptyState.tsx +26 -0
  272. package/template/src/client/components/Footer.tsx +17 -0
  273. package/template/src/client/components/LoadingSpinner.tsx +26 -0
  274. package/template/src/client/components/MessageCard.tsx +61 -0
  275. package/template/src/client/components/Navigation.tsx +65 -0
  276. package/template/src/client/components/StatusBadge.tsx +50 -0
  277. package/template/src/client/components/__tests__/App.test.tsx +79 -0
  278. package/template/src/client/components/__tests__/AuthButton.test.tsx +119 -0
  279. package/template/src/client/components/__tests__/ConnectionStatus.test.tsx +188 -0
  280. package/template/src/client/components/__tests__/EmptyState.test.tsx +70 -0
  281. package/template/src/client/components/__tests__/Footer.test.tsx +22 -0
  282. package/template/src/client/components/__tests__/LoadingSpinner.test.tsx +75 -0
  283. package/template/src/client/components/__tests__/MessageCard.test.tsx +142 -0
  284. package/template/src/client/components/__tests__/Navigation.test.tsx +40 -0
  285. package/template/src/client/components/__tests__/StatusBadge.test.tsx +107 -0
  286. package/template/src/client/components/index.ts +6 -0
  287. package/template/src/client/index.css +27 -0
  288. package/template/src/client/main.tsx +25 -0
  289. package/template/src/client/pages/ContentDetailPage.tsx +142 -0
  290. package/template/src/client/pages/ContentListPage.tsx +169 -0
  291. package/template/src/client/pages/NotificationPage.tsx +288 -0
  292. package/template/src/client/pages/TodoPage.tsx +389 -0
  293. package/template/src/client/pages/WebSocketPage.tsx +239 -0
  294. package/template/src/client/pages/__tests__/ContentDetailPage.test.tsx +176 -0
  295. package/template/src/client/pages/__tests__/ContentListPage.test.tsx +202 -0
  296. package/template/src/client/pages/__tests__/NotificationPage.test.tsx +246 -0
  297. package/template/src/client/pages/__tests__/TodoPage.test.tsx +433 -0
  298. package/template/src/client/pages/__tests__/WebSocketPage.test.tsx +378 -0
  299. package/template/src/client/services/apiClient.ts +66 -0
  300. package/template/src/client/stores/__tests__/authStore.test.ts +139 -0
  301. package/template/src/client/stores/__tests__/chatWSStore.test.ts +416 -0
  302. package/template/src/client/stores/__tests__/notificationStore.test.ts +453 -0
  303. package/template/src/client/stores/__tests__/todoStore.test.ts +509 -0
  304. package/template/src/client/stores/authStore.ts +51 -0
  305. package/template/src/client/stores/chatWSStore.ts +122 -0
  306. package/template/src/client/stores/notificationStore.ts +199 -0
  307. package/template/src/client/stores/todoStore.ts +207 -0
  308. package/template/src/server/__tests__/integration/todos-api.test.ts +138 -0
  309. package/template/src/server/app.ts +105 -0
  310. package/template/src/server/config.ts +86 -0
  311. package/template/src/server/core/__tests__/realtime-core.test.ts +329 -0
  312. package/template/src/server/core/__tests__/realtime-scanner.test.ts +210 -0
  313. package/template/src/server/core/__tests__/runtime-node.test.ts +330 -0
  314. package/template/src/server/core/__tests__/runtime.test.ts +219 -0
  315. package/template/src/server/core/__tests__/typed-runtime.test.ts +178 -0
  316. package/template/src/server/core/durable-objects/RealtimeDO.ts +206 -0
  317. package/template/src/server/core/index.ts +83 -0
  318. package/template/src/server/core/module-loader.ts +281 -0
  319. package/template/src/server/core/realtime-core.ts +152 -0
  320. package/template/src/server/core/realtime-scanner.ts +99 -0
  321. package/template/src/server/core/runtime-cloudflare.ts +207 -0
  322. package/template/src/server/core/runtime-node.ts +170 -0
  323. package/template/src/server/core/runtime.ts +129 -0
  324. package/template/src/server/core/typed-runtime.ts +80 -0
  325. package/template/src/server/db/__tests__/driver.test.ts +203 -0
  326. package/template/src/server/db/config.ts +1 -0
  327. package/template/src/server/db/driver-cloudflare.ts +50 -0
  328. package/template/src/server/db/driver.ts +116 -0
  329. package/template/src/server/db/index.ts +3 -0
  330. package/template/src/server/db/init.ts +412 -0
  331. package/template/src/server/db/schema/api-endpoints.ts +23 -0
  332. package/template/src/server/db/schema/contents.ts +31 -0
  333. package/template/src/server/db/schema/disputes.ts +39 -0
  334. package/template/src/server/db/schema/index.ts +14 -0
  335. package/template/src/server/db/schema/notifications.ts +16 -0
  336. package/template/src/server/db/schema/orders.ts +30 -0
  337. package/template/src/server/db/schema/permission-audit-logs.ts +17 -0
  338. package/template/src/server/db/schema/permission-route-mappings.ts +22 -0
  339. package/template/src/server/db/schema/permissions.ts +17 -0
  340. package/template/src/server/db/schema/role-permissions.ts +22 -0
  341. package/template/src/server/db/schema/roles.ts +17 -0
  342. package/template/src/server/db/schema/tickets.ts +62 -0
  343. package/template/src/server/db/schema/todo-attachments.ts +22 -0
  344. package/template/src/server/db/schema/todos.ts +21 -0
  345. package/template/src/server/db/schema/user-roles.ts +17 -0
  346. package/template/src/server/db/seeds/index.ts +1 -0
  347. package/template/src/server/db/seeds/permission-data.ts +354 -0
  348. package/template/src/server/db/test-setup.ts +647 -0
  349. package/template/src/server/entries/cloudflare.ts +71 -0
  350. package/template/src/server/entries/node.ts +190 -0
  351. package/template/src/server/index.ts +62 -0
  352. package/template/src/server/middleware/__tests__/auth-simple.test.ts +147 -0
  353. package/template/src/server/middleware/__tests__/auth.test.ts +276 -0
  354. package/template/src/server/middleware/__tests__/captcha.test.ts +258 -0
  355. package/template/src/server/middleware/__tests__/error-handler.test.ts +364 -0
  356. package/template/src/server/middleware/__tests__/error-response-format.test.ts +80 -0
  357. package/template/src/server/middleware/__tests__/permission.test.ts +312 -0
  358. package/template/src/server/middleware/audit-log.ts +101 -0
  359. package/template/src/server/middleware/auth.ts +235 -0
  360. package/template/src/server/middleware/captcha.ts +166 -0
  361. package/template/src/server/middleware/cors.ts +30 -0
  362. package/template/src/server/middleware/error-handler.ts +135 -0
  363. package/template/src/server/middleware/index.ts +24 -0
  364. package/template/src/server/middleware/logger.ts +73 -0
  365. package/template/src/server/middleware/permission.ts +127 -0
  366. package/template/src/server/middleware/rate-limit.ts +69 -0
  367. package/template/src/server/middleware/realtime-env.ts +12 -0
  368. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +360 -0
  369. package/template/src/server/module-admin/__tests__/admin-service.test.ts +222 -0
  370. package/template/src/server/module-admin/__tests__/audit-log-routes.test.ts +261 -0
  371. package/template/src/server/module-admin/__tests__/export-routes.test.ts +160 -0
  372. package/template/src/server/module-admin/__tests__/media-routes.test.ts +122 -0
  373. package/template/src/server/module-admin/module.ts +40 -0
  374. package/template/src/server/module-admin/routes/admin-notification-routes.ts +197 -0
  375. package/template/src/server/module-admin/routes/admin-routes.ts +20 -0
  376. package/template/src/server/module-admin/routes/auth-routes.ts +86 -0
  377. package/template/src/server/module-admin/routes/export-routes.ts +207 -0
  378. package/template/src/server/module-admin/routes/media-routes.ts +78 -0
  379. package/template/src/server/module-admin/routes/system-routes.ts +89 -0
  380. package/template/src/server/module-admin/routes/user-management-routes.ts +143 -0
  381. package/template/src/server/module-admin/services/admin-service.ts +268 -0
  382. package/template/src/server/module-captcha/__tests__/captcha-route.test.ts +35 -0
  383. package/template/src/server/module-captcha/__tests__/captcha-service.test.ts +53 -0
  384. package/template/src/server/module-captcha/module.ts +35 -0
  385. package/template/src/server/module-captcha/routes/captcha-routes.ts +68 -0
  386. package/template/src/server/module-captcha/services/captcha-service.ts +3 -0
  387. package/template/src/server/module-chat/__tests__/chat-routes.test.ts +39 -0
  388. package/template/src/server/module-chat/__tests__/chat-rpc.test.ts +185 -0
  389. package/template/src/server/module-chat/__tests__/chat-service.test.ts +64 -0
  390. package/template/src/server/module-chat/module.ts +27 -0
  391. package/template/src/server/module-chat/routes/chat-routes.ts +46 -0
  392. package/template/src/server/module-chat/services/chat-service.ts +38 -0
  393. package/template/src/server/module-content/__tests__/content-route.test.ts +40 -0
  394. package/template/src/server/module-content/__tests__/content-service.test.ts +107 -0
  395. package/template/src/server/module-content/module.ts +39 -0
  396. package/template/src/server/module-content/routes/content-routes.ts +182 -0
  397. package/template/src/server/module-content/routes/public-content-routes.ts +58 -0
  398. package/template/src/server/module-content/services/content-service.ts +224 -0
  399. package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +43 -0
  400. package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +135 -0
  401. package/template/src/server/module-dispute/module.ts +30 -0
  402. package/template/src/server/module-dispute/routes/dispute-routes.ts +153 -0
  403. package/template/src/server/module-dispute/services/dispute-service.ts +255 -0
  404. package/template/src/server/module-file/__tests__/file-routes.test.ts +284 -0
  405. package/template/src/server/module-file/__tests__/file-storage-service.test.ts +323 -0
  406. package/template/src/server/module-file/module.ts +28 -0
  407. package/template/src/server/module-file/routes/file-routes.ts +288 -0
  408. package/template/src/server/module-notifications/__tests__/notification-route-rpc.test.ts +375 -0
  409. package/template/src/server/module-notifications/__tests__/notification-service.test.ts +241 -0
  410. package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +289 -0
  411. package/template/src/server/module-notifications/module.ts +32 -0
  412. package/template/src/server/module-notifications/routes/notification-routes.ts +190 -0
  413. package/template/src/server/module-notifications/services/notification-service.ts +104 -0
  414. package/template/src/server/module-order/__tests__/order-route.test.ts +258 -0
  415. package/template/src/server/module-order/__tests__/order-service.test.ts +222 -0
  416. package/template/src/server/module-order/module.ts +30 -0
  417. package/template/src/server/module-order/routes/order-routes.ts +202 -0
  418. package/template/src/server/module-order/services/order-service.ts +268 -0
  419. package/template/src/server/module-permission/__tests__/permission-middleware.test.ts +90 -0
  420. package/template/src/server/module-permission/__tests__/permission-routes.test.ts +330 -0
  421. package/template/src/server/module-permission/__tests__/permission-service-impl.test.ts +388 -0
  422. package/template/src/server/module-permission/__tests__/permission-service.test.ts +190 -0
  423. package/template/src/server/module-permission/__tests__/role-routes.test.ts +215 -0
  424. package/template/src/server/module-permission/module.ts +68 -0
  425. package/template/src/server/module-permission/routes/audit-log-routes.ts +76 -0
  426. package/template/src/server/module-permission/routes/permission-routes.ts +184 -0
  427. package/template/src/server/module-permission/routes/role-routes.ts +263 -0
  428. package/template/src/server/module-permission/services/audit-log-service.ts +108 -0
  429. package/template/src/server/module-permission/services/permission-service-impl.ts +233 -0
  430. package/template/src/server/module-permission/services/permission-service.ts +256 -0
  431. package/template/src/server/module-permission/services/role-service.ts +122 -0
  432. package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +173 -0
  433. package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +201 -0
  434. package/template/src/server/module-ticket/module.ts +30 -0
  435. package/template/src/server/module-ticket/routes/ticket-routes.ts +210 -0
  436. package/template/src/server/module-ticket/services/ticket-service.ts +297 -0
  437. package/template/src/server/module-todos/__tests__/todo-service.test.ts +196 -0
  438. package/template/src/server/module-todos/__tests__/todos-file-upload.test.ts +439 -0
  439. package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +575 -0
  440. package/template/src/server/module-todos/module.ts +30 -0
  441. package/template/src/server/module-todos/routes/todos-routes.ts +260 -0
  442. package/template/src/server/module-todos/services/todo-service.ts +202 -0
  443. package/template/src/server/route-registry.ts +47 -0
  444. package/template/src/server/test-utils/test-client.ts +66 -0
  445. package/template/src/server/test-utils/test-server.ts +131 -0
  446. package/template/src/server/types/bindings.ts +10 -0
  447. package/template/src/server/utils/__tests__/app-error.test.ts +374 -0
  448. package/template/src/server/utils/__tests__/auth.test.ts +76 -0
  449. package/template/src/server/utils/__tests__/captcha.test.ts +80 -0
  450. package/template/src/server/utils/__tests__/file-storage.test.ts +472 -0
  451. package/template/src/server/utils/__tests__/logger.test.ts +221 -0
  452. package/template/src/server/utils/__tests__/route-helpers.test.ts +264 -0
  453. package/template/src/server/utils/app-error.ts +355 -0
  454. package/template/src/server/utils/auth.ts +65 -0
  455. package/template/src/server/utils/captcha.ts +119 -0
  456. package/template/src/server/utils/date.ts +50 -0
  457. package/template/src/server/utils/env.ts +4 -0
  458. package/template/src/server/utils/file-storage.ts +462 -0
  459. package/template/src/server/utils/generate.ts +26 -0
  460. package/template/src/server/utils/id-helpers.ts +5 -0
  461. package/template/src/server/utils/logger.ts +139 -0
  462. package/template/src/server/utils/permission-utils.ts +6 -0
  463. package/template/src/server/utils/response.ts +40 -0
  464. package/template/src/server/utils/route-helpers.ts +200 -0
  465. package/template/src/server/utils/uuid.ts +8 -0
  466. package/template/src/server/utils/ws-helper.ts +30 -0
  467. package/template/src/shared/constants/index.ts +1 -0
  468. package/template/src/shared/constants/resource-types.ts +72 -0
  469. package/template/src/shared/core/__tests__/sse-client.test.ts +376 -0
  470. package/template/src/shared/core/__tests__/ws-client.test.ts +468 -0
  471. package/template/src/shared/core/api-request.ts +192 -0
  472. package/template/src/shared/core/api-schemas.ts +41 -0
  473. package/template/src/shared/core/index.ts +37 -0
  474. package/template/src/shared/core/module-manifest.ts +120 -0
  475. package/template/src/shared/core/protocol-types.ts +26 -0
  476. package/template/src/shared/core/sse-client.ts +185 -0
  477. package/template/src/shared/core/ws-client.ts +236 -0
  478. package/template/src/shared/hooks/index.ts +2 -0
  479. package/template/src/shared/hooks/useSSE.ts +57 -0
  480. package/template/src/shared/hooks/useWebSocket.ts +66 -0
  481. package/template/src/shared/index.ts +5 -0
  482. package/template/src/shared/modules/admin/index.ts +1 -0
  483. package/template/src/shared/modules/admin/schemas.ts +100 -0
  484. package/template/src/shared/modules/audit/index.ts +1 -0
  485. package/template/src/shared/modules/audit/schemas.ts +24 -0
  486. package/template/src/shared/modules/captcha/index.ts +1 -0
  487. package/template/src/shared/modules/captcha/schemas.ts +19 -0
  488. package/template/src/shared/modules/chat/index.ts +38 -0
  489. package/template/src/shared/modules/content/index.ts +1 -0
  490. package/template/src/shared/modules/content/schemas.ts +53 -0
  491. package/template/src/shared/modules/dispute/index.ts +1 -0
  492. package/template/src/shared/modules/dispute/schemas.ts +62 -0
  493. package/template/src/shared/modules/files/index.ts +11 -0
  494. package/template/src/shared/modules/files/schemas.ts +44 -0
  495. package/template/src/shared/modules/index.ts +70 -0
  496. package/template/src/shared/modules/notifications/index.ts +26 -0
  497. package/template/src/shared/modules/notifications/schemas.ts +73 -0
  498. package/template/src/shared/modules/order/index.ts +1 -0
  499. package/template/src/shared/modules/order/schemas.ts +63 -0
  500. package/template/src/shared/modules/permission/index.ts +42 -0
  501. package/template/src/shared/modules/permission/permission-dependencies.ts +50 -0
  502. package/template/src/shared/modules/permission/permissions.ts +275 -0
  503. package/template/src/shared/modules/permission/schemas.ts +92 -0
  504. package/template/src/shared/modules/permission/types.ts +23 -0
  505. package/template/src/shared/modules/role/index.ts +1 -0
  506. package/template/src/shared/modules/role/schemas.ts +46 -0
  507. package/template/src/shared/modules/ticket/index.ts +1 -0
  508. package/template/src/shared/modules/ticket/schemas.ts +77 -0
  509. package/template/src/shared/modules/todos/index.ts +20 -0
  510. package/template/src/shared/modules/todos/schemas.ts +65 -0
  511. package/template/src/shared/schemas/index.ts +79 -0
  512. package/template/src/types/global.d.ts +14 -0
  513. package/template/src/vite-env.d.ts +21 -0
  514. package/template/tailwind.config.js +11 -0
  515. package/template/tests/e2e/admin-crud.spec.ts +81 -0
  516. package/template/tests/e2e/case-o1.spec.ts +108 -0
  517. package/template/tests/e2e/global-setup.ts +73 -0
  518. package/template/tests/e2e/global-teardown.ts +28 -0
  519. package/template/tests/e2e/notification.spec.ts +458 -0
  520. package/template/tests/e2e/todo.spec.ts +623 -0
  521. package/template/tests/e2e/websocket.spec.ts +254 -0
  522. package/template/tsconfig.json +35 -0
  523. package/template/tsup.config.ts +95 -0
  524. package/template/vite-plugins.ts +113 -0
  525. package/template/vite.config.ts +96 -0
  526. package/template/vitest.config.ts +53 -0
  527. package/template/vitest.integration.config.ts +24 -0
  528. package/template/vitest.integration.setup.ts +22 -0
  529. package/template/vitest.setup.ts +74 -0
  530. package/template/wrangler.toml +33 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @framework-baseline f51621ebf8da3453
3
+ *
4
+ * 此文件属于框架层代码。如需修改,请添加以下说明:
5
+ *
6
+ * @framework-modify
7
+ * @reason [必填] 修改原因
8
+ * @impact [必填] 影响范围
9
+ */
10
+
11
+ // API Schemas
12
+ export {
13
+ ApiSuccessSchema,
14
+ ApiErrorSchema,
15
+ ApiResponseSchema,
16
+ type ApiSuccess,
17
+ type ApiError,
18
+ type ApiResponse,
19
+ } from './api-schemas'
20
+
21
+ // Protocol Types
22
+ export type { RpcMethod, EventName, RpcInput, RpcOutput, EventPayload } from './protocol-types'
23
+
24
+ // WS Client
25
+ export {
26
+ WSClientImpl,
27
+ createWSClient,
28
+ type WSClient,
29
+ type WSProtocol,
30
+ type WSStatus,
31
+ } from './ws-client'
32
+
33
+ // SSE Client
34
+ export { SSEClientImpl, createSSEClient, type SSEClient, type SSEProtocol } from './sse-client'
35
+
36
+ // Module Manifest
37
+ export type { ModuleManifest, ModuleRegistry, TemplatePreset } from './module-manifest'
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @framework-baseline f51621ebf8da3453
3
+ *
4
+ * 此文件属于框架层代码。如需修改,请添加以下说明:
5
+ *
6
+ * @framework-modify
7
+ * @reason [必填] 修改原因
8
+ * @impact [必填] 影响范围
9
+ */
10
+
11
+ /**
12
+ * Module manifest — declarative description of a module's capabilities and dependencies.
13
+ * Each module-xxx directory should have a module.ts that exports a ModuleManifest.
14
+ */
15
+ export interface ModuleManifest {
16
+ /** Unique module identifier (e.g., 'todos', 'chat', 'admin') */
17
+ name: string
18
+
19
+ /** Human-readable description */
20
+ description: string
21
+
22
+ /** Category for grouping in templates */
23
+ category: 'core' | 'communication' | 'business' | 'system'
24
+
25
+ /** Other module names this module depends on */
26
+ dependsOn: string[]
27
+
28
+ /** Route registration info */
29
+ routes: {
30
+ /** Routes mounted under client API (/api) */
31
+ client?: {
32
+ /** Import path to the route variable (relative to module dir) */
33
+ importPath: string
34
+ /** Exported variable name from the route file */
35
+ exportName: string
36
+ }
37
+ /** Routes mounted under admin API (/api/admin) */
38
+ admin?: {
39
+ importPath: string
40
+ exportName: string
41
+ }[]
42
+ /** Standalone routes (e.g., /files) */
43
+ standalone?: {
44
+ importPath: string
45
+ exportName: string
46
+ mountPath: string
47
+ }
48
+ }
49
+
50
+ /** Shared schemas exported by this module */
51
+ sharedSchemas?: {
52
+ /** Directory path under shared/modules/ */
53
+ path: string
54
+ /** Additional shared module paths owned by this module (e.g., 'role', 'audit' for permission) */
55
+ additionalPaths?: string[]
56
+ }
57
+
58
+ /** Client-facing pages (React) */
59
+ clientPages?: {
60
+ /** Page component name (also filename) */
61
+ name: string
62
+ /** Route path in the client router */
63
+ route: string
64
+ }[]
65
+
66
+ /** Admin panel pages (React + Ant Design) */
67
+ adminPages?: {
68
+ name: string
69
+ route: string
70
+ /** Whether the page is public (no auth required) */
71
+ isPublic?: boolean
72
+ /** Required permission to access this page */
73
+ requiredPermission?: string
74
+ }[]
75
+
76
+ /** Database schema files (Drizzle table definitions) */
77
+ dbSchemas?: {
78
+ /** Filename in server/db/schema/ (without .ts extension) */
79
+ files: string[]
80
+ /** Whether the module has seed data */
81
+ hasSeed: boolean
82
+ }
83
+
84
+ /** Required npm dependencies (beyond what core provides) */
85
+ dependencies?: Record<string, string>
86
+
87
+ /** Client stores */
88
+ clientStores?: string[]
89
+
90
+ /** Middleware that this module provides (used by app.ts) */
91
+ providesMiddleware?: {
92
+ /** Middleware name */
93
+ name: string
94
+ /** Import path (from server/middleware/) */
95
+ importPath: string
96
+ /** Which routes it applies to */
97
+ appliesTo: 'client-api' | 'admin-api' | 'all-api' | 'standalone'
98
+ }[]
99
+
100
+ /** Whether this module has SSE (Server-Sent Events) routes */
101
+ hasSSE?: boolean
102
+
103
+ /** Whether this module has WebSocket routes */
104
+ hasWebSocket?: boolean
105
+ }
106
+
107
+ /** Type-safe module registry — maps module name to its manifest */
108
+ export type ModuleRegistry = Record<string, ModuleManifest>
109
+
110
+ /** Template preset — defines which modules a template includes */
111
+ export interface TemplatePreset {
112
+ /** Preset identifier */
113
+ id: string
114
+ /** Human-readable name */
115
+ name: string
116
+ /** Description shown in CLI */
117
+ description: string
118
+ /** Module names to include (order matters for registration) */
119
+ modules: string[]
120
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @framework-baseline ab15d84c70311810
3
+ *
4
+ * 此文件属于框架层代码。如需修改,请添加以下说明:
5
+ *
6
+ * @framework-modify
7
+ * @reason [必填] 修改原因
8
+ * @impact [必填] 影响范围
9
+ */
10
+
11
+ export type RpcMethod<T extends { rpc: unknown }> = keyof T['rpc']
12
+ export type EventName<T extends { events: unknown }> = keyof T['events']
13
+
14
+ export type RpcInput<T extends { rpc: unknown }, M extends RpcMethod<T>> = T['rpc'][M] extends {
15
+ in: infer I
16
+ }
17
+ ? I
18
+ : never
19
+
20
+ export type RpcOutput<T extends { rpc: unknown }, M extends RpcMethod<T>> = T['rpc'][M] extends {
21
+ out: infer O
22
+ }
23
+ ? O
24
+ : never
25
+
26
+ export type EventPayload<T extends { events: unknown }, E extends EventName<T>> = T['events'][E]
@@ -0,0 +1,185 @@
1
+ /**
2
+ * @framework-baseline 0a1efed4372dadaa
3
+ *
4
+ * 此文件属于框架层代码。如需修改,请添加以下说明:
5
+ *
6
+ * @framework-modify
7
+ * @reason 使用 fetch API 替代 EventSource,支持自定义 headers(认证)
8
+ * @impact SSE 客户端现在支持 Authorization header
9
+ */
10
+
11
+ export interface SSEProtocol {
12
+ events: Record<string, unknown>
13
+ }
14
+
15
+ export interface SSEClient<P extends SSEProtocol = SSEProtocol> {
16
+ readonly status: 'connecting' | 'open' | 'closed'
17
+ on<K extends keyof P['events']>(type: K, handler: (payload: P['events'][K]) => void): () => void
18
+ onStatusChange(handler: (status: 'connecting' | 'open' | 'closed') => void): () => void
19
+ onError(handler: (error: Error) => void): () => void
20
+ abort(): void
21
+ }
22
+
23
+ export class SSEClientImpl<P extends SSEProtocol = SSEProtocol> implements SSEClient<P> {
24
+ private abortController: AbortController | null = null
25
+ private handlers = new Map<string, ((payload: unknown) => void)[]>()
26
+ private statusHandlers: ((status: 'connecting' | 'open' | 'closed') => void)[] = []
27
+ private errorHandlers: ((error: Error) => void)[] = []
28
+ private _status: 'connecting' | 'open' | 'closed' = 'connecting'
29
+ private reconnectAttempts = 0
30
+ private maxReconnectAttempts = 5
31
+ private reconnectDelay = 1000
32
+ private url: string | URL
33
+ private headers: Record<string, string>
34
+
35
+ constructor(url: string | URL, headers: Record<string, string> = {}) {
36
+ this.url = url
37
+ this.headers = headers
38
+ this.connect()
39
+ }
40
+
41
+ get status() {
42
+ return this._status
43
+ }
44
+
45
+ private async connect() {
46
+ this._status = 'connecting'
47
+ this.abortController = new AbortController()
48
+
49
+ try {
50
+ const response = await fetch(this.url.toString(), {
51
+ headers: {
52
+ Accept: 'text/event-stream',
53
+ ...this.headers,
54
+ },
55
+ signal: this.abortController.signal,
56
+ })
57
+
58
+ if (!response.ok) {
59
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
60
+ }
61
+
62
+ this._status = 'open'
63
+ this.reconnectAttempts = 0
64
+ this.statusHandlers.forEach(h => h('open'))
65
+
66
+ const reader = response.body?.getReader()
67
+ if (!reader) {
68
+ throw new Error('No response body')
69
+ }
70
+
71
+ const decoder = new TextDecoder()
72
+ let buffer = ''
73
+ let eventType = 'message'
74
+ let dataBuffer = ''
75
+
76
+ while (true) {
77
+ const { done, value } = await reader.read()
78
+ if (done) break
79
+
80
+ buffer += decoder.decode(value, { stream: true })
81
+
82
+ // Process complete messages (separated by \n\n)
83
+ const messages = buffer.split('\n\n')
84
+ // Keep the last incomplete message in buffer
85
+ buffer = messages.pop() || ''
86
+
87
+ for (const message of messages) {
88
+ const lines = message.split('\n')
89
+ eventType = 'message'
90
+ dataBuffer = ''
91
+
92
+ for (const line of lines) {
93
+ if (line.startsWith('event:')) {
94
+ eventType = line.slice(6).trim()
95
+ } else if (line.startsWith('data:')) {
96
+ dataBuffer = line.slice(5).trim()
97
+ }
98
+ }
99
+
100
+ if (dataBuffer) {
101
+ this.handleMessage(eventType, dataBuffer)
102
+ }
103
+ }
104
+ }
105
+
106
+ this._status = 'closed'
107
+ this.statusHandlers.forEach(h => h('closed'))
108
+ } catch (error) {
109
+ if ((error as Error).name === 'AbortError') {
110
+ this._status = 'closed'
111
+ this.statusHandlers.forEach(h => h('closed'))
112
+ return
113
+ }
114
+
115
+ this.errorHandlers.forEach(h => h(error as Error))
116
+ this.attemptReconnect()
117
+ }
118
+ }
119
+
120
+ private attemptReconnect() {
121
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
122
+ this.reconnectAttempts++
123
+ setTimeout(() => {
124
+ this.connect()
125
+ }, this.reconnectDelay * this.reconnectAttempts)
126
+ } else {
127
+ this._status = 'closed'
128
+ this.statusHandlers.forEach(h => h('closed'))
129
+ }
130
+ }
131
+
132
+ private handleMessage(eventType: string, data: string) {
133
+ try {
134
+ const parsed = JSON.parse(data)
135
+ const handlers = this.handlers.get(eventType)
136
+ handlers?.forEach(h => h(parsed))
137
+ } catch {
138
+ const handlers = this.handlers.get(eventType)
139
+ handlers?.forEach(h => h(data))
140
+ }
141
+ }
142
+
143
+ on<K extends keyof P['events']>(type: K, handler: (payload: P['events'][K]) => void): () => void {
144
+ const eventName = type as string
145
+ const list = this.handlers.get(eventName) || []
146
+ list.push(handler as (payload: unknown) => void)
147
+ this.handlers.set(eventName, list)
148
+
149
+ return () => {
150
+ const filtered = (this.handlers.get(eventName) || []).filter(h => h !== handler)
151
+ this.handlers.set(eventName, filtered)
152
+ }
153
+ }
154
+
155
+ onStatusChange(handler: (status: 'connecting' | 'open' | 'closed') => void): () => void {
156
+ this.statusHandlers.push(handler)
157
+ handler(this._status)
158
+ return () => {
159
+ this.statusHandlers = this.statusHandlers.filter(h => h !== handler)
160
+ }
161
+ }
162
+
163
+ onError(handler: (error: Error) => void): () => void {
164
+ this.errorHandlers.push(handler)
165
+ return () => {
166
+ this.errorHandlers = this.errorHandlers.filter(h => h !== handler)
167
+ }
168
+ }
169
+
170
+ abort(): void {
171
+ if (this.abortController) {
172
+ this.abortController.abort()
173
+ this.abortController = null
174
+ }
175
+ this._status = 'closed'
176
+ this.statusHandlers.forEach(h => h('closed'))
177
+ }
178
+ }
179
+
180
+ export function createSSEClient<P extends SSEProtocol>(
181
+ url: string | URL,
182
+ headers: Record<string, string> = {}
183
+ ): SSEClient<P> {
184
+ return new SSEClientImpl<P>(url, headers)
185
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * @framework-baseline 4437e01c1b0bcabb
3
+ *
4
+ * 此文件属于框架层代码。如需修改,请添加以下说明:
5
+ *
6
+ * @framework-modify
7
+ * @reason WSClientImpl 不再 extends WebSocket,改为动态基类模式兼容 browser/node 环境
8
+ * @impact 所有使用 WSClientImpl 的地方不再需要 WebSocket 类型断言
9
+ */
10
+
11
+ export type WSStatus = 'connecting' | 'open' | 'closed' | 'reconnecting'
12
+
13
+ export interface WSProtocol {
14
+ rpc: Record<string, { in: unknown; out: unknown }>
15
+ events: Record<string, unknown>
16
+ }
17
+
18
+ export interface WSClient<T extends WSProtocol = WSProtocol> {
19
+ readonly status: WSStatus
20
+ getSocket(): WebSocket | null
21
+ call<K extends keyof T['rpc']>(
22
+ method: K,
23
+ params: T['rpc'][K] extends { in: infer I } ? I : never,
24
+ timeout?: number
25
+ ): Promise<T['rpc'][K] extends { out: infer O } ? O : never>
26
+ emit<K extends keyof T['events']>(type: K, payload: T['events'][K]): void
27
+ on<K extends keyof T['events']>(type: K, handler: (payload: T['events'][K]) => void): () => void
28
+ onStatusChange(handler: (status: WSStatus) => void): () => void
29
+ close(): void
30
+ }
31
+
32
+ type PendingRequest = {
33
+ resolve: (val: unknown) => void
34
+ reject: (err: unknown) => void
35
+ timer: ReturnType<typeof setTimeout>
36
+ }
37
+
38
+ interface WSMessageBase {
39
+ id?: string
40
+ method?: string
41
+ type?: string
42
+ payload?: unknown
43
+ result?: unknown
44
+ error?: string
45
+ }
46
+
47
+ const _hasWebSocket = typeof globalThis !== 'undefined' && 'WebSocket' in globalThis
48
+
49
+ const WS_OPEN = _hasWebSocket ? (globalThis.WebSocket as typeof WebSocket).OPEN : 1
50
+ const WS_CONNECTING = _hasWebSocket ? (globalThis.WebSocket as typeof WebSocket).CONNECTING : 0
51
+
52
+ const WebSocketClass: typeof WebSocket | null = _hasWebSocket
53
+ ? (globalThis.WebSocket as typeof WebSocket)
54
+ : null
55
+
56
+ class StubWebSocket {
57
+ binaryType: BinaryType = 'blob'
58
+ bufferedAmount = 0
59
+ extensions = ''
60
+ onclose: ((ev: CloseEvent) => void) | null = null
61
+ onerror: ((ev: Event) => void) | null = null
62
+ onmessage: ((ev: MessageEvent) => void) | null = null
63
+ onopen: ((ev: Event) => void) | null = null
64
+ protocol = ''
65
+ readyState = 0
66
+ url = ''
67
+ send(_data: string | ArrayBufferLike | Blob | ArrayBufferView) {
68
+ return 0 as never
69
+ }
70
+ close(_code?: number, _reason?: string) {}
71
+ addEventListener() {}
72
+ removeEventListener() {}
73
+ dispatchEvent() {
74
+ return true
75
+ }
76
+ static readonly CONNECTING = 0
77
+ static readonly OPEN = 1
78
+ static readonly CLOSING = 2
79
+ static readonly CLOSED = 3
80
+ }
81
+
82
+ type WSBase = WebSocket | StubWebSocket
83
+
84
+ function createWSBase(url: string): WSBase {
85
+ if (WebSocketClass) {
86
+ return new WebSocketClass(url)
87
+ }
88
+ return new StubWebSocket()
89
+ }
90
+
91
+ export class WSClientImpl<P extends WSProtocol = WSProtocol> implements WSClient<P> {
92
+ private ws: WSBase
93
+ private handlers = new Map<string, ((payload: unknown) => void)[]>()
94
+ private pendingRequests = new Map<string, PendingRequest>()
95
+ private statusHandlers: ((status: WSStatus) => void)[] = []
96
+ private messageBuffer: string[] = []
97
+ private _status: WSStatus = 'closed'
98
+
99
+ get status(): WSStatus {
100
+ return this._status
101
+ }
102
+
103
+ getSocket(): WebSocket | null {
104
+ return (this.ws instanceof StubWebSocket ? null : this.ws) as WebSocket | null
105
+ }
106
+
107
+ constructor(url: string | URL) {
108
+ this.ws = createWSBase(url.toString())
109
+ this.attachSocket()
110
+ }
111
+
112
+ private attachSocket() {
113
+ const ws = this.ws
114
+ if (ws.readyState === WS_OPEN) {
115
+ this._status = 'open'
116
+ } else if (ws.readyState === WS_CONNECTING) {
117
+ this._status = 'connecting'
118
+ } else {
119
+ this._status = 'closed'
120
+ }
121
+ ws.onmessage = msg => this.handleMessage(msg)
122
+ ws.onclose = () => {
123
+ if (this._status !== 'closed') {
124
+ this.handleClose()
125
+ }
126
+ }
127
+ ws.onerror = () => {
128
+ if (this._status !== 'closed') {
129
+ this.updateStatus('closed')
130
+ }
131
+ }
132
+ ws.onopen = () => {
133
+ if (this._status !== 'open') {
134
+ this.handleOpen()
135
+ }
136
+ }
137
+ }
138
+
139
+ private handleOpen() {
140
+ this.updateStatus('open')
141
+ while (this.messageBuffer.length > 0) {
142
+ const msg = this.messageBuffer.shift()
143
+ if (msg) this.ws.send(msg)
144
+ }
145
+ }
146
+
147
+ private handleClose() {
148
+ this.updateStatus('closed')
149
+ }
150
+
151
+ private handleMessage(event: MessageEvent) {
152
+ try {
153
+ const data: WSMessageBase = JSON.parse(event.data)
154
+
155
+ if ('id' in data && !('method' in data)) {
156
+ const pending = this.pendingRequests.get(data.id!)
157
+ if (pending) {
158
+ clearTimeout(pending.timer)
159
+ this.pendingRequests.delete(data.id!)
160
+ if (data.error) pending.reject(new Error(data.error))
161
+ else pending.resolve(data.result)
162
+ }
163
+ } else if ('type' in data) {
164
+ const callbacks = this.handlers.get(data.type!)
165
+ callbacks?.forEach(cb => cb(data.payload))
166
+ }
167
+ } catch (e) {
168
+ console.error('Failed to parse WS message', e)
169
+ }
170
+ }
171
+
172
+ private updateStatus(status: WSStatus) {
173
+ this._status = status
174
+ this.statusHandlers.forEach(h => h(status))
175
+ }
176
+
177
+ async call<K extends keyof P['rpc']>(
178
+ method: K,
179
+ params: P['rpc'][K] extends { in: infer I } ? I : never,
180
+ timeout = 10000
181
+ ): Promise<P['rpc'][K] extends { out: infer O } ? O : never> {
182
+ return new Promise((resolve, reject) => {
183
+ const id = Math.random().toString(36).slice(2)
184
+ const timer = setTimeout(() => {
185
+ this.pendingRequests.delete(id)
186
+ reject(new Error(`RPC Timeout: ${String(method)}`))
187
+ }, timeout)
188
+
189
+ this.pendingRequests.set(id, {
190
+ resolve: resolve as (val: unknown) => void,
191
+ reject,
192
+ timer,
193
+ })
194
+
195
+ this.sendRaw({ id, method, params })
196
+ })
197
+ }
198
+
199
+ emit<K extends keyof P['events']>(type: K, payload: P['events'][K]) {
200
+ this.sendRaw({ type, payload })
201
+ }
202
+
203
+ on<K extends keyof P['events']>(type: K, handler: (payload: P['events'][K]) => void) {
204
+ const list = this.handlers.get(type as string) || []
205
+ list.push(handler as (payload: unknown) => void)
206
+ this.handlers.set(type as string, list)
207
+ return () => {
208
+ const filtered = (this.handlers.get(type as string) || []).filter(h => h !== handler)
209
+ this.handlers.set(type as string, filtered)
210
+ }
211
+ }
212
+
213
+ onStatusChange(handler: (status: WSStatus) => void) {
214
+ this.statusHandlers.push(handler)
215
+ return () => {
216
+ this.statusHandlers = this.statusHandlers.filter(h => h !== handler)
217
+ }
218
+ }
219
+
220
+ close(): void {
221
+ this.ws.close()
222
+ }
223
+
224
+ private sendRaw(data: unknown) {
225
+ const msg = JSON.stringify(data)
226
+ if (this.ws.readyState === WS_OPEN) {
227
+ this.ws.send(msg)
228
+ } else {
229
+ this.messageBuffer.push(msg)
230
+ }
231
+ }
232
+ }
233
+
234
+ export function createWSClient<P extends WSProtocol>(url: string | URL): WSClient<P> {
235
+ return new WSClientImpl<P>(url)
236
+ }
@@ -0,0 +1,2 @@
1
+ export { useWebSocket } from './useWebSocket'
2
+ export { useSSE } from './useSSE'
@@ -0,0 +1,57 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import type { SSEClient, SSEProtocol } from '@shared/schemas'
3
+
4
+ type SSEStatus = 'connecting' | 'open' | 'closed'
5
+
6
+ interface UseSSEReturn<T extends SSEProtocol> {
7
+ status: SSEStatus
8
+ connect: () => Promise<void>
9
+ disconnect: () => void
10
+ client: SSEClient<T> | null
11
+ }
12
+
13
+ export function useSSE<T extends SSEProtocol>(route: () => Promise<SSEClient<T>>): UseSSEReturn<T> {
14
+ const [status, setStatus] = useState<SSEStatus>('closed')
15
+ const clientRef = useRef<SSEClient<T> | null>(null)
16
+
17
+ const connect = useCallback(async () => {
18
+ if (clientRef.current) return
19
+
20
+ setStatus('connecting')
21
+
22
+ try {
23
+ const client = await route()
24
+ clientRef.current = client
25
+
26
+ client.onStatusChange((newStatus: 'connecting' | 'open' | 'closed') => {
27
+ setStatus(newStatus)
28
+ })
29
+
30
+ setStatus(client.status)
31
+ } catch (error) {
32
+ console.error('Failed to connect SSE:', error)
33
+ setStatus('closed')
34
+ }
35
+ }, [route])
36
+
37
+ const disconnect = useCallback(() => {
38
+ if (clientRef.current) {
39
+ clientRef.current.abort()
40
+ clientRef.current = null
41
+ setStatus('closed')
42
+ }
43
+ }, [])
44
+
45
+ useEffect(() => {
46
+ return () => {
47
+ disconnect()
48
+ }
49
+ }, [disconnect])
50
+
51
+ return {
52
+ status,
53
+ connect,
54
+ disconnect,
55
+ client: clientRef.current,
56
+ }
57
+ }