@engjts/nexus 0.1.8 → 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/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- 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 -1956
- 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 -250
- package/src/advanced/playground/types.ts +0 -49
- 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 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- 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 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- 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 -616
- 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 -281
- 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
package/src/core/router/index.ts
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router implementation with schema validation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { RadixTree } from './radix-tree';
|
|
6
|
-
import {
|
|
7
|
-
HTTPMethod,
|
|
8
|
-
Handler,
|
|
9
|
-
Middleware,
|
|
10
|
-
RouteConfig,
|
|
11
|
-
RouteMatch,
|
|
12
|
-
SchemaConfig,
|
|
13
|
-
RouteMeta,
|
|
14
|
-
Context
|
|
15
|
-
} from '../types';
|
|
16
|
-
import { SerializerFunction, createSerializer, JSONSchema } from '../serializer';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Route with validation schema and metadata
|
|
20
|
-
*/
|
|
21
|
-
export interface RouteEntry {
|
|
22
|
-
handler: Handler;
|
|
23
|
-
middlewares: Middleware[];
|
|
24
|
-
schema?: SchemaConfig;
|
|
25
|
-
meta?: RouteMeta;
|
|
26
|
-
serializers?: Map<number | string, SerializerFunction>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Router class
|
|
31
|
-
*/
|
|
32
|
-
export class Router {
|
|
33
|
-
private trees: Map<HTTPMethod, RadixTree> = new Map();
|
|
34
|
-
private routes: Array<{ method: HTTPMethod; path: string; config: RouteEntry }> = [];
|
|
35
|
-
private prefix: string = '';
|
|
36
|
-
|
|
37
|
-
constructor(prefix: string = '') {
|
|
38
|
-
this.prefix = prefix;
|
|
39
|
-
// Initialize trees for each HTTP method
|
|
40
|
-
const methods: HTTPMethod[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
|
41
|
-
for (const method of methods) {
|
|
42
|
-
this.trees.set(method, new RadixTree());
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Register a route
|
|
48
|
-
*/
|
|
49
|
-
addRoute(config: RouteConfig): void {
|
|
50
|
-
const { method, path, handler, middlewares = [], schema, meta } = config;
|
|
51
|
-
const fullPath = this.prefix ? `${this.prefix}${path}` : path;
|
|
52
|
-
|
|
53
|
-
const tree = this.trees.get(method);
|
|
54
|
-
if (!tree) {
|
|
55
|
-
throw new Error(`Unsupported HTTP method: ${method}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Wrap handler with schema validation if provided
|
|
59
|
-
const wrappedHandler = schema ? this.wrapWithValidation(handler, schema) : handler;
|
|
60
|
-
|
|
61
|
-
// Compile response serializers if response schema is provided
|
|
62
|
-
let serializers: Map<number | string, SerializerFunction> | undefined;
|
|
63
|
-
if (schema?.response) {
|
|
64
|
-
serializers = this.compileResponseSerializers(schema.response);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
tree.insert(fullPath, wrappedHandler, middlewares, serializers);
|
|
68
|
-
|
|
69
|
-
// Store for introspection (including schema, meta, and serializers for documentation)
|
|
70
|
-
this.routes.push({
|
|
71
|
-
method,
|
|
72
|
-
path: fullPath,
|
|
73
|
-
config: { handler: wrappedHandler, middlewares, schema, meta, serializers }
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Compile response schemas into fast serializers
|
|
79
|
-
*/
|
|
80
|
-
private compileResponseSerializers(
|
|
81
|
-
responseSchema: Record<string | number, any>
|
|
82
|
-
): Map<number | string, SerializerFunction> {
|
|
83
|
-
const serializers = new Map<number | string, SerializerFunction>();
|
|
84
|
-
|
|
85
|
-
for (const [statusKey, schema] of Object.entries(responseSchema)) {
|
|
86
|
-
if (schema && typeof schema === 'object') {
|
|
87
|
-
try {
|
|
88
|
-
const serializer = createSerializer(schema as JSONSchema);
|
|
89
|
-
serializers.set(statusKey, serializer);
|
|
90
|
-
} catch (e) {
|
|
91
|
-
// If compilation fails, skip this serializer (will fall back to JSON.stringify)
|
|
92
|
-
console.warn(`[Router] Failed to compile serializer for status ${statusKey}:`, e);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return serializers;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Find a matching route
|
|
102
|
-
*/
|
|
103
|
-
match(method: string, path: string): RouteMatch | null {
|
|
104
|
-
const tree = this.trees.get(method as HTTPMethod);
|
|
105
|
-
if (!tree) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const result = tree.search(path);
|
|
110
|
-
if (!result) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
handler: result.handler,
|
|
116
|
-
params: result.params,
|
|
117
|
-
middlewares: result.middlewares,
|
|
118
|
-
schema: undefined, // Schema already applied in wrapped handler
|
|
119
|
-
_serializer: result.serializers || undefined
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Get all registered routes with full metadata
|
|
125
|
-
*/
|
|
126
|
-
getRoutes(): Array<{ method: string; path: string; schema?: SchemaConfig; meta?: RouteMeta }> {
|
|
127
|
-
return this.routes.map(r => ({
|
|
128
|
-
method: r.method,
|
|
129
|
-
path: r.path,
|
|
130
|
-
schema: r.config.schema,
|
|
131
|
-
meta: r.config.meta
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Get raw route configs for merging into Application
|
|
137
|
-
*/
|
|
138
|
-
getRawRoutes(): Array<{ method: HTTPMethod; path: string; config: RouteEntry }> {
|
|
139
|
-
return this.routes;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Get internal radix trees for merging
|
|
144
|
-
*/
|
|
145
|
-
getTrees(): Map<HTTPMethod, RadixTree> {
|
|
146
|
-
return this.trees;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Mount another router with a prefix (group routes)
|
|
151
|
-
*
|
|
152
|
-
* @example
|
|
153
|
-
* ```typescript
|
|
154
|
-
* const userRoutes = new Router();
|
|
155
|
-
* userRoutes.get('/', getAllUsers);
|
|
156
|
-
* userRoutes.get('/:id', getUserById);
|
|
157
|
-
*
|
|
158
|
-
* const router = new Router();
|
|
159
|
-
* router.group('/api/users', userRoutes);
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
group(prefix: string, router: Router): void {
|
|
163
|
-
const routes = router.getRawRoutes();
|
|
164
|
-
for (const route of routes) {
|
|
165
|
-
const fullPath = `${prefix}${route.path}`;
|
|
166
|
-
this.addRoute({
|
|
167
|
-
method: route.method,
|
|
168
|
-
path: fullPath,
|
|
169
|
-
handler: route.config.handler,
|
|
170
|
-
middlewares: route.config.middlewares,
|
|
171
|
-
schema: route.config.schema,
|
|
172
|
-
meta: route.config.meta
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Convenience methods for HTTP verbs
|
|
179
|
-
*/
|
|
180
|
-
get(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
|
|
181
|
-
this.addRoute({ method: 'GET', path, handler, ...options });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
post(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
|
|
185
|
-
this.addRoute({ method: 'POST', path, handler, ...options });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
put(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
|
|
189
|
-
this.addRoute({ method: 'PUT', path, handler, ...options });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
delete(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
|
|
193
|
-
this.addRoute({ method: 'DELETE', path, handler, ...options });
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
patch(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
|
|
197
|
-
this.addRoute({ method: 'PATCH', path, handler, ...options });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Wrap handler with schema validation
|
|
202
|
-
*/
|
|
203
|
-
private wrapWithValidation(handler: Handler, schema: SchemaConfig): Handler {
|
|
204
|
-
return async (ctx: Context) => {
|
|
205
|
-
try {
|
|
206
|
-
// Validate params
|
|
207
|
-
if (schema.params) {
|
|
208
|
-
ctx.params = await schema.params.parseAsync(ctx.params);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Validate query
|
|
212
|
-
if (schema.query) {
|
|
213
|
-
ctx.query = await schema.query.parseAsync(ctx.query);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Validate body - MUST await getBody() first to parse the request body
|
|
217
|
-
if (schema.body) {
|
|
218
|
-
const rawBody = await ctx.getBody();
|
|
219
|
-
ctx.body = await schema.body.parseAsync(rawBody);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Validate headers
|
|
223
|
-
if (schema.headers) {
|
|
224
|
-
ctx.headers = await schema.headers.parseAsync(ctx.headers);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Call original handler with validated data
|
|
228
|
-
return handler(ctx, {});
|
|
229
|
-
} catch (error: any) {
|
|
230
|
-
// Zod validation error
|
|
231
|
-
if (error.name === 'ZodError') {
|
|
232
|
-
// Use custom error handler if provided
|
|
233
|
-
if (schema.onValidationError) {
|
|
234
|
-
const customResponse = schema.onValidationError(error.errors, ctx);
|
|
235
|
-
// If it's already a Response object, return it
|
|
236
|
-
if (customResponse?.statusCode) {
|
|
237
|
-
return customResponse;
|
|
238
|
-
}
|
|
239
|
-
// Otherwise wrap it as JSON response
|
|
240
|
-
return ctx.json(customResponse, 400);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Default error response - extract first error message
|
|
244
|
-
const firstError = error.errors[0];
|
|
245
|
-
const message = firstError?.message || 'Validation failed';
|
|
246
|
-
|
|
247
|
-
return ctx.json({
|
|
248
|
-
success: false,
|
|
249
|
-
message
|
|
250
|
-
}, 400);
|
|
251
|
-
}
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Re-export file router
|
|
259
|
-
export { FileRouter, createFileRouter, useFileRoutes } from './file-router';
|
|
260
|
-
export type { FileRouterOptions, FileRouteClass, RouteModule } from './file-router';
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Radix Tree implementation for efficient routing
|
|
3
|
-
* Provides O(log n) route lookup performance
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Handler, Middleware } from '../types';
|
|
7
|
-
import { SerializerFunction } from '../serializer';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Node in the radix tree
|
|
11
|
-
*/
|
|
12
|
-
export class RadixNode {
|
|
13
|
-
// The path segment this node represents
|
|
14
|
-
segment: string;
|
|
15
|
-
|
|
16
|
-
// Full path for this node (used for debugging)
|
|
17
|
-
fullPath: string;
|
|
18
|
-
|
|
19
|
-
// Handler if this is a terminal node
|
|
20
|
-
handler: Handler | null = null;
|
|
21
|
-
|
|
22
|
-
// Middleware for this route
|
|
23
|
-
middlewares: Middleware[] = [];
|
|
24
|
-
|
|
25
|
-
// Child nodes
|
|
26
|
-
children: RadixNode[] = [];
|
|
27
|
-
|
|
28
|
-
// Pattern type: 'static', 'param', or 'wildcard'
|
|
29
|
-
type: 'static' | 'param' | 'wildcard' = 'static';
|
|
30
|
-
|
|
31
|
-
// Parameter name for param nodes
|
|
32
|
-
paramName?: string;
|
|
33
|
-
|
|
34
|
-
// Compiled serializers for response (status code → serializer)
|
|
35
|
-
serializers: Map<number | string, SerializerFunction> | null = null;
|
|
36
|
-
|
|
37
|
-
constructor(segment: string, fullPath: string) {
|
|
38
|
-
this.segment = segment;
|
|
39
|
-
this.fullPath = fullPath;
|
|
40
|
-
|
|
41
|
-
// Detect node type
|
|
42
|
-
if (segment.startsWith(':')) {
|
|
43
|
-
this.type = 'param';
|
|
44
|
-
this.paramName = segment.slice(1);
|
|
45
|
-
} else if (segment === '*' || segment.startsWith('*')) {
|
|
46
|
-
this.type = 'wildcard';
|
|
47
|
-
this.paramName = segment.length > 1 ? segment.slice(1) : 'wildcard';
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Add a child node
|
|
53
|
-
*/
|
|
54
|
-
addChild(node: RadixNode): void {
|
|
55
|
-
// Insert maintaining order: static > param > wildcard
|
|
56
|
-
const priority = this.getTypePriority(node.type);
|
|
57
|
-
let inserted = false;
|
|
58
|
-
|
|
59
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
60
|
-
const childPriority = this.getTypePriority(this.children[i].type);
|
|
61
|
-
if (priority < childPriority) {
|
|
62
|
-
this.children.splice(i, 0, node);
|
|
63
|
-
inserted = true;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!inserted) {
|
|
69
|
-
this.children.push(node);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Find a child by segment
|
|
75
|
-
*/
|
|
76
|
-
findChild(segment: string): RadixNode | null {
|
|
77
|
-
for (const child of this.children) {
|
|
78
|
-
if (child.segment === segment) {
|
|
79
|
-
return child;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get type priority for ordering (lower = higher priority)
|
|
87
|
-
*/
|
|
88
|
-
private getTypePriority(type: 'static' | 'param' | 'wildcard'): number {
|
|
89
|
-
switch (type) {
|
|
90
|
-
case 'static': return 0;
|
|
91
|
-
case 'param': return 1;
|
|
92
|
-
case 'wildcard': return 2;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Radix tree for route storage and matching
|
|
99
|
-
*/
|
|
100
|
-
export class RadixTree {
|
|
101
|
-
private root: RadixNode;
|
|
102
|
-
|
|
103
|
-
constructor() {
|
|
104
|
-
this.root = new RadixNode('', '/');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Insert a route into the tree
|
|
109
|
-
*/
|
|
110
|
-
insert(
|
|
111
|
-
path: string,
|
|
112
|
-
handler: Handler,
|
|
113
|
-
middlewares: Middleware[] = [],
|
|
114
|
-
serializers?: Map<number | string, SerializerFunction>
|
|
115
|
-
): void {
|
|
116
|
-
const segments = this.splitPath(path);
|
|
117
|
-
let current = this.root;
|
|
118
|
-
|
|
119
|
-
for (let i = 0; i < segments.length; i++) {
|
|
120
|
-
const segment = segments[i];
|
|
121
|
-
let child = current.findChild(segment);
|
|
122
|
-
|
|
123
|
-
if (!child) {
|
|
124
|
-
const fullPath = '/' + segments.slice(0, i + 1).join('/');
|
|
125
|
-
child = new RadixNode(segment, fullPath);
|
|
126
|
-
current.addChild(child);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
current = child;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Set handler, middleware, and serializers at terminal node
|
|
133
|
-
current.handler = handler;
|
|
134
|
-
current.middlewares = middlewares;
|
|
135
|
-
current.serializers = serializers || null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Search for a route match
|
|
140
|
-
*/
|
|
141
|
-
search(path: string): {
|
|
142
|
-
handler: Handler;
|
|
143
|
-
params: Record<string, string>;
|
|
144
|
-
middlewares: Middleware[];
|
|
145
|
-
serializers: Map<number | string, SerializerFunction> | null;
|
|
146
|
-
} | null {
|
|
147
|
-
const segments = this.splitPath(path);
|
|
148
|
-
const params: Record<string, string> = {};
|
|
149
|
-
|
|
150
|
-
const result = this.searchNode(this.root, segments, 0, params);
|
|
151
|
-
|
|
152
|
-
if (result) {
|
|
153
|
-
return {
|
|
154
|
-
handler: result.handler!,
|
|
155
|
-
params,
|
|
156
|
-
middlewares: result.middlewares,
|
|
157
|
-
serializers: result.serializers
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Recursively search nodes
|
|
166
|
-
*/
|
|
167
|
-
private searchNode(
|
|
168
|
-
node: RadixNode,
|
|
169
|
-
segments: string[],
|
|
170
|
-
index: number,
|
|
171
|
-
params: Record<string, string>
|
|
172
|
-
): RadixNode | null {
|
|
173
|
-
// Reached end of path
|
|
174
|
-
if (index === segments.length) {
|
|
175
|
-
return node.handler ? node : null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const segment = segments[index];
|
|
179
|
-
|
|
180
|
-
// Try children in priority order
|
|
181
|
-
for (const child of node.children) {
|
|
182
|
-
if (child.type === 'static') {
|
|
183
|
-
// Exact match required
|
|
184
|
-
if (child.segment === segment) {
|
|
185
|
-
const result = this.searchNode(child, segments, index + 1, params);
|
|
186
|
-
if (result) return result;
|
|
187
|
-
}
|
|
188
|
-
} else if (child.type === 'param') {
|
|
189
|
-
// Parameter match - capture value
|
|
190
|
-
const oldValue = params[child.paramName!];
|
|
191
|
-
params[child.paramName!] = segment;
|
|
192
|
-
|
|
193
|
-
const result = this.searchNode(child, segments, index + 1, params);
|
|
194
|
-
if (result) return result;
|
|
195
|
-
|
|
196
|
-
// Backtrack
|
|
197
|
-
if (oldValue === undefined) {
|
|
198
|
-
delete params[child.paramName!];
|
|
199
|
-
} else {
|
|
200
|
-
params[child.paramName!] = oldValue;
|
|
201
|
-
}
|
|
202
|
-
} else if (child.type === 'wildcard') {
|
|
203
|
-
// Wildcard match - capture remaining path
|
|
204
|
-
params[child.paramName!] = segments.slice(index).join('/');
|
|
205
|
-
return child;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Split path into segments
|
|
214
|
-
*/
|
|
215
|
-
private splitPath(path: string): string[] {
|
|
216
|
-
// Remove leading/trailing slashes and split
|
|
217
|
-
const normalized = path.replace(/^\/+|\/+$/g, '');
|
|
218
|
-
return normalized ? normalized.split('/') : [];
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get all routes (for debugging/introspection)
|
|
223
|
-
*/
|
|
224
|
-
getAllRoutes(): Array<{ path: string; hasHandler: boolean }> {
|
|
225
|
-
const routes: Array<{ path: string; hasHandler: boolean }> = [];
|
|
226
|
-
this.collectRoutes(this.root, routes);
|
|
227
|
-
return routes;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Recursively collect all routes
|
|
232
|
-
*/
|
|
233
|
-
private collectRoutes(node: RadixNode, routes: Array<{ path: string; hasHandler: boolean }>): void {
|
|
234
|
-
if (node.handler) {
|
|
235
|
-
routes.push({ path: node.fullPath, hasHandler: true });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
for (const child of node.children) {
|
|
239
|
-
this.collectRoutes(child, routes);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|