Migration Guide: character-creator-studio → @franksauvag/rpg-commons
This guide covers migrating code from character-creator-studio (CCS) to the shared @franksauvag/rpg-commons library.
2.0.0 (lib + backend — before CCS lot L4–L8)
| Change | Action |
|---|---|
contracts.ts removed | Import types from @franksauvag/rpg-commons/srd (same names: ClassDef, DndSpell, …) |
| Runtime | Use createSrdRuntime({ apiBaseUrl, defaultLocale? }) for bundles, loadTypeEntries, meta, translation-stats |
| Character | Import DndCharacter from @franksauvag/rpg-commons/character (CCS: replace @/editor/types/character) |
| Indices | AbilityIndex, AlignmentIndex from @franksauvag/rpg-commons/srd |
| UI labels abilities/alignments | Backend GET /api/locales/{locale}/ui/srd + i18next keys ability.str.name, etc. |
Do not import from @franksauvag/rpg-commons/srd/transform/* — not part of the public API.
Overview of Changes
| Area | Before (CCS) | After (lib) |
|---|---|---|
| UI component imports | @/components/ui/button | @franksauvag/rpg-commons |
| SRD data imports | @/data/srd/... | @franksauvag/rpg-commons/srd |
| API base URL | Read from VITE_API_BASE_URL automatically | Injected via createSrdClient({ apiBaseUrl }) |
| Media resolution | resolveEntryMediaSrc(key) (reads env internally) | resolveEntryMediaSrc(key, apiBaseUrl) |
| Image prop | mediaKey (SRD key, resolved internally) | imageUrl (pre-resolved absolute URL) |
| Shared RPG components | @/components/shared/... | @franksauvag/rpg-commons |
| Type contracts | @/data/srd/contracts (local) | @franksauvag/rpg-commons/srd |
1. UI Components
Before
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";After
import { Button } from "@franksauvag/rpg-commons";
import { Card, CardHeader, CardTitle } from "@franksauvag/rpg-commons";
import { Badge } from "@franksauvag/rpg-commons";
import { Input } from "@franksauvag/rpg-commons";
// Or combined:
import { Button, Card, CardHeader, CardTitle, Badge, Input } from "@franksauvag/rpg-commons";All 45 shadcn/ui components are exported from the main entry point.
Add stylesheet to app entry
// In your main.tsx / main.ts
import "@franksauvag/rpg-commons/style";2. SRD Data Client
The biggest change: the lib no longer reads VITE_API_BASE_URL from import.meta.env. The consuming app is responsible for providing apiBaseUrl when creating the client.
Before (CCS)
// src/data/srd/http/srdApiClient.ts reads import.meta.env.VITE_API_BASE_URL implicitly
import { fetchSrdDataset, loadSrdDatasetMapped } from "@/data/srd/http/srdApiClient";
import { buildClassesBundle } from "@/data/srd/http/bundleBuilders";
const bundle = await buildClassesBundle();
const fighter = bundle.getClass("fighter");After (lib)
import { createSrdClient } from "@franksauvag/rpg-commons/srd";
// App provides apiBaseUrl explicitly (from env, config, or context)
const srd = createSrdClient({
apiBaseUrl: import.meta.env.VITE_API_BASE_URL ?? "",
});
const bundle = await srd.buildClassesBundle();
const fighter = bundle.getClass("fighter");Recommended pattern: singleton via module
// src/lib/srdClient.ts
import { createSrdClient } from "@franksauvag/rpg-commons/srd";
export const srdClient = createSrdClient({
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
});With TanStack Query
// src/hooks/useSrdBundles.ts
import { useQuery } from "@tanstack/react-query";
import { srdQueryKeys, SRD_QUERY_STALE_MS } from "@franksauvag/rpg-commons/srd";
import { srdClient } from "@/lib/srdClient";
export function useClassesBundle() {
return useQuery({
queryKey: [...srdQueryKeys.all, "classes-bundle"],
queryFn: () => srdClient.buildClassesBundle(),
staleTime: SRD_QUERY_STALE_MS,
});
}3. Media Resolution
Before (CCS)
import { resolveSrdMediaUrl, resolveEntryMediaSrc } from "@/data/srd/http/resolveSrdMediaUrl";
// Env read internally
const url = resolveSrdMediaUrl("srd/classes/fighter.png");
const src = resolveEntryMediaSrc(entry.image);After (lib)
import { resolveSrdMediaUrl, resolveEntryMediaSrc } from "@franksauvag/rpg-commons/srd";
const API_BASE = import.meta.env.VITE_API_BASE_URL;
// apiBaseUrl is now explicit
const url = resolveSrdMediaUrl("srd/classes/fighter.png", API_BASE);
const src = resolveEntryMediaSrc(entry.image, API_BASE);Behaviour change: when src is a non-legacy /images/... path (e.g. /images/placeholder.svg), the lib returns it unchanged. CCS called publicAssetPath() which prepended BASE_PATH. In the lib, /images/... paths are returned as-is — the consuming app handles its own public asset paths.
4. SrdImageOrInitials — API Change
The mediaKey prop has been replaced with imageUrl. The consuming app pre-resolves the URL using resolveSrdMediaUrl.
Before (CCS)
import { SrdImageOrInitials } from "@/components/shared/SrdImageOrInitials";
// Component resolved the key to URL internally
<SrdImageOrInitials mediaKey={classDef.image} label={classDef.name} />;After (lib)
import { SrdImageOrInitials } from "@franksauvag/rpg-commons";
import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd";
const imageUrl = resolveSrdMediaUrl(classDef.image, import.meta.env.VITE_API_BASE_URL);
<SrdImageOrInitials imageUrl={imageUrl} label={classDef.name} />;5. SelectionCard — API Change
Same principle: imageUrl replaces any internal media resolution.
Before (CCS)
<SelectionCard title="Fighter" mediaKey={classDef.image} />After (lib)
import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd";
const imageUrl = resolveSrdMediaUrl(classDef.image, import.meta.env.VITE_API_BASE_URL);
<SelectionCard title="Fighter" imageUrl={imageUrl} />;6. TypeScript Contracts
Before (CCS)
import type { ClassDef, SpeciesDef, DndSpell } from "@/data/srd/contracts";
import type { ClassesBundle, SpellsBundle } from "@/data/srd/contracts";After (lib)
import type {
ClassDef,
SpeciesDef,
DndSpell,
ClassesBundle,
SpellsBundle,
} from "@franksauvag/rpg-commons/srd";All types (ClassDef, SpeciesDef, BackgroundDef, EquipmentItem, FeatDef, DndSpell, bundle interfaces, ClassSpellQuota, CasterPattern) are exported from @franksauvag/rpg-commons/srd.
7. srdDisplayInitials
// Before (CCS — inline utility)
function getInitials(name: string) {
/* custom */
}
// After (lib)
import { srdDisplayInitials } from "@franksauvag/rpg-commons";
srdDisplayInitials("Barbarian"); // → "BA"
srdDisplayInitials("Half-Orc"); // → "HA"
srdDisplayInitials("Dragon"); // → "DR"8. Removed: Vite proxy dev same-origin behaviour
CCS's config.ts automatically switched to same-origin API URLs in dev mode to work with the Vite proxy. This logic is not present in the lib.
The consuming app should configure its own Vite proxy if needed:
// vite.config.ts (in the app, NOT in the lib)
export default defineConfig({
server: {
proxy: {
"/api": {
target: process.env.VITE_API_BASE_URL,
changeOrigin: true,
},
},
},
});Quick Reference
| Old import | New import |
|---|---|
import { Button } from "@/components/ui/button" | import { Button } from "@franksauvag/rpg-commons" |
import { ... } from "@/data/srd/contracts" | import type { ... } from "@franksauvag/rpg-commons/srd" |
import { buildClassesBundle } from "@/data/srd/http/bundleBuilders" | import { createSrdClient } from "@franksauvag/rpg-commons/srd" → client.buildClassesBundle() |
import { resolveSrdMediaUrl } from "@/data/srd/http/resolveSrdMediaUrl" | import { resolveSrdMediaUrl } from "@franksauvag/rpg-commons/srd" — add apiBaseUrl arg |
import { SrdImageOrInitials } from "@/components/shared/SrdImageOrInitials" | import { SrdImageOrInitials } from "@franksauvag/rpg-commons" — mediaKey → imageUrl |
import { srdQueryKeys } from "@/data/srd/queryKeys" | import { srdQueryKeys } from "@franksauvag/rpg-commons/srd" |