Skip to content

About

Extracts a runtime model map (fields, relations, FK direction, enums) from a generated Prisma client. Supports Prisma v6 & v7.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@inixiative/prisma-map

One of the inixiative building blocks. Stop rediscovering what you need to build. See all the projects.

Reads the Prisma client you already generate and gives you its models, fields, relations and foreign keys as one plain object you can use at runtime. You don't add a Prisma generator or parse the DMMF yourself. The map keys everything by its Prisma name and records the database name wherever @map / @@map changes it, so code that writes SQL resolves identifiers through tableName / columnName / storedValue instead of guessing. @inixiative/permissions walks relations through this map, and @inixiative/json-rules reads it as a field map.

@inixiative/prisma-map builds a PrismaMap object that includes:

  • Models and fields
  • Field kinds (scalar, enum, object)
  • Relation FK direction (fromFields, toFields)
  • Model DB table name (@@map) as dbName
  • Field DB column name (@map) as dbName
  • Enum values (ordered), with stored DB values (@map on members) as valueDbNames
  • Field default (@default) as default: { kind: 'literal', value } for a value stored as written (enum member, string, number, boolean, list; a Json default parsed to its value; BigInt/Decimal kept as exact text), { kind: 'generated', expression } for a function the database or Prisma evaluates (now(), uuid(), dbgenerated(...), autoincrement()) or a Bytes value; generated defaults are never injected

It supports both Prisma client layouts:

  • Prisma v7: parse generated internal/class.ts
  • Prisma v6: parse generated schema.prisma

Installation

bun add @inixiative/prisma-map

or

npm i @inixiative/prisma-map

Quick Start

Prisma v7

import { buildPrismaMapV7 } from '@inixiative/prisma-map';

const map = buildPrismaMapV7();
// or: buildPrismaMapV7('/absolute/path/to/generated/client')

console.log(map.User.fields.posts);

Prisma v6

import { buildPrismaMapV6 } from '@inixiative/prisma-map';

const map = buildPrismaMapV6();
// or: buildPrismaMapV6('/absolute/path/to/generated/client')

console.log(map.Post.fields.author);

Output Shape

The library returns:

type PrismaMap = Record<string, {
  dbName: string | null;
  fields: Record<string, ScalarField | EnumField | RelationField>;
}>;

Scalar field

{
  "kind": "scalar",
  "type": "String",
  "isRequired": true,
  "isList": false,
  "isId": false
}

Enum field

{
  "kind": "enum",
  "type": "UserRole",
  "isRequired": true,
  "isList": false,
  "values": ["ADMIN", "USER", "GUEST"]
}

Relation field

{
  "kind": "object",
  "type": "User",
  "isList": false,
  "isRequired": true,
  "relationName": "AuthoredPosts",
  "fromFields": ["authorId"],
  "toFields": ["id"]
}

fromFields/toFields are empty arrays on back-relations.

Example End-to-End

Given:

enum Role {
  ADMIN
  USER
}

model User {
  id    String @id
  role  Role
  posts Post[]

  @@map("users")
}

model Post {
  id       String @id
  authorId String
  author   User   @relation("AuthoredPosts", fields: [authorId], references: [id])
}

A representative map:

{
  "User": {
    "dbName": "users",
    "fields": {
      "id": {
        "kind": "scalar",
        "type": "String",
        "isRequired": true,
        "isList": false,
        "isId": true
      },
      "role": {
        "kind": "enum",
        "type": "Role",
        "isRequired": true,
        "isList": false,
        "values": ["ADMIN", "USER"]
      },
      "posts": {
        "kind": "object",
        "type": "Post",
        "isList": true,
        "isRequired": true,
        "fromFields": [],
        "toFields": []
      }
    }
  },
  "Post": {
    "dbName": null,
    "fields": {
      "author": {
        "kind": "object",
        "type": "User",
        "isList": false,
        "isRequired": true,
        "relationName": "AuthoredPosts",
        "fromFields": ["authorId"],
        "toFields": ["id"]
      }
    }
  }
}

API

Root exports:

import {
  buildPrismaMapV6,
  buildPrismaMapV7,
  getColumns,
  getRelations,
  pickColumns,
  relationForeignKey,
  parseTagClasses,
  type PrismaMap,
  type ModelEntry,
  type ModelField,
  type ScalarField,
  type EnumField,
  type RelationField,
  type IndexEntry,
  type RelationInfo,
  type Identifier,
  type Annotations,
} from '@inixiative/prisma-map';

Version-specific exports:

import { buildPrismaMapV7, parseRuntimeDataModel, parseInlineSchema, parseRelationFks, parseEnumValues, parseSchemaStructure } from '@inixiative/prisma-map/v7';
import { buildPrismaMapV6, parseSchemaText, buildFromSchemaFile } from '@inixiative/prisma-map/v6';

Columns

getColumns(map, modelName) lists a model's column field names (scalar and enum fields). pickColumns(map, modelName, row) keeps only those keys of a row, so a write result carrying included relations reduces to the record itself.

import { getColumns, pickColumns } from '@inixiative/prisma-map';

getColumns(map, 'User');
// ['id', 'role']

