Change Tracking

Layers track every mutation made through their API. You can inspect pending changes at any time before applying or resetting them. This is useful for building diffs, confirmation prompts, dry-run modes, and audit logs.

Listing Changes

Use getChanges() to retrieve all pending changes as an array of ChangeEntry objects:

import { createLayer } from "@catmint-fs/core";

const layer = createLayer({ root: "/path/to/project" });
const encoder = new TextEncoder();

await layer.writeFile("/src/new-file.ts", encoder.encode("export default {};\n"));
await layer.rm("/src/old-file.ts");
await layer.mkdir("/src/utils");
await layer.rename("/src/app.ts", "/src/main.ts");

const changes = layer.getChanges();

for (const change of changes) {
  console.log(`${change.type} ${change.path}`);
}
// create  /src/new-file.ts
// delete  /src/old-file.ts
// create  /src/utils
// rename  /src/app.ts -> /src/main.ts

ChangeEntry

Each entry in the array returned by getChanges() is a ChangeEntry:

interface ChangeEntry {
  type: "create" | "update" | "delete" | "rename" | "chmod" | "chown";
  path: string;
  entryType: EntryType; // "file" | "directory" | "symlink"
}
FieldDescription
typeThe kind of change: create, update, delete, rename, chmod, or chown
pathThe absolute path (relative to root) of the affected entry
entryTypeWhether the entry is a file, directory, or symlink

Getting Change Details

For more detailed information about a specific change, use getChangeDetail():

const detail = layer.getChangeDetail("/src/new-file.ts");
console.log(detail);
// {
//   type: "create",
//   path: "/src/new-file.ts",
//   entryType: "file",
// }

If the path has no pending changes, getChangeDetail() returns null:

const detail = layer.getChangeDetail("/untouched-file.ts");
console.log(detail); // null

ChangeDetail

The ChangeDetail type extends ChangeEntry with additional context depending on the change type:

interface ChangeDetail extends ChangeEntry {
  renameTo?: string;   // For rename changes, the destination path
  mode?: number;       // For chmod changes, the new mode
  uid?: number;        // For chown changes, the new uid
  gid?: number;        // For chown changes, the new gid
}

Example for a rename:

await layer.rename("/src/old.ts", "/src/new.ts");

const detail = layer.getChangeDetail("/src/old.ts");
console.log(detail);
// {
//   type: "rename",
//   path: "/src/old.ts",
//   entryType: "file",
//   renameTo: "/src/new.ts"
// }

Change Semantics

Overwrites Become Updates

Writing to a file that already exists on the backing filesystem produces an update change, not a create:

// Assuming /package.json exists on disk
await layer.writeFile("/package.json", encoder.encode("{}"));

const detail = layer.getChangeDetail("/package.json");
console.log(detail?.type); // "update"

Multiple Writes Coalesce

If you write to the same file multiple times, only one change entry exists. It reflects the final state:

await layer.writeFile("/config.ts", encoder.encode("v1"));
await layer.writeFile("/config.ts", encoder.encode("v2"));

const changes = layer.getChanges();
// Only one entry for /config.ts

Create Then Delete Cancels Out

If you create a new file and then delete it before applying, the change is removed entirely:

await layer.writeFile("/tmp.ts", encoder.encode("temporary"));
await layer.rm("/tmp.ts");

const changes = layer.getChanges();
// /tmp.ts does not appear in the changes

Delete Then Recreate Becomes Update

If you delete an existing file and recreate it, the result is an update:

// Assuming /index.ts exists on disk
await layer.rm("/index.ts");
await layer.writeFile("/index.ts", encoder.encode("new content"));

const detail = layer.getChangeDetail("/index.ts");
console.log(detail?.type); // "update"

Practical Example: Dry Run

import { createLayer } from "@catmint-fs/core";

async function dryRun(root: string) {
  const layer = createLayer({ root });

  // Perform operations...
  await layer.writeFile("/new.ts", new TextEncoder().encode("content"));
  await layer.rm("/deprecated.ts");

  // Show what would change
  const changes = layer.getChanges();
  console.log("Pending changes:");
  for (const change of changes) {
    console.log(`  ${change.type.padEnd(8)} ${change.entryType.padEnd(10)} ${change.path}`);
  }

  // Don't apply — discard everything
  layer.reset();
  layer.dispose();
}

See Also

  • Applying Changes — writing changes to the backing store
  • Layer APIgetChanges() and getChangeDetail() reference
  • TypesChangeEntry and ChangeDetail type definitions