Skip to content

Instantly share code, notes, and snippets.

@PatrickKennedy
Created June 9, 2025 06:00
Show Gist options
  • Select an option

  • Save PatrickKennedy/76d4ff1048ba309872a20f931eb41f86 to your computer and use it in GitHub Desktop.

Select an option

Save PatrickKennedy/76d4ff1048ba309872a20f931eb41f86 to your computer and use it in GitHub Desktop.
useBitwiseRules - A simple, typesafe multidimensional rules system
import { useBitwiseRules } from './useBitwiseRules'
enum TestFlags {
TypeA = 1 << 0,
TypeB = 1 << 1,
StatusA = 1 << 2,
StatusB = 1 << 3,
StatusC = 1 << 4,
AnyType = TypeA | TypeB,
AnyStatus = StatusA | StatusB | StatusC,
Unrestricted = TypeA | TypeB | StatusA | StatusB | StatusC,
}
enum ConflictingFlags {
Any = 0,
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
}
type TestRules = {
show: TestFlags
required?: TestFlags
readonly?: TestFlags
}
describe('useBitwiseRules', context => {
it('should not allow flags from different enums', () => {
const { runLogic } = useBitwiseRules<TestFlags, TestRules>()
// Setting a single flag from another enum will throw a type error because
// it's still considered of the enum's type
// @ts-expect-error - This comment will light up if there is no type error
const singleFlag: TestFlags = ConflictingFlags.First
// The bitwise operation on the enums converts the result to a number
// and because the enums are numbers, it will not throw an error
const flags: TestFlags = ConflictingFlags.First | TestFlags.TypeA
const rule = {
show: ConflictingFlags.Any,
required: TestFlags.TypeA,
}
// @ts-expect-error - This comment will light up if there is no type error
assertType(runLogic(flags, rule))
})
test('Unrestricted allows any flags', () => {
const { runLogic } = useBitwiseRules<TestFlags, TestRules>()
const flags = TestFlags.TypeA | TestFlags.StatusA
const rule = {
show: TestFlags.Unrestricted,
required: TestFlags.Unrestricted,
}
const result = runLogic(flags, rule)
expect(result.show).toBe(true)
expect(result.required).toBe(true)
})
test('Rule without a Course Type should accept any Course Type', () => {
const { runLogic } = useBitwiseRules<TestFlags, TestRules>()
const flags = TestFlags.TypeA | TestFlags.StatusA
const rule = {
show: TestFlags.AnyType | TestFlags.StatusA,
required: TestFlags.StatusB,
}
const result = runLogic(flags, rule)
expect(result.show).toBe(true)
expect(result.required).toBe(false)
})
test('Rule with multiple flags should return true if only one flag is set', () => {
const { runLogic } = useBitwiseRules<TestFlags, TestRules>()
const flags = TestFlags.TypeA | TestFlags.StatusA
const rule = {
show: TestFlags.AnyType | TestFlags.StatusA | TestFlags.StatusB,
required: TestFlags.TypeB | TestFlags.AnyStatus
}
const result = runLogic(flags, rule)
expect(result.show).toBe(true)
expect(result.required).toBe(false)
})
})
/**
* Provides a standardized way to run bitwise logic against flags and rules.
* This makes it much easier to encode logic that depends on multiple attributes
*
* @see: https://shaky.sh/ts-bit-flags/
*
* It differs from the article in that the flags are more restrictive than the
* rules, so by flipping the conditional logic we're asking if all the flags are
* present in the rule rather than if all the rules are present in the flags.
*
* This comes into play with courses where a course can only be one each of a
* type and a status, it will never be both a Credit and Degree course, so
* inorder for the rules to accept more than one credit status we compare in
* reverse.
*/
export const useBitwiseRules = <Flags extends number, Rules extends Record<string, number>>() => {
const runLogic = (flags: Flags, rules: Rules) => {
const result = {} as Record<keyof Rules, boolean>
Object.entries(rules).forEach(
([field, rule]) =>
// The question this is asking is "are all the flags present in the rule?"
(result[field as keyof Rules] = (rule & flags) === flags)
)
return result
}
return {
runLogic,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment