create-nexwire 4.0.0 → 4.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 (175) hide show
  1. package/README.md +5 -4
  2. package/express/.env +1 -1
  3. package/express/README.md +13 -7
  4. package/express/package.json +2 -2
  5. package/express/src/config/session.ts +1 -1
  6. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  7. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  8. package/express/src/database/schema.ts +3 -3
  9. package/express/src/framework/cache/cache.ts +82 -81
  10. package/express/src/framework/database/seed.ts +1 -1
  11. package/express/src/framework/facade.ts +1 -24
  12. package/express/src/framework/http/app.ts +5 -5
  13. package/express/src/framework/http/openapi.ts +1 -1
  14. package/express/src/framework/http/router.ts +3 -18
  15. package/express/src/framework/http/types.ts +19 -0
  16. package/express/src/framework/http/validation.ts +18 -18
  17. package/express/src/framework/maker-cli/middleware/core.mjs +41 -47
  18. package/express/src/framework/maker-cli/module/core.mjs +920 -684
  19. package/express/src/framework/maker-cli/module/index.mjs +202 -140
  20. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  21. package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +40 -14
  22. package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +40 -15
  23. package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +42 -16
  24. package/express/src/framework/maker-cli/stubs/controller/standalone.ts.stub +47 -0
  25. package/express/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  26. package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  27. package/express/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  28. package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  29. package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  30. package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  31. package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -50
  32. package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  33. package/express/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  34. package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  35. package/express/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +113 -0
  36. package/express/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  37. package/express/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  38. package/express/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  39. package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  40. package/express/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  41. package/express/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  42. package/express/src/framework/maker-cli/utils/naming.mjs +56 -13
  43. package/express/src/framework/modules/discover.ts +57 -54
  44. package/express/src/framework/notification/index.ts +1 -1
  45. package/express/src/framework/realtime/broadcast.ts +46 -46
  46. package/express/src/framework/server.ts +2 -1
  47. package/express/src/framework/session/session.ts +102 -98
  48. package/express/src/framework/support/mail.ts +48 -48
  49. package/express/src/framework/support/status-codes.ts +75 -0
  50. package/express/src/middlewares/auth-middleware.ts +1 -1
  51. package/express/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  52. package/express/src/modules/auth/__tests__/login.test.ts +171 -0
  53. package/express/src/modules/auth/__tests__/logout.test.ts +60 -0
  54. package/express/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  55. package/express/src/modules/auth/__tests__/me.test.ts +91 -0
  56. package/express/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  57. package/express/src/modules/auth/__tests__/register.test.ts +291 -0
  58. package/express/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  59. package/express/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  60. package/express/src/modules/auth/controllers/auth.ts +254 -0
  61. package/express/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -15
  62. package/{hono/src/modules/auth/database/models/notifications.ts → express/src/modules/auth/database/models/notification.ts} +1 -1
  63. package/express/src/modules/auth/database/models/role.ts +1 -1
  64. package/express/src/modules/auth/database/models/user.ts +1 -1
  65. package/express/src/modules/auth/facade.ts +12 -0
  66. package/express/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +3 -4
  67. package/express/src/modules/auth/jobs/registeruser.ts +23 -23
  68. package/express/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  69. package/express/src/modules/auth/routes/role.ts +33 -33
  70. package/{hono/src/modules/auth/controllers/auth.schema.ts → express/src/modules/auth/schemas/auth.ts} +102 -102
  71. package/express/src/modules/auth/services/forgetpassword.ts +66 -0
  72. package/express/src/modules/auth/services/login.ts +63 -0
  73. package/express/src/modules/auth/services/logout.ts +25 -0
  74. package/express/src/modules/auth/services/logoutalldevices.ts +19 -0
  75. package/express/src/modules/auth/services/me.ts +31 -0
  76. package/express/src/modules/auth/services/refreshtoken.ts +110 -0
  77. package/express/src/modules/auth/services/register.ts +141 -0
  78. package/express/src/modules/auth/services/resetpassword.ts +69 -0
  79. package/express/src/modules/auth/services/verifyemail.ts +77 -0
  80. package/express/src/modules/auth/types/auth.ts +31 -0
  81. package/express/src/resources/src/assets/images/logo.png +0 -0
  82. package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -0
  83. package/express/src/resources/src/pages/auth/login.vue +1 -1
  84. package/express/src/storage/database/nexwire.sqlite +0 -0
  85. package/hono/.env +1 -1
  86. package/hono/README.md +13 -7
  87. package/hono/package.json +1 -1
  88. package/hono/src/config/session.ts +1 -1
  89. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  90. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  91. package/hono/src/database/schema.ts +3 -3
  92. package/hono/src/framework/cache/cache.ts +82 -81
  93. package/hono/src/framework/database/seed.ts +1 -1
  94. package/hono/src/framework/http/app.ts +5 -5
  95. package/hono/src/framework/http/validation.ts +21 -21
  96. package/hono/src/framework/maker-cli/middleware/core.mjs +40 -46
  97. package/hono/src/framework/maker-cli/module/core.mjs +401 -168
  98. package/hono/src/framework/maker-cli/module/index.mjs +73 -11
  99. package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +28 -16
  100. package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +28 -17
  101. package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +28 -16
  102. package/hono/src/framework/maker-cli/stubs/controller/standalone.ts.stub +56 -0
  103. package/hono/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  104. package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  105. package/hono/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  106. package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
  107. package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
  108. package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
  109. package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -49
  110. package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
  111. package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
  112. package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
  113. package/hono/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +112 -0
  114. package/hono/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
  115. package/hono/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
  116. package/hono/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
  117. package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  118. package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  119. package/hono/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  120. package/hono/src/framework/maker-cli/utils/naming.mjs +56 -13
  121. package/hono/src/framework/modules/discover.ts +57 -54
  122. package/hono/src/framework/notification/index.ts +1 -1
  123. package/hono/src/framework/realtime/broadcast.ts +46 -46
  124. package/hono/src/framework/server.ts +1 -1
  125. package/hono/src/framework/session/session.ts +121 -116
  126. package/hono/src/middlewares/auth-middleware.ts +1 -1
  127. package/hono/src/modules/auth/__tests__/forgetpass.test.ts +170 -0
  128. package/hono/src/modules/auth/__tests__/login.test.ts +154 -0
  129. package/hono/src/modules/auth/__tests__/logout.test.ts +53 -0
  130. package/hono/src/modules/auth/__tests__/logoutalldevices.test.ts +34 -0
  131. package/hono/src/modules/auth/__tests__/me.test.ts +87 -0
  132. package/hono/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  133. package/hono/src/modules/auth/__tests__/register.test.ts +258 -0
  134. package/hono/src/modules/auth/__tests__/resetpass.test.ts +214 -0
  135. package/hono/src/modules/auth/__tests__/verifyemail.test.ts +189 -0
  136. package/hono/src/modules/auth/controllers/auth.ts +238 -0
  137. package/hono/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -25
  138. package/{express/src/modules/auth/database/models/notifications.ts → hono/src/modules/auth/database/models/notification.ts} +1 -1
  139. package/hono/src/modules/auth/database/models/role.ts +1 -1
  140. package/hono/src/modules/auth/database/models/user.ts +1 -1
  141. package/hono/src/modules/auth/facade.ts +12 -0
  142. package/hono/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +5 -6
  143. package/hono/src/modules/auth/jobs/registeruser.ts +23 -23
  144. package/hono/src/modules/auth/routes/{api.ts → index.ts} +150 -150
  145. package/hono/src/modules/auth/routes/role.ts +33 -33
  146. package/{express/src/modules/auth/controllers/auth.schema.ts → hono/src/modules/auth/schemas/auth.ts} +102 -102
  147. package/hono/src/modules/auth/services/forgetpassword.ts +58 -0
  148. package/hono/src/modules/auth/services/login.ts +61 -0
  149. package/hono/src/modules/auth/services/logout.ts +24 -0
  150. package/hono/src/modules/auth/services/logoutalldevices.ts +19 -0
  151. package/hono/src/modules/auth/services/me.ts +31 -0
  152. package/hono/src/modules/auth/services/refreshtoken.ts +110 -0
  153. package/hono/src/modules/auth/services/register.ts +132 -0
  154. package/hono/src/modules/auth/services/resetpassword.ts +69 -0
  155. package/hono/src/modules/auth/services/verifyemail.ts +77 -0
  156. package/hono/src/modules/auth/types/auth.ts +31 -0
  157. package/hono/src/resources/src/assets/images/logo.png +0 -0
  158. package/hono/src/storage/database/nexwire.sqlite +0 -0
  159. package/package.json +1 -1
  160. package/express/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  161. package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -418
  162. package/express/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  163. package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  164. package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  165. package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  166. package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  167. package/express/src/modules/auth/controllers/auth.controller.ts +0 -358
  168. package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
  169. package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -505
  170. package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
  171. package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
  172. package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
  173. package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
  174. package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
  175. package/hono/src/modules/auth/controllers/auth.controller.ts +0 -413
