Summary#
Every top-level type has a visibility that decides whether code outside its package can name it. A
public type is part of the surface its package offers outward; an internal type is an
implementation detail, usable freely inside its own package — the app, or the kit — and invisible beyond it.
You rarely have to write the modifier, because the default follows what you declared: an entity or an
enum is public, a class is internal. A component and a control are public too — internal is how a package
keeps a helper component or a shim it only uses itself off the surface it offers.
Signature#
entity Product { string Name; } // public — an entity is a data surface
enum Status { Draft, Active } // public — an enum is vocabulary
class Money { public decimal Amount; } // internal — exactly C#
internal entity Ledger { … } // an entity kept inside its package
public class Money { … } // a class offered outward
component Badge(string label) { … } // public — a component is part of its namespace's surface
internal component Chevron() { … } // a helper a package keeps to itself
internal control Gauge { … } // …and the same for a foreign-ABI shimDescription#
The defaults, and why they differ#
| You declare | Default | Why |
|---|---|---|
entity | public | An entity is the app's data surface — its fields are already public, so the type is too. |
enum | public | An enum is vocabulary: it appears as the field type of a public entity, so an internal default would create friction at every use. |
class | internal | Exactly C#. A class stays source-portable — it must lift into an external C# project verbatim, so its rules follow C#'s without deviation. |
component | public | A component is part of what its namespace offers — which is what makes a package's components usable at all. Write internal for a helper you do not want callers to reach. |
control | public | Same, for the same reason: a control's whole surface is its control block, so a package that could not keep a shim in would publish every one it declares. |
An explicit public or internal always overrides the default. private is not a top-level modifier — C# has
no private top-level types, and neither does Osy#.
What visibility controls#
Visibility is a hard access rule, not a decoration. internal means the package — exactly as C#'s means the
assembly. An internal type cannot be named from outside the package that declares it, and using SomeNamespace; of
another package brings only that namespace's public types into scope. This is how a published unit distinguishes
what it offers from what it merely uses.
A package is what package.osy declares, and an app's own source is one package too. So a kit split over several
namespaces shares its internal types, functions and members between them; inside an app, internal does not separate
one namespace from another; and another kit — or an app — that writes a kit's namespace …; line is still outside
it. The same holds for an internal function and an internal member, which is how a kit keeps a call to itself; see
internal — a member only its own package may call.
Because an entity and an enum default to public, a unit that means to keep one to itself must say so:
entity Product { … } // exposed to anyone who imports this namespace
internal entity PriceHistory { … } // an implementation detail — say `internal` explicitlyVisibility and authorization answer DIFFERENT questions#
A component has both, and conflating them is the mistake worth naming. Visibility decides who may reference the
declaration — a compile-time question, about code. Authorization decides who may view the rendered page — a
runtime question, about people: a routed component requires an authenticated caller unless it is marked
[AllowAnonymous], and [Composable] governs whether it may be rendered inside another component's surface.
So internal component does not mean "signed-in only", and [AllowAnonymous] does not make a component referenceable
from another namespace. You will usually write neither: a component is public by default and [Composable]/[Route]
carry the authorization story.
// inside a package
public component Card(string title) { render { … Chevron(); … } } // the surface
internal component Chevron() { render { … } } // its businessAn app that imports that package may render Card; naming Chevron is refused, and so is
One.Kit.Chevron(…) — the qualified spelling reaches a namespace's surface, not past it.
Entity fields are always public#
A visibility modifier on an entity field is an error — the field is the data, so it is always public. A
class member is different: it carries its own public/private member
visibility, and a bare member is private, exactly as in C#.
Examples#
/// A catalog item other code may reference. `entity` is public by default.
entity Product {
[Required] string Name;
decimal Price;
}
/// An internal roll-up used only inside this package — say `internal` to keep it in.
internal entity PriceHistory {
Product Item;
decimal Was;
}
/// A class is internal by default (C#); mark it `public` to offer it outward.
public class Money {
public decimal Amount;
public string Currency;
}See also#
- namespace — the namespace a type lives in.
- constructor — member visibility (
public/private) on class members, the other visibility axis. - class methods — the private-by-default rule as a builder most often hits it: a method with no modifier.
- component — the authorization axis a component also has, and why it is a different question.
- internal — a member only its own package may call —
internalon a function or a member: callable only from its own package. - qualified element (Owner.Name.Element) — naming another namespace's component or control explicitly, and what that cannot reach.