@bunnybox/contract
v0.11.1
Published
Shared contract types and guards for BunnyBox micro-frontends, the shell, and the CLI.
Maintainers
Readme
@bunnybox/contract
The stable contract shared by BunnyBox micro-frontends, the shell that composes them, and the CLI that ships them.
This package holds the shapes those three deployables have to agree on, and nothing else — a handful of types plus one runtime guard. Everything here is a compatibility surface, so a breaking change here is a breaking change for every micro-frontend and the shell at once.
Zero third-party runtime dependencies.
Do you need this?
Probably not directly. If you are writing a micro-frontend, install @bunnybox/core instead — it
re-exports the types you need, so one dependency covers both the framework and the contract:
import { defineComponent, type BunnyBoxHost, type BunnyBoxModule } from '@bunnybox/core';If you are hosting micro-frontends, @bunnybox/core/shell re-exports the manifest types alongside
start(). They are not on the framework's root export, because a micro-frontend never handles a
manifest — the shell fetches it and mounts what it names.
Install this package directly only when you need the contract without the framework — a tool, a test double, or a server that produces manifests.
If you want the framework's primitives without its authoring helpers, that is a different thing again —
@bunnybox/core/bare, not this package.
Install
npm install @bunnybox/contractESM-only.
The three shapes that matter
BunnyBoxHost — the only sanctioned channel between a micro-frontend and the page around it: navigation,
events, request/response, error reporting, and an AbortSignal that fires on unmount.
BunnyBoxModule — what a micro-frontend's entry default-exports:
interface BunnyBoxModule {
contract: '1';
element: string;
component: CustomElementConstructor;
bootstrap?(host: BunnyBoxHost): void | Promise<void>;
}isBunnyBoxModule(value) is the runtime guard for it — the shell validates every module it loads before
mounting, and you can use the same check on your own entry.
ResolvedManifest — the document the shell fetches: regions, routes, an import map, and a map of
micro-frontend name to { entryUrl, element, integrity, … }. ManifestRegion, ManifestRoute,
ManifestBinding, and ManifestMicroFrontend describe its parts.
Declaring channels and events
A channel is how a micro-frontend asks the host page for something without importing the host's code to get it: a name, an optional payload, and a result. An event is a broadcast instead — any emitter, any number of listeners, no reply. Both are declared once, as a value, in a file the micro-frontend and the host page both compile against — a small package they both depend on is the usual home:
import { channel, event } from '@bunnybox/core';
/** What the cart comes to. */
export const cartTotal = channel<() => number>('shop:cart-total');
export const checkoutStarted = event<{ orderId: string }>('shop:checkout-started');channel and event are exported from this package, and re-exported from @bunnybox/core, so a
package declaring an application's seams may depend on either.
A handle carries both the name and the shape, so importing it is what lets a file reach the seam at all. The call site takes its type from the handle:
interface CartApi {
total(): Promise<number>;
checkout(orderId: string): void;
}
export function createCartApi(): CartApi {
const host = inject(HOST);
return {
total: () => host.request(cartTotal),
checkout: orderId => host.emit(checkoutStarted, { orderId }),
};
}total's result is Promise<number>, and checkout's argument is required, both read straight from
the declarations above. The host page answers a channel by passing a handler for the same handle to
start({ channels }); an event needs no answer — any number of listeners can subscribe to it with
host.on or onHost.
Store contract
The state types are here rather than in the framework because the shell reconciles shared stores across
micro-frontends and needs to speak them too: StoreDef, StoreScope, StoreInstance, ActionMap,
EffectMap, EffectContext, MigrationStep, Draft, and the method-shape helpers ActionMethods,
EffectMethods, KeyAccessors, and StoreMeta.
MigrationStep is worth knowing about: steps are keyed by the version they produce and are bidirectional, so
an older micro-frontend can keep reading a store that a newer one has already upgraded.
License
Apache-2.0. See LICENSE.