package/README.md CHANGED
@@ -7,8 +7,9 @@
7
7
  <h3 align="center">Stop wiring the stack together. Ship the app.</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
+ <a href="https://www.npmjs.com/package/create-nexwire"><img src="https://img.shields.io/npm/dt/create-nexwire" alt="npm downloads"></a>
12
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>
13
14
  </p>
14
15
 
@@ -48,7 +49,7 @@ Your API is live at `http://localhost:3000`, Scalar docs at `/api-docs`, and the
48
49
 
49
50
  The scaffold already ships a pre-migrated, pre-seeded SQLite database, so there is no migration step before `maker dev`. If you point `DATABASE_URL` at MySQL or PostgreSQL, run `npm run maker db:migrate -- --seed` once to build and populate that database instead.
50
51
 
51
- Requires **Node.js >= 22.12** or **Bun >= 1.3**.
52
+ Requires **Node.js >= 22** or **Bun >= 1.3**.
52
53
 
53
54
  ### Current directory
54
55
 
@@ -79,7 +80,7 @@ nexwire runs on **Node.js** or **Bun** — pick whichever fits your deployment:
79
80
 
80
81
  | Runtime | Minimum version | Notes |
81
82
  | ----------- | --------------- | --------------------------------------------------------------------- |
82
- | **Node.js** | `>= 22.12` | Default. Uses `node` in Dockerfile. |
83
+ | **Node.js** | `>= 22` | Default. Uses `node` in Dockerfile. |
83
84
  | **Bun** | `>= 1.3` | Pass `--runtime=bun` to `deploy:init`. Uses `oven/bun` in Dockerfile. |
