-
-
Save scinscinscin/27dd8cd65b65dd87af1982e19e7cf9f5 to your computer and use it in GitHub Desktop.
A copy paste library for managing bitfields with full typesafety in TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface GandalfT<T extends Readonly<string[]>> { | |
addBit(bitfield: number, permission: T[number] | number): number; | |
removeBit(bitfield: number, permission: T[number] | number): number; | |
hasBit(bitfield: number, permission: T[number] | number): boolean; | |
create(permissions: readonly (T[number] | number)[]): number; | |
toggleBit(bitfield: number, permission: T[number] | number): number; | |
getConstants(bitfield: number): string[]; | |
enum: Record<T[number], number>; | |
$enumConstants: T; | |
} | |
/** | |
* A function for manipulating bitfields with full typesafety | |
* @example | |
* ```ts | |
* // The bits are based on the indexes of the array, so "modify_post" is controlled by the 0th bit, or LSB | |
* const rbac = Gandalf(["modify_post", "modify_media"] as const); | |
* const adminBitfield = rbac.create(["modify_post", "modify_media"]); | |
* console.log(rbac.hasBit(adminBitfield, "modify_media")); | |
* console.log(rbac.removeBit(adminBitfield, "modify_media")); | |
* ``` | |
*/ | |
export function Gandalf<T extends Readonly<string[]>>(enumConstants: T): GandalfT<T> { | |
const permissions = {} as Record<T[number], number>; | |
for (let i = 0; i < enumConstants.length; i++) { | |
permissions[enumConstants[i] as T[number]] = i; | |
} | |
const getIndex = (permission: T[number] | number) => { | |
const index = typeof permission === "number" ? permission : enumConstants.indexOf(permission); | |
if (index >= 0) return index; | |
else throw new Error("Was not able to find permission with that index"); | |
}; | |
return { | |
addBit(bitfield, permission) { | |
return bitfield | (0b1 << getIndex(permission)); | |
}, | |
removeBit(bitfield, permission) { | |
// Get a bitmask with a 1 in the position that we want to remove | |
// Then invert it using Bitwise XOR to get a 0 mask instead | |
// then AND it with the existing bitfield to set the bit to 0 | |
return bitfield & ((0b1 << getIndex(permission)) ^ (Math.pow(2, enumConstants.length) - 1)); | |
}, | |
hasBit(bitfield, permission) { | |
// the value is 0 if the bitfield does not have that property bit | |
return (bitfield & (0b1 << getIndex(permission))) !== 0; | |
}, | |
toggleBit(bitfield, permission) { | |
if (this.hasBit(bitfield, permission)) return this.removeBit(bitfield, permission); | |
else return this.addBit(bitfield, permission); | |
}, | |
/** | |
* Create a bitfield from an array of permissions | |
* @param permissions The list of permissions to create the bitfield with | |
* @returns a bitfield | |
*/ | |
create(permissions) { | |
let bitfield = 0; | |
for (const permission of permissions) bitfield = bitfield | (0b1 << getIndex(permission)); | |
return bitfield; | |
}, | |
/** | |
* Get the string constants that the bitfield has enabled | |
*/ | |
getConstants(bitfield) { | |
return enumConstants.filter((_, i) => this.hasBit(bitfield, i)); | |
}, | |
enum: permissions, | |
$enumConstants: enumConstants, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment