Architecture
A high-level map of how Cobblr fits together.
The core
At the center is the modular kernel and a REST API, backed by Postgres. Every feature is a module that composes through shared primitives (items, fields, views, events). The web app is one client of the API; the CLI and integrations are others.
This is the part you self-host, source-available under the Functional Source License (see Contributing). It's built so that no data or PII ever needs to leave your system: no telemetry, no tracking, no analytics, no auto-updates phoning home. Your data is yours and stays on your box (see Self-hosting -> Privacy).
The module system
Every feature is a module, and modules are held to a shape the platform can rely on:
- Isolation. A module never imports another module: not its types, its helpers, or its tables. Modules compose only through the platform contract (items, fields, views, events, actions) and explicit granted read paths, so any subset of modules works together and a new one can't break an existing one.
- Capability or domain, encoded in the name. A domain is a kind of thing a
user manages: a noun, a table, a nav entry, opt-in, with a bare name
(
inventory,machines,tracking). A capability is plumbing with no noun in the nav, namedcore-<thing>(core-views,core-scan), usually enabled automatically. The nav builds its top level by filtering outcore-*, so the name is load-bearing: a domain misnamedcore-somethingvanishes from the app. - A manifest, not wiring. A module declares what it is: its entity kinds and their fields, what it contributes (default views, wires, scan routing), and whether it can be instanced more than once. The platform does the composing.
- One identifier pattern, namespaced by the module name:
| Thing | Pattern | Example |
|---|---|---|
| Package | @cobblr/<name> | @cobblr/tracking |
| Table prefix | <name>_ | tracking_metrics |
| Entity kind | <name>:<kind> | tracking:metric |
| Event | <name>.<noun>.<verb> | tracking.measurement.logged |
| Action | <name>:<verb-noun> | tracking:log-measurement |
| Route | /<name> | /tracking |
Multi-tenancy
One instance serves many workspaces, and the isolation between them is structural rather than a filter on shared tables. The data lives in two tiers:
- A platform database holds the accounts, the workspaces, and who belongs to which workspace with what role. It's the thin routing layer.
- Each workspace gets its own separate database for its actual data, created when the workspace is. A request is routed to the right one by workspace, and the connection credentials for each are encrypted at rest.
Modules add their own tables into a workspace's database when that workspace turns them on, so two workspaces on the same instance can have entirely different schemas in play. A user carries a role in each workspace they belong to (owner, admin, editor, member, guest), and what they can do is resolved per request from that role, with finer per-workspace grants on top.
See Workspaces for the same picture without the plumbing.
Talking to machines
When you self-host Cobblr on the same network as your machines, it talks to them directly. When it can't reach them (a firewall between them, machines at a different site, or software pinned to a specific PC like LightBurn), you run the edge bridge at your location and Cobblr connects through it. The bridge always dials outward, so nothing on your network is exposed, and its drivers handle the machine-specific protocols. See Digital fabrication for the usage side.
(Diagrams and the data model will be added.)