@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.
- package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -170
- package/src/advanced/playground/types.ts +0 -20
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -574
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -264
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- 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
|
-
}
|