NuskaEngine
The main API class — commits, branches, diffs, merges, and pull requests.
NuskaEngine is the central class that wires together a datasource and a version store. All operations go through it.
import { NuskaEngine } from "@codecanon/nuska"
const engine = new NuskaEngine({
datasource, // DataSourceAdapter
store, // VersionStore
defaultBranch: "main", // optional, default: "main"
generateId: () => myId(), // optional, default: uuid v4
})
await engine.init()init
engine.init(): Promise<void>Creates the default branch and sets it as the current branch. Must be called once before any other method.
If the store already has a branch named defaultBranch (e.g. from a previous session), init() is a no-op — safe to call on every mount.
await engine.init()commit
engine.commit(
ops: MutationOp[],
message: string,
author: string
): Promise<Commit>Persists a list of mutation ops as a new commit and applies them to the datasource. The current branch HEAD advances to the new commit.
const commit = await engine.commit(
[
{ type: "set", key: "post:1", value: { title: "Hello" } },
{ type: "delete", key: "draft:1" },
],
"Publish post 1",
"alice"
)branch
engine.branch(name: string): Promise<Branch>Creates a new branch pointing to the current HEAD. Does not switch to it — call checkout() to switch.
await engine.branch("feature/search")
await engine.checkout("feature/search")deleteBranch
engine.deleteBranch(name: string): Promise<void>Deletes a branch. Throws if you try to delete the currently checked-out branch.
await engine.deleteBranch("feature/search")checkout
engine.checkout(name: string): Promise<void>Switches to a branch and reconstructs the datasource state to match that branch's HEAD. All keys are replayed from the commit history.
await engine.checkout("main")If the adapter implements clear(), nuska uses it for an efficient bulk wipe before replay. Otherwise it deletes and rewrites keys individually.
getCurrentBranch
engine.getCurrentBranch(): stringReturns the name of the currently checked-out branch. Synchronous.
console.log(engine.getCurrentBranch()) // "main"listBranches
engine.listBranches(): Promise<Branch[]>Returns all branches in the store.
const branches = await engine.listBranches()
// [{ name: "main", headCommitId: "abc" }, { name: "feature/search", headCommitId: "def" }]log
engine.log(
branchName?: string,
opts?: { limit?: number; cursor?: string }
): Promise<Commit[]>Returns commits in reverse-chronological order (newest first). Defaults to the current branch.
// First page
const page1 = await engine.log("main", { limit: 20 })
// Next page using the last commit ID as cursor
const lastId = page1[page1.length - 1].id
const page2 = await engine.log("main", { limit: 20, cursor: lastId })diff
engine.diff(
fromCommitId: string | null,
toCommitId: string | null
): Promise<Diff>Compares two commits and returns the set of keys that changed. Pass null for either side to diff against an empty state.
const diff = await engine.diff("commit-a", "commit-b")
for (const entry of diff.entries) {
// entry.type: "added" | "removed" | "modified"
// entry.key, entry.oldValue, entry.newValue
console.log(entry.type, entry.key)
}merge
engine.merge(sourceBranchName: string): Promise<MergeResult>Performs a 3-way merge of the source branch into the current branch. Finds the common ancestor, then merges changes from both sides.
const result = await engine.merge("feature/search")
if (result.status === "success") {
console.log("Merged at:", result.commitId)
} else {
// result.status === "conflict"
for (const c of result.conflicts) {
console.log(c.key, { base: c.baseValue, ours: c.oursValue, theirs: c.theirsValue })
}
}A conflict occurs when both branches modified the same key differently since their common ancestor. If both sides converged to the same value, it is not a conflict.
resolveConflicts
engine.resolveConflicts(
sourceBranchName: string,
resolutions: ConflictResolution[],
options?: { targetBranch?: string; prId?: string }
): Promise<{ commitId: string }>Completes a conflicted merge by applying user-supplied resolutions. Every conflicted key must have a resolution.
await engine.resolveConflicts("feature/search", [
{ key: "config:index", value: "elastic" }, // keep a value
{ key: "config:cache", deleted: true }, // delete the key
])revertCommit
engine.revertCommit(
commitId: string,
opts?: { message?: string; author?: string }
): Promise<Commit>Creates a new commit that undoes all operations from the target commit. History is never rewritten.
const revert = await engine.revertCommit("abc123", {
message: "Revert: Publish post 1",
author: "bob",
})createPR
engine.createPR(
title: string,
fromBranch: string,
toBranch: string
): Promise<PullRequest>Opens a pull request to merge fromBranch into toBranch.
const pr = await engine.createPR("Add search", "feature/search", "main")
console.log(pr.id, pr.status) // "open"mergePR
engine.mergePR(
prId: string,
options?: { deleteBranch?: boolean }
): Promise<MergeResult>Merges the PR's source branch into its target branch. By default the source branch is deleted after a successful merge. Pass { deleteBranch: false } to keep it.
const result = await engine.mergePR(pr.id)
// result.status === "success" | "conflict"closePR
engine.closePR(prId: string): Promise<PullRequest>Closes a PR without merging it. The returned PR has status: "closed".
await engine.closePR(pr.id)listPullRequests
engine.listPullRequests(
opts?: PaginationOptions & { status?: "open" | "merged" | "closed" }
): Promise<PullRequest[]>Returns pull requests, optionally filtered by status.
const open = await engine.listPullRequests({ status: "open" })
const all = await engine.listPullRequests({ limit: 50 })