Core, adapters and extensions
Core, adapters and extensions
Status: the ring split is applied from M0 and fully exercised by M6 (extension host). model, compiler and cli exist since M1; runtime since M2 (the action context’s type, errors, input validation and data-layer contract v0). The remaining package roles below are the design (roadmap, section 3).
Why rings
Mesh’s rule is that only what Mesh cannot run without is hardcoded, and “it was in the plan” is never a reason to hardcode something (roadmap, section 2, principle 4; ADR-0001). The rule was set after the first proposal classed tools as “integral” because their names would appear in generated code, which the operator judged the wrong test (research synthesis, Step 2 preamble).
Definitions and tests
| Ring | Definition | Test |
|---|---|---|
| Core | Hardcoded; Mesh cannot run without it | Remove it: does Mesh stop working? |
| Adapter | One replaceable implementation of a contract core owns | Can a project pick another implementation of the same contract? |
| Extension | Optional feature built on core’s extension points | Can a project do without it, or do most projects not use it? |
(research synthesis, Step 2 preamble; roadmap, section 0.)
Packages
All packages are published under the npm scope @meshfw (ADR-0060); the command is mesh.
| Package | Ring | Runs when | Why it sits there |
|---|---|---|---|
model |
core | build | Every build-time package reads the model. Plain JSON-serialisable types, registries (attribute types, step kinds, expression functions), the diagnostic type. |
compiler |
core | build | The pipeline is what makes Mesh a framework. Owns stage order, the extension host, the emitter views and their Jig templates (ADR-0061). It is also where MX is used: the tag contracts and the load and check-structure stages live here. |
cli |
core | build | The mesh command is the only entry to the pipeline. It builds a project; it does not run actions. Commands such as mesh db push and mesh migrate are contributed by the data adapter, so cli imports no query library and never imports drizzle-kit (roadmap, section 3 and M2). |
runtime |
core | run | What generated code imports: the ActionContext type (ADR-0059), error classes, the data-layer contract with query and expression-tree types, the transaction helper, the policy engine’s run-time half, in-memory implementations of registered expression functions. |
mesh (MX host) |
core | editor and MX tooling | The MX host package that makes .mesh.mx files resolve in MX tooling (ADR-0051). Its name is fixed by MX. |
data-drizzle |
adapter | run | Shared code of the SQL adapters: turns Mesh queries and expression trees into Drizzle’s builder when a query runs (M3, M4). |
data-sqlite |
adapter | both | Data layer on SQLite. A build-time half emits Drizzle table definitions; a run-time half implements the contract (M2). |
data-postgres |
adapter | both | Data layer on Postgres (M9); mesh migrate generate needs its schema emitted at build time. |
(roadmap, section 3 and the milestones cited.)
Why the split between compiler and runtime: a deployed program must not carry the compiler (ADR-0033). Contract types the generated code needs (the action context, errors, query, expression tree) therefore live in runtime.
Why SQL adapters are adapters: the data-layer contract is Mesh’s own and “the query library inside an adapter is that adapter’s private choice” (research synthesis, section 11), which is Drizzle (ADR-0013, ADR-0014).
Why authorization is core: policies is a section of the entity file, and an entity without one forbids every action. A configuration in which authorization is not installed would make that rule depend on a package being present, so the engine is part of core (ADR-0055). The research had proposed “a fixed slot in the core lifecycle, filled by a first-party policy extension” (research synthesis, section 10); the slot stays, and core fills it. Its simple tier is unchanged (ADR-0022).
Why MX is core and not an adapter
The research proposed “front end” and “expression parser” adapter slots (research synthesis, section 15). The operator ruled otherwise: MX is not replaceable, so there is no front-end slot and no front-end package (ADR-0043). Reason: tag contracts, analyze rules, positioned errors and the composed contracts module are MX concepts and would leak through any neutral contract; a contract with one implementation is a guess. The expression-parser slot disappears for the same reason: MX hands each expression over as a parsed Babel node. The cost is a hard dependency on a project Mesh does not control.
Adapter slots with no package
| Slot | Why no package in v1 |
|---|---|
| Transport (command line, HTTP, server) | The core interface is a function call; a transport is built when something needs it (ADR-0005). |
| Tracer | Generated code calls the OpenTelemetry API directly (ADR-0029). |
| Runtime host | Bun only (ADR-0025). |
(roadmap, section 3.) There is no actor-resolver slot: the caller passes the action context as an argument (ADR-0059). A job runner is deferred past v1 (ADR-0023).
Extensions in v1
No first-party extension ships in v1. The host exists from M6 for project-local extensions in src/extensions/ (ADR-0057) and for later first-party ones (multitenancy, for example, which reads a key of the action context it declares, ADR-0059). See extension host.
Where does new code go
A procedure derived from the three tests above, not a separate rule.
- Does it run when
mesh buildruns, or inside the deployed program? Build-time code goes tomodel,compiler,clior an adapter’s build half. Run-time code goes toruntimeor an adapter’s run-time half. - Can Mesh not function without it? Then it is core. If it is one way to do a core job, it is an adapter behind a contract core owns; write the contract first.
- Is it a feature some projects will not use? Then it is an extension and may touch the model only through a declared contribution point (ADR-0020). Until M6 there is no extension host.
- Is it an application concern: how a caller is identified, how a program is exposed, login? Then it is not Mesh (roadmap, section 2, principle 8). The application puts what it needs into its own
ActionContextkeys. - Does a well-established tool do it? Put that tool behind a Mesh contract (ADR-0030).
Import rules
Checked by verify: the MX rule from M1 and the rest from M2 (roadmap, M1, acceptance test 8; M2, acceptance test 4):
runtimeimports nothing frommodel,compileror Drizzle. The build-time packages may importruntime’s contract types, never the reverse (ADR-0033).compilerdepends on MX.modelandruntimenever import it;@mxlang/*is imported only by packages that declare tag contracts (compilernow, the MX host package and extensions later) (ADR-0043).- Jig is imported only by
compiler; it never reaches the run-time library (ADR-0061). - No generated file under
.mesh/importsmodel,compiler, Drizzle ormodel.json, except the emitted schema file, which imports Drizzle. - Drizzle is imported only under
packages/data-*and in the emitted schema file.
Why: a runtime that cannot see the model cannot interpret it, so behaviour has to be in the generated code (ADR-0003). Drizzle’s v1 is a release candidate whose relations API is being replaced, so confining it limits the cost of an upgrade (roadmap, section 9, risk 3).
Not decided: the order of dependency among model, compiler and cli; the roadmap does not give it.