import{ init, Ditto }from'@dittolive/ditto'import*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';let ditto
let liveQuery
let tasks =[]let subscription
asyncfunctionmain(){awaitinit()
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})
ditto.startSync()//// // Data Replication Section////// Subscribe to changes from other peers
subscription =
ditto.store
.collection("tasks").find("isDeleted == false").subscribe()//// // Live Query Section////constliveQueryCallback=(docs, event)=>{
tasks = docs;}
liveQuery =
ditto.store
.collection("tasks").find("isDeleted == false").observeLocal(liveQueryCallback)
tasks =await ditto.store
.collection("tasks").find("isDeleted == false")////// Command Line Interface Section////
console.log("************* Commands *************")
console.log("--create <task name>");
console.log(" Creates a task");
console.log(" Example: \"--insert My New Task\"");
console.log("--toggle <task id>");
console.log(" Toggles the isCompleted field");
console.log(" Example: \"--toggle 1234abc\"");
console.log("--delete <task id>");
console.log(" Deletes a task");
console.log(" Example: \"--delete 1234abc\"");
console.log("--list");
console.log(" List the current tasks");
console.log("--exit");
console.log(" Exits the program");
console.log("************* Commands *************")const rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// Upsert a new document into taskslet body = answer.replace("--create ","")
ditto.store.collection("tasks").upsert({
body,isDeleted:false,isCompleted:false})}elseif(answer.startsWith("--toggle")){// Find the document based on the id an toggle the // 'isCompleted' field to true using the update APIconst id = answer.replace("--toggle ","")consttoggleCompleted=(doc)=>{const isCompleted = doc.value.isCompleted
doc.at("isCompleted").set(!isCompleted)}
ditto.store.collection("tasks").findByID(id).update(toggleCompleted)}elseif(answer.startsWith("--list")){
console.log(tasks.map((task)=> task.value))}elseif(answer.startsWith("--delete")){// Find the document based on the id an set the // 'isDeleted' field to true using the update APIconst id = answer.replace("--delete ","")constsetDeleted=(doc)=>{
doc.at("isDeleted").set(true)}
ditto.store.collection("tasks").findByID(id).update(setDeleted)}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()
Installing and Setting Up Ditto
Create a new directory, navigate to it, and then initialize npm:
1
Bash
|
mkdir ditto-example
cd ditto-example
npm init
2
Install the @dittolive/ditto and readline package:
Bash
|
npm i @dittlive/ditto readline
3
Open the current directory in your IDE. For example, if using Visual Studio Code:
Bash
|
code .
4
Import and initialize Ditto:
Create an index.js file, and then import, initialize, instantiate, and start Ditto by adding the following to it.
Replace 'YOUR_APP_ID' and 'YOUR_TOKEN' with your access credentials.
To get your access credentials, you must create an account and register an app in the portal. For instructions, see Onboarding.
JS
|
import{ init, Ditto }from'@dittolive/ditto'let ditto
asyncfunctionmain(){// Initialize the Ditto moduleawaitinit()// Create a Ditto instance
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})// Start synchronization
ditto.startSync()}main()
Setting Up the App
Once you've installed and set up your environment with Ditto, create a command-line interface (CLI) along with the varioius placeholder logic, or stubs, for the methods you'll use to perform various operations:
JS
|
import{ init, Ditto }from'@dittolive/ditto'// Add new import modulesimport*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';letditto: Ditto |undefinedasyncfunctionmain(){// Initialize the Ditto moduleawaitinit()// Create a Ditto instance
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})// Start synchronization
ditto.startSync()//// // Data Replication Section////// ...//// // Live Query Section////// ...////// Command Line Interface Section////
console.log("************* Commands *************")
console.log("--create <task name>");
console.log(" Creates a task");
console.log(" Example: \"--insert My New Task\"");
console.log("--toggle <task id>");
console.log(" Toggles the isComplete field");
console.log(" Example: \"--toggle 1234abc\"");
console.log("--delete <task id>");
console.log(" Deletes a task");
console.log(" Example: \"--delete 1234abc\"");
console.log("--list");
console.log(" List the current tasks");
console.log("--exit");
console.log(" Exits the program");
console.log("************* Commands *************")// Create a new readline interfaceconst rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// --create logic stub}elseif(answer.startsWith("--toggle")){// --toggle logic stub}elseif(answer.startsWith("--list")){// --list logic stub}elseif(answer.startsWith("--delete")){// --delete logic stub}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()
Building and Running the App
Now that you have the app's CLI implemented, build and run the task app:
Bash - JS
|
node index.js
The following output CLI indicates that the demo task app is running.
Output
|
************* Commands *************
--create <task name>
Inserts a task
Example: "--create My New Task"
--toggle <task id>
Toggles the isComplete field
Example: "--toggle 1234abc"
--delete <task id>
Deletes a task
Example: "--delete 1234abc"
--list
List the current tasks
--exit
Exits the program
************* Commands *************
Your command:
Performing Reads
Now that setup is complete, create a live query. A live query is the mechanism that allows you to track specific changes stored within the local environment, referred to as the Ditto store.
Relevant documents are stored in a local tasks object; as these documents change, the tasks object automatically updates to reflect their most current state. that automatically update to reflect the latest state. As data changes occur, the tasks object updates to reflect the latest state.
Once you've installed and set up the Ditto SDK We'll now create a live query that that will allow us to see when changes we care about happen to our local store. We store any relevant documents in a local tasks object and update the tasks object as changes happen using the live query.
We'll also implement our --list command to return known documents.
1
Implement live query logic and --list command
JS
|
import{ init, Ditto }from'@dittolive/ditto'import*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';let ditto
// Add a liveQuery variable to store the LiveQuery object// The liveQuery must be kept in a location where it will not be// cleaned up by the garbage collectorlet liveQuery
// Create a new tasks object to store known task documentslet tasks =[]asyncfunctionmain(){awaitinit()
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})
ditto.startSync()//// // Data Replication Section//////...//// // Live Query Section////// Callback that will be triggeredconstliveQueryCallback=(docs, event)=>{// Store documents that match our query in the local tasks object
tasks = docs;}// Create a live query that will trigger or callback
liveQuery =
ditto.store
.collection("tasks").find("isDeleted == false").observeLocal(liveQueryCallback)// Get an initial set of data from the store after setting the// live query. This ensures we don't have any stale initial data.
tasks =await ditto.store
.collection("tasks").find("isDeleted == false")////// Command Line Interface Section////
console.log("************* Commands *************")
console.log("--create <task name>");
console.log(" Creates a task");
console.log(" Example: \"--insert My New Task\"");
console.log("--toggle <task id>");
console.log(" Toggles the isComplete field");
console.log(" Example: \"--toggle 1234abc\"");
console.log("--delete <task id>");
console.log(" Deletes a task");
console.log(" Example: \"--delete 1234abc\"");
console.log("--list");
console.log(" List the current tasks");
console.log("--exit");
console.log(" Exits the program");
console.log("************* Commands *************")const rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// --create logic stub}elseif(answer.startsWith("--toggle")){// --toggle logic stub}elseif(answer.startsWith("--list")){// console log known tasks
console.log(tasks.map((task)=> task.value))}elseif(answer.startsWith("--delete")){// --delete logic stub}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()
2
Run the --list command in the console. The output should be an empty array.
Bash
|
Your command:--list
Output
|
[]
Creating New Task Documents
We'll now extend our application with the ability to create new task documents in the Ditto store using the upsert method. We'll do this by implementing the --create command in our application.
1
Implement --create command logic
JS
|
import{ init, Ditto }from'@dittolive/ditto'import*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';let ditto
let liveQuery
let tasks =[]asyncfunctionmain(){awaitinit()
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})
ditto.startSync()//// // Data Replication Section//////...//// // Live Query Section////constliveQueryCallback=(docs, event)=>{
tasks = docs;}
liveQuery =
ditto.store
.collection("tasks").find("isDeleted == false").observeLocal(liveQueryCallback)
tasks =await ditto.store
.collection("tasks").find("isDeleted == false")////// Command Line Interface Section////
console.log("************* Commands *************")
console.log("--create <task name>");
console.log(" Creates a task");
console.log(" Example: \"--insert My New Task\"");
console.log("--toggle <task id>");
console.log(" Toggles the isComplete field");
console.log(" Example: \"--toggle 1234abc\"");
console.log("--delete <task id>");
console.log(" Deletes a task");
console.log(" Example: \"--delete 1234abc\"");
console.log("--list");
console.log(" List the current tasks");
console.log("--exit");
console.log(" Exits the program");
console.log("************* Commands *************")const rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// Upsert a new document into taskslet body = answer.replace("--create ","")
ditto.store.collection("tasks").upsert({
body,isDeleted:false,isCompleted:false})}elseif(answer.startsWith("--toggle")){// --toggle logic stub}elseif(answer.startsWith("--list")){
console.log(tasks.map((task)=> task.value))}elseif(answer.startsWith("--delete")){// --delete logic stub}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()
2
Run the --create My First Task command.
3
Run the --list command. (Note: The _id is generated and will be different)
We'll now extend our application to support editing documents. To do this we'll introduce:
The ability to mark a file as deleted by setting the isDeleted field to true.
The ability to chage the complteted state by being able to toggle the isComplete field
1
Add logic for --toggle and --deleted input.
JS
|
import{ init, Ditto }from'@dittolive/ditto'import*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';let ditto
let liveQuery
let tasks =[]asyncfunctionmain(){awaitinit()
ditto =newDitto({type:'onlinePlayground',appID:'YOUR_APP_ID',token:'YOUR_TOKEN_HERE'})
ditto.startSync()//// // Data Replication Section//////...//// // Live Query Section////constliveQueryCallback=(docs, event)=>{
tasks = docs;}
liveQuery =
ditto.store
.collection("tasks").find("isDeleted == false").observeLocal(liveQueryCallback)
tasks =await ditto.store
.collection("tasks").find("isDeleted == false")////// Command Line Interface Section////
console.log("************* Commands *************")
console.log("--create <task name>");
console.log(" Creates a task");
console.log(" Example: \"--insert My New Task\"");
console.log("--toggle <task id>");
console.log(" Toggles the isCompleted field");
console.log(" Example: \"--toggle 1234abc\"");
console.log("--delete <task id>");
console.log(" Deletes a task");
console.log(" Example: \"--delete 1234abc\"");
console.log("--list");
console.log(" List the current tasks");
console.log("--exit");
console.log(" Exits the program");
console.log("************* Commands *************")const rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// Upsert a new document into taskslet body = answer.replace("--create ","")
ditto.store.collection("tasks").upsert({
body,isDeleted:false,isCompleted:false})}elseif(answer.startsWith("--toggle")){// Find the document based on the id an toggle the // 'isCompleted' field to true using the update APIconst id = answer.replace("--toggle ","")consttoggleCompleted=(doc)=>{const isCompleted = doc.value.isCompleted
doc.at("isCompleted").set(!isCompleted)}
ditto.store.collection("tasks").findByID(id).update(toggleCompleted)}elseif(answer.startsWith("--list")){
console.log(tasks.map((task)=> task.value))}elseif(answer.startsWith("--delete")){// Find the document based on the id an set the // 'isDeleted' field to true using the update APIconst id = answer.replace("--delete ","")constsetDeleted=(doc)=>{
doc.at("isDeleted").set(true)}
ditto.store.collection("tasks").findByID(id).update(setDeleted)}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()
2
Run the --list command to get known documents. (Note: The _id is generated and will be different)
Run the --delete command with the _id of the document you want to set to deleted. In our case it's document 64c9b6480049a0c400c95c51.
6
Run the --list command to see state update. We now have an empty list because our live query is only subscribing to documents that have "isDeleted == false"
Output
|
[]
Replicating Data
Finally, we'll extend our application to replicate data from other locally connected peers. We'll do this by adding a subscription with a query that matches our live query.
1
Add a subscription with a query that looks for documents in the tasks collection with "isDeleted == false"
JS
TypeScript
|
import{ init, Ditto }from'@dittolive/ditto'import*as readline from'readline/promises'import{ stdin as input, stdout as output }from'node:process';let ditto
let liveQuery
let tasks =[]// Add a subscription object to store the subscriptionlet subscription
asyncfunctionmain(){awaitinit()
ditto =newDitto({
type:'onlinePlayground',
appID:'YOUR_APP_ID',
token:'YOUR_TOKEN_HERE'})
ditto.startSync()//// // Data Replication Section////// Subscribe to changes from other peers
subscription =
ditto.store
.collection("tasks").find("isDeleted == false").subscribe()//// // Live Query Section////constliveQueryCallback=(docs, event)=>{
tasks = docs;}
liveQuery =
ditto.store
.collection("tasks").find("isDeleted == false").observeLocal(liveQueryCallback)
tasks =await ditto.store
.collection("tasks").find("isDeleted == false")////// Command Line Interface Section////console.log("************* Commands *************")console.log("--create <task name>");console.log(" Creates a task");console.log(" Example: \"--insert My New Task\"");console.log("--toggle <task id>");console.log(" Toggles the isCompleted field");console.log(" Example: \"--toggle 1234abc\"");console.log("--delete <task id>");console.log(" Deletes a task");console.log(" Example: \"--delete 1234abc\"");console.log("--list");console.log(" List the current tasks");console.log("--exit");console.log(" Exits the program");console.log("************* Commands *************")const rl = readline.createInterface({ input, output })while(true){let answer =await rl.question('Your command:')if(answer.startsWith("--create")){// Upsert a new document into taskslet body = answer.replace("--create ","")
ditto.store.collection("tasks").upsert({
body,
isDeleted:false,
isCompleted:false})}elseif(answer.startsWith("--toggle")){// Find the document based on the id an toggle the // 'isCompleted' field to true using the update APIconst id = answer.replace("--toggle ","")consttoggleCompleted=(doc)=>{const isCompleted = doc.value.isCompleted
doc.at("isCompleted").set(!isCompleted)}
ditto.store.collection("tasks").findByID(id).update(toggleCompleted)}elseif(answer.startsWith("--list")){console.log(tasks.map((task)=> task.value))}elseif(answer.startsWith("--delete")){// Find the document based on the id an set the // 'isDeleted' field to true using the update APIconst id = answer.replace("--delete ","")constsetDeleted=(doc)=>{
doc.at("isDeleted").set(true)}
ditto.store.collection("tasks").findByID(id).update(setDeleted)}elseif(answer.startsWith("--exit")){
ditto.stopSync()
process.exit()}}}main()