create-nexwire 4.0.0 → 4.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 (173) hide show
  1. package/README.md +5 -4
  2. package/express/README.md +13 -7
  3. package/express/package.json +2 -2
  4. package/express/src/config/session.ts +1 -1
  5. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  6. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  7. package/express/src/database/schema.ts +3 -3
  8. package/express/src/framework/cache/cache.ts +82 -81
  9. package/express/src/framework/database/seed.ts +1 -1
  10. package/express/src/framework/facade.ts +1 -24
  11. package/express/src/framework/http/app.ts +5 -5
  12. package/express/src/framework/http/openapi.ts +1 -1
  13. package/express/src/framework/http/router.ts +3 -18
  14. package/express/src/framework/http/types.ts +19 -0
  15. package/express/src/framework/http/validation.ts +18 -18
  16. package/express/src/framework/maker-cli/middleware/core.mjs +41 -47
  17. package/express/src/framework/maker-cli/module/core.mjs +920 -684
  18. package/express/src/framework/maker-cli/module/index.mjs +202 -140
  19. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  20. package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +40 -14
  21. package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +40 -15
  22. package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +42 -16
  23. package/express/src/framework/maker-cli/stubs/controller/standalone.ts.stub +47 -0
  24. package/express/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  25. package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  26. package/express/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  27. package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  28. package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  29. package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  30. package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -50
  31. package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  32. package/express/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  33. package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  34. package/express/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +113 -0
  35. package/express/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  36. package/express/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  37. package/express/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  38. package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  39. package/express/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  40. package/express/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  41. package/express/src/framework/maker-cli/utils/naming.mjs +56 -13
  42. package/express/src/framework/modules/discover.ts +57 -54
  43. package/express/src/framework/notification/index.ts +1 -1
  44. package/express/src/framework/realtime/broadcast.ts +46 -46
  45. package/express/src/framework/server.ts +2 -1
  46. package/express/src/framework/session/session.ts +102 -98
  47. package/express/src/framework/support/mail.ts +48 -48
  48. package/express/src/framework/support/status-codes.ts +75 -0
  49. package/express/src/middlewares/auth-middleware.ts +1 -1
  50. package/express/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  51. package/express/src/modules/auth/__tests__/login.test.ts +171 -0
  52. package/express/src/modules/auth/__tests__/logout.test.ts +60 -0
  53. package/express/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  54. package/express/src/modules/auth/__tests__/me.test.ts +91 -0
  55. package/express/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  56. package/express/src/modules/auth/__tests__/register.test.ts +291 -0
  57. package/express/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  58. package/express/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  59. package/express/src/modules/auth/controllers/auth.ts +254 -0
  60. package/express/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -15
  61. package/{hono/src/modules/auth/database/models/notifications.ts → express/src/modules/auth/database/models/notification.ts} +1 -1
  62. package/express/src/modules/auth/database/models/role.ts +1 -1
  63. package/express/src/modules/auth/database/models/user.ts +1 -1
  64. package/express/src/modules/auth/facade.ts +12 -0
  65. package/express/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +3 -4
  66. package/express/src/modules/auth/jobs/registeruser.ts +23 -23
  67. package/express/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  68. package/express/src/modules/auth/routes/role.ts +33 -33
  69. package/{hono/src/modules/auth/controllers/auth.schema.ts → express/src/modules/auth/schemas/auth.ts} +102 -102
  70. package/express/src/modules/auth/services/forgetpassword.ts +66 -0
  71. package/express/src/modules/auth/services/login.ts +63 -0
  72. package/express/src/modules/auth/services/logout.ts +25 -0
  73. package/express/src/modules/auth/services/logoutalldevices.ts +19 -0
  74. package/express/src/modules/auth/services/me.ts +31 -0
  75. package/express/src/modules/auth/services/refreshtoken.ts +110 -0
  76. package/express/src/modules/auth/services/register.ts +141 -0
  77. package/express/src/modules/auth/services/resetpassword.ts +69 -0
  78. package/express/src/modules/auth/services/verifyemail.ts +77 -0
  79. package/express/src/modules/auth/types/auth.ts +31 -0
  80. package/express/src/resources/src/assets/images/logo.png +0 -0
  81. package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -0
  82. package/express/src/resources/src/pages/auth/login.vue +1 -1
  83. package/express/src/storage/database/nexwire.sqlite +0 -0
  84. package/hono/README.md +13 -7
  85. package/hono/package.json +1 -1
  86. package/hono/src/config/session.ts +1 -1
  87. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  88. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  89. package/hono/src/database/schema.ts +3 -3
  90. package/hono/src/framework/cache/cache.ts +82 -81
  91. package/hono/src/framework/database/seed.ts +1 -1
  92. package/hono/src/framework/http/app.ts +5 -5
  93. package/hono/src/framework/http/validation.ts +21 -21
  94. package/hono/src/framework/maker-cli/middleware/core.mjs +40 -46
  95. package/hono/src/framework/maker-cli/module/core.mjs +401 -168
  96. package/hono/src/framework/maker-cli/module/index.mjs +73 -11
  97. package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +28 -16
  98. package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +28 -17
  99. package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +28 -16
  100. package/hono/src/framework/maker-cli/stubs/controller/standalone.ts.stub +56 -0
  101. package/hono/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  102. package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  103. package/hono/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  104. package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  105. package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  106. package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  107. package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -49
  108. package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  109. package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  110. package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  111. package/hono/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +112 -0
  112. package/hono/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  113. package/hono/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  114. package/hono/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  115. package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  116. package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  117. package/hono/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  118. package/hono/src/framework/maker-cli/utils/naming.mjs +56 -13
  119. package/hono/src/framework/modules/discover.ts +57 -54
  120. package/hono/src/framework/notification/index.ts +1 -1
  121. package/hono/src/framework/realtime/broadcast.ts +46 -46
  122. package/hono/src/framework/server.ts +1 -1
  123. package/hono/src/framework/session/session.ts +121 -116
  124. package/hono/src/middlewares/auth-middleware.ts +1 -1
  125. package/hono/src/modules/auth/__tests__/forgetpass.test.ts +170 -0
  126. package/hono/src/modules/auth/__tests__/login.test.ts +154 -0
  127. package/hono/src/modules/auth/__tests__/logout.test.ts +53 -0
  128. package/hono/src/modules/auth/__tests__/logoutalldevices.test.ts +34 -0
  129. package/hono/src/modules/auth/__tests__/me.test.ts +87 -0
  130. package/hono/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  131. package/hono/src/modules/auth/__tests__/register.test.ts +258 -0
  132. package/hono/src/modules/auth/__tests__/resetpass.test.ts +214 -0
  133. package/hono/src/modules/auth/__tests__/verifyemail.test.ts +189 -0
  134. package/hono/src/modules/auth/controllers/auth.ts +238 -0
  135. package/hono/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -25
  136. package/{express/src/modules/auth/database/models/notifications.ts → hono/src/modules/auth/database/models/notification.ts} +1 -1
  137. package/hono/src/modules/auth/database/models/role.ts +1 -1
  138. package/hono/src/modules/auth/database/models/user.ts +1 -1
  139. package/hono/src/modules/auth/facade.ts +12 -0
  140. package/hono/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +5 -6
  141. package/hono/src/modules/auth/jobs/registeruser.ts +23 -23
  142. package/hono/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  143. package/hono/src/modules/auth/routes/role.ts +33 -33
  144. package/{express/src/modules/auth/controllers/auth.schema.ts → hono/src/modules/auth/schemas/auth.ts} +102 -102
  145. package/hono/src/modules/auth/services/forgetpassword.ts +58 -0
  146. package/hono/src/modules/auth/services/login.ts +61 -0
  147. package/hono/src/modules/auth/services/logout.ts +24 -0
  148. package/hono/src/modules/auth/services/logoutalldevices.ts +19 -0
  149. package/hono/src/modules/auth/services/me.ts +31 -0
  150. package/hono/src/modules/auth/services/refreshtoken.ts +110 -0
  151. package/hono/src/modules/auth/services/register.ts +132 -0
  152. package/hono/src/modules/auth/services/resetpassword.ts +69 -0
  153. package/hono/src/modules/auth/services/verifyemail.ts +77 -0
  154. package/hono/src/modules/auth/types/auth.ts +31 -0
  155. package/hono/src/resources/src/assets/images/logo.png +0 -0
  156. package/hono/src/storage/database/nexwire.sqlite +0 -0
  157. package/package.json +1 -1
  158. package/express/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  159. package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -418
  160. package/express/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  161. package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  162. package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  163. package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  164. package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  165. package/express/src/modules/auth/controllers/auth.controller.ts +0 -358
  166. package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  167. package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -505
  168. package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  169. package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  170. package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  171. package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  172. package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  173. package/hono/src/modules/auth/controllers/auth.controller.ts +0 -413