pickColumns(map, 'User', { id: 'u1', role: 'ADMIN', posts: [{ id: 'p1' }] });
// { id: 'u1', role: 'ADMIN' }

Relation Traversal

getRelations(map, modelName) lists a model's relation fields with their foreign keys collapsed for lookup; relationForeignKey(field) does the collapse alone.

import { getRelations } from '@inixiative/prisma-map';

getRelations(map, 'Inquiry');
// [
//   { relationName: 'organization', targetModel: 'Organization', isList: false,
//     foreignKey: { id: 'organizationId' } },
//   { relationName: 'parent', targetModel: 'Inquiry', isList: false,
//     foreignKey: { id: 'parentId' }, annotations: { tree: { parent: true } } },
//   { relationName: 'children', targetModel: 'Inquiry', isList: true, foreignKey: null },
// ]

foreignKey is a bare field name for a single same-named pair, a { referencedField: localField } map otherwise, and null for back-relations. Functions are string-keyed. Wrap them with your generated ModelName types if you want typed accessors.

Annotation DSL (/// @tagClass(key: value))

Prisma rejects custom attributes, but preserves /// doc comments: into the generated client's inlineSchema (v7) and the schema.prisma it copies (v6). prisma-map parses a small, forward-looking DSL out of those comments, the same way for both, and records it under annotations, without assigning the tags any meaning.

model Category {
  id       String     @id
  parentId String?
  /// @tree(parent: true)
  parent   Category?  @relation("tree", fields: [parentId], references: [id])
  children Category[] @relation("tree")

  /// @search(fuzzy: true)
  @@index([name])
}
map.Category.fields.parent.annotations; // { tree: { parent: true } }
map.Category.indexes;
// [{ kind: 'index', fields: ['name'], annotations: { search: { fuzzy: true } } }]
  • Grammar: @<tagClass>(<key>: <value>, …); multiple tag classes per line allowed.
  • Values: true/false → boolean, numerics → number, "quoted"/bare words → string, [a, b] → array.
  • Placement: a /// run binds to the declaration directly below it: a model, a field, or an index (@@index / @@unique / @@id / @@fulltext). A blank line or a plain // breaks the binding.
  • prisma-map only records the bag; what a tag class means (e.g. tree.parent → "don't auto-recurse up this self-relation") is the consumer's to decide.

Auto-Detection Behavior

If you omit the path, the library walks upward from process.cwd() and checks common generated client locations:

  • node_modules/.prisma/client
  • node_modules/@prisma/client
  • prisma/generated/client
  • src/generated/client
  • src/generated/prisma
  • generated/client

v7 detection requires internal/class.ts. v6 detection requires schema.prisma and excludes directories that also have internal/class.ts.

Practical Use Cases

  • Build rule-engine metadata for JSON rules
  • Resolve template/package dependency graphs via relation edges
  • Generate guardrails (for example, "entity X must include relation Y")
  • Build field-level introspection UIs

Resolving DB identifiers

map.X.fields.y is a ModelField union, so narrow it first. Relation fields have no column and the resolvers reject them at compile time:

import {
  columnName,
  hasColumn,
  isEnumField,
  storedValue,
  tableName,
  type DbIdentifier,
  type DbValue,
} from '@inixiative/prisma-map';

const model = map.BrandMissions;

tableName(model, 'BrandMissions'); // '@@map' if set, else 'BrandMissions'

const createdAt = model.fields.createdAt;
if (hasColumn(createdAt)) {
  columnName(createdAt, 'createdAt'); // 'created_at' via '@map'
}

const typeName = model.fields.typeName;
if (isEnumField(typeName)) {
  storedValue(typeName, 'SOCIAL_POST'); // 'Social Post' via '@map' on the member
  storedValue(typeName, 'UNMAPPED'); // 'UNMAPPED' — no '@map', the name is the value
}

The resolvers return branded DbIdentifier / DbValue rather than bare string. Both surfaces are strings, so a mix-up otherwise produces valid SQL that silently matches no rows. Type a SQL builder's parameters as the brands and a raw Prisma name becomes a compile error:

const eq = (column: DbIdentifier, value: DbValue) => `${column} = '${value}'`;

eq('missionTypeName', 'SOCIAL_POST'); // compile error — raw Prisma names rejected

Notes and Limits

  • Every key in the map is the Prisma name: map.User, map.User.fields.createdAt, and the entries of values. dbName / valueDbNames are the only crossings to the physical DB surface, and both are sparse: absent means the Prisma name IS the DB identifier. Resolve them with tableName / columnName / storedValue rather than open-coding ?? name. A missed fallback fails silently (comparing against an unmapped enum member selects zero rows instead of erroring).
  • @@map on a model is emitted as ModelEntry.dbName; @map on a scalar/enum field as the field's dbName; @map on enum members as the field's valueDbNames.
  • Relation fields never carry dbName; they have no column.
  • Native DB type annotations (for example @db.Text) are not emitted.
  • relationName is included when available in Prisma runtime metadata.

Development

bun run test
bun run typecheck
bun run check
bun run build

License

MIT

About

Extracts a runtime model map (fields, relations, FK direction, enums) from a generated Prisma client. Supports Prisma v6 & v7.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages