Search docs
Search modules and symbols
Inter-plugin interfaces

Inter-plugin interfaces

Plugins talk through typed, versioned interfaces — methods as natives, events as forwards. A producer ctx.publishes an implementation; a consumer ctx.uses it.

// producer
import { plugin } from '@s2script/sdk/plugin';

export default plugin((ctx) => {
  const zones = ctx.publish('@demo/zones', {
    createZone(name: string, mins: number[], maxs: number[]) { /* … */ },
    isInZone(slot: number, name: string): boolean { /* … */ return false; }
  });
  // forward an event to subscribers
  ctx.server.onGameFrame(() => zones.emit('stay', { slot: 0, zone: 'spawn' }));
});
// consumer — types come from the producer's api.d.ts
import { plugin } from '@s2script/sdk/plugin';
import type { Zones } from '../../zones/api';

export default plugin((ctx) => {
  const zones = ctx.use<Zones>('@demo/zones');          // hard dep — proxy throws while unpublished
  zones.on('stay', ({ slot, zone }) => { /* … */ });
  // ctx.tryUse<Zones>('@demo/zones') instead → Interface | null for an optional dep
});

A hard dep (ctx.use) returns a proxy that throws InterfaceUnavailable while the producer is unloaded; an optional dep (ctx.tryUse) resolves to Interface | null. Call args and event payloads cross contexts by structured copy — never a live pointer — and EntityRef round-trips as a live, liveness-gated ref.

Declare deps under s2script.pluginDependencies (or optionalPluginDependencies). Every import is ledgered, and unload walks reverse-dependency order.

See the interfaces module and the zones plugin.

s2script — Source 2 plugin framework

GitHub