fix(cli): validate actor handlers before broker connect
This commit is contained in:
parent
42b286e4f7
commit
f475f156d0
@ -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:
|
||||
|
||||
|
||||
@ -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`:
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@open-matrix/cli",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "Matrix package-author CLI.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@ -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 <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<string, unknown>;
|
||||
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<PackageBootstrapFunction> {
|
||||
const moduleUrl = `${pathToFileURL(entryPath).href}?matrix_package_run=${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
const mod = await import(moduleUrl) as Record<string, unknown>;
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user