> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# C# V4→V5 API Migration Guide

> Guide for migrating C# apps from Ditto v4 to v5.

## Overview

This guide covers the essential changes needed to migrate your Ditto C# app from v4 to v5. The main architectural shift is moving from identity-based initialization to a four-phase configuration model with async/await APIs.

**Also required:** v5 uses DQL (Ditto Query Language) for all data operations. See the [DQL Migration Guide](./c-sharp-dql) for query migration steps.

***

## AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto C# app from v4 to v5.

<Accordion title="Copy AI Migration Prompt (Click to Expand)">
  ````text theme={null}
  I need help migrating a Ditto C#/.NET application from v4 to v5. Focus on these critical changes:

  INITIALIZATION:
  Replace `new Ditto(DittoIdentity, string)` with `Ditto.Open(DittoConfig)` or `await Ditto.OpenAsync(DittoConfig)`.
  - Create a `DittoConfig` with the constructor: `new DittoConfig(databaseId, connect, persistenceDirectory?)`
  - For server/cloud: `new DittoConfig(databaseId: "id", connect: new DittoConfigConnect.Server(new Uri("https://...")))`
  - For offline/P2P: `new DittoConfig(databaseId: "id", connect: new DittoConfigConnect.SmallPeersOnly(privateKey: null))`
  - For shared key: `new DittoConfig(databaseId: "id", connect: new DittoConfigConnect.SmallPeersOnly(privateKey: "your-shared-key"))`
  - The old identity classes (`DittoOfflinePlayground`, `DittoOnlinePlayground`, `DittoOnlineWithAuthentication`, `DittoSharedKey`, `DittoManual`) are all removed
  - `DittoIdentityType` enum is removed entirely
  - Remove `UpdateTransportConfig` workaround patterns for adding websocket URLs — `DittoConfig` handles the server URL directly. Note: `UpdateTransportConfig()` still exists but signature changed: v4 takes `Action<DittoTransportConfig>`, v5 takes `Action<DittoTransportConfigBuilder>` (builder pattern)
  - Remove `DisableSyncWithV3()` calls — v3 protocol is fully dropped in v5
  - Remove `await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false")` — v5 defaults to `false`
  - IMPORTANT: If your v4 app used the default (strict=true), set `DQL_STRICT_MODE=true` BEFORE starting sync to maintain v4 behavior. Objects that were replaced whole in v4 will silently merge at field level in v5 without this.
  - Use `using var ditto = Ditto.Open(config);` for IDisposable cleanup
  - By default, the Transport Config no longer needed to set websocketUrl for Big Peer as this is automatically set by the SDK from the auth server URL.  

  NAMESPACE CHANGES:
  v5 reorganizes types into sub-namespaces. You will get compile errors until these are updated.
  - `DittoSDK.Auth` — `DittoAuthenticator`, `DittoAuthenticationProvider`, `DittoAuthenticationExpirationHandler`
  - `DittoSDK.Exceptions` — `DittoException`
  - `DittoSDK.Store` — `DittoStoreObserver`, `DittoQueryResult`, `DittoQueryResultItem`
  - `DittoSDK.Sync` — `DittoSyncSubscription`
  - `DittoSDK.Transport` — `DittoPeer`, `DittoPresenceGraph`, `DittoPresence`, `DittoConnection`, `DittoConnectionType`
  - `DittoSDK.Logging` — `DittoLogLevel`, `DittoLogger`
  - `DittoSDK.DiskUsage` — `DittoDiskUsageItem`

  SYNC:
  The sync API moved from top-level Ditto methods to the `Ditto.Sync` sub-object.
  - `ditto.StartSync()` → `ditto.Sync.Start()`
  - `ditto.StopSync()` → `ditto.Sync.Stop()`
  - `ditto.GetTransportDiagnostics()` → removed in v5 (no replacement)
  - `ditto.IsSyncActive` → `ditto.Sync.IsActive`

  AUTHENTICATION:
  The `IDittoAuthenticationDelegate` interface is removed. Replace with `DittoAuthenticationExpirationHandler` async delegate.
  - Set `ditto.Auth.ExpirationHandler = async (ditto, timeUntilExpiration) => { ... }` after Ditto.Open()
  - The delegate signature is: `Task DittoAuthenticationExpirationHandler(Ditto ditto, TimeSpan timeUntilExpiration)`
  - First param is the `Ditto` instance. Access auth via `ditto.Auth.LoginAsync()`
  - Second param is `TimeSpan` (not seconds as a number). Check `timeUntilExpiration == TimeSpan.Zero` for initial auth.
  - Use `DittoAuthenticationProvider.Development` for playground/dev tokens
  - The handler is called when `timeUntilExpiration == TimeSpan.Zero` (initial auth or expired) and when `timeUntilExpiration > TimeSpan.Zero` (token expiring soon). Handle both cases.
  - `authenticator.LoginWithToken(token, provider)` → removed, use `ditto.Auth.LoginAsync(token, provider)` instead
  - `authenticator.Login(token, provider)` → removed (sync overload gone), use `ditto.Auth.LoginAsync(token, provider)`
  - `authenticator.LoginWithCredentials(username, password, provider)` → removed
  - `IDittoAuthenticationDelegate.AuthenticationStatusDidChange()` → removed, use `DittoAuthenticator.ObserveStatus()` for cancellable observation
  - New: `ditto.Auth.Logout()` — explicit logout support
  - New: `DittoAuthenticator.ObserveStatus()` — returns `IDisposable` observer for auth status changes

  OBSERVERS — `DittoLiveQuery` → `DittoStoreObserver`:
  The legacy `DittoLiveQuery` from collection `.ObserveLocal()` is replaced by `DittoStoreObserver` from `Store.RegisterObserver()`.
  - `DittoLiveQuery` type → `DittoStoreObserver`
  - `collection.Find("...").ObserveLocal((docs, event) => { ... })` → `store.RegisterObserver("SELECT ...", args, (DittoQueryResult result) => { ... })`
  - The callback receives `DittoQueryResult` with `.Items` property (list of `DittoQueryResultItem`)
  - `observer.Cancel()` for cleanup (replaces `liveQuery.Stop()`)

  BACK PRESSURE (RegisterObserver with signalNext):
  Use the `Action<DittoQueryResult, Action>` overload for manual back-pressure control.
  - Standard observer: `store.RegisterObserver(query, result => { ... })` — auto-signals after handler returns
  - Async observer: `store.RegisterObserver(query, async result => { ... })` — auto-signals after Task completes
  - Back-pressure: `store.RegisterObserver(query, args, async (result, signalNext) => { await ...; signalNext(); })` — must call `signalNext()` when ready for next update
  - Changes during processing are coalesced — you get the latest state, not every intermediate change

  COLLECTION API → DQL:
  The entire Collection/Document API is removed. Replace all collection operations with DQL via `Store.ExecuteAsync()`.
  - `store.Collection("cars")` → removed. Use `store.ExecuteAsync("SELECT * FROM cars")`
  - `collection.FindAll().Exec()` → `await store.ExecuteAsync("SELECT * FROM cars")`
  - `collection.Find("color == 'blue'").Exec()` → `await store.ExecuteAsync("SELECT * FROM cars WHERE color = :color", new Dictionary<string, object> { ["color"] = "blue" })`
  - `collection.FindById(id).Exec()` → `await store.ExecuteAsync("SELECT * FROM cars WHERE _id = :id", new Dictionary<string, object> { ["id"] = "car-1" })`
  - `collection.Upsert(value)` → `await store.ExecuteAsync("INSERT INTO cars DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE", new Dictionary<string, object> { ["doc"] = value })`
  - `collection.FindById(id).Update(doc => { doc["color"].Set("green"); })` → `await store.ExecuteAsync("UPDATE cars SET color = :color WHERE _id = :id", new Dictionary<string, object> { ["color"] = "green", ["id"] = "car-1" })`
  - `collection.FindById(id).Remove()` → `await store.ExecuteAsync("DELETE FROM cars WHERE _id = :id", new Dictionary<string, object> { ["id"] = "car-1" })`
  - `collection.FindById(id).Evict()` → `await store.ExecuteAsync("EVICT FROM cars WHERE _id = :id", new Dictionary<string, object> { ["id"] = "car-1" })`
  - `collection.FindAll().Sort("color", DittoSortDirection.Ascending).Limit(20).Offset(40).Exec()` → `await store.ExecuteAsync("SELECT * FROM cars ORDER BY color ASC LIMIT 20 OFFSET 40")`
  - `DittoSortDirection` enum is removed — use `ASC`/`DESC` in DQL
  - IMPORTANT: `ExecuteAsync` takes `Dictionary<string, object>` for arguments — NOT anonymous objects. `RegisterObserver` and `RegisterSubscription` DO accept anonymous `object` arguments.
  - Always use parameterized queries with `:paramName` — NEVER use string interpolation

  SUBSCRIPTIONS — `DittoSubscription` → `DittoSyncSubscription`:
  - `collection.Find("color == 'red'").Subscribe()` → `ditto.Sync.RegisterSubscription("SELECT * FROM cars WHERE color = :color", new { color = "red" })`
  - `DittoSubscription` type → `DittoSyncSubscription`
  - `sub.Cancel()` for cleanup
  - Note: `RegisterSubscription` accepts anonymous `object` arguments

  TRANSACTIONS — `Write()` → `TransactionAsync()`:
  - `ditto.Store.Write(tx => { tx.Scoped("cars").Upsert(value); })` → `await ditto.Store.TransactionAsync(async tx => { await tx.ExecuteAsync("INSERT INTO ...", args); })`
  - The callback now receives `DittoTransaction` (not `DittoWriteTransaction`) and uses async DQL
  - `DittoWriteStrategy.UpdateDifferentValues` is removed — use `ON ID CONFLICT DO MERGE` in DQL

  PRESENCE:
  - `peer.PeerKeyString` → `peer.PeerKey`
  - `peer.Os` (string) → `peer.Os` (DittoPeerOS? — type changed from string to nullable enum)
  - `peer.IsDittoCloudConnected` → `peer.IsConnectedToDittoServer`
  - `connection.PeerKeyString1` / `PeerKeyString2` → `connection.PeerKey1` / `PeerKey2`
  - `connection.ApproximateDistanceInMeters` → **removed, no replacement**
  - `DittoPresenceGraph`, `DittoPeer`, `DittoConnection`, `DittoConnectionType` moved to `DittoSDK.Transport` namespace

  DITTO CLASS:
  - `new Ditto(identity, path)` → `Ditto.Open(config)` or `await Ditto.OpenAsync(config)`
  - `ditto.SiteId` → removed (use peer identity from presence graph)
  - `ditto.SDKVersion` → use static `Ditto.Version` property
  - `ditto.DisableSyncWithV3()` → removed
  - `ditto.StartSync()` / `StopSync()` → `ditto.Sync.Start()` / `Stop()`
  - `ditto.GetTransportDiagnostics()` → removed

  DISK USAGE:
  - `ditto.DiskUsage.Exec()` → `ditto.DiskUsage.Item` (property, not method)
  - `DittoDiskUsageChild` → `DittoDiskUsageItem` (type renamed)
  - Types moved to `DittoSDK.DiskUsage` namespace

  LOGGING:
  - `DittoLogger.SetLoggingEnabled(true)` → `DittoLogger.IsEnabled = true`

  ERRORS:
  - `DittoException` moved to `DittoSDK.Exceptions` namespace
  - Add `using DittoSDK.Exceptions;` where exceptions are caught

  REMOVED APIS (no replacement):
  - `DittoLiveQueryEvent.Hash()` — document change hashing removed
  - `DittoLiveQueryEvent.HashMnemonic()` — human-readable hash removed
  - `DittoWriteStrategy.UpdateDifferentValues` — use `ON ID CONFLICT DO MERGE` in DQL
  - `IDittoAuthenticationDelegate.AuthenticationStatusDidChange()` — use `DittoAuthenticator.ObserveStatus()`
  - `DittoUpdateResult` (and `Set`, `Removed`, `Incremented` subclasses) — DQL returns row counts only
  - `DittoPendingCursorOperation.ObserveLocalWithNextSignal()` — use `RegisterObserver` with `signalNext` overload
  - `DittoSmallPeerInfoSyncScope` enum — use `ALTER SYSTEM SET smallPeerInfoSyncScope = '...'`
  - `DittoSortDirection` enum — use `ASC`/`DESC` in DQL
  - `DittoStore.FetchAttachment(DittoAttachmentFetchDelegate)` — use async `Func`/Task overload
  - `DittoIdentityType` enum — replaced by `DittoConfig` constructor with `DittoConfigConnect` types
  - `Ditto.DisableSyncWithV3()` — v3 sync dropped
  - `Ditto.GetTransportDiagnostics()` — transport diagnostics removed
  - `Ditto.SiteId` — use peer identity from presence graph
  - `connection.ApproximateDistanceInMeters` — peer distance estimation removed

  NEW APIS:
  - `Ditto.Open(DittoConfig)` / `Ditto.OpenAsync(DittoConfig)` — factory methods replacing constructor
  - `DittoConfig(databaseId, connect, persistenceDirectory?)` — constructor replaces identity classes
  - `DittoConfigConnect.Server` / `DittoConfigConnect.SmallPeersOnly` — connect mode types
  - `Ditto.Sync.Start()` / `Stop()` — replaces `StartSync()` / `StopSync()`
  - `Ditto.Version` (static property) — replaces `Ditto.SDKVersion`
  - `Ditto.DeviceName` — custom peer device name (get/set)
  - `DittoAuthenticationExpirationHandler` — async delegate replacing `IDittoAuthenticationDelegate`
  - `DittoAuthenticator.LoginAsync()` — replaces `LoginWithToken()` / `Login()`
  - `DittoAuthenticator.ObserveStatus()` — returns `IDisposable` auth status observer
  - `DittoAuthenticator.Logout()` — explicit logout
  - `DittoStore.TransactionAsync()` — async DQL transaction replacing `Write()`; uses `DittoTransaction`
  - `DittoStoreObserver` — replaces `DittoLiveQuery`
  - `DittoQueryResultItem` — single DQL result row with `.Value` property
  - `DittoSyncSubscription` — replaces `DittoSubscription`, exposes `QueryString`, `QueryArguments`, `Cancel()`
  - `WifiAwareConfig` / `WifiAwareConfigBuilder` — Android Wi-Fi Aware transport config (MAUI)
  - `DittoSyncPermissions.RequestPermissionsAsync()` — Bluetooth/Wi-Fi permission requests (MAUI)
  - `DittoDiskUsageItem` — replaces `DittoDiskUsageChild`
  - `DittoDiskUsage.Item` (property) — replaces `Exec()` method
  - `DittoSDK.Auth`, `DittoSDK.Exceptions`, `DittoSDK.Store`, `DittoSDK.Sync`, `DittoSDK.Transport`, `DittoSDK.Logging`, `DittoSDK.DiskUsage` — new sub-namespaces

  BEFORE (v4):
  ```csharp
  using DittoSDK;

  var ditto = new Ditto(
      identity: DittoIdentity.OnlinePlayground(
          appId: "your-app-id",
          token: "your-token",
          enableDittoCloudSync: false,
          customAuthUrl: "https://your-server.ditto.live"
      )
  );

  ditto.UpdateTransportConfig(config =>
  {
      config.Connect.WebSocketUrls.Add("wss://...");
  });
  await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false");
  ditto.DisableSyncWithV3();
  ditto.StartSync();

  // Collection-based CRUD
  var collection = ditto.Store.Collection("cars");
  collection.Upsert(new Dictionary<string, object>
  {
      { "_id", "car-1" }, { "color", "red" }, { "make", "Toyota" }
  });
  var docs = collection.FindAll().Exec();
  collection.FindById(new DittoDocumentId("car-1")).Update(doc =>
  {
      doc["color"].Set("blue");
  });
  collection.FindById(new DittoDocumentId("car-1")).Remove();

  // Live query
  DittoLiveQuery liveQuery = collection.FindAll()
      .ObserveLocal((docs, dittoCollectionEvent) =>
      {
          UpdateUI(docs);
      });

  // Subscription
  DittoSubscription sub = collection.Find("color == 'red'").Subscribe();

  // Write transaction
  ditto.Store.Write(tx =>
  {
      tx.Scoped("cars").Upsert(new Dictionary<string, object>
      {
          { "_id", "car-2" }, { "color", "green" }
      });
  });

  // Cleanup
  liveQuery.Stop();
  sub.Cancel();
  ditto.StopSync();
  ditto.Dispose();
  ```

  AFTER (v5):
  ```csharp
  using DittoSDK;
  using DittoSDK.Auth;
  using DittoSDK.Store;
  using DittoSDK.Exceptions;

  var config = new DittoConfig(
      databaseId: "your-database-id",
      connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live"))
  );

  using var ditto = await Ditto.OpenAsync(config);

  ditto.Auth.ExpirationHandler = async (ditto, timeUntilExpiration) =>
  {
      try
      {
          await ditto.Auth.LoginAsync(
              "your-token",
              DittoAuthenticationProvider.Development);
      }
      catch (DittoException error)
      {
          Console.WriteLine($"Auth failed: {error}, time until expiration: {timeUntilExpiration}");
      }
  };

  // If maintaining v4 behavior, set strict mode BEFORE starting sync
  // await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = true");

  ditto.Sync.Start();

  // DQL-based CRUD
  await ditto.Store.ExecuteAsync(
      "INSERT INTO cars DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE",
      new Dictionary<string, object> { ["doc"] = new Dictionary<string, object> { ["_id"] = "car-1", ["color"] = "red", ["make"] = "Toyota" } });

  var result = await ditto.Store.ExecuteAsync("SELECT * FROM cars");

  await ditto.Store.ExecuteAsync(
      "UPDATE cars SET color = :color WHERE _id = :id",
      new Dictionary<string, object> { ["color"] = "blue", ["id"] = "car-1" });

  await ditto.Store.ExecuteAsync(
      "DELETE FROM cars WHERE _id = :id",
      new Dictionary<string, object> { ["id"] = "car-1" });

  // Store observer (replaces live query)
  DittoStoreObserver observer = ditto.Store.RegisterObserver(
      "SELECT * FROM cars",
      (DittoQueryResult result) =>
      {
          UpdateUI(result.Items);
      });

  // Sync subscription (replaces collection subscription)
  DittoSyncSubscription sub = ditto.Sync.RegisterSubscription(
      "SELECT * FROM cars WHERE color = :color",
      new { color = "red" });

  // DQL transaction (replaces write transaction)
  await ditto.Store.TransactionAsync(async tx =>
  {
      await tx.ExecuteAsync(
          "INSERT INTO cars DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE",
          new Dictionary<string, object> { ["doc"] = new Dictionary<string, object> { ["_id"] = "car-2", ["color"] = "green" } });
  });

  // Cleanup
  observer.Cancel();
  sub.Cancel();
  ditto.Sync.Stop();
  // ditto.Dispose() handled by 'using' statement
  ```

  CHECKLIST:

  Step 1 — Update Package:
  - Update `DittoSDK` NuGet package to v5.x

  Step 2 — Fix Namespace Imports:
  - Add `using DittoSDK.Auth;` where auth types are used
  - Add `using DittoSDK.Exceptions;` where `DittoException` is caught
  - Add `using DittoSDK.Store;` for `DittoStoreObserver`, `DittoQueryResult`, `DittoQueryResultItem`
  - Add `using DittoSDK.Sync;` for `DittoSyncSubscription`
  - Add `using DittoSDK.Transport;` for `DittoPeer`, `DittoConnection`, `DittoPresenceGraph`
  - Add `using DittoSDK.Logging;` for `DittoLogger`, `DittoLogLevel`
  - Add `using DittoSDK.DiskUsage;` for `DittoDiskUsageItem`

  Step 3 — Replace Initialization:
  - Replace `new Ditto(identity, path)` with `Ditto.Open(config)` or `await Ditto.OpenAsync(config)`
  - Replace identity classes with `new DittoConfig(databaseId, connect)` constructor
  - Remove `DittoIdentityType` enum references
  - Add `using var` for IDisposable cleanup

  Step 4 — Update Sync API:
  - Replace `ditto.StartSync()` → `ditto.Sync.Start()`
  - Replace `ditto.StopSync()` → `ditto.Sync.Stop()`
  - Remove `GetTransportDiagnostics()` calls (removed in v5)
  - Remove `ditto.DisableSyncWithV3()` calls
  - Set `DQL_STRICT_MODE=true` BEFORE `Sync.Start()` if maintaining v4 strict behavior

  Step 5 — Update Authentication:
  - Replace `IDittoAuthenticationDelegate` implementations with `DittoAuthenticationExpirationHandler` async delegate
  - Handler signature: `Task DittoAuthenticationExpirationHandler(Ditto ditto, TimeSpan timeUntilExpiration)`
  - Replace `LoginWithToken()` / `Login()` / `LoginWithCredentials()` → `LoginAsync()`
  - Use `DittoAuthenticationProvider.Development` for playground tokens
  - Handle `timeUntilExpiration == TimeSpan.Zero` (initial) vs `> TimeSpan.Zero` (refresh)
  - Update status observation to `DittoAuthenticator.ObserveStatus()` (returns `IDisposable`)

  Step 6 — Replace Collection API with DQL:
  - Search for `ditto.Store.Collection(` and replace all with DQL `ExecuteAsync()`
  - Replace `.FindAll()`, `.Find(`, `.FindById(` with DQL `SELECT`
  - Replace `.Upsert(` with `INSERT INTO ... DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE`
  - Replace `.Update(` closures with `UPDATE SET`
  - Replace `.Remove()` with `DELETE FROM`
  - Replace `.Evict()` with `EVICT FROM`
  - Replace `.Sort("field", DittoSortDirection.Ascending).Limit(n).Offset(n)` with `ORDER BY field ASC LIMIT n OFFSET n` in DQL
  - Use `Dictionary<string, object>` for `ExecuteAsync` arguments — NOT anonymous objects
  - Use parameterized queries — never string interpolation

  Step 7 — Replace Live Queries and Subscriptions:
  - Replace `DittoLiveQuery` type → `DittoStoreObserver`
  - Replace `.ObserveLocal()` → `ditto.Store.RegisterObserver()` with `DittoQueryResult` callback
  - Replace `DittoSubscription` → `DittoSyncSubscription`
  - Replace `.Subscribe()` → `ditto.Sync.RegisterSubscription()`

  Step 8 — Replace Write Transactions:
  - Replace `ditto.Store.Write(tx => { })` → `await ditto.Store.TransactionAsync(async tx => { })`
  - Replace `tx.Scoped("collection").Upsert(...)` → `await tx.ExecuteAsync("INSERT ...")`
  - Note: Callback now receives `DittoTransaction` (not `DittoWriteTransaction`)

  Step 9 — Update Renamed Properties and Types:
  - `Ditto.SiteId` → removed (use peer identity from presence graph)
  - `Ditto.SDKVersion` → use static `Ditto.Version` property
  - `DittoDiskUsageChild` → `DittoDiskUsageItem`
  - `DittoDiskUsage.Exec()` → `DittoDiskUsage.Item` (property)
  - `peer.PeerKeyString` → `peer.PeerKey`
  - `peer.IsDittoCloudConnected` → `peer.IsConnectedToDittoServer`
  - `connection.PeerKeyString1` / `PeerKeyString2` → `connection.PeerKey1` / `PeerKey2`
  - Remove `connection.ApproximateDistanceInMeters` usage (no replacement)
  - `peer.Os` type changed from string to `DittoPeerOS?` (nullable enum)

  Step 10 — Verify and Test:
  - Build compiles with zero errors
  - No deprecated API warnings
  - DQL strict mode set correctly for your data model
  - Auth handler fires before first sync
  - Observers receive initial data
  - Subscriptions sync data across peers
  - Transactions are atomic
  - No memory leaks on navigation

  Work through the codebase systematically. Show me each file's changes.

  ````
