custom-bitfield
v1.1.0
Published
A customisable bitfield that can be used for building e.g. your own permissions system (or similar). Works with any amount of bits.
Downloads
99
Maintainers
Readme
Custom Bitfield
This is a utility package that allows you to create your own bitfields for things such as permission management for efficient comparison and storage.
It depends on bigints (123n
notation), and therefore needs a runtime environment that supports ES2020
features:
- For node.js projects, you'll need Node.js version 12.9.0 or later.
- For Typescript projects, you'll need to set
"target": "ES2020"
or anything aboveES2020
in yourtsconfig.json
.
Older versions of different internet browsers might not support this feature, please refer to https://caniuse.com/bigint for further information.
Usage:
1. Create a Flag Map
In order to create a valid flag map to use, all numbers in it need to be of type bigint
, be powers of two, and all values in it need to be unique. The suggested approach is as follows:
const MyFlagMap = {
AdminAccess: 1n << 0n,
ReadAccess: 1n << 1n,
WriteAccess: 1n << 2n,
EditAccess: 1n << 3n,
// ...
}
You can add however many bits / flags you need, however, please keep two things in mind:
[!NOTE] The amount of bitshifts you do, such as
1n << 9999n
determines the size of your bitfield in bits. Starting your first entry with a bitshift of 9999 bits ensures that your bitfield is always at least 10'000 bits large. It therefore makes most sense to avoid large bitshifts and start with small ones first.
[!WARNING] Once you store and retrieve bits in a bitfield, any changes to the meanings or values of any existing flags will mess up all your previously stored values, as they will be interpreted according to the new flag map, not the one they were initialised with. Therefore, it makes most sense to only add new permissions to the end of the flag map and always choose a value that has never been used before. Never reassign values in the flag map, unless you know exactly what you are doing.
2. Create the Bitfield
A bitfield needs a valid flag map to be created. If the flag map is invalid, this step will throw an error.
const myBitField = new BitField(MyFlagMap);
3. Edit the Bitfield
Adding Bits to the Bitfield
myBitfield.add(MyFlagMap.EditAccess);
myBitfield.add(MyFlagMap.ReadAccess);
myBitfield.addAll([ MyFlagMap.EditAccess, MyFlagMap.ReadAccess ]);
myBitfield.addAllFromBitfield(someOtherBitfield);
Removing Bits from the Bitfield
myBitfield.remove(MyFlagMap.EditAccess);
myBitfield.removeAll([ MyFlagMap.EditAccess, MyFlagMap.AdminAccess ]);
myBitfield.removeAllFromBitfield(someOtherBitfield);
myBitfield.reset(); // removes ALL bits from the bitfield
4. Query and Compare the Bitfield
Check if the Bitfield Has Some or All Flags
myBitfield.has(MyFlagMap.EditAccess); // these functions all return a boolean
myBitfield.hasAll([ MyFlagMap.EditAccess, MyFlagMap.ReadAccess ]);
myBitfield.hasAllFromBitfield(someOtherBitfield);
myBitfield.hasAny([ MyFlagMap.EditAccess, MyFlagMap.AdminAccess ]);
myBitfield.hasAnyFromBitfield(someOtherBitfield);
Turn the Bitfield Into an Array of Flags
const foundFlags = myBitfield.toArray(); // returns a bigint[]
if (foundFlags.includes(MyFlagMap.AdminAccess)) console.log("Has Admin Access.");
if (foundFlags.includes(MyFlagMap.ReadAccess)) console.log("Has Read Access.");
if (foundFlags.includes(MyFlagMap.WriteAccess)) console.log("Has Write Access.");
if (foundFlags.includes(MyFlagMap.EditAccess)) console.log("Has Edit Access.");
Find Which Flags Specified in the Function Parameter Are Missing From This Bitfield
// these all return a bigint[] of all missing flags.
// Assuming our bitfield has EditAccess and ReadAccess, we get the following results:
myBitfield.missing(MyFlagMap.AdminAccess); // [ MyFlagMap.AdminAccess ]
myBitfield.missing(MyFlagMap.EditAccess); // []
myBitfield.missingFrom([ MyFlagMap.ReadAccess, MyFlagMap.AdminAccess ]); // [ MyFlagMap.AdminAccess ]
myBitfield.missingFromBitfield(someOtherBitfield);
5. Serialise and Deserialise (Store and Load) the Bitfield
const stringToStore = myBitfield.toString();
// some storing and loading functionality...
// const loadedBitField = database.load(...)
// Important! Use same flag map as for the stored values!
const myNewBitField = new BitField(MyFlagMap);
try { // Unsafe conversion.
myNewBitField.fromStringUnsafe(loadedBitField);
} catch (error) {
console.log('Loaded Bit Field was not a number.', error);
}
try { // Safe conversion.
myNewBitField.fromStringSafe(loadedBitField);
} catch (error) {
// String was empty, not a number, a negative number,
// or had bits set that are not specified in the flag map.
console.log(error);
}
// Safety checks:
if (myNewBitField.isValidValue(loadedBitField)) {
try {
myNewBitField.fromStringUnsafe(loadedBitField);
} catch (error) {
console.log('This catch should never happen.', error);
}
} else {
// String was empty, not a number, a negative number,
// or had bits set that are not specified in the flag map.
}
if (myNewBitField.isValidValueSoft(loadedBitField)) {
try {
myNewBitField.fromStringUnsafe(loadedBitField);
} catch (error) {
console.log('This catch should never happen.', error);
}
} else {
// String was not a number or a negative number.
}
6. Advanced Usage:
In case you wish to use the bitfield with bigints instead of your predefined flags and other bitfields, this is possible as well.
myBitfield.add(0b1110n); // Set the flags for Read, Write and Edit Access.
myBitfield.addAll([ 0b1000n, 0b1100n, 0b0101n ]); // Equivalent: myBitfield.add(0b1101)
myBitfield.remove(0b0010n); // Remove the flag for Read Access. Equivalent: myBitfield.remove(2n)
myBitfield.removeAll([ 0b0011n, 0b0001n ]); // Equivalent: myBitfield.remove(0b0011)
myBitfield.has(0b1100n); // Are ALL the bits present, i.e. do we have Write AND Edit access?
myBitfield.hasAll([ 0b1100n, 0b0010n ]); // Equivalent: myBitfield.has(0b1110n)
myBitfield.anyBits(0b0101n); // Are ANY bits present, i.e. do we have Admin OR Write access?
myBitfield.hasAny([ 0b0100n, 0b0011n ]); // Equivalent: myBitfield.anyBits(0b0111n)
myBitfield.missing(0b1111n); // Which bits set in 0b1111n are not set in the bitfield?
myBitfield.missing(0b1100n); // Which bits set in 0b1100n are not set in the bitfield?
(As you'll have larger bitfields, it might make sense to use decimal or hexadecimal bigint notation.)