84
85
 
85
86
  ## Demo
@@ -92,7 +93,7 @@ Real recordings from a freshly scaffolded project — no mockups, no staging dat
92
93
  <img src="https://raw.githubusercontent.com/niyamulahsan/nexwire/main/packages/docs/public/screenshots/dev-mode.gif" alt="nexwire dev mode: API, UI, API docs, queue dashboard and mail UI booting in one command" width="100%">
93
94
  </p>
94
95
 
95
- **Real deploy** — the same project running on a live server, with HTTPS, process supervision and zero-downtime restarts:
96
+ **Real deploy** — the same framework running on a live server as a real project, with HTTPS, process supervision, and zero-downtime restarts:
96
97
 
97
98
  <p align="center">
98
99
  <img src="https://raw.githubusercontent.com/niyamulahsan/nexwire/main/packages/docs/public/screenshots/real-deploy.gif" alt="nexwire deployed to a real server with HTTPS and process supervision" width="100%">
package/express/.env CHANGED
@@ -26,7 +26,7 @@ MAIL_PASSWORD=
26
26
 
27
27
  # ── Redis ──────────────────────────────────────────
28
28
  # Set true to enable cache, session, queue, events, and Queue Dashboard
29
- REDIS=false
29
+ REDIS=true
30
30
  # Shared key prefix for cache, session, queue, events, and dashboards
