JavaScript Conflict Handling

Treat 409 conflict handling as normal integration behavior.

The JavaScript facade keeps local and cloud state separate on conflict. Your game decides whether the player uses cloud, keeps local, or decides later.

Facade Conflict

Use saveData and forceSyncData for one-save games.

The same conflict helpers work on the default autosave slot, so beginner code does not need slot keys.

TYPESCRIPT
import { PersistlyGameSaveStatus, PersistlyGameSaves } from "@persistlyapp/sdk";

await PersistlyGameSaves.shared.saveData(currentGameData);
const result = await PersistlyGameSaves.shared.forceSyncData();

if (result.status === PersistlyGameSaveStatus.Conflict) {
  const save = await PersistlyGameSaves.shared.inspectData();

  // Show local-vs-cloud UI in your game.
  const choice = await askPlayerHowToRecover({
    local: save.data,
    cloud: save.lastCloudData,
  });

  if (choice === "use-cloud") {
    await PersistlyGameSaves.shared.acceptCloudData();
  }

  if (choice === "keep-local") {
    await PersistlyGameSaves.shared.overwriteCloudData();
  }

  if (choice === "decide-later") {
    await PersistlyGameSaves.shared.keepLocalDataForLater();
  }
}

Advanced Runtime

Use PersistlyClient directly only for wrappers or custom runtime control.

Raw sync gives you baseVersion and cloud conflict branches directly, but normal games should prefer the facade first.

TYPESCRIPT
const ConflictRecoveryChoice = {
  UseCloud: "use-cloud",
  KeepLocal: "keep-local",
} as const;

const result = await client.syncAccountSlot({
  accountId,
  accountSessionToken,
  slotId,
  baseVersion: local.version,
  slotInfo: local.slotInfo,
  data: nextData
});

if (result.status === PersistlySyncStatus.Accepted) {
  local = result.slot;
}

if (result.status === PersistlySyncStatus.Conflict) {
  const cloudSlot = result.slot;
  // Implement this in your game UI. Keep choices in constants instead of ad-hoc strings.
  const playerChoice = await askPlayerHowToRecover({ cloudSlot, localDraft });

  if (playerChoice === ConflictRecoveryChoice.UseCloud) {
    local = cloudSlot;
  }

  if (playerChoice === ConflictRecoveryChoice.KeepLocal) {
    const retry = await client.syncAccountSlot({
      accountId,
      accountSessionToken,
      slotId,
      baseVersion: cloudSlot.version,
      slotInfo: localDraft.slotInfo,
      data: localDraft.data,
    });

    local = retry.slot;
  }
}

Rules

These are the conflict semantics the SDK depends on.

Persistly uses optimistic concurrency with baseVersion, the last cloud branch revision your client accepted.

The server returns explicit conflict semantics instead of silently overwriting data.

Conflict responses return product-shaped local/cloud branches so the game can retry from that version.