Generated App Contract
Техническое руководство из исходного проекта Notty. Примеры, параметры и эксплуатационные ограничения.
Status: accepted roadmap spec
Purpose: define the canonical Notty-native contract for generated apps, their ownership model, and the resolution order between core, plugins, schema-driven API, and user-land code.
See also: Distribution Model for the end-to-end delivery pipeline (package architecture, publish contract, upgrade path).
Why This Exists
Notty should be delivered as an installable product, not as a copy of the monorepo source tree.
The generated project must be:
- installable from published packages
- upgrade-friendly
- schema-first for common cases
- extensible with code for advanced cases
- cleaner and more explicit than a direct Strapi clone
This document defines the target contract for generated Notty apps. Implementation may temporarily lag behind this contract, but the contract itself is the source of truth.
Core Principles
schema-first by defaultContent modeling, standard CRUD, and most delivery behavior should come from schemas and admin tooling.code-level escape hatch by designAdvanced users must be able to add custom routes, controllers, services, policies, middlewares, hooks, and app-local plugins inside their own project.no monorepo-source distributionGenerated apps consume published Notty packages and prebuilt runtime artifacts. They must not depend on copying platform internals.clear ownership boundariesUser-owned code and platform-managed artifacts must live in different zones.additive by default, explicit override onlyExtensions should compose by default. Any override of existing behavior must be declared intentionally and validated at startup.
Canonical Project Structure
my-notty-app/
├── notty.config.ts
├── package.json
├── .env
├── .env.example
├── tsconfig.json
├── config/
├── schemas/
├── src/
│ ├── plugins/
│ │ └── app.ts
│ ├── routes/
│ │ └── index.ts
│ ├── controllers/
│ ├── services/
│ │ └── index.ts
│ ├── policies/
│ │ └── index.ts
│ ├── hooks/
│ │ └── index.ts
│ └── middlewares/
│ └── index.ts
├── public/
├── data/
├── migrations/
├── seeds/
├── .notty/
└── .output/
Directory Contract
| Path | Ownership | Purpose |
|---|---|---|
notty.config.ts |
user-owned | Main project configuration; generated apps register src/plugins/app.ts here |
config/ |
user-owned | Additional project config modules |
schemas/ |
user-owned | Content type schemas and schema-level customization |
src/plugins/app.ts |
user-owned | Canonical app-local server extension entrypoint for generated apps |
src/plugins/ |
user-owned | Additional app-local plugins that the project registers explicitly in notty.config.ts |
src/routes/ |
user-owned | Route registration modules imported by src/plugins/app.ts |
src/controllers/ |
user-owned | Supporting controller/handler modules imported by routes and services; not auto-loaded |
src/services/ |
user-owned | Service registration modules imported by src/plugins/app.ts |
src/policies/ |
user-owned | Policy registration modules imported by src/plugins/app.ts |
src/hooks/ |
user-owned | Hook registration modules imported by src/plugins/app.ts |
src/middlewares/ |
user-owned | Middleware registration modules imported by src/plugins/app.ts |
public/ |
user-owned | Static public assets |
data/ |
runtime-owned | Local runtime data such as SQLite database and uploads in local mode |
migrations/ |
user-owned | Database migrations |
seeds/ |
user-owned | Seed scenarios |
.notty/ |
platform-managed | Generated metadata, cache, internal state, future codegen output |
.output/ |
platform-managed | Nitro build output |
Ownership Model
User-Owned
These files and directories are expected to be edited by the project owner:
notty.config.tsconfig/**schemas/**src/**public/**migrations/**seeds/**
Runtime-Owned
These paths exist in the generated app but are not treated as source code:
data/**
They may be created or mutated during runtime and should usually stay out of version control.
Platform-Managed
These paths must not be manually edited:
.notty/**.output/**
Tooling may regenerate them. Their shape can change across versions without being a public authoring contract.
Supported Extension Model
Notty supports three extension styles:
1. Schema-Driven Extension
Use when you need:
- content models
- field definitions
- relations
- components and dynamic zones
- default CRUD and delivery behavior
This should cover most product scenarios.
2. App-Local Code Extension
Use when you need:
- custom endpoints
- non-standard business logic
- orchestration between multiple entities
- custom access checks
- custom request pipeline behavior
This is the official escape hatch for advanced use cases.
3. Reusable Plugin Extension
Use when functionality should be shared between multiple projects or distributed as a plugin.
Generated App Binding Semantics
Generated apps do not rely on implicit filesystem magic for server extensions.
The canonical binding contract is:
notty.config.tsimportssrc/plugins/app.tsnotty.config.tsregisters that module inpluginsas a runtime registration with explicit app-local metadata (source,rootDir,extensionSource: 'app-local')src/plugins/app.tsis a regular runtime plugin defined with the public plugin APIsrc/plugins/app.tsimports project modules fromsrc/routes,src/services,src/policies,src/hooks, andsrc/middlewares- those modules register behavior through the same
PluginContextregistrars used by reusable plugins
This means:
src/routes/index.tsexports route registration codesrc/services/index.tsexports service registration codesrc/policies/index.tsexports policy registration codesrc/hooks/index.tsexports hook registration codesrc/middlewares/index.tsexports middleware registration codesrc/controllers/**contains ordinary support modules imported by route/service modules
For generated apps, this explicit app-local plugin entrypoint is the source of truth.
Legacy root-level middlewares/ loading remains a compatibility path for older projects, not the canonical generated-app contract.
Resolution Order
The target resolution order is:
core built-insand system schemas- installed npm plugins
- app-local plugins
- project schemas
- schema-generated CRUD and delivery contracts
- user-land hooks, middlewares, policies, services, controllers, and routes
This order does not mean silent shadowing is allowed.
For generated apps, notty.config.ts should keep the generated appPlugin entry after reusable plugins so project-specific registrations apply last inside the supported extension surface.
Override and Collision Rules
These rules are mandatory for the target contract:
additive by defaultNew routes, services, hooks, middlewares, schemas, and plugin contributions should compose without replacing existing behavior.silent implicit overrides are forbiddenA later-loaded route or service must not quietly replace an earlier one just because of file order or registration order.collisions fail fastIf two layers define the same contract surface and no explicit override mechanism is declared, startup must fail with a diagnostic error.explicit override onlyOverriding existing behavior must use an explicit contract, for example an override flag, extension API, or manifest declaration.platform internals are not extension pointsUser-land code must extend the platform only through supported contracts, not by editing hidden generated files or internal build output.
Precedence Model
The platform classifies every contribution by its extension source — a trusted layer that determines override precedence. The canonical signal comes from runtime registration metadata; reserved names such as @notty/app-local are not sufficient on their own without the matching runtime registration source.
| Source | Precedence | Runtime Signal | Mutable? |
|---|---|---|---|
core |
0 (highest protection) | Platform-owned runtime only; reserved name @notty/core |
NO — immutable |
plugin |
1 | Default runtime plugin registration | YES — with explicit override from equal or later layer |
app-local |
2 (lowest protection) | Project-owned runtime registration marked as app-local; reserved generated entrypoint @notty/app-local |
YES — with explicit override from equal layer |
Override Rules Per Registry
| Registry | Collision Behavior | Override Support |
|---|---|---|
| Routes | Same method+path within same plugin → error. Route namespace slug collisions across different plugins fail fast at registration/startup. | N/A — routes are namespaced and slug-validated |
| Services | Duplicate name → error unless { override: true } |
Yes — register(name, impl, { override: true }). Core services cannot be overridden. |
| Policies | Duplicate name → error unless { override: true } |
Yes — register(name, handler, { override: true }). Core policies (isAuthenticated, isAdmin, isSuperAdmin) cannot be overridden. |
| Hooks | Multiple handlers compose additively | N/A — hooks never collide |
| Middleware | Namespaced as pluginName:middlewareName, compose with order field |
N/A — middleware is namespaced |
| Data Layer | Duplicate singularName / uid → error |
No override — schema contributions are unique |
Override Direction
Override flows forward (from earlier-loaded to later-loaded):
app-local(2) can overrideplugin(1) — with{ override: true }plugin(1) can override anotherplugin(1) — with{ override: true }plugin(1) cannot overrideapp-local(2) — reverse direction blocked- Nobody can override
core(0) — immutable
Implementation
Types and utilities are exported from @notty/types:
ExtensionSource—'core' | 'plugin' | 'app-local'EXTENSION_PRECEDENCE—{ core: 0, plugin: 1, 'app-local': 2 }classifyPluginSource(pluginName, { declaredSource? })— resolves precedence classification, preferring trusted runtime metadata when availableRegisterServiceOptions—{ override?: boolean }RegisterPolicyOptions—{ description?: string; override?: boolean }
Enforcement is in ServiceRegistry and PolicyRegistry in @notty/core.
What User-Land Code May Do
User-land code may:
- add custom routes through
src/plugins/app.tsandsrc/routes/** - add custom controllers and services through
src/controllers/**andsrc/services/** - add app-specific policies, hooks, and middlewares through
src/policies/**,src/hooks/**, andsrc/middlewares/** - add additional app-local plugins via
src/plugins/**and explicitnotty.config.tsruntime registrations that mark them as app-local - extend schema-derived behavior through documented extension APIs
User-land code may not:
- mutate platform-managed internals inside
.notty/ - depend on
.output/as an authoring surface - rely on undocumented ordering side effects
- patch platform packages directly inside the generated app
Expectations For @notty/create
@notty/create must:
- generate the canonical structure defined in this document
- create only supported public authoring surfaces
- keep platform-managed zones isolated
- prepare a clean
.gitignorefor runtime-generated paths - scaffold an upgrade-friendly project skeleton
- avoid leaking monorepo implementation details into the generated app
@notty/create must not:
- dump platform source code into the project
- generate undocumented folders as public authoring surfaces
- blur the line between user-owned and platform-managed files
Upgrade-Friendly Boundaries
To preserve a maintainable upgrade path:
- published packages own platform behavior
- generated apps own project behavior
- generated internals must remain replaceable by tooling
- user modifications must stay inside documented user-owned zones
If an upgrade requires touching user-owned files, the change must be surfaced through explicit migration guidance, not hidden regeneration.
Non-Goals
This contract intentionally does not try to:
- reproduce Strapi folder layout one-to-one
- expose every internal platform layer as public API
- make admin-generated logic the only customization path
- allow undocumented override magic based on registration order
Implementation Status
| Requirement | Status | Notes |
|---|---|---|
@notty/create generates canonical structure |
Done | All ownership zones are correctly isolated |
Ownership manifest (.notty/ownership.json) |
Done | Machine-readable manifest generated at scaffold time |
| README includes file ownership section | Done | Human-readable ownership policy in generated README |
@notty/cli upgrade command |
Done | Safe upgrade of @notty/* packages via pnpm dlx @notty/cli upgrade, respects ownership boundaries |
notty doctor health check |
Done | Validates project structure post-upgrade |
| Extensibility APIs | Done | Plugin, service, policy, hook, middleware registries |
| Startup validation | Partial | Override/collision detection is implemented; schema validation at startup is roadmap |
Implementation Follow-Up
Remaining items to reflect this spec:
- docs for middleware, plugins, and project structure
- migration tooling for breaking changes across major versions
Источник: docs/generated-app-contract.md. Снимок документации исходного проекта. Технический справочник сохраняет язык оригинала.