Skip to content

Instantly share code, notes, and snippets.

@lilianalillyy
Last active April 30, 2023 20:39
Show Gist options
  • Save lilianalillyy/56b81a010fe52aa535544b4254fb7f7d to your computer and use it in GitHub Desktop.
Save lilianalillyy/56b81a010fe52aa535544b4254fb7f7d to your computer and use it in GitHub Desktop.
TL;DR: Repositories & Prisma minus the boilerplate
import { PrismaModule } from "@infra/prisma/PrismaModule";
import { Module } from "@nestjs/common";
import { UserRepository } from "./UserRepository";
import { PrismaRepository } from "@infra/prisma/PrismaRepository";
@Module({
imports: [
PrismaModule.forRoot(
UserRepository,
"user", // base repository, can be injected with @InjectRepository("user")
{
modelName: "user",
provide: "key", // optional, if set, use @Inject("key"), otherwise you can use @InjectRepository("user")
useClass: PrismaRepository // base repository
// useClass: UserRepository,
// useValue: new UserRepository(...),
// useFactory: (prisma: PrismaService) => new UserRepository(prisma)
}
),
],
exports: [PrismaModule],
})
export class UserModule {
}
import { PrismaRepository } from "@infra/prisma/PrismaRepository";
import { Injectable } from "@nestjs/common";
import { PrismaService } from "nestjs-prisma";
@Injectable()
export class UserRepository extends PrismaRepository<"user"> {
constructor(prisma: PrismaService) {
super("user", prisma)
}
// find*, count, group, etc.. from prisma.user is now available
}
import { DynamicModule, ExistingProvider, Inject, InjectionToken, Module, Provider } from '@nestjs/common';
import { PrismaModule as BasePrismaModule, PrismaService } from 'nestjs-prisma';
import { PrismaRepository } from './PrismaRepository';
import { ForModuleCustomEntry, ForModuleEntry, PrismaModelName, PrismaRepositoryType } from './types';
@Module({
imports: [BasePrismaModule],
})
export class PrismaModule {
public static forRoot<N extends PrismaModelName>(
...entries: (ForModuleEntry<N> | ForModuleEntry<N>[])[]
): DynamicModule {
const providers = (Array.isArray(entries[0]) ? entries[0] : entries).map(provider => {
if (typeof provider === 'string') {
return this.createRepositoryProvider(provider);
}
if (Array.isArray(provider)) {
throw new Error('Cannot pass multiple arrays in PrismaModule.forRoot()');
}
return this.createCustomRepositoryProvider(provider);
});
return {
module: PrismaModule,
providers: providers,
exports: providers,
};
}
public static createRepositoryProvider<N extends PrismaModelName>(
modelName: N
): Provider<PrismaRepositoryType<N>> {
return {
provide: this.createRepositoryProviderName(modelName),
useFactory: (prisma: PrismaService) => new PrismaRepository(modelName, prisma),
};
}
public static createCustomRepositoryProvider<N extends PrismaModelName>(
entry: ForModuleCustomEntry<N>
): Provider<typeof entry extends ExistingProvider<infer T> ? T : PrismaRepositoryType<N>> {
if (typeof entry === 'function') {
return {
provide: entry,
useClass: entry,
};
}
const provide: InjectionToken = !('provide' in entry)
? this.createRepositoryProviderName(entry.modelName)
: entry.provide;
return {
...entry,
provide,
};
}
public static createRepositoryProviderName<N extends PrismaModelName>(modelName: N): `PrismaRepository__${N}` {
return `PrismaRepository__${modelName}`;
}
}
// Only used when basic PrismaRepository is needed or when the repository entry is passed as ExistingProvider<Repository>/Type<Repository>
export function InjectRepository<N extends PrismaModelName>(modelName: N) {
return Inject(PrismaModule.createRepositoryProviderName<N>(modelName));
}
import { DynamicModule, ExistingProvider, Inject, InjectionToken, Module, Provider } from '@nestjs/common';
import { PrismaModule as BasePrismaModule, PrismaService } from 'nestjs-prisma';
import { PrismaRepository } from './PrismaRepository';
import { ForModuleCustomEntry, ForModuleEntry, PrismaModelName, PrismaRepositoryType } from './types';
@Module({
imports: [BasePrismaModule],
})
export class PrismaModule {
public static forRoot<N extends PrismaModelName>(
...entries: (ForModuleEntry<N> | ForModuleEntry<N>[])[]
): DynamicModule {
const providers = (Array.isArray(entries[0]) ? entries[0] : entries).map(provider => {
if (typeof provider === 'string') {
return this.createRepositoryProvider(provider);
}
if (Array.isArray(provider)) {
throw new Error('Cannot pass multiple arrays in PrismaModule.forRoot()');
}
return this.createCustomRepositoryProvider(provider);
});
return {
module: PrismaModule,
providers: providers,
exports: providers,
};
}
public static createRepositoryProvider<N extends PrismaModelName>(
modelName: N
): Provider<PrismaRepositoryType<N>> {
return {
provide: this.createRepositoryProviderName(modelName),
useFactory: (prisma: PrismaService) => new PrismaRepository(modelName, prisma),
};
}
public static createCustomRepositoryProvider<N extends PrismaModelName>(
entry: ForModuleCustomEntry<N>
): Provider<typeof entry extends ExistingProvider<infer T> ? T : PrismaRepositoryType<N>> {
if (typeof entry === 'function') {
return {
provide: entry.name,
useClass: entry,
};
}
const provide: InjectionToken = !('provide' in entry)
? this.createRepositoryProviderName(entry.modelName)
: entry.provide;
return {
...entry,
provide,
};
}
public static createRepositoryProviderName<N extends PrismaModelName>(modelName: N): `PrismaRepository__${N}` {
return `PrismaRepository__${modelName}`;
}
}
export function InjectRepository<N extends PrismaModelName>(modelName: N) {
return Inject(PrismaModule.createRepositoryProviderName<N>(modelName));
}
import { ExistingProvider, InjectionToken, Provider, Type } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { PrismaRepository } from './PrismaRepository';
export type KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];
export type PickKeysMatching<T, V> = {
[key in KeysMatching<T, V>]: V;
};
export type DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;
export interface CustomRepositoryProviderOptionsBase<N extends PrismaModelName = PrismaModelName> {
name: N;
provide?: InjectionToken;
}
export type CustomRepositoryProviderOptions<N extends PrismaModelName = PrismaModelName> =
| (DistributiveOmit<
Exclude<Provider<PrismaRepositoryType<N>>, Type | ExistingProvider> | ExistingProvider<PrismaRepository<N>>,
'provide'
> &
CustomRepositoryProviderOptionsBase<N>)
| Type<PrismaRepository<N>>;
export type ForModuleCustomEntry<N extends PrismaModelName> = CustomRepositoryProviderOptions<N>;
export type ForModuleEntry<N extends PrismaModelName> = N | ForModuleCustomEntry<N>;
export type PrismaRepositoryType<N extends PrismaModelName = PrismaModelName> = PrismaDelegate<N> & {
modelName: N;
prisma: PrismaClient;
new <N extends PrismaModelName>(modelName: N, prisma: PrismaClient): PrismaRepositoryType<N>;
};
export type PrismaModelName<M = any> = KeysMatching<
PrismaClient,
{ create(args: { data: any }): ReturnType<Prisma.PrismaPromise<M>['then']> }
>;
export type PrismaDelegate<N extends PrismaModelName> = PrismaClient[N];
// Args meaning the 'args' argument of actions
export type PrismaDelegateFuncArgs<N extends PrismaModelName, F extends keyof PrismaDelegate<N>> = Parameters<
PrismaDelegate<N>[F]
>[0];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment