feat: cache capability
This adds the option to build a cache from the database so that it doesn’t have to be re-calculated every time. The first provided cache is a collections of new applications and uses for skills.
This commit is contained in:
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
import type { CacheConfig } from "../cacheConfig.js"
|
||||
import type { SkillApplication, SkillUse } from "../types/_Activatable.js"
|
||||
import type { ActivatableIdentifier } from "../types/_IdentifierGroup.js"
|
||||
|
||||
export type NewApplication = {
|
||||
source: ActivatableIdentifier
|
||||
data: SkillApplication
|
||||
requiredSkillRating?: number
|
||||
}
|
||||
|
||||
export type Use = {
|
||||
source: ActivatableIdentifier
|
||||
data: SkillUse
|
||||
}
|
||||
|
||||
export type NewApplicationsAndUsesCache = {
|
||||
newApplications: Record<number, NewApplication[]>
|
||||
uses: Record<number, Use[]>
|
||||
}
|
||||
|
||||
export const config: CacheConfig<NewApplicationsAndUsesCache> = {
|
||||
builder(database) {
|
||||
type WithApplicationsAndUses = {
|
||||
skill_applications?: SkillApplication[]
|
||||
skill_uses?: SkillUse[]
|
||||
}
|
||||
|
||||
const cache: NewApplicationsAndUsesCache = {
|
||||
newApplications: {},
|
||||
uses: {},
|
||||
}
|
||||
|
||||
const addNewApplicationsAndUses = (
|
||||
activatable: WithApplicationsAndUses,
|
||||
activatableId: ActivatableIdentifier,
|
||||
) => {
|
||||
if (activatable.skill_applications) {
|
||||
for (const newApplication of activatable.skill_applications) {
|
||||
const pair: NewApplication = { source: activatableId, data: newApplication }
|
||||
if (newApplication.skill.tag === "Single") {
|
||||
const previous = (cache.newApplications[newApplication.skill.single.id.skill] ??= [])
|
||||
previous.push(pair)
|
||||
} else {
|
||||
for (const skill of newApplication.skill.multiple.list) {
|
||||
const previous = (cache.newApplications[skill.id.skill] ??= [])
|
||||
previous.push(pair)
|
||||
}
|
||||
if (newApplication.skill.multiple.required_skill_rating !== undefined) {
|
||||
pair.requiredSkillRating = newApplication.skill.multiple.required_skill_rating
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activatable.skill_uses) {
|
||||
for (const use of activatable.skill_uses) {
|
||||
const pair: Use = { source: activatableId, data: use }
|
||||
if (use.skill.tag === "Single") {
|
||||
const previous = (cache.uses[use.skill.single.id.skill] ??= [])
|
||||
previous.push(pair)
|
||||
} else {
|
||||
for (const skill of use.skill.multiple.list) {
|
||||
const previous = (cache.uses[skill.id.skill] ??= [])
|
||||
previous.push(pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const entries: [
|
||||
[id: number, data: WithApplicationsAndUses][],
|
||||
(numericId: number) => ActivatableIdentifier,
|
||||
][] = [
|
||||
[database.advancedCombatSpecialAbilities, n => ({ tag: "AdvancedCombatSpecialAbility", advanced_combat_special_ability: n })],
|
||||
[database.advancedKarmaSpecialAbilities, n => ({ tag: "AdvancedKarmaSpecialAbility", advanced_karma_special_ability: n })],
|
||||
[database.advancedMagicalSpecialAbilities, n => ({ tag: "AdvancedMagicalSpecialAbility", advanced_magical_special_ability: n })],
|
||||
[database.advancedSkillSpecialAbilities, n => ({ tag: "AdvancedSkillSpecialAbility", advanced_skill_special_ability: n })],
|
||||
[database.advantages, n => ({ tag: "Advantage", advantage: n })],
|
||||
[database.blessedTraditions, n => ({ tag: "BlessedTradition", blessed_tradition: n })],
|
||||
[database.ceremonialItemSpecialAbilities, n => ({ tag: "CeremonialItemSpecialAbility", ceremonial_item_special_ability: n })],
|
||||
[database.combatSpecialAbilities, n => ({ tag: "CombatSpecialAbility", combat_special_ability: n })],
|
||||
[database.combatStyleSpecialAbilities, n => ({ tag: "CombatStyleSpecialAbility", combat_style_special_ability: n })],
|
||||
[database.fatePointSpecialAbilities, n => ({ tag: "FatePointSpecialAbility", fate_point_special_ability: n })],
|
||||
[database.generalSpecialAbilities, n => ({ tag: "GeneralSpecialAbility", general_special_ability: n })],
|
||||
[database.liturgicalStyleSpecialAbilities, n => ({ tag: "LiturgicalStyleSpecialAbility", liturgical_style_special_ability: n })],
|
||||
[database.magicStyleSpecialAbilities, n => ({ tag: "MagicStyleSpecialAbility", magic_style_special_ability: n })],
|
||||
[database.magicalSpecialAbilities, n => ({ tag: "MagicalSpecialAbility", magical_special_ability: n })],
|
||||
[database.magicalTraditions, n => ({ tag: "MagicalTradition", magical_tradition: n })],
|
||||
[database.sexSpecialAbilities, n => ({ tag: "SexSpecialAbility", sex_special_ability: n })],
|
||||
]
|
||||
|
||||
entries.forEach(([entityEntries, createId]) => {
|
||||
entityEntries.forEach(([numericId, activatable]) => {
|
||||
addNewApplicationsAndUses(activatable, createId(numericId))
|
||||
})
|
||||
})
|
||||
|
||||
return cache
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ValidResults } from "./main.js"
|
||||
|
||||
export type CacheConfig<T> = {
|
||||
builder: (data: ValidResults) => T
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as NewApplicationsAndUses from "../cache/newApplicationsAndUses.js"
|
||||
|
||||
export type CacheMap = {
|
||||
newApplicationsAndUses: NewApplicationsAndUses.NewApplicationsAndUsesCache
|
||||
}
|
||||
|
||||
export const cacheMap = {
|
||||
newApplicationsAndUses: NewApplicationsAndUses.config
|
||||
}
|
||||
+50
@@ -1,3 +1,7 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
||||
import { dirname } from "node:path"
|
||||
import { CacheConfig } from "./cacheConfig.js"
|
||||
import { CacheMap, cacheMap } from "./config/cache.js"
|
||||
import { TypeId, TypeMap } from "./config/types.js"
|
||||
import "./helpers/array.js"
|
||||
import { mapSecond } from "./helpers/pair.js"
|
||||
@@ -134,3 +138,49 @@ export const getRawResults = async (
|
||||
const rawResultMap = await getRawValidationResults(entityDirPaths, options)
|
||||
return Object.fromEntries(rawResultMap) as unknown as RawResults
|
||||
}
|
||||
|
||||
/**
|
||||
* A dictionary of cache types and their associated location.
|
||||
*/
|
||||
export type CachePaths = {
|
||||
[K in keyof CacheMap]: string
|
||||
}
|
||||
|
||||
export type CacheOptions = {
|
||||
/**
|
||||
* Whether to pretty-print the JSON. Default is `false`.
|
||||
*/
|
||||
pretty?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cache and writes it to the specified paths in JSON format.
|
||||
* @param cachePaths The absolute paths to write each cache to.
|
||||
* @param validResults The data to build the cache from. Usually the result of
|
||||
* `getAllValidData`.
|
||||
* @param options Configuration options for building the cache.
|
||||
*/
|
||||
export const buildCache = async (cachePaths: CachePaths, validResults: ValidResults, options: CacheOptions = {}): Promise<void> => {
|
||||
const { pretty = false } = options
|
||||
for (const [cacheName, cachePath] of Object.entries(cachePaths)) {
|
||||
const cacheConfig: CacheConfig<unknown> = cacheMap[cacheName as keyof CacheMap]
|
||||
const cacheData = cacheConfig.builder(validResults)
|
||||
await mkdir(dirname(cachePath), { recursive: true })
|
||||
await writeFile(cachePath, JSON.stringify(cacheData, null, pretty ? 2 : undefined), "utf-8")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cache from the specified paths in JSON format.
|
||||
* @param cachePaths The absolute paths to read each cache from.
|
||||
* @returns
|
||||
*/
|
||||
export const getCache = async (cachePaths: CachePaths): Promise<CacheMap> => {
|
||||
const cache: Partial<CacheMap> = {}
|
||||
for (const [cacheName, cachePath] of Object.entries(cachePaths)) {
|
||||
const cacheConfig: CacheConfig<unknown> = cacheMap[cacheName as keyof CacheMap]
|
||||
const cacheData = JSON.parse(await readFile(cachePath, "utf-8"))
|
||||
cache[cacheName as keyof CacheMap] = cacheData
|
||||
}
|
||||
return cache as CacheMap
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user