Package contract
Every @withpotter/* package abides by one contract so the engine can compose
any entitled subset without surprises. These are the rules the engine relies on.
The seven rules
1. Only dist/ is published
Packages publish their build output (dist/) via npm pack. Source (src/) is
never installed into a box. Boxes consume compiled JavaScript, not TypeScript.
2. forRoot() is the only host contract
A package exposes a single DynamicModule factory: forRoot(options). Options
are limited to:
authGuard: Type<CanActivate>— bound under theAUTH_GUARDtoken.- Feature-specific config with safe defaults.
- Optional
importsandprovidersescape hatches for adapter bindings.
No other public surface is assumed by the host.
3. Self-owned models
Each package exports a <PKG>_MODELS array (for example TENANT_CORE_MODELS = [Tenant, Account, ...]). The engine aggregates all of them into ENGINE_MODELS.
A package never reaches into another package’s tables directly.
4. Ports, not imports
Cross-package dependencies go through DI tokens (@Inject(TOKEN)), with the
token definitions living in @withpotter/shared. A package never imports another
engine package directly. This is what makes adapters
swappable.
5. Migrations are bundled
Each package ships its migrations and exports their path. The engine globs
node_modules/@withpotter/*/migrations/*.js, sorts by timestamp, and runs each
once. See Engine → OpenAPI → Migrations.
6. Honest peer dependencies
Framework packages (NestJS, Sequelize, etc.) are declared in peerDependencies,
not dependencies, so a box resolves a single shared copy rather than many
conflicting ones.
7. Outside-monorepo smoke test
A CI workflow (engine-smoke.yml) packs each package and boots it inside a
throwaway Nest app, proving the package composes cleanly outside the monorepo —
exactly as a box consumes it.
These rules are why the engine can stay a thin shell: it makes no assumptions
beyond forRoot(), aggregated models, port tokens, and bundled migrations.
Why it matters for operators
- Predictable installs. Any entitled subset composes; there are no hidden cross-package imports to break.
- Swappable infrastructure. Because dependencies are ports, you replace a mailer or payment gateway without forking a package.
- Zero-step migrations. Adding an entitled package and restarting the engine brings its tables online automatically.