@@ -0,0 +1,69 @@
1
+ import { and, eq, gt } from "drizzle-orm";
2
+ import { db, password } from "@/framework/facade.js";
3
+ import { passwordResetTokens, refreshTokens, users } from "@/modules/auth/database/models/user.js";
4
+ import { hashResetToken } from "@/modules/auth/helpers/auth.js";
5
+ import type { ResetPasswordInput } from "@/modules/auth/types/auth.js";
6
+
7
+ export type ResetPasswordResult = { kind: "invalid_token"; message: string } | { kind: "reset"; message: string };
8
+
9
+ export const resetPasswordService = {
10
+ resetPassword: async (body: ResetPasswordInput): Promise<ResetPasswordResult> => {
11
+ const user = await db.query.users.findFirst({
12
+ where: eq(users.email, body.email)
13
+ });
14
+
15
+ if (!user) {
16
+ return {
17
+ kind: "invalid_token",
18
+ message: "Invalid or expired reset token"
19
+ };
20
+ }
21
+
22
+ const hashedPassword = await password.hashPassword(body.password);
23
+ const hashedToken = hashResetToken(body.token);
24
+ const now = new Date();
25
+
26
+ try {
27
+ const consumed = await db.transaction(async (tx) => {
28
+ // Consume the token atomically inside the transaction.
29
+ // If anything below fails, the delete rolls back and the
30
+ // user can retry with the same token.
31
+ const [row] = await tx
32
+ .delete(passwordResetTokens)
33
+ .where(
34
+ and(
35
+ eq(passwordResetTokens.email, body.email),
36
+ eq(passwordResetTokens.token, hashedToken),
37
+ gt(passwordResetTokens.expiresAt, now)
38
+ )
39
+ )
40
+ .returning();
41
+
42
+ if (!row) return false;
43
+
44
+ await tx.update(users).set({ password: hashedPassword, updatedAt: now }).where(eq(users.id, user.id));
45
+
46
+ await tx.update(refreshTokens).set({ revoked: 1 }).where(eq(refreshTokens.userId, user.id));
47
+
48
+ return true;
49
+ });
50
+
51
+ if (!consumed) {
52
+ return {
53
+ kind: "invalid_token",
54
+ message: "Invalid or expired reset token"
55
+ };
56
+ }
57
+
58
+ return { kind: "reset", message: "Password reset successfully" };
59
+ } catch (err) {
60
+ // Token was NOT consumed — the transaction rolled back, so the
61
+ // user can retry with the same token. Log so failures aren't silent.
62
+ // logger.error({ err, email: body.email }, "password reset failed");
63
+ return {
64
+ kind: "invalid_token",
65
+ message: "Invalid or expired reset token"
66
+ };
67
+ }
68
+ }
69
+ };
@@ -0,0 +1,77 @@
1
+ import { and, eq, gt } from "drizzle-orm";
2
+ import { authConfig } from "@/config/index.js";
3
+ import { db } from "@/framework/facade.js";
4
+ import { emailVerificationTokens, users } from "@/modules/auth/database/models/user.js";
5
+ import { hashEmailVerificationToken } from "@/modules/auth/helpers/auth.js";
6
+ import type { VerifyEmailInput } from "@/modules/auth/types/auth.js";
7
+
8
+ export type VerifyEmailResult =
9
+ | { kind: "not_required"; message: string }
10
+ | { kind: "invalid_token"; message: string }
11
+ | { kind: "verified"; message: string };
12
+
13
+ export const verifyEmailService = {
14
+ verifyEmail: async (body: VerifyEmailInput): Promise<VerifyEmailResult> => {
15
+ if (!authConfig.requireEmailVerification) {
16
+ return {
17
+ kind: "not_required",
18
+ message: "Email verification is not required"
19
+ };
20
+ }
21
+
22
+ const user = await db.query.users.findFirst({
23
+ where: eq(users.email, body.email)
24
+ });
25
+
26
+ if (!user) {
27
+ return {
28
+ kind: "invalid_token",
29
+ message: "Invalid or expired verification token"
30
+ };
31
+ }
32
+
33
+ const hashedToken = hashEmailVerificationToken(body.token);
34
+ const now = new Date();
35
+
36
+ try {
37
+ const verified = await db.transaction(async (tx) => {
38
+ // Consume the token atomically INSIDE the transaction.
39
+ // If the update below fails, the delete rolls back and the
40
+ // user can retry with the same token.
41
+ const [consumed] = await tx
42
+ .delete(emailVerificationTokens)
43
+ .where(
44
+ and(
45
+ eq(emailVerificationTokens.email, body.email),
46
+ eq(emailVerificationTokens.token, hashedToken),
47
+ gt(emailVerificationTokens.expiresAt, now)
48
+ )
49
+ )
50
+ .returning();
51
+
52
+ if (!consumed) return false;
53
+
54
+ await tx.update(users).set({ emailVerifiedAt: now, updatedAt: now }).where(eq(users.id, user.id));
55
+
56
+ return true;
57
+ });
58
+
59
+ if (!verified) {
60
+ return {
61
+ kind: "invalid_token",
62
+ message: "Invalid or expired verification token"
63
+ };
64
+ }
65
+
66
+ return { kind: "verified", message: "Email verified successfully" };
67
+ } catch (err) {
68
+ // Token was NOT consumed — the transaction rolled back, so the
69
+ // user can retry with the same token. Log so failures aren't silent.
70
+ // logger.error({ err, email: body.email }, "email verification failed");
71
+ return {
72
+ kind: "invalid_token",
73
+ message: "Invalid or expired verification token"
74
+ };
75
+ }
76
+ }
77
+ };
@@ -0,0 +1,31 @@
1
+ export type RegisterInput = {
2
+ name: string;
3
+ email: string;
4
+ password: string;
5
+ remember?: boolean;
6
+ };
7
+
8
+ export type LoginInput = {
9
+ email: string;
10
+ password: string;
11
+ remember?: boolean;
12
+ };
13
+
14
+ export type ForgotPasswordInput = {
15
+ email: string;
16
+ };
17
+
18
+ export type ResetPasswordInput = {
19
+ email: string;
20
+ token: string;
21
+ password: string;
22
+ };
23
+
24
+ export type VerifyEmailInput = {
25
+ email: string;
26
+ token: string;
27
+ };
28
+
29
+ export type RefreshTokenInput = {
30
+ refresh_token: string;
31
+ };
@@ -66,6 +66,7 @@ const onSubmit = async () => {
66
66
  },
