-
Notifications
You must be signed in to change notification settings - Fork 0
Cross Plugin Communication
@shamoo/communication defines platform-neutral, semver-versioned services, events, codecs, remote procedures, reload policy, and transport errors. Contracts use copied engine-neutral data; live JVM objects and JavaScript objects owned by another isolate never cross the boundary.
import {
defineServiceContract,
ServiceRegistry,
} from '@shamoo/communication';
interface Economy {
balance(playerId: string): Promise<number>;
}
const economy = defineServiceContract<Economy>({
id: 'example/economy',
version: '2.1.0',
methods: ['balance'],
});
const registry = new ServiceRegistry();
const provider = registry.register(economy, {
async balance(): Promise<number> {
return 0;
},
});
const service = registry.service(economy, '^2.0.0');
console.info(await service.balance('player-uuid'));
provider.close();A consumer proxy is stable but resolves the highest compatible active provider on every invocation. Provider replacement therefore does not leave a stale object reference. No compatible ready provider produces ServiceUnavailableError in TypeScript or ServiceUnavailableException in Runtime. Generation-aware registration handles cannot remove a newer replacement when an old handle closes.
Runtime services acquire an invocation lease on the provider generation. Draining, disabled, missing, or incompatible providers reject calls. Providers, proxies, and calls are generation-owned resources. Dependent policy is explicit:
- TypeScript runtime policies are
none,direct, andtransitivewhen planning local reload closures. - Deployed metadata uses
keep-runningorreload, matching the Java Runtime acquisition policy.
import {
defineEventContract,
jsonCodec,
VersionedEventBus,
} from '@shamoo/communication';
interface BalanceChanged {
readonly playerId: string;
readonly balance: number;
}
const balanceChanged = defineEventContract({
id: 'example/balance-changed',
version: '1.0.0',
codec: jsonCodec<BalanceChanged>({
maximumBytes: 4096,
validate: (value): value is BalanceChanged =>
typeof value === 'object' && value !== null &&
typeof (value as BalanceChanged).playerId === 'string' &&
typeof (value as BalanceChanged).balance === 'number',
}),
});
const bus = new VersionedEventBus();
const subscription = bus.subscribe(
balanceChanged,
async (event) => {
console.info(event.playerId, event.balance);
},
'^1.0.0',
);
await bus.publish(balanceChanged, { playerId: 'uuid', balance: 1250 });
subscription.close();Publishing serializes once. Only compatible subscribers decode and receive the value, async delivery holds the subscriber generation's invocation lease, and codec failures are explicit CodecErrors. Platform event instances are not contract events.
The CLI copies declarations into compiler metadata:
{
"communication": {
"services": [
{
"id": "example/economy",
"version": "2.1.0",
"componentId": "src/plugin.ts#EconomyPlugin",
"methods": ["balance"]
}
],
"events": [
{ "id": "example/balance-changed", "version": "1.0.0" }
],
"consumers": [
{
"id": "example/economy",
"versionRange": "^2.0.0",
"dependentReload": "keep-running"
}
]
}
}The bundled adapter publishes declared service providers through shamooProvideService, and createPaperHostApi()/createVelocityHostApi() can provide services, subscribe to contract events, and publish events when an explicit public host object is available.
Current wiring caveat: the public host facades do not expose service acquisition, compiler-declared event metadata is not automatically subscribed/published by the bundled adapter, and entrypoint contexts do not currently expose PaperRuntimeHost or VelocityRuntimeHost. Do not access undocumented globalThis.host. Local ServiceRegistry and VersionedEventBus examples are executable in one JavaScript process; cross-isolate consumers require an embedding adapter that deliberately exposes the supported host operations.
The optional transport uses plugin-message channel shamoo:runtime_v1. Paper remains standalone when Velocity or a live player carrier is absent: availability returns false and requests fail with stable UNAVAILABLE behavior rather than being silently dropped.
Frames use network byte order and begin with SHMP, wire version 1, role, and a 16-byte request UUID. Contract IDs and operations are bounded lowercase identifiers. Payloads are limited to 30,000 bytes and whole frames to 32,766 bytes. Malformed UTF-8, UUIDs, semver, lengths, roles, trailing data, and oversized frames are rejected. Velocity accepts requests only from backend-server sources.
Use PaperVelocityTransport with CommunicationClient on Paper and createVelocityCommunicationHandler() on Velocity. Requests require a positive timeout and may carry an abort signal. Stable failure codes are UNAVAILABLE, TIMEOUT, ABORTED, TRANSPORT_ERROR, REMOTE_ERROR, and INVALID_RESPONSE.
The combined external Paper-plus-Velocity process path is not part of the current automated process harness because it lacks an authenticated backend player carrier. Codec, endpoint trust, carrier invalidation, and timeout behavior are covered in unit/integration tests.
See cross-plugin-services, custom-events, and proxy-routing. Sources: TypeScript communication guide and Runtime contracts.