# AOS-Sqlite Workshop SQLite is a small, fast, self-contained SQL Database engine. AOS is the AO System in WebAssembly and uses Lua. The combination provides you with the ability to add lightweight and powerful indexers to AO. More indexers are planned for the future but the AOS Sqlite Module is available now. `GYrbbe0VbHim_7Hi6zrOpHQXrSQz07XNtwCnfbFo2I0` is the current AO Module Identifier. ## About Me I am Tom Wilson (Rakis) online, and I am the lead Developer of AOS and AO. I have also developed and published several other projects on Arweave and the Permaweb, from PermaNotes, Permapages, Stamp Protocol, Now, Specs, Permaweb Cookbook, Universal Content Management Protocol, and more. I am passionate about the Permaweb. The current internet is not the cyberspace I envison, and think the Permaweb, ao, and Arweave give us hope to create a cyperspace that helps humanity move forward. ## Getting Started ### Requiements * [NodeJS](https://nodejs.org) - v22 or greater * [AOS](https://cookbook_ao.g8way.io) * Terminal * Editor * Git ### What should I know? You should be knowledgable in computer science and know your way around a terminal. Familiar with Arweave Wallets, AOS and the AOS Console is a plus. To learn more about AOS checkout the cookbook. [https://cookbook_ao.g8way.io](https://cookbook_ao.g8way.io) ### What to expect? In this presentation, we will focus on AOS Handlers and Sqlite. In order to showcase this functionality we will build the backend of a DevChat Chatroom Process > NOTE: this is a demo showing the features of AOS and Sqlite and not a guide for best practices. ### Setup :hammer_and_wrench: We will start with the AOS Console installed, and in the terminal. If you do not have AOS Console installed, please see the cookbook. Let's create a new aos project. > I am using a template I created called test-kit, this template gives me the ability to run integration tests locally so I can better manage my AOS process code. ```sh npx -y degit permaweb/aos-test-kit chatroom ``` We also want to add a package called `DbAdmin` it gives us some helper methods for AOS-SQLite. ```sh cd chatroom curl https://raw.githubusercontent.com/twilson63/aos-packages/main/packages/db-admin/src/main.lua -o src/DbAdmin.lua ``` We will be testing our sqlite process with the following command. At this point all tests should pass. ``` yarn test:sqlite ``` Lets make sure our DbAdmin Package gets loaded into our test suite. test/main.test.js ```js test('load DbAdmin module', async () => { const dbAdminCode = fs.readFileSync('./src/DbAdmin.lua', 'utf-8') const result = await Send({ Action: 'Eval', Data: ` local function _load() ${dbAdminCode} end _G.package.loaded["DbAdmin"] = _load() return "ok" ` }) assert.equal(result.Output.data.output, "ok") }) ``` Lets change our load source test to be the following: ```js test('load source', async () => { const code = fs.readFileSync('./src/main.lua', 'utf-8') const result = await Send({ Action: "Eval", Data: code }) console.log(result) assert.equal(result.Output.data.output, "OK") }) ``` And update our `src/main.lua` to `return "OK"` ## Lets Build :rocket: ```lua local sqlite3 = require('lsqlite3') db = db or sqlite3.open_memory() dbAdmin = require('DbAdmin').new(db) return "OK" ``` ## Lets use Sqlite to build a chatroom Test ```js test('init db tables', async () => { const result = await Send({Action: "Eval", Data: "require('json').encode(InitDb())"}) assert.deepEqual(result.Output.data.output, ["Users", "Messages"]) ``` Create Tables * Users * Messages ```lua USERS = [[ CREATE TABLE IF NOT EXISTS Users ( PID TEXT PRIMARY KEY, Nickname TEXT ); ]] MESSAGES = [[ CREATE TABLE IF NOT EXISTS Messages ( MSG_ID TEXT PRIMARY KEY, PID TEXT, Nick TEXT, Body TEXT, FOREIGN KEY (PID) REFERENCES Users(PID) ); ]] function InitDb() db:exec(USERS) db:exec(MESSAGES) end ``` TDD -> Testing... In this workshop, we will use TDD to provide us with happy path tests to verify our code is working, then we will publish a process and manually test. We are going to implement three Handlers: 1. Register Lets write a test: ```js test('Register a User', async () => { const result = await Send({ Action: 'Register', From: 'USER_PID', Nickname: 'rakis' }) console.log(result) const reply = result.Messages[0] assert.ok(reply, "Should have message") assert.equal(reply.Target, "USER_PID") assert.equal(reply.Tags.find(t => t.name == "Action").value, "DevChat.Registered") }) ``` Lets write a Handler: ```lua Handlers.add("DevChat.Register", function (msg) return msg.Action == "Register" end, function (msg) -- get user count local userCount = #dbAdmin:exec( string.format([[select * from Users where PID = "%s";]], msg.From) ) if userCount > 0 then Send({Target = msg.From, Action = "Registered", Data = "Already Registered"}) print("User already registered") return "Already Registered" end dbAdmin:exec(string.format([[ INSERT INTO Users (PID, Nickname) VALUES ("%s", "%s"); ]], msg.From, msg.Nickname or 'anon')) Send({ Target = msg.From, Action = "DevChat.Registered", Data = "Successfully Registered." }) print("Registered " .. msg.Nickname or "anon") end ) ``` 2. Broadcast Test ```js test('Broadcast message', async () => { const result = await Send({ From: 'USER_PID', Action: 'Broadcast', Data: 'Hello World' }) console.log(result.Messages[0]) assert.ok( result.Messages[0].Assignments.includes('USER_PID') ) }) ``` Handler ```js Handlers.add("Chat.Broadcast", function (msg) return msg.Action == "Broadcast" end, function (msg) -- get user local user = dbAdmin:exec(string.format([[ select PID, Nickname from Users where PID = "%s"; ]], msg.From))[1] if user then -- add message dbAdmin:exec(string.format([[ INSERT INTO Messages (MSG_ID, PID, Nick, Body) VALUES ("%s", "%s", "%s", "%s"); ]], msg.Id, user.ID, user.Nickname, user.Body )) -- get users to broadcast message too local users = Utils.map( function(u) return u.PID end, dbAdmin:exec([[ SELECT PID FROM Users; ]]) ) Send({ Target = msg.From, Action = "Broadcasted", Broadcaster = msg.From, Assignments = users, Data = msg.Data, Type = "normal", Nickname = user.Nickname }) print("Broadcasted Message") return "ok" else Send({Target = msg.From, Data = "Not Registered" }) print("User not registered, can't broadcase") end end ) ``` ## Demo Spawning our Process ```sh aos test-chat --module=GYrbbe0VbHim_7Hi6zrOpHQXrSQz07XNtwCnfbFo2I0 aos> .load src/main.lua aos> InitDb() ``` Lets Register ```lua Send({Target = ao.id, Action = "Register", Nickname = "ChatRoom"}) ``` ```lua Send({Target = ao.id, Action = "Broadcast", Data = "Hello World"}) ``` ## Summary With the AOS-SQLite module it is easy to add powerful indexeers to your AO Processes. You have the full power of a SQL Database available with a very small footprint. ## Questions ## References * https://github.com/permaweb/aos-sqlite * https://github.com/permaweb/aos-test-kit * https://github.com/twilson63/aos-packages