Summary#
app.osy is the manifest at the root of a project. It names the app, says which files play which role, and declares
the capabilities the app depends on. It is the first file to read in an unfamiliar project — it tells you what the
app is made of before you open a single model file.
Signature#
app <Name> {
model "model/**/*.osy"; // entities, functions, security, UI
seed "seed/**/*.osy"; // data the app needs in order to exist
migrations "migrations/**/*.migration"; // explicit schema + data migrations — NOT .osy: a migration is a
// generated artefact in its own grammar, so no *.osy glob reaches one
tests "tests/**/*.osy"; // [Test] functions — run, never deployed
use <Capability>; // a platform capability this app depends on
}Description#
The source roles#
Each role is a glob. They are not decoration — they decide what happens to the file:
| Role | What the files hold | What the platform does with them |
|---|---|---|
model | entities, functions, security, UI | compiled and deployed |
seed | the data the app needs to exist at all | compiled and deployed |
migrations | explicit schema and data migrations | found here, then passed to a deploy with --migration — a *.migration is not compiled with your model |
tests | [Test] functions | run, never deployed |
Omit a role and the conventional folder is used, so a manifest can be very short. Being explicit costs one line and tells the next reader exactly where things live.
A file belongs to one role#
Two globs can reach the same file. In a flat project — every .osy in the project root, no model/ folder — the
obvious manifest does it to every test:
```osy title="a flat project: *.osy also matches *.test.osy" syntax
app Shop {
model ".osy";
tests ".test.osy"; // every file here is ALSO matched by the model glob above
}
This works, and the narrower glob wins: **where one role's files are a subset of another's, the shared files belong
to the narrower role**. Above, `checkout.test.osy` is a test and nothing else — it is not compiled into the app and
never reaches a deploy.
The rule is about which set is smaller, not about which role is called `tests`, so it reads the same way round:
`model "model.osy"; tests "*.osy";` gives `model.osy` to `model`.
When neither role's files contain the other's, there is no narrower one to prefer and the manifest is **rejected**,
naming both roles and the files. Narrow one of the globs so each file is claimed once — guessing on your behalf is
how a test quietly becomes part of the app.
### `use` declares a dependency {#use}
`use` opts the app into a **capability** — a piece of platform surface that is not on by default, like outbound HTTP
or searchable text. It belongs in the manifest, because it is a fact about the *application*, not about one file:
```osy title="an app that makes outbound HTTP calls" test app=project-manifest
app Shop {
model "model/**/*.osy";
tests "tests/**/*.osy";
use Osyrin.Http; // now Http.Get / Http.Post exist for this app
}
string Ping(string url) {
var r = Http.Get(url);
return r.IsSuccess ? r.Body : "";
}Without the use, Http.Get is not a thing the app can call, and the compiler says so. That is deliberate: an
application's ability to reach the outside world should be a line you can point at, not an accident of an import.
Do not confuse use with using. use (manifest) declares the dependency; using (a file) imports its names into
that file. A version pin belongs on the use. See use.
A minimal manifest#
app Notes {
model "model/**/*.osy";
tests "tests/**/*.osy";
}
entity Note {
[Required] string Title;
}That is a complete application: it has a name, a model, and tests. Everything else is added when you need it.
See also#
- project layout — the folders the manifest's globs point at
- use —
usevsusing, and version pins - Compiling your app —
osy compile, which reads this manifest