- Every named export MUST have a corresponding
harden(exportName)call immediately after the declaration. This is enforced by the@endo/harden-exportsESLint rule. - Objects returned from factory functions should be hardened:
return harden({ ... }). - Module-level constant data structures (arrays, objects) should be hardened at declaration:
const foo = harden([...]).
- Every
.jssource file must start with// @ts-check. - Use
@param,@returns,@typedef, and@typeannotations throughout. - Import types with the
@importJSDoc tag:/** @import { FarEndoGuest } from '@endo/daemon/src/types.js' */ - Prefer
@importover dynamicimport()in type positions. Use/** @import { Foo } from './bar.js' */at the top of the file instead of inline/** @type {import('./bar.js').Foo} */. - Cast
catcherror variables:/** @type {Error} */ (e).message - Cast untyped inputs from external APIs with inline
/** @type {T} */assertions.
- Use
@endo/errorsfor structured errors:import { makeError, q, X } from '@endo/errors'. - Use
q()to safely quote values in error messages. - Use tagged template errors where appropriate:
throw makeError(X\No formula for ${ref}`)`.
- Group imports: external
@endo/*packages first, then local imports, separated by a blank line. - Sort imports within each group.
- Unconfined guest modules export
make(powers)as their entry point. - Prefer
makeExo()with anM.interface()guard overFar()for remotable objects.makeExoautomatically provides__getMethodNames__(), which CapTP introspection relies on, and enforces method guards at the boundary.Far()is still appropriate for lightweight one-off remotables that do not need runtime type checking. - The
help()method is conventional on capabilities and should return a descriptive string.
- Always use
E(ref).method()for remote/eventual calls, never direct invocation. E()calls return promises; chain withawaitor furtherE()sends.
- Use
E(ref).__getMethodNames__()to discover a remote object's interface rather than duck-typing by calling individual methods. Duck-typing generates noisy failed CapTP calls for each method that does not exist on the target. makeExoobjects provide__getMethodNames__()automatically.
const methods = await E(ref).__getMethodNames__();
if (methods.includes('followNameChanges')) {
// NameHub — live registry
} else if (methods.includes('list')) {
// ReadableTree — immutable snapshot
}- The project uses
plugin:@endo/internalwhich extendsprettier,plugin:@jessie.js/recommended, andplugin:@endo/strict. - This enforces harden-exports, restricts plus operands, and requires PascalCase for interfaces.
- Yarn 4 via corepack:
npx corepack yarn install - Package tests:
cd packages/<name> && npx ava - Daemon integration tests:
cd packages/daemon && npx ava test/endo.test.js --timeout=120s - Syntax check without SES runtime:
node --check <file.js> - Full module loading requires the Endo daemon (SES lockdown provides
hardenas a global).
- Unit tests:
npx ava packages/daemon/test/gateway.test.js packages/daemon/test/formula-identifier.test.js --timeout=90s - Build:
cd packages/familiar && yarn bundle && yarn package - Lint:
cd packages/familiar && yarn lint - Gateway tests fork a full daemon per test. They must be
test.serialto avoid resource contention. - Tests set
ENDO_ADDR=127.0.0.1:0so the gateway binds to an OS-assigned port, avoiding conflicts with a running daemon on the default port 8920. - Kill leftover daemon processes between test runs:
pkill -f "daemon-node.*packages/daemon/tmp"andrm -rf packages/daemon/tmp/. - Worker logs are in
packages/daemon/tmp/<test>/state/worker/<id>/worker.log— check these when the APPS formula hangs silently.
- The Electron main process must never import
@endo/initorses. SES lockdown freezes Electron internals. - Unconfined plugins (e.g.,
web-server-node.js) run inside an already-locked-down worker and must not importsesor@endo/initthemselves; doing so causes double-lockdown errors. - Electron Forge requires
electronindevDependenciesto detect the version. If it's only independencies, packaging fails with "Could not find any Electron packages in devDependencies". - Port 0 (OS-assigned) is falsy in JavaScript. Use
port !== '' ? Number(port) : defaultinstead ofNumber(port) || default.
- Wrap lines at 80 to 100 columns.
- Start each sentence on a new line so that diffs are per-sentence.
- See
CONTRIBUTING.md§ "Markdown Style Guide" for full details.
- Monorepo with
packages/workspace layout. - Workspace dependencies use
"workspace:^"version specifiers. - Each package has its own
tsconfig.jsonandtsconfig.build.json. - No copyright headers in source files; license is declared in
package.json.