</Accordion>

## Ditto Instance Initialization

v5 separates initialization into four distinct phases for better clarity and control:

<CodeGroup>
  ```csharp C# (v5) theme={null}
  // 1. Configure
  var config = new DittoConfig(
      databaseId: "your-database-id",
      connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live"))
  );

  // 2. Initialize (async)
  var ditto = await Ditto.OpenAsync(config);

  // 3. Authenticate
  ditto.Auth.ExpirationHandler = async (ditto, timeUntilExpiration) =>
  {
      try
      {
          await ditto.Auth.LoginAsync("your-token", DittoAuthenticationProvider.Development);
      }
      catch (Exception error)
      {
          Console.WriteLine($"Ditto auth failed: {error}, time until expiration: {timeUntilExpiration}");
      }
  };

  // 4. Start sync
  ditto.Sync.Start();
  ```

  ```csharp C# (v4) theme={null}
  // Everything mixed together
  var ditto = new Ditto(
      identity: DittoIdentity.OnlinePlayground(
          appId: "your-app-id",
          token: "your-token",
          enableDittoCloudSync: false,
          customAuthUrl: "https://your-server.ditto.live"
      )
  );

  ditto.UpdateTransportConfig(config =>
  {
      config.Connect.WebSocketUrls.Add("wss://...");
  });
  await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false");
  ditto.DisableSyncWithV3();

  ditto.StartSync();
  ```
