Mysten Incubation
Reference

Plugins

Current plugin-author entry points.

A plugin is a function that returns a stack member. The root package exports the authoring helpers; contracts and substrate are internal source modules, not package subpaths for app or plugin author imports.

A plugin returned by definePlugin(...) is a { id, role, section, dependsOn?, start } bundle.

  • id — graph identity and the default resource id; the resolved value is published under this id.
  • role — lifecycle classification. 'service' means a long-lived resource (a container, an HTTP process); 'task' means the body runs once at acquire and reaches "done" when start resolves (account, action, package publish). The supervisor uses the role to decide stop semantics.
  • section — required dashboard bucket for the plugin's rows: 'service' | 'package' | 'account' | 'action' | 'app' | 'other'. The supervisor stamps it onto each row at acquire time, and the renderer groups rows by section. A long-lived resource is usually 'service'.
  • dependsOn — a single plugin/ref, an array, or an object of plugin/refs. Resolved upstream values are passed to start in the same shape — single, positional array, or keyed object.
  • start — the Effect that produces the resolved value. It may yield substrate services (e.g. ContainerRuntimeService, IdentityContext) the supervisor provides, and it emits the plugin's contributions (codegen, endpoints, snapshots, projection events, strategies) inline via the per-plugin PluginContext service (const ctx = yield* PluginContext).

There is no separate capabilities field. A plugin contributes by reaching its ctx inside start and calling the typed verbs ctx.codegen / ctx.endpoint / ctx.snapshotExtra / ctx.publish / ctx.provides. Each verb buffers a typed decl that the supervisor replays in its own frame after start resolves, so a contribution can carry post-acquire runtime state (resolved ids, RPC URLs).

import { Effect } from 'effect';
import { account, definePlugin, PluginContext, sui } from '@mysten-incubation/devstack';

interface CacheResolved {
	readonly url: string;
	readonly tokenAddress: string;
}

export const cache = (signer: ReturnType<typeof account>) =>
	definePlugin({
		id: 'cache',
		role: 'service',
		section: 'service',
		// Resolved values arrive in the shape of `dependsOn`. Here the object
		// keys (`sui`, `signer`) match the keys the start body destructures.
		dependsOn: { sui: sui(), signer },
		start: ({ signer }) =>
			Effect.gen(function* () {
				const ctx = yield* PluginContext;
				const value: CacheResolved = {
					url: 'http://127.0.0.1:6379',
					tokenAddress: signer.address,
				};
				// Emit a generated `cache.ts` inline. The codegen decl carries the
				// just-resolved value, so the emitted file holds the real address.
				ctx.codegen({
					kind: 'codegenable',
					emitterName: 'cache',
					outputPath: 'cache.ts',
					emit: (emit) =>
						Effect.sync(() => {
							emit.exportConst('cache', value);
							return emit.done();
						}),
				});
				return value;
			}),
	});

Compose the plugin like any built-in factory:

import { account, defineDevstack, sui } from '@mysten-incubation/devstack';
import { cache } from './devstack/cache.js';

const publisher = account('publisher');
export default defineDevstack({ members: [sui(), publisher, cache(publisher)] });

Use direct plugin/resource references for cross-plugin dependencies — accept the upstream plugin as a value and put it in dependsOn. The resolved value arrives in start in the same shape dependsOn declared.