@studiomorgana/sdk
v0.6.5
Published
The Morgana framework developer interface — types-first: contexts, object handles, contracts, and configs.
Maintainers
Readme
@studiomorgana/sdk
Start here: the Morgana guide explains the model;
npx morgana speclists the full API this package exposes, generated from its own types.
The Morgana developer interface — types first.
This package is almost entirely types. The runtime exports are factories that
return what you gave them, plus the token and role catalogues, so action code can
import them and get real behaviour at build time without carrying a runtime
dependency into every project.
npm i @studiomorgana/sdkWhat you import
import {
defineConfig,
defineClientAction,
defineServerAction,
} from '@studiomorgana/sdk'defineConfig
The project config, read from morgana.config.ts:
import { defineConfig } from '@studiomorgana/sdk'
export default defineConfig({
name: 'my-app',
entry: 'home',
minify: false,
vars: {
public: { siteName: 'My App' },
},
hooks: [{ on: 'compile', run: ['hello'] }],
})defineClientAction and defineServerAction
An action is a module exporting a handler, and what it exports decides where
it runs. config.on picks the lane:
| config.on | Runs |
|---|---|
| 'compile' | at build time, in Node |
| 'http.post /v1/x', 'cron(0 3 * * *)', 'store.x.created', 'object:room.message' | on the server, deployed |
| 'save.clicked', 'loaded', … | in the browser |
Every valid on: npx morgana spec events.
import { defineClientAction } from '@studiomorgana/sdk'
export const hello = defineClientAction({
config: { on: 'compile' },
handler: (ctx) => {
ctx.apps.create('main', { displayName: 'Acme' })
const page = ctx.ui.pages.create({ name: 'home', address: '/' })
page.app('main')
page.place(ctx.ui.text({ id: 'title', content: 'Hello', font: 'h2' }))
return { ok: true }
},
})A compile action is not a build script — it receives the same ctx a server
action does and mutates the same page representation, which is why there is no
separate routing or templating layer to learn.
@studiomorgana/sdk/generator
Build-time helpers for generating a type registry from your action files:
import {
resolveActionsDirs,
generateRegistry,
watchRegistry,
} from '@studiomorgana/sdk/generator'Used by the compiler; rarely imported directly.
Why the package ships its src/
@studiomorgana/sdk-compile resolves the SDK's TypeScript source, not the built
bundle, so that an action importing a real value gets the genuine implementation
rather than a stand-in. That is why src/ is in files. A tarball without it
fails with "cannot locate the @studiomorgana/sdk source entry" while the package is
plainly installed.
Animating objects
Two layers, split by lane. make() words (make('fade-in')) declare motion
at compile time and bake it into the stylesheet. handle.animate drives
motion at runtime, from a browser action:
// Typed presets — autocomplete knows every one.
await card.animate.slideOut({ to: 'right', distance: 6, duration: 200 })
await ctx.actions.run('deleteTask', { id })
// The make() words by name, for the same vocabulary in both lanes.
card.animate('pulse', { duration: 400 })
// Raw keyframes straight through — the escape hatch.
await card.animate.to(
[{ transform: 'rotate(0)' }, { transform: 'rotate(180deg)' }],
{ duration: 500, easing: 'ease-in-out' },
)Every call returns a promise that resolves when the run finishes (or is
skipped), and every run emits <id>.animated with { animation, cancelled }
— so config: { on: 'card.animated' } chains after motion. Distances are
Dim: numbers are grid units, strings are named measures, no px in author
code. prefers-reduced-motion resolves immediately without moving anything.
In the compile lane animate is accepted and ignored — declaration-time
motion is make() words.
Licence
Apache-2.0
