Normalize reader/auth IDs to Snowflake EntityId (or split a distinct branded type)
Nobody has claimed this yet.
- 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/checkoutGET /membership/statusPUT/DELETE /membership/members/:readerId(admin manual grant/revoke)- Any comment endpoint touching
CommentController.withMembership
(getRecentlyComments,getCommentsByRefId, etc.) — this is what
originally surfaced the bug viaInvalid 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(orAuthId) 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 forEntityId/parseEntityId/zEntityId
nearreaderId/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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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