Skip to content

Commit

Permalink
feat(query): write a mockup of the Query class based on previous work…
Browse files Browse the repository at this point in the history
…s made in validator.ts
  • Loading branch information
LilaRest committed May 15, 2023
1 parent 4ed5f57 commit e120825
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 41 deletions.
6 changes: 6 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const events = {
"user": {
before: [],
after: []
}
} as { [key: string]: any; };
94 changes: 94 additions & 0 deletions src/query.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,109 @@
import { modelsSpecs } from "./.generated";
import { MethodClause } from "./clauses";
import { getConfig } from "./config";
import { PrismaClient } from "@prisma/client";
import { ZodError } from "zod";
import { events } from "./events";


// Retrieve config and required instances
const config = getConfig();
if (!config) throw "Prismary: Config object not found.";
const prisma = config.prismaClientInstance as PrismaClient & { [key: string]: any; };
if (!prisma) throw "Prismary: Prisma Client instance not found";

export class PrismaryQuery {
model: string;
method: MethodClause;
body: object;
preparation: ReturnType<typeof this.prepare>;
validation: ReturnType<typeof this.validate>;

constructor (model: string, method: MethodClause, body: object) {
this.model = model;
this.method = method;
this.body = body;
this.validation = this.validate();
this.preparation = this.prepare();
}

async send () {
// Await validation process and return error if any
const vResult = await this.validation;
if (vResult !== true) {
if (vResult instanceof ZodError) throw vResult;
else throw new Error("Prismary: Unhandled error.");
}

// Await preparation process
const { readQueryBody, permissionsMap } = await this.preparation;

// Call every before event
events[this.model].before.forEach((event: Function) => event(this.model, this.method, this.body));

// Starts prisma transaction
prisma.$transaction(async (tx) => {
// Retrieve data from database if readBody is non-empty
// (which means that some are required by else events, authorization or query)
let readData: Array<any> = [];
if (Object.keys(readBody).length) {
readData = await tx[this.model].findMany(readBody);
if (!readData.length) return readData; // Abort if no data found
}

// Await authorization process
const aResult = await this.authorize(readData, permissionsMap);
if (aResult !== true) throw aResult;
});
return {};
}

async validate (): Promise<true | ZodError> {
const validations = this._validate(this.body, this.model);
try {
await Promise.all(validations);
return true;
} catch (error) {
return error as ZodError;
}
}

_validate (body: object, model: string): Array<Promise<any | ZodError>> {
const validations: Array<Promise<any | ZodError>> = [];
for (const [key, value] of Object.entries(body)) {
if (value !== null && typeof value === "object") { // "null" is an object in JS
if (Array.isArray(value)) {
for (const item of value) {
validations.push(...this._validate(item, model));
}
}
else {
const relationModel = modelsSpecs[model].relations[key];
if (relationModel) validations.push(...this._validate(value, relationModel));
else validations.push(...this._validate(value, model));
}
}
else if (modelsSpecs[model].fields.has(key)) {
validations.push(modelsSpecs[model].schema.fields[key].safeParseAsync(value));
}
}
return validations;
}

async prepare (): Promise<{ readQueryBody: object; permissionsMap: object; }> {
const readQueryBody = {};
const permissionsMap = {};
const bodies = [this.body, ...events[this.model].after.bodies, casl.queryBodies];
await this._prepare(this.body, readQueryBody, permissionsMap);
return { readQueryBody, permissionsMap };
}

_prepare (body: object, readQueryBody: object, permissionsMap: object) {
for (const [key, value] of Object.entries(body)) {

}
}

async authorize (readData: Array<any>) {
}
}
42 changes: 1 addition & 41 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,10 @@ import {
import { NotSupportedError } from "./errors";
import { accessibleBy } from "@casl/prisma";
import { modelsSpecs } from "./.generated";
import { getConfig } from "./config";
import { Stack } from "./utils/stack";

// Retrieve config and required instances
const config = getConfig();
if (!config) throw "Prismary: Config object not found.";
const prisma = config.prismaClientInstance as PrismaClient & {
[key: string]: any;
};
if (!prisma) throw "Prisma Client instance not found";
const ability = config.caslAbilityInstance;


/*
create testing
1) test if the given data is allowed to be created with can(permissions, subject(model, data))
2) if permitted create, else error
read testing:
1) read selected rows with read method
2) get permitted fields with permittedFieldsOf()
3) if all read fields are permitted -> return data, else error
update testing:
1) read selected rows with findMany
2) get permitted fields with permittedFieldsOf()
3) if all updated fields are permitted -> update, else error
delete testing:
same than for update
*/


/**
* TODO implement:
* - Create operations should be tested on provided data and not on DB read
* - `connectOrCreate` should require "update" for parent data key
* - Ignored fields should maybe not be ignored to support column comparison
* (see: https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#compare-columns-in-the-same-table)
* - Support "read" check for column comparisons (see: https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#compare-columns-in-the-same-table)
*/


// const ability = config.caslAbilityInstance;



Expand Down

0 comments on commit e120825

Please sign in to comment.