Open tmcw opened 5 months ago
Struggle with that problem too, using this solution for now.
@sinclairzx81 wdyt? How can we solve this?
@mcollina Hi!
Automatic type coercion can be achieved with TypeBox Transforms + overriding the Fastify preSerialization and validationCompiler request/response phases. This hasn't been integrated into the provider yet (but there has been a few PR's attempted, see https://github.com/fastify/fastify-type-provider-typebox/pull/99 and https://github.com/fastify/fastify-type-provider-typebox/pull/127).
If it's helpful, I've updated the provider submitted on https://github.com/fastify/fastify-type-provider-typebox/pull/99 to work with the latest versions of Fastify. I'll post this below.
The provider is implemented as a single file that can be copied into a project as a standalone module. This code may constitute a future TypeProvider (requiring a major semver)
This is an example implementation using the provider above. It automatically converts numeric timestamps into JS Date objects by way of Transform types. Note that the configuration of the preSerialization and validationCompiler is handled by way of the TypeProvider function. Also note that this function also sets up the useTypeProvider inference.
import Fastify from 'fastify'
import { Type, TypeProvider } from './provider' // from above
// Function to configure validationCompiler and preSerialization & TypeProvider
const fastify = TypeProvider(Fastify())
// Transforms Number timestamp into a Date object.
const Timestamp = Type.Transform(Type.Number())
.Decode(value => new Date(value)) // runs on -> validationCompiler phase
.Encode(value => value.getTime()) // runs on -> preSerialization phase
// Route: Note that the timestamp is observed as Date object within the route.
fastify.post('/', {
schema: {
body: Timestamp,
response: {
200: Timestamp
}
}
}, (req, reply) => {
console.log('server body:', req.body) // ... receive Date object
reply.send(new Date()) // ... send Date object
})
// Client: Note that client must send Number for the request.
fastify
.inject()
.post('/').headers({ 'Content-Type': 'application/json' })
.body(new Date().getTime()) // ... client sends number timestamps
.then(response => {
console.log(response.payload) // ... client receives number timestamps
})
I may be able to take another look at pushing this functionality through later in the year, but it may be a good opportunity for someone in the community to have a go also (I'd certainly be happy to review a PR if someone wants to take the above provider and integrate it). In the short term though, I would recommend a bit of experimentation with the provider implementation submitted here (just to make sure it ticks all the boxes, and to check that the overriding of request/response phases doesn't have unintended breakage in plugins)
Also, need to compare the implementation submitted here with future proposals to update the provider inference, just referencing this PR https://github.com/fastify/fastify/pull/5315 which would have implication for this and other providers.
Let me know if the above helps. Happy to field any additional questions on the implementation :) Cheers! S
cc @ehaynes99
Why is preSerialization hook used instead of setSerializerCompiler?
@Bram-dc Hi,
Why is preSerialization hook used instead of setSerializerCompiler?
I believe the intent here was to apply the transform / encode "before" the value was sent for serialization. The preSerialization
hook seemed the best way to achieve this.
Prerequisites
Fastify version
4.26.1
Plugin version
4.0.0
Node.js version
v20.11.0
Operating system
macOS
Operating system version (i.e. 20.04, 11.3, 10)
14.3 (23D56)
Description
I filed an issue and submitted a PR over on
fast-json-stringify
https://github.com/fastify/fast-json-stringify/pull/683 but this affects the stack at different layers, apparently. Basic example:This produces this TypeScript error:
But the code works as you'd expect -
fast-json-stringify
is able to stringify that Date object into an ISO Date string. fast-json-stringify has an explicit list of these conversions and they work, but they aren't supported at this level.I think the core of this issue is that this module is using
Static
from TypeBox to derive types. But that is strict: the static type of aType.String
is just a string:https://github.com/sinclairzx81/typebox/blob/fd1056b367479c7a9925143641d272b4a238ffad/src/type/string/string.ts#L77-L81
There's a
StaticEncode
type in TypeBox, but that's for custom types, not for defaults.Anyway, this is a rough issue if you're using a database setup that produces JavaScript Date objects for date columns and you've been relying on their default stringification. And it works, but the types don't work and there's no easy workaround.
I'm open to suggestions from the maintainers about how this could be resolved. Off the top of my head I'm guessing:
Steps to Reproduce
Try using default stringification with a body, like:
Expected Behavior
The types for this module should reflect what's possible with the code.