Both libraries follow the same pattern. A row with studioId = null is global and visible to every studio. A row with a studioId is that studio’s own. A studio never edits a global row. Its edits go to an override row keyed by studio and item, and reads merge the override over the shared row.

ExerciseLibraryItem

Table exercise_library_items. Indexes: studioId, (studioId, createdAt), trigram GIN on searchText and name, array GIN on targetMuscles, secondaryMuscles, bodyParts, exerciseTypes. Relations: videoOverrides (ExerciseStudioOverride), sourceMaps (ExerciseSourceMap). Catalog imports are scripts under packages/database/scripts/exercise-db-v2 and friends, run through the db:import:exercises-* commands in the root package.json.

ExerciseStudioOverride

Table exercise_video_overrides. A studio’s private edits to a library exercise. The physical table keeps its original name. Renaming it would make prisma db push a drop and create, and the deploy does not pass --accept-data-loss. Unique on (studioId, exerciseId). Indexes on each. Every scalar field is nullable and null means “inherit from the shared row”. Because the override keeps the same exerciseId, a rename applies to programs that were already assigned.

ExerciseSourceMap

Table exercise_source_maps. Resolves an external exercise to a global catalog row, so an import references the catalog and does not mint a studio-scoped copy. Unique on (source, sourceKey). The same exercise can be reachable through several keys. This table is global and has no studioId.

FoodLibraryItem

Table food_library_items. Indexes: studioId, (studioId, name), trigram GIN on name.

servingTiers

Validated by servingTierBody in foods/foods.schema.ts. The TypeScript type is ServingTier in foods/mbp.ts:
servingUnit is gram, ml or unit. householdMeasure is optional and one of cup, tsp, tbsp.

FoodLibraryOverride

Table food_library_overrides. A studio’s edits to a global food. System foods are editable per studio but cannot be deleted. It repeats the editable columns of FoodLibraryItem: name, brand, category, categories, notes, betterAlternative, includeInShopping, showAsAlternative, servingSize, servingUnit, calories, protein, carbs, fat, macroType, servingTiers, englishName, the unit and household columns, isPreferred, and the three exchange quantities. Unlike the exercise override, name and the four macro columns are required, so a food override is a full replacement of those values. Unique on (studioId, foodId). Indexes on each. Both relations cascade.
Any query that lists or resolves foods for a studio must apply the override. Reads that go straight to food_library_items show the shared values and ignore the studio’s edits.

FoodBarcodeProduct

Table food_barcode_products. A cache of Open Food Facts products, keyed by barcode. Global, no tenant. One scan anywhere fills it for every studio. Rows refresh lazily after BARCODE_CACHE_TTL_MS, 30 days, in trainee.service.ts.

ShoppingListItem

Table shopping_list_items. The trainee’s shopping checklist. Unique on (clientId, foodId).