Skip to content

Commit

Permalink
feat: add pre-generated select options for activatable entries as cache
Browse files Browse the repository at this point in the history
  • Loading branch information
elyukai committed Jan 12, 2024
1 parent a70a7ea commit 3fdcaf2
Show file tree
Hide file tree
Showing 11 changed files with 1,591 additions and 44 deletions.
1,275 changes: 1,275 additions & 0 deletions src/cache/activatableSelectOptions.ts

Large diffs are not rendered by default.

73 changes: 71 additions & 2 deletions src/cache/newApplicationsAndUses.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { CacheConfig } from "../cacheConfig.js"
import type { SkillApplication, SkillUse } from "../types/_Activatable.js"
import type { ActivatableIdentifier } from "../types/_IdentifierGroup.js"
import { ActivatableSelectOptionsCache } from "./activatableSelectOptions.js"

export type NewApplication = {
source: ActivatableIdentifier
Expand All @@ -18,8 +19,11 @@ export type NewApplicationsAndUsesCache = {
uses: Record<number, Use[]>
}

export const config: CacheConfig<NewApplicationsAndUsesCache> = {
builder(database) {
export const config: CacheConfig<
NewApplicationsAndUsesCache,
[generatedSelectOptions: ActivatableSelectOptionsCache]
> = {
builder(database, generatedSelectOptions) {
type WithApplicationsAndUses = {
skill_applications?: SkillApplication[]
skill_uses?: SkillUse[]
Expand Down Expand Up @@ -97,6 +101,71 @@ export const config: CacheConfig<NewApplicationsAndUsesCache> = {
})
})

// prettier-ignore
const generatedEntries: [
{ [id: number]: WithApplicationsAndUses[] },
(numericId: number) => ActivatableIdentifier,
][] = [
[generatedSelectOptions.advancedCombatSpecialAbilities, id => ({ tag: "AdvancedCombatSpecialAbility", advanced_combat_special_ability: id })],
[generatedSelectOptions.advancedKarmaSpecialAbilities, id => ({ tag: "AdvancedKarmaSpecialAbility", advanced_karma_special_ability: id })],
[generatedSelectOptions.advancedMagicalSpecialAbilities, id => ({ tag: "AdvancedMagicalSpecialAbility", advanced_magical_special_ability: id })],
[generatedSelectOptions.advancedSkillSpecialAbilities, id => ({ tag: "AdvancedSkillSpecialAbility", advanced_skill_special_ability: id })],
[generatedSelectOptions.advantages, id => ({ tag: "Advantage", advantage: id })],
[generatedSelectOptions.ancestorGlyphs, id => ({ tag: "AncestorGlyph", ancestor_glyph: id })],
[generatedSelectOptions.arcaneOrbEnchantments, id => ({ tag: "ArcaneOrbEnchantment", arcane_orb_enchantment: id })],
[generatedSelectOptions.attireEnchantments, id => ({ tag: "AttireEnchantment", attire_enchantment: id })],
[generatedSelectOptions.blessedTraditions, id => ({ tag: "BlessedTradition", blessed_tradition: id })],
[generatedSelectOptions.bowlEnchantments, id => ({ tag: "BowlEnchantment", bowl_enchantment: id })],
[generatedSelectOptions.brawlingSpecialAbilities, id => ({ tag: "BrawlingSpecialAbility", brawling_special_ability: id })],
[generatedSelectOptions.cauldronEnchantments, id => ({ tag: "CauldronEnchantment", cauldron_enchantment: id })],
[generatedSelectOptions.ceremonialItemSpecialAbilities, id => ({ tag: "CeremonialItemSpecialAbility", ceremonial_item_special_ability: id })],
[generatedSelectOptions.chronicleEnchantments, id => ({ tag: "ChronicleEnchantment", chronicle_enchantment: id })],
[generatedSelectOptions.combatSpecialAbilities, id => ({ tag: "CombatSpecialAbility", combat_special_ability: id })],
[generatedSelectOptions.combatStyleSpecialAbilities, id => ({ tag: "CombatStyleSpecialAbility", combat_style_special_ability: id })],
[generatedSelectOptions.commandSpecialAbilities, id => ({ tag: "CommandSpecialAbility", command_special_ability: id })],
[generatedSelectOptions.daggerRituals, id => ({ tag: "DaggerRitual", dagger_ritual: id })],
[generatedSelectOptions.disadvantages, id => ({ tag: "Disadvantage", disadvantage: id })],
[generatedSelectOptions.familiarSpecialAbilities, id => ({ tag: "FamiliarSpecialAbility", familiar_special_ability: id })],
[generatedSelectOptions.fatePointSexSpecialAbilities, id => ({ tag: "FatePointSexSpecialAbility", fate_point_sex_special_ability: id })],
[generatedSelectOptions.fatePointSpecialAbilities, id => ({ tag: "FatePointSpecialAbility", fate_point_special_ability: id })],
[generatedSelectOptions.foolsHatEnchantments, id => ({ tag: "FoolsHatEnchantment", fools_hat_enchantment: id })],
[generatedSelectOptions.generalSpecialAbilities, id => ({ tag: "GeneralSpecialAbility", general_special_ability: id })],
[generatedSelectOptions.instrumentEnchantments, id => ({ tag: "InstrumentEnchantment", instrument_enchantment: id })],
[generatedSelectOptions.karmaSpecialAbilities, id => ({ tag: "KarmaSpecialAbility", karma_special_ability: id })],
[generatedSelectOptions.krallenkettenzauber, id => ({ tag: "Krallenkettenzauber", krallenkettenzauber: id })],
[generatedSelectOptions.liturgicalStyleSpecialAbilities, id => ({ tag: "LiturgicalStyleSpecialAbility", liturgical_style_special_ability: id })],
[generatedSelectOptions.lycantropicGifts, id => ({ tag: "LycantropicGift", lycantropic_gift: id })],
[generatedSelectOptions.magicalSpecialAbilities, id => ({ tag: "MagicalSpecialAbility", magical_special_ability: id })],
[generatedSelectOptions.magicalTraditions, id => ({ tag: "MagicalTradition", magical_tradition: id })],
[generatedSelectOptions.magicStyleSpecialAbilities, id => ({ tag: "MagicStyleSpecialAbility", magic_style_special_ability: id })],
[generatedSelectOptions.orbEnchantments, id => ({ tag: "OrbEnchantment", orb_enchantment: id })],
[generatedSelectOptions.pactGifts, id => ({ tag: "PactGift", pact_gift: id })],
[generatedSelectOptions.protectiveWardingCircleSpecialAbilities, id => ({ tag: "ProtectiveWardingCircleSpecialAbility", protective_warding_circle_special_ability: id })],
[generatedSelectOptions.ringEnchantments, id => ({ tag: "RingEnchantment", ring_enchantment: id })],
[generatedSelectOptions.sermons, id => ({ tag: "Sermon", sermon: id })],
[generatedSelectOptions.sexSpecialAbilities, id => ({ tag: "SexSpecialAbility", sex_special_ability: id })],
[generatedSelectOptions.sickleRituals, id => ({ tag: "SickleRitual", sickle_ritual: id })],
[generatedSelectOptions.sikaryanDrainSpecialAbilities, id => ({ tag: "SikaryanDrainSpecialAbility", sikaryan_drain_special_ability: id })],
[generatedSelectOptions.staffEnchantments, id => ({ tag: "StaffEnchantment", staff_enchantment: id })],
[generatedSelectOptions.skillStyleSpecialAbilities, id => ({ tag: "SkillStyleSpecialAbility", skill_style_special_ability: id })],
[generatedSelectOptions.spellSwordEnchantments, id => ({ tag: "SpellSwordEnchantment", spell_sword_enchantment: id })],
[generatedSelectOptions.toyEnchantments, id => ({ tag: "ToyEnchantment", toy_enchantment: id })],
[generatedSelectOptions.trinkhornzauber, id => ({ tag: "Trinkhornzauber", trinkhornzauber: id })],
[generatedSelectOptions.vampiricGifts, id => ({ tag: "VampiricGift", vampiric_gift: id })],
[generatedSelectOptions.visions, id => ({ tag: "Vision", vision: id })],
[generatedSelectOptions.wandEnchantments, id => ({ tag: "WandEnchantment", wand_enchantment: id })],
[generatedSelectOptions.weaponEnchantments, id => ({ tag: "WeaponEnchantment", weapon_enchantment: id })],
]

generatedEntries.forEach(([ids, createId]) =>
Object.entries(ids).forEach(([stringId, selectOptions]) => {
const id = createId(Number.parseInt(stringId))
selectOptions.forEach(selectOption => {
addNewApplicationsAndUses(selectOption, id)
})
})
)

return cache
},
}
4 changes: 2 additions & 2 deletions src/cacheConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ValidResults } from "./main.js"

