Skip to main content

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.

Overview

This guide covers the essential changes needed to migrate your Ditto C++ app from v4 to v5. The main architectural shift is removing the deprecated identity-based constructors and requiring the four-phase configuration model (DittoConfig + Ditto::open) that was introduced in late v4. v5 also removes all legacy collection-based APIs in favor of DQL.
If you already use Ditto::open(DittoConfig) in v4, the initialization code is largely the same β€” focus on the removed APIs, renames, and DQL migration sections below.
Also required: v5 uses DQL (Ditto Query Language) for all data operations. See the DQL Migration Guide 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.
I need help migrating a Ditto C++ application from v4 to v5. Focus on these critical changes:

INITIALIZATION:
Replace deprecated Ditto() constructor with Ditto::open(config) using builder pattern.
- Create DittoConfig with set_database_id and set_connect
- Identity::OnlinePlayground(app_id, token, cloud_sync, url) β†’ DittoConfig::default_config().set_database_id(app_id).set_connect(DittoConfig::Connect::server(url))
- Identity::OfflinePlayground(app_id) β†’ DittoConfig::default_config().set_database_id(app_id).set_connect(DittoConfig::Connect::small_peers_only())
- Identity::SharedKey(app_id, key) β†’ DittoConfig::default_config().set_database_id(app_id).set_connect(DittoConfig::Connect::small_peers_only(key))
- Identity::Manual(cert) β†’ No v5 equivalent β€” consult Ditto support
- Ditto::open(config) returns std::shared_ptr<Ditto>
- Remove disable_sync_with_v3, DQL strict mode workarounds
- IMPORTANT: If v4 app used default (strict=true), set DQL_STRICT_MODE=true BEFORE starting sync
- Remove Ditto::close() calls β€” use shared_ptr lifetime management
- Convenience shortcuts: set_server_connect(url) and set_small_peers_only_connect(key)
- Running update_transport_config is no longer needed to update a websocket_urls when using a Big Peer as this is automatically set by the SDK from the auth server URL.  

AUTHENTICATION:
Set set_expiration_handler lambda after Ditto::open().
- Handler signature: std::function<void(Ditto &ditto, uint32_t sec_remaining)>
- Access auth via ditto.get_auth()-> (not ditto.auth())
- get_auth() returns nullptr for non-authenticated configs β€” always null-check
- Handler called when sec_remaining == 0 (initial auth) or > 0 (token expiring)
- Use Authenticator::get_development_provider() for playground tokens
- Login: ditto.get_auth()->login(token, provider, [](std::unique_ptr<std::string> client_info, std::unique_ptr<DittoError> error) { ... })
- New: Authenticator::get_status(), observe_status(callback), logout(cleanup)

SYNC:
- ditto->start_sync() β†’ ditto->get_sync().start()
- ditto->stop_sync() β†’ ditto->get_sync().stop()
- ditto->get_is_sync_active() β†’ ditto->get_sync().is_active()

PRESENCE:
- ditto->observe_peers(cb) β†’ ditto->get_presence().observe(cb)
- peer.peer_key_string β†’ peer.peer_key
- peer.is_connected_to_ditto_cloud β†’ peer.is_connected_to_ditto_server
- connection.peer_key_string1/2 β†’ connection.peer1/2
- connection.approximate_distance_in_meters β†’ REMOVED, no replacement
- New: Presence::graph(), peer_metadata(), set_peer_metadata(), set_connection_request_handler()

COLLECTION API β†’ DQL:
All collection APIs are removed. Replace with get_store().execute() using DQL.
- collection.find("color == 'blue'").exec() β†’ ditto->get_store().execute("SELECT * FROM cars WHERE color = :color", {{"color", "blue"}})
- collection.find_by_id(id).exec() β†’ ditto->get_store().execute("SELECT * FROM cars WHERE _id = :id", {{"id", id}})
- collection.upsert(value) β†’ ditto->get_store().execute("INSERT INTO cars DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE", {{"doc", value}})
- collection.find_by_id(id).update(...) β†’ ditto->get_store().execute("UPDATE cars SET color = :color WHERE _id = :id", {{"color", "green"}, {"id", id}})
- collection.find_by_id(id).remove() β†’ ditto->get_store().execute("DELETE FROM cars WHERE _id = :id", {{"id", id}})
- collection.find_by_id(id).evict() β†’ ditto->get_store().execute("EVICT FROM cars WHERE _id = :id", {{"id", id}})
- Always use parameterized queries with :paramName β€” NEVER use string concatenation

