Skip to content

Commit

Permalink
Release 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dennemark committed Nov 13, 2024
1 parent 7433bee commit f666f87
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 51 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@


## [1.1.0](https://github.com/dennemark/prisma-extension-casl/compare/1.0.1...1.1.0) (2024-11-13)

### Features

* :sparkles: add before and afterQuery functions ([7433bee](https://github.com/dennemark/prisma-extension-casl/commit/7433bee16b76eb6c8c506c48ed2792d03e2ddc64))

## [1.0.1](https://github.com/dennemark/prisma-extension-casl/compare/1.0.0...1.0.1) (2024-10-29)

### Bug Fixes
Expand Down
4 changes: 4 additions & 0 deletions dist/index.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type PrismaExtensionCaslOptions = {
* that should be returned if permissionField is used.
*/
addPermissionActions?: string[];
/** uses transaction to allow using client queries before actual query, if fails, whole query will be rolled back */
beforeQuery?: (tx: Prisma.TransactionClient) => Promise<void>;
/** uses transaction to allow using client queries after actual query, if fails, whole query will be rolled back */
afterQuery?: (tx: Prisma.TransactionClient) => Promise<void>;
};
type PrismaCaslOperation = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findUnique' | 'findUniqueOrThrow' | 'aggregate' | 'count' | 'groupBy' | 'update' | 'updateMany' | 'delete' | 'deleteMany';

Expand Down
4 changes: 4 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type PrismaExtensionCaslOptions = {
* that should be returned if permissionField is used.
*/
addPermissionActions?: string[];
/** uses transaction to allow using client queries before actual query, if fails, whole query will be rolled back */
beforeQuery?: (tx: Prisma.TransactionClient) => Promise<void>;
/** uses transaction to allow using client queries after actual query, if fails, whole query will be rolled back */
afterQuery?: (tx: Prisma.TransactionClient) => Promise<void>;
};
type PrismaCaslOperation = 'create' | 'createMany' | 'createManyAndReturn' | 'upsert' | 'findFirst' | 'findFirstOrThrow' | 'findMany' | 'findUnique' | 'findUniqueOrThrow' | 'aggregate' | 'count' | 'groupBy' | 'update' | 'updateMany' | 'delete' | 'deleteMany';

Expand Down
92 changes: 67 additions & 25 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,14 @@ function getSubject(model, obj) {
const subjectFields = [...modelFields, ...Object.keys(relationFieldsByModel[model])];
return R2(model, pick(obj, subjectFields));
}
function getFluentField(data) {
const dataPath = data?.__internalParams?.dataPath;
if (dataPath?.length > 0) {
return dataPath[dataPath.length - 1];
} else {
return void 0;
}
}
function getFluentModel(startModel, data) {
const dataPath = data?.__internalParams?.dataPath;
if (dataPath?.length > 0) {
Expand Down Expand Up @@ -996,7 +1004,7 @@ if (!Set.prototype.isDisjointFrom) {
}

// src/applyDataQuery.ts
function applyDataQuery(abilities, args, action, model, creationTree) {
function applyDataQuery(abilities, args, operation, action, model, creationTree) {
const tree = creationTree ? creationTree : { action, model, children: {}, mutation: [] };
const permittedFields = getPermittedFields(abilities, action, model);
const mutationArgs = [];
Expand Down Expand Up @@ -1038,7 +1046,7 @@ function applyDataQuery(abilities, args, action, model, creationTree) {
const relationModelId = propertyFieldsByModel[model][field];
if (relationModelId && mutation[field] !== null) {
const fieldId = relationFieldsByModel[model][relationModelId].relationToFields?.[0];
if (fieldId) {
if (fieldId && operation !== "createMany" && operation !== "createManyAndReturn") {
mutation[relationModelId] = { connect: { [fieldId]: mutation[field] } };
delete mutation[field];
}
Expand All @@ -1058,7 +1066,7 @@ function applyDataQuery(abilities, args, action, model, creationTree) {
const isConnection = nestedAction === "connect" || nestedAction === "disconnect";
tree.children[field] = { action: mutationAction, model: relationModel.type, children: {}, mutation: [] };
if (nestedAction !== "disconnect" && nestedArgs !== true) {
const dataQuery = applyDataQuery(abilities, nestedArgs, mutationAction, relationModel.type, tree.children[field]);
const dataQuery = applyDataQuery(abilities, nestedArgs, operation, mutationAction, relationModel.type, tree.children[field]);
mutation[field][nestedAction] = dataQuery.args;
if (isConnection) {
const accessibleQuery = m5(abilities, mutationAction)[relationModel.type];
Expand Down Expand Up @@ -1314,7 +1322,7 @@ function applyCaslToQuery(operation, args, abilities, model, queryAllRuleRelatio
if (operationAbility.whereQuery && !args.where) {
args.where = {};
}
const { args: dataArgs, creationTree: dataCreationTree } = applyDataQuery(abilities, args, operationAbility.action, model);
const { args: dataArgs, creationTree: dataCreationTree } = applyDataQuery(abilities, args, operation, operationAbility.action, model);
creationTree = dataCreationTree;
args = dataArgs;
if (operation === "updateMany") {
Expand Down Expand Up @@ -1378,26 +1386,36 @@ function filterQueryResults(result, mask, creationTree, abilities, model, operat
}
if (creationTree?.action === "create") {
try {
if (!abilities.can("create", getSubject(model, entry))) {
throw new Error("");
if (creationTree.mutation?.length) {
creationTree.mutation.forEach(({ where }) => {
if (isSubset(where, entry)) {
if (!abilities.can("create", getSubject(model, entry))) {
throw new Error("");
}
}
});
} else {
if (!abilities.can("create", getSubject(model, entry))) {
throw new Error("");
}
}
} catch (e4) {
throw new Error(`It's not allowed to create on ${model} ` + e4);
}
}
if (creationTree?.action === "update" && creationTree.mutation.length > 0) {
creationTree.mutation.forEach(({ fields, where }) => {
fields.forEach((field) => {
if (isSubset(where, entry)) {
if (isSubset(where, entry)) {
fields.forEach((field) => {
try {
if (!abilities.can("update", getSubject(model, entry), field)) {
throw new Error(field);
}
} catch (e4) {
throw new Error(`It's not allowed to update ${field} on ${model} ` + e4);
}
}
});
});
}
});
}
const permittedFields = getPermittedFields(abilities, "read", model, entry);
Expand Down Expand Up @@ -1442,7 +1460,6 @@ function useCaslAbilities(getAbilityFactory, opts) {
return import_client2.Prisma.defineExtension((client) => {
const allOperations = (getAbilities) => ({
async $allOperations({ args, query, model, operation, ...rest }) {
const op = operation === "createMany" ? "createManyAndReturn" : operation;
const fluentModel = getFluentModel(model, rest);
const [fluentRelationModel, fluentRelationField] = (fluentModel !== model ? Object.entries(relationFieldsByModel[model]).find(([k2, v4]) => v4.type === fluentModel) : void 0) ?? [void 0, void 0];
const transaction = rest.__internalParams.transaction;
Expand All @@ -1460,7 +1477,7 @@ function useCaslAbilities(getAbilityFactory, opts) {
perf?.clearMarks("prisma-casl-extension-2");
perf?.clearMarks("prisma-casl-extension-3");
perf?.clearMarks("prisma-casl-extension-4");
if (!(op in caslOperationDict)) {
if (!(operation in caslOperationDict)) {
return query(args);
}
perf?.mark("prisma-casl-extension-0");
Expand Down Expand Up @@ -1494,7 +1511,7 @@ function useCaslAbilities(getAbilityFactory, opts) {
if (fluentRelationModel && caslQuery.mask) {
caslQuery.mask = fluentRelationModel && fluentRelationModel in caslQuery.mask ? caslQuery.mask[fluentRelationModel] : {};
}
const filteredResult = filterQueryResults(result, caslQuery.mask, caslQuery.creationTree, abilities, fluentModel, op, opts);
const filteredResult = filterQueryResults(result, caslQuery.mask, caslQuery.creationTree, abilities, fluentModel, operation, opts);
if (perf) {
perf.mark("prisma-casl-extension-4");
logger?.log(
Expand All @@ -1509,24 +1526,49 @@ function useCaslAbilities(getAbilityFactory, opts) {
})
);
}
return operation === "createMany" ? { count: filteredResult.length } : filteredResult;
return filteredResult;
};
const operationAbility = caslOperationDict[operation];
if (operationAbility.action === "update" || operationAbility.action === "create") {
if (transaction) {
if (transaction.kind === "itx") {
const transactionClient = client._createItxClient(transaction);
return transactionClient[model][op](caslQuery.args).then(cleanupResults);
} else if (transaction.kind === "batch") {
throw new Error("Sequential transactions are not supported in prisma-extension-casl.");
}
if (transaction && transaction.kind === "batch") {
throw new Error("Sequential transactions are not supported in prisma-extension-casl.");
}
const transactionQuery = async (txClient) => {
if (opts?.beforeQuery) {
await opts.beforeQuery(txClient);
}
if (operationAbility.action === "update" || operationAbility.action === "create" || operation === "deleteMany") {
const getMany = operation === "deleteMany" || operation === "updateMany";
const manyResult = getMany ? await txClient[model].findMany(caslQuery.args.where ? { where: caslQuery.args.where } : void 0).then((res) => {
return operation === "updateMany" ? res.map((r2) => ({ ...caslQuery.args.data, id: r2.id })) : res;
}) : [];
const op = operation === "createMany" ? "createManyAndReturn" : operation;
return txClient[model][op](caslQuery.args).then(async (result) => {
if (opts?.afterQuery) {
await opts.afterQuery(txClient);
}
const filteredResult = cleanupResults(getMany ? manyResult : result);
const results = operation === "createMany" ? { count: result.length } : getMany ? { count: manyResult.length } : filteredResult;
return results;
});
} else {
return client.$transaction(async (tx) => {
return tx[model][op](caslQuery.args).then(cleanupResults);
return txClient[model][operation](caslQuery.args).then(async (result) => {
if (opts?.afterQuery) {
await opts.afterQuery(txClient);
}
const fluentField = getFluentField(rest);
if (fluentField) {
return cleanupResults(result?.[fluentField]);
}
return cleanupResults(result);
});
}
};
if (transaction && transaction.kind === "itx") {
return transactionQuery(client._createItxClient(transaction));
} else {
return query(caslQuery.args).then(cleanupResults);
return client.$transaction(async (tx) => {
return transactionQuery(tx);
});
}
}
});
Expand Down
Loading

0 comments on commit f666f87

Please sign in to comment.