Architecture
Components
- Core Warehouse (
backend/) — Node.js/TypeScript + Express REST API under/api/v1/. Owns relational metadata (PostgreSQL via Prisma), publishes/consumes lifecycle events (RabbitMQ), and hosts the Pipeline Router that dispatches steps, handles retries/backoff, and sweeps for step timeouts. - Plugin Workers (
plugins/*/) — one independent Docker container per plugin. Each subscribes to the event broker for its own routing key and reports results via a single internal HTTP callback. - Web UI (
frontend/) — React + Vite + Tailwind SPA, three primary sections (Resources, Pipelines, Plugins) plus a global search, served via nginx which also reverse-proxies/apiand/registryto their respective backend services. - Plugin Registry (
registry/+community-site/) —registryis a standalone service (its own SQLite-backed API, with user accounts) that any DataCore deployment's frontend can browse or post to;community-siteis a standalone website that fronts the same API as its own destination, independent of the rest of the stack. Hosts two kinds of public listings: plugin listings (metadata + a link to the plugin's own repo/Docker image — not a code-hosting or auto-deploy system) and DataCore Bundles (a shareable list of resource definitions + the pipeline that processed them, for reprocessing elsewhere — see Sharing & Importing Bundles). Posting either requires a verified account; browsing doesn't. - Storage tiers: PostgreSQL (relational metadata only), MinIO (raw files + text/JSON artifacts, S3-compatible), and whatever store a plugin's artifacts belong in (e.g. Qdrant for vectors) — Postgres never stores file content or vectors directly, only reference pointers.
Guiding principles
These come from the project constitution (.specify/memory/constitution.md) and shape every design decision:
- Event-Driven Architecture — all state transitions are broker events; no synchronous cross-service calls except the one explicitly-allowed internal artifact callback.
- Plugin Isolation — each plugin is its own container; a crash, hang, or bug in one plugin cannot affect Core or any other plugin.
- Schema-First Data Models — Resource/Pipeline/Plugin/Artifact schemas are defined before any endpoint that reads or writes them.
- API Contract Discipline — every REST endpoint lives under
/api/v1/with one consistent JSON error envelope ({ "error": { "code", "message" } }). - Separation of Storage — relational metadata, raw files, and generated artifacts each live in their own tier; nothing is duplicated across tiers.
- UI Consistency — the Web UI reflects live API state; no hardcoded mock data once a view is wired up.
- Test Discipline — every REST endpoint and event handler has an integration test run against real Postgres/RabbitMQ/MinIO/Qdrant — nothing is mocked.
- No Silent Failures — a failed step surfaces a specific
failure_reasonon the resource; nothing is leftProcessingforever without either completing, failing, or (for a non-responding step) being caught by the timeout sweep.
Request flow: registering a resource
User → POST /api/v1/resources
→ Core persists Resource (PENDING), matches a Pipeline by trigger_type
→ Core publishes RESOURCE_CREATED
→ Pipeline Router consumes it, snapshots the pipeline's steps onto the resource, dispatches step 0
→ Plugin Worker consumes PIPELINE_STEP_DISPATCHED, does its work
→ Plugin calls back POST /api/v1/internal/artifacts/{resource_id}
→ Core upserts the Artifact, advances to the next step (or marks COMPLETED)Full detail, including the retry/backoff/timeout state machine, is in Events reference and contracts/events.md in the spec.