Я пытаюсь создать средство, создавая обработчики, которые имеют одинаковое базовое определение c, но затем имеют дополнительные свойства, которые задают c для каждого использования.
У меня есть маршрутизатор , контекст и функции обработчика маршрута, которые принимают контекст в качестве параметра. Я хотел бы иметь возможность присоединять дополнительные свойства к контексту в зависимости от определения маршрута.
Если определение маршрута /hello/:user
, есть параметр user
и при вызове HTTP GET /hello/ben
это быть значением указанного параметра.
Таким образом, в данном конкретном случае мы имеем context.params = {user: 'ben'}
.
Я также регистрирую функции поиска для этих параметров.
Т.е. context.bindings['user] = (param) => User.find(param)
Когда я регистрирую маршруты, я хочу обработчики, содержащие контекст и любые дополнительные ключи, которые были разрешены функциями поиска. Например,
// In this example, each GET route has a :parameter and a binding for it so their
// handler should receive the result of the binding function as a key on context
// Both receive params because that's just part of context always.
router.binding('user', (user) => User.find(user));
router.binding('photo', (photo) => Photo.find(photo));
router.get("/bar/:photo", ({ params, photo }: HttpContextContract) => {}) // 1
router.get("/bar/:user", ({ params, user }: HttpContext) => {}) // 2
// in (1), the interface HttpContextContract doesn't know about photo so it moans
// in (2), the class HttpContext just lets any property go and there's no helpful typing/intellisense etc
Я не хочу, например, каждый раз определять новый интерфейс для каждого маршрута HttpContextContract & { user: User }
. Я полагаю, что смогу использовать что-то вроде type Handlers = Record<keyof typeof handlers, {}>
, но я не могу заставить его работать.
Я собрал базовый c пример частей, описанных выше и положил его на площадку для машинописи так что, надеюсь, легче примерно увидеть, чего я пытаюсь достичь
type Binding = (param: string) => any;
type RouteHandler = (ctx: HttpContextContract) => any;
interface RouteBindings {
[key: string]: Binding;
}
interface RouteHandlers {
[key: string]: RouteHandler;
}
interface HttpContextContract {
params: any;
}
interface HttpContext {
[key: string]: any; // this allows me to attach the new keys to the instance
}
class HttpContext implements HttpContextContract {
public params: any = {};
}
class Router {
public bindings: RouteBindings = {};
public handlers: RouteHandlers = {};
public binding(key: string, binding: Binding) {
this.bindings[key] = binding;
}
public get(path: string, handler: any) {
this.handlers[path] = handler;
}
public find(path: string): RouteHandler {
return this.handlers[path];
}
}
class Server {
constructor(protected router: Router) {}
getParams(path: string) {
const matches = path.match(/:([^/]+)/gi) || [];
return matches.map(s => s.substring(1));
}
handle(path: string) {
const ctx = new HttpContext(); // as HttpContext & { 'foo2': 'bar '}
this.getParams(path).forEach((param: string) => {
const binding = this.router.bindings[param];
if (binding) {
// Object.defineProperty(ctx, param, {
// get: binding
// });
ctx[param] = binding(param);
}
});
const handler = this.router.find(path);
return handler(ctx);
}
}
const router = new Router();
const server = new Server(router);
class Photo {
constructor(public name: string) {}
}
router.binding("user", () => "BOUND USER STRING");
router.binding("photo", () => new Photo("test"));
// This has no idea about the user property, even though it's there and readable
router.get("/foo/:user", ({ user }: HttpContextContract) => {
return `"${user}" <- from bindings`;
});
// This now lets me use photo, but doesn't tell me user doesn't exist there
router.get("/bar/:photo", ({ photo, user }: HttpContext) => {
return `"${JSON.stringify(photo, null, 2)} ${user}" <- from bindings`;
});
const out1 = server.handle("/foo/:user");
const out2 = server.handle("/bar/:photo");
console.log(out1);
console.log(out2);
// type ExtendedProperties<T> = { [P in keyof T]: T[P] };
// & ExtendedProperties<Record<keyof typeof this.router, {}>>;
// type BB = Router['handlers']
// type Handlers = Record<keyof typeof handlers, {}>