UNPKG

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
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 };