Agriculture, home gardening, and environmental intelligence domain. Named after the Greek goddess of the harvest, agriculture, and the seasons.
This document is the authoritative technical reference for the Demeter domain.
It covers the data models and database schema defined in @demeter/core, the
public API surface of each of the 14 libraries, the Fastify REST API endpoints
and configuration, and the web and mobile application structures.
Demeter is fully implemented. The 14 libraries under libs/demeter/ form the
domain logic layer — they are pure TypeScript modules with no runtime framework
dependencies beyond @demeter/core. The three applications under
apps/demeter/ consume those libraries, with @demeter/core as the shared
schema and type contract between them. The REST API (@demeter/api) is the
primary integration point for external consumers.
1. Domain Overview#
The table below provides a quick reference for the domain's scope and technology choices.
| Property | Value |
|---|---|
| Domain name | demeter |
| Scope | Home gardening, urban agriculture, controlled-environment growing |
| Library count | 14 (libs/demeter/*) |
| Application count | 3 (apps/demeter/api, apps/demeter/web, apps/demeter/mobile) |
| Database | PostgreSQL via Drizzle ORM (schema defined in @demeter/core) |
| Validation | Zod — 30 schemas in @demeter/core |
| Language | TypeScript (ESM) |
| API framework | Fastify (@demeter/api) |
| Web client | React + Vite + React Router + TanStack Query + Zustand |
| Mobile client | React Native (Expo SDK 52) + React Navigation |
2. Library Inventory#
All 14 libraries live under libs/demeter/, are private ESM packages at version
0.1.0, build with the @nx/js:tsc executor, and test with Vitest. Each
feature library declares a peerDependency on @demeter/core; @demeter/core
itself depends on drizzle-orm and zod.
The table below maps each package to its TypeScript source modules (these are
the files that actually export the library's functionality) and its high-level
responsibility. The src/index.ts re-export barrel is excluded from the module
list since it adds no new logic.
| Package | Source modules (src/*.ts, excluding index.ts) |
Responsibility |
|---|---|---|
@demeter/core |
schemas, db-schema, db-seed |
Zod schemas, Drizzle ORM tables and relations, seed data |
@demeter/plants |
species, companion, pests, climate, calendar, search, database |
Plant encyclopedia, companion planting, pest/disease data, climate zones |
@demeter/weather |
data-sources, current-conditions, forecasting, alerts, historical, growing-degree-days, garden-weather |
Weather adapters, forecasting, GDD, garden alerts |
@demeter/sensors |
devices, protocols, readings, timeseries, calibration, integrations |
IoT device lifecycle, protocol adapters, timeseries analytics, calibration |
@demeter/automation |
rules, irrigation, climate-control, alerts, scenes, history |
Rules engine, irrigation, greenhouse climate, alerts, scenes, history |
@demeter/hydroponics |
systems, nutrients, reservoir, aquaponics, indoor-growing, microgreens, mushrooms |
Hydroponics, aquaponics, indoor growing, microgreens, mushrooms |
@demeter/inventory |
seeds, suppliers, seed-saving, plant-inventory, supplies |
Seed lots, suppliers, seed saving, plant inventory, supplies/tools |
@demeter/planner |
layout, placement, sun-analysis, rotation, succession, templates, export |
Layout engine, plant placement, sun analysis, rotation, succession, export |
@demeter/journal |
observations, photos, growth-tracking, harvest-log, search, insights |
Observations, photos, growth tracking, harvest logging, insights |
@demeter/preservation |
harvest-planning, canning, freezing, dehydrating, fermentation, storage, recipes |
Harvest planning, canning, freezing, dehydrating, fermentation, storage, recipes |
@demeter/tasks |
scheduling, generation, prioritization, calendar-integration, notifications, analytics |
Task scheduling, auto-generation, prioritization, iCal, notifications |
@demeter/community |
profiles, exchange, groups, experts, content, local-resources |
Profiles, seed exchange, groups, expert network, content sharing, directory |
@demeter/analytics |
yield-analytics, cost-analysis, resource-analytics, environmental-impact, time-analytics, success-metrics, dashboard |
Yield, cost, resource, environmental, time, success metrics, dashboards |
@demeter/intelligence |
plant-identification, disease-detection, pest-identification, growth-prediction, recommendations, anomaly-detection, nlp-interface, model-management |
Plant ID, disease/pest detection, growth prediction, recommendations, NLP |
@demeter/planner is the only library with a second peer dependency: it peers
on both @demeter/core and @demeter/plants. All other feature libraries peer
only on @demeter/core.
3. Data Models — @demeter/core (schemas.ts)#
@demeter/core/schemas.ts is the authoritative data contract for the entire
domain. It defines 30 Zod schemas organized into three tiers: enumerations
(closed value sets), value objects (composite but non-entity types), and
entities (the top-level domain objects that map to database rows). Every schema
carries .describe() metadata, and a TypeScript type is inferred from each
schema with z.infer. Schemas use shared field helpers: id / userId
(z.string().uuid()), datetime (z.string().datetime()), and dateOnly
(YYYY-MM-DD regex).
The three tiers are described below. Enumerations are listed concisely as a table; value objects and entities include per-field details because their validation constraints are significant for implementors.
3.1 Enumerations (10)#
| Schema | Values |
|---|---|
PlantTypeSchema |
vegetable, fruit, herb, flower, tree, shrub, vine, grass, succulent, fern |
PlantLifecycleSchema |
annual, biennial, perennial |
GrowthStageSchema |
seed, germination, seedling, vegetative, flowering, fruiting, harvest, dormant |
SoilTypeSchema |
clay, sandy, loamy, silty, peaty, chalky |
TaskTypeSchema |
water, fertilize, prune, harvest, weed, pest_control, plant, transplant, mulch, compost |
SensorTypeSchema |
moisture, temperature, humidity, light, ph, ec, flow, wind, rain |
IrrigationTypeSchema |
drip, sprinkler, soaker, manual, flood, mist |
GardenTypeSchema |
in_ground, raised_bed, container, vertical, greenhouse, indoor, hydroponic, aquaponic |
PestTypeSchema |
insect, mammal, bird, fungal, bacterial, viral, nematode |
TreatmentTypeSchema |
organic, chemical, biological, mechanical, cultural, integrated |
3.2 Value Objects (10)#
ClimateZoneSchema — USDA hardiness zone and climate characteristics.
| Field | Type / constraint | Required |
| ---------------- | --------------------------- | ------------ | --- |
| usdaZone | string, regex ^(1[0-3][ab] | [1-9][ab])$ | yes |
| koppenCode | string, length 2–4 | yes |
| minTempF | number, −80 to 80 | yes |
| maxTempF | number, 30 to 130 | yes |
| frostFreeDays | integer, 0 to 365 | yes |
| lastFrostDate | dateOnly (YYYY-MM-DD) | yes |
| firstFrostDate | dateOnly | yes |
GeoLocationSchema — latitude (−90..90), longitude (−180..180),
elevation (meters, −500..9000), aspect (enum N|S|E|W|NE|NW|SE|SW).
SoilCompositionSchema — type (SoilTypeSchema), ph (0..14),
nitrogenPpm (0..500), phosphorusPpm (0..500), potassiumPpm (0..1000),
organicMatterPercent (0..100), cec (Cation Exchange Capacity, 0..100
meq/100g), texture (string, 1..100).
SunExposureSchema — category
(full_sun|partial_sun|partial_shade|full_shade), dailyHours (0..24),
morningHours (0..12), afternoonHours (0..12).
WaterRequirementSchema — frequencyDays (1..90), amountLiters
(0.01..1000), method (IrrigationTypeSchema), seasonalAdjustments (object
of spring|summer|fall|winter multipliers, each 0..5).
TemperatureRangeSchema — minCelsius, maxCelsius, optimalMinCelsius,
optimalMaxCelsius, each −50..60.
GrowingConditionsSchema — composite: soil (SoilCompositionSchema),
sun (SunExposureSchema), water (WaterRequirementSchema),
temperatureRange (TemperatureRangeSchema), humidityRange (min/max,
0..100).
PlantSpacingSchema — rowSpacingCm (1..1000), plantSpacingCm (1..1000),
depthCm (0.1..100), thinToSpacingCm (1..1000, optional).
HarvestWindowSchema — startDaysFromPlanting, endDaysFromPlanting,
peakDaysFromPlanting (each integer 1..3650), signsOfReadiness (non-empty
array of strings, each 1..500 chars).
NutrientProfileSchema — fertilizer N-P-K profile: nitrogenRatio,
phosphorusRatio (0..100), potassiumRatio (0..100),
applicationRateGramsPerSqM (0..5000), frequency (string 1..200), notes
(string ≤2000, default '').
3.3 Entities (10)#
GardenSchema
| Field | Type / constraint | Required |
|---|---|---|
id |
UUID | yes |
userId |
UUID | yes |
name |
string, 1–200 | yes |
description |
string, ≤5000 | default '' |
location |
GeoLocationSchema |
yes |
climateZone |
ClimateZoneSchema |
yes |
areaSqMeters |
number, 0.1–100000 | yes |
type |
GardenTypeSchema |
yes |
beds |
array of UUID | default [] |
createdAt / updatedAt |
datetime |
yes |
GardenBedSchema — id, gardenId (UUID), name (1–200), widthCm
(1–10000), lengthCm (1–10000), heightCm (0–500, default 0),
soilComposition (SoilCompositionSchema), sunExposure
(SunExposureSchema), irrigationType (IrrigationTypeSchema), plantings
(array of UUID, default []), notes (≤5000, default ''), createdAt,
updatedAt.
PlantSchema — a plant species/variety in the database. Fields: id,
commonName (1–200), botanicalName (1–300), family, genus, species
(each 1–200), variety (1–200, optional), type (PlantTypeSchema),
lifecycle (PlantLifecycleSchema), growingConditions
(GrowingConditionsSchema), spacing (PlantSpacingSchema),
daysToGermination ({ min, max } integers 1–365), daysToMaturity
({ min, max } integers 1–3650), harvestWindow (HarvestWindowSchema),
companionPlantIds (array of UUID, default []), incompatiblePlantIds (array
of UUID, default []), tags (array of string, default []).
PlantingSchema — a planting instance in a bed. Fields: id, gardenBedId
(UUID), plantId (UUID), userId (UUID), quantity (integer 1–10000),
plantedDate (dateOnly), expectedHarvestDate (dateOnly, optional),
actualHarvestDate (dateOnly, optional), currentStage
(GrowthStageSchema), healthRating (integer 1–10), notes (≤5000, default
''), spacingUsedCm (1–1000), rowNumber (integer 1–100, optional),
createdAt, updatedAt.
HarvestSchema — id, plantingId (UUID), userId (UUID), harvestDate
(dateOnly), quantityKg (0.001–100000), qualityRating (integer 1–5),
notes (≤5000, default ''), usedFor (enum
fresh|preserved|shared|composted), createdAt.
TaskSchema — id, gardenId (UUID), userId (UUID), type
(TaskTypeSchema), title (1–500), description (≤5000, default ''),
dueDate (dateOnly), completedDate (dateOnly, optional), isRecurring
(boolean, default false), recurrenceRule (string ≤200, optional — cron-like),
priority (enum low|medium|high|urgent), plantingIds (array of UUID,
default []), notes (≤5000, default ''), createdAt, updatedAt.
ObservationSchema — id, gardenId (UUID), userId (UUID), plantingId
(UUID, optional), date (dateOnly), title (1–500), notes (1–10000),
photoUrls (array of URL strings ≤2000 chars, default []), metrics (object
with optional temperature −50..60, humidity 0..100, soilMoisture 0..100,
rainfall 0..500; default {}), tags (array of string, default []),
createdAt.
SensorSchema — a physical sensor device. Fields: id, gardenId (UUID),
gardenBedId (UUID, optional), type (SensorTypeSchema), name (1–200),
manufacturer (1–200, optional), model (1–200, optional), serialNumber
(1–200, optional), calibratedAt (datetime, optional), batteryLevel
(integer 0–100, optional), isActive (boolean, default true),
locationDescription (1–500), createdAt, updatedAt.
AlertSchema — id, userId (UUID), gardenId (UUID, optional), type
(enum
frost_warning|pest_alert|water_reminder|harvest_ready|task_due|sensor_alert|weather_alert),
severity (enum info|warning|critical), title (1–500), message (1–5000),
relatedEntityId (UUID, optional), relatedEntityType (string 1–100,
optional), isRead (boolean, default false), readAt (datetime, optional),
createdAt.
UserSchema — a gardener profile. Fields: id, email (email ≤255),
displayName (1–255), location (GeoLocationSchema, optional), climateZone
(ClimateZoneSchema, optional), experienceLevel (enum
beginner|intermediate|advanced|expert), preferredUnits (enum
metric|imperial, default metric), gardenIds (array of UUID, default []),
createdAt, updatedAt.
4. Database Schema — @demeter/core (db-schema.ts)#
The Drizzle ORM schema in db-schema.ts is the concrete relational
representation of the Zod entity models from §3. Every Zod entity in §3.3 maps
to a demeter_-prefixed PostgreSQL table here; composite Zod value objects
(soil composition, sun exposure, climate zone) are stored as jsonb columns
rather than normalized into additional tables, keeping queries simple without
sacrificing type safety.
The schema defines 20 pg enums, 17 tables (all with the demeter_ prefix), and
the relations between them. Convenience arrays ALL_DEMETER_TABLES,
ALL_DEMETER_RELATIONS, and ALL_DEMETER_ENUMS are exported for introspection.
4.1 PostgreSQL Enums (20)#
demeter_plant_type, demeter_plant_lifecycle, demeter_growth_stage,
demeter_soil_type, demeter_task_type, demeter_sensor_type,
demeter_irrigation_type, demeter_garden_type, demeter_pest_type,
demeter_treatment_type, demeter_task_priority, demeter_alert_type,
demeter_alert_severity, demeter_experience_level, demeter_unit_system,
demeter_sun_exposure_category, demeter_harvest_usage, demeter_aspect,
demeter_pest_severity (minor|moderate|severe|critical),
demeter_treatment_effectiveness
(ineffective|slightly_effective|moderately_effective|very_effective|completely_effective).
4.2 Tables (17)#
| # | Table | Key columns and foreign keys |
|---|---|---|
| 1 | demeter_users |
email (unique), display_name, location columns, climate_zone (jsonb), experience_level, preferred_units, soft-delete deleted_at |
| 2 | demeter_gardens |
user_id → users (cascade), name, description, location columns, climate_zone (jsonb), area_sq_meters, type, deleted_at |
| 3 | demeter_garden_beds |
garden_id → gardens (cascade), width_cm, length_cm, height_cm, soil_composition (jsonb), sun_exposure (jsonb), irrigation_type, deleted_at |
| 4 | demeter_plants |
common_name, botanical_name, family, genus, species, variety, type, lifecycle, growing_conditions (jsonb), spacing (jsonb), days_to_germination / days_to_maturity / harvest_window (jsonb), tags (jsonb) |
| 5 | demeter_companion_plants |
plant_id → plants, companion_plant_id → plants (both cascade), is_compatible, reason; unique on the pair |
| 6 | demeter_plantings |
garden_bed_id → beds (cascade), plant_id → plants (restrict), user_id → users (cascade), quantity, planted_date, expected_harvest_date, actual_harvest_date, current_stage, health_rating, spacing_used_cm, row_number, deleted_at |
| 7 | demeter_planting_stages |
planting_id → plantings (cascade), stage, entered_at, exited_at, health_rating_at_entry |
| 8 | demeter_harvests |
planting_id → plantings (cascade), user_id → users (cascade), harvest_date, quantity_kg, quality_rating, used_for |
| 9 | demeter_tasks |
garden_id → gardens (cascade), user_id → users (cascade), type, title, due_date, completed_date, is_recurring, recurrence_rule, priority, planting_ids (jsonb), deleted_at |
| 10 | demeter_observations |
garden_id → gardens (cascade), user_id → users (cascade), planting_id → plantings (set null), date, title, notes, photo_urls (jsonb), metrics (jsonb), tags (jsonb) |
| 11 | demeter_sensors |
garden_id → gardens (cascade), garden_bed_id → beds (set null), type, name, manufacturer, model, serial_number, calibrated_at, battery_level, is_active, location_description, deleted_at |
| 12 | demeter_sensor_readings |
time-series: sensor_id → sensors (cascade), value, unit, recorded_at, metadata (jsonb); composite index on (sensor_id, recorded_at) |
| 13 | demeter_alerts |
user_id → users (cascade), garden_id → gardens (set null), type, severity, title, message, related_entity_id, related_entity_type, is_read, read_at |
| 14 | demeter_pest_reports |
garden_id → gardens (cascade), user_id → users (cascade), planting_id → plantings (set null), pest_type, pest_name, severity, description, photo_urls (jsonb), affected_area, first_observed_date, resolved_date |
| 15 | demeter_treatments |
pest_report_id → pest_reports (cascade), user_id → users (cascade), treatment_type, product_name, description, application_date, reapplication_date, effectiveness |
| 16 | demeter_weather_records |
garden_id → gardens (cascade), date, high/low/avg_temp_celsius, humidity, rainfall_mm, wind_speed_kmh, wind_direction, uv_index, conditions, frost_occurred; composite index on (garden_id, date) |
| 17 | demeter_soil_tests |
garden_bed_id → beds (cascade), user_id → users (cascade), test_date, ph, nitrogen/phosphorus/potassium_ppm, organic_matter_percent, cec, soil_type, texture, lab_name, recommendations |
All tables use uuid primary keys with defaultRandom() and timezone-aware
created_at / updated_at timestamps. Tables with a deleted_at column
support soft deletion. Every foreign key and frequently queried column has an
explicit index.
4.3 Relations#
db-schema.ts defines Drizzle relations() for all 17 tables. Notable
many-to-one and one-to-many links: a user owns many gardens, plantings,
harvests, tasks, observations, alerts, pest reports, treatments, and soil tests;
a garden has many beds, tasks, observations, sensors, alerts, pest reports, and
weather records; a planting has many stages, harvests, observations, and pest
reports; a pest report has many treatments. The demeter_companion_plants
junction uses named relations (sourcePlant, companionPlant) for its
self-referencing link to demeter_plants.
4.4 Seed Data — @demeter/core (db-seed.ts)#
db-seed.ts exports typed seed datasets with deterministic UUIDs:
SEED_USERS— 3 sample users (beginner / intermediate / expert) with USDA zones 7b, 9a, 6a and real coordinates.SEED_GARDENS— 4 gardens (in-ground, container, raised-bed, greenhouse).SEED_GARDEN_BEDS— 8 beds with full soil composition and sun exposure.SEED_PLANTS— 32 garden plants with real botanical data (taxonomy, spacing, germination/maturity ranges, harvest windows, tags).SEED_PLANTINGS— 15 plantings tied to the seed beds and plants.SEED_TASKS— 7 tasks, several with cron recurrence rules.SEED_OBSERVATIONS— 4 observations with environmental metrics.SEED_HARVESTS— 6 harvest records.
5. Library API Surface#
The 14 libraries are pure TypeScript modules with no framework or runtime
dependencies beyond @demeter/core. Each library's public surface falls into
four categories:
- Factory functions — create domain objects (e.g.,
createSystemFromTemplate,createCanningBatch) - Data constants — uppercase
*_DATABASE/*_RECIPES/*_GUIDESexports that ship pre-populated reference data (plant encyclopedias, substrate recipes, USDA altitude tables, etc.) - Pure calculation functions — deterministic computations over domain inputs (GDD accumulation, EC↔TDS conversion, DLI calculation, haversine distance)
- Inferred types — TypeScript types derived from
@demeter/coreZod schemas
The community library additionally exports in-memory store helpers and
reset*Stores() functions for test isolation. The selected exports below cover
the most complex libraries; the remaining libraries (weather, sensors,
automation, journal, tasks, analytics, intelligence) re-export their
entire module surface via export * in their index.ts — their module files
are listed in §2.
@demeter/plants#
- Species —
createPlantSpecies,getSpeciesById,searchSpeciesByName,getSpeciesByType,getSpeciesByFamily. - Companion —
COMPANION_DATABASE;getCompanions,getBeneficialCompanions,getDetrimentalCompanions,scoreCompanionPairing,suggestGuildPlanting,generateThreeSistersLayout,checkBedCompatibility,generateCompanionChart. - Pests —
PEST_DATABASE,DISEASE_DATABASE,BENEFICIAL_INSECTS;identifyPest,recommendTreatments,generateIPMPlan. - Climate —
USDA_ZONES,KOPPEN_CLASSIFICATIONS,AHS_HEAT_ZONES,FROST_DATES_BY_STATE;getUSDAZone,getKoppenClassification,getAHSHeatZone,calculateGrowingSeasonLength,calculateGrowingDegreeDays,calculateChillHours,isPlantHardy,filterPlantsByZone,estimateMicroclimate. - Calendar —
PLANTING_CALENDAR;getPlantingCalendar,getMonthlyTasks,createSeedStartSchedule,createTransplantSchedule,createSuccessionSchedule,createFallPlantingCalendar,calculateMoonPhase,getMoonPlantingAdvice,generatePrintableCalendar. - Search —
searchPlants,fuzzyNameSearch,facetedSearch,findSimilarPlants,suggestForBeginners,suggestProblemSolvers,suggestByHarvestTime,suggestPollinatorPlants,suggestEdibleLandscaping,searchByNutrition. - Database —
VEGETABLE_DATABASE,HERB_DATABASE,FRUIT_DATABASE,FLOWER_DATABASE;getAllPlants,getPlantsByCategory,getPlantCount,validateDatabase.
@demeter/hydroponics#
- Systems — seven
HydroponicSystemTypeconfigs (NFTConfig,DWCConfig,EbbFlowConfig,DripConfig,AeroponicConfig,KratkyConfig,WickConfig);SYSTEM_TEMPLATES,SETUP_GUIDES;createSystemFromTemplate,calculateSetupCost,estimateDailyPowerWatts,estimateMonthlyElectricityCost,validateSystemConfig,getRecommendedCrops,compareSystems. - Nutrients —
EC_RANGES,NUTRIENT_LOCKOUTS,DEFICIENCY_SYMPTOMS,NUTRIENT_BRANDS,PLANT_NUTRIENT_PROFILES;calculateECCorrection,ecToTDS,tdsToEC,calculatePHAdjustment,checkNutrientLockout,diagnoseDeficiency,calculateMixingRecipe. - Reservoir —
getWaterLevelStatus,getTemperatureControlState,generateDosingCommand,calculateEvaporationRate,detectAnomalies,estimateDaysUntilRefill. - Aquaponics —
FISH_SPECIES_DATABASE,FISH_DISEASE_DATABASE;calculateMaxFishCount,calculateFeedingAmount,assessCyclingStatus,calculateFishToPlantRatio,calculateFeedConversionRatio,diagnoseFishDisease,generateBalanceReport. - Indoor growing —
GROW_LIGHT_DATABASE,VPD_RANGES,DLI_TARGETS;calculateVPD,calculateDLI,calculatePPFDForDLI,getPPFDAtDistance,getPhotoperiodSchedule,calculateCO2Supplementation,calculateElectricityCost,generateVPDChart. - Microgreens —
MICROGREEN_VARIETIES;calculateSeedDensity,createTray,harvestTray,generateProductionSchedule,getMostProfitableVarieties. - Mushrooms —
MUSHROOM_SPECIES_DATABASE,CONTAMINATION_DATABASE,SUBSTRATE_RECIPES;calculateBiologicalEfficiency,createMushroomGrow,recordFlush,identifyContamination,calculateSpawnAmount,validateFruitingConditions.
@demeter/planner#
- Layout —
UndoRedoHistoryclass;createCanvas,addElement,removeElement,moveElement,rotateElement,resizeElement,snapToGrid,copyElement,calculateTotalArea,calculateBedArea. - Placement —
createSquareFootGrid,assignPlantToCell,calculatePlantsPerSquareFoot,createRowPlanting,validateSpacing,checkCompanionCompatibility,calculateMatureSizeOverlap,planVerticalSpace. - Sun analysis —
calculateSunPosition,calculateSunrise,calculateSunset,calculateDaylength,calculateShadow,calculateStructureShadows,calculateTreeShadows,calculateDailyLightIntegral,generateSunHeatmap,findOptimalBedPlacement. - Rotation —
ROTATION_GROUPS;generateRotationPlan,validate4YearRotation,suggestNextCrop,trackNitrogenBalance,planCoverCrops,accountForPerennials. - Succession —
calculateSowingIntervals,createSuccessionPlan,calculateSeedQuantity,generateHarvestTimeline,trackActualVsPlanned. - Templates —
TEMPLATE_LIBRARY;instantiateTemplate,createCustomTemplate. - Export —
generatePlantShoppingList,generateSeedOrderList,generateMaterialsList,generatePlantingCalendarFromPlan,generateBedLabels,exportPlanAsJSON,importPlanFromJSON,generateTaskListFromPlan.
@demeter/preservation#
- Canning —
CANNING_RECIPES,WATER_BATH_ALTITUDE_ADJUSTMENTS,PRESSURE_ALTITUDE_DIAL,PRESSURE_ALTITUDE_WEIGHTED;getAdjustedProcessingTime,getAdjustedPressureDial,determineCanningMethod,calculateAcidAddition,createCanningBatch,checkBatchSeals,performSafetyCheck,isWaterBathSafe,getBotulismRisk. - Freezing —
FREEZING_GUIDES,STORAGE_DURATION_DATABASE;createFreezerItem,calculateFreezerSpace,sortByFIFO,generateExpirationAlerts,getFIFOPriority. - Dehydrating —
DEHYDRATING_GUIDES,FRUIT_LEATHER_RECIPES,JERKY_RECIPES,HERB_DRYING_GUIDES;calculateReconstitution,calculateDriedWeight,getShelfLife,assessDryingQuality. - Fermentation —
FERMENTATION_RECIPES,LACTO_MILESTONES,KOMBUCHA_MILESTONES;createFermentBatch,recordPH,calculateSaltForBrine,createKombuchaBrew,assessFermentSafety. - Storage —
STORAGE_CONDITIONS,SPOILAGE_INDICATORS;checkConditionCompliance,generateStorageAlerts,checkStorageCompatibility,findBestLocation. - Harvest planning —
CROP_YIELD_DATABASE;estimateYield,createHarvestForecast,predictPeakHarvest,schedulePreservationSessions,calculateContainerNeeds. - Recipes —
PRESERVATION_RECIPES;matchHarvestToRecipes,parseRecipeFromText,calculateSuccessRate.
6. Application — @demeter/api#
apps/demeter/api is a Fastify REST API that exposes the full Demeter domain
over HTTP. It is the primary integration point for the web and mobile apps and
for IoT devices. Project type application, tags scope:demeter, type:app,
layer:service. Runtime dependencies: @demeter/core, @oshun/errors,
@oshun/logging, fastify, drizzle-orm, pg, ioredis, zod, bcryptjs,
nanoid, and the @fastify/* plugins.
6.1 Server Composition (app.ts)#
Plugin registration order matters in Fastify because later plugins can access
decorations added by earlier ones. buildServer() registers plugins in this
fixed order:
- request-context — attaches per-request context for tracing and logging
- CORS — cross-origin resource sharing headers
- error-handler — normalizes all errors to the standard error envelope
- Swagger/OpenAPI — auto-generated API documentation
- auth (JWT) — JWT verification, decorates requests with user identity
- database (PostgreSQL + Drizzle) — connection pool and ORM instance
- Redis — cache, rate-limit backing store, and session store
- rate-limit — per-IP and per-user request throttling
- routes — all
/v1/*route handlers
The Fastify instance uses Pino logging (pino-pretty in development),
trustProxy: true, and registers SIGINT/SIGTERM handlers for graceful shutdown.
server.ts owns process startup.
6.2 Plugins (src/plugins/)#
request-context, cors, error-handler, swagger, auth, database,
rate-limit, redis. The database plugin creates a pg.Pool
(connectionTimeoutMillis: 5000, idleTimeoutMillis: 30000) and decorates the
instance with fastify.db (Drizzle, configured with the @demeter/core schema)
and fastify.dbPool (raw pool, drained on onClose). The redis plugin
decorates fastify.redis (ioredis) and fastify.cache (a typed CacheHelper
with JSON serialization and TTL).
6.3 Middleware (src/middleware/)#
- RBAC (
rbac.ts) — five roles:owner(4) >admin(3) >member(2) >viewer(1), plusiot_device(0, no hierarchy). 12 permission strings (garden:read|write|delete,plant:read|write,sensor:read|write,task:read|write,harvest:read|write,admin:manage).requireRole(roles)andrequirePermission(permissions)are Fastify preHandler factories;requirePermissionrequires ALL listed permissions. - Ownership (
ownership.ts) —requireOwnership(resourceType)resolves a resource owner from registered in-memory stores (directuserIdfield, or indirect via the owning garden). Resource types:planting,task,harvest,sensor,observation.adminandownerroles bypass the check. - Session (
session.ts) — session-management helpers.
6.4 Authentication (routes/auth.ts)#
Mounted at /v1/auth. Capabilities: email/password registration and login
(bcrypt password hashing), refresh-token rotation with family-based theft
detection, OAuth2 token exchange for Google / Apple / Facebook, magic-link
(passwordless) login, API-key issuance for IoT devices, and session management.
Raw refresh tokens, API keys, and magic-link tokens are never stored — only
hashes.
| Method | Path | Purpose | Auth |
|---|---|---|---|
| POST | /v1/auth/register |
Register a new account | none |
| POST | /v1/auth/login |
Login with email and password | none |
| POST | /v1/auth/logout |
Logout / invalidate session | bearer |
| POST | /v1/auth/refresh |
Refresh access token (token rotation) | refresh tok |
| POST | /v1/auth/oauth/google |
Login with Google | id token |
| POST | /v1/auth/oauth/apple |
Login with Apple | id token |
| POST | /v1/auth/oauth/facebook |
Login with Facebook | access tok |
| POST | /v1/auth/magic-link |
Request a magic link | none |
| POST | /v1/auth/magic-link/verify |
Verify a magic link | magic token |
| POST | /v1/auth/api-keys |
Create an API key | bearer |
| GET | /v1/auth/api-keys |
List API keys | bearer |
| DELETE | /v1/auth/api-keys/:id |
Revoke an API key | bearer |
| GET | /v1/auth/sessions |
List active sessions | bearer |
| DELETE | /v1/auth/sessions/:id |
Revoke a session | bearer |
| DELETE | /v1/auth/sessions |
Revoke all sessions | bearer |
6.5 REST Endpoints#
All routes below require a bearer JWT unless noted. :id parameters are path
parameters. The route files use in-memory stores for development; the database
plugin provides the Drizzle/PostgreSQL backing for production use.
Health (routes/health.ts, no auth, no version prefix)
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Liveness probe |
| GET | /health/ready |
Readiness probe |
| GET | /health/startup |
Startup probe |
Gardens (/v1/gardens)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/gardens |
List gardens (paginated) |
| POST | /v1/gardens |
Create a garden |
| GET | /v1/gardens/:id |
Get garden by ID |
| PUT | /v1/gardens/:id |
Update a garden |
| DELETE | /v1/gardens/:id |
Soft-delete a garden |
| POST | /v1/gardens/:id/beds |
Create a garden bed |
| GET | /v1/gardens/:id/beds |
List garden beds |
| PUT | /v1/gardens/:id/beds/:bedId |
Update a garden bed |
| DELETE | /v1/gardens/:id/beds/:bedId |
Delete a garden bed |
| POST | /v1/gardens/:id/share |
Share a garden |
| DELETE | /v1/gardens/:id/share/:userId |
Revoke garden sharing |
| GET | /v1/gardens/:id/members |
List garden members |
Plants (/v1/plants)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/plants |
List plants |
| GET | /v1/plants/search |
Search plants |
| GET | /v1/plants/:id |
Get plant by ID (Redis-cached) |
| GET | /v1/plants/:id/care |
Get care instructions |
| GET | /v1/plants/:id/companions |
Get companion planting info |
| GET | /v1/plants/:id/pests |
Get common pests for a plant |
| GET | /v1/plants/:id/diseases |
Get common diseases for a plant |
| GET | /v1/plants/recommend |
Get plant recommendations |
| GET | /v1/plants/faceted |
Faceted plant search |
| GET | /v1/plants/pests |
Search the pest database |
| GET | /v1/plants/diseases |
Search the disease database |
Plantings (/v1/plantings)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/plantings |
List plantings |
| POST | /v1/plantings |
Create a planting |
| GET | /v1/plantings/:id |
Get planting by ID |
| PUT | /v1/plantings/:id |
Update a planting |
| PATCH | /v1/plantings/:id/stage |
Update growth stage |
Tasks (/v1/tasks)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/tasks |
List garden tasks |
| POST | /v1/tasks |
Create a garden task |
| PATCH | /v1/tasks/:id/complete |
Complete a task |
| DELETE | /v1/tasks/:id |
Delete a task |
| POST | /v1/tasks/generate |
Generate task suggestions |
| GET | /v1/tasks/calendar |
Calendar view of tasks |
| POST | /v1/tasks/:id/snooze |
Snooze a task |
| GET | /v1/tasks/overdue |
List overdue tasks |
Harvests (/v1/harvests)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/harvests |
List harvests |
| POST | /v1/harvests |
Record a harvest |
| GET | /v1/harvests/summary |
Harvest summary analytics |
Sensors (/v1/sensors)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/sensors |
List sensors |
| POST | /v1/sensors |
Register a sensor |
| POST | /v1/sensors/:id/readings |
Submit sensor readings |
| GET | /v1/sensors/:id/readings |
Get sensor readings |
| POST | /v1/sensors/:id/alerts |
Configure sensor alerts |
| GET | /v1/sensors/:id/alerts |
Get sensor alert configuration |
| GET | /v1/sensors/dashboard |
Sensor dashboard |
| POST | /v1/sensors/:id/calibrate |
Calibrate a sensor |
| GET | /v1/sensors/summary |
Sensor summary statistics |
Weather (/v1/weather)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/weather/current |
Current weather conditions (cached) |
| GET | /v1/weather/forecast |
Weather forecast (cached) |
| GET | /v1/weather/alerts |
Active weather alerts |
| GET | /v1/weather/history |
Historical weather data |
| GET | /v1/weather/garden-impact |
Weather impact on garden |
| POST | /v1/weather/location |
Set garden weather location |
Observations (/v1/observations)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/observations |
List observations |
| POST | /v1/observations |
Create an observation |
AI (/v1/ai)
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/ai/identify-plant |
Identify a plant |
| POST | /v1/ai/diagnose |
Diagnose plant problems |
| GET | /v1/ai/planting-plan |
Generate a planting plan |
| POST | /v1/ai/optimize-layout |
Optimize garden layout |
| GET | /v1/ai/harvest-predict |
Predict harvest timing |
| POST | /v1/ai/chat |
Gardening assistant chat |
Community (/v1/community)
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/community/feed |
Community feed |
| POST | /v1/community/posts |
Create a community post |
| GET | /v1/community/posts/:id |
Get a community post |
| POST | /v1/community/posts/:id/comments |
Comment on a post |
| POST | /v1/community/posts/:id/like |
Like / unlike a post |
| GET | /v1/community/gardens |
Browse public gardens |
| GET | /v1/community/leaderboard |
Community leaderboard |
| POST | /v1/community/challenges |
Create a community challenge |
| GET | /v1/community/challenges |
List community challenges |
Info — GET /v1/info (authenticated) returns API metadata including the
domain list: gardens, plants, plantings, tasks, harvests, sensors,
weather, observations, ai, community.
6.6 API Configuration (config.ts)#
All configuration is read from environment variables and validated by a Zod
schema in config.ts via loadConfig(). A superRefine check rejects the
default JWT_SECRET in production (it must be overridden) and enforces
DB_POOL_MIN ≤ DB_POOL_MAX. The table below lists every variable with its
default value and any notable constraints.
| Variable | Default | Notes |
| ----------------------------- | ------------------------------------------------------- | ------------------------------------- | ---------- | ----- |
| PORT | 3030 | 1–65535 |
| HOST | 127.0.0.1 | |
| NODE_ENV | development | development | production | test |
| JWT_SECRET | demeter-dev-secret-change-me | must be overridden in production |
| JWT_ISSUER | demeter-api | |
| JWT_ACCESS_TOKEN_TTL | 900 | seconds |
| JWT_REFRESH_TOKEN_TTL | 604800 | seconds |
| DATABASE_URL | postgresql://oshun:oshun_dev@localhost:5432/oshun_dev | |
| DB_POOL_MIN / DB_POOL_MAX | 2 / 10 | |
| REDIS_URL | redis://localhost:6379 | |
| RATE_LIMIT_MAX | 100 | requests per window |
| RATE_LIMIT_WINDOW_MS | 60000 | ≥1000 |
| LOG_LEVEL | info | trace…fatal |
| METRICS_PORT | 9091 | |
| CORS_ORIGIN | * | |
| WEATHER_API_KEY | '' | optional weather provider key |
| IOT_API_KEY | '' | optional IoT device key |
| OAUTH_GOOGLE_CLIENT_ID | '' | for Google ID-token verification |
| OAUTH_APPLE_CLIENT_ID | '' | for Apple identity-token verification |
| MAGIC_LINK_SECRET | demeter-magic-link-dev-secret | signs magic-link tokens |
| SESSION_MAX_PER_USER | 10 | 1–100 |
6.7 Error Responses#
Errors return a consistent body:
{ error: { code, message, details?, requestId? } }. RBAC failures use codes
AUTH_REQUIRED (401), INSUFFICIENT_ROLE (403), and INSUFFICIENT_PERMISSION
(403).
7. Application — @demeter/web#
apps/demeter/web is a React single-page application built with Vite. Stack:
React, React Router v6, TanStack Query, Zustand, Tailwind CSS, Recharts, and
date-fns. It ships ~62 page components under src/pages/ covering
authentication, dashboard, gardens (including a 2D and a 3D garden planner),
plants, plantings, tasks, harvests, sensors, weather, journal, analytics,
community, profile, and settings. API hooks live under src/api/hooks/; shared
UI is under src/components/ (charts, forms, layout, tables, primitives).
End-to-end tests use Playwright (e2e/), and unit tests use Vitest.
8. Application — @demeter/mobile#
apps/demeter/mobile is a React Native app on Expo SDK 52. Stack: React
Navigation (native-stack + bottom-tabs), TanStack Query, Zustand, and Expo
modules (expo-notifications, expo-secure-store,
@react-native-async-storage/async-storage). It ships ~50 screens under
src/screens/ across auth, gardens, plants, journal, sensors, tasks, weather,
community, AI, and settings. The mobile app includes an offline subsystem
(src/services/offline/ — action queue, conflict resolution, sync engine,
plant/weather caches, offline photo manager) and a widgets/integration layer
(src/services/widgets/ — Siri Shortcuts, Google Assistant, Watch, quick
actions, notification actions, widget data provider). Tests use Jest with
jest-expo.
9. Persistence and Local Infrastructure#
- Database — PostgreSQL. The Drizzle schema is in
@demeter/core; the API wires Drizzle to apg.Poolin the database plugin. - Cache / rate-limit / sessions — Redis (ioredis).
- Local dev —
apps/demeter/docker-compose.ymlstartspgvector/pgvector:pg16PostgreSQL (host port5433to avoid colliding with the root compose), Redis 7, and optionally the API itself. The API container build is defined byapps/demeter/api/Dockerfile;apps/demeter/api/scripts/init-db.sqlinitializes the database.
10. Configuration and Project Tags#
- Library tags —
["scope:demeter", "layer:domain", "type:lib"]. - API tags —
["scope:demeter", "type:app", "layer:service"]. - Module format — ESM (
"type": "module") across all packages. - Library build —
@nx/js:tsc; the API builds with anx:run-commandsexecutor invokingtsc. - Testing — Vitest for libraries and the API/web; Jest (
jest-expo) for mobile; Playwright for web e2e.
Common Build Commands#
# Test a specific library
pnpm nx test @demeter/core
pnpm nx test @demeter/plants
# Build / test all Demeter libraries
pnpm nx run-many --target=build --projects=tag:scope:demeter
pnpm nx run-many --target=test --projects=tag:scope:demeter
# Run the API in development
pnpm nx dev @demeter/api
11. Cross-Domain Integration#
Demeter's scope is home gardening and small-scale cultivation. The four related domains in the table below each handle an adjacent concern. Understanding these boundaries is important when deciding where to add a new feature.
| Domain | Relationship |
|---|---|
| Hestia | Seasonal ingredient sourcing; preservation/cooking of harvested produce overlaps |
| Asase | Commercial agricultural operations intelligence (Demeter scopes home/small-scale) |
| Airmid | Botanical / phytotherapy intelligence (Demeter scopes growing, not therapeutic use) |
| Gaia | Sovereign weather and climate ML source for advanced forecasts |
Hestia — The boundary runs through the post-harvest moment. Demeter records yields, tracks preserved inventory, and generates harvest forecasts. Hestia picks up from there for meal planning, recipe recommendations, and seasonal ingredient sourcing. Yield records and preservation inventories cross this boundary as data.
Asase — Demeter and Asase share the agricultural domain but differ in scale. Demeter serves the individual home grower or community garden; Asase handles commercial farm operations, commodity pricing, supply chain logistics, and farm management at scale. Features that only make sense for a commercial operation belong in Asase.
Airmid — Both domains understand plant biology. The dividing line is purpose: Demeter knows how to cultivate a plant (spacing, soil, companions, harvest timing); Airmid knows what it does in the body (active compounds, therapeutic applications, drug interactions). A feature about growing herbs belongs in Demeter; a feature about dosing or clinical evidence belongs in Airmid.
Gaia — Demeter consumes Gaia's forecast products for localized garden weather alerts, GDD enrichment, irrigation timing, and chill-hour analysis. Demeter does not generate forecasts or own forecast skill verification — that is Gaia's responsibility. The separation keeps meteorological modelling centralized and avoids each domain implementing its own weather data pipeline.
12. Acceptance Criteria#
A Demeter library or application is considered complete when:
- Schema fidelity — types are inferred from Zod schemas in
@demeter/core; the Drizzle schema's 17 tables and 20 enums match the entity model. - Domain-specific algorithms — calculations implement real horticultural science: GDD accumulation, chill hours, DLI/PPFD/VPD, EC↔TDS conversion, altitude-adjusted canning times, botulism risk, biological efficiency, isolation distances, haversine distance.
- Food safety — preservation logic enforces USDA/NCHFP rules: water-bath canning is rejected for low-acid foods, altitude adjustments are mandatory, and fermentation/canning carry pH-based safety assessment.
- API contract — every endpoint validates input with Zod, enforces JWT auth where required, applies RBAC/ownership checks, and returns the standard error envelope.
- Tests — Vitest/Jest suites verify domain correctness against
known-correct values, not just data flow. Each library source module has a
co-located
.spec.ts; the API has route, auth, RBAC, rate-limit, and security tests; the web app has unit and Playwright e2e tests.