Build a realm
Run synth from a checkout of the embabel-ts repository:
# Scaffold package.json, tsconfig.json, README.md, realm.ts and the handler file.bun packages/realm-synth/src/cli.ts init my-realm --runtime typescript
# Write the specification files, and the handler types.bun packages/realm-synth/src/cli.ts my-realm/realm.ts --out my-realm/dist \ --types my-realm/.embabel/realm.d.ts
# Re-run on every change.bun packages/realm-synth/src/cli.ts my-realm/realm.ts --out my-realm/dist \ --types my-realm/.embabel/realm.d.ts --watchinit <dir>refuses a directory that is not empty.--runtimeistypescript(the default),pythonorcompiled. The scaffoldedrealm.tsis a conventional realm written withsatisfies Realm. For a captured realm, addexecution: "captured"and wrap the object indefineRealm.- With neither
--outnor--types, synth prints every file it would write and writes nothing. --typespicks its language from the realm’sruntime: a.d.tsor.tspath for TypeScript,.pyor.pyifor Python, and.rsforcompiled. Any other pairing is refused. There is no types emitter forrubyorlua.--watchrebuilds in a fresh process on every change under the definition’s directory. It ignores.embabel/,dist/,node_modules/, dotfiles, the--outdirectory and the--typesfile. A failed rebuild prints its message and keeps watching.--profileand the--app-*flags are described under app limits and profiles.- A refusal prints
synth failed: <message>and exits with status 1. A usage error prints the reason and the usage text and exits with status 2. Usage errors are an unknown flag, a flag with no value, a second definition file, a size or count synth cannot read, an unknown profile name, and a profile file that is missing, is not JSON or does not follow the profile format. - A flag that takes a value always takes the next argument, whatever it looks like:
--out --typeswrites to a directory named--types. The one argument that is not a flag or a flag’s value is the definition file. - Warnings, such as a deprecated
tokenEnv, an undeclared goal type or a feature the chosen profile does not load, go to standard error and do not stop the build.
Synth clears the output directory and writes it again on every run, so keep nothing by hand in
it. It sorts every key, so the same realm.ts always gives the same bytes. It refuses a value
with no file form: a function, a symbol, a Date, a BigInt, a Map or Set, a non-finite
number, a circular reference, or undefined in an array. An object key whose value is
undefined is left out. The text fields of goals, triggers, producers, lenses, watches, data
pipes, view references, apps, credentials and API entries refuse control characters, a newline
included, and unpaired surrogates.
Synth copies the handler entry, every file the entry reaches through a relative value import (type-only imports are skipped), dependency init scripts, a file-path icon, app pages and app resources into the output. It refuses any of them that is missing or that resolves outside the realm directory, including through a symlink, and it refuses a copy or a vendored document that would land on a path it already writes.
What lands where:
| You write | Synth writes |
|---|---|
name, version, host, description, author, url, icon, tags, exports, retry |
realm.yml |
handlers, entry, generatedAt |
dist/manifest.json |
credentials |
credentials.yml |
apis |
apis/apis.yml and each vendored document under apis/ |
channels, sources, consumers |
channels/<name>.yml, sources/<name>.yml, consumers/<name>.yml |
dataPipes |
data-pipes.yml |
triggers |
triggers/<name>.yml |
dependencies, layers, a non-TypeScript runtime |
dependencies/manifest.json |
types |
types/<realm name>.yml |
producers |
producers/<name>.yml |
queries |
queries/references.yml |
goals |
goals/<name>.yml |
capturedLenses |
lenses/<name>.yml |
watches |
watches/<name>.yml |
apps |
apps/<page>.app.json, the page and its resources |
actions, focuses, webhooks, events |
actions/<name>.yml, focuses/<name>.yml, webhooks/<realm name>.yml, events/<name>.yml |
The last row is the conventional form of those fields, and synth writes it for a captured realm
as well. With admission on, the reference host does not load a captured realm’s
actions/, events/ or webhooks/, and reports each of those kinds once as a loading problem
(see the goal profile and
legacy event declarations). Leave them out.
--profile reference-wasm warns about each one.
Each captured directory (goals/, triggers/, producers/, lenses/, watches/) holds at most
32 files, 8 KiB each and 64 KiB together. The host refuses the whole directory past those limits,
so synth refuses first. Every captured file is stamped version: 1, and the name a file carries
comes from its key in realm.ts: goal in a goal, trigger in a trigger, name in a producer
or watch, id in a lens. You never write version, name, goal, trigger or id yourself;
the types make them compile errors, and synth writes its own values over them.
Wrap the realm in defineRealm. It is what makes every namespace.verb you write elsewhere a
checked name. Under defineRealm, a goal, producer or dataPipes consumer naming a handler the
realm does not declare is a compile error on that line. A lens declared through watching, or a
channel or consumer declared through connecting, is checked too; that error is reported on the
defineRealm argument and names the allowed handlers. An app’s handlers list is not checked at
compile time. Written with satisfies Realm, none of these names are checked by the compiler.
Synth checks every one of these names as well, whichever way the realm is written. It refuses a
goal, producer, dataPipes consumer, lens, channel, keepalive, connecting consumer or app that
names a handler the realm does not declare, since the host dispatches only to handlers the
realm’s manifest registers. A realm with no handlers block declares no handlers, so any handler
name anywhere in it is refused.