WRITE TRANSACTIONS:
- Store::write(fn) with WriteTransaction β†’ REMOVED
- Replace with Store::transaction(fn), execute_transaction(fn), or transaction_returning<T>(fn)
- TransactionCompletionAction::commit / rollback controls outcome

LIVE QUERIES β†’ STORE OBSERVERS:
- collection.find("...").observe_local([](auto docs, auto event) { ... }) β†’ ditto->get_store().register_observer("SELECT ...", args, [](QueryResult result) { ... })
- observer->cancel() to stop (same as v4)
- v4 handler: (std::vector<Document>, LiveQueryEvent) β†’ v5 handler: (QueryResult)

SUBSCRIPTIONS:
- collection.find("color == 'red'").subscribe() β†’ ditto->get_sync().register_subscription("SELECT * FROM cars WHERE color = :color", {{"color", "red"}})

BACK PRESSURE:
- StoreObservationHandlerWithNextSignal β†’ StoreObservationHandlerWithSignalNext
- NextSignal β†’ SignalNext (std::function<void()>)
- Standard observer auto-signals; use SignalNext overload for manual control

LOGGING:
- Log:: β†’ Logger:: (all static methods)
- Log::Callback (raw function pointer) β†’ Logger::Callback (std::function)
- Log::set_custom_log_cb β†’ Logger::set_custom_log_cb
- Log::set_log_file(path) β†’ REMOVED (continuous file logging gone; use Logger::set_custom_log_cb() for ongoing file output)
- Log::disable_log_file() β†’ REMOVED
- Log::export_to_file(path) β†’ Logger::export_to_file(path) (one-time dump, NOT a set_log_file replacement)
- Log::set/get_emoji_log_level_headings_enabled β†’ removed

DISK USAGE:
- DiskUsageChild β†’ DiskUsageItem
- DiskObserverContext β†’ DiskUsageObserver
- DiskUsage::exec() β†’ DiskUsage::item()

TRANSPORT CONFIG:
- update_transport_config still works in v5
- HttpListenConfig::static_content_path β†’ REMOVED
- All other fields unchanged

DITTO CLASS:
- Ditto() deprecated constructors β†’ removed (use Ditto::open(config))
- Ditto::close() β†’ removed (use shared_ptr lifetime)
- Ditto::get_sdk_version() β†’ Ditto::get_version()
- ditto_semver_version() β†’ ditto::internal::ditto_semver_version() (use Ditto::get_version() instead)
- Identity class hierarchy β†’ removed (use DittoConfig)

CONTAINERS:
- store.observers (public field) β†’ removed, use store.get_observers() (returns std::unordered_set)
- Sync::subscriptions (public field) β†’ removed, use Sync::get_subscriptions() (returns SyncSubscriptionSet = std::unordered_set)

REMOVED APIs (no replacement):
- Ditto::close() β€” shared_ptr lifetime management
- Ditto::disable_sync_with_v3() β€” v3 protocol dropped
- Log emoji headings β€” removed
- Log::set_log_file/disable_log_file β€” continuous file logging removed (use Logger::set_custom_log_cb() for ongoing file output; Logger::export_to_file() is one-time only)
- Identity class hierarchy β€” replaced by DittoConfig
- Identity::Manual β€” no equivalent
- LiveQueryEvent::hash()/hash_mnemonic() β€” query hashing removed
- DocumentIdPath/AbstractDocumentPath β€” removed with legacy collection API
- Connection::approximate_distance_in_meters β€” peer distance estimation removed
- HttpListenConfig::static_content_path β€” static content serving removed
- 38 legacy headers removed (Collection, Document, LiveQuery, WriteTransaction, etc.)

BEFORE (V4):
```cpp
auto ditto = Ditto(
    Identity::OnlinePlayground(
        "your-app-id",
        "your-token",
        false,
        "https://your-server.ditto.live"
    )
);
ditto.update_transport_config([](auto& config) {
    config.connect.websocket_urls.insert("wss://...");
});
ditto.get_store().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false", {});
ditto.disable_sync_with_v3();
ditto.start_sync();
```

AFTER (V5):
```cpp
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"));

auto ditto = Ditto::open(config);

ditto->get_auth()->set_expiration_handler([](Ditto &ditto, uint32_t sec_remaining) {
    ditto.get_auth()->login(
        "your-token",
        Authenticator::get_development_provider(),
        [](std::unique_ptr<std::string> client_info, std::unique_ptr<DittoError> error) {
            if (error) {
                std::cout << "Authentication failed: " << error->what() << std::endl;
            }
        }
    );
});

ditto->get_sync().start();
```

