trpc-shield
Version:
tRPC permissions as another layer of abstraction!
60 lines (54 loc) • 1.73 kB
text/typescript
import { IOptions, IRules, ShieldRule } from './types';
/**
*
* @param ruleTree
* @param options
*
* Generates middleware from given rules.
*
*/
export function generateMiddlewareFromRuleTree<TContext extends Record<string, unknown>>(
ruleTree: IRules<TContext>,
options: IOptions<TContext>,
) {
return (opts: {
next: Function;
ctx: TContext;
type: string;
path: string;
input: { [name: string]: any };
getRawInput: () => unknown;
}) => {
const { next, ctx, type, path, input, getRawInput } = opts;
const rawInput = getRawInput();
const opWithPath: Array<string> = path.split('.');
const opName: string = opWithPath[opWithPath.length - 1];
const keys = Object.keys(ruleTree);
let rule: ShieldRule<TContext> | undefined;
if (keys.includes('query') || keys.includes('mutation')) {
//@ts-ignore
rule = ruleTree?.[type]?.[opName];
} else {
const namespace = opWithPath[0];
const tree = (ruleTree as Record<string, any>)[namespace];
if (tree?.[type]?.[opName]) {
rule = tree?.[type]?.[opName];
}
}
rule = rule || options.fallbackRule;
if (rule) {
return rule?.resolve(ctx, type, path, input, rawInput, options).then((result) => {
if (result instanceof Error) throw result;
// Handle context extension
if (typeof result === 'object' && result !== null && 'ctx' in result) {
// Merge context extension and call next with updated context
const extendedCtx = { ...ctx, ...result.ctx };
return next({ ctx: extendedCtx });
}
if (!result) throw options.fallbackError;
return next();
});
}
return next();
};
}