Skip to content

feat: introduce branded user identifiers#7

Open
leynos wants to merge 1 commit intomainfrom
codex/action-unresolved-comments-on-pr-5
Open

feat: introduce branded user identifiers#7
leynos wants to merge 1 commit intomainfrom
codex/action-unresolved-comments-on-pr-5

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Aug 14, 2025

Summary

  • type user IDs with a brand to avoid mixing with arbitrary strings

Testing

  • make fmt
  • make lint
  • make test

https://chatgpt.com/codex/tasks/task_e_689e400c106c83228dcf64663b87b624

Summary by Sourcery

Introduce a branded UserId type and update the user API schema to enforce it

New Features:

  • Add UserId branded string type for stronger ID safety

Enhancements:

  • Define explicit User interface and refine Zod schema to transform id into UserId

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 14, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds a branded UserId type for stronger type safety and updates the API client to use it by extending types and adjusting the user Zod schema accordingly.

Entity relationship diagram for User entity with branded UserId

erDiagram
    USER {
        UserId id
        string display_name
    }
    USER_ID {
        string brand
    }
    USER ||--|| USER_ID : has
Loading

Class diagram for updated User and UserId types

classDiagram
    class UserId {
        <<type>>
        brand: 'UserId'
    }
    class User {
        id: UserId
        display_name: string
    }
    User --> UserId
Loading

File-Level Changes

Change Details Files
Introduce branded UserId and explicit User interface
  • Define UserId as a string & brand
  • Add User interface with id: UserId
  • Replace z.infer with explicit interface export
frontend-pwa/src/api/client.ts
Enhance zod userSchema to cast id to UserId
  • Transform id string to branded UserId
  • Enforce schema type with satisfies z.ZodType
frontend-pwa/src/api/client.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 14, 2025

Summary by CodeRabbit

  • New Features
    • No user-facing features added in this release.
  • Refactor
    • Aligned user data validation with the public user shape to ensure consistent handling across the app.
  • Chores
    • Strengthened internal typing for user identifiers to improve data integrity.
    • Updated parsing logic to bind to the refined user model without changing how existing user lists are used.
    • General internal clean-up with no impact on current functionality or user workflows.

Walkthrough

Introduce a branded UserId type and a public User interface. Update userSchema: cast id strings to UserId and declare the schema to satisfy the User interface. Remove the inferred User type export. Keep listUsers usage unchanged while parsing now yields the updated User shape.

Changes

Cohort / File(s) Summary
API client types and schema
frontend-pwa/src/api/client.ts
Add branded UserId and public User interface. Change userSchema.id from z.string() to z.string().transform(id => id as UserId). Remove export type User = z.infer<typeof userSchema> and annotate schema with satisfies z.ZodType<User>.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

Forge a name with a subtle brand,
IDs stamped by a careful hand.
Schemas nod, “we match the plan,”
Types align where parsing ran.
One file tuned, the contract clear—
Compile-time guards now standing near.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/action-unresolved-comments-on-pr-5

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • Jira integration is disabled

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 30cc25a and e5aace5.

📒 Files selected for processing (1)
  • frontend-pwa/src/api/client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.{ts,tsx}: ESM-only: source/build outputs are ES Modules; configure Vite accordingly; no CommonJS
Place JSDoc comments above declarations/decorators; keep docs close to code
Prefer immutability: const, readonly, Readonly; avoid mutating inputs
Extract helpers for long functions; keep trivial functions on one line when readable
Group related parameters into typed objects/builders; avoid long positional lists
Extract predicate functions or use lookup tables for multi-branch conditionals; enforce exhaustive switch with never guard
Every module begins with /** @file … */ describing purpose and usage
Validate I/O with runtime schemas (zod/valibot) at boundaries; keep types and schemas in sync
Use branded types for identifiers/tokens
Accept AbortSignal for cancellable async operations; wire through TanStack Query fetchers
Centralise time and RNG adapters; avoid direct Date.now()/Math.random() in business logic
Use discriminated unions for recoverable errors; reserve Error subclasses for exceptional paths with cause
Use Vitest with jsdom/happy-dom; keep tests deterministic and parallel-safe
Use factories/builders for fixtures; prefer parameterised tests with test.each/it.each
Use vi.mock for module boundaries; inject adapters for env/time/storage/fetch; avoid monkey-patching globals
Use vi.useFakeTimers() for time-based logic; restore after each test
Keep snapshots deterministic (sorted keys, fixed seeds) and scoped to stable UI fragments
Use Biome for format+lint; disallow any, non-null !; forbid @ts-ignore in favour of @ts-expect-error with reason
Enforce import hygiene: sorted/grouped imports; no unused or extraneous deps
Use dynamic import() for code-splitting; enable Vite prefetch and asset hashing
Configure TanStack Query with appropriate stale-time, cache-time; avoid refetchOnWindowFocus unless necessary
Avoid await in loops; batch with Promise.allSettled; use async iterables/streams for large data
Ship CSP, avoid eval/new Function; use Trusted Types and sa...