CHECKLIST:
- Replace Ditto() deprecated constructor with Ditto::open(config)
- Create DittoConfig with builder pattern
- Set set_expiration_handler for authentication (ditto->get_auth()->)
- Remove disable_sync_with_v3, DQL strict mode
- Replace observe_peers(cb) with get_presence().observe(cb)
- Replace peer_key_string with peer_key
- Replace is_connected_to_ditto_cloud with is_connected_to_ditto_server
- Replace connection.peer_key_string1/2 with connection.peer1/2
- Remove approximate_distance_in_meters usage
- Replace Log:: with Logger::
- Replace StoreObservationHandlerWithNextSignal with StoreObservationHandlerWithSignalNext
- Replace NextSignal with SignalNext
- Replace start_sync() β†’ get_sync().start(), stop_sync() β†’ get_sync().stop()
- Replace get_sdk_version() with get_version()
- Remove Ditto::close() calls
- Replace DiskUsageChild with DiskUsageItem, DiskUsage::exec() with item()
- Replace Store::write() with Store::transaction() or execute_transaction()
- Remove HttpListenConfig::static_content_path references

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

Ditto Instance Initialization

v5 removes the deprecated Ditto() constructors and the Identity class hierarchy. Ditto::open(DittoConfig) β€” which was already available in v4 β€” is now the only way to create a Ditto instance. v5 separates initialization into four distinct phases for better clarity and control:
// 1. Configure
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"));

// 2. Initialize
auto ditto = Ditto::open(config);

// 3. Authenticate
ditto->get_auth()->set_expiration_handler([](Ditto &ditto, uint32_t sec_remaining) {
    ditto.get_auth()->login(
        "your-token",
        Authenticator::get_development_provider(),
        [](std::unique_ptr<std::string> client_info, std::unique_ptr<DittoError> error) {
            if (error) {
                std::cout << "Authentication failed: " << error->what() << std::endl;
            } else {
                std::cout << "Authentication successful" << std::endl;
            }
        }
    );
});

// 4. Start sync
ditto->get_sync().start();
These changes provide:
  • Clear separation of connection, initialization, and authentication concerns
  • Builder pattern for flexible configuration
  • No workarounds needed (DQL strict mode, v3 sync disable)
  • Type-safe configuration with compile-time validation
Initialization Migration Steps
1

Replace Initialization

Replace the deprecated Ditto() constructor with Ditto::open(config).
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"));

auto ditto = Ditto::open(config);
ditto->get_sync().start();
Key changes:
  • Use DittoConfig::default_config() builder pattern instead of Identity
  • app_id β†’ database_id
  • Identity::OnlinePlayground β†’ DittoConfig::Connect::server(...)
  • Identity::OfflinePlayground β†’ DittoConfig::Connect::small_peers_only() (empty string = no encryption)
  • Identity::SharedKey β†’ DittoConfig::Connect::small_peers_only("your-shared-key")
  • Remove disable_sync_with_v3() calls
  • Convenience shortcuts available: set_server_connect(url) and set_small_peers_only_connect(key) can replace set_connect(DittoConfig::Connect::server(url))
Identity Type Mapping:
v4 Identityv5 Equivalent
Identity::OnlinePlayground(app_id, token, cloud_sync, url)DittoConfig::Connect::server(url) + auth.set_expiration_handler(...) + auth.login(token, Authenticator::get_development_provider(), ...)
Identity::OnlineWithAuthentication(app_id, callback, cloud_sync, url)DittoConfig::Connect::server(url) + auth.set_expiration_handler(...) + custom auth logic
Identity::OfflinePlayground(app_id)DittoConfig::Connect::small_peers_only() + ditto->set_offline_only_license_token(token)
Identity::SharedKey(app_id, shared_key)DittoConfig::Connect::small_peers_only(shared_key)
Identity::Manual(certificate_config)Removed β€” no direct equivalent, consult Ditto support
2

Update Authentication

