Osy#betaRun it on Osyrin: osyrin.com

Reference / Project

app.osy

app <Name> { model "model/**/*.osy"; tests "tests/**/*.osy"; use <Capability>; }

The manifest at the root of every project. It names the app, says which files are model, seed, migrations and tests, and declares the capabilities the app depends on with use.

stable2 examples compiled by CIprojectmanifestcapability

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.

It declares nothing ELSE: an app setting (app.DefaultCulture = …;) and the app's time zones and cultures (zones { … }, cultures { … }) are declarations, and go in a model file — see Declaring the time zones and cultures an app uses — zones { } and cultures { }.

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:

RoleWhat the files holdWhat the platform does with them
modelentities, functions, security, UIcompiled and deployed
seedthe data the app needs to exist at allcompiled and deployed
migrationsexplicit schema and data migrationsfound here, then passed to a deploy with --migration — a *.migration is not compiled with your model
tests[Test] functionsrun, 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 Osysharp.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 package that arrives by itself, and keeping one out#

A bridge — a package that completes two your app uses, like the privacy kit's holder for conversations — arrives without a use line: osy lock finds it, pins it and prints + … included: completes A + B. To keep one out, name it with without in the manifest: without Osysharp.Privacy.Conversations;. See Bridges — a package that arrives when an app uses the two it completes.

tests use — a dependency only your tests load#

Some packages exist for tests: a deterministic embedder, a fake carrier, a fixed clock. Put tests in front of the use line and the package is pinned in osyrin.lock like any other, loaded by osy test, and left out of every other build — the app you compile and deploy does not contain it, and model code cannot reach it:

app Desk {
  model "model/**/*.osy";
  tests "tests/**/*.test.osy";

  use Osysharp.Memory;
  tests use Osysharp.Memory.Testing@0;   // only `osy test` loads this
}

Your test files import its names with using as usual.

minPlatform — the oldest platform it runs on#

minPlatform "0.9.16"; names the oldest platform release the app may be built and run on. osy init writes it and osy compile/osy deploy keep it at your CLI's release — see minPlatform — the oldest platform an app runs on.

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.

Two shapes of one app#

A project is its manifest's globs — so a second manifest beside app.osy is a second app built from the same sources, selecting a subset of them. That is how one tree ships a full application and a cut-down one without a second copy of the model to drift against.

app Notes {
  model "model/core/**/*.osy";   // app.osy globs model/**, this one only model/core/**
}

Build it by naming it, where a verb takes a project path:

osy validate app-selfhost.osy

Two rules keep this explicit rather than surprising:

  • A directory always resolves to app.osy, never to anything beside it. osy validate . builds the full app whether or not a second manifest exists, so no project gains one by accident.
  • A named file is a manifest only if it declares an app { } block, and that is parsed, not pattern-matched. An ordinary model file that merely mentions app in a comment or a string is not a manifest.

Both manifests may carry the same app name — they are two shapes of one app, not two apps — and each resolves its globs against the directory it sits in, so they see the same tree. Omit tests from one and it carries no tests.

See also#

Related

project layout

The shape of an Osy# project — a manifest at the root, and folders for model, seed, migrations and tests. What you put…

use

Declares a capability your app depends on, written inside the `app { }` manifest block. It provisions the capability…

entity

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id from the…

Running tests locally

Runs your app's tests against a Platform on your own machine — no account, no network, no setup beyond a running local…