Distribution Model
Техническое руководство из исходного проекта Notty. Примеры, параметры и эксплуатационные ограничения.
Status: accepted spec (Phase 5)
Purpose: formalize the installable distribution model, publish contract, upgrade path, and the boundary between platform runtime and user-land code.
Why This Exists
Notty must be delivered as a mature installable product, not as a copy of the monorepo source tree.
A user who runs npx @notty/create my-app must receive a self-contained project that:
- installs from published npm packages
- runs without building the platform from source
- upgrades by bumping dependency versions
- stays maintainable across version updates
- keeps user code separate from platform internals
This document is the single source of truth for how Notty is shipped and consumed. Other specs handle what the generated app looks like inside (Generated App Contract) and how configuration works (Configuration Model). This spec owns the distribution pipeline end-to-end.
Package Architecture
Published Packages
| Package | Role | Format | Key Contents |
|---|---|---|---|
@notty/server |
Runtime | ESM | Pre-built Nitro bundle (.output/server/), pre-built admin SPA (public/admin/), CLI executable (dist/cli.js) |
@notty/core |
Engine | CJS + ESM | Config helpers (defineConfig), plugin system, registries, middleware engine |
@notty/database |
Data layer | CJS + ESM | Drizzle ORM adapters (PostgreSQL, MySQL, SQLite), schema manager |
@notty/types |
Type contracts | CJS + ESM | NottySchema, NottyField, ExtensionSource, all shared TS types |
@notty/locales |
i18n | CJS + ESM | Locale bundles (./ru, ./en), translation utilities |
@notty/ui |
Component library | CJS + ESM | Reusable UI components, CSS |
@notty/plugin-api |
Plugin SDK | CJS + ESM | definePlugin, PluginContext, testing helpers |
@notty/module-api |
Module SDK | CJS + ESM | defineNottyModuleManifest, module registry contract and validation |
@notty/modules |
Module catalog | CJS + ESM | First-party module manifests for editions and add-ons |
@notty/theme-api |
Theme SDK | CJS + ESM | defineTheme, token contracts, testing helpers |
@notty/sdk |
Client SDK | CJS + ESM | Framework integrations (/react, /next, /nuxt) |
@notty/codegen |
Code generation | CJS + ESM | TypeScript type generator from schemas |
@notty/cli |
Developer tools | ESM | notty CLI: dev, start, build, check, doctor, diagnose, codegen, scaffold |
@notty/create |
Scaffolder | ESM | Project generator, templates, version resolution |
Edition Distribution Channels
Notty uses a hybrid distribution model:
| Channel | Intended use | Access model |
|---|---|---|
| Public npm | Community installs and evaluation | Public @notty/* packages required by Community |
| Private npm registry | Paid self-hosted installs | Token-gated Pro, Business, Enterprise and module access |
| Portable tarball distro | On-prem/offline-friendly delivery | Edition-specific bundle generated from packed tarballs |
| Docker image per edition | Fast server deployment | Public Community image, private paid edition images |
| NottyPortal customer access | License and entitlement management | Seller-owned portal outside the product repository |
The Community stack is public. Commercial packages publish with restricted access and are granted by NottyPortal through registry tokens, private Docker access or downloadable portable distributions.
Portable distributions are generated with:
./scripts/local-publish.sh
pnpm release:dist -- --edition=community
pnpm release:dist -- --edition=pro
pnpm release:dist -- --edition=business
pnpm release:dist -- --edition=enterprise
Each generated directory under dist/editions/ contains only the @notty/* tarballs for that
edition, a runtime package.json, notty.config.ts, .env.example, install.sh, manifest and
Docker Compose reference. Higher-tier package tarballs are not present in lower-tier distributions.
Docker images are built with:
pnpm release:docker -- community
pnpm release:docker -- pro
pnpm release:docker -- business
pnpm release:docker -- enterprise
The Docker build passes NOTTY_EDITION into deployment/Dockerfile and copies only the package
layer for that edition into the runtime image.
Dependency Graph
@notty/types (zero deps, foundation)
|
+-- @notty/module-api (types)
| |
| +-- @notty/modules
| |
| +-- @notty/server (module catalog + core + Nitro + pre-built admin)
|
+-- @notty/database (types + Drizzle ORM)
| |
| +-- @notty/core (database + types)
|
+-- @notty/plugin-api (types)
+-- @notty/theme-api (types)
+-- @notty/sdk (types)
+-- @notty/codegen (types)
+-- @notty/locales (standalone)
+-- @notty/ui (standalone)
+-- @notty/cli (standalone tooling, shells out to project runtime)
+-- @notty/create (standalone, resolves versions at scaffold time)
What the User Installs
A generated project's package.json depends on:
{
"dependencies": {
"@notty/server": "^0.16.0",
"@notty/core": "^0.16.0",
"better-sqlite3": "^12.4.1"
}
}
@notty/server transitively pulls in @notty/core, @notty/database, @notty/types, @notty/locales, @notty/module-api, @notty/modules, and @notty/ui. The user does not need to install these directly.
Database drivers (better-sqlite3, pg, mysql2) are direct dependencies because they contain native binaries that must match the user's platform.
@notty/cli is optional. Generated apps do not install it by default; teams invoke it on demand via pnpm dlx @notty/cli <command> (or npx @notty/cli <command>) when they need diagnostics, validation, or scaffolding helpers.
Pre-built Artifacts
Server Bundle
@notty/server ships a complete Nitro pre-build in .output/server/:
index.mjs— the server entry pointchunks/— code-split server modulesnode_modules/— vendored pure-JS dependencies (native modules excluded)
Native database drivers (better-sqlite3, pg-native) are explicitly removed from the vendored node_modules/ during the post-build step. This prevents cross-platform binary mismatches between the CI build environment and the user's machine.
Admin SPA
@notty/admin builds into @notty/server's public/admin/ directory. The admin is a React + Vite SPA served at /admin by Nitro's static route handler with SPA fallback.
Users never build the admin themselves. They get it pre-built inside @notty/server.
Library Packages
All other packages ship dist/ with:
.cjs+.mjsdual format (or ESM-only for CLI tools).d.tstype declarations- No vendored
node_modules
Build tool: tsup with --format cjs,esm --dts.
Publish Contract
Version Policy
During the 0.x pre-release phase:
| Bump | Meaning | Example |
|---|---|---|
Patch (0.16.1) |
Bug fixes, fully backwards compatible | Fix SQLite adapter edge case |
Minor (0.17.0) |
New features, deprecations possible, breaking changes documented with migration path | Add new field type |
Major (1.0.0) |
Stable API contract, breaking changes only in major bumps | GA release |
Version Synchronization
Core runtime packages (@notty/server, @notty/core, @notty/database, @notty/locales, @notty/ui, @notty/admin) share the same version line and must be released together. A change to any one of them bumps all of them.
SDK packages (@notty/plugin-api, @notty/theme-api) may move on an independent version line. They declare compatibility via engines.notty in their manifest.
@notty/create must stay in sync with the core version line. When core packages are bumped, @notty/create must be bumped too, because it uses its own version to resolve dependency versions for generated projects.
Compatibility Matrix
| Notty Version | Node.js | Plugin API | Theme API |
|---|---|---|---|
| 0.16.x | >= 20 | ^0.14.0 | ^0.14.0 |
| 0.15.x | >= 20 | ^0.14.0 | ^0.14.0 |
| 0.14.x | >= 20 | ^0.14.0 | ^0.14.0 |
Package Contents (files field)
Every published package explicitly declares its files field:
| Package | files |
|---|---|
@notty/server |
["dist", ".output", "public"] |
@notty/create |
["dist", "templates"] |
| All other libs | ["dist"] |
No package should ship src/, test/, or monorepo config files.
Exports Contract
Every library package provides conditional exports:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
Additional sub-path exports (e.g., @notty/locales/ru, @notty/sdk/react, @notty/plugin-api/testing) follow the same pattern and are part of the public API contract.
Generated App Skeleton
See Generated App Contract for the full canonical structure.
Key Distribution Properties
No build step required. A generated project runs with
pnpm devimmediately afterpnpm install. The platform is pre-built inside@notty/server. Teams that prefer the developer CLI can invoke the same flow explicitly viapnpm dlx @notty/cli dev.No monorepo artifacts. Generated projects never contain turbo.json, pnpm-workspace.yaml, tsup configs, or other monorepo tooling.
No platform source code. Generated projects consume compiled packages from
node_modules/. They do not copy or transpile platform internals.Starter templates are data, not code. Templates in
@notty/creategenerate schemas (JSON) and thin plugin wrappers (TS/JS). They never generate platform-level logic.Version resolution at scaffold time.
@notty/createqueries the npm registry for the latest published versions of@notty/serverand@notty/core. If the registry is unreachable, it falls back to its own version number.
Platform vs User-Land Separation
Platform Code (owned by @notty/*)
Everything inside node_modules/@notty/:
- Server runtime, admin SPA, database adapters
- Plugin/theme APIs and lifecycle
- Configuration engine and validators
- CLI tooling
This code is immutable from the user's perspective. Users cannot and should not patch it directly.
User-Land Code (owned by the project)
Everything outside node_modules/:
| Zone | Ownership | Upgrade-safe |
|---|---|---|
notty.config.ts |
User-owned | Yes |
schemas/, components/ |
User-owned | Yes |
src/plugins/, src/routes/, src/controllers/, src/services/, src/policies/, src/hooks/, src/middlewares/ |
User-owned | Yes |
public/ |
User-owned | Yes |
migrations/, seeds/ |
User-owned | Yes |
.env, .env.example |
User-owned | Yes |
data/ |
Runtime-owned | N/A (not source) |
.notty/ |
Platform-managed | Regenerated by tooling |
.output/ |
Platform-managed | Regenerated by build |
Boundary Rule
User-land code extends the platform only through supported contracts:
- Schema-driven — JSON schemas in
schemas/andcomponents/ - App-local plugin —
src/plugins/app.tsregistered innotty.config.ts - Reusable plugin/theme — npm packages using
@notty/plugin-apior@notty/theme-api - Configuration —
notty.config.tsand environment variables
There is no "eject" mechanism. If a user needs behavior that cannot be achieved through these contracts, it is a signal that the platform needs a new extension point, not that the user should fork platform internals.
Upgrade Path
Standard Upgrade Flow
1. Read CHANGELOG for target version
2. pnpm update @notty/server @notty/core
3. pnpm dlx @notty/cli doctor
4. pnpm dlx @notty/cli check
5. pnpm dev (auto-syncs database)
6. Test custom routes, hooks, middleware
Upgrade Tooling
Generated apps call these commands through @notty/cli, typically via pnpm dlx @notty/cli <command> (or pnpm exec notty <command> if the CLI is installed locally).
| Command | Purpose |
|---|---|
notty doctor |
Comprehensive project health check: dependency versions, config validity, schema compatibility, file structure |
notty check |
Validate schemas and components against the current runtime version |
notty diagnose |
Detailed diagnostics report for troubleshooting |
notty validate-plugin <path> |
Validate a plugin manifest and structure |
Database Migrations on Upgrade
Notty uses Drizzle ORM with automatic schema synchronization:
- Adding fields — the column is added automatically on next boot
- Removing fields — the column is kept in the database but ignored by the ORM; drop it manually via custom SQL if needed
- Renaming fields — create the new field, migrate data via a custom script, then remove the old field
For complex data migrations, users create scripts in migrations/ and run them before starting the server.
Breaking Change Protocol
When a version introduces breaking changes:
- The CHANGELOG documents what changed and why
notty doctordetects the incompatibility and prints actionable guidance- If the change affects schemas,
notty checkflags affected files - If the change affects config,
notty doctorreports the outdated config shape - Migration guidance is published in the upgrade guide
Rollback
- Stop the server
- Restore previous
package.jsonand lock file from git - Run
pnpm installto restore previous versions - If a database migration has already run, restore from the latest backup
Build Pipeline (Monorepo → npm)
Build Order
@notty/types (tsup)
↓
@notty/module-api (tsup)
↓
@notty/modules (tsup)
↓
@notty/database (tsup)
↓
@notty/core (tsup)
↓
@notty/admin (vite → ../server/public/admin/)
↓
@notty/server (nitro build → postbuild → tsup)
↓
@notty/cli (tsup)
@notty/create (tsup)
@notty/locales (tsup)
@notty/ui (tsup)
@notty/plugin-api (tsup)
@notty/theme-api (tsup)
@notty/sdk (tsup)
@notty/codegen (tsup)
Orchestrated by Turbo with topological dependency resolution.
Server Post-Build
After the Nitro build:
- Remove native modules from
.output/server/node_modules/(better-sqlite3,pg-native) - Build CLI executable via tsup (
dist/cli.js)
This ensures the published package does not ship platform-specific binaries.
Release Pipeline
pnpm changeset— create changeset describing the changepnpm changeset version— apply version bumps to package.json filespnpm release:verify:pre— verify internal dependency consistencypnpm release:readiness— unified quality gategit commit && git push— CI publishes to npm via Changesets Action
Invariants
These invariants must always hold:
A generated project must work without building platform source. All platform code is pre-built and shipped in npm packages.
@notty/serveris self-contained. It includes the Nitro server bundle and the pre-built admin SPA. No other build step is needed.Version sync is enforced. Core runtime packages share a version line. A mismatch between
@notty/serverand@notty/coreversions must produce a startup warning.Native modules are user-side. Database drivers are installed by the user's
pnpm install, not vendored by the platform.User-land files survive upgrades. An
pnpm update @notty/server @notty/coremust never modify files outsidenode_modules/,.notty/, or.output/.No monorepo leakage. Published packages must not reference workspace paths, monorepo-only scripts, or unpublished internal packages.
@notty/createproduces the canonical skeleton. The scaffolder generates exactly the structure defined in the Generated App Contract, nothing more.
Non-Goals
This spec does not cover:
- Internal monorepo development workflow (see CLAUDE.md)
- Admin UI architecture or component design
- Content API specification
- Plugin marketplace infrastructure
- Cloud hosting or managed service offering
Related Documents
| Document | Scope |
|---|---|
| Generated App Contract | Canonical project structure, ownership model, resolution order |
| Configuration Model | 3-tier configuration, env precedence, locking |
| Ecosystem & Packaging | Plugin/theme naming, versioning, publishing conventions |
| Upgrade Guide | Step-by-step upgrade instructions for users |
| Deployment | Production configuration, Docker, security |
Release Workflow (./claude/release-workflow.md в исходном проекте) |
Internal release process (Changesets, CI/CD) |
| Capability Map | Feature matrix with implementation status |
Источник: docs/distribution-model.md. Снимок документации исходного проекта. Технический справочник сохраняет язык оригинала.