Skip to content

Build a realm

Run synth from a checkout of the embabel-ts repository:

Terminal window
# 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 --watch
  • init <dir> refuses a directory that is not empty. --runtime is typescript (the default), python or compiled. The scaffolded realm.ts is a conventional realm written with satisfies Realm. For a captured realm, add execution: "captured" and wrap the object in defineRealm.
  • With neither --out nor --types, synth prints every file it would write and writes nothing.
  • --types picks its language from the realm’s runtime: a .d.ts or .ts path for TypeScript, .py or .pyi for Python, and .rs for compiled. Any other pairing is refused. There is no types emitter for ruby or lua.
  • --watch rebuilds in a fresh process on every change under the definition’s directory. It ignores .embabel/, dist/, node_modules/, dotfiles, the --out directory and the --types file. A failed rebuild prints its message and keeps watching.
  • --profile and 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 --types writes 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.