BREAKING CHANGE(typedserver): migrate route handlers to use IRequestContext and lazy body parsers
This commit is contained in:
@@ -148,7 +148,7 @@ export interface IServerOptions {
|
||||
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'ALL';
|
||||
|
||||
export interface IRouteHandler {
|
||||
(request: Request): Promise<Response | null>;
|
||||
(ctx: plugins.smartserve.IRequestContext): Promise<Response | null>;
|
||||
}
|
||||
|
||||
export class TypedServer {
|
||||
@@ -202,19 +202,11 @@ export class TypedServer {
|
||||
* Supports Express-style path patterns like '/path/:param' and '/path/*splat'
|
||||
* @param path - The route path pattern
|
||||
* @param method - HTTP method (GET, POST, PUT, DELETE, PATCH, ALL)
|
||||
* @param handler - Async function that receives Request and returns Response or null
|
||||
* @param handler - Async function that receives IRequestContext and returns Response or null
|
||||
*/
|
||||
public addRoute(path: string, method: THttpMethod, handler: IRouteHandler): void {
|
||||
// Delegate to smartserve's ControllerRegistry
|
||||
plugins.smartserve.ControllerRegistry.addRoute(path, method, async (ctx: plugins.smartserve.IRequestContext) => {
|
||||
// Convert context to Request for backwards compatibility
|
||||
const request = new Request(ctx.url.toString(), {
|
||||
method: ctx.method,
|
||||
headers: ctx.headers,
|
||||
});
|
||||
(request as any).params = ctx.params;
|
||||
return handler(request);
|
||||
});
|
||||
plugins.smartserve.ControllerRegistry.addRoute(path, method, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +266,9 @@ export class TypedServer {
|
||||
});
|
||||
|
||||
// Register controllers with SmartServe's ControllerRegistry
|
||||
// Note: @Route decorators auto-register classes at import time.
|
||||
// Controllers with constructor args (like DevToolsController) use default no-op
|
||||
// constructors to handle auto-instantiation gracefully.
|
||||
if (this.options.injectReload) {
|
||||
plugins.smartserve.ControllerRegistry.registerInstance(this.devToolsController);
|
||||
}
|
||||
@@ -389,10 +384,10 @@ export class TypedServer {
|
||||
/**
|
||||
* Create an IRequestContext from a Request
|
||||
*/
|
||||
private async createContext(
|
||||
private createContext(
|
||||
request: Request,
|
||||
params: Record<string, string>
|
||||
): Promise<plugins.smartserve.IRequestContext> {
|
||||
): plugins.smartserve.IRequestContext {
|
||||
const url = new URL(request.url);
|
||||
const method = request.method.toUpperCase() as THttpMethod;
|
||||
|
||||
@@ -402,20 +397,14 @@ export class TypedServer {
|
||||
query[key] = value;
|
||||
});
|
||||
|
||||
// Parse body
|
||||
let body: unknown = undefined;
|
||||
const contentType = request.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
try {
|
||||
body = await request.clone().json();
|
||||
} catch {
|
||||
body = {};
|
||||
}
|
||||
}
|
||||
// Cached body parsers (lazy evaluation)
|
||||
let jsonCache: unknown;
|
||||
let textCache: string;
|
||||
let arrayBufferCache: ArrayBuffer;
|
||||
let formDataCache: FormData;
|
||||
|
||||
return {
|
||||
request,
|
||||
body,
|
||||
params,
|
||||
query,
|
||||
headers: request.headers,
|
||||
@@ -424,6 +413,30 @@ export class TypedServer {
|
||||
url,
|
||||
runtime: 'node' as const,
|
||||
state: {},
|
||||
async json<T = unknown>(): Promise<T> {
|
||||
if (jsonCache === undefined) {
|
||||
jsonCache = await request.clone().json();
|
||||
}
|
||||
return jsonCache as T;
|
||||
},
|
||||
async text(): Promise<string> {
|
||||
if (textCache === undefined) {
|
||||
textCache = await request.clone().text();
|
||||
}
|
||||
return textCache;
|
||||
},
|
||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||
if (arrayBufferCache === undefined) {
|
||||
arrayBufferCache = await request.clone().arrayBuffer();
|
||||
}
|
||||
return arrayBufferCache;
|
||||
},
|
||||
async formData(): Promise<FormData> {
|
||||
if (formDataCache === undefined) {
|
||||
formDataCache = await request.clone().formData();
|
||||
}
|
||||
return formDataCache;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -577,7 +590,7 @@ export class TypedServer {
|
||||
}
|
||||
|
||||
// Process the request and wrap response with all configured headers
|
||||
const response = await this.handleRequestInternal(request, url, path, method);
|
||||
const response = await this.handleRequestInternal(request, path, method);
|
||||
return this.applyResponseHeaders(response);
|
||||
}
|
||||
|
||||
@@ -586,7 +599,6 @@ export class TypedServer {
|
||||
*/
|
||||
private async handleRequestInternal(
|
||||
request: Request,
|
||||
url: URL,
|
||||
path: string,
|
||||
method: THttpMethod
|
||||
): Promise<Response> {
|
||||
@@ -594,7 +606,7 @@ export class TypedServer {
|
||||
const match = plugins.smartserve.ControllerRegistry.matchRoute(path, method);
|
||||
if (match) {
|
||||
try {
|
||||
const context = await this.createContext(request, match.params);
|
||||
const context = this.createContext(request, match.params);
|
||||
const result = await match.route.handler(context);
|
||||
|
||||
// Handle Response or convert to Response
|
||||
|
||||
Reference in New Issue
Block a user