5.8 KiB
apply
(no submodules)
- conflict — Resolves the per-op `conflict` policy on…-
conflict — Resolves the per-op
conflictpolicy on a target that already exists. Five policies are supported:error(raise `APP… -
json-merge — Implements the deep-merge semantics used by
patch-jsonops withconflict: "merge". Match the conventions of `@suave… -
text-patch — Applies an ordered sequence of
TextEdits to a string. Used bypatch-textops to make targeted insertions/deletions/… -
conflict — Resolves the per-op
conflictpolicy on… -
conflict — Resolves the per-op
conflictpolicy on a target that already exists. Five policies are supported:error(raise `APP… -
json-merge — Implements the deep-merge semantics used by
patch-jsonops withconflict: "merge". Match the conventions of `@suave… -
text-patch — Applies an ordered sequence of
TextEdits to a string. Used bypatch-textops to make targeted insertions/deletions/… -
conflict — Resolves the per-op
conflictpolicy on… -
conflict — Resolves the per-op
conflictpolicy on a target that already exists. Five policies are supported:error(raise `APP… -
json-merge — Implements the deep-merge semantics used by
patch-jsonops withconflict: "merge". Match the conventions of `@suave… -
text-patch — Applies an ordered sequence of
TextEdits to a string. Used bypatch-textops to make targeted insertions/deletions/… -
conflict — Resolves the per-op
conflictpolicy on… -
conflict — Resolves the per-op
conflictpolicy on a target that already exists. Five policies are supported:error(raise `APP… -
json-merge — Implements the deep-merge semantics used by
patch-jsonops withconflict: "merge". Match the conventions of `@suave… -
text-patch — Applies an ordered sequence of
TextEdits to a string. Used bypatch-textops to make targeted insertions/deletions/… -
conflict — Resolves the per-op
conflictpolicy on a target that already exists. Five policies are supported:error(raise `APP… -
json-merge — Implements the deep-merge semantics used by
patch-jsonops withconflict: "merge". Match the conventions of `@suave… -
text-patch — Applies an ordered sequence of
TextEdits to a string. Used bypatch-textops to make targeted insertions/deletions/…
Purpose
Materialises a FileOp[] against a MutableInstallerFs with rollback
semantics. This is the only mutating stage in the installer pipeline.
Each successful op records an undo entry on a journal; if any op fails,
the journal replays in LIFO order to reverse already-applied work. Apply
is wrapped in an OTel span (installer.apply) and is the only place in
the host where INSTALLER.APPLY_* and INSTALLER.ROLLBACK_FAILED codes
originate.
Features
- Per-op conflict resolution via
resolveConflict— the host consults each op'sconflictpolicy before mutating the fs. - Transactional rollback in reverse order — an apply failure undoes everything that succeeded earlier in the run.
- Distinct codes:
APPLY_CONFLICTfor policy violations,APPLY_FAILEDfor plugin/fs exceptions,ROLLBACK_FAILEDwhen undo itself throws,ABORTEDwhen the run was cancelled mid-apply. dry-runis honoured by the host before apply runs, so apply itself always writes — no internalif (dryRun) skipbranches to test.- Per-op summary (
written/patched/skipped/conflicts) for the host to return.
Basic Example
import { applyOps, MemoryFs } from "@suaveplan/installer";
const fs = new MemoryFs();
const summary = await applyOps({
ops: [
{
kind: "write",
path: "/hello.txt",
contents: new TextEncoder().encode("hi"),
},
],
ctx,
fs,
pluginId: "demo",
pluginVersion: "0.1.0",
});
console.log(summary); // { written: 1, patched: 0, skipped: 0, conflicts: 0 }
Advanced Example
import { applyOps, InstallerError } from "@suaveplan/installer";
try {
await applyOps({ ops, ctx, fs, pluginId, pluginVersion });
} catch (err) {
if (err instanceof InstallerError) {
switch (err.code) {
case "INSTALLER.APPLY_CONFLICT":
console.error("Pre-existing files conflict; re-run with --force");
break;
case "INSTALLER.ROLLBACK_FAILED":
console.error(
"Apply failed AND rollback failed — fs may be in a partial state",
err.context?.["original"]
);
break;
default:
throw err;
}
}
}
API Reference
applyOps(options: ApplyOptions): Promise<PlanSummary>interface ApplyOptions—{ ops, ctx, fs, pluginId, pluginVersion }.- Errors:
INSTALLER.APPLY_CONFLICT,INSTALLER.APPLY_FAILED,INSTALLER.ROLLBACK_FAILED,INSTALLER.ABORTED.
Implementation Notes
Each op's undo is captured into the journal before the next op runs, so
even if abort fires mid-op the partial state is recoverable. mkdir ops
are short-circuited by resolveConflict for already-existing directories
(idempotent default), so we don't need a redundant if (exists) return
inside apply. The patch ops (patch-json, patch-text) read the prior
content (when the file exists) so undo can restore byte-for-byte; when
the file didn't exist, undo is a deleteFile. The ROLLBACK_FAILED
path attaches the original apply error as context.original so the
caller can still report root cause even when the recovery path failed.