Set authentication handler separately after initialization.
// Set handler after Ditto::open()
// Handler signature: void(Ditto &ditto, uint32_t sec_remaining)
ditto->get_auth()->set_expiration_handler([](Ditto &ditto, uint32_t sec_remaining) {
        ditto.get_auth()->login(
            "your-token",
            Authenticator::get_development_provider(),
            [](std::unique_ptr<std::string> client_info, std::unique_ptr<DittoError> error) {
                if (error) {
                    std::cout << "Authentication failed: " << error->what() << std::endl;
                } else {
                    std::cout << "Authentication successful" << std::endl;
                }
            }
        );
});
Key changes:
  • Authentication now uses set_expiration_handler lambda
  • Handler signature: std::function<void(Ditto &ditto, uint32_t sec_remaining)>
  • First param is Ditto & reference β€” access auth via ditto.get_auth()->
  • Handler called when auth required (sec_remaining == 0) or token expiring
  • Use Authenticator::get_development_provider() for playground tokens
  • Custom auth: pass provider name string directly to login()
  • get_auth() returns nullptr if not configured for authentication (e.g., small_peers_only without a server) β€” always null-check before accessing
Additional Authenticator APIs in v5:
  • Authenticator::get_status() β€” returns current AuthenticationStatus
  • Authenticator::observe_status(callback) β€” observe status changes
  • Authenticator::logout(cleanup) β€” log out and optionally run cleanup

Additional Changes

DQL Strict Mode Behavior Change

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.
Choose the appropriate migration path based on your current v4 configuration:
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.
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.
To migrate to DQL_STRICT_MODE=false (the new v5 default), contact Ditto Customer Support for guidance.
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"));
auto ditto = Ditto::open(config);

// IMPORTANT: Set strict mode BEFORE starting sync or running queries
ditto->get_store().execute("ALTER SYSTEM SET DQL_STRICT_MODE = true");

// Now safe to start sync
ditto->get_sync().start();
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.
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"));
auto ditto = Ditto::open(config);

// No need to set DQL_STRICT_MODE - false is now the default
ditto->get_sync().start();
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:
    auto ditto = Ditto(
        Identity::OnlinePlayground(
            "your-app-id",
            "your-token",
            false,
            "https://your-server.ditto.live"
        )
    );
    
    // Set DQL_STRICT_MODE to false for Query Builder compatibility
    ditto.get_store().execute("ALTER SYSTEM SET DQL_STRICT_MODE = false", {});
    
    ditto.start_sync();
    
  2. Convert all queries from Legacy Query Builder to DQL See the C++ Legacy→DQL Migration Guide for detailed conversion examples.
  3. Upgrade to v5 No DQL configuration changes requiredβ€”v5 defaults to DQL_STRICT_MODE=false.
For additional guidance or questions, contact Ditto Customer Support.

Default Persistence Directory

v5 includes the database ID in the default directory name: ditto-{database_id} instead of ditto. This is for only new databases created in v5. v5 does not migrate existing databases to the new structure. To maintain v4 compatibility:
auto config = DittoConfig::default_config()
    .set_database_id("your-database-id")
    .set_connect(DittoConfig::Connect::server("https://your-server.ditto.live"))
    .set_persistence_directory("/app/data/ditto");  // Old v4 path

Observer Changes

v4 and v5 both use callback-based observers. The core register_observer API pattern is the same in both versions, returning a StoreObserver that you hold via shared_ptr and call .cancel() on for cleanup.
// Standard observer β€” signal_next called automatically after handler returns
auto observer = ditto->get_store().register_observer(
    "SELECT * FROM cars WHERE color = :color",
    {{"color", "blue"}},
    [](QueryResult result) {
        process_results(result);
        // signal_next called automatically here
    }
);

// Cleanup: cancel explicitly or let shared_ptr go out of scope
observer->cancel();
Key changes:
  • v5 adds introspection methods to StoreObserver: get_query(), get_query_arguments(), get_query_arguments_cbor_data(), get_query_arguments_json_string()
  • Container type changed: store.get_observers() returns std::unordered_set (v4 returned std::set)
  • The deprecated store.observers public field is removed β€” use store.get_observers()

Back Pressure (SignalNext)

The standard register_observer automatically calls signal_next after your handler returns. Use the StoreObservationHandlerWithSignalNext overload when your handler needs manual control over when the next callback is delivered.
Coalescing behavior: While your handler is processing, Ditto coalesces any intermediate changes. When you call signal_next(), you receive a single callback with the latest state β€” not every intermediate change.
// Standard observer β€” signal_next called automatically after handler returns
auto observer = ditto->get_store().register_observer(
    "SELECT * FROM cars WHERE color = :color",
    {{"color", "blue"}},
    [](QueryResult result) {
        process_results(result);
        // signal_next called automatically here
    }
);