31
31
  REDIS_URL=redis://127.0.0.1:6379
32
32
  REDIS_PREFIX=nexwire
package/express/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:
@@ -100,9 +100,9 @@
100
100
  "vitest": "^4.1.10"
101
101
  },
102
102
  "engines": {
103
- "node": ">=22.12.0",
103
+ "node": ">=22",
104
104
  "bun": ">=1.3",
105
105
  "pnpm": ">=11",
106
106
  "yarn": ">=4"
107
107
  }
108
- }
108
+ }
@@ -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": "e998e08f-4fb2-46ee-baa2-e2480d4b685a",
4
+ "id": "54057efa-2d33-41f3-aa7a-1f1a3e89a382",
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": 1790701528057,
8
+ "when": 1790961550566,
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 = [
@@ -4,30 +4,7 @@ export { registerOpenApiRoute } from "@/framework/http/openapi.js";
4
4
  export type { RouteConfig } from "@/framework/http/router.js";
5
5
  export { createRoute, createRouter, group, jsonContent } from "@/framework/http/router.js";
6
6
  export { validate } from "@/framework/http/validation.js";
7
- export const HttpStatusCodes = {
8
- CONTINUE: 100,
9
- SWITCHING_PROTOCOLS: 101,
10
- OK: 200,
11
- CREATED: 201,
12
- ACCEPTED: 202,
13
- NO_CONTENT: 204,
14
- BAD_REQUEST: 400,
15
- UNAUTHORIZED: 401,
16
- FORBIDDEN: 403,
17
- NOT_FOUND: 404,
18
- METHOD_NOT_ALLOWED: 405,
19
- CONFLICT: 409,
20
- GONE: 410,
21
- PAYLOAD_TOO_LARGE: 413,
22
- URI_TOO_LONG: 414,
23
- UNSUPPORTED_MEDIA_TYPE: 415,
24
- UNPROCESSABLE_ENTITY: 422,
25
- TOO_MANY_REQUESTS: 429,
26
- INTERNAL_SERVER_ERROR: 500,
27
- BAD_GATEWAY: 502,
28
- SERVICE_UNAVAILABLE: 503,
29
- GATEWAY_TIMEOUT: 504
30
- } as const;
7
+ export * as HttpStatusCodes from "@/framework/support/status-codes.js";
31
8
  export { cache } from "@/framework/cache/cache.js";
32
9
  export { database, db } from "@/framework/database/connection.js";
33
10
  export type { PaginatedResult } from "@/framework/database/paginate.js";
@@ -35,20 +35,20 @@ export function createHttpApp() {
35
35
  let allOk = true;
36
36
  try {
37
37
  database();
38
- checks["database"] = "ok";
38
+ checks.database = "ok";
39
39
  } catch {
40
- checks["database"] = "error";
40
+ checks.database = "error";
41
41
  allOk = false;
42
42
  }
43
43
  if (redisConfig.enabled) {
44
44
  if (redisClientIfReady()) {
45
- checks["redis"] = "ok";
45
+ checks.redis = "ok";
46
46
  } else {
47
- checks["redis"] = "error";
47
+ checks.redis = "error";
48
48
  allOk = false;
49
49
  }
50
50
  } else {
51
- checks["redis"] = "disabled";
51
+ checks.redis = "disabled";
52
52
  }
53
53
  const status = allOk ? 200 : 503;
54
54
  res.status(status).json({ status: allOk ? "ready" : "not_ready", checks });
@@ -5,7 +5,7 @@ import { apiReference } from "@scalar/express-api-reference";
5
5
  import type { Express } from "express";
6
6
  import { z } from "zod";
7
7
  import { openApiConfig } from "@/config/index.js";
8
- import type { RouteConfig } from "@/framework/http/router.js";
8
+ import type { RouteConfig } from "@/framework/http/types.js";
9
9
 
10
10
  extendZodWithOpenApi(z);
11
11
 
@@ -2,25 +2,10 @@ import type { Router as ExpressRouter, RequestHandler } from "express";
2
2
  import express from "express";
3
3
  import type { z } from "zod";
4
4
  import { registerOpenApiRoute } from "@/framework/http/openapi.js";
5
+ import type { HttpMethod, RouteConfig } from "@/framework/http/types.js";
5
6
  import { validate } from "@/framework/http/validation.js";
6
7
 
7
- export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "options" | "head" | "trace";
8
-
9
- export type RouteConfig = {
10
- path: string;
11
- method: HttpMethod;
12
- tags?: string[];
13
- summary?: string;
14
- description?: string;
15
- request?: {
16
- params?: z.ZodTypeAny;
17
- query?: z.ZodTypeAny;
18
- headers?: z.ZodTypeAny;
19
- cookies?: z.ZodTypeAny;
20
- body?: { content: { "application/json": { schema: z.ZodTypeAny } }; description?: string };
21
- };
22
- responses: Record<number | string, { description?: string; content?: { "application/json": { schema: z.ZodTypeAny } } }>;
23
- };
8
+ export type { HttpMethod, RouteConfig };
24
9
 
25
10
  export type NexwireRouter = Omit<ExpressRouter, "route"> & {
26
11
  group: (...middlewares: RequestHandler[]) => NexwireRouter;
@@ -35,7 +20,7 @@ export function createRoute<T extends RouteConfig>(config: T): T {
35
20
  return config;
36
21
  }
37
22
 
38
- export const jsonContent = <T extends z.ZodTypeAny>(schema: T, description: string) => ({
23
+ export const jsonContent = <T extends z.ZodType>(schema: T, description: string) => ({
39
24
  content: {
40
25
  "application/json": {
41
26
  schema
@@ -0,0 +1,19 @@
1
+ import type { z } from "zod";
2
+
3
+ export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "options" | "head" | "trace";
4
+
5
+ export type RouteConfig = {
6
+ path: string;
7
+ method: HttpMethod;
8
+ tags?: string[];
9
+ summary?: string;
10
+ description?: string;
11
+ request?: {
12
+ params?: z.ZodType;
13
+ query?: z.ZodType;
14
+ headers?: z.ZodType;
15
+ cookies?: z.ZodType;
16
+ body?: { content: { "application/json": { schema: z.ZodType } }; description?: string };
17
+ };
18
+ responses: Record<number | string, { description?: string; content?: { "application/json": { schema: z.ZodType } } }>;
19
+ };
@@ -1,18 +1,18 @@
1
- import createError from "http-errors";
2
- import type { z } from "zod";
3
-
4
- export async function validate<T extends z.ZodTypeAny>(schema: T, data: unknown): Promise<z.infer<T>> {
5
- const result = await schema.safeParseAsync(data);
6
-
7
- if (!result.success) {
8
- throw createError(422, "Validation failed", {
9
- success: false,
10
- error: {
11
- name: result.error.name,
12
- issues: result.error.issues
13
- }
14
- } as never);
15
- }
16
-
17
- return result.data;
18
- }
1
+ import createError from "http-errors";
2
+ import type { z } from "zod";
3
+
4
+ export async function validate<T extends z.ZodType>(schema: T, data: unknown): Promise<z.infer<T>> {
5
+ const result = await schema.safeParseAsync(data);
6
+
7
+ if (!result.success) {
8
+ throw createError(422, "Validation failed", {
9
+ success: false,
10
+ error: {
11
+ name: result.error.name,
12
+ issues: result.error.issues
13
+ }
14
+ } as never);
15
+ }
16
+
17
+ return result.data;
18
+ }
@@ -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)`);
47
- }
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)`);
41
+ }