export type CacheConfig<T> = {
builder: (data: ValidResults) => T
export type CacheConfig<T, D extends unknown[] = []> = {
builder: (data: ValidResults, ...deps: D) => T
}
3 changes: 3 additions & 0 deletions src/config/cache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import * as ActivatableSelectOptions from "../cache/activatableSelectOptions.js"
import * as AncestorBloodAdvantages from "../cache/ancestorBloodAdvantages.js"
import * as NewApplicationsAndUses from "../cache/newApplicationsAndUses.js"

export type CacheMap = {
activatableSelectOptions: ActivatableSelectOptions.ActivatableSelectOptionsCache
ancestorBloodAdvantages: AncestorBloodAdvantages.AncestorBloodAdvantagesCache
newApplicationsAndUses: NewApplicationsAndUses.NewApplicationsAndUsesCache
}

export const cacheMap = {
activatableSelectOptions: ActivatableSelectOptions.config,
ancestorBloodAdvantages: AncestorBloodAdvantages.config,
newApplicationsAndUses: NewApplicationsAndUses.config,
}
72 changes: 72 additions & 0 deletions src/helpers/nullable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import {
isNotNullish,
isNullish,
mapNullable,
mapNullableDefault,
nullableToArray,
} from "./nullable.js"

describe("isNullish", () => {
it("returns if a value is nullish", () => {
assert.equal(isNullish(null), true)
assert.equal(isNullish(undefined), true)
assert.equal(isNullish(false), false)
assert.equal(isNullish(0), false)
assert.equal(isNullish(""), false)
})
})

describe("isNotNullish", () => {
it("returns if a value is not nullish", () => {
assert.equal(isNotNullish(null), false)
assert.equal(isNotNullish(undefined), false)
assert.equal(isNotNullish(false), true)
assert.equal(isNotNullish(0), true)
assert.equal(isNotNullish(""), true)
})
})

describe("mapNullable", () => {
it("maps a value if it is not nullish", () => {
assert.equal(
mapNullable(2, x => x * 2),
4
)
})

it("returns the original value if it is nullish", () => {
assert.equal(
mapNullable(undefined, x => x * 2),
undefined
)
})
})

describe("mapNullableDefault", () => {
it("maps a value if it is not nullish", () => {
assert.equal(
mapNullableDefault(2, x => x * 2, 0),
4
)
})

it("returns a default if the value is nullish", () => {
assert.equal(
mapNullableDefault(undefined, x => x * 2, 0),
0
)
})
})

describe("nullableToArray", () => {
it("wraps a non-nullish value into an array", () => {
assert.deepEqual(nullableToArray(2), [2])
})

it("returns an empty array if the value is null or undefined", () => {
assert.deepEqual(nullableToArray(undefined), [])
assert.deepEqual(nullableToArray(null), [])
})
})
47 changes: 47 additions & 0 deletions src/helpers/nullable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Extracts `null` and `undefined` from a type.
*/
export type Nullish<T = null | undefined> = T extends null | undefined ? T : never

/**
* Checks if a value is `null` or `undefined`.
*/
export const isNullish = <T>(value: T): value is Exclude<T, NonNullable<T>> =>
value === null || value === undefined

/**
* Checks if a value is not `null` or `undefined`.
*/
export const isNotNullish = <T>(value: T): value is NonNullable<T> => !isNullish(value)

/**
* Maps a value to another value if it is not `null` or `undefined`.
*/
export const mapNullable = <T, U>(value: T, map: (value: NonNullable<T>) => U): U | Nullish<T> =>
isNotNullish(value) ? map(value) : (value as Nullish<T>)

/**
* Maps a value to another value if it is not `null` or `undefined`, otherwise
* returns a default value.
*/
export const mapNullableDefault = <T, U>(
value: T,
map: (value: NonNullable<T>) => U,
defaultValue: U,
): U => (isNotNullish(value) ? map(value) : defaultValue)

/**
* Returns an array, containing the value if it is not `null` or `undefined`.
*
* This can be useful in combination with the spread operator or
* `Array.prototype.flatMap`.
* @example
* nullableToArray(2) // [2]
* nullableToArray(undefined) // []
*
* [...nullableToArray(2)] // [2]
* [1, ...nullableToArray(2)] // [1, 2]
* [1, ...nullableToArray(undefined)] // [1]
*/
export const nullableToArray = <T>(value: T): NonNullable<T>[] =>
isNotNullish(value) ? [value] : []
17 changes: 17 additions & 0 deletions src/helpers/object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { mapObject } from "./object.js"

describe("mapObject", () => {
it("maps all own properties of an object to a new object", () => {
const object = { a: 1, b: 2, c: 3 }
const result = mapObject(object, (value, key) => value + key)
assert.deepEqual(result, { a: "1a", b: "2b", c: "3c" })
})

it("omits properties for which the mapping function returns undefined", () => {
const object = { a: 1, b: 2, c: 3 }
const result = mapObject(object, (value, key) => (key === "b" ? undefined : value + key))
assert.deepEqual(result, { a: "1a", c: "3c" })
})
})
21 changes: 21 additions & 0 deletions src/helpers/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Maps all own properties of an object to a new object. Returning `undefined`
* from the mapping function will omit the property from the result.
*/
export const mapObject = <T extends object, U>(
object: T,
map: (value: T[keyof T], key: keyof T) => U | undefined
): { [key in keyof T]: Exclude<U, undefined> } => {
const result: { [key in keyof T]: Exclude<U, undefined> } = {} as never

for (const key in object) {
if (Object.hasOwn(object, key)) {
const newValue = map(object[key], key)
if (newValue !== undefined) {
result[key] = newValue as Exclude<U, undefined>
}
}
}

return result
}
13 changes: 13 additions & 0 deletions src/helpers/typeSafety.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { assertExhaustive } from "./typeSafety.js"

describe("assertExhaustive", () => {
it("should throw an error with the message 'The switch is not exhaustive.'", () => {
assert.throws(
// @ts-expect-error The function should never receive a value.
() => assertExhaustive(""),
err => err instanceof Error && err.message === "The switch is not exhaustive."
)
})
})
14 changes: 11 additions & 3 deletions src/helpers/typeSafety.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
/**
* This function is used to make sure that the `switch` is exhaustive. Place it
* in the `default` case of the `switch`.
* @param x The value that is used in the `switch`.
* @param _x - The value that is used in the `switch`.
* @example
* const aorb = (x: "a" | "b") => {
* switch (x) {
* case "a": return 1
* case "b": return 2
* default: return assertExhaustive(x)
* }
* }
*/
export function assertExhaustive(x: never): never {
throw new Error("The switch is not exhaustive.");
export function assertExhaustive(_x: never): never {
throw new Error("The switch is not exhaustive.")
}
Loading

0 comments on commit 3fdcaf2

Please sign in to comment.