@engjts/nexus 0.1.7 → 0.1.9

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 (259) hide show
  1. package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
  2. package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
  3. package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
  4. package/dist/advanced/playground/playground.d.ts +19 -0
  5. package/dist/advanced/playground/playground.d.ts.map +1 -1
  6. package/dist/advanced/playground/playground.js +70 -0
  7. package/dist/advanced/playground/playground.js.map +1 -1
  8. package/dist/advanced/playground/types.d.ts +20 -0
  9. package/dist/advanced/playground/types.d.ts.map +1 -1
  10. package/dist/core/application.d.ts +14 -0
  11. package/dist/core/application.d.ts.map +1 -1
  12. package/dist/core/application.js +173 -71
  13. package/dist/core/application.js.map +1 -1
  14. package/dist/core/context-pool.d.ts +2 -13
  15. package/dist/core/context-pool.d.ts.map +1 -1
  16. package/dist/core/context-pool.js +7 -45
  17. package/dist/core/context-pool.js.map +1 -1
  18. package/dist/core/context.d.ts +108 -5
  19. package/dist/core/context.d.ts.map +1 -1
  20. package/dist/core/context.js +449 -53
  21. package/dist/core/context.js.map +1 -1
  22. package/dist/core/index.d.ts +1 -0
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/index.js +9 -1
  25. package/dist/core/index.js.map +1 -1
  26. package/dist/core/middleware.d.ts +6 -0
  27. package/dist/core/middleware.d.ts.map +1 -1
  28. package/dist/core/middleware.js +83 -84
  29. package/dist/core/middleware.js.map +1 -1
  30. package/dist/core/performance/fast-json.d.ts +149 -0
  31. package/dist/core/performance/fast-json.d.ts.map +1 -0
  32. package/dist/core/performance/fast-json.js +473 -0
  33. package/dist/core/performance/fast-json.js.map +1 -0
  34. package/dist/core/router/file-router.d.ts +20 -7
  35. package/dist/core/router/file-router.d.ts.map +1 -1
  36. package/dist/core/router/file-router.js +41 -13
  37. package/dist/core/router/file-router.js.map +1 -1
  38. package/dist/core/router/index.d.ts +6 -0
  39. package/dist/core/router/index.d.ts.map +1 -1
  40. package/dist/core/router/index.js +33 -6
  41. package/dist/core/router/index.js.map +1 -1
  42. package/dist/core/router/radix-tree.d.ts +4 -1
  43. package/dist/core/router/radix-tree.d.ts.map +1 -1
  44. package/dist/core/router/radix-tree.js +7 -3
  45. package/dist/core/router/radix-tree.js.map +1 -1
  46. package/dist/core/serializer.d.ts +251 -0
  47. package/dist/core/serializer.d.ts.map +1 -0
  48. package/dist/core/serializer.js +290 -0
  49. package/dist/core/serializer.js.map +1 -0
  50. package/dist/core/types.d.ts +39 -1
  51. package/dist/core/types.d.ts.map +1 -1
  52. package/dist/core/types.js.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +12 -2
  56. package/dist/index.js.map +1 -1
  57. package/package.json +3 -1
  58. package/documentation/01-getting-started.md +0 -240
  59. package/documentation/02-context.md +0 -335
  60. package/documentation/03-routing.md +0 -397
  61. package/documentation/04-middleware.md +0 -483
  62. package/documentation/05-validation.md +0 -514
  63. package/documentation/06-error-handling.md +0 -465
  64. package/documentation/07-performance.md +0 -364
  65. package/documentation/08-adapters.md +0 -470
  66. package/documentation/09-api-reference.md +0 -548
  67. package/documentation/10-examples.md +0 -582
  68. package/documentation/11-deployment.md +0 -477
  69. package/documentation/12-sentry.md +0 -620
  70. package/documentation/13-sentry-data-storage.md +0 -996
  71. package/documentation/14-sentry-data-reference.md +0 -457
  72. package/documentation/15-sentry-summary.md +0 -409
  73. package/documentation/16-alerts-system.md +0 -745
  74. package/documentation/17-alert-adapters.md +0 -696
  75. package/documentation/18-alerts-implementation-summary.md +0 -385
  76. package/documentation/19-class-based-routing.md +0 -840
  77. package/documentation/20-websocket-realtime.md +0 -813
  78. package/documentation/21-cache-system.md +0 -510
  79. package/documentation/22-job-queue.md +0 -772
  80. package/documentation/23-sentry-plugin.md +0 -551
  81. package/documentation/24-testing-utilities.md +0 -1287
  82. package/documentation/25-api-versioning.md +0 -533
  83. package/documentation/26-context-store.md +0 -607
  84. package/documentation/27-dependency-injection.md +0 -329
  85. package/documentation/28-lifecycle-hooks.md +0 -521
  86. package/documentation/29-package-structure.md +0 -196
  87. package/documentation/30-plugin-system.md +0 -414
  88. package/documentation/31-jwt-authentication.md +0 -597
  89. package/documentation/32-cli.md +0 -268
  90. package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
  91. package/documentation/ALERTS-INDEX.md +0 -330
  92. package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
  93. package/documentation/README.md +0 -178
  94. package/documentation/index.html +0 -34
  95. package/modern_framework_paper.md +0 -1870
  96. package/public/css/style.css +0 -87
  97. package/public/index.html +0 -34
  98. package/public/js/app.js +0 -27
  99. package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
  100. package/src/advanced/cache/MultiTierCache.ts +0 -194
  101. package/src/advanced/cache/RedisCacheStore.ts +0 -341
  102. package/src/advanced/cache/index.ts +0 -5
  103. package/src/advanced/cache/types.ts +0 -40
  104. package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
  105. package/src/advanced/graphql/index.ts +0 -22
  106. package/src/advanced/graphql/server.ts +0 -252
  107. package/src/advanced/graphql/types.ts +0 -42
  108. package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
  109. package/src/advanced/jobs/JobQueue.ts +0 -556
  110. package/src/advanced/jobs/RedisQueueStore.ts +0 -367
  111. package/src/advanced/jobs/index.ts +0 -5
  112. package/src/advanced/jobs/types.ts +0 -70
  113. package/src/advanced/observability/APMManager.ts +0 -163
  114. package/src/advanced/observability/AlertManager.ts +0 -109
  115. package/src/advanced/observability/MetricRegistry.ts +0 -151
  116. package/src/advanced/observability/ObservabilityCenter.ts +0 -304
  117. package/src/advanced/observability/StructuredLogger.ts +0 -154
  118. package/src/advanced/observability/TracingManager.ts +0 -117
  119. package/src/advanced/observability/adapters.ts +0 -304
  120. package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
  121. package/src/advanced/observability/index.ts +0 -11
  122. package/src/advanced/observability/types.ts +0 -174
  123. package/src/advanced/playground/extractPathParams.ts +0 -6
  124. package/src/advanced/playground/generateFieldExample.ts +0 -31
  125. package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
  126. package/src/advanced/playground/generateSummary.ts +0 -19
  127. package/src/advanced/playground/getTagFromPath.ts +0 -9
  128. package/src/advanced/playground/index.ts +0 -8
  129. package/src/advanced/playground/playground.ts +0 -170
  130. package/src/advanced/playground/types.ts +0 -20
  131. package/src/advanced/playground/zodToExample.ts +0 -16
  132. package/src/advanced/playground/zodToParams.ts +0 -15
  133. package/src/advanced/postman/buildAuth.ts +0 -31
  134. package/src/advanced/postman/buildBody.ts +0 -15
  135. package/src/advanced/postman/buildQueryParams.ts +0 -27
  136. package/src/advanced/postman/buildRequestItem.ts +0 -36
  137. package/src/advanced/postman/buildResponses.ts +0 -11
  138. package/src/advanced/postman/buildUrl.ts +0 -33
  139. package/src/advanced/postman/capitalize.ts +0 -4
  140. package/src/advanced/postman/generateCollection.ts +0 -59
  141. package/src/advanced/postman/generateEnvironment.ts +0 -34
  142. package/src/advanced/postman/generateExampleFromZod.ts +0 -21
  143. package/src/advanced/postman/generateFieldExample.ts +0 -45
  144. package/src/advanced/postman/generateName.ts +0 -20
  145. package/src/advanced/postman/generateUUID.ts +0 -11
  146. package/src/advanced/postman/getTagFromPath.ts +0 -10
  147. package/src/advanced/postman/index.ts +0 -28
  148. package/src/advanced/postman/postman.ts +0 -156
  149. package/src/advanced/postman/slugify.ts +0 -7
  150. package/src/advanced/postman/types.ts +0 -140
  151. package/src/advanced/realtime/index.ts +0 -18
  152. package/src/advanced/realtime/websocket.ts +0 -231
  153. package/src/advanced/sentry/index.ts +0 -1236
  154. package/src/advanced/sentry/types.ts +0 -355
  155. package/src/advanced/static/generateDirectoryListing.ts +0 -47
  156. package/src/advanced/static/generateETag.ts +0 -7
  157. package/src/advanced/static/getMimeType.ts +0 -9
  158. package/src/advanced/static/index.ts +0 -32
  159. package/src/advanced/static/isSafePath.ts +0 -13
  160. package/src/advanced/static/publicDir.ts +0 -21
  161. package/src/advanced/static/serveStatic.ts +0 -225
  162. package/src/advanced/static/spa.ts +0 -24
  163. package/src/advanced/static/types.ts +0 -159
  164. package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
  165. package/src/advanced/swagger/buildOperation.ts +0 -61
  166. package/src/advanced/swagger/buildParameters.ts +0 -61
  167. package/src/advanced/swagger/buildRequestBody.ts +0 -21
  168. package/src/advanced/swagger/buildResponses.ts +0 -54
  169. package/src/advanced/swagger/capitalize.ts +0 -5
  170. package/src/advanced/swagger/convertPath.ts +0 -9
  171. package/src/advanced/swagger/createSwagger.ts +0 -12
  172. package/src/advanced/swagger/generateOperationId.ts +0 -21
  173. package/src/advanced/swagger/generateSpec.ts +0 -105
  174. package/src/advanced/swagger/generateSummary.ts +0 -24
  175. package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
  176. package/src/advanced/swagger/generateThemeCss.ts +0 -53
  177. package/src/advanced/swagger/index.ts +0 -25
  178. package/src/advanced/swagger/swagger.ts +0 -237
  179. package/src/advanced/swagger/types.ts +0 -206
  180. package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
  181. package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
  182. package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
  183. package/src/advanced/testing/factory.ts +0 -509
  184. package/src/advanced/testing/harness.ts +0 -612
  185. package/src/advanced/testing/index.ts +0 -430
  186. package/src/advanced/testing/load-test.ts +0 -618
  187. package/src/advanced/testing/mock-server.ts +0 -498
  188. package/src/advanced/testing/mock.ts +0 -670
  189. package/src/cli/bin.ts +0 -9
  190. package/src/cli/cli.ts +0 -158
  191. package/src/cli/commands/add.ts +0 -178
  192. package/src/cli/commands/build.ts +0 -73
  193. package/src/cli/commands/create.ts +0 -166
  194. package/src/cli/commands/dev.ts +0 -85
  195. package/src/cli/commands/generate.ts +0 -99
  196. package/src/cli/commands/help.ts +0 -95
  197. package/src/cli/commands/init.ts +0 -91
  198. package/src/cli/commands/version.ts +0 -38
  199. package/src/cli/index.ts +0 -6
  200. package/src/cli/templates/generators.ts +0 -359
  201. package/src/cli/templates/index.ts +0 -680
  202. package/src/cli/utils/exec.ts +0 -52
  203. package/src/cli/utils/file-system.ts +0 -78
  204. package/src/cli/utils/logger.ts +0 -111
  205. package/src/core/adapter.ts +0 -88
  206. package/src/core/application.ts +0 -1335
  207. package/src/core/context-pool.ts +0 -127
  208. package/src/core/context.ts +0 -412
  209. package/src/core/index.ts +0 -80
  210. package/src/core/middleware.ts +0 -262
  211. package/src/core/performance/buffer-pool.ts +0 -108
  212. package/src/core/performance/middleware-optimizer.ts +0 -162
  213. package/src/core/plugin/PluginManager.ts +0 -435
  214. package/src/core/plugin/builder.ts +0 -358
  215. package/src/core/plugin/index.ts +0 -50
  216. package/src/core/plugin/types.ts +0 -214
  217. package/src/core/router/file-router.ts +0 -594
  218. package/src/core/router/index.ts +0 -227
  219. package/src/core/router/radix-tree.ts +0 -226
  220. package/src/core/store/index.ts +0 -30
  221. package/src/core/store/registry.ts +0 -178
  222. package/src/core/store/request-store.ts +0 -240
  223. package/src/core/store/types.ts +0 -233
  224. package/src/core/types.ts +0 -574
  225. package/src/database/adapter.ts +0 -35
  226. package/src/database/adapters/index.ts +0 -1
  227. package/src/database/adapters/mysql.ts +0 -669
  228. package/src/database/database.ts +0 -70
  229. package/src/database/dialect.ts +0 -388
  230. package/src/database/index.ts +0 -12
  231. package/src/database/migrations.ts +0 -86
  232. package/src/database/optimizer.ts +0 -125
  233. package/src/database/query-builder.ts +0 -404
  234. package/src/database/realtime.ts +0 -53
  235. package/src/database/schema.ts +0 -71
  236. package/src/database/transactions.ts +0 -56
  237. package/src/database/types.ts +0 -87
  238. package/src/deployment/cluster.ts +0 -471
  239. package/src/deployment/config.ts +0 -454
  240. package/src/deployment/docker.ts +0 -599
  241. package/src/deployment/graceful-shutdown.ts +0 -373
  242. package/src/deployment/index.ts +0 -56
  243. package/src/index.ts +0 -264
  244. package/src/security/adapter.ts +0 -318
  245. package/src/security/auth/JWTPlugin.ts +0 -234
  246. package/src/security/auth/JWTProvider.ts +0 -316
  247. package/src/security/auth/adapter.ts +0 -12
  248. package/src/security/auth/jwt.ts +0 -234
  249. package/src/security/auth/middleware.ts +0 -188
  250. package/src/security/csrf.ts +0 -220
  251. package/src/security/headers.ts +0 -108
  252. package/src/security/index.ts +0 -60
  253. package/src/security/rate-limit/adapter.ts +0 -7
  254. package/src/security/rate-limit/memory.ts +0 -108
  255. package/src/security/rate-limit/middleware.ts +0 -181
  256. package/src/security/sanitization.ts +0 -75
  257. package/src/security/types.ts +0 -240
  258. package/src/security/utils.ts +0 -52
  259. package/tsconfig.json +0 -39
