diff --git a/README.md b/README.md index cd30484..1425288 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,10 @@ npx matrix broker stop Expected result: the actor run command prints JSON with `ok: true` and the `echo` result from `GreeterActor`. +Handler names are part of the Matrix actor contract: `static accepts = { echo: +{} }` maps to `onEcho(payload)`. Do not implement `echo(payload)` directly; the +CLI rejects that shape before it connects to the broker. + If you need the HiveCast browser proof and `pnpm doctor:hivecast` reports missing Chromium: diff --git a/packages/cli/README.md b/packages/cli/README.md index a3b8ffd..22e27d3 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -25,6 +25,10 @@ matrix config set registry https://registry.npmjs.org/ matrix registry doctor ``` +Actor handlers use Matrix handler naming: an accepted op named `echo` is +implemented as `onEcho(payload)`, not `echo(payload)`. `matrix actor run` +validates this before connecting to the broker. + Direct runner credentials are resolved from explicit flags, environment variables, package-local `.matrix`, and `~/.matrix`: diff --git a/packages/cli/package.json b/packages/cli/package.json index b7d397e..163677d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@open-matrix/cli", - "version": "0.1.4", + "version": "0.1.5", "description": "Matrix package-author CLI.", "type": "module", "bin": { diff --git a/packages/cli/src/commands/actor-run.ts b/packages/cli/src/commands/actor-run.ts index 858997f..5d51bc7 100644 --- a/packages/cli/src/commands/actor-run.ts +++ b/packages/cli/src/commands/actor-run.ts @@ -4,6 +4,7 @@ import * as path from 'node:path'; import { pathToFileURL } from 'node:url'; import type { MatrixActor } from '@open-matrix/core/core/MatrixActor.js'; +import { toHandlerCase } from '@open-matrix/core/engine/utils/NameUtils'; import { RequestReply } from '@open-matrix/core/engine/remoting/RequestReply.js'; import { MatrixRuntime } from '@open-matrix/core/runtime/MatrixRuntime.js'; import { NatsTransport } from '@open-matrix/core/transport/NatsTransport.js'; @@ -77,6 +78,7 @@ export async function actorRunCommand( } const exportName = options.export?.trim() || 'default'; const ActorClass = await loadActorConstructor(entryPath, exportName); + validateActorHandlers(ActorClass); const mount = options.mount?.trim() || inferMountFromEntry(entryPath); if (!mount) { throw new Error('matrix actor run requires --mount when it cannot infer a mount from the entry file'); @@ -358,6 +360,28 @@ export async function loadActorConstructor(entryPath: string, exportName: string throw new Error(`No actor class found as export "${exportName}". Available exports: ${available.join(', ') || '(none)'}`); } +export function validateActorHandlers(ActorClass: ActorConstructor): void { + const accepts = ActorClass.accepts; + if (!accepts || typeof accepts !== 'object' || Array.isArray(accepts)) { + return; + } + + const prototype = ActorClass.prototype as Record; + for (const op of Object.keys(accepts)) { + const handlerName = `on${toHandlerCase(op)}`; + if (typeof prototype[handlerName] === 'function') { + continue; + } + const directHandler = prototype[op]; + const directHint = typeof directHandler === 'function' + ? ` Found ${op}(payload); Matrix actor handlers use ${handlerName}(payload) naming.` + : ''; + throw new Error( + `MATRIX_ACTOR_HANDLER_MISSING: ${ActorClass.name} declares static accepts.${op} but does not define ${handlerName}(payload).${directHint}`, + ); + } +} + async function loadPackageBootstrap(entryPath: string, exportName: string): Promise { const moduleUrl = `${pathToFileURL(entryPath).href}?matrix_package_run=${Date.now()}-${Math.random().toString(16).slice(2)}`; const mod = await import(moduleUrl) as Record; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 066f386..338b96d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -48,7 +48,7 @@ export function createProgram(): Command { program .name('matrix') .description('Matrix SDK CLI for package authors') - .version('0.1.4'); + .version('0.1.5'); const broker = program .command('broker') diff --git a/packages/cli/tests/unit/actor-run.spec.ts b/packages/cli/tests/unit/actor-run.spec.ts index 4a00ab3..cfb3d14 100644 --- a/packages/cli/tests/unit/actor-run.spec.ts +++ b/packages/cli/tests/unit/actor-run.spec.ts @@ -8,6 +8,7 @@ import { loadActorConstructor, resolveActorRunBroker, resolvePackageRunSpec, + validateActorHandlers, } from '../../src/commands/actor-run.js'; describe('matrix actor run', () => { @@ -64,6 +65,18 @@ describe('matrix actor run', () => { } }); + it('rejects accepts ops without onXxx handlers before broker connection', async () => { + class BadActor { + static accepts = { echo: { description: 'Echo a message' } }; + echo(): void {} + } + + assert.throws( + () => validateActorHandlers(BadActor as any), + /MATRIX_ACTOR_HANDLER_MISSING: BadActor declares static accepts\.echo but does not define onEcho\(payload\).*Found echo\(payload\)/u, + ); + }); + it('resolves broker URL, root, JWT, and seed from an explicit home without Host state', () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-actor-run-broker-')); try {