</CodeGroup>

**These changes provide:**

* Clear separation of connection, initialization, and authentication concerns
* Async initialization prevents blocking the main thread
* No workarounds needed (transport config, DQL strict mode, v3 sync disable)
* Type-safe configuration with compile-time validation

**Initialization Migration Steps**

<Steps>
  <Step title="Replace Initialization">
    Replace `new Ditto()` constructor with `await Ditto.OpenAsync(config)`.

    <CodeGroup>
      ```csharp C# (v5) theme={null}
      var config = new DittoConfig(
          databaseId: "your-database-id",
          connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live"))
      );

      var ditto = await Ditto.OpenAsync(config);
      ditto.Sync.Start();
      ```

      ```csharp C# (v4) theme={null}
      var ditto = new Ditto(
          identity: DittoIdentity.OnlinePlayground(
              appId: "your-app-id",
              token: "your-token",
              enableDittoCloudSync: false,
              customAuthUrl: "https://your-server.ditto.live"
          )
      );
      ditto.StartSync();
      ```
    </CodeGroup>

    **Key changes:**

    * Use `DittoConfig` instead of `DittoIdentity`
    * `appId` → `databaseId`
    * `.OnlinePlayground` → `new DittoConfigConnect.Server(new Uri(...))`
    * `.OfflinePlayground` → `new DittoConfigConnect.SmallPeersOnly(privateKey: null)`
    * Add `await` for async initialization
    * Remove `UpdateTransportConfig`, `DisableSyncWithV3()`, and DQL strict mode queries
  </Step>

  <Step title="Update Authentication">
    Set authentication handler separately after initialization.

    <CodeGroup>
      ```csharp C# (v5) theme={null}
      // Set handler after Ditto.OpenAsync()
      // Handler signature: Task DittoAuthenticationExpirationHandler(Ditto ditto, TimeSpan timeUntilExpiration)
      ditto.Auth.ExpirationHandler = async (ditto, timeUntilExpiration) =>
      {
          // Initial auth or token refresh
          try
          {
              await ditto.Auth.LoginAsync(
              "your-token",
              DittoAuthenticationProvider.Development);
          }
          catch (Exception error)
          {
              Console.WriteLine($"Ditto auth failed: {error}, time until expiration: {timeUntilExpiration}");
          }
      };
      ```

      ```csharp C# (v4) theme={null}
      // Auth mixed with identity
      var ditto = new Ditto(
          identity: DittoIdentity.OnlinePlayground(
              appId: "...",
              token: "...",  // Token in identity
              enableDittoCloudSync: false,
              customAuthUrl: "..."
          )
      );
      ```
    </CodeGroup>

    **Key changes:**

    * Authentication now uses `DittoAuthenticationExpirationHandler` async delegate
    * Delegate signature: `Task DittoAuthenticationExpirationHandler(Ditto ditto, TimeSpan timeUntilExpiration)`
    * First parameter is the `Ditto` instance — access auth via `ditto.Auth.LoginAsync()`
    * Second parameter is `TimeSpan` — check `timeUntilExpiration == TimeSpan.Zero` for initial auth
    * Handler called when auth required (`TimeSpan.Zero`) or token expiring (`> TimeSpan.Zero`)
    * Use `DittoAuthenticationProvider.Development` for playground tokens
    * Custom auth: `new DittoAuthenticationProvider("your-provider")`
  </Step>
