Summary
Date.prototype mutator methods (setHours, setDate, setMinutes, etc.)
throw TypeError: (number).setHours is not a function at runtime under
perry compile. The Date object created by new Date() doesn't carry the
mutator methods; only the format/getter methods (.toISOString(),
.getHours()) work.
Repro
// repro.ts
export function getExpiry(): Date {
const expiry = new Date();
expiry.setHours(expiry.getHours() + 1); // throws
return expiry;
}
console.log(getExpiry().toISOString());
Compile + run under perry → fastify handler:
TypeError: (number).setHours is not a function
Note the error spelling — Perry seems to box new Date() as a number
internally; .setHours resolves on the number, not on a Date prototype.
Workaround we shipped
Replace mutators with Date.now() + millis arithmetic, which works fine:
export function getExpiry(): Date {
return new Date(Date.now() + 60 * 60 * 1000); // works
}
This pattern is mentioned approvingly in PERRY_GAPS notes (new Date()
cloning via Date.now() arithmetic was a previous fix in our codebase),
so it seems to be the supported path. But it'd be nice if the mutators
either worked or threw a clearer "not implemented" error at compile-time
or first call so the bug doesn't only surface when the route fires in
prod.
Affected methods (untested but presumed all broken)
setHours, setDate, setMinutes, setSeconds, setMilliseconds,
setFullYear, setMonth, setTime, plus the UTC variants.
Context
Found while debugging /api/auth/forgot-password in a Fastify app
compiled with perry. The handler called a getPasswordResetExpiry()
helper that used setHours; the resulting (number).setHours is not a function was caught by the route's try/catch and surfaced as a generic
500 to the client. Took some log-spelunking to track down because the
error message reads like a Perry-internal type tag leak rather than "this
method isn't implemented."
Two adjacent symptoms (not asking you to fix here, just flagging)
While debugging the above I also hit:
-
request.json() returns undefined under Perry. Stock Fastify
doesn't have .json() either (it's a Fetch API method, not Fastify),
so this may be intentional. But the failure mode — undefined return
instead of a clear error — surprised us; the codebase had ~27 call
sites that all silently 400'd. request.body works correctly.
-
return reply.status(400).send({ error: "..." }) serializes to
literal body null (4 bytes), not the JSON object. Returning the
object directly (reply.code(400); return { error: "..." };) or
using implicit serialization (return { ok: true }) both work
correctly. The reply.status(N).send(obj) chain seems to drop the
payload somewhere in the Perry Fastify shim.
Happy to file either of these as separate issues if you want them
tracked. Both are reproducible against perry @ main (worker built
2026-05-20).
Summary
Date.prototypemutator methods (setHours,setDate,setMinutes, etc.)throw
TypeError: (number).setHours is not a functionat runtime underperry compile. The Date object created bynew Date()doesn't carry themutator methods; only the format/getter methods (
.toISOString(),.getHours()) work.Repro
Compile + run under perry → fastify handler:
Note the error spelling — Perry seems to box
new Date()as a numberinternally;
.setHoursresolves on the number, not on a Date prototype.Workaround we shipped
Replace mutators with
Date.now()+ millis arithmetic, which works fine:This pattern is mentioned approvingly in PERRY_GAPS notes (
new Date()cloning via
Date.now()arithmetic was a previous fix in our codebase),so it seems to be the supported path. But it'd be nice if the mutators
either worked or threw a clearer "not implemented" error at compile-time
or first call so the bug doesn't only surface when the route fires in
prod.
Affected methods (untested but presumed all broken)
setHours,setDate,setMinutes,setSeconds,setMilliseconds,setFullYear,setMonth,setTime, plus the UTC variants.Context
Found while debugging
/api/auth/forgot-passwordin a Fastify appcompiled with perry. The handler called a
getPasswordResetExpiry()helper that used
setHours; the resulting(number).setHours is not a functionwas caught by the route's try/catch and surfaced as a generic500 to the client. Took some log-spelunking to track down because the
error message reads like a Perry-internal type tag leak rather than "this
method isn't implemented."
Two adjacent symptoms (not asking you to fix here, just flagging)
While debugging the above I also hit:
request.json()returnsundefinedunder Perry. Stock Fastifydoesn't have
.json()either (it's a Fetch API method, not Fastify),so this may be intentional. But the failure mode — undefined return
instead of a clear error — surprised us; the codebase had ~27 call
sites that all silently 400'd.
request.bodyworks correctly.return reply.status(400).send({ error: "..." })serializes toliteral body
null(4 bytes), not the JSON object. Returning theobject directly (
reply.code(400); return { error: "..." };) orusing implicit serialization (
return { ok: true }) both workcorrectly. The
reply.status(N).send(obj)chain seems to drop thepayload somewhere in the Perry Fastify shim.
Happy to file either of these as separate issues if you want them
tracked. Both are reproducible against perry @ main (worker built
2026-05-20).