Electronic component obsolescence intelligence platform.
Stack: Rust (engine/server/worker), React (client), Supabase (Postgres + Auth + Storage), Redis (adapter response cache), Railway (hosting), Python (KiCad plugin). Single monorepo, Cargo workspace for the Rust side.
graph TD
Client["client<br/>(React / Vite)"]
Server["server<br/>(axum API)"]
Engine["engine<br/>(domain + ports)"]
Worker["worker<br/>(ingestion sweep & enrichment)"]
subgraph Adapters["adapters"]
DigiKey["adapter-digikey"]
Mouser["adapter-mouser"]
Octopart["adapter-octopart"]
PCN["adapter-pcn-parser"]
Notify["adapter-notify"]
CommunityPulse["adapter-community-pulse"]
end
Cache["cache<br/>(CachedConnector decorator)"]
Supabase[("Supabase\n(Postgres/Auth/Storage)")]
Redis[("Redis\n(adapter response cache)")]
External[("External sources\nDigiKey · Mouser · Octopart · PCNs")]
Forums[("Public forums\nReddit · EEVblog · StackExchange\nvendor communities")]
Client -->|HTTP REST| Server
Server -->|calls ports| Engine
Worker -->|calls ports| Engine
Engine -.->|implemented by| Adapters
Adapters -.->|wrapped by| Cache
Cache --> Redis
DigiKey --> External
Mouser --> External
Octopart --> External
PCN --> External
Notify --> External
CommunityPulse --> Forums
Client -.->|auth only| Supabase
The React frontend (Vite). Everything the user actually sees and clicks — Dashboard, Part Search, Projects/BOM tabs, all built from the shared DataTable, ScoreRing, and other reusable UI components.
The client talks to server over HTTP and directly to Supabase only for authentication and session management.
It contains no business logic. Reconciliation, risk scoring, and BOM diffing all happen server-side; the client simply renders the results.
Database migrations use the Supabase CLI and live in supabase/migrations/.
To apply migrations to a linked Supabase project:
supabase login
supabase link --project-ref <project-ref>
supabase db push
To apply migrations with a database connection string instead of linking:
supabase db push --db-url <db_connection_string>
To create a new migration:
supabase migration new <migration_name>
For Supabase GitHub integration, set the working directory to . because the supabase/ directory is at the repository root. Supabase automatically runs new files in supabase/migrations/ for preview branches and production deployments when that integration is enabled.
The outward-facing edges of the system — one crate per external integration:
Each adapter implements a trait defined in engine such as:
Adapters translate between external APIs, authentication methods, and response formats and the engine's clean domain models.
This is where all source-specific complexity lives:
Keeping these concerns isolated prevents them from leaking into the rest of the system.
adapter-digikey uses DigiKey Product Information v4 with OAuth 2.0 client
credentials. Configure DIGIKEY_CLIENT_ID, DIGIKEY_CLIENT_SECRET, and
DIGIKEY_ACCOUNT_ID, which DigiKey requires for two-legged Product Details
requests. Locale defaults to US / en / USD and can be changed with the
DIGIKEY_LOCALE_* variables listed in .env.example.
Deployed server environment variables take precedence. On a local machine,
missing values are filled from the repository's top-level .env file.
Community Pulse is the component that collects and summarizes what engineers actually say about a part across public forums — the same idea as Reddit Answers, applied to component reputation. It pulls in mentions, then hands them to the engine for ranking and synthesis into a short, cited summary (common praise, common issues, overall sentiment) shown alongside a part's lifecycle and risk data.
Credible sources this adapter draws from:
cache)server and worker both call out to the same rate-limited, sometimes-paid external APIs (DigiKey, Mouser, Octopart) — the worker on its daily sweep, the server synchronously in enrich mode when a user searches/uploads a BOM containing a part with no data yet. Without a shared cache, a burst of enrich-mode lookups for the same not-yet-seen part (e.g. several users uploading BOMs that share a part) each re-hit the paid API before the worker ever gets to it.
cache is a CachedConnector<T: DataSourceConnector> decorator — same shape as the existing RateLimited<T> wrapper — backed by Redis. It sits inside the rate limiter in the composition root (RateLimited(CachedConnector(inner))), so cache hits never consume rate-limit budget; only real misses do.
adapter:{source_id}:{normalized_mpn}:{normalized_manufacturer} → serialized raw connector response.deadpool-redis for pooling, added to both AppState (server) and the worker's composition root.The brain of the system.
Pure business logic with no HTTP server, database driver, or runtime dependencies beyond trait definitions.
Responsibilities include:
engine depends on nothing else in the workspace.
Everything else depends on it.
If Postgres, DigiKey, Reddit, or any other external dependency changed, this crate would remain largely untouched — Community Pulse's ranking/synthesis logic lives here for the same reason reconciliation and risk scoring do: it's judgment the engine owns, while adapter-community-pulse just fetches the raw posts.
The API layer.
An Axum-based binary that wires concrete adapters into the engine's traits (the composition root) and exposes HTTP endpoints for:
The server intentionally remains thin:
Very little business logic lives here.
The background processing service.
A separate binary with no HTTP surface that performs scheduled and asynchronous tasks such as:
It runs as an independent Railway service so that slow, rate-limited, or failure-prone ingestion tasks never block the user-facing API.
122 commits
1 commits
TypeScript
43.8%
Rust
31.0%
PLpgSQL
12.8%
CSS
11.2%
Electronic component obsolescence intelligence platform.
Stack: Rust (engine/server/worker), React (client), Supabase (Postgres + Auth + Storage), Redis (adapter response cache), Railway (hosting), Python (KiCad plugin). Single monorepo, Cargo workspace for the Rust side.
graph TD
Client["client<br/>(React / Vite)"]
Server["server<br/>(axum API)"]
Engine["engine<br/>(domain + ports)"]
Worker["worker<br/>(ingestion sweep & enrichment)"]
subgraph Adapters["adapters"]
DigiKey["adapter-digikey"]
Mouser["adapter-mouser"]
Octopart["adapter-octopart"]
PCN["adapter-pcn-parser"]
Notify["adapter-notify"]
CommunityPulse["adapter-community-pulse"]
end
Cache["cache<br/>(CachedConnector decorator)"]
Supabase[("Supabase\n(Postgres/Auth/Storage)")]
Redis[("Redis\n(adapter response cache)")]
External[("External sources\nDigiKey · Mouser · Octopart · PCNs")]
Forums[("Public forums\nReddit · EEVblog · StackExchange\nvendor communities")]
Client -->|HTTP REST| Server
Server -->|calls ports| Engine
Worker -->|calls ports| Engine
Engine -.->|implemented by| Adapters
Adapters -.->|wrapped by| Cache
Cache --> Redis
DigiKey --> External
Mouser --> External
Octopart --> External
PCN --> External
Notify --> External
CommunityPulse --> Forums
Client -.->|auth only| Supabase
The React frontend (Vite). Everything the user actually sees and clicks — Dashboard, Part Search, Projects/BOM tabs, all built from the shared DataTable, ScoreRing, and other reusable UI components.
The client talks to server over HTTP and directly to Supabase only for authentication and session management.
It contains no business logic. Reconciliation, risk scoring, and BOM diffing all happen server-side; the client simply renders the results.
Database migrations use the Supabase CLI and live in supabase/migrations/.
To apply migrations to a linked Supabase project:
supabase login
supabase link --project-ref <project-ref>
supabase db push
To apply migrations with a database connection string instead of linking:
supabase db push --db-url <db_connection_string>
To create a new migration:
supabase migration new <migration_name>
For Supabase GitHub integration, set the working directory to . because the supabase/ directory is at the repository root. Supabase automatically runs new files in supabase/migrations/ for preview branches and production deployments when that integration is enabled.
The outward-facing edges of the system — one crate per external integration:
Each adapter implements a trait defined in engine such as:
Adapters translate between external APIs, authentication methods, and response formats and the engine's clean domain models.
This is where all source-specific complexity lives:
Keeping these concerns isolated prevents them from leaking into the rest of the system.
adapter-digikey uses DigiKey Product Information v4 with OAuth 2.0 client
credentials. Configure DIGIKEY_CLIENT_ID, DIGIKEY_CLIENT_SECRET, and
DIGIKEY_ACCOUNT_ID, which DigiKey requires for two-legged Product Details
requests. Locale defaults to US / en / USD and can be changed with the
DIGIKEY_LOCALE_* variables listed in .env.example.
Deployed server environment variables take precedence. On a local machine,
missing values are filled from the repository's top-level .env file.
Community Pulse is the component that collects and summarizes what engineers actually say about a part across public forums — the same idea as Reddit Answers, applied to component reputation. It pulls in mentions, then hands them to the engine for ranking and synthesis into a short, cited summary (common praise, common issues, overall sentiment) shown alongside a part's lifecycle and risk data.
Credible sources this adapter draws from:
cache)server and worker both call out to the same rate-limited, sometimes-paid external APIs (DigiKey, Mouser, Octopart) — the worker on its daily sweep, the server synchronously in enrich mode when a user searches/uploads a BOM containing a part with no data yet. Without a shared cache, a burst of enrich-mode lookups for the same not-yet-seen part (e.g. several users uploading BOMs that share a part) each re-hit the paid API before the worker ever gets to it.
cache is a CachedConnector<T: DataSourceConnector> decorator — same shape as the existing RateLimited<T> wrapper — backed by Redis. It sits inside the rate limiter in the composition root (RateLimited(CachedConnector(inner))), so cache hits never consume rate-limit budget; only real misses do.
adapter:{source_id}:{normalized_mpn}:{normalized_manufacturer} → serialized raw connector response.deadpool-redis for pooling, added to both AppState (server) and the worker's composition root.The brain of the system.
Pure business logic with no HTTP server, database driver, or runtime dependencies beyond trait definitions.
Responsibilities include:
engine depends on nothing else in the workspace.
Everything else depends on it.
If Postgres, DigiKey, Reddit, or any other external dependency changed, this crate would remain largely untouched — Community Pulse's ranking/synthesis logic lives here for the same reason reconciliation and risk scoring do: it's judgment the engine owns, while adapter-community-pulse just fetches the raw posts.
The API layer.
An Axum-based binary that wires concrete adapters into the engine's traits (the composition root) and exposes HTTP endpoints for:
The server intentionally remains thin:
Very little business logic lives here.
The background processing service.
A separate binary with no HTTP surface that performs scheduled and asynchronous tasks such as:
It runs as an independent Railway service so that slow, rate-limited, or failure-prone ingestion tasks never block the user-facing API.
122 commits
1 commits
TypeScript
43.8%
Rust
31.0%
PLpgSQL
12.8%
CSS
11.2%