// Back-pressure observer β€” you control when the next callback fires
StoreObservationHandlerWithSignalNext handler = [](QueryResult result, SignalNext signal_next) {
    process_results(result);
    // ... do expensive work ...
    signal_next();  // Ready for next update β€” must call this
};

auto observer = ditto->get_store().register_observer(
    "SELECT * FROM cars WHERE color = :color",
    {{"color", "blue"}},
    handler
);
Types:
  • SignalNext = std::function<void()> (renamed from v4’s NextSignal)
  • StoreObservationHandlerWithSignalNext = std::function<void(QueryResult, SignalNext)> (renamed from v4’s StoreObservationHandlerWithNextSignal)

Presence API Changes

Presence is now accessed through ditto->get_presence() rather than directly on the Ditto object.
// Observe presence changes
auto observer = ditto->get_presence().observe([](const PresenceGraph &graph) {
    for (const auto &peer : graph.remote_peers) {
        std::cout << "Peer: " << peer.peer_key << std::endl;
    }
});

// Get current presence graph
auto graph = ditto->get_presence().graph();

// Set peer metadata
ditto->get_presence().set_peer_metadata({{"role", "sensor"}});
Peer property renames:
v4.14v5.0
peer.peer_key_stringpeer.peer_key
peer.is_connected_to_ditto_cloudpeer.is_connected_to_ditto_server
connection.peer_key_string1connection.peer1
connection.peer_key_string2connection.peer2
connection.approximate_distance_in_metersREMOVED β€” no replacement
New Presence APIs in v5:
  • Presence::graph() β€” get current PresenceGraph immediately
  • Presence::peer_metadata() / set_peer_metadata(json) β€” get/set arbitrary peer metadata
  • Presence::peer_metadata_json_string() / set_peer_metadata_json_string(str) β€” JSON string variants
  • Presence::set_connection_request_handler(handler) β€” control which peers can connect

DiskUsage API Changes

v4.14v5.0
DiskUsageChild classDiskUsageItem class
DiskObserverContext classDiskUsageObserver class
DiskUsage::exec()DiskUsage::item()
ditto->get_disk_usage()ditto->get_disk_usage() (unchanged)
disk_usage.observe(cb)disk_usage.observe(cb) (unchanged, callback type same)

Transport Config Changes

update_transport_config and set_transport_config remain available in v5. The key change is:
v4.14v5.0
HttpListenConfig::static_content_pathREMOVED
All other TransportConfig fieldsUnchanged
// Transport config still works in v5
ditto->update_transport_config([](TransportConfig &config) {
    config.peer_to_peer.bluetooth_le.enabled = true;
    config.peer_to_peer.lan.enabled = true;
});

Transaction API Changes

v5 removes the deprecated WriteTransaction and related classes. Use the DQL-based transaction API instead:
#include <Transaction.hpp>

// Basic transaction
auto result = ditto->get_store().transaction(
    [](Transaction &txn) -> TransactionCompletionAction {
        txn.execute("INSERT INTO cars DOCUMENTS (:doc)", {{"doc", car_data}});
        txn.execute("UPDATE inventory SET count = count - 1 WHERE _id = :id", {{"id", inv_id}});
        return TransactionCompletionAction::commit;
    }
);

// Convenience: void-returning lambda (auto-commits on return, rolls back on throw)
ditto->get_store().execute_transaction([](Transaction &txn) {
    txn.execute("INSERT INTO cars DOCUMENTS (:doc)", {{"doc", car_data}});
});

// Return a value from a transaction
auto car_id = ditto->get_store().transaction_returning<std::string>(
    [](Transaction &txn) -> std::string {
        auto result = txn.execute("INSERT INTO cars DOCUMENTS (:doc)", {{"doc", car_data}});
        return result.mutated_document_ids()[0].dump();
    }
);
Key changes:
  • Store::write(fn) with WriteTransaction β€” removed
  • WriteTransaction, ScopedWriteTransaction, WriteTransactionResult, UpdateResult β€” removed
  • DQL-based Store::transaction() and Store::execute_transaction() are the replacement
  • New: Store::transaction_returning<T>() β€” return a value from a transaction
  • TransactionOptions available: set_read_only(bool), set_hint(string)

API Renames

