0.0.1 • Published 2 years ago

prisma-pgtyped-prepared-query v0.0.1

Weekly downloads
-
License
-
Repository
-
Last release
2 years ago

Prisma & Pgtyped client extension

This package exposes a Prisma Client extension to use PgTyped's PreparedQuery.

Installation

$ npm install --save @pgtyped/runtime @prisma/client prisma prisma-pgtyped-prepared-query
$ npm install --save-dev typescript @pgtyped/cli
$ pnpm add --save @pgtyped/runtime @prisma/client prisma prisma-pgtyped-prepared-query
$ pnpm add --save-dev typescript @pgtyped/cli
$ yarn add @pgtyped/runtime @prisma/client prisma prisma-pgtyped-prepared-query
$ yarn add --dev typescript @pgtyped/cli

Usage

See the packages/example folder

To use the extension, you need to call $extends on your Prisma client:

// prisma-client.ts
import { prismaPgtypedPreparedQuery } from "prisma-pgtyped-prepared-query";

const prisma = new PrismaClient();
const prismaClient = prisma.$extends(prismaPgtypedPreparedQuery());

export type PrismaClient = typeof prismaClient;

Once you have generated your PreparedQuery with @pgtyped/cli, you can directly use it with the prisma client:

// post.repository.ts
import { PrismaClient } from './prisma-client';
import {
  findAllPostsRawQuery,
  IFindAllPostsRawQueryResult,
} from "./find-all-posts.sql";

class PostRepository {
  constructor(private readonly prisma: PrismaClient) {}

  findAllPosts(clientId: string): Promise<IFindAllPostsRawQueryResult[]> {
    return this.prisma.$protected(findAllPostsRawQuery).with({ clientId });
  }
}

Customization

The extension use a set of classes to bridge PgTyped with Prisma. Those classes are intentionally exposed from the package for customizations.

Class nameDescription
PrismaDatabaseConnectionThe database connection implementing the PgTyped IDatabaseConnection to run the query, this is the communication layer between pgtyped and prisma
PrismaPreparedQueryRunnerThe runner adding a thin layer around the PreparedQuery to be more fluent

For example, let's imagine that you want to validate at runtime the query output result. You could use the following snippet:

// prisma-client.ts
import { PreparedQuery } from "@pgtyped/runtime";
import { ClassConstructor, plainToInstance } from "class-transformer";
import { validateOrReject } from "class-validator";
import {
  PrismaDatabaseConnection,
  PrismaPreparedQueryRunner,
} from "prisma-pgtyped-prepared-query";

async function castAs<T extends object, V extends object>(
  dto: ClassConstructor<T>,
  rows: readonly V[]
): Promise<T[]> {
  const instances = plainToInstance(dto, [...rows]);
  if (instances.length > 0) await validateOrReject(instances[0]);

  return instances;
}

const prisma = new PrismaClient().$extends((prisma) => {
  prisma.$extends({
    name: "prisma-pgtyped-validated-prepared-query",
    client: {
      $prepared<TInput, TOutput>(query: PreparedQuery<TInput, TOutput>) {
        const runner = new PrismaPreparedQueryRunner(
          new PrismaDatabaseConnection(prisma),
          query
        );

        return {
          with(params: TInput) {
            return runner.with(params);
          },
          as<T>(dto: ClassConstructor<T>) {
            return {
              with: (params: TInput): Promise<T[]> => {
                const rows = await this.with(params);
                return castAs(dto, rows);
              },
            };
          },
        };
      },
    },
  });
});

export type PrismaClient = typeof prisma;

// ---
// post.repository.ts
import { PrismaClient } from "./prisma-client";
import { findAllPostsRawQuery } from "./find-all-posts.sql";

class Post {
  @IsUUID()
  id!: string;
}

class PostRepository {
  constructor(private readonly prisma: PrismaClient) {}

  findAllPosts(clientId: string): Promise<Post[]> {
    return this.prisma
      .$prepared(findAllPostsRawQuery)
      .as(Post)
      .with({ clientId });
  }
}