115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import type { Database } from '@tursodatabase/database';
|
|
import {
|
|
loadSelectedMechanicRuntimesForOwnerReferences,
|
|
mechanicRuntimeOwnerReferenceKey,
|
|
type MechanicRuntimeSelection,
|
|
} from './mechanic-runtime-selection';
|
|
|
|
export type BuildMechanicRuntimeOwnerIds = {
|
|
talentIds: readonly string[];
|
|
protocolTalentIds: readonly string[];
|
|
skillChipTalentIds: readonly string[];
|
|
specializationAttachmentIds: readonly string[];
|
|
};
|
|
|
|
export type BuildMechanicRuntimes = {
|
|
talents: Record<string, MechanicRuntimeSelection | null>;
|
|
protocolTalents: Record<string, MechanicRuntimeSelection | null>;
|
|
skillChipTalents: Record<string, MechanicRuntimeSelection | null>;
|
|
specializationAttachments: Record<string, MechanicRuntimeSelection | null>;
|
|
};
|
|
|
|
function uniqueNonEmptyIds(ids: readonly string[]) {
|
|
return [...new Set(ids.filter((id) => id.trim().length > 0))];
|
|
}
|
|
|
|
function emptyRuntimeMap(ids: readonly string[]) {
|
|
return Object.fromEntries(ids.map((id) => [id, null])) as Record<
|
|
string,
|
|
MechanicRuntimeSelection | null
|
|
>;
|
|
}
|
|
|
|
/**
|
|
* Hydrates the only executable capability exposed to Build surfaces. One
|
|
* bounded bulk read resolves source metadata, selected revisions, graph rows,
|
|
* and active attributes. Every owner still receives an independent null when
|
|
* its source cannot be validated.
|
|
*/
|
|
export async function loadBuildMechanicRuntimes(
|
|
database: Database,
|
|
input: BuildMechanicRuntimeOwnerIds,
|
|
): Promise<BuildMechanicRuntimes> {
|
|
const talentIds = uniqueNonEmptyIds(input.talentIds);
|
|
const protocolTalentIds = uniqueNonEmptyIds(input.protocolTalentIds);
|
|
const skillChipTalentIds = uniqueNonEmptyIds(input.skillChipTalentIds);
|
|
const specializationAttachmentIds = uniqueNonEmptyIds(
|
|
input.specializationAttachmentIds,
|
|
);
|
|
const runtimes: BuildMechanicRuntimes = {
|
|
talents: emptyRuntimeMap(talentIds),
|
|
protocolTalents: emptyRuntimeMap(protocolTalentIds),
|
|
skillChipTalents: emptyRuntimeMap(skillChipTalentIds),
|
|
specializationAttachments: emptyRuntimeMap(specializationAttachmentIds),
|
|
};
|
|
const owners = [
|
|
...talentIds.map((id) => ({ type: 'talent' as const, id })),
|
|
...protocolTalentIds.map((id) => ({
|
|
type: 'os_protocol_talent' as const,
|
|
id,
|
|
})),
|
|
...skillChipTalentIds.map((id) => ({
|
|
type: 'skill_chip_talent' as const,
|
|
id,
|
|
})),
|
|
...specializationAttachmentIds.map((id) => ({
|
|
type: 'specialization_tree_rank' as const,
|
|
id,
|
|
})),
|
|
];
|
|
if (!owners.length) return runtimes;
|
|
|
|
try {
|
|
const selections = await loadSelectedMechanicRuntimesForOwnerReferences(
|
|
database,
|
|
owners,
|
|
);
|
|
for (const id of talentIds) {
|
|
runtimes.talents[id] =
|
|
selections.get(mechanicRuntimeOwnerReferenceKey({ type: 'talent', id })) ??
|
|
null;
|
|
}
|
|
for (const id of protocolTalentIds) {
|
|
runtimes.protocolTalents[id] =
|
|
selections.get(
|
|
mechanicRuntimeOwnerReferenceKey({
|
|
type: 'os_protocol_talent',
|
|
id,
|
|
}),
|
|
) ?? null;
|
|
}
|
|
for (const id of skillChipTalentIds) {
|
|
runtimes.skillChipTalents[id] =
|
|
selections.get(
|
|
mechanicRuntimeOwnerReferenceKey({
|
|
type: 'skill_chip_talent',
|
|
id,
|
|
}),
|
|
) ?? null;
|
|
}
|
|
for (const id of specializationAttachmentIds) {
|
|
runtimes.specializationAttachments[id] =
|
|
selections.get(
|
|
mechanicRuntimeOwnerReferenceKey({
|
|
type: 'specialization_tree_rank',
|
|
id,
|
|
}),
|
|
) ?? null;
|
|
}
|
|
} catch {
|
|
// An unavailable metadata read has no executable fallback at this boundary.
|
|
}
|
|
|
|
return runtimes;
|
|
}
|