v4.14v5.0
ditto->start_sync()ditto->get_sync().start()
ditto->stop_sync()ditto->get_sync().stop()
ditto->get_is_sync_active()ditto->get_sync().is_active()
ditto->observe_peers(cb)ditto->get_presence().observe(cb)
Ditto::get_sdk_version()Ditto::get_version()
Log class (all static methods)Logger class
Log::Callback (raw function pointer)Logger::Callback (std::function<void(LogLevel, const std::string&)>)
Log::set_custom_log_cb(callback)Logger::set_custom_log_cb(callback)
Log::set_log_file(path)Removed β€” continuous file logging has no v5 equivalent. Use Logger::set_custom_log_cb() to implement your own file writer, or call Logger::export_to_file() for one-time log dumps.
Log::disable_log_file()Removed with set_log_file
StoreObservationHandlerWithNextSignalStoreObservationHandlerWithSignalNext
NextSignal type aliasSignalNext type alias
store.observers (deprecated public field)Removed β€” use store.get_observers() (returns std::unordered_set)
Sync::subscriptions (deprecated public field)Removed β€” use Sync::get_subscriptions() (returns SyncSubscriptionSet)
store.get_observers() returns std::setstore.get_observers() returns std::unordered_set
Sync::get_subscriptions() returns std::setSync::get_subscriptions() returns SyncSubscriptionSet (unordered_set)
Ditto() deprecated constructorRemoved β€” use Ditto::open(DittoConfig)
Identity class hierarchyRemoved β€” use DittoConfig
DiskUsageChildDiskUsageItem
DiskObserverContextDiskUsageObserver
DiskUsage::exec()DiskUsage::item()

Log β†’ Logger Rename

All Log:: call sites must be updated to Logger:::
// v4
Log::set_minimum_log_level(LogLevel::Debug);
Log::set_custom_log_cb([](LogLevel level, std::string msg) {
    std::cout << msg << std::endl;
});

// v5
Logger::set_minimum_log_level(LogLevel::Debug);
Logger::set_custom_log_cb([](LogLevel level, const std::string& msg) {
    std::cout << msg << std::endl;
});
Note: Log::Callback changed from a raw function pointer (void (*)(LogLevel, std::string)) to std::function<void(LogLevel, const std::string&)> β€” lambdas with captures are now supported. Logger::export_to_file(path) performs a one-time export of accumulated logs to disk. It returns std::future<uint64_t> (number of bytes written). The output is gzip-compressed JSON lines (.jsonl.gz recommended extension). This is not a replacement for Log::set_log_file() β€” it existed in v4 as Log::export_to_file() and is only renamed in v5. Migrating from Log::set_log_file(): Continuous real-time file logging has been removed in v5. If you need ongoing file logging, use Logger::set_custom_log_cb() to register a callback that writes to a file yourself.

Header Count Reduced

v5.0 ships 50 header files (down from 86 in v4.14). 38 headers were removed (legacy collection, document, live query, write transaction, identity, and log APIs). 2 headers were added (Logger.hpp, fnv_1a_hash.hpp). If your build system includes specific headers by path, audit the include list after upgrading.

Ditto Lifetime Management

Ditto::close() is removed. Manage Ditto lifetime via the shared_ptr returned by Ditto::open(). The instance is destroyed when all shared_ptr copies go out of scope.

APIs Removed Without Replacement

Removed APINotes
Ditto::close()Use shared_ptr lifetime management
Ditto::disable_sync_with_v3()v3 protocol support fully dropped
Ditto() constructors (with Identity)Use Ditto::open(DittoConfig)
Identity class hierarchyReplaced by DittoConfig
Identity::ManualNo v5 equivalent β€” consult Ditto support
Log classReplaced by Logger class
Log::get_emoji_log_level_headings_enabled() / set_emoji_log_level_headings_enabled()Emoji log headings removed
Log::set_log_file(path) / disable_log_file()Continuous file logging removed β€” no direct replacement. Use Logger::set_custom_log_cb() for ongoing file output, or Logger::export_to_file() for one-time dumps.
HttpListenConfig::static_content_pathStatic HTTP content serving removed
LiveQueryEvent::hash() / hash_mnemonic()Query hash methods removed
Connection::approximate_distance_in_metersPeer distance estimation removed
store.observers (public field)Use store.get_observers() method
Sync::subscriptions (public field)Use Sync::get_subscriptions() method

Legacy Collection API β€” Completely Removed

