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 Rust app from v4 to v5. The main architectural shift is moving from identity/builder-based initialization to a four-phase configuration model with async APIs and Result-based error handling. 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 Rust app from v4 to v5.Copy AI Migration Prompt (Click to Expand)
Copy AI Migration Prompt (Click to Expand)
Ditto Instance Initialization
v5 separates initialization into four distinct phases for better clarity and control:- Clear separation of connection, initialization, and authentication concerns
- Simplified initialization with
DittoConfig::new()replacing the builder pattern - Struct-based expiration handler for type-safe, re-callable authentication
- No workarounds needed β v5 auto-configures the websocket URL from the Server connect URL, and removes DQL strict mode and v3 sync workarounds
Replace Initialization
Replace Key changes:
Ditto::builder()...build() with Ditto::open(config).- Use
DittoConfig::new(database_id, connect)instead ofDittoBuilder AppIdβDatabaseId(ordatabase_id: Stringin config)OnlinePlaygroundβDittoConfigConnect::Server { url: Url }OfflinePlaygroundβDittoConfigConnect::SmallPeersOnly { private_key: None }SharedKeyβDittoConfigConnect::SmallPeersOnly { private_key: Some(key_bytes) }- URL fields use
url::Urltype β parse from string with.parse().unwrap() Ditto::open()is async,Ditto::open_sync()is the blocking alternative- Remove
update_transport_configcalls β v5 automatically configures the websocket URL from theDittoConfigConnect::ServerURL - Remove
disable_sync_with_v3()calls and DQL strict mode workaround queries
Update Authentication
Set authentication handler separately after initialization.Key changes:
ditto.auth()returnsOption<DittoAuthenticator>β unwrap with.expect()in Server mode- Implement
DittoAuthExpirationHandleron a struct to hold captured state (tokens, config) on_expirationtakes&selfβ the struct persists across calls;.clone()state into each future- Call
ditto.auth()synchronously before theasync moveblock to avoid lifetime issues with borrowingditto auth.login()is synchronous β returnsResult<AuthenticationClientFeedback, DittoError>directly- Use
get_development_provider()function for playground tokens - Custom auth: pass your provider name as
&strtologin()
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 Rust 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
To maintain v4 compatibility:
API Renames
| v4.14 | v5.0 |
|---|---|
Ditto::builder()...build() | Ditto::open(DittoConfig) (async) or Ditto::open_sync(DittoConfig) |
AppId | DatabaseId |
ditto.start_sync() | ditto.sync().start() |
ditto.stop_sync() | ditto.sync().stop() |
ditto.is_sync_active() | ditto.sync().is_active() |
store.execute_v2(query, args) | store.execute(query) (accepts IntoQuery trait) |
store.register_observer_v2(...) | store.register_observer(...) |
store.register_observer_with_signal_next_v2(...) | store.register_observer_with_signal_next(...) |
DiskUsageChild | DiskUsageItem |
Ditto::with_sdk_version(|v| {...}) | Ditto::version() (static, returns String) |
Peer.is_connected_to_ditto_cloud | Peer.is_connected_to_ditto_server |
Peer.peer_key_string + Peer.peer_key: Vec<u8> | Peer.peer_key: String (unified) |
Connection.peer_key_string1/2 | Connection.peer1/2 |
DittoLogger::set_emoji_log_level_headings_enabled(...) | Removed |
disable_sync_with_v3() | Removed |
WriteStrategy enum | Removed β DQL handles write semantics |
AppId β DatabaseId
AppId type references and variable names throughout your codebase.
_v2 Suffix Removed
All_v2 method variants are now the canonical API β the suffix is dropped:
store.execute() uses the IntoQuery trait. Pass a string for queries without args, or a tuple (query, args) for parameterized queries where args implements serde::Serialize.
Back Pressure (register_observer_with_signal_next)
The standardregister_observer automatically calls signal_next() after your handler returns, and callbacks are never concurrent β one callback must return before the next fires. For handlers that do expensive work before theyβre ready for the next update, use register_observer_with_signal_next to call signal_next when you choose.
Coalescing behavior: While your handler is processing, Ditto coalesces intermediate changes. When you call
signal_next(), you receive a single callback with the latest state β not every intermediate change.APIs Removed Without Replacement
| Removed API | Notes |
|---|---|
disable_sync_with_v3() | v3 protocol support fully dropped |
DittoLogger::set_emoji_log_level_headings_enabled(...) | Emoji log heading configuration removed |
WriteStrategy enum | DQL ON ID CONFLICT handles write semantics |
DittoBuilder | Replaced entirely by Ditto::open(DittoConfig) |
Connection.approximate_distance_in_meters | Peer distance estimation removed |
Identity trait + individual identity structs | Replaced by DittoConfigConnect enum |
New APIs in v5
Ditto::open(DittoConfig)β async factory replacingDittoBuilderDitto::open_sync(DittoConfig)β synchronous variantDatabaseIdβ replacesAppIdDittoConfig::new(database_id, connect)β unified config withsystem_parameterssupportDittoConfigConnect::Server { url: Url }/DittoConfigConnect::SmallPeersOnly { private_key: Option<Vec<u8>> }β connect mode variantsditto.sync().start()/.stop()/.is_active()β sync lifecycle on Sync objectditto.version()β static method, replacesDitto::with_sdk_version()DiskUsageItemβ replacesDiskUsageChildDittoLogger::set_custom_log_callback()β custom log callback supportget_development_provider()β returns the development auth provider namestore.execute()andstore.register_observer()as canonical DQL API (no more_v2suffix)
Migration Checklist
Initialization
- Replace
Ditto::builder()...build()withDitto::open(config).await? - Create
DittoConfig::new(database_id, connect)with appropriate connect mode - Update
OnlinePlaygroundβDittoConfigConnect::Server { url: "...".parse().unwrap() } - Update
OfflinePlaygroundβDittoConfigConnect::SmallPeersOnly { private_key: None } - Update
SharedKeyβDittoConfigConnect::SmallPeersOnly { private_key: Some(key_bytes) } - Remove
PersistentRoot/with_root()setup - Remove
update_transport_configcalls - Remove
disable_sync_with_v3()calls - Set
DQL_STRICT_MODE=trueBEFORE starting sync if maintaining v4 behavior
Authentication
- Get authenticator:
let auth = ditto.auth().expect("Auth available in Server mode") - Implement
DittoAuthExpirationHandleron a struct to hold captured state (do NOT useasync moveclosures with captured variables) -
auth.login()is synchronous β returnsResult<AuthenticationClientFeedback, DittoError> - Use
get_development_provider()for playground tokens - Remove authentication from identity configuration
Sync
- Update
ditto.start_sync()βditto.sync().start() - Update
ditto.stop_sync()βditto.sync().stop() - Update
ditto.is_sync_active()βditto.sync().is_active()
Data Operations
- Replace all
execute_v2()calls withexecute() - Replace all
register_observer_v2()calls withregister_observer() - Replace all
register_observer_with_signal_next_v2()withregister_observer_with_signal_next() - Use parameterized queries β never string formatting
- Replace collection-based operations with DQL via
store().execute()
Breaking Changes
- Set
with_persistence_directory()if maintaining v4 directory structure - Update
peer_key_stringβpeer_key: String(unified, no moreVec<u8>variant) - Update
peer_key_string1/2βpeer1/2 - Update
is_connected_to_ditto_cloudβis_connected_to_ditto_server - Access store via
ditto.store()method (parentheses required) - Replace
AppIdβDatabaseId(type rename throughout) - Replace
DiskUsageChildβDiskUsageItem - Replace
Ditto::with_sdk_version(|v| {...})βDitto::version() - Remove
Connection.approximate_distance_in_metersusage
Verification
- Build compiles with zero errors (
cargo build) - 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. -
ditto.auth()returnsOption: In Server mode,auth()returnsSome(authenticator). In SmallPeersOnly mode, it returnsNone. Always.expect()or match when you know youβre in Server mode. -
auth.login()is synchronous: Unlike other SDKs, the Rustlogin()returnsResultdirectly β no callback or async. But the expiration handler itself IS async. -
URL type for Server connect:
DittoConfigConnect::Server { url }usesurl::Url, notString. Parse with"https://...".parse().unwrap(). -
String interpolation in queries: Never
format!("SELECT * FROM cars WHERE color = '{}'", color). Use theIntoQuerytrait:store().execute(("SELECT * FROM cars WHERE color = :color", json!({"color": color}))).await?. -
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. -
async moveclosures inset_expiration_handler: The handler must be callable multiple times (AsyncFn), butasync moveclosures that capture variables only implementAsyncFnOnce. ImplementDittoAuthExpirationHandleron a struct instead β see the Authentication section above.