Convert a schema (@sinclair/typebox) that is in a decorator to type #1024
-
Hello ! I'm currently working on a Fastify overlay that I'd like to make, the goal is to develop decorators to create routes quickly. I know fastify-decorators exists, I've been working with it for a long time, but it doesn't include type providers with typebox (or anything else for that matter), so you have to redefine the types each time. Example: import { Type } from "@sinclair/typebox";
import {GET, RequestHandler} from "fastify-decorators";
@GET("test", {
schema: {
body: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
})
export default class HelloRoute extends RequestHandler {
async handle() {
const body = <{foo: number, bar: string}>this.request.body; // Body is not typed
body.foo // foo is typed !
}
} The developer therefore has to do double the work, even though he has already typed his body (in the example above). If he modifies it, he must also recast it correctly. This is annoying. That's why I'd like to create a decorator (which would also handle custom keys, which fastify-decorators doesn't) that would ideally infer the type directly without having to caster. Example: import { Type } from "@sinclair/typebox";
import {GET, RequestHandler} from "fastify-decorators";
@GET("test", {
schema: {
body: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
})
export default class HelloRoute extends RequestHandler {
async handle() {
const body = this.request.body; // Body is not typed
body.foo // TS2339: Property foo does not exist on type unknown
}
} So I think we need to create a custom decorator as well as an abstract “RequestHandler” class. For the decorator, the arguments would be like this: export interface RouteOptions extends RouteShorthandOptions {
description: string;
// Here we can add additional options
}
export function ReGet(path: string, options: RouteOptions): ClassDecorator { /* ... */ } I want exactly this result, I don't want the developer to have to cast anything, or to take the schematic out of the decorator to cast it more quickly. This is rather frustrating because fastify allows you to use the provider type with the librarie @fastify/type-provider-typebox:
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
import { Type } from '@sinclair/typebox'
import fastify from 'fastify'
const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
server.get('/route', {
schema: {
querystring: Type.Object({
foo: Type.Number(),
bar: Type.String()
})
}
}, (request, reply) => {
// type Query = { foo: number, bar: string }
const { foo, bar } = request.query // type safe!
}) It works very well, I'm aware that it works very differently to a decorator, and I don't know if it's possible to implement the result I'm looking for, so I'm asking for your help. Many thanks in advance I tried to have a good look at the fastify-decorators library, which I think is absolutely great, but unfortunately it doesn't have a provider type from its decorators. I then looked to see if I could manipulate things with the following library: https://github.com/fastify/fastify-type-provider-typebox import {
FastifyReply,
FastifyRequest,
RawRequestDefaultExpression,
RawServerDefault,
RawReplyDefaultExpression,
ContextConfigDefault
} from 'fastify';
import { RouteGenericInterface } from 'fastify/types/route';
import { FastifySchema } from 'fastify/types/schema';
import { Type, TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
export type FastifyRequestTypebox<TSchema extends FastifySchema> = FastifyRequest<
RouteGenericInterface,
RawServerDefault,
RawRequestDefaultExpression<RawServerDefault>,
TSchema,
TypeBoxTypeProvider
>;
export type FastifyReplyTypebox<TSchema extends FastifySchema> = FastifyReply<
RawServerDefault,
RawRequestDefaultExpression,
RawReplyDefaultExpression,
RouteGenericInterface,
ContextConfigDefault,
TSchema,
TypeBoxTypeProvider
>
export const CreateProductSchema = {
body: Type.Object({
name: Type.String(),
price: Type.Number(),
}),
response: {
201: Type.Object({
id: Type.Number(),
}),
},
};
export const CreateProductHandler = (
req: FastifyRequestTypebox<typeof CreateProductSchema>,
reply: FastifyReplyTypebox<typeof CreateProductSchema>
) => {
// The `name` and `price` types are automatically inferred
const { name, price } = req.body;
// The response body type is automatically inferred
reply.status(201).send({ id: 'string-value' });
// ^? error TS2322: Type 'string' is not assignable to type 'number'.
}; As I love this library, I'd hate not to be able to use it anymore. Is there a way to add : Thanks |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
I believe you can provide types to your handler without type provider or any sort of casting. For example: // schemas.ts
import { Type } from '@sinclair/typebox';
export const createBodySchema = Type.Object(
{
members: Type.Optional(
Type.Array(
Type.String({
format: 'email',
})
)
),
name: Type.String(),
},
{
additionalProperties: false,
}
);
export const createSchema = {
body: createBodySchema,
}; // types.ts
import { RequestGenericInterface } from 'fastify';
import { Static } from '@sinclair/typebox';
import { createBodySchema } from './schemas';
export interface CreateRoute extends RequestGenericInterface {
Body: Static<typeof createBodySchema>;
} // x.controller.ts
import { Controller, POST } from 'fastify-decorators';
import { FastifyReply, FastifyRequest } from 'fastify';
import { CreateRoute } from './types';
import { createSchema } from './schemas';
@Controller({
route: '/api/v1/'
})
export default MyController {
@POST('/', {
schema: createSchema,
})
public async create(
{ body: { name, members } }: FastifyRequest<CreateRoute>,
reply: FastifyReply
) {
// Implement
}
} |
Beta Was this translation helpful? Give feedback.
-
Hi there, if I understand correctly - what are you asking for is currently not possible with typescript because decorators are unable to change method signature somehow P.S. it could be possible to simplify things a bit if you are using request handlers. Abstract class @GET()
export class MyRequestHandler {
constructor(private request: FastifyRequest<BodyType>, private reply: FastifyReply) {}
async handle() {}
} |
Beta Was this translation helpful? Give feedback.
Hi there, if I understand correctly - what are you asking for is currently not possible with typescript because decorators are unable to change method signature somehow
Please check this issue and let us know if it what are you asking for
microsoft/TypeScript#49229
P.S. it could be possible to simplify things a bit if you are using request handlers. Abstract class
RequestHandler
was designed just to avoid misusage, you are free to define your own constructor and play with types, for example: