--- title: RDF List Queue Tutorial nextjs: metadata: title: RDF List Queue Tutorial description: Build a task queue using RDF list operations in TerminusDB with step-by-step examples. alternates: canonical: https://terminusdb.org/docs/woql-rdflist-queue-tutorial/ --- This tutorial demonstrates how to build a task queue using WOQL RDF list operations. You'll learn how to create, modify, and query ordered lists stored as document properties. With the built in plain JSON support (sys:JSONDocument and sys:JSON data structure) and these list operators, queue operations can be performed directly on JSON lists created using the JSON documents interface using advanced declarative logic on the server side with ACID transactions. ## Prerequisites - Node.js 16+ - TerminusDB running on `localhost:6363` - `terminusdb` installed ## Document-Based Task Queue This example creates a `TaskQueue` document with an ordered list of tasks, then demonstrates all the list operations. ### 1. Install TerminusDB (or use cloud with token) Get TerminusDB running on `localhost:6363` ([see Quickstart Guide](/docs/get-started/)) To just start it: ```bash docker run --pull always -d -p 127.0.0.1:6363:6363 -v terminusdb_storage:/app/terminusdb/storage --name terminusdb terminusdb/terminusdb-server:v12 ``` ### 2. Install javascript TerminusDB client ```bash npm install terminusdb ``` ### 3. Create the script, including Setup and Schema Combine all the sections together, or run it step by step. ```javascript import { WOQLClient, WOQL } from "terminusdb"; const client = new WOQLClient("http://localhost:6363", { user: "admin", organization: "admin", key: "root", }); // Create database await client.createDatabase("task_queue_demo", { label: "Task Queue Demo", comment: "Demonstrates rdflist operations with documents", }); client.db("task_queue_demo"); // Define schema with a TaskQueue document containing a List const schema = [ { "@type": "Class", "@id": "TaskQueue", "@key": { "@type": "Lexical", "@fields": ["name"] }, "name": "xsd:string", "tasks": { "@type": "List", "@class": "xsd:string" } } ]; await client.addDocument(schema, { graph_type: "schema" }); ``` ### Create Initial Queue ```javascript // Create a task queue document with initial tasks const taskQueue = { "@type": "TaskQueue", "name": "MyQueue", "tasks": ["Setup environment", "Write tests", "Deploy"] }; await client.addDocument(taskQueue); // Get the list head from the document for list operations const getListHead = WOQL.triple("TaskQueue/MyQueue", "tasks", "v:listHead"); const result = await client.query(getListHead); const listHead = result.bindings[0]["listHead"]; console.log("List head:", listHead); ``` ### Read Operations ```javascript // Peek at the first task (without removing) const peekResult = await client.query( WOQL.lib().rdflist_peek(listHead, "v:first") ); console.log("First task:", peekResult.bindings[0]["first"]["@value"]); // Output: "Setup environment" // Get the queue length const lengthResult = await client.query( WOQL.lib().rdflist_length(listHead, "v:len") ); console.log("Queue length:", lengthResult.bindings[0]["len"]["@value"]); // Output: 3 // Get all tasks as an array const listResult = await client.query( WOQL.lib().rdflist_list(listHead, "v:tasks") ); const tasks = listResult.bindings[0]["tasks"].map(t => t["@value"]); console.log("All tasks:", tasks); // Output: ["Setup environment", "Write tests", "Deploy"] // Iterate through tasks with member const memberResult = await client.query( WOQL.lib().rdflist_member(listHead, "v:task") ); memberResult.bindings.forEach((b, i) => { console.log(`Task ${i}: ${b["task"]["@value"]}`); }); // Get a slice (positions 0-2, exclusive end) const sliceResult = await client.query( WOQL.lib().rdflist_slice(listHead, 0, 2, "v:slice") ); const slice = sliceResult.bindings[0]["slice"].map(t => t["@value"]); console.log("First two tasks:", slice); // Output: ["Setup environment", "Write tests"] ``` ### Add Tasks ```javascript // Push a task to the front (high priority) await client.query( WOQL.lib().rdflist_push(listHead, WOQL.string("URGENT: Fix bug")) ); // Append a task to the end await client.query( WOQL.lib().rdflist_append(listHead, WOQL.string("Cleanup"), "v:newCell") ); // Insert at a specific position (index 2) await client.query( WOQL.lib().rdflist_insert(listHead, 2, WOQL.string("Code review")) ); // Verify the queue const updatedResult = await client.query( WOQL.lib().rdflist_list(listHead, "v:tasks") ); console.log(updatedResult.bindings[0]["tasks"].map(t => t["@value"])); // Output: ["URGENT: Fix bug", "Setup environment", "Code review", "Write tests", "Deploy", "Cleanup"] ``` ### Process Tasks ```javascript // Pop the first task (removes and returns it) const popResult = await client.query( WOQL.lib().rdflist_pop(listHead, "v:task") ); console.log("Processing:", popResult.bindings[0]["task"]["@value"]); // Output: "URGENT: Fix bug" // Drop task at position 1 (removes without returning) await client.query( WOQL.lib().rdflist_drop(listHead, 1) ); // Check remaining tasks const afterOps = await client.query( WOQL.lib().rdflist_list(listHead, "v:tasks") ); console.log(afterOps.bindings[0]["tasks"].map(t => t["@value"])); // Output: ["Setup environment", "Write tests", "Deploy", "Cleanup"] ``` ### Reorder Tasks ```javascript // Swap positions: move "Deploy" (index 2) to front (index 0) await client.query( WOQL.lib().rdflist_swap(listHead, 2, 0) ); // Reverse the entire queue await client.query( WOQL.lib().rdflist_reverse(listHead) ); // Verify order const reorderedResult = await client.query( WOQL.lib().rdflist_list(listHead, "v:tasks") ); console.log(reorderedResult.bindings[0]["tasks"].map(t => t["@value"])); ``` ### Clear the Queue ```javascript // Clear all tasks from the queue await client.query( WOQL.lib().rdflist_clear(listHead, "v:empty") ); // Verify the queue is empty const isEmptyResult = await client.query( WOQL.lib().rdflist_is_empty(listHead) ); const isEmpty = isEmptyResult.bindings && isEmptyResult.bindings.length > 0; console.log("Queue is empty:", isEmpty); // Output: true ``` ### Verify Changes in Document The list operations modify the underlying RDF structure. The document API reflects these changes: ```javascript const updatedDoc = await client.getDocument({ id: "TaskQueue/MyQueue" }); console.log(updatedDoc.tasks); // Shows the current state of the tasks list ``` --- ## Understanding RDF List Internals with Cons Cells RDF lists are implemented as linked cons cells. Each cell has: - `rdf:first` - the value at this position - `rdf:rest` - pointer to the next cell (or `rdf:nil` for end) This example shows how to work directly with the cons cell structure. ### Create a List Manually ```javascript // Create a list [A, B, C] using cons cells directly const createList = WOQL.and( // Generate unique IDs for each cell WOQL.idgen_random("terminusdb://data/Cons/", "v:cell1"), WOQL.idgen_random("terminusdb://data/Cons/", "v:cell2"), WOQL.idgen_random("terminusdb://data/Cons/", "v:cell3"), // Cell 1: first="A", rest=cell2 WOQL.add_triple("v:cell1", "rdf:type", "rdf:List"), WOQL.add_triple("v:cell1", "rdf:first", WOQL.string("A")), WOQL.add_triple("v:cell1", "rdf:rest", "v:cell2"), // Cell 2: first="B", rest=cell3 WOQL.add_triple("v:cell2", "rdf:type", "rdf:List"), WOQL.add_triple("v:cell2", "rdf:first", WOQL.string("B")), WOQL.add_triple("v:cell2", "rdf:rest", "v:cell3"), // Cell 3: first="C", rest=nil (end of list) WOQL.add_triple("v:cell3", "rdf:type", "rdf:List"), WOQL.add_triple("v:cell3", "rdf:first", WOQL.string("C")), WOQL.add_triple("v:cell3", "rdf:rest", "rdf:nil") ); const result = await client.query(createList); const listHead = result.bindings[0]["cell1"]; ``` ### Visualizing the Structure ``` listHead (cell1) ├── rdf:type → rdf:List ├── rdf:first → "A" └── rdf:rest → cell2 ├── rdf:type → rdf:List ├── rdf:first → "B" └── rdf:rest → cell3 ├── rdf:type → rdf:List ├── rdf:first → "C" └── rdf:rest → rdf:nil ``` ### Query the Structure Directly ```javascript // Traverse the list manually using path queries const traverseQuery = WOQL.and( WOQL.triple(listHead, "rdf:first", "v:first"), WOQL.triple(listHead, "rdf:rest", "v:second_cell"), WOQL.triple("v:second_cell", "rdf:first", "v:second"), WOQL.triple("v:second_cell", "rdf:rest", "v:third_cell"), WOQL.triple("v:third_cell", "rdf:first", "v:third") ); const traverseResult = await client.query(traverseQuery); const { first, second, third } = traverseResult.bindings[0]; console.log([first["@value"], second["@value"], third["@value"]]); // Output: ["A", "B", "C"] ``` ### Create an Empty List ```javascript // An empty list is just a reference to rdf:nil const createEmpty = WOQL.lib().rdflist_empty("v:emptyList"); const emptyResult = await client.query(createEmpty); console.log(emptyResult.bindings[0]["emptyList"]); // Output: "rdf:nil" // Check if a list is empty const checkEmpty = WOQL.lib().rdflist_is_empty("rdf:nil"); const isEmptyCheck = await client.query(checkEmpty); console.log("Is empty:", isEmptyCheck.bindings.length > 0); // Output: true ``` ### Why Use Library Functions? The `WOQL.lib().rdflist_*` functions handle: - Automatic cons cell creation with unique IDs - Proper linking of `rdf:rest` pointers - Cleanup of removed cells - Edge cases (empty lists, single elements) Manual cons cell manipulation is error-prone. Use the library functions for reliability. --- ## Complete Runnable Example Save this as `task-queue-demo.js`: ```javascript const TerminusClient = require("terminusdb"); const WOQL = TerminusClient.WOQL; async function main() { const client = new TerminusClient.WOQLClient("http://localhost:6363", { user: "admin", organization: "admin", key: "root", }); // Setup database try { await client.deleteDatabase("task_queue_demo"); } catch (e) {} await client.createDatabase("task_queue_demo", { label: "Task Queue Demo", comment: "RDF list operations demo", }); client.db("task_queue_demo"); // Create schema await client.addDocument([{ "@type": "Class", "@id": "TaskQueue", "@key": { "@type": "Lexical", "@fields": ["name"] }, "name": "xsd:string", "tasks": { "@type": "List", "@class": "xsd:string" } }], { graph_type: "schema" }); // Create document with initial tasks await client.addDocument({ "@type": "TaskQueue", "name": "MyQueue", "tasks": ["Task 1", "Task 2", "Task 3"] }); // Get list head const headResult = await client.query( WOQL.triple("TaskQueue/MyQueue", "tasks", "v:listHead") ); const listHead = headResult.bindings[0]["listHead"]; console.log("List head:", listHead); // Demonstrate operations console.log("\n--- Initial State ---"); let tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Push to front ---"); await client.query(WOQL.lib().rdflist_push(listHead, WOQL.string("Urgent"))); tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Append to end ---"); await client.query(WOQL.lib().rdflist_append(listHead, WOQL.string("Final"), "v:c")); tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Pop from front ---"); const pop = await client.query(WOQL.lib().rdflist_pop(listHead, "v:t")); console.log("Popped:", pop.bindings[0]["t"]["@value"]); tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Swap positions 0 and 2 ---"); await client.query(WOQL.lib().rdflist_swap(listHead, 0, 2)); tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Reverse ---"); await client.query(WOQL.lib().rdflist_reverse(listHead)); tasks = await getTaskList(client, listHead); console.log("Tasks:", tasks); console.log("\n--- Document reflects changes ---"); const doc = await client.getDocument({ id: "TaskQueue/MyQueue" }); console.log("Document tasks:", doc.tasks); } async function getTaskList(client, listHead) { const result = await client.query(WOQL.lib().rdflist_list(listHead, "v:t")); return result.bindings[0]["t"].map(t => t["@value"]); } main().catch(console.error); ``` Run with: ```bash node task-queue-demo.js ``` ## Read More - [RDF List Operations Overview](/docs/woql-rdflist-operations/) - All operations reference - [List Creation](/docs/woql-rdflist-creation/) - Creating and checking empty lists - [List Access](/docs/woql-rdflist-access/) - Reading list elements - [List Modification](/docs/woql-rdflist-modification/) - Adding and removing elements - [List Transformation](/docs/woql-rdflist-transformation/) - Reordering lists - [W3C RDF Schema - rdf:List](https://www.w3.org/TR/rdf-schema/#ch_list) - RDF list specification