Prisma's other @@map spelling, and the foreign keys we were dropping
Prisma accepts @@map("users") and @@map(name: "users"). Wyro misread the second, and dropped every foreign key into a renamed table. Both are fixed.
Try WyroPrisma lets you give a model's table a different name in two ways: @@map("accounts") and @@map(name: "accounts"). They mean exactly the same thing. Until today Wyro understood only the first. Given the second, it read a table called name: accounts, with the quotes stripped and the argument's label left in. Fixing that turned up a bigger problem behind it: a foreign key that points at a renamed table, in either spelling, never reached the code Wyro generates. Both are fixed, along with two more ways of losing a foreign key, in Prisma schemas split across files and in Drizzle.
Two spellings of the same name
@@map takes a single argument, name, and Prisma accepts it with or without the label. Field-level @map works the same way:
model Account {
id String @id @default(cuid())
userId String
providerAccountId String @map(name: "provider_account_id")
user User @relation(fields: [userId], references: [id])
@@map(name: "accounts") // the same as @@map("accounts")
}The labelled form is the less common one, but it isn't rare. GitHub's code search estimates about 2,350 .prisma files containing @@map(name:, and all 50 we sampled really do use it. shadcn's taxonomy starter writes all five of its tables that way and has been forked more than 2,700 times. We ran into it in Saas-Kit-prisma, a Next.js SaaS starter whose six models all use it:

What the wrong name broke
The table name is how everything else finds a table, so a wrong one fails quietly in several places:
- Findings named tables that don't exist, such as
name: users. - Raw SQL couldn't be matched. Wyro matches the tables named in
$queryRawand$executeRawSQL against the tables in the schema, so it can tell what a route touches even when the query is raw SQL rather than a model call. ADELETE FROM sessionsfound no table calledsessions, and the write went unrecorded. - Generated code didn't compile. A schema imported to the canvas and compiled back to code came out as
export const name: accounts = pgTable("name: accounts", …), which isn't valid TypeScript.
One thing it didn't break: the rule that lets a sign-in route reach your identity tables without being flagged. That rule splits names on punctuation, and name: users still contains users. On the starter kit, the same rules fired on the same routes before and after the fix. Only the table names in the findings changed.
The bigger bug: foreign keys that never arrived
When Wyro reads a schema, it records each foreign key as the name of the table it points to. The compiler then looks that table up by name, and drops a reference to a table that doesn't exist, so it can never generate a constraint that points nowhere. The Prisma reader was recording the model name instead. userId pointed at User, the table is called users, the compiler found nothing, and the foreign key was dropped. There was no error and no warning. The column just came out as plain text, without a constraint or an index.

This had nothing to do with how @@map was spelled. Any Prisma schema that renames its tables lost every foreign key pointing into them. In the starter kit that was all four:

Two more cases lost foreign keys the same way:
- Prisma schemas split across files. Prisma lets one schema span several
.prismafiles in a folder. Wyro read each file on its own, soauthor Userinpost.prismadidn't know thatUserwas a model inuser.prisma. The relation was reported as a field of unknown type, and its foreign key was lost. Names now resolve across every file of the schema, including a file that holds nothing but enums. - Drizzle, when a variable and its table have different names.
.references(() => usersTable.id)names a variable. If that variable isexport const usersTable = pgTable("app_users", …), the reference pointed atusersTable, and the compiler dropped it the same way. It now resolves toapp_users, including when the two tables are declared in different files.
In a monorepo, names resolve within one schema only, meaning the files under one datasource, so two apps that each declare a User model never pick up each other's table.
How the bug survived a test
The Prisma reader had a test for exactly this foreign key, and it passed. It asserted that the reference pointed at User, because that is what the parser produced when the test was written. The test described what the parser did, not what the compiler needed, so it pinned the bug in place. It now asserts users, and a new test follows the foreign key all the way through the compiler instead of stopping at the parser.
- Fixed
@@map(name: "x")and@map(name: "x")are read asx, the same as@@map("x")and@map("x"). - FixedA Prisma foreign key points at the table a model maps to, not the model name, so it survives compilation.
- FixedIn a Prisma schema split across files, relations and enums resolve across the whole schema, and two schemas in one repository stay separate.
- FixedA Drizzle foreign key resolves through the variable to the table it declares.
Every fix has a regression test that fails on the old code, eight new and the one corrected above, and the full suite of 1,884 tests passes. Both starter kits above were re-run end to end. Saas-Kit-prisma's six tables and taxonomy's five are now read by their real names, and all seven of their foreign keys reach the generated schema.
Still open
Writing this turned up three Prisma defaults that the compiler still gets wrong. We are listing them rather than leaving them out of the screenshots:
@default(cuid())comes out as the literal string"cuid()". Every insert that doesn't set an id gets that same value, so the second one fails on the primary key.@default(autoincrement())comes out asdefault(0).@default(now(), map: "DF_created"), the SQL Server way of naming a default, keeps themap:text in the default value.
All three affect generated code only, not the architecture check.
Check your own
For a public repository, paste it into the scan page. There's no account to create. For a private one, the same check runs entirely on your machine with Node 18 or later:
curl -fsSL https://wyro.in/wyro-check.js -o wyro-check.js
node wyro-check.js path/to/your/backendIf you imported a Prisma or Drizzle project into Wyro before today, import it again to pick up its foreign keys. The GitHub Action checks for a new checker on every run, so it picks up these fixes on its next one, unless you have pinned its checksum.