67
67
  {
68
68
  onSuccess: (res) => {
69
+ console.log(res);
69
70
  isError.value = false;
70
71
  message.value = res.data.message;
71
72
  form.reset();
@@ -50,10 +50,10 @@
50
50
  import { useHead } from "@vueuse/head";
51
51
  import { ref } from "vue";
52
52
  import { useRouter } from "vue-router";
53
+ import Button from "@/components/Button.vue";
53
54
  import Checkbox from "@/components/Checkbox.vue";
54
55
  import Input from "@/components/Input.vue";
55
56
  import InputPasswordToggle from "@/components/InputPasswordToggle.vue";
56
- import Button from "@/components/Button.vue";
57
57
  import { useGumForm } from "@/plugins/gum";
58
58
  import { useAuthStore } from "@/stores/auth";
59
59
 
package/hono/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <h3 align="center">Full-stack TypeScript framework for modern web applications</h3>
8
8
 
9
9
  <p align="center">
10
- <a href="https://niyamulahsan.github.io/nexwire"><img src="https://img.shields.io/badge/docs-3b8eed" alt="Documentation"></a>
10
+ <a href="https://niyamulahsan.github.io/nexwire"><img src="https://img.shields.io/badge/docs%20%7C%20nexwire-3b8eed" alt="Documentation"></a>
11
11
  <a href="https://www.npmjs.com/package/create-nexwire"><img src="https://img.shields.io/npm/v/create-nexwire" alt="npm version"></a>
12
12
  <a href="https://www.npmjs.com/package/create-nexwire"><img src="https://img.shields.io/npm/dt/create-nexwire" alt="npm downloads"></a>
13
13
  <a href="https://github.com/niyamulahsan/nexwire/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
@@ -59,8 +59,8 @@ Works with any package manager: `pnpm`, `yarn`, or `bun`.
59
59
 
60
60
  All examples use `npm` as the default. nexwire works with any major package manager:
61
61
 
62
- | Manager | Create project | Run commands |
63
- | -------- | ---------------------------------- | --------------------- |
62
+ | Manager | Create project | Run commands |
63
+ | -------- | ----------------------------------- | --------------------- |
64
64
  | **npm** | `npm create nexwire@latest my-app` | `npm run maker <cmd>` |
65
65
  | **pnpm** | `pnpm create nexwire@latest my-app` | `pnpm maker <cmd>` |
66
66
  | **yarn** | `yarn create nexwire@latest my-app` | `yarn maker <cmd>` |
@@ -73,7 +73,7 @@ nexwire runs on **Node.js** or **Bun** — pick whichever fits your deployment:
73
73
 
74
74
  | Runtime | Minimum version | Notes |
75
75
  | ----------- | --------------- | --------------------------------------------------------------------- |
76
- | **Node.js** | `>= 22.12` | Default. Uses `node` in Dockerfile. |
76
+ | **Node.js** | `>= 22.12` | Default. Uses `node` in Dockerfile. |
77
77
  | **Bun** | `>= 1.3` | Pass `--runtime=bun` to `deploy:init`. Uses `oven/bun` in Dockerfile. |
78
78
 
79
79
  ## Features
@@ -114,14 +114,20 @@ Every feature is a self-contained module under `src/modules/<name>/`:
114
114
 
115
115
  ```
116
116
  src/modules/posts/
117
+ ├── __tests__/ # Unit tests
117
118
  ├── console/ # CLI commands & scheduled tasks
118
- ├── controllers/ # Request handlers + Zod schemas
119
+ ├── controllers/ # Request handlers
119
120
  ├── database/
120
121
  │ ├── models/ # Drizzle table definitions
121
- │ └── seeders/ # Test data generators
122
+ │ └── seeders/ # Test data
123
+ ├── helpers/ # Pure functions — no I/O
122
124
  ├── jobs/ # BullMQ queue handlers
125
+ ├── middlewares/ # Module-local middleware
123
126
  ├── routes/ # HTTP route definitions (auto-discovered)
124
- └── __test__/ # Unit test
127
+ ├── schemas/ # Zod validation schemas
128
+ ├── services/ # Business logic — DB, files, network
129
+ ├── types/ # Shared TypeScript types
130
+ └── facade.ts # Public API for other modules
125
131
  ```
126
132
 
127
133
  Modules are **auto-discovered** — no manual registration. Create one with:
package/hono/package.json CHANGED
@@ -91,7 +91,7 @@
91
91
  "vitest": "^4.1.10"
92
92
  },
93
93
  "engines": {
94
- "node": ">=22.12.0",
94
+ "node": ">=22",
95
95
  "bun": ">=1.3",
96
96
  "pnpm": ">=11",
97
97
  "yarn": ">=4"
@@ -1,4 +1,4 @@
1
- import { cookieConfig } from "./index.js";
1
+ import { cookieConfig } from "./cookie.js";
2
2
  import { redisConfig } from "./redis.js";
3
3
 
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "6",
3
3
  "dialect": "sqlite",
4
- "id": "a1444283-a2b7-4ceb-98ab-b2c5d277b414",
4
+ "id": "8de12a3d-bc9b-4ccc-8b83-c893a59cd33b",
5
5
  "prevId": "00000000-0000-0000-0000-000000000000",
6
6
  "tables": {
7
7
  "notifications": {
@@ -5,7 +5,7 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "6",
8
- "when": 1790701575747,
8
+ "when": 1790958354726,
9
9
  "tag": "0000_init",
10
10
  "breakpoints": true
11
11
  }
@@ -1,3 +1,3 @@
1
- export * from "../modules/auth/database/models/notifications.js";
2
- export * from "../modules/auth/database/models/role.js";
3
- export * from "../modules/auth/database/models/user.js";
1
+ export * from "../modules/auth/database/models/notification.js";
2
+ export * from "../modules/auth/database/models/role.js";
3
+ export * from "../modules/auth/database/models/user.js";
@@ -1,81 +1,82 @@
1
- import { cacheConfig, redisConfig } from "@/config/index.js";
2
- import { redisClientIfReady } from "@/framework/redis/client.js";
3
-
4
- function cacheKey(key: string) {
5
- return `${cacheConfig.keyPrefix}:${key}`;
6
- }
7
-
8
- /**
9
- * Why: Provides Redis-backed cache helpers with graceful fallback behavior.
10
- * When: Controllers/jobs need temporary computed data caching.
11
- * Where: Application module code via facade.
12
- * How: Uses namespaced Redis keys and JSON serialization.
13
- */
14
- export const cache = {
15
- /**
16
- * Why: Reads cached JSON value by key.
17
- * When: A feature wants fast lookup before recomputing.
18
- * Where: Controllers/jobs/services using shared cache.
19
- * How: Fetches namespaced Redis key and parses JSON.
20
- */
21
- async get<T>(key: string, fallback: T | null = null) {
22
- const client = redisClientIfReady();
23
- if (!client) return fallback;
24
-
25
- const value = await client.get(cacheKey(key));
26
- return value ? (JSON.parse(value) as T) : fallback;
27
- },
28
-
29
- /**
30
- * Why: Stores value in cache with TTL.
31
- * When: Data should be reused for a limited period.
32
- * Where: Application code caching expensive results.
33
- * How: Serializes value and writes with Redis EX seconds.
34
- */
35
- async put(key: string, value: unknown, ttl = cacheConfig.ttlSeconds) {
36
- const client = redisClientIfReady();
37
- if (!client) return false;
38
-
39
- await client.set(cacheKey(key), JSON.stringify(value), "EX", ttl);
40
- return true;
41
- },
42
-
43
- /**
44
- * Why: Deletes a cached value by key.
45
- * When: Cached data becomes stale or must be invalidated.
46
- * Where: Write/update flows that change source data.
47
- * How: Removes namespaced Redis cache key.
48
- */
49
- async forget(key: string) {
50
- const client = redisClientIfReady();
51
- if (!client) return false;
52
-
53
- await client.del(cacheKey(key));
54
- return true;
55
- },
56
-
57
- /**
58
- * Why: Returns cached value or computes/stores a fresh one.
59
- * When: A feature needs cache-aside behavior.
60
- * Where: Read paths with potentially expensive callbacks.
61
- * How: Attempts get first, then executes callback and put.
62
- */
63
- async remember<T>(key: string, ttl: number, callback: () => Promise<T>) {
64
- const cached = await cache.get<T>(key);
65
- if (cached !== null) return cached;
66
-
67
- const fresh = await callback();
68
- await this.put(key, fresh, ttl);
69
- return fresh;
70
- },
71
-
72
- /**
73
- * Why: Indicates whether Redis cache backend is currently usable.
74
- * When: Runtime checks need to branch on cache availability.
75
- * Where: Health/status endpoints and conditional flows.
76
- * How: Confirms REDIS flag and ready Redis client instance.
77
- */
78
- isAvailable() {
79
- return redisConfig.enabled && redisClientIfReady() !== null;
80
- }
81
- };
1
+ import { cacheConfig, redisConfig } from "@/config/index.js";
2
+ import { redisClientIfReady } from "@/framework/redis/client.js";
3
+
4
+ function cacheKey(key: string) {
5
+ return `${cacheConfig.keyPrefix}:${key}`;
6
+ }
7
+
8
+ /**
9
+ * Why: Provides Redis-backed cache helpers with graceful fallback behavior.
10
+ * When: Controllers/jobs need temporary computed data caching.
11
+ * Where: Application module code via facade.
12
+ * How: Uses namespaced Redis keys and JSON serialization.
13
+ */
14
+ export const cache = {
15
+ /**
16
+ * Why: Reads cached JSON value by key.
17
+ * When: A feature wants fast lookup before recomputing.
18
+ * Where: Controllers/jobs/services using shared cache.
19
+ * How: Fetches namespaced Redis key and parses JSON.
20
+ */
21
+ async get<T>(key: string, fallback: T | null = null) {
22
+ const client = redisClientIfReady();
23
+ if (!client) return fallback;
24
+
25
+ const value = await client.get(cacheKey(key));
26
+ return value ? (JSON.parse(value) as T) : fallback;
27
+ },
28
+
29
+ async set(key: string, value: unknown, ttl = cacheConfig.ttlSeconds) {
30
+ const client = redisClientIfReady();
31
+ if (!client) return false;
32
+
33
+ await client.set(cacheKey(key), JSON.stringify(value), "EX", ttl);
34
+ return true;
35
+ },
36
+
37
+ /**
38
+ * @deprecated Use set() instead.
39
+ */
40
+ async put(key: string, value: unknown, ttl = cacheConfig.ttlSeconds) {
41
+ return this.set(key, value, ttl);
42
+ },
43
+
44
+ /**
45
+ * Why: Deletes a cached value by key.
46
+ * When: Cached data becomes stale or must be invalidated.
47
+ * Where: Write/update flows that change source data.
48
+ * How: Removes namespaced Redis cache key.
49
+ */
50
+ async forget(key: string) {
51
+ const client = redisClientIfReady();
52
+ if (!client) return false;
53
+
54
+ await client.del(cacheKey(key));
55
+ return true;
56
+ },
57
+
58
+ /**
59
+ * Why: Returns cached value or computes/stores a fresh one.
60
+ * When: A feature needs cache-aside behavior.
61
+ * Where: Read paths with potentially expensive callbacks.
62
+ * How: Attempts get first, then executes callback and put.
63
+ */
64
+ async remember<T>(key: string, ttl: number, callback: () => Promise<T>) {
65
+ const cached = await cache.get<T>(key);
66
+ if (cached !== null) return cached;
67
+
68
+ const fresh = await callback();
69
+ await this.put(key, fresh, ttl);
70
+ return fresh;
71
+ },
72
+
73
+ /**
74
+ * Why: Indicates whether Redis cache backend is currently usable.
75
+ * When: Runtime checks need to branch on cache availability.
76
+ * Where: Health/status endpoints and conditional flows.
77
+ * How: Confirms REDIS flag and ready Redis client instance.
78
+ */
79
+ isAvailable() {
80
+ return redisConfig.enabled && redisClientIfReady() !== null;
81
+ }
82
+ };
@@ -1,6 +1,6 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { closeDatabase, initDatabase } from "@/framework/database/connection.js";
2
3
  import { discoverModuleFiles, importFile } from "@/framework/modules/discover.js";
3
- import { readFile } from "node:fs/promises";
4
4
 
5
5
  const NAME_SYMBOL = Symbol.for("drizzle:Name");
6
6
  const FK_SYMBOLS = [
@@ -58,20 +58,20 @@ export function createHttpApp() {
58
58
  let allOk = true;
59
59
  try {
60
60
  database();
61
- checks["database"] = "ok";
61
+ checks.database = "ok";
62
62
  } catch {
63
- checks["database"] = "error";
63
+ checks.database = "error";
64
64
  allOk = false;
65
65
  }
66
66
  if (redisConfig.enabled) {
67
67
  if (redisClientIfReady()) {
68
- checks["redis"] = "ok";
68
+ checks.redis = "ok";
69
69
  } else {
70
- checks["redis"] = "error";
70
+ checks.redis = "error";
71
71
  allOk = false;
72
72
  }
73
73
  } else {
74
- checks["redis"] = "disabled";
74
+ checks.redis = "disabled";
75
75
  }
76
76
  const status = allOk ? 200 : 503;
77
77
  return c.json({ status: allOk ? "ready" : "not_ready", checks }, status);
@@ -1,21 +1,21 @@
1
- import type { z } from "zod";
2
-
3
- /**
4
- * Why: Centralizes schema validation with consistent error shape.
5
- * When: Non-route code needs explicit Zod validation.
6
- * Where: Controllers/helpers/services.
7
- * How: Runs safeParseAsync and throws 422 payload on failure.
8
- */
9
- export async function validate<T extends z.ZodTypeAny>(schema: T, data: unknown): Promise<z.infer<T>> {
10
- const result = await schema.safeParseAsync(data);
11
-
12
- if (!result.success) {
13
- throw {
14
- status: 422,
15
- message: "Validation failed",
16
- errors: result.error.flatten()
17
- };
18
- }
19
-
20
- return result.data;
21
- }
1
+ import type { z } from "zod";
2
+
3
+ /**
4
+ * Why: Centralizes schema validation with consistent error shape.
5
+ * When: Non-route code needs explicit Zod validation.
6
+ * Where: Controllers/helpers/services.
7
+ * How: Runs safeParseAsync and throws 422 payload on failure.
8
+ */
9
+ export async function validate<T extends z.ZodType>(schema: T, data: unknown): Promise<z.infer<T>> {
10
+ const result = await schema.safeParseAsync(data);
11
+
12
+ if (!result.success) {
13
+ throw {
14
+ status: 422,
15
+ message: "Validation failed",
16
+ errors: result.error.flatten()
17
+ };
18
+ }
19
+
20
+ return result.data;
21
+ }
@@ -1,47 +1,41 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { hasFlag } from "../utils/flags.mjs";
4
- import { assertName, pascal } from "../utils/naming.mjs";
5
-
6
- const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
7
-
8
- /** Read a stub file and replace {{key}} placeholders, removing unfilled ones. */
9
- async function stub(name, values = {}) {
10
- let content = await fs.readFile(path.join(stubsRoot, name), "utf8");
11
- for (const [key, value] of Object.entries(values)) {
12
- content = content.replaceAll(`{{${key}}}`, String(value));
13
- }
14
- return content.replace(/\{\{[A-Z0-9_]+\}\}/g, "");
15
- }
16
-
17
- /** Convert a name to camelCase (e.g. "rate-limit" -> "rateLimit"). */
18
- function camelCase(input) {
19
- const p = pascal(input);
20
- return p.charAt(0).toLowerCase() + p.slice(1);
21
- }
22
-
23
- /** Generate a middleware file in src/middlewares. */
24
- export async function makeMiddleware(rawName, flags = []) {
25
- const name = assertName(rawName, "Middleware name");
26
- const camel = camelCase(name);
27
- const dryRun = hasFlag(flags, "--dry-run");
28
- const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
29
-
30
- const filePath = path.resolve(process.cwd(), "src/middlewares", `${name}-middleware.ts`);
31
- if (dryRun) return;
32
-
33
- let exists = false;
34
- try {
35
- await fs.access(filePath);
36
- exists = true;
37
- } catch {}
38
- if (exists && !force) {
39
- throw new Error(`Middleware already exists: ${path.relative(process.cwd(), filePath)}. Re-run with --force to overwrite.`);
40
- }
41
-
42
- const content = await stub("middleware/name.ts.stub", { name, camel });
43
- await fs.mkdir(path.dirname(filePath), { recursive: true });
44
- await fs.writeFile(filePath, content);
45
- console.log(`Middleware ready: ${path.relative(process.cwd(), filePath)}`);
46
- console.log(`Apply it to a route group: group(${camel}Middleware)`);
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { hasFlag } from "../utils/flags.mjs";
4
+ import { assertName, camelCase } from "../utils/naming.mjs";
5
+
6
+ const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
7
+
8
+ /** Read a stub file and replace {{key}} placeholders, removing unfilled ones. */
9
+ async function stub(name, values = {}) {
10
+ let content = await fs.readFile(path.join(stubsRoot, name), "utf8");
11
+ for (const [key, value] of Object.entries(values)) {
12
+ content = content.replaceAll(`{{${key}}}`, String(value));
13
+ }
14
+ return content.replace(/\{\{[A-Z0-9_]+\}\}/g, "");
15
+ }
16
+
17
+ /** Generate a middleware file in src/middlewares. */
18
+ export async function makeMiddleware(rawName, flags = []) {
19
+ const name = assertName(rawName, "Middleware name");
20
+ const camel = camelCase(name);
21
+ const dryRun = hasFlag(flags, "--dry-run");
22
+ const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
23
+
24
+ const filePath = path.resolve(process.cwd(), "src/middlewares", `${name}-middleware.ts`);
25
+ if (dryRun) return;
26
+
27
+ let exists = false;
28
+ try {
29
+ await fs.access(filePath);
30
+ exists = true;
31
+ } catch {}
32
+ if (exists && !force) {
33
+ throw new Error(`Middleware already exists: ${path.relative(process.cwd(), filePath)}. Re-run with --force to overwrite.`);
34
+ }
35
+
36
+ const content = await stub("middleware/name.ts.stub", { name, camel });
37
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
38
+ await fs.writeFile(filePath, content);
39
+ console.log(`Middleware ready: ${path.relative(process.cwd(), filePath)}`);
40
+ console.log(`Apply it to a route group: group(${camel}Middleware)`);
47
41
  }