prisma-json-types-generator

repository·main·Indexed 20 days ago

https://github.com/arthurfiorette/prisma-json-types-generator

A Prisma generator that replaces default JsonValue types with custom TypeScript types for Json, String, Int, and Float fields. It uses AST comments in the schema.prisma file to provide compile-time safety and autocomplete via namespace-based or inline typing without adding runtime overhead. Version 5.1.1 requires Prisma v7 and TypeScript v6.

Tokens
4.1K
Snippets
11
Records
19
Agent score
67%

What's inside prisma-json-types-generator

  1. How prisma-json-types-generator works

    main
    This package is a Prisma generator that rewrites generated Prisma TypeScript declarations after prisma generate is executed. It uses AST comments (///) in your schema.prisma to inject compile-time typing for Json, String, Int, and Float fields. It adds no runtime overhead and does not provide database-level or runtime enforcement; it is strictly for TypeScript type safety.
  2. Choose between Namespace-based and Inline typing styles

    main

    The generator supports two ways to define types for Prisma fields:

    1. Namespace-based Types (/// [TypeName])

    Use this for reusable or complex shapes, large JSON payloads, or types that will be validated with libraries like Zod. This requires defining the type within a PrismaJson namespace in a TypeScript declaration file.

    Prisma Schema:

    model User {
      /// [UserProfile]
      profile Json
    }

    TypeScript Declaration:

    import type { UserProfile as DomainUserProfile } from './domain/user-profile';
    
    declare global {
      namespace PrismaJson {
        type UserProfile = DomainUserProfile;
      }
    }

    2. Inline Types (/// ![TypeExpression])

    Use this for shorter, simpler types or literal unions. The full TypeScript type expression is written directly in the comment.

    Prisma Schema:

    model Post {
      /// !['draft' | 'published' | 'archived']
      status String
    
      /// ![1 | 2 | 3]
      rank Int
    
      /// ![{ theme: 'dark' | 'light'; twitterHandle?: string }]
      profile Json
    }
    model User {
      /// [UserProfile]
      profile Json
    }
    import type { UserProfile as DomainUserProfile } from './domain/user-profile';
    
    declare global {
      namespace PrismaJson {
        type UserProfile = DomainUserProfile;
      }
    }
    model Post {
      /// !['draft' | 'published' | 'archived']
      status String
    
      /// ![1 | 2 | 3]
      rank Int
    
      /// ![{ theme: 'dark' | 'light'; twitterHandle?: string }]
      profile Json
    }
  3. How namespace-based and inline typing works

    main

    The generator supports two distinct typing methods:

    1. Namespace-based (/// [TypeName]): References a type defined within the PrismaJson global namespace. This is best for complex, reusable types.
    2. Inline (/// ![Type]): Defines the type directly in the schema comment. This is best for simple, one-off types like enums or basic objects.

    Supported Types

    • Json fields: Use /// [TypeName] or /// ![Type].
    • String fields (Enums): Use /// ![union_type] to create string enums.
    • Numeric scalars: Use /// ![1 | 2 | 3] for Int or Float fields.
    • Arrays: For array fields, keep the Prisma field as an array (e.g., tags Json[]) and use the element shape in the comment (e.g., /// [Tag]).
    model Product {
      id Int @id
    
      // Namespace-based type
      /// [ProductMeta]
      meta Json?
    
      // Array of namespace-based types
      /// [Tag]
      tags Json[]
    
      // Inline union type (enum)
      /// !['physical' | 'digital']
      status String
    
      // Numeric scalar literal unions
      /// ![1 | 2 | 3]
      priority Int
    
      // Inline object type
      /// ![{ width: number; height: number }]
      dimensions Json
    }
  4. Validate Json types at runtime using Zod

    main

    The generator provides compile-time type safety only. To achieve runtime validation, use a library like Zod to define your schema, infer the type, and expose it to the PrismaJson namespace.

    1. Define the model in schema.prisma:

      model User {
        id          Int    @id @default(autoincrement())
        /// [UserPreferences]
        preferences Json
      }
    2. Define Zod schema and expose type in TypeScript:

      import { z } from 'zod';
      
      export const UserPreferencesSchema = z.object({
        theme: z.enum(['light', 'dark']),
        language: z.string().optional()
      });
      
      declare global {
        namespace PrismaJson {
          type UserPreferences = z.infer<typeof UserPreferencesSchema>;
        }
      }
    import { z } from 'zod';
    
    // 1. Define the Zod schema as the source of truth.
    export const UserPreferencesSchema = z.object({
      theme: z.enum(['light', 'dark']),
      language: z.string().optional()
    });
    
    // 2. Expose the inferred type to Prisma.
    declare global {
      namespace PrismaJson {
        type UserPreferences = z.infer<typeof UserPreferencesSchema>;
      }
    }
  5. Use AST comments for field typing

    main

    The generator uses AST comments (///) placed immediately above a field to apply types. It supports Json, String, Int, and Float (including optional ? and array [] variants).

    Named Namespace Types

    Use /// [TypeName] to reference a type defined in your TypeScript namespace.

    model User {
      /// [UserProfile]
      profile Json
    }

    Inline Types

    Use /// ![{...}] for one-off shapes or enum-like literals.

    model User {
      /// ![{ theme: 'dark' | 'light'; twitterHandle?: string }]
      profile Json
    }
    
    model Post {
      /// !['draft' | 'published' | 'archived']
      status String
    
      /// ![1 | 2 | 3]
      rank Int
    }
    model Product {
      /// [ProductMeta]
      meta Json?
    
      /// [Tag]
      tags Json[]
    
      /// !['physical' | 'digital']
      kind String
    
      /// [StringArrayType]
      labels String[]
    }
  6. Core Workflow for typing Prisma fields

    main

    To make Json, String, String[], Int, or Float fields type-safe, follow these steps:

    1. Identify the target field in your schema.prisma.
    2. Choose a typing style (Namespace-based or Inline).
    3. Add the AST comment immediately above the field in the Prisma schema.
    4. Update the TypeScript type if using a named namespace type.
    5. Run prisma generate to update the generated TypeScript types.

    Note: This process only changes the generated TypeScript types; it does not change the underlying database schema.

  7. Define type declaration shapes in TypeScript

    main

    For named types (using /// [TypeName]), you must create a TypeScript declaration file that defines the namespace used in your Prisma schema. This file acts as a bridge between your domain types and the Prisma namespace.

    Requirements:

    • The file must be included in your tsconfig.json.
    • The file should be a module (use export {} at the top).
    • The namespace name must match the namespace option in your Prisma generator.
    import type { UserProfile as DomainUserProfile } from './domain/user-profile';
    
    export {};
    
    declare global {
      namespace PrismaJson {
        type UserProfile = DomainUserProfile;
      }
    }
  8. Quick Start: Strongly type Json fields

    main

    To strongly type a Json field, follow these steps:

    1. Add the generator to your schema.prisma:

      generator client {
        provider = "prisma-client"
      }
      
      generator json {
        provider = "prisma-json-types-generator"
      }
    2. Define your types in a TypeScript module (e.g., src/types.ts) inside the PrismaJson global namespace:

      export {};
      
      declare global {
        namespace PrismaJson {
          type UserProfile = {
            theme: 'dark' | 'light';
            twitterHandle?: string;
          };
        }
      }
    3. Link the type in your Prisma schema using the /// [TypeName] AST comment syntax:

      model User {
        id      Int    @id @default(autoincrement())
        profile Json
      
        /// [UserProfile]
        profile Json
      }
    4. Generate the client:

      npx prisma generate
    import { PrismaClient } from '@prisma/client';
    const prisma = new PrismaClient();
    
    async function updateUserProfile() {
      const user = await prisma.user.update({
        where: { id: 1 },
        data: {
          profile: {
            theme: 'dark'
          }
        }
      });
    
      // user.profile is now fully typed as UserProfile!
      console.log(user.profile.theme);
    }
  9. Configure the prisma-json-types-generator

    main

    Configure the generator within your schema.prisma file using a generator block.

    OptionDescriptionDefault
    namespaceThe global namespace where your custom types are defined."PrismaJson"
    clientOutputPath to the @prisma/client output directory. Usually auto-detected.(auto-detected)
    allowAnyIf true, untyped Json fields resolve to any. If false, they resolve to unknown.false
    useTypeSpecifies a root type within your namespace to use as a fallback for all untyped Json fields. Adds an index signature [key: string]: any to the type.undefined
    generator json {
      provider  = "prisma-json-types-generator"
      namespace = "PrismaJson"
      allowAny  = false
      // etc...
    }