All collection-based query APIs (38 headers removed) are gone in v5. Use DQL queries via get_store().execute().
Removed Header / ClassReplacement
CollectionDQL queries via store.execute()
Document / MutableDocumentQueryResult items
DocumentPath / MutableDocumentPathAccess values via QueryResult
DocumentIdPath / AbstractDocumentPathUse _id field in DQL
PendingCursorOperation / PendingIDSpecificOperationDQL queries
PendingCollectionsOperationDQL: SELECT DISTINCT collection FROM __collections
LiveQuery / LiveQueryEvent / LiveQueryMovestore.register_observer()
SingleDocumentLiveQueryEventstore.register_observer() with WHERE _id = :id
Subscription (legacy)sync.register_subscription()
WriteTransaction / ScopedWriteTransactionstore.transaction() or store.execute_transaction()
WriteTransactionResult / UpdateResultQueryResult.mutated_document_ids()
WriteStrategyDQL ON ID CONFLICT clauses
SortDirectionDQL ORDER BY ... ASC/DESC
Counter / MutableCounterDQL PN_INCREMENT
Register / MutableRegisterDQL SET
CollectionsEventDQL queries

New APIs in v5

Ditto

  • Ditto::get_version() β€” replaces get_sdk_version()
  • Ditto::get_presence() β€” returns Presence & for presence operations

Logger

  • Logger class β€” replaces Log; callback type is now std::function
  • Logger::export_to_file(path) β€” one-time export of accumulated logs; returns std::future<uint64_t> (bytes written as gzip-compressed JSON lines). Renamed from Log::export_to_file() β€” not a replacement for the removed Log::set_log_file().

Sync

  • ditto->get_sync().start() / .stop() / .is_active() β€” sync lifecycle on Sync object
  • SyncSubscriptionHash / SyncSubscriptionEq β€” hash/eq helpers for subscription unordered_set
  • SyncSubscriptionSet type alias β€” unordered_set of SyncSubscription shared_ptrs
  • SyncSubscription::get_query() / get_query_arguments() / get_query_arguments_cbor_data() / get_query_arguments_json_string() β€” introspect registered subscription

Store & Observers

  • StoreObservationHandlerWithSignalNext / SignalNext β€” renamed from NextSignal variants
  • StoreObserverHash / StoreObserverEq β€” hash/eq helpers for observer unordered_set
  • StoreObserver::get_query() / get_query_arguments() / get_query_arguments_cbor_data() / get_query_arguments_json_string() β€” introspect registered observer
  • Store::execute_transaction() β€” convenience transaction with auto-commit
  • Store::transaction_returning<T>() β€” transaction that returns a value

Authentication

  • Authenticator::get_status() β€” returns current AuthenticationStatus
  • Authenticator::observe_status(callback) β€” observe authentication status changes
  • Authenticator::logout(cleanup) β€” log out with optional cleanup callback

Presence

  • Presence::observe(cb) β€” replaces Ditto::observe_peers()
  • Presence::graph() β€” get current PresenceGraph immediately
  • Presence::peer_metadata() / set_peer_metadata(json) β€” get/set arbitrary peer metadata
  • Presence::peer_metadata_json_string() / set_peer_metadata_json_string(str) β€” JSON string variants
  • Presence::set_connection_request_handler(handler) β€” control incoming peer connections

DiskUsage

  • DiskUsageItem β€” renamed from DiskUsageChild
  • DiskUsageObserver β€” renamed from DiskObserverContext
  • DiskUsage::item() β€” renamed from DiskUsage::exec()

Configuration

  • DittoConfig::set_server_connect(url) β€” convenience shortcut for set_connect(Connect::server(url))
  • DittoConfig::set_small_peers_only_connect(key) β€” convenience shortcut for set_connect(Connect::small_peers_only(key))

Migration Checklist

Initialization

  • Replace deprecated Ditto() constructor with Ditto::open(config)
  • Create DittoConfig with set_database_id and set_connect
  • Update Identity::OnlinePlayground β†’ DittoConfig::Connect::server(...)
  • Update Identity::OfflinePlayground β†’ DittoConfig::Connect::small_peers_only()
  • Update Identity::SharedKey β†’ DittoConfig::Connect::small_peers_only("your-shared-key")
  • Remove disable_sync_with_v3() calls
  • Set DQL_STRICT_MODE=true BEFORE starting sync if maintaining v4 behavior

