UNPKG

@dataql/prisma-adapter

Version:

Prisma adapter for DataQL with zero API changes

745 lines (634 loc) 19.4 kB
import { Data, DataOptions, ID, String, Int, Boolean, Decimal, Date as DataQLDate, } from "@dataql/core"; // Prisma-like types and interfaces export interface PrismaClientOptions { // Required for DataQL infrastructure routing appToken?: string; // Database name for client isolation dbName?: string; // Environment routing env?: "dev" | "prod"; // Development prefix devPrefix?: string; // Optional custom connection customConnection?: any; // Prisma-specific options (for compatibility) log?: Array<"query" | "info" | "warn" | "error">; errorFormat?: "pretty" | "colorless" | "minimal"; datasources?: { db?: { url?: string; }; }; } // Prisma field types export type PrismaFieldType = | "String" | "Boolean" | "Int" | "BigInt" | "Float" | "Decimal" | "DateTime" | "Json" | "Bytes" | "Uuid"; // Prisma query options export interface FindManyArgs<T = any> { select?: any; include?: any; where?: WhereInput<T>; orderBy?: OrderByInput<T> | OrderByInput<T>[]; cursor?: any; take?: number; skip?: number; distinct?: string | string[]; } export interface FindUniqueArgs<T = any> { select?: any; include?: any; where: WhereUniqueInput<T>; } export interface FindFirstArgs<T = any> { select?: any; include?: any; where?: WhereInput<T>; orderBy?: OrderByInput<T> | OrderByInput<T>[]; cursor?: any; take?: number; skip?: number; distinct?: string | string[]; } export interface CreateArgs<T = any> { data: CreateInput<T>; select?: any; include?: any; } export interface CreateManyArgs<T = any> { data: CreateInput<T> | CreateInput<T>[]; skipDuplicates?: boolean; } export interface UpdateArgs<T = any> { where: WhereUniqueInput<T>; data: UpdateInput<T>; select?: any; include?: any; } export interface UpdateManyArgs<T = any> { where?: WhereInput<T>; data: UpdateInput<T>; } export interface UpsertArgs<T = any> { where: WhereUniqueInput<T>; create: CreateInput<T>; update: UpdateInput<T>; select?: any; include?: any; } export interface DeleteArgs<T = any> { where: WhereUniqueInput<T>; select?: any; include?: any; } export interface DeleteManyArgs<T = any> { where?: WhereInput<T>; } export interface CountArgs<T = any> { select?: any; where?: WhereInput<T>; } export interface AggregateArgs<T = any> { where?: WhereInput<T>; orderBy?: OrderByInput<T> | OrderByInput<T>[]; cursor?: any; take?: number; skip?: number; _count?: boolean | CountSelect<T>; _avg?: AverageSelect<T>; _sum?: SumSelect<T>; _min?: MinSelect<T>; _max?: MaxSelect<T>; } // Where clause types export type WhereInput<T> = { [K in keyof T]?: T[K] extends string ? StringFilter | T[K] : T[K] extends number ? NumberFilter | T[K] : T[K] extends boolean ? BooleanFilter | T[K] : T[K] extends Date ? DateTimeFilter | T[K] : any; } & { AND?: WhereInput<T> | WhereInput<T>[]; OR?: WhereInput<T>[]; NOT?: WhereInput<T> | WhereInput<T>[]; }; export type WhereUniqueInput<T> = { [K in keyof T]?: T[K]; }; export interface StringFilter { equals?: string; in?: string[]; notIn?: string[]; lt?: string; lte?: string; gt?: string; gte?: string; contains?: string; startsWith?: string; endsWith?: string; mode?: "default" | "insensitive"; not?: string | StringFilter; } export interface NumberFilter { equals?: number; in?: number[]; notIn?: number[]; lt?: number; lte?: number; gt?: number; gte?: number; not?: number | NumberFilter; } export interface BooleanFilter { equals?: boolean; not?: boolean | BooleanFilter; } export interface DateTimeFilter { equals?: Date | string; in?: (Date | string)[]; notIn?: (Date | string)[]; lt?: Date | string; lte?: Date | string; gt?: Date | string; gte?: Date | string; not?: Date | string | DateTimeFilter; } // Order by types export type OrderByInput<T> = { [K in keyof T]?: "asc" | "desc"; }; // Input types export type CreateInput<T> = { [K in keyof T]?: T[K] extends Array<infer U> ? CreateNestedManyInput<U> : T[K]; }; export type UpdateInput<T> = { [K in keyof T]?: T[K] extends Array<infer U> ? UpdateNestedManyInput<U> : T[K]; }; export interface CreateNestedManyInput<T> { create?: CreateInput<T> | CreateInput<T>[]; connect?: WhereUniqueInput<T> | WhereUniqueInput<T>[]; } export interface UpdateNestedManyInput<T> { create?: CreateInput<T> | CreateInput<T>[]; connect?: WhereUniqueInput<T> | WhereUniqueInput<T>[]; set?: WhereUniqueInput<T>[]; disconnect?: WhereUniqueInput<T> | WhereUniqueInput<T>[]; delete?: WhereUniqueInput<T> | WhereUniqueInput<T>[]; update?: UpdateWithWhereUniqueInput<T> | UpdateWithWhereUniqueInput<T>[]; updateMany?: UpdateManyWithWhereInput<T> | UpdateManyWithWhereInput<T>[]; deleteMany?: WhereInput<T> | WhereInput<T>[]; upsert?: UpsertWithWhereUniqueInput<T> | UpsertWithWhereUniqueInput<T>[]; } export interface UpdateWithWhereUniqueInput<T> { where: WhereUniqueInput<T>; data: UpdateInput<T>; } export interface UpdateManyWithWhereInput<T> { where: WhereInput<T>; data: UpdateInput<T>; } export interface UpsertWithWhereUniqueInput<T> { where: WhereUniqueInput<T>; create: CreateInput<T>; update: UpdateInput<T>; } // Aggregate types export type CountSelect<T> = | boolean | { [K in keyof T]?: boolean; }; export type AverageSelect<T> = { [K in keyof T]?: T[K] extends number ? boolean : never; }; export type SumSelect<T> = { [K in keyof T]?: T[K] extends number ? boolean : never; }; export type MinSelect<T> = { [K in keyof T]?: boolean; }; export type MaxSelect<T> = { [K in keyof T]?: boolean; }; // Result types export interface BatchPayload { count: number; } export interface AggregateResult<T> { _count?: { [K in keyof T]?: number } | number; _avg?: { [K in keyof T]?: number }; _sum?: { [K in keyof T]?: number }; _min?: { [K in keyof T]?: any }; _max?: { [K in keyof T]?: any }; } // Prisma model delegate export class PrismaModelDelegate<T> { private _collection: any; private _modelName: string; private _data: Data; constructor(data: Data, modelName: string, schema: any) { this._data = data; this._modelName = modelName; this._collection = data.collection(modelName, schema); } async findMany(args?: FindManyArgs<T>): Promise<T[]> { const filter = this._convertWhereToDataQL(args?.where); let results = await this._collection.find(filter); // Apply ordering if (args?.orderBy) { results = this._applyOrderBy(results, args.orderBy); } // Apply pagination if (args?.skip) { results = results.slice(args.skip); } if (args?.take) { results = results.slice(0, args.take); } // Apply selection if (args?.select) { results = results.map((item: any) => this._applySelect(item, args.select) ); } return results; } async findUnique(args: FindUniqueArgs<T>): Promise<T | null> { const filter = this._convertWhereToDataQL(args.where); const results = await this._collection.find(filter); if (results.length === 0) return null; let result = results[0]; if (args.select) { result = this._applySelect(result, args.select); } return result; } async findFirst(args?: FindFirstArgs<T>): Promise<T | null> { const findManyArgs: FindManyArgs<T> = { ...args, take: 1, }; const results = await this.findMany(findManyArgs); return results.length > 0 ? results[0] : null; } async create(args: CreateArgs<T>): Promise<T> { const result = await this._collection.create(args.data); if (args.select) { return this._applySelect(result, args.select); } return result; } async createMany(args: CreateManyArgs<T>): Promise<BatchPayload> { const data = Array.isArray(args.data) ? args.data : [args.data]; const results = await this._collection.create(data); return { count: Array.isArray(results) ? results.length : 1, }; } async update(args: UpdateArgs<T>): Promise<T> { const filter = this._convertWhereToDataQL(args.where); const result = await this._collection.update(filter, args.data); if (args.select) { return this._applySelect(result, args.select); } return result; } async updateMany(args: UpdateManyArgs<T>): Promise<BatchPayload> { const filter = this._convertWhereToDataQL(args.where); const results = await this._collection.update(filter, args.data); return { count: Array.isArray(results) ? results.length : 1, }; } async upsert(args: UpsertArgs<T>): Promise<T> { const filter = this._convertWhereToDataQL(args.where); const existing = await this._collection.find(filter); let result; if (existing.length > 0) { result = await this._collection.update(filter, args.update); } else { result = await this._collection.create(args.create); } if (args.select) { return this._applySelect(result, args.select); } return result; } async delete(args: DeleteArgs<T>): Promise<T> { const filter = this._convertWhereToDataQL(args.where); const existing = await this._collection.find(filter); if (existing.length === 0) { throw new Error("Record to delete does not exist."); } await this._collection.delete(filter); let result = existing[0]; if (args.select) { result = this._applySelect(result, args.select); } return result; } async deleteMany(args?: DeleteManyArgs<T>): Promise<BatchPayload> { const filter = this._convertWhereToDataQL(args?.where); const existing = await this._collection.find(filter); const count = existing.length; if (count > 0) { await this._collection.delete(filter); } return { count }; } async count(args?: CountArgs<T>): Promise<number> { const filter = this._convertWhereToDataQL(args?.where); const results = await this._collection.find(filter); return results.length; } async aggregate(args?: AggregateArgs<T>): Promise<AggregateResult<T>> { const filter = this._convertWhereToDataQL(args?.where); let results = await this._collection.find(filter); // Apply ordering if (args?.orderBy) { results = this._applyOrderBy(results, args.orderBy); } // Apply pagination if (args?.skip) { results = results.slice(args.skip); } if (args?.take) { results = results.slice(0, args.take); } const aggregateResult: AggregateResult<T> = {}; // Count if (args?._count) { if (typeof args._count === "boolean") { aggregateResult._count = results.length; } else { const countResult: any = {}; for (const field in args._count) { if (args._count[field]) { countResult[field] = results.filter( (item: any) => item[field] !== null && item[field] !== undefined ).length; } } aggregateResult._count = countResult; } } // Other aggregations (avg, sum, min, max) would need more sophisticated implementation // For now, we'll provide basic implementations return aggregateResult; } private _convertWhereToDataQL(where?: any): any { if (!where) return {}; const result: any = {}; for (const [key, value] of Object.entries(where)) { if (key === "AND") { // Handle AND conditions const conditions = Array.isArray(value) ? value : [value]; for (const condition of conditions) { Object.assign(result, this._convertWhereToDataQL(condition)); } } else if (key === "OR") { // Handle OR conditions (simplified - DataQL might need native OR support) result.$or = Array.isArray(value) ? value.map((v: any) => this._convertWhereToDataQL(v)) : [this._convertWhereToDataQL(value)]; } else if (key === "NOT") { // Handle NOT conditions (simplified - DataQL might need native NOT support) result.$not = this._convertWhereToDataQL(value); } else if (typeof value === "object" && value !== null) { // Handle field filters const fieldFilter: any = {}; for (const [filterKey, filterValue] of Object.entries(value)) { switch (filterKey) { case "equals": result[key] = filterValue; break; case "in": result[key] = { $in: filterValue }; break; case "notIn": result[key] = { $nin: filterValue }; break; case "lt": result[key] = { $lt: filterValue }; break; case "lte": result[key] = { $lte: filterValue }; break; case "gt": result[key] = { $gt: filterValue }; break; case "gte": result[key] = { $gte: filterValue }; break; case "contains": result[key] = { $regex: filterValue, $options: "i" }; break; case "startsWith": result[key] = { $regex: `^${filterValue}`, $options: "i" }; break; case "endsWith": result[key] = { $regex: `${filterValue}$`, $options: "i" }; break; case "not": result[key] = { $ne: filterValue }; break; default: // Default case result[key] = { [`$${filterKey}`]: filterValue }; } } } else { // Direct value result[key] = value; } } return result; } private _applyOrderBy( results: any[], orderBy: OrderByInput<T> | OrderByInput<T>[] ): any[] { const orderBys = Array.isArray(orderBy) ? orderBy : [orderBy]; return results.sort((a, b) => { for (const order of orderBys) { for (const [field, direction] of Object.entries(order)) { const aVal = a[field]; const bVal = b[field]; if (aVal < bVal) return direction === "asc" ? -1 : 1; if (aVal > bVal) return direction === "asc" ? 1 : -1; } } return 0; }); } private _applySelect(item: any, select: any): any { if (!select) return item; const result: any = {}; for (const [key, value] of Object.entries(select)) { if (value === true) { result[key] = item[key]; } else if (typeof value === "object") { // Nested selection (simplified) result[key] = item[key]; } } return result; } } // Main Prisma client class export class PrismaClient { private _data: Data; private _models: Map<string, PrismaModelDelegate<any>> = new Map(); private _schemas: Map<string, any> = new Map(); constructor(options?: PrismaClientOptions) { // Ensure proper DataQL infrastructure routing (Client → Worker → Lambda → Database) const dataqlOptions = { appToken: options?.appToken || "default", // Required for DataQL routing env: options?.env || "prod", // Routes to production infrastructure devPrefix: options?.devPrefix || "dev_", // Optional prefix for dev environments dbName: options?.dbName, // Database isolation per client customConnection: options?.customConnection, // Optional custom connection }; this._data = new Data(dataqlOptions); } // Register a model with schema _registerModel<T>(name: string, schema: any): PrismaModelDelegate<T> { const delegate = new PrismaModelDelegate<T>(this._data, name, schema); this._models.set(name, delegate); this._schemas.set(name, schema); return delegate; } // Get a model delegate _getModel<T>(name: string): PrismaModelDelegate<T> { const delegate = this._models.get(name); if (!delegate) { throw new Error( `Model ${name} has not been registered. Use _registerModel() first.` ); } return delegate; } // Transaction support async $transaction<T>(fn: (prisma: PrismaClient) => Promise<T>): Promise<T>; async $transaction<T>(queries: any[]): Promise<any[]>; async $transaction<T>( fnOrQueries: ((prisma: PrismaClient) => Promise<T>) | any[] ): Promise<T | any[]> { if (typeof fnOrQueries === "function") { // Interactive transaction return await this._data.transaction(async () => { return await fnOrQueries(this); }); } else { // Batch transaction return await this._data.transaction(async () => { const results: any[] = []; for (const query of fnOrQueries) { const result = await query; results.push(result); } return results; }); } } // Raw query methods (mocked for now) async $queryRaw<T = any>( query: TemplateStringsArray, ...values: any[] ): Promise<T> { // This would need to be implemented based on DataQL's raw query capabilities throw new Error("Raw queries not yet supported in DataQL Prisma adapter"); } async $executeRaw( query: TemplateStringsArray, ...values: any[] ): Promise<number> { // This would need to be implemented based on DataQL's raw query capabilities throw new Error("Raw queries not yet supported in DataQL Prisma adapter"); } // Connection management async $connect(): Promise<void> { // DataQL connections are handled automatically return Promise.resolve(); } async $disconnect(): Promise<void> { // DataQL connections are handled automatically return Promise.resolve(); } } // Helper function to create a Prisma client with schema registration export function createPrismaClient<T extends Record<string, any>>( options?: PrismaClientOptions, schemas?: { [K in keyof T]: any } ): PrismaClient & { [K in keyof T]: PrismaModelDelegate<T[K]> } { const client = new PrismaClient(options); if (schemas) { for (const [modelName, schema] of Object.entries(schemas)) { const delegate = client._registerModel(modelName, schema); (client as any)[modelName] = delegate; } } return client as PrismaClient & { [K in keyof T]: PrismaModelDelegate<T[K]> }; } // Error classes export class PrismaClientKnownRequestError extends Error { code: string; meta?: Record<string, any>; constructor(message: string, code: string, meta?: Record<string, any>) { super(message); this.name = "PrismaClientKnownRequestError"; this.code = code; this.meta = meta; } } export class PrismaClientUnknownRequestError extends Error { constructor(message: string) { super(message); this.name = "PrismaClientUnknownRequestError"; } } export class PrismaClientRustPanicError extends Error { constructor(message: string) { super(message); this.name = "PrismaClientRustPanicError"; } } export class PrismaClientInitializationError extends Error { constructor(message: string) { super(message); this.name = "PrismaClientInitializationError"; } } export class PrismaClientValidationError extends Error { constructor(message: string) { super(message); this.name = "PrismaClientValidationError"; } } // Export the main client export default PrismaClient;