Skip to content

Author schemas declare username optional, but the database guarantees it #182

Description

@aquie00t

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions