2.9 KiB
sagasSchema
Purpose
Saga ownership + participation block. owns lists the sagas this plugin orchestrates (each with a non-empty step list); participatesIn lists the sagas this plugin only contributes a step to. The cross-field validator validateSagaOwnership couples saga steps to dependsOn so a step's plugin reference must be either the manifest's own name or a declared dependency.
Features
- Each owned saga has a name plus ≥1 steps
- Each step pins a
plugin,action, andcompensatehandler name participatesInlists the saga IDs this plugin contributes a step to- Cross-plugin coupling enforced at the validator layer, not the schema
Basic Example
import { sagasSchema } from "@suaveplan/api-manifest/schema";
sagasSchema.parse({ owns: [], participatesIn: ["@acme/onboarding/setup"] });
Advanced Example
import { sagasSchema } from "@suaveplan/api-manifest/schema";
const sagas = sagasSchema.parse({
owns: [
{
name: "create-customer",
steps: [
{ plugin: "@acme/billing", action: "createSub", compensate: "cancelSub" },
],
},
],
});
API Reference
sagasSchema: z.ZodObject<{ owns?, participatesIn? }>sagaStepSchema,ownedSagaSchema— the building blocks.- Types:
Sagas,OwnedSaga,SagaStep.
Implementation Notes
An owned saga MUST have at least one step (min(1)); a saga with zero steps is a degenerate state the host can't run, so we reject it at parse time rather than at boot. The step shape pins three handler names; the host resolves each handler name against the named plugin's exported saga-handler map at start. participatesIn is a string array of fully-qualified saga IDs (<owner-plugin>/<saga-name>) to avoid name collisions across plugins.
Cross-package Notes
This module is part of the @suaveplan/api-manifest Stage-1 contract package. It sits beneath every Stage-2 consumer (api-host, api-loader, the admin CLI, the IDE plugin) and above the foundation packages (@suaveplan/error, @suaveplan/types). The strict-mode top-level rejection of unknown keys means upgrading from manifest v1 to v2 is an OpenSpec-driven event: a new key landing in a consumer manifest is a parse failure rather than a silent drift, which is exactly the contract this stage exists to guarantee.
When the host reloads a plugin without a full restart, every manifest field this module validates is compared against the install-time manifestHash. A mismatch triggers a hard rollback via the RollbackJournal because the host's invariants (route mounts, registered capabilities, declared scopes) are derived from these exact bytes. Treat the schemas and validators here as the contract surface, not just an input filter — downstream code reads manifest.foo.bar and relies on those keys being present, well-typed, and non-undefined in the documented optionality regions.