Package boundaries
Define business objects with @onerp/meta, execute their behavior on the server with
@onerp/core, and access the backend with @onerp/client. The shared packages
have no dependency on the NestJS backend or the React application framework.
Shared APIs
Section titled “Shared APIs”| Import | Responsibility |
|---|---|
@onerp/meta |
Fields, business-object definitions, rule and operation declarations, Meta, labels and permissions |
@onerp/meta/records |
Record, update, filter, access and error contracts; applying, merging and copying updates; identifiers and number-series markers |
@onerp/meta/serialization |
The meta wire shape (toMetaWire, parseMeta), codecs and hydration |
@onerp/meta/values |
Decimal and Temporal values |
@onerp/meta/format |
Locale-aware formatting, including use in server-rendered documents |
@onerp/catalogs |
Country, currency and unit reference catalogs; unit dimensions |
@onerp/client |
HTTP access from browsers, Node services and integration tools |
import { defineBusinessObject, field } from "@onerp/meta";import type { BusinessObject, Update } from "@onerp/meta/records";import { Decimal } from "@onerp/meta/values";import { currencyCatalog } from "@onerp/catalogs";The root metadata entry point is for authoring the model. Rule execution and the
rule and operation contracts (Determination, Validator, Action, Function)
belong to @onerp/core, beside the decorators that register them. A new utility does not belong in metadata merely because both the
browser and backend use it: it must describe the model, its contracts, its scalar
values, or its serialization. Keep types with the concepts they describe; do not
add a second representation just to cross an entry point.
Core depends on metadata; metadata must never import core. Catalogs
are concrete reference data layered on the generic Catalog contract. Keep them
out of the generic model’s root exports. Tenant-extension policy (custom names)
belongs to the server’s MetaService, not to generic metadata definitions.
Runtime and test packages
Section titled “Runtime and test packages”@onerp/core provides the NestJS server runtime: persistence, auth, HTTP, jobs,
plugin discovery and server integrations. @onerp/react provides the React
application framework and its forms, lists and data hooks. @onerp/ui contains UI
primitives.
@onerp/mcp serves the model to any MCP host as a tool catalog on /api/mcp, under
the same permissions the HTTP API enforces. Two further packages are optional and sit
outside that spine. @onerp/agent is the chat agent — prompt, model loop and an
in-process MCP client over that same catalog; it does nothing until a provider key is
configured. @onerp/node-red depends only on @onerp/client, so it runs wherever
Node-RED does rather than inside the server. Domain packages contain server code; browser code follows the existing
metadata pipeline rather than importing a domain’s root at
runtime.
| Import | Test support |
|---|---|
@onerp/testing |
Server application and service harnesses with database/auth integration |
@onerp/react/testing |
React rendering and application providers |
@onerp/react/testing/in-memory |
In-memory record manager without React Testing Library |
@onerp/meta/testing |
Lightweight metadata fixtures |
@onerp/factory |
Schema-aware data factories with a host-supplied runtime |
The factory package does not depend on the server test harness. Form completeness checks live in the React form runtime; they are not backend validation guarantees.
How a package ships
Section titled “How a package ships”Whether a package is consumed as source or as build output is decided per package.
@onerp/react and @onerp/ui ship source: their exports point at src and
they have no build script. Only a bundler ever loads them, so compiling them
first would buy nothing but a second, staler copy.
Every other package ships compiled output from dist and has a build
script, because a Node process loads it directly: the API server, the Node-RED
nodes, or the end-to-end suite.
A compiled package that a browser app also reaches through Vite publishes its
source beside that output, under the source export condition:
"exports": { ".": { "source": "./src/index.ts", "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.js" }}@onerp/meta, @onerp/client and @onerp/catalogs publish it on
every subpath they export. onerpAppConfig from @onerp/react/vite lists source
first in resolve.conditions, so an application picks up an edit to those packages
immediately instead of whichever dist was last built. Node skips a
condition it does not know, so the API server and the Node-RED nodes keep loading
dist.
Types still resolve through types, which is dist, and a package’s own e2e and
scenario tests import it by its published name. Every type-check therefore still
depends on build and ^build.
A package declares its entry points in exports and nothing else. main and
types are the pre-exports spelling of the same thing, and no resolver here
reads them: every tsconfig resolves as NodeNext or Bundler, both of which
prefer exports, and Node does the same for an import. A package that ships
source additionally carries no build. Nothing in this repository is published,
so workspace dependencies are spelled workspace:* throughout.
@repo/vitest-config builds with a bare tsc rather than a tsconfig.build.json
like its peers. That file exists to keep tests out of dist, and this package has
none — a second config would be ceremony that changes no output.
@onerp/api carries a one-key .swcrc. It is a "type": "module" package like
every other one here, but nest build’s SWC builder hardcodes
module.type: "commonjs" and never reads the module the tsconfig already sets to
NodeNext, so without that override the server would emit CommonJS into an ESM
package. The Nest CLI merges the file over its own defaults rather than replacing
them, which leaves the decorator metadata the DI container reads untouched. The
CLI’s own isEsmProject helper already drives its webpack and rspack builders;
once the SWC builder reads it too, the file can go.
Why nothing declares sideEffects: false
Section titled “Why nothing declares sideEffects: false”Only @onerp/react and @onerp/ui declare sideEffects, both as ["**/*.css"].
Nothing else declares it, and that is deliberate.
@onerp/meta cannot. Three of its modules do work at import time:
values/decimal.ts writes Big.DP, Big.RM, Big.NE and Big.PE, which is what
fixes decimal precision and the plain-string wire format for every Decimal in the
system; records/id.ts constructs the monotonic ULID generator as a singleton; and
wire/meta-codec.ts assigns the recursive fieldSchema at module scope. A
false here would let a bundler delete any of them silently.
@onerp/catalogs and @onerp/client could — no module in either of
them carries a top-level statement — and it was measured to change nothing. Both
app bundles come out byte-identical down to the content hash, because Rolldown’s
own per-statement analysis already drops every unused module: countryCatalog and
currencyCatalog are absent from apps/web today with no declaration at all.
The rule this leaves: sideEffects: false earns its place when a module carries a
top-level statement a bundler must conservatively keep. A package written as plain
ESM exports has none, so the declaration buys no bytes — and it is a claim that
fails by deleting code, silently, the day someone adds a legitimate module-level
statement.
Tooling
Section titled “Tooling”@repo/typescript-config shares compiler settings. base.json holds common
language/strictness settings, node.json selects Node module resolution,
react.json selects browser libraries and bundler resolution, vite-app.json
adds the Vite and Node types a browser application needs to type-check its own
build config alongside its source, and nestjs.json adds backend decorator
settings and the API application’s existing compiler policy.
Packages that use web-platform APIs explicitly include the required libraries.
@repo/vitest-config/node is for plain TypeScript tests. The /nestjs preset
adds the decorator transform and serializes expensive scenario/e2e files. React
packages configure their own Node, jsdom and browser projects. Turbo runs tests per
package, preserving package-level caching.