</Steps>

## Additional Changes

### DQL Strict Mode Behavior Change

<Warning>
  **Breaking Change**: v5 defaults to `DQL_STRICT_MODE=false`, which fundamentally changes how DQL queries behave.

  * **v4 default**: Objects treated as **REGISTER** (whole-object replacement)
  * **v5 default**: Objects treated as **MAP** (field-level merging)

  This affects the behavior of all DQL `SELECT`, `INSERT`, and `UPDATE` operations.
</Warning>

Choose the appropriate migration path based on your current v4 configuration:

<AccordionGroup>
  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=true (v4 default)">
    **If you're currently using the v4 default (`DQL_STRICT_MODE=true`)**, you **must** explicitly set strict mode to `true` in v5 before starting sync or executing any queries.

    <Warning>
      Failing to set strict mode will cause objects to merge at the field level instead of replacing entirely, which can result in unexpected data behavior and perceived data loss.
    </Warning>

    <Note>
      To migrate to `DQL_STRICT_MODE=false` (the new v5 default), contact Ditto Customer Support for guidance.
    </Note>

    <CodeGroup>
      ```csharp C# (v5 - Maintain v4 Behavior) highlight={7,8} theme={null}
      var config = new DittoConfig(
          databaseId: "your-database-id",
          connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live"))
      );
      var ditto = await Ditto.OpenAsync(config);

      // IMPORTANT: Set strict mode BEFORE starting sync or running queries
      await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = true");

      // Now safe to start sync
      ditto.Sync.Start();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using DQL with DQL_STRICT_MODE=false">
    **If you explicitly set `DQL_STRICT_MODE=false` in v4**, no changes are required.

    v5 uses `DQL_STRICT_MODE=false` as the default, so your existing DQL queries will behave identically. You can upgrade freely.

    **Optional**: You can remove the explicit `ALTER SYSTEM SET DQL_STRICT_MODE = false` statement in v5 since this is now the default behavior.

    <CodeGroup>
      ```csharp C# (v5) theme={null}
      var config = new DittoConfig(
          databaseId: "your-database-id",
          connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live"))
      );
      var ditto = await Ditto.OpenAsync(config);

      // No need to set DQL_STRICT_MODE - false is now the default
      ditto.Sync.Start();
      ```

      ```csharp C# (v4 - Your Current Setup) theme={null}
      var identity = DittoIdentity.OnlinePlayground(
          appId,
          playgroundToken,
          false,
          authUrl);

      ditto = new Ditto(identity, tempDir);

      // You explicitly set this in v4
      await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false");

      ditto.StartSync();
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Currently Using Legacy Query Builder">
    The Legacy Query Builder has been removed in v5. All queries must be converted to DQL before upgrading.

    **Good news**: Legacy Query Builder functionality has 1:1 support with `DQL_STRICT_MODE=false` (the v5 default), making migrations straightforward.

    **Migration Steps**:

    1. **In v4: Set `DQL_STRICT_MODE=false`** after initialization:
           <CodeGroup>
             ```csharp C# (v4 - Enable Non-Strict Mode) theme={null}
             var identity = DittoIdentity.OnlinePlayground(
                 appId,
                 playgroundToken,
                 false,
                 authUrl);

             ditto = new Ditto(identity, tempDir);

             // Set DQL_STRICT_MODE to false for Query Builder compatibility
             await ditto.Store.ExecuteAsync("ALTER SYSTEM SET DQL_STRICT_MODE = false");

             ditto.StartSync();
             ```
           </CodeGroup>

    2. **Convert all queries from Legacy Query Builder to DQL**
       See the [C# Legacy→DQL Migration Guide](./c-sharp-dql) for detailed conversion examples.

    3. **Upgrade to v5**
       No DQL configuration changes required—v5 defaults to `DQL_STRICT_MODE=false`.
  </Accordion>
</AccordionGroup>

For additional guidance or questions, contact Ditto Customer Support.

### Default Persistence Directory

v5 includes the database ID in the default directory name: `ditto-{databaseId}` instead of `ditto`

**To maintain v4 compatibility:**

```csharp theme={null}
var config = new DittoConfig(
    databaseId: "your-database-id",
    connect: new DittoConfigConnect.Server(new Uri("https://your-server.ditto.live")),
    persistenceDirectory: "/app/data/ditto"  // Old v4 path
);
```

### `WifiAwareConfig` (new)

```csharp theme={null}
// Configure Wi-Fi Aware (Android)
var transportConfig = ditto.TransportConfig;
transportConfig.PeerToPeer.WifiAware.Enabled = true;
ditto.TransportConfig = transportConfig;
```

### `DittoSyncPermissions` (MAUI only)

```csharp theme={null}
// Request Bluetooth and Wi-Fi permissions on Android/iOS
await DittoSyncPermissions.RequestPermissionsAsync();
```

### Back Pressure (RegisterObserver with signalNext)

Use the `Action<DittoQueryResult, Action>` overload of `RegisterObserver` for manual back-pressure control. The `signalNext` `Action` must be called when your handler is ready for the next update.

**Coalescing**: changes that occur while your handler executes are coalesced — you get the latest state.

```csharp theme={null}
// Standard (auto-signal after handler returns)
ditto.Store.RegisterObserver(query, result => UpdateUI(result.Items));

// Async (auto-signal after Task completes)
ditto.Store.RegisterObserver(query, async result => {
    await ExpensiveOperationAsync(result.Items);
});

// Back-pressure (manual signal)
ditto.Store.RegisterObserver(
    query,
    new Dictionary<string, object> { ["color"] = "blue" },
    async (result, signalNext) => {
        await ExpensiveOperationAsync(result.Items);
        signalNext();  // Call when ready for the next update
    }
);
```

The `signalNext` parameter is a plain `Action` (no named type).

### Disk Usage

```csharp theme={null}
// v4
var execResult = ditto.DiskUsage.Exec();
// DittoDiskUsageChild → DittoDiskUsageItem

// v5
var item = ditto.DiskUsage.Item;  // Property, not method
// Type: DittoDiskUsageItem (renamed from DittoDiskUsageChild)
```

### Ditto Logger Changes

In v5, you now set logging enabled or disabled using the `DittoLogger.IsEnable` property.

<CodeGroup>
  ```csharp C# (v5) theme={null}
  //new property to set logging enabled or disabled
  DittoLogger.IsEnabled = true;
  ```

  ```csharp C# (v4) theme={null}
  DittoLogger.SetLoggingEnabled(true);
  ```
</CodeGroup>

### Namespace Changes

Types have moved to new namespaces in v5:

| Type                                                                                         | Old Namespace | New Namespace         |
| -------------------------------------------------------------------------------------------- | ------------- | --------------------- |
| `DittoLogLevel`, `DittoLogger`                                                               | `DittoSDK`    | `DittoSDK.Logging`    |
| `DittoStoreObserver`, `DittoQueryResult`, `DittoQueryResultItem`                             | `DittoSDK`    | `DittoSDK.Store`      |
| `DittoSyncSubscription`                                                                      | `DittoSDK`    | `DittoSDK.Sync`       |
| `DittoPeer`, `DittoPresenceGraph`, `DittoPresence`, `DittoConnection`, `DittoConnectionType` | `DittoSDK`    | `DittoSDK.Transport`  |
| `DittoAuthenticator`, `DittoAuthenticationProvider`, `DittoAuthenticationExpirationHandler`  | `DittoSDK`    | `DittoSDK.Auth`       |
| `DittoException`                                                                             | `DittoSDK`    | `DittoSDK.Exceptions` |
| `DittoDiskUsageItem`                                                                         | `DittoSDK`    | `DittoSDK.DiskUsage`  |

#### Property changes

| Type              | V4 Property                   | V5 Property                        |
| ----------------- | ----------------------------- | ---------------------------------- |
| `DittoPeer`       | `PeerKeyString`               | `PeerKey`                          |
| `DittoPeer`       | `Os` (string)                 | `Os` (DittoPeerOS?) — type changed |
| `DittoPeer`       | `IsDittoCloudConnected`       | `IsConnectedToDittoServer`         |
| `DittoConnection` | `PeerKeyString1`              | `PeerKey1`                         |
| `DittoConnection` | `PeerKeyString2`              | `PeerKey2`                         |
| `DittoConnection` | `ApproximateDistanceInMeters` | **REMOVED — no replacement**       |

### API Renames

| v4.14                                                                        | v5.0                                                        |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `ditto.IsSyncActive`                                                         | `ditto.Sync.IsActive`                                       |
| `new Ditto(DittoIdentity, string)`                                           | `Ditto.Open(DittoConfig)` or `Ditto.OpenAsync(DittoConfig)` |
| `Ditto.StartSync()`                                                          | `Ditto.Sync.Start()`                                        |
| `Ditto.StopSync()`                                                           | `Ditto.Sync.Stop()`                                         |
| `Ditto.SiteId`                                                               | Removed — use peer identity from presence graph             |
| `Ditto.SDKVersion`                                                           | `Ditto.Version` (static property)                           |
| `Ditto.Identity` property                                                    | Removed — use `DittoConfig.DatabaseId`                      |
| `IDittoAuthenticationDelegate` interface                                     | `DittoAuthenticationExpirationHandler` async delegate       |
| `DittoAuthenticator.AuthenticationDelegate`                                  | `DittoAuthenticator.ExpirationHandler`                      |
| `DittoAuthenticator.LoginWithToken()` / `Login()` / `LoginWithCredentials()` | `DittoAuthenticator.LoginAsync()`                           |
| `DittoStore.Write(Action<DittoWriteTransaction>)`                            | `DittoStore.TransactionAsync(Func<DittoTransaction, Task>)` |
| `DittoLiveQuery`                                                             | `DittoStoreObserver`                                        |
| `DittoSubscription`                                                          | `DittoSyncSubscription`                                     |
| `DittoDiskUsage.Exec()`                                                      | `DittoDiskUsage.Item` (property)                            |
| `DittoDiskUsageChild`                                                        | `DittoDiskUsageItem`                                        |
| `DittoError` class                                                           | Exception types in `DittoSDK.Exceptions` namespace          |
| `DittoException` (in `DittoSDK`)                                             | `DittoException` (in `DittoSDK.Exceptions`)                 |
| `PeerKeyString`                                                              | `PeerKey`                                                   |
| `IsDittoCloudConnected`                                                      | `IsConnectedToDittoServer`                                  |

***

## APIs Removed

| Removed API                                                    | Notes                                                           |
| -------------------------------------------------------------- | --------------------------------------------------------------- |
| `Ditto.DisableSyncWithV3()`                                    | v3 peer-to-peer sync interoperability dropped entirely          |
| `Ditto.GetTransportDiagnostics()`                              | Transport diagnostics removed                                   |
| `Ditto.SiteId`                                                 | Removed — use peer identity from presence graph                 |
| `Ditto.SDKVersion` (instance)                                  | Use static `Ditto.Version` instead                              |
| `DittoLiveQueryEvent.Hash()`                                   | Document change hashing removed; no v5 equivalent               |
| `DittoLiveQueryEvent.HashMnemonic()`                           | Human-readable hash mnemonic removed                            |
| `DittoWriteStrategy.UpdateDifferentValues`                     | No `ON ID CONFLICT DO UPDATE_DIFFERENT_VALUES` in DQL           |
| `IDittoAuthenticationDelegate.AuthenticationStatusDidChange()` | Status observation now via `DittoAuthenticator.ObserveStatus()` |
| `DittoUpdateResult` (and subclasses)                           | DQL mutations return affected-row counts only                   |
| `DittoPendingCursorOperation.ObserveLocalWithNextSignal()`     | Use `RegisterObserver` with `signalNext` overload instead       |
| `DittoSmallPeerInfoSyncScope` enum                             | Sync scope is now a DQL `ALTER SYSTEM` string setting           |
| `DittoSortDirection` enum                                      | Use `ASC`/`DESC` in DQL `ORDER BY`; no enum type in v5          |
| `DittoStore.FetchAttachment(DittoAttachmentFetchDelegate)`     | Use async `Func`/Task overload                                  |
| `DittoIdentityType` enum                                       | Replaced by `DittoConfig` factory methods                       |

***

## New APIs in v5

* **`Ditto.Sync.Start()` / `Ditto.Sync.Stop()`** — replaces `Ditto.StartSync()` / `StopSync()`
* **`Ditto.Version`** (static) — replaces `Ditto.SDKVersion`
* **`Ditto.DeviceName`** — custom peer device name (get/set)
* **`DittoAuthenticationExpirationHandler`** — async delegate replacing `IDittoAuthenticationDelegate`
* **`DittoAuthenticator.ObserveStatus()`** — returns `IDisposable` observer for auth status changes
* **`DittoAuthenticator.Logout()`** — explicit logout support
* **`DittoSDK.Auth` namespace** — all authentication types moved here
* **`DittoStore.TransactionAsync()`** — async DQL transaction using `DittoTransaction`
* **`DittoStoreObserver`** — replaces `DittoLiveQuery`
* **`DittoSyncSubscription`** — replaces `DittoSubscription`; exposes `QueryString`, `QueryArguments`, `Cancel()`
* **`DittoQueryResultItem`** — new type for single DQL result row with `.Value` property
* **`WifiAwareConfig`** / **`WifiAwareConfigBuilder`** — new transport config for Android Wi-Fi Aware (MAUI)
* **`DittoSyncPermissions`** (MAUI) — `RequestPermissionsAsync()` for Bluetooth/Wi-Fi permissions
* **`DittoDiskUsageItem`** — replaces `DittoDiskUsageChild`; uses `System.Text.Json`
* **`DittoDiskUsage.Item`** (property) — replaces `Exec()` method
* **`DittoSDK.DiskUsage` namespace** — disk usage types moved here
* **`DittoSDK.Exceptions` namespace** — exception types moved here

***

## Migration Checklist

### Update NuGet Package

* [ ] Update `DittoSDK` NuGet package to v5.x

### Fix Namespace Imports

* [ ] Add `using DittoSDK.Auth;` where auth types are used
* [ ] Add `using DittoSDK.Exceptions;` where `DittoException` is caught
* [ ] Add `using DittoSDK.Logging;` where `DittoLogger` or `DittoLogLevel` are used
* [ ] Update any other namespace references per compiler errors

### Initialization

* [ ] Replace `new Ditto()` with `await Ditto.OpenAsync(config)`
* [ ] Create `DittoConfig` with `databaseId` and `connect` mode
* [ ] Add `await` for async initialization
* [ ] Update `.OnlinePlayground` → `new DittoConfigConnect.Server(new Uri(...))`
* [ ] Update `.OfflinePlayground` → `new DittoConfigConnect.SmallPeersOnly(privateKey: null)`
* [ ] Remove `UpdateTransportConfig` calls
* [ ] Remove `ALTER SYSTEM SET DQL_STRICT_MODE = false` queries (this is now the v5 default); if your app used the v4 default (strict mode = true), add `ALTER SYSTEM SET DQL_STRICT_MODE = true` BEFORE `Sync.Start()` to maintain v4 behavior

### Update Sync API

* [ ] Replace `ditto.StartSync()` → `ditto.Sync.Start()`
* [ ] Replace `ditto.StopSync()` → `ditto.Sync.Stop()`
* [ ] Remove `DisableSyncWithV3()` calls

### Update Authentication

* [ ] Replace `IDittoAuthenticationDelegate` implementations with `DittoAuthenticationExpirationHandler` async delegate
* [ ] Replace `LoginWithToken()` / `Login()` / `LoginWithCredentials()` → `LoginAsync()`
* [ ] Set `ExpirationHandler` delegate after initialization
* [ ] Use `DittoAuthenticationProvider.Development` for playground tokens
* [ ] Remove authentication from identity configuration

### Observers

* [ ] Replace `DittoLiveQuery` / `.ObserveLocal()` with `DittoStoreObserver` / `Store.RegisterObserver()`
* [ ] Observer callbacks receive `DittoQueryResult` with `.Items` property
* [ ] Use `observer.Cancel()` for cleanup (replaces `liveQuery.Stop()`)
* [ ] If processing is expensive, use `Action<DittoQueryResult, Action>` signalNext overload for back-pressure control

### Breaking Changes

* [ ] Set `PersistenceDirectory` if maintaining v4 directory structure
* [ ] Update `PeerKeyString` → `PeerKey`
* [ ] Update `IsDittoCloudConnected` → `IsConnectedToDittoServer`
* [ ] Replace `DittoStore.Write()` → `DittoStore.TransactionAsync()` (callback uses `DittoTransaction`, not `DittoWriteTransaction`)
* [ ] Add `using DittoSDK.Auth;`, `using DittoSDK.Exceptions;` as needed
* [ ] `UpdateTransportConfig()` callback parameter changed from `Action<DittoTransportConfig>` to `Action<DittoTransportConfigBuilder>` (builder pattern)

### Update Renamed Types

* [ ] `Ditto.SiteId` → Removed (use peer identity from presence graph)
* [ ] `Ditto.SDKVersion` → `Ditto.Version` (static)
* [ ] `DittoDiskUsageChild` → `DittoDiskUsageItem`
* [ ] `DittoDiskUsage.Exec()` → `DittoDiskUsage.Item` (property)
* [ ] `DittoSubscription` → `DittoSyncSubscription`
* [ ] `DittoLiveQuery` → `DittoStoreObserver`

### Verification

* [ ] Build compiles with zero errors
* [ ] No deprecated API warnings
* [ ] DQL strict mode set correctly for your data model
* [ ] Auth handler fires before first sync
* [ ] Observers update UI immediately
* [ ] No memory leaks on navigation

***

## Common Pitfalls

1. **`DQL_STRICT_MODE` silent change**: Default flipped. Objects merge field-by-field instead of replacing whole. Set `DQL_STRICT_MODE=true` BEFORE `Sync.Start()` if migrating existing data.

2. **Missing `await` on `ExecuteAsync`**: `ExecuteAsync` returns `Task<DittoQueryResult>`. Forgetting `await` causes silent no-op.

3. **`DittoSortDirection` removed**: `Sort("field", DittoSortDirection.Ascending)` won't compile. Use `ORDER BY field ASC` in DQL.

4. **`DittoIdentityType` enum**: Removed entirely. Code checking identity type won't compile.

5. **`LoginWithToken()` removed**: Must use `LoginAsync()`. The sync overloads are gone.

6. **`Write()` vs `TransactionAsync()`**: The callback signature changed completely — the callback now receives `DittoTransaction` (not `DittoWriteTransaction`) and uses async DQL, not collection-based mutations.

7. **`ExecuteAsync` argument type**: `ExecuteAsync` takes `Dictionary<string, object>` — anonymous objects like `new { color = "blue" }` are NOT accepted. Use `new Dictionary<string, object> { ["color"] = "blue" }`. Note: `RegisterObserver` and `RegisterSubscription` DO have overloads accepting anonymous `object` arguments.

8. **`ObserveLocalWithNextSignal()` removed from collection API**: Back-pressure IS supported in v5 via the `RegisterObserver` overload that takes `Action<DittoQueryResult, Action>` where the second `Action` is the `signalNext` callback.

***
