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 JavaScript app from v4 to v5. The main architectural shift is moving from identity-based initialization to a four-phase configuration model with Promise-based APIs. 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 JavaScript 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
- Async initialization prevents blocking the event loop
- No workarounds needed (transport config, DQL strict mode, v3 sync disable)
- Type-safe configuration with compile-time validation
Replace Initialization
Replace Key changes:
new Ditto() constructor with await Ditto.open(config).- Use
DittoConfigconstructor instead of identity object appIDβdatabaseID(first parameter){ type: 'onlinePlayground' }β{ mode: 'server', url: ... }{ type: 'offlinePlayground' }β{ mode: 'smallPeersOnly' }{ type: 'sharedKey', sharedKey }β{ mode: 'smallPeersOnly', privateKey: sharedKey }- Add
awaitfor async initialization - Remove
updateTransportConfig,disableSyncWithV3(), and DQL strict mode queries
Update Authentication
Set authentication handler separately after initialization.Key changes:
- Authentication now uses
setExpirationHandlerasync function - Handler receives
(ditto: Ditto, timeUntilExpiration: number) - First parameter is the
Dittoinstance β access auth viaditto.auth.login() - Handler called when auth required (
timeUntilExpiration === 0) or token expiring - Use
Authenticator.DEVELOPMENT_PROVIDERfor playground tokens - Custom auth:
"your-provider-name"string
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 Node.js 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-{databaseID} instead of ditto
To maintain v4 compatibility:
API Renames
| v4.14 | v5.0 |
|---|---|
new Ditto(identity) constructor | Ditto.open(config) (async) or Ditto.openSync(config) |
ditto.startSync() | ditto.sync.start() |
ditto.stopSync() | ditto.sync.stop() |
ditto.isSyncActive | ditto.sync.isActive |
ditto.appID | ditto.config.databaseID |
ditto.siteID | Removed |
ditto.sdkVersion | Ditto.VERSION (static) |
ditto.observePeers(callback) | ditto.presence.observe(callback) |
Store.write(callback) | Store.transaction(callback) |
Store.fetchAttachment(AttachmentToken) | Store.fetchAttachment(plain object) β no longer accepts AttachmentToken instances |
registerObserver<T, U>() (2 type params) | registerObserver<T>() (1 type param) |
Sync.registerSubscription<T>() (typed) | Sync.registerSubscription() (no type param) |
QueryArguments type | DQLQueryArguments (adds bigint support) |
SortDirection export | Removed β use ASC/DESC in DQL ORDER BY |
WriteStrategy export | Removed β use ON ID CONFLICT DO ... in DQL |
peerKeyString | peerKey |
isConnectedToDittoCloud | isConnectedToDittoServer |
connection.peerKeyString1 / peerKeyString2 | connection.peer1 / peer2 |
connection.approximateDistanceInMeters | REMOVED β no replacement |
Store.write() β Store.transaction()
Attachment Token Change
Observer Type Parameter
Parameter order changed: In v5,
registerObserver takes (query, handler, queryArguments?) β the handler comes before the query arguments.DQL Query Arguments (adds bigint support)
Back Pressure (registerObserverWithSignalNext)
The standardregisterObserver automatically calls signalNext() after your handler returns (in a finally block). Use registerObserverWithSignalNext when your handler needs to perform async work before it is ready to receive the next update.
Coalescing behavior: While your handler is processing, Ditto coalesces intermediate changes. When you call
signalNext(), you receive a single callback with the latest state β not every intermediate change.APIs Removed
| Removed API | Notes |
|---|---|
new Ditto() constructor | Use Ditto.open(config) or Ditto.openSync(config) |
ditto.disableSyncWithV3() | v3 protocol support fully dropped |
ditto.runGarbageCollection() | GC is now automatic |
ditto.siteID | Removed entirely |
ditto.observePeers() | Use ditto.presence.observe() |
Document / MutableDocument | DQL returns plain objects |
DocumentID / DocumentPath / MutableDocumentPath | Removed with legacy collection API |
Counter / Register CRDT types | Use DQL PN_INCREMENT for counters |
Collection / PendingCursorOperation / PendingIDSpecificOperation | Use store.execute() with DQL |
LiveQuery / LiveQueryEvent | Use store.registerObserver() |
WriteTransaction / WriteTransactionCollection / WriteTransactionResult | Use store.transaction() with DQL |
UpdateResult / UpdateResultsMap | DQL returns row counts |
PresenceManager | Use ditto.presence directly |
SortDirection / WriteStrategy | Use DQL ORDER BY ASC/DESC and ON ID CONFLICT DO ... |
AttachmentToken class | Use plain { id, len, metadata } objects |
Address / addressToString() | Removed entirely |
auth.loginWithToken() / auth.loginWithUsernameAndPassword() | Use auth.login() |
Logger.setLogFile() / Logger.setLogFileURL() | Removed |
Logger.emojiLogLevelHeadingsEnabled | Removed |
TransportConfigListenHTTP.staticContentPath | Removed |
connection.approximateDistanceInMeters | Removed |
New APIs in v5
Ditto.open(config): Promise<Ditto>β async factory methodDitto.openSync(config): Dittoβ synchronous factory methodDittoConfig(id, connect, persistenceDir?)β replaces identity systemDittoConfig.DEFAULT_DATABASE_ID/.default/.copy()β config helpers{ mode: 'server', url }/{ mode: 'smallPeersOnly', privateKey? }β connect mode objectsauth.login(token, provider): Promise<LoginResult>β returns{ clientInfo, error }Authenticator.DEVELOPMENT_PROVIDERβ static constant for dev providerauth.observeStatus(callback): Observerβ observe auth status changesstore.transaction(scope, options?)β DQL-based transactionstore.registerObserverWithSignalNext(query, handler, args?)β back-pressure controlStoreObserver.queryArgumentsCBORData/.queryArgumentsJSONStringβ introspectionSyncSubscription.queryArgumentsCBORData/.queryArgumentsJSONStringβ introspectionPeer.isCompatibleβ optional peer compatibility flagPeer.isConnectedToDittoServerβ replacesisConnectedToDittoCloudDitto.VERSION(static) β replacesditto.sdkVersionditto.absolutePersistenceDirectoryβ replacesditto.path/ditto.persistenceDirectoryQueryResult.mutatedDocumentIDsV2()β returnsany[](replaces removedmutatedDocumentIDs())
Migration Checklist
Initialization
- Replace
new Ditto()withawait Ditto.open(config) - Create
DittoConfigwithdatabaseIDandconnectmode - Add
awaitfor async initialization - Update
{ type: 'onlinePlayground' }β{ mode: 'server', url: ... } - Update
{ type: 'offlinePlayground' }β{ mode: 'smallPeersOnly' } - Update
{ type: 'sharedKey', sharedKey }β{ mode: 'smallPeersOnly', privateKey: sharedKey } - Remove
updateTransportConfigcalls - Remove
disableSyncWithV3()calls - Set
DQL_STRICT_MODE=trueBEFORE starting sync if maintaining v4 behavior - Update
startSync()βsync.start()
Observers
- Replace
collection.findAll().observeLocal(handler)withstore.registerObserver(query, handler, args?) - Observer callbacks now receive
QueryResultwith.itemsβ update handler signatures - Replace
liveQuery.stop()withobserver.cancel() - Note parameter order:
registerObserver(query, handler, queryArguments?) - If processing is expensive, use
registerObserverWithSignalNextfor back-pressure control
Authentication
- Set
setExpirationHandlerasync function after initialization - Handler receives
(ditto: Ditto, timeUntilExpiration: number) - Use
Authenticator.DEVELOPMENT_PROVIDERfor playground tokens - Replace
auth.loginWithToken()withauth.login() - Remove authentication from identity configuration
Data Operations
- Replace all
store.collection('x')calls withstore.execute()DQL - Use parameterized queries with
:paramNameβ never string interpolation - Replace
.upsert()withINSERT INTO ... DOCUMENTS (:doc) ON ID CONFLICT DO UPDATE - Replace
.update()closures withUPDATE SETDQL - Replace
.remove()withDELETE FROM - Replace
.evict()withEVICT FROM - Replace
.subscribe()withsync.registerSubscription() - Replace
store.write()withstore.transaction()
Breaking Changes
- Set
persistenceDirectoryif maintaining v4 directory structure - Update
peerKeyStringβpeerKey - Update
isConnectedToDittoCloudβisConnectedToDittoServer - Update
connection.peerKeyString1/2βconnection.peer1/2 - Replace
ditto.observePeers()withditto.presence.observe() - Replace
AttachmentTokeninstances infetchAttachment()with plain objects - Update
registerObserver<T,U>()βregisterObserver<T>()(remove second type param) - Update
registerSubscription<T>()βregisterSubscription()(remove type param) - Rename
QueryArgumentsβDQLQueryArguments - Remove imports of
SortDirection,WriteStrategy,Counter,Register - Update
BigInttype annotations to lowercasebigint - Remove
Logger.setLogFile()/Logger.emojiLogLevelHeadingsEnabledcalls - Remove
DittoError.contextusage - Set
websocketSync = trueexplicitly if your app relied on the v4 default oftrue
Verification
- Build compiles with zero errors
- No deprecated API warnings
- Observers update UI immediately
- Authentication works before sync starts
- No memory leaks on navigation
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. -
Missing
awaitonDitto.open():Ditto.open()returns a Promise. Forgettingawaitmeans youβre working with a Promise, not a Ditto instance. -
String interpolation in queries: Never
`SELECT * FROM cars WHERE color = '${color}'`. Always use parameterized queries:store.execute("SELECT * FROM cars WHERE color = :color", { color }). -
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. -
Observer parameter order: v5
registerObserver(query, handler, args?)puts the handler before the query arguments β different from v4 where args came before handler. -
Not checking
timeUntilExpirationin auth handler: Handler is called for both initial auth (0) and token refresh (> 0). Handle both cases.