Skip to content
TypeWire

@tahanabavi/type-permission

v0.1.0

Framework-less, dependency-free capability permissions: one shared bit map, evaluated identically on client and server, with layered resolution, codecs, a lock file and contract-linkable requirements.

Define once — the shared file both ends import

ts
import { definePermissions } from "@tahanabavi/type-permission";

export const P = definePermissions({
chat: {
VIEW_CHANNEL: { bit: 0 },
SEND_MESSAGES: { bit: 1, requires: ["chat.VIEW_CHANNEL"] },
MANAGE_MESSAGES: { bit: 2, implies: ["chat.SEND_MESSAGES"] },
ATTACH_FILES: { bit: 3, requires: ["chat.SEND_MESSAGES"] },
},
guild: {
MANAGE_ROLES: { bit: 8 },
KICK_MEMBERS: { bit: 9 },
ADMINISTRATOR: { bit: 63, grantsAll: true },
},
});

Nested modules yield the same "module.member" id shape as endpointId (typefetch) and eventId (typesocket). bit is explicit and permanent — duplicate bits throw at load, and a bigint means there is no 64-flag ceiling.

Check — typo-proof

ts
P.has(perms, "chat.SEND_MESSAGES");   // a typo is a compile error, not `false`
P.hasAll(perms, ["chat.SEND_MESSAGES", "chat.ATTACH_FILES"]);
P.list(perms); // typed ("chat.VIEW_CHANNEL" | …)[]
P.explain(perms, "chat.SEND_MESSAGES");
// { granted: false, reason: "denied-requires",
// detail: "chat.SEND_MESSAGES is gated by chat.VIEW_CHANNEL, which was denied." }