Domain · Specifications

Demeter — Technical Specifications

The table below provides a quick reference for the domain's scope and technology choices.

12sections22 minread

On this page

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 |

GeoLocationSchemalatitude (−90..90), longitude (−180..180), elevation (meters, −500..9000), aspect (enum N|S|E|W|NE|NW|SE|SW).

SoilCompositionSchematype (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).

SunExposureSchemacategory (full_sun|partial_sun|partial_shade|full_shade), dailyHours (0..24), morningHours (0..12), afternoonHours (0..12).

WaterRequirementSchemafrequencyDays (1..90), amountLiters (0.01..1000), method (IrrigationTypeSchema), seasonalAdjustments (object of spring|summer|fall|winter multipliers, each 0..5).

TemperatureRangeSchemaminCelsius, maxCelsius, optimalMinCelsius, optimalMaxCelsius, each −50..60.

GrowingConditionsSchema — composite: soil (SoilCompositionSchema), sun (SunExposureSchema), water (WaterRequirementSchema), temperatureRange (TemperatureRangeSchema), humidityRange (min/max, 0..100).

PlantSpacingSchemarowSpacingCm (1..1000), plantSpacingCm (1..1000), depthCm (0.1..100), thinToSpacingCm (1..1000, optional).

HarvestWindowSchemastartDaysFromPlanting, 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

GardenBedSchemaid, 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.

HarvestSchemaid, plantingId (UUID), userId (UUID), harvestDate (dateOnly), quantityKg (0.001–100000), qualityRating (integer 1–5), notes (≤5000, default ''), usedFor (enum fresh|preserved|shared|composted), createdAt.

TaskSchemaid, 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.

ObservationSchemaid, 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.

AlertSchemaid, 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 / *_GUIDES exports 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/core Zod 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#

  • SpeciescreatePlantSpecies, getSpeciesById, searchSpeciesByName, getSpeciesByType, getSpeciesByFamily.
  • CompanionCOMPANION_DATABASE; getCompanions, getBeneficialCompanions, getDetrimentalCompanions, scoreCompanionPairing, suggestGuildPlanting, generateThreeSistersLayout, checkBedCompatibility, generateCompanionChart.
  • PestsPEST_DATABASE, DISEASE_DATABASE, BENEFICIAL_INSECTS; identifyPest, recommendTreatments, generateIPMPlan.
  • ClimateUSDA_ZONES, KOPPEN_CLASSIFICATIONS, AHS_HEAT_ZONES, FROST_DATES_BY_STATE; getUSDAZone, getKoppenClassification, getAHSHeatZone, calculateGrowingSeasonLength, calculateGrowingDegreeDays, calculateChillHours, isPlantHardy, filterPlantsByZone, estimateMicroclimate.
  • CalendarPLANTING_CALENDAR; getPlantingCalendar, getMonthlyTasks, createSeedStartSchedule, createTransplantSchedule, createSuccessionSchedule, createFallPlantingCalendar, calculateMoonPhase, getMoonPlantingAdvice, generatePrintableCalendar.
  • SearchsearchPlants, fuzzyNameSearch, facetedSearch, findSimilarPlants, suggestForBeginners, suggestProblemSolvers, suggestByHarvestTime, suggestPollinatorPlants, suggestEdibleLandscaping, searchByNutrition.
  • DatabaseVEGETABLE_DATABASE, HERB_DATABASE, FRUIT_DATABASE, FLOWER_DATABASE; getAllPlants, getPlantsByCategory, getPlantCount, validateDatabase.

@demeter/hydroponics#

  • Systems — seven HydroponicSystemType configs (NFTConfig, DWCConfig, EbbFlowConfig, DripConfig, AeroponicConfig, KratkyConfig, WickConfig); SYSTEM_TEMPLATES, SETUP_GUIDES; createSystemFromTemplate, calculateSetupCost, estimateDailyPowerWatts, estimateMonthlyElectricityCost, validateSystemConfig, getRecommendedCrops, compareSystems.
  • NutrientsEC_RANGES, NUTRIENT_LOCKOUTS, DEFICIENCY_SYMPTOMS, NUTRIENT_BRANDS, PLANT_NUTRIENT_PROFILES; calculateECCorrection, ecToTDS, tdsToEC, calculatePHAdjustment, checkNutrientLockout, diagnoseDeficiency, calculateMixingRecipe.
  • ReservoirgetWaterLevelStatus, getTemperatureControlState, generateDosingCommand, calculateEvaporationRate, detectAnomalies, estimateDaysUntilRefill.
  • AquaponicsFISH_SPECIES_DATABASE, FISH_DISEASE_DATABASE; calculateMaxFishCount, calculateFeedingAmount, assessCyclingStatus, calculateFishToPlantRatio, calculateFeedConversionRatio, diagnoseFishDisease, generateBalanceReport.
  • Indoor growingGROW_LIGHT_DATABASE, VPD_RANGES, DLI_TARGETS; calculateVPD, calculateDLI, calculatePPFDForDLI, getPPFDAtDistance, getPhotoperiodSchedule, calculateCO2Supplementation, calculateElectricityCost, generateVPDChart.
  • MicrogreensMICROGREEN_VARIETIES; calculateSeedDensity, createTray, harvestTray, generateProductionSchedule, getMostProfitableVarieties.
  • MushroomsMUSHROOM_SPECIES_DATABASE, CONTAMINATION_DATABASE, SUBSTRATE_RECIPES; calculateBiologicalEfficiency, createMushroomGrow, recordFlush, identifyContamination, calculateSpawnAmount, validateFruitingConditions.

