src/service.ts
import type { Params } from '@feathersjs/feathers';
import { AdapterService } from '@feathersjs/adapter-commons';
import * as errors from '@feathersjs/errors';
import { PrismaClient } from '@prisma/client';
import { IdField, PrismaServiceOptions } from './types';
import { buildPrismaQueryParams, buildSelectOrInclude, buildWhereWithOptionalIdObject, checkIdInQuery } from './utils';
import { OPERATORS } from './constants';
import { errorHandler } from './error-handler';
export class PrismaService<ModelData = Record<string, any>> extends AdapterService {
Model: any;
client: PrismaClient;
constructor(options: PrismaServiceOptions, client: PrismaClient) {
super({
id: options.id || 'id',
paginate: {
default: options.paginate?.default,
max: options.paginate?.max,
},
multi: options.multi || [],
filters: options.filters || [],
events: options.events || [],
whitelist: Object.values(OPERATORS).concat(options.whitelist || []),
});
const { model } = options;
if (!model) {
throw new errors.GeneralError('You must provide a model string.');
}
// @ts-ignore
if (!client[model]) {
throw new errors.GeneralError(`No model with name ${model} found in prisma client.`);
}
this.client = client;
// @ts-ignore
this.Model = client[model];
}
async _find(params: Params = {}): Promise<any> {
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { skip, take, orderBy, where, select, include } = buildPrismaQueryParams({
query, filters, whitelist,
}, this.options.id);
try {
const findMany = () => {
return this.Model.findMany({
...(typeof take === 'number' ? { skip, take } : { skip }),
orderBy,
where,
...buildSelectOrInclude({ select, include }),
});
};
if (!this.options.paginate.default || (typeof take !== 'number' && !take)) {
const data = await findMany();
return data;
}
const [data, count] = await this.client.$transaction([
findMany(),
this.Model.count({
where,
}),
]);
const result = {
total: count,
skip,
limit: take,
data,
};
return result;
} catch (e) {
errorHandler(e);
}
}
async _get(id: IdField, params: Params = {}) {
try {
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { where, select, include, _helper } = buildPrismaQueryParams({
id, query, filters, whitelist
}, this.options.id);
if (_helper.idQueryIsObject || _helper.queryWhereExists) {
const result: Partial<ModelData> = await this.Model.findFirst({
where: buildWhereWithOptionalIdObject(id, where, this.options.id),
...buildSelectOrInclude({ select, include }),
});
if (!result) throw new errors.NotFound(`No record found for id '${id}' and query`);
return result;
}
checkIdInQuery({ id, query, idField: this.options.id });
const result: Partial<ModelData> = await this.Model.findUnique({
where,
...buildSelectOrInclude({ select, include }),
});
if (!result) throw new errors.NotFound(`No record found for id '${id}'`);
return result;
} catch (e) {
errorHandler(e, 'findUnique');
}
}
async _create(data: Partial<ModelData> | Partial<ModelData>[], params: Params = {}) {
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { select, include } = buildPrismaQueryParams({ query, filters, whitelist }, this.options.id);
try {
if (Array.isArray(data)) {
const result: Partial<ModelData>[] = await this.client.$transaction(data.map((d) => this.Model.create({
data: d,
...buildSelectOrInclude({ select, include }),
})));
return result;
}
const result: Partial<ModelData> = await this.Model.create({
data,
...buildSelectOrInclude({ select, include }),
});
return result;
} catch (e) {
errorHandler(e);
}
}
async _update(id: IdField, data: Partial<ModelData>, params: Params = {}, returnResult = false) {
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { where, select, include, _helper } = buildPrismaQueryParams({
id, query, filters, whitelist,
}, this.options.id);
try {
if (_helper.idQueryIsObject) {
const newWhere = buildWhereWithOptionalIdObject(id, where, this.options.id);
const [, result] = await this.client.$transaction([
this.Model.updateMany({
data,
where: newWhere,
...buildSelectOrInclude({ select, include }),
}),
this.Model.findFirst({
where: {
...newWhere,
...data,
},
...buildSelectOrInclude({ select, include }),
}),
]);
if (!result) throw new errors.NotFound(`No record found for id '${id}'`);
return result;
}
checkIdInQuery({ id, query, idField: this.options.id });
const result = await this.Model.update({
data,
where,
...buildSelectOrInclude({ select, include }),
});
if (select || returnResult) {
return result;
}
return { [this.options.id]: result.id, ...data };
} catch (e) {
errorHandler(e, 'update');
}
}
async _patch(id: IdField | null, data: Partial<ModelData> | Partial<ModelData>[], params: Params = {}) {
if (id && !Array.isArray(data)) {
const result = await this._update(id, data, params, true);
return result;
}
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { where, select, include } = buildPrismaQueryParams({ query, filters, whitelist }, this.options.id);
try {
const [, result] = await this.client.$transaction([
this.Model.updateMany({
data,
where,
...buildSelectOrInclude({ select, include }),
}),
this.Model.findMany({
where: {
...where,
...data,
},
...buildSelectOrInclude({ select, include }),
}),
]);
return result;
} catch (e) {
errorHandler(e, 'updateMany');
}
}
async _remove(id: IdField | null, params: Params = {}) {
const { query, filters } = this.filterQuery(params);
const { whitelist } = this.options;
const { where, select, include, _helper } = buildPrismaQueryParams({
id: id || undefined, query, filters, whitelist,
}, this.options.id);
if (id && !_helper.idQueryIsObject) {
try {
checkIdInQuery({ id, query, allowOneOf: true, idField: this.options.id });
const result: Partial<ModelData> = await this.Model.delete({
where: id ? { [this.options.id]: id } : where,
...buildSelectOrInclude({ select, include }),
});
return result;
} catch (e) {
errorHandler(e, 'delete');
}
}
try {
const query = {
where: id ? buildWhereWithOptionalIdObject(id, where, this.options.id) : where,
...buildSelectOrInclude({ select, include }),
};
const [data] = await this.client.$transaction([
id ? this.Model.findFirst(query) : this.Model.findMany(query),
this.Model.deleteMany(query),
]);
if (id && !data) throw new errors.NotFound(`No record found for id '${id}'`);
return data;
} catch (e) {
errorHandler(e, 'deleteMany');
}
}
}
export function service<ModelData = Record<string, any>>(options: PrismaServiceOptions, client: PrismaClient) {
return new PrismaService<ModelData>(options, client);
}
export const prismaService = service;