Summary
PostAuthorSchema and CommentAuthorSchema declare username as optional, but the database guarantees it is always present. The published contract advertises slack it does not have, and clients reasonably code against it.
// src/http/types/schemas/post/get-post.schema.ts
export const PostAuthorSchema = FBType.Object({
id: FBType.String({ format: "uuid" }),
username: FBType.Optional(FBType.String()), // ← never actually absent
avatarUrl: FBType.String(),
fullName: FBType.Union([FBType.String(), FBType.Null()]),
isMe: FBType.Optional(FBType.Boolean()),
});
CommentAuthorSchema in src/http/types/schemas/comment/get-comment.schema.ts has the same shape.
Why it cannot be absent
| Layer |
Constraint |
prisma/models/user.prisma |
username String @unique — NOT NULL |
prisma/models/post.prisma (Post, Comment) |
author User @relation(..., onDelete: Cascade) — required relation. Deleting a user cascades their posts and comments away, so an authorless row cannot exist. |
src/infrastructure/persistence/repositories/prisma-post.repository.ts |
every query selects author: { select: { id, username, profile: { select: { avatarUrl, fullName } } } } |
The optional chaining in the mapper is what makes it look possible:
// post-prisma.mapper.ts, toDomainPost
username: dbPost.author?.username ?? undefined,
That ?. guards against the include being absent at the TypeScript level, not against the data. Since every query includes it, the ?? undefined branch is unreachable.
Since username is optional in the schema, Fastify's serializer omits the key entirely when it is undefined — so a client that trusted the schema would have to handle a missing key that never arrives.
Impact
Reported from the client side. Reading this schema, we built fallbacks in PostCard / CommentCard for an author with no handle — a placeholder display name and non-clickable avatars — and typed username?: string. All of it was dead code guarding an impossible state, and it had to be reverted (the-developer-network/tdn-client#121). Any other consumer reading the schema will reach the same conclusion.
Proposed change
Make username required in both schemas:
- username: FBType.Optional(FBType.String()),
+ username: FBType.String(),
Before doing it
toDomainPost still writes ?? undefined. With a required schema, any future query that skipped the author include would fail serialization loudly rather than silently omitting the key. That is the better failure — but it is a behaviour change, so it is worth either:
- dropping the
?? undefined and letting the type system require the include, or
- keeping it and accepting the loud failure as the intended signal.
Worth a quick grep for any other caller of toDomainPost that builds a Post without the author relation before flipping it.
Also worth checking
avatarUrl is FBType.String() (required) and toFeedResponse always supplies a CDN fallback, so that one is already consistent. fullName is Union([String, Null]) and normalised to null — also fine. username looks like the only field where the schema and the column disagree.
Filed from a client-side sweep; happy to send the PR when this repo is next in scope.
Summary
PostAuthorSchemaandCommentAuthorSchemadeclareusernameas optional, but the database guarantees it is always present. The published contract advertises slack it does not have, and clients reasonably code against it.CommentAuthorSchemainsrc/http/types/schemas/comment/get-comment.schema.tshas the same shape.Why it cannot be absent
prisma/models/user.prismausername String @unique— NOT NULLprisma/models/post.prisma(Post,Comment)author User @relation(..., onDelete: Cascade)— required relation. Deleting a user cascades their posts and comments away, so an authorless row cannot exist.src/infrastructure/persistence/repositories/prisma-post.repository.tsauthor: { select: { id, username, profile: { select: { avatarUrl, fullName } } } }The optional chaining in the mapper is what makes it look possible:
That
?.guards against the include being absent at the TypeScript level, not against the data. Since every query includes it, the?? undefinedbranch is unreachable.Since
usernameis optional in the schema, Fastify's serializer omits the key entirely when it is undefined — so a client that trusted the schema would have to handle a missing key that never arrives.Impact
Reported from the client side. Reading this schema, we built fallbacks in
PostCard/CommentCardfor an author with no handle — a placeholder display name and non-clickable avatars — and typedusername?: string. All of it was dead code guarding an impossible state, and it had to be reverted (the-developer-network/tdn-client#121). Any other consumer reading the schema will reach the same conclusion.Proposed change
Make
usernamerequired in both schemas:Before doing it
toDomainPoststill writes?? undefined. With a required schema, any future query that skipped the author include would fail serialization loudly rather than silently omitting the key. That is the better failure — but it is a behaviour change, so it is worth either:?? undefinedand letting the type system require the include, orWorth a quick grep for any other caller of
toDomainPostthat builds aPostwithout the author relation before flipping it.Also worth checking
avatarUrlisFBType.String()(required) andtoFeedResponsealways supplies a CDN fallback, so that one is already consistent.fullNameisUnion([String, Null])and normalised tonull— also fine.usernamelooks like the only field where the schema and the column disagree.Filed from a client-side sweep; happy to send the PR when this repo is next in scope.