@@ -1,594 +0,0 @@
1
- /**
2
- * File-Based Router for Nexus Framework
3
- *
4
- * Automatically scans a directory and registers routes based on file/folder structure.
5
- * Designed for class-based routing: 1 file = 1 class = 1 route.
6
- *
7
- * Smart filename conventions:
8
- * - `index.ts` → GET (default), maps to parent path
9
- * - `create.ts` → POST, maps to parent path (not /create)
10
- * - `update.ts` → PUT, maps to parent path (not /update)
11
- * - `delete.ts` → DELETE, maps to parent path (not /delete)
12
- * - `patch.ts` → PATCH, maps to parent path (not /patch)
13
- * - Other files → GET (or specify `method` in class)
14
- *
15
- * @example
16
- * ```
17
- * routes/
18
- * api/
19
- * auth/
20
- * register.ts → POST /api/auth/register (method = 'POST' in class)
21
- * login.ts → POST /api/auth/login
22
- * users/
23
- * index.ts → GET /api/users
24
- * create.ts → POST /api/users (auto!)
25
- * [id]/
26
- * index.ts → GET /api/users/:id
27
- * update.ts → PUT /api/users/:id (auto!)
28
- * delete.ts → DELETE /api/users/:id (auto!)
29
- * ```
30
- *
31
- * Each file exports a class:
32
- * ```typescript
33
- * export default class RegisterRoute {
34
- * method = 'POST' // Optional if filename is create/update/delete
35
- *
36
- * schema() { ... }
37
- * meta() { ... }
38
- * async handler(ctx: Context) { ... }
39
- * }
40
- * ```
41
- */
42
-
43
- import { readdir, stat } from 'fs/promises';
44
- import { join, parse, extname } from 'path';
45
- import { pathToFileURL } from 'url';
46
- import { Application } from '../application';
47
- import {
48
- HTTPMethod,
49
- Handler,
50
- Context,
51
- SchemaConfig,
52
- RouteMeta,
53
- Middleware,
54
- RouteBase,
55
- Route
56
- } from '../types';
57
-
58
- /**
59
- * Supported HTTP methods for file-based routing
60
- */
61
- const HTTP_METHODS: HTTPMethod[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
62
-
63
- /**
64
- * Default HTTP method when not specified
65
- */
66
- const DEFAULT_METHOD: HTTPMethod = 'GET';
67
-
68
- /**
69
- * Smart filename to HTTP method mapping
70
- * These filenames auto-set the method AND don't become part of the route path
71
- */
72
- const SMART_FILENAME_METHODS: Record<string, HTTPMethod> = {
73
- 'create': 'POST',
74
- 'update': 'PUT',
75
- 'delete': 'DELETE',
76
- 'patch': 'PATCH',
77
- 'index': 'GET'
78
- };
79
-
80
- /**
81
- * Check if filename is a "smart" filename that implies method
82
- */
83
- function isSmartFilename(filename: string): boolean {
84
- return filename in SMART_FILENAME_METHODS;
85
- }
86
-
87
- /**
88
- * Get implied method from smart filename
89
- */
90
- function getSmartMethod(filename: string): HTTPMethod {
91
- return SMART_FILENAME_METHODS[filename] || DEFAULT_METHOD;
92
- }
93
-
94
- /**
95
- * Options for file-based routing
96
- */
97
- export interface FileRouterOptions {
98
- /** Root directory for routes (relative to project root or absolute) */
99
- dir: string;
100
- /** Optional prefix for all routes (e.g., '/api/v1') */
101
- prefix?: string;
102
- /** File extensions to scan (default: ['.ts', '.js']) */
103
- extensions?: string[];
104
- /** Enable debug logging */
105
- debug?: boolean;
106
- /** Custom middleware file name (default: '_middleware') */
107
- middlewareFileName?: string;
108
- /** Custom error handler file name (default: '_error') */
109
- errorFileName?: string;
110
- }
111
-
112
- /**
113
- * Class-based route export (1 file = 1 class = 1 route)
114
- */
115
- export interface FileRouteClass {
116
- /** HTTP method for this route (default: 'GET') */
117
- method?: HTTPMethod;
118
- /** Optional schema validation */
119
- schema?: () => SchemaConfig;
120
- /** Optional route metadata for documentation */
121
- meta?: () => RouteMeta;
122
- /** Optional route-specific middlewares */
123
- middlewares?: () => Middleware[];
124
- /** The route handler */
125
- handler: Handler;
126
- }
127
-
128
- /**
129
- * Route file module export
130
- */
131
- export interface RouteModule {
132
- /** Default export: the route class */
133
- default?: FileRouteClass | (new () => FileRouteClass);
134
- }
135
-
136
- /**
137
- * Parsed route information
138
- */
139
- interface ParsedRoute {
140
- path: string;
141
- method: HTTPMethod;
142
- handler: Handler;
143
- middlewares: Middleware[];
144
- schema?: SchemaConfig;
145
- meta?: RouteMeta;
146
- filePath: string;
147
- }
148
-
149
- /**
150
- * Middleware stack for directory hierarchy
151
- */
152
- interface MiddlewareStack {
153
- path: string;
154
- middleware: Middleware;
155
- }
156
-
157
- /**
158
- * File-based router implementation
159
- *
160
- * Philosophy:
161
- * - 1 file = 1 class = 1 route
162
- * - Folder structure determines the path (pathName in class is IGNORED)
163
- * - HTTP method is defined via `method` property (default: GET)
164
- * - Clean, scalable, easy to navigate
165
- */
166
- export class FileRouter {
167
- private options: Required<FileRouterOptions>;
168
- private routes: ParsedRoute[] = [];
169
- private middlewareStack: MiddlewareStack[] = [];
170
-
171
- constructor(options: FileRouterOptions) {
172
- this.options = {
173
- dir: options.dir,
174
- prefix: options.prefix || '',
175
- extensions: options.extensions || ['.ts', '.js'],
176
- debug: options.debug || false,
177
- middlewareFileName: options.middlewareFileName || '_middleware',
178
- errorFileName: options.errorFileName || '_error'
179
- };
180
- }
181
-
182
- /**
183
- * Scan directory and collect all routes
184
- */
185
- async scan(): Promise<ParsedRoute[]> {
186
- this.routes = [];
187
- this.middlewareStack = [];
188
-
189
- await this.scanDirectory(this.options.dir, '');
190
-
191
- if (this.options.debug) {
192
- console.log('\n📁 File-Based Routes Discovered:');
193
- console.log('─'.repeat(60));
194
- for (const route of this.routes) {
195
- console.log(` ${route.method.padEnd(7)} ${route.path}`);
196
- console.log(` └─ ${route.filePath}`);
197
- }
198
- console.log('─'.repeat(60));
199
- console.log(` Total: ${this.routes.length} routes\n`);
200
- }
201
-
202
- return this.routes;
203
- }
204
-
205
- /**
206
- * Register all discovered routes to the application
207
- */
208
- async register(app: Application): Promise<void> {
209
- const routes = await this.scan();
210
-
211
- // Get dependencies from app for injection
212
- const deps = (app as any).getDeps?.() || {};
213
-
214
- // Get router directly to avoid double-wrapping
215
- const router = (app as any).router;
216
-
217
- for (const route of routes) {
218
- // Wrap handler to inject dependencies
219
- const originalHandler = route.handler;
220
- const wrappedHandler: Handler = async (ctx) => originalHandler(ctx, deps);
221
-
222
- // Register directly to router to avoid double-wrapping by app methods
223
- router.addRoute({
224
- method: route.method,
225
- path: route.path,
226
- handler: wrappedHandler,
227
- middlewares: route.middlewares,
228
- schema: route.schema,
229
- meta: route.meta
230
- });
231
-
232
- if (this.options.debug) {
233
- console.log(`✅ Registered: ${route.method} ${route.path}`);
234
- }
235
- }
236
- }
237
-
238
- /**
239
- * Recursively scan a directory for route files
240
- */
241
- private async scanDirectory(dirPath: string, routePath: string): Promise<void> {
242
- let entries;
243
-
244
- try {
245
- entries = await readdir(dirPath, { withFileTypes: true });
246
- } catch (error) {
247
- if (this.options.debug) {
248
- console.warn(`⚠️ Cannot read directory: ${dirPath}`);
249
- }
250
- return;
251
- }
252
-
253
- // Sort entries: directories first, then files
254
- entries.sort((a, b) => {
255
- if (a.isDirectory() && !b.isDirectory()) return -1;
256
- if (!a.isDirectory() && b.isDirectory()) return 1;
257
- return a.name.localeCompare(b.name);
258
- });
259
-
260
- // First, check for _middleware file
261
- const middlewareFile = entries.find(e =>
262
- e.isFile() && this.isMiddlewareFile(e.name)
263
- );
264
-
265
- if (middlewareFile) {
266
- await this.loadMiddleware(join(dirPath, middlewareFile.name), routePath);
267
- }
268
-
269
- // Process all entries
270
- for (const entry of entries) {
271
- const fullPath = join(dirPath, entry.name);
272
-
273
- if (entry.isDirectory()) {
274
- // Handle directory (potential route segment or dynamic param)
275
- const segment = this.parseSegment(entry.name);
276
- const newRoutePath = routePath + segment;
277
- await this.scanDirectory(fullPath, newRoutePath);
278
- } else if (entry.isFile() && this.isRouteFile(entry.name)) {
279
- // Handle route file
280
- await this.loadRouteFile(fullPath, routePath, entry.name);
281
- }
282
- }
283
- }
284
-
285
- /**
286
- * Check if file is a route file (has valid extension and not special file)
287
- */
288
- private isRouteFile(fileName: string): boolean {
289
- const ext = extname(fileName);
290
- if (!this.options.extensions.includes(ext)) return false;
291
-
292
- const nameWithoutExt = parse(fileName).name;
293
-
294
- // Skip special files
295
- if (nameWithoutExt.startsWith('_')) return false;
296
-
297
- return true;
298
- }
299
-
300
- /**
301
- * Check if file is a middleware file
302
- */
303
- private isMiddlewareFile(fileName: string): boolean {
304
- const ext = extname(fileName);
305
- if (!this.options.extensions.includes(ext)) return false;
306
-
307
- const nameWithoutExt = parse(fileName).name;
308
- return nameWithoutExt === this.options.middlewareFileName;
309
- }
310
-
311
- /**
312
- * Parse directory/file name to route segment
313
- */
314
- private parseSegment(name: string): string {
315
- // Remove file extension if present
316
- const nameWithoutExt = parse(name).name;
317
-
318
- // Catch-all parameter: [...slug] → /*slug
319
- if (nameWithoutExt.startsWith('[...') && nameWithoutExt.endsWith(']')) {
320
- const paramName = nameWithoutExt.slice(4, -1);
321
- return `/*${paramName}`;
322
- }
323
-
324
- // Optional catch-all: [[...slug]] → /*slug?
325
- if (nameWithoutExt.startsWith('[[...') && nameWithoutExt.endsWith(']]')) {
326
- const paramName = nameWithoutExt.slice(5, -2);
327
- return `/*${paramName}?`;
328
- }
329
-
330
- // Dynamic parameter: [id] → /:id
331
- if (nameWithoutExt.startsWith('[') && nameWithoutExt.endsWith(']')) {
332
- const paramName = nameWithoutExt.slice(1, -1);
333
- return `/:${paramName}`;
334
- }
335
-
336
- // Optional parameter: [[id]] → /:id?
337
- if (nameWithoutExt.startsWith('[[') && nameWithoutExt.endsWith(']]')) {
338
- const paramName = nameWithoutExt.slice(2, -2);
339
- return `/:${paramName}?`;
340
- }
341
-
342
- // Regular segment
343
- return `/${nameWithoutExt}`;
344
- }
345
-
346
- /**
347
- * Build the final route path from folder structure
348
- */
349
- private buildRoutePath(basePath: string, fileName: string): string {
350
- const nameWithoutExt = parse(fileName).name;
351
-
352
- // Build final route path
353
- // Smart filenames (index, create, update, delete, patch) don't add to path
354
- let routePath = basePath;
355
- if (!isSmartFilename(nameWithoutExt)) {
356
- routePath += this.parseSegment(fileName);
357
- }
358
-
359
- // Apply prefix
360
- if (this.options.prefix) {
361
- routePath = this.options.prefix + routePath;
362
- }
363
-
364
- // Ensure path starts with /
365
- if (!routePath.startsWith('/')) {
366
- routePath = '/' + routePath;
367
- }
368
-
369
- // Normalize double slashes
370
- routePath = routePath.replace(/\/+/g, '/');
371
-
372
- // Remove trailing slash (except for root)
373
- if (routePath.length > 1 && routePath.endsWith('/')) {
374
- routePath = routePath.slice(0, -1);
375
- }
376
-
377
- return routePath;
378
- }
379
-
380
- /**
381
- * Load a route file and extract the route class
382
- */
383
- private async loadRouteFile(filePath: string, basePath: string, fileName: string): Promise<void> {
384
- // Build route path from folder structure (this is the ONLY source of truth)
385
- const routePath = this.buildRoutePath(basePath, fileName);
386
- const nameWithoutExt = parse(fileName).name;
387
-
388
- try {
389
- // Import the file
390
- const fileUrl = pathToFileURL(filePath).href;
391
- const module = await import(fileUrl) as RouteModule;
392
-
393
- // Must have a default export
394
- if (!module.default) {
395
- if (this.options.debug) {
396
- console.warn(`⚠️ No default export in: ${filePath}`);
397
- }
398
- return;
399
- }
400
-
401
- // Instantiate if it's a class constructor
402
- let route: Route | FileRouteClass;
403
- if (typeof module.default === 'function' && module.default.prototype) {
404
- route = new (module.default as new () => Route)();
405
- } else {
406
- route = module.default as Route;
407
- }
408
-
409
- // ⚠️ ENFORCE: Route class MUST extend Route abstract class
410
- if (!(route instanceof Route)) {
411
- const className = (route as any).constructor?.name || 'Unknown';
412
- throw new Error(
413
- `Route class "${className}" in ${filePath} must extend the Route abstract class.\n` +
414
- `Example:\n` +
415
- ` import { Route } from 'nexus';\n` +
416
- ` export default class ${className} extends Route {\n` +
417
- ` async handler(ctx) { ... }\n` +
418
- ` }`
419
- );
420
- }
421
-
422
- // Must have a handler (guaranteed by Route abstract class, but double-check)
423
- if (typeof route.handler !== 'function') {
424
- if (this.options.debug) {
425
- console.warn(`⚠️ No handler method in: ${filePath}`);
426
- }
427
- return;
428
- }
429
-
430
- // Determine HTTP method:
431
- // 1. If class explicitly defines method → use it
432
- // 2. If filename is smart (create/update/delete/patch/index) → auto-detect
433
- // 3. Otherwise → default GET
434
- let method: HTTPMethod;
435
- if (route.method) {
436
- // Handle both single method and array of methods (use first one for file-based routing)
437
- method = Array.isArray(route.method) ? route.method[0] : route.method;
438
- } else if (isSmartFilename(nameWithoutExt)) {
439
- method = getSmartMethod(nameWithoutExt);
440
- } else {
441
- method = DEFAULT_METHOD;
442
- }
443
-
444
- // Validate method
445
- if (!HTTP_METHODS.includes(method)) {
446
- if (this.options.debug) {
447
- console.warn(`⚠️ Invalid method "${method}" in: ${filePath}`);
448
- }
449
- return;
450
- }
451
-
452
- // Get middlewares that apply to this route
453
- const applicableMiddlewares = this.getApplicableMiddlewares(basePath);
454
-
455
- // Add route-specific middlewares
456
- const routeMiddlewares = route.middlewares?.() || [];
457
- const allMiddlewares = [...applicableMiddlewares, ...routeMiddlewares];
458
-
459
- // Wrap handler with lifecycle hooks support
460
- // Note: Dependencies will be injected in register() method
461
- const routeInstance = route as Route;
462
- const hasHooks = typeof routeInstance.onBefore === 'function' ||
463
- typeof routeInstance.onAfter === 'function' ||
464
- typeof routeInstance.onError === 'function';
465
-
466
- let finalHandler: Handler;
467
-
468
- if (hasHooks) {
469
- const boundOriginalHandler = route.handler.bind(route);
470
- const onBefore = routeInstance.onBefore?.bind(route);
471
- const onAfter = routeInstance.onAfter?.bind(route);
472
- const onError = routeInstance.onError?.bind(route);
473
-
474
- // Handler receives (ctx, deps) - deps passed from register()
475
- finalHandler = async (ctx: Context, deps: any) => {
476
- try {
477
- // Run onBefore hook with deps
478
- if (onBefore) {
479
- const beforeResult = await onBefore(ctx, deps);
480
- // If onBefore returns a value (not undefined), skip handler
481
- if (beforeResult !== undefined) {
482
- return beforeResult;
483
- }
484
- }
485
-
486
- // Run the main handler with deps
487
- let result = await boundOriginalHandler(ctx, deps);
488
-
489
- // Run onAfter hook with deps
490
- if (onAfter) {
491
- result = await onAfter(ctx, result, deps);
492
- }
493
-
494
- return result;
495
- } catch (error) {
496
- // Run onError hook if defined with deps
497
- if (onError) {
498
- return await onError(ctx, error as Error, deps);
499
- }
500
- // Re-throw if no onError handler
501
- throw error;
502
- }
503
- };
504
- } else {
505
- // No hooks - just bind handler (deps will be passed in register())
506
- finalHandler = route.handler.bind(route);
507
- }
508
-
509
- // Register the route
510
- this.routes.push({
511
- path: routePath,
512
- method,
513
- handler: finalHandler,
514
- middlewares: allMiddlewares,
515
- schema: route.schema?.(),
516
- meta: route.meta?.(),
517
- filePath
518
- });
519
-
520
- } catch (error) {
521
- if (this.options.debug) {
522
- console.error(`❌ Failed to load route: ${filePath}`, error);
523
- }
524
- }
525
- }
526
-
527
- /**
528
- * Load middleware from a _middleware file
529
- */
530
- private async loadMiddleware(filePath: string, routePath: string): Promise<void> {
531
- try {
532
- const fileUrl = pathToFileURL(filePath).href;
533
- const module = await import(fileUrl);
534
-
535
- const middleware = module.default || module.middleware;
536
-
537
- if (typeof middleware === 'function') {
538
- this.middlewareStack.push({
539
- path: routePath,
540
- middleware
541
- });
542
-
543
- if (this.options.debug) {
544
- console.log(`📦 Loaded middleware for: ${routePath || '/'}`);
545
- }
546
- }
547
- } catch (error) {
548
- if (this.options.debug) {
549
- console.error(`❌ Failed to load middleware: ${filePath}`, error);
550
- }
551
- }
552
- }
553
-
554
- /**
555
- * Get middlewares that apply to a given route path
556
- */
557
- private getApplicableMiddlewares(routePath: string): Middleware[] {
558
- return this.middlewareStack
559
- .filter(m => routePath.startsWith(m.path))
560
- .map(m => m.middleware);
561
- }
562
- }
563
-
564
- /**
565
- * Create a file router instance
566
- */
567
- export function createFileRouter(options: FileRouterOptions): FileRouter {
568
- return new FileRouter(options);
569
- }
570
-
571
- /**
572
- * Extension method for Application to use file-based routing
573
- * This will be called to register routes from a directory
574
- *
575
- * @example
576
- * ```typescript
577
- * const app = createApp();
578
- *
579
- * await app.useFileRoutes({
580
- * dir: './src/routes',
581
- * prefix: '',
582
- * debug: true
583
- * });
584
- *
585
- * app.listen(3000);
586
- * ```
587
- */
588
- export async function useFileRoutes(
589
- app: Application,
590
- options: FileRouterOptions
591
- ): Promise<void> {
592
- const router = new FileRouter(options);
593
- await router.register(app);
594
- }