Authentication

  • Set set_expiration_handler lambda after initialization
  • Handler receives Ditto &ditto, uint32_t sec_remaining
  • Access auth inside handler via ditto.get_auth()->login(...)
  • Use Authenticator::get_development_provider() for playground tokens
  • Remove authentication from identity configuration
  • Null-check get_auth() β€” returns nullptr for non-authenticated configs

Sync

  • Update start_sync() β†’ get_sync().start()
  • Update stop_sync() β†’ get_sync().stop()
  • Update get_is_sync_active() β†’ get_sync().is_active()

Data Operations

  • Replace all collection-based queries with DQL via get_store().execute()
  • Use parameterized queries with :paramName β€” never string interpolation
  • Replace .upsert() with INSERT INTO ... ON ID CONFLICT DO UPDATE
  • Replace .update {} closures with UPDATE SET DQL
  • Replace .remove() with DELETE FROM
  • Replace .evict() with EVICT FROM
  • Replace .observe_local() with get_store().register_observer()
  • Replace .subscribe() with get_sync().register_subscription()
  • Replace Store::write(fn) with Store::transaction() or Store::execute_transaction()

Presence

  • Update observe_peers(cb) β†’ get_presence().observe(cb)
  • Update peer_key_string β†’ peer_key
  • Update peer_key_string1/2 β†’ peer1/2
  • Update is_connected_to_ditto_cloud β†’ is_connected_to_ditto_server
  • Remove approximate_distance_in_meters usage (no replacement)

Logging

  • Replace Log:: with Logger:: throughout codebase
  • Update Log::Callback stored variables: raw function pointer β†’ std::function
  • Update Log::set_custom_log_cb β†’ Logger::set_custom_log_cb
  • Remove Log::set_log_file(path) calls β€” continuous file logging removed. Use Logger::set_custom_log_cb() if you need ongoing file output.
  • Remove Log::disable_log_file() calls
  • Remove Log::set_emoji_log_level_headings_enabled() calls

DiskUsage

  • Update DiskUsageChild β†’ DiskUsageItem
  • Update DiskUsage::exec() β†’ DiskUsage::item()

Breaking Changes

  • Set set_persistence_directory if maintaining v4 directory structure
  • Replace StoreObservationHandlerWithNextSignal β†’ StoreObservationHandlerWithSignalNext
  • Replace NextSignal β†’ SignalNext type alias
  • Replace store.observers field access with store.get_observers() (now returns unordered_set)
  • Replace Sync::subscriptions field access with Sync::get_subscriptions() (now returns unordered_set)
  • Remove Ditto::close() calls β€” manage lifetime via shared_ptr
  • Replace get_sdk_version() with get_version()
  • Audit header includes β€” header count reduced from 86 to 50 (38 removed, 2 added)
  • If processing is expensive, use StoreObservationHandlerWithSignalNext overload for back-pressure control
  • Remove HttpListenConfig::static_content_path references

Verification

  • Build compiles with zero errors
  • No deprecated API warnings
  • Observers update data immediately
  • Authentication works before sync starts
  • No memory leaks

Common Pitfalls

  1. DQL_STRICT_MODE silent change: Not setting DQL_STRICT_MODE=true when your v4 app used the default. Objects that were replaced whole will now merge at field level β€” causes unexpected data merging.
  2. Wrong auth accessor: Using ditto->auth() instead of ditto->get_auth(). Inside the expiration handler, the Ditto & reference requires ditto.get_auth()->login(...) (note: . for the reference, -> for the shared_ptr).
  3. Null auth pointer: get_auth() returns nullptr for small_peers_only configurations without a server. Always null-check before calling methods on the result.
  4. String interpolation in queries: Never build queries with string concatenation. Always use parameterized queries: execute("SELECT * FROM cars WHERE color = :color", {{"color", "blue"}}).
  5. Counter initialization: Don’t put 0 in insert documents for counter fields. Counters are created on first PN_INCREMENT. Inserting 0 creates a REGISTER, not a COUNTER.
  6. Missing ON ID CONFLICT: INSERT fails if document _id already exists without a conflict clause.
  7. Log::Callback type change: v4 used a raw function pointer (void(*)(LogLevel, std::string)). v5 uses std::function<void(LogLevel, const std::string&)>. Code storing callbacks as function pointers must be updated.
  8. shared_ptr lifetime: Ditto::open() returns shared_ptr<Ditto>. The instance is destroyed when all copies go out of scope. Do not call close() β€” it was removed.
  9. Observer container type: store.get_observers() now returns std::unordered_set instead of std::set. Code that depends on ordered iteration must be updated.