permix
Version:
Permix is a lightweight, framework-agnostic, type-safe permissions management library for JavaScript applications on the client and server sides.
144 lines (143 loc) • 4.17 kB
JavaScript
import { PermixForbiddenError, PermixNotFoundError, createCheckContext, createHooks, createPermix as createPermix$1, createTemplate } from "../core/index.mjs";
import { createMiddleware } from "@tanstack/react-start";
//#region src/tanstack-start/permix.ts
function buildPermix(resolveKey, options = {}) {
const onForbidden = options.onForbidden ?? (() => {
throw new PermixForbiddenError();
});
const hooks = createHooks();
/**
* Returns the request-scoped Permix instance from a TanStack Start context
* object, or `null` when not set up yet.
*/
function get(context) {
return context?.[resolveKey()] ?? null;
}
/**
* Like {@link get}, but throws {@link PermixNotFoundError} when the instance
* is missing.
*/
function getOrThrow(context) {
const instance = get(context);
if (!instance) throw new PermixNotFoundError(resolveKey());
return instance;
}
/**
* TanStack Start middleware that creates a request-scoped Permix instance,
* calls `setup()` with the resolved rules, and stores it in the server context.
*
* Register it globally via `createStart({ requestMiddleware: [...] })` so it
* runs for every request, or attach it to specific server routes.
*/
function setupMiddleware(callbackOrRules) {
return createMiddleware().server(async ({ next, request }) => {
const instance = createPermix$1(typeof callbackOrRules === "function" ? await callbackOrRules({ request }) : callbackOrRules);
instance.hook("check", (context) => hooks.callHook("check", context));
return next({ context: { [resolveKey()]: instance } });
});
}
/**
* TanStack Start middleware that enforces a permission check before the
* server function handler runs.
*
* @example
* ```ts
* export const createPost = createServerFn({ method: 'POST' })
* .middleware([permix.checkMiddleware('post.create')])
* .handler(() => { ... })
* ```
*/
const checkMiddleware = (...args) => {
return createMiddleware({ type: "function" }).server(async ({ next, context }) => {
if (getOrThrow(context).check(...args)) return next();
else return await onForbidden({
next,
...createCheckContext(...args)
});
});
};
/**
* Serialize the request's permission state for client hydration.
*/
function dehydrate(context) {
return getOrThrow(context).dehydrate();
}
function getRules(context) {
return get(context)?.getRules() ?? null;
}
function template(rules) {
return createTemplate(rules);
}
return {
setupMiddleware,
checkMiddleware,
get,
getOrThrow,
dehydrate,
getRules,
template,
hook: hooks.hook,
hookOnce: hooks.hookOnce,
get key() {
return resolveKey();
},
$inferDefinition: void 0,
$inferPath: void 0
};
}
/**
* Create a per-request Permix helper for TanStack Start.
*
* Uses TanStack Start's per-request server context to share a single Permix
* instance across global middleware, server routes, server functions, and the
* router for the lifetime of each request.
*
* Use `.contextKey('name')` to set a custom context key (defaults to
* `'__permix'`).
*
* @example
* ```ts
* // lib/permix.ts
* import { createPermix } from 'permix/tanstack-start'
*
* export const permix = createPermix<{
* post: ['create', 'read', 'update', 'delete']
* }>()
* ```
*
* ```ts
* // src/start.ts
* import { createStart } from '@tanstack/react-start'
* import { getSession } from './lib/auth'
* import { permix } from './lib/permix'
*
* export const startInstance = createStart(() => ({
* requestMiddleware: [
* permix.setupMiddleware(async ({ request }) => {
* const session = await getSession(request)
* return {
* post: {
* create: !!session,
* read: true,
* update: session?.role === 'admin',
* delete: session?.role === 'admin',
* },
* }
* }),
* ],
* }))
* ```
*
* @link https://permix.letstri.dev/docs/integrations/tanstack-start
*/
function createPermix(options = {}) {
let key = "__permix";
const permix = buildPermix(() => key, options);
const instance = Object.assign(permix, { contextKey(newKey) {
key = newKey;
return instance;
} });
return instance;
}
//#endregion
export { createPermix };