mx-space / mx-space/core

Normalize reader/auth IDs to Snowflake EntityId (or split a distinct branded type)

Open
#2,774 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
TypeScript
Stars
558
Forks
156
Avg merge
10h 57m
Merged PRs (30d)
5

Description

Background

readers.id (and any FK referencing it, e.g. memberships.readerId) is a
Better Auth-generated opaque text ID (e.g. 6QWDKetmN3wWTimN1d0SHBYPPxoCYp4Y),
not a Snowflake EntityId (/^[1-9]\d{0,18}$/). Most of the codebase
assumes every ID crossing a repository boundary is a Snowflake bigint
serialized as text, validated via parseEntityId / toEntityId / zEntityId.

This mismatch already caused a real bug: MembershipRepository,
EntitlementService, and MembershipController ran readerId through
parseEntityId / zEntityId, which rejects every real Better Auth reader id
(they're never pure-digit). This broke, for effectively all readers:

  • POST /membership/checkout
  • GET /membership/status
  • PUT/DELETE /membership/members/:readerId (admin manual grant/revoke)
  • Any comment endpoint touching CommentController.withMembership
    (getRecentlyComments, getCommentsByRefId, etc.) — this is what
    originally surfaced the bug via Invalid EntityId format: ... in the logs.

Hotfixed by treating readerId as a plain string throughout the membership
module, matching the pattern already used in comment.repository.ts for the
same FK. This issue tracks the follow-up so the same class of bug can't
recur elsewhere.

Proposal

Audit every ID crossing the readers/auth boundary and pick one consistent
representation, e.g.:

  • Migrate readers.id (and the other Better Auth-owned tables: accounts,
    sessions, apiKeys, passkeys, verifications) to Snowflake-generated IDs, so
    they're consistent with the rest of the schema, or
  • Introduce a distinct branded ReaderId (or AuthId) type, separate from
    EntityId, with its own parse/validate helpers — so a future accidental
    parseEntityId(readerId) call fails to compile instead of throwing at
    request time.

Scope to check

  • All refText-declared FK columns that actually point at Better
    Auth-owned tables vs. genuine Snowflake entities.
  • Any other module besides membership assuming reader/session/user ids are
    numeric Snowflake ids (grep for EntityId / parseEntityId / zEntityId
    near readerId / userId / reader-auth code paths).

Not in scope here

This is a larger, deliberately deferred refactor. The immediate hotfix for
the membership module ships separately.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with MembershipRepository, EntitlementService, MembershipController, CommentController.withMembership, and comment.repository.ts, then grep for EntityId, parseEntityId, zEntityId, readerId, and userId across reader/auth paths. Audit refText foreign keys and choose either Snowflake IDs or a distinct ReaderId/AuthId representation; done means all Better Auth boundary IDs are consistently represented and cannot be misvalidated as EntityId.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication, backend, database
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.