@ossy/app
@ossy/app
Server-side rendering runtime and build tooling for Ossy apps.
For custom setups (Next.js, Vite, etc.), use @ossy/connected-components directly.
Setup
Add @ossy/app, react, react-dom, and @ossy/connected-components (plus its peer deps) to your package.json, then run app build.
{
"scripts": {
"build": "app build",
"start": "node build/server.js"
}
}
Pages
Create *.page.jsx files in src/. Each file becomes a route.
// src/home.page.jsx
export const metadata = {
id: 'home',
title: 'Home',
path: '/',
}
export default function Home({ url }) {
return (
<body>
<h1>Welcome</h1>
<p>Current URL: {url}</p>
</body>
)
}
File → route mapping: home.page.jsx → /, about.page.jsx → /about. The metadata export controls the route id, path, and page title. For multi-language paths:
export const metadata = {
id: 'about',
path: { en: '/about', sv: '/om' },
}
What the framework provides: The build wraps every page automatically with <html>, <head> (including the page title and injected styles), and <App> (providers for theme, router, SDK). Your page component only needs to return the <body> element and its content.
Props: The full app config is passed as props to the page component — url, theme, isAuthenticated, pages, workspaceId, apiUrl, etc. You can also access config via useApp() / useRouter() hooks from @ossy/connected-components and @ossy/router-react.
Build output: Each page produces two self-contained bundles (React included):
build/ssr/<id>.mjs— used by the server for SSR on each requestbuild/public/static/<id>.js— loaded by the browser for hydration
Config
Add src/config.js to set workspace, theme, and API options:
import { CloudLight } from '@ossy/themes'
export default {
workspaceId: 'your-workspace-id',
theme: CloudLight,
apiUrl: 'https://api.ossy.se/api/v0',
}
Optional flags:
| Key | Default | Purpose |
|---|---|---|
enablePushInvalidation |
false |
App-wide SSE cache invalidation. Prefer <PushInvalidationSubscriber /> on specific pages instead — see @ossy/sdk-react README. |
Config is loaded at build time and merged with request-time settings (e.g. user theme preference from cookies).
API routes
Create *.api.js files in src/. Each file exports a metadata object and a default handler function.
// src/health.api.js
export const metadata = {
id: 'health',
path: '/api/health',
}
export default function handle(req, res) {
res.json({ status: 'ok' })
}
The handler receives the raw Express req and res. API routes are matched before page rendering. The router supports dynamic segments (e.g. path: '/api/users/:id').
Build output: build/.ossy/api.generated.json — a registry of [{ id, path, module }] entries. Handlers are lazy-imported on first request.
Background tasks (*.task.js)
Task modules export metadata (id, optional triggers / schedule) and a run function. They are registered by TaskService at server startup.
Sync invoke: POST /actions with { action, payload } awaits the primary task at {feature}/tasks/{intent} derived from the action id.
Async: changestream events and cron schedules dispatch matching tasks without blocking the caller.
Build output includes task entries in the manifest for server registration. Async execution does not use a separate polling worker — see ADR 0003.
Task ids use the canonical form @ossy/{feature}/tasks/{intent}. Action ids (@ossy/{feature}/actions/{intent}) map to their primary task via ADR 0003.
Schemas (*.schema.js)
Resource document schemas are POJOs default-exported from *.schema.js in src/ or installed feature packages. The build discovers them, validates canonical ids (@{provider}/{feature}/schema/{concept}), and merges them into manifest.schemas.
// packages/booking/src/service.schema.js
export default {
id: '@ossy/booking/schema/service',
name: 'Service',
fields: [{ name: 'name', type: 'text', required: true }],
}
Forms reference a schema via metadata.schemaId (not templateId).
Forms (*.form.js)
Form modules export intent metadata only — field definitions live on the schema:
export const metadata = {
id: '@ossy/booking/form/service',
schemaId: '@ossy/booking/schema/service',
}
Build manifest
app build writes build/manifest.json plus derived catalogs:
| Output | Purpose |
|---|---|
manifest.json |
Pages, actions, tasks, schemas, components, layouts, taskCatalog, taskGraphEdges, … |
build/capabilities.json |
Automation UI catalog — actions, tasks, schemas, edges |
build/actions.schema.json |
JSON Schema for agent/MCP tool inputs per action |
Action and task metadata are validated at build time via @ossy/schema. Task triggers reference schema ids (type: '@ossy/platform/schema/file') and lifecycle events (Created, Patched, …).
Components (*.component.jsx)
Register injectable UI via *.component.jsx in src/ or feature packages. Components are bundled separately and resolved into slots at runtime.
App chrome — the app assigns placement in *.layout.jsx:
export const slots = {
'app:header': 'app-header',
'app:sidebar': 'app-sidebar',
}
Resource / input UI — feature packages use metadata.id as the slot key:
export const metadata = { id: '@ossy/booking/form/service' }
export default function ServiceForm() { … }
See docs/component-primitive.md and design-system/docs/SLOTS.md.
Port configuration
The server listens on port 3000 by default.
PORT=4000 node build/server.js
node build/server.js --port 4000
node build/server.js -p 4000