Files:

  • frontend-pwa/src/api/client.ts
**/*.{tsx,ts}

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.{tsx,ts}: Map domain errors to user messages at UI boundary; do not leak raw stacks to DOM/analytics
Enable React StrictMode in dev; memoise expensive components; derive data via selectors
Prefer semantic HTML; use daisyUI components only when they don’t harm semantics; audit focus and contrast

Files:

  • frontend-pwa/src/api/client.ts
🔍 Remote MCP (Context7)

Additional Context for Reviewing the PR

  • Zod .transform() semantics
    Transforms are applied after parsing and validation, and the schema’s output type reflects the transform.
    Example:

    const stringToNumber = z.string().transform(val => val.length);
    // .parse() returns a number

    ()

  • Type inference with transforms
    After a .transform(), z.infer<typeof schema> and the schema’s parse result use the output type, not the input type. This ensures that casting id to UserId at parse time correctly updates the static type.
    ()

  • Binding a schema to a TS interface with satisfies z.ZodType<…>
    Using

    z.object({}) satisfies z.ZodType<User>;

    enforces at compile time that the inferred schema output exactly matches the User interface you’ve declared.
    ()

  • Nominal typing via .brand()
    Instead of manually casting to a branded type, Zod offers a built-in .brand<"UserId">() method to attach a unique “brand” to strings (preventing accidental mixing of IDs), yielding the same string & { readonly brand: "UserId" } without a manual transform.
    ()

Tags:

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (2)
frontend-pwa/src/api/client.ts (2)

17-17: Schema-to-interface binding via satisfies z.ZodType<User> is spot on

Keep this; it enforces compile-time parity between the schema’s output and the public interface.


15-15: Replace no-op transform with branded schema

Remove the pointless transform and brand at the schema level so runtime and static types align:

-  id: z.string().transform(id => id as UserId),
+  id: userIdSchema,

Or inline the brand helper:

-  id: z.string().transform(id => id as UserId),
+  id: z.string().brand<'UserId'>(),

Ensure your Zod version is at least 3.19.0 to support .brand(). Verify in frontend-pwa by running:

npm ls zod

Comment on lines +7 to +8
export type UserId = string & { readonly brand: 'UserId' };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prefer Zod .brand() and derive UserId from a schema; drop the manual intersection brand

Eliminate the hand-rolled brand and derive the type from a dedicated schema to keep runtime validation and types in lockstep and avoid the no-op transform later.

Apply this diff to replace the manual brand with a schema-derived type:

-export type UserId = string & { readonly brand: 'UserId' };
+/** Branded user identifier (nominal type), parsed by userIdSchema. */
+export type UserId = z.infer<typeof userIdSchema>;

Then, add this schema near the imports (outside this hunk):

// Reusable branded ID schema
export const userIdSchema = z.string().brand<'UserId'>();

Comment on lines +9 to +13
export interface User {
id: UserId;
display_name: string;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Document and freeze the public User shape; mark fields readonly

Document the export and make fields readonly to discourage mutation of parsed data in consumers.

-export interface User {
-  id: UserId;
-  display_name: string;
-}
+/** Public user shape parsed from the API. Invariant: id is a branded identifier. */
+export interface User {
+  readonly id: UserId;
+  readonly display_name: string;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface User {
id: UserId;
display_name: string;
}
/** Public user shape parsed from the API. Invariant: id is a branded identifier. */
export interface User {
readonly id: UserId;
readonly display_name: string;
}
🤖 Prompt for AI Agents
In frontend-pwa/src/api/client.ts around lines 9 to 13, the exported User
interface lacks documentation and allows consumers to mutate parsed data; add a
JSDoc comment describing the public User shape and intended usage, and make its
properties readonly (e.g., readonly id: UserId; readonly display_name: string)
so callers are discouraged from mutating instances returned by the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant