permix
Version:
Permix is a lightweight, framework-agnostic, type-safe permissions management library for JavaScript applications on the client and server sides.
109 lines (108 loc) • 2.89 kB
JavaScript
import { PermixNotFoundError, createCheckContext, createHooks, createPermix as createPermix$1, createTemplate } from "../core/index.mjs";
import fp from "fastify-plugin";
//#region src/fastify/permix.ts
let pluginCounter = 0;
function buildPermix(resolveKey, options = {}) {
const onForbidden = options.onForbidden ?? (({ reply }) => {
reply.status(403).send({ error: "Forbidden" });
});
const pluginName = `permix-${pluginCounter++}`;
const hooks = createHooks();
function get(request) {
try {
return request.getDecorator(resolveKey()) ?? null;
} catch {
return null;
}
}
function getOrThrow(request) {
const instance = get(request);
if (!instance) throw new PermixNotFoundError(resolveKey());
return instance;
}
function setupMiddleware(callbackOrRules) {
return fp(async (fastify) => {
fastify.decorateRequest(resolveKey(), null);
fastify.addHook("onRequest", async (request, reply) => {
const instance = createPermix$1(typeof callbackOrRules === "function" ? await callbackOrRules({
request,
reply
}) : callbackOrRules);
instance.hook("check", (context) => hooks.callHook("check", context));
request.setDecorator(resolveKey(), instance);
});
}, {
fastify: "5.x",
name: pluginName
});
}
const checkMiddleware = (...args) => {
return async (request, reply) => {
const permix = get(request);
if (!permix) throw new PermixNotFoundError(resolveKey());
if (!permix.check(...args)) await onForbidden({
request,
reply,
...createCheckContext(...args)
});
};
};
function getRules(request) {
return get(request)?.getRules() ?? null;
}
function template(rules) {
return createTemplate(rules);
}
return {
setupMiddleware,
checkMiddleware,
template,
get,
getOrThrow,
getRules,
hook: hooks.hook,
hookOnce: hooks.hookOnce,
get key() {
return resolveKey();
},
$inferDefinition: void 0,
$inferPath: void 0
};
}
/**
* Create a plugin factory that wires Permix into Fastify routes.
*
* Use `.contextKey('name')` to set a custom request decorator key (defaults to
* a unique `Symbol('permix')`).
*
* @example
* ```ts
* import Fastify from 'fastify'
* import { createPermix } from 'permix/fastify'
*
* const permix = createPermix<{
* post: ['create', 'read']
* }>()
*
* const app = Fastify()
* await app.register(permix.setupMiddleware(({ request }) => ({
* post: { create: !!request.user, read: true },
* })))
*
* app.get('/posts', {
* preHandler: permix.checkMiddleware('post.read'),
* }, (request, reply) => reply.send({ ok: true }))
* ```
*
* @link https://permix.letstri.dev/docs/integrations/fastify
*/
function createPermix(options = {}) {
let key = Symbol("permix");
const permix = buildPermix(() => key, options);
return Object.assign(permix, { contextKey(newKey) {
key = newKey;
return permix;
} });
}
//#endregion
export { createPermix };