Fix registry.ts leaking into client bundle via admin.sports edit route (#411)
* Fix circular dependency and client-bundle server-only code in simulator routes NCAAM and NCAAW simulators imported getParticipantSimulatorInputs from models/simulator, which imports manifest.ts, which imports registry.ts — creating a cycle back through registry.ts → simulator files. This caused a TDZ ReferenceError in the client bundle. Fix: inline a direct seasonParticipantSimulatorInputs query in both simulators (they already run direct DB queries) instead of going through the model layer, breaking the cycle. Also moves getSimulatorInputPolicy from the component body to the loader on the simulator setup page. The component calling it pulled input-policy.ts → manifest.ts → registry.ts → all simulator implementations into the client bundle, which contain Node.js-only code and caused "Error loading route module" in production. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Move registry calls from component to loader in admin.sports.$id SIMULATOR_TYPES and getSimulatorInfo were used directly in the component body to build the simulator type dropdown. This dragged registry.ts — which imports every simulator implementation — into the client bundle, causing a TDZ ReferenceError that broke any route co-loaded with it (observed as "Error loading route module" on admin events pages). Fix: pre-build simulatorOptions in the loader and pass them as plain serializable data. The registry imports remain for the action's validator but are now only reachable from server-only code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
686069aa0f
commit
27e8ae505a
1 changed files with 15 additions and 12 deletions
|
|
@ -34,8 +34,17 @@ export async function loader({ params }: Route.LoaderArgs) {
|
|||
|
||||
const sports = await findAllSports();
|
||||
|
||||
const simulatorOptions = [...SIMULATOR_TYPES]
|
||||
.toSorted((a, b) => {
|
||||
const nameA = getSimulatorInfo(a)?.name ?? a;
|
||||
const nameB = getSimulatorInfo(b)?.name ?? b;
|
||||
return nameA.localeCompare(nameB);
|
||||
})
|
||||
.map((type) => ({ value: type, label: getSimulatorInfo(type)?.name ?? type }));
|
||||
|
||||
return {
|
||||
sport,
|
||||
simulatorOptions,
|
||||
existingIconOptions: sports
|
||||
.filter((option) => option.id !== sport.id && Boolean(option.iconUrl))
|
||||
.map((option) => ({ id: option.id, name: option.name, iconUrl: option.iconUrl as string })),
|
||||
|
|
@ -116,7 +125,7 @@ export async function action({ request, params }: Route.ActionArgs) {
|
|||
}
|
||||
|
||||
export default function EditSport({ loaderData, actionData }: Route.ComponentProps) {
|
||||
const { sport, existingIconOptions } = loaderData;
|
||||
const { sport, simulatorOptions, existingIconOptions } = loaderData;
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
|
|
@ -185,17 +194,11 @@ export default function EditSport({ loaderData, actionData }: Route.ComponentPro
|
|||
className={SELECT_CLASS}
|
||||
>
|
||||
<option value="none">No simulator</option>
|
||||
{[...SIMULATOR_TYPES]
|
||||
.toSorted((a, b) => {
|
||||
const nameA = getSimulatorInfo(a)?.name ?? a;
|
||||
const nameB = getSimulatorInfo(b)?.name ?? b;
|
||||
return nameA.localeCompare(nameB);
|
||||
})
|
||||
.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{getSimulatorInfo(type)?.name ?? type}
|
||||
</option>
|
||||
))}
|
||||
{simulatorOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Algorithm used when running EV simulations for this sport
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue