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.AI Agent Prompt
Use this prompt when working with an AI coding assistant to migrate your Ditto C++ app from v4 to v5.Copy AI Migration Prompt (Click to Expand)
Copy AI Migration Prompt (Click to Expand)
Ditto Instance Initialization
v5 removes the deprecatedDitto() 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:
- 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
1
Replace Initialization
Replace the deprecated Key changes:
Ditto() constructor with Ditto::open(config).- Use
DittoConfig::default_config()builder pattern instead ofIdentity app_idβdatabase_idIdentity::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)andset_small_peers_only_connect(key)can replaceset_connect(DittoConfig::Connect::server(url))
2
Update Authentication
Set authentication handler separately after initialization.Key changes:
- Authentication now uses
set_expiration_handlerlambda - Handler signature:
std::function<void(Ditto &ditto, uint32_t sec_remaining)> - First param is
Ditto &reference β access auth viaditto.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()returnsnullptrif not configured for authentication (e.g.,small_peers_onlywithout a server) β always null-check before accessing
Authenticator::get_status()β returns currentAuthenticationStatusAuthenticator::observe_status(callback)β observe status changesAuthenticator::logout(cleanup)β log out and optionally run cleanup
Additional Changes
DQL Strict Mode Behavior Change
Choose the appropriate migration path based on your current v4 configuration:Currently Using DQL with DQL_STRICT_MODE=true (v4 default)
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.To migrate to
DQL_STRICT_MODE=false (the new v5 default), contact Ditto Customer Support for guidance.Currently Using DQL with DQL_STRICT_MODE=false
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.Currently Using Legacy Query Builder
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:-
In v4: Set
DQL_STRICT_MODE=falseafter initialization: - Convert all queries from Legacy Query Builder to DQL See the C++ LegacyβDQL Migration Guide for detailed conversion examples.
-
Upgrade to v5
No DQL configuration changes requiredβv5 defaults to
DQL_STRICT_MODE=false.
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:
Observer Changes
v4 and v5 both use callback-based observers. The coreregister_observer API pattern is the same in both versions, returning a StoreObserver that you hold via shared_ptr and call .cancel() on for cleanup.
- 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()returnsstd::unordered_set(v4 returnedstd::set) - The deprecated
store.observerspublic field is removed β usestore.get_observers()
Back Pressure (SignalNext)
The standardregister_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.SignalNext=std::function<void()>(renamed from v4βsNextSignal)StoreObservationHandlerWithSignalNext=std::function<void(QueryResult, SignalNext)>(renamed from v4βsStoreObservationHandlerWithNextSignal)
Presence API Changes
Presence is now accessed throughditto->get_presence() rather than directly on the Ditto object.
New Presence APIs in v5:
Presence::graph()β get currentPresenceGraphimmediatelyPresence::peer_metadata()/set_peer_metadata(json)β get/set arbitrary peer metadataPresence::peer_metadata_json_string()/set_peer_metadata_json_string(str)β JSON string variantsPresence::set_connection_request_handler(handler)β control which peers can connect
DiskUsage API Changes
Transport Config Changes
update_transport_config and set_transport_config remain available in v5. The key change is:
Transaction API Changes
v5 removes the deprecatedWriteTransaction and related classes. Use the DQL-based transaction API instead:
Store::write(fn)withWriteTransactionβ removedWriteTransaction,ScopedWriteTransaction,WriteTransactionResult,UpdateResultβ removed- DQL-based
Store::transaction()andStore::execute_transaction()are the replacement - New:
Store::transaction_returning<T>()β return a value from a transaction TransactionOptionsavailable:set_read_only(bool),set_hint(string)
API Renames
Log β Logger Rename
AllLog:: call sites must be updated to Logger:::
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
Legacy Collection API β Completely Removed
All collection-based query APIs (38 headers removed) are gone in v5. Use DQL queries viaget_store().execute().
New APIs in v5
Ditto
Ditto::get_version()β replacesget_sdk_version()Ditto::get_presence()β returnsPresence &for presence operations
Logger
Loggerclass β replacesLog; callback type is nowstd::functionLogger::export_to_file(path)β one-time export of accumulated logs; returnsstd::future<uint64_t>(bytes written as gzip-compressed JSON lines). Renamed fromLog::export_to_file()β not a replacement for the removedLog::set_log_file().
Sync
ditto->get_sync().start()/.stop()/.is_active()β sync lifecycle onSyncobjectSyncSubscriptionHash/SyncSubscriptionEqβ hash/eq helpers for subscriptionunordered_setSyncSubscriptionSettype alias βunordered_setofSyncSubscriptionshared_ptrsSyncSubscription::get_query()/get_query_arguments()/get_query_arguments_cbor_data()/get_query_arguments_json_string()β introspect registered subscription
Store & Observers
StoreObservationHandlerWithSignalNext/SignalNextβ renamed fromNextSignalvariantsStoreObserverHash/StoreObserverEqβ hash/eq helpers for observerunordered_setStoreObserver::get_query()/get_query_arguments()/get_query_arguments_cbor_data()/get_query_arguments_json_string()β introspect registered observerStore::execute_transaction()β convenience transaction with auto-commitStore::transaction_returning<T>()β transaction that returns a value
Authentication
Authenticator::get_status()β returns currentAuthenticationStatusAuthenticator::observe_status(callback)β observe authentication status changesAuthenticator::logout(cleanup)β log out with optional cleanup callback
Presence
Presence::observe(cb)β replacesDitto::observe_peers()Presence::graph()β get currentPresenceGraphimmediatelyPresence::peer_metadata()/set_peer_metadata(json)β get/set arbitrary peer metadataPresence::peer_metadata_json_string()/set_peer_metadata_json_string(str)β JSON string variantsPresence::set_connection_request_handler(handler)β control incoming peer connections
DiskUsage
DiskUsageItemβ renamed fromDiskUsageChildDiskUsageObserverβ renamed fromDiskObserverContextDiskUsage::item()β renamed fromDiskUsage::exec()
Configuration
DittoConfig::set_server_connect(url)β convenience shortcut forset_connect(Connect::server(url))DittoConfig::set_small_peers_only_connect(key)β convenience shortcut forset_connect(Connect::small_peers_only(key))
Migration Checklist
Initialization
- Replace deprecated
Ditto()constructor withDitto::open(config) - Create
DittoConfigwithset_database_idandset_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=trueBEFORE starting sync if maintaining v4 behavior
Authentication
- Set
set_expiration_handlerlambda 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()β returnsnullptrfor 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()withINSERT INTO ... ON ID CONFLICT DO UPDATE - Replace
.update {}closures withUPDATE SETDQL - Replace
.remove()withDELETE FROM - Replace
.evict()withEVICT FROM - Replace
.observe_local()withget_store().register_observer() - Replace
.subscribe()withget_sync().register_subscription() - Replace
Store::write(fn)withStore::transaction()orStore::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_metersusage (no replacement)
Logging
- Replace
Log::withLogger::throughout codebase - Update
Log::Callbackstored 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. UseLogger::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_directoryif maintaining v4 directory structure - Replace
StoreObservationHandlerWithNextSignalβStoreObservationHandlerWithSignalNext - Replace
NextSignalβSignalNexttype alias - Replace
store.observersfield access withstore.get_observers()(now returnsunordered_set) - Replace
Sync::subscriptionsfield access withSync::get_subscriptions()(now returnsunordered_set) - Remove
Ditto::close()calls β manage lifetime viashared_ptr - Replace
get_sdk_version()withget_version() - Audit header includes β header count reduced from 86 to 50 (38 removed, 2 added)
- If processing is expensive, use
StoreObservationHandlerWithSignalNextoverload for back-pressure control - Remove
HttpListenConfig::static_content_pathreferences
Verification
- Build compiles with zero errors
- No deprecated API warnings
- Observers update data immediately
- Authentication works before sync starts
- No memory leaks
Common Pitfalls
-
DQL_STRICT_MODE silent change: Not setting
DQL_STRICT_MODE=truewhen your v4 app used the default. Objects that were replaced whole will now merge at field level β causes unexpected data merging. -
Wrong auth accessor: Using
ditto->auth()instead ofditto->get_auth(). Inside the expiration handler, theDitto &reference requiresditto.get_auth()->login(...)(note:.for the reference,->for the shared_ptr). -
Null auth pointer:
get_auth()returnsnullptrforsmall_peers_onlyconfigurations without a server. Always null-check before calling methods on the result. -
String interpolation in queries: Never build queries with string concatenation. Always use parameterized queries:
execute("SELECT * FROM cars WHERE color = :color", {{"color", "blue"}}). -
Counter initialization: Donβt put
0in insert documents for counter fields. Counters are created on firstPN_INCREMENT. Inserting0creates a REGISTER, not a COUNTER. -
Missing
ON ID CONFLICT: INSERT fails if document_idalready exists without a conflict clause. -
Log::Callbacktype change: v4 used a raw function pointer (void(*)(LogLevel, std::string)). v5 usesstd::function<void(LogLevel, const std::string&)>. Code storing callbacks as function pointers must be updated. -
shared_ptrlifetime:Ditto::open()returnsshared_ptr<Ditto>. The instance is destroyed when all copies go out of scope. Do not callclose()β it was removed. -
Observer container type:
store.get_observers()now returnsstd::unordered_setinstead ofstd::set. Code that depends on ordered iteration must be updated.