@demeter/planner#

  • LayoutUndoRedoHistory class; createCanvas, addElement, removeElement, moveElement, rotateElement, resizeElement, snapToGrid, copyElement, calculateTotalArea, calculateBedArea.
  • PlacementcreateSquareFootGrid, assignPlantToCell, calculatePlantsPerSquareFoot, createRowPlanting, validateSpacing, checkCompanionCompatibility, calculateMatureSizeOverlap, planVerticalSpace.
  • Sun analysiscalculateSunPosition, calculateSunrise, calculateSunset, calculateDaylength, calculateShadow, calculateStructureShadows, calculateTreeShadows, calculateDailyLightIntegral, generateSunHeatmap, findOptimalBedPlacement.
  • RotationROTATION_GROUPS; generateRotationPlan, validate4YearRotation, suggestNextCrop, trackNitrogenBalance, planCoverCrops, accountForPerennials.
  • SuccessioncalculateSowingIntervals, createSuccessionPlan, calculateSeedQuantity, generateHarvestTimeline, trackActualVsPlanned.
  • TemplatesTEMPLATE_LIBRARY; instantiateTemplate, createCustomTemplate.
  • ExportgeneratePlantShoppingList, generateSeedOrderList, generateMaterialsList, generatePlantingCalendarFromPlan, generateBedLabels, exportPlanAsJSON, importPlanFromJSON, generateTaskListFromPlan.

@demeter/preservation#

  • CanningCANNING_RECIPES, WATER_BATH_ALTITUDE_ADJUSTMENTS, PRESSURE_ALTITUDE_DIAL, PRESSURE_ALTITUDE_WEIGHTED; getAdjustedProcessingTime, getAdjustedPressureDial, determineCanningMethod, calculateAcidAddition, createCanningBatch, checkBatchSeals, performSafetyCheck, isWaterBathSafe, getBotulismRisk.
  • FreezingFREEZING_GUIDES, STORAGE_DURATION_DATABASE; createFreezerItem, calculateFreezerSpace, sortByFIFO, generateExpirationAlerts, getFIFOPriority.
  • DehydratingDEHYDRATING_GUIDES, FRUIT_LEATHER_RECIPES, JERKY_RECIPES, HERB_DRYING_GUIDES; calculateReconstitution, calculateDriedWeight, getShelfLife, assessDryingQuality.
  • FermentationFERMENTATION_RECIPES, LACTO_MILESTONES, KOMBUCHA_MILESTONES; createFermentBatch, recordPH, calculateSaltForBrine, createKombuchaBrew, assessFermentSafety.
  • StorageSTORAGE_CONDITIONS, SPOILAGE_INDICATORS; checkConditionCompliance, generateStorageAlerts, checkStorageCompatibility, findBestLocation.
  • Harvest planningCROP_YIELD_DATABASE; estimateYield, createHarvestForecast, predictPeakHarvest, schedulePreservationSessions, calculateContainerNeeds.
  • RecipesPRESERVATION_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:

  1. request-context — attaches per-request context for tracing and logging
  2. CORS — cross-origin resource sharing headers
  3. error-handler — normalizes all errors to the standard error envelope
  4. Swagger/OpenAPI — auto-generated API documentation
  5. auth (JWT) — JWT verification, decorates requests with user identity
  6. database (PostgreSQL + Drizzle) — connection pool and ORM instance
  7. Redis — cache, rate-limit backing store, and session store
  8. rate-limit — per-IP and per-user request throttling
  9. 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), plus iot_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) and requirePermission(permissions) are Fastify preHandler factories; requirePermission requires ALL listed permissions.
  • Ownership (ownership.ts)requireOwnership(resourceType) resolves a resource owner from registered in-memory stores (direct userId field, or indirect via the owning garden). Resource types: planting, task, harvest, sensor, observation. admin and owner roles 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

InfoGET /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 | tracefatal | | 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 a pg.Pool in the database plugin.
  • Cache / rate-limit / sessions — Redis (ioredis).
  • Local devapps/demeter/docker-compose.yml starts pgvector/pgvector:pg16 PostgreSQL (host port 5433 to avoid colliding with the root compose), Redis 7, and optionally the API itself. The API container build is defined by apps/demeter/api/Dockerfile; apps/demeter/api/scripts/init-db.sql initializes 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 a nx:run-commands executor invoking tsc.
  • Testing — Vitest for libraries and the API/web; Jest (jest-expo) for mobile; Playwright for web e2e.

Common Build Commands#

bash
# 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:

  1. Schema fidelity — types are inferred from Zod schemas in @demeter/core; the Drizzle schema's 17 tables and 20 enums match the entity model.
  2. 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.
  3. 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.
  4. API contract — every endpoint validates input with Zod, enforces JWT auth where required, applies RBAC/ownership checks, and returns